This one deserves a longer post, but my schedule is not forgiving. This assumes you’ve read and understand the concepts from my Magento object manager series.
Many Magento provided classes have a special context object in their constructor.
#File: vendor/magento/module-catalog/Controller/Product/View.php
//...
use MagentoFrameworkAppActionContext;
//...
public function __construct(
Context $context,
MagentoCatalogHelperProductView $viewHelper,
MagentoFrameworkControllerResultForwardFactory $resultForwardFactory,
PageFactory $resultPageFactory
) {
$this->viewHelper = $viewHelper;
$this->resultForwardFactory = $resultForwardFactory;
$this->resultPageFactory = $resultPageFactory;
parent::__construct($context);
}
These objects are there to help keep the number of objects injected in a constructor to a minimum. If you look at the source of this controller action context object, you see it’s just a bunch of getters and setters for other injected objects.
#File: vendor/magento/framework/App/Action/Context.php
//...
public function __construct(
MagentoFrameworkAppRequestInterface $request,
MagentoFrameworkAppResponseInterface $response,
MagentoFrameworkObjectManagerInterface $objectManager,
MagentoFrameworkEventManagerInterface $eventManager,
MagentoFrameworkUrlInterface $url,
MagentoFrameworkAppResponseRedirectInterface $redirect,
MagentoFrameworkAppActionFlag $actionFlag,
MagentoFrameworkAppViewInterface $view,
MagentoFrameworkMessageManagerInterface $messageManager,
MagentoFrameworkControllerResultRedirectFactory $resultRedirectFactory,
ResultFactory $resultFactory
) {
$this->_request = $request;
$this->_response = $response;
$this->_objectManager = $objectManager;
$this->_eventManager = $eventManager;
$this->_url = $url;
$this->_redirect = $redirect;
$this->_actionFlag = $actionFlag;
$this->_view = $view;
$this->messageManager = $messageManager;
$this->resultRedirectFactory = $resultRedirectFactory;
$this->resultFactory = $resultFactory;
}
//...
/**
* @return MagentoFrameworkEventManagerInterface
*/
public function getEventManager()
{
return $this->_eventManager;
}
/**
* @return MagentoFrameworkAppViewInterface
*/
public function getView()
{
return $this->_view;
}
By using a context object, Magento core developers ensure we don’t need to inject each and every one of these objects individually in out own controllers. If we need one, we can just grab it, and then pass the context object to the parent constructor so the ancestor classes can do the same
public function __construct(
Context $context
) {
$eventManager = $context->getEventManager();
//do something with the event manager here
parent::__construct($context);
}
There’s one caveat to this. When you deploy Magento to production, you need to run a command named setup:di:compile
. This pre-generates a number of a classes and serializes dependency injection information. Its purpose is to improve the performance of the application in production, and reduce the surface area of a code generation based attack vector.
This command also performs some rudimentary code validation, which includes checking your objects to make sure they have not injected something that’s already available in a context object. i.e., if you created a constructor that looked like this
use MagentoFrameworkAppActionContext;
//...
public function __construct(
Context $context,
MagentoFrameworkEventManagerInterface $eventManager
) {
$this->eventManager = $eventManager;
//do something with the event manager here
parent::__construct($context);
}
The setup:di:command
would fail, and complain. It wants your code to look like this instead.
public function __construct(
Context $context
) {
$this->eventManager = $context->getEventManager();
//do something with the event manager here
parent::__construct($context);
}
i.e. you shouldn’t inject the event manager, you should grab it from the context object.
The first time you encounter this, it’s going to be annoying, but ultimately this check is here to ensure you don’t inject objects you don’t need to. This helps keep your own constructor argument count down, and also ensures you get the correct instances of each object instead of instantiating a new one (for objects that are marked shared=false
in di.config
). Some will argue it theoretically improve performance by reducing the number of calls to the object manager – but that one seems more like a micro optimization to me.
Your annoyance is going to be magnified given how long setup:di:compile
takes to run, and that fact the developer mode object manager doesn’t warn you of this. I’ve taken to running setup:di:compile
at the end of each work day, and I’ve added a command to scan fot this to pestle
’s wish list.