Lewati ke konten
getnextpdf.com

Jalankan NextPDF di platform serverless

Engine core NextPDF yang native dan in-process adalah beban kerja serverless yang nyaris ideal. Ia pure PHP yang berjalan di dalam proses Andacomposer require nextpdf/core, bangun sebuah dokumen, ambil byte-nya. Tidak ada binary eksternal untuk dijalankan, tidak ada peramban headless, tidak ada daemon yang harus dijaga tetap hidup, dan tidak ada socket ke layanan sidecar. Sebuah fungsi yang membangun PDF dimulai dalam keadaan dingin, menjalankan PHP Anda, mengembalikan byte-nya, lalu keluar. Itu memetakan dengan rapi ke AWS Lambda (melalui runtime Bref), Google Cloud Run, dan AWS App Runner.

Halaman ini mencakup penerapan engine native itu ke ketiga runtime tersebut serta sekumpulan kecil kendala nyata yang mereka berlakukan:

  • filesystem runtime tidak durable: Lambda hanya menjamin /tmp yang dapat ditulisi, sedangkan runtime kontainer (Cloud Run, App Runner) memiliki filesystem ephemeral yang lingkupnya per-kontainer — bagaimanapun juga fon harus ikut di dalam paket deployment atau image dan didaftarkan di PHP (engine tidak membaca variabel lingkungan font-path apa pun);
  • cold start membayar untuk autoloading dan pemanasan fon apa pun, jadi panaskan FontRegistry sekali per kontainer, bukan per pemanggilan;
  • ukuran paket, memori, dan timeout harus disesuaikan dengan proses build, bukan dengan permintaan yang remeh.

Halaman ini hanya untuk engine native. Jembatan Chrome (writeHtmlChrome melalui paket nextpdf/artisan yang disarankan) adalah cerita berbeda yang lebih berat: ia memanggil Chromium headless melalui symfony/process, yang tidak terdapat di sebuah zip Lambda biasa atau kontainer ramping. Menjalankan Chromium di Lambda berarti sebuah custom layer berisi peramban dan pustaka bersamanya, paket yang jauh lebih besar, dan cold start yang jauh lebih lama — di luar lingkup di sini. Engine telanjang tidak memerlukan semua itu.

Sebelum memulai, pastikan komponen-komponen ini sudah siap:

  • Aplikasi Anda memiliki composer.json dan composer.lock yang ter-commit, dengan nextpdf/core sebagai dependensi.
  • Anda memiliki file fon yang ingin Anda tanamkan, dan Anda berlisensi untuk menanamkannya.
  • Anda memiliki toolchain untuk target Anda — Bref CLI dan framework serverless untuk Lambda, atau sebuah container build untuk Cloud Run / App Runner.

Mengapa engine native cocok untuk serverless

Bagian berjudul “Mengapa engine native cocok untuk serverless”

Dibaca langsung dari paketnya, nextpdf/core memerlukan php: >=8.4 <9.0 dan sekumpulan kecil ekstensi PHP — ext-mbstring, ext-intl, ext-gd, ext-openssl, ext-zlib, dan ext-curl. Layer PHP Bref standar membundel semuanya. Image kontainer php:8.4 resmi menyediakan openssl, curl, dan zlib secara langsung, tetapi mbstring, gd, dan intl tidak dibundel — keduanya memerlukan pemasangan dependensi sistem dan pengaktifan ekstensi dengan docker-php-ext-install (lihat panduan deployment Docker). Pada Bref tidak ada hal eksotis yang perlu dikompilasi; pada jalur kontainer Anda mengaktifkan ketiga ekstensi itu dalam image build untuk engine telanjang.

