+ All Categories
Home > Engineering > A Series of Fortunate Events - Drupalcon Europe, Amsterdam 2014

A Series of Fortunate Events - Drupalcon Europe, Amsterdam 2014

Date post: 27-Nov-2014
Category:
Upload: matthiasnoback
View: 816 times
Download: 6 times
Share this document with a friend
Description:
Events are the new hooks. But what is an event really? How can you best describe an event in your code? What types of events are there, and how do you decide whether or not to implement something as an event? In this talk we take a look at how events are essential to processing a Request and producing a Response. We take a look at the Symfony EventDispatcher and related classes that you need when you want to hook into this process. When you know all about the event system and how you can implement your own events, we discuss some situations which may or may not be good use cases for events. You will learn to decide if using events is the right solution for your problem.
Popular Tags:
98
Track: PHP Feedback: /session/series-fortunate-events Twitter: @matthiasnoback Matthias Noback A Series of Fortunate Events
Transcript
Page 1: A Series of Fortunate Events - Drupalcon Europe, Amsterdam 2014

Track: PHPFeedback: /session/series-fortunate-events

Twitter: @matthiasnoback

Matthias Noback

A Series of Fortunate Events

Page 2: A Series of Fortunate Events - Drupalcon Europe, Amsterdam 2014

What are events, really?

Things that happen

Page 3: A Series of Fortunate Events - Drupalcon Europe, Amsterdam 2014

They trigger actions

Page 4: A Series of Fortunate Events - Drupalcon Europe, Amsterdam 2014

Just now...Attendees arrived,

triggered me to turn on microphone,

which triggered you to stop talking,

which triggered me to start talking

Page 5: A Series of Fortunate Events - Drupalcon Europe, Amsterdam 2014

Events in software Events model what happened in a system

Page 6: A Series of Fortunate Events - Drupalcon Europe, Amsterdam 2014

Other parts of the system can respond to what happened

Page 7: A Series of Fortunate Events - Drupalcon Europe, Amsterdam 2014

Imperative programmingOnly commands

doThis();

doThat();

updateSomething($something);

return $something;

Page 8: A Series of Fortunate Events - Drupalcon Europe, Amsterdam 2014

Extracting eventsdoThis();// this was done

doThat();// that was done

updateSomething($something)// something was updated

return $something;

Page 9: A Series of Fortunate Events - Drupalcon Europe, Amsterdam 2014

Starting positionclass PostService{    ...    function addComment($postId, $comment)     {        $post = $this­>fetchPost($postId);        $post­>addComment($comment);        $this­>save($post);

        $this­>logger­>info('New comment');        $this­>mailer­>send('New comment');    }}

Page 10: A Series of Fortunate Events - Drupalcon Europe, Amsterdam 2014

Starting positionclass PostService {    function __construct(        Mailer $mailer,        Logger $logger    ) {        $this­>mailer = $mailer;        $this­>logger = $logger;    }

    function addComment($postId, $comment)     {        ...    }}

Page 11: A Series of Fortunate Events - Drupalcon Europe, Amsterdam 2014

Making events explicitclass PostService {    function addComment($postId, $comment)    {        ...        $this­>newCommentAdded();    }

    function newCommentAdded()     {        $this­>logger­>info('New comment');        $this­>mailer­>send('New comment');    }}

Page 12: A Series of Fortunate Events - Drupalcon Europe, Amsterdam 2014

Dependency graph

PostServicePostService

Mailer

Logger

Page 13: A Series of Fortunate Events - Drupalcon Europe, Amsterdam 2014

Design issues (1)I don't think the PostService should know how to use a Mailer and a Logger

Page 14: A Series of Fortunate Events - Drupalcon Europe, Amsterdam 2014

Design issues (2)I want to change the behavior of PostService without modifying the class itself

Page 15: A Series of Fortunate Events - Drupalcon Europe, Amsterdam 2014

Fix the problems

By introducing events!(later)

Page 16: A Series of Fortunate Events - Drupalcon Europe, Amsterdam 2014

Observer patternNotify other parts of the application when a change occurs

class PostService {    function newCommentAdded()    {        foreach ($this­>observers as $observer) {            $observer­>notify();        }    }}

Page 17: A Series of Fortunate Events - Drupalcon Europe, Amsterdam 2014

