Pull to refresh

Symfony 2.0, RequestHandler Component

Symfony *
На сайте Symfony Components про компонент RequestHandler сказано примерно следующее:
Гибкое микро-ядро для быстрых фреймворков.

Так ли это и что из себя представляет RequestHandler в Symfony 2 я попробую рассмотреть в этом топике.

Если взглянуть на процесс обработки запроса в приложении на базе Symfony 2, а именно рассмотреть метод run() класса Symfony\Foundation\Kernel, то увидим что после формирования и сохранения в кеше либо загрузки из кеша DI контейнера, происходит получение из контейнера объекта обработчика запроса и вызов его метода handle() в параметрах, которому передается объект запроса, кстати также полученный из DI контейнера. Данный метод должен вернуть объект ответа у которого вызывается метод send() для отправки ответа. В общем первый удар при обработке запроса принимает на себя RequestHandler, он же возвращает объект ответа, поэтому я решил начать обзор компонентов входящих в состав Symfony 2 именно с него.

Для начала заглянем в Symfony/Foundation/Resources/services.xml так как именно здесь происходит описание сервиса RequestHandlerService в DI контейнере.

<parameter key="request_handler.class">Symfony\Components\RequestHandler\RequestHandler</parameter>
<parameter key="request.class">Symfony\components\RequestHandler\Response</parameter>
...
<service id="request_handler" class="%request_handler.class%">
  <argument type="service" id="event_dispatcher" />
</service>
<service id="request" class="%request.class%" />

Из этого описания становится ясно, что объект запроса создается просто new Request(), а объект обработчика запроса, как new RequestHandler($dispatcher), где $dispatcher объект класса EventDispatcher, представляющий собой диспетчер событий.

Заглянув в Symfony/Components/RequestHandler мы увидим следующий набор файлов:
  • Exception/: директория с классами исключений
    • ForbiddenHttpException.php: исключение кидаемое при 403 Forbidden
    • HttpException.php: базовый класс для исключений
    • NotFoundHttpException.php: исключение кидаемое при 404 Not Found
    • UnauthorizedHttpException.php: исключение кидаемое при 401 Unauthorized

  • Request.php: класс запроса
  • RequestInterface.php: интерфейс, который должен реализовать объект запроса
  • Response.php: класс ответа
  • ResponseInterface.php: интерфейс, который должен реализовать объект ответа
  • RequestHandler.php: обработчик запроса

Ну в общем все почти очевидно. Многое ясно даже не заглядывая внутрь классов. Но все же коротко обо всем. Классы исключений, которые используются при возникновении соответствующих ситуаций, все что в них делается, так это прописывается сообщение и код ответа. Request — класс запроса, который разбирает переменные окружения, параметры и все что связано с запросом. Response — класс ответа, который содержит в себе тело ответва, формирует заголовки и все связанное с ответом. RequestHandler — это обработчик запроса. Пожалуй рассмотрим его подробнее.

Фактически все, что делает обработчик запроса это используя переданный ему в параметрах диспетчер событий (EventDispatcher) генерирует различные события, тем самым как бы оповещая приложения о различных этапах разбора запроса и формирования ответа. И в зависимости от реакции слушателей (listeners) на эти события завершает работу либо генерирует следующее событие.

Список событий, которые создает обработчик запросов:
  • core.request
  • core.load_controller
  • core.controller
  • core.view
  • core.response
  • core.exception: в случае возникновения исключений

То есть события отражают основные этапы разбора запроса и формирования ответа. RequestHandler проверяя реакцию приложения на события продолжает создавать события либо завершает работу. Соответственно приложение навешивая свои обработчики на те или иные события участвует в формировании ответа. При этом некоторые части приложения могут ничего не знать друг о друге.

Лучше, наверное это все попробовать на примере. Что в общем я сейчас и сделаю. Итак, разденем Symfony до гола, убрав все кроме ядра и навесим свой обработчик на событие core.request, заодно будет небольшая демонстрация гибкости фреймворка.

Пример будет на базе sandbox с обновленной версией Symfony, как создавалось окружение можно увидеть вот здесь.

