Создаем движок на mvc с нуля. MVC: что это такое и какое отношение имеет к пользовательскому интерфейсу. Стилизация представления Thanks
Многие начинают писать проект для работы с единственной задачей, не подразумевая, что это может вырасти в многопользовательскую систему управления, ну допустим, контентом или упаси бог, производством. И всё вроде здорово и классно, всё работает, пока не начинаешь понимать, что тот код, который написан - состоит целиком и полностью из костылей и хардкода. Код перемешанный с версткой, запросами и костылями, неподдающийся иногда даже прочтению. Возникает насущная проблема: при добавлении новых фич, приходится с этим кодом очень долго и долго возиться, вспоминая «а что же там такое написано то было?» и проклинать себя в прошлом.Вы можеть быть даже слышали о шаблонах проектирования и даже листали эти прекрасные книги:
- Э. Гамма, Р. Хелм, Р. Джонсон, Дж. Влиссидесс «Приемы объектно ориентированного проектирования. Паттерны проектирования»;
- М. Фаулер «Архитектура корпоративных программных приложений».
Представленная статья будет полезна в первую очередь новичкам. Во всяком случае, я надеюсь что за пару часов вы сможете получить представление о реализации MVC паттерна, который лежит в основе всех современных веб-фреймворков, а также получить «пищу» для дальнейших размышлений над тем - «как стоит делать». В конце статьи приводится подборка полезных ссылок, которые также помогут разобраться из чего состоят веб-фреймворки (помимо MVC) и как они работают.
Прожженные PHP-программисты вряд ли найдут в данной статье что-то новое для себя, но их замечания и комментарии к основному тексту были бы очень кстати! Т.к. без теории практика невозможна, а без практики теория бесполезна, то сначала будет чуть-чуть теории, а потом перейдем к практике. Если вы уже знакомы с концепцией MVC, можете пропустить раздел с теорией и сразу перейти к практике.
1. Теория Шаблон MVC описывает простой способ построения структуры приложения, целью которого является отделение бизнес-логики от пользовательского интерфейса. В результате, приложение легче масштабируется, тестируется, сопровождается и конечно же реализуется.Рассмотрим концептуальную схему шаблона MVC (на мой взгляд - это наиболее удачная схема из тех, что я видел):
В архитектуре MVC модель предоставляет данные и правила бизнес-логики, представление отвечает за пользовательский интерфейс, а контроллер обеспечивает взаимодействие между моделью и представлением.
Типичную последовательность работы MVC-приложения можно описать следующим образом:
При этом отображается вид, скажем главной страницы сайта.
в котором, к примеру, содержаться вызовы модели, считывающие информацию из базы данных.
Модель не должна напрямую взаимодействовать с пользователем. Все переменные, относящиеся к запросу пользователя должны обрабатываться в контроллере.
Модель не должна генерировать HTML или другой код отображения, который может изменяться в зависимости от нужд пользователя. Такой код должен обрабатываться в видах.
Одна и та же модель, например: модель аутентификации пользователей может использоваться как в пользовательской, так и в административной части приложения. В таком случае можно вынести общий код в отдельный класс и наследоваться от него, определяя в наследниках специфичные для подприложений методы.
Вид
- используется для задания внешнего отображения данных, полученных из контроллера и модели.
Виды cодержат HTML-разметку и небольшие вставки PHP-кода для обхода, форматирования и отображения данных.
Не должны напрямую обращаться к базе данных. Этим должны заниматься модели.
Не должны работать с данными, полученными из запроса пользователя. Эту задачу должен выполнять контроллер.
Может напрямую обращаться к свойствам и методам контроллера или моделей, для получения готовых к выводу данных.
Виды обычно разделяют на общий шаблон, содержащий разметку, общую для всех страниц (например, шапку и подвал) и части шаблона, которые используют для отображения данных выводимых из модели или отображения форм ввода данных.
Контроллер
- связующее звено, соединяющее модели, виды и другие компоненты в рабочее приложение. Контроллер отвечает за обработку запросов пользователя. Контроллер не должен содержать SQL-запросов. Их лучше держать в моделях. Контроллер не должен содержать HTML и другой разметки. Её стоит выносить в виды.
В хорошо спроектированном MVC-приложении контроллеры обычно очень тонкие и содержат только несколько десятков строк кода. Чего, не скажешь о Stupid Fat Controllers (SFC) в CMS Joomla. Логика контроллера довольно типична и большая ее часть выносится в базовые классы.
Модели, наоборот, очень толстые и содержат большую часть кода, связанную с обработкой данных, т.к. структура данных и бизнес-логика, содержащаяся в них, обычно довольно специфична для конкретного приложения.
Надеюсь, вы уже успели заметить, что у разных сайтов могут быть совершенные разные форматы построения адресной строки. Каждый формат может отображать архитектуру web-приложения. Хотя это и не всегда так, но в большинстве случаев это явный факт.
Рассмотрим два варианта адресной строки, по которым показывается какой-то текст и профиль пользователя.
Приблизительный код обработки в таком случае:
switch($_GET["action"])
{
case "about" :
require_once("about.php"); // страница "О Нас"
break;
case "contacts" :
require_once("contacts.php"); // страница "Контакты"
break;
case "feedback" :
require_once("feedback.php"); // страница "Обратная связь"
break;
default:
require_once("page404.php"); // страница "404"
break;
}
Думаю, почти все так раньше делали.
С использованием движка маршрутизации URL вы сможете для отображения той же информации настроить приложение на прием таких запросов:
http://www.example.com/contacts/feedback
Здесь contacts представляет собой контроллер, а feedback - это метод контроллера contacts, отображающий форму обратной связи и т.д. Мы еще вернемся к этому вопросу в практической части.
Также стоит знать, что маршрутизаторы многих веб-фреймворков позволяют создавать произвольные маршруты URL (указать, что означает каждая часть URL) и правила их обработки.
Теперь мы обладаем достаточными теоретическими знаниями, чтобы перейти к практике.
Забегая вперед, скажу, что в папке core будут храниться базовые классы Model, View и Controller.
Их потомки будут храниться в директориях controllers, models и views. Файл index.php
это точка в хода в приложение. Файл bootstrap.php
инициирует загрузку приложения, подключая все необходимые модули и пр.
Будем идти последовательно; откроем файл index.php и наполним его следующим кодом:
ini_set("display_errors", 1);
require_once "application/bootstrap.php";
Тут вопросов возникнуть не должно.
Следом, сразу же перейдем к фалу bootstrap.php
:
require_once "core/model.php";
require_once "core/view.php";
require_once "core/controller.php";
require_once "core/route.php";
Route::start(); // запускаем маршрутизатор
Первые три строки будут подключать пока что несуществующие файлы ядра. Последние строки подключают файл с классом маршрутизатора и запускают его на выполнение вызовом статического метода start.
RewriteEngine On RewriteCond %{REQUEST_FILENAME} !-f RewriteCond %{REQUEST_FILENAME} !-d RewriteRule .* index.php [L]
Этот код перенаправит обработку всех страниц на index.php , что нам и нужно. Помните в первой части мы говорили о Front Controller?!
Маршрутизацию мы поместим в отдельный файл route.php в директорию core. В этом файле опишем класс Route, который будет запускать методы контроллеров, которые в свою очередь будут генерировать вид страниц.
Содержимое файла route.php
class Route
{
static function start()
{
// контроллер и действие по умолчанию
$controller_name = "Main";
$action_name = "index";
$routes = explode("/", $_SERVER["REQUEST_URI"]);
// получаем имя контроллера
if (!empty($routes))
{
$controller_name = $routes;
}
// получаем имя экшена
if (!empty($routes))
{
$action_name = $routes;
}
// добавляем префиксы
$model_name = "Model_".$controller_name;
$controller_name = "Controller_".$controller_name;
$action_name = "action_".$action_name;
// подцепляем файл с классом модели (файла модели может и не быть)
$model_file = strtolower($model_name).".php";
$model_path = "application/models/".$model_file;
if(file_exists($model_path))
{
include "application/models/".$model_file;
}
// подцепляем файл с классом контроллера
$controller_file = strtolower($controller_name).".php";
$controller_path = "application/controllers/".$controller_file;
if(file_exists($controller_path))
{
include "application/controllers/".$controller_file;
}
else
{
/*
правильно было бы кинуть здесь исключение,
но для упрощения сразу сделаем редирект на страницу 404
*/
Route::ErrorPage404();
}
// создаем контроллер
$controller = new $controller_name;
$action = $action_name;
if(method_exists($controller, $action))
{
// вызываем действие контроллера
$controller->$action();
}
else
{
// здесь также разумнее было бы кинуть исключение
Route::ErrorPage404();
}
}
function ErrorPage404()
{
$host = "http://".$_SERVER["HTTP_HOST"]."/";
header("HTTP/1.1 404 Not Found");
header("Status: 404 Not Found");
header("Location:".$host."404");
}
}
Замечу, что в классе реализована очень упрощенная логика (несмотря на объемный код) и возможно даже имеет проблемы безопасности. Это было сделано намерено, т.к. написание полноценного класса маршрутизации заслуживает как минимум отдельной статьи. Рассмотрим основные моменты…
В элементе глобального массива $_SERVER["REQUEST_URI"] содержится полный адрес по которому обратился пользователь.
Например: example.ru/contacts/feedback
С помощью функции explode производится разделение адреса на составлющие. В результате мы получаем имя контроллера, для приведенного примера, это контроллер contacts и имя действия, в нашем случае - feedback .
Далее подключается файл модели (модель может отсутствовать) и файл контроллера, если таковые имеются и наконец, создается экземпляр контроллера и вызывается действие, опять же, если оно было описано в классе контроллера.
Таким образом, при переходе, к примеру, по адресу:
example.com/portfolio
или
example.com/portfolio/index
роутер выполнит следующие действия:
example.com/ufo
то его перебросит на страницу «404»:
example.com/404
То же самое произойдет если пользователь обратится к действию, которое не описано в контроллере.2.2. Возвращаемся к реализации MVC Перейдем в папку core и добавим к файлу route.php еще три файла: model.php, view.php и controller.php
Напомню, что они будут содержать базовые классы, к написанию которых мы сейчас и приступим.
Содержимое файла model.php
class Model
{
public function get_data()
{
}
}
Класс модели содержит единственный пустой метод выборки данных, который будет перекрываться в классах потомках. Когда мы будем создавать классы потомки все станет понятней.
Содержимое файла view.php
class View
{
//public $template_view; // здесь можно указать общий вид по умолчанию.
function generate($content_view, $template_view, $data = null)
{
/*
if(is_array($data)) {
// преобразуем элементы массива в переменные
extract($data);
}
*/
include "application/views/".$template_view;
}
}
Не трудно догадаться, что метод generate
предназначен для формирования вида. В него передаются следующие параметры:
для отображения контента конкретной страницы.
В нашем случае общий шаблон будет содержать header, menu, sidebar и footer, а контент страниц будет содержаться в отдельном виде. Опять же это сделано для упрощения.
Содержимое файла controller.php
class Controller {
public $model;
public $view;
function __construct()
{
$this->view = new View();
}
function action_index()
{
}
}
Метод action_index
- это действие, вызываемое по умолчанию, его мы перекроем при реализации классов потомков.
На предыдущем рисунке отдельно выделен файл template_view.php
- это шаблон, содержащий общую для всех страниц разметку. В простейшем случае он мог бы выглядеть так:
Главная
Для придания сайту презентабельного вида сверстаем CSS шаблон и интегририруем его в наш сайт путем изменения структуры HTML-разметки и подключения CSS и JavaScript файлов:
В конце статьи, в разделе «Результат», приводится ссылка на GitHub-репозиторий с проектом, в котором проделаны действия по интеграции простенького шаблона.
class Controller_Main extends Controller { function action_index() { $this->view->generate("main_view.php", "template_view.php"); } }
В метод generate экземпляра класса View передаются имена файлов общего шаблона и вида c контентом страницы.
Помимо индексного действия в контроллере конечно же могут содержаться и другие действия.
Файл с общим видом мы рассмотрели ранее. Рассмотрим файл контента main_view.php
:
Добро пожаловать!
ОЛОЛОША TEAM - команда первоклассных специалистов в области разработки веб-сайтов с многолетним опытом коллекционирования мексиканских масок, бронзовых и каменных статуй из Индии и Цейлона, барельефов и изваяний, созданных мастерами Экваториальной Африки пять-шесть веков назад...
Здесь содержиться простая разметка без каких либо PHP-вызовов.
Для отображения главной странички можно воспользоваться одним из следующих адресов:
Пример с использованием вида, отображающего данные полученные из модели мы рассмотрим далее.2.3.2. Создадаем страницу «Портфолио» В нашем случае, страница «Портфолио» - это единственная страница использующая модель.
Модель обычно включает методы выборки данных, например:
Файл модели model_portfolio.php поместим в папку models. Вот его содержимое:
class Model_Portfolio extends Model { public function get_data() { return array(array("Year" => "2012", "Site" => "http://DunkelBeer.ru", "Description" => "Промо-сайт темного пива Dunkel от немецкого производителя Löwenbraü выпускаемого в России пивоваренной компанией "CАН ИнБев"."), array("Year" => "2012", "Site" => "http://ZopoMobile.ru", "Description" => "Русскоязычный каталог китайских телефонов компании Zopo на базе Android OS и аксессуаров к ним."), // todo); } }
Класс контроллера модели содержится в файле controller_portfolio.php
, вот его код:
class Controller_Portfolio extends Controller
{
function __construct()
{
$this->model = new Model_Portfolio();
$this->view = new View();
}
function action_index()
{
$data = $this->model->get_data();
$this->view->generate("portfolio_view.php", "template_view.php", $data);
}
}
В переменную data
записывается массив, возвращаемый методом get_data
, который мы рассматривали ранее.
Далее эта переменная передается в качестве параметра метода generate
, в который также передаются: имя файла с общим шаблон и имя файла, содержащего вид c контентом страницы.
Вид содержащий контент страницы находится в файле portfolio_view.php
.
Портфолио
Год | Проект | Описание |
Здесь все просто, вид отображает данные полученные из модели.2.3.3. Создаем остальные страницы Остальные страницы создаются аналогично. Их код досутпен в репозитории на GitHub, ссылка на который приводится в конце статьи, в разделе «Результат».3. Результат А вот что получилось в итоге:
Скриншот получившегося сайта-визитки
Ссылка на GitHub: https://github.com/vitalyswipe/tinymvc/zipball/v0.1
А вот в этой версии я набросал следующие классы (и соответствующие им виды):
- Controller_Login в котором генерируется вид с формой для ввода логина и пароля, после заполнения которой производится процедура аутентификации и в случае успеха пользователь перенаправляется в админку.
- Contorller_Admin с индексным действием, в котором проверяется был ли пользователь ранее авторизован на сайте как администратор (если был, то отображается вид админки) и действием logout для разлогинивания.
Но, использование веб-фреймворков, типа Yii или Kohana, состоящих из нескольких сотен файлов, при разработке простых веб-приложений (например, сайтов-визиткок) не всегда целесообразно. Теперь мы умеем создавать красивую MVC модель, чтобы не перемешивать Php, Html, CSS и JavaScript код в одном файле.
Данная статья является скорее отправной точкой для изучения CMF, чем примером чего-то истинно правильного, что можно взять за основу своего веб-приложения. Возможно она даже вдохновила Вас и вы уже подумываете написать свой микрофреймворк или CMS, основанные на MVC. Но, прежде чем изобретать очередной велосипед с «блекджеком и шлюхами», еще раз подумайте, может ваши усилия разумнее направить на развитие и в помощь сообществу уже существующего проекта?!
P.S.: Статья была переписана с учетом некоторых замечаний, оставленных в комментариях. Критика оказалась очень полезной. Судя по отклику: комментариям, обращениям в личку и количеству юзеров добавивших пост в избранное затея написать этот пост оказалось не такой уж плохой. К сожалению, не возможно учесть все пожелания и написать больше и подробнее по причине нехватки времени… но возможно это сделают те таинственные личности, кто минусовал первоначальный вариант. Удачи в проектах!
5. Подборка полезных ссылок по сабжу В статье очень часто затрагивается тема веб-фреймворков - это очень обширная тема, потому что даже микрофреймворки состоят из многих компонентов хитро увязанных между собой и потребовалась бы не одна статья, чтобы рассказать об этих компонентах. Тем не менее, я решил привести здесь небольшую подборку ссылок (по которым я ходил при написаниие этой статьи), которые так или иначе касаются темы фреймворков.Теги:
- php
- framework
- cmf
- mvc
- site
Всем привет! Продолжаем строить собственное MVC приложение , и сегодня мы начнем заниматься выводом страниц.
Создадим файл View.php в папке libs .
Теперь откроем наш главный файл index.php и подключим его.
Require "libs/View.php";
Открыв страницу, мы должны увидеть надпись "Это вид".
Теперь немного изменим наш класс, отвечающий за вид, добавив метод render , который будет заниматься выводом.
Теперь создадим папки index и error в папке views . Они будут отвечать за представление index страницы и страницы ошибок. В папке error создадим новый index.php файл и пропишем следующее
Это страница ошибки!
Теперь создадим файл header.php в самой папке views с таким содержанием
Header
Перейдем в файл error.php , который находится в папке error и добавим в конструктор вызов метода render .
Теперь на странице мы увидим "Header Это страница ошибки!"
Давайте немного улучшим наш контроллер, добавив к нему перед вызовом метода render следующее:
А теперь в файле index.php , который отвечает за вид ошибки, вместо нашей надписи "Это страница ошибки!" выведим то, что хранится в свойстве msg .
Теперь нам вывелась наша надпись.
Давайте теперь создадим модель help_model.php в папке models .
Теперь откроем контроллер help.php из папки controllers и добавим вызов нашей модели
Наша модель будет работать с базой данных, но пока что закомментируем это, т.к. у нас еще нет базы данных.
Подключим нашу базовую модель в нашем главном файле index.php .
Require "libs/Bootstrap.php"; require "libs/Controller.php"; require "libs/model.php"; require "libs/View.php";
Итак, на этом сегодня остановимся. Мы уже сделали некоторый вывод на страницу, а в следующих статьях продолжим это улучшать. Спасибо за внимание и удачного кодинга!
Шаблон проектирования Модель-Представление-Контроллер (MVC) — это шаблон программной архитектуры, построенный на основе сохранения представления данных отдельно от методов, которые взаимодействуют с данными.
Не смотря на то, что схема MVC была первоначально разработана для персональных компьютеров, она была адаптирована и широко используется веб-разработчиками из-за точного разграничения задач и возможности повторного использования кода. Схема стимулирует развитие модульных систем, что позволяет разработчикам быстро обновлять, добавлять или удалять функционал.
В этой статье я опишу основные принципы, а также рассмотрю определение схемы построения и простой MVC PHP пример.
Что такое MVCНазвание шаблона проектирования определяется тремя его основными составляющими частями: Модель, Представление и Контроллер. Визуальное представление шаблона MVC выглядит, как показано на приведенной ниже диаграмме :
На рисунке показана структура одностороннего потока данных и пути его следования между различными компонентами, а также их взаимодействие. Модель
Моделью называют постоянное хранилище данных, используемых во всей структуре. Она должна обеспечивать доступ к данным для их просмотра, отбора или записи. В общей структуре «Модель » является мостом между компонентами «Представление » и «Контроллер ».
При этом «Модель » не имеет никакой связи или информации о том, что происходит с данными, когда они передаются компонентам «Представление » или «Контроллер ». Единственная задача «Модели » — обработка данных в постоянном хранилище, поиск и подготовка данных, передаваемых другим составляющим MVC .
«Модель » должна выступать в качестве «привратника », стоящего возле хранилища данных и не задающего вопросов, но принимающего все поступающие запросы. Зачастую это наиболее сложная часть системы MVC . Компонент «Модель » — это вершина всей структуры, так как без нее невозможна связь между «Контроллером » и «Представлением ».
ПредставлениеПредставление — это часть системы, в которой данным, запрашиваемым у «Модели », задается окончательный вид их вывода. В веб-приложениях, созданных на основе MVC , «Представление » — это компонент, в котором генерируется и отображается HTML -код.
Представление также перехватывает действие пользователя, которое затем передается «Контроллеру ». Характерным примером этого является кнопка, генерируемая «Представлением ». Когда пользователь нажимает ее, запускается действие в «Контроллере».
Существует несколько распространенных заблуждений относительно компонента «Представление ». Например, многие ошибочно полагают, что «Представление » не имеет никакой связи с «Моделью », а все отображаемые данные передаются от «Контроллера ». В действительности такая схема потока данных не учитывает теорию, лежащую в основе MVC архитектуры. В своей статье Фабио Чеваско описывает этот некорректный подход на примере одного из нетрадиционных MVC PHP фреймворков:
«Чтобы правильно применять архитектуру MVC, между «Моделью» и «Представлением» не должно быть никакого взаимодействия: вся логика обрабатывается «Контроллером».
Кроме этого определение «Представления » как файла шаблона также является неточным. Но это не вина одного человека, а результат множества ошибок различных разработчиков, которые приводят общему заблуждению. После чего они неправильно объясняют это другим. На самом деле «Представление » это намного больше, чем просто шаблон. Но современные MVC -ориентированные фреймворки до такой степени впитали этот подход, что никто уже не заботится о том, поддерживается ли верная структура MVC или нет.
Компоненту «Представление » никогда не передаются данные непосредственно «Контроллером ». Между «Представлением » и «Контроллером » нет прямой связи — они соединяются с помощью «Модели ».
КонтроллерЕго задача заключается в обработке данных, которые пользователь вводит и обновлении «Модели ». Это единственная часть схемы, для которой необходимо взаимодействие пользователя.
«Контроллер » можно определить, как сборщик информации, которая затем передается в «Модель » с последующей организацией для хранения. Он не содержит никакой другой логики, кроме необходимости собрать входящие данные. «Контроллер » также подключается только к одному «Представлению » и одной «Модели ». Это создает систему с односторонним потоком данных с одним входом и одним выходом в точках обмена данными.
«Контроллер » получает задачи на выполнение только когда пользователь взаимодействует с «Представлением », и каждая функция зависит от взаимодействия пользователя с «Представлением ». Наиболее распространенная ошибка разработчиков заключается в том, что они путают «Контроллер » со шлюзом, поэтому присваивают ему функции и задачи, которые относятся к «Представлению ».
Также распространенной ошибкой является наделение «Контроллера » функциями, которые отвечают только за обработку и передачу данных из «Модели » в «Представление ». Но согласно структуре MVC паттерна это взаимодействие должно осуществляться между «Моделью » и «Представлением ».
MVC в PHPНапишем на PHP веб-приложение, архитектура которого основана MVC . Давайте начнем с примера каркаса: