Categories


Archives


Recent Posts


Categories


Quick Note on “already exists in context object” Errors

astorm

Frustrated by Magento? Then you’ll love Commerce Bug, the must have debugging extension for anyone using Magento. Whether you’re just starting out or you’re a seasoned pro, Commerce Bug will save you and your team hours everyday. Grab a copy and start working with Magento instead of against it.

No Frills Magento Layout is the only Magento front end book you'll ever need. Get your copy today!

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.

Copyright © Alan Storm 1975 – 2017 All Rights Reserved

Originally Posted: 4th February 2016