主題

PHP 與 Laravel 中的 OG 圖片

本指南涵蓋使用 FastOG 在 PHP 與 Laravel 中實作 OG 圖片 — 在您的後端簽署 HMAC URL,將其放入 meta 標籤中,您的部落格文章即可自動獲得動態 Open Graph 圖片。採用訂閱制,提供免費帳戶(選擇 Dynamic API 方案;在儀表板中建立您的 API 金鑰);在整合前,可先使用免費的 OG 圖片測試工具預覽範本。

在 PHP 與 Laravel 中實作 HMAC 簽署 OG 圖片 URL 的完整指南。

快速開始

1. 建立簽署者服務(signer service)

php
// 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. 設定

php
// 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'),
    ],
];
bash
# .env
FASTOG_API_KEY=sk_your_full_api_key_here
FASTOG_HMAC_SECRET=your_64_char_hex_secret_here

3. 註冊為單例(singleton)

php
// app/Providers/AppServiceProvider.php
use App\Services\QuickogSigner;

public function register(): void
{
    $this->app->singleton(QuickogSigner::class);
}

Laravel Blade

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

Controller

php
// 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 路由

php
// 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]);
});

電子商務範例

php
// 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'));
}

部落格文章範例

php
// 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(無框架)

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);
}
php
// 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) — 方法 + 路徑、一個換行,然後是正規化查詢字串。

將 key 遺漏於簽章之外

❌ 錯誤:只簽署你的內容參數,然後事後將 key 附加到 URL。 ✅ 正確:key 在建構正規化查詢字串之前就已合併進參數中,並出現在最終的 URL 裡。

重複參數(陣列)

將陣列值傳給 generateSignedOgUrl,讓 http_build_query 負責撰寫 URL:它會產生方括號鍵(features[0]=a&features[1]=b),而正規化則簽署純重複鍵(features=a&features=b)。請勿在 URL 中手動建構 features=a&features=b — PHP 會將重複鍵解碼為純量(最後一個值勝出),因此伺服器會重建出不同的正規化字串並以 invalid_signature 拒絕請求。

401 missing_signature / invalid_signature

missing_signature(401)表示 URL 中缺少 key 或 s。invalid_signature(401)表示正規化字串不符 — 請確認簽署訊息中的換行、兩端使用相同的 hmac_secret、每個鍵與值都使用 rawurlencode(而非 urlencode),以及 URL 中重複參數使用方括號鍵。

暴露 HMAC 密鑰

切勿在前端 JavaScript 或公開設定檔中暴露 FASTOG_HMAC_SECRET。

完整 API 參考文件

免費試用 — 無需註冊

在幾秒內預覽和測試動態 OG 圖片。

開啟免費 OG 圖片測試器

A BeCaSimulations site