В предыдущей части этой серии мы понизили связанность симфонийского контроллера и фреймворка, удалив зависимость от базового класса контроллера из
Теперь давайте посмотрим на аннотации. Первоначально они были подключены для ускорения разработки (исчезает потребность редактировать конфигурационный файл, просто решайте проблемы прямо на месте!):
Когда вы подключите эти аннотации,
Отлично! И всего в несколько строк кода. Но все эти автомагические вещи незаметно связывают наш контроллер с фреймворком. Пусть явных зависимостей тут и нет, есть несколько зависимостей неявных. Контроллер будет работать только при подключенном
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. Он заботится о превращении возвращаемого массива в корректный объект
Response
3. Он угадывает, какой шаблон нужно применить
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. В следующем посте мы разберемся и с этой проблемой тоже.