Oluşturulan PDF'leri CI'da test etme
Bir bakışta
“Bir bakışta” başlıklı bölümBu tarif, NextPDF ile PDF oluşturan ve kendi çıktısını test altında tutmak isteyen uygulama geliştiricileri içindir. Bu, motorun kendi test disiplininin tüketici tarafıdır: NextPDF’i yeniden test etmezsiniz; belgenizin hâlâ söylemesi gerekeni söylediğini ve hâlâ eskisi gibi göründüğünü onaylarsınız.
İki onay biçimi neredeyse her şeyi kapsar:
- Çıkarılan metin üzerindeki anlamsal onaylar — oluşturun, Unicode metni geri kazanın ve beklediğiniz dizeleri içerdiğini onaylayın. Bu, yerleşim düzenlemelerine ve yazı tipi değişikliklerine dayanır.
- Baytlar üzerindeki altın (anlık görüntü) onaylar — bir yeniden oluşturma
bayt düzeyinde özdeş olsun diye
DeterministicSettings’i sabitleyin, sonra yeni baytları, gönderime alınmış bir referans dosyasıyla karşılaştırın. Bu, her türlü istenmeyen değişikliği yakalar.
İçerik doğruluğu için anlamsal onayları ve bir gerileme tetikleyicisi olarak altın onayları kullanın. Çalıştırıcı, iş istasyonunuzun ürettiği baytların aynısını ürettiğinde, ikisi de CI’da değiştirilmeden çalışır.
Kurulum
“Kurulum” başlıklı bölümcomposer require --dev phpunit/phpunitcomposer require nextpdf/core:^3Bir bayt farkı üzerinde değil, çıkarılan metin üzerinde onaylayın
“Bir bayt farkı üzerinde değil, çıkarılan metin üzerinde onaylayın” başlıklı bölümİki PDF’in ham bayt farkı kırılgandır: yeni bir zaman damgası, yeniden alt kümelenmiş bir yazı tipi ya da yeniden sıralanmış bir nesne; bir okuyucunun gördüğünü değiştirmeden baytları değiştirir. Bunun yerine içerik üzerinde onaylayın.
NextPDF Core bir üreticidir; bu yüzden önce metni çıkarılabilir kılın. Bunlar tek
değil, iki ayrı mekanizmadır. Metin çıkarımı, glif kodlarını Unicode’a geri eşleyen
doğru bir /ToUnicode CMap’ine (ISO 32000-2 §9.10.2) dayanır — motor onu gömülü
yazı tipleri için yayar; böylece çıkarıcılar ham glif dizinleri yerine gerçek
karakterleri geri kazanır. Etiketli PDF ayrıdır: enableTaggedPdf() ve
setLanguage(), okuma sırasını ve erişilebilirliği kaydeden yapı ağacını ekler;
ki bu, /ToUnicode CMap’ini oluşturan şey değildir. İçerik yazmadan önce ikisini
de etkinleştirin: temiz metin geri kazanımı için CMap’i, okuma sırası için
etiketlemeyi. Üretici ayrıntıları için bkz.
Çıkarılabilir metin içeriği üretme.
Sonra metni geri kazanın ve onun üzerinde onaylayın.
Sayfa sayısı ve yapısal gerçekler için, Inspect modülünün Quick derinliğinin,
hiçbir Spectrum yan hizmeti yokken süreç içinde çalışan saf PHP bir yedeği
vardır — bir CI çalıştırıcısında kullanışlıdır ama bozulmuş bir taramadır. Bir
INSPECT-FALLBACK-001 “accuracy may be limited” sorunu işaretler ve sayfa
sayısını, tam bir nesne ağacı ayrıştırmasından değil, ham baytlar üzerindeki kaba
bir /Type /Page regex’inden türetir. Bir Spectrum yan hizmeti yapılandırılmış
olduğunda, Quick derinliği bile onu kullanır — InspectDepth, yan hizmetin ne
kadar analiz yaptığını denetler; bu yüzden Quick doğası gereği yan hizmetsiz
değildir.
<?php
declare(strict_types=1);
use NextPDF\Inspect\Inspector;use NextPDF\Inspect\InspectConfig;
$result = (new Inspector())->inspect($pdfBytes, InspectConfig::quick());
// With no sidecar injected, Quick depth takes the in-process PHP fallback:// a degraded scan (page count from a regex) that flags INSPECT-FALLBACK-001.// If a Spectrum sidecar is available, Inspector uses it even at Quick depth.$pageCount = $result->pageCount; // int (regex-derived in the fallback)$version = $result->pdfVersion; // e.g. "2.0"$encrypted = $result->isEncrypted; // boolInspector::inspect(), değişmez bir InspectResult döndürür. Tam metin geri
kazanımı için, baytlar üzerinde bir alt akış çıkarıcısı (pdftotext ya da
Standard derinliğindeki Inspect Spectrum yan hizmeti) çalıştırın ve çıktısı
üzerinde onaylayın — geri kazanılan metin üzerinde onaylayın, asla üreticinin
tam baytları üzerinde değil.
Altın anlık görüntüler için çıktıyı bayt düzeyinde özdeş kılın
“Altın anlık görüntüler için çıktıyı bayt düzeyinde özdeş kılın” başlıklı bölümBir altın testi yalnızca bir yeniden oluşturma aynı baytları ürettiğinde işe
yarar. PDF’in yerleşik iki belirsizlik kaynağı vardır: tarih alanları
(CreationDate / ModDate) ve fragmandaki dosya tanımlayıcısı (ISO 32000-2
§7.5.5). NextPDF, ikisini de DeterministicSettings aracılığıyla kaldırır; bu,
bir test kıvırması değil, birinci sınıf bir yapılandırma değeridir.
DeterministicSettings, sabit bir DateTimeImmutable ve 32 karakterlik onaltılık
bir fileIdSeed alır. Onu Config’te geçirin, sonra belgenizi o yapılandırmadan
oluşturun. Belirleyimci profil sabitlendiğinde (sabit zaman damgası ve /ID),
aynı girdi aynı sabitlenmiş araç zincirinde çalıştırmalar arasında bayt düzeyinde
özdeş çıktı verir — PHP yaması, uzantı ile sıkıştırma kitaplığı sürümleri ve yazı
tipi dosyaları sabit tutulur. Bunlardan herhangi birinde farklılaşan makineler
arasında baytlar yine de ayrışabilir; orada metin çıkarımı onaylarını tercih edin
ve altın anlık görüntüyü, sabit, sabitlenmiş bir ortam için ayırın.
<?php
declare(strict_types=1);
use DateTimeImmutable;use NextPDF\Core\Config;use NextPDF\Core\Document;use NextPDF\Core\DeterministicSettings;
function buildInvoice(int $invoiceId): string{ $config = new Config( deterministic: new DeterministicSettings( timestamp: new DateTimeImmutable('2026-01-01T00:00:00+00:00'), fileIdSeed: '00000000000000000000000000000000', // exactly 32 hex chars ), );
$document = Document::createStandalone($config); $document->setLanguage('en'); $document->enableTaggedPdf('en'); // structure tree for reading order; /ToUnicode is emitted separately $document->addPage(); $document->setFont('helvetica', '', 12); $document->multiCell(0, 7, "Invoice #{$invoiceId}");
return $document->getPdfData();}fileIdSeed tam olarak 32 onaltılık karakter olmalıdır; yoksa kurucu
InvalidConfigException fırlatır. Zaten bir Config tutuyorsanız, onu yeniden
oluşturmak yerine $config->withDeterministic($settings) ile belirleyimci bir
kopya türetebilirsiniz.
Her iki onay biçimi için bir PHPUnit testi
“Her iki onay biçimi için bir PHPUnit testi” başlıklı bölümBu test sınıfı, aynı oluşturucuya karşı bir anlamsal onayı ve bir altın onayı çalıştırır. Altın dosya bir kez oluşturulur, bir insan tarafından gözden geçirilir ve gönderime alınır; ondan sonra test, her bayt değişikliğinde başarısız olur.
<?php
declare(strict_types=1);
namespace App\Tests\Pdf;
use PHPUnit\Framework\TestCase;
use function App\Pdf\buildInvoice; // the deterministic builder above
final class InvoicePdfTest extends TestCase{ private const GOLDEN = __DIR__ . '/__snapshots__/invoice-42.pdf';
public function testInvoiceTextIsPresent(): void { $pdf = buildInvoice(42);
// Recover text with an external extractor (installed in CI, see below). $text = self::extractText($pdf);
self::assertStringContainsString('Invoice #42', $text); }
public function testInvoiceBytesMatchGolden(): void { $pdf = buildInvoice(42);
// First run: write the golden, then review and commit it by hand. if (! \is_file(self::GOLDEN)) { \file_put_contents(self::GOLDEN, $pdf); self::markTestIncomplete('Golden file created — review and commit it.'); }
self::assertSame( \file_get_contents(self::GOLDEN), $pdf, 'Generated PDF bytes drifted from the committed golden snapshot.', ); }
private static function extractText(string $pdf): string { // tempnam() creates a zero-byte file; track it so the finally block // removes both it and the .pdf path, leaking neither. $tmp = \tempnam(\sys_get_temp_dir(), 'pdf'); $tmpPdf = $tmp . '.pdf'; try { \file_put_contents($tmpPdf, $pdf);
// Run pdftotext via proc_open so we can read the exit code AND // stderr. shell_exec() returns "" on a missing/failed binary, which // would silently turn a broken runner into a passing assertion — // the opposite of a reliable CI test. pdftotext writes UTF-8 to "-" // (stdout). Requires poppler-utils on the runner (see workflow). $descriptors = [ 1 => ['pipe', 'w'], // stdout 2 => ['pipe', 'w'], // stderr ]; $process = \proc_open( ['pdftotext', $tmpPdf, '-'], $descriptors, $pipes, );
if (! \is_resource($process)) { throw new \RuntimeException( 'Could not start pdftotext. Install poppler-utils on the runner.', ); }
$text = \stream_get_contents($pipes[1]); $stderr = \stream_get_contents($pipes[2]); \fclose($pipes[1]); \fclose($pipes[2]); $exitCode = \proc_close($process);
if ($exitCode !== 0) { throw new \RuntimeException(\sprintf( 'pdftotext failed (exit %d): %s. Is poppler-utils installed on the runner?', $exitCode, \trim((string) $stderr) !== '' ? \trim((string) $stderr) : '(no stderr)', )); }
return (string) $text; } finally { // Remove both the original tempnam() file and the .pdf we wrote. @\unlink($tmp); @\unlink($tmpPdf); } }}Bayt onayı yalnızca buildInvoice(), DeterministicSettings’i sabitlediği için
anlamlıdır. Onsuz, tek başına CreationDate her çalıştırmada altın testi
başarısız kılardı.
CI’nın aynı baytları üretmesi için yazı tiplerini sabitleyin
“CI’nın aynı baytları üretmesi için yazı tiplerini sabitleyin” başlıklı bölümBayt düzeyinde özdeş çıktı, her makinede aynı yazı tipi baytlarının alt
kümelenmesine bağlıdır. Çalıştırıcıda iş istasyonunuzdakinden farklı çözümlenen
bir yazı tipi, gömülü alt kümeyi değiştirir ve — DeterministicSettings
sabitlenmiş olsa bile — altın testi bozar.
İki kural yazı tiplerini kararlı tutar:
- Belirli bir yazı tipine ihtiyaç duymadığınız altın testler için Base 14
standart yazı tiplerini (örneğin
helvetica) kullanın. Bunlar özel yazı tipi baytları gömmekten kaçınır — kararlı yerleşik metriklere dayanırlar; ancak tam oluşturulan görünüm yine de görüntüleyicinin yazı tipi değişimine bağlı olabilir. - Sistemler arasında farklılaşan bir sistem yazı tipi yoluna güvenmek yerine,
her özel yazı tipini depoya tedarik edin ve NextPDF’i açıkça ona yönlendirin.
Config(fontsDirectory: ...)ayarlayın ya da gönderime alınmış dizinleaddFontDirectory()çağırın:
<?php
declare(strict_types=1);
use NextPDF\Core\Config;use NextPDF\Core\Document;
$config = new Config(fontsDirectory: __DIR__ . '/fonts'); // committed to the repo$document = Document::createStandalone($config);$document->addFontDirectory(__DIR__ . '/fonts'); // or add it imperatively$document->addPage();$document->setFont('dejavusans', '', 12); // resolved from the repoAltın testler için yazı tiplerini işletim sistemi paket yöneticisinden kurmayın: dağıtım yazı tipi paketleri sürüm ve hinting bakımından farklılaşır; bu yüzden bir çalıştırıcı yükseltmesi baytlarınızı sessizce değiştirir. Tedarik edilmiş bir yazı tipleri dizini o değişkeni kaldırır.
GitHub Actions iş akışı
“GitHub Actions iş akışı” başlıklı bölümBu iş akışı, PHP’yi NextPDF’in ihtiyaç duyduğu uzantılarla kurar, anlamsal
onaylar için bir metin çıkarıcısı kurar ve PHPUnit çalıştırır. php-version: "8.4"
satırı, yamayı değil, PHP minör sürümünü (8.4) sabitler — setup-php onu mevcut
en son 8.4.x sürümüne çözer. Bayt düzeyinde yeniden üretilebilirlik için,
desteklediğiniz somut bir yamayı sabitleyin (örneğin php-version: "8.4.8");
böylece bir çalıştırıcı görüntüsü yükseltmesi, altın anlık görüntülerinizin
altındaki PHP yapısını kaydıramaz.
name: PDF tests
on: [push, pull_request]
jobs: test: runs-on: ubuntu-24.04 steps: - uses: actions/checkout@v4
- name: Set up PHP uses: shivammathur/setup-php@v2 with: php-version: "8.4" extensions: curl, gd, intl, mbstring, openssl, zlib coverage: none
- name: Install text extractor for PDF assertions run: sudo apt-get update && sudo apt-get install -y poppler-utils
- name: Install dependencies run: composer install --no-interaction --no-progress --prefer-dist
- name: Run the test suite run: vendor/bin/phpunit --testsuite=pdfpoppler-utils, metin onayları için pdftotext’i sağlar. Uzantı listesi,
NextPDF Core’un katı biçimde gerektirdiğiyle eşleşir: curl, gd, intl,
mbstring, openssl ve zlib; ağ iletişimini, raster görüntü işlemeyi,
uluslararasılaştırılmış metin ile harmanlamayı, çok baytlı metni, şifreleme/imzalama
için kriptografiyi ve akış sıkıştırmasını kapsar. Hepsini kurun — Core’un
composer.json’u her birini gerektirir; bu yüzden eksik bir uzantı yalnızca tek
bir özelliği değil, composer install’u başarısız kılar. Sonraki bir onay adımı
HTML ya da XML çıktısını ayrıştırırsa, o adım için dom ekleyin; bu bir Core
gereksinimi değildir. Yazı tipleri depoya tedarik edildiği için hiçbir yazı tipi
paketi kurulumu gerekmez — çalıştırıcının baytlarını sizinkilere eşit tutan da
budur.
Uç durumlar ve dikkat edilecek noktalar
“Uç durumlar ve dikkat edilecek noktalar” başlıklı bölüm- Altın testler
DeterministicSettingsgerektirir. Sabitlenmiş bir zaman damgası vefileIdSeedolmadan;CreationDate,ModDateve fragman dosya tanımlayıcısı her çalıştırmada değişir ve bayt onayı asla geçmez. fileIdSeedtam olarak 32 onaltılık karakterdir. Başka herhangi bir uzunluk ya da onaltılık olmayan bir karakter, kuruluştaInvalidConfigExceptionfırlatır.- Yazı tipleri baytların parçasıdır. Çalıştırıcıda farklı bir yazı tipi sürümü, glifleri yeniden alt kümeler ve altın testi başarısız kılar. Yazı tipini tedarik edin ya da Base 14 kullanın.
- Core hiçbir
extractText()göndermez. Onaylar için metin geri kazanımı tüketici işidir:pdftotextya da Inspect Spectrum yan hizmetini kullanın. Üreticinin işi, çıkarıcıların gerçek Unicode’u geri kazanması için doğru bir/ToUnicodeCMap’i yaymaktır (gömülü yazı tipleri için otomatik);enableTaggedPdf(), üzerine yapı ağacını ekler ama CMap’i üreten o değildir. - Inspect Quick derinliğinin, hiçbir yan hizmet yokken süreç içi bir PHP yedeği
vardır (sınırlı doğruluk —
INSPECT-FALLBACK-001işaretler); Standard ve Full her zaman yan hizmeti gerektirir. Yan hizmetsiz CI için, Quick yedeği sayfa sayısını, sürümü ve şifreleme bayrağını verir — sonuçlarını yaklaşık olarak ele alın ve içerik doğruluğu için çıkarılan metne yaslanın. - Altın dosyaları kasıtlı olarak yeniden oluşturun. Bir değişiklik amaçlandığında, anlık görüntüyü silin, taze bir tane yazmak için yeniden çalıştırın ve gönderime almadan önce farkı gözden geçirin. Bir altını CI’da asla otomatik üzerine yazmayın.
Performans
“Performans” başlıklı bölümHer iki onay biçimi de ucuzdur. Bir altın karşılaştırması, bir oluşturma artı bir
dize karşılaştırmasıdır. Anlamsal yol, belge başına süreç dışı bir pdftotext
çağrısı ekler; bunları, metnini gerçekten onayladığınız belgelerle sınırlı tutun.
Inspect Quick PHP yedeği (yan hizmet yok), baytların tek geçişli bir taramasıdır;
bu yüzden bir teste ihmal edilebilir süre ekler; bir yan hizmet yapılandırıldığında,
Quick derinliği bunun yerine bir yan hizmet gidiş-dönüşü yapar.
Güvenlik notları
“Güvenlik notları” başlıklı bölüm- Çıkarılan metni makine tarafından okunabilir olarak ele alın: bir gizli bilginin baytlardan yoksun olduğunu asla bir gizlilik denetimi olarak onaylamayın. Etiketli metin, dosyaya sahip herkes tarafından okunabilir. Gizlilik için şifreleyin.
- Çıkarıcı için geçici dosya yolunu
tempnam()ile oluşturun ve temizleyin; test fikstürlerini öngörülebilir, paylaşılan bir yoldan geçirmeyin. - Bir tedarik zinciri sıçramasının altın baytlarınızı ya da araç zincirinizi
sessizce değiştiremeyeceğinden emin olmak için araç ve eylem sürümlerini
sabitleyin (yalnızca
8.4minörünü değil,8.4.8gibi somut bir PHP yaması; dağıtım üzerindenpoppler-utils; eylem SHA’ları ya da etiketleri).
Uygunluk
“Uygunluk” başlıklı bölümBu kılavuz hiçbir normatif standart iddiasında bulunmaz. Dayandığı belirleyimcilik,
ISO 32000-2’de adı geçen iki belirsiz olmayan alanın — fragman dosya tanımlayıcısı
(/ID, §7.5.5) ve belge bilgisi tarih alanları (CreationDate / ModDate,
fragmandan ayrı bir konum olan belge bilgisi sözlüğünde taşınan) — DeterministicSettings
aracılığıyla kaldırılmasıdır. Metin onayları, motorun gömülü yazı tipleri için
yaydığı /ToUnicode CMap’ine (§9.10.2) dayanır; enableTaggedPdf(), yapı ağacını
ayrıca ekler ve o CMap’i oluşturmaz. Gösterilen her NextPDF çağrısı doğrulanmış
genel API’dir.