Uzun ömürlü bir işçi sürecinde PDF'leri güvenle oluşturma
Bir bakışta
“Bir bakışta” başlıklı bölümUzun ömürlü bir PHP (PHP: Hypertext Preprocessor) işçisi (RoadRunner, Swoole, Laravel Octane), tek bir süreci birçok istek boyunca canlı tutar. Aynı yazı tiplerini her istekte yeniden ayrıştırıp aynı görüntüleri her istekte çözerseniz, işlemci süresini boşa harcar ve yerleşik bellek kullanımını artırırsınız. NextPDF, iki yaşam süresini ayırarak bu maliyetten kaçınır:
- Süreç ömürlü ve paylaşılan:
FontRegistryveImageRegistry, ayrıştırılmış yazı tipi tablolarını ve çözülmüş görüntü önbelleklerini tutar. Kayıtları, işçi başlatılırken bir kez oluşturun. - İstek ömürlü ve atılabilir:
Document, yaniDocumentFactory::create()tarafından döndürülen nesne. Nesneyi oluşturun, yazın ve kapsamdan çıkmasına izin verin. PHP’nin çöp toplayıcısı bundan sonra tüm nesne grafiğini geri kazanabilir.
Bu tarif, işçi açılış dizisini, istek başına çalışacak gövdeyi ve en yüksek bellek kullanımını sabit tutan döngü başına sıfırlamayı gösterir.
Kurulum
“Kurulum” başlıklı bölümcomposer require nextpdf/core:^3İşçi deseni başka bir uzantı gerektirmez ve bir işçi çalışma zamanı (RoadRunner / Swoole / Octane) isteğe bağlıdır. Aynı fabrika desenini bir komut satırı arabirimi (CLI) for döngüsünde çalıştırabilirsiniz; test koşum takımının doğruladığı şey de budur.
Kavramsal genel bakış
“Kavramsal genel bakış” başlıklı bölümİşçi kodunda DocumentFactory ile başlayın. Bunu paylaşılan bir FontRegistry ve ImageRegistry ile bir kez oluşturun:
FontRegistry::warmup()sağladığınız yazı tipi dosyalarını ayrıştırır ve ayrıştırılmış tabloları önbelleğe alır.FontRegistry::lock()kaydı dondurur; böylece istek başına çalışan kod paylaşılan yazı tipi kümesini değiştiremez.isLocked()geçerli durumu bildirir. Kaydı kilitledikten sonra, eşzamanlı yordamlar (coroutine) arasında paylaşılması güvenlidir.- Bir
ImageRegistrykaydını birmaxCacheBytesbütçesiyle oluşturun. Bütçe aşıldığında, en uzun süredir kullanılmayan girdileri çıkarır. Bütçeden büyük bir görüntüde, önbelleği sürekli boşaltıp doldurmak yerine önbelleğe alma atlanır. ImageRegistry::reset()önbelleğe alınmış her görüntüyü çıkarır; ancak kayıt kullanıma hazır kalır. Sonraki istek onu talep üzerine yeniden doldurur. En yüksek değeri başlangıç düzeyine geri getirmek için bunu belirli bir sıklıkta (her N istekte bir veyamemoryUsage()bir eşiği aştığında) çağırın.
Fabrikanın oluşturduğu her belge, bağımsız bir Taşınabilir Belge Biçimi (PDF) dosyasıdır. ISO 32000-2 §7.5.5, hiç güncellenmemiş bir dosyada fragmanın (trailer) Prev girdisi içermediğini tanımlar ve her işçi isteği bu tür bir ilk nesil dosya üretir. Bu nedenle istekler, yazı tipi ve görüntü önbelleklerini paylaşsalar bile belge durumunu paylaşmaz. Alt küme yazı tipi BaseFont etiketi (ISO 32000-2 §9.6.4) istekler arasında kararlı kalır, çünkü ayrıştırılmış yazı tipi paylaşılan kayıtta tutulur.
API yüzeyi
“API yüzeyi” başlıklı bölümBu tarif, NextPDF\Core\DocumentFactory, NextPDF\Typography\FontRegistry, NextPDF\Graphics\ImageRegistry ve NextPDF\Support\MemoryReport üzerindeki PHPDoc’tan oluşturulan API yüzeyini kullanır. Başlıca üyeler şunlardır: DocumentFactory::create(), FontRegistry::warmup() / lock() / isLocked() / memoryUsage(), ImageRegistry::reset() / memoryUsage() ve MemoryReport::$currentBytes / $peakBytes / $entryCount / utilizationPercent().
Kod örneği — Hızlı başlangıç
“Kod örneği — Hızlı başlangıç” başlıklı bölüm<?php
declare(strict_types=1);
require_once __DIR__ . '/vendor/autoload.php';
use NextPDF\Core\DocumentFactory;use NextPDF\Graphics\ImageRegistry;use NextPDF\Typography\FontRegistry;
// --- Worker boot (run ONCE, before the request loop) ---------------------$fonts = new FontRegistry();$fonts->lock(); // freeze the shared font set$images = new ImageRegistry(maxCacheBytes: 50 * 1024 * 1024);$factory = new DocumentFactory($fonts, $images);
// --- Per request ---------------------------------------------------------$doc = $factory->create();$doc->setTitle('Worker output');$doc->addPage();$doc->setFont('helvetica', 'B', 16);$doc->cell(0, 12, 'Generated in a shared-registry worker', newLine: true);$doc->save(getenv('NEXTPDF_COOKBOOK_OUTPUT') ?: __DIR__ . '/out.pdf');// $doc leaves scope here → GC reclaims the whole document tree.Kod örneği — Üretim
“Kod örneği — Üretim” başlıklı bölümTam örnek, test koşum takımının çıktı kanalına göre düzenlenmiştir. Açılış dizisini, sınırlı bir istek döngüsünü, döngü başına reset() çağrısını ve en yüksek bellek değeri doğrulamasını gösterir. Yeniden üretilebilirlik test koşum takımının iki kez çalıştırdığı betik de budur.
<?php
declare(strict_types=1);
require_once __DIR__ . '/vendor/autoload.php';
use NextPDF\Core\DocumentFactory;use NextPDF\Graphics\ImageRegistry;use NextPDF\Typography\FontRegistry;
// --- Worker boot: shared, process-lifetime registries --------------------$fonts = new FontRegistry();$fonts->lock(); // share-safe once locked$images = new ImageRegistry(maxCacheBytes: 50 * 1024 * 1024);$factory = new DocumentFactory($fonts, $images);
$resetEvery = 4; // reset cadence in requests$peakAfterReset = 0;
// --- Simulated request loop ---------------------------------------------for ($request = 1; $request <= 12; $request++) { $doc = $factory->create(); $doc->setTitle("Worker Request #{$request}"); $doc->addPage(); $doc->setFont('helvetica', 'B', 16); $doc->cell(0, 12, "Worker Request #{$request}", newLine: true); $doc->setFont('helvetica', '', 11); $doc->cell(0, 8, 'Shared FontRegistry / ImageRegistry across requests.', newLine: true);
// The harness captures the LAST request's PDF via the side channel. if ($request === 12) { $doc->save(getenv('NEXTPDF_COOKBOOK_OUTPUT') ?: __DIR__ . '/out.pdf'); } else { $doc->getPdfData(); // force render, then drop }
unset($doc); // explicit end-of-request
// Bound the cache high-water mark on a fixed cadence. if ($request % $resetEvery === 0) { $images->reset(); \gc_collect_cycles(); $report = $images->memoryUsage(); $peakAfterReset = \max($peakAfterReset, $report->currentBytes); }}
$final = $images->memoryUsage();
fwrite(STDERR, \sprintf( "fonts.locked=%s images.entries=%d images.current=%dB peak_after_reset=%dB\n", $fonts->isLocked() ? 'yes' : 'no', $final->entryCount, $final->currentBytes, $peakAfterReset,));STDOUT test koşum takımı için boş kalır; ilerleme metni STDERR kanalına gider. PDF, yalnızca NEXTPDF_COOKBOOK_OUTPUT hedefine yazılır; asla ekrana basılmaz.
Sınır durumları ve tuzaklar
“Sınır durumları ve tuzaklar” başlıklı bölüm- Paylaşmadan önce kilitleyin. Açılışta
FontRegistry::lock()çağrısını yapın. İki yordam (coroutine) aynı kayda dokunduğunda hâlâ değiştirilebilir durumda olan bir kayıt, veri yarışına yol açar. Bir sağlık denetiminde doğrulama olarakisLocked()kullanın. reset(),unset()ile aynı şey değildir.ImageRegistry::reset()önbelleğe alınmış ikili verileri çıkarır ve kaydı kullanılabilir tutar; bu nedenle doğru periyodik çağrıdır. Kaydı her istek için yok edip yeniden oluşturursanız, paylaşılan önbelleğin sağladığı yararı kaybedersiniz.- Aşırı büyük görüntülerin atlanması.
maxCacheBytesdeğerinden büyük bir görüntü kullanım başına çözülür ve asla önbelleğe alınmaz; bu nedenle çalışma kümesini önbellekten atmaz. Bu kasıtlıdır. Bütçeyi nadir görülen büyük görüntü için değil, sık kullandığınız görüntüler için boyutlandırın. - Belge kapsamdan çıkmalıdır.
Documentnesnesini bir statik değişkende, uzun ömürlü bir kapsayıcı bağlamasında veya işçinin yakaladığı bir kapanışta (closure) tutarsanız, tüm nesne grafiği canlı kalır ve istek başına toplama çalışamaz. Birunset()çağrısı ya da bir kapsam çıkışı zorunludur. gc_collect_cycles()yerleşimi. PHP’nin döngü toplayıcısı istek sınırlarını bilmez. Bunu her istekte değil, sıfırlama sıklığından sonra çağırın. Bu, sıcak yola toplama maliyeti eklemeden en yüksek değeri sınırlar.- Belirleyicilik uyarısı. Belge zaman damgaları ve fragman (trailer)
/IDdeğeri her kaydetmede yeniden üretilir (ISO 32000-2 §14.3). Bu nedenle elde edilen PDF, anlamsal profille (yapısal soyut sözdizimi ağacı (AST) ve meta veri; değişken baytlar asla değil) karşılaştırılır. “Uyumluluk” bölümüne bakın.
Başarım
“Başarım” başlıklı bölüm- Paylaşılan kayıt, yinelenen yazı tipi ayrıştırmasını ve görüntü çözmeyi tek seferlik bir açılış maliyetine dönüştürür. Bunun ardından istek başına iş, yerleşim ve serileştirmeden ibaret olur.
- Yerleşik belleğin en yüksek kullanımı,
maxCacheBytesartı işlemdeki tek bir belgenin çalışma kümesiyle sınırlıdır. Döngü başınareset()önbelleği başlangıç düzeyine geri döndürür; böylece uzun ömürlü bir işçi yukarı doğru kayarak büyüyen bir testere dişi eğrisi göstermez. - Buradaki
performance_budgetön veri bloğu (wall_ms: 4000,peak_mb: 192) test koşum takımının 12 istekten oluşan döngüyü çalıştırmasını sınırlar. Test koşum takımı bu bütçeyi uygular; bu, keyfi belgeler için bir garanti değildir. - Bu tarif, #31 kapsamındaki §4.3 boşluk listesinde yer alan “bellek/GC” maddesini karşılar. Bunu destekleyen
examples/14-worker-factory.phpmevcuttur vetests/Cookbook/Php/WorkerSafeBatchRenderingRecipeTest.php, eksik bellek/GC doğrulamasını ekler (en yüksek değer sıfırlamadan sonra döngüler boyunca artmaz).
Güvenlik notları
“Güvenlik notları” başlıklı bölüm- İşçi deseni istek başına bir belge işler ve yalnızca ayrıştırılmış yazı tipi ile çözülmüş görüntü önbelleklerini paylaşır. Belge içeriği istek sınırını geçmez. Bir istek, paylaşılan kayıtlar aracılığıyla başka bir isteğin belge verisini okuyamaz.
- Güvenilmeyen girdi, yine normal NextPDF girdi sınırlarından geçer ve işçi deseni doğrulamayı gevşetmez. Her isteğin HyperText Markup Language (HTML) ve varlık girdisini, istek başına süreç modelinde yaptığınız gibi güvenilmeyen olarak değerlendirin.
Uyumluluk
“Uyumluluk” başlıklı bölüm| İfade | Belirtim | Madde | reference_id |
|---|---|---|---|
| Belgenin değiştirilme tarihi her kaydetmede yeniden üretilir; bu nedenle istek başına çıktı bayt düzeyinde kararlı değildir. | ISO 32000-2 | §14.3 | |
Her işçi belgesi, hiç güncellenmemiş bir dosyadır (fragmanında (trailer) Prev bulunmaz); istekler belge durumunu paylaşmaz. | ISO 32000-2 | §7.5.5 | |
| Alt küme yazı tipinin etiket öneki istekler arasında kararlıdır, çünkü ayrıştırılmış yazı tipi paylaşılan kayıtta tutulur. | ISO 32000-2 | §9.6.4 |
Fragman (trailer) /ID değeri ve değiştirilme tarihi her kaydetmede yeniden üretildiğinden, bu tarif anlamsal yeniden üretilebilirlik profiliyle (yapısal soyut sözdizimi ağacı (AST) eşitliği ve yalnızca meta veri karşılaştırması) doğrulanır. İşçi çıktısı için bayt düzeyinde ya da yapısal kararlılık iddiası yanlış olurdu.