Question

I've been creating my own plugins in Magento 2 however I'm struggling to understand references to the Magento core libraries. Say I have a plugin like the one below, why do we know that GetName is in this directory \Magento\Catalog\Model\Product) and how do we know it requires $product, $name?

If I wanted to change the price of a product, how would I know which library to call? Do I guess at the function name GetPrice or is there a way to be able to know for sure the name of the function I wish to change in order to create a plugin?

  <?php

 namespace Inchoo\Custom\Plugins;

 Class AfterProduct {
 public function afterGetName(\Magento\Catalog\Model\Product $product, 
 $name){

  $price = $product ->getData('price');
  if ($price < 60 ) {
    $name .= " -> cheap" ;
}
  else {
    $name .= " -> expensive";
}
  return $name;
    }
}
Was it helpful?

Solution

First, take a look at this method Magento\Framework\Interception\Interceptor::___callPlugins. this is where the magic happens. (I'll explain why you need to look at this later).
Notice that Magento\Framework\Interception\Interceptor is a trait and not a class. So $this inside the trait references the object that has this trait.
This method accepts 3 parameters.

  • the method being pluginized,
  • the arguments of the original method
  • some information about the plugin.

This method defines a variable (that is a function) that does this.

if (isset($currentPluginInfo[DefinitionInterface::LISTENER_BEFORE])) {

checks if the method has before plugins. if it does, it loops through all declared plugins. (foreach ($currentPluginInfo[DefinitionInterface::LISTENER_BEFORE] as $code) {) . and calls the beforeOriginalMethod (the plugin itself) that receives as parameter the class being pluginized ($this) and the arguments of the original method

Then it checks for around plugins (if (isset($currentPluginInfo[DefinitionInterface::LISTENER_AROUND])) {) similar as for before plugins.
The difference is that the plugin method receives an extra parameter $next which is a callable the variable (function) defined above. this is used to be able to call the orignial method inside the around plugin since the around plugin replaces the original method.

Then it checks for after plugins (if (isset($currentPluginInfo[DefinitionInterface::LISTENER_AFTER])) {) this is very similar to the before plugins. The difference is that this receives as parameters the result of the original method also.

What the plugins should return.

  • before This plugin is useful if you want to change the arguments of the original method. So it must return an array with the new values of the arguments. For example if you pluginize the method setName from the product model, this one receives as parameter a string called $name. Your plugin should return an array with a string [$name]. This type of plugin can return null. This means that the original method will receive the parameters unchanged.
  • around should return the same thing as the original method returns. THere is also a catch. If you don't call inside your plugin the original method (second parameter described above) all other plugins that come after yours will be ignored.
  • after. Must return the same thing as the original method.

How the magic happens.

When instantiating a class through the ObjectManager magento checks if that class has methods that are pluginizable (say that 3 times fast :D). See here what methods / classes support plugins.

long story short, it reads the di.xml files, checks for declared plugins to the class it tries to instantiate, checks if the classes that are declared as plugins have methods that start with before, after, around and that match the public method names of the class it tries to instantiate.

If it finds something it generates a class that extends the original class and it is called Original\ClassName\Interceptor (adds interceptor at the end.) and instantiates this class instead of the original one.
Just look in the generated folder for a file named Interceptor.php.

All these classes look the same.
They have the trait \Magento\Framework\Interception\Interceptor;, the one I described above.

The constructor is the same as the original class constructor with an extra ___init() call, which is located in the \Magento\Framework\Interception\Interceptor trait.

Then all the pluginized methods are listed and they all look the same

public function someMethodHere(arguments here)
{
    $pluginInfo = $this->pluginList->getNext($this->subjectType, 'someMethodHere');
    if (!$pluginInfo) {
        return parent::someMethodHere(arguments here);
    } else {
        return $this->___callPlugins('someMethodHere', func_get_args(), $pluginInfo);
    }
}

This is how the link is made from the plugins to the actual classes. Through a child class of the original class that either calls the original class method or the plugins declared for that class.

OTHER TIPS

You might have added a di.xml file .

<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:noNamespaceSchemaLocation="urn:magento:framework:ObjectManager/etc/config.xsd">
    <type name="Magento\Catalog\Model\Product">
        <plugin name="plugin_update_product_name" type="Inchoo\Custom\Plugins\AfterProduct"></plugin>
    </type>
</config>

In the above di.xml file we have declared the name="Magento\Catalog\Model\Product" , this refers to the class whose method we are going to override .

so you can find all the related methods that you can use by going to magento_rootdirectory/vendor/module-catalog/Model/Product.php file .

Hope this helps.

Licensed under: CC-BY-SA with attribution
Not affiliated with magento.stackexchange
scroll top