The PHP 8.4 foundations
Spec: ISO 32000-2, §7.5.2 ISO 32000-2 §7.5.2 Evidence: Code-backed PHP constraint: ≥8.4 <9.0
At a glance
Section titled “At a glance”NextPDF requires PHP 8.4. This page explains which 8.4 language features the engine relies on, why that version is a hard floor rather than a polite suggestion, and how a separate downgrade build keeps the option of running on an older runtime open without weakening the codebase you read.
Why this matters
Section titled “Why this matters”A PDF engine turns ambiguous input into a byte-exact file format. PDF is a long-established format with firm, fixed rules, and it is strict enough that a wrong guess is costly. The language the engine is written in is the first place where those guesses are either caught or passed through unchecked. A version floor is not a restriction for its own sake. It is the line below which the engine can no longer make the type guarantees the rest of its design depends on.
If that floor is vague, two costs follow. The codebase fills with compatibility shims that obscure the real logic. The type system also stops being load-bearing, which is exactly the property a document pipeline cannot afford to lose.
The short version
Section titled “The short version”- The core package declares
"php": ">=8.4 <9.0". That is the real constraint, verified incomposer.json, not a documentation aspiration. - 8.4 is the floor because the engine uses 8.4 (and recent 8.x) language features as structural guarantees: asymmetric visibility, backed enums with behavior, typed class constants, readonly state, and first-class named arguments in the public API.
- Running on PHP 8.1–8.3 is still possible through a separate downgrade build (the backport). It is build tooling, not a runtime dependency. It does not change the code you read in the core repository.
- The upper bound
<9.0is deliberate: a new major PHP release is treated as a thing to validate, not to assume.
How NextPDF approaches it
Section titled “How NextPDF approaches it”The floor is set by what the code does. So the honest way to explain it is to show the features in the code rather than list them abstractly.
Asymmetric visibility lets state be publicly readable but only privately writable, without a hand-written getter for every field. The rendering context uses it directly:
declare(strict_types=1);
final class RenderingContext{ // Readable everywhere, writable only inside the class. public private(set) float $x = 0.0; public private(set) float $y = 0.0; public private(set) int $currentPageIndex = -1; public private(set) string $currentContentStream = '';}That single language feature removes a whole category of accidental external mutation. The cursor cannot be moved from outside the engine. The guarantee is enforced by the runtime, not by a convention reviewers have to remember.
Backed enums with behavior replace scattered boolean flags and string constants with one typed value that also answers questions about itself. The document conformance discriminator is a backed enum whose methods decide spec-level outcomes:
declare(strict_types=1);
enum ConformanceMode: string{ case Plain = 'plain'; case PdfUa2 = 'pdfua2'; case PdfA4 = 'pdfa4'; case PdfA4f = 'pdfa4f';
public function isTagged(): bool { return match ($this) { self::Plain => false, default => true, }; }
/** @return 2|3|4|null */ public function pdfaPart(): ?int { return match ($this) { self::PdfA4, self::PdfA4f => 4, default => null, }; }}The enum is the single source of truth for “is this document spec-tagged?” and “which ISO part applies?”. When those questions were expressed as loose booleans, several places could answer them inconsistently.
Typed class constants make even constants part of the type contract. The HTML engine’s hard limits are declared as typed constants:
private const int MAX_NESTING_DEPTH = 100;private const int MAX_ELEMENT_COUNT = 50_000;Named arguments are part of the public API surface, not an internal trick. The example programs pass them at call sites a reader is meant to copy:
$doc->cell(0, 15, 'Hello, NextPDF!', newLine: true);These are not decorative uses of new syntax. Each one converts a convention that a reviewer would otherwise have to enforce by hand into a property the runtime enforces.
What the evidence says
Section titled “What the evidence says”This page is Evidence: Code-backed . Every claim above maps to a file in the core repository, not to a feature list:
- The version constraint is the
require.phpvalue">=8.4 <9.0"in the corecomposer.json. - The asymmetric-visibility example is the real declaration style in
src/Core/RenderingContext.php(public private(set)fields). - The enum example reflects
src/Conformance/ConformanceMode.php, a backedenum … : stringwhosematch-based methods drive conformance decisions. - The typed constants are
src/Html/HtmlParser.php(private const int MAX_NESTING_DEPTH,MAX_ELEMENT_COUNT). - The named-argument call is from the shipped
examples/programs.
The floor also has a standards dimension. The engine’s job is to emit files that conform to Spec: ISO 32000-2, §7.5.2 ISO 32000-2 §7.5.2 . A conforming PDF 2.0 writer must declare the document version as 2.0 in the file header or the catalog. Meeting an obligation that exact is far easier when the language beneath the writer makes mismatched state hard to construct in the first place. The version floor and the format’s strictness are aligned.
Practical example
Section titled “Practical example”The smallest correct program already exercises the floor. It runs on PHP 8.4, uses a named argument, and produces a conforming file:
<?php
declare(strict_types=1);
require_once __DIR__ . '/vendor/autoload.php';
use NextPDF\Core\Document;
$doc = Document::createStandalone();$doc->setTitle('Hello World');$doc->addPage();$doc->setFont('helvetica', '', 24);$doc->cell(0, 15, 'Hello, NextPDF!', newLine: true);$doc->save(__DIR__ . '/hello.pdf');Nothing here is exotic. The point is that the same strict-typing, enum, and asymmetric-visibility machinery that makes the engine internals trustworthy is already active under this five-line program. You do not opt into it.
Common misconception
Section titled “Common misconception”The most common misreading is that “requires PHP 8.4” means “will not work unless you upgrade to 8.4.” It means the core source targets 8.4. A separate downgrade build exists for teams pinned to PHP 8.1–8.3.
It is important to be precise about what that build is. It is a Rector-based downgrade pipeline that transforms the 8.4 source into older-syntax output. It is build infrastructure, not a runtime library you add to your application’s dependencies. It does not introduce a parallel, weaker-typed codebase. The code reviewed in these pages and the code that ships are the same code. The backport is a transformation applied to it, not an alternative to it.
Limits and boundaries
Section titled “Limits and boundaries”This page explains why 8.4 is the floor and what the backport preserves.
It does not document how to run the downgrade pipeline, its supported target
versions, or its operational caveats. Those belong to the backport build’s
own documentation. The internal package layout of that tooling is out of
scope here. The feature usages shown are illustrative of the engine’s style.
They are not the complete inventory of every 8.x feature the codebase uses.
Exact APIs are defined by the reference, not by this explanation. The
version constraint is accurate as of this page’s review date. The
authoritative value is always the require block in the core
composer.json.
Edition has no bearing on the language floor. Every edition is built from the same PHP 8.4 source:
| Edition | Availability |
|---|---|
| Core | Core is authored against PHP 8.4. |
| Pro | Pro builds on the same 8.4 source floor. |
| Enterprise | Enterprise builds on the same 8.4 source floor. |
Related docs
Section titled “Related docs”- Strict types, everywhere — how the static-analysis discipline turns the language floor into a guarantee.
- The pipeline model — the architecture those language features hold together.
- The NextPDF design philosophy — why the engine prefers explicit, runtime-enforced contracts.
Glossary
Section titled “Glossary”- Version floor — the minimum PHP version the core source targets
(
>=8.4). Below it, the engine’s type guarantees cannot be expressed. - Asymmetric visibility — a PHP 8.4 feature allowing a property to be
publicly readable but writable only from a narrower scope
(
public private(set)). - Backed enum — a PHP enum whose cases have scalar values and which can carry behavior (methods), used here as a single typed source of truth.
- Backport — the separate Rector-based downgrade build that transforms the 8.4 source into output runnable on older PHP. Build tooling, not a runtime dependency.
- Conformance mode — the engine’s typed discriminator for which ISO conformance contract a document must satisfy.