We now know that we have an Event system in place in Drupal 8 which has been imported from Symfony, the Symfony Event Dispatcher Component. The Event system will likely replace the traditional hook system altogether in coming drupal versions. This talk will try to throw some light on how to create, trigger, subscribe and listen to events in drupal 8.
This session will mainly elustrate:
What are Events. Why do we need to trigger events?
What all do we need Events for and where we are still stuck with hooks.
Events in Drupal 8
Symfony Event Dispatcher component
When to fire an event.
When to expose your hook in Drupal 8? Never.
How to create and dispatch an Event from your module.
How to listen or subscribe to an Event from your custom Event or an event from core.
How are event listeners and event subscribers different.
Different types of Event dispatchers.
Examples where events have been exhaustively used.
A simple demo implementation of events.
Attendees should be familiar with:
Basic Drupal 8 module development.
Basic OOP principles and practices.
Symfony components used in D8.
After attending this session attendees will be able to:
Create their custom events in Drupal 8
Subscribe to the events from core and other modules and to their custom events too.
Will have a knowledge of where to explore the events and where we have to still use hooks.
4. Overview
The Symfony Event Dispatcher component.
Installation and usage
Creating and dispatching an event
Subscribing/Listening to events
Events in Drupal 8
Creating Events.
Subscribing to Events
6. “ The idea is to be able to run random code at given places in the
engine. This random code should then be able to do whatever needed to
enhance the functionality. The places where code can be executed are
called “hooks” and are defined by a fixed interface.
~ Dries Buytaert.
7. “ The Event Dispatcher component provides
tools that allow your application components to
communicate with each other by dispatching
events and listening to them.
8. “ an event is an action or an occurrence
recognised by software that may be handled
by software.
event?
13. Installing the symfony event
dispatcher component
Text
Text
Use the official Git repository
( )https://github.com/symfony/event-dispatcher
composer require symfony/event-dispatcher
Using version ^3.2 for symfony/event-dispatcher
./composer.json has been created
Loading composer repositories with package information
Updating dependencies (including require-dev)
- Installing symfony/event-dispatcher (v3.2.6)
Loading from cache
symfony/event-dispatcher suggests installing symfony/dependency-injection ()
symfony/event-dispatcher suggests installing symfony/http-kernel ()
Writing lock file
Generating autoload files
14. Using the symfony event dispatcher
component
1. Event - representing the event or the state of the application.
2. Dispatcher - to notify the subscribers or listeners about the occurrence of the
event.
3. Subscriber/Listener - to extend the application once the event has occurred.
15. Pub-Sub pattern
The pub sub exemplifies the
proper decoupling of
components of an application.
Publishers publish the messages
into classes without knowledge
of which subscribers would be
interested in the message.
Subscribers express interest in
one or more classes and only
receive messages that are of
interest, without knowledge of
which publishers.
Mediator pattern
Define an object that
encapsulates how a set of
objects interact.
Mediator promotes loose
coupling by keeping objects
from referring to each other
explicitly, and it lets you vary
their interaction independently.
Design an intermediary to
decouple many peers.
17. Workflow
A listener (PHP object) tells a central dispatcher object that it wants to listen to
the 'xyz' event
At some point, Symfony tells the dispatcher object to dispatch the 'xyz' event,
passing with it an Event object that has access to the Object defining the state
of the application at that point.
The dispatcher notifies (i.e. calls a method on) all listeners of the 'xyz' event,
allowing each of them to make modifications to the State object.
19. the dispatcher
// create an EventDispatcher instance.
$dispatcher = new EventDispatcher();
// the order is somehow created or retrieved
// contains the state of our application
// or the information we want expose.
$order = new Order();
// ...
// create the OrderPlacedEvent and dispatch it
$event = new OrderPlacedEvent($order);
// dispatch the event.
$dispatcher->dispatch(OrderPlacedEvent::NAME, $event);
// or $dispatcher->dispatch('order.placed', $event);
20. the event
/**
* The order.placed event is dispatched each time an order is created
* in the system.
*/
class OrderPlacedEvent extends Event {
const NAME = 'order.placed';
protected $order;
public function __construct(Order $order) {
$this->order = $order;
}
public function getOrder() {
return $this->order;
}
}
21. the base event
/**
* Event is the base class for classes containing event data.
* This class contains no event data. It is used by events that do not pass
* state information to an event handler when an event is raised.
*/
class Event
{
/**
* @var bool Whether no further event listeners should be triggered
*/
private $propagationStopped = false;
/**
* Returns whether further event listeners should be triggered.
*/
public function isPropagationStopped()
{
return $this->propagationStopped;
}
/**
* Stops the propagation of the event to further event listeners.
*/
public function stopPropagation()
{
$this->propagationStopped = true;
}
}
22. “ The base Event class provided by the Event
Dispatcher component is deliberately sparse to
allow the creation of API specific event objects by
inheritance using OOP. This allows for elegant and
readable code in complex applications.
23. the subscriber
class StoreSubscriber implements EventSubscriberInterface {
public static function getSubscribedEvents() {
return array(
KernelEvents::RESPONSE => array(
array('onKernelResponsePre', 10),
array('onKernelResponsePost', -10),
),
OrderPlacedEvent::NAME => 'onStoreOrder',
);
}
public function onKernelResponsePre(FilterResponseEvent $event) {
// do something.
}
public function onKernelResponsePost(FilterResponseEvent $event) {
// do something.
}
public function onStoreOrder(OrderPlacedEvent $event) {
// do something.
}
}
24. the listener
class AcmeListener {
// ...
public function onFooAction(Event $event) {
// ... do something
}
}
// This is very similar to a subscriber class,
// except that the class itself cant tell the dispatcher which events it should listen to.
25. register listener/subscriber
// create an EventDispatcher instance.
$dispatcher = new EventDispatcher();
$subscriber = new StoreSubscriber();
// Register subscriber
$dispatcher->addSubscriber($subscriber);
// add a listener
$listener = new AcmeListener();
$dispatcher->addListener('acme.foo.action', array($listener, 'onFooAction'));
// create the OrderPlacedEvent and dispatch it
$event = new OrderPlacedEvent($order);
// dispatch the event.
$dispatcher->dispatch(OrderPlacedEvent::NAME, $event);
// or $dispatcher->dispatch(order.placed, $event);
26. other ways to register
# app/config/services.yml
services:
kernel.listener.your_listener_name:
class: AppBundleEventListenerAcmeExceptionListener
tags:
- { name: kernel.event_listener, event: kernel.exception, method:
onKernelException }
With the use of ContainerAwareEventDispatcher and dependency
injection:
Use the to tag services as event
listeners/subscribers.
Define event subscriber/listener as a service.
Tag them as kernel.event_listener or kernel.event_subscriber.
RegisterListenersPass
27. subscriber vs listener
Event listeners and Subscribers serve the same purpose and can be used in
an application indistinctly.
Event listeners can be added via service definition and also with
addListener()method.
Event subscribers are added via service definition and by implementing the
getSubscribedEvents() method and also with addSubscriber() method.
Event subscribers are easier to use and reuse.
Event listener is registered specifying the events on which it listens. The
subscriber has a method telling the dispatcher what events it is listening to.
More here: http://nidashah.com/drupal/events-and-listeners.html
28. more dispatchers
ContainerAwareEventDispatcher
Use services within your events, and subscribers as services
TraceableEventDispatcher
wraps any other event dispatcher and can then be used to determine
which event listeners have been called by the dispatcher
ImmutableEventDispatcher
is a locked or frozen event dispatcher. The dispatcher cannot register
new listeners or subscribers.
29. ContainerAwareEventDispatcher
The ContainerAwareEventDispatcher is a special Event Dispatcher
implementation which is coupled to the service container that is part of the
DependencyInjection component.
It allows services to be specified as event listeners making the
EventDispatcher extremely powerful.
Services are lazy loaded meaning the services attached as listeners will only
be created if an event is dispatched that requires those listeners.
31. TraceableEventDispatcher
The TraceableEventDispatcher is an event dispatcher that wraps any other
event dispatcher and can then be used to determine which event listeners have
been called by the dispatcher.
// the event dispatcher to debug
$eventDispatcher = ...;
$traceableEventDispatcher = new TraceableEventDispatcher( $eventDispatcher, new Stopwatch()
);
$traceableEventDispatcher->addListener(
'event.the_name',
$eventListener,
$priority
);
// dispatch an event
$traceableEventDispatcher->dispatch('event.the_name', $event);
$calledListeners = $traceableEventDispatcher->getCalledListeners();
$notCalledListeners = $traceableEventDispatcher->getNotCalledListeners();
32. ImmutableEventDispatcher
The ImmutableEventDispatcher is a locked or frozen event dispatcher. The
dispatcher cannot register new listeners or subscribers.
The ImmutableEventDispatcher takes another event dispatcher with all the
listeners and subscribers. The immutable dispatcher is just a proxy of this
original dispatcher.
Using it
first create a normal dispatcher (EventDispatcher or
ContainerAwareEventDispatcher) and register some listeners or
subscribers
Now, inject that into an ImmutableEventDispatcher
35. Events are part of the Symfony framework: they allow for different
components of the system to interact and communicate with each
other.
Object oriented way of interaction with core and other modules.
Mediator Pattern
Container Aware dispatcher
Will probably replace hooks in future drupal versions.
36. Since Drupal is using ContainerAwareEventDispatcher, we always
have the dispatcher object available as a service.
Consequently, Drupal supports the service definition way of adding
event subscribers.
Service definition way of adding event listeners is not supported.
something to note
37. 1. Get the dispatcher object from the service container.
2. Create the event.
3. Dispatch the event.
4. Define a service tagged with event_subscriber in services.yml.
5. Implement the EventSubscriberInterface to write getSubscribedEvents()
method to return what events you want to subscribe to.
Workflow in Drupal
39. dispatching the event
$dispatcher = Drupal::service('event_dispatcher');
// or inject as a dependency
$event = new EventDemo($config);
$event = $dispatcher->dispatch(EVENT_NAME, $event);
40. core registering event subscribers
namespace DrupalCoreDependencyInjectionCompiler;
use SymfonyComponentDependencyInjectionContainerBuilder;
use SymfonyComponentDependencyInjectionCompilerCompilerPassInterface;
/**
* Registers all event subscribers to the event dispatcher.
*/
class RegisterEventSubscribersPass implements CompilerPassInterface {
/**
* {@inheritdoc}
*/
public function process(ContainerBuilder $container) {
if (!$container->hasDefinition('event_dispatcher')) {
return;
}
$definition = $container->getDefinition('event_dispatcher');
$event_subscriber_info = [];
foreach ($container->findTaggedServiceIds('event_subscriber') as $id => $attributes) {
// We must assume that the class value has been correctly filled, even if
// the service is created by a factory.
$class = $container->getDefinition($id)->getClass();
$refClass = new ReflectionClass($class);
$interface = 'SymfonyComponentEventDispatcherEventSubscriberInterface';
if (!$refClass->implementsInterface($interface)) {
throw new InvalidArgumentException(sprintf('Service "%s" must implement interface "%s".', $id, $interface));
}
// Get all subscribed events.
foreach ($class::getSubscribedEvents() as $event_name => $params) {
if (is_string($params)) {
$priority = 0;
42. path forward
Writing your own module?
trigger an Event for everything.
Interacting with or alter core?
subscribe to an event (if one is fired).
Hooks … you don't have too many options.
Configuration, Admin forms?
Plugins
Simple Extensions
Tagged services
45. Join Us for Contribution Sprints
Friday, April 28, 2017
First-Time Sprinter
Workshop
9:00am-12:00pm
Room: 307-308
Mentored Core Sprint
9:00am-12:00pm
Room:301-303
General Sprints
9:00am-6:00pm
Room:309-310
#drupalsprints
46. WHAT DID
YOU THINK?
Locate this session at the DrupalCon
Baltimore website:
Take the survey!
https://www.surveymonkey.com/r/dr
upalconbaltimore
http://baltimore2017.drupal.org/schedule