Yang membuat kecocokannya bersih adalah apa yang tidak dilakukan engine:

  • Tanpa subproses untuk jalur core. Membangun dokumen dan memanggil getPdfData() adalah PHP in-process dari awal hingga akhir. Dependensi symfony/process ada untuk jembatan Chrome opsional, bukan untuk render native — pembuatan PDF native tidak pernah menjalankan sebuah proses.
  • Tanpa state persisten. Setiap pemanggilan membangun dokumen baru dan mengembalikan byte. Tidak ada yang harus bertahan antar permintaan kecuali kontainer yang sudah panas, yang Anda manfaatkan untuk pemanasan fon (di bawah) tetapi tidak pernah diandalkan untuk kebenaran.
  • Tidak perlu direktori kerja yang dapat ditulisi. Engine membangun PDF di memori dan mengembalikannya sebagai string; ia menyentuh disk hanya jika Anda memanggil save(). Di serverless Anda tidak melakukannya — Anda mengembalikan byte-nya — sehingga ketiadaan filesystem yang durable tidak pernah menghambat jalur build.

Satu kendala keras: tanpa filesystem yang durable dan dapat ditulisi

Bagian berjudul “Satu kendala keras: tanpa filesystem yang durable dan dapat ditulisi”

Filesystem deployment tidak durable, tetapi modelnya berbeda menurut runtime. AWS Lambda hanya menjamin /tmp yang dapat ditulisi (512 MB secara default, dapat dikonfigurasi sampai 10 GB); sisa filesystem fungsi bersifat read-only. Runtime kontainer (Cloud Run, App Runner) memiliki filesystem ephemeral yang dapat ditulisi dan lingkupnya per-kontainer, bukan model hanya-/tmp — tetapi apa pun yang ditulis di sana hilang saat kontainer didaur ulang, jadi itu ruang sementara, bukan penyimpanan. Dalam setiap kasus, utamakan /tmp atau volume terkonfigurasi untuk staging, dan jangan pernah mengandalkan tulisan ke path image aplikasi sebagai penyimpanan durable. Dua konsekuensi mengikuti.

Jangan pernah memanggil save() dengan harapan keluaran yang durable. NextPDF\Core\Document mengekspos baik save(string $path): void maupun getPdfData(): string. Di serverless Anda menggunakan getPdfData() dan mengembalikan atau mengunggah byte-nya — jangan perlakukan tulisan ke direktori aplikasi sebagai penyimpanan persisten. Jika Anda harus men-staging sebuah file (misalnya, untuk multipart-upload ke object storage), tulis di bawah /tmp (atau volume terkonfigurasi) dan bersihkan, dengan mengingat bahwa pada kontainer yang panas ruang sementara ini bertahan antar pemanggilan dan dihitung terhadap batas ukurannya.

use NextPDF\Core\Document;
// Right for serverless: get the bytes, return or upload them.
$pdf = $document->getPdfData(); // string of PDF bytes, built in memory
// Avoid on serverless: save() writes to disk. On Lambda the application
// directory is read-only; on Cloud Run / App Runner it is writable but
// ephemeral (lost on container recycle). Neither is durable storage.
// $document->save('/var/task/out.pdf'); // not durable — return the bytes instead

Jangan memasang fon OS saat runtime, dan jangan mengandalkan penemuan fon otomatis; bundel file fon Anda untuk produksi. Pada Lambda filesystem read-only memblokir apt-get install fonts-* secara langsung; pada runtime kontainer setiap pemasangan saat runtime mendarat di filesystem ephemeral dan hilang pada daur ulang berikutnya. Lagipula itu tidak akan membantu, karena engine native tidak membaca fon OS/fontconfig — ia menyelesaikan fon hanya dari file yang Anda daftarkan. Jadi untuk produksi file fon harus ikut dikirim di dalam artefak deployment. Jika Anda dengan sengaja mengambil file fon ke /tmp atau volume terkonfigurasi, Anda harus mendaftarkannya secara eksplisit ke font registry dan menerima tambahan biaya cold-start serta keandalan — itu bukan pola produksi yang direkomendasikan.

Bundel dan daftarkan fon di dalam paket atau image

Bagian berjudul “Bundel dan daftarkan fon di dalam paket atau image”

Engine native menyelesaikan fon dari file fon melalui NextPDF\Typography\FontRegistry, bukan dari fontconfig atau fon yang dipasang OS. Di serverless hal ini tidak bisa ditawar: tidak ada filesystem persisten untuk menaruh fon setelah deploy, jadi keduanya ikut di dalam paket (sebuah zip atau layer Lambda) atau di dalam image (Cloud Run / App Runner).

