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), если не найдено
  • он также должен поддерживать весь поток, поскольку в процессе разрешения контроллера нет хаков.