Ścisłe typy wszędzie
Spec: ISO 32000-2, §7.5.5 ISO 32000-2 §7.5.5 Evidence: Code-backed PHPStan: Level 10, no src baseline
W skrócie
Dział zatytułowany „W skrócie”NextPDF uruchamia PHPStan na poziomie 10 dla źródła silnika, bez bazowego pliku wyłączeń. Ta strona wyjaśnia, dlaczego „brak bazowego pliku wyłączeń” jest decyzją projektową, a nie szczegółem narzędziowym, oraz co ta restrykcyjność rzeczywiście daje potokowi, którego zadaniem jest zapobieganie cichej, błędnej obsłudze danych.
Dlaczego to ma znaczenie
Dział zatytułowany „Dlaczego to ma znaczenie”W większości aplikacji ścisłe typowanie jest kwestią higieny. W silniku PDF pełni raczej rolę mechanizmu poprawności. Format nie wybacza błędów. Czytnik ma odnaleźć treść, odczytując plik od końca, przez trailer i tablicę odsyłaczy, więc przesunięcia bajtowe po stronie zapisu muszą być dokładne. Weźmy typ, który po cichu rozszerza się do mixed, wartość int, która po cichu staje się string, albo wartość dopuszczającą null, która jest dereferencjonowana bez sprawdzenia. Każdy z tych przypadków może doprowadzić do powstania pliku, który otwiera się bez problemu w jednej przeglądarce, a w innej nie przechodzi walidacji — po tygodniach i bez śladu stosu wskazującego na przyczynę.
W tej dziedzinie najbardziej kosztowne są ciche awarie. Ścisłe typowanie wraz z restrykcyjnym analizatorem to sposób, w jaki silnik przekształca pewną klasę cichych awarii w czasie wykonania w głośne awarie w czasie kompilacji.
W skrócie najważniejsze
Dział zatytułowany „W skrócie najważniejsze”- Źródło silnika jest analizowane na poziomie 10 PHPStan — najbardziej restrykcyjnym poziomie — co jest zweryfikowane w
phpstan.neon.dist. - Nie istnieje żaden bazowy plik wyłączeń dla źródła. Konfiguracja wymusza analizę źródła z zerową liczbą błędów. Regresja powoduje niepowodzenie kompilacji, zamiast zostać wchłonięta przez rosnący plik wyłączeń.
- Nieliczne istniejące wpisy
ignoreErrorssą wąskie, ograniczone do konkretnego identyfikatora i ścieżki oraz uzasadnione indywidualnie w konfiguracji (granice miękkich zależności między pakietami oraz szwy testowe na potrzeby refleksji) — a nie zbiorczym bazowym plikiem wyłączeń. - Osobny profil restrykcyjny uruchamia
level: maxi zabrania dodawania jakichkolwiek nowych wpisów wyłączeń, więc nowy kod jest utrzymywany według jeszcze ostrzejszego standardu. - Zamierzonym efektem jest presja projektowa: kod, którego nie da się uczciwie wyrazić w systemie typów, nie przechodzi, więc zostaje przeprojektowany zamiast wyciszony.
Jak podchodzi do tego NextPDF
Dział zatytułowany „Jak podchodzi do tego NextPDF”Różnica między „używamy restrykcyjnego analizatora” a „używamy restrykcyjnego analizatora bez bazowego pliku wyłączeń” jest sednem sprawy, więc warto być precyzyjnym.
Każdy bazowy plik wyłączeń rejestruje każde istniejące naruszenie i każe analizatorowi ignorować dokładnie te przypadki. To pragmatyczny sposób na wprowadzenie analizy statycznej w starszej bazie kodu, ale ma swoją cenę. Bazowy plik wyłączeń staje się cichą księgą długu, którego system typów zgodził się nie widzieć. Nowe naruszenia tego samego rodzaju mogą prześlizgnąć się obok już istniejących. Obietnica analizatora słabnie z „ten kod jest czysty pod względem typów” do „ten kod nie jest gorszy niż wcześniej”.
NextPDF nie idzie na ten kompromis w przypadku źródła silnika. Konfiguracja wymusza analizę źródła z zerową liczbą błędów i włącza reportUnmatchedIgnoredErrors, więc nawet nieaktualne wyłączenie — takie, które już niczego nie dopasowuje — powoduje niepowodzenie kompilacji. Pozostałe wąskie wyłączenia są ograniczone do konkretnego identyfikatora błędu i pliku. Każde z nich zawiera wbudowane wyjaśnienie, dlaczego dana granica jest celowa (na przykład core programowane względem interfejsu Pro/Enterprise, od którego świadomie nie zależy w sposób konkretny). Recenzent może przeczytać każde z nich i ocenić. Nie ma nieprzejrzystej listy, nad którą można by stracić kontrolę.
Przepływ, który utrzymuje to w ryzach:
- Change proposed New or modified engine code.
- Level 10 analysis Strictest PHPStan level over src/, treatPhpDocTypesAsCertain on.
- Zero-error gate No source baseline; unmatched ignores also fail.
- Strict profile level: max; no new ignore entries permitted.
- Redesign, not suppress If it cannot be expressed honestly, the design changes.
treatPhpDocTypesAsCertain jest częścią tego mechanizmu. Adnotacje PHPDoc są traktowane jako stan faktyczny, więc @param list<T> albo @return non-empty-string nie jest komentarzem, który analizator po prostu ignoruje. To zweryfikowana obietnica. Adnotacja i typ w czasie wykonania muszą być zgodne.
Co mówią dowody
Dział zatytułowany „Co mówią dowody”Ta strona jest Evidence: Code-backed . Dowodem jest sama konfiguracja:
phpstan.neon.distustawialevel: 10,phpVersion: 80400, analizujesrci nie zawiera kluczabaseline:— dla analizy źródła nie istniejephpstan-baseline.neon.- Ten sam plik ustawia
treatPhpDocTypesAsCertain: trueorazreportUnmatchedIgnoredErrors: true, z wbudowaną adnotacją, że analiza źródła na L10 jest zablokowana na zerową liczbę błędów, a każda regresja musi powodować niepowodzenie CI. - Pozostałe
ignoreErrorssą ograniczone przezidentifier, a często takżepath, z komentarzami wyjaśniającymi uzasadnienie miękkiej zależności oraz użycia refleksji — nie są zbiorczo wygenerowanym bazowym plikiem wyłączeń. phpstan-strict.neon.distdziedziczy tę konfigurację, podnosi poziom domaxi zamraża listę wyłączeń, tak aby w profilu restrykcyjnym nie można było dodać żadnego nowego wpisu.
Związek ze standardami jest bezpośredni. Silnik musi wytwarzać pliki, które czytnik może przetwarzać, zaczynając od trailera i tablicy odsyłaczy, zgodnie z Spec: ISO 32000-2, §7.5.5 ISO 32000-2 §7.5.5 . Dokładne przesunięcia bajtowe są problemem typów, zanim staną się problemem serializacji. Przesunięcie to liczba całkowita, która nigdy nie może po cichu stać się czymkolwiek innym. Potok, który jest czysty pod względem typów na poziomie 10, usunął już większość scenariuszy, w których arytmetyka może po cichu zawieść.
Przykład praktyczny
Dział zatytułowany „Przykład praktyczny”Ścisłe typowanie jest najbardziej widoczne tam, gdzie reguła dziedzinowa jest zakodowana jako typ, a nie jako sprawdzenie w czasie wykonania. Dyskryminator zgodności rozstrzyga kwestie na poziomie specyfikacji za pomocą wyczerpującego match, więc nieobsłużony przypadek jest błędem typu, a nie błędnym plikiem PDF:
declare(strict_types=1);
enum ConformanceMode: string{ case Plain = 'plain'; case PdfUa2 = 'pdfua2'; case PdfA4 = 'pdfa4';
/** @return 2|3|4|null */ public function pdfaPart(): ?int { return match ($this) { self::PdfA4 => 4, default => null, }; }}Adnotacja @return 2|3|4|null nie jest samą dokumentacją. Przy
treatPhpDocTypesAsCertain podlega sprawdzeniu. Wywołujący, który zakłada, że wynik jest zawsze typu int, dowiaduje się o tym w czasie analizy, zanim zostanie zapisany choćby jeden bajt niezgodnego numeru części PDF/A.
Częste nieporozumienie
Dział zatytułowany „Częste nieporozumienie”Pułapka polega na odczytywaniu „braku bazowego pliku wyłączeń” jako „kod akurat nie ma naruszeń”. To odwrócone rozumowanie. Brak bazowego pliku wyłączeń jest przyczyną, a nie szczęśliwym rezultatem. Ponieważ nie ma gdzie odłożyć naruszenia, kod, który by je powodował, musi zostać napisany inaczej. Poziom 10 bez bazowego pliku wyłączeń dla źródła to ograniczenie kształtujące projekt, a nie świadectwo opisujące go po fakcie.
Drugie nieporozumienie polega na tym, że garstkę wpisów ignoreErrors uznaje się za bazowy plik wyłączeń pod inną nazwą. Nie są nim. Bazowy plik wyłączeń jest generowany zbiorczo i nieprzejrzysty. Te są pisane indywidualnie, ograniczone do konkretnego identyfikatora, wyjaśnione i zabezpieczone przez reportUnmatchedIgnoredErrors, więc nie mogą zdezaktualizować się niezauważenie.
Ograniczenia i granice
Dział zatytułowany „Ograniczenia i granice”Ta strona dotyczy analizy źródła silnika. Zestaw testów jest analizowany w osobnym, celowo wydzielonym zakresie i konfiguracji; „brak bazowego pliku wyłączeń” odnosi się tutaj do src/, a nie oznacza, że każda pomocnicza analiza w repozytorium jest pozbawiona bazowego pliku wyłączeń. PHPStan dowodzi poprawności typów, a nie poprawności zachowania. Nie zastępuje piramidy testów, a jedynie usuwa kategorię awarii, którą testy musiałyby w przeciwnym razie tropić. Dokładny poziom, flagi i zestaw wyłączeń są aktualne na dzień przeglądu tej strony. Miarodajnym źródłem są zawsze phpstan.neon.dist i phpstan-strict.neon.dist w repozytorium core.
Edycja produktu nie zmienia tej dyscypliny. Każda edycja jest budowana z tego samego źródła na poziomie 10:
| Edition | Availability |
|---|---|
| Core | Źródło Core jest analizowane na poziomie 10 bez bazowego pliku wyłączeń dla źródła. |
| Pro | Pro jest zbudowane na tej samej dyscyplinie źródła na poziomie 10. |
| Enterprise | Enterprise jest zbudowane na tej samej dyscyplinie źródła na poziomie 10. |
Powiązana dokumentacja
Dział zatytułowany „Powiązana dokumentacja”- Podstawy PHP 8.4 — funkcje języka, na których opiera się system typów.
- Błędy jako funkcja — co dzieje się z awariami, które ujawnia ścisłe typowanie.
- Model potoku — architektura, którą chroni ta dyscyplina.
Słownik pojęć
Dział zatytułowany „Słownik pojęć”- Poziom 10 PHPStan — najbardziej restrykcyjny poziom analizy, traktujący wartości nietypowane i luźno typowane jako błędy, a nie ostrzeżenia.
- Bazowy plik wyłączeń (baseline) — wygenerowany rejestr istniejących naruszeń, które analizator ma ignorować. NextPDF nie używa żadnego dla źródła silnika.
treatPhpDocTypesAsCertain— ustawienie PHPStan, które traktuje adnotacje typów PHPDoc jako sprawdzane fakty, a nie komentarze o charakterze sugestii.reportUnmatchedIgnoredErrors— ustawienie, które powoduje niepowodzenie kompilacji, gdy wpis wyłączenia nie dopasowuje już żadnego błędu, dzięki czemu wyłączenia nie dezaktualizują się po cichu.- Presja projektowa — efekt ograniczenia, które wymusza pisanie kodu w określony sposób, w przeciwieństwie do sprawdzenia, które jedynie go mierzy.