Введение в Generics в PHP
В мире современной разработки на PHP всё больше внимания уделяется строгой типизации и безопасности кода. Одним из мощнейших инструментов, появившихся в PHP 8.0, стали Generics (обобщённые типы). Если вы новичок в PHP и слышите этот термин впервые — не пугайтесь. В этой статье мы простым языком разберём, что такое Generics, зачем они нужны и как их использовать на практике.
Представьте ситуацию: вы пишете функцию, которая должна работать с разными типами данных — числами, строками, объектами. Без дженериков вам пришлось бы либо использовать mixed (что снижает безопасность), либо писать отдельные функции для каждого типа. Generics решают эту проблему элегантно и эффективно.
В этой статье мы рассмотрим:
- Что такое Generics и их основные преимущества
- Как объявлять обобщённые классы и функции
- Реальные примеры использования в проектах
- Типичные ошибки новичков и как их избежать
Что такое Generics и зачем они нужны?
Проблема без Generics
Допустим, вы создаёте класс для работы с коллекцией элементов. Без дженериков вам придётся использовать тип mixed или писать специализированные классы для каждого типа данных:
<?php
// Плохой подход — используем mixed
class Collection {
private array $items = [];
public function add(mixed $item): void {
$this->items[] = $item;
}
public function get(int $index): mixed {
return $this->items[$index] ?? null;
}
}
// Теперь можно случайно добавить строку в коллекцию чисел
$numbers = new Collection();
$numbers->add(42);
$numbers->add('строка'); // Ошибка не возникнет, но логика нарушена
?>
Этот код работает, но он небезопасен. Вы не знаете, какие типы данных хранятся в коллекции, и можете получить ошибку во время выполнения.
Решение с Generics
Generics позволяют указать, с какими типами данных будет работать ваш класс, функция или интерфейс. Синтаксис похож на угловые скобки <T>, где T — это placeholder (заполнитель) для конкретного типа:
<?php
// Хороший подход — используем Generics
class Collection<T> {
private array $items = [];
public function add(T $item): void {
$this->items[] = $item;
}
public function get(int $index): ?T {
return $this->items[$index] ?? null;
}
}
// Теперь PHP будет проверять типы
$numbers = new Collection<int>();
$numbers->add(42);
// $numbers->add('строка'); // Fatal error: Uncaught TypeError
?>
Теперь компилятор PHP знает, что коллекция $numbers может содержать только целые числа. Любая попытка добавить строку вызовет ошибку на этапе разработки.
Основные преимущества Generics
- Безопасность типов (Type Safety) — ошибки обнаруживаются на этапе компиляции, а не во время выполнения.
- Читаемость кода — сразу понятно, с какими типами работает функция или класс.
- Уменьшение дублирования — один обобщённый класс заменяет десятки специализированных.
- Улучшенная поддержка IDE — автодополнение и подсказки работают корректно.
Как использовать Generics в PHP: практические примеры
1. Обобщённые классы
Самый частый сценарий — создание коллекций, стеков, очередей и других структур данных. Рассмотрим пример стека:
<?php
class Stack<T> {
private array $stack = [];
public function push(T $item): void {
$this->stack[] = $item;
}
public function pop(): ?T {
return array_pop($this->stack);
}
public function isEmpty(): bool {
return empty($this->stack);
}
}
// Использование
$stringStack = new Stack<string>();
$stringStack->push('Hello');
$stringStack->push('World');
echo $stringStack->pop(); // World
// $stringStack->push(123); // Ошибка!
?>
2. Обобщённые функции
Generics работают не только с классами, но и с функциями. Например, функция для поиска элемента в массиве:
<?php
function findFirst<T>(array $items, callable $predicate): ?T {
foreach ($items as $item) {
if ($predicate($item)) {
return $item;
}
}
return null;
}
// Используем с разными типами
$numbers = [1, 2, 3, 4, 5];
$result = findFirst($numbers, fn($n) => $n > 3);
echo $result; // 4
$names = ['Alice', 'Bob', 'Charlie'];
$found = findFirst($names, fn($name) => str_starts_with($name, 'C'));
echo $found; // Charlie
?>
3. Ограничения типов (Constraints)
Иногда нужно ограничить дженерик только определёнными типами. Например, только объектами, реализующими интерфейс Comparable:
<?php
interface Comparable {
public function compareTo($other): int;
}
class Box<T extends Comparable> {
private T $item;
public function __construct(T $item) {
$this->item = $item;
}
public function isGreaterThan(T $other): bool {
return $this->item->compareTo($other) > 0;
}
}
// Пример реализации Comparable для чисел
class Number implements Comparable {
public function __construct(private int $value) {}
public function compareTo($other): int {
return $this->value <=> $other->value;
}
}
$box1 = new Box(new Number(10));
$box2 = new Box(new Number(5));
var_dump($box1->isGreaterThan($box2)); // true
?>
4. Множественные дженерики
Можно использовать несколько типов одновременно. Например, класс Pair для хранения пары значений разных типов:
<?php
class Pair<K, V> {
public function __construct(
private K $key,
private V $value
) {}
public function getKey():