엄격한 타입을, 어디에서나
Spec: ISO 32000-2, §7.5.5 ISO 32000-2 §7.5.5 Evidence: Code-backed PHPStan: Level 10, no src baseline
한눈에 보기
섹션 제목: “한눈에 보기”NextPDF는 억제 베이스라인 없이 엔진 소스 전체에 대해 PHPStan을 Level 10으로 실행합니다. 이 페이지는 “베이스라인 없음”이 도구 설정상의 세부 사항이 아니라 설계상의 결정인 이유, 그리고 그 엄격함이 데이터를 조용히 잘못 다루지 않는 것을 책무로 하는 파이프라인에 실제로 무엇을 가져다주는지 설명합니다.
왜 중요한가
섹션 제목: “왜 중요한가”대부분의 애플리케이션에서 엄격한 타이핑은 코드 위생입니다. PDF 엔진에서는
정확성을 떠받치는 메커니즘에 더 가깝습니다. 이 포맷은 오차를 용납하지
않습니다. 리더는 파일을 끝에서부터 읽어, 트레일러와 상호 참조 테이블을 거쳐
콘텐츠를 찾도록 되어 있으므로, 라이터의 바이트 오프셋은 정확해야 합니다.
조용히 mixed로 넓어지는 타입, 암묵적으로 string이 되어 버리는 int,
확인 없이 역참조되는 nullable을 생각해 보십시오. 이 가운데 어느 것이든, 한
뷰어에서는 멀쩡히 열리지만 다른 뷰어에서는 검증에 실패하는 파일을 만들어 낼
수 있습니다. 그것도 몇 주 뒤에, 원인을 가리키는 스택 추적도 없이 말입니다.
이 영역에서 대가가 큰 실패는 조용한 실패입니다. 엄격한 타이핑과 엄격한 분석기의 조합은, 한 부류의 조용한 런타임 실패를 시끄러운 빌드 시점의 실패로 바꾸는 엔진의 수단입니다.
짧게 말하면
섹션 제목: “짧게 말하면”- 엔진 소스는 가장 엄격한 레벨인 PHPStan Level 10으로 분석되며, 이는
phpstan.neon.dist에서 검증됩니다. - 소스 억제 베이스라인은 존재하지 않습니다. 이 설정은 소스 분석을 에러 0의 상태로 고정합니다. 회귀는 점점 커지는 ignore 파일에 흡수되는 대신 빌드를 실패시킵니다.
- 남아 있는 소수의
ignoreErrors항목은 설정 안에서 좁게, 식별자 및 경로로 범위가 한정되며, 개별적으로 근거가 제시됩니다(패키지 간 소프트 의존 경계, 그리고 리플렉션을 대상으로 하는 테스트 접합부) — 일괄 생성된 베이스라인이 아닙니다. - 별도의 strict 프로파일은
level: max로 실행되며 새로운 ignore 항목을 일절 금지하므로, 새 코드에는 한층 더 엄격한 기준이 적용됩니다. - 의도한 효과는 설계상의 압력입니다. 타입에 정직하게 표현할 수 없는 코드는 통과하지 못하므로, 억제되는 대신 다시 설계됩니다.
NextPDF가 접근하는 방식
섹션 제목: “NextPDF가 접근하는 방식”“우리는 엄격한 분석기를 사용한다”와 “우리는 베이스라인 없는 엄격한 분석기를 사용한다”의 차이가 바로 요점이므로, 정확하게 짚어 둘 가치가 있습니다.
베이스라인은 기존의 모든 위반을 기록하고, 분석기에게 정확히 그것들만 무시하도록 지시합니다. 레거시 코드베이스에 정적 분석을 도입하는 현실적인 방법이지만, 대가가 따릅니다. 베이스라인은 타입 시스템이 보지 않기로 합의한 부채를 조용히 기록하는 장부가 됩니다. 같은 종류의 새 위반이 기존 위반 옆에 슬그머니 끼어들 수 있습니다. 분석기의 약속은 “이 코드는 타입상 깨끗하다”에서 “이 코드는 이전보다 나빠지지는 않았다”로 약해집니다.
NextPDF는 엔진 소스에 대해서는 그 거래를 받아들이지 않습니다. 이 설정은
소스 분석을 에러 0으로 고정하고 reportUnmatchedIgnoredErrors를 켜므로,
더 이상 아무것에도 일치하지 않는 낡은 억제마저 빌드를 실패시킵니다. 남아
있는 좁은 ignore는 특정 에러 식별자와 파일로 범위가 한정됩니다. 각 항목에는
그 경계가 의도적인 이유를 설명하는 인라인 설명이 붙어 있습니다(예를 들어,
Core가 구체적으로 의존하지 않기로 의도한 Pro/Enterprise 인터페이스를
대상으로 프로그래밍하는 경우). 리뷰어는 각 항목을 읽고 판단할 수 있습니다.
놓쳐 버릴 만한 불투명한 목록은 없습니다.
이를 정직하게 유지하는 흐름은 다음과 같습니다.
- 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은 이 흐름의 일부입니다. PHPDoc 어노테이션은
확실한 사실로 취급되므로, @param list<T>나 @return non-empty-string은
분석기가 정중히 무시하는 주석이 아닙니다. 검증되는 약속입니다. 어노테이션과
런타임 타입은 일치하도록 강제됩니다.
증거가 말하는 것
섹션 제목: “증거가 말하는 것”이 페이지는 Evidence: Code-backed 입니다. 설정 자체가 증거입니다.
phpstan.neon.dist는level: 10,phpVersion: 80400을 설정하고src를 분석하며,baseline:키를 두지 않습니다 — 소스 분석을 위한phpstan-baseline.neon은 존재하지 않습니다.- 같은 파일은
treatPhpDocTypesAsCertain: true와reportUnmatchedIgnoredErrors: true를 설정하고, L10 소스 분석이 에러 0에 고정되어 있으며 어떤 회귀든 CI를 실패시켜야 한다는 점을 인라인으로 주석으로 남깁니다. - 남아 있는
ignoreErrors는 각각identifier로, 흔히path로 범위가 한정되며, 소프트 의존과 리플렉션 대상의 근거를 설명하는 주석이 붙어 있습니다 — 일괄 생성된 베이스라인이 아닙니다. phpstan-strict.neon.dist는 그 설정을 상속하고 레벨을max로 끌어올리며 ignore 목록을 동결하므로, strict 프로파일 아래에서는 새 항목을 일절 추가할 수 없습니다.
표준 사양의 관점에서 보면 이야기는 단순합니다. 엔진은 리더가 트레일러와 상호 참조 테이블에서부터 따라갈 수 있는 파일을 생성해야 합니다. 이는 Spec: ISO 32000-2, §7.5.5 ISO 32000-2 §7.5.5 에 따른 것입니다. 정확한 바이트 오프셋은 직렬화의 문제이기 이전에 타입의 문제입니다. 오프셋은 결코 조용히 다른 무언가가 되어서는 안 되는 정수입니다. Level 10에서 타입상 깨끗한 파이프라인은, 산술이 조용히 어긋날 수 있는 경로의 대부분을 이미 제거해 둔 상태입니다.
실용적인 예제
섹션 제목: “실용적인 예제”엄격한 타이핑은 도메인 규칙이 런타임 검사 대신 타입으로 인코딩된 지점에서
가장 잘 드러납니다. 적합성 디스크리미네이터는 사양 수준의 질문에 망라적인
match로 답하므로, 처리되지 않은 케이스는 잘못된 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, }; }}@return 2|3|4|null은 문서용 기술이 아닙니다.
treatPhpDocTypesAsCertain 아래에서 이것은 검증됩니다. 결과가 항상 int라고
가정하는 호출자는, 적합하지 않은 PDF/A 파트 번호가 단 1바이트라도 쓰이기
전에 분석 시점에 그 사실을 통보받습니다.
흔한 오해
섹션 제목: “흔한 오해”함정은 “베이스라인 없음”을 “코드에 마침 위반이 하나도 없다”로 읽는 것입니다. 이는 거꾸로 된 이해입니다. 베이스라인이 없다는 것은 운 좋은 결과가 아니라 원인입니다. 위반을 세워 둘 자리가 어디에도 없기 때문에, 위반을 만들어 낼 코드는 다른 방식으로 작성되어야 합니다. 소스 베이스라인 없는 Level 10은, 사후에 그것을 기술하는 성적표가 아니라 설계를 빚어내는 제약입니다.
두 번째 오해는, 소수의 ignoreErrors 항목이 이름만 바꾼 베이스라인이라는
생각입니다. 그렇지 않습니다. 베이스라인은 일괄 생성되며 불투명합니다. 이
항목들은 개별적으로 작성되고, 식별자로 범위가 한정되며, 설명이 붙어 있고,
reportUnmatchedIgnoredErrors로 보호되므로 눈에 띄지 않게 낡아 갈 수
없습니다.
한계와 경계
섹션 제목: “한계와 경계”이 페이지는 엔진 소스 분석에 관한 것입니다. 테스트 스위트는 의도적으로
구분된 별도의 범위와 설정 아래에서 분석됩니다. 여기서 “베이스라인 없음”은
src/에 대한 진술이지, 리포지토리 내의 모든 보조 분석이 베이스라인 없이
이뤄진다는 주장이 아닙니다. PHPStan이 증명하는 것은 타입의 건전성이지,
동작의 정확성이 아닙니다. 이것은 테스트 피라미드를 대체하는 것이 아니라,
그렇지 않았다면 테스트가 뒤쫓아야 했을 한 부류의 실패를 제거할 뿐입니다.
정확한 레벨, 플래그, ignore 집합은 이 페이지의 검토 일자 기준으로
정확합니다. 권위 있는 출처는 언제나 core 리포지토리의
phpstan.neon.dist와 phpstan-strict.neon.dist입니다.
에디션이 이 규율을 바꾸지는 않습니다. 모든 에디션은 동일한 Level 10 소스에서 빌드됩니다.
| Edition | Availability |
|---|---|
| Core | Core source is analysed at Level 10 with no source baseline. |
| Pro | Pro is built on the same Level 10 source discipline. |
| Enterprise | Enterprise is built on the same Level 10 source discipline. |
관련 문서
섹션 제목: “관련 문서”- PHP 8.4 기반 — 타입 시스템이 의존하는 언어 기능입니다.
- 기능으로서의 오류 — 엄격한 타이핑이 표면화시킨 실패가 어떻게 처리되는지 다룹니다.
- 파이프라인 모델 — 이 규율이 보호하는 아키텍처입니다.
용어집
섹션 제목: “용어집”- PHPStan Level 10 — 가장 엄격한 분석 레벨로, 타입이 없거나 느슨하게 타입이 지정된 값을 경고가 아니라 에러로 취급합니다.
- 베이스라인 — 분석기에게 무시하도록 지시하는, 기존 위반의 생성된 기록입니다. NextPDF는 엔진 소스에 대해 일절 사용하지 않습니다.
treatPhpDocTypesAsCertain— PHPDoc 타입 어노테이션을 조언적 주석이 아니라 검증된 사실로 취급하는 PHPStan 설정입니다.reportUnmatchedIgnoredErrors— ignore 항목이 더 이상 아무것에도 일치하지 않을 때 빌드를 실패시켜 낡은 억제를 방지하는 설정입니다.- 설계상의 압력 — 단지 코드를 측정하기만 하는 검사와 달리, 코드가 특정 방식으로 작성되도록 강제하는 제약의 효과입니다.