Многие начинают писать проект для работы с единственной задачей, не подразумевая, что это может вырасти в многопользовательскую систему управления, ну допустим, контентом или упаси бог, производством. И всё вроде здорово и классно, всё работает, пока не начинаешь понимать, что тот код, который написан — состоит целиком и полностью из костылей и хардкода. Код перемешанный с версткой, запросами и костылями, неподдающийся иногда даже прочтению. Возникает насущная проблема: при добавлении новых фич, приходится с этим кодом очень долго и долго возиться, вспоминая «а что же там такое написано то было?» и проклинать себя в прошлом.
Вы можеть быть даже слышали о шаблонах проектирования и даже листали эти прекрасные книги:
- Э. Гамма, Р. Хелм, Р. Джонсон, Дж. Влиссидесс «Приемы объектно ориентированного проектирования. Паттерны проектирования»;
- М. Фаулер «Архитектура корпоративных программных приложений».
В общем, сегодня речь пойдет о самом популярном (разве что после Singleton) шаблоне проектирования MVC и его простой реализации дабы восполнить пробел и помочь вам в рефакторинге и разрешении неприятной ситуации в которую, возможно попал ваш проект.
Т.к. без теории практика невозможна, а без практики теория бесполезна, то сначала будет чуть-чуть теории, а потом перейдем к практике. Если вы уже знакомы с концепцией MVC, можете пропустить раздел с теорией.
Теория
Шаблон MVC описывает простой способ построения структуры приложения, целью которого является отделение бизнес-логики от пользовательского интерфейса. В результате, приложение легче масштабируется, тестируется, сопровождается и конечно же реализуется.
Рассмотрим концептуальную схему шаблона MVC (на мой взгляд — это наиболее удачная схема из тех, что я видел):
В архитектуре MVC модель предоставляет данные и правила бизнес-логики, представление отвечает за пользовательский интерфейс, а контроллер обеспечивает взаимодействие между моделью и представлением.
Типичную последовательность работы MVC-приложения можно описать следующим образом:
- При заходе пользователя на веб-ресурс, скрипт инициализации создает экземпляр приложения и запускает его на выполнение.
При этом отображается вид, скажем главной страницы сайта. - Приложение получает запрос от пользователя и определяет запрошенные контроллер и действие. В случае главной страницы, выполняется действие по умолчанию (index).
- Приложение создает экземпляр контроллера и запускает метод действия,
в котором, к примеру, содержаться вызовы модели, считывающие информацию из базы данных. - После этого, действие формирует представление с данными, полученными из модели и выводит результат пользователю.
Модель — содержит бизнес-логику приложения и включает методы выборки (это могут быть методы ORM), обработки (например, правила валидации) и предоставления конкретных данных, что зачастую делает ее очень толстой, что вполне нормально.
Модель не должна напрямую взаимодействовать с пользователем. Все переменные, относящиеся к запросу пользователя должны обрабатываться в контроллере.
Модель не должна генерировать HTML или другой код отображения, который может изменяться в зависимости от нужд пользователя. Такой код должен обрабатываться в видах.
Одна и та же модель, например: модель аутентификации пользователей может использоваться как в пользовательской, так и в административной части приложения. В таком случае можно вынести общий код в отдельный класс и наследоваться от него, определяя в наследниках специфичные для подприложений методы.
Вид — используется для задания внешнего отображения данных, полученных из контроллера и модели.
Виды cодержат HTML-разметку и небольшие вставки PHP-кода для обхода, форматирования и отображения данных.
Не должны напрямую обращаться к базе данных. Этим должны заниматься модели.
Не должны работать с данными, полученными из запроса пользователя. Эту задачу должен выполнять контроллер.
Может напрямую обращаться к свойствам и методам контроллера или моделей, для получения готовых к выводу данных.
Виды обычно разделяют на общий шаблон, содержащий разметку, общую для всех страниц (например, шапку и подвал) и части шаблона, которые используют для отображения данных выводимых из модели или отображения форм ввода данных.
Контроллер — связующее звено, соединяющее модели, виды и другие компоненты в рабочее приложение. Контроллер отвечает за обработку запросов пользователя. Контроллер не должен содержать SQL-запросов. Их лучше держать в моделях. Контроллер не должен содержать HTML и другой разметки. Её стоит выносить в виды.
В хорошо спроектированном MVC-приложении контроллеры обычно очень тонкие и содержат только несколько десятков строк кода. Чего, не скажешь о Stupid Fat Controllers (SFC) в CMS Joomla. Логика контроллера довольно типична и большая ее часть выносится в базовые классы.
Модели, наоборот, очень толстые и содержат большую часть кода, связанную с обработкой данных, т.к. структура данных и бизнес-логика, содержащаяся в них, обычно довольно специфична для конкретного приложения.
Front Controller и Page Controller
В большинстве случае, взаимодействие пользователя с web-приложением проходит посредством переходов по ссылкам. Посмотрите сейчас на адресную строку браузера — по этой ссылке вы получили данный текст. По другим ссылкам, например, находящимся справа на этой странице, вы получите другое содержимое. Таким образом, ссылка представляет конкретную команду web-приложению.
Надеюсь, вы уже успели заметить, что у разных сайтов могут быть совершенные разные форматы построения адресной строки. Каждый формат может отображать архитектуру web-приложения. Хотя это и не всегда так, но в большинстве случаев это явный факт.
Рассмотрим два варианта адресной строки, по которым показывается какой-то текст:
Соответственно имеются также два варианта для показа профиля пользователя:
Из примеров видно, что логика обращения к web-приложению является разной. Для сайта domain1.com каждый сценарий отвечает за выполнение определённой команды, а для сайта domain2.com все обращения происходят в одном сценарии index.php. Подход с множеством точек взаимодействия вы можете наблюдать на форумах с движком phpBB. Просмотр форума происходит через сценарий viewforum.php, просмотр топика через viewtopic.php и т.д. Второй подход, с доступом через один физический файл сценария, можно наблюдать в моей любимой CMS MODX, где все обращения проходят через index.php.
Эти два подхода совершенно различны. Первый — характерен для шаблона контроллер страниц (Page Controller), а второй подход реализуется паттерном контроллер запросов (Front Controller). Контроллер страниц хорошо применять для сайтов с достаточно простой логикой. В свою очередь, контроллер запросов объединяет все действия по обработке запросов в одном месте, что даёт ему дополнительные возможности, благодаря которым можно реализовать более трудные задачи, чем обычно решаются контроллером страниц. Я не буду вдаваться в подробности реализации контроллера страниц, а скажу лишь, что в практической части будет разработан именно контроллер запросов (некоторое подобие).
Теперь мы обладаем достаточными теоретическими знаниями, чтобы перейти к практике.
Практика
Для начала создадим следующую структуру файлов и папок:
Теперь, откроем файл index.php и наполним его следующим кодом:
<?php
// отображение сообщений об ошибках
ini_set('display_errors', 1);
// подключаем другие файлы
//require 'bootstrap.php';
// подключаем файл ядра
require 'application/app.php';
?>
Тут вопросов возникнуть не должно.
Переходим к файлу app.php
<?php
require 'load.php';
require 'model.php';
require 'controller.php';
new Controller();
?>
Подцепляем составные части нашего веб-приложения (далее будет приведен их код).
В конце создаем экземпляр контроллера (фронт-контроллер).
Содержимое файла load.php
<?php
class Load {
function view($file_name, $data = null)
{
if(is_array($data)) {
// преобразуем элементы массива в переменные
extract($data);
}
// динамически подключаем шаблон отображения (вид)
include 'views/'.$file_name;
}
}
?>
Не трудно догадаться, что данный класс предназначен для отображения видов. Для этого в метод view передается имя файла вида, содержащего html-разметку и массив параметров, значения которых будут отображены в виде.
Базовый класс контроллера содержится в файле controller.php
<?php
class Controller {
public $load;
public $model;
function __construct()
{
$this->load = new Load();
$this->model = new Model();
// после того, как создана модель
// создаем метод index - действие по умолчанию
$this->index();
}
function index()
{
$data = $this->model->user_info();
$this->load->view('someview.php', $data);
}
}
?>
В методе index (действие по умолчанию), создается массив параметров, который наполняется данными модели, путем вызова метода user_info (о нем далее). Далее этот массив передается в метод view для отображения заданного вида.
Переходим к файлу model.php
<?php
class Model{
public function user_info()
{
//mysql query:
//query='';
// симулируем реальные данные
return array(
'first'=>'Nicolas',
'last'=>'Cage'
);
}
}
?>
Для простоты, здесь мы не будем использовать SQL-запросы или ORM-операторы. Вместо этого мы сэмулируем данные, сразу возвратив массив результатов.
Осталось написать файл вида someview.php, который мы указали в качестве параметра метода view в классе контроллера.
<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="utf-8">
<title>untitled</title>
<!-- Подключаем Css и JavaScript -->
</head>
<body>
<h1>Hello!</h1>
<?php echo $first.' '.$last; ?>
</body>
</html>
Здесь отображается HTML-шаблон, в котором выводятся данные полученные из модели.
Результат
Окей, запустим на выполнение и посмотрим, что у нас получилось.
Использование веб-фреймворков, типа Yii или Kohana, состоящих из нескольких сотен файлов, при разработке простых веб-приложений (например, сайтов-визиток) не всегда целесообразно. Теперь мы умеем создавать красивую MVC модель, чтобы не перемешивать Php, Html, CSS и JavaScript код в одном файле. Продолжение в следующей статье.
Исходные файлы
Ссылка на GitHub: https://github.com/vitalyswipe/tinymvc/zipball/part1
Полезные ссылки:
- Концепция MVC для чайников
http://ruseller.com/lessons.php?rub=37&id=666 - Тот самый ролик, после ознакомления с которым я написал эту статью
http://www.youtube.com/watch?v=CGiIVQPaOJQ - Примеры шаблонов проектирования или как написать свой PHP Framework. Часть 1: Строковый фасад
http://www.itdumka.com.ua/index.php?cmd=shownode&node=12 - Примеры шаблонов проектирования или как написать свой PHP Framework. Часть 2: Объект запроса
http://www.itdumka.com.ua/index.php?cmd=shownode&node=13 - Примеры шаблонов проектирования или как написать свой PHP Framework. Часть 3: Контроллер
http://www.itdumka.com.ua/index.php?cmd=shownode&node=14
Автор: vitalyswipe