多數 PHP 的圖片程式碼,其實都是排版程式碼。用 GD 或 Imagick 時,每個設計決定都會變成一次函式呼叫:
$image = imagecreatetruecolor(1200, 630);
$blue = imagecolorallocate($image, 37, 99, 235);
imagefilledrectangle($image, 0, 0, 1200, 630, $blue);
imagettftext($image, 48, 0, 80, 300, $white, 'Inter-Bold.ttf', 'Summer sale: 30% off');
這樣做可以運作,直到有人想把標題拆成兩行、在價格後面加陰影,或在角落放一個 QR code。這時設計就卡在你的 PHP 裡,每次微調都要改程式碼、再部署一次。
在「自己畫」和「架一整群無頭瀏覽器」之間,還有第三種做法:把版面留在範本裡,PHP 只負責把資料傳給圖片生成 API。
API 版本的運作方式
字型、間距、漸層、陰影、表格和 QR code 都留在範本裡。設計師可以修改這一切,完全不必動到你的 PHP;而你的 PHP 繼續做它擅長的事:搬運資料。
你需要準備什麼
- API 金鑰。 在 儀表板 → API 金鑰 建立,並且只放在伺服器上。把它當成資料庫密碼來保管。
- 範本 ID。 開啟你設計好的範本,複製它的 ID,格式像
tpl_kd93mz。 - 圖層名稱。 你在編輯器中為圖層取的名稱,就是請求裡要用的名稱,必須完全一致。
做法一:純 cURL
不需要任何相依套件,在任何 PHP 主機上都能用,包括那些連安裝擴充套件都很麻煩的主機。
<?php
$payload = [
'apiKey' => getenv('BANNERIFY_API_KEY'),
'templateId' => 'tpl_kd93mz',
'format' => 'png',
'modifications' => [
['name' => 'headline', 'text' => 'Summer sale: 30% off'],
['name' => 'price', 'text' => '$149.00'],
['name' => 'photo', 'src' => 'https://example.com/sneaker.jpg'],
],
];
$ch = curl_init('https://api.bannerify.co/v1/templates/createImage');
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => ['Content-Type: application/json'],
CURLOPT_POSTFIELDS => json_encode($payload),
CURLOPT_TIMEOUT => 30,
]);
$image = curl_exec($ch);
if ($image === false) {
throw new RuntimeException('Transport error: ' . curl_error($ch));
}
$status = (int) curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
if ($status !== 200) {
throw new RuntimeException("Bannerify returned {$status}: " . substr($image, 0, 200));
}
file_put_contents(__DIR__ . '/banner.png', $image);
有兩個細節常讓人踩雷:
- 回應內容本身就是圖片,不是內含 base64 的 JSON。直接寫入檔案,或串流輸出到瀏覽器即可。
- 錯誤會以 JSON 搭配 4xx 狀態碼回傳。 所以上面的程式碼會先檢查狀態碼,才儲存檔案。
做法二:官方 PHP SDK
如果你不想自己寫請求,可以安裝 SDK:
composer require bannerify/bannerify
SDK 只會回傳 result 或 error 其中之一,不會同時出現,所以不可能忘記處理失敗的情況:
<?php
use Bannerify\Bannerify\BannerifyClient;
$client = new BannerifyClient(getenv('BANNERIFY_API_KEY'));
$result = $client->createImage('tpl_kd93mz', [
'modifications' => [
['name' => 'headline', 'text' => $product['name']],
['name' => 'price', 'text' => '$' . number_format($product['price'], 2)],
['name' => 'photo', 'src' => $product['image_url']],
],
]);
if (isset($result['error'])) {
error_log('Render failed: ' . $result['error']['message']);
return null;
}
$path = __DIR__ . "/banners/{$product['sku']}.png";
file_put_contents($path, $result['result']);
完整的方法清單與選項請見 PHP SDK 文件。
送入真實資料
你不需要手動組出 modifications 陣列。API 也支援物件簡寫,以圖層名稱當作 key:
'modifications' => [
'headline' => $product['name'],
'price' => '$' . number_format($product['price'], 2),
'photo' => ['src' => $product['image_url']],
'qr' => ['qrcode' => $product['checkout_url']],
],
一般的值會被當成文字。巢狀陣列則保留各自的欄位,圖片、QR code、條碼、圖表和表格都需要這種寫法。
每個圖層可以送出的欄位:
text取代文字圖層的內容color覆寫圖層顏色,例如#FF0000src替換圖片圖層,可使用任何公開可存取的 HTTPS 網址qrcode和barcode用來編碼連結、SKU 或追蹤編號rows和columns用來填入圖表與表格visible只在單次生成中隱藏圖層star設定評分數值
每個圖層一個欄位,依名稱送出。如果兩個圖層需要相同的資料,就送兩次。
批次處理與快取
生成一千張圖片就是跑一個迴圈,不是什麼特殊情況:
foreach ($products as $product) {
$payload = buildPayload($product);
$cacheKey = hash('xxh128', json_encode($payload));
$path = __DIR__ . "/cache/{$cacheKey}.png";
if (file_exists($path)) {
continue;
}
file_put_contents($path, renderBanner($payload));
}
迴圈要設上限(同時只送出幾個請求),並以 payload 作為快取的 key,這樣重複的請求就不會重複付費。生成是同步的,所以你在同一個請求裡就能拿到檔案,不必輪詢任務佇列。
如果你完全不想儲存檔案,可以改用 createStoredImage。它會上傳生成的圖片,並回傳一個託管的網址:
{ "url": "https://cdn.bannerify.co/..." }
對於 Open Graph 圖片、電子郵件標頭,以及任何用網址比用檔案更方便傳遞的場合,這是最方便的做法。
格式與尺寸
format支援png(預設)、jpeg或webp。- 需要 PDF?同一個範本可以用
createPdf輸出成 PDF。細節請見 PDF 生成 API 頁面,PHP 程式碼請見發票 PDF 指南。 - 畫布尺寸由範本決定,不是由請求決定。每種需要的尺寸各留一個範本:連結預覽用 1200x630,動態消息用 1080x1080,限時動態用 1080x1920。
- 要用
src傳照片嗎?渲染器會去抓取那個網址,所以網址必須是公開的。本機檔案或私有的儲存空間都會失敗。
常見錯誤
- 把 API 金鑰放在瀏覽器的 JavaScript 或行動 App 裡。它應該放在伺服器上。
- 內容沒變,卻重新生成圖片。請以 payload 的雜湊值做快取。
- 在單一個網頁請求裡生成上千張圖片。幾十張還行,上千張請用佇列。
- 以為回應是 JSON。除非使用 stored 端點,否則回應就是檔案本身。
費用
免費方案每月包含 100 次生成(每天 10 次)、一個範本和 1GB 的圖片儲存空間,足夠讓你先把它接進真實的應用程式。Automate 方案每月 $29 起,包含 3,000 次生成與不限數量的範本。詳情請見價格。
如果你現在是用 PHP 生成圖片,可以從免費方案開始,先把一個範本從 GD 搬走。第一張版面完整無誤的圖片回傳時,通常就是大家不再寫座標定位程式碼的時刻。