MVC + Scenario против Толстых Контроллеров

MVC + Scenario Против Толстых Контроллеров


Современные PHP фреймворки (Symphony, Laravel, далее везде) убедительно показывают, что реализовать паттерн Model-View-Controller не так уж просто. Все реализации почему-то склонны к Толстым Контроллерам (fat controllers), осуждаемыми всеми, и разработчиками, и самими фреймворками.


Почему все так? И можно ли с этим как-то справиться? Давайте разбираться.


Терминология


  • Model — модель (формирователь запрошенных данных)
  • View — представление (оформитель данных модели)
  • Controller — контроллер (координатор модель-представление согласно запросу)
  • Template — шаблон представления
  • Rendering — рендеринг (формирование, оформление образа представления)
  • Renderer — рендерер (формирователь, оформитель образа представления)

Толстый контроллер


Вот типичный Толстый Контроллер:


class UserController
{
    /**
     * Действие контроллера
     * Возвращает приветствие юзеру с заданным ID
     */
    public function actionUserHello($userId)
    {
        // Получаем имя и фамилию юзера из модели юзера (База Данных)
        $user = UserModel::find($userId);

        // Шаблону представления нужно полное имя юзера - делаем его
        $name = $user->firstName.' '.$user->lastName;

        // Создаем представление с нужным шаблоном и полным именем
        $view = new View('hello', ['name' => $name]);

        // Рендерим (создаем образ) представление и возвращаем приветствие
        return $view->render();
    }
}

Что мы видим? Мы видим винегрет! В контроллере намешано все, что можно — и модель, и представление, и, собственно, сам контроллер!


Мы видим имена модели и шаблона, намертво зашитые в контроллер. Это не гуд. Мы видим манипуляции с данными модели в контроллере — формирование полного имени из имени и фамилии. И это не гуд.


И еще: мы не видим этого примере явно, но неявно оно есть. А именно: есть только один способ рендеринга (формирования образа)! Только один: по шаблону в php файле! А если я хочу pdf? А если я хочу не в файле, а в php строке? У меня были проекты с вычурным дизайном на сотне маленьких шаблончиков. Приходилось самому ляпать рендерер для строковых шаблонов. Не перегревался конечно, но дело ведь в принципе.


Краткое резюме:


Современные фреймворки имеют общие для всех недостатки в реализации MVC:
  1. Узкая трактовка MVC-представления (View) только как "Представление с шаблоном в PHP файле" вместо "Представление с любым рендерером".
  2. Узкая трактовка MVC-модели только как как "Модель домена Базы Данных" вместо "Любой компилятор данных для представления".
  3. Провоцируют использование так называемых "Толстых Контроллеров" содержащих одновременно все логики: бизнеса, представления и взаимодействия. Это полностью разрушает основную цель MVC — разделение ответственностей между компонентами триады.

Для устранения этих недостатков неплохо бы взглянуть на компоненты MVC повнимательнее.


Представление — это рендерер


Глянем на первый недостаток:


  1. Узкая трактовка MVC-представления (View) только как "Представление с шаблоном в PHP файле" вместо "Представление с любым рендерером".

Здесь все довольно просто — решение проблемы уже указано в самой формулировке проблемы. Мы должны просто сказать, что представление может использовать любой рендерер. Для реализации этого достаточно просто добавить новое свойство renderer к классу View:


class View {
    public $template, $data, $renderer;

    public function __costruct($template, $data, $renderer = NULL) {}
}

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


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


Просто? Просто. На самом деле не так уж и просто. Дело в том, что тот View, который в MVC — это не совсем тот View, который в фреймворках. Тот View, который в фреймворках, не может жить без шаблона. А вот тот View, который в MVC, почему-то ничего не знает про эти самые шаблоны. Почему? Да потому, что для MVC View — это любой преобразователь данных модели в образ, а не только и исключительно шаблонизатор. Когда мы в обработчике запроса пишем что-нибудь типа:


$name = 'дядя Ваня';
return "Hello, {$name}!";

или даже:


$return json_encode($name); // Ajax response

то мы реально определяем тот View, который в MVC, не трогая при этом никаких тех View, которые в фреймворках!


А вот теперь все на самом деле просто: те View, которые в фреймворках — это подмножество тех View, которые в MVC. Причем очень узкое подмножество, а именно, это только шаблонизаторы на основе PHP файлов.


