In order to extend Shopware or modify its behavior you will need some sort of extension system, that allows you to hook onto Shopware. The following guide will give an overview of event systems in general and the Shopware event system specifically
Event systems also known as publish subscribe pattern
are basically a pattern to layout software. They allow to emit an event at any point in the software - and let other pieces
of software react to that event. The main aspect is the fact, that the piece of software emitting the event does not need
to know the piece of software consuming the event. So very generally speaking, Shopware could emit an event ORDER_FINISHED
with the additional information AMOUNT: 300; PRODUCTS: [SW-123, SW-456]
and a plugin developer could just subscribe to that very
event and push those information to the ERP.
ORDER_FINISHED
and the payload was AMOUNT: 300; PRODUCTS: [SW-123, SW-456]
. Depending on the
event system, the payload can be an object, an array or any other data type.Generally when talking about events, three parties are involved:
First create an event subscriber class in PluginName/Subscriber/
that implements the Enlight\Event\SubscriberInterface
.
The interface determines that the getSubscribedEvents
method must be implemented.
To register an event subscriber in the services.xml use the shopware.event_subscriber
tag.
A very simple example could look like this:
<service id="swag_events.subscriber.frontend_listing_subscriber"
class="SwagEvents\Subscriber\FrontendListingSubscriber">
<tag name="shopware.event_subscriber"/>
</service>
<?php
namespace SwagEvents\Subscriber;
use Enlight\Event\SubscriberInterface;
class FrontendListingSubscriber implements SubscriberInterface
{
public static function getSubscribedEvents()
{
return [
'Enlight_Controller_Action_PostDispatchSecure_Frontend_Listing' => 'onFrontendListing'
];
}
public function onFrontendListing(\Enlight_Event_EventArgs $args)
{
/** @var \Shopware_Controllers_Frontend_Listing $subject */
$subject = $args->getSubject();
// Do some magic with the listing data
}
}
It will basically tell the Shopware event manager: If an event with the name 'Enlight_Controller_Action_PostDispatchSecure_Frontend_Listing' occurs, please execute the given callback.
Now how does Shopware emit those events? Consider this example of the indexAction
of a controller, which runs, when you call the url http://your-domain.com/SwagEvents/
:
<?php
class Shopware_Controllers_Frontend_SwagEvents extends Enlight_Controller_Action
{
public function indexAction()
{
// set no renderer is only for testing without creating a template.
$this->container->get('front')->Plugins()->ViewRenderer()->setNoRender();
// do some magic
$this->container->get('events')->notify(
'SwagEvent_Controller_indexAction', // give the event a unique name and add the payload
[
'payload' => 123,
'payload2' => 'more Payload',
]
);
// do some magic
}
}
This will emit the SwagEvent_Controller_indexAction
event. The event manager will now call any subscriber, who registered to this event.
The second parameter is the payload of that event - some additional context information.
The event subscriber will print out the $args object and the payload.
Events in Shopware do work pretty much as described above. There are some details, however, that will help you writing plugins.
The payload of an event is passed to the subscriber with a simple context object called Enlight_Event_EventArgs
. This
is basically a container object, that will give you access to the payload. Given the example from above:
$this->container->get('events')->notify(
'SwagEvent_Controller_Index_After_Do_Some_Magic',
[
'payload' => 123,
'payload2' => 'more Payload',
]
);
You can access the payload like this:
<service id="swag_events.subscriber.swag_event_subscriber" class="SwagEvents\Subscriber\SwagEventsSubscriber">
<tag name="shopware.event_subscriber"/>
</service>
<?php
namespace SwagEvents\Subscriber;
use Enlight\Event\SubscriberInterface;
class SwagEventsSubscriber implements SubscriberInterface
{
public static function getSubscribedEvents()
{
return [
'SwagEvent_Controller_notifyAction' => 'onNotify'
];
}
public function onNotify(\Enlight_Event_EventArgs $args)
{
$payload1 = $args->get('payload');
$payload2 = $args->get('payload2');
$payload3 = $args->get('yourPayload');
}
}
In the examples above, the SwagEvent_Controller_Index_After_Do_Some_Magic
event was emitted as a notify
event: The subscriber was just informed about
the event - but there is no way to modify something. Shopware knows four different event types:
notify
notifyUntil
filter
collect
These four types behave differently and are useful in different ways.
As described in the example above:
$this->container->get('events')->notify(
'SwagEvent_Controller_notifyAction',
[
'payload' => 123,
'payload2' => 'more Payload',
'yourPayload' => 'LoremIpsum',
]
);
This will emit the SwagEvent_Controller_notifyAction
event and allow you to read payload
, payload2
and yourPayload
. A modification is usually
not possible, except an object is passed (those could be modified by reference).
A notifyUntil
event is usually used to allow you to stop Shopware from doing something:
public function notifyUntilAction()
{
// set no renderer is only for testing without creating a template.
$this->container->get('front')->Plugins()->ViewRenderer()->setNoRender();
// do some magic
$stop = $this->container->get('events')->notifyUntil(
'SwagEvent_Controller_notifyUntilAction',
[
'someData' => $someData
]
);
if ($stop) {
return;
}
echo 'Do some magic';
}
Now imagine, you subscribed to the `SwagEvent_Controller_Index_After_Do_Some_Magic_Notify_Until` event with the following event callback:
```php
public function onNotifyUntil(\Enlight_Event_EventArgs $args)
{
$someData = $args->get('someData');
// Some condition
if ($someData === true) {
// if you return some result you stop the callStack
return true;
}
}
In this case, we disallow echo 'Do some magic';
if the 'stop' payload is true. The general rule here:
Return null
if you want Shopware to proceed; return anything else to stop Shopware from proceeding. In the example above
Shopware will proceed and print the results;
Some real world examples for notifyUntil
are:
Shopware_Modules_Order_SendMail_Send
: Prevent sending the order confirmation mailShopware_Modules_Basket_AddVoucher_Start
: Prevent adding a voucherShopware_Modules_Basket_AddArticle_Start
: Prevent adding an article to cartThe filter
event allows you to modify certain data. It is often used to allow you to modify a computed result set:
public function filterAction()
{
$result = [
['id' => 1],
['id' => 2],
['id' => 3],
];
$eventManager = $this->container->get('events');
$result = $eventManager->filter('SwagEvent_Controller_filterAction', 'some value', ['data' => $result]);
echo '<pre>';
var_export($result);
echo '<br />';
die('END');
}
In this example a list of ids has been loaded. The SwagEvent_Controller_Filter
event would allow you
to modify this list and return a modified set with your subscriber:
public function onFilter(\Enlight_Event_EventArgs $args)
{
$return = $args->get('data');
$value = $args->getReturn();
if ($value === 'some value') {
foreach ($return as $key => $value) {
if ($value['id'] === 2) {
$return[$key] = [
'id' => 178
];
}
}
}
return $return;
}
In this example, the method filterAction
would return three ids for scenario:
'id' => 1
, 'id' => 2
and 'id' => 3
. The event subscriber reads the original result using the call $args->get('data')
and removes the 'id' => 2
in the result set. This way, every call to that method would replace the id 2,
Some real world examples for filter
are:
Shopware_Modules_Basket_GetBasket_FilterResult
: Allows the modification of the sGetBasket
resultShopware_Modules_Admin_GetUserData_FilterResult
: Allows modifying the result of the sGetUserData
methodShopware_Modules_RewriteTable_sCreateRewriteTableArticles_filterArticles
: Allows modifying the article context data used for the SEO engineThe collect
event is used in places, where Shopware wants to allow you to register e.g. handlers for certain situations.
The following example would (by default) just print hello
:
public function collectAction()
{
$collection = new ArrayCollection([
new \SwagEvents\Components\NameClass1(),
new \SwagEvents\Components\NameClass2()
]);
$eventManager = $this->container->get('events');
$eventManager->collect('SwagEvent_Controller_collectAction', $collection);
foreach ($collection->toArray() as $nameClass) {
echo $nameClass->getName();
echo '<br />';
}
echo '<pre>';
\Doctrine\Common\Util\Debug::dump($collection->toArray());
echo '<br />';
die('END');
}
The following subscriber will add additional classes to the SwagEvent_Controller_collectAction
event:
public function onCollect(\Enlight_Event_EventArgs $args)
{
return new ArrayCollection(
[
new NameClass3(),
new NameClass4()
]
);
}
Now the script would print:
SwagEvents\Components\NameClass1
SwagEvents\Components\NameClass2
SwagEvents\Components\NameClass3
SwagEvents\Components\NameClass4
/var/www/html/doku/vendor/doctrine/common/lib/Doctrine/Common/Util/Debug.php:71:
array (size=4)
0 =>
object(stdClass)[1319]
public '__CLASS__' => string 'SwagEvents\Components\NameClass1' (length=32)
1 =>
object(stdClass)[1316]
public '__CLASS__' => string 'SwagEvents\Components\NameClass2' (length=32)
2 =>
object(stdClass)[1322]
public '__CLASS__' => string 'SwagEvents\Components\NameClass3' (length=32)
3 =>
object(stdClass)[1323]
public '__CLASS__' => string 'SwagEvents\Components\NameClass4' (length=32)
END
Some real world examples for collect
are:
Shopware_Console_Add_Command
: Add one or more Symfony console commandsShopware_SearchBundleDBAL_Collect_Facet_Handlers
: Register a facet handler for the DBAL gatewayTheme_Compiler_Collect_Plugin_Javascript
: Collect javascript files for the JS compilerYou can download an example plugin here
The default way is to use subscriber classes that are registered in the PluginBaseDirectory/Resources/services.xml
:
Subscribers are basically custom classes which register to Shopware events. This way, the plugin base class doesn't know about the
events, which can, in turn, be encapsulated in corresponding classes.
First create an event subscriber class in PluginName/Subscriber/
that implements the Enlight\Event\SubscriberInterface
.
The interface determines that the method getSubscribedEvents
must be implemented and must return a array.
For this call, you just pass the event name as array key and the name of your callback function as array value.
<?php
namespace SwagEvents\Subscriber;
use Doctrine\Common\Collections\ArrayCollection;
use Enlight\Event\SubscriberInterface;
use SwagEvents\Components\NameClass3;
use SwagEvents\Components\NameClass4;
class SwagEventsSubscriber implements SubscriberInterface
{
/**
* @return array
*/
public static function getSubscribedEvents()
{
return [
'SwagEvent_Controller_collectAction' => 'onCollect',
];
}
/**
* @param \Enlight_Event_EventArgs $args
*
* @return ArrayCollection
*/
public function onCollect(\Enlight_Event_EventArgs $args)
{
return new ArrayCollection(
[
new NameClass3(),
new NameClass4()
]
);
}
}
The second step is to register the subscriber class in the PluginBaseDirectory/Resources/services.xml
.
To tell Shopware that the registered service is a subscriber class add the shopware.event_subscriber
tag.
xml<tag name="shopware.event_subscriber"/>
<container xmlns="http://symfony.com/schema/dic/services"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd">
<services>
<service id="swag_events.subscriber.swag_event_subscriber" class="SwagEvents\Subscriber\SwagEventsSubscriber">
<tag name="shopware.event_subscriber"/>
</service>
</services>
</container>
(in this case it is onCollect
). The shopware event manager will now automatically call this callback
as soon as the event SwagEvent_Controller_collectAction
occurs.
The services (subscriber) in the services.xml will be registered automatically by installing the plugin.
What do the subscribers now look like? The MySubscriber
subscriber gives a good example:
<?php
namespace Shopware\MyPlugin\Subscriber;
class MySubscriber implements \Enlight\Event\SubscriberInterface
{
public static function getSubscribedEvents()
{
return array(
'Enlight_Controller_Dispatcher_ControllerPath_Frontend_MyPlugin' => 'onGetControllerPromotionFrontend',
'Enlight_Controller_Dispatcher_ControllerPath_Api_Promotion' => 'getApiControllerPromotion',
);
}
public function onGetControllerPromotionFrontend()
{
return __DIR__ . '/../Controllers/Frontend/MyPlugin.php';
}
public function getApiControllerPromotion(\Enlight_Event_EventArgs $args)
{
return __DIR__ . '/../Controllers/Api/Promotion.php';
}
}
getSubscribedEvents
will return an array that maps event names to callback methods within the subscriber.
It is basically the same mechanism that is used in the Bootstrap.php
, but it replaces subscribeEvent
calls with subscriber
classes.
This has two main benefits:
By the way: not only events can be handled in this way: hooks can also be registered using subscribers. In order to get started with subscribers, you can make use of the Shopware plugin code generator.
By the nature of events, you will always need to know the context in order to work with them properly. For that reason, there is no generalized overview of all events. Instead of that, we suggest to step into the code, you want to extend.
Application events are those events, that are explicitly emitted for a certain purpose, e.g. "stop you from buying this" or "modify basket item price". Let's imagine, you want to stop a user from adding a certain item to the cart. The easiest approach is to have a look which controller is responsible for the behavior you want to influence - in our case "adding items to the cart". So in this example an item is added to the cart with an opened developer toolbar:
Here you can see, that the "ajaxAddArticle" method of the "checkout" controller is called. That method will then call
\sBasket::sAddArticle
which takes care of the rest. Looking at that method, the following event is emitted:
$this->eventManager->notifyUntil(
'Shopware_Modules_Basket_AddArticle_Start',
array(
'subject' => $this,
'id' => $id,
"quantity" => $quantity
)
)
That event will finally allow us to reject certain products from being added to the basket. Stepping through the code this way is usually a good and fast way to find appropriate events.
Global events are events that are emitted by our framework automatically. They are available in any request and useful, if you cannot find a more specific application event. In order to understand the control flow, consider this diagram:
Generally speaking Shopware is about converting user requests into shop responses (green). In order to do so, the so called "front controller" (red) will handle requests and subrequests, until there is nothing more to handle. The front controller will call the dispatcher (red) which will figure out, which controller is responsible for the current request. Finally a controller (blue) will be called which might a) return a response directly b) render a template / view or c) trigger another subrequest by forwarding to another controller.
In regards to events, this can be told apart in these steps:
All these steps come with different events:
The router mainly has the two events Enlight_Controller_Front_RouteStartup
(before the routing) and
Enlight_Controller_Front_RouteShutdown
(after the routing). During routing the event Enlight_Controller_Router_Route
is emitted - it allows you to perform your own routings via plugin. See \Shopware\Components\Routing\Router
for more details.
It is important to remember, that the full routing information (module, controller, action) is only passed with the
"RouteShutdown" event. Any event before will not have this information.
The first dispatch event available is Enlight_Controller_Front_StartDispatch
- it is also the first useful event in the
Shopware stack. As it is even triggered before the routing, you will not have routing information available. So choosing
one of the later dispatching events might be more suitable for some scenarios. In those cases, Enlight_Controller_Front_DispatchLoopStartup
might be an alternative: It is emitted just before the dispatcher enters the actual dispatch loop. Within that loop
Enlight_Controller_Front_PreDispatch
and Enlight_Controller_Front_PostDispatch
are emitted: Those two events
surround the controller dispatching. During that controller dispatching Enlight_Controller_Dispatcher_ControllerPath_MODULE_CONTROLLER
is emitted: It will allow you to return a path to the controller MODULE_CONTROLLER
.
After all (sub)requests are handled, the event Enlight_Controller_Front_DispatchLoopShutdown
is emitted. It is one of the latest events in the stack.
Controller dispatching will happen between the Enlight_Controller_Front_PreDispatch
and Enlight_Controller_Front_PostDispatch
events. As the controller dispatching is all about calling controller actions to handle a certain request (e.g. checkout/cart)
it is very useful to modify request parameters, view variables or even the view itself.
Before actually calling a controller action (e.g. checkout::cartAction), Shopware will automatically emit three
PreDispatch events for that controller: Enlight_Controller_Action_PreDispatch
does not contain any specific namespacing,
so it is available for any request. Enlight_Controller_Action_PreDispatch_MODULE
does contain the module name, e.g. frontend
,
backend
, widgets
or api
. It can be used to only subscribe to the PreDispatch event of a certain module. Finally
Enlight_Controller_Action_PreDispatch_MODULE_CONTROLLER
will contain the module name as well as the controller name,
e.g. Enlight_Controller_Action_PreDispatch_Backend_Article
- it can be used to subscribe to a specific controller in a specific module.
After the last PreDispatch event was emitted, Shopware will call the actual preDispatch
method of the controller that was figured out by
the routing. In this case it would be \Shopware_Controllers_Frontend_Checkout::preDispatch
. If the method is not implemented
by that controller, the base method of the base controller will be run. Now finally the \Shopware_Controllers_Frontend_Checkout::cartAction
from the example above would be called: From that controller method Shopware will usually call various methods
of the business logic and assign variables to the template.
After the actual controller method was run, the postDispatch
method of the controller will be called, in this case
\Shopware_Controllers_Frontend_Checkout::postDispatch
. Again: If the method is not implemented by the controller, the
postDispatch
method of the base controller will be run. After that, various PostDispatch
event are triggered:
Enlight_Controller_Action_PostDispatchSecure_MODULE_CONTROLLER
Enlight_Controller_Action_PostDispatchSecure_MODULE
Enlight_Controller_Action_PostDispatchSecure
Enlight_Controller_Action_PostDispatch_MODULE_CONTROLLER
Enlight_Controller_Action_PostDispatch_MODULE
Enlight_Controller_Action_PostDispatch
Basically the same mechanics as for the PreDispatch
event apply (so there is a MODULE_CONTROLLER and a MODULE suffix)
as well as a "global" PostDispatch event without suffix. Notice that there are two types of PostDispatch event:
Enlight_Controller_Action_PostDispatchSecure*
will only be emitted, if a template is available and no exception
occurred before; the Enlight_Controller_Action_PostDispatch
event (without secure as suffix) will also be emitted
when an exception occurred or no template was rendered for some reason. Usually the Enlight_Controller_Action_PostDispatchSecure*
events are recommended - if you are not using them, you need to perform the checks by yourself.
Technically container events are also global events - but they are specific to Shopware's integration of the Symfony
dependency injection container. Enlight_Bootstrap_InitResource_SERVICE
will be emitted, if the service with the
name SERVICE
was requested. In your event subscriber just return an instance of that class.
Enlight_Bootstrap_AfterInitResource_SERIVCE
is emitted, when SERVICE
was just loaded by the DI container. You can
use this event to decorate / replace that SERVICE with your own one.
The following examples should briefly show the usage of some common Shopware events.
<service id="swag_events.subscriber.frontend_listing_subscriber"
class="SwagEvents\Subscriber\FrontendListingSubscriber">
<tag name="shopware.event_subscriber"/>
</service>
<?php
namespace SwagEvents\Subscriber;
use Enlight\Event\SubscriberInterface;
class FrontendListingSubscriber implements SubscriberInterface
{
public static function getSubscribedEvents()
{
return [
'Enlight_Controller_Action_PostDispatchSecure_Frontend_Listing' => 'onFrontendListing'
];
}
public function onFrontendListing(\Enlight_Event_EventArgs $args)
{
/** @var \Shopware_Controllers_Frontend_Listing $subject */
$subject = $args->getSubject();
// ...
}
}
This controller will subscribe to the PostDispatchSecure_Frontend_Listing
event - so it will only be called after
the frontend listing controller and only if there was no exception and a template is available. Notice, that template
variables and the template directory Resources/views
is registered. By registering the Resources/views
directory
this way, you can overwrite / extend core templates, if you create them with the same name as in the core. See
the plugin quick start guide for more
infos.
Controllers are loaded automatically if you use the directories PluginBaseDirectory/Controllers/(Frontend | Backend | Api | Widgets)/YourController.php
.
Because of that a manual controller registration is not necessary.
Notice that when you create a controller you have to register a template directory manually as well. This is needed, as it makes sure, that all your templates are available in the controller - so the automatic template loading will work.
For more about controller, see the controller documentation.
Hooks are the "last resort" for a programmer: If there is no good global or application event for your use case, hooks will allow you to literally "hook" into an existing function and execute your code before, afterwards or even instead the original function.
Hooks are very powerful - but also very tightly bound to our internal code. As such, events should always be used
whenever possible, and hooks should only be used as a last resort. For that same reason, we don't allow hooks on every
class, but only for controllers, core classes and repositories.
Since Shopware 5.6 every hookable class implements the Enlight_Hook
interface. Usually you will recognize hooks by the
FQN::METHOD::TYPE
syntax, e.g. sBasket::sGetBasket::after
. Valid types are before
, replace
and after
.
There is a more detailed blog post
about hooks in Shopware and an event list.