Note, this is based off of an older version of the Magento 2 codebase.
With the minimal research I have done on Magento 2 recently the only thing I am apprehensive about is the invocation chain. Its purpose is to open the doors wide open to where you can hook your application in. I suppose that’s not a bad goal, though I do think that there is wisdom in creative restraint. Additionally, I don’t like code generation, which is what it uses. I never have and probably never will.
But why am I saying that at the beginning of a blog post on using events in Magento 2? That’s because the premise of the invocation chain is that events are going to be less important than they were before. That’s not to say that they won’t be important, only that they will be less important. In Magento 1 if you wanted to integrate with the system you needed events (or you could modify core files… which would give you a taste of the back of me hand). In Magento 2 you might need events.
One of the things that has not changed between Magentos is is the notion of an area. You still have adminhtml and frontend. However, instead of having their own node in an XML document they have their own XML files. Or, more correctly, their more XML file directories. Event observers are declared in an events.xml file but where those observers are active will depend on the location of an events.xml file. They will always be under <module>/etc. If it is in that directory then the event will be watched globally. If it is under etc/frontend, then it will be observed only when the frontend area is active and the same for adminhtml. I really like this split.
In terms of declaring an observer a lot has changed, and it’s for the better. One of the things that was problematic with Magento 1 is that observers needed to be defined under a unique XML node and if there was an accidental collision one of those observers the last merged node would be the one that would win out. It didn’t happen often, but it was a potential side effect due to the nature of the system. Now instead of silently overwriting the value you will get an error such as “Duplicate key-sequence [‘controller_action_predispatch’] in unique identity-constraint ‘uniqueEventName'” if your observer has a duplicate name.
Configuring an observer is stupid easy. No more memorization of “OK, I’m going to put this under /config/global/observers… no, wait. /config/global/events/observers … no…”. It is this:
<?xml version="1.0" encoding="UTF-8"?> <config> <event name="controller_action_predispatch"> <observer name="eschrade_helloworld_echo" instance="Eschrade\HelloWorld\Model\Observer" method="echoHello" /> </event> </config>
The code for our observer is very similar to how it looked in Magento 1.
1 2 3 4 5 6 7 8 9 | namespace Eschrade\HelloWorld\Model; class Observer { public function echoHello(\Magento\Event\Observer $observer) { echo 'Hello World'; } } |
This code echos “Hello World” when the “controller_action_predispatch” event is triggered. Pretty simple.
But this is a poor example. Why? Because I’m echoing the content. There is a dependency I need to properly handle output. So, as with dependency injection I simply define what I need in the constructor (I have not blogged about this functionality in detail yet, but you don’t need to know the underlying implementation and you can trust that this works as displayed here).
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | namespace Eschrade\HelloWorld\Model; class Observer { protected $_response; public function __construct( \Magento\App\Response\Http $response ) { $this->_response = $response; } public function echoHello(\Magento\Event\Observer $observer) { $this->_response->appendBody('Hello World'); } } |
There. That is how you get things done. And did you notice that you didn’t need to know anything about dependency injection? It all kind of work itself out. It’s even stupider easier to observe events in Magento 2.
But how about triggering them? It’s a little harder, but not by much. Did you see earlier where we needed to define a dependency for the response object if we wanted to use it later on? We simply need to do the same thing for the class \Magento\Event\Manager. There is no more Mage::app()->dispatchEvent(… and this is a good thing. It makes for much better testing. So if you want to trigger an event you simple define the event manager as a dependency.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | namespace Eschrade\HelloWorld\Model; class Observer { protected $_response; protected $_eventManager; public function __construct( \Magento\App\Response\Http $response, \Magento\Event\Manager $manager ) { $this->_response = $response; $this->_eventManager = $manager; } public function echoHello(\Magento\Event\Observer $observer) { $this->_response->appendBody('Hello World'); $this->_eventManager->dispatch('eschrade_echo_helloworld'); } public function catchEvent(\Magento\Event\Observer $observer) { $this->_response->appendBody('Observed event'); } } |
In our constructor we defined the event manager as a dependency and in the echoHello() method we call the dispatch() method on it. But simply dispatching the event will not call the catchEvent() method. In order to do that we need to modify the events.xml file.
<?xml version="1.0" encoding="UTF-8"?> <config> <event name="controller_action_predispatch"> <observer name="eschrade_helloworld_echo" instance="Eschrade\HelloWorld\Model\Observer" method="echoHello" /> </event> <event name="eschrade_echo_helloworld"> <observer name="eschrade_helloworld_echo" instance="Eschrade\HelloWorld\Model\Observer" method="catchEvent" shared="false" /> </event> </config>
In this updated configuration file we added the second event which defined the observer configuration. It’s pretty much exactly the same as the previous definition except that we are observing the event that our observer dispatches (though I would not recommend doing too many nested events). But I added an additional attribute called “shared” which I set to “false”. When the event manager dispatches an event it retrieves the observer object from the dependency injection container. But whether it asks the DI container for a shared object (the default) or a new object depends on the value of the “shared” attribute. If it is set to false it calls the create() method on the DI container. For all other scenarios it calls get() which checks the shared instance property for an existing object of the type prior to returning it.
So there you have it. Basic events in Magento 2.
Comments
Kevin Schroeder: Using events in Magento 2 | facebooklikes
[…] 2 users out there, Kevin Schroeder has posted a guide that could be helpful in your work – using events, specifically how to add an observer on a pre-dispatch controller […]
Kevin Schroeder: Using events in Magento 2 | htaccess
[…] 2 users out there, Kevin Schroeder has posted a guide that could be helpful in your work – using events, specifically how to add an observer on a pre-dispatch controller […]
PHPDeveloper.org: Kevin Schroeder: Using events in Magento 2
[…] Magento 2 users out there, Kevin Schroeder has posted a guide that could be helpful in your work – using events, specifically how to add an observer on a pre-dispatch controller […]
rofavadeka
Hello Kevin,
First of all thanks for the article! It was a good read.
The classes you used are moved to the `\Magento\Framework` namespace.
Therefor code in the article will not work.
`\Magento\Event\Observer` is now `\Magento\Framework\Event\Observer`
`\Magento\App\Response\Http` is now `\Magento\Framework\App\Response\Http`
`\Magento\Event\Manager` is now `\Magento\Framework\Event\Manager`
Kevin Schroeder
Yes, this is based off of an older version of the code. I will make a note of that at the top of the article.