MVC (Model-View-Controller) в PHP

Пока я не стал баловаться программированием на Javа, особо не задумывался над использованием MVC в PHP. Обычно действуешь по своему опыту или как это принято в php-фреймворке (или CMS, не важно). Тем более почти все фреймворки декларируют свою преданность концепции MVC: дескать вот у нас всё сделано правильно, по теории.

После Java, где объектное программирование возведено почти в абсолют, MVC в PHP выглядит уже достаточно «бледно», пытаясь хоть как-то соответствовать этой концепции. Интересно ещё и то, что php-разработчики Model-View-Controller видят по разному и не всегда верно.

Базовая концепция Model-View-Controller

Основа MVC была заложена аж в 1978 году для Smalltalk. Она безумно простая и учитывала реалии того времени. Если чуть утрировать, то Контролёр — это устройство ввода — клавиатура и её обработчики (например драйвера). Как только в Контролёре что-то произошло, например пользователь нажал клавишу, это событие/данные передаются в Модель. Именно в Модели решается что именно нужно сделать с введенной информацией. Например, если нажата Enter, то происходит запуск введенной команды.

После того, как действие выполнено, необходимо как-то его отобразить. За это уже отвечает Вид (Представление) и в простом варианте это обычный монитор, принтер или файл.

Смысл был в том, чтобы выделить из общей кучи кода модули ввода/вывода, оставив основную функциональность программы в виде Модели.

Со временем MVC приняла более абстрактный вид, но общая концепция не изменилась.

  • Контролёр указывает модели что нужно сделать.
  • Модель выполняет работу.
  • Вид отображает/отдаёт результат.

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

Поэтому взаимодействие с пользователем здесь довольно просто реализуется через Контролёр, который понимает, что нажата какая-то кнопка и нужно выполнить определенное действие. Но с PHP всё сложней.

Работа web-приложения

Сайт может взаимодействовать с пользователем разными способами. Например нажатие кнопки, по которому появится всплывающий popup. То есть onClick кнопки должен отслеживаться Контролёром, хотя программно это HTML-код, который вроде как уже Представление.

Однако во всех сайтах есть одна общая черта — все они взаимодействуют через URL-адреса. Любая ссылка — это указание на действие. Например вывести главную страницу, или записи рубрики, или отправить форму по ajax-запросу (там тоже URL). То есть во всех случаях пользователь передаёт своё действие в виде ссылки (http-запроса).

Другой вариант — это действия, где происходит скрытая передача данных через POST (Ajax). Адрес страницы при этом не меняется, хотя обработчик уже должен как-то понимать, что если это post-запрос, то нужно сделать одно, а если get, то просто вывести типовую страницу.

Таким образом, в web-приложении Контролёр должен иметь функции роутинга. Реализация роутинга разнится, но во всех случаях представляет собой в первую очередь разбор URL на части, например с помощью parse_url() и определения метода передачи данных ($_POST и т.п.).

Роутинг в Контролёре определяет дальнейшее действие, которое сильно завязано на структуру приложения. Например можно вызвать функцию, подключить файл или выполнить метод php-класса. См. для примера PHP-роутинг (Routing) для новичков.

В любом случае Конролёру нужно точно знать что дальше делать и какой функциональный модуль будет выполнять эту работу. Также возможен вариант, что Контролёр только передает входящие данные в Модель, которая уже сама принимает решение какой модуль будет выполнять задачу. По сути это одно и тоже, разница лишь в том, где именно будет размещён это «action list».

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

Вид/Представление по своей сути — это результирующий html-код, который помимо всего прочего, верстается под необходимый дизайн. Скажем, если нужно вывести дату другим цветом, всё что потребуется сделать, так это поменять css-стили в шаблоне. При этом ни Контролёр, ни Модель не меняются.

Проблемы View

Здесь мы сталкиваемся с проблемой: как именно можно передать данные в html-код? Ведь HTML статичен — это не язык программирования, а всего лишь вид разметки документа.

Даже в самой простой реализации, html-шаблон нужно будет сделать в виде php-файла, где придётся смешивать html и php-код между собой. В простых случаях это не проблема, но когда данных для вывода много, то такая смесь уже представляется серьёзной проблемой (низкая читабельность и сложная поддержка).

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

Неверное понимание MVC

Очень часто встречаются искаженные толкования Model-View-Controller. Вопрос здесь скорее в практической реализации: PHP очень гибкий язык и позволяет использовать любой подход. Не думаю, что кто-то бросится переписывать свой проект под строгий MVC, но понимание этой концепции может пригодится при создании нового.

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

Схема работы MVC

Работа с БД — только часть работы Модели. Ни Контролёр, ни Вид не должны ничего знать ни про базу, ни про то, как получаются данные. Для них Модель — это черный ящик, куда можно передать входящие данные и получить готовые.

