Позбавтеся від анотацій у своїх контролерах!

      У попередній частині цій серії ми знизили зв'язаність сімфонійского контролера і фреймворка, видаливши залежність від базового класу контролера з
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. У наступному пості ми розберемося і з цією проблемою теж.
  
Джерело: Хабрахабр

0 коментарів

Тільки зареєстровані та авторизовані користувачі можуть залишати коментарі.