📜 ⬆️ ⬇️

We create our own framework based on symfony2. (Part 6)

In the sixth part of the series, we will learn how to use the HttpKernel component.




It would seem that our framework is already fairly complete, to some extent it is, but let's still see what could be improved.
At the moment, all our examples use procedural code, but we remember that the controller can use object methods, including static class methods. So let's turn our controller into a class:
')
class LeapYearController { public function indexAction($request) { if (is_leap_year($request->attributes->get('year'))) { return new Response('Yep, this is a leap year!'); } return new Response('Nope, this is not a leap year.'); } } 


Update the route definition accordingly:

 $routes->add('leap_year', new Routing\Route('/is_leap_year/{year}', array( 'year' => null, '_controller' => array(new LeapYearController(), 'indexAction'), ))); 


This step is very simple and makes more sense as soon as you start adding pages. However, you can immediately notice that the LeapYearController Class is always created, even if the requested URL does not correspond to the leap_year route. This is very impractical from the point of view of performance: now all the controllers of all routes will be initialized for each request. It would be much better to use deferred initialization so the initialization of the controllers will be performed “on demand” of the corresponding route.
To solve this problem, and a number of others, let's install the HttpKernel component:

 { "require": { "symfony/class-loader": "2.1.*", "symfony/http-foundation": "2.1.*", "symfony/routing": "2.1.*", "symfony/http-kernel": "2.1.*" } } 


The HttpKernel component has many interesting features, but right now we need a controller resolver. It determines when and how the controller will be executed, and what arguments to pass to it. All controller resolvers use the following interface.

 namespace Symfony\Component\HttpKernel\Controller; interface ControllerResolverInterface { function getController(Request $request); function getArguments(Request $request, $controller); } 


The GetController () method is based on the same convention we adopted earlier: _controller must contain the controller associated with the request. As well as PHP's built-in callback functions, getController () accepts strings consisting of the class name plus two colons and the method name is "class :: method":

 $routes->add('leap_year', new Routing\Route('/is_leap_year/{year}', array( 'year' => null, '_controller' => 'LeapYearController::indexAction', ))); 


To make this code work, change the framework code to use the HttpKernel controller resolver :

 use Symfony\Component\HttpKernel; $resolver = new HttpKernel\Controller\ControllerResolver(); $controller = $resolver->getController($request); $arguments = $resolver->getArguments($request, $controller); $response = call_user_func_array($controller, $arguments); 


A nice little thing: the controller resolver correctly handles errors for you, for example: if you forget to define a _controller, it will generate an appropriate error.


Now let's see how the controller arguments are recognized. getArguments () parses the controller's signature to determine which arguments to pass to it using PHP Reflection reverse engineering.
The argument to the IndexAction () method is an object of class Request . The getArguments () method correctly passes an argument if the type is correctly defined:

 public function indexAction(Request $request) // won't work public function indexAction($request) 


An interesting fact is that in getArguments () you can also pass any attribute of the class Request ; it is sufficient that the argument is named in the same way as the corresponding attribute:

 public function indexAction($year) 


You can also simultaneously pass various attributes along with an object of class Request (the order is not important, as the type is indicated)

 public function indexAction(Request $request, $year) public function indexAction($year, Request $request) 


Naturally, you can define default values ​​for any argument:

 public function indexAction($year = 2012) 


Let's just pass $ year to our controller:

 class LeapYearController { public function indexAction($year) { if (is_leap_year($year)) { return new Response('Yep, this is a leap year!'); } return new Response('Nope, this is not a leap year.'); } } 


The controller resolver also takes care of checking the controller's call and its arguments. In case of a problem, it generates an exception, with a clear message explaining the problem: the class controller does not exist, this method is not defined, the argument does not match the attribute, ...
Let's summarize our new version of the framework:

 <?php // example.com/web/front.php require_once __DIR__.'/../vendor/.composer/autoload.php'; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\Routing; use Symfony\Component\HttpKernel; function render_template($request) { extract($request->attributes->all()); ob_start(); include sprintf(__DIR__.'/../src/pages/%s.php', $_route); return new Response(ob_get_clean()); } $request = Request::createFromGlobals(); $routes = include __DIR__.'/../src/app.php'; $context = new Routing\RequestContext(); $context->fromRequest($request); $matcher = new Routing\Matcher\UrlMatcher($routes, $context); $resolver = new HttpKernel\Controller\ControllerResolver(); try { $request->attributes->add($matcher->match($request->getPathInfo())); $controller = $resolver->getController($request); $arguments = $resolver->getArguments($request, $controller); $response = call_user_func_array($controller, $arguments); } catch (Routing\Exception\ResourceNotFoundException $e) { $response = new Response('Not Found', 404); } catch (Exception $e) { $response = new Response('An error occurred', 500); } $response->send(); 


Think for a minute: our framework has become even more reliable and flexible, and still consists of less than 40 lines of code .

Source: https://habr.com/ru/post/138893/


All Articles