Резюме: именно рендерер, т.е. любой оформитель образа данных и является тем View, который в MVC. А те View, которые в фреймворках, это только разновидность рендереров.


Модель домена / Модель представления (ViewModel / DomainModel)


А теперь глянем на второй недостаток:


  1. Узкая трактовка MVC-модели только как как "Модель домена Базы Данных" вместо "Любой компилятор данных для представления".

Всем очевидно, что MVC-модель — это сложная штука, которая состоит из других штук. В сообществе есть согласие по декомпозиции модели на две компоненты: доменная модель (DomainModel) и модель представления (ViewModel).


Доменная модель — это то, что хранится в базах данных, т.е. нормализованные данные модели. Типа, 'имя' и 'фамилия' в разных полях. Фреймворки заняты именно этой частью модели просто потому, что хранение данных — это своя вселенная, неплохо изученная.


Однако приложению нужны агрегированные, а не нормализованные данные. Доменные данные надо компилировать в образы типа: 'Привет, Иван!', или 'Дорогой Иван Петров!', или даже 'Для Ивана Петрова!'. Вот эти преобразованные данные и относят к другой модели — модели представления. Так вот именно эта часть модели пока игнорируется современными фреймворками. Игнорируется потому, что нет согласия как с ней быть. А если фреймворки решения не дают, то программисты идут по самому простому пути — кидают модель представления в контроллер. И получают ненавистные, но неизбежные Толстые Контроллеры!


Итого: чтобы реализовать MVC необходимо реализовать модель представления. Других опций нет. Учитывая, что представления и их данные могут быть любыми, констатируем, что имеем проблему.


Сценарий против Толстых Контроллеров


Остался последний недостаток фреймворков:


  1. Провоцируют использование так называемых "Толстых Контроллеров" содержащих одновременно все логики: бизнеса, представления и взаимодействия. Это полностью разрушает основную цель MVC — разделение ответственностей между компонентами триады.

Здесь мы подобрались к основам MVC. Давайте наведем ясность. Итак, MVC предполагает такое распределение ответственностей между компонентами триады:


  • Контроллер — логика взаимодействия, т.е. взаимодействия с как с внешним миром (запрос — ответ), так и с внутренним (Модель — Представление),
  • Модель — бизнес-логика, т.е. формирование данных для конкретного запроса,
  • Представление — логика представления, т.е. декорация данных, сформированных Моделью.

Идем дальше. Ясно просматриваются два уровня ответственностей:


  • Организационный уровень — это Контроллер,
  • Исполнительный уровень — это Модель и Представление.

Говоря по-простому, Контроллер рулит, Модель и Представление пашут. Это если по-простому. А если не по-простому, а поконкретнее? Как именно рулит Контроллер? И как именно пашут Модель и Представление?


Контроллер рулит так:


  • Получает запрос от приложения,
  • Решает, какую Модель и какое Представление использовать для этого запроса,
  • Вызывает выбранную Модель и получает от нее данные,
  • Вызывает выбранное Представление с полученными от Модели данными,
  • Возвращает декорированные Представлением данные обратно приложению.

Вот как-то так. Существенным в этой схеме является то, что Модель и Представление оказываются звеньями цепи исполнения запроса. Причем последовательными звеньями: сначала Модель преобразует запрос в некоторые данные, затем эти данные Модели преобразуются Представлением в ответ, декорированный так, как надо конкретному запросу. Типа, запрос гуманоида декорируется визуально шаблонизаторами, запрос андроида декорируется кодировщиками JSON.


Теперь попробуем разобраться, как конкретно пашут исполнители — Модель и Представление. Выше мы говорили, что есть консенсус насчет декомпозиции Модели на две под-компоненты: Модель домена и Модель представления. Это значит, что исполнителей может быть больше — не два, а три. Вместо цепочки исполнения


Модель >> Представление

вполне может быть цепочка


Модель домена >> Модель представления >> Представление

Сам собой напрашивается вопрос: а почему только два или три? А если надо больше? Естественный ответ — да ради бога, сколько надо, столько и берите!


Сходу просматриваются иные полезные исполнители: валидаторы, редиректоры, разнообразные рендереры и вообще все, что непредсказуемо, но угодно.


Давайте резюмируем:


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

Я назвал эту цепочку Сценарием (Scenario), а для звеньев цепочки с названием пока не определился. Текущие варианты — сцена (как часть сценария), фильтр (как преобразователь данных), действие сценария. Вообще говоря, название для звена не столь важно, есть более существенная вещь.


