Как стать автором
Обновить

Комментарии 56

Автор пишет так как будто фреймворк заставляет его писать логику в констроллере. Для логики создают классы-сервисы, для базы данных — репозитории или менеджеры.
«Узкая трактовка MVC-модели только как как „Модель домена Базы Данных“ вместо „Любой компилятор данных для представления“.»
— так сделано, потому что для большинства задач этого достаточно. Если недостаточно, то при использовании Data mapper можно использовать все что угодно, хоть текстовый файл.
Не заставляет. Фреймворк просто не реализует Модель MVC полностью, а только ту часть, которая связана с первичными данными модели.
Правильно, как раз таки дает возможность расширить реализацию

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


Попробуйте написать консольную команду, которая выполняет ту же самую функцию, что и экшен в контроллере. Создание нового пользователя к примеру. Если не пришлось дублировать код из контроллера — всё нормально, контроллер выполняет только свою работу, и не пытается быть моделью. Если код пришлось дублировать — это именно тот код, который должен быть вынесен из контроллера в модель.
Краткий пересказ статьи (для себя): в начале symfony назвали mvc фреймворком, в конце переизобрели pipeline. Или я что то не понял?
Наверное так. Фреймворкам не хватает pipeline.
На любом фреймворке ваш код можно переписать так:
    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-модели только как как «Модель домена Базы Данных» вместо «Любой компилятор данных для представления».

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

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

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

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

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

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

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

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

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

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

Да ладно

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

Вам так господин Тейлор сказал? То, что в доке приведены самые простые примеры уже обсуждалось не раз. В доке городить сервисный слой оверхед, она решает другую задачу. А вот в проекте больше шести человеко-месяцев выделить бизнес логику в отдельный слой уже необходимость.
Вам в issue ответили, что фреймворк никак не ограничивает вас структурировать проект так, как вы хотите. И толстые контроллеры у вас получаются только потому, то вы так пишете код. И никаких согласий что «Laravel провоцирует писать толстые контроллеры» не заметил (можете тыкнуть в конкретный ответ если я пропустил).

Вы, кстати, сами обратили внимание на то, что вы сделали?
Вы взяли фреймворк, придумали новый паттерн и воплотили его в жизнь на этом же самом фреймворке! То есть он достаточно гибок, чтобы воплотить другой архитектурный паттерн. А теперь задумайтесь, не в этом ли благо? Каждый может реализовать логику так, как он хочет. Только теперь я вообще не понимаю ваших претензий к фреймворку.
Ответ был достаточно красноречив — У вас будет только «толстый» контроллер, если вы их так пишете. В самом деле, если прицепиться к конкретному примеру, то на ларавэл это делается вообще одной строчкой:
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() и все встанет на свои места. Никакого разброда и шатания.
Проблема принципиальная. У вас в контроллере сидит модель:
return view('info', ['name' => $user->getFullName()]);
Вам придется переписывать контроллер всякий раз, когда меняется представление, скажем, вместо полного имени захочется просто имя.

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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


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


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

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

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

Да ладно

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

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

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


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


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


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


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

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

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

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

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

— согласно MVC, контроллер определяет, какие модель и представления использовать и создает-вызывает их. Но если в контроллере есть код формирования или представления данных — то это смешение ответственности. Как я понял, у Вас смешения нет и все у Вас гуд.
У меня вызывается модель (например, 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). Совершенно очевидно, что грузить для таких целей модель нецелесообразно.
Совершенно верно. Все так. Включая и то, что модель или представление могут отсутствовать в каких-то крайних ситуациях. Или даже могут отсутствовать оба.
А можете проиллюстрировать как в вашем подходе будет выглядеть создание/редактирование?
Могу. Если кратко, то это ВСЕГДА цепочка действий, одна — для создания, другая — для редактирования. Каждая цепочка начинается с модели домена (БД), далее действия валидации, редиректы, преобразования данных для требований Представления (вьюха) и завершится цепочка собственно рендерингом.
Смысл вот в чем: базовая MVC цепочка Модель -> Представления просто делится на составные части. Типа, возьмите любой Толстый Контроллер и разбейте его на части.

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

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

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

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

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

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

UserModel ? UserViewModel : WrongUserViewModel > view, hello

Ну, или что-то подобное.
Спасибо за наводку. Глянул вскользь и не нашел условий — они там есть?

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


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


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

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

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


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


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

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

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


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

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

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

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

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

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


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


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


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

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

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

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