추측하지 않는 API
Spec: ISO/IEC 25010 ISO/IEC 25010 Spec: ISO 32000-2 ISO 32000-2 Evidence: Code-backed
한눈에 보기
섹션 제목: “한눈에 보기”NextPDF는 여러분의 의도를 그대로 밝히게 합니다. 의도가 바이트를 바꾸는 곳 — 서명 수준, 출력 대상, 적합성 목표 — 에서는 엔진이 문맥에서 추론하지 않고, 필수적인 명시 인수로 받습니다.
이 페이지는 그 원칙을 엔진 자체의 소스로 보여줍니다. 메서드 시그니처, 명명된 인수, 그리고 모호한 입력이 바이트가 생성되기 전에 거부되는 지점을 살펴봅니다.
이것이 중요한 이유
섹션 제목: “이것이 중요한 이유”추측은 여러분에게 알리지 않은 채 여러분을 대신해 내려진 결정입니다. 텍스트 필드라면 다소 성가신 정도로 끝날 수 있습니다. PDF에서는 잠재적 결함입니다. 여러분이 제공하는 것은 흔히 법적 또는 보존용 산출물이며, 그 정확성은 나중에 다른 사람이 검증기로 확인하기 때문입니다.
서명을 생각해 보십시오. 서명의 다이제스트는 서명 값 자체를 의도적으로 제외하는 선언된 바이트 범위에 대해 계산됩니다( Spec: ISO 32000-2, §12.8 ISO 32000-2 §12.8 ). 구조를 다시 쓰고, 수준을 추론하고, 자리표시자를 채우면서 조용히 “돕는” API는 도운 것이 아닙니다. 그것은 서명이 보호하기로 되어 있던 바이트를 바꾼 것입니다. 호출 지점에서는 친절해 보였던 그 추측이 몇 주 뒤 운영 사고가 됩니다. 두 일은 같은 코드 줄에서 비롯됩니다.
- 어떤 선택이 출력을 바꾸고 안전한 기본값이 없다면, NextPDF는 그것을 추론되는 인수가 아니라 필수 인수로 만듭니다.
- 모호하게 읽힐 수 있는 선택적 인수는 명명되므로, 호출 지점에서 의도를 기술합니다(맨
true가 아니라newLine: true). - 안전하지 않을 수 있는 입력은 렌더링 전에 검증되며, 원인을 명시하는 타입 지정 예외와 함께 거부됩니다.
- 문서 인스턴스는 일회용입니다. 빌드되고, 방출되고, 폐기됩니다.
reset()이 없으므로, “이게 재사용된 건가?”라는 추측도 없습니다. - 엔진은 여러분이 요청한 산출물 대신 그럴듯해 보이는 산출물을 결코 방출하지 않습니다. 대신 거부합니다.
NextPDF가 접근하는 방식
섹션 제목: “NextPDF가 접근하는 방식”그 메커니즘은 화려하지 않습니다. 바로 그 점이 핵심입니다. 타입 시스템, 명명된 인수, 매직 문자열 대신 사용하는 열거형, 그리고 출력 이전에 배치된 몇 가지 의도적인 가드 절이 전부입니다.
이 표는 몇 가지 모호한 입력을 비교합니다. 각 입력에 대해, “돕는” 라이브러리가 무엇을 추론할지와 NextPDF가 대신 무엇을 하는지 보여줍니다. NextPDF 열의 모든 내용은 이 페이지 뒤에 표시된 소스에서 인용한 동작입니다.
| 모호한 입력 | 추측하는 라이브러리가 하는 일 | NextPDF가 하는 일 |
|---|---|---|
"portait" 같은 방향 문자열 | 기본값으로 되돌아가 어쨌든 렌더링함 | addPage()는 문자열이 아니라 Orientation 열거형을 받습니다 — 오타는 조용한 기본값이 아니라 타입 오류가 됩니다 |
cell()에 전달하는 맨 끝의 true | 여러분이 의도했다고 가정하는 불리언 위치를 고름 | 불리언이 호출 지점에서 명명됩니다(newLine: true). 명명되지 않은 리터럴이 바로 이 API가 제거하는 코드 냄새입니다 |
save()에 전달하는 php:// 래퍼 또는 경로 순회 경로 | ”최선을 다해” 어딘가에 씀 | PDF가 빌드되기 이전에 키, 값, 기대 타입을 명시하는 타입 지정 InvalidConfigException과 함께 거부됨 |
고수준 서명자가 연결되지 않은 상태에서 setSignature() 후 save() | 호출자가 서명되었다고 믿는 서명되지 않은 파일을 방출함 | 바이트를 생성하기 전에 NotImplementedException을 던지며, 지원되는 경로를 명시함 |
Document 인스턴스를 두 번째 렌더링에 재사용함 | 잔여 상태가 여전히 적용되는지 추측함 | reset()도 없고 재사용 경로도 없음 — DocumentFactory를 통해 요청마다 새 인스턴스를 만들므로, 추측할 잔여 상태가 없음 |
의도는 필수 인수입니다. 핵심 계약인 PdfDocumentInterface는 기하 정보와 정렬을 느슨한 원시 타입이 아니라 타입 지정된 값 객체와 열거형으로 받습니다:
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과 Alignment는 열거형이므로, 호출자가 "portait"를 전달해도 그것이 조용히 “기본값”을 의미하게 만들 수 없습니다. 기본값이 있는 곳에서도 그것은 여러분이 아마 원했을 것이라는 추측이 아니라 안전한 기본값(세로 방향, 왼쪽, 테두리 없음)입니다.
모호한 불리언은 호출 지점에서 명명됩니다. 사실상의 API 레퍼런스 역할을 하는 예제 전반에 걸쳐, 같은 형태가 반복됩니다:
$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는 명확합니다. 맨 끝의 true는 그렇지 않습니다. 서명 수준은 열거형 케이스인 SignatureLevel::PAdES_B_B이며, 엔진이 해석해야 하는 문자열이 결코 아닙니다. 출력 대상은 OutputDestination::String이므로 “HTTP 헤더나 파일 없이 바이트만 달라”는 의도가 파일 이름 전달 여부에서 추정되지 않고 명시됩니다.
안전하지 않은 입력은 바이트가 하나라도 쓰이기 전에 거부됩니다. save()는 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}엔진은 php:// 래퍼나 경로 순회 경로를 “최선을 다해” 처리하지 않습니다. 거부하고, 예외는 키, 값, 그리고 기대한 조건을 명시합니다.
엔진은 오해의 소지가 있는 산출물을 방출하기보다 거부합니다. 추측을 거부하는 가장 강력한 형태는, 출력이 진실하지 않을 때 출력 생성 자체를 거절하는 것입니다. 고수준 서명이 구성되었으나 실제로 서명할 작성기 연결 지점이 연결되지 않은 경우, 빌드 경로는 호출자가 서명되었다고 믿는 서명되지 않은 파일을 방출하는 대신, 바이트를 생성하기 이전에 예외를 던집니다:
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 ...', );}서명된 것처럼 보이는 서명되지 않은 PDF는 바로 이 원칙이 막으려는, 그럴듯해 보이지만 잘못된 산출물의 정확한 사례입니다. 같은 입장이 엄격 CSS 경로에도 나타납니다. 등록되지 않은 사양 일탈은 근사치를 렌더링하고 일탈을 탐지되지 않은 채 남겨두는 대신, 탐지 시점에 StrictModeViolation을 던집니다.
일회용은 추측의 한 부류 전체를 제거합니다. Document는 일회용입니다 — 빌드되고, 방출되고, 폐기됩니다. reset()도 없고 재사용 경로도 없습니다. 장기 실행 워커는 DocumentFactory를 통해 요청마다 새 인스턴스를 생성합니다. 엔진은 이전 문서의 잔여 상태가 여전히 의미 있는지 결코 추측할 필요가 없습니다. 애초에 그런 상태가 구성상 없기 때문입니다.
증거가 말하는 것
섹션 제목: “증거가 말하는 것”이 페이지는 Evidence: Code-backed 입니다. 위의 모든 형태는 의도를 의역한 것이 아니라 엔진 자체의 소스와 그 예제에서 인용한 것입니다.
- 타입이 지정되고 열거형을 포함한 시그니처는
PdfDocumentInterface의 공개 계약입니다. 명명된 인수 호출 방식은 사실상의 API 레퍼런스 역할을 하는 표준 예제 전반에 걸친 일관된 형태입니다. - 렌더링 전 경로 검증과 타입 지정
InvalidConfigException, 그리고 방출 전 거부NotImplementedException가드는 문서 파사드의 출력 경로에서 그대로 인용한 것입니다. - 표준 기준점은 Spec: ISO/IEC 25010, §3.32 ISO/IEC 25010 §3.32 입니다 — 사용자 오류 보호, 즉 추측을 거부하는 API가 호출 지점에서 충족하기 위해 존재하는 품질 속성입니다. 두 번째 기준점은 Spec: ISO 32000-2, §12.8 ISO 32000-2 §12.8 이며, 이것이 서명된 문서를 둘러싼 추측이 결코 무해하지 않은 이유입니다. 다이제스트는 서명 값을 제외하는 선언된 바이트 범위를 대상으로 하므로, 어떤 조용한 재작성이라도 그것을 무효화합니다.
실용 예제
섹션 제목: “실용 예제”작고 완결된 프로그램입니다. 모호할 수 있는 모든 줄이 그 의도를 기술합니다. 유일하게 안전하지 않은 입력은 어떤 작업이 수행되기 전에 거부됩니다.
<?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);}이 프로그램에는 조용히 잘못된 일을 하는 경로가 없습니다. 의도를 기술하고 진행하거나, 문제를 명시하고 멈추거나 둘 중 하나입니다.
흔한 오해
섹션 제목: “흔한 오해”자주 나오는 반론은 “이건 그저 장황함일 뿐”이라는 것입니다. 그것은 장황함이 아닙니다. 숨겨진 기본값이 없다는 뜻입니다. 맨 true는 newLine: true보다 짧지만, 정확히 그 명료함을 제거한 만큼만 짧습니다. 엔진은 호출 지점에서의 몇 글자를 한 부류의 버그 — 코드가 컴파일되고, 실행되고, 파일을 생성하지만 잘못되는 그 버그 — 의 제거와 맞바꿉니다.
관련된 오해는 빠른 실패가 “예외를 많이 던진다”는 뜻이라는 것입니다. 정상 사용에서 NextPDF는 아무것도 던지지 않습니다. 유효한 입력은 그대로 통과합니다. 가드는 진정으로 모호하거나 안전하지 않은 입력 — 여러분이 추측되기를 바라기보다 즉시 알기를 바라는 바로 그 입력 — 에서만 작동합니다.
한계와 경계
섹션 제목: “한계와 경계”추측 거부는 모든 편의 기능이 아니라 의도와 안전에 적용됩니다. NextPDF에는 여전히 안전한 기본값이 있습니다. 세로 방향, 왼쪽 정렬, 테두리 없음입니다. 이 원칙은 기본값이 안전하고 놀랍지 않은 곳에서만 제공되며, 잘못된 추론이 잘못된 문서를 만드는 곳에서는 결코 제공되지 않는다는 것입니다.
이 페이지는 핵심 공개 API 표면(문서 파사드, 그 계약, 그리고 출력 경로)에서 이 원칙을 보여줍니다. 하위 시스템은 각자의 진입점을 가지며, 각각 자체의 검증 동작을 문서화합니다. 여기에 인용된 형태는 이 검토 시점 기준으로 현행입니다. 이들은 패턴을 예시할 뿐이며, 엔진의 모든 가드를 망라하는 목록은 아닙니다.
설명된 빠른 실패 가드는 정확성 및 안전 가드입니다. 그 자체로 보안 경계는 아닙니다. 입력 검증은 한 계층입니다. 설계 철학과 보안 문서가 더 넓은 관점을 설명합니다.
관련 문서
섹션 제목: “관련 문서”- NextPDF 설계 철학 — 이 페이지가 시연하는 원칙을, 우선순위의 맥락에서 다룹니다.
- 기능으로서의 오류 — 이 가드가 던지는 타입 지정 예외가 여러분에게 무엇을 알려 주도록 설계되었는지 다룹니다.
- 모든 곳에서의 엄격 타입 — 타입 시스템이 “여러분의 의도를 기술하라”를 권고가 아니라 강제 가능한 것으로 만드는 방식을 다룹니다.
용어집
섹션 제목: “용어집”- 코드 기반(증거 수준) — 주장이 의역이 아니라 인용으로 제시되어, 엔진 자체의 소스나 실행 가능한 예제에 대해 확인된 페이지.
- 빠른 실패 — 유효하지 않은 입력을, 진행하다가 나중에 모호하게 실패하는 대신, 가장 이른 지점에서 명확한 원인과 함께 거부하는 것.
- 명명된 인수 — 값을 이름으로 매개변수에 바인딩하여, 그렇지 않으면 모호한 리터럴을 자기 설명적으로 만드는 PHP 호출 지점 구문(
newLine: true). - 일회용 수명 주기 — 일회용
Document계약: 인스턴스화, 쓰기, 저장, 폐기.reset()도 없고, 재사용도 없습니다. 워커는DocumentFactory를 통해 요청마다 새 인스턴스를 생성합니다. - PAdES — PDF Advanced Electronic Signatures, PDF 서명을 위한 ETSI 프로파일 계열. 처음 등장할 때 풀어 설명되며, 서명 페이지에서 심층적으로 다룹니다.