Pull to refresh

Создаем собственный фреймворк на основе Symfony2. (Часть 6)

Reading time4 min
Views4.7K
Original author: Fabien Potencier
В шестой части серии, мы научимся использовать компонент HttpKernel.




Казалось бы, что наш фреймворк уже достаточно целостный, в какой-то мере так и есть, но давайте все-таки взглянем, что можно было бы улучшить.
В данный момент, все наши примеры используют процедурный код, но мы ведь помним, что контроллер может использовать методы объектов, включая статические методы классов. Поэтому давайте превратим наш контроллер в класс:

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.');
    }
}


Обновим определение маршрута соответственно:

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


Этот шаг очень прост и возымеет больший смысл, как только вы начнете добавлять страницы. Правда, сразу можно заметить, что Класс LeapYearController всегда создается, даже если запрашиваемый URL не соответствует маршруту leap_year. Это очень нецелесообразно с точки зрения производительности: теперь на каждый запрос будут инициализированы все контроллеры всех маршрутов. Было бы куда лучше использовать отложенную инициализацию таким образом инициализация контроллеров будет выполняется «по требованию» соответствующего маршрута.
Чтобы решить эту проблему, и ряд других, давайте установим компонент HttpKernel:

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


Компонент HttpKernel имеет много интересных функций, но прямо сейчас нам необходима — controller resolver. Она определяет, когда и как выполнятся контроллеру, и какие аргументы ему передать. Все резольверы контроллеров используют следующий интерфейс

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


GetController() метод основан на том же соглашении, которое мы приняли ранее: _controller должен содержать контроллер, связанный с запросом. Так же как и встроенные в PHP функции обратного вызова, getController() принимает строки, состоящие из имени класса плюс два двоеточия и имя метода — «класс:: метод»:

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


Чтобы заставить этот код работать, измените код фреймворка таким образом, чтобы использовать controller resolver от HttpKernel:

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);


Приятная мелочь: controller resolver корректно обрабатывает ошибки за вас, например: если вы забыли определить _controller он выдаст соответствующую ошибку.


Теперь давайте посмотрим, как узнаются аргументы контроллера. getArguments() разбирает подпись контроллера, чтобы определить, какие аргументы ему передавать используя реверс-инжиниринг PHP Reflection.
Аргументом метода IndexAction() является объект класса Request. Метод getArguments() верно передает аргумент, если корректно определен тип:

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


Интересный факт то, что в getArguments() так же можно передать любой атрибут класса Request; достаточно чтобы аргумент назывался так же, как и соответствующий атрибут:

public function indexAction($year)


Также вы можете одновременно передавать различные атрибуты вместе с объектом класса Request (порядок не важен, так как указывается тип)

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


Естественно, вы можете определить значения по умолчанию для любого аргумента:

public function indexAction($year = 2012)


Давайте просто передадим $year в наш контроллер:

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.');
    }
}


controller resolver также заботится о проверке вызова контроллера и его аргументах. В случае возникновения проблемы, он генерирует исключение, с понятным сообщением, объясняющим проблему: контроллер класса не существует, этот метод не определен, аргумент не соответствует атрибуту,…
Подведем итоги по нашей новой версии фреймворка:

<?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();


Задумайтесь на минутку: наш фреймворк стал еще более надежным и гибким, и до сих пор состоит из менее чем 40 строк кода.
Tags:
Hubs:
Total votes 16: ↑11 and ↓5+6
Comments1

Articles