Подтвердить что ты не робот

Составление класса Controller с Injection Dependency в PHP

Как решить проблему составления класса Controller в PHP, который должен быть:

  • легко проверяемый, используя инъекцию зависимостей,
  • предоставить общие объекты для конечного программиста
  • предоставить способ загружать новые пользовательские библиотеки

Посмотрите вниз, для создания экземпляра контроллера с инфраструктурой внедрения зависимостей


Проблема состоит в том, что производные контроллеры могут использовать любые ресурсы, на которые программист хочет (например, предоставляет инфраструктуру). Как создать единый доступ к общим ресурсам (DB, User, Storage, Cache, Helpers), определенные пользователем классы или другие библиотеки?

Элегантное решение?

Есть несколько возможных решений моей проблемы, но никто не выглядит элегантным

  • Попробуйте передать все общедоступные объекты конструктором ? (может создавать конструктор даже с 10 заполнителями)
  • Создать getters, settters? (раздутый код) $controller->setApplication($app)
  • Применить сильные синглеты на общих ресурсах? User::getInstance() или Database::getInstance()
  • Использовать контейнер для инъекций как одноэлемент для совместного использования объектов внутри контроллера
  • предоставить одно глобальное приложение singleton как factory? (этот выглядит очень часто в php-фреймворках, он сильно противоречит принципам DI и закону Деметры).

Я понимаю, что создание сильно связанных классов обескураживается и изгоняется за:), однако я не знаю, как эта парадигма применяется к отправной точке для других программистов (класс Controller), в которых они должны иметь доступ общие ресурсы, предоставляемые архитектуре MVC. Я считаю, что разбиение класса контроллера на более мелкие классы каким-то образом разрушит практический смысл MVC.


Инъекционная платформа зависимостей

Схема DI выглядит как жизнеспособный выбор. Однако проблема все еще сохраняется. Класс, такой как Controller, не находится на уровне приложения, а на уровне RequestHandler/Response.

Как этот слой должен создать экземпляр контроллера?

  • передать инжектор DI в этот слой?
  • Плата DI как одноэлемент?
  • установить изолированную конфигурацию DI-рамки только для этого слоя и создать отдельный экземпляр инжектора DI?
4b9b3361

Ответ 1

Вы сами разрабатываете структуру? Если нет, ваш вопрос не применяется, потому что вам нужно выбрать уже существующие фреймворки и их существующие решения. В этом случае ваш вопрос должен быть переформулирован так же, как "как сделать модульное тестирование/инъекцию зависимостей в рамках X".

Если вы разрабатываете структуру самостоятельно, сначала проверьте, как уже существующие подходы к этой проблеме. И вы также должны разработать свои собственные требования, а затем просто пойти с самым простым решением. Без требований ваш вопрос является чисто эстетичным и аргументированным.

Imho - самое простое решение - иметь общедоступные свойства, которые инициализируются значениями по умолчанию, предоставляемыми вашей инфраструктурой, в противном случае вы можете вводить сюда свои макеты. (Это равно вашему решению getters/seters, но без упомянутого раздувания. Вам не всегда нужны геттеры и сеттеры.) Необязательно, если вам это действительно нужно, вы можете предоставить конструктор для инициализации тех, которые были в одном вызове (как вы предположили).

Синглтоны - изящное решение, но опять же, вы должны спросить себя, применимо ли это в вашем случае? Если в вашем приложении должны быть разные экземпляры одного и того же типа объектов, вы не можете пойти с ним (например, если вы хотите высмеять класс только в половине вашего приложения).

Конечно, это действительно потрясающе, чтобы иметь все варианты. У вас могут быть getters/setter, конструкторы, а когда инициализация опущена, по умолчанию берутся из singleton factory. Но, имея слишком много вариантов, когда это не нужно, это не удивительно, это вызывает беспокойство, поскольку программист должен выяснить, какое соглашение, вариант и шаблон использовать. Я определенно не хочу делать десятки дизайнерских решений, чтобы просто запустить CRUD.

Если вы посмотрите на другие рамки, вы увидите, что нет серебряной пули. Часто одна структура использует различные методы в зависимости от контекста. В контроллерах DI - это очень простая вещь, посмотрите на помощники CakePHP $, переменные $components, которые инструктируют вставлять соответствующие переменные в класс контроллера. Для самого приложения одноэлемент по-прежнему хорош, так как всегда есть только одно приложение. Свойства, которые реже меняются/издеваются, вводятся с использованием общедоступных свойств. В случае MVC подклассы также являются вполне жизнеспособным вариантом: как AppController, AppView, AppModel в CakePHP. Они вставляются в иерархию классов между фреймворками и всеми вашими классами Controller, View и Model. Таким образом, у вас есть одна точка для объявления глобальных переменных для вашего основного типа классов.

В Java из-за динамических загрузчиков классов и отражения у вас есть еще много вариантов выбора. Но, с другой стороны, вы должны поддерживать гораздо больше требований: параллельные запросы, общие объекты и состояния между рабочими потоками, распределенными серверами приложений и т.д.

