Generics в PHP: зачем нужны и как использовать для начинающих

Онлайн-тренажер Python для начинающих

Изучайте Python легко и без перегрузки теорией. Решайте практические задачи с автоматической проверкой, получайте подсказки на русском языке и пишите код прямо в браузере — без необходимости что-либо устанавливать.

Начать курс

Введение в 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():                

Блоги

Рекомендаций по книгам