Прежде чем перейти к статье, хочу вам представить, экономическую онлайн игру Brave Knights, в которой вы можете играть и зарабатывать. Регистируйтесь, играйте и зарабатывайте!
ObjectManager можно назвать одной из основных концепций, которая лежит в основе построения Magento2, и абсолютно новый если брать в сравнении с Magento1. Если мы вспомним Magento1, то там, для создания нужных нам для работы объектов, мы использовали класс Mage, который предоставлял статические методы для создания разных типов объектов — будь то модели, ресурс-модели, хелперы, или для создания объектов, которые мы хотели иметь в едином экземпляре(метод Mage::getSingleton). При создании Magento2 команда разработчиков отказалась от этой идеи и имплементировали принцип инъекции зависимостей и сервис-контрактов(ServiceContracts). Именно это позволило сделать Magento2 такой гибкой, легко кастомизируемой и тестируемой. Так же наличие функционала построенного вокруг ObjectManager’а делает возможным наличие всего функционала кастомизации поведения системы, который мы можем настраивать посредством конфигурационного файла di.xml.
Если смотреть глобально на функционал, который реализует ObjectManager, то можно сказать, что он является некой реализацией DI container, который в мире PHP представлен в виде PSR-11, хотя сам ObjectManager напрямую не реализует Psr\Container\ContainerInterface (и не имеет метода has, наличие которого предполагает Psr\Container\ContainerInterface). Он является централизованным средством для создания и получения объектов. Наличие такого централизованного класса для генерации необходимых объектов несет в себе следующие преимущества.
- Нам не нужно в ручную инициализировать и менеджить объекты (так же нужно сказать, что ObjectManager используется для генерации объектов внутри классов Factory и Proxy, которые создаются посредством кодогенерации)
- является возможным через настройки прописать какую именно реализацию некоторого интерфейса должен получать класс и использовать принцип инверсии зависимостей
- систему становится легче тестировать
- возможно использовать Proxy-классы и классы фабрики (Factory)
- Экономия ресурсов сервера, так как некоторые из объектов повторно не инициализируются, а берутся из кэша уже созданные до этого объекты (настройка shared)
Если более детально рассмотреть последний пункт, то нужно сказать, что сам класс Magento\Framework\ObjectManager\ObjectManager имеет protected атрибут $_sharedInstances = []. Именно этот атрибут содержит объекты, которые не должны быть созданы более 1 раза и при запросе на их получение просто берет их из этого массива (кэша) по ключу — ключом является полное имя класса, которое включает пространство имен(namespace). Но как именно класс ObjectManager знает какие классы должны быть помещены в этот массив?
Все объекты, которые создаются через ObjectManager по-умолчанию имеют настройку shared=true. Но это поведение можно поменять — для этого служит специальная настройка shared в xml-файле. Для примера можно взять объявление классов слушателей (observer).
<observer name="legacy_model_save"
instance="Magento\Framework\EntityManager\Observer\BeforeEntitySave" shared="false"/>
Такой класс будет инстанциироваться каждый раз как новый объект. Также такая настройка есть у объектов, которые передаются в конструктор через конструкцию type. Как было сказано выше, этот параметр равен true по-умолчанию, чтобы экономить ресурсы сервера.
Сейчас хотелось бы сфокусироваться на процессе инициализации класса ObjectManager и том как он инстанциирует объекты. В этом месте нужно сделать оговорку, что ниже в статье будет рассматриваться процесс применимо к приложению типа Http.
Если посмотреть на файл index.php, то мы можем увидеть, что в нем используется класс Magento\Framework\App\Bootstrap
И сначала вызывается его статический метод create, который принимает 2 параметра:
- адрес корневой директории проекта
- массив параметров — $_SERVER + настройки адресов директорий
Внутри метода create вызывается статический метод createObjectManagerFactory, который инстанциирует объект класса Magento\Framework\App\ObjectManagerFactory и передает его в конструктор класса Bootstrap, где он используется, чтобы создать объект класса Magento\Framework\App\ObjectManager. Именно этот объект и используется в последствии, чтобы инстанциировать все далее используемые объекты. Далее в методе Magento\Framework\App\Http::launch объекту класса ObjectManager задается конфигурация через вызов метода Magento\Framework\App\ObjectManager::configure. Параметром этого метода является уже подготовленный для использования массив настроек с файлов di.xml. Именно благодаря этим настройкам ObjectManager реализует работу таких возможностей как preference, type, virtualType. Сами эти настройки не хранятся в классе ObjectManager — он делегирует их использование классу Magento\Framework\ObjectManager\Config\Config, который реализует Magento\Framework\ObjectManager\ConfigInterface. Этот класс обрабатывает массив настроек с файлов di.xml и объединяет их по типам: preference, type, virtualType и потом позволяет получать их в подготовленном виде. Так же именно этот класс хранит карту preference’ов и отдает ObjectManager’у правильное имя объекта для создания.
Далее в работе приложения используются 2 метода класса ObjectManager: create и get. Разница этих методов состоит в том, что метод create всегда создает новый объект переданного класса (можно сказать, что это реализация паттерна Factory method), а метод get при повторном запросе объекта класса, который уже создавался ранее, просто отдаст его из кэша.
Если рассмотреть детальнее эти методы — то они оба создают объекты через специальную фабрику (класс, который имплементирует интерфейс Magento\Framework\ObjectManager\FactoryInterface), которая в зависимости от режима работы приложения может быть представлена классами, которые находятся в пространстве имен Magento\Framework\ObjectManager\Factory. Такая фабрика получает через механизм рефлексии php аргументы конструктора необходимого класса и рекурсивно их инициализирует. Потом эти подготовленные аргументы передаются в метод Magento\Framework\ObjectManager\Factory\AbstractFactory::createObject, который и возвращает готовый к использованию объект. Далее ObjectManager может или просто вернуть такой объект или сохранить его в кэше для дальнейшего использования, в зависимости от настроек, как было указано выше.
Так же следует обратить внимание на пространство имен Magento\Framework\ObjectManager\Code\Generator. Классы, которые находятся в нем, используются для того, чтобы сгенерировать Factory и Proxy классы, которые передаются в пользовательские классы как аргументы конструктора (Proxy должен задаваться как аргумент конструктора через файл di.xml) и создаются через механизм кодогенерации мадженты. Proxy-классы, как очевидно из названия, используются для того, чтобы уменьшить нагрузку на сервер путем замены оригинального класса на его proxy-класс, который инициализирует оригинальный класс только в том случае, если он вызывается (реализация паттерн Proxy). Классы фабрики используются для создания объектов внутри кастомного кода, обычно это объекты классов доменных моделей. Сами классы Factory реализуют паттерн factory method, имея публичный метод create, который внутри себя вызывает ObjectManager и инстанциирует объекты посредством использования метода Magento\Framework\App\ObjectManager::create.