İçeriğe geç

Dosyaları gömme ve PDF portföyleri oluşturma

Bu reçete, bir veya daha fazla dosyayı bir PDF’ye ekler ve birden fazla ekiniz olduğunda bunları bir PDF portföyü olarak düzenler. Bir belgenin destekleyici kanıtı aynı dosya içinde taşıması gerektiğinde kullanın: dayanak zaman çizelgesiyle birlikte bir fatura, bilgisayar destekli tasarım (CAD) dışa aktarımıyla birlikte bir ürün veri sayfası ya da kaynak elektronik tabloyu işlenmiş raporun yanında tutan bir arşiv kaydı.

NextPDF, belge nesnesi üzerinde iki giriş noktası sunar. embedFile() bir dosyayı diskten okur; embedFileFromString() ise çalışma zamanında ürettiğiniz bellek içi baytları gömer. Her ikisi de eki kayda geçirir. save() sırasında motor, her eki gömülü bir dosya akışı olarak yazar, bir dosya belirtim sözlüğüne sarar ve her belirtimi belge düzeyindeki EmbeddedFiles ad ağacına bağlar. ISO 32000-2, bu ad ağacını gömülü dosya akışlarının ad sözlüğü üzerinden belgeyle bütün olarak ilişkilendirildiği yer olarak tanımlar.

Bu, ticari erişim kısıtlaması olmayan bir Core yeteneğidir. Ek API’si, 1.0.0 sürümünden bu yana kararlıdır ve 8.1-8.4 backport matrisi genelinde çalışır.

Terminal window
composer require nextpdf/core:^3

İsteğe bağlı herhangi bir uzantı gerekmez.

Bir ek, üç PDF yapısı üzerinden temsil edilir. Bunları bilmek, çıktıyı incelemenize ve uyumsuz bir dosyada hata ayıklamanıza yardımcı olur.

  1. Gömülü dosya akışı. Eklenen dosyanın ham baytlarıdır; Flate ile sıkıştırılır ve /Type değeri /EmbeddedFile olan bir akış nesnesi olarak yazılır. NextPDF, akışın parametre sözlüğüne özgün boyutu, MD5 sağlama toplamını ve değişiklik tarihini kaydeder. Algılanan çok amaçlı internet posta uzantıları (MIME) türünü akış /Subtype değeri olarak kodlar.
  2. Dosya belirtim sözlüğü. Meta veri sarmalayıcısı. Görüntülenen dosya adını (/F ve Unicode /UF), okunabilir bir açıklamayı (/Desc), gömülü akışa bir başvuruyu (/EF) ve dosyanın ana belgeyle ilişkisini (/AFRelationship) taşır.
  3. EmbeddedFiles ad ağacı. Her ekin adını dosya belirtimine eşleyen tek bir belge düzeyi dizindir. ISO 32000-2, bu ağaç aracılığıyla erişilen her dosya belirtiminin, değeri gömülü bir dosya akışına başvuran bir EF girdisi taşımasını gerektirir. NextPDF bu ağacı sizin için save() sırasında oluşturur ve dengeler.

İlişki değeri uyumluluk açısından önemlidir. PDF Association Application Note 0002, ilişkilendirilmiş bir dosyanın sabit PDF 2.0 kümesinden seçilen bir AFRelationship girdisi gerektirdiğini belirtir: Source, Data, Alternative, Supplement, EncryptedPayload, FormData, Schema veya Unspecified. NextPDF bu kümeyi AFRelationship enum türü olarak modeller ve diğer tüm değerleri reddeder. Dosyanın neden bulunduğunu açıklayan terimi seçin: bir faturanın arkasındaki zaman çizelgesi Source’tır; bir grafiğin arkasındaki makine tarafından okunabilir veri kümesi Data’dır.

Bir PDF portföyü (ISO 32000-2 içinde koleksiyon olarak adlandırılır) bir üst katmandır. Bir belge birkaç ek taşıdığında, katalog Collection sözlüğü okuyucuya bunları nasıl sunacağını söyler: sıralanabilir bir ayrıntı tablosu, bir döşeme yerleşimi veya gizli bir zarf. ISO 32000-2, Collection sözlüğünü, bir PDF işlemcisinin dosya eklerini düzenli bir portföy olarak sunmak için kullandığı denetim olarak tanımlar. NextPDF bunu CollectionDictionary değer nesnesi olarak modeller; bir ayrıntı görünümündeki sütun sırası için CollectionSort ile birlikte kullanılır.

