HTTP Kernel #1

ROUTING AND RESOLVING CONTROLLERS

study-group-02

Created by Juan Manuel Torres / @onema / onema.io


Follow Live

http://slides.com/onema/http-kernel/live 

So far

Request - Response

Our application

Simple application intended to translate "Hello, World!" to different languages.

Client must send the translations using the query parameters "hello" and "world"  in a GET request.

A simple framework calls a controller and passes a request.

The controller looks for the query parameters hello and world and returns a string appending the values. 

Today we will refactor

Use of pretty URLs

Controller names not tide to routes

Can use any controller method we wish

Can add any number of args to controller methods

User can select desired language for translation

Controller will perform translations

Application will remember user preference

looking back at our 

first application

$request = Request::createFromGlobals(); // Create request object
$path = $request->getPathInfo(); // Get simple path info
$class = '\SDPHP\StudyGroup01\Controller\\'// Map path to class (BAD)
. trim($path, '/');

if (class_exists($class)) { // Check that class exists
$controller = new $class(); // Create new class
$response = $controller // Call action method (BAD)
->action($request);
} else {
$html = '<html><body><h1>Page Not Found</h1></body></html>';
$response = new Response($html, Response::HTTP_NOT_FOUND);
}
if ($response) { // RAW PHP Support
$response->prepare($request); // Tweak response
$response->send(); // Send back response
}
See Code on GitHub for more info

Resolving routes

The way we are resolving routes is limiting:
  1. Simple single routes supported
  2. Exposes internal details about our code: 
  • Filenames, Class Names, etc

We should re-factor our code...
or should we?

Symfony2 Routing

Support for "pretty/ulrs/and/more"

  • A route is a mapping from a path to a controller
  • We separate all routing info to it's own file
  • Individual routes are placed in RouteCollections
  • A "UrlMatcher" matches paths with RouteCollections
  • Flexibility when resolving controllers, methods, and args

Routing File

Introducing RouteCollection and Route 
$routes = new RouteCollection();

$routes->add(
'hello', // NAME THE ROUTE
new Route( // CREATE A NEW ROUTE OBJECT
'/hello/{lang}', // CHOOSE A PATH AND WILDCARD
array(
'lang' => false, // WILDCARD DEFAULT VALUE
'_controller' => array( // CONTROLLER OPTIONS
// @todo WHY IS THIS BAD?!
// CREATE A NEW CONTROLLER
new \SDPHP\StudyGroup02\Controller\HelloWorldController(),
'translateAction' // OBJECT METHOD TO BE CALLED
)
)
))
;

Refactoring the Front Controller

$request = Request::createFromGlobals();
include __DIR__ . '/../app/config/routing.php';// Include routing file
$context = new RequestContext(); // create a new context
$context->fromRequest($request); // Init request context
$matcher = new UrlMatcher($routes, $context); // Create a matcher
$parameters = $matcher->match($request->getPathInfo()); // Get parameters
list($lang, $_controller, $_route) = array_values($parameters);
$request->attributes->add(['lang' => $lang]); // Lang to request (BETTER)

try { $controller = $_controller[0]; // assign controller to var
$method = $_controller[1]; // assign method to var
$response = $controller->$method($request);// call method (BETTER) } catch (ResourceNotFoundException $e) { $response = new Response('Not Found: ' . $e->getMessage(), 404); } catch (\Exception $e) { $response = new Response('Error occurred: ' . $e->getMessage(), 500);
}

More Refactoring

  1. We should support more than one input parameter 
  • Enable our framework to be generic
  • Class methods should support multiple arguments 
  • Better error handling when controller doesn't exist
  • New class: Controller resolver

    $request = Request::createFromGlobals();
    include __DIR__ . '/../app/config/routing.php';
    $context = new RequestContext();
    $context->fromRequest($request);
    $matcher = new UrlMatcher($routes, $context);

    $parameters = $matcher->match($request->getPathInfo());
    $request->attributes->add($parameters);

    try {

    $resolver = new ControllerResolver(); // New Class
    $controller = $resolver->getController($request);
    $arguments = $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);
    }

    Benefits 

    1. Resolve Controller
    2. Resolve Arguments for Controller Method
    3. Handles injection of request attributes
    4. Handles Exceptions

    New Controller

    Test the routes

    /hello/{lang} 
    Route will instantiate controller even if it is not used
    /hello/improved/{lang} 
    It will not instantiate the class until it is needed
    /hello/inject/two/{lang} 
    Will inject two parameters to the method, request and lang
    /test/no/controller 
    Controller doesn't exist should throw and handle exception
    /test/no/method 
    Controller method doesn't exist should throw and handle exception

    Up Next: HTTP Kernel #2


    Creating a reusable framework by extending the HTTPKernel

    "Hooking" into the framework functionality with the  EventDispatcher

    THE END

    BY Juan Manuel Torres / onema.io / @onema

    REFERENCES

    [1] http://symfony.com/doc/current/components/http_kernel/introduction.html
    [2] http://symfony.com/doc/current/book/routing.html
    [3] http://symfony.com/doc/current/components/routing/introduction.html
    [4] http://fabien.potencier.org/article/52/create-your-own-framework-on-top-of-the-symfony2-components-part-3
    [5] http://fabien.potencier.org/article/53/create-your-own-framework-on-top-of-the-symfony2-components-part-4
    [6] http://fabien.potencier.org/article/54/create-your-own-framework-on-top-of-the-symfony2-components-part-5
    [7] http://fabien.potencier.org/article/55/create-your-own-framework-on-top-of-the-symfony2-components-part-6
    [8] http://fabien.potencier.org/article/56/create-your-own-framework-on-top-of-the-symfony2-components-part-7

    HTTP Kernel #1

    By Juan Manuel Torres

    HTTP Kernel #1

    • 1,342