1 of 96

Generics in

from <A> to <Z>

2 of 96

Jiří Pudil

from Brno, CZE

open-sourcerer

coffee addict

guitar noisemaker

3 of 96

What are generics<?>

4 of 96

🤓 / 🤯 / 🤔

5 of 96

6 of 96

The identity function

7 of 96

🍎 → 🍎

8 of 96

🍏 → 🍏

9 of 96

🍊 → 🍊

10 of 96

The identity function

function identity(mixed $value): mixed {

return $value;

}

11 of 96

The identity function

function identity(mixed $value): mixed {

return $value;

}��PHPStan\dumpType(identity(42)); // mixed

PHPStan\dumpType(identity("string")); // mixed

PHPStan\dumpType(identity(true)); // mixed

PHPStan\dumpType(identity([])); // mixed

PHPStan\dumpType(identity(new stdClass)); // mixed

12 of 96

🍎 → ❓

13 of 96

🍏 → ❓

14 of 96

🍊 → ❓

15 of 96

The identity function

$result = identity(42);

$half = intdiv($result, 2);��

ERROR

Parameter #1 $num1 of function intdiv expects int, mixed given.

16 of 96

The identity function

function identity🍎() { /* … */ }

function identity🍏() { /* … */ }

function identity🍊() { /* … */ }

17 of 96

The identity function

�����function identity(mixed $value): mixed {

return $value;

}

18 of 96

The identity function

/**� * @template Value� * @param Value $value� * @return Value� */�function identity(mixed $value): mixed {

return $value;

}

19 of 96

The identity function

/**� * @template Value� * @param Value $value� * @return Value� */�function identity(mixed $value): mixed {

return $value;

}

20 of 96

The identity function

PHPStan\dumpType(identity(42)); // int

PHPStan\dumpType(identity("string")); // string

PHPStan\dumpType(identity(true)); // bool

PHPStan\dumpType(identity([])); // array{}

PHPStan\dumpType(identity(new stdClass)); // stdClass

21 of 96

The identity function

/**� * @template Value� * @param Value $value� * @return Value� */�function identity(mixed $value): mixed {

return $value;

}��PHPStan\dumpType(identity(42));

22 of 96

The identity function

/**� * @template Value� * @param Value $value� * @return Value� */�function identity(mixed $value): mixed {

return $value;

}��PHPStan\dumpType(identity(42));

23 of 96

The identity function

/**� * @template Value� * @param Value $value� * @return Value� */�function identity(mixed $value): mixed {

return $value;

}��PHPStan\dumpType(identity(42));

24 of 96

The identity function

/**� * @template Value = 42� * @param Value $value� * @return Value� */�function identity(mixed $value): mixed {

return $value;

}��PHPStan\dumpType(identity(42));

25 of 96

The identity function

/**� * @template Value = 42� * @param 42 $value� * @return 42� */�function identity(mixed $value): mixed {

return $value;

}��PHPStan\dumpType(identity(42));

26 of 96

You have already used them.

27 of 96

You have already used them.

