PHP 8.2: DNF Types

Version8.2
TypeNew Feature

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 to A&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 with A|(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.


RFC Discussion Implementation