Введение в Generics в PHP: что это и зачем нужно
Если вы только начинаете изучать PHP или уже пишете на нём какое-то время, то наверняка сталкивались с ситуацией, когда функция или класс должны работать с разными типами данных. Например, вы создаёте коллекцию, которая может хранить и числа, и строки, и объекты. Без Generics (обобщённых типов) вам приходится либо писать отдельные классы для каждого типа, либо использовать смешанные типы (mixed), что снижает безопасность кода.
Generics — это механизм, который позволяет создавать классы, интерфейсы и функции, работающие с различными типами данных, сохраняя при этом строгую типизацию. В PHP поддержка Generics появилась начиная с версии 8.0, а в PHP 8.1 и 8.2 была значительно улучшена. В этой статье мы разберём, зачем они нужны, как их использовать и какие преимущества они дают.
Зачем нужны Generics в PHP?
Проблема отсутствия обобщённых типов
Представьте, что вы пишете класс для работы со списком элементов. Без Generics вам придётся использовать тип mixed или писать отдельные классы для каждого типа данных:
<?php
// Без Generics — небезопасно
class SimpleList {
private array $items = [];
public function add($item): void {
$this->items[] = $item;
}
public function get(int $index): mixed {
return $this->items[$index] ?? null;
}
}
// Использование
$list = new SimpleList();
$list->add(42);
$list->add('строка'); // Ошибка не возникнет, но логика нарушена
$value = $list->get(0); // Тип mixed — IDE не подскажет, что это int
?>
Как видите, такой подход приводит к потенциальным ошибкам: вы можете случайно добавить строку туда, где ожидаются только числа, и IDE не сможет предупредить вас об этом.
Преимущества Generics
- Типобезопасность: компилятор и IDE проверяют соответствие типов на этапе написания кода.
- Повторное использование: один класс или функция работают с любыми типами данных.
- Читаемость кода: сразу понятно, какие типы ожидаются.
- Меньше ошибок: исключаются случайные несоответствия типов.
С Generics тот же класс списка становится безопасным и универсальным:
<?php
// С Generics — безопасно
class GenericList {
private array $items = [];
public function add(T $item): void {
$this->items[] = $item;
}
public function get(int $index): ?T {
return $this->items[$index] ?? null;
}
}
// Использование
$intList = new GenericList();
$intList->add(42);
// $intList->add('строка'); // Ошибка типов!
$stringList = new GenericList();
$stringList->add('Привет');
$value = $stringList->get(0); // IDE знает, что это string
?>
Как использовать Generics в PHP
Базовый синтаксис
Generics в PHP объявляются с помощью угловых скобок <> и буквы T (от Type) — это общепринятое имя для переменной типа. Вы можете использовать любые имена, но T — стандарт.
Пример с классом:
<?php
class Container {
private T $value;
public function __construct(T $value) {
$this->value = $value;
}
public function getValue(): T {
return $this->value;
}
}
$intContainer = new Container(100);
$stringContainer = new Container('Hello');
?>
Ограничения типов
Иногда нужно, чтобы тип параметра соответствовал определённому интерфейсу или классу. Для этого используются ограничения (bounds):
<?php
interface Printable {
public function print(): string;
}
class Document implements Printable {
public function print(): string {
return 'Документ';
}
}
// Ограничение: T должен реализовывать Printable
class Printer {
public function printItem(T $item): void {
echo $item->print();
}
}
$printer = new Printer();
$printer->printItem(new Document()); // Работает
// $printer->printItem(123); // Ошибка: int не реализует Printable
?>
Generics в функциях
Обобщённые типы можно использовать не только в классах, но и в обычных функциях:
<?php
function identity(T $value): T {
return $value;
}
echo identity(42); // int
echo identity('текст'); // string
?>
Множественные параметры типа
Если нужно работать с несколькими типами, используйте запятые:
<?php
class Pair {
public function __construct(
private T $first,
private U $second
) {}
public function getFirst(): T {
return $this->first;
}
public function getSecond(): U {
return $this->second;
}
}
$pair = new Pair(1, 'один');
echo $pair->getFirst(); // int
echo $pair->getSecond(); // string
?>
Практические примеры использования Generics
Коллекции и репозитории
Одно из самых частых применений Generics — создание типобезопасных коллекций:
<?php
class Collection {
private array $items = [];
public function add(T $item): void {
$this->items[] = $item;
}
public function filter(callable $callback): Collection {
$result = new Collection();
foreach ($this->items as $item) {
if ($callback($item)) {
$result->add($item);
}
}
return $result;
}
public function all(): array {
return $this->items;
}
}
// Использование с конкретным типом
$userCollection = new Collection();
$userCollection->add(new User('Alice'));
$userCollection->add(new User('Bob'));
$filtered = $userCollection->filter(fn(User $user) => $user->name !== 'Alice');
?>
DTO и Value Objects
Generics отлично подходят для создания объектов-значений (Value Objects):
<?php
class Result {
private function __construct(
private readonly bool $success,
private readonly mixed $data,
private readonly ?string $error
) {}
public static function success(T $data): self {
return new self(true, $data, null);
}
public static function error(string $error): self {
return new