Bundel file .ttf / .otf / .ttc Anda di bawah sebuah direktori dalam proyek Anda — resources/fonts/ adalah konvensinya — sehingga mereka disertakan dalam artefak. Lalu daftarkan direktori itu di PHP. Engine tidak membaca variabel lingkungan font-path apa pun: NEXTPDF_FONTS_PATH adalah nilai default dari kunci config fonts_path pada paket nextpdf/laravel (env('NEXTPDF_FONTS_PATH', resource_path('fonts'))) dan hanya dikonsumsi oleh integrasi framework itu, bukan oleh nextpdf/core. Sebuah fungsi telanjang harus membangun registry dengan direktori yang dibundel:

use NextPDF\Typography\FontRegistry;
use NextPDF\Core\DocumentFactory;
use NextPDF\Graphics\ImageRegistry;
// Register the directory the deployment artifact bundled the fonts into.
// On Lambda/Bref the code root is /var/task; adjust for your runtime.
$registry = new FontRegistry(__DIR__ . '/resources/fonts');
// (equivalently, $registry->addFontDirectory(__DIR__ . '/resources/fonts');)
$factory = new DocumentFactory($registry, new ImageRegistry(maxCacheBytes: 0));
$document = $factory->create();

Itulah seluruh kepedulian serverless untuk fon. Aturan penamaan file, API registry lengkap, dan penanganan filesystem-tidak-durable berada di halaman khusus — jangan menduplikasinya di sini. Baca Provisikan fon untuk engine native di produksi untuk pola lengkapnya, dan daftarkan direktori yang sama dengan yang Anda bundel. Panduan deployment Docker mencakup pembundelan sisi-image yang setara untuk kasus Cloud Run / App Runner.

Cold start: panaskan FontRegistry sekali per kontainer

Bagian berjudul “Cold start: panaskan FontRegistry sekali per kontainer”

Sebuah cold start membayar untuk bootstrap PHP, autoloader teroptimasi Composer, serta penguraian fon apa pun yang dipicu build pertama. Anda tidak dapat menghindari bootstrap, tetapi Anda dapat memindahkan pekerjaan fon keluar dari jalur panas dan menggunakannya kembali lintas pemanggilan yang panas.

Bangun FontRegistry dan DocumentFactory sekali, di luar handler, sehingga keduanya hidup selama masa hidup kontainer dan digunakan ulang pada setiap pemanggilan yang panas. Secara opsional panggil warmup() dengan file fon yang Anda yakin akan digunakan, sehingga mereka diurai selama inisialisasi alih-alih pada render pertama, lalu lock() registry sehingga state terurainya dibekukan dan tidak ada mutasi per-pemanggilan yang dapat berebut:

use NextPDF\Typography\FontRegistry;
use NextPDF\Core\DocumentFactory;
use NextPDF\Graphics\ImageRegistry;
// Container-scoped, built once at cold start (module scope, not per request).
$fontsDir = __DIR__ . '/resources/fonts';
$registry = new FontRegistry($fontsDir);
// Parse the fonts you will actually use now, so the first render does not.
$registry->warmup([
$fontsDir . '/liberation/LiberationSans-Regular.ttf',
$fontsDir . '/liberation/LiberationSans-Bold.ttf',
]);
// Freeze the parsed state for the life of the warm container.
$registry->lock();
$factory = new DocumentFactory($registry, new ImageRegistry(maxCacheBytes: 0));
// Each invocation: fresh document from the shared, warm factory.
$handler = static function (array $event) use ($factory): string {
$document = $factory->create();
$document->addPage();
$document->cell(0, 10, 'Hello from serverless', newLine: true);
return $document->getPdfData();
};

Panggil warmup() sebelum lock() — registry dibekukan begitu terkunci, sehingga sebuah warmup setelah itu memunculkan error konfigurasi. Perlakukan fon yang gagal dimuat saat warmup sebagai error saat-deploy, bukan detail runtime: validasi bahwa setiap path fon yang Anda ingin panaskan benar-benar ada dan dapat diurai saat startup, dan gagalkan deploy (atau health check Anda) jika ada yang tidak, alih-alih membiarkan path yang salah ketik muncul kemudian sebagai glyph yang hilang. Jaga daftar warmup pada fon yang dibutuhkan pemanggilan tipikal; memanaskan keluarga besar yang jarang Anda gunakan hanya memperpanjang setiap cold start.

