Создаем прототип микрофреймворка на PHP. Часть 1: реализация MVC шаблона

в 3:03, , рубрики: cmf, framework, mvc, php, site, Веб-разработка, метки: , , , ,

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

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

  • Э. Гамма, Р. Хелм, Р. Джонсон, Дж. Влиссидесс «Приемы объектно ориентированного проектирования. Паттерны проектирования»;
  • М. Фаулер «Архитектура корпоративных программных приложений».

В общем, сегодня речь пойдет о самом популярном (разве что после Singleton) шаблоне проектирования MVC и его простой реализации дабы восполнить пробел и помочь вам в рефакторинге и разрешении неприятной ситуации в которую, возможно попал ваш проект.

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

Теория

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

Рассмотрим концептуальную схему шаблона MVC (на мой взгляд — это наиболее удачная схема из тех, что я видел):

MVC

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

Типичную последовательность работы MVC-приложения можно описать следующим образом:

  1. При заходе пользователя на веб-ресурс, скрипт инициализации создает экземпляр приложения и запускает его на выполнение.
    При этом отображается вид, скажем главной страницы сайта.
  2. Приложение получает запрос от пользователя и определяет запрошенные контроллер и действие. В случае главной страницы, выполняется действие по умолчанию (index).
  3. Приложение создает экземпляр контроллера и запускает метод действия,
    в котором, к примеру, содержаться вызовы модели, считывающие информацию из базы данных.
  4. После этого, действие формирует представление с данными, полученными из модели и выводит результат пользователю.

Модель — содержит бизнес-логику приложения и включает методы выборки (это могут быть методы ORM), обработки (например, правила валидации) и предоставления конкретных данных, что зачастую делает ее очень толстой, что вполне нормально.
Модель не должна напрямую взаимодействовать с пользователем. Все переменные, относящиеся к запросу пользователя должны обрабатываться в контроллере.
Модель не должна генерировать HTML или другой код отображения, который может изменяться в зависимости от нужд пользователя. Такой код должен обрабатываться в видах.
Одна и та же модель, например: модель аутентификации пользователей может использоваться как в пользовательской, так и в административной части приложения. В таком случае можно вынести общий код в отдельный класс и наследоваться от него, определяя в наследниках специфичные для подприложений методы.

Вид — используется для задания внешнего отображения данных, полученных из контроллера и модели.
Виды cодержат HTML-разметку и небольшие вставки PHP-кода для обхода, форматирования и отображения данных.
Не должны напрямую обращаться к базе данных. Этим должны заниматься модели.
Не должны работать с данными, полученными из запроса пользователя. Эту задачу должен выполнять контроллер.
Может напрямую обращаться к свойствам и методам контроллера или моделей, для получения готовых к выводу данных.
Виды обычно разделяют на общий шаблон, содержащий разметку, общую для всех страниц (например, шапку и подвал) и части шаблона, которые используют для отображения данных выводимых из модели или отображения форм ввода данных.

Контроллер — связующее звено, соединяющее модели, виды и другие компоненты в рабочее приложение. Контроллер отвечает за обработку запросов пользователя. Контроллер не должен содержать SQL-запросов. Их лучше держать в моделях. Контроллер не должен содержать HTML и другой разметки. Её стоит выносить в виды.
В хорошо спроектированном MVC-приложении контроллеры обычно очень тонкие и содержат только несколько десятков строк кода. Чего, не скажешь о Stupid Fat Controllers (SFC) в CMS Joomla. Логика контроллера довольно типична и большая ее часть выносится в базовые классы.
Модели, наоборот, очень толстые и содержат большую часть кода, связанную с обработкой данных, т.к. структура данных и бизнес-логика, содержащаяся в них, обычно довольно специфична для конкретного приложения.

Front Controller и Page Controller

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

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

Рассмотрим два варианта адресной строки, по которым показывается какой-то текст:

  1. www.domain1.com/article.php?id=3
  2. www.domain2.com/index.php?article=3

Соответственно имеются также два варианта для показа профиля пользователя:

  1. www.domain1.com/user.php?id=4
  2. www.domain2.com/index.php?user=4

Из примеров видно, что логика обращения к web-приложению является разной. Для сайта domain1.com каждый сценарий отвечает за выполнение определённой команды, а для сайта domain2.com все обращения происходят в одном сценарии index.php. Подход с множеством точек взаимодействия вы можете наблюдать на форумах с движком phpBB. Просмотр форума происходит через сценарий viewforum.php, просмотр топика через viewtopic.php и т.д. Второй подход, с доступом через один физический файл сценария, можно наблюдать в моей любимой CMS MODX, где все обращения проходят через index.php.

Эти два подхода совершенно различны. Первый — характерен для шаблона контроллер страниц (Page Controller), а второй подход реализуется паттерном контроллер запросов (Front Controller). Контроллер страниц хорошо применять для сайтов с достаточно простой логикой. В свою очередь, контроллер запросов объединяет все действия по обработке запросов в одном месте, что даёт ему дополнительные возможности, благодаря которым можно реализовать более трудные задачи, чем обычно решаются контроллером страниц. Я не буду вдаваться в подробности реализации контроллера страниц, а скажу лишь, что в практической части будет разработан именно контроллер запросов (некоторое подобие).

Теперь мы обладаем достаточными теоретическими знаниями, чтобы перейти к практике.

Практика

Для начала создадим следующую структуру файлов и папок:

folders

Теперь, откроем файл 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-шаблон, в котором выводятся данные полученные из модели.

Результат

Окей, запустим на выполнение и посмотрим, что у нас получилось.

results

Использование веб-фреймворков, типа Yii или Kohana, состоящих из нескольких сотен файлов, при разработке простых веб-приложений (например, сайтов-визиток) не всегда целесообразно. Теперь мы умеем создавать красивую MVC модель, чтобы не перемешивать Php, Html, CSS и JavaScript код в одном файле. Продолжение в следующей статье.

Исходные файлы

Ссылка на GitHub: https://github.com/vitalyswipe/tinymvc/zipball/part1

Полезные ссылки:

Автор: vitalyswipe

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js