Targeted Dependency Injection

Global Overrides

When setting up class overrides, you generally want them to apply across Magento as a whole. There are times, however, where you might want to override a class only in one specific location. We at Classy Llama were recently developing a module for Magento 2 that required a Magento controller to return a different URL from what was normally provided by that controller’s service dependency. We wanted this modification to apply to this one controller class and nowhere else.

In the following example controller, let’s say you wanted to change the way this controller’s URL helper functions by having the getBaseUrl() method return a different URL, and it is not possible to directly edit the controller or its dependency:

	class Index extends MagentoFrameworkAppActionAction
	{
	    /**
	     * @var MagentoFrameworkUrl
	     */
	    protected $urlHelper;
		
	    /**
	     * @param MagentoFrameworkAppActionContext $context
	     * @param MagentoFrameworkUrl $url
	     */
	    public function __construct(
	        MagentoFrameworkAppActionContext $context,
	        MagentoFrameworkUrl $url
			
	    ) {
	        $this->urlHelper = $url;
	        parent::__construct($context);
	    }
		
	    /**
	     * @return void
	     */
	    public function execute()
	    {
	        echo $this->urlHelper->getBaseUrl();
	    }
	}

You could create a class that extends MagentoFrameworkUrl, override the getBaseURL() function, and then set up a preference. You could even set up a plugin to modify the method’s behavior. Unfortunately for us, both of these options are essentially global overrides. Since the getBaseURL() method is used in many other places, overriding it globally would potentially break other core classes that rely on MagentoFrameworkUrl as a dependency.

There is, however, a way to selectively modify functionality using constructor argument injection. As we will see in the following examples, constructor argument injection can replace a specific class’ dependencies with new or updated dependencies, and as long as the new class you create extends the required class specified in the constructor, Magento 2 will not complain about incorrect types. To illustrate the concept, I have written a small module that you can grab from GitHub.

Dependency Injection

This article assumes familiarity with the concepts of dependency injection as implemented in Magento 2. For further reading, check out the Magento 2 developer documentation. Alan Kent has also written an excellent article about dependency injection that touches on concepts such as injectables, non-injectables, and factories.

Overriding a Dependency’s Method in a Single Location

Extending on our previous example, let’s take a look at how we can use constructor argument injection to selectively modify a class’ behavior. Here is a simple example using the class DholdenDiExampleControllerExampleOneIndex from the GitHub project:

	namespace DholdenDiExampleControllerExampleOne;
	
	class Index extends MagentoFrameworkAppActionAction
	{
		
	    /**
	     * @var MagentoFrameworkUrl
	     */
	    protected $original;
		
	    /**
	     * @var MagentoFrameworkUrl
	     */
	    protected $modified;
		
	    /**
	     * @param MagentoFrameworkAppActionContext $context
	     * @param MagentoFrameworkUrl $url1
	     * @param MagentoFrameworkUrl $url2
	     */
	    public function __construct(
	        MagentoFrameworkAppActionContext $context,
	        MagentoFrameworkUrl $url1,
	        MagentoFrameworkUrl $url2
			
	    ) {
	        $this->original = $url1;
	        $this->modified = $url2;
	        parent::__construct($context);
	    }
		
		
	    /**
	     * @return void
	     */
	    public function execute()
	    {
	        echo '

Original Base URL: ‘ . $this->original->getBaseUrl() . ‘

‘; echo ‘

Modified Base URL: ‘ . $this->modified->getBaseUrl() . ‘

‘; } }

This simple controller calls the getBaseUrl() method from MagentoFrameworkUrl. Notice that $url1 and $url2 both instantiate MagentoFrameworkUrl, yet the output of the “ExampleOne” controller is as follows:

Original Base URL: http://example.dev/

Modified Base URL: http://newurl.dev/

While the exact same method is being called, the output is completely different, and this difference in functionality applies only to this controller class. To understand what is happening, let’s first look at DholdenDiExampleModelModifiedUrl:

	namespace DholdenDiExampleModel;
	
	/**
	 * Returns modified URL
	 */
	class ModifiedUrl extends MagentoFrameworkUrl
	{
		
	    /**
	     * {@inheritdoc}
	     */
	    public function getBaseUrl($params = []) {
	        return 'http://newurl.dev/';
	    }
	}

This simple class extends MagentoFrameworkUrl and overrides the parent function getBaseUrl(). Nothing too special here. We could at this point, setup a configuration preference for the class, but this would override this method across all of Magento 2. We shall instead use dependency injection to selectively override one constructor argument in one class. Let’s take a look at our etc/di.xml:

	
	
    	
        	
            	DholdenDiExampleModelModifiedUrl
        	
    	
    	...
	

We’re replacing the third constructor argument of our controller (i.e. $url2) with the object DholdenDiExampleModelModifiedUrl, and since this class extends MagentoFrameworkUrl, it fulfills the requirement as set in the constructor.

Taking it a Step Further