Существенным является последствия появления Сценария. А именно: Сценарий взял на себя основную ответственность Контроллера — определять нужные для запроса Модель и Представление и запускать их. Тем самым у контроллера остаются только две ответственности: взаимодействие с внешним миром (запрос-ответ) и запуск сценария. И это хорошо в том плане, что все компоненты триады MVC последовательно декомпозируются и становятся более конкретными и управляемыми. И еще хорошо в другом плане — контроллер MVCS становится чисто внутренним неизменяемым классом, и поэтому даже в принципе не может стать толстым.


Использование Сценариев приводит к очередной вариации паттерна MVC, эту вариацию я обозвал как MVCSModel-View-Controller-Scenario.


И еще пару строк насчет декомпозиции MVC. Современные фреймворки, где все типовые функции декомпозированы до предела, вполне себе естественным образом забрали у концептуальной MVC часть ответственностей по взаимодействию с внешним миром. Так, обработкой запроса пользователя занимаются специально обученные классы типа HTTP запрос и Роутер. В результате Контроллер получает не исходный запрос пользователя, а некоторое рафинированное действие, и это позволяет изолировать контроллер от специфики запроса. Аналогичным образом делается изоляция от специфики HTTP ответа, позволяя модулю MVC определять свой собственный тип ответа. Кроме того, фреймворки полностью реализовали две компоненты MVC — Модель домена и Шаблонизатор представления, впрочем, это мы уже обсуждали. Я все это к тому, что уточнение и конкретизация MVC идет постоянно и непрерывно, и это гуд.


Пример использования MVCS


А теперь давайте посмотрим, как пример "Толстого Кортроллера" в начале этой статьи может быть реализован в MVCS.


Начинаем с создания контроллера MVCS:


$mvcs = new MvcsController();

Контроллер MVCS получает запрос от внешнего роутера. Пусть роутер преобразует URI вида 'user/hello/XXX' в такие действие и параметры запроса:


$requestAction = 'user/hello';  // Действие запроса
$requestParams = ['XXX'];   // Параметры действия - ИД юзера

Учитывая, что контроллер MVCS принимает не URI, а сценарии, нам надо сопоставить действию запроса некоторый сценарий. Лучше всего это делать в контейнере MVCS:


// Определяем сценарий MVCS для URI запроса
$mvcs->set('scenarios', [
    'user/hello' => 'UserModel > UserViewModel > view, hello',
    ...,
]);

Давайте глянем на этот сценарий повнимательнее. Это цепь из трех преобразователей данных, разделенных знаком '>':


  • 'UserModel' — это имя Модели домена 'User', входом модели будут параметры запроса, выходом — собственно данные модели,
  • 'UserViewModel' — это имя Модели Представления, которая преобразует доменные данные в данные представления,
  • 'view, hello' — это системный шаблонизатор 'view' для PHP шаблона с именем 'hello'.

Теперь нам осталось только добавить в контейнер MVCS два задействованных в сценарии преобразователя как функции-замыкания:


// Модель домена UserModel
$mvcs->set('UserModel', function($id) {
    $users = [
        1 => ['first' => 'Иван', 'last' => 'Петров'],
        2 => ['first' => 'Петр', 'last' => 'Иванов'],
    ];
    return isset($users[$id]) ? $users[$id] : NULL;
});
// Модель представления UserViewModel
$mvcs->set('UserViewModel', function($user) {
    // Слепить данные для PHP шаблона типа: 'echo "Hello, $name!"';
    return ['name' => $user['first'].' '.$user['last']];
});

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


А теперь мы готовы протестировать MVCS для разных запросов:


// Получить из контейнера сценарий для текущего запроса
$scenarios = $mvcs->get('scenarios');
$scenario = $scenarios[$requestAction];

// Исполнить сценарий с параметрами текущего запроса...

// Для запроса 'user/hello/1' получим 'Иван Петров' декорированный шаблоном 'hello'
$requestParams = ['1'];
$response = $mvcs->play($scenario, $requestParams);

// Для запроса 'user/hello/2' получим 'Петр Иванов' декорированный шаблоном 'hello'
$requestParams = ['2'];
$response = $mvcs->play($scenario, $requestParams);

