A Series of Fortunate Events 
Matthias Noback 
Twitter: @matthiasnoback
What are events, really? 
Things that happen
They trigger actions
Just now... 
Attendees arrived, 
triggered me to turn on microphone, 
which triggered you to stop talking, 
which triggered me to start talking
Events in software 
Events model what happened in a system
Other parts of the system 
can respond to what happened
Imperative programming 
Only commands 
doThis(); 
doThat(); 
updateSomething($something); 
return $something;
Extracting events 
doThis(); 
// this was done 
doThat(); 
// that was done 
updateSomething($something) 
// something was updated 
return $something;
Starting position 
class 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'); 
} 
}
Starting position 
class PostService 
{ 
function __construct( 
Mailer $mailer, 
Logger $logger 
) { 
$this->mailer = $mailer; 
$this->logger = $logger; 
} 
function addComment($postId, $comment) 
{ 
... 
} 
}
Making events explicit 
class PostService 
{ 
function addComment($postId, $comment) 
{ 
... 
$this->newCommentAdded(); 
} 
function newCommentAdded() 
{ 
$this->logger->info('New comment'); 
$this->mailer->send('New comment'); 
} 
}
Dependency graph 
PPoossttSSeerrvviiccee 
Logger 
Mailer
Design issues (1) 
I don't think the PostService should 
know how to use a Mailer and a Logger
Design issues (2) 
I want to change the behavior of 
PostService without modifying the 
class itself
Fix the problems 
By introducing events! 
(later)
Observer pattern 
Notify other parts of the application 
when a change occurs 
class PostService 
{ 
function newCommentAdded() 
{ 
foreach ($this->observers as $observer) { 
$observer->notify(); 
} 
} 
}
Observer contract 
Subject knows nothing about its 
observers, except their very simple 
interface 
interface Observer 
{ 
function notify(); 
}
Concrete observers 
class LoggingObserver implements Observer 
{ 
function __construct(Logger $logger) 
{ 
$this->logger = $logger; 
} 
function notify() 
{ 
$this->logger->info('New comment'); 
} 
}
Concrete observers 
class NotificationMailObserver implements Observer 
{ 
function __construct(Mailer $mailer) 
{ 
$this->mailer = $mailer; 
} 
function notify() 
{ 
$this->mailer->send('New comment'); 
} 
}
Configuration 
class PostService 
{ 
function __construct(array $observers) 
{ 
$this->observers = $observers; 
} 
} 
$postService = new PostService( 
array( 
new LoggingObserver($logger), 
new NotificationMailObserver($mailer) 
) 
);
Before 
PPoossttSSeerrvviiccee 
Logger 
Mailer
After 
NotificationMailObserver 
Observer 
Observer 
LoggingObserver 
Logger 
Mailer 
PostService
Design Principles Party
Single responsibility 
Each class has one small, 
well-defined responsibility
Single responsibility 
● PostService: 
“add comments to posts” 
● LoggingObserver: 
“write a line to the log” 
● NotificationMailObserver: 
“send a notification mail”
Single responsibility 
When a change is required, it can be 
isolated to just a small part of the 
application
Single responsibility 
● “Capitalize the comment!”: 
PostService 
● “Use a different logger!”: 
LoggerObserver 
● “Add a timestamp to the notification mail!”: 
NotificationMailObserver
Dependency inversion 
Depend on abstractions, not on 
concretions
Dependency inversion 
First PostService depended on 
something concrete: the Mailer, the 
Logger.
PostService Logger 
Mailer
Dependency inversion 
Now it depends on something abstract: 
an Observer
PostService 
Observer 
Observer
Dependency inversion 
Only the concrete observers depend on 
concrete things like Mailer and Logger
LoggingObserver 
NotificationMailObserver 
Logger 
Mailer
Open/closed 
A class should be open for extension and 
closed for modification
Open/closed 
You don't need to modify the class to 
change its behavior
PostService 
Observer 
Observer Observer
Open/closed 
We made it closed for modification, 
open for extension
Event data 
Mr. Boddy was murdered! 
● By Mrs. Peacock 
● In the dining room 
● With a candle stick
Currently missing! 
class LogNewCommentObserver implements Observer 
{ 
function notify() 
{ 
// we'd like to be more specific 
$this->logger->info('New comment'); 
} 
}
Event object 
class CommentAddedEvent 
{ 
public function __construct($postId, $comment) 
{ 
$this->postId = $postId; 
$this->comment = $comment; 
} 
function comment() 
{ 
return $this->comment; 
} 
function postId() 
{ 
return $this->postId; 
} 
}
Event object 
We use the event object 
to store the context of the event
From observer... 
interface Observer 
{ 
function notify(); 
}
… to event handler 
interface CommentAddedEventHandler 
{ 
function handle(CommentAddedEvent $event); 
}
Event handlers 
class LoggingEventHandler implements 
CommentAddedEventHandler 
{ 
function __construct(Logger $logger) 
{ 
$this->logger = $logger; 
} 
public function handle(CommentAddedEvent $event) 
{ 
$this->logger->info( 
'New comment' . $event->comment() 
); 
} 
}
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(); 
); 
} 
}
Configuration 
class PostService 
{ 
function __construct(array $eventHandlers) 
{ 
$this->eventHandlers = $eventHandlers; 
} 
} 
$postService = new PostService( 
array( 
new LoggingEventHandler($logger), 
new NotificationMailEventHandler($mailer) 
) 
);
Looping over event handlers 
class 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); 
} 
} 
}
Introducing a Mediator 
Instead of talking to the event handlers 
Let's leave the talking to a mediator
Mediators for events 
● Doctrine, Zend: Event manager 
● The PHP League: Event emitter 
● Symfony: Event dispatcher
Before 
PostService 
LoggingEventHandler::handle() 
NotificationMailEventHandler::handle()
After 
PostService EventDispatcher 
LoggingEventHandler::handle() 
NotificationMailEventHandler::handle()
In code 
class PostService 
{ 
function __construct(EventDispatcherInterface $dispatcher) 
{ 
$this->dispatcher = $dispatcher; 
} 
function newCommentAdded($postId, $comment) 
{ 
$event = new CommentAddedEvent($postId, $comment); 
$this->dispatcher->dispatch( 
'comment_added', 
$event 
); 
} 
}
Event class 
Custom event classes should extend 
Symfony Event class: 
use SymfonyComponentEventDispatcherEvent; 
class CommentAddedEvent extends Event 
{ 
... 
}
Configuration 
use SymfonyComponentEventDispatcherEvent; 
$dispatcher = new EventDispatcher(); 
$loggingEventHandler = new 
LoggingEventHandler($logger); 
$dispatcher->addListener( 
'comment_added', 
array($loggingEventHandler, 'handle') 
); 
... 
$postService = new PostService($dispatcher);
Symfony2 
● An event dispatcher is available as the 
event_dispatcher service 
● You can register event listeners using 
service tags
Inject the event dispatcher 
# services.yml 
services: 
post_service: 
class: PostService 
arguments: [@event_dispatcher]
Register your listeners 
# services.yml 
services: 
... 
logging_event_handler: 
class: LoggingEventHandler 
arguments: [@logger] 
tags: 
- { 
name: kernel.event_listener 
event: comment_added 
method: handle 
}
Events and application flow 
Symfony2 uses events to generate 
response for any given HTTP request
The HttpKernel 
$request = Request::createFromGlobals(); 
// $kernel is in an instance of HttpKernelInterface 
$response = $kernel->handle($request); 
$response->send();
Kernel events
kernel.request 
● Route matching 
● Authentication
kernel.controller 
● Replace the controller 
● Do some access checks
kernel.view 
● Render a template
kernel.response 
● Modify the response 
● E.g. inject the Symfony toolbar
kernel.exception 
● Generate a response 
● Render a nice page with the stack trace
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
Chain of responsibility 
Some sort 
of request 
Handler 1 Handler 2 Handler 3 
Some sort 
of request 
Some sort 
of request 
Response
Symfony example 
I've got an exception! 
What should I tell the user? 
Listener 1 Listener 2 Listener 3 
Exception! Exception! 
Response
Propagation 
class HandleExceptionListener 
{ 
function onKernelException( 
GetResponseForExceptionEvent $event 
) { 
$event->setResponse(new Response('Error!')); 
// this is the best response ever, don't let 
// other spoil it! 
$event->stopPropagation(); 
} 
}
Priorities 
$dispatcher = new EventDispatcher(); 
$dispatcher->addListener( 
'comment_added', 
array($object, $method), 
// priority 
100 
);
Concerns
Concern 1: Hard to understand 
“Click-through understanding” impossible 
$event = new CommentAddedEvent($postId, $comment); 
$this->dispatcher->dispatch( 
'comment_added', 
$event 
);
interface EventDispatcherInterface 
{ 
function dispatch($eventName, Event $event = null); 
... 
}
Solution 
Use Xdebug
Concern 2: Out-of-domain concepts 
● “Comment” 
● “PostId” 
● “Add comment to post” 
● “Dispatcher” (?!)
We did a good thing 
We fixed coupling issues
But this guy, Coupling, 
has a sister 
She's called Cohesion
Cohesion 
● Belonging together 
● Concepts like “dispatcher”, “event listener”, even 
“event”, don't belong in your code
Solutions (1) 
Descriptive, explicit naming: 
● NotificationMailEventListener becomes 
SendNotificationMailWhenCommentAdded 
● CommentAddedEvent becomes CommentAdded 
● onCommentAdded becomes 
whenCommentAdded
Solutions (1) 
This also hides implementation details!
Solutions (2) 
Use an event dispatcher for things 
that are not naturally cohesive anyway
Solutions (2) 
Use something else 
when an event dispatcher causes low cohesion
Example: resolving the controller 
$event = new GetResponseEvent($request); 
$dispatcher->dispatch('kernel.request', $event); 
$controller = $request->attributes->get('_controller'); 
$controller = $controllerResolver->resolve($request);
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?
Solution 
● “Won't fix” 
● You have to learn to live with it
It's good
exercise 
control 
Inversion of control 
give up 
control!
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
I admit, inversion of control can be scary
But it will 
● lead to better design 
● require less change 
● make maintenance easier
PresentationFinished 
AskQuestionsWhenPresentationFinished 
SayThankYouWhenNoMoreQuestions
Symfony, service definitions, kernel events 
http://leanpub.com/a-year-with-symfony/c/symfony-camp 
Pay even less ;)
Class and package design principles 
http://leanpub.com/principles-of-php-package-design/c/symfony-camp 
Get a $10 discount!
Design patterns 
● Observer 
● Mediator 
● Chain of responsibility 
● ... 
Design Patterns by “The Gang of Four”
SOLID principles 
● Single responsibility 
● Open/closed 
● Dependency inversion 
● ... 
Agile Software Development by Robert C. Martin
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/
What did you think? 
joind.in/12541 
Twitter: @matthiasnoback

