HTTP Kernel #1
ROUTING AND RESOLVING CONTROLLERS
study-group-02
Created by Juan Manuel Torres / @onema / onema.io
Follow 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
}
Resolving routes
The way we are resolving routes is limiting:
- Simple single routes supported
- 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
- We should support more than one input parameter
- Enable our framework to be generic
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
- Resolve Controller
- Resolve Arguments for Controller Method
- Handles injection of request attributes
- 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,354