# Templating Docblocks allow you to tell Psalm some simple information about how your code works. For example `@return int` in a function return type tells Psalm that a function should return an `int` and `@return MyContainer` tells Psalm that a function should return an instance of a user-defined class `MyContainer`. In either case, Psalm can check that the function actually returns those types _and_ that anything calling that function uses its returned value properly. Templated types allow you to tell Psalm even more information about how your code works. Let's look at a simple class `MyContainer`: ```php value = $value; } public function getValue() { return $this->value; } } ``` When Psalm handles the return type of `$my_container->getValue()` it doesn't know what it's getting out, because the value can be arbitrary. Templated annotations provide us with a workaround - we can define a generic/templated param `T` that is a placeholder for the value inside `MyContainer`: ```php value = $value; } /** @return T */ public function getValue() { return $this->value; } } ``` Now we can substitute values for that templated param when we reference `MyContainer` in docblocks e.g. `@return MyContainer`. This tells Psalm to substitute `T` for `int` when evaluating that return type, effectively treating it as a class that looks like ```php value = $value; } /** @return int */ public function getValue() { return $this->value; } } ``` This pattern can be used in large number of different situations like mocking, collections, iterators and loading arbitrary objects. Psalm has a large number of annotations to make it easy to use templated types in your codebase. ## `@template` The `@template` tag allows classes and functions to declare a generic type parameter. As a very simple example, this function returns whatever is passed in: ```php $arr * @param array $arr2 * @return array */ function array_combine(array $arr, array $arr2) {} ``` ### Notes - `@template` tag order matters for class docblocks, as they dictate the order in which those generic parameters are referenced in docblocks. - The names of your templated types (e.g. `TKey`, `TValue` don't matter outside the scope of the class or function in which they're declared. ## @param class-string<T> Psalm also allows you to parameterize class types ```php $class * @return T */ function instantiator(string $class) { return new $class(); } class Foo {} $a = instantiator(Foo::class); // Psalm knows the result is an object of type Foo ``` ## Template inheritance Psalm allows you to extend templated classes with `@extends`/`@template-extends`: ```php */ class ChildClass extends ParentClass {} ``` similarly you can implement interfaces with `@implements`/`@template-implements` ```php */ class Foo implements IFoo {} ``` and import traits with `@use`/`@template-use` ```php */ use MyTrait; } ``` You can also extend one templated class with another, e.g. ```php */ class ChildClass extends ParentClass {} ``` ## Template constraints You can use `@template of ` to restrict input. For example, to restrict to a given class you can use ```php */ function makeArray($t) { return [$t]; } $a = makeArray(new Foo()); // typed as array $b = makeArray(new FooChild()); // typed as array $c = makeArray(new stdClass()); // type error ``` Templated types aren't limited to key-value pairs, and you can re-use templates across multiple arguments of a template-supporting type: ```php */ abstract class Foo implements IteratorAggregate { /** * @var int */ protected $rand_min; /** * @var int */ protected $rand_max; public function __construct(int $rand_min, int $rand_max) { $this->rand_min = $rand_min; $this->rand_max = $rand_max; } /** * @return Generator */ public function getIterator() : Generator { $j = random_int($this->rand_min, $this->rand_max); for($i = $this->rand_min; $i <= $j; $i += 1) { yield $this->getFuzzyType($i) => $i ** $i; } return $this->getFuzzyType($j); } /** * @return T0 */ abstract protected function getFuzzyType(int $i); } /** * @template-extends Foo */ class Bar extends Foo { protected function getFuzzyType(int $i) : int { return $i; } } /** * @template-extends Foo */ class Baz extends Foo { protected function getFuzzyType(int $i) : string { return static::class . '[' . $i . ']'; } } ``` ## Template covariance Imagine you have code like this: ```php */ public array $list; /** * @param array $list */ public function __construct(array $list) { $this->list = $list; } /** * @param T $t */ public function add($t) : void { $this->list[] = $t; } } /** * @param Collection $collection */ function addAnimal(Collection $collection) : void { $collection->add(new Cat()); } /** * @param Collection $dog_collection */ function takesDogList(Collection $dog_collection) : void { addAnimal($dog_collection); } ``` That last call `addAnimal($dog_collection)` breaks the type of the collection – suddenly a collection of dogs becomes a collection of dogs _or_ cats. That is bad. To prevent this, Psalm emits an error when calling `addAnimal($dog_collection)` saying "addAnimal expects a `Collection`, but `Collection` was passed". If you haven't encountered this rule before it's probably confusing to you – any function that accepted an `Animal` would be happy to accept a subtype thereof. But as we see in the example above, doing so can lead to problems. But there are also times where it's perfectly safe to pass template param subtypes: ```php */ public array $list = []; } /** * @param Collection $collection */ function getNoises(Collection $collection) : void { foreach ($collection->list as $animal) { echo $animal->getNoise(); } } /** * @param Collection $dog_collection */ function takesDogList(Collection $dog_collection) : void { getNoises($dog_collection); } ``` Here we're not doing anything bad – we're just iterating over an array of objects. But Psalm still gives that same basic error – "getNoises expects a `Collection`, but `Collection` was passed". We can tell Psalm that it's safe to pass subtypes for the templated param `T` by using the annotation `@template-covariant T`: ```php */ public array $list = []; } ``` Doing this for the above example produces no errors: [https://psalm.dev/r/5254af7a8b](https://psalm.dev/r/5254af7a8b) But `@template-covariant` doesn't get rid of _all_ errors – if you add it to the first example, you get a new error – [https://psalm.dev/r/0fcd699231](https://psalm.dev/r/0fcd699231) – complaining that you're attempting to use a covariant template parameter for function input. That’s no good, as it means you're likely altering the collection somehow (which is, again, a violation). ### But what about immutability? Psalm has [comprehensive support for declaring functional immutability](https://psalm.dev/articles/immutability-and-beyond). If we make sure that the class is immutable, we can declare a class with an `add` method that still takes a covariant param as input, but which does not modify the collection at all, instead returning a new one: ```php */ public array $list = []; /** * @param array $list */ public function __construct(array $list) { $this->list = $list; } /** * @param T $t * @return Collection */ public function add($t) : Collection { return new Collection(array_merge($this->list, [$t])); } } ``` This is perfectly valid, and Psalm won't complain. ## Builtin templated classes and interfaces Psalm has support for a number of builtin classes and interfaces that you can extend/implement in your own code. - `interface Traversable` - `interface ArrayAccess` - `interface IteratorAggregate extends Traversable` - `interface Iterator extends Traversable` - `interface SeekableIterator extends Iterator` - `class Generator extends Traversable` - `class ArrayObject implements IteratorAggregate, ArrayAccess` - `class ArrayIterator implements SeekableIterator, ArrayAccess` - `class DOMNodeList implements Traversable` - `class SplDoublyLinkedList implements Iterator, ArrayAccess` - `class SplQueue extends SplDoublyLinkedList`