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

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

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

Начать курс

Введение: что такое Generics и почему они важны в PHP

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

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

Зачем нужны Generics в PHP?

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

Вот основные преимущества использования Generics:

  • Типобезопасность — ошибки типов выявляются на этапе компиляции, а не в рантайме.
  • Переиспользование кода — одна функция или класс работают с любыми типами данных.
  • Читаемость — код становится самодокументируемым: сразу видно, какие типы принимаются и возвращаются.
  • Интеграция с IDE — современные редакторы (PhpStorm, VS Code) лучше понимают код с Generics, предлагая автодополнение и подсказки.

Как работают Generics в PHP: синтаксис и основные конструкции

В PHP Generics реализованы через шаблоны типов, которые задаются в угловых скобках <T>. Буква T — это условное обозначение типа, который будет подставлен при вызове функции или создании объекта. Вы можете использовать любые имена: T, K, V или осмысленные названия.

1. Обобщённые функции

Рассмотрим простейший пример — функцию, которая возвращает первый элемент массива:

<?php

function firstElement(array $items): mixed {
    return $items[0] ?? null;
}

// Использование
$numbers = [1, 2, 3];
$first = firstElement($numbers); // тип mixed, IDE не знает, что это int

С Generics мы можем указать, что функция работает с массивом элементов любого типа, но возвращает именно тот тип, который был передан:

<?php

/**
 * @template T
 * @param array<T> $items
 * @return T|null
 */
function firstElement(array $items): mixed {
    return $items[0] ?? null;
}

// Теперь IDE понимает, что $first — int|null
$first = firstElement([1, 2, 3]);

Обратите внимание: в PHP 8.0+ Generics поддерживаются через PHPDoc-аннотации. Нативная поддержка (без комментариев) пока отсутствует, но это стандарт де-факто для современных проектов.

2. Обобщённые классы

Самый частый сценарий использования Generics — это классы-коллекции. Создадим простой класс Collection, который может хранить элементы любого типа:

<?php

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

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

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

    /**
     * @return array<T>
     */
    public function all(): array {
        return $this->items;
    }
}

// Использование
$intCollection = new Collection<int>();
$intCollection->add(10);
$intCollection->add(20);
// $intCollection->add('string'); // Ошибка типа, если IDE проверяет

$stringCollection = new Collection<string>();
$stringCollection->add('Hello');
$stringCollection->add('World');

Теперь, если вы попытаетесь добавить строку в коллекцию целых чисел, среда разработки (IDE) укажет на ошибку до запуска кода.

3. Ограничения типов (Bounds)

Иногда нужно ограничить допустимые типы. Например, мы хотим, чтобы коллекция работала только с объектами, реализующими интерфейс Comparable:

<?php

interface Comparable {
    public function compareTo(object $other): int;
}

/**
 * @template T of Comparable
 */
class SortedCollection {
    /** @var array<T> */
    private array $items = [];

    /**
     * @param T $item
     */
    public function add(Comparable $item): void {
        $this->items[] = $item;
        usort($this->items, function (Comparable $a, Comparable $b) {
            return $a->compareTo($b);
        });
    }
}

Здесь @template T of Comparable означает, что T должен быть подтипом Comparable. Это гарантирует, что в коллекцию попадут только объекты с нужным методом.

Практические примеры использования Generics

1. Репозитории и Data Mapper

В архитектуре приложений часто используются паттерны Repository и Data Mapper. Generics позволяют создать единый интерфейс для работы с разными сущностями:

<?php

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

    /**
     * @return array<T>
     */
    public function findAll(): array;

    /**
     * @param T $entity
     */
    public function save(mixed $entity): void;
}

/**
 * @implements Repository<User>
 */
class UserRepository implements Repository {
    public function find(int $id): mixed {
        // логика поиска пользователя
    }

    public function findAll(): array {
        // логика получения всех пользователей
    }

    public function save(mixed $entity): void {
        // логика сохранения
    }
}

2. Сервисы с Dependency Injection

Generics отлично работают с контейнерами внедрения зависимостей. Например, можно создать универсальный серви

Блоги

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