Observer contract

interface Observer{    function notify();}

Subject knows nothing about its observers, except their very simple interface

Page 18: A Series of Fortunate Events - Drupalcon Europe, Amsterdam 2014

Concrete observersclass LoggingObserver implements Observer{    function __construct(Logger $logger)     {        $this­>logger = $logger;    }

    function notify()     {        $this­>logger­>info('New comment');    }}

Page 19: A Series of Fortunate Events - Drupalcon Europe, Amsterdam 2014

Concrete observers

class NotificationMailObserver implements Observer{    function __construct(Mailer $mailer)    {        $this­>mailer = $mailer;    }

    function notify()    {        $this­>mailer­>send('New comment');    }}

Page 20: A Series of Fortunate Events - Drupalcon Europe, Amsterdam 2014

Configurationclass PostService{    function __construct(array $observers)     {        $this­>observers = $observers;    }}

$postService = new PostService(    array(        new LoggingObserver($logger),        new NotificationMailObserver($mailer)    ));

Page 21: A Series of Fortunate Events - Drupalcon Europe, Amsterdam 2014

Before

PostServicePostService

Mailer

Logger

Page 22: A Series of Fortunate Events - Drupalcon Europe, Amsterdam 2014

After

NotificationMailObserver

Observer

Observer

LoggingObserver

Mailer

Logger

PostService

Page 23: A Series of Fortunate Events - Drupalcon Europe, Amsterdam 2014

Design Principles Party

Page 24: A Series of Fortunate Events - Drupalcon Europe, Amsterdam 2014

Single responsibilityEach class has one small,

well-defined responsibility

Page 25: A Series of Fortunate Events - Drupalcon Europe, Amsterdam 2014

Single responsibility● PostService:

“add comments to posts”

● LoggingObserver: “write a line to the log”

● NotificationMailObserver: “send a notification mail”

Page 26: A Series of Fortunate Events - Drupalcon Europe, Amsterdam 2014

Single responsibilityWhen a change is required, it can be isolated to just a small part of the application

Page 27: A Series of Fortunate Events - Drupalcon Europe, Amsterdam 2014

Single responsibility● “Capitalize the comment!”: PostService

● “Use a different logger!”: LoggerObserver

● “Add a timestamp to the notification mail!”: NotificationMailObserver

Page 28: A Series of Fortunate Events - Drupalcon Europe, Amsterdam 2014

Dependency inversionDepend on abstractions, not on concretions

Page 29: A Series of Fortunate Events - Drupalcon Europe, Amsterdam 2014

Dependency inversionFirst PostService depended on something concrete: the Mailer, the Logger.

Page 30: A Series of Fortunate Events - Drupalcon Europe, Amsterdam 2014

Mailer

LoggerPostService

Page 31: A Series of Fortunate Events - Drupalcon Europe, Amsterdam 2014

Dependency inversionNow it depends on something abstract: an Observer

Page 32: A Series of Fortunate Events - Drupalcon Europe, Amsterdam 2014

Observer

Observer

PostService

Page 33: A Series of Fortunate Events - Drupalcon Europe, Amsterdam 2014

Dependency inversionOnly the concrete observers depend on concrete things like Mailer and Logger

Page 34: A Series of Fortunate Events - Drupalcon Europe, Amsterdam 2014

NotificationMailObserver

LoggingObserver

Mailer

Logger

Page 35: A Series of Fortunate Events - Drupalcon Europe, Amsterdam 2014

Open/closedA class should be open for extension and closed for modification

Page 36: A Series of Fortunate Events - Drupalcon Europe, Amsterdam 2014

Open/closedYou don't need to modify the class to change its behavior

Page 37: A Series of Fortunate Events - Drupalcon Europe, Amsterdam 2014

Observer

Observer Observer

PostService

Page 38: A Series of Fortunate Events - Drupalcon Europe, Amsterdam 2014

Open/closedWe made it closed for modification,

open for extension

Page 39: A Series of Fortunate Events - Drupalcon Europe, Amsterdam 2014

Event data

Mr. Boddy was murdered!● By Mrs. Peacock● In the dining room● With a candle stick

Page 40: A Series of Fortunate Events - Drupalcon Europe, Amsterdam 2014

Currently missing!

