Oluşturulan büyük bir PDF'yi HTTP yanıtı olarak akışla gönderme
Bir bakışta
“Bir bakışta” başlıklı bölümBir denetleyicide büyük bir PDF oluşturur ve baytları, yanıt arabelleğinde ikinci bir tam kopya tutmadan döndürmek istersiniz. Her çerçeve entegrasyonu, kendi PdfResponse fabrikasının akış varyantlarını içerir: streamInline() ve streamDownload(). Her yöntem, PDF gövdesini istemciye sabit 64 KB’lik parçalar hâlinde yazan bir geri çağırmayla birlikte bir çerçeve StreamedResponse nesnesi döndürür.
Bu yolu seçmeden önce bellek modelini inceleyin. Motor önce belgenin tamamını bellekte oluşturur. Akış geri çağırması, PDF’nin tamamını tek bir dize olarak somutlaştıran getPdfData() çağrısını yapar, ardından bu dize üzerinde 64 KB’lik dilimler hâlinde ilerler. İkinci kopyanın tepe maliyetinden kurtulursunuz; bu kopya, arabelleğe alınmış bir Illuminate\Http\Response veya Symfony\Component\HttpFoundation\Response nesnesinin, çerçeve Content-Length değerini ölçerken tuttuğu kopyadır. Akış varyantı uzunluğu ölçmez, bu nedenle Content-Length başlığını atlar. Yanıt gövdesini ve belge dizesini hiçbir zaman aynı anda tutmaz. Bu, gerçek artımlı akış değildir: NextPDF artımlı yazıcı yüzeyine sahip değildir; bu nedenle belge, ilk bayt sokete ulaşmadan önce tamamen somutlaştırılır.
Başlamadan önce şu parçaların yerinde olduğundan emin olun:
- NextPDF core kuruludur;
nextpdf/laravelveyanextpdf/symfonyçerçeve entegrasyonlarından biri kurulmuş ve keşfedilmiştir. - Çerçevenizde bir isteği bir denetleyiciye nasıl yönlendireceğinizi zaten biliyorsunuz.
- Şu sayfayı okudunuz: Bir denetleyiciden oluşturulan bir PDF döndürme; bu sayfa, bu reçetenin üzerine inşa edildiği arabelleğe alınmış
inline()vedownload()fabrikalarını kapsar.
Bu nasıl-yapılır kılavuzu, Laravel ve Symfony tarafından paylaşılan StreamedResponse desenine odaklanır. CodeIgniter 4 aynı streamInline() / streamDownload() yöntem adlarını sunar, ancak baytları bir CodeIgniter\HTTP\DownloadResponse nesnesine sarar; bu nesne, geri çağırma temelli bir StreamedResponse değildir. Uç durumlar bölümü bu farkı ele alır.
Kurulum
“Kurulum” başlıklı bölümÇerçeveniz için entegrasyonu kurun. Aşağıdaki komutlardan birini çalıştırın.
composer require nextpdf/laravelcomposer require nextpdf/symfonyLaravel için, kurulumdan sonra yapılandırmayı yayımlayın.
php artisan vendor:publish --tag=nextpdf-configSymfony paketi Flex aracılığıyla kaydedilir. Devam etmeden önce çerçevenizin kurulum sayfasında keşfedildiğini doğrulayın.
Kavramsal genel bakış
“Kavramsal genel bakış” başlıklı bölümArabelleğe alınmış bir yanıt fabrikası, PdfResponse::download() veya PdfResponse::inline(), getPdfData() çağrısını yapar, döndürülen dizeyi bir Response nesnesinde saklar ve Content-Length değerini strlen() ile ayarlar. Çerçeve daha sonra bu dizeyi yanıtın ömrü boyunca tutar. Büyük bir belgede, belge dizesi ile yanıt gövdesi dizesi aynı anda bellekte bulunur.
Akış fabrikası farklı bir biçim kullanır. PdfResponse::streamDownload() ve PdfResponse::streamInline(), bir geri çağırmayla oluşturulmuş bir StreamedResponse döndürür. Çerçeve, bu geri çağırmayı yalnızca gövdeyi göndermeye hazır olduğunda çağırır. Geri çağırma içinde entegrasyon getPdfData() çağrısını bir kez yapar, döndürülen dizeyi 64 KB’lik parçalara böler, her parçayı echo ile yazar ve ardından flush() çağrısı yapar. Gövdenin ikinci bir kalıcı kopyasını tutmaz ve bir Content-Length başlığı yaymaz.
Bu sayfadaki her kararı iki olgu şekillendirir:
- Oluşturma istekli, aktarım parçalıdır.
getPdfData(),NextPDF\Core\Documentüzerinde yazıcıyı çağırır ve PDF’nin tamamını tek bir dize olarak döndürür. 64 KB’lik parçalama yalnızca halihazırda oluşturulmuş baytların süreçten nasıl çıktığını denetler. Tepe bellek, küçük bir akış penceresiyle değil, tamamlanmış belgenin boyutuyla sınırlıdır. Content-Lengthyok. Akış varyantı, gövdeyi geri çağırma içinde oluşturmadan gövde uzunluğunu bilemez, bu nedenle başlığı atlar. İstemci tarafındaki bir ilerleme çubuğu, birRangeisteği veya uzunluğa duyarlı bir proxy boyut bilgisi görmez. Bilinen bir uzunluk, yanıt kopyasından tasarruf etmekten daha önemli olduğunda arabelleğe alınmışdownload()/inline()seçeneğini seçin.
Belgeyi çerçevenin alışılmış çözümleme yolu üzerinden edinin:
- Laravel:
NextPDF\Contracts\DocumentFactoryInterfacearayüzünü kapsayıcıdan çözümleyin vecreate()çağrısını yapın. Bu çağrı, akış fabrikalarının kabul ettiği somut tür olan yeni birNextPDF\Core\Documentdöndürür. - Symfony:
NextPDF\Symfony\Service\PdfFactorysınıfını enjekte edin vecreate()çağrısını yapın. Bu çağrı, yapılandırılmış varsayılanlar uygulanmış yeni birNextPDF\Core\Documentdöndürür.
API yüzeyi
“API yüzeyi” başlıklı bölüm| İlgi alanı | Laravel | Symfony |
|---|---|---|
| Yeni belge | app(DocumentFactoryInterface::class)->create() | PdfFactory::create() |
| Akışlı satır içi | PdfResponse::streamInline($doc, $name) | PdfResponse::streamInline($doc, $name) |
| Akışlı indirme | PdfResponse::streamDownload($doc, $name) | PdfResponse::streamDownload($doc, $name) |
| Döndürülen tür | Symfony\Component\HttpFoundation\StreamedResponse | Symfony\Component\HttpFoundation\StreamedResponse |
| Geri çağırma içindeki oluşturma çağrısı | NextPDF\Core\Document::getPdfData() | NextPDF\Core\Document::getPdfData() |
| Parça boyutu | 64 KB (belirlenimci str_split) | 64 KB (belirlenimci substr döngüsü) |
Laravel PdfResponse sınıfı NextPDF\Laravel\Http\PdfResponse konumunda bulunur; Symfony karşılığı ise NextPDF\Symfony\Http\PdfResponse konumundadır. Her ikisinin akış fabrikaları da aynı Symfony\Component\HttpFoundation\StreamedResponse türünü döndürür. İkisi de aynı sabit Open Web Application Security Project (OWASP) yanıt sıkılaştırma başlığı kümesini (X-Content-Type-Options: nosniff, X-Frame-Options: DENY, Content-Security-Policy: default-src 'none', X-Robots-Tag: noindex, nofollow, Referrer-Policy: no-referrer) uygular ve indirme dosya adını arındırır. Bu başlıkları kendiniz eklemezsiniz.
Her iki fabrika da PDF ikilisinin tamamını oluşturup döndüren aynı temel core yüzeyini, NextPDF\Core\Document::getPdfData(): string, çağırır. Bunun kardeşi olan save(string $path): void, aynı baytları atomik bir yazıcı aracılığıyla diske yazar. Bu reçete getPdfData() kullanır; çünkü hedef bir dosya değil, bir HTTP soketidir.
Kod örneği — hızlı başlangıç
“Kod örneği — hızlı başlangıç” başlıklı bölümHer çerçevedeki en yalın akışlı indirme eylemi aşağıdadır. Belge çağrıları aynı core yüzeyini kullanır; yalnızca denetleyici iskeleti farklıdır. Akış fabrikası çerçeveye bir geri çağırma verir; bu nedenle eyleminiz hemen geri döner. Gövde, çerçeve yanıtı gönderdiğinde oluşturulur ve boşaltılır.
<?php
declare(strict_types=1);
namespace App\Http\Controllers;
use NextPDF\Contracts\DocumentFactoryInterface;use NextPDF\Laravel\Http\PdfResponse;use Symfony\Component\HttpFoundation\StreamedResponse;
final class ReportController extends Controller{ public function annualReport(): StreamedResponse { $document = app(DocumentFactoryInterface::class)->create(); $document->addPage(); $document->cell(0, 10, 'Annual report', newLine: true);
return PdfResponse::streamDownload($document, 'annual-report.pdf'); }}<?php
declare(strict_types=1);
namespace App\Controller;
use NextPDF\Symfony\Http\PdfResponse;use NextPDF\Symfony\Service\PdfFactory;use Symfony\Component\HttpFoundation\StreamedResponse;use Symfony\Component\Routing\Attribute\Route;
final class ReportController{ #[Route('/report', name: 'report_pdf')] public function annualReport(PdfFactory $pdf): StreamedResponse { $document = $pdf->create(); $document->addPage(); $document->cell(0, 10, 'Annual report', newLine: true);
return PdfResponse::streamDownload($document, 'annual-report.pdf'); }}İndirmeyi zorlamak yerine bir tarayıcı sekmesinde önizlemek için streamInline(...) çağrısını streamDownload(...) yerine yapın. Content-Disposition başlığı inline olur ve diğer tüm başlıklar aynı kalır.
Kod örneği — üretim
“Kod örneği — üretim” başlıklı bölümBir üretim eylemi bağımlılıklarını enjekte eder, yol girişini doğrular, oluşturmanın yükseltebileceği en özel istisnayı yakalar, bir izleme sızdırmadan hata sınıfını günlüğe kaydeder ve tanımlanmış bir Hypertext Transfer Protocol (HTTP) hatası döndürür. Aşağıdaki örnek Laravel yapıcı enjeksiyonunu kullanır. Symfony karşılığı aynı biçimi izler; eylem başına PdfFactory enjekte edilir.
getPdfData() akış geri çağırması içinde çalışır; bu nedenle yükselttiği bir istisna, çerçeve başlıkları göndermeye başladıktan sonra ortaya çıkar. Hata işlemeyi yararlı tutmak için belgeyi, yani başarısız olabilecek adımı, yanıtı geri vermeden önce oluşturun ve oluşturma başarısızlığını orada yakalayın. Ardından geri çağırma içinde yalnızca halihazırda oluşturulmuş baytların parçalı aktarımı gerçekleşir.
<?php
declare(strict_types=1);
namespace App\Http\Controllers;
use Illuminate\Http\Response;use NextPDF\Contracts\DocumentFactoryInterface;use NextPDF\Core\Document;use NextPDF\Exception\NextPdfException;use NextPDF\Laravel\Http\PdfResponse;use Psr\Log\LoggerInterface;use Symfony\Component\HttpFoundation\StreamedResponse;
final class StatementController extends Controller{ private const int MAX_STATEMENT_ID = 9_999_999;
public function __construct( private readonly DocumentFactoryInterface $documents, private readonly LoggerInterface $logger, ) {}
public function show(int $statementId): StreamedResponse|Response { // Validate input at the boundary before any build work runs. if ($statementId < 1 || $statementId > self::MAX_STATEMENT_ID) { return new Response('Invalid statement identifier.', 422); }
try { // Build the whole document up front. getPdfData(), invoked inside // the streamed callback, materializes the full PDF in memory, so // do the failure-prone build here, where the catch can still set a // clean HTTP status before any byte is sent. $document = $this->buildStatement($statementId); $document->getPdfData(); } catch (NextPdfException $exception) { // Log the exception class, never the message or a stack trace, so // internal detail does not leak into the log sink. $this->logger->error('Statement PDF build failed', [ 'statement_id' => $statementId, 'exception' => $exception::class, ]);
return new Response('Could not generate the statement PDF.', 500); }
// The build succeeded. The streamed factory rebuilds the bytes inside // its callback and flushes them to the client in 64 KB chunks. return PdfResponse::streamDownload( $document, "statement-{$statementId}.pdf", ); }
private function buildStatement(int $statementId): Document { $document = $this->documents->create(); $document->addPage(); $document->cell(0, 10, "Statement #{$statementId}", newLine: true);
return $document; }}Herhangi bir oluşturma başarısızlığı için tek bir işleyici istediğinizde, her NextPDF istisnasının türediği soyut taban olan NextPDF\Exception\NextPdfException istisnasını yakalayın. Belirli nedenlere yanıt vermek için önce getPdfData() tarafından yükseltilebilecek somut alt türleri yakalayın: içerik sayfa geometrisine sığmadığında NextPDF\Exception\PageLayoutException, akış sıkıştırması başarısız olduğunda NextPDF\Exception\CompressionException ve geçersiz bir çıktı yapılandırması için NextPDF\Exception\InvalidConfigException. Hiçbir zaman boş bir catch bloğu yazmayın. Buradaki her dal, hata sınıfını günlüğe kaydeder ve tanımlanmış bir durum döndürür.
Eylem başına yeni bir belge çözümlemek, fabrikayı testlerde değiştirilebilir tutar. Uzun süre çalışan tek bir işçi sürecinde, ilgisiz iki belge için aynı denetleyici örneğini yeniden kullanmayın; çünkü eski içerik durumu taşınır.
Uç durumlar ve dikkat edilecek noktalar
“Uç durumlar ve dikkat edilecek noktalar” başlıklı bölüm- Belge, önce-doğrula-sonra-akışla-gönder deseninde iki kez oluşturulur. Üretim örneği, oluşturmayı doğrulamak için
getPdfData()çağrısını bir kez yapar; ardından fabrika bunu geri çağırma içinde tekrar çağırır. Bu, başarısızlık noktasını başlıkların önüne taşımanın maliyetidir. Belirli bir belgede çift oluşturma çok maliyetli olduğunda, ön oluşturma yoklamasını atlayın ve geri çağırma içindeki bir oluşturma başarısızlığının halihazırda başlamış bir yanıtı kesintiye uğratacağını kabul edin. Content-Lengthyok. Akış varyantı başlığı atlar. İndirme ilerleme çubukları veRangeistekleri çalışmaz. Bilinen bir uzunluk gerektiğinde, arabelleğe alınmışdownload()/inline()seçeneğini kullanın.- Arabelleğe alan proxy yararı ortadan kaldırır. Gövdenin tamamını iletmeden önce yakalayan bir ters proxy veya PHP çıktı arabelleği, PDF’nin tamamını yeniden tutar ve bu da kaydedilen kopyayı ortadan kaldırır. Proxy’yi
application/pdfyanıtlarını akışla gönderecek şekilde yapılandırın veya o yolda arabelleğe alınmış bir yanıt kullanın. - CodeIgniter 4 geri çağırma ile akış yapmaz. CodeIgniter entegrasyonu aynı
streamInline()/streamDownload()yöntem adlarını sunar, ancak bunlar gövdenin tamamını tutan birCodeIgniter\HTTP\DownloadResponsedöndürür; bu nesne, geri çağırma temelli birStreamedResponsedeğildir. Bu sayfadaki StreamedResponse deseni yalnızca Laravel ve Symfony için geçerlidir. - Geri döndükten sonra gövdeye yazmayın. Çıktı, akış geri çağırmasına aittir. Yanıt gövdesine kendiniz
echoile ya da başka bir yolla yazmayın; özellikleStreamedResponsenesnesini çerçeveye geri döndürdükten sonra bunu yapmayın. - İmzalı belgeler hızla başarısız olur. Üst düzey bir PAdES imzası için kurulmuş bir belge üzerinde
getPdfData()çağrısı, imzasız bir dosya yaymak yerineNextPDF\Exception\NotImplementedExceptionyükseltir. İmzalı çıktıyı bu reçete üzerinden değil, belgelenmiş imzalama yolu üzerinden akışla gönderin.
Performans
“Performans” başlıklı bölümAkış, belge oluşturmayı değil, yanıt kopyasını sınırlar. Tepe bellek kabaca tamamlanmış bir PDF boyutundadır; çünkü getPdfData() ilk parçayı göndermeden önce belgenin tamamını somutlaştırır. Gerçekten büyük veya çok sayfalı bir belgede, istek bütçesine aktarım değil, oluşturmanın kendisi hâkim olur. Oluşturmayı kuyruğa alınmış bir işle istek iş parçacığından uzaklaştırın. Kuyruğa alınmış bir işte PDF oluşturma sayfasına bakın.
64 KB’lik parça boyutu her iki entegrasyonda da sabit ve belirlenimcidir. Yalnızca aktarım ayrıntı düzeyini denetler; gönderilen toplam baytları veya tepe belleği değiştirmez. Kısıt, tasarruf edilen yanıt kopyası olduğunda ve ilerleme çubuğu gerekmediğinde akış varyantını seçin. Bilinen bir Content-Length değerinden yararlanan küçük, gecikmeye duyarlı yanıtlar için arabelleğe alınmış varyantı seçin.
Güvenlik notları
“Güvenlik notları” başlıklı bölüm- Oluşturmadan önce girişi doğrulayın. Üretim eylemi, herhangi bir oluşturma işi çalışmadan önce aralık dışındaki bir tanımlayıcıyı
422ile reddeder. Doğrulanmamış girişi hiçbir zaman oluşturmaya veya dosya adına eklemeyin. - Dosya adı arındırması sizin için uygulanır. Her iki akış fabrikası da dosya adını arındırır ve OWASP yanıt sıkılaştırma başlığı kümesini ekler. Denetlediğiniz bir değer geçirin ve fabrikanın bunu ikinci bir katman olarak arındırmasına izin verin. Dosya adını elle kodlamayın.
- Eşzamanlı belleği sınırlayın. PDF’nin tamamı istek başına bellekte somutlaştırıldığından, yüksek eşzamanlı trafik tepe belleği katlar. Bellek tükenmesi kaynaklı hizmet reddini azaltmak için, bir oluşturmayı yönlendiren girişlere boyut ve hız sınırları uygulayın.
- İletiyi değil, hata sınıfını günlüğe kaydedin. catch bloğu, istisna iletisini veya yığın izini değil,
$exception::classdeğerini ve bir ilişkilendirme tanımlayıcısını günlüğe kaydeder. Günlük havuzundaki ham bir izleme, bilgi sızıntısıdır. - Boş catch yok. Bu sayfadaki her catch dalı günlüğe kaydeder ve tanımlanmış bir hata yanıtı döndürür.
Uygunluk
“Uygunluk” başlıklı bölümBu kılavuz hiçbir normatif standart iddiasında bulunmaz. Gösterilen her sınıf, yöntem ve başlık, adı geçen entegrasyonun doğrulanmış genel yüzeyidir: NextPDF\Core\Document::getPdfData(), NextPDF\Laravel\Http\PdfResponse ve NextPDF\Symfony\Http\PdfResponse akış fabrikaları ve Symfony\Component\HttpFoundation\StreamedResponse dönüş türü. Fabrikaların uyguladığı OWASP yanıt sıkılaştırma başlığı semantiği, alıntılarıyla birlikte, Ayrıca bakınız altında bağlantılı her entegrasyonun güvenlik ve işlemler sayfasında belgelenmiştir. Bu Cookbook sayfası kullanımı yeniden ifade eder ve normatif alıntıları o sayfalara bırakır.
Ayrıca bakınız
“Ayrıca bakınız” başlıklı bölüm- Bir denetleyiciden oluşturulan bir PDF döndürme: arabelleğe alınmış
inline()vedownload()karşılıkları. - Kuyruğa alınmış bir işte PDF oluşturma: oluşturmayı istek iş parçacığından uzaklaştırın.
- Laravel üretim kullanımı: DI ile bağlanmış denetleyici, OWASP başlık kümesi ve kapsayıcı bağlama sözleşmesi.
- Symfony üretim kullanımı: akış geri çağırması, 64 KB’lik parça yayıcısı ve oluşturucu konum belirleyicisi.