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

HTTP Kernel #2

By Juan Manuel Torres

HTTP Kernel #2

  • 2,488