A Series of Fortunate Events - Symfony Camp Sweden 2014

  • 1.
    A Series ofFortunate Events Matthias Noback Twitter: @matthiasnoback
  • 2.
    What are events,really? Things that happen
  • 3.
  • 4.
    Just now... Attendeesarrived, triggered me to turn on microphone, which triggered you to stop talking, which triggered me to start talking
  • 5.
    Events in software Events model what happened in a system
  • 6.
    Other parts ofthe system can respond to what happened
  • 7.
    Imperative programming Onlycommands doThis(); doThat(); updateSomething($something); return $something;
  • 8.
    Extracting events doThis(); // this was done doThat(); // that was done updateSomething($something) // something was updated return $something;
  • 9.
    Starting position classPostService { ... function addComment($postId, $comment) { $post = $this->fetchPost($postId); $post->addComment($comment); $this->save($post); $this->logger->info('New comment'); $this->mailer->send('New comment'); } }
  • 10.
    Starting position classPostService { function __construct( Mailer $mailer, Logger $logger ) { $this->mailer = $mailer; $this->logger = $logger; } function addComment($postId, $comment) { ... } }
  • 11.
    Making events explicit class PostService { function addComment($postId, $comment) { ... $this->newCommentAdded(); } function newCommentAdded() { $this->logger->info('New comment'); $this->mailer->send('New comment'); } }
  • 12.
  • 13.
    Design issues (1) I don't think the PostService should know how to use a Mailer and a Logger
  • 14.
    Design issues (2) I want to change the behavior of PostService without modifying the class itself
  • 15.
    Fix the problems By introducing events! (later)
  • 16.
    Observer pattern Notifyother parts of the application when a change occurs class PostService { function newCommentAdded() { foreach ($this->observers as $observer) { $observer->notify(); } } }
  • 17.
    Observer contract Subjectknows nothing about its observers, except their very simple interface interface Observer { function notify(); }
  • 18.
    Concrete observers classLoggingObserver implements Observer { function __construct(Logger $logger) { $this->logger = $logger; } function notify() { $this->logger->info('New comment'); } }
  • 19.
    Concrete observers classNotificationMailObserver implements Observer { function __construct(Mailer $mailer) { $this->mailer = $mailer; } function notify() { $this->mailer->send('New comment'); } }
  • 20.
    Configuration class PostService { function __construct(array $observers) { $this->observers = $observers; } } $postService = new PostService( array( new LoggingObserver($logger), new NotificationMailObserver($mailer) ) );
  • 21.
  • 22.
    After NotificationMailObserver Observer Observer LoggingObserver Logger Mailer PostService
  • 23.
  • 24.
    Single responsibility Eachclass has one small, well-defined responsibility
  • 25.
    Single responsibility ●PostService: “add comments to posts” ● LoggingObserver: “write a line to the log” ● NotificationMailObserver: “send a notification mail”
  • 26.
    Single responsibility Whena change is required, it can be isolated to just a small part of the application
  • 27.
    Single responsibility ●“Capitalize the comment!”: PostService ● “Use a different logger!”: LoggerObserver ● “Add a timestamp to the notification mail!”: NotificationMailObserver
  • 28.
    Dependency inversion Dependon abstractions, not on concretions
  • 29.
    Dependency inversion FirstPostService depended on something concrete: the Mailer, the Logger.
  • 30.
  • 31.
    Dependency inversion Nowit depends on something abstract: an Observer
  • 32.
  • 33.
    Dependency inversion Onlythe concrete observers depend on concrete things like Mailer and Logger
  • 34.
  • 35.
    Open/closed A classshould be open for extension and closed for modification
  • 36.
    Open/closed You don'tneed to modify the class to change its behavior
  • 37.
  • 38.
    Open/closed We madeit closed for modification, open for extension
  • 39.
    Event data Mr.Boddy was murdered! ● By Mrs. Peacock ● In the dining room ● With a candle stick
  • 40.
    Currently missing! classLogNewCommentObserver implements Observer { function notify() { // we'd like to be more specific $this->logger->info('New comment'); } }
  • 41.
    Event object classCommentAddedEvent { public function __construct($postId, $comment) { $this->postId = $postId; $this->comment = $comment; } function comment() { return $this->comment; } function postId() { return $this->postId; } }
  • 42.
    Event object Weuse the event object to store the context of the event
  • 43.
    From observer... interfaceObserver { function notify(); }
  • 44.
    … to eventhandler interface CommentAddedEventHandler { function handle(CommentAddedEvent $event); }
  • 45.
    Event handlers classLoggingEventHandler implements CommentAddedEventHandler { function __construct(Logger $logger) { $this->logger = $logger; } public function handle(CommentAddedEvent $event) { $this->logger->info( 'New comment' . $event->comment() ); } }
  • 46.
    Event handlers classNotificationMailEventHandler implements CommentAddedEventHandler { function __construct(Mailer $mailer) { $this->mailer = $mailer; } public function handle(CommentAddedEvent $event) { $this->mailer->send( 'New comment: ' . $event->comment(); ); } }
  • 47.
    Configuration class PostService { function __construct(array $eventHandlers) { $this->eventHandlers = $eventHandlers; } } $postService = new PostService( array( new LoggingEventHandler($logger), new NotificationMailEventHandler($mailer) ) );
  • 48.
    Looping over eventhandlers class 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); } } }
  • 49.
    Introducing a Mediator Instead of talking to the event handlers Let's leave the talking to a mediator
  • 50.
    Mediators for events ● Doctrine, Zend: Event manager ● The PHP League: Event emitter ● Symfony: Event dispatcher
  • 51.
    Before PostService LoggingEventHandler::handle() NotificationMailEventHandler::handle()
  • 52.
    After PostService EventDispatcher LoggingEventHandler::handle() NotificationMailEventHandler::handle()
  • 53.
    In code classPostService { function __construct(EventDispatcherInterface $dispatcher) { $this->dispatcher = $dispatcher; } function newCommentAdded($postId, $comment) { $event = new CommentAddedEvent($postId, $comment); $this->dispatcher->dispatch( 'comment_added', $event ); } }
  • 54.
    Event class Customevent classes should extend Symfony Event class: use SymfonyComponentEventDispatcherEvent; class CommentAddedEvent extends Event { ... }
  • 55.
    Configuration use SymfonyComponentEventDispatcherEvent; $dispatcher = new EventDispatcher(); $loggingEventHandler = new LoggingEventHandler($logger); $dispatcher->addListener( 'comment_added', array($loggingEventHandler, 'handle') ); ... $postService = new PostService($dispatcher);
  • 56.
    Symfony2 ● Anevent dispatcher is available as the event_dispatcher service ● You can register event listeners using service tags
  • 57.
    Inject the eventdispatcher # services.yml services: post_service: class: PostService arguments: [@event_dispatcher]
  • 58.
    Register your listeners # services.yml services: ... logging_event_handler: class: LoggingEventHandler arguments: [@logger] tags: - { name: kernel.event_listener event: comment_added method: handle }
  • 59.
    Events and applicationflow Symfony2 uses events to generate response for any given HTTP request
  • 60.
    The HttpKernel $request= Request::createFromGlobals(); // $kernel is in an instance of HttpKernelInterface $response = $kernel->handle($request); $response->send();
  • 61.
  • 62.
    kernel.request ● Routematching ● Authentication
  • 63.
    kernel.controller ● Replacethe controller ● Do some access checks
  • 64.
  • 65.
    kernel.response ● Modifythe response ● E.g. inject the Symfony toolbar
  • 66.
    kernel.exception ● Generatea response ● Render a nice page with the stack trace
  • 67.
    Special types ofevents ● Kernel events are not merely notifications ● They allow other parts of the application to step in and modify or override behavior
  • 68.
    Chain of responsibility Some sort of request Handler 1 Handler 2 Handler 3 Some sort of request Some sort of request Response
  • 69.
    Symfony example I'vegot an exception! What should I tell the user? Listener 1 Listener 2 Listener 3 Exception! Exception! Response
  • 70.
    Propagation class HandleExceptionListener { function onKernelException( GetResponseForExceptionEvent $event ) { $event->setResponse(new Response('Error!')); // this is the best response ever, don't let // other spoil it! $event->stopPropagation(); } }
  • 71.
    Priorities $dispatcher =new EventDispatcher(); $dispatcher->addListener( 'comment_added', array($object, $method), // priority 100 );
  • 72.
  • 73.
    Concern 1: Hardto understand “Click-through understanding” impossible $event = new CommentAddedEvent($postId, $comment); $this->dispatcher->dispatch( 'comment_added', $event );
  • 74.
    interface EventDispatcherInterface { function dispatch($eventName, Event $event = null); ... }
  • 75.
  • 76.
    Concern 2: Out-of-domainconcepts ● “Comment” ● “PostId” ● “Add comment to post” ● “Dispatcher” (?!)
  • 77.
    We did agood thing We fixed coupling issues
  • 78.
    But this guy,Coupling, has a sister She's called Cohesion
  • 79.
    Cohesion ● Belongingtogether ● Concepts like “dispatcher”, “event listener”, even “event”, don't belong in your code
  • 80.
    Solutions (1) Descriptive,explicit naming: ● NotificationMailEventListener becomes SendNotificationMailWhenCommentAdded ● CommentAddedEvent becomes CommentAdded ● onCommentAdded becomes whenCommentAdded
  • 81.
    Solutions (1) Thisalso hides implementation details!
  • 82.
    Solutions (2) Usean event dispatcher for things that are not naturally cohesive anyway
  • 83.
    Solutions (2) Usesomething else when an event dispatcher causes low cohesion
  • 84.
    Example: resolving thecontroller $event = new GetResponseEvent($request); $dispatcher->dispatch('kernel.request', $event); $controller = $request->attributes->get('_controller'); $controller = $controllerResolver->resolve($request);
  • 85.
    Concern 3: Lossof 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?
  • 86.
    Solution ● “Won'tfix” ● You have to learn to live with it
  • 87.
  • 88.
    exercise control Inversionof control give up control!
  • 89.
    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
  • 90.
    I admit, inversionof control can be scary
  • 91.
    But it will ● lead to better design ● require less change ● make maintenance easier
  • 92.
  • 93.
    Symfony, service definitions,kernel events http://leanpub.com/a-year-with-symfony/c/symfony-camp Pay even less ;)
  • 94.
    Class and packagedesign principles http://leanpub.com/principles-of-php-package-design/c/symfony-camp Get a $10 discount!
  • 95.
    Design patterns ●Observer ● Mediator ● Chain of responsibility ● ... Design Patterns by “The Gang of Four”
  • 96.
    SOLID principles ●Single responsibility ● Open/closed ● Dependency inversion ● ... Agile Software Development by Robert C. Martin
  • 97.
    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/
  • 98.
    What did youthink? joind.in/12541 Twitter: @matthiasnoback