Bref menyediakan runtime PHP untuk Lambda sebagai layer yang dipublikasikan dan sebuah plugin serverless.yml. Runtime php-84 sudah mengirimkan ekstensi yang dibutuhkan nextpdf/core, jadi Anda menerapkan kode dan fon Anda lalu mengarahkan sebuah fungsi ke sebuah handler. Sebuah serverless.yml minimal:

service: nextpdf-serverless
provider:
name: aws
region: us-east-1
runtime: provided.al2023
plugins:
- ./vendor/bref/bref
functions:
generate:
handler: handler.php
description: Generate a PDF with the native NextPDF engine
runtime: php-84
memorySize: 1024 # size to the build; see "Sizing" below
timeout: 30 # seconds; raise for large documents
# The Lambda filesystem is read-only except /tmp. Fonts ship in the
# package under resources/fonts and are registered in the handler.

Handler membangun dokumen dengan factory yang panas dan lingkupnya per-kontainer lalu mengembalikan byte-nya. Untuk sebuah HTTP API, kembalikan byte-nya dalam bentuk base64 dengan tipe konten application/pdf agar API Gateway memperlakukan body sebagai biner; untuk pemicu invoke atau queue, unggah byte-nya ke object storage dan kembalikan key-nya:

handler.php (outline)
<?php
declare(strict_types=1);
require __DIR__ . '/vendor/autoload.php';
use NextPDF\Core\DocumentFactory;
use NextPDF\Graphics\ImageRegistry;
use NextPDF\Typography\FontRegistry;
// --- Cold-start: built once per container, reused across warm invocations. ---
$fontsDir = __DIR__ . '/resources/fonts';
$registry = new FontRegistry($fontsDir);
$registry->warmup([$fontsDir . '/liberation/LiberationSans-Regular.ttf']);
$registry->lock();
$factory = new DocumentFactory($registry, new ImageRegistry(maxCacheBytes: 0));
// --- Per-invocation handler. ---
return static function (array $event) use ($factory): array {
$document = $factory->create();
$document->addPage();
$document->cell(0, 10, 'Invoice', newLine: true);
// getPdfData() materializes the whole PDF in memory and returns it.
$bytes = $document->getPdfData();
return [
'statusCode' => 200,
'isBase64Encoded' => true,
'headers' => ['Content-Type' => 'application/pdf'],
'body' => base64_encode($bytes),
];
};

Verifikasi bahwa paket berisi lingkungan yang sehat sebelum Anda mengalirkan trafik ke sana. nextpdf/core mengirimkan sebuah CLI yang terpasang di vendor/bin/nextpdf yang perintah doctor-nya melaporkan persis ekstensi yang dibutuhkan engine. Jalankan sekali terhadap image atau layer runtime yang sama untuk memastikan PHP 8.4 dan setiap ekstensi yang diperlukan ada.

Cloud Run dan App Runner menjalankan sebuah kontainer, bukan fungsi yang di-zip, jadi build-nya adalah image Docker dari Kontainerisasi aplikasi NextPDF, bukan sebuah paket Bref. Kendala engine-native-nya identik: bundel fon ke dalam image, daftarkan direktori yang dibundel di PHP, jalankan tanpa privilege, dan perlakukan filesystem sebagai tidak durable. Tidak seperti model hanya-/tmp milik Lambda, sebuah kontainer Cloud Run / App Runner memiliki filesystem ephemeral yang dapat ditulisi dan lingkupnya per-kontainer — tetapi ia direset pada setiap daur ulang, jadi gunakan /tmp (sebuah tmpfs di Cloud Run) atau volume terkonfigurasi untuk ruang sementara dan jangan pernah mengandalkan tulisan ke path image aplikasi sebagai penyimpanan durable.

