Generics in PHP: The Typing You've Been Missing

Online Python Trainer for Beginners

Learn Python easily without overwhelming theory. Solve practical tasks with automatic checking, get hints in Russian, and write code directly in your browser — no installation required.

Start Course

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 runtime


This 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(); //

Blogs

Book Recommendations