Принцип Модель-Представление-Контроллер (Model-View-Controller, MVC) был сформулирован еще в конце 70-х годов XX века. Это архитектура построения программного обеспечения, при которой данные независимы от методов, которые с ними работают.
Теоретически, грамотно построенная MVC-система должна позволять фронтэнд — и бэкэнд-разработчикам работать над одним проектом без необходимости работать над одними и теми же файлами одновременно.
Несмотря на то, что изначально MVC был разработан для локальных приложений, позже он был адаптирован и широко распространился среди веб-разработчиков из-за акцента на разделение функций, и возможностей повторного использования кода.
MVC поощряет разработку модульных систем, позволяя быстро модифицировать проект, добавлять и удалять функционал.
В этой статье будут рассмотрены базовые принципы подхода MVC, дано определение этому понятию и разобраны небольшие примеры на PHP.
Целевая аудитория — все те, кто раньше никогда не создавал MVC-программы, а также те, кто хочет укрепить свои знания по этой теме.
Понятие MVC
В название принципа входит три основополагающих компонента: модель (Model), представление (View) и контроллер (Controller).
Визуальное представление завершенного и корректного MVC-приложения выглядит так, как показано ниже:
Изображение демонстрирует прохождение одиночного потока данных через все компоненты и их взаимное отношение друг к другу.
Модель
Название «модель» обозначает хранилище данных, используемых во всей конструкции. Оно должно предоставлять доступ к данным для их просмотра и изменения. Модель является своего рода мостом между представлением и контроллером.
Одним из важных аспектов модели является то, что технически она «слепа» и не имеет представления о том, что происходит с данными, когда они проходят через представление или контроллер.
Модель не вызывает и не ожидает ответа от других компонентов, а просто обрабатывает данные в своем хранилище и готовит их к отправлению другим компонентам.
Модель, тем не менее, не может быть названа базой данных или шлюзом другой системы, которая управляет процессом обработки данных. Модель должна работать как сторож данных, не задавая вопросов и отвечая на все приходящие запросы.
Зачастую наиболее сложной частью системы MVC является именно модель — связующее звено между контроллером и представлением.
Представление
Этот компонент отображает данные, полученные из модели, в определенном виде. Традиционно, в веб-приложениях использующих MVC, представление является набором HTML-файлов, которые и формируют облик выводимых данных.
Представление также определяет реакцию пользователя, который затем взаимодействует с контроллером. Простейшим примером этого может служить кнопка, сгенерированная представлением, по которой кликает пользователь, тем самым запуская взаимодействие с контроллером.
Среди веб-разработчиков, использующих MVC, существуют некоторые заблуждения относительно компонента представления.
Например, о том, что представление не имеет никакой связи с моделью, а все данные, отображаемые представлением, проходят через контроллер. В действительности, описанное выше, полностью идет в разрез с концепцией MVC.
«Важно заметить, что для корректного воплощения архитектуры MVC, не должно быть взаимодействия между моделью и представлением: вся логика реализована в контроллере.»
Более того, описание представления как файла шаблона неверно. Однако, как заметил Tom Butler, это не ошибка какого-то одного человека, а совокупность заблуждений многих разработчиков, неверно усвоивших теорию MVC. А затем они передают свой опыт другим, что сами понимаете к чему ведет.
Представление, на самом деле, это нечто большее, чем просто шаблон. Но современное понимание, породившее множество фреймворков, настолько исказило изначальный смысл, что разработчики уже не особо заботятся о соблюдении принципов MVC.
Также стоит заметить, что представление никогда не получает данные из контроллера. Как было замечено при обсуждении модели, прямой связи между представлением и контроллером, минуя модель, нет.
Контроллер
Завершает триаду компонентов контроллер. Его функция заключается в управлении данными, вводимыми и отправляемыми пользователем, а также соответствующее обновление модели.
Источником, приводящим в движение контроллер, является пользователь; без взаимодействия с ним, существование контроллера не имеет смысла. Это единственная часть MVC, с которой напрямую имеет дело пользователь.
Контроллер можно описать как сборщик информации, который передает её на хранение модели, и не содержит других логических компонентов, кроме тех, которые нужны для приема вводимых данных.
Также, контроллер подключен только к одной модели и одному представлению, что делает его единственным каналом прохождения данных в системе, который поддерживает механизмы обеспечения безопасности передачи.
Контроллер активен только тогда, когда пользователь взаимодействует с представлением. Иными словами, пользователь, работая с представлением, по принципу триггера запускает контроллер.
Распространенной среди разработчиков ошибкой является присвоение контроллеру функций, которые должно иметь представление.
Это результат того, что некоторые разработчики считают представление просто шаблоном. В дополнение ко всему, ошибкой является придавать контроллеру функции, делающие его единственным ответственным за передачу и обработку данных от модели к представлению, в то время как в MVC эта ответственность должна распределяться между моделью и представлением.
MVC в PHP
Давайте напишем веб-приложение на PHP, которое будет использовать MVC.
Начнем с простейших примеров:
<?php
class Model
{
public $string;
public function __construct(){
$this->string = "MVC + PHP = Awesome!";
}
}
<?php
class View
{
private $model;
private $controller;
public function __construct($controller,$model) {
$this->controller = $controller;
$this->model = $model;
}
public function output(){
return "<p>" . $this->model->string . "</p>";
}
}
<?php
class Controller
{
private $model;
public function __construct($model) {
$this->model = $model;
}
}
Для начала, мы создали несколько базовых классов для каждой части нашей реализации MVC.
Теперь настроим отношения между ними:
<?php
$model = new Model();
$controller = new Controller($model);
$view = new View($controller, $model);
echo $view->output();
Как можно было заметить в примере выше, мы не создавали специфического функционала контроллера, потому что отсутствует взаимодействие пользователя с нашим приложением.
Представление хранит все функции, что сделано исключительно в демонстрационных целях.
Давайте теперь расширим наш пример, чтобы показать как добавить функциональность в контроллер и придать интерактивности нашему приложению:
<?php
class Model
{
public $string;
public function __construct(){
$this->string = “MVC + PHP = Awesome, click here!”;
}
}
<?php
class View
{
private $model;
private $controller;
public function __construct($controller,$model) {
$this->controller = $controller;
$this->model = $model;
}
public function output() {
return '<p><a href="/mvc.php?action=clicked"' . $this->model->string . "</a></p>";
}
}
<?php
class Controller
{
private $model;
public function __construct($model){
$this->model = $model;
}
public function clicked() {
$this->model->string = “Updated Data, thanks to MVC and PHP!”
}
}
Мы расширили наше приложение.
Отношения между нашими компонентами теперь выглядят так:
<?php
$model = new Model();
$controller = new Controller($model);
$view = new View($controller, $model);
if (isset($_GET['action']) && !empty($_GET['action'])) {
$controller->{$_GET['action']}();
}
echo $view->output();
После запуска кода, кликните по ссылке, и вы сможете увидеть строку с измененными данными.
Заключение
Мы рассмотрели основы MVC и создали простейшее приложение на основе этой архитектуры, но впереди еще долгий путь, прежде чем мы получим желаемый результат.
Перевод статьи «The MVC Pattern and PHP, Part 1» был подготовлен дружной командой проекта Сайтостроение от А до Я.
Добро пожаловать! Это вторая часть цикла статей, касающегося использования MVC в PHP-приложениях, в которой мы обсудим некоторые вопросы, возникающие в связи выше обозначенной темой.
Если вы не читали первую статью из данного цикла, то настоятельно рекомендуется начать чтение именно с нее, чтобы полностью быть в курсе дела. Итак, начнем!
Перенаправление и URL-адреса
Хотя теоретически MVC должна работать независимо от языка программирования, подружить её с PHP может оказаться непросто.
Первая проблема касается перенаправления URL-адресов. Этот аспект не продумывался при создании схемы MVC, вследствие чего, этот вопрос стал вставать очень остро.
Какие возможности мы имеем для решения проблемы переадресации? Первое, что можно попытаться сделать, это заранее записать все URL-адреса вашего сайта в специальное хранилище, вместе с информацией о том, какие модель, представление и контроллер будут загружены для каждой страницы или её части.
В этом случае, система берет запрошенный пользователем URL-адрес и загружает компоненты, привязанные к данной странице.
Этот способ подходит, если вы создаете, к примеру, сайт-портфолио со статическими адресами (но не динамическими):
<?php
$page = $_GET['page'];
if (!empty($page)) {
$data = array(
'about' => array('model' => 'AboutModel', 'view' => 'AboutView', 'controller' => 'AboutController'),
'portfolio' => array('model' => 'PortfolioModel', 'view' => 'PortfolioView', 'controller' => 'PortfolioController')
);
foreach($data as $key => $components){
if ($page == $key) {
$model = $components['model'];
$view = $components['view'];
$controller = $components['controller'];
break;
}
}
if (isset($model)) {
$m = new $model();
$c = new $controller($model);
$v = new $view($model);
echo $v->output();
}
}
Теперь, наши URL-адреса будут выглядеть так:
example.com/index.php?page=about
либо
example.com/index.php?page=portfolio
Приведенный выше пример MVC-системы, загружает модель, представление и контроллер, указанные для запрошенной страницы. Если параметр имеет значение «about», то будет отображена страница About. Если параметр имеет значение «portfolio», то, соответственно, будет загружена страница Portfolio.
Как мы можем видеть, даже у этого простого примера статического перенаправления, есть недостатки. Один из самых очевидных из них, это проблемы с масштабируемостью при расширении сайта, так как все ссылки жестко «зашиты» в массив.
Другим способом решения нашей задачи является создание классов модели, представления и контроллера, позволив им формировать значения параметров URL-адреса.
В примере со статической переадресацией, мы просто вытаскивали значения из массива и возвращали их в качестве параметров при загрузке страниц. Удаление данного массива позволит нам реализовать динамическую переадресацию.
Несмотря на то, что мы вынимаем значения из заранее сформированного массива, отношения между классами уже настроены.
При наличии фиксированного массива, мы не можем динамически переназначать параметры. Но зачем нам вообще может понадобиться это делать?
Ответ прост: в таком случае нам не нужно будет заполнять вручную массив всеми возможными ссылками, имеющимися на сайте.
Мы можем создавать страницы или их части с помощью модели, представления и контроллера.
Например:
<?php
$model = $_GET['model'];
$view = $_GET['view'];
$controller = $_GET['controller'];
$action = $_GET['action'];
if (!(empty($model) || empty($view) || empty($controller) || empty($action))) {
$m = new $model();
$c = new $controller($m, $action);
$v = new $view($m);
echo $v->output();
}
Теперь наши ссылки будут выглядеть так:
example.com/index.php?controller=controllername&model=modelname&view=viewname&action=actionname
Параметр «action» говорит системе, какую функцию контроллера нужно запустить. Важно помнить, что когда эта функция передает данные в модель, то вместе с ними передается информация о том, какое представление и действие нужно загрузить.
Это может быть, опять же, значение переменной «action», или отдельные данные, либо данные, сохраненные контроллером. Никогда не позволяйте контроллеру загружать или напрямую передавать данные представлению. Эти два компонента должны взаимодействовать только через модель и действия пользователя.
Оба подхода имеют свои преимущества и недостатки: статическое перенаправление работает более стабильно, быстрее в реализации и предоставляет разработчику больший контроль над системой, в то время как динамическое перенаправление позволяет создавать более сложные системы, где имеется потенциальная возможность расширения и усложнения.
Динамическое перенаправление позволяет возложить всю ответственность на контроллер, чего нельзя сделать при статическом перенаправлении, которое рассматривается как альтернатива традиционной схеме MVC. Тем не менее, при корректной и эффективной реализации динамическая маршрутизация в большинстве случаев более предпочтительна, чем статическая.
Добавление фронт-контроллера позволит вашей системе динамически загружать секции, в зависимости от того, что нужно показать. Человек по имени Alejandro Gervasio написал потрясающую статью в двух частях о шаблоне фронт-контроллера (the Front Controller pattern), которая затрагивает вопросы использования фронт-контроллера в MVC-приложении.
DRY (Don’t Repeat Yourself — принцип программирования «не повторяй сам себя») и шаблоны
Одним из аргументов «за» использование MVC, является общее улучшение организации вашего проекта, насколько это возможно.
Также, это позволяет свести к минимуму возможное повторное использование ранее написанного кода. Любой разработчик согласится, что худшее, что можно обнаружить в любом проекте, это повторение кода. Техника и целая философия, призванная сохранить читаемость кода и позволить повторно использовать компоненты, известна под названием DRY (Don’t Repeat Yourself) — «Не повторяй сам себя».
Принцип DRY говорит, что «каждый кусочек знания должен иметь единственное, однозначное и авторитетное представление внутри системы». Цель DRY состоит в том, чтобы дать разработчику все возможности для создания максимально динамичных и оптимизированных систем.
DRY также подразумевает, что если вам необходимо использовать один и тот же кусок кода в разных местах, то необходимо оформить его в виде метода и вызывать его при необходимости. Это позволяет оптимизировать работу системы и предоставляет возможность кэширования внутри системы для повышения общей производительности.
Корректная реализация этого принципа также означает, что изменение какого-либо элемента не влияет на работу других, не связанных с ним элементов, что делает DRY очень важным при разработке MVC-приложений.
Шаблоны
Слово «шаблон» может вызвать некоторые вопрос при попытке понимания его сути для тех, кто ранее сталкивался с веб-фреймворками на основе MVC, потому как большинство людей подразумевает под этим только представление.
Такое понимание неправильно, если вы хотите придерживаться традиционной схемы MVC. В идеале, вы будете иметь представление, которое взаимодействует с моделью при передаче и обработке данных, после того, как они были помещены в эту самую модель.
Затем, представление выбирает шаблон и передает в него полученные данные. Теперь, все готово для отображения с использованием блочной разметки, или с использованием операторов PHP: echo, print и подобных.
Независимо от выбранного метода, самое важное помнить, что данные должны быть в состоянии готовности к выводу через шаблон. Если вы обрабатываете данные непосредственно в шаблоне, то вероятно, что схема MVC вами реализована неправильно.
Ниже дан простой пример загрузки шаблона и передачи в него данных:
<?php
class Model
{
public $tstring;
public function __construct(){
$this->tstring = "Эта строка была загружена через шаблон.";
$this->template = "tpl/template.php";
}
}
<?php
class View
{
private $model;
public function __construct($model) {
$this->controller = $controller;
$this->model = $model;
}
public function output(){
$data = "<p>" . $this->model->tstring ."</p>";
require_once($this->model->template);
}
}
<!DOCTYPE html>
<html>
<head>
<meta charset="charset=utf-8">
<title>The Template name</title>
</head>
<body>
<h1><?php echo $data; ?></h1>
</body>
</html>
В дополнение к этому, шаблон пропускается через модель. Это позволяет динамически выбирать шаблон в зависимости от назначения той или иной модели.
Этот метод позволяет эффективно и беспрепятственно расширять MVC-систему, разделив разработку интерфейсной и внутренней частей приложения, что как раз и является истинной целью MVC-подхода.
Заключение
Схема MVC, только появившись, создала почву для бесконечных дебатов на тему интерпретации и реализации этой концепции в конкретных приложениях.
Споры становятся все более интенсивными по мере роста популярности использования PHP для создания веб-приложений. Это хороший знак, который указывает на подключение все большего количества разработчиков, которые желают усовершенствовать подход в программировании на PHP.
Использование MVC является очень предпочтительным, так как поощряет разделение разработки на независимые ветви, иначе говоря, позволяет отделить разработку фронт-энда от разработки бэк-энда.
Всем тем, кто пока еще не вкусил мощи и потенциала MVC, очень рекомендую срочно взять себе на вооружение эту технологию. Уверяю, вы не будете разочарованы.
Перевод статьи «The MVC Pattern and PHP, Part 2» был подготовлен дружной командой проекта Сайтостроение от А до Я.