
Это небольшой разбор поста от PHP Foundation: Compile time generics: yay or nay?, пропитанный личным мнением.
Я сторонник того, что пыхе родные дженерики не очень то и нужны.
Джентльменских дженериков вполне хватает.
Мы, джентльмены, верим друг другу на слово: написан дженерик в аннотации — прекрасно! Стат. анализ рассудит.
Однако команда PHP Foundation проделала колоссальную работу в вопросе добавления дженериков в PHP и сейчас хочет получить обратную связь от сообщества, прежде чем вливать огромные ресурсы в продолжение темы.
Знаю и понимаю, что некоторым из нас хочется дженериков настолько, что без разницы, как именно, ведь “что-то” уже лучше, чем “ничего”.

Но давайте попробуем не выкрикивать сразу “ДА!” а сначала разберёмся.
Мономорфизация
Мы привыкли к дженерикам в формате коллекции: есть интерфейс Collection<T>
; создаём коллекцию типа Collection<User>
и понимаем, что из этой коллекции кроме User
нам ничего не вывалится. Для нас, разработчиков, Collection<User>
— что-то на уровне метаданных. А что под капотом?
Мономорфизация — это техника компиляции дженериков, при которой создаётся отдельная специализированная версия класса (или функции) для каждого конкретного типа, с которым дженерик используется.
Применительно к нашей коллекции это означает, что в рантайме будут существовать конкретные типы Collection<User>
, Collection<Post>
и так далее для каждого варианта использования коллекции.
В RFC на текущем этапе заявлена только “ручная мономорфизация”:
разработчику необходимо создавать отдельный класс на каждый подтип коллекции;
записи вида $users = new Collection<User>();
пока не поддерживаются.
interface Collection<T> {}
abstract class BaseCollection<T: Entity> {}
final class Users implements BaseCollection<User> {}
final class Posts implements BaseCollection<Post> {}
final class Comments implements BaseCollection<Comment> {}
Кажется, мы и сейчас примерно так можем делать ?
Однако, в данном случае мы получаем проверку типов во всех методах с T
, ведь PHP будет при компиляции заменять T
в сигнатурах методов на соответствующий класс.
Я почему-то уверен, что никто из нас не захочет заморачиваться и создавать отдельный класс только для того, чтобы добавить типов и обмазаться настоящими дженериками из ядра.
Вопросы и ответы
Дженерики в параметрах
Уже возможно, но в простом варианте (без Union Types):
class DataProcessor
{
public function __construct(private Repository<UserEntity> $repo) {}
}
Вариантность
Базово дженерики подразумеваются инвариантными. Для ковариантности и контрвариантности рассматривается синтаксис из Kotlin и C#:
interface EventProcessor<in Event, out Result, Context>
{
public function process(Event $event): Result;
public function updateContext(Context $context): void;
public function getCurrentContext(): Context;
}
Вывод типов
class Car<Driver>
{
public function __construct(private Driver $driver) {}
}
// PHP понимает, что $car имеет тип Car<StudentDriver>
$car = new Car(new StudentDriver());
Это сложно и не вписывается в концепт, ведь требует дженерики в рантайме.
Трейты
Как трейты будут в это вписываться пока не ясно. Скорее всего как-то так:
trait Tools<T>
{
public function useful(T $param): int { ... }
}
class C
{
use Tools<Book>;
}
однако, подводных камней много.
Перечисления (enum)
Про них опять забыли или просто не написали. А это, поверьте, отдельная головная боль.
Union Types
Можно забыть про Union внутри дженерика Collection<User|Post>
, если нам важна производительность. Но когда дженерик — часть составного типа Repository<UserEntity>|null
— в будущем возможно.
iterable<T>
Этот вид дженериков относится к дженерикам рантайма.
Функции
Любители фасадов сразу захотят дженерики в функции collect()
. Что-ж, теоретически что-то такое должно работать:
function collect<T>(T ...$items): Collection<T> { /* ... */ }
$users = collect<User>(User::fetchAll());
Вложенные дженерики
В статье отсутствует упоминание типов вида Repository<Collection<BlogPost>>
и ограничения глубины вложенности.
На самом деле это интересный вопрос, когда мы говорим о мономорфизации, особенно ручной.
Eval
Ограничения на генерирование дженериков на лету не упоминаются. Вероятно, с этим проблем не будет.
Я уверен, что дай эту имплементацию сообществу, и в Доктрину тут же добавят генерацию классов GenericCollection
под каждую ToMany связь. Больше применять этот подход, вроде, негде.
Когда?
Реализация первой части концепта возможна к версии PHP 8.6.
Мои выводы
Compile-Time дженерики не влияют на производительность в рантайме (почти). Но что, если они добавляют сложности?
Без нормального вывода типов дженерики могут стать необходимым злом и дополнительной когнитивной нагрузкой для новичков и тех, кому они не упёрлись.
Просто представьте, что весь вендор обмазан дженериками. Разработчику теперь необходимо их заполнять в своём коде; либо нам делают вывод типов в рантайме и прощай производительность.
Или переложим эту ответственность на IDE?
Джентльменские же дженерики не приносят вреда тем, кто не в клубе джентльменов и не пользуется стат. анализом.
Мне кажется, что этот концепт не выйдет из эксперименталки: уж больно много минусов. Но если выйдет, то хотелось бы иметь возможность переключать через что-то вроде declare(generics=1)
в стартовом скрипте. Стираемые или отключаемые дженерики — подходящая опция для меня.
Если принимать такой RFC в работу, то только после полного понимания всей дорожной карты: как мы придём к полноценным дженерикам, которые не будут мешать и будут помогать.
Добавление подобного рода функционала — путь в один конец. Назад будет уже не вертануть.
И самое главное в таком деле — не допускать разработчиков Symfony до ядра PHP. Хватило уже сбрасываемых readonly свойств.
Другие мои статьи, не вписывающиеся в формат хабра, можно найти здесь или в Телеграм-канале PHP Fart Time.
Также вам может быть интересно:
Мнение Романа Пронского (исполнительный директор PHP Foundation)
Мнение Daniil Gentili (мейнтейнер Psalm)
Сейчас compile-time дженерики находятся в экспериментальной стадии и работы еще немерено.
А вы проголосовали бы за то, чтобы втащить такие дженерики?
Комментарии (3)
ForsakenROX
05.08.2025 10:15А какие ещё могут быть дженерики ? в первую очередь нужно думать о бенефитах которые это всё предоставляет, а исходя из прочтения оригинальных статей я их для себя так и не смог вывести. Возможно сообществу стоило сместить акцент именно на это, например тотальная типизация могла бы быть использована для кодогенерации в какой нибудь Си. Но пока что это выглядит как тотальное усложнение без видимых преимуществ (о чем критики и говорят упоминая стат анализаторы которые не дают импакта на производительность). Надо продолжать работать, например над библиотеками для ML о чем Пронский и переживает за уход разрабов из стэка в питонисты например. Только тогда комьюнити будет расти и набираться свежей крови.
Пока что я против, по крайней мере до момента когда не появится вменяемый роадмэп который наглядно объяснит где это можно использовать и принесёт преимущества
eee
По мне так дженериков через аннотации хватает, разве что можно добавить native-аннотации типа
#[Template]
#[Extends]
#[Implements]
, чтобы IDE-шкам легче было индексировать.