В PHP 8.4 добавили ленивые объекты и прокси (RFC , дока).
Давайте пройдёмся по RFC.
Автор ссылается на то, что отложенная инициализация объектов в PHP уже используется и это важный механизм, например: ленивые сервисы в DI и ленивые связи в ORM.
Сделать ленивость непросто и подобные вещи были реализованы в ocramius/proxy-manager и symfony/var-exporter. Реализация проксей костылями накладывает ограничения и штрафы на производительность.
RFC предлагает внедрить такие ленивые объекты, как Ghost Object и State Proxy в ядро.
<aside> 📌
Начало бодрое!
Когда я пилил Cycle ORM v2, тоже стояла задача сделать прокси-объекты. Я изучил ocramius/proxy-manager, нашёл там кучу багов и пришёл к выводу, что это неюзабельное говно. Я не могу подобрать ни одной причины, почему в доктрине или где либо ещё это использовалось… Может Ocramius был соавтором докрины? 🤔
В итоге мы написали свои прокси, которые даже работают быстрее. Но ограничения те же: нельзя финалить классы сущностей, и какие механизмы PHP ломаются при использовании проксей, типа $entity->relation[] = $item;
.
В Spiral DI тоже есть прокси на области видимости. Там мы отстранились от классов и просто завязались на интерфейсы.
</aside>
Ленивый объект — обычный объект, который инициализируется потом: при первом обращении к его свойствам или методам. Создаётся через рефлексию с передачей функции инициализации.
Отличия предлагаемых ленивых объектов:
Ghost Object внешне неотличим от обычного объекта: его ID и имя класса — всё выглядит также, как и обычно. Объект просто инициализируется позже, при обращении. При этом в функцию инициализации сразу попадает объект, созданный без вызова конструктора, как если бы был вызван метод рефлексии ReflectionClass::newInstanceWithoutConstructor()
, остаётся просто дёрнуть конструктор.
State Proxy — это уже не тот же самый объект, который в итоге лениво создаётся, у них даже ID разный. Задача инициализатора в этом случае — создать и вернуть объект, а прокся будет проксировать на него всё взаимодействие.
Для ленивых объектов есть возможность закинуть некоторые проперти заранее, обращение к которым не будут вызывать инициализацию.
<aside> 📌
Мне пока всё нравится: Ghost Object дают возможность легально отложить инициализацию объекта, а с Proxy можно максимально элегантно и круто завернуть любой класс, в том числе финальный.
Даже в Java нет такой крутотеньки!
</aside>
Далее секция Proposal, в которой можно посмотреть самое интересное: что добавится в рефлексию и как оно будет использовано. Я не буду обозревать нововведения в ReflectionProperty
и очевидные методы в ReflectionClass
типа isUninitializedLazyObject()
: с ними всё плюс-минус понятно при беглом ознакомлении с RFC. Возьму то, что интересно.