Давным давно передо мной встала задача реализовать механизм инициализации контроллеров в Symfony, т.е. выполнение неких дефолтных действий перед каждым вызовом экшна контроллера. Первое, что пришло на ум, — это добавить EventListener для события kernel.controller, в котором будет вызываться метод контроллера initialize, если он есть. Данным способом я пользуюсь уже на протяжении нескольких лет.
Буквально на днях я задумался: а что если необходимо перед экшном выполнить разные методы для разных контроллеров, несколько методов подряд, а некоторые из них даже несколько раз и с разными параметрами? В данной статье я хочу рассказать, как я решил эту проблему с помощью аннотаций. Думаю, эта статья будет полезна в том числе и тем, кто никогда не работал с аннотациями.
В первую очередь, я наглядно покажу, как реализовать механизм инициализации контроллеров.
Сначала создадим интерфейс, который поможет отлавливать те контроллеры, которым необходима инициализация:
<?php namespace MyBundle\Controller; interface InitializableControllerInterface { }
Затем создадим EventListener для события kernel.controller, который и будет осуществлять инициализацию:
<?php namespace MyBundle\EventListener; use MyBundle\Controller\InitializableControllerInterface; use Symfony\Component\HttpKernel\Event\FilterControllerEvent; class KernelControllerListener { // Метод, вызываемый при событии kernel.controller public function onKernelController(FilterControllerEvent $event) { $controller = $event->getController(); // Если контроллер реализует интерфейс InitializableControllerInterface if (is_array($controller) && $controller[0] instanseof InitializableControllerInterface) { // Вызов методов инициализации контроллера } } }
И добавим для него конфигурацию сервиса (services.xml):
<?xml version="1.0" encoding="UTF-8" ?> <container xmlns="http://symfony.com/schema/dic/services" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd"> <services> <service id="my_bundle.kernel_controller_listener" class="MyBundle\EventListener\KernelControllerListener"> <tag name="kernel.event_listener" event="kernel.controller" method="onKernelController" /> </service> </services> </container>
В принципе, этого уже достаточно, но мы же хотим сделать инициализацию контроллеров более гибкой, поэтому переходим к аннотациям.
На самом деле, с аннотациями работать очень просто, особенно если используешь ридер аннотаций от Doctrine. Начнем с того, что создадим класс-аннотацию, который следует применять к инициализирующим методам контроллера:
<?php namespace MyBundle\Annotation; /** * @Annotation * @Target({"METHOD"}) * * С помощью @Annotation мы указываем, что данный класс должен использоваться как аннотация, * а @Target({"METHOD"}) - что данную аннотацию можно применять только к методам класса. */ class Init { // Параметры аннотации: /** * @var array * * Массив передаваемых аргументов */ public $args = []; /** * @var int * * Приоритет вызова (чем больше, тем раньше будет вызов) */ public $priority = 0; }
Рекомендую указывать тип параметров аннотации для осуществления контроля Doctrine над типами входящих данных.
Теперь аннотацию можно использовать в контроллере:
<?php namespace MyBundle\Controller; use MyBundle\Annotation\Init; use MyBundle\Controller\InitializableControllerInterface; use Symfony\Bundle\FrameworkBundle\Controller\Controller; class MyController extends Controller implements InitializableControllerInterface { /** * @Init(args = {"test"}, priority = 200) * * Данный метод будет вызван (initialize("test");) перед вызовом экшна контроллера */ public function initialize($value) { // ... какой-то код ... } }
Осталось только добавить обработку аннотаций в наш KernelControllerListener:
<?php namespace MyBundle\EventListener; use Doctrine\Common\Annotations\Reader; use MyBundle\Annotation\Init; use MyBundle\Controller\InitializableControllerInterface; use Symfony\Component\HttpKernel\Event\FilterControllerEvent; class KernelControllerListener { protected $annotationReader; // Передаем в конструктор ридер аннотаций public function __construct(Reader $annotationReader) { $this->annotationReader = $annotationReader; } public function onKernelController(FilterControllerEvent $event) { $controller = $event->getController(); if (is_array($controller) && $controller[0] instanceof InitializableControllerInterface) { // Получаем информацию о классе $reflector = new \ReflectionClass($controller[0]); // Получаем список всех публичных методов класса $methods = $reflector->getMethods(\ReflectionMethod::IS_PUBLIC); $initMethods = []; // Сохраняем только те методы, у которых есть аннотация @Init foreach ($methods as $method) { // Получаем все аннотации метода $annotations = $this->annotationReader->getMethodAnnotations($method); foreach ($annotations as $annotation) { // Если аннотация - наша, то сохраняем метод в отдельный список с параметрами приоритета и аргументов if ($annotation instanceof Init) { $initMethods[] = [ 'method' => $method, 'args' => $annotation->args, 'priority' => $annotation->priority ]; } } } // Сортируем список сохраненных методов по порядку убывания приоритета usort($initMethods, function($a, $b) { return $b['priority'] - $a['priority']; }); foreach ($initMethods as $initMethod) { $method = $initMethod['method']; // Осуществляем вызов метода с учетом, есть ли для него аргументы или нет if (count($initMethod['args'])) { $method->invokeArgs($controller[0], $initMethod['args']); } else { $method->invoke($controller[0]); } } } } }
И дополним конфигурацию сервиса:
<?xml version="1.0" encoding="UTF-8" ?> <container xmlns="http://symfony.com/schema/dic/services" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd"> <services> <service id="my_bundle.kernel_controller_listener" class="MyBundle\EventListener\KernelControllerListener"> <argument type="service" id="annotation_reader" /> <!-- Передача ридера аннотаций в конструктор --> <tag name="kernel.event_listener" event="kernel.controller" method="onKernelController" /> </service> </services> </container>
Вот и все. Весь мой код можно посмотреть на GitHub, буду рад объективной критике.
См. также:
ссылка на оригинал статьи https://habrahabr.ru/post/277465/
Добавить комментарий