[Перевод] Избавьтесь от аннотаций в своих контроллерах!
В предыдущей части этой серии мы понизили связанность симфонийского контроллера и фреймворка, удалив зависимость от базового класса контроллера из 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. Он заботится о превращении возвращаемого массива в корректный объект Response3. Он угадывает, какой шаблон нужно применить4. Он превращает параметр id из запроса в реальную модель
Казалось бы, не так это все и страшно, но SensioFrameworkExtraBundle — бандл, а значит, что работает он только в контексте приложения Symfony 2. Но мы же не хотим быть привязанными к конкретному фреймворку (в этом, собственно, суть этой серии постов), так что от этой зависимости нам надо избавиться.
Вместо аннотаций мы будем использовать обычные конфигурационные файлы и PHP-код.
Используем конфигурацию роутера
В первую очередь убедимся, что наши роуты подключаются в Resources/config/routing.xml:
Можете использовать 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. В следующем посте мы разберемся и с этой проблемой тоже.