Introduction: Why PHP Developers Dream of Generics
If you've ever written code in Java, C#, or TypeScript and then returned to PHP, you've probably felt a slight disappointment. Those languages have an elegant system of generics that allows you to create flexible yet type-safe data structures. In PHP, however, "typing anarchy" reigned for a long time: arrays could contain anything, and IDEs only highlighted the most obvious errors.
But times are changing. Starting with PHP 7.0 and especially in the era of PHP 8.0/8.1/8.2, the community has found a way to use Generics in PHP. Yes, they still don't exist at the language syntax level (and most likely won't in the coming years), but that doesn't mean you can't enjoy all the benefits of generic programming today.
In this article, we'll break down how typing in PHP with Generics works, what tools exist for this (PHPStan, Psalm), and how to write code that is both flexible and safe. Buckle up — it's going to be intense!
What Are Generics and Why Are They Needed in PHP?
Generics are a mechanism that allows you to parameterize classes, interfaces, and functions with types. Instead of writing a separate implementation for each data type, you create one universal one and substitute the specific type when using it.
Imagine you're writing a class for a Queue. Without generics, you can only store one data type in it or use mixed (which kills all typing). With Generics, you can say: "This is a queue of integers" or "This is a queue of User objects."
The Problem of "Raw" Typing
Look at this typical PHP code:
<?php
class Collection { private array $items = [];
public function add($item): void { $this->items[] = $item; }
public function get(int $index) { return $this->items[$index] ?? null; }}
$users = new Collection();$users->add(new User('Alice'));$users->add(new User('Bob'));$users->add('this is a string, not a user'); // Error! But PHP will stay silent
$user = $users->get(2); // Will return a string, IDE won't suspect anything$user->getName(); // Fatal error at runtimeThis is a classic PHP developer problem: lack of type control at the code writing stage. You only find out about the error at runtime, when the user has already clicked the button. Generics solve this problem at its root.
Generics in PHP: How It Works in Practice
Since native Generics syntax is absent in PHP, we use PHPDoc annotations that are processed by static analyzers — PHPStan and Psalm. These tools check code for compliance with declared types, effectively emulating how generics work.
Basic Syntax: Template Types
Suppose we want to make our collection type-safe. Here's what it looks like using Generics via PHPDoc:
<?php
/** * @template T */class GenericCollection { /** @var array<int, T> */ private array $items = [];
/** * @param T $item */ public function add($item): void { $this->items[] = $item; }
/** * @param int $index * @return T|null */ public function get(int $index) { return $this->items[$index] ?? null; }}
// Now let's use it:/** @var GenericCollection<User> $users */$users = new GenericCollection();$users->add(new User('Alice'));$users->add('string'); // PHPStan will throw an error: Expected type User, got string
$user = $users->get(0); // PHPStan knows this is Userecho $user->getName(); //