class LogNewCommentObserver implements Observer{    function notify()     {        // we'd like to be more specific        $this­>logger­>info('New comment');    }}

Page 41: A Series of Fortunate Events - Drupalcon Europe, Amsterdam 2014

Event objectclass CommentAddedEvent {    public function __construct($postId, $comment)    {        $this­>postId = $postId;        $this­>comment = $comment;    }

    function comment()    {        return $this­>comment;    }

    function postId()    {        return $this­>postId;    }}

Page 42: A Series of Fortunate Events - Drupalcon Europe, Amsterdam 2014

Event object

We use the event object to store the context of the event

Page 43: A Series of Fortunate Events - Drupalcon Europe, Amsterdam 2014

From observer...

interface Observer{    function notify();}

Page 44: A Series of Fortunate Events - Drupalcon Europe, Amsterdam 2014

… to event handler

interface CommentAddedEventHandler{    function handle(CommentAddedEvent $event);}

Page 45: A Series of Fortunate Events - Drupalcon Europe, Amsterdam 2014

Event handlersclass LoggingEventHandler implements    CommentAddedEventHandler{    function __construct(Logger $logger)    {        $this­>logger = $logger;    }

    public function handle(CommentAddedEvent $event)     {        $this­>logger­>info(            'New comment' . $event­>comment()        );    }}

Page 46: A Series of Fortunate Events - Drupalcon Europe, Amsterdam 2014

Event handlers

class NotificationMailEventHandler implements    CommentAddedEventHandler{    function __construct(Mailer $mailer)    {        $this­>mailer = $mailer;    }

