On Github ericgsmith / eventdispatcher
Eric Smith / @ericgsmith / @cameronwildinghttp://ericgsmith.github.io/eventdispatcher
Adventure time!... an event is an action or occurrence recognised by software that may be handled by the software.
Think javascript. Responding to events is something we often do. These events can be anything.Something happened
A form submitted, users logged in, web service called. Refer back to boot, init, exit.The EventDispatcher component provides tools that allow your application components to communicate with each other by dispatching events and listening to them.
A component - drupal is more modern and component based. Component - a bit of functionality we can use. Object the encapsulates the communication of other objects. Example of the mediator design pattern.We have a form that collects a name and email address.
public function submitForm(array &$form, FormStateInterface $form_state) { $name = $form_state->getValue('name'); $email = $form_state->getValue('email'); $this->mailer->mail('example', 'signup_form', $email, .... ['name' => $name]); $this->logger->log('notice', 'Registration of interest submitted...'); $this->crmManager->subscribe($name, $email); $this->mailChimpSubscriptionManager->add($name, $email); $tweet = TweetFactory::create('blah blah ' . $name . ' blah blah'); $this->tweeter->tweet($tweet); }Fictional classes and methods.
Any PHP object that performs some sort of "global" task.
http://symfony.com/doc/current/book/service_container.htmlInject dependencies.
public function myMethod() { $mailer = new Mailhandler(); $mailer->send('This is bad.'); }Tight coupling
protected $mailer; public function __construct(MailHandlerInterface $mailer) { $this->mailer = $mailer; } public function myMethod() { $this->mailer->send('This is better.'); }Class is constructed outside the object. Injected through either constructor or setter method. So, how do we get our object with all its dependencies created outside of it.
final class ExampleModuleEvents { /** docBlock */ const SIGNUP_FORM_SUBMIT = 'module_name.signup_form_submit'; }This doesn't do anything... Used for docs / console. Remember how we had modulename.api
use Symfony\Component\EventDispatcher\Event; class SignupFormEvent extends Event { protected $submittedName; protected $submittedEmail; public function __construct($name, $email) { $this->submittedName = $name; $this->submittedEmail = $email; } public function getSubmittedName() { return $this->submittedName; } public function getSubmittedEmail() { return $this->submittedEmail; } }This is the object we will create and pass around. It has the data we want to share. Extend the event class - it doesn't do a lot by itself. Just keeps the event name and stopPropogation method that we will discuss later. In our example we are not allowing data to be overridden, only notified.
public function submitForm(array &$form, FormStateInterface $form_state) { $name = $form_state->getValue('name'); $email = $form_state->getValue('email'); $event = new SignupFormEvent($name, $email); $this->eventDispatcher->dispatch(ExampleModuleEvents::SIGNUP_FORM_SUBMIT, $event); }Easy as that. But how do we get the event dispatcher?
public static function create(ContainerInterface $container) { return new static(); }Not just for forms Gives a common factory method for creation of objects with dependencies in the service container.
protected $eventDispatcher; public function __construct(EventDispatcherInterface $eventDispatcher) { $this->eventDispatcher = $eventDispatcher; } public static function create(ContainerInterface $container) { return new static($container->get('event_dispatcher')); }This is back on the form class. We depend on the event dispatcher, so inject it as a dependency. Can use getters and setters if you want
public function buildForm(array $form, FormStateInterface $form_state); public function validateForm(array &$form, FormStateInterface $form_state); public function submitForm(array &$form, FormStateInterface $form_state);
use Symfony\Component\EventDispatcher\EventSubscriberInterface; class RegistrationMailer implements EventSubscriberInterface { protected $mailManager; protected $languageManager; public function __construct(MailManagerInterface $mailManager, LanguageManagerInterface $languageManager) { $this->mailManager = $mailManager; $this->languageManager = $languageManager; }; public static function getSubscribedEvents() { $events[ExampleModuleEvents::SIGNUP_FORM_SUBMIT][] = array('onRegister', 0); return $events; } public function onRegister(SignupFormEvent $event) { ... } }Mention priority here Mention ability to subscribe multiple times getSubscribedEvents is called by the dispatcher, it is where we tell it about the callback to use.
public function onRegister($event) { $module = 'event_demo'; $key = 'register_interest'; $to = $event->getSubmittedEmail(); $params = ['name' => $event->getSubmittedName()]; $language = $this->languageManager->getDefaultLanguage(); $this->mailManager->mail($module, $key, $to, $language, $params); }This is just an expanded version of what we were doing in the form submit - now in its own decoupled class.
services: example_module.registration_mailer: class: "Drupal\example_crm_module\EventSubscriber\RegistrationMailer" arguments: ["@plugin.manager.mail", "@language_manager"] tags: - { name: event_subscriber }The tag is how the container aware dispatcher will register our listener. Explain upcoming video.
$event->stopPropagation()Sometimes, when a event can modify the data associated to it, it is a good idea to stop the event. This is the chain of responsibiliy - go through the observers until we find an object that is responsible for this situation.
$request = $event->getRequest(); $config = $this->configFactory->get('system.performance'); $exclude_paths = $config->get('fast_404.exclude_paths'); if ($config->get('fast_404.enabled') && $exclude_paths && !preg_match($exclude_paths, $request->getPathInfo())) { $fast_paths = $config->get('fast_404.paths'); if ($fast_paths && preg_match($fast_paths, $request->getPathInfo())) { $fast_404_html = strtr($config->get('fast_404.html'), ['@path' => Html::escape($request->getUri())]); $response = new Response($fast_404_html, Response::HTTP_NOT_FOUND); $event->setResponse($response); } } }
/** * Implements hook_user_login(). */ function rules_user_login($account) { // Set the account twice on the event: as the main subject but also in the // list of arguments. $event = new UserLoginEvent($account); $event_dispatcher = \Drupal::service('event_dispatcher'); $event_dispatcher->dispatch(UserLoginEvent::EVENT_NAME, $event); }
Eric Smith / @ericgsmith / @cameronwilding