Процесс создания объектов в Magento 2 включает в себя некоторые особенности, присущие именно Magento 2 и связанные с автоматической генерацией кода при определенных условиях (proxies, factories и interceptors). С interceptor'ами для меня было все более-менее понятно, а вот назначение двух других типов генерируемых классов было скрыто туманом непонимания. И если относительно фабрик у меня туман пока еще остается, то вот относительно прокси-классов — туман развеялся.
Предположим, что мы делаем команду для выполнения каких-либо действий в режиме CLI. Предположим, что мы используем в этой команде имплементацию интерфейса \Magento\Quote\Api\CartManagementInterface
, которую нам передает в конструктор ObjectManager:
public function __construct(\Magento\Quote\Api\CartManagementInterface $cartManagement) {}
Мы можем не использовать имплементацию \Magento\Quote\Api\CartManagementInterface
напрямую в нашей команде, эта имплементация может подтягиваться косвенно — наша команда зависит от \ClassA
, который зависит от \ClassB
, который зависит от ..., который зависит от \Magento\Quote\Api\CartManagementInterface
.
Тем не менее, если в нашей команде есть прямая или косвенная зависимость от default'ой имплементации интерфейса \Magento\Quote\Api\CartManagementInterface
, то при попытке запустить команду, например:
$ ./bin/magento list
мы вывалимся по ошибке
[Magento\Framework\Exception\SessionException]
Area code not set: Area code must be set before starting a session.
Это происходит потому, что перед началом работы ./bin/magento
создает все команды, которые прописаны в etc/di.xml
дескрипторах. Для создания нашей команды ObjectManager создает для нее класс \Magento\Quote\Model\QuoteManagement
, имплементирующий интерфейс \Magento\Quote\Api\CartManagementInterface
. В процессе создания зависимостей для этого класса ObjectManager доходит до создания зависимости \Magento\Framework\Session\SessionManager
в конструкторе которого есть строка:
$this->start();
и если по каким-то причинам (например, Area code not set) сессия не стартует, то выбрасывается исключение и выполнение CLI-команды аварийно завершается. И совершенно не важно, что мы не собирались использовать сессии при выполнении нашей команды (или не подозревали о том, что создание чего-то там, имплементирующего нужный нам интерфейс, вылетит по причине того, что для создания этого "чего-то там" нужен ресурс, который в данном режиме непроинициирован).
Разработчики Magento 2, столкнувшись с таким некошерным поведением объектов в процессе их создания ObjectManager'ом пришли к простому решению — прокси-классы. Достаточно указать в конструкторе зависимость не от \Magento\Quote\Api\CartManagementInterface
, а от \Magento\Quote\Api\CartManagementInterface\Proxy
, и при создании вашего объекта в него будет передаваться не непосредственная имплементация интерфейса, а класс-декоратор (генерируется автоматически), который и создаст непосредственную имплементацию при первом же использовании какого-либо имплементируемого метода.
Другими словами, достаточно дописать после имени интерфейса или класса \Proxy
:
public function __construct(\Magento\Quote\Api\CartManagementInterface\Proxy $cartManagement) {}
как проблема с запуском команды решается (если не решается — почистите нагенеренное: rm -fr ./var/cache/* ./var/generation/*
). \ClassA\Proxy
, ClassB\Proxy
,…, \Magento\Quote\Api\CartManagementInterface\Proxy
— выбирай уровень по душе.
У Алана Сторма есть хорошая статья "Magento 2 Object Manager: Proxy Objects" (у него вообще, много хороших статей по Magento 2), но из нее мне не совсем было понятно, для чего именно создавались Proxy-декораторы в Magento 2 и какова сфера их применения. Только после того, как я столкнулся с невозможностью перевести свое приложение в production mode
$ ./bin/magento deploy:mode:set production
из-за предательского поведения некоторых конструкторов, я несколько прояснил для себя сферу применения прокси-классов в Magento 2.
Alkar778
1. Proxy-классы предназначены для lazy loading. Для того, что бы не подымать зависимости класса сразу, а только по требованию.
2. В некоторых места Proxy-классы используются для решения circular dependency.
3. Затягивать напрямую в конструктор Proxy-класс это bad practice. Прокси стоит прокидовать в обьект только через di.xml, потому что обьект может быть сконфигурырован и реальным классом и вследствие чего ты получаешь фатал.
Автогенерируемые фабрики.
Представь ситуацию когда тебе необходимо создать обьект, что ты будешь делать? Просто затягивать обьект как зависимость? Выглядит нормально. А теперь вдруг необходимо добавить кастомную логику по инициализированию обьекта, в этом случае ты просто возьмешь сгенерированную фабрику и скопируешь себе в модуль и добавишь туда свою логику. И тебе не нужно будет искать и рефакторить 100500 мест где ты напрямую затягивал пустой обьект.