API, które odmawia zgadywania
Spec: ISO/IEC 25010 ISO/IEC 25010 Spec: ISO 32000-2 ISO 32000-2 Evidence: Code-backed
W skrócie
Dział zatytułowany „W skrócie”NextPDF wymaga jednoznacznego wskazania intencji. Tam, gdzie intencja zmienia bajty — poziom podpisu, miejsce docelowe wyjścia, cel zgodności — jest wymaganym, jawnym argumentem, a nie czymś, co silnik wnioskuje z kontekstu.
Ta strona pokazuje to podejście bezpośrednio w kodzie źródłowym silnika: sygnatury metod, argumenty nazwane oraz miejsca, w których niejednoznaczne dane wejściowe są odrzucane, zanim powstanie jakikolwiek bajt.
Dlaczego to ważne
Dział zatytułowany „Dlaczego to ważne”Zgadnięcie oznacza decyzję podjętą w imieniu wywołującego, ale bez uprzedzenia. W przypadku pola tekstowego jest to co najwyżej irytujące. W przypadku pliku PDF jest to ukryta wada, ponieważ dostarczany plik bywa artefaktem prawnym lub archiwalnym, którego poprawność ktoś inny sprawdza później za pomocą walidatora.
Rozważmy podpis. Jego skrót jest obliczany na podstawie zadeklarowanego zakresu bajtów, który celowo pomija samą wartość podpisu ( Spec: ISO 32000-2, §12.8 ISO 32000-2 §12.8 ). API, które po cichu „pomaga” — przepisując strukturę, wnioskując poziom, uzupełniając obszar zastępczy — w rzeczywistości nie pomaga. Zmienia bajty, które podpis miał chronić. Zgadnięcie, które w miejscu wywołania wygląda przyjaźnie, kilka tygodni później staje się incydentem produkcyjnym. To ta sama linia kodu.
Wersja skrócona
Dział zatytułowany „Wersja skrócona”- Jeśli wybór zmienia wynik i nie ma bezpiecznej wartości domyślnej, NextPDF czyni go wymaganym argumentem, a nie wnioskowanym.
- Argumenty opcjonalne, które można odczytać niejednoznacznie, są nazwane, więc miejsce wywołania określa intencję (
newLine: true, a nie samotrue). - Dane wejściowe, które mogą być niebezpieczne, są walidowane przed renderowaniem i odrzucane wraz z typowanym wyjątkiem, który nazywa przyczynę.
- Instancja dokumentu jest jednorazowa: zostaje zbudowana, wyemitowana i odrzucona. Nie ma metody
reset(), więc nie ma też zgadywania „czy ten obiekt jest używany ponownie?”. - Silnik nigdy nie emituje wiarygodnie wyglądającego artefaktu zamiast tego, o który poprosiłeś. Zamiast tego odmawia.
Jak podchodzi do tego NextPDF
Dział zatytułowany „Jak podchodzi do tego NextPDF”Mechanizm jest prosty i o to właśnie chodzi. To system typów, argumenty nazwane, typy wyliczeniowe zamiast magicznych łańcuchów znaków oraz niewielka liczba celowych klauzul strażniczych umieszczonych przed emisją danych wyjściowych.
Tabela zestawia kilka niejednoznacznych danych wejściowych. Dla każdego przypadku pokazuje, co wywnioskowałaby biblioteka, która „pomaga”, oraz co zamiast tego robi NextPDF. Każda kolumna NextPDF opisuje zachowanie zacytowane z kodu źródłowego pokazanego w dalszej części tej strony.
| Niejednoznaczne dane wejściowe | Co robi biblioteka, która zgaduje | Co robi NextPDF |
|---|---|---|
Łańcuch znaków orientacji taki jak "portait" | Cofa się do wartości domyślnej i renderuje mimo to | addPage() przyjmuje typ wyliczeniowy Orientation, a nie łańcuch znaków — literówka jest błędem typu, a nie cichą wartością domyślną |
Samo, końcowe true przekazane do cell() | Wybiera tę pozycję wartości logicznej, którą uznaje za zamierzoną | Wartość logiczna jest nazwana w miejscu wywołania (newLine: true); nienazwany literał to zapach kodu, który API eliminuje |
Wrapper php:// lub ścieżka z przejściem po katalogach przekazana do save() | Próbuje mimo wszystko i zapisuje w jakimś miejscu | Odrzucone przed zbudowaniem pliku PDF, wraz z typowanym InvalidConfigException nazywającym klucz, wartość i oczekiwany typ |
setSignature(), a następnie save(), podczas gdy wysokopoziomowy podpisywacz nie jest podłączony | Emituje niepodpisany plik, który wywołujący uważa za podpisany | Zgłasza NotImplementedException przed wyprodukowaniem bajtów, nazywając obsługiwaną ścieżkę |
Ponowne użycie instancji Document do drugiego renderowania | Próbuje odgadnąć, czy stan szczątkowy nadal obowiązuje | Brak reset() i brak ścieżki ponownego użycia — nowa instancja na każde żądanie poprzez DocumentFactory, więc nie ma stanu szczątkowego, który trzeba by zgadywać |
Intencja jest wymaganym argumentem. Główny kontrakt, PdfDocumentInterface, przyjmuje geometrię i wyrównanie jako typowane obiekty wartości oraz typy wyliczeniowe, a nie luźno typowane wartości proste:
public function addPage( ?PageSize $size = null, Orientation $orientation = Orientation::Portrait,): static;
public function cell( float $width, float $height, string $text = '', bool|string $border = false, bool $newLine = false, Alignment $align = Alignment::Left, bool $fill = false,): static;Orientation i Alignment to typy wyliczeniowe, więc w wywołaniu nie można przekazać "portait" i sprawić, że po cichu będzie to oznaczać „wartość domyślną”. Tam, gdzie istnieje wartość domyślna, jest ona bezpieczna (orientacja pionowa, wyrównanie do lewej, brak obramowania), a nie zgadnięciem prawdopodobnej intencji.
Niejednoznaczne wartości logiczne są nazywane w miejscu wywołania. W przykładach, które de facto pełnią rolę dokumentacji referencyjnej API, powtarza się ten sam kształt:
$document->cell(0, 15, 'Hello, NextPDF!', newLine: true);$document->setSignature(certInfo: $certInfo, level: SignatureLevel::PAdES_B_B);$pdf = $document->output(dest: OutputDestination::String);newLine: true jest jednoznaczne. Samo, końcowe true już nie. Poziom podpisu to SignatureLevel::PAdES_B_B, przypadek typu wyliczeniowego — nigdy łańcuch znaków, który silnik musiałby interpretować. Miejsce docelowe wyjścia to OutputDestination::String, więc „daj mi bajty, bez nagłówków HTTP, bez pliku” jest wyrażone wprost. Nie wynika z tego, czy przekazano nazwę pliku.
Niebezpieczne dane wejściowe są odrzucane przed zapisaniem jakiegokolwiek bajtu. save() waliduje ścieżkę docelową przed zbudowaniem pliku PDF:
public function save(string $path): void{ // Reject stream wrappers and null bytes if (\str_contains($path, "\0") || \preg_match('#^[a-zA-Z]+://#', $path)) { throw new InvalidConfigException( configKey: 'output_path', givenValue: $path, expectedType: 'valid_path', ); } // Resolve the parent directory to prevent path traversal $dir = \dirname($path); $realDir = \realpath($dir); if ($realDir === false) { throw new InvalidConfigException( configKey: 'output_path', givenValue: $dir, expectedType: 'existing_directory', ); } // ... only now is the PDF built and written atomically}Silnik nie „stara się jak może” w przypadku wrappera php:// ani ścieżki z przejściem po katalogach. Odmawia, a wyjątek nazywa klucz, wartość oraz to, czego oczekiwano.
Silnik odmawia, zamiast emitować mylący artefakt. Najsilniejszą formą odmowy zgadywania jest całkowita rezygnacja z wytworzenia wyniku, gdy ten wynik byłby nieprawdziwy. Gdy skonfigurowano podpis wysokopoziomowy, ale punkt integracji zapisu, który faktycznie by go złożył, nie jest podłączony, ścieżka budowania zgłasza wyjątek przed wyprodukowaniem bajtów, zamiast emitować niepodpisany plik, który wywołujący uważa za podpisany:
if ($this->padesOrchestrator !== null) { throw new NotImplementedException( feature: 'Document::setSignature()->save()/output()/getPdfData()', followUp: 'The high-level PAdES writer seam is not yet wired ... ' . 'Produce a signed PDF via the direct two-phase ' . 'PadesOrchestrator::signDocument() then finalizeSignature() ' . 'buffer API ...', );}Niepodpisany plik PDF, który wygląda na podpisany, to dokładnie ten rodzaj wiarygodnie wyglądającego, błędnego artefaktu, któremu ta zasada ma zapobiegać. To samo podejście pojawia się w ścieżce obsługi ścisłego CSS. Niezarejestrowane odstępstwo od specyfikacji zgłasza StrictModeViolation już w momencie wykrycia, zamiast renderować przybliżenie i pozostawiać odstępstwo niewykryte.
Jednorazowość usuwa całą klasę zgadnięć. Document jest jednorazowy — zbudowany, wyemitowany i odrzucony. Nie ma reset() ani ścieżki ponownego użycia. Długo działający worker tworzy nową instancję na każde żądanie poprzez DocumentFactory. Silnik nigdy nie musi zgadywać, czy stan szczątkowy z poprzedniego dokumentu nadal ma znaczenie, ponieważ taki stan z założenia nie istnieje.
Co mówią dowody
Dział zatytułowany „Co mówią dowody”Ta strona ma status Evidence: Code-backed : każdy opisany wyżej wzorzec jest zacytowany z samego kodu źródłowego silnika i jego przykładów, a nie sparafrazowany z intencji.
- Typowane sygnatury zawierające typy wyliczeniowe stanowią publiczny kontrakt w
PdfDocumentInterface. Styl wywołania z argumentami nazwanymi to spójna forma we wszystkich kanonicznych przykładach, które de facto pełnią rolę dokumentacji referencyjnej API. - Walidacja ścieżki przed renderowaniem, wraz z typowanym
InvalidConfigException, oraz strażnikNotImplementedExceptionodmawiający przed emisją są cytowane dosłownie ze ścieżki wyjścia fasady dokumentu. - Kotwicą w standardach jest Spec: ISO/IEC 25010, §3.32 ISO/IEC 25010 §3.32 — ochrona przed błędem użytkownika, właściwość jakościowa, którą API odmawiające zgadywania ma spełniać w miejscu wywołania. Drugą kotwicą jest Spec: ISO 32000-2, §12.8 ISO 32000-2 §12.8 , dlatego zgadywanie wokół podpisanego dokumentu nigdy nie jest nieszkodliwe. Skrót obejmuje zadeklarowany zakres bajtów, który pomija wartość podpisu, więc każdy cichy ponowny zapis go unieważnia.
Praktyczny przykład
Dział zatytułowany „Praktyczny przykład”Poniżej znajduje się niewielki, kompletny program. Każda linia, która mogłaby być niejednoznaczna, wskazuje swoją intencję. Niebezpieczne dane wejściowe są odrzucane, zanim wykonana zostanie jakakolwiek praca.
<?php
declare(strict_types=1);
use NextPDF\Contracts\OutputDestination;use NextPDF\Core\Document;use NextPDF\Exception\InvalidConfigException;use NextPDF\ValueObjects\PageSize;use NextPDF\Contracts\Orientation;
$document = Document::createStandalone();$document->setTitle('Quarterly Report');
// Intent is explicit: a typed page size and an Orientation enum case,// not a string the engine has to interpret.$document->addPage(PageSize::a4(), Orientation::Landscape);$document->setFont('helvetica', 'B', 16);
// Ambiguous boolean is named, so the call reads as intent.$document->cell(0, 12, 'Quarterly Report', newLine: true);
try { // Unsafe path is rejected before a byte is built. $document->save('php://output/report.pdf');} catch (InvalidConfigException $e) { // "Invalid configuration for key "output_path": expected valid_path, ..." error_log($e->getMessage());
// The String destination is explicit: bytes only, no HTTP headers, // no file side effect. Nothing is inferred from a missing filename. $bytes = $document->output(dest: OutputDestination::String);}Nie ma ścieżki, w której ten program po cichu robiłby coś niewłaściwego. Określa intencję i kontynuuje albo nazywa problem i zatrzymuje się.
Częste nieporozumienie
Dział zatytułowany „Częste nieporozumienie”Częstym zarzutem jest „to tylko rozwlekłość”. To nie jest rozwlekłość. To brak ukrytych wartości domyślnych. Samo true jest krótsze od newLine: true dokładnie o tyle przejrzystości, ile usuwa. Silnik wymienia kilka znaków w miejscu wywołania na wyeliminowanie całej kategorii błędów — tej, w której kod się kompiluje, działa, tworzy plik i mimo to jest błędny.
Pokrewnym nieporozumieniem jest przekonanie, że fail-fast oznacza „zgłasza dużo wyjątków”. W normalnym użyciu NextPDF nie zgłasza niczego. Prawidłowe dane wejściowe przechodzą bez przeszkód. Strażniki uruchamiają się tylko dla danych wejściowych, które są naprawdę niejednoznaczne lub niebezpieczne — dokładnie tych, o których chcesz wiedzieć natychmiast, a nie tych, które miałyby zostać odgadnięte.
Ograniczenia i granice
Dział zatytułowany „Ograniczenia i granice”Odmowa zgadywania dotyczy intencji i bezpieczeństwa, a nie każdej wygody. NextPDF nadal ma bezpieczne wartości domyślne: orientację pionową, wyrównanie do lewej, brak obramowania. Zasada jest taka, że wartość domyślną oferuje się tylko tam, gdzie jest bezpieczna i nie zaskakuje, i nigdy tam, gdzie błędne wnioskowanie tworzy błędny dokument.
Ta strona demonstruje tę zasadę na podstawowej, publicznej powierzchni API (fasadzie dokumentu, jej kontrakcie oraz ścieżce wyjścia). Podsystemy mają własne punkty wejścia i każdy dokumentuje własne zachowanie walidacji. Wzorce zacytowane tutaj są aktualne na dzień tego przeglądu. Ilustrują podejście; nie są wyczerpującym katalogiem każdego strażnika w silniku.
Opisane mechanizmy fail-fast to strażnicy poprawności i bezpieczeństwa. Same w sobie nie stanowią granicy bezpieczeństwa. Walidacja danych wejściowych to jedna warstwa. Filozofia projektowania oraz dokumentacja bezpieczeństwa opisują szersze podejście.
Powiązana dokumentacja
Dział zatytułowany „Powiązana dokumentacja”- Filozofia projektowania NextPDF — zasada, którą demonstruje ta strona, w kontekście jej priorytetów.
- Błędy jako funkcja — co mają przekazać typowane wyjątki zgłaszane przez tych strażników.
- Ścisłe typy, wszędzie — jak system typów sprawia, że „określ swoją intencję” jest egzekwowalne, a nie jedynie zalecane.
Słownik pojęć
Dział zatytułowany „Słownik pojęć”- Poparte kodem (poziom dowodów) — strona, której twierdzenia są weryfikowane względem samego kodu źródłowego silnika lub uruchamialnego przykładu, na podstawie cytatów, a nie parafraz.
- Fail fast — odrzucanie nieprawidłowych danych wejściowych w najwcześniejszym możliwym punkcie, z jasną przyczyną, zamiast kontynuowania i niejasnego niepowodzenia później.
- Argument nazwany — składnia miejsca wywołania w PHP (
newLine: true), która wiąże wartość z parametrem po nazwie, sprawiając, że w przeciwnym razie niejednoznaczny literał staje się samoopisowy. - Cykl życia jednorazowego użycia — jednorazowy kontrakt
Document: utwórz instancję, wyemituj, zapisz do pliku, odrzuć. Brakreset(), brak ponownego użycia. Workery tworzą nową instancję na każde żądanie poprzezDocumentFactory. - PAdES — PDF Advanced Electronic Signatures, rodzina profili ETSI do podpisywania plików PDF. Rozwijany przy pierwszym użyciu; szczegółowo omówiony na stronach dotyczących podpisywania.