📜 ⬆️ ⬇️

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

In the fifth part of the series we will talk about controllers.



The attentive reader noticed that in our framework the way that the code of templates is executed is rigidly registered. For simple pages, like the ones we created earlier, this is not a problem. But if you plan to add more logic, you will have to embed it in the template itself.

Which, naturally, is not a good idea, especially if you wanted to divide the system into separate functional structures.
Let's separate the template code from the logic by adding a new layer - the controller: “The task of the controller is to create a Response based on the information sent by the Client Request.”
Modify the rendering part of the template as follows:
')
<?php // example.com/web/front.php // ... try { $request->attributes->add($matcher->match($request->getPathInfo())); $response = call_user_func('render_template', $request); } catch (Routing\Exception\ResourceNotFoundException $e) { $response = new Response('Not Found', 404); } catch (Exception $e) { $response = new Response('An error occurred', 500); } 


Since the rendering is now done by the external function render_template () , we must pass to it the attributes retrieved from the URL. We could pass them as an additional argument to the render_template () , but instead we use another feature of the Request class (attributes class). Request Attributes allow you to include additional information about the Request, which is not directly related to the HTTP request data.
Now we ’ll write the render_template () function, a common controller that renders the template, without much logic. To preserve the previous structure of the template, the attributes of the query are retrieved prior to rendering the template:

 function render_template($request) { extract($request->attributes->all(), EXTR_SKIP); ob_start(); include sprintf(__DIR__.'/../src/pages/%s.php', $_route); return new Response(ob_get_clean()); } 


Since the render_template is used as an argument to PHP call_user_func () , we can replace it with any valid PHP callback function. This will allow you to use a function, an anonymous function, or a controller class method ... of your choice.
To standardize the definition of any path, the associated controller is configured via the _controller route attribute:

 $routes->add('hello', new Routing\Route('/hello/{name}', array( 'name' => 'World', '_controller' => 'render_template', ))); try { $request->attributes->add($matcher->match($request->getPathInfo())); $response = call_user_func($request->attributes->get('_controller'), $request); } catch (Routing\Exception\ResourceNotFoundException $e) { $response = new Response('Not Found', 404); } catch (Exception $e) { $response = new Response('An error occurred', 500); } 


The route can now be connected to any controller and, of course, in the controller, you can use the render_template () to render the template:

 $routes->add('hello', new Routing\Route('/hello/{name}', array( 'name' => 'World', '_controller' => function ($request) { return render_template($request); } ))); 


This is more flexible since you can change the Response object , and you can even pass additional arguments to the template:

 $routes->add('hello', new Routing\Route('/hello/{name}', array( 'name' => 'World', '_controller' => function ($request) { // $foo will be available in the template $request->attributes->set('foo', 'bar'); $response = render_template($request); // change some header $response->headers->set('Content-Type', 'text/plain'); return $response; } ))); 


Here is the updated and improved version of our system:

 <?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; function render_template($request) { extract($request->attributes->all(), EXTR_SKIP); 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); try { $request->attributes->add($matcher->match($request->getPathInfo())); $response = call_user_func($request->attributes->get('_controller'), $request); } catch (Routing\Exception\ResourceNotFoundException $e) { $response = new Response('Not Found', 404); } catch (Exception $e) { $response = new Response('An error occurred', 500); } $response->send(); 


In honor of the birthday of our framework, let's create a new application with the simplest logic. Our application consists of one page that says: is this a leap year or not. When you call / is_leap_year , you will get the answer for the current year, but you can also specify the year, in the form / is_leap_year / 2009 . Being universal, the framework does not need changes, just create a new app.php file:

 <?php // example.com/src/app.php use Symfony\Component\Routing; use Symfony\Component\HttpFoundation\Response; function is_leap_year($year = null) { if (null === $year) { $year = date('Y'); } return 0 == $year % 400 || (0 == $year % 4 && 0 != $year % 100); } $routes = new Routing\RouteCollection(); $routes->add('leap_year', new Routing\Route('/is_leap_year/{year}', array( 'year' => null, '_controller' => function ($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.'); } ))); return $routes; 


Is_leap_year () returns true if the given year is a leap year, false otherwise. If the year is null, then the current year is checked. The controller is simple: it gets the year from the attributes of the query, passes it to is_leap_year () , and according to the return value, it creates a new Response object.
As always, you can decide to stop there and use the framework as it is. This is probably enough to create simple one-page sites, and even perhaps with two pages or more.

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


All Articles