When you need to override a core class, Magento 2 provides not just one but two ways to do this. The first one is Preference and another one is Plugin.
What is the difference between Preference and Plugin?
Although both of them are used for overriding the core modules, the way to use them is completely different.
With Preference, it must extend a core class. Preference can rewrite function. When you declare a Preference, your new class is expected to be a complete implementation of the class you want to override.
While a plugin allows you to execute your functions before, after or around (before & after) the core function is executed. It's NOT really rewritten function like Preference.
Since your plugin class doesn’t replace the core class, in case there are many plugins hooked onto a target class, Magento 2 just executes them sequentially based on the sortOrder parameter in your file di.xml.
How to use Preference to override a core class in Magento 2?
Make sure you have registered the new module to test it before, we will practice on this module. We will change Product Name by using Preference and Plugin.
At the first step, you need to create a new file di.xml in app/code/Magenest/SamplePreference/etc and add the following codes to it:
<?xml version="1.0"?> <config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:ObjectManager/etc/config.xsd"> <preference for="Magento\Catalog\Model\Product" type="Magenest\Sample\Model\Product"/> </config>
According to the above codes, the “for” attribute is where you place the path of the core class while the path of the class which will override is placed in the “type” attribute.
Next step, let’s create file Product.php in Magenest\Sample\Model and add the below codes:
<?php namespace Magenest\Sample\Model; class Product extends \Magento\Catalog\Model\Product { public function getName() { $changeNamebyPreference = $this->_getData('name') . ' modified by Preference'; return $changeNamebyPreference; } }
Remember that class Product has to extend the core class defined in file di.xml. To change name of all products, you need to declare getName() method in your class.
Use this code: $this->_getData('name') to get the original product name, and you can add condition or business logic to process it. The above codes are concatenating the original product name with ' modified by Preference' string. Method getName() must return a value like its parent.
When you've done, clean cache and go to product view, you will see the change.
As you can see, the product name is ‘Bag modified by Preference’ instead of ‘Bag’. Now, wherever getName() method is called, Magento 2 will use your method.
But everything hasn't stopped yet, what will happen if there are two preferences, which override the same core class. Let's try this case!
Register a new sample 2 module, and add the below codes to the file di.xml:
<?xml version="1.0"?> <config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:ObjectManager/etc/config.xsd"> <preference for="Magento\Catalog\Model\Product" type="Magenest\Sample2\Model\Product"/> </config>
After that, create a new file Product.php in Magenest\Sample2\Model add copy the following codes to it:
<?php namespace Magenest\Sample\Model; class Product extends \Magento\Catalog\Model\Product { public function getName() { $changeNamebyPreference = $this->_getData('name') . ' modified by Preference Sample 2'; return $changeNamebyPreference; } }
Then, run php bin/magento setup:upgrade command to install the sample 2 module and refresh your product view. The result is that the preference in sample 2 module is overriding the one in sample module.
Let's check out the file config.php in app/code/etc, you will see that the sample 2 module is below the sample module. This means the sample 2 module will load after the first one loaded.
'Magenest_Sample' => 1, "Magenest_Sample2' => 1,
To change the order of preference, open the file module.xml in Magenest/Sample/etc of the first module. In this file, paste the content <module name="Magenest_Sample2"/> to tag <sequence>. Magento 2 will load sequentially modules based on tag <sequence>.
Then, run php bin/magento setup:upgrade and check out the file config.php again. As you can see, the order of the two modules has changed. Now, the sample 2 module is above the sample module.
'Magenest_Sample2' => 1, "Magenest_Sample' => 1,
Refresh your product page and you will see that the product name is "Bag modified by Preference"!
How to use Plugin to override a core class in Magento 2
After changing product name by using Preference, we will try to do this by another way, using Plugin.
To begin, go to the file di.xml which is created when you used Preference, and add this code:
<type name="Magento\Catalog\Model\Product"> <plugin name="Magenest_Sample::changeProductName" type="Magenest\Sample\Plugin\Catalog\Model\ProductPlugin" sortOrder="1" />
You need to remember that the "name" attribute in <type> element must map with the path of the target class.
In <plugin> element, the "name" attribute may follow this type: VendorName_ModuleName::PluginName. You have to put the path of the class override the target class in the "type" attribute and set sortOrder for you plugin. Magento 2 will run your plugin based on sortOrder.
Next, let's create file ProductPlugin.php in Magenest\Sample\Plugin\Catalog\Model and add the following codes:
<?php namespace Magenest\Sample\Plugin\Catalog\Model; use Magento\Catalog\Model\Product; class ProductPlugin { public function afterGetName(Product $subject, $result) { return $result. ' modified by After Plugin'; }
We will use After listener Plugin for example. You have to pay attention to your plugin class, it does not extend the class you want to override.
There is a rule to set method name, you should follow if you want your plugin to work correctly: add prefix before|anround|after to method name and set the first letter of the target method to capital. E.g: afterGetName().
In afterGetName() method, the $subject parameter plays a role as $this of the core class. It means you can get public method or variable of the core class.
And after the core method is executed, Magento 2 passes the result and arguments to the next after method that follows. It is the reason why the $result is passed in afterGetName() method.
And then clean cache and see your result.
Last but not least, Plugin cannot be used on the following:
- Final methods
- Final classes
- Non-public methods
- Class methods (such as static methods)
- __construct
- Virtual types
- Objects that are instantiated before Magento\Framework\Interception is bootstrapped
Every way has its feature, you will choose Preference or Plugin to use as long as it meets your demand.
Have fun and good luck with override a core class with Preference & Plugin in Magento 2!
I followed these steps in order to customize Magenest\Pin\Model\Observer\Order Delivery class observer, but with no luck: my custom execute() function doesn't seem to perform.
My setup:
app/code/Magenest/Tpcs/Model/Observer/Order/Delivery.php:
namespace Magenest\Pin\Model\Observer\Order;
use Magenest\Pin\Model\Pin;
use Magenest\Pin\Model\Purchased;
use Magento\Framework\Event\Observer;
use Magento\Framework\Event\ObserverInterface;
use Magento\Sales\Model\Order;
use Symfony\Component\Config\Definition\Exception\Exception;
class Delivery extends \Magenest\Pin\Model\Observer\Order\AbstractObserver implements ObserverInterface
{
public function execute(Observer $observer)
{
//write to the log
}
.....
}
app/code/Magenest/Tpcs/etc/di.xml:
app/code/Magenest/Tpcs/etc/module.xml:
app/code/Magenest/Tpcs/registration.php:
<?php
\Magento\Framework\Component\ComponentRegistrar::register(
\Magento\Framework\Component\ComponentRegistrar::MODULE,
'Magenest_Tpcs',
__DIR__
);
We are using Composer-installed magenest/module-license-delivery code under /vendor.
What am doing wrong?
Your advise will be appreciated!