Проблема View в MVC
08-10-2019Reading time ~ 4 min.PHP 5459
Я уже рассказывал, что в свободное время занимаюсь своим микрофреймворком и хочу поделиться некоторыми мыслями по этому поводу. Речь пойдёт о представлениях (view) в концепции MVC, точнее о том, что они на самом деле представляют.
Это скорее мысли вслух, а не готовые решения. Я даже буду рассуждать больше абстрактно, чтобы можно было примерить идеи под любой php-фреймворк.
Пусть у нас есть некий фреймворк, который основан на роутинге и MVC. То есть роутер передает управление контролёру, тот уже дёргает Model и отдает результат в View. Вьюшка в свою очередь рендерит данные в html-код и отдает в браузер.
Это совершенно типовая схема и за мелкими различиями используется повсеместно.
Ок. Рассмотрим представление. Пусть нужно сделать вывод контактной формы по какому-то адресу, вроде «contact». Для простоты пусть это будет только отображение формы.
Делаем такой модуль и во View размещаем html-код формы.
<form ...> ... </form>
Всё прекрасно работает, но мы понимаем, что этот код недостаточен для полной html-страницы: нужна ещё секция HEAD, в ней стили, скрипты и вообще прочее оформление. Также нужен какой-то закрывающий html-код.
Выносим их в файлы start.php
и end.php
(имена файлов непринципиальны) и подключаем их в View. (Я утрирую код.)
require 'start.php'; <form ...> ... </form> require 'end.php';
Всё работает, все довольны.
Теперь, например нам нужно сделать ещё один модуль, скажем форму поиска «search». Делаем аналогично и подключаем те же самые start.php
и end.php
, поскольку работа происходит в рамках одного сайта.
Этот подход также используется повсеместно: в нём View выполняет роль шаблона сайта. Принимать решение, что будет подключаться до и после форм, происходит на уровне вьюшки.
Теперь усложним задачу. Пусть на странице «contact» после основной формы нужно ещё вывести форму «search», а на «search» форму «contact». То есть у нас уже есть полностью готовые модули и, очевидно, что мы должны их использовать. В контролёре «contact» пишем примерно так:
$this->view->render($dataModel); $this->search->render($dataModel);
А в контролёре «search»:
$this->view->render($dataModel); $this->contact->render($dataModel);
Тут получается казус в том, что обе формы выводятся в полном html-формате, поскольку сами подключают start.php
и end.php
. В итоге на странице окажется двойное включение этих файлов. Если мы еще больше усложним задачу, например если нужно её вывести из 10 модулей, то получится жуткое месиво...
Понятно, что для решения таких задач мы могли бы придумать разные условия или хитрости, вроде отслеживания уже подключенных файлов, но корень проблемы здесь в том, что View отдаёт больше, чем должен.
Это подводит нас к мысли, что View должна отдавать только тот html-код, который имеет непосредственное отношение к работе модуля. Ни больше, ни меньше. Если мы исключим из представления файлы start.php
и end.php
, то всё прекрасным образом начинает работать.
Нужно понимать, что в концепции MVC — View это не шаблон всего сайта, а только представление для конкретного модуля/контролёра/модели.
Осталось разобраться с одним моментом — кто же тогда должен отвечать за подключение start.php
и end.php
? Очевидным решением будет введение ещё одного уровня — Template, то есть тот, который и формирует готовый html-код и который сам знает где подключать start.php
и end.php
.
Уровень Template — это самый высокий уровень приложения и он, по идее, должен быть только один для всего сайта (ну или переключаться). Вот так это может выглядеть:
$templateData['contact']['html'] = $this->view->render($dataModel); $templateData['search']['html'] = $this->contact->render($dataModel); Core::template($templateData);
Понятно, что для простых приложений, где каждая страница формируется одним модулем (Контролёром/Моделью/Представлением) дополнительный уровень шаблона просто не нужен. Но, когда стоит задача сделать что-то более сложное, то перенос непосредственного вывода на отдельный уровень, сильно упрощает работу. Получается эдакий паттерн MVC-T
К этому результату я пришёл в своём фреймворке, поскольку хочу его сделать модульным. Но не сугубо декларативным, как у других, а реальным. Например есть модуль Auth, который реализует всю логику регистрации, авторизации, восстановления пароля, разлогиневания. Он имеет файл конфигурации, где указывается только простые данные, вроде алиаса БД. И вот, чтобы его использовать в своём приложении, не нужно будет его переписывать — достаточно только скопировать в своё приложение и поправить только конфиг. Понятно, что если его вьюшка будет отдавать только свою часть работы, то эту форму можно встраивать в шаблон сайта произвольно. А ведь сайт состоит из множества блоков и будет лучше, если каждый из их модулей будет отдавать только то, что действительно ему принадлежит.
Ну и понятно, что сам по себе шаблон (Template) может строиться произвольно под свою задачу.
Если сравнивать этот подход с «большими» языками, например Delphi, то кнопка (модуль) строится по схеме MVC (сама определяет своё поведение, реакцию на события и визуальный вид). Но кнопка является частью всего приложения, которое отображается как форма (Form). Вот для сайта, форма — это и есть Template.
Проблема start/end отлично решается layout-ом: https://rmcreative.ru/blog/post/mvc-view
Не решается. Это всё также вьюшка, которая подключает все файлы шаблона/layout. А должна возвращать только свой html-код.