Итак, начнем с конфигов. В файле hello/config/config.yml закоментируем все за исключением строки kernel.config: ~. Теперь поправим ядро приложения, файл hello/HelloKernel.php

# hello/HelloKernel.php
// убираем все бандлы, за исключения ядра и нашего приложения
public function registerBundles()
{
  return array(
    new Symfony\Foundation\Bundle\KernelBundle(),
    new Application\HelloBundle\Bundle(),
  );
}

Теперь, надо описать в DI контейнере необходимые параметры и сервисы, для этого сначала создадим свое расширение для DI контейнера, создаем директорию src/Application/HelloBundle/DependencyInjection, а в ней файл HelloExtension.php со следующим содержимым:

namespace Application\HelloBundle\DependencyInjection;

use Symfony\Components\DependencyInjection\Loader\LoaderExtension,
    Symfony\Components\DependencyInjection\Loader\XmlFileLoader,
    Symfony\Components\DependencyInjection\BuilderConfiguration,
    Symfony\Components\DependencyInjection\Reference,
    Symfony\Components\DependencyInjection\Definition;

class HelloExtension extends LoaderExtension
{
  public function helloLoad($config)
  {
    $configuration = new BuilderConfiguration();

    $loader = new XmlFileLoader(__DIR__.'/../Resources/config');
    $configuration->merge($loader->load('hello.xml'));
    
    return $configuration;
  }

  public function getAlias()
  {
    return 'hello';
  }

  public function getNamespace()
  {
    return 'http://poulikov.ru/schema/dic/hello';
  }

  public function getXsdValidationBasePath()
  {
    return __DIR__.'/../Resources/config/';
  }
}

теперь зарегистрируем наше расширение:

# src/Application/HelloBundle/Bundle.php
// используем наше расширение
use Application\HelloBundle\DependencyInjection\HelloExtension;

...

// зарегистрируем расширение в контейнере
public function buildContainer(ContainerInterface $container)
{
  Loader::registerExtension(new HelloExtension());
}

Наше расширение будет искать файл hello.xml по пути src/Application/HelloBundle/Resources/config/hello.xml
так что надо создать этот файл, с примерно таким содержимым:

<container xmlns="http://www.symfony-project.org/schema/dic/services"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://www.symfony-project.org/schema/dic/services http://www.symfony-project.org/schema/dic/services/services-1.0.xsd">

  <parameters>
      <parameter key="request_parser.class">Application\HelloBundle\Request\Parser</parameter>
  </parameters>

  <services>
    <service id="request_parser" class="%request_parser.class%">
        <annotation name="kernel.listener" event="core.request" method="resolve" />
        <argument type="service" id="service_container" />
    </service>
  </services>
</container>

В этом файле мы определили сервис, который навешивается на прослушиваение события core.request
Теперь создадим парсер запроса:

# src/Application/HelloBundle/Request/Parser.php

namespace Application\HelloBundle\Request;

use Symfony\Components\DependencyInjection\ContainerInterface;
use Symfony\Components\EventDispatcher\Event;
use Symfony\Components\Routing\RouterInterface;

class Parser
{
  protected $container;

  public function __construct(ContainerInterface $container)
  {
    $this->container = $container;
  }

  public function register()
  {
    $this->container->getEventDispatcherService()->connect('core.request', array($this, 'resolve'));
  }

  public function resolve(Event $event)
  {
    $response = $this->container->getResponseService();
    $response->setContent("<div>Hello, World! I'm Symfony 2.0 lite</div>");

    $event->setReturnValue($response);
    $event->setProcessed(true);
  }
}

Теперь последний штрих, в конфиге hello/config/config.yml добавим строчку hello.hello: ~
Это всё! Теперь обратившись по любому адресу мы получим ответ:
Результат
Совершенно бесполезное приложение получилось. Но на этом примере легко показать как оно работает, подцепляться можно и к другим событиям. Все очень гибко и удобно.
Tags:
Hubs:
Total votes 30: ↑26 and ↓4 +22
Views 1.8K
Comments Comments 32