/**� * @param Token[] @tokens� */�function assemble(array $tokens): string {� // ...�}

28 of 96

You have already used them.

/**� * @param Token[] @tokens� */�function assemble(array $tokens): string {� // ...�}

29 of 96

You have already used them.

/**� * @param Token[] @tokens� */�function assemble(array $tokens): string {� PHPStan\dumpType($tokens); // array<Token>�}

30 of 96

You have already used them.

/**� * @template Value� */�type array {� /** @return Value */� public function offsetGet(): mixed;�}

31 of 96

You have already used them.

/**� * @template Value� */�type array {� /** @return Value */� public function offsetGet(): mixed;�}

array<Token>

32 of 96

You have already used them.

/**� * @template Value� */�type array {� /** @return Value */� public function offsetGet(): mixed;�}

array<Token>

33 of 96

You have already used them.

/**� * @template Value = Token� */�type array {� /** @return Value */� public function offsetGet(): mixed;�}

array<Token>

34 of 96

You have already used them.

/**� * @template Value = Token� */�type array {� /** @return Token */� public function offsetGet(): mixed;�}��array<Token>

35 of 96

You have already used them.

/**� * @param Token[] @tokens� */�function assemble(array $tokens): string {� $token = $tokens[0];� PHPStan\dumpType($token); // Token�}

36 of 96

A generic class

37 of 96

A generic class

/**

* @template T

*/

final class Reference

{

/**

* @param T $value

*/

public function __construct(

public readonly mixed $value,

) {}

}

38 of 96

A generic class

$ref = new Reference(42);

PHPStan\dumpType($ref); // Reference<int>

PHPStan\dumpType($ref->value); // int

39 of 96

A generic class

$ref = new Reference("hello");

PHPStan\dumpType($ref); // Reference<string>

PHPStan\dumpType($ref->value); // string

40 of 96

A generic class

/**

* @template T

*/

final class Queue

{

/** @var T[] */

private mixed $items = [];

/** @return T|null */

public function dequeue(): mixed

{

return array_shift($this->items);

}

/** @param T $item */

public function enqueue(mixed $item): void

{

$this->items[] = $item;

}

}

41 of 96

A generic class

/** @var Queue<int> $queue */

$queue = new Queue();

$first = $queue->dequeue();

PHPStan\dumpType($first); // int|null

42 of 96

🤓 / 🤯 / 🤔

43 of 96

Once generic, always generic

44 of 96

Once generic, always generic

/**

* @template T

*/

interface Comparator

{

/**

* @param T $a

* @param T $b

*/

public function compare(mixed $a, mixed $b): int;

}

45 of 96

Once generic, always generic

abstract class AbstractComparator implements Comparator�{�}

ERROR

Class AbstractComparator implements generic interface Comparator but does not specify its types: T

46 of 96

Once generic, always generic

/**� * @template T� * @implements Comparator<T>� */�abstract class AbstractComparator implements Comparator�{�}

47 of 96

Once generic, always generic

/**� * @template T� * @implements Comparator<T>� */�abstract class AbstractComparator implements Comparator�{�}

48 of 96

Once generic, always generic

/**

* @implements Comparator<HasWeight>

*/

final class Scales implements Comparator

{

public function compare(mixed $a, mixed $b): int

{

return $a->weight() <=> $b->weight();

}

}

49 of 96

Type parameter bounds

50 of 96

Type parameter bounds

interface Consumer

{

public function consume(Message $message): void;

}

51 of 96

Type parameter bounds

final class ProcessOrderConsumer implements Consumer

{

public function consume(ProcessOrder $message): void;

}

52 of 96

Type parameter bounds

/**

* @template M

*/

interface Consumer

{� /** @param M $message */

public function consume(Message $message): void;

}

53 of 96

Type parameter bounds

/**� * @implements Consumer<ProcessOrder>� */�final class ProcessOrderConsumer implements Consumer

{

public function consume(Message $message): void;

}

54 of 96

Type parameter bounds

/**

* @template M

*/

interface Consumer

{� /** @param M $message */

public function consume(Message $message): void;

}��ERROR

PHPDoc tag @param for parameter $message with type M is not subtype of native type Message.

55 of 96

Type parameter bounds

/**

* @template M of Message

*/

interface Consumer

{� /** @param M $message */

public function consume(Message $message): void;

}

56 of 96

Type parameter bounds

/**� * @implements Consumer<stdClass>� */�final class ProcessOrderConsumer implements Consumer

{

public function consume(Message $message): void;

}

ERROR

Type stdClass in generic type Consumer<stdClass> in PHPDoc tag @implements is not subtype of template type M of Message of interface Consumer.

57 of 96

Generic class string

58 of 96

Generic class string

�����function resolve(string $type): object {� // ...�}��$scales = resolve(Scales::class);�PHPStan\dumpType($scales);

59 of 96

Generic class string

�����function resolve(string $type): object {� // ...�}��$scales = resolve(Scales::class);�PHPStan\dumpType($scales); // mixed

60 of 96

Generic class string

/**� * @template T� * @param class-string<T> $type� * @return T� */�function resolve(string $type): object {� // ...�}��$scales = resolve(Scales::class);�PHPStan\dumpType($scales); // Scales

61 of 96

🍺

62 of 96

Generic variance

63 of 96

Generic variance

/**

* @template T

*/

final class Queue

{

/** @var T[] */

private mixed $items = [];

/** @return T|null */

public function dequeue(): mixed

{

return array_shift($this->items);

}

/** @param T $item */

public function enqueue(mixed $item): void

{

$this->items[] = $item;

}

}

64 of 96

Generic variance

Animal

Queue<Animal>

Dog

Queue<Dog>

?

65 of 96

Generic variance

/**� * @param Queue<Animal> $queue� */�function printFirstFrom(Queue $queue): void {� $animal = $queue->dequeue();� var_dump($animal);�}

66 of 96

Generic variance

/** @var Queue<Dog> $queue */�$queue = new Queue();��printFirstFrom($queue);

67 of 96

Generic variance

/** @var Queue<Dog> $queue */�$queue = new Queue();��printFirstFrom($queue);

ERROR

Parameter #1 $queue of function printFirstFrom expects Queue<Animal>, Queue<Dog> given.

68 of 96

Generic variance

Animal

Queue<Animal>

Dog

Queue<Dog>

X

69 of 96

Generic variance

/**� * @param Queue<Animal> $queue� */�function helloKitty(Queue $queue): void {� $queue->enqueue(new Cat);�}

70 of 96

Generic variance

/** @var Queue<Dog> $queue */�$queue = new Queue();��helloKitty($queue);

71 of 96

Generic covariance

72 of 96

Generic covariance

Animal

Queue<Animal>

Dog

Queue<Dog>

73 of 96

Generic covariance

/**

* @template-covariant T

*/

final class Queue

{

// ...

}

74 of 96

Generic covariance

/** @var Queue<Dog> $queue */�$queue = new Queue();��printFirstFrom($queue);

75 of 96

Generic covariance

/** @var Queue<Dog> $queue */�$queue = new Queue();��helloKitty($queue);

76 of 96

Generic covariance

/**

* @template T

*/

final class Queue

{

/** @param T $item */

public function enqueue(mixed $item): void

{

$this->items[] = $item;

}

}

ERROR

Template type T is declared as covariant, but occurs in a contravariant position in parameter item of method Queue::enqueue().

77 of 96

Generic covariance

/**� * @param Queue<Animal> $queue� */�function printFirstFrom(Queue $queue): void {� $animal = $queue->dequeue();� var_dump($animal);�}

78 of 96

Generic covariance

/**� * @param Queue<Animal> $queue� */�function helloKitty(Queue $queue): void {� $queue->enqueue(new Cat);�}

79 of 96

Generic contravariance

80 of 96

Generic contravariance

Animal

Queue<Animal>

Dog

Queue<Dog>

81 of 96

Generic contravariance

HasWeight

Comparator<HasWeight>

Animal

Comparator<Animal>

82 of 96

Generic contravariance

/**

* @template T

*/

interface Comparator

{

/**

* @param T $a

* @param T $b

*/

public function compare(mixed $a, mixed $b): int;

}

83 of 96

Generic contravariance

/**

* @implements Comparator<HasWeight>

*/

final class Scales implements Comparator

{

public function compare(mixed $a, mixed $b): int

{

return $a->weight() <=> $b->weight();

}

}

84 of 96

Generic contravariance

/**� * @template T� */�final class Queue�{� /**� * @param Comparator<T> $comparator� */� public function sort(Comparator $comparator): void {}�}

85 of 96

Generic contravariance

/** @var Queue<Animal> $queue */

$queue = new Queue();�$queue->sort(new Scales);

ERROR

Parameter #1 $comparator of method Queue<Animal>::sort() expects Comparator<Animal>, Scales given.

86 of 96

Generic contravariance

/**

* @template-contravariant T

*/

interface Comparator

{

/**

* @param T $a

* @param T $b

*/

public function compare(mixed $a, mixed $b): int;

}

87 of 96

Generic contravariance

/** @var Queue<Animal> $queue */

$queue = new Queue();�$queue->sort(new Scales);

88 of 96

Generic contravariance

/**

* @template-contravariant T

*/

interface Comparator

{

/** @return T */

public function getReferenceValue(): mixed;

}

ERROR

Template type T is declared as contravariant, but occurs in covariant position in return type of method Comparator::getReferenceValue().

89 of 96

🤓 / 🤯 / 🤔

90 of 96

Type projections

91 of 96

Type projections

/**

* @template T

*/

final class Queue

{

// ...� public function dequeue(): mixed { /* ... */ }� public function enqueue(mixed $item): void { /* ... */ }

}

92 of 96

Type projections

/**� * @param Queue<covariant Animal> $queue� */�function printFirstFrom(Queue $queue): void {� $animal = $queue->dequeue();� var_dump($animal);�}

93 of 96

Type projections

/**� * @param Queue<covariant Animal> $queue� */�function helloKitty(Queue $queue): void {� $queue->enqueue(new Cat);}

94 of 96

🤓 / 🤯 / 🤔

95 of 96

🍺

96 of 96

Any questions<?>