OG Images ใน PHP และ Laravel
คู่มือนี้ครอบคลุมการใช้งาน OG Images ใน PHP และ Laravel ร่วมกับ FastOG — ลงนาม URL แบบ HMAC บน backend ของคุณ ใส่ลงใน meta tag แล้วบทความบล็อกของคุณจะได้รับ Open Graph images แบบไดนามิกโดยอัตโนมัติ บริการนี้ใช้ระบบสมัครสมาชิกพร้อมบัญชีฟรี (เลือกแผน Dynamic API; สร้าง API key ใน dashboard); ลองใช้ Free OG Image Tester เพื่อดูตัวอย่างเทมเพลตก่อนนำไปเชื่อมต่อ
คู่มือฉบับสมบูรณ์สำหรับการใช้งาน URL OG images ที่ลงนามด้วย HMAC ใน PHP และ Laravel
เริ่มต้นใช้งานอย่างรวดเร็ว
1. สร้างบริการ signer
// app/Services/QuickogSigner.php
namespace App\Services;
class QuickogSigner
{
public function generateSignedOgUrl(array $params, array $options = []): string
{
$baseUrl = $options['base_url'] ?? config('services.fastog.base_url', 'https://fastog.com/api/v1/og');
$apiKey = $options['api_key'] ?? config('services.fastog.api_key');
$hmacSecret = $options['hmac_secret'] ?? config('services.fastog.hmac_secret');
if (!$apiKey || !$hmacSecret) {
throw new \RuntimeException('Missing FASTOG_API_KEY or FASTOG_HMAC_SECRET');
}
// Canonical query: every param except `s`, including `key`
$all = array_merge($params, ['key' => $apiKey]);
$pairs = [];
foreach ($all as $k => $v) {
$values = is_array($v) ? $v : [$v];
sort($values, SORT_STRING);
foreach ($values as $vv) {
$pairs[] = rawurlencode((string) $k) . '=' . rawurlencode((string) $vv);
}
}
sort($pairs, SORT_STRING);
$canonical = implode('&', $pairs);
$signature = hash_hmac('sha256', 'GET/api/v1/og' . "\n" . $canonical, $hmacSecret);
return $baseUrl . '?' . http_build_query(array_merge($all, ['s' => $signature]), '', '&', PHP_QUERY_RFC3986);
}
}
ทำไมต้อง rawurlencode? คิวรีตามรูปแบบบัญญัติ (canonical query) ต้องเข้ารหัสแบบ percent-encoding ตามมาตรฐาน RFC-3986 (ช่องว่าง → %20 ไม่ใช่ +) rawurlencode ตรงกับข้อกำหนดนี้พอดี — ส่วน urlencode ไม่ตรง (เพราะสร้าง +) ซึ่งทำให้ลายเซ็นไม่ถูกต้องและส่งผลให้ได้ผลลัพธ์ invalid_signature การส่ง PHP_QUERY_RFC3986 ไปยัง http_build_query จะช่วยให้ URL สุดท้ายคงรูปแบบการเข้ารหัสเดียวกัน
2. การกำหนดค่า
// config/services.php
return [
// ...
'fastog' => [
'api_key' => env('FASTOG_API_KEY'),
'hmac_secret' => env('FASTOG_HMAC_SECRET'),
'base_url' => env('FASTOG_BASE_URL', 'https://fastog.com/api/v1/og'),
],
];
# .env
FASTOG_API_KEY=sk_your_full_api_key_here
FASTOG_HMAC_SECRET=your_64_char_hex_secret_here
3. ลงทะเบียนเป็น singleton
// app/Providers/AppServiceProvider.php
use App\Services\QuickogSigner;
public function register(): void
{
$this->app->singleton(QuickogSigner::class);
}
Laravel Blade
{{-- resources/views/blog/show.blade.php --}}
@extends('layouts.app')
@section('head')
<meta property="og:image" content="{{ $ogImageUrl }}" />
<meta property="og:title" content="{{ $post->title }}" />
<meta property="og:description" content="{{ $post->excerpt }}" />
@endsection
@section('content')
<article>
<h1>{{ $post->title }}</h1>
<p>{{ $post->excerpt }}</p>
</article>
@endsection
คอนโทรลเลอร์
// app/Http/Controllers/BlogController.php
namespace App\Http\Controllers;
use App\Services\QuickogSigner;
use App\Models\Post;
class BlogController extends Controller
{
public function show(Post $post, QuickogSigner $signer)
{
$ogImageUrl = $signer->generateSignedOgUrl([
'template' => 'blog',
'title' => $post->title,
'subtitle' => $post->excerpt,
'author' => $post->author->name,
]);
return view('blog.show', compact('post', 'ogImageUrl'));
}
}
เส้นทาง API
// routes/api.php
use App\Services\QuickogSigner;
use Illuminate\Http\Request;
Route::get('/og', function (Request $request, QuickogSigner $signer) {
$validated = $request->validate([
'title' => 'required|string',
'template' => 'nullable|string',
]);
$ogImageUrl = $signer->generateSignedOgUrl([
'template' => $validated['template'] ?? 'default',
'title' => $validated['title'],
]);
return response()->json(['ogImageUrl' => $ogImageUrl]);
});
ตัวอย่างอีคอมเมิร์ซ
// app/Http/Controllers/ProductController.php
public function show(Product $product, QuickogSigner $signer)
{
$ogImageUrl = $signer->generateSignedOgUrl([
'template' => 'ecommerce',
'title' => $product->name,
'price' => $product->formatted_price,
'image' => $product->image_url,
]);
return view('products.show', compact('product', 'ogImageUrl'));
}
ตัวอย่างบทความบล็อก
// app/Http/Controllers/ProfileController.php
public function show(User $user, QuickogSigner $signer)
{
$ogImageUrl = $signer->generateSignedOgUrl([
'template' => 'blog',
'title' => $user->name,
'subtitle' => $user->bio,
'author' => $user->name,
]);
return view('profile.show', compact('user', 'ogImageUrl'));
}
Plain PHP (ไม่มี Framework)
// lib/fastog-signer.php
function generateSignedOgUrl(array $params, array $options = []): string
{
$baseUrl = $options['base_url'] ?? 'https://fastog.com/api/v1/og';
$apiKey = $options['api_key'] ?? $_ENV['FASTOG_API_KEY'];
$hmacSecret = $options['hmac_secret'] ?? $_ENV['FASTOG_HMAC_SECRET'];
$all = array_merge($params, ['key' => $apiKey]);
$pairs = [];
foreach ($all as $k => $v) {
$values = is_array($v) ? $v : [$v];
sort($values, SORT_STRING);
foreach ($values as $vv) {
$pairs[] = rawurlencode((string) $k) . '=' . rawurlencode((string) $vv);
}
}
sort($pairs, SORT_STRING);
$canonical = implode('&', $pairs);
$signature = hash_hmac('sha256', 'GET/api/v1/og' . "\n" . $canonical, $hmacSecret);
return $baseUrl . '?' . http_build_query(array_merge($all, ['s' => $signature]), '', '&', PHP_QUERY_RFC3986);
}
// index.php
require_once 'lib/fastog-signer.php';
$ogUrl = generateSignedOgUrl([
'template' => 'blog',
'title' => 'My Blog Post',
]);
?>
<!DOCTYPE html>
<html>
<head>
<meta property="og:image" content="<?= htmlspecialchars($ogUrl) ?>" />
<meta property="og:title" content="My Blog Post" />
</head>
<body>
<h1>My Blog Post</h1>
</body>
</html>
ข้อผิดพลาดทั่วไป
การเซ็นข้อความผิด
❌ ผิด: hash_hmac('sha256', 'GET/api/v1/og' . $canonical, $hmacSecret) — ขาดตัวคั่นบรรทัดใหม่
✅ ถูก: hash_hmac('sha256', 'GET/api/v1/og' . "\n" . $canonical, $hmacSecret) — เมธอด + พาธ, ขึ้นบรรทัดใหม่, แล้วตามด้วยคิวรีแบบ canonical
การละ key ออกจากลายเซ็น
❌ ผิด: เซ็นเฉพาะพารามิเตอร์เนื้อหาของคุณ แล้วต่อท้าย key เข้าไปใน URL ทีหลัง
✅ ถูก: key ถูกผสานเข้ากับพารามิเตอร์ก่อนที่จะสร้างคิวรีแบบ canonical และปรากฏใน URL สุดท้าย
พารามิเตอร์ซ้ำ (อาร์เรย์)
ส่งค่าอาร์เรย์ไปยัง generateSignedOgUrl และให้ http_build_query เขียน URL ให้: มันจะสร้างคีย์แบบวงเล็บ (features[0]=a&features[1]=b) ในขณะที่ canonical เซ็นคีย์ซ้ำแบบธรรมดา (features=a&features=b) อย่าสร้าง features=a&features=b ใน URL ด้วยมือ — PHP ถอดรหัสคีย์ซ้ำให้เป็นสเกลาร์ (ค่าสุดท้ายชนะ) ดังนั้นเซิร์ฟเวอร์จะสร้าง canonical ใหม่ที่ต่างออกไปและปฏิเสธด้วย invalid_signature
401 missing_signature / invalid_signature
missing_signature (401) หมายความว่า key หรือ s หายไปจาก URL invalid_signature (401) หมายความว่า canonical ไม่ตรงกัน — ตรวจสอบบรรทัดใหม่ในข้อความที่เซ็น, hmac_secret เดียวกันทั้งสองฝั่ง, rawurlencode (ไม่ใช่ urlencode) สำหรับทุกคีย์และค่า และคีย์แบบวงเล็บสำหรับพารามิเตอร์ซ้ำใน URL
การเปิดเผย HMAC Secret
ห้ามเปิดเผย FASTOG_HMAC_SECRET ใน JavaScript ฝั่ง frontend หรือไฟล์คอนฟิกที่เปิดเผยต่อสาธารณะ