Symfony - изменение способа создания и выполнения контроллеров
Примечание. Начиная с версии 2.8 Symfony предоставила autowire: true
для конфигурации службы, а с версии 3.3 Symfony предоставила alias
(вместо autowire_types
) псевдоним конкретного объекта для интерфейса для автоматической инъекции зависимостей в "контроллеры как службы". Там также имеется пучок, позволяющий автоувеличивать методы "действия" контроллера, хотя я отошел от этого и больше сосредоточился на вариации шаблона ADR (который, в основном, представляет собой один класс "действия" с методом интерфейса и не толкая нагрузку методов действий в рамках одного класса, что в конечном итоге приводит к архитектурному кошмару). Это, фактически, то, что я искал все эти годы, и теперь больше не нужно "подключаться" к достойному рекурсивному инжектору зависимостей (auryn), поскольку структура теперь обрабатывает то, что у него должно было быть четыре года назад. Я оставлю этот ответ здесь, если кто-то захочет проследить шаги, которые я сделал, чтобы посмотреть, как работает ядро, и некоторые параметры, доступные на этом уровне.
Примечание. Хотя этот вопрос в первую очередь нацелен на Symfony 3, он также должен иметь отношение к пользователям Symfony 2, поскольку логика ядра, похоже, не сильно изменилась.
Я хочу изменить способ создания контроллеров в Symfony. Логика их создания в настоящее время находится в HttpKernel:: handle и, более конкретно, HttpKernel:: handleRaw. Я хочу заменить call_user_func_array($controller, $arguments)
на свой собственный инжектор, выполняющий эту конкретную строку.
Параметры, которые я пробовал до сих пор:
- Расширение
HttpKernel::handle
с помощью моего собственного метода, а затем с вызовом symfony
http_kernel:
class: AppBundle\HttpKernel
arguments: ['@event_dispatcher', '@controller_resolver', '@request_stack']
Недостатком этого является то, что, поскольку handleRaw
является закрытым, я не могу его распространять без хакерского отражения, поэтому мне пришлось бы копировать и вставлять тонну кода.
- Создание и регистрация нового контроллера.
controller_resolver:
class: AppBundle\ControllerResolver
arguments: []
Это было фундаментальное недоразумение, которое у меня было, поэтому я решил записать его здесь. Задача распознавателя - решить, где найти контроллер как вызываемый. На самом деле это еще не вызвано. Я более чем доволен тем, как Symfony берет маршруты от routes.yml
и вычисляет класс и метод для вызова контроллера как вызываемого.
- Добавление прослушивателя событий на
kernel.request
kernel.request:
class: MyCustomRequestListener
tags:
- { name: kernel.event_listener, event: kernel.request, method: onKernelRequest, priority: 33 /** Important, we'll get to why in a minute **/ }
Взглянув на Документацию компонента Http Kernel, мы видим, что она имеет следующую типичную цель:
Чтобы добавить дополнительную информацию в запрос, инициализировать части системы или вернуть ответ, если это возможно (например, уровень безопасности, который запрещает доступ).
Я решил, что, создав нового слушателя, используя мой пользовательский инжектор для создания моего контроллера, а затем вернет ответ в этом слушателе, обойдется остальная часть кода, который создает экземпляр контроллера. Это то, что я хочу! Но есть главный недостаток:
Symfony Profiler не появляется или ничего из этого материала, это просто мой ответ и что он. Мертв. Я обнаружил, что я могу переключить приоритет с 31 до 33 и переключиться между моим кодом и Symfonys, и я считаю, что это из-за прослушивателя роутера priority. Я чувствую, что иду по неверному пути.
Нет, это позволяет мне изменить вызываемый, который будет вызываться call_user_func_array()
, а не то, как фактически создается экземпляр контроллера, что является моей целью.
Я документировал свои идеи, но я вышел. Как я могу достичь следующего?
- Измените способ создания и выполнения контроллеров, в частности
call_user_func_array()
, который находится в черном частном методе (спасибо Symfony)
- Вернитесь к экземпляру контроллера по умолчанию, если моя работа не работает.
- Разрешить все остальное работать так, как ожидалось, например, загрузку профилировщика.
- Уметь связывать это с расширением для других пользователей.
Почему я хочу это сделать?
Контроллеры могут иметь множество разных методов для разных обстоятельств, и каждый метод должен иметь возможность вводить текст для того, что он требует по отдельности, а не иметь конструктор, чтобы взять все вещи, некоторые из которых могут даже не использоваться в зависимости от выполняемого метода контроллера, Контроллеры действительно не придерживаются принципа единой ответственности, и они являются "краеугольным камнем объекта". Но они такие, какие они есть.
Я хочу заменить, как контроллеры создаются с помощью моего собственного рекурсивного инсталлятора autowiring, а также то, как они выполняются, снова с рекурсивной интроспекцией через мой инжектор, поскольку пакет Symfony по умолчанию, похоже, не обладает этой функциональностью. Даже с последней опцией службы "autowire" в Symfony 2.8 +.
Ответы
Ответ 1
Почему бы вам не вернуть свой собственный вызов из пользовательского ControllerResolverInterface
, который бы создавал экземпляр Controller
так, как вы хотите, и называть его?
Это будет в основном декоратор.
Вы можете расширить Symfony\Component\HttpKernel\Controller\ControllerResolver
с помощью собственной реализации метода instantiateController()
или реализовать ControllerResolverInterface
с нуля.
UPD:
Когда Symfony выполняет вызов call_user_func_array($controller, $arguments);
в handleRaw()
, переменная $controller
- это то, что вы вернули из своего пользовательского ControllerResolver
. Это означает, что вы можете вернуть любой вызываемый из вашего распознавателя (он может быть [$this, "callController"]
f.e.), и внутри этого вызываемого вы создадите новый Controller
с Auryn и назовите его.
UPD2:
Если вы все еще боретесь с этим, я добавлю пример, потому что вы можете пропустить то, что я имел в виду здесь.
use Symfony\Bundle\FrameworkBundle\Controller\ControllerResolver;
class AutowiringControllerResolver extends ControllerResolver
{
// ... constructor stuff; assume $injector is a part of this class
protected function createController($controller)
{
$controller = parent::createController($controller);
return function (...$arguments) use ($controller) {
// you can do with resolved $arguments whatever you want
// or you can override getArguments() method and return
// empty array to discard getArguments() functionality completely
return $this->injector->execute($controller);
};
}
protected function instantiateController($classname)
{
return $this->injector->make($classname);
}
}
Ответ 2
Контроллер-резольвер фактически выполняет две вещи. Первый - получить контроллер. Второй - получить список аргументов для данного действия.
$arguments = $this->resolver->getArguments($request, $controller);
$response = call_user_func_array($controller, $arguments);
Это метод getArguments, который вы можете переопределить для реализации вашей специальной функции "инъекции метода действия". Вам просто нужно определить, какие аргументы необходимо для метода действий, и вернуть их массив.
Исходя из другого вопроса, я также думаю, что вы можете не понимать функциональность autwire. Autowire действительно применим только к инъекции конструктора. Это не поможет с инъекцией метода действия.
Если getArguments не решит ваше требование, тогда переопределение метода handle действительно является вашим единственным вариантом. Да, есть много кода для копирования/вставки из handleRaw, но это потому, что там есть немного. И даже если handleRaw был защищен, вам все равно придется копировать/вставлять код, чтобы получить одну строку, которую вы хотите заменить.
Ответ 3
Слушатель слишком поздно для решения ваших задач, поскольку он исключает контейнер Injection Dependency, который имеет решающее значение для создания допустимого объекта (~ = service).
Вероятно, вы ищете функцию Autowiring контроллера.
Если это так, вы можете найти решение или, по крайней мере, вдохновение в этом комплекте: http://www.tomasvotruba.cz/blog/2016/03/10/autowired-controllers-as-services-for-lazy-people/
Он отвечает вашим потребностям в этих точках:
- инжектор autowire
- Резервное копирование по умолчанию (FrameworkBundle), если не найдено
- он также должен поддерживать весь поток, поскольку в процессе разрешения контроллера нет хаков.