Kesalahan sebagai fitur
Spec: ISO 9241-110, §5.6.4 ISO 9241-110 §5.6.4 Evidence: Code-backed
Sekilas pandang
Bagian berjudul “Sekilas pandang”NextPDF memperlakukan hierarki exception-nya sebagai permukaan API, dirancang dengan ketelitian yang sama seperti metode yang melemparkannya. Setiap kegagalan bersifat spesifik, bertipe, dapat ditangkap pada tingkat kedetailan yang Anda butuhkan, dan membawa konteks terstruktur untuk log Anda.
Halaman ini menampilkan permukaan tersebut di dalam kode sumber mesin itu sendiri: tipe dasar, subkelas bertipe, named constructor yang mengikat akar penyebab ke pesan, serta konteks terstruktur yang diekspos oleh setiap exception NextPDF.
Mengapa ini penting
Bagian berjudul “Mengapa ini penting”Pesan kesalahan adalah cara mesin berbicara kepada Anda pada saat yang paling buruk: di lingkungan produksi, pukul 2 a.m., saat sebuah dokumen seharusnya sudah dikirim. Isi pesan itu kemudian menentukan apakah langkah berikutnya adalah perbaikan atau investigasi panjang.
RuntimeException: something went wrong yang generik tidak memberi Anda jalan ke mana pun. Ia memberi tahu bahwa mesin gagal, tetapi bukan bagian mana yang gagal, bukan lokasinya, dan tentu saja bukan apa yang harus dilakukan. Panduan faktor manusia tegas mengenai hal ini. Sebuah kesalahan harus menjelaskan dirinya dengan cukup jelas sehingga perbaikannya menjadi langkah berikutnya yang nyata, bukan sebuah proyek riset ( Spec: ISO 9241-110, §5.6.4.3 ISO 9241-110 §5.6.4.3 ). Exception yang menyebutkan penyebab dan solusinya bukan sekadar pelengkap. Itulah perbedaan antara perbaikan lima menit dan perbaikan lima jam.
Versi singkatnya
Bagian berjudul “Versi singkatnya”- Setiap kegagalan NextPDF mewarisi satu basis abstrak,
NextPdfException, sehingga Anda dapat menangkap semua kesalahan pustaka dengan satu tipe. - Di bawahnya terdapat subkelas yang spesifik dan bertipe — fon yang tidak dapat ditemukan, konfigurasi yang tidak valid, operasi tanda tangan yang gagal — sehingga Anda dapat menangkap tepat kegagalan yang mampu Anda tangani.
- Setiap exception NextPDF mengimplementasikan
ContextAwareExceptionInterfacedan mengeksposgetContext(): peta terstruktur yang aman untuk log, sehingga Anda tidak perlu mengurai string pesan untuk memulihkan diagnostik. - Pesannya dapat ditindaklanjuti: named constructor mengikat akar penyebab yang sebenarnya (dan sering kali perbaikannya) ke pesan, bukan menggunakan templat generik.
- Setiap kelas exception mendokumentasikan siapa yang dapat menanganinya — pengembang, infrastruktur, atau pemanggil pustaka — sehingga triase sudah dimulai sebelum Anda membaca stack trace.
Bagaimana NextPDF menanganinya
Bagian berjudul “Bagaimana NextPDF menanganinya”Hierarkinya dangkal dan disengaja. Ada satu basis, satu lapisan tipe khusus domain, dan satu kontrak yang dipatuhi semuanya.
Satu basis, penangkap-semua secara rancangan. NextPdfException bersifat abstrak, mewarisi RuntimeException, dan mengimplementasikan ContextAwareExceptionInterface:
abstract class NextPdfException extends RuntimeException implements ContextAwareExceptionInterface{ /** @return array<string, mixed> */ public function getContext(): array { return []; }}Sifat abstrak itu disengaja. Anda tidak pernah menangkap basis yang terlalu umum secara tidak sengaja, karena ia tidak pernah dilemparkan secara langsung. Anda menangkapnya dengan sengaja, sebagai penangkap terakhir, dan menangkap subkelas yang spesifik ketika Anda dapat melakukan sesuatu yang spesifik.
Subkelas yang spesifik dan bertipe. Fon yang hilang bukanlah kesalahan generik; itu adalah FontNotFoundException, dan ia membawa data yang Anda butuhkan untuk bertindak:
final class FontNotFoundException extends NextPdfException{ public function __construct( private readonly string $fontName, private readonly array $searchPaths, private readonly bool $fallbackAttempted, ?Throwable $previous = null, ) { parent::__construct( \sprintf('Font "%s" not found. Searched: [%s].', $fontName, \implode(', ', $searchPaths)), 0, $previous, ); } // getFontName(), getSearchPaths(), wasFallbackAttempted(), getContext()}Pesan tersebut menyebutkan fon dan path persis yang dicari. Anda tidak perlu menebak direktori mana yang hilang; exception-nya memberi tahu Anda.
Konteks terstruktur, bukan penguraian string. Setiap exception mengembalikan peta snake_case yang hanya berisi primitif dan aman untuk diserialisasi langsung ke log atau payload APM:
public function getContext(): array{ return [ 'config_key' => $this->configKey, 'given_value' => $this->givenValue, 'expected_type' => $this->expectedType, ];}Kontrak ini menjelaskan alasannya secara eksplisit. Sebuah middleware logging dapat memanggil $logger->error($e->getMessage(), $e->getContext()) untuk exception NextPDF apa pun tanpa pernah mengurai pesannya. Pesan ditujukan untuk manusia. Konteks ditujukan untuk mesin. Keduanya tidak perlu saling menggantikan.
Pesan yang dapat ditindaklanjuti melalui named constructor. Di sinilah kesalahan berhenti menjadi sesuatu yang kebetulan dan menjadi sesuatu yang dirancang. SignatureException tidak hanya mengatakan “penandatanganan gagal pada level B-LT”. Ia menyediakan named constructor yang mengikat akar penyebab yang sebenarnya, dan sering kali solusi yang tepat, ke pesan:
public static function tsaUrlEmpty(string $signatureLevel): self{ return new self('', $signatureLevel, null, 'TSA endpoint URL is empty: pass a non-empty `tsaUrl` to the TsaClient ' . 'constructor (e.g. "https://timestamp.example.com/tsa") or remove the ' . 'TSA client wiring if no timestamping is required at this signature level');}Pesan tersebut menyatakan apa yang salah dan apa yang harus dilakukan untuk mengatasinya. Ada constructor sejenis untuk paket kapabilitas yang hilang, HTTP client yang tidak ada, algoritma digest-only yang dipilih secara keliru, tipe kunci yang tidak cocok dengan algoritma, dan lainnya. Masing-masing mengubah satu kelas kegagalan menjadi kalimat yang dapat langsung ditindaklanjuti oleh pengembang tanpa membaca kode sumber mesin.
Kegagalan yang sengaja dibuat eksplisit. Beberapa exception hadir justru agar celah yang semula senyap menjadi kegagalan yang jelas terlihat. NotImplementedException membawa label feature yang dapat di-grep oleh mesin dan referensi followUp:
final class NotImplementedException extends NextPdfException{ public function __construct( public readonly string $feature, public readonly string $followUp, ?Throwable $previous = null, ) { parent::__construct( \sprintf('%s is not implemented in this release. %s', $feature, $followUp), 0, $previous, ); }}Jalur yang tercapai tetapi belum tersambung melemparkan ini alih-alih mengembalikan no-op yang tampak masuk akal. Gagasan yang sama mendorong StrictModeViolation, yang subkelasnya membawa label pendek yang dapat di-grep untuk konstruksi yang menyimpang, ditambah konteks lokasi dan kutipan opsional. Penyimpangan dari spesifikasi menjadi penghentian yang bertipe dan kontekstual, bukan hasil render yang salah secara diam-diam.
Metadata triase di dalam kelas itu sendiri. Setiap kelas exception menyebutkan siapa yang dapat menanganinya di dalam docblock-nya. Sebagai contoh, FontNotFoundException adalah “Pengembang (verifikasi path fon) atau Infrastruktur (perbaiki izin berkas)”. InvalidConfigException adalah “Pengembang (perbaiki konfigurasi sebelum memanggil NextPDF)”. NotImplementedException adalah “Pemanggil pustaka — hapus pemanggilannya atau sematkan ke rilis mendatang”. Triase dimulai sebelum stack trace, karena pertanyaan “apakah ini tanggung jawab saya atau tim operasi?” sudah tertulis jawabannya.
Tabel ini merangkum rancangan tersebut dan apa yang Anda peroleh dari setiap propertinya.
| Properti rancangan | Di dalam kode sumber | Apa yang Anda peroleh |
|---|---|---|
| Satu basis abstrak | NextPdfException (abstrak, mengimplementasikan antarmuka konteks) | Tangkap setiap kesalahan pustaka dengan satu tipe, tanpa pernah menangkap basis yang terlalu umum secara tidak sengaja |
| Subkelas spesifik yang bertipe | FontNotFoundException, InvalidConfigException, SignatureException, … | Tangkap tepat kegagalan yang mampu Anda tangani |
| Konteks terstruktur | getContext() — hanya primitif snake_case | Log atau kirim ke APM tanpa mengurai string pesan |
| Pesan yang dapat ditindaklanjuti | Named constructor mengikat akar penyebab + solusi | Sebuah kalimat yang dapat Anda tindaklanjuti, bukan sebuah templat |
| Eksplisit secara sengaja | NotImplementedException, StrictModeViolation | Celah yang senyap menjadi penghentian yang bertipe dan dapat di-grep |
| Metadata triase | ”Actionable by:” di dalam setiap docblock kelas | Ketahui masalah siapa ini sebelum membaca trace |
Apa yang dikatakan bukti
Bagian berjudul “Apa yang dikatakan bukti”Halaman ini bersifat Evidence: Code-backed : setiap kelas, signature, dan bentuk pesan dikutip dari namespace exception mesin, bukan diparafrasekan.
- Basis abstrak dan kontrak
ContextAwareExceptionInterface-nya, subkelas bertipe, bentukgetContext(), dan named constructorSignatureExceptiondikutip persis dari kode sumber. - Baris triase “Actionable by:” adalah kontrak docblock kelas di dalam berkas yang sama.
- Acuan faktor manusianya adalah Spec: ISO 9241-110 ISO 9241-110 — §5.6.4.3, tentang kesalahan yang menjelaskan dirinya cukup baik untuk diperbaiki, dan prinsip ketahanan terhadap kesalahan penggunaan §6. Mesin memperlakukan pengembang sebagai pengguna dan exception sebagai antarmuka yang harus memenuhi klausul-klausul tersebut.
Contoh praktis
Bagian berjudul “Contoh praktis”Tangkap secara luas sebagai penangkap terakhir, tangkap secara spesifik di tempat Anda dapat bertindak, dan teruskan konteks terstruktur langsung ke logger Anda — tanpa mengurai pesan.
<?php
declare(strict_types=1);
use NextPDF\Core\Document;use NextPDF\Exception\FontNotFoundException;use NextPDF\Exception\NextPdfException;use Psr\Log\LoggerInterface;
function renderInvoice(LoggerInterface $logger): ?string{ try { $document = Document::createStandalone(); $document->setTitle('Invoice 2026-0042'); $document->addPage(); $document->setFont('BrandSans', '', 12); $document->cell(0, 10, 'Thank you for your business.', newLine: true);
return $document->getPdfData(); } catch (FontNotFoundException $e) { // Specific: we can recover — fall back to a built-in font. // getContext() is log-safe structured data, not a parsed string. $logger->warning($e->getMessage(), $e->getContext());
return null; // caller re-renders with 'helvetica' } catch (NextPdfException $e) { // Backstop: any other NextPDF failure, still with structured context. $logger->error($e->getMessage(), $e->getContext());
return null; }}Blok catch yang spesifik dapat memulihkan karena tipe exception-nya memberi tahu bahwa pemulihan dimungkinkan. Penangkap terakhir mencatat konteks terstruktur untuk segala hal lainnya. Aplikasi sama sekali tidak pernah membaca pesan untuk mengetahui apa yang terjadi.
Kesalahpahaman yang umum
Bagian berjudul “Kesalahpahaman yang umum”Kesalahpahaman yang biasa terjadi adalah bahwa pohon exception yang dalam merupakan rekayasa berlebihan, dan bahwa satu tipe kesalahan akan lebih sederhana. Itu akan lebih sederhana bagi mesin dan lebih buruk bagi Anda. Satu tipe berarti setiap kegagalan adalah stack trace generik dan logika pemulihannya bergantung pada pencocokan string. Pencocokan itu rapuh; perubahan kata pada pesan berikutnya akan merusaknya. Hierarki yang kecil dan spesifik memindahkan pengetahuan itu ke dalam sistem tipe, tempat kompiler dan blok catch Anda dapat memanfaatkannya.
Kesalahpahaman kedua adalah bahwa pesan dan konteks bersifat redundan. Keduanya tidak demikian. Pesan adalah prosa untuk manusia yang membaca satu baris log. Konteks adalah peta bertipe untuk perutean kode, peringatan, atau dasbor. Menyamakan keduanya justru menjebak Anda kembali ke penguraian string; kontrak getContext() hadir untuk menghilangkan jebakan itu.
Batasan dan ruang lingkup
Bagian berjudul “Batasan dan ruang lingkup”Hierarkinya sengaja dibuat dangkal. NextPDF tidak membuat kelas exception tersendiri untuk setiap kemungkinan kegagalan. Ia membuat kelas tersendiri hanya ketika pemanggil memang wajar menangkap kegagalan itu secara spesifik. Pemisahan yang berlebihan akan menukar masalah penguraian string dengan masalah daftar catch yang membengkak.
getContext() disusun untuk log dan APM, jadi sesuai kontrak ia hanya mengembalikan primitif dan daftar primitif, tanpa objek bersarang. Ini adalah konteks diagnostik, bukan snapshot terserialisasi dari internal mesin. Ia juga bukan format wire yang stabil untuk dijadikan dasar skema eksternal.
Halaman ini menjelaskan permukaan rancangan exception. Kumpulan exception yang persis beserta field-nya berkembang seiring mesin. Kelas dan bentuk yang dikutip di sini berlaku pada saat tinjauan ini dan bersifat ilustratif terhadap kontrak, bukan katalog yang dibekukan. Kontraknya — satu basis, subkelas bertipe, konteks terstruktur, pesan yang dapat ditindaklanjuti — adalah bagian yang stabil.
Dokumen terkait
Bagian berjudul “Dokumen terkait”- API yang menolak menebak — penjaga fail-fast yang melemparkan exception ini sejak awal.
- Filosofi rancangan NextPDF — alasan “kesalahan adalah bidang API” menjadi prinsip kelas satu.
- Model pipeline — tempat kegagalan-kegagalan ini muncul saat sebuah dokumen bergerak melewati mesin, dan bagaimana mereka diamati.
Glosarium
Bagian berjudul “Glosarium”- Code-backed (tingkat bukti) — halaman yang klaimnya diperiksa terhadap kode sumber mesin itu sendiri, dikutip alih-alih diparafrasekan.
- Context-aware exception — sebuah exception NextPDF yang mengimplementasikan
ContextAwareExceptionInterfacedan mengeksposgetContext(). Metode tersebut mengembalikan peta snake_case berisi field diagnostik primitif yang aman untuk diserialisasi ke dalam log atau payload APM tanpa mengurai string pesan. - Named constructor — sebuah metode factory statis (misalnya
SignatureException::tsaUrlEmpty()) yang membangun sebuah exception dengan pesan yang terikat pada akar penyebab tertentu dan, sering kali, solusinya. - PAdES — PDF Advanced Electronic Signatures, keluarga profil ETSI untuk penandatanganan PDF. Dijabarkan saat pertama kali digunakan; dibahas secara mendalam di halaman penandatanganan.
- TSA — Time-Stamping Authority, layanan tepercaya yang menerbitkan timestamp RFC 3161 yang digunakan oleh profil PAdES yang lebih tinggi.