Một API từ chối phỏng đoán
Spec: ISO/IEC 25010 ISO/IEC 25010 Spec: ISO 32000-2 ISO 32000-2 Evidence: Code-backed
Tổng quan nhanh
Phần tiêu đề “Tổng quan nhanh”NextPDF buộc bạn nói rõ chính xác điều mình muốn. Khi ý định có thể làm thay đổi các byte — mức chữ ký, đích xuất, mục tiêu tuân thủ — nó phải là một đối số bắt buộc và tường minh, chứ không phải thứ engine suy ra từ ngữ cảnh.
Trang này chỉ ra quan điểm đó ngay trong mã nguồn của engine: chữ ký phương thức, đối số có tên, và các điểm mà đầu vào không rõ ràng bị từ chối trước khi bất kỳ byte nào được tạo ra.
Vì sao điều này quan trọng
Phần tiêu đề “Vì sao điều này quan trọng”Phỏng đoán là một quyết định được đưa ra thay bạn mà bạn không hề được báo trước. Với một trường văn bản, điều đó chỉ hơi khó chịu. Với một PDF, đó là một lỗi tiềm ẩn, bởi tài liệu bạn bàn giao thường là tài liệu pháp lý hoặc lưu trữ mà tính đúng đắn của nó sẽ được người khác kiểm tra sau bằng một trình kiểm định.
Hãy xét một chữ ký. Bản tóm lược (digest) của nó được tính trên một dải byte đã khai báo, cố ý loại trừ chính giá trị chữ ký ( Spec: ISO 32000-2, §12.8 ISO 32000-2 §12.8 ). Một API âm thầm “giúp đỡ” — viết lại cấu trúc, suy ra một mức, chèn đệm vào một chỗ giữ chỗ — không thật sự giúp ích. Nó đã thay đổi chính những byte mà một chữ ký lẽ ra phải bảo vệ. Phỏng đoán trông thân thiện tại điểm gọi sẽ trở thành sự cố trên môi trường sản xuất vài tuần sau đó. Vẫn là cùng một dòng mã.
Tóm tắt ngắn gọn
Phần tiêu đề “Tóm tắt ngắn gọn”- Nếu một lựa chọn làm thay đổi đầu ra và không có giá trị mặc định an toàn, NextPDF biến nó thành đối số bắt buộc, chứ không phải một đối số được suy ra.
- Các đối số tùy chọn khi đọc dễ gây mơ hồ thì được đặt tên, để điểm gọi nêu rõ ý định (
newLine: true, chứ không phải mộttruetrơ trọi). - Những đầu vào có thể không an toàn thì được kiểm tra trước khi kết xuất, và bị từ chối bằng một ngoại lệ có kiểu, nêu rõ nguyên nhân.
- Một thực thể tài liệu là dùng-một-lần: nó được dựng, xuất ra, rồi bỏ đi. Không có
reset(), nên cũng không cần phỏng đoán xem “liệu thứ này có bị dùng lại không?”. - Engine không bao giờ xuất ra một tài liệu trông có vẻ hợp lý để thay cho tài liệu mà bạn đã yêu cầu. Thay vào đó, nó từ chối.
Cách NextPDF tiếp cận vấn đề
Phần tiêu đề “Cách NextPDF tiếp cận vấn đề”Cơ chế này rất đơn giản, và đó chính là điều cốt lõi. Nó dựa vào hệ thống kiểu, đối số có tên, enum thay cho chuỗi mơ hồ, và một số ít mệnh đề kiểm tra được đặt có chủ đích trước khi xuất.
Bảng dưới đây đối chiếu một vài đầu vào không rõ ràng. Với mỗi trường hợp, bảng cho thấy một thư viện “giúp đỡ” sẽ suy ra điều gì, và NextPDF làm gì thay vào đó. Mỗi ô trong cột NextPDF là một hành vi được trích dẫn từ mã nguồn ở phần sau của trang này.
| Đầu vào không rõ ràng | Một thư viện phỏng đoán sẽ làm gì | NextPDF làm gì |
|---|---|---|
Một chuỗi hướng trang như "portait" | Rơi về một giá trị mặc định rồi vẫn kết xuất | addPage() nhận enum Orientation chứ không phải một chuỗi — gõ sai là lỗi kiểu, chứ không phải một mặc định âm thầm |
Một true trơ trọi đặt ở cuối cell() | Chọn một vị trí boolean nào đó mà nó đoán là bạn muốn | Giá trị boolean được đặt tên tại điểm gọi (newLine: true); một literal không tên chính là dấu hiệu xấu mà API này loại bỏ |
Một wrapper php:// hoặc đường dẫn duyệt ngược truyền vào save() | ”Cố hết sức” rồi ghi ra một nơi nào đó | Bị từ chối trước khi PDF được dựng, bằng một InvalidConfigException có kiểu nêu rõ khóa, giá trị và kiểu dữ liệu mong đợi |
setSignature() rồi save() trong khi trình ký cấp cao chưa được kết nối | Xuất ra một tệp chưa ký mà bên gọi lại tin là đã được ký | Ném NotImplementedException trước khi tạo ra byte nào, nêu rõ lối đi được hỗ trợ |
Dùng lại một thực thể Document cho một lần kết xuất thứ hai | Phỏng đoán xem trạng thái còn sót lại có còn áp dụng hay không | Không có reset() và không có cách dùng lại — mỗi yêu cầu có một thực thể mới thông qua DocumentFactory, nên không có trạng thái còn sót lại nào để phải phỏng đoán |
Ý định là một đối số bắt buộc. Hợp đồng cốt lõi, PdfDocumentInterface, nhận hình học và canh lề dưới dạng value object có kiểu và enum, chứ không phải các kiểu nguyên thủy lỏng lẻo:
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 và Alignment là các enum, nên lời gọi không thể truyền "portait" rồi để nó âm thầm hiểu thành “mặc định”. Ở những nơi có giá trị mặc định, đó là một mặc định an toàn (dọc, canh trái, không viền), chứ không phải một phỏng đoán về điều bạn có thể đã muốn.
Các boolean mơ hồ được đặt tên tại điểm gọi. Trong các ví dụ thực tế đóng vai trò như tài liệu tham khảo API, cùng một khuôn dạng lặp đi lặp lại:
$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 thì không thể nhầm lẫn. Một true trơ trọi ở cuối thì không được như vậy. Mức chữ ký là SignatureLevel::PAdES_B_B, một trường hợp enum — không bao giờ là một chuỗi mà engine phải diễn giải. Đích xuất là OutputDestination::String, nên ý “trả về các byte cho tôi, không header HTTP, không ghi tệp” được nêu rõ. Nó không được suy ra từ việc có truyền tên tệp hay không.
Đầu vào không an toàn bị từ chối trước khi một byte nào được ghi. save() kiểm tra đường dẫn đích trước khi dựng 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}Engine không “cố hết sức” với một wrapper php:// hay một đường dẫn duyệt ngược. Nó từ chối, và ngoại lệ nêu rõ khóa, giá trị cùng điều được mong đợi.
Engine thà từ chối còn hơn xuất ra một tài liệu gây hiểu lầm. Hình thức mạnh nhất của việc từ chối phỏng đoán là không tạo ra đầu ra nào cả khi đầu ra đó sẽ không trung thực. Khi một chữ ký cấp cao đã được cấu hình nhưng điểm nối tới trình ghi chịu trách nhiệm ký thật sự chưa được kết nối, quy trình dựng sẽ ném lỗi trước khi tạo ra byte nào, thay vì xuất ra một tệp chưa ký mà bên gọi lại tin là đã được ký:
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 ...', );}Một PDF chưa ký mà trông như đã ký chính là loại tài liệu sai nhưng trông có vẻ hợp lý mà nguyên tắc này sinh ra để ngăn chặn. Quan điểm tương tự xuất hiện ở đường xử lý CSS nghiêm ngặt. Một sai lệch khỏi đặc tả mà chưa được đăng ký sẽ ném ra một StrictModeViolation ngay tại điểm phát hiện, thay vì kết xuất một bản gần đúng và để sai lệch đó không bị phát hiện.
Dùng-một-lần loại bỏ cả một lớp phỏng đoán. Một Document là dùng-rồi-bỏ — được dựng, xuất ra, rồi bỏ đi. Không có reset() và không có cách dùng lại. Một worker chạy lâu dài tạo ra một thực thể mới cho mỗi yêu cầu thông qua DocumentFactory. Engine không bao giờ phải phỏng đoán xem trạng thái còn sót lại từ một tài liệu trước đó có còn ý nghĩa hay không, vì theo thiết kế, trạng thái như vậy không tồn tại.
Bằng chứng nói gì
Phần tiêu đề “Bằng chứng nói gì”Trang này là Evidence: Code-backed : mọi khuôn mẫu phía trên đều được trích dẫn từ chính mã nguồn và các ví dụ của engine, chứ không phải được diễn giải lại từ ý định.
- Các chữ ký phương thức có kiểu và có enum chính là hợp đồng công khai trong
PdfDocumentInterface. Phong cách gọi bằng đối số có tên là khuôn mẫu nhất quán xuyên suốt các ví dụ chuẩn vốn đóng vai trò là tài liệu tham khảo API trên thực tế. - Phần kiểm tra đường dẫn trước khi kết xuất, với
InvalidConfigExceptioncó kiểu của nó, và bộ kiểm tra từ-chối-trước-khi-xuấtNotImplementedExceptionđược trích dẫn nguyên văn từ đường xuất của facade tài liệu. - Mốc tiêu chuẩn là Spec: ISO/IEC 25010, §3.32 ISO/IEC 25010 §3.32 — bảo vệ chống lỗi người dùng, thuộc tính chất lượng mà một API từ chối phỏng đoán sinh ra để đáp ứng ngay tại điểm gọi. Mốc thứ hai là Spec: ISO 32000-2, §12.8 ISO 32000-2 §12.8 , đó là lý do vì sao việc phỏng đoán xoay quanh một tài liệu đã ký không bao giờ là vô hại. Bản tóm lược (digest) bao trùm một dải byte đã khai báo, loại trừ giá trị chữ ký, nên bất kỳ thay đổi âm thầm nào cũng làm nó mất hiệu lực.
Ví dụ thực tế
Phần tiêu đề “Ví dụ thực tế”Dưới đây là một chương trình nhỏ, hoàn chỉnh. Mọi dòng có khả năng gây mơ hồ đều nêu rõ ý định. Một đầu vào không an toàn duy nhất bị từ chối trước khi bất kỳ công việc nào được thực hiện.
<?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);}Không có đường chạy nào mà chương trình này âm thầm làm sai. Nó nêu rõ ý định rồi tiếp tục, hoặc nêu rõ vấn đề rồi dừng lại.
Quan niệm sai thường gặp
Phần tiêu đề “Quan niệm sai thường gặp”Lời phản bác thường gặp là “đây chỉ là dài dòng”. Đó không phải là dài dòng. Đó là sự vắng mặt của những giá trị mặc định ẩn. Một true trơ trọi ngắn hơn newLine: true đúng bằng phần rõ ràng mà nó lược bỏ. Engine đánh đổi vài ký tự tại điểm gọi để loại bỏ hẳn một loại lỗi — loại mà mã vẫn biên dịch, vẫn chạy, vẫn tạo ra một tệp, nhưng lại sai.
Một quan niệm sai liên quan là cho rằng báo lỗi sớm nghĩa là “ném lỗi rất nhiều”. Trong sử dụng thông thường, NextPDF không ném lỗi nào cả. Đầu vào hợp lệ trôi qua suôn sẻ. Các bộ kiểm tra chỉ kích hoạt với những đầu vào thực sự mơ hồ hoặc không an toàn — chính là những đầu vào bạn muốn biết ngay lập tức, chứ không phải những đầu vào bạn muốn được phỏng đoán.
Giới hạn và ranh giới
Phần tiêu đề “Giới hạn và ranh giới”Việc từ chối phỏng đoán áp dụng cho ý định và an toàn, chứ không phải mọi tiện ích. NextPDF vẫn có các giá trị mặc định an toàn: hướng dọc, canh trái, không viền. Nguyên tắc là một giá trị mặc định chỉ được cung cấp ở nơi nó an toàn và không gây bất ngờ, và không bao giờ ở nơi mà suy đoán sai sẽ tạo ra một tài liệu sai.
Trang này minh họa nguyên tắc đó trên bề mặt API công khai cốt lõi (facade tài liệu, hợp đồng của nó, và đường xuất). Các hệ thống con có điểm vào riêng, và mỗi hệ thống có tài liệu riêng cho hành vi kiểm tra của chính nó. Các khuôn dạng được trích dẫn ở đây là hiện hành tính đến lần rà soát này. Chúng minh họa cho khuôn mẫu; chúng không phải là một danh mục đầy đủ của mọi bộ kiểm tra trong engine.
Các bộ kiểm tra báo lỗi sớm được mô tả ở đây là các bộ kiểm tra về tính đúng đắn và an toàn. Bản thân chúng không phải là một ranh giới bảo mật. Kiểm tra đầu vào là một lớp. Phần triết lý thiết kế và tài liệu bảo mật mô tả quan điểm rộng hơn.
Tài liệu liên quan
Phần tiêu đề “Tài liệu liên quan”- Triết lý thiết kế của NextPDF — nguyên tắc mà trang này minh họa, đặt trong bối cảnh các ưu tiên của nó.
- Lỗi như một tính năng — những ngoại lệ có kiểu do các bộ kiểm tra này ném ra được thiết kế để cho bạn biết điều gì.
- Kiểu nghiêm ngặt, ở khắp nơi — cách hệ thống kiểu biến “nêu rõ ý định của bạn” thành điều bắt buộc thay vì chỉ là khuyến nghị.
Thuật ngữ
Phần tiêu đề “Thuật ngữ”- Có mã làm bằng chứng (mức bằng chứng) — một trang có các khẳng định được đối chiếu với chính mã nguồn của engine hoặc một ví dụ chạy được, được trích dẫn thay vì diễn giải lại.
- Báo lỗi sớm — từ chối một đầu vào không hợp lệ ngay ở điểm sớm nhất, với một nguyên nhân rõ ràng, thay vì tiếp tục rồi thất bại một cách khó hiểu về sau.
- Đối số có tên — một cú pháp tại điểm gọi của PHP (
newLine: true) gắn một giá trị vào một tham số theo tên, khiến một literal vốn mơ hồ trở nên tự giải nghĩa. - Vòng đời dùng-một-lần — hợp đồng
Documentdùng-rồi-bỏ: khởi tạo, ghi, lưu, bỏ đi. Khôngreset(), không dùng lại. Worker tạo ra một thực thể mới cho mỗi yêu cầu thông quaDocumentFactory. - PAdES — PDF Advanced Electronic Signatures, họ hồ sơ ETSI dành cho việc ký PDF. Được viết đầy đủ ở lần dùng đầu tiên; được trình bày chuyên sâu trên các trang về ký.