صور OG في PHP و Laravel
يغطي هذا الدليل صور OG في PHP و Laravel باستخدام FastOG — قم بتوقيع عنوان HMAC URL على الخادم الخلفي، ثم ضعه في وسم meta، وستحصل منشورات مدونتك تلقائيًا على صور Open Graph ديناميكية. يعتمد الاشتراك على خطة مجانية مع حساب مجاني (اختر خطة Dynamic API؛ وأنشئ مفتاح API الخاص بك في لوحة التحكم)؛ جرّب أداة اختبار صور OG المجانية لمعاينة قالب قبل ربطه.
دليل شامل لتنفيذ عناوين URL لصور OG الموقّعة بـ HMAC في PHP و Laravel.
بدء الاستخدام السريع
1. إنشاء خدمة التوقيع
// 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؟ الاستعلام الأساسي يتم ترميزه بنسبة مئوية وفقًا لمعيار 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'));
}
PHP عادي (بدون إطار عمل)
// 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 query).
استبعاد key من التوقيع
❌ خطأ: توقيع معاملات المحتوى فقط وإلحاق key بعنوان URL لاحقًا.
✅ صحيح: يتم دمج key في المعاملات قبل بناء الاستعلام الأساسي، ويظهر في عنوان URL النهائي.
المعاملات المتكررة (المصفوفات)
مرّر قيم المصفوفات إلى generateSignedOgUrl ودع http_build_query يكتب عنوان URL: فهو يُصدر مفاتيح بأقواس (features[0]=a&features[1]=b) بينما يوقّع الاستعلام الأساسي مفاتيح متكررة عادية (features=a&features=b). لا تُنشئ features=a&features=b يدويًا في عنوان URL — فـ PHP تفكّك المفاتيح المتكررة إلى قيمة واحدة (القيمة الأخيرة هي التي تسود)، لذا يعيد الخادم بناء استعلام أساسي مختلف ويرفض الطلب برمز invalid_signature.
401 missing_signature / invalid_signature
missing_signature (401) تعني أن key أو s مفقود من عنوان URL. invalid_signature (401) تعني أن الاستعلام الأساسي غير متطابق — تأكد من وجود السطر الجديد في الرسالة الموقّعة، ومن استخدام نفس hmac_secret على الجانبين، ومن استخدام rawurlencode (وليس urlencode) لكل مفتاح وقيمة، ومن استخدام مفاتيح بأقواس للمعاملات المتكررة في عنوان URL.
كشف سر HMAC
لا تكشف أبدًا عن FASTOG_HMAC_SECRET في JavaScript الخاص بالواجهة الأمامية أو في ملفات الإعدادات العامة.