Cùng những byte như nhau mỗi lần: PDF tái lập được
Spec: ISO 32000-2, §14.4ISO 32000-2 §14.4Spec: ISO 32000-2, §14.3.3ISO 32000-2 §14.3.3
Tổng quan nhanh
Phần tiêu đề “Tổng quan nhanh”Xây một tệp PDF từ cùng đầu vào hai lần và bạn sẽ mong đợi cùng một tệp. Hầu hết các thư viện PDF không thể hứa điều đó — xây lại và diff, và các byte trôi dạt. NextPDF có thể ghim hai thứ di chuyển, để cùng đầu vào tạo ra cùng các byte, mỗi lần.
Vì sao điều này quan trọng
Phần tiêu đề “Vì sao điều này quan trọng”Đầu ra giống-hệt-từng-byte không phải một chỉ số phù phiếm. Nó là nền tảng nằm dưới ba thứ mà các nhóm thực sự muốn.
Thứ nhất là cache. Nếu một lần build là một hàm thuần túy của các đầu vào của nó, thì hash đầu ra của nó là một cache key. Cùng đầu vào, cùng hash, bỏ qua công việc và phục vụ tệp đã lưu. Khi các byte lang thang, hash lang thang, và cache không bao giờ trúng.
Thứ hai là bằng chứng chống can thiệp. Một đường ống có thể tái tạo đúng cái tệp nó đã ship có thể chứng minh, về sau, rằng một tài liệu được lưu trữ đã không bị thay đổi: xây lại nó, hash cả hai, so sánh. Nếu thậm chí một byte khác đi vì một đồng hồ được nhúng, bằng chứng biến mất và bạn quay lại với “hãy tin tôi”.
Thứ ba là CI đáng tin cậy. Một golden-file test ghi lại một đầu ra biết-là-tốt và thất bại khi một thay đổi làm biến đổi nó. Tín hiệu đó chỉ có ý nghĩa nếu một engine không-đổi tái tạo một tệp không-đổi. Nếu mỗi lần chạy khác nhau ở một timestamp, golden file là nhiễu, và nhóm học được cách phớt lờ một build đỏ — thói quen đắt giá nhất trong kiểm thử.
Tóm tắt ngắn gọn
Phần tiêu đề “Tóm tắt ngắn gọn”Trong profile tất định của NextPDF, hai trường do-engine-kiểm-soát mà nếu không sẽ trôi
dạt giữa các lần build giống hệt nhau là các mốc ngày và /ID. Điều này giả định rằng
phần còn lại của đường ống đã ổn định sẵn — cùng đầu vào, và một cách serialize không tự
biến đổi (thêm về điều đó bên dưới):
- Các mốc ngày được nhúng. Document information dictionary mang theo
CreationDatevàModDate(Spec: ISO 32000-2, §14.3.3ISO 32000-2 §14.3.3), và metadata XMP phản chiếu chúng. Bắt lấy “bây giờ” tại thời điểm build và mỗi lần xây lại khác đi. - Định danh tệp. Mảng
/IDlà một cặp chuỗi byte định danh tệp (Spec: ISO 32000-2, §14.4ISO 32000-2 §14.4), được lưu trong trailer dictionary (Spec: ISO 32000-2, §7.5.5ISO 32000-2 §7.5.5). Các thư viện thường suy ra nó từ thời gian hiện tại cộng với các byte ngẫu nhiên, nên nó khác đi trên mỗi lần chạy theo thiết kế.
Ghim cả hai — một timestamp cố định và một seed cố định cho /ID — và đầu ra trở thành
một hàm tất định của nội dung của nó. Để nguyên nội dung và tệp giống-hệt-từng-byte. Đây
là cùng kỷ luật mà dự án Reproducible Builds thiết lập cho phần mềm được biên dịch, áp
dụng cho tầng tài liệu.
NextPDF tiếp cận điều này như thế nào
Phần tiêu đề “NextPDF tiếp cận điều này như thế nào”Tính tất định trong NextPDF là một đối tượng cấu hình, không phải một mẹo kiểm thử.
Engine phơi ra một value object DeterministicSettings trong namespace NextPDF\Core.
Nó là final readonly, bất biến, và nó ghim đúng hai nguồn trôi dạt phái-sinh-từ-đồng-hồ
và phái-sinh-từ-ngẫu-nhiên được nêu trên: các mốc ngày và /ID. Ghim chúng loại bỏ hai
nguồn trôi dạt phổ biến nhất, nhưng tự thân nó không bảo đảm đầu ra giống-hệt-từng-byte.
Hành vi serialize khác của engine — thứ tự đối tượng, font subsetting, và các cài đặt nén
— cũng phải tất định thì đầu ra mới tái lập được, và NextPDF giữ những thứ đó ổn định
theo thiết kế.
Constructor của nó nhận hai đối số:
public function __construct( public DateTimeImmutable $timestamp, public string $fileIdSeed,) { // ...}$timestamp là khoảnh khắc cố định duy nhất được ghi vào mọi trường ngày —
CreationDate, ModDate, và bản phản chiếu XMP của chúng. Truyền một
DateTimeImmutable và tài liệu thôi không còn hỏi đồng hồ treo tường mấy giờ rồi nữa.
$fileIdSeed là đầu vào ghim /ID của trailer: một chuỗi thập-lục-phân 32 ký tự. Đưa
cùng seed và engine suy ra cùng định danh tệp thay vì lấy mẫu từ đồng hồ và một nguồn
ngẫu nhiên.
Đối tượng tự xác nhận đầu vào của chính nó. Seed phải đúng 32 ký tự thập-lục-phân; bất
cứ thứ gì khác bị từ chối tại thời điểm khởi tạo với một
InvalidConfigException thay vì lặng lẽ tạo ra một /ID trông-khác-đi.
Đây là cùng lập trường từ-chối-đoán mà phần còn lại của engine áp dụng —
một đầu vào mơ hồ thất bại ầm ĩ thay vì lặng lẽ thay đổi các byte.
Với cả hai được ghim, công thức là cái mà dự án Reproducible Builds đã làm cho quen thuộc: xây lại nó, diff nó, và bản diff rỗng.
- Fix the inputsThe same content, fonts, and settings that produced the original document.
- Pin the timestampOne DateTimeImmutable feeds CreationDate, ModDate, and the XMP dates — no wall clock.
- Pin the /ID seedA 32-character hex seed derives the trailer /ID instead of a clock-plus-random value.
- BuildThe output is now a pure function of content; the two moving parts are held still.
- Rebuild and diffRegenerate from the same inputs and compare bytes — an empty diff is the proof.
Ví dụ thực tế
Phần tiêu đề “Ví dụ thực tế”Một hình mẫu nhỏ, hoàn chỉnh. Các settings được khởi tạo một lần và tái sử dụng, nên hai lần chạy cùng một chương trình phát ra cùng một tệp.
<?php
declare(strict_types=1);
use NextPDF\Core\DeterministicSettings;use NextPDF\Exception\InvalidConfigException;
// One fixed instant for every date field — never the wall clock.$timestamp = new DateTimeImmutable('2026-01-01T00:00:00+00:00');
// A 32-character hex seed pins the trailer /ID. Same seed, same /ID.$fileIdSeed = '0123456789abcdef0123456789abcdef';
try { $deterministic = new DeterministicSettings( timestamp: $timestamp, fileIdSeed: $fileIdSeed, );} catch (InvalidConfigException $e) { // A malformed seed (not exactly 32 hex chars) is refused here, // before any document is built — not silently coerced. error_log($e->getMessage());
throw $e;}
// Hand $deterministic to the document configuration. With both moving// parts pinned, building the same content twice yields identical bytes://// sha256(build_one) === sha256(build_two)Seed là một đầu vào build mà bạn kiểm soát, không phải một bí mật. Lưu nó cạnh phần còn lại của cấu hình build của bạn. Điểm mấu chốt là nó cố định, nên định danh tệp mà nó tạo ra cũng cố định.
Hiểu lầm thường gặp
Phần tiêu đề “Hiểu lầm thường gặp”Cái bẫy thứ nhất là “Tôi đã bỏ timestamp, vậy build của tôi giờ tái lập được.” Thường thì
không, vì mảng /ID là cái lặng lẽ hơn trong hai nguồn. Các mốc ngày thì thấy được trong
một bảng metadata và dễ nhớ; /ID của trailer thì vô hình với hầu hết trình đọc và được
tái tạo từ đồng hồ và một nguồn ngẫu nhiên trên mỗi lần chạy. Một build chỉ ghim các mốc
ngày vẫn tạo ra một tệp khác nhau mỗi lần. Bạn phải giữ cả hai đứng yên.
Cái bẫy thứ hai là coi tính tất định như một tính năng bảo mật tự thân. Một /ID được
ghim làm cho một tệp tái lập được; nó không làm cho tệp được ký, và tự thân nó không
chứng minh rằng hai build khớp nhau. Một phép so sánh từng-byte hay một hash chứng minh
các build khớp; ghim /ID chỉ loại bỏ một nguồn khác biệt giả tạo. Và không cái nào
trong số đó chứng minh rằng một bên thứ ba bảo lãnh cho tệp. Tính tái lập và việc ký là
các tầng bổ sung cho nhau, không phải thay thế cho nhau.
Giới hạn và ranh giới
Phần tiêu đề “Giới hạn và ranh giới”Tính tất định ghim các bộ phận di chuyển của chính engine. Nó không ghim đầu vào của
bạn. Nếu nội dung của bạn nhúng một timestamp sống, kéo một font đã thay đổi trên đĩa,
hoặc hiển thị một giá trị phụ thuộc vào ngày hiện tại, thì đầu ra thay đổi vì đầu vào
đã thay đổi — và điều đó là đúng. DeterministicSettings loại bỏ tính không tất định của
engine, không phải của bạn. Một build tái lập được vẫn đòi hỏi các đầu vào tái lập được.
| Edition | Availability |
|---|---|
| Core | Full support. |
| Pro | Not in this edition |
| Enterprise | Not in this edition |
Tài liệu liên quan
Phần tiêu đề “Tài liệu liên quan”- Golden-file testing — kỹ thuật CI phụ thuộc vào đầu ra giống-hệt-từng-byte, và vì sao một engine tất định là điều kiện tiên quyết của nó.
- Incremental updates — cách một tệp PDF lớn dần
bằng cách nối thêm, nơi mảng
/IDlại quan trọng lần nữa để liên hệ một tệp với các phiên bản trước của nó. - Metadata and the XMP packet — nơi các mốc ngày được nhúng sống, và cách gói XMP phản chiếu document information dictionary.
- The anatomy of a PDF file — trailer,
cross-reference table, và nơi mảng
/IDnằm trong cấu trúc tệp.
Bảng thuật ngữ
Phần tiêu đề “Bảng thuật ngữ”- Giống-hệt-từng-byte (Byte-identical) — hai tệp khớp nhau chính xác, từng byte một. Hình thức mạnh nhất của “giống nhau”, và là hình thức mà một hash hay một diff có thể xác minh.
/ID(định danh tệp) — mảng gồm hai chuỗi byte định danh một tệp PDF và các phiên bản của nó (ISO 32000-2 §14.4), được lưu trong trailer dictionary (§7.5.5). Thường được suy ra từ đồng hồ cộng các byte ngẫu nhiên, đó là lý do nó thay đổi trên mỗi build chưa-ghim.- Document information dictionary — cấu trúc mang theo
CreationDatevàModDate(ISO 32000-2 §14.3.3). Một trong hai nguồn của tính không tất định mà một build tất định phải ghim. - Golden file — một đầu ra biết-là-tốt được ghi lại mà một test so sánh với; chỉ có ý nghĩa khi một engine không-đổi tái tạo một tệp không-đổi.
- Build tái lập được (Reproducible build) — một build mà đầu ra của nó là một hàm tất định của các đầu vào của nó, nên xây lại từ cùng đầu vào cho ra cùng các byte. Thuật ngữ này đến từ dự án Reproducible Builds cho phần mềm được biên dịch.