Disjunctive Normal Form Types (PHP 8.2)
debt(d3/e3/b3/t5)
Closest to 'default linter catches the common case' (d3). PHPStan and Psalm (both listed in detection_hints.tools) will flag type mismatches when DNF types are used incorrectly. These are standard tools in modern PHP workflows and catch the common case of malformed type expressions at the static analysis phase.
Closest to 'simple parameterised fix' (e3). The quick_fix indicates this is about expressing complex types natively in PHP 8.2 rather than via docblocks. Fixing a misuse typically involves restructuring the type declaration with proper parentheses — a localized change within method signatures, but may touch multiple signatures across a file or component.
Closest to 'localised tax' (b3). DNF types are an advanced type-system feature that applies to specific scenarios where intersection-union combinations are needed. The applies_to shows it works across contexts (web/cli/queue), but the feature itself is opt-in for complex type scenarios. It doesn't impose a system-wide architectural commitment — it's a localized enhancement where the complexity exists.
Closest to 'notable trap' (t5). The misconception field explicitly states developers think DNF types are 'just a combination of union and intersection types' when actually they enforce a specific structure requiring parentheses. Common mistakes include mixing AND/OR without parentheses and relying on operator precedence — this is a documented gotcha that most developers eventually learn but initially guess wrong about.
Also Known As
TL;DR
Explanation
PHP 8.2 introduced Disjunctive Normal Form (DNF) types, allowing intersection types (A&B) to be combined with union types using parentheses: (Countable&Iterator)|null means 'either something that is both Countable and Iterator, or null'. Prior to 8.2, intersection types and union types could not appear in the same declaration. DNF resolves real-world type scenarios common in library code — for example a parameter accepting either a validated PSR-7 request or null: (ServerRequestInterface&ValidatedRequest)|null. The type must be in strict DNF form with no redundant terms.
Common Misconception
Why It Matters
Common Mistakes
- Mixing AND and OR without parentheses and relying on operator precedence — && binds tighter than || in PHP, confusing readers.
- Not simplifying redundant clauses — ($a && $b) || ($a && $b && $c) can be simplified to ($a && $b).
- Deeply nested conditions instead of flat DNF — three levels of nesting is harder to read than two OR'd AND clauses.
- Not extracting named boolean variables for complex sub-expressions — $isEligible = $age >= 18 && $hasConsent reads better than the raw condition.
Code Examples
// Hard to read — precedence unclear without parens:
if ($a || $b && $c || $d && $e && $f) { // What does this actually mean?
// DNF — clear and testable:
if (($a) || ($b && $c) || ($d && $e && $f)) {
function process((Stringable&Countable)|null $value): void {}