Troubleshoot compat-legacy
At a glance
Section titled “At a glance”Most migration issues fit a small set of patterns. Each entry below
lists the symptom, cause, and fix. When you are unsure about a specific
method, check /integrations/tcpdf-compat/method-coverage/ and the
authoritative in-repo matrix docs/TCPDF_COVERAGE.md.
The process used to stop on a PDF error; now an exception escapes
Section titled “The process used to stop on a PDF error; now an exception escapes”Symptom. Code that once stopped on a bad render now throws an
uncaught RuntimeException, and the request or job reports an error.
Cause. Legacy TCPDF Error() calls die(). The adapter throws
RuntimeException instead, by design, so failures are observable.
Fix. Wrap render entry points in try/catch, and map the
exception to your error contract. Do not restore die() behavior. See
/integrations/tcpdf-compat/production-usage/ § Failure handling.
new \TCPDF() still resolves to the real TCPDF library
Section titled “new \TCPDF() still resolves to the real TCPDF library”Symptom. You enabled LegacyBootstrap::enableAliases() but the
output still looks like legacy TCPDF, or behavior has not changed.
Cause. enableAliases() registers an alias only when no class with
that name already exists. If tecnickcom/tcpdf remains autoloadable
and its \TCPDF class loads first, the alias is skipped, and your code
keeps using legacy TCPDF.
Fix. During migration, use explicit per-file imports
(use NextPDF\Compat\Tcpdf\TCPDF;) so each call site is unambiguous.
Remove tecnickcom/tcpdf after the audit passes (see /integrations/tcpdf-compat/migration/
Stage 5). Do not run both libraries with global aliases enabled in the
same process.
A method “works” but the parameter I passed is ignored
Section titled “A method “works” but the parameter I passed is ignored”Symptom. A call succeeds and produces a Portable Document Format (PDF) file, but an option you passed (image link, alignment, dots per inch (DPI), bookmark color, …) has no effect.
Cause. The method is in the silent-ignore set. It accepts the parameter for source compatibility, then drops it. This is documented behavior, not a bug; see /integrations/tcpdf-compat/method-coverage/ §2.
Fix. Run a strict-mode audit to find every such call:
<?php
declare(strict_types=1);
require __DIR__ . '/vendor/autoload.php';
use NextPDF\Compat\Tcpdf\Exception\TcpdfNotImplementedException;use NextPDF\Compat\Tcpdf\TCPDF;
$pdf = new TCPDF();$pdf->setStrictMode(true);$pdf->AddPage();$pdf->SetFont('helvetica', '', 12);
try { $pdf->Image('logo.png', 10, 10, 50, 0, '', 'https://example.com');} catch (TcpdfNotImplementedException $e) { // Message lists every ignored parameter and a migration hint. echo $e->getMessage(), "\n";}Then drop the parameter, or re-express it through the modern API
($pdf->getDocument()), as shown in /integrations/tcpdf-compat/migration/ Stage 4.
MultiCell() return value is always 1
Section titled “MultiCell() return value is always 1”Symptom. Code that branches on the return value of MultiCell()
(for example, to compute used height or line count) behaves incorrectly.
Cause. The adapter’s MultiCell() returns the compatibility
placeholder 1, not the rendered cell count or line count. Write()
similarly returns 0.
Fix. Do not branch on these return values. If you need the rendered
height, compute it from getStringHeight() / getNumLines(), or move
that logic to the modern API.
setPDFVersion('1.4') did not produce a PDF 1.4 file
Section titled “setPDFVersion('1.4') did not produce a PDF 1.4 file”Symptom. You asked for an older PDF version; the output is still PDF 2.0.
Cause. The adapter always outputs PDF 2.0 (ISO 32000-2).
setPDFVersion() is in the not-applicable set; the adapter emits a
notice and continues.
Fix. Remove the call. If a downstream consumer requires an older PDF version, resolve that requirement separately; the adapter cannot down-target.
setSignature() did nothing — the PDF is not signed
Section titled “setSignature() did nothing — the PDF is not signed”Symptom. You called setSignature() with a certificate; the output
PDF has no signature.
Cause. The core engine does not implement setSignature() through
this adapter. In default mode it is a no-op; in strict mode it throws.
Fix. Signing requires a commercial NextPDF edition and the modern
signature API. See /integrations/tcpdf-compat/security-and-operations/ § Digital signatures.
Do not expect the legacy setSignature() call to sign anything.
Output() corrupted my HTTP response or worker output
Section titled “Output() corrupted my HTTP response or worker output”Symptom. PDF bytes appear in a Hypertext Transfer Protocol (HTTP) response, or a worker log is polluted with PDF bytes.
Cause. You used an output destination that writes to the output
path (I/D) while you control the response yourself.
The adapter does not echo into your buffer the way legacy TCPDF does,
but I/D still drive engine output.
Fix. In workers and handlers you control, use Output($path, 'F')
to write a file, or Output($name, 'S') to get bytes and emit them
yourself. tests/Unit/Compat/Tcpdf/Bridge/OutputBridgeTest.php asserts
that destination mapping is case-insensitive and whitespace-trimmed:
| Code | Returns | Side effect |
|---|---|---|
S | PDF bytes (%PDF…) | none |
F | empty string | writes file |
E | base64 MIME body | none |
FI / FD | empty string | writes file, then engine output |
I / D / unknown | empty string | engine output (inline/download) |
Exact-byte PDF assertions fail after switching
Section titled “Exact-byte PDF assertions fail after switching”Symptom. Snapshot tests comparing raw PDF bytes fail everywhere.
Cause. The engine uses an independent PDF 2.0 implementation. Delegated methods produce compatible visible output, but the bytes differ. This is expected.
Fix. Re-baseline your tests to assert on rendered content
(extracted text), structure (page count, page size), or a smoke check
(str_starts_with($bytes, '%PDF')). See /integrations/tcpdf-compat/migration/ Stage 4.
A legacy K_* / PDF_* constant has the wrong value
Section titled “A legacy K_* / PDF_* constant has the wrong value”Symptom. A custom path or default you set through a constant is not taking effect.
Cause. The adapter auto-defines a constant only if it is not
already defined, and it does so during first construction. If your
define() runs after the first adapter is constructed, the adapter’s
default is already in effect.
Fix. Define every custom K_* / PDF_* constant in your bootstrap,
before any adapter instance is created. See /integrations/tcpdf-compat/configuration/ §
Configuration resolution order.
Engine version mismatch at construction
Section titled “Engine version mismatch at construction”Symptom. Construction fails or behavior changes unexpectedly after a dependency update.
Cause. The adapter requires nextpdf/core ^3.0. A resolved core
version outside that range is unsupported.
Fix. Run composer show nextpdf/core, and pin the engine to
^3.0. See /integrations/tcpdf-compat/install/ § Verify the engine version.
Diagnostic quick reference
Section titled “Diagnostic quick reference”| Question | Where to look |
|---|---|
| What does method X actually do here? | /integrations/tcpdf-compat/method-coverage/, docs/TCPDF_COVERAGE.md |
| Which of my calls lose parameters? | Strict-mode audit (this page; /integrations/tcpdf-compat/migration/) |
| Why did the process not stop on error? | /integrations/tcpdf-compat/security-and-operations/ § Hardened behaviors |
| Why is output not signed / not PDF/A? | /integrations/tcpdf-compat/security-and-operations/ |
| Alias vs explicit import conflict | This page; /integrations/tcpdf-compat/boot-and-discovery/ |
See also
Section titled “See also”- /integrations/tcpdf-compat/migration/ — the staged migration that prevents most of the above
- /integrations/tcpdf-compat/method-coverage/ — per-method behavior reference
- /integrations/tcpdf-compat/boot-and-discovery/ — alias registration and conflict avoidance
docs/TCPDF_COVERAGE.md— authoritative coverage matrix