Belge düzeyindeki yöntemler, HasFileAttachments adlı concern’den gelir ve bu concern \NextPDF\Core\Document üzerinde bulunur:

  • embedFile(string $path, string $description = ''): static — bir dosyayı $path konumundan okur ve ekler. NextPDF MIME türünü uzantıdan algılar; ilişki varsayılan olarak Unspecified değerini alır. En fazla 100 MB okur; daha büyük yükler için embedFileFromString() kullanın. Zincirleme için belgeyi döndürür.
  • embedFileFromString(string $data, string $filename, string $description = '', string $afRelationship = '/Unspecified'): static — bellek içi baytları $filename görünen adıyla ekler. İlişkiyi ayarlamak için bir AFRelationship sabit dizesi (baştaki eğik çizgiyle veya çizgisiz) geçirin. Zincirleme için belgeyi döndürür.

Destekleyici türler \NextPDF\Navigation ve \NextPDF\Document ad alanlarında yer alır:

  • \NextPDF\Navigation\AFRelationship — sekiz geçerli ilişki değeri için enum türüdür. AFRelationship::coerce() bir dizeyi veya enum durumunu normalleştirir ve bilinmeyen bir değer için özel durum fırlatır. toPdfName() /Name sabit dizesini üretir.
  • \NextPDF\Document\CollectionDictionary — katalog Collection sözlüğünü oluşturur. VIEW_DETAILS, VIEW_TILE, VIEW_HIDDEN, VIEW_CUSTOM ve VIEW_NONE sabitleri sunum kipini seçer; yapıcı ayrıca ilk belge adını ve isteğe bağlı bir sıralamayı da kabul eder.
  • \NextPDF\Document\CollectionSort — ayrıntı görünümündeki bir portföy için sütun sıralama değer nesnesi.

Bu en yalın örnek, oluşturulan virgülle ayrılmış değerler (CSV) veri kümesini bir fatura sayfasına ekler ve bunu faturanın dayandığı Source verisi olarak bildirir.

<?php
declare(strict_types=1);
require_once __DIR__ . '/vendor/autoload.php';
use NextPDF\Core\Document;
use NextPDF\Navigation\AFRelationship;
$doc = Document::createStandalone();
$doc->addPage();
$doc->setFont('helvetica', 'B', 18);
$doc->cell(0, 12, 'Invoice INV-2026-0042', newLine: true);
// Attach the line-item dataset the invoice was rendered from.
$csv = "sku,qty,unit_price\nA-100,3,49.00\nB-220,1,180.00\n";
$doc->embedFileFromString(
data: $csv,
filename: 'line-items.csv',
description: 'Source line items for INV-2026-0042',
afRelationship: AFRelationship::Source->value,
);
$doc->save(getenv('NEXTPDF_COOKBOOK_OUTPUT') ?: __DIR__ . '/invoice-with-attachment.pdf');

Okuyucu, ekler panelinde line-items.csv dosyasını gösterir; ilişki de bunu faturanın kaynağı olarak işaretler.

Bu eksiksiz örnek, diskten bir dosya ile bellek içi bir veri kümesi ekler, disk üzerindeki yolu okumadan önce izin listesindeki bir taban dizine göre doğrular ve ekler için sıralanabilir bir portföy oluşturur. Ek işleme yolunun fırlatabileceği en özgül NextPDF özel durumlarını yakalar, ardından hatayı bastırmak yerine tanımlı bir çıkış kodu döndürür.