Другим искажением MVC будет считать Контролёр ответственным за всю логику и работу приложения. С одной стороны, он действительно отвечает за маршрутизацию, но в нём не должно быть логики выполнения. По хорошему, Контролёр даже не должен ничего знать и о Представлении. Когда Контролёру отводится другая роль, то это уже другая концепция: Model-View-ViewModel, Model-View-Presenter и т.п.

Модульность PHP

На практике принято разделять php-код на отдельные файлы. Именно такой подход позволяет избежать загромождения кода и разделить ответственность за каждое действие. Устоявшейся практикой являются не просто отдельные файлы, но и их группировка по отдельным каталогам.

Если проект основан на ООП, то обычно стоит задача организации многочисленных классов. Например в CodeIgniter 2/3 версий каждый класс представлял собой отдельный файл, но все файлы всех классов находятся в одном общем каталоге. Это накладывает некоторые ограничения, поскольку требует размещения всего кода относящегося к классу в один файл.

В CodeIgniter 4 уже используется модульный подход, где классы размещаются в отдельных каталогах и могут состоять из нескольких файлов.

MVC — лишь концепция, поэтому практическая реализация может быть разной. Обычно используют ООП, поскольку это решает проблему пространства имён в PHP, а модульность упрощает поддержку классов. Хотя использование классов и ООП не означает следованию MVC.

View не так прост

Ошибочно принято считать, что html-шаблон — это единственное, что отводится Виду. На самом деле это порождает проблему смешивания html и php-кода, о которой я написал выше.

В простом варианте это может выглядеть так:

model.php
 
$title = 'Титул';
$descr = 'Описание';
$link  = 'ссылка';
$link_url  = 'адрес';
 
view.php
 
<h1><?= $title ?></h1>
<h4><?= $descr ?></h4>
<div class="my-link"><a href="<?= $link_url ?>"><?= $link ?></a></div>

На помощь может прийти php-шаблонизатор, который позволит использовать более простой синтаксис, что улучшает читабельность:

view.php
 
<h1>{{ $title }}</h1>
<h4>{{ $descr }}</h4>
<div class="my-link"><a href="{{ $link_url }}">{{ $link }}</a></div>

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

Другой вариант решения — это переместить html-код в сам php-шаблон. То есть это уже будет php-код, который выводит html. Понятно, что здесь уже можно произвольно усложнять логику вывода данных.

view.php
 
echo '<h1>' . $title . '</h1>';
echo '<h4>' . $descr . '</h4>';
echo '<div class="my-link"><a href="' . $link_url .'">' . $link . '</a></div>';

Такой подход наиболее часто встречается в CMS-шаблонах. Проблема здесь в том, что вместо html-верстки мы вынуждены заниматься php-программированием.

Также View обычно разбивается на несколько частей/файлов. Например секция HEAD, после компоненты шапки, вывод основного контента, сайдбар и подвал. Здесь главное разделить блоки по функциональному признаку и так, чтобы между ними по возможности не было зависимостей.

Расскажу про ещё один вариант, который я придумал для своей MaxSite CMS. Используется специальный php-класс для форматированного вывода. Вначале в него загружаются данные записи, после этого задается формат вывода, где можно указать дополнительные параметры, а сам вывод осуществляется в виде метода, который делает замены «псевдокода» на основе реальных данных и заданного формата.

// формат
$p->format('title', '<h1 class="t-gray800">', '</h1>', false);
$p->format('cat', ', ', '<div class="t90">', '</div>');
$p->format('date', 'j F Y г.', '<time datetime="[page_date_publish_iso]">', '</time>');
 
// вывод
$p->html('<header class="mar20-tb">');
    $p->line('[title]');
    $p->line('[cat][date]');
$p->html('</header>');

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

Архитектура приложения

Можно ли реализовать строгую MVC в PHP? Если вопрос стоит принципиально, то PHP без проблем реализуется какой-то один из шаблонов проектирования, реализующий MVC: Наблюдатель, Стратегия и Компоновщик и т.д. Всё это взаимодействие между классами в рамках ООП, с которым у PHP нет проблем.

Но с моей точки зрения, более важным будет общая архитектура приложения и практическая реализация отдельных модулей. Например в том же CodeIgniter 2/3 конфигурация представляет собой обычный массив, а в 4-й версии — это уже PHP-класс, который работает с таким же массивом, но уже внутри себя. То есть это более строгое следование теории, но с практической точки зрения — бессмысленное раздувание кода. Или, скажем, во многих случаях удобней и проще воспользоваться обычной require_once() вместо создания сложного загрузчика php-классов.

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

Оставьте комментарий!

Комментарий будет опубликован после проверки. Вы соглашаетесь с правилами сайта.

(обязательно)