PHP 8.2: DNF Types
PHP 8.2 adds support for declaring a type by combining Union Types (PHP 8.0) and Intersection Types (PHP 8.1). One of the most common use cases is to declare a type that accepts an Intersection type, or null
(i.e. nullable intersection types)
function respond(): (JSONResponse&SuccessResponse)|HTMLResponse|string {
}
In the snippet above, respond
function declares its return type as an intersection type JSONResponse&SuccessResponse
, or HTMLResponse
, or, string
.
Some of the examples of correct DNF types are shown below:
A|B|C
A|B|(C&D)
(A&B&C)|null
It is important that the type declarations are in DNF form. PHP rejects conjuctive type declarations not in DNF form with a Parse error. The following type declarations are not in DNF form, and causes a Parse error.
A&(B|C)
A|(B&(C|D))
These types can be rewritten in DNF form as follows:
- A&(B|C)
+ (A&B)|(A&C)
- A|(B&(C|D))
+ A|(B&C)|B&D)
DNF Type Variance
When extending a PHP class or implementing an interface, DNF types also follow the same principles of LSP.
- Parameter types are invariant: The extending class is not allowed to change the property types at all.
- Return-types covariance: Return types of subclasses and interface implementations are allowed narrow the return type. In a DNF type, this means that the return type can be made more restrictive. For example, a return type of
A&B|C
can be narrowed toA&B
in a subclass, and is allowed. - Parameter-type contra-variance: Parameter types can be widened. In a DNF types, this means additional conjunctions in the subclass parameter declaration. For example,
A|(B&C)
can be widened withA|(B&C)|D
in a subclass parameter type declaration, and is allowed.
DNF Type Declaration Constraints
When declaring a DNF type, PHP enforces a few constraints.
Redundant types are not allowed: This check is performed at parse-time, and if it encounters a redundant type, PHP errors out with a fatal error:
interface X {}
interface Y {}
function foo(): (X&Y)|(Y&X) {}
Fatal error: Type X&A is redundant with type X&A in ... on line ...
However, class aliases declared at run-time do not cause this error because this constraint is only enforced at parse-time.
Less restrictive types are not allowed as part of the DNF Type: When declaring a DNF type, it is not allowed to have a component that is less restrictive than another component.
interface A {}
interface B {}
function foo(): (A&B)|A {}
Fatal error: Type A&B is redundant as it is more restrictive than type A in ... on line ...
Backwards Compatibility Impact
DNF types is a new feature in PHP 8.2 that also brings parser changes. DNF types cannot be ported to older PHP versions.