    public function handle(CommentAddedEvent $event)     {        $this­>mailer­>send(            'New comment: ' . $event­>comment();        );    }}

Page 47: A Series of Fortunate Events - Drupalcon Europe, Amsterdam 2014

Configuration

class PostService{    function __construct(array $eventHandlers)    {        $this­>eventHandlers = $eventHandlers;    }}

$postService = new PostService(    array(        new LoggingEventHandler($logger),        new NotificationMailEventHandler($mailer)    ));

Page 48: A Series of Fortunate Events - Drupalcon Europe, Amsterdam 2014

Looping over event handlersclass PostService{    public function addComment($postId, $comment)    {        $this­>newCommentAdded($postId, $comment);    }

    function newCommentAdded($postId, $comment)     {        $event = new CommentAddedEvent(            $postId,             $comment        );

        foreach ($this­>eventHandlers as $eventHandler) {             $eventHandler­>handle($event);        }    }}

Page 49: A Series of Fortunate Events - Drupalcon Europe, Amsterdam 2014

Introducing a MediatorInstead of talking to the event handlers

Let's leave the talking to a mediator

Page 51: A Series of Fortunate Events - Drupalcon Europe, Amsterdam 2014

Before

LoggingEventHandler::handle()

NotificationMailEventHandler::handle()

PostService

Page 52: A Series of Fortunate Events - Drupalcon Europe, Amsterdam 2014

After

LoggingEventHandler::handle()

NotificationMailEventHandler::handle()

EventDispatcherPostService

Page 53: A Series of Fortunate Events - Drupalcon Europe, Amsterdam 2014

In codeclass PostService{    function __construct(EventDispatcherInterface $dispatcher)    {        $this­>dispatcher = $dispatcher;    }

    function newCommentAdded($postId, $comment)     {        $event = new CommentAddedEvent($postId, $comment);

        $this­>dispatcher­>dispatch(            'comment_added',            $event        );    }}

Page 54: A Series of Fortunate Events - Drupalcon Europe, Amsterdam 2014

Event class

use Symfony\Component\EventDispatcher\Event;

class CommentAddedEvent extends Event{    ...}

Custom event classes should extend Symfony Event class:

Page 55: A Series of Fortunate Events - Drupalcon Europe, Amsterdam 2014

Configurationuse Symfony\Component\EventDispatcher\Event;

$dispatcher = new EventDispatcher();

$loggingEventHandler = new LoggingEventHandler($logger);

$dispatcher­>addListener(    'comment_added',     array($loggingEventHandler, 'handle'));...

$postService = new PostService($dispatcher);

Page 56: A Series of Fortunate Events - Drupalcon Europe, Amsterdam 2014

Symfony2 - and Drupal8!● An event dispatcher is available as the event_dispatcher service

● You can register event listeners using service tags

Page 57: A Series of Fortunate Events - Drupalcon Europe, Amsterdam 2014

Inject the event dispatcher# your­module­name.service.yml

services:

post_service:class: PostServicearguments: [@event_dispatcher]

Page 58: A Series of Fortunate Events - Drupalcon Europe, Amsterdam 2014

Register your listeners# your­module­name.service.yml

services:   ...

logging_event_handler:class: LoggingEventHandlerarguments: [@logger]tags:

­ { name: kernel.event_listenerevent: comment_addedmethod: handle 

}

Page 59: A Series of Fortunate Events - Drupalcon Europe, Amsterdam 2014

Events and application flowSymfony2 uses events to generate response for any given HTTP request

Page 60: A Series of Fortunate Events - Drupalcon Europe, Amsterdam 2014

The HttpKernel$request = Request::createFromGlobals();

// $kernel is in an instance of HttpKernelInterface

$response = $kernel­>handle($request);

$response­>send();

Page 61: A Series of Fortunate Events - Drupalcon Europe, Amsterdam 2014

Kernel events

Page 62: A Series of Fortunate Events - Drupalcon Europe, Amsterdam 2014

kernel.request● Route matching

● Authentication

Page 63: A Series of Fortunate Events - Drupalcon Europe, Amsterdam 2014

kernel.controller● Replace the controller

● Do some access checks

Page 64: A Series of Fortunate Events - Drupalcon Europe, Amsterdam 2014

kernel.view● Render a template

Page 65: A Series of Fortunate Events - Drupalcon Europe, Amsterdam 2014

kernel.response● Modify the response

● E.g. inject the Symfony toolbar

Page 66: A Series of Fortunate Events - Drupalcon Europe, Amsterdam 2014

kernel.exception● Generate a response

● Render a nice page with the stack trace

Page 67: A Series of Fortunate Events - Drupalcon Europe, Amsterdam 2014

Special types of events● Kernel events are not merely

notifications

● They allow other parts of the application to step in and modify or override behavior

Page 68: A Series of Fortunate Events - Drupalcon Europe, Amsterdam 2014

Chain of responsibility

Handler 3Handler 1 Handler 2

Some sort of request

Some sort of request

Response

Some sort of request

Page 69: A Series of Fortunate Events - Drupalcon Europe, Amsterdam 2014

Symfony example

Listener 3Listener 1 Listener 2

Exception! Exception!

Response

I've got an exception! What should I tell the user?

Page 70: A Series of Fortunate Events - Drupalcon Europe, Amsterdam 2014

Propagationclass HandleExceptionListener{

function onKernelException(GetResponseForExceptionEvent $event

) {$event­>setResponse(new Response('Error!'));

// this is the best response ever, don't let// other spoil it!

$event­>stopPropagation();}

}

Page 71: A Series of Fortunate Events - Drupalcon Europe, Amsterdam 2014

Priorities$dispatcher = new EventDispatcher();

$dispatcher­>addListener(    'comment_added',     array($object, $method),    // priority    100);

Page 72: A Series of Fortunate Events - Drupalcon Europe, Amsterdam 2014

Concerns

Page 73: A Series of Fortunate Events - Drupalcon Europe, Amsterdam 2014

Concern 1: Hard to understand“Click-through understanding” impossible

$event = new CommentAddedEvent($postId, $comment);

$this­>dispatcher­>dispatch('comment_added',$event

);

Page 74: A Series of Fortunate Events - Drupalcon Europe, Amsterdam 2014

interface EventDispatcherInterface{    function dispatch($eventName, Event $event = null);

    ...}

Page 75: A Series of Fortunate Events - Drupalcon Europe, Amsterdam 2014

SolutionUse Xdebug

Page 76: A Series of Fortunate Events - Drupalcon Europe, Amsterdam 2014

Concern 2: Out-of-domain concepts● “Comment”

● “PostId”

● “Add comment to post”

● “Dispatcher” (?!)

Page 77: A Series of Fortunate Events - Drupalcon Europe, Amsterdam 2014

We did a good thing

We fixed coupling issues

Page 78: A Series of Fortunate Events - Drupalcon Europe, Amsterdam 2014

She's called Cohesion

But this guy, Coupling, has a sister

Page 79: A Series of Fortunate Events - Drupalcon Europe, Amsterdam 2014

Cohesion● Belonging together

● Concepts like “dispatcher”, “event listener”, even “event”, don't belong in your code

Page 80: A Series of Fortunate Events - Drupalcon Europe, Amsterdam 2014

Solutions (1)Descriptive, explicit naming:

● NotificationMailEventListener becomes SendNotificationMailWhenCommentAdded

● CommentAddedEvent becomes CommentAdded

● onCommentAdded becomes whenCommentAdded

Page 81: A Series of Fortunate Events - Drupalcon Europe, Amsterdam 2014

Solutions (1)

This also hides implementation details!

Page 82: A Series of Fortunate Events - Drupalcon Europe, Amsterdam 2014

Solutions (2)Use an event dispatcher for things

that are not naturally cohesive anyway

Page 83: A Series of Fortunate Events - Drupalcon Europe, Amsterdam 2014

Solutions (2)Use something else

when an event dispatcher causes low cohesion

Page 84: A Series of Fortunate Events - Drupalcon Europe, Amsterdam 2014

Example: resolving the controller

$event = new GetResponseEvent($request);

$dispatcher­>dispatch('kernel.request', $event);

$controller = $request­>attributes­>get('_controller');

$controller = $controllerResolver­>resolve($request);

Page 85: A Series of Fortunate Events - Drupalcon Europe, Amsterdam 2014

Concern 3: Loss of control● You rely on event listeners to do some really

important work● How do you know if they are in place

and do their job?

Page 86: A Series of Fortunate Events - Drupalcon Europe, Amsterdam 2014

Solution● “Won't fix”

● You have to learn to live with it

Page 87: A Series of Fortunate Events - Drupalcon Europe, Amsterdam 2014

It's good

Page 88: A Series of Fortunate Events - Drupalcon Europe, Amsterdam 2014

Inversion of control

exercise control

give up control!

Page 89: A Series of Fortunate Events - Drupalcon Europe, Amsterdam 2014

Just like...● A router determines the right controller

● The service container injects the right constructor arguments

● And when you die, someone will bury your body for you

Page 90: A Series of Fortunate Events - Drupalcon Europe, Amsterdam 2014

Sometimes I'mterrified,

mortified,petrified,stupefied,

by inversion of control too

Page 91: A Series of Fortunate Events - Drupalcon Europe, Amsterdam 2014

But it will● lead to better design

● require less change

● make maintenance easier

Page 92: A Series of Fortunate Events - Drupalcon Europe, Amsterdam 2014

PresentationFinishedAskQuestionsWhenPresentationFinished

SayThankYouWhenNoMoreQuestions

Page 93: A Series of Fortunate Events - Drupalcon Europe, Amsterdam 2014

Symfony, service definitions, kernel events

leanpub.com/a-year-with-symfony/c/drupalcon

Get a 30% discount!

Page 94: A Series of Fortunate Events - Drupalcon Europe, Amsterdam 2014

Class and package design principles

leanpub.com/principles-of-php-package-design/c/drupalcon

Get a $10 discount!

Page 95: A Series of Fortunate Events - Drupalcon Europe, Amsterdam 2014

Design patterns● Observer

● Mediator

● Chain of responsibility

● ...

Design Patterns by “The Gang of Four”

Page 96: A Series of Fortunate Events - Drupalcon Europe, Amsterdam 2014

SOLID principles● Single responsibility

● Open/closed

● Dependency inversion

● ...

Agile Software Development by Robert C. Martin

Page 97: A Series of Fortunate Events - Drupalcon Europe, Amsterdam 2014

Images● www.ohiseered.com/2011_11_01_archive.html

● Mrs. Peacock, Candlestick:www.cluecult.com

● Leonardo DiCaprio: screenrant.com/leonardo-dicaprio-defends-wolf-wall-street-controversy/

● Book covers:Amazon

● Party:todesignoffsite.com/events-2/to-do-closing-party-with-love-design/

● Russell Crowe: malinaelena.wordpress.com/2014/04/18/top-8-filme-cu-russell-crowe/

Page 98: A Series of Fortunate Events - Drupalcon Europe, Amsterdam 2014

Twitter: @matthiasnoback

https://amsterdam2014.drupal.org/session/series-fortunate-events

What did you think?


Recommended