The document discusses events and event-driven architecture in software. It begins by explaining what events are and how they can be modeled in code. It then describes how to refactor imperative code to extract events using an example of adding comments to blog posts. This improves the design by removing dependencies and applying patterns like observer and mediator. Finally, it discusses best practices like using explicit events/handlers, single responsibility, and inversion of control.
16. Observer pattern
Notify other parts of the application
when a change occurs
class PostService
{
function newCommentAdded()
{
foreach ($this->observers as $observer) {
$observer->notify();
}
}
}
17. Observer contract
Subject knows nothing about its
observers, except their very simple
interface
interface Observer
{
function notify();
}
18. Concrete observers
class LoggingObserver implements Observer
{
function __construct(Logger $logger)
{
$this->logger = $logger;
}
function notify()
{
$this->logger->info('New comment');
}
}
19. Concrete observers
class NotificationMailObserver 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)
)
);
25. Single responsibility
● PostService:
“add comments to posts”
● LoggingObserver:
“write a line to the log”
● NotificationMailObserver:
“send a notification mail”
26. Single responsibility
When a 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
39. Event data
Mr. Boddy was murdered!
● By Mrs. Peacock
● In the dining room
● With a candle stick
40. Currently missing!
class LogNewCommentObserver implements Observer
{
function notify()
{
// we'd like to be more specific
$this->logger->info('New comment');
}
}
41. 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;
}
}
42. Event object
We use the event object
to store the context of the event
44. … to event handler
interface CommentAddedEventHandler
{
function handle(CommentAddedEvent $event);
}
45. 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()
);
}
}
46. 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();
);
}
}
47. Configuration
class PostService
{
function __construct(array $eventHandlers)
{
$this->eventHandlers = $eventHandlers;
}
}
$postService = new PostService(
array(
new LoggingEventHandler($logger),
new NotificationMailEventHandler($mailer)
)
);
48. 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);
}
}
}
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
53. 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
);
}
}
54. Event class
Custom event 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
● An event dispatcher is available as the
event_dispatcher service
● You can register event listeners using
service tags
59. Events and application flow
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();
67. 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
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've got 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();
}
}
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