DNF Types (PHP 8.2)
debt(d1/e1/b1/t3)
Closest to 'caught instantly (compiler/syntax error)' (d1). The most common mistake — forgetting parentheses around intersection in union — produces an immediate syntax/parse error in PHP. phpstan and psalm (listed tools) would also catch type logic issues, but the primary detection mechanism is the PHP parser itself before any tooling runs.
Closest to 'one-line patch or single-call swap' (e1). The quick_fix is explicit: add parentheses around intersections and use (A&B)|null. This is a localised, single-declaration fix requiring no refactor.
Closest to 'minimal commitment' (b1). DNF types are a per-function/per-property type annotation. Misuse is localised to a single signature. There is no architectural gravity; the rest of the codebase is unaffected by a wrong DNF type on one method.
Closest to 'minor surprise (one edge case)' (t3). The misconception field states developers may read (A&B)|C as 'A and B or C' in the natural-language sense rather than the strict set-theory sense '(A&&B)||C'. This is a minor logical confusion but the correct reading is fairly intuitive once you see the parentheses, and the syntax enforces the grouping explicitly. It does not contradict other language constructs broadly.
TL;DR
Explanation
DNF type: (A&B)|C where intersections appear in parentheses within a union. PHP 8.1 added intersection types (A&B — object must implement both). PHP 8.2 adds DNF — combining intersections with unions: (Countable&Traversable)|null means either null OR an object implementing both Countable and Traversable. Rules: intersections must be in parentheses within unions. Cannot have redundant types. Cannot duplicate types within a union. Use cases: nullable intersection types (the most common: (A&B)|null), optional complex type parameters, return types that are either an intersection or a primitive.
Common Misconception
Why It Matters
Common Mistakes
- Forgetting parentheses around intersection in union — syntax error.
- Redundant types in union — (A&A)|B is invalid.
- Using DNF where simple union suffices — only needed for intersections in unions.
Code Examples
// PHP 8.1 — no way to express nullable intersection:
function process(Countable&Iterator $items): void {} // Non-nullable only
// PHP 8.2 DNF — nullable intersection:
function process((Countable&Iterator)|null $items): void {
if ($items === null) return;
// $items is guaranteed to be both Countable AND Iterator
}
// Other uses:
function transform((Stringable&JsonSerializable)|array $data): string {}