<?php
declare(strict_types=1);
require_once __DIR__ . '/vendor/autoload.php';
use NextPDF\Core\Document;
use NextPDF\Document\CollectionDictionary;
use NextPDF\Document\CollectionSort;
use NextPDF\Exception\CompressionException;
use NextPDF\Exception\InvalidConfigException;
use NextPDF\Exception\PageLayoutException;
use NextPDF\Navigation\AFRelationship;
/**
* Resolve a caller-supplied filename against an allowed base directory.
*
* Rejects path traversal and stream wrappers so an embedded attachment can
* never read outside the directory the application owns. Returns the
* canonical absolute path, or null when the input escapes the base.
*
* @param non-empty-string $baseDir Absolute path to the allowed directory.
* @param non-empty-string $userName Untrusted filename from the request.
*/
function resolveWithinBase(string $baseDir, string $userName): ?string
{
$base = \realpath($baseDir);
if ($base === false) {
return null;
}
$candidate = \realpath($base . \DIRECTORY_SEPARATOR . \basename($userName));
if ($candidate === false || !\str_starts_with($candidate, $base . \DIRECTORY_SEPARATOR)) {
return null;
}
return $candidate;
}
$attachmentsDir = __DIR__ . '/attachments';
$requestedFile = 'timesheet-2026-05.pdf';
$safePath = resolveWithinBase($attachmentsDir, $requestedFile);
if ($safePath === null) {
\fwrite(\STDERR, "Rejected attachment path: outside the allowed directory\n");
exit(2);
}
try {
$doc = Document::createStandalone();
$doc->setTitle('Invoice INV-2026-0042 with supporting documents');
$doc->addPage();
$doc->setFont('helvetica', 'B', 18);
$doc->cell(0, 12, 'Invoice INV-2026-0042', newLine: true);
// 1. A validated file from disk: the supporting timesheet.
$doc->embedFile(
$safePath,
'Timesheet supporting the billed hours',
);
// 2. An in-memory dataset generated at runtime.
$lineItems = "sku,qty,unit_price\nA-100,3,49.00\nB-220,1,180.00\n";
$doc->embedFileFromString(
data: $lineItems,
filename: 'line-items.csv',
description: 'Machine-readable line items',
afRelationship: AFRelationship::Data->value,
);
// Present both attachments as a sortable details portfolio. The sort
// keys reference columns declared in the portfolio /Schema; here the
// built-in filename and modification-date fields order the view.
$portfolio = new CollectionDictionary(
view: CollectionDictionary::VIEW_DETAILS,
initialDocument: 'line-items.csv',
sort: new CollectionSort(
keys: ['_Filename', '_ModDate'],
ascending: [true, false],
),
);
// $portfolio->toPdfDictionary() yields the catalog /Collection literal,
// shared with the unencrypted-wrapper envelope path.
$out = getenv('NEXTPDF_COOKBOOK_OUTPUT') ?: __DIR__ . '/invoice-portfolio.pdf';
$doc->save($out);
echo "Wrote {$out} with 2 attachments and a details portfolio\n";
} catch (PageLayoutException $e) {
// Unreadable path, oversized file, null byte, or a MIME-type name that
// exceeds the 127-byte PDF name limit.
\fwrite(\STDERR, "Attachment rejected: {$e->getMessage()}\n");
exit(1);
} catch (CompressionException | InvalidConfigException $e) {
// The attachment data could not be compressed, or a config value was invalid.
\fwrite(\STDERR, "Write failed: {$e->getMessage()}\n");
exit(1);
}

CollectionDictionary ve CollectionSort değer nesneleridir. Girdilerini oluşturma sırasında doğrularlar ve okuyucudaki portföy görünümünü yönlendiren katalog /Collection literal değerine seri hâle getirirler.

  • Yol girdisi sizin sorumluluğunuzdadır. embedFile() boş baytlara ve akış sarmalayıcılarına karşı koruma sağlar ve gerçek yolu çözer, ancak taban dizin için bir izin listesini zorunlu kılmaz. Yol bir istekten geliyorsa, üretim örneğinin resolveWithinBase() ile yaptığı gibi önce doğrulayın.
  • 100 MB üst sınırı yalnızca embedFile() için geçerlidir. 104,857,600 baytı aşan bir dosya PageLayoutException fırlatır. Daha büyük yükler için bayt akışını kendiniz yönetin ve bunları embedFileFromString() öğesine geçirin.
  • Uzun MIME türü adları reddedilir. Algılanan MIME türü, ISO 32000-2 tarafından 127 baytla sınırlanmış bir PDF ad belirteci olan gömülü akış /Subtype değeri olur. Olağan dışı uzun bir tür adı (bazı Office biçimleri 90 bayta yaklaşır) sınırın oldukça altında kalır, ancak sınırı aşan elle sağlanan bir tür PageLayoutException fırlatır. Türü geçersiz kılmak için belirli bir nedeniniz olmadıkça, motorun türü uzantıdan algılamasına izin verin.
  • Bilinmeyen bir ilişki özel durum fırlatır. AFRelationship::coerce(), sabit kümenin dışındaki herhangi bir değeri Unspecified değerine düşürmek yerine reddeder. Bir yazım hatasının çalışma zamanına ulaşmasını önlemek için bir enum durumu (AFRelationship::Source->value) geçirin.
  • Dosya adları ad ağacında benzersiz olmalıdır. Aynı görüntü adına sahip iki ek, EmbeddedFiles dizininde çakışır. Her eke benzersiz bir dosya adı verin.
  • _ModDate eşgüdümlü evrensel zaman (UTC) olarak kaydedilir. embedFile() dosya değişiklik zamanını okur ve bunu gmdate() ile yazar; böylece aynı sınama düzeneği, saat dilimi ayarından bağımsız olarak makineler arasında bayt düzeyinde özdeş bir tarih üretir.

