...

четверг, 26 июня 2014 г.

[Перевод] Избавьтесь от аннотаций в своих контроллерах!

В предыдущей части этой серии мы понизили связанность симфонийского контроллера и фреймворка, удалив зависимость от базового класса контроллера из FrameworkBundle. А в этой части мы избавимся от некоторых неявных зависимостей, которые появляются из-за аннотаций.



Теперь давайте посмотрим на аннотации. Первоначально они были подключены для ускорения разработки (исчезает потребность редактировать конфигурационный файл, просто решайте проблемы прямо на месте!):

namespace Matthias\ClientBundle\Controller;

use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Method;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Template;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\ParamConverter;

/**
* @Route("/client")
*/
class ClientController
{
/**
* @Route('/{id}')
* @Method("GET")
* @ParamConverter(name="client")
* @Template
*/
public function detailsAction(Client $client)
{
return array(
'client' => $client
);
}
}


Когда вы подключите эти аннотации, detailsAction будет выполнена, когда URL совпадет с шаблоном /client/{id}. Конвертер параметров получит из БД сущность клиента на основании параметра id, который будет извлечен из УРЛа роутером. И аннотация @Template укажет на то, что возвращаемый массив является набором переменных для шаблона Resources/views/Client/Details.html.twig.


Отлично! И всего в несколько строк кода. Но все эти автомагические вещи незаметно связывают наш контроллер с фреймворком. Пусть явных зависимостей тут и нет, есть несколько зависимостей неявных. Контроллер будет работать только при подключенном SensioFrameworkExtraBundle в силу следующих причин:


1. Он (SensioFrameworkExtraBundle) генерирует роутинг на основе аннотаций

2. Он заботится о превращении возвращаемого массива в корректный объект Response

3. Он угадывает, какой шаблон нужно применить

4. Он превращает параметр id из запроса в реальную модель


Казалось бы, не так это все и страшно, но SensioFrameworkExtraBundle — бандл, а значит, что работает он только в контексте приложения Symfony 2. Но мы же не хотим быть привязанными к конкретному фреймворку (в этом, собственно, суть этой серии постов), так что от этой зависимости нам надо избавиться.


Вместо аннотаций мы будем использовать обычные конфигурационные файлы и PHP-код.


Используем конфигурацию роутера



В первую очередь убедимся, что наши роуты подключаются в Resources/config/routing.xml:

<?xml version="1.0" encoding="UTF-8" ?>
<routes xmlns="http://ift.tt/1npbUgM"
xmlns:xsi="http://ift.tt/ra1lAU"
xsi:schemaLocation="http://ift.tt/1npbUgM
http://ift.tt/1npbUx2">

<route id="client.details" path="/client/{id}" methods="GET">
<default key="_controller">client_controller:detailsAction</default>
</route>

</routes>


Можете использовать YAML, но я последнее время что-то подсел на XML.


Убедитесь, что сервис client_controller на самом деле существует, и не забудьте импортировать новый routing.xml в настройках приложения, в файле app/config/routing.yml:



MatthiasClientBundle:
resource: @MatthiasClientBundle/Resources/config/routing.xml


Теперь можно убрать аннотации @Route и @Method из класса контроллера!


Самостоятельно создавайте объект Response



Теперь, вместо того, чтобы надеяться на аннотацию @Template, вы вполне можете рендерить шаблон самостоятельно, и создавать объект Response, содержащий результат рендеринга. Вам просто надо инъектировать шаблонизатор в ваш контроллер, и указать имя шаблона, который вы хотите отрендерить:

use Sensio\Bundle\FrameworkExtraBundle\Configuration\Template;

use Sensio\Bundle\FrameworkExtraBundle\Configuration\ParamConverter;

use Symfony\Component\Templating\EngineInterface;

use Symfony\Component\HttpFoundation\Response;



class ClientController
{
private $templating;

public function __construct(EngineInterface $templating)
{
$this->templating = $templating;
}

/**
* @ParamConverter(name="client")
*/
public function detailsAction(Client $client)
{
return new Response(
$this->templating->render(
'@MatthiasClientBundle/Resources/views/Client/Details.html.twig',
array(
'client' => $client
)
)
);
}
}




В объявлении сервиса для этого контроллера также надо указать сервис @templating как аргумент конструктора:

services:
client_controller:
class: Matthias\ClientBundle\Controller\ClientController
arguments:
- @templating


После этих изменений можно смело убирать аннотацию @Template


Самостоятельно получайте требуемые данные



И еще один шаг, чтобы понизить связанность нашего контроллера. Мы по-прежнему зависим от SensioFrameworkExtraBundle, он автоматически превращает параметр id из запроса в реальные сущности. Это должно быть несложно исправить, мы ведь можем просто получать сущность сами, используя репозиторий сущностей напрямую:

...
use Doctrine\Common\Persistence\ObjectRepository;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;

class ClientController
{
private $clientRepository;
...

public function __construct(ObjectRepository $clientRepository, ...)
{
$this->clientRepository = $clientRepository;
...
}

public function detailsAction(Request $request)
{
$client = $this->clientRepository->find($request->attributes->get('id'));

if (!($client instanceof Client) {
throw new NotFoundHttpException();
}

return new Response(...);
}
}




Объявление сервиса должно возвращать нужный нам репозиторий. Мы добьемся этого вот таким способом:

services:
client_controller:
class: Matthias\ClientBundle\Controller\ClientController
arguments:
- @templating
- @client_repository

client_repository:
class: Doctrine\Common\Persistence\ObjectRepository
factory_service: doctrine
factory_method: getRepository
public: false
arguments:
- "Matthias\ClientBundle\Entity\Client"




Наконец, мы избавились от аннотаций, значит, наш контроллер вполне можно использовать вне приложения Symfony 2 (то есть такого, которое не зависит ни от FrameworkBundle, ни от SensioFrameworkExtraBundle). Все зависимости явные, то есть чтобы контроллер заработал, вам нужны:

— компонент HttpFoundation (для классов Response и NotFoundHttpException)

— шаблонизатор (для EngineInterface)

— какая-либо реализация репозиториев Doctrine (Doctrine ORM, Doctrine MongoDB ODM, ...)

— Twig для шаблонизации


Остался только один слабый момент: имена наших шаблонов все еще основаны на соглашениях фреймворка (т.е. используют имя бандла в качестве пространства имен, напр. @MatthiasClientBundle/...). Это неявная зависимость от фреймворка, поскольку эти пространства имен регистрируются в загрузчике из файловой системы Twig. В следующем посте мы разберемся и с этой проблемой тоже.


This entry passed through the Full-Text RSS service — if this is your content and you're reading it on someone else's site, please read the FAQ at http://ift.tt/jcXqJW.


Комментариев нет:

Отправить комментарий