What can DI do for me? An overview
A big change coming from Magento 1 to Magento 2 is the use of Dependency Injection. Essentially DI means to insert your dependencies as parameters as opposed to establishing them within the functions/classes themselves. In Magento 2 this is done via class constructors, so anything you will require within a given class will be passed in as a parameter. This helps to decouple code, allows for much easier unit testing and also allows Magento 2 to perform a bit of magic between what the function ‘asks for’ and what it gets. Using DI in it’s most straightforward sense in Magento 2 couldn’t be easier, you simply specify a class as a parameter in a class’ constructor and it ‘magically’ gets passed in for you to use.
This post will give an overview over the various things you can do with DI, as opposed to looking at its innermost workings. However if you want a bit more visibility of this, looking at the Object Manager (we recommend the Object Manager post series by Alan Storm) would be a good place to start.
The three types of plugins
Plugins – rather confusingly named – are not anything like extensions. Plugins are classes in their own right and act as a sort of thin layer on top of classes to allow you to run a block of code before, around or after a classes functions. This is achieved in Magento 2 with the use of Interceptor classes. There is one caveat to this extremely useful feature; it can only be used on public functions.
‘Before’ plugins are used when you want to run a block of code before a public function. They give you the ability to change arguments by returning a modified version of them as either a single argument or an array of arguments.
‘After’ plugins are used when you want to run a block of code after a public function. They allow you to affect the return value of the original function.
‘Around’ plugins are used when you have code that you’d like to run before and after a public function. The public function in question is passed in as a PHP closure (a variable that can be run like a function) which you can run at any point in your ‘around’ function, allowing you to make use of arguments, retrieve the return value and then use it or modify it in any way you like before returning as if your plugin were the original function.
Which to use…
It’s mostly obvious which to use: if you want to run code before or after (like an observer) then use the type with the matching name, and if you need to do both use an around plugin. However, as after plugins do not give you access to the functions parameters, you will often find that you will need to use an around plugin in place of an after plugin. For instance if you want to do something with the original parameters of the function which will effect the value you intend to return.
Essentially plugins are much like – although not a replacement for – observers, they just give us more power with regards to modifying arguments, return values and where exactly they can be used – we are no longer restricted to just searching for events but instead find ourselves wishing for more public functions. Plugins also give us the ability to set an ordering, which allows us to chain various plugins together in any required order.
Preferences are what allow you to dictate to the Object Manager what gets injected when a class has another class specified as a constructor parameter. They can be thought of as a direct descendant of rewrites in Magento 1, though you can use this both to replace a class and specify which class is used as an instance for an interface. If you are changing a class preference, rewrite conflicts will still therefore be an issue, as one preference will ultimately win out above all others.
When to use…
The absolutely correct time to use a preference is when creating a new implementation for an existing interface or class – for example a new logger or template renderer. These should use preferences to replace the standard implementation.
For less complete replacements of functionality, preferences should be avoided where possible; ideally we don’t want to replace whole class’ functions by extending core code and creating preferences due to the possibility of conflicts – it’s better to use plugins and observers where possible. Unfortunately due to the limitations of which events are fired and plugin function visibility (plugins only work on public methods), the use of these more flexible approaches is not always possible. It is at this point we fall back to using preferences.
Example in di.xml:
<preference for="Magento\Catalog\Ui\DataProvider\Product\Form\Modifier\Related" type="C3\BundleConfigurables\Ui\DataProvider\Product\Form\Modifier\Related"/>
Argument replacement – as the name suggests – allows you to directly change the parameters passed in to a type. If a classes takes an array as a parameter then this will allow you to add to it – as all DI XML is merged. Argument replacement can also be used as a more specific alternative to a preference – replacing a class with another when it is injected into a specific class.
Example in di.xml:
<type name="Magento\Catalog\Model\Product\LinkTypeProvider"> <arguments> <argument name="linkTypes" xsi:type="array"> <item name="bundleconf" xsi:type="const">C3\Bundles\Model\Product::TYPE_BUNDLE</item> </argument> </arguments> </type>
Virtual Types are used in rather specific circumstances. To put it as simply as we can – they allow you to create a version of an existing type, which can be modified and injected without affecting the original type. Whereas preferences allow you to replace what’s injected in all cases, Virtual Types allow you to make a ‘copy’ of a type, set something – say a template – and then use it in a particular case, leaving the original untouched. This is a very powerful feature when paired with argument replacement and provides much more flexibility than the old rewrites of Magento 1.
Example in di.xml:
<virtualType name="Magento\Sales\Model\ResourceModel\Order\Grid" type="Magento\Sales\Model\ResourceModel\Grid"> <arguments> <argument name="columns" xsi:type="array"> <item name="discount_amount" xsi:type="object">sales_order_discount_amount</item> <item name="base_discount_amount" xsi:type="object">sales_order_base_discount_amount</item> </argument> </arguments> </virtualType>