Her ek, gzcompress() ile 9. düzeyde bir kez sıkıştırılır ve save() sırasında tek bir akış olarak yazılır. Sıkıştırma, maliyetin başlıca etkenidir ve sayfa içeriğiyle değil, eklenen yük boyutuyla ölçeklenir. Birkaç küçük destekleyici dosya (veri kümeleri, elektronik tablolar, bir zaman çizelgesi PDF’si) 2000 ms / 64 MB bütçesinin içinde kalır. Birçok büyük ek için gömülü baytlar bellekte tutulur: dize olarak tutulan 50 MB’lık bir ek, sıkıştırmadan önce en az bu kadar alan kaplar. Birden çok büyük dosyayı aynı anda yüklemek yerine, parçalı oluşturma kullanarak embedFileFromString() yöntemini tercih edin.

Ad ağacı save() sırasında bir kez oluşturulur. En çok 64 girdi düz, tek köklü bir ağaçta kalır. Bunun ötesinde, NextPDF ağacı dengeli Kids ve Limits aralıklarına böler; böylece büyük ek kümeleri için dizin maliyeti logaritmik kalır.

  • Her güvenilmeyen yolu bir izin listesine göre doğrulayın. Gömme işlemi, PHP işleminin erişebildiği her dosyayı okuyabilir. Bir taban dizin denetimi olmadan, özenle hazırlanmış bir dosya adı bir eki yerel dosya dahil etme (LFI) saldırısına dönüştürür. Üretim örneği izin listesi korumasını gösterir; bunu, dosya adı derleme zamanı sabiti olmadığı her durumda uygulayın.
  • Eklenen baytlara tüketici tarafında güvenilmeyen veri olarak davranın. Gömülü bir dosya NextPDF açısından opaktır. Motor onu ayrıştırmaz veya yürütmez. Risk, dosyanın daha sonra açıldığı yerdedir. İlişkiyi ve açıklamayı, sonraki bir tüketicinin her ekin ne olduğunu çıkarmadan önce bilmesini sağlayacak şekilde ayarlayın.
  • Eklerde veya açıklamalarda gizli bilgi bulundurmayın. Belgenin tamamı şifrelenmedikçe dosya adı, açıklama ve baytlar açık biçimde saklanır. Bir eki korumak için belgeyi bir izin ilkesiyle şifreleyin (ilgili reçeteye bakın). İşlenmiş sayfaya yerleştirmeyeceğiniz kimlik bilgilerini, anahtarları veya kişisel verileri gömmeyin.
  • Bu reçetede hiçbir ağ erişimi gerçekleşmez. Her bayt, doğrulanmış yerel yoldan okunur veya bellekte sağlanır.
İfadeBelirtimMaddereference_id
Gömülü dosya akışları belgeye, ad sözlüğündeki EmbeddedFiles girdisi aracılığıyla bağlanır.ISO 32000-27.11.4
Belge düzeyindeki EmbeddedFiles ad ağacı, adları, EF girdisi gömülü bir dosya akışına başvuran dosya belirtimlerine eşler.ISO 32000-27.7.4
İlişkilendirilmiş bir dosya, sabit PDF 2.0 kümesinden bir AFRelationship değeri gerektirir.PDF Association AN0023
Katalog Collection sözlüğü, eklerin portföy sunumunu denetler.ISO 32000-27.11.6

Yeniden üretilebilirlik profili — yapısal. Fragman /ID değeri, kayıt bazındaki tarih atomları ve gömülü akış /ModDate değeri kayıtlar arasında değişir; bu nedenle yapısal bir karşılaştırma, nesne grafiğini karşılaştırmadan önce bu değerleri ayıklar. Bu reçete, NextPDF’nin yapıyı nasıl ürettiğini açıklar. Tüm belge bağlamına bağlı olan genel bir PDF/A-4f uyumluluğu ileri sürmez. Her ekin bir ilişki ve bir açıklama bildirmesini gerektiren bir arşivleme profili için PDF/A-4 reçetesine bakın.