Menu

Generics By Examples

March 14, 2022 · 7 min read

There’s a comprehensive guide on generics in PHP using PHPDocs, but after reading it someone might still not be sure where to begin.

That’s why I collected these examples of generics in practice. It’s here to give you an idea how to actually use generics in real-world code every day.

Return the same type the function accepts #

/**
* @template T
* @param T $p
* @return T
*/

function foo($p)
{
// ...
}

Return the same type the function accepts and limit what the type can be #

The of Foo part is called “template type variable bound”.

/**
* @template T of Foo
* @param T $p
* @return T
*/

function foo(Foo $p): Foo
{
// ...
}

Accept a class string and return the object of that type #

/**
* @template T of object
* @param class-string<T> $className
* @return T
*/

function foo(string $className): object
{
// ...
}

Accept a class string and return an array of objects of that type #

/**
* @template T of object
* @param class-string<T> $className
* @return array<int, T>
*/

function foo(string $className): array
{
// ...
}

Accept an array and return an array with the same key type #

/**
* @template TKey of array-key
* @param array<TKey, mixed> $p
* @return array<TKey, mixed>
*/

function foo(array $p): array
{
// ...
}

Define a generic interface #

/**
* @template T
*/

interface Collection
{
/** @param T $item */
public function add($item): void
{
// ...
}

/** @return T */
public function get()
{

}
}

Specify template type variable of a generic interface or class in a parameter type #

Specifying the template type with Collection<Foo> will get you rid of this error:

Function foo() has parameter $c with generic interface Collection but does not specify its types: T

/**
* @param Collection<Foo> $c
*/

function foo(Collection $c): void
{
// ...
}

Specify template type variable of a generic interface when implementing it #

Specifying the template type with @implements Collection<Foo> will get you rid of this error:

Class Bar implements generic interface Collection but does not specify its types: T

/** @implements Collection<Foo> */
class Bar implements Collection
{
}

If you want the child class to be also generic, repeat @template above it:

/**
* @template T
* @implements Collection<T>
*/

class Bar implements Collection
{
}

The child class can also have different number and names of @template tags.

Specify template type variable of a generic class when extending it #

The @extends tag needs to be used instead of @implements:

/** @extends Collection<Foo> */
class Bar extends Collection
{
}

This will get you rid of the following error:

Class Bar extends generic class Collection but does not specify its types: T

Function accepts any string, but returns object of the same type if it’s a class-string #

Typical scenario for the service locator (dependency injection container) pattern.

The following code doesn’t work because string|class-string<T> is normalized to string:

class Container
{
/**
* @template T of object
* @param string|class-string<T> $name
* @return ($name is class-string ? T : object)
*/

public function get(string $name): object
{
// ...
}
}

But the following code works as expected:

class Container
{
/**
* @template T of object
* @return ($name is class-string<T> ? T : object)
*/

public function get(string $name): object
{
// ...
}
}

Couple relevant classes together #

The following example is especially useful when you want to couple together things like an Event + its EventHandler.

Let’s say you have the following interfaces and classes:

interface Original
{
}

interface Derivative
{
}

class MyOriginal implements Original
{
}

class MyDerivative implements Derivative
{
}

And you have a function that returns MyDerivative when MyOriginal is given:

function foo(Original $o): Derivative
{
// ...
}

PHPStan out of the box unfortunately can’t know that:

$d = foo(new MyOriginal());
\PHPStan\dumpType($d); // Derivative, not MyDerivative :(

But you can couple those relevant classes together with the help of generics. First, you have to make Original generic so that it’s aware of its derivative:

/** @template TDerivative of Derivative */
interface Original
{
}

After that you need to say that MyOriginal is coupled to MyDerivative:

/** @implements Original<MyDerivative> */
class MyOriginal implements Original
{
}

And you need to modify the signature of the foo() function to “extract” the Derivative from the given Original:

/**
* @template T of Derivative
* @param Original<T> $o
* @return T
*/

function foo(Original $o): Derivative
{
// ...
}

And once you do all that, the type in the following code is correctly inferred:

$d = foo(new MyOriginal());
\PHPStan\dumpType($d); // MyDerivative :)

Link to this example on the playground »

© 2016–2022 Ondřej Mirtes