Вы можете ответить только на вопрос, что подходит вам, если вы знаете, что вам нужно в первую очередь. Но на самом деле, почему вы все равно пишете еще одну новую фреймворк?

Ответ 2

Синглтоны неодобрились, когда инъекция зависимостей жизнеспособна (и мне еще предстоит найти случай, когда нужно было использовать Singleton).

Скорее всего, у вас будет контроль над созданием контроллеров, поэтому вы можете уйти с указанным $controller->setApplication($application), но при необходимости вы можете использовать статические методы и переменные (которые гораздо менее вредны для ортогональности приложения, чем Синглтоны); а именно Controller::setApplication() и доступ к статическим переменным через методы экземпляра.

например:

// defining the Application within the controller -- more than likely in the bootstrap
$application = new Application();
Controller::setApplication($application);

// somewhere within the Controller class definition
public function setContentType($contentType)
{
    self::$application->setContentType($contentType);
}

Я сделал привычку отделять статические свойства и свойства экземпляра (при необходимости и все еще группируя свойства в верхней части определения класса). Я чувствую, что это менее громоздко, чем синглтоны, поскольку классы все еще остаются довольно компактными.

Ответ 3

Как о рефакторинг?

Предположим, что это был не один из ваших вариантов, но вы заявляете, что код является в значительной степени связанным классом. Почему бы не потратить это время и усилия на реорганизацию его на более модульные, тестируемые компоненты?

Ответ 4

Насколько я понимаю, класс приложения должен быть диспетчером. Если это так, я предпочел бы использовать конструктор контроллера для передачи экземпляра приложения, поэтому контроллер узнает, кто его вызывает. В более позднем случае, если вы хотите иметь другой экземпляр приложения в зависимости от того, вызывается ли код из CLI, вы можете иметь ApplicationInterface, который будет реализован Application\Http и Application\Cli, и все будет легко поддерживать.

Вы также можете реализовать некоторый шаблон factory, чтобы получить хорошую реализацию DI. Например, проверьте метод createThroughReflection: https://github.com/troelskn/bucket/blob/master/lib/bucket.inc.php

Надеюсь, это имеет смысл.

С уважением, Ник

Ответ 5

Вы также можете использовать ControllerFatory, в котором вы могли бы предоставить свое приложение или маршрутизатор/диспетчер

Вы могли бы вызвать $controllerFactory- > createController ($ name);

В вашем приложении не было бы представления о том, как создавать контроллеры Factory. Поскольку вы вводите свой собственный ControllerFactory в свой контейнер DI, вы можете управлять всеми зависимостями, которые вы хотите, в зависимости от контроллера.

class ControllerFactory {
    public function __construct(EvenDispatcher $dispatcher,
                                Request $request,
                                ResponseFactory $responseFactory, 
                                ModelFactory $modelFactory, 
                                FormFactory $formFactory) {
        ...
    }

    public function createController($name = 'Default') {
        switch ($name) {
            case 'User':
              return new UserController($dispatcher, 
                                        $request, 
                                        $responseFactory->createResponse('Html'), 
                                        $modelFactory->createModel('User'),
                                        $formFactory->createForm('User'),...);

              break;
            case 'Ajax':
              return new AjaxController($dispatcher, 
                                        $request, 
                                        $responseFactory->createResponse('Json'), 
                                        $modelFactory->createModel('User'));
              break;
            default:
                 return new DefaultController($dispatcher, $request, $responseFactory->createResponse('Html'));
        }
    }

} 

Поэтому вам просто нужно добавить этот Factory в свой контейнер DI и передать его в свое приложение. Всякий раз, когда вам нужен новый контроллер, вы добавляете его в Factory, и если требуются новые зависимости, вы даете их Factory через ваш контейнер DI.

class App {
    public function __construct(Router $router,Request $request, ControllerFactory $cf, ... ) {
      ...
    }

    public function execute() {
        $controllerName = $this->router->getMatchedController();
        $actionName $this->router->getMatchedAction();

        $controller = $cf->createController($controllerName);

        if(is_callable($controller, $actionName)) {
            $response = $controller->$action(request);
            $response->send();     
        }
    }
}

Это не производственный код, я не тестировал, но вот как вы отключаете свои контроллеры от своего приложения. Обратите внимание на то, что здесь есть одна плохая связь, потому что мой контроллер возвращает ответ, и я выполняю ответ в приложении. Но, как я уже сказал, это всего лишь небольшой пример.

Обычно рекомендуется передавать фабрики для моделей, форм и контроллеров своим родителям, потому что вы в конечном итоге загрузите весь свой объект Graph во время начальной загрузки, что действительно плохо и потребляет память.

Я знаю, что этот ответ уже одобрен, но это мои 2 цента на тему

Есть хорошая статья по этому вопросу

http://miller.limethinking.co.uk/2011/07/07/dependency-injection-moving-from-basics-to-container/