HTTP Kernke #2
THE EVENT DISPATCHER
study-group-03
Created by Juan Manuel Torres / @onema / onema.io
Follow Live
http://slides.com/onema/http-kernel-2/live
So Far
We have learned about the HTTP Reques/Response and how to handle them using the Symfony2 HTTP Foundation Component.
Last Time
Pretty Urls and.
Resolve Controllers and Arguments.
Created a better controller.
Today
- Encapsulate code for re-use.
- Enable developers to extend functionality.
- Add Interoperability.
- Create our own extension/plugin.
Creating a Re-Usable Framework
// src/SDPHP/SGFramework/SGFramework.php
class SGFramework {
protected $matcher;
protected $resolver;
// Inject Matcher and Resolver
public function __construct(UrlMatcher $matcher, ControllerResolver $resolver) {
$this->matcher = $matcher;
$this->resolver = $resolver;
}
// Everything else is the same
public function handle(Request $request) {
try {
$parameters = $this->matcher->match($request->getPathInfo());
$request->attributes->add($parameters);
$controller = $this->resolver->getController($request);
$arguments = $this->resolver->getArguments($request, $controller);
$response = call_user_func_array($controller, $arguments);
} catch (ResourceNotFoundException $e) {
$response = new Response('Not Found: ' . $e->getMessage(), 404);
} catch (\Exception $e) {
$response = new Response('An error occurred: ' . $e->getMessage(), 500);
}
return $response;
}
}
New
Old
Update Front Controller
// web/app.php
$request = Request::createFromGlobals();
include __DIR__ . '/../app/config/routing.php';
$context = new RequestContext();
$context->fromRequest($request);
$matcher = new UrlMatcher($routes, $context);
$resolver = new ControllerResolver();
$framework = new SGFramework($matcher, $resolver);
$response = $framework->handle($request);
$response->prepare($request);
$response->send();
Our
Framework!
Event Dispatcher Component
Extending behavior without inheritance.
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.
The Symfony2 Event Dispatcher Component
Implements the Mediator pattern, enabling developers to extend the symfony framework, or use the component in their own projects.
Event Dispatcher
Loose coupling is achieved by communicating with a dispatcher instead of eachother.
Event Dispatcher
Consumer wants to be informed (listen) on certain events.
Event Dispatcher
New consumers (plugins) may listen for events.
Event dispatcher
When an event occurs the producer informs the dispatcher.
Mediator Pattern
Finally the dispatcher notifies the consummers in order of priority.
// web/app.php
$testEvent = new TestEvent();
$dispatcher = new EventDispatcher();
$dispatcher->addListener('framework.request', array($testEvent, 'onFrameworkRequest'));
$dispatcher->addListener('framework.response', array($testEvent, 'onFrameworkResponse'));
// Initialize framework and give it the request to handle
$framework = new SGFramework($dispatcher, $matcher, $resolver);
Code Example
Samll update to the Front Controller.
In this case the producer is our SGFramework.
The dispatcher is added to the framework.
Code Example
// src/SDPHP/SGFramework/SGFramework.php
public function __construct(EventDispatcher $dispatcher,
UrlMatcher $matcher,
ControllerResolver $resolver)
{
$this->dispatcher = $dispatcher;
$this->matcher = $matcher;
$this->resolver = $resolver;
}
public function handle(Request $request)
{
$event = new GenericEvent();
// . . .
$event->setArgument('request', $request);
$this->dispatcher->dispatch('framework.request', $event);
// . . .
}
Create a generic event. These objects hold information about the event.
Add relevant info to event
Dispatch Event
Inject Dispatcher
Update the Framework.
The framework will "produce" events.
Code Example
// src/SDPHP/StudyGroup03/Event/TestEvent.php
class TestEvent
{
public function onFrameworkRequest(GenericEvent $event)
{
// . . .
}
public function onFrameworkResponse(GenericEvent $event)
{
// . . .
}
}
Test Event: It can be any class.
DEMO 1
Modify the request to set a default value other than ES.
public function onFrameworkRequest(GenericEvent $event)
{
// Change Default language
$request = $event->getArgument('request');
$language = $request->cookies->get('language');
if (!isset($language)) {
$request->cookies->set('language', 'DE');
}
}
http://dev.studygroup.com:8080/hello
http://dev.studygroup.com:8080/hello/FR
Hallo Welt
Bonjour tout le monde!
DEMO 2
Modify the response to add styles to the content.
public function onFrameworkResponse(GenericEvent $event)
{
$response = $event->getArgument('response');
$content = $response->getContent();
$response->setContent(
'<p style="background:#ccc;...">'
.$content.
'</p>'
);
}
http://dev.studygroup.com:8080/hello/SW
Hujambo dunia
Interoperability
// src/SDPHP/SGFramework/SGFramework.php
use Symfony\Component\HttpKernel\HttpKernelInterface;
class SGFramework implements HttpKernelInterface
{
// . . .
public function handle(Request $request, $type = self::MASTER_REQUEST, $catch = true)
{
// . . .
}
}
Implement HttpKernelInterface
HttpKernelInterface middlewares
We will get a lot of functionality for "free".
Our Framework can be Wrapped arround other libraries to extend functionality.
Creating a Custom Event Subscriber
// web/app.php
use Symfony\Component\HttpKernel\HttpCache\HttpCache;
use Symfony\Component\HttpKernel\HttpCache\Store;
// . . .
// Initialize framework and give it the request to handle
$framework = new SGFramework($dispatcher, $matcher, $resolver);
// Add HTTP Caching
$framework = new HttpCache($framework, new Store(__DIR__.'/../app/cache'));
// src/SDPHP/StudyGroup03/Controller/HelloWorldController.php
class HelloWorldController
{
public function translateAction(Request $request)
{
// . . .
$response->setTtl(20);
return $response;
}
}
DEMO 3
Enable and test HTTP Caching
CLASSWORK
Some Frameworks provide the means to call a "Before" and "After" methods in your controller.
- Add events to enable the framework to call before and after methods if available.
HOMEWORK
- Move the caching logic to a custom listener.
- Create a custom plugin to allow the framework to log information (exceptions should be logged!): the information can be dump to the screen, a file, a twitter account or any output you wish to use! .
THE END
BY Juan Manuel Torres / onema.io / @onema / kinojman@gmail.com
References
- Head First Design Patterns
- SourceMaking design patterns
- The Whens and Whys for PHP Design Patterns
- Event Dispatcher Component
- Symfony2 components overview: EventDispatcher
- Stack middlewares
- Create your own Framework using Symfony2 Component Part 9
- Create your own Framework using Symfony2 Component Part 10
HTTP Kernel #2
By Juan Manuel Torres
HTTP Kernel #2
- 2,537