В предыдущей части этой серии мы понизили связанность симфонийского контроллера и фреймворка, удалив зависимость от базового класса контроллера из
Теперь давайте посмотрим на аннотации. Первоначально они были подключены для ускорения разработки (исчезает потребность редактировать конфигурационный файл, просто решайте проблемы прямо на месте!):
Когда вы подключите эти аннотации,
Отлично! И всего в несколько строк кода. Но все эти автомагические вещи незаметно связывают наш контроллер с фреймворком. Пусть явных зависимостей тут и нет, есть несколько зависимостей неявных. Контроллер будет работать только при подключенном
1. Он (SensioFrameworkExtraBundle) генерирует роутинг на основе аннотаций
2. Он заботится о превращении возвращаемого массива в корректный объект
3. Он угадывает, какой шаблон нужно применить
4. Он превращает параметр
Казалось бы, не так это все и страшно, но
Вместо аннотаций мы будем использовать обычные конфигурационные файлы и PHP-код.
В первую очередь убедимся, что наши роуты подключаются в
Можете использовать YAML, но я последнее время что-то подсел на XML.
Убедитесь, что сервис
Теперь можно убрать аннотации
Теперь, вместо того, чтобы надеяться на аннотацию
В объявлении сервиса для этого контроллера также надо указать сервис
После этих изменений можно смело убирать аннотацию
И еще один шаг, чтобы понизить связанность нашего контроллера. Мы по-прежнему зависим от
Объявление сервиса должно возвращать нужный нам репозиторий. Мы добьемся этого вот таким способом:
Наконец, мы избавились от аннотаций, значит, наш контроллер вполне можно использовать вне приложения Symfony 2 (то есть такого, которое не зависит ни от
— компонент HttpFoundation (для классов
— шаблонизатор (для
— какая-либо реализация репозиториев Doctrine (Doctrine ORM, Doctrine MongoDB ODM, ...)
— Twig для шаблонизации
Остался только один слабый момент: имена наших шаблонов все еще основаны на соглашениях фреймворка (т.е. используют имя бандла в качестве пространства имен, напр.
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:<?xml version="1.0" encoding="UTF-8" ?> <routes xmlns="http://symfony.com/schema/routing" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://symfony.com/schema/routing http://symfony.com/schema/routing/routing-1.0.xsd"> <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. В следующем посте мы разберемся и с этой проблемой тоже.