Generics в PHP: зачем нужны и как использовать с примерами

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

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

Начать курс

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

Современная разработка на PHP требует не только скорости, но и надежности кода. Одним из инструментов, который помогает достичь этой цели, являются Generics (обобщенные типы). Несмотря на то, что PHP — язык с динамической типизацией, с выходом PHP 7.0 и выше, а особенно с PHP 8.0, статическая типизация стала доступна и востребована. Generics позволяют писать более гибкие, переиспользуемые и безопасные структуры данных, такие как коллекции, стеки, очереди и репозитории, без потери информации о типах.

В этой статье мы подробно разберем, что такое Generics в PHP, зачем они нужны, как их правильно использовать на практике, и рассмотрим конкретные примеры кода.

Что такое Generics и зачем они нужны?

Generics (обобщенные типы) — это механизм, позволяющий параметризовать классы, интерфейсы, трейты и функции типом данных. Вместо того чтобы писать отдельную реализацию для каждого типа (например, IntegerList, StringList, UserList), вы создаете один универсальный класс List<T>, где T — это placeholder (заменитель) для конкретного типа, который будет передан при использовании.

Зачем это нужно?

  • Безопасность типов: IDE и статические анализаторы (Psalm, PHPStan) могут проверять, что в коллекцию попадают только элементы одного типа, предотвращая ошибки на этапе разработки.
  • Уменьшение дублирования кода: Вам не нужно писать один и тот же класс для разных типов данных.
  • Улучшение читаемости: Код становится самодокументируемым — сразу видно, какие типы данных ожидаются.
  • Повышение производительности: Хотя PHP сам по себе не использует Generics на уровне виртуальной машины (как Java или C#), статические анализаторы помогают находить потенциальные ошибки до выполнения кода.

Как использовать Generics в PHP? Синтаксис и примеры

Начиная с PHP 8.0, была введена поддержка Generics на уровне аннотаций (docblock) и частично на уровне кода (для variadic arguments). Полноценная поддержка на уровне синтаксиса ожидается в будущих версиях, но уже сейчас можно эффективно использовать Generics с помощью PHPDoc и статических анализаторов.

1. Базовый синтаксис с PHPDoc

Самый распространенный способ — использовать аннотации @template и @param/@return с указанием обобщенного типа.

<?php

/**
 * @template T
 */
class Collection {
    /** @var array<T> */
    private array $items;

    /**
     * @param T $item
     */
    public function add($item): void {
        $this->items[] = $item;
    }

    /**
     * @return T|null
     */
    public function get(int $index) {
        return $this->items[$index] ?? null;
    }
}

// Пример использования
$intCollection = new Collection();
$intCollection->add(42);
// $intCollection->add('string'); // Ошибка статического анализатора

$userCollection = new Collection();
$userCollection->add(new User());
// $userCollection->add(123); // Ошибка
?>

В этом примере @template T объявляет обобщенный тип. @param T $item и @return T|null говорят анализатору, что тип элементов должен быть согласован.

2. Ограничения типов (bounded generics)

Вы можете ограничить, какие типы могут использоваться в качестве параметра. Это делается с помощью @extends или @implements.

<?php

/**
 * @template T of \Stringable
 */
class StringableCollection {
    /** @var array<T> */
    private array $items;

    /**
     * @param T $item
     */
    public function add($item): void {
        $this->items[] = $item;
    }
}

// Можно использовать только классы, реализующие Stringable
class User implements \Stringable {
    public function __toString(): string {
        return 'User';
    }
}

$collection = new StringableCollection();
$collection->add(new User());
// $collection->add(123); // Ошибка - int не реализует Stringable
?>

Здесь @template T of \Stringable означает, что T может быть только типом, который реализует интерфейс \Stringable.

3. Generics для функций и методов

Обобщенные типы работают не только с классами, но и с функциями.

<?php

/**
 * @template T
 * @param T $value
 * @return T
 */
function identity($value) {
    return $value;
}

$result = identity(42); // int
$result2 = identity('hello'); // string
?>

Типичные сценарии использования Generics в PHP

Generics особенно полезны в следующих ситуациях:

  • Коллекции и контейнеры: Collection<User>, Stack<int>, Map<string, Product>. Это самый частый случай.
  • Репозитории: Repository<User> с методами find(int $id): ?User и save(User $user): void.
  • Паттерн "Строитель" (Builder): Builder<Query> может возвращать конкретный тип запроса.
  • Прокси и декораторы: Proxy<T> может оборачивать любой объект, сохраняя его тип.
  • Работа с API: Response<UserData> гарантирует, что ответ будет содержать данные нужной структуры.

Пример: Репозиторий с Generics

<?php

/**
 * @template T
 */
interface RepositoryInterface {
    /**
     * @param int $id
     * @return T|null
     */
    public function find(int $id);
}

/**
 * @implements RepositoryInterface<User>
 */
class UserRepository implements RepositoryInterface {
    public function find(int $id): ?User {
        // Логика поиска
        return new User();
    }
}

// Теперь IDE знает, что find() возвращает User|null
$repo = new UserRepository();
$user =                

Блоги

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