Perbedaan dari Lambda bersifat operasional, bukan struktural:

  • Kontainer dapat tetap panas lintas permintaan di bawah sebuah setelan concurrency, sehingga pemanasan FontRegistry/DocumentFactory yang lingkupnya per-kontainer di atas berbuah lintas banyak permintaan, bukan hanya pemanggilan berikutnya.
  • Anda melayani melalui HTTP (sebuah SAPI server PHP FPM atau bawaan) alih-alih sebuah event invoke, jadi Anda mengembalikan byte melalui respons framework Anda. Untuk dokumen besar, kembalikan sebagai respons yang di-stream — lihat Stream PDF besar yang dihasilkan sebagai respons HTTP.
  • Timeout dan memori permintaan diatur pada service (timeout / memori service Cloud Run; konfigurasi instance App Runner) alih-alih per fungsi.

Selebihnya — himpunan ekstensi, pendaftaran fon, pemanggilan keluaran getPdfData() — adalah kode yang sama dengan handler Lambda.

Penyesuaian ukuran: paket, memori, dan timeout

Bagian berjudul “Penyesuaian ukuran: paket, memori, dan timeout”
  • Ukuran paket dan image. Artefak membawa vendor/ (produksi saja — pasang dengan --no-dev) dan fon yang Anda bundel. Fon mendominasi: sebuah keluarga CJK lengkap berukuran puluhan megabyte. Kirim hanya fon yang benar-benar Anda render untuk menjaga paket Lambda di bawah batasnya dan image tetap kecil, yang juga memperpendek cold start. Keluarga Liberation yang dibundel (resources/fonts/liberation/) berukuran kecil dan mencakup substitusi Helvetica yang kompatibel-metrik.
  • Memori. getPdfData() membangun seluruh dokumen di memori dan mengembalikannya sebagai satu string, sehingga memori puncak kira-kira seukuran satu PDF jadi ditambah working set build-nya. Sesuaikan memori fungsi/kontainer dengan dokumen terbesar yang Anda hasilkan, bukan dengan rata-rata. Pada Lambda, memori juga menskala CPU, jadi lebih banyak memori sering berarti build yang lebih cepat dan eksekusi yang lebih murah meski laju per-milidetiknya lebih tinggi — ukur keduanya. Dokumen beberapa halaman nyaman pada 512–1024 MB; dokumen padat-gambar atau banyak-halaman membutuhkan lebih.
  • Timeout. Build, bukan transfer, yang mendominasi anggaran permintaan. Atur timeout fungsi di atas waktu build kasus-terburuk dengan margin. Jika sebuah dokumen cukup besar sehingga berisiko timeout, pindahkan generasi ke pemicu asinkron (sebuah Lambda yang didukung queue atau sebuah job Cloud Run) yang menulis hasilnya ke object storage alih-alih memblokir permintaan sinkron.
  • Ukuran /tmp. Jika Anda men-staging apa pun di bawah /tmp, perhitungkan batas ukurannya dan ingat ia bertahan lintas pemanggilan yang panas — bersihkan, atau kontainer yang berumur panjang perlahan akan mengisinya.
  • Tanpa save() durable ke direktori aplikasi. Filesystem deployment tidak durable — direktori aplikasi Lambda read-only (hanya /tmp menerima tulisan), dan filesystem kontainer Cloud Run / App Runner dapat ditulisi tetapi ephemeral. Gunakan getPdfData() dan kembalikan/unggah byte-nya; staging di bawah /tmp atau volume terkonfigurasi jika Anda harus.
  • Jangan mengandalkan penemuan fon otomatis. Jangan memasang fon OS saat runtime, dan jangan mengandalkan penemuan fon otomatis; bundel file fon Anda untuk produksi. Engine native tidak membaca fon OS/fontconfig — ia menyelesaikan hanya file yang Anda daftarkan. Jika Anda dengan sengaja mengambil file fon ke /tmp atau volume terkonfigurasi, Anda harus mendaftarkannya secara eksplisit ke font registry dan menerima tambahan biaya cold-start serta keandalan. Bundel dan daftarkan file-nya. Lihat halaman fon yang ditautkan di atas.
  • NEXTPDF_FONTS_PATH tidak berbuat apa-apa untuk engine telanjang. Ia adalah default config nextpdf/laravel, bukan variabel yang dibaca nextpdf/core. Sebuah handler Bref telanjang yang hanya menyetel variabel itu mendaftarkan nol fon dan merender tofu.
  • Jembatan Chrome tidak cocok untuk fungsi biasa. writeHtmlChrome membutuhkan Chromium headless dan jalur subproses symfony/process. Menaruh Chromium di Lambda memerlukan sebuah custom layer berisi peramban dan pustakanya, paket yang jauh lebih besar, dan cold start yang panjang. Engine native dan writeHtml tidak memerlukan semua itu — utamakan keduanya di serverless.
  • Biaya cold-start adalah autoload ditambah penguraian fon. Gunakan --optimize-autoloader pada pemasangan produksi dan panaskan registry sekali per kontainer. Jangan memanaskan fon yang jarang Anda gunakan.
  • API Gateway membutuhkan penanganan biner. Kembalikan isBase64Encoded: true dengan Content-Type: application/pdf, dan konfigurasi API untuk memperlakukan application/pdf sebagai tipe media biner, atau klien menerima byte yang rusak.
  • Premium dan ionCube adalah kepedulian artefak yang lebih berat. Build NextPDF Pro / Enterprise yang ter-encode-ionCube membutuhkan ionCube Loader yang cocok dengan build PHP persis di runtime, yang tidak disertakan layer Bref standar. Itu di luar lingkup untuk sebuah deploy serverless core.
  • Jangan kirim dependensi dev. Pasang dengan --no-dev agar tooling pengujian dan analisis tidak pernah masuk ke paket atau image fungsi.
  • Validasi input sebelum membangun. Sebuah build PDF yang digerakkan input permintaan adalah vektor pengurasan memori; tolak input di luar rentang atau yang berukuran berlebih pada batas sebelum pekerjaan build apa pun berjalan, dan batasi concurrency agar trafik tinggi tidak menggandakan memori puncak menjadi kegagalan out-of-memory.
  • Jaga fon dan lisensi keluar dari artefak publik. Bundel hanya fon yang Anda berlisensi untuk menanamkannya, dan jangan pernah memanggang sebuah file lisensi premium ke dalam image atau layer yang di-push secara publik — sediakan saat runtime melalui sebuah nilai lingkungan atau secret manager.
  • Privilege paling sedikit. Berikan fungsi/service hanya izin IAM yang dibutuhkannya (misalnya, akses tulis ke satu bucket keluaran), dan jalankan kontainer tanpa privilege seperti yang ditunjukkan panduan Docker.