PHP реализация MVCS размещена на github.com.
Этот пример находится в директории example MVCS.
Поделиться публикацией
Комментарии 56
    +2
    Автор пишет так как будто фреймворк заставляет его писать логику в констроллере. Для логики создают классы-сервисы, для базы данных — репозитории или менеджеры.
    «Узкая трактовка MVC-модели только как как „Модель домена Базы Данных“ вместо „Любой компилятор данных для представления“.»
    — так сделано, потому что для большинства задач этого достаточно. Если недостаточно, то при использовании Data mapper можно использовать все что угодно, хоть текстовый файл.
      –1
      Не заставляет. Фреймворк просто не реализует Модель MVC полностью, а только ту часть, которая связана с первичными данными модели.
        +1
        Правильно, как раз таки дает возможность расширить реализацию
      +3

      Самый простой сценарий для проверки контроллера на излишний вес я нашел здесь:


      Попробуйте написать консольную команду, которая выполняет ту же самую функцию, что и экшен в контроллере. Создание нового пользователя к примеру. Если не пришлось дублировать код из контроллера — всё нормально, контроллер выполняет только свою работу, и не пытается быть моделью. Если код пришлось дублировать — это именно тот код, который должен быть вынесен из контроллера в модель.
        0
        Краткий пересказ статьи (для себя): в начале symfony назвали mvc фреймворком, в конце переизобрели pipeline. Или я что то не понял?
          0
          Наверное так. Фреймворкам не хватает pipeline.
          +2
          На любом фреймворке ваш код можно переписать так:
              public function actionUserHello($userId)
              {
                  $user = $this->userRepository->get($userId);
          
                  return $this->renderView('hello', ['name' => $user->getFullName()]);
              }
          
          


          И решение озвученных вами проблем:
          Современные фреймворки имеют общие для всех недостатки в реализации MVC:
          1. Узкая трактовка MVC-представления (View) только как «Представление с шаблоном в PHP файле» вместо «Представление с любым рендерером».

          Как раз наоборот, сейчас все более распространяется json формат, и современные фреймворки дают инструменты вернуть ответ в любом формате (json, xml, csv и даже pdf!).
          Узкая трактовка MVC-модели только как как «Модель домена Базы Данных» вместо «Любой компилятор данных для представления».

          Как слова «модель домена» и «база данных» вообще могут стоять рядом? То, что некоторые люди олицетворяют модель с табличкой в БД это их проблемы.

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

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

            Такие слова мне писали из команды Laravel. В статье я старался подчеркнуть, что Толстые Контроллеры возникают из-за того, что фреймворки реализуют только часть Модели MVC, а именно — только модель домена.
            В остальном согласен с Вами.

              –2
              Как раз наоборот, сейчас все более распространяется json формат, и современные фреймворки дают инструменты вернуть ответ в любом формате (json, xml, csv и даже pdf!).

              Согласен, есть такие. Но монстры типа Symphony & Laravel не дают.

              Как слова «модель домена» и «база данных» вообще могут стоять рядом?

              Типа согласен, но по жизни они рядом.

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

              Они провоцируют. С этим глаголом согласились разработчики Laravel. Где-то выше в комментах про это уже отвечал.
                +1
                Согласен, есть такие. Но монстры типа Symphony & Laravel не дают.

                Да ладно

                Они провоцируют. С этим глаголом согласились разработчики Laravel.

                Вам так господин Тейлор сказал? То, что в доке приведены самые простые примеры уже обсуждалось не раз. В доке городить сервисный слой оверхед, она решает другую задачу. А вот в проекте больше шести человеко-месяцев выделить бизнес логику в отдельный слой уже необходимость.
                  –1
                  Саппорт сказал: github.com/laravel/framework/issues/18786.
                    +3
                    Вам в issue ответили, что фреймворк никак не ограничивает вас структурировать проект так, как вы хотите. И толстые контроллеры у вас получаются только потому, то вы так пишете код. И никаких согласий что «Laravel провоцирует писать толстые контроллеры» не заметил (можете тыкнуть в конкретный ответ если я пропустил).

                    Вы, кстати, сами обратили внимание на то, что вы сделали?
                    Вы взяли фреймворк, придумали новый паттерн и воплотили его в жизнь на этом же самом фреймворке! То есть он достаточно гибок, чтобы воплотить другой архитектурный паттерн. А теперь задумайтесь, не в этом ли благо? Каждый может реализовать логику так, как он хочет. Только теперь я вообще не понимаю ваших претензий к фреймворку.
                      0
                      Ответ был достаточно красноречив — У вас будет только «толстый» контроллер, если вы их так пишете. В самом деле, если прицепиться к конкретному примеру, то на ларавэл это делается вообще одной строчкой:
                      class UserController extends Controller
                      {
                          public function info(User $user)
                          {
                              return view('info', ['name' => $user->getFullName()]);
                          }
                      }
                      

                      в контроллере и:
                      class User extends Model 
                      {
                          public function getFullName()
                          {
                              return $this->first_name . ' ' . $this->last_name;
                          }
                      }
                      

                      в самой модели. Зачем писать развесисто, если можно не писать развесисто.

                      Если говорить о единообразии — напишите response()->view() вместо view() и все встанет на свои места. Никакого разброда и шатания.
                        0
                        Проблема принципиальная. У вас в контроллере сидит модель:
                        return view('info', ['name' => $user->getFullName()]);
                        Вам придется переписывать контроллер всякий раз, когда меняется представление, скажем, вместо полного имени захочется просто имя.

                        Лучше полная независимость контроллера от моделей и представлений, согласитесь. Контроллер тогда просто вызывает нужные для запроса модели и представления (это делается в роутере). И контроллера как-бы вообще нет. Он есть, конечно. Но вся специфика приложения строго в модели.
                          0
                          Окей, можно сделать прям в роутере:
                          function(User $user) {
                              return view('info', [$user => 'user'];
                          }
                          

                          а в модель добавить:
                              public function getFullNameAttribute()
                              {
                                  return $this->first_name . ' ' . $this->last_name;
                              }
                          

                          чтобы во вьюхе вызвать $user->full_name

                          Никто не мешает. Зачем городить огород вокруг колхоза?
                            0
                            Обратите внимание: моя основная цель — соблюдение принципа разделения ответственностей, ради которого и был создан паттерн MVC.

                            Во всех ваших примерах этот принцип нарушен.
                            0
                            У вас в контроллере сидит модель:
                            return view('info', ['name' => $user->getFullName()]);

                            Так это как раз не проблема, модель и должна сидеть в контроллере, согласно MVC. Проблема в том, что вы из контроллера вид вызываете, в то время как оповещение вида должно происходить из модели (явно или в виде подписки), то есть в примере зависимости вывернуты шиворот-навыворот.
                            Не забывайте, что Model из MVC — это как раз ViewModel, а не DomainModel.

                              0
                              Но там же под капотом return response()->view()…
                              Это ж PHP а не JS там или что-то еще такое событийное. И как так у вас Model легким движением превратился во ViewModel?
                                0

                                Под капотом где?

                                  0
                                  ViewModel тут в контексте «модель данных для вьюхи» а не ViewModel из MVVM.

                                  Ну то есть опять же, MVC это исключительно про UI. Про то как формируется представление данных пользователя, конвертация ментальной модели. Все остальное приложение выходит за рамки MVC, чего многие не понимают. Для многих «модель это штука которая ходит в базу», из чего следует что логику нужно ложить в контроллеры. И в итоге вся идея с разделением ответственности как бы умирает.

                                  что до ивентов, в оригинальной MVC 1978-ого года выпуска «моделька» это была не тупая структура данных, и вьюшки подписывались на события, и поток данных шел строго по кругу, а не как сейчас, когда контроллер действует больше как презентер из MVP (хотя мне больше нравится сравнение с mediating controller MVC, или MVA, так как концепция мидлварь туда вписывается больше).

                                  Короче мое мнение — MVC это такая штука, которую не стоит даже объяснять бэкэндщикам. Вопросы разделение ответственности можно подругому объяснять. Не вводя абстрактных терминов типа «модель». Ну или коль уж вводить надо объяснять что модели это модели, это не слои и не уровни приложения. И что все элементы MVC ложатся в один слой. Presentatio layer.
                                    0
                                    Ну то есть опять же, MVC это исключительно про UI. Про то как формируется представление данных пользователя, конвертация ментальной модели. Все остальное приложение выходит за рамки MVC, чего многие не понимают.

                                    Да, вот это надо где-то большими красными буквами написать.

                                      0
                                      Народ!!! Прошу внимания:::
                                      Мы здесь обсуждаем не сервисные слои приложения (pipes, middleware), а MVC. Если вы под «сервисным слоем» понимаете Сценарий (цепочку преобразователей запроса в ответ), то это некорректно. Дело в том, что модуль MVC скорее относится к ядру приложения (выше кто-то отнес к UI, я почти согласен), а не к сервисам. Да, в MVC есть цепочка преобразования (как минимум Модель-Представление), да, она может напоминать пайплайн или оболочки сервисов. Но это только напоминание.
                                        +1

                                        Мы не о сервисах, мы о том, что MVC — создана изначально для того, чтобы описывать интерфейсы пользователя. Слоя бд, запросов и всего такого там нет вовсе — они выходят за пределы MVC, они где-то сбоку. MVC описывает внутреннюю логику самого интерфейса — реакцию интерфейса на действия пользователя.
                                        И только впоследствии уже понятие MVC расширили то общего паттерна, который описывает абстрактную систему, поддерживающую ввод/вывод.
                                        Но, опять же, MVC описывает саму логику ввода/вывода в данном случае, то, как ваша система взаимодействует с внешним миром. То, что внутри системы — оно внутри, и MVC не об этом. MVC про самый внешний слой. MVC про общение системы с внешним миром, а не про функционирование системы и ее внутреннюю логику.

                                          –1
                                          Понятно. Тогда уточняю, что эта статья — не про изначальную MVC, а про MVC для архитектуры клиент-сервер с одним запросом.
                                            0
                                            Вот давайте такой вот пример.

                                            Есть, значится, у нас апишка для обновления каких-то данных. На вход принимает json, на выход — 204 если все хорошо (когда все плохо пока не рассматриваем). После того, как мы успешно что-то обновили, нам надо отправить всем подписавшимся по вэбсокетам обновление (много пользователей мониторят одни и те же данные, потому только так).

                                            Как подобное вписывается в вашу «архитектуру»? Что будет где? И главный вопрос — вэбсокеты — как это все вписывается в ваши эти MVCS?
                                          0
                                          Дело в том, что модуль MVC скорее относится к ядру приложения

                                          1. Это не так.
                                          2. если у приложения есть "ядро" — с декомпозицией явно пошло чтто-то не так.

                                          Да, в MVC есть цепочка преобразования

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


                                          И да, судя по тому что я вижу вы эти три буквы воспринимаете как три слоя — хотя это не так. все эти три буквы ложатся в один слой.


                                          Ну и опять же — всегда можно обратиться к первоисточнику. Возможно вам будет интересно.

                              0
                              И никаких согласий что «Laravel провоцирует писать толстые контроллеры» не заметил (можете тыкнуть в конкретный ответ если я пропустил).

                              Тыкаю: в конце обсуждения пост от ConnorVG commented on Apr 12 2017.
                            0
                            Fantyk 27.09.18 в 20:05
                            Согласен, есть такие. Но монстры типа Symphony & Laravel не дают.

                            Да ладно

                            Уточнить можете? Или вы про то, что в фреймворке можете сами все сделать. Если про это, то ответ один: PHP — лучший фреймворк.
                              0
                              Вам так господин Тейлор сказал? То, что в доке приведены самые простые примеры уже обсуждалось не раз. В доке городить сервисный слой оверхед, она решает другую задачу. А вот в проекте больше шести человеко-месяцев выделить бизнес логику в отдельный слой уже необходимость.

                              Это тоже обсуждалось: github.com/laravel/framework/issues/18786
                                0

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


                                Идея в том что для с точки зрения изучения вещей важно получить первый работающий результат как можно скорее. Потому почти всегда в документации упрощенные примеры. Документация фреймворка не то место где людей надо декомпозиции и разделению ответственности учить.


                                Еще есть нюанс — если мы говорим о каком-то дальнейшем разделении — уже есть масса вариантов (помимо ваших "сценариев", которые у кого-то называются интеракторами (дядя Боб) а у кого-то обработчиками команд (и CQRS тут не причем еще).


                                Вариантов разделения отвесвенности внутри приложения очень много. И что самое главное — фреймворк редко на это все влияет. Вы привыкли делать интеракторы (под стиль clean architecture) — делайте, фреймворк вас не должен в этом ограничивать. Вы привыкли больше к event driven подходам и eventual consistency — да не вопрос. Вам нужно бложик запилить — тут можно и в контроллере все сгрузить. А еще есть мидлвары, и если мы наши контроллеры от http абстрагируем необходимости в отдельном сервисном слое тоже может не быть.


                                Потому еще раз — документация к фреймворку не должна нести в себе информации о том как делать что-то что к этому фреймворку не привязано.

                          0
                          И чем это проще «толстого контроллера»?..

                          Поделюсь вкратце своим архитектурным опытом:
                          — Запрос принимается небольшим объектом класса Request, который определяет, какой контроллер грузить, и какое действие в нём вызвать;
                          — Внутри контроллера может создаваться модель, может не вызываться. В конечном итоге совершается некое действие, результатом которого станет ассоциативный массив — Response;
                          — Всё! Получившийся массив можно скормить любой вменяемой системе шаблонизации.

                          По маршруту определяем, какой шаблон использовать. Нужна выборка юзеров? Пусть маршрут будет таким — users/index. Значит у берём шаблон из файла, скажем, templates/users/index. Контроллер у нас будет называться controllerUsers, метод — actionIndex…

                          Логика прямолинейная. Что же касается дополнения данных, то система хуков при описанной мною выше архитектуре — задача тривиальнейшая. Тупо пропускаем итоговый массив через цепочку Data-хуков, которые при необходимости модифицируют этот самый массив.
                            0
                            Внутри контроллера может создаваться модель, может не вызываться
                            ,
                            По маршруту определяем, какой шаблон использовать.

                            — согласно MVC, контроллер определяет, какие модель и представления использовать и создает-вызывает их. Но если в контроллере есть код формирования или представления данных — то это смешение ответственности. Как я понял, у Вас смешения нет и все у Вас гуд.
                              0
                              У меня вызывается модель (например, users), которая делает нужные запросы к источнику данных, формирует данные, которые затем пропускаются через pipeline (надеюсь, я правильно понял общераспространённое название паттерна) обработчиков хука вызванного действия (например, usersIndex), которые могут чем-то дополнить (например, присоединить дополнительные данные пользователей из других модулей) или преобразовать (например, скорректировать ACL-таблицы) получившиеся данные. Затем итоговый ассоциативный массив тупо скармливается используемому шаблонизатору, который в свою очередь сам уже определит как эти данные выводить согласно выбранному для конкретного запроса к системе шаблону. Или же превращается в JSON одной строчкой и просто отдаётся в браузер.

                              Хотя некоторые простейшие действия у меня всё таки делаются без модели. Например, пометка сущности удалённой или снятие таковой пометки — это один запрос к БД. Совершенно незачем для этого грузить целую модель. Приведу пример такового запроса:

                              "update `[+prefix+]fields` set `flags` = `flags` | {$f} where (`id` in ({$ids})) and (`table` = 'users')"

                              Где $f — целочисленное значение флага, $ids — массив ID полей из запроса (тупо пробегаемся по нужному полю запроса array_walk с функцией intval, потом объединяем функцией implode). Совершенно очевидно, что грузить для таких целей модель нецелесообразно.
                                0
                                Совершенно верно. Все так. Включая и то, что модель или представление могут отсутствовать в каких-то крайних ситуациях. Или даже могут отсутствовать оба.
                            0
                            А можете проиллюстрировать как в вашем подходе будет выглядеть создание/редактирование?
                              0
                              Могу. Если кратко, то это ВСЕГДА цепочка действий, одна — для создания, другая — для редактирования. Каждая цепочка начинается с модели домена (БД), далее действия валидации, редиректы, преобразования данных для требований Представления (вьюха) и завершится цепочка собственно рендерингом.
                              Смысл вот в чем: базовая MVC цепочка Модель -> Представления просто делится на составные части. Типа, возьмите любой Толстый Контроллер и разбейте его на части.

                              Главное — вытащить все это из контроллера и облегчить жизнь путем декомпозиции и разделения ответственностей.
                                0

                                Хотелось бы увидеть код. Основной вопрос: как быть с условиями? К примеру, если валидация прошла успешно, то создаем пользователя и редиректим на страницу просмотра, если нет — то рендерим форму с ошибками. В ваших примерах все линейно:
                                'UserModel > UserViewModel > view, hello'

                                  0
                                  Код есть в примере, ссылка в конце статьи.

                                  Насчет условий — вы прям в точку попали. Что делать с ветвлениями, циклами, — мне пока неясно.

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

                                  Буду признателен любым предложениям как можно условия вписать в цепь Модель-Представление.
                                    0
                                    Синтаксис тернарного оператора:

                                    UserModel ? UserViewModel : WrongUserViewModel > view, hello

                                    Ну, или что-то подобное.
                                      +1
                                      Попробуйте не изобретать велосипед
                                      docs.zendframework.com/zend-expressive/v3/getting-started/quick-start/#piping
                                        0
                                        Спасибо за наводку. Глянул вскользь и не нашел условий — они там есть?
                                          0

                                          Условия противоречат принципу пайплайна.


                                          Вы или обрабатываете запрос или просто передаёте управление дальше.


                                          Если очень хочется ветвлений, то можно посмотреть в сторону Symfony workflow, но мне он не очень нравится

                                            0
                                            Мы здесь обсуждаем не сервисные слои приложения (pipes, middleware), а MVC. Если вы под «сервисным слоем» понимаете Сценарий (цепочку преобразователей запроса в ответ), то это некорректно. Дело в том, что модуль MVC скорее относится к ядру приложения, а не к сервисам. Да, в MVC есть цепочка преобразования (как минимум Модель-Представление), да, она может напоминать пайплайн или оболочки сервисов. Но это только напоминание.
                                              0

                                              Это не только напоминание.


                                              Никто вас не ограничивает использовать пайплайны в рамках внутренней механики вашего MVC.


                                              Это будет несколько проще чем пытаться внедрить сценарии. Тем более с условиями и ветвлениями

                                  0
                                  А чем ваш scenarios отличается от pipeline и middleware?
                                    0
                                    тем что о pipeline и middleware нужно прочитать. а scenarios можно придумать самому.
                                    lurkmore.to/Изобретать_велосипед
                                      0
                                      Целью было не изобретать новый паттерн, а подкрутить MVC под имеющуюся проблему. Насчет наличия проблемы согласие есть, насчет ее разрешения согласия нет. Мое предложение простое: Model-View — это цепочка преобразователей, которая может быть расщеплена, не нарушая самой концепции MVC.
                                    0

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


                                    class UserController
                                    {
                                        /**
                                         * Действие контроллера
                                         * Возвращает приветствие юзеру с заданным ID
                                         */
                                        public function actionUserHello($userId)
                                        {
                                            // Получаем имя и фамилию юзера из модели юзера (База Данных)
                                            $user = UserModel::find($userId);
                                    
                                            // Шаблону представления нужно полное имя юзера - делаем его
                                            $name = $user->firstName.' '.$user->lastName;
                                    
                                            // Создаем представление с нужным шаблоном и полным именем
                                            $view = new View('hello', ['name' => $name]);
                                    
                                            // Рендерим (создаем образ) представление и возвращаем приветствие
                                            return $view->render();
                                        }
                                    }

                                    А примеров с использованием сервисного слоя — минимум. И хотя для некоторого количества кейсов упомянутого варианта достаточно, такой вариант реализации продолжают использовать и в более сложных кейсах.
                                    А что касается рендереров и миддлварей, то в большинстве как раз без проблем можно сделать вывод хоть в php, хоть в шаблонизатор, хоть в json

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

                                        Чтобы не быть голословным:

                                        Сейчас просто нет еще согласия насчет есть ли этот сервисный слой, если есть — то каков он и т.д.

                                        Смотреть раздел Domain Logic Patterns
                                          0
                                          Мы здесь обсуждаем не сервисные слои приложения (pipes, middleware), а MVC. Если вы под «сервисным слоем» понимаете Сценарий (цепочку преобразователей запроса в ответ), то это некорректно. Дело в том, что модуль MVC скорее относится к ядру приложения, а не к сервисам. Да, в MVC есть цепочка преобразования (как минимум Модель-Представление), да, она может напоминать пайплайн или оболочки сервисов. Но это только напоминание.
                                          0

                                          начните с простого — дайте определение "сервисному слою".


                                          Например у меня контроллеры — это тоже сервисы, что усложняет трактовку этого термина (я нахожу его бесполезным ровно настолько же, насколько бесполезно говорить о MVC в модели request/response).


                                          Если подразумевать некие прикладные сервисы (application layer) в которых реализация юзкейсов, то опять же они не всегда нужны (особенно если вам нравятся event driven подходы, не путать с хуками аля вордпресса).


                                          Словом, попробуйте для себя хотя бы сформулировать определение недвусмысленное.

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

                                              Да, но неопытнаые разработчики воспринимают это не как пример, а что так и надо делать. И более того, только с опытом приходит реальное понимание — зачем делать еще какие-то классы, когда можно просто и быстро всю логику в контроллере нафигачить

                                                0
                                                Неопытные разработчики могут не понять и mvc. Можно же в каждом файле писать if($_GET['action']) { //что то там, например SQL запрос}

                                          Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

                                          Самое читаемое