PHP MVC: слишком много зависимостей в контроллере?

Я работаю над личным проектом HMVC:

  • Нет локаторов сервисов, глобального состояния (например, static или global ), нет синглтонов.
  • Обработка модели инкапсулируется в сервисах (service = domain objects + repositories + data mappers).
  • Все контроллеры расширяют абстрактный контроллер.
  • Все зависимости проекта вводятся через контейнер инъекций зависимостей Auryn .

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

 class UsersController extends AbstractController { private $authentication; public function __construct( Config $config , Request $request , Session $session , View $view , Response $response , Logger $logger , Authentication $authentication // Domain model service ) { parent::__construct(/* All dependencies except authentication service */); $this->authentication = $authentication; } // Id passed by routing. public function authenticateUser($id) { // Use the authentication service... } } 

Список зависимостей будет расти далее. Это необходимо изменить. Поэтому я думал о:

  • Полностью отдельные контроллеры из представлений .
    Затем они разделили бы сервисный уровень. Представления больше не будут принадлежать контроллерам, и Response будет зависеть от представлений.
  • Использовать инъекцию установщика в контроллерах
    Как для Request , Session , Logger и т. Д .;
  • Вкладывание зависимостей в действия контроллера
    Только при необходимости.
    Как для Request , Session , Logger и т. Д .;
  • Используйте узор декоратора .
    Как для регистрации после вызова действия.
  • Внедрить некоторые фабрики .
  • Конструктор вводит только необходимые зависимости только для дочерних контроллеров .
    Так что не в AbstractController .

Я пытаюсь найти элегантный способ справиться с этой задачей, и я буду благодарен за любые предложения. Спасибо.

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

  • Инъекция конструктора является подходящей опцией. Но мне показалось, что, следуя этой строке, я получаю слишком много зависимостей / аргументов в конструкторах. Поэтому предоставление диспетчерам слишком много обязанностей (чтение запрошенных значений, изменение состояния объектов домена, ведение журналов, запрос на просмотр для загрузки шаблонов и визуализации данных и т. Д.).
  • Утилизация сеттера также была решением, которое необходимо принять во внимание. Но в ходе развивающегося времени в моем проекте я понял, что это решение действительно не подходило (по крайней мере) к картине моих отношений с контроллером.
  • Включение зависимостей непосредственно в действия контроллера вызвало у меня нелегкое (но большое время) тоже, имея в виду, что у меня уже были значения url, введенные в качестве аргументов действия, и что я не использовал диспетчеров маршрутизации.
  • Реализация фабрик также была отличной идеей, чтобы иметь возможность иметь объекты в моем распоряжении внутри каждого действия контроллера. Фабрики – отличный инструмент для использования, но только исходя из предпосылки необходимых объектов времени выполнения, а не просто уменьшения количества зависимостей в конструкторах.
  • Образцом декоратора также является хорошая альтернатива. Но если, например, вы хотите что-то регистрировать в рамках действия контроллера, это не решение: вам все равно нужно передать регистратор в качестве зависимости (в конструкторе, установщике или действии).
  • Я подумал о том, чтобы вводить нужные зависимости только на дочерние контроллеры . Но тогда проблема множественных обязанностей соответствующих контроллеров остается неизменной.

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

  • Model-View-Confusion часть 1: The View получает свои собственные данные из модели
  • Model-View-Confusion часть 2: Модели MVC не являются моделью домена

Его работы основаны на глубоком, хорошо аргументированном анализе концепций MVC. Они не только очень легко следовать, но и поддерживаются объяснительными примерами. Одним словом: замечательный вклад в сообщество MVC и разработчиков.

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

Итак, какова недостающая ссылка в моем проекте HMVC? Это называется ОТДЕЛЕНИЕ КОНЦЕРН .

Для простоты я попытаюсь объяснить это, обратившись только к одному контроллеру, одному действию контроллера, одному представлению, одной модели (объекту домена) и одному шаблону (файлу), представляя их в контексте User .

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

Классическим способом создание контроллера и процесс вызова действий включают в себя два этапа:

  • Создание контроллера – передача всех зависимостей, просмотр включительно;
  • Вызовите действие контроллера.

Код:

 $controller = new UserController(/* Controller dependencies */); $controller->{action}(/* Action dependencies */); 

Это означает, что контроллер несет ответственность за все, что угодно. Поэтому неудивительно, почему контроллеру необходимо вводить столько зависимостей.

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

  • Определите метод output для отображения информации на экране в представлении.
  • Создайте контроллер – передайте все зависимости, за исключением вида и связанных с ним зависимостей (ответ, объект шаблона и т. Д.).
  • Создайте представление – передайте ему соответствующие зависимости (ответ, объект шаблона и т. Д.).
  • Вызовите действие контроллера.
  • Вызвать метод output вида:

И код:

 class UserView { //.... // Display information on screen. public function output () { return $this ->load('<template-name>') ->render(array(<data-to-display>)) ; } //.... } $controller = new UserController(/* (less) controller dependencies */); $view = new UserView(/* View dependencies */); $controller->{action}(/* Action dependencies */); echo $view->output(); 

Достигнув пяти верхних шагов, нам удалось полностью отделить контроллер от представления.

Но есть аспект, который мы предположили ранее: мы не использовали ни одну модель. Итак, какова роль контроллера в этом созвездии? Ответ таков: нет. Контроллер должен существовать только как middlemann между некоторым местом хранения (база данных, файловая система и т. Д.) И представление. В противном случае, например, только для вывода какой-либо информации в определенном формате на экране, метод output представления полностью достаточен.

Вещи меняются, если модель появляется на сцене. Но куда его следует вводить? В контроллере или в представлении? В обоих. Они имеют один и тот же экземпляр модели. В этот момент контроллер приобретает роль посредника – между хранением и представлением – сам по себе. Теоретическая форма:

 $model = new UserModel; $controller = new UserController($model, /* Other controller dependencies */); $view = new UserView($model, /* Other view dependencies */); $controller->{action}(/* Action dependencies */); echo $view->output(); 

При этом контроллер может изменить состояние модели и убедиться, что она сохранена в системе хранения. Тот же экземпляр модели, соответствующий его состоянию, считывается и отображается. Контроллер передает информацию о логике отображения в представление через модель. Проблема состоит в том, что эти данные не относятся к бизнес-логике, которая, как предполагается, должна иметь только модель. Они являются только участниками логики отображения.

Чтобы избежать предоставления логики отображения логики модели, мы должны представить новый компонент на картинке: модель-образец. Вместо совместного использования объекта модели контроллер и представление будут совместно использовать экземпляр модели представления. Только эта модель получит модель как зависимость. Реализация:

 $model = new UserModel; $viewModel = new UserViewModel($model, /* Other view-model dependencies */); $controller = new UserController($viewModel /* Other controller dependencies */); $view = new UserView($viewModel, /* Other view dependencies */); $controller->{action}(/* Action dependencies */); echo $view->output(); 

И рабочий процесс можно описать следующим образом:

  • Значения запроса отправляются браузером («пользователь») на контроллер.
  • Контроллер хранит их в экземпляре модели представления как свойства (элементы данных), поэтому изменяет логическое состояние отображения модели представления.
  • В рамках своего метода output представление считывает значения из модели представления и запрашивает модель для запроса хранилища на их основе.
  • Модель запускает соответствующий запрос и возвращает результаты обратно в представление.
  • Вид читает и передает их соответствующему шаблону.
  • После создания шаблона результаты отображаются на экране.

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

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

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