Panduan ini tidak membuat klaim standar normatif. Fakta platform dibaca langsung dari paket nextpdf/core: kendala php: >=8.4 <9.0 dan ekstensi yang diperlukan ext-mbstring, ext-intl, ext-gd, ext-openssl, ext-zlib, dan ext-curl. Layer runtime Bref PHP-8.4 standar membundel keenamnya; image php:8.4 resmi menyediakan openssl, curl, dan zlib, tetapi mbstring, gd, dan intl harus dipasang dan diaktifkan dalam image build dengan docker-php-ext-install (lihat halaman Docker). Pemanggilan keluaran adalah permukaan core nyata NextPDF\Core\Document::getPdfData(): string (saudara disknya adalah save(string $path): void). Fon didaftarkan melalui NextPDF\Typography\FontRegistry — argumen constructor direktorinya / addFontDirectory(), dengan warmup(array $fontFiles) dan lock() untuk pola cold-start — disambungkan melalui NextPDF\Core\DocumentFactory::create(). NEXTPDF_FONTS_PATH adalah kunci config fonts_path pada paket nextpdf/laravel (env('NEXTPDF_FONTS_PATH', resource_path('fonts'))), bukan variabel yang dibaca nextpdf/core. Perintah doctor pada CLI nextpdf dideklarasikan sebagai "bin": ["bin/nextpdf"] dalam paket dan terpasang di vendor/bin/nextpdf pada aplikasi yang mengonsumsinya. Nama runtime Bref dan perilaku AWS Lambda / Cloud Run / App Runner adalah fitur terdokumentasi dari vendor-vendor tersebut.