Union Types vs. Intersection Types

One of the headlining features of PHPStan 0.9 is the introduction of intersection types. Since this is a very useful feature that helps us understand the code much better, but the terminology is largely unknown and mysterious to the PHP community, I decided to write up and compare these two kinds of compound types.

Union Types

  • Written as Foo|Bar. Common syntax used in phpDocs for many years.

The most common way of utilising union types is in function arguments that accept multiple different types. They can be compared with instanceof or a comparison operator to trim down the possibilities when working with them:

/**
* @param Foo|Bar $object
*/
public function doSomethingUseful($object)
{
if ($object instanceof Foo) {
// now we can be sure that $object is just Foo in this branch
} elseif ($object instanceof Bar) {
// dtto for Bar
}
}

Do not confuse union types with the way developers usually mark types of items in a collection (that's usually an iterator):

/**
* @param Collection|Foo[] $object
*/
public function doSomethingUseful($object)
{
foreach ($object as $foo) {
// $foo is Foo here
}
}

Intersection Types

  • Written as Foo&Bar. This is a very rare syntax seen in the wild and it might not even be supported by your IDE. It's because when people write Foo|Bar, they sometimes mean a union type and sometimes an intersection type by that. Let's hope that this changes in the following months and years (not only) thanks to PHPStan. Differentiating between them helps the code to be more understandable and also more friendly to static analyzers which benefits everyone.

The thing is that you're already creating intersection types in your code and not even realize it! Consider this code:

public function doSomethingUseful(Foo $object)
{
if ($object instanceof BarInterface) {
// $foo is Foo&BarInterface here!
}
}

Other use case for them are mock objects in unit-testing frameworks, typically PHPUnit. Think about it: you create an object and it's possible to call methods on it from both mocked class and mock-specific configuration methods!

class Foo
{
public function doFooStuff()
{
}
}
class SomeTest extends \PHPUnit\Framework\TestCase
{
public function testSomething()
{
$mockedFoo = $this–>createMock(Foo::class);
// $mockedFoo is Foo&PHPUnit_Framework_MockObject_MockObject
// we can call mock-configuration methods:
$mockedFoo->method('doFooStuff')
->will($this->returnValue('fooResult'));

// and also methods from Foo itself:
$mockedFoo->doFooStuff();
}
}

You can also take advantage of intersection types if you don't want to tie your code to a specific class, but want to typehint multiple interfaces at once. For example, if you want to safely iterate over an object and at the same time pass it to count(), you can do it like this:

/**
* @param \Traversable&\Countable $object
*/
public function doSomethingUseful($object)
{
echo sprintf(
'We are going to iterate over %d values!',
count($object)
);
foreach ($object as $foo) {

}
}

This is really nice because it supports designing your codebase with small and simple interfaces.

If you’re a PHP developer, give PHPStan a shot. If you’re interested in various insights about software development, follow me on Twitter. If you’re interested in my consulting services on code quality, continuous delivery, hiring developers, open-source and plethora of other topics, please get in touch.

Author of PHPStan, static analysis tool for finding bugs in code without running it.

Author of PHPStan, static analysis tool for finding bugs in code without running it.