PHP 8.1 : Pure intersection types
07, October 2022

Intersection types, a new addition to PHP type system that allows us to constraint a value to satisfy multiple types at same time.

Until PHP 8.0, we could only define a single type as parameter/return/property type. On PHP 8.1, we got Union type, which allowed us to restrict a value to satisfy either type X OR type Y. Intersection type is exactly the opposite of that. It allows us to restrict a value to satisfy both type X AND type Y.

<?php
interface X {}

interface Y {}

class A {
   // UNION type, $param must be either X or Y
   public function foo(X|Y $param) {}

   // INTERSECTION type, $param must be both X and Y
   public function bar(X&Y $param){}
}


Don't worry, intersection type can be used in return type and typed properties too.

Full example:

<?php
interface X {}

interface Y {}

class Something implements X, Y {}


class AnotherThing implements X {}


class User {
  public X&Y $xAndY;

  public function foo (X&Y $param): X&Y{
    return $param;
  }
}

$something = new Something;
$anotherThing = new AnotherThing;

$user = new User;

$user->xAndY = $something; // works
$user->xAndY = $anotherThing; // doesn't work, because anotherThing is not both X and Y

$user->foo($something); // works, because something is both X and Y
$user->foo($anotherThing); // doesn't work

// returns types follow the same rule, foo() can only return value that is both X and Y


Restrictions:

  • Only interfaces and class types can be used in intersection types. Native types (int, string, array) are not allowed.
  • Types that are possible to satisfy together, can be used. For example, string&int is not allowed, because no value can be string and int at the same time.
  • mixed type is not allowed. Because mixed already allows all types, it doesn't make sense to use it in intersection type.
  • iterable, callable, self, parent, static types are not allowed.
  • Same type name cannot be used multiple times in same intersection type.


Inheritance rules:

  • Intersection types must follow standard variance rule of PHP.
  • parameter types must be contra-variant
  • return types must be co-variant.
  • properties must be in-variant.
  • In child class, types can be removed from parameter types, but cannot be added.
interface A {}
interface B {}

class Test {
  public function foo(A&B $param) {}

  public function bar(A $param) {}
}

class AnotherTest extends Test {
  public function foo(A $param) {} // Allowed, can remove type

  public function bar(A&B $param) {} // Forbidden, cannot add type
}
  • In child class, types can be added to return types, but cannot be removed.
interface A {}
interface B {}

class Test {
  public function foo(): A {}

  public function bar(): A&B {}
}

class AnotherTest extends Test {
  public function foo(): A&B {} // Allowed, can add type

  public function bar($param): A {} // Forbidden, cannot remove type
}


For reflection purpose, ReflectionIntersectionType class has been introduced.


RFC: https://wiki.php.net/rfc/pure-intersection-types


As I hoped on my union type post, We got intersection type as well. Maybe we are very close to get our hands on type alias feature too. Should I start dreaming about having generics? Faith is all we got.

Till then, keep your code type safe.


Write comment about this article: