Комментарии 56
«Узкая трактовка MVC-модели только как как „Модель домена Базы Данных“ вместо „Любой компилятор данных для представления“.»— так сделано, потому что для большинства задач этого достаточно. Если недостаточно, то при использовании Data mapper можно использовать все что угодно, хоть текстовый файл.
Самый простой сценарий для проверки контроллера на излишний вес я нашел здесь:
Попробуйте написать консольную команду, которая выполняет ту же самую функцию, что и экшен в контроллере. Создание нового пользователя к примеру. Если не пришлось дублировать код из контроллера — всё нормально, контроллер выполняет только свою работу, и не пытается быть моделью. Если код пришлось дублировать — это именно тот код, который должен быть вынесен из контроллера в модель.
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.
Вам так господин Тейлор сказал? То, что в доке приведены самые простые примеры уже обсуждалось не раз. В доке городить сервисный слой оверхед, она решает другую задачу. А вот в проекте больше шести человеко-месяцев выделить бизнес логику в отдельный слой уже необходимость.
Вы, кстати, сами обратили внимание на то, что вы сделали?
Вы взяли фреймворк, придумали новый паттерн и воплотили его в жизнь на этом же самом фреймворке! То есть он достаточно гибок, чтобы воплотить другой архитектурный паттерн. А теперь задумайтесь, не в этом ли благо? Каждый может реализовать логику так, как он хочет. Только теперь я вообще не понимаю ваших претензий к фреймворку.
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
Никто не мешает. Зачем городить огород вокруг колхоза?
У вас в контроллере сидит модель:
return view('info', ['name' => $user->getFullName()]);
Так это как раз не проблема, модель и должна сидеть в контроллере, согласно MVC. Проблема в том, что вы из контроллера вид вызываете, в то время как оповещение вида должно происходить из модели (явно или в виде подписки), то есть в примере зависимости вывернуты шиворот-навыворот.
Не забывайте, что Model из MVC — это как раз ViewModel, а не DomainModel.
Это ж PHP а не JS там или что-то еще такое событийное. И как так у вас Model легким движением превратился во ViewModel?
Под капотом где?
Ну то есть опять же, 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 про общение системы с внешним миром, а не про функционирование системы и ее внутреннюю логику.
Есть, значится, у нас апишка для обновления каких-то данных. На вход принимает json, на выход — 204 если все хорошо (когда все плохо пока не рассматриваем). После того, как мы успешно что-то обновили, нам надо отправить всем подписавшимся по вэбсокетам обновление (много пользователей мониторят одни и те же данные, потому только так).
Как подобное вписывается в вашу «архитектуру»? Что будет где? И главный вопрос — вэбсокеты — как это все вписывается в ваши эти MVCS?
Дело в том, что модуль MVC скорее относится к ядру приложения
- Это не так.
- если у приложения есть "ядро" — с декомпозицией явно пошло чтто-то не так.
Да, в 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, контроллер определяет, какие модель и представления использовать и создает-вызывает их. Но если в контроллере есть код формирования или представления данных — то это смешение ответственности. Как я понял, у Вас смешения нет и все у Вас гуд.
Хотя некоторые простейшие действия у меня всё таки делаются без модели. Например, пометка сущности удалённой или снятие таковой пометки — это один запрос к БД. Совершенно незачем для этого грузить целую модель. Приведу пример такового запроса:
"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
Ну, или что-то подобное.
docs.zendframework.com/zend-expressive/v3/getting-started/quick-start/#piping
Условия противоречат принципу пайплайна.
Вы или обрабатываете запрос или просто передаёте управление дальше.
Если очень хочется ветвлений, то можно посмотреть в сторону Symfony workflow, но мне он не очень нравится
lurkmore.to/Изобретать_велосипед
Я думаю корень проблемы всего лишь в том, что такой подход демонстрируется в примерах всех документаций.
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
начните с простого — дайте определение "сервисному слою".
Например у меня контроллеры — это тоже сервисы, что усложняет трактовку этого термина (я нахожу его бесполезным ровно настолько же, насколько бесполезно говорить о MVC в модели request/response).
Если подразумевать некие прикладные сервисы (application layer) в которых реализация юзкейсов, то опять же они не всегда нужны (особенно если вам нравятся event driven подходы, не путать с хуками аля вордпресса).
Словом, попробуйте для себя хотя бы сформулировать определение недвусмысленное.
Да, но неопытнаые разработчики воспринимают это не как пример, а что так и надо делать. И более того, только с опытом приходит реальное понимание — зачем делать еще какие-то классы, когда можно просто и быстро всю логику в контроллере нафигачить
MVC + Scenario против Толстых Контроллеров