Introduction to Plugin Pattern Design
Interception is a software design pattern which is used when there is some need to insert code dynamically without necessarily changing the original class behavior. This works by dynamically inserting code between the calling code and the target object.
The interception pattern in Magento is implemented via plugins. They provide the before, after, and around listeners, which help us extend the observed method behavior.
In this tutorial, I will cover the following topics:
- Creating a plugin
- Using the before listener
- Using the after listener
- Using the around listener
Please note: plugins cannot be created for just any class or method, as they do not work for the following:
- Final classes
- Final methods
- The classes that are created without a dependency injection
Creating a new Module
Start by creating a new module named ‘Jeff_Plugin’ with following steps
create registration.php file
#app/code/Jeff/Plugin/registration.php
create module.xml file
#app/code/Jeff/Plugin/etc/module.xml
create di.xml file
We are going to use Magento 2 command line feature for the tutorial
#app/code/Jeff/Plugin/etc/di.xml - Jeff\Plugin\Command\Testbed
create Testbed.php fiel
#app/code/Jeff/Plugin/Command/Testbe.php om = $om; $this->example = $ex; return parent::__construct(); } protected function configure() { $this->setName("ps:plugin"); $this->setDescription("The command for testing magento 2 plugins."); parent::configure(); } protected function execute(InputInterface $input, OutputInterface $output) { $output->writeln("Hello World"); } }
Create a test Model class Example.php
#app/code/Jeff/Plugin/Model/Example.php
After following commands, you will make sure everything is working correctly
Partial output looks like:php bin/magento moduel:enable Jeff_Plugin php bin/magento cache:clean php bin/magento setup:upgrade php bin/magento ps:plugin
Hello World
Creating a plugin
Update di.xml file as following:
- Jeff\Plugin\Command\Testbed
Plugins are defined within the module di.xml file. To define a plugin, by using the type elemeent and its name attribute, we first map the class that we want to observe. In this case, we are observing the \Jeff\Plugin\Model\Example class.
In the type element, we then define one ore more plugins using the plugin element.
The plugin element has the following four attributes assigned to it:
- name: Using this attribute, you can provide a unique and recognizable name value that is specific to the plugin.
- sortOrder: This attribute determines the order of execution when multiple plugins are observing the same method.
- disabled: The default value of this attribute is set to default, but if it is set to true, it will disabled the plugin
- type: This attribute points to the class that we will be using to implement the before, after, or around listener
create Plugin.php file
#app/code/Jeff/Plugin/Model/Example/Plugin.php
As you can see, we are observing before, after and around listeners for the getMessage()method which is defined in the \Jeff\Plugin\Model\Example class.
Update the Testbed.php
#app/code/Jeff/Plugin/Command/Testbed.php om = $om; $this->example = $ex; return parent::__construct(); } protected function configure() { $this->setName("ps:plugin"); $this->setDescription("The command for testing magento 2 plugins."); parent::configure(); } protected function execute(InputInterface $input, OutputInterface $output) { $output->writeln( "\nWe're going to call the `getMessage` method on the class " . "\n\n" . ' ' . get_class($this->example) . "\n" ); $result = $this->example->getMessage("Hola", true); $output->writeln('Result: ' . $result); } }
The results are shown following
run command: php bin/magento ps:plugin result: We're going to call the `getMessage` method on the class Jeff\Plugin\Model\Example\Interceptor Calling Jeff\Plugin\Model\Example\Plugin::beforeGetMessage Calling Jeff\Plugin\Model\Example\Plugin::aroundGetMessage -- before Calling Jeff\Plugin\Model\Example\Plugin::aroundGetMessage -- after Calling Jeff\Plugin\Model\Example\Plugin::afterGetMessage Result: Hello world! Something appending here. Hear from you.
The Plugin class does not have to be extends from another class for the plugin to work. We define the before, after and around listeners for the getMessage() method by using the naming convention, as follows:
+ = beforeGetMessage + = afterGetMessage + = aroundGetmessage
An after plugin method has two parameters. The first is the object the method was called on (In our case, that’s Jeff\Plugin\Model\Example\Intercepter object). The second is the result of the original method call.
The before plugin method has three parameters, the first is the object the method was called on (Jeff\Plugin\Model\Example\Intercepter object), second and third parameters are the parameters for the observed method getMessage(). As you can see, in addition to being able to take programmatic action before a method is called, the before plugin methods let us change the arguments passed into the mesthod.
The around plugin method has four parameters, the first , third, fourth are the same as before plugin method. The second parameter is an anonymous function(PHP Closure). If you want to get result from the original method, we have to put this code: $rsult = $procede();
. While the around plugin methods give you the power to completely replace a piece of system functionality, this also means that you are responsible for making sure your new code is a suitable replacement for the code you’re replacing.
Conclusion
In this tutorial, I showed you how to create a new module, a command line script for magento 2 and a plugin for listening before, after, and around observer for an original method.
Magento 2’s plugins system allows your to:
- Listen to any method call made on an object manager controlled object and take programmatic action
- Change the return value of any method call made on an object manager controlled object.
- Change the arguments of any method call made to an object manager controlled object.