Great, but let’s say you wanted to do this with a factory. The same principle applies, but with one small caveat, to which we will get shortly. Let’s take a look at DholdenDiExampleControllerExampleTwoIndex from our Github project:

	namespace DholdenDiExampleControllerExampleTwo;
	
	class Index extends MagentoFrameworkAppActionAction
	{
		
	    /**
	     * @var MagentoCatalogModelCategory
	     */
	    protected $categoryOriginal;
		
	    /**
	     * @var MagentoCatalogModelCategory
	     */
	    protected $categoryModified;
		
	    /**
	     * @param MagentoFrameworkAppActionContext $context
	     * @param MagentoCatalogModelCategoryFactory $categoryFactory1
	     * @param MagentoCatalogModelCategoryFactory $categoryFactory2
	     */
	    public function __construct(
	        MagentoFrameworkAppActionContext $context,
	        MagentoCatalogModelCategoryFactory $categoryFactory1,
	        MagentoCatalogModelCategoryFactory $categoryFactory2
			
	    ) {
	        $this->categoryFactory1 = $categoryFactory1;
	        $this->categoryFactory2 = $categoryFactory2;
	        $this->categoryOriginal = $categoryFactory1->create();
	        $this->categoryModified = $categoryFactory2->create();
	        parent::__construct($context);
	    }
		
		
	    /**
	     * @return void
	     */
	    public function execute()
	    {
	        $this->categoryOriginal->load(1);
	        $this->categoryModified->load(1);
	        echo '

Name of Category 1: ‘ . $this->categoryOriginal->getName() . ‘

‘; echo ‘

Name of Category 2: ‘ . $this->categoryModified->getName() . ‘

‘; } }

This controller outputs the name of the category with an id of ‘1’, but like in our first example, the result of calling getName is different even though the same method is called. The primary difference between this example and the previous one is that we are calling factories in the controller’s constructor. We are then instantiating two new instances of MagentoCatalogModelCategory using the factory’s create() method. Our desire is to override the getName() method of MagentoCatalogModelCategory, but only in our specific example class and nowhere else.

Like in our first example, we have created a new class (DholdenDiExampleModelModifiedCategory) that extends MagentoCatalogModelCategory and overrides the getName() method, but now our etc/di.xml looks a bit different:

	
	
		...
	    
	        
	            DholdenDiExampleModelModifiedCategoryFactory
	        
	    
	 

Since we are using factories in the class’ constructor to create new instances of the category class, we need to override these factories in some manner. But we have created a new category class (DholdenDiExampleModelModifiedCategory) that modifies the getName() method. As you may have noticed, factories are not the classes themselves. They only create new instances of the classes whose methods we wish to selectively override.

We obviously need to pass a factory to the controller, so the logical approach is to append ‘Factory’ to the end of our constructor argument, i.e. our new class, in di.xml. Unfortunately, this will cause an error because the factory that will the auto-generated by Magento 2 will not fulfill the requirements of the constructor argument because it does not extend MagentoCatalogModelCategoryFactory as the constructor has specified. If we try to view the controller as-is, the page will return the following error in our browser:

Recoverable Error: Argument 3 passed to DholdenDiExampleControllerExampleTwoIndexInterceptor::__construct() must be an instance of MagentoCatalogModelCategoryFactory, instance of DholdenDiExampleModelModifiedCategoryFactory given...

From the error message, we learn that the factory created by Magento 2 does not extend the factory that is specified in the controller’s constructor. The solution is to have that factory inherit from MagentoCatalogModelCategoryFactory. But how do we do this, since the factory was auto-generated? Fortunately for us, we can manually create our own factories and have Magento 2 use these over its own auto-generated classes. Take a look at the following factory located in the same folder as our new ModifiedCategory class:

	namespace DholdenDiExampleModel;
	
	class ModifiedCategoryFactory extends MagentoCatalogModelCategoryFactory
	{
		
	    /**
	     * {@inheritdoc}
	     */
	    public function __construct(MagentoFrameworkObjectManagerInterface $objectManager, $instanceName = 'DholdenDiExampleModelModifiedCategory')
	    {
	        parent::__construct($objectManager, $instanceName);
	    }
		
	    /**
	     * {@inheritdoc}
	     */
	    public function create(array $data = [])
	    {
	        return $this->_objectManager->create($this->_instanceName, $data);
	    }
		
	}

Here we create a factory that extends MagentoCatalogModelCategoryFactory. The CategoryFactory class will still be auto-generated by Magento 2, but since our factory now extends it, it will now fulfill the requirement of the controller’s constructor argument.

Conclusion

Constructor argument injection allows you to modify a class’ behavior, specifically methods given to you by class dependencies, without directly modifying the classes themselves and without changing their behavior system- or area-wide. I hope this concept will be useful to you in your future Magento 2 projects.

Magento Imagine 2016 Recap

Magento unveiled a number of bold new ideas at Imagine 2016. “We Are Magento” appropriately drives home a simple, meaningful pride that we at Classy Llama share with the greater Magento community. We are poised, ready and thrilled to continue building on a platform that keeps churning out newer and newer innovation. With so many bold ideas, unveiled at the Imagine 2016 conference in Las Vegas, we can’t wait to see the ripple effects this will have on the SMB merchant community.

Magento Unveiled:

For all of these groundbreaking announcements and events at Imagine 2016, one of the most moving and important moments of Imagine 2016 was during the Partner Summit. Perhaps it was my imagination, but on what seemed the verge of tears, Mark Lavelle detailed the Magento story since the eBay takeover. Many of you in the community have your own version of this story. Regardless of your view, I think everyone in that room would agree, Mark’s account was honest, insightful, and very personal.

It was clear that Magento isn’t just another company for Mark to run. He loves it. He loves Magento’s customers. He loves Magento’s community. He loves Magento’s future—empowering commerce in ways that we have yet to realize. Even though he did not found Magento, it feels like he did, like this is his home—his fifth child. This is absolutely key for Magento’s future. To have a leader who is truly invested and is willing to make sacrifices and hard decisions in order to see it succeed. A leader who believes in Magento and the community.

One part of Mark’s account is particularly important: when Mark realized the precarious position Magento was in, he sat down with Paul Boisvert and they made a commitment—that their say/do ratio needed to be as close to one-to-one as possible. Even prior to knowing this detail, I can tell you the Classy Llama team has been very aware of this position, because Magento has been delivering. And while the split from eBay was an important part of Magento’s evolution, we were able to see Magento was on track before the split even happened.

Empowering Others

Mark has done a phenomenal job lifting up and empowering innovative and a diverse set of leaders. Those leaders, in turn, have enabled and empowered their teams to deliver. Not only has Mark empowered the leaders in his organization, but he and his team have recommitted to leaders in the community.

The theme of the conference was “We Are Magento,” and you could feel it. The energy was high. Many people walked away from the conference with the feeling that it was the best Imagine yet. The session content was on point. Shout out to Classy Llama’s Rob Tull for his breakout session talk on requirements gathering, called Winning the Requirements Game. And I want to mention both Rob Tull and Daniel Nettleton for their level of insight on the Payments Panel.

Remaining Committed

Merchants had peer-to-peer engagement and conversations dedicated to discussing their needs and opportunities. A significant portion of the Marketplace was committed to an Ask Magento forum. Magento polled the SI partners; and is making updates to the SI partner program as a result. At the partner summit, Mark made a definitive statement committing Magento to open source and Magento CE. A commitment that was, frankly, already implied when Magento decided to stay open source. In 2015, the Magento community created over 2,500 pull requests and issues for Magento 2.0.

Breaking New Ground

The outcome of all this is that while certain announcements were previously unrevealed, their innovative and groundbreaking nature was not surprising. And these announcements are definitely groundbreaking. Magento Enterprise Cloud Edition is a Platform as a Service (PaaS)—the first of its kind. It will provide merchants with all the flexibility and features of Magento Enterprise and all the benefits of SaaS at the same time. It’s really the best of both worlds, and we look forward to diving in.

The Magento Marketplace is exciting because of the new quality control standards. Many people have described Magento Connect as the Wild West of extension stores. The Marketplace is intended to be the opposite of that, with robust code and functionality testing, curation, and ease of use.

Magento 2.1 (to be released in June), is chalk full of updates and new features, two of which are particularly exciting:

  • ElasticSearch integration which will provide merchants with a powerful search tool that is much easier to leverage than Solr.
  • Content staging, preview, and scheduling tool which will save merchants time and help ensure content is properly formatted and pushed.

On a final note, the food, parties, events, design, and vibe of this show were off the hook. Best ever. If you didn’t attend, you really missed out. Make sure you come next year.

Thank you Magento, for hosting the best Imag… no, I’ll go further. Thank you for hosting the best Commerce Conference in the world.

Classy Llama at Imagine 2016

Of course, no tradeshow experience is complete without some of our signature swag. This year we returned with our third annual edition of our legendary plush llama. “Louie,” as we called him, was a complete hit. In addition, we brought some brand new Classy Llama cape and mask kits. As you’ll see, these were a major success with kids and… uh grown up kids, alike.

Louie the Llama

Classy Capes and Masks

Magento Imagine 2016 Highlights

Still want to know more? Check out the official highlights video from the conference.

Get Prepared for Magento 2 with Classy Llama’s Dev Docs.

As an authority on Magento 2, Classy Llama is prepared to guide your eCommerce business into the future. Several major brands have already used our world-class expertise and service to bring them into the future with Magento 2.

We are one of the largest outside contributors of code to the Magento 2 base. Between retooling our infrastructure, training and other necessary steps for equipping our agency for success, Classy Llama has invested a great deal in preparation for Magento 2.

Magento is a community and we believe collaboration is essential to its success. As one of the first agencies in the world to be certified as a Magento 2 Trained Solution Partner, we find it to be an important role for us to share our knowledge with the Magento community.

The Classy Llama team has prepared multiple Magento 2 dev docs for the benefit of the Magento community. Here are a few:

http://devdocs.magento.com/guides/v2.0/extension-dev-guide/service-contracts/service-to-web-service.html

http://devdocs.magento.com/guides/v2.0/extension-dev-guide/factories.html

http://devdocs.magento.com/guides/v2.0/extension-dev-guide/proxies.html

Contact Us