Простая реализация модели MVC с поддержкой иерархии шаблонов

от автора

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

Немного о MVC

Паттерн разработки MVC обсуждался многократно и подробно описывать его вряд-ли есть смысл. Для ознакомления с предметом можно почитать:

Поэтому только кратко упомянем ключевые компоненты этой системы:

  • Контроллер (controller) — Важнейший компонент системы. Именно набор контроллеров и обрабатываемых ими команд (actions) определяют, каким именно функционалом будет обладать система. Контроллер является первым элементом реализации бизнес-логики приложения и должен определять что произошло в системе, и каким способом на это отреагировать. В частности — контроллер определяет какое представление (View) потребуется для отображения состояния системы и каким образом (Model) эти данные должны быть получены. Однако, контроллер, как хороший командир, не должен вникать в то, как его подчиненные будут выполнять задачу. Его дело — выбрать исполнителей и отдать им приказы. Важно учитывать, что даже в рамках обработки одной команды контроллер не привязан к одному представлению и одной модели. Наоборот, он может выбирать ил «на лету». Для иллюстрации рассмотри следующий пример: авторизация пользователя (псевдокод):
     <code> userController->actionAuth($login,$pass) { $model=new userModel(); if($model->authorize($login,$pass) { $view="authok"; } else { $view="authfailure"; } processView($view); } </code> 

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

  • Представление (View) — Отвечает за представление данных пользователю. Представление не имеет ни малейшего представления (простите за каламбур) о том, каким образом получены данные, которые ему нужны. Оно знает только о том, откуда эти данные можно взять и как их представить пользователю. Как правило, основным источником данных для представления является модель. Однако, как мы увидим дальше — модель не всегда способна предоставить данные в полном объеме. Важно, что с одной и той же моделью могут взаимодействовать несколько представлений. Как было показано в примере и представление «authok» и представление «authfailure» будут использовать данные одной модели $model.
  • Модель (model) — Очень важный компонент, однако несколько напоминающий рассеянного ученого, который способен с одинаковым интересом решать проблемы бессмертия и уничтожения всего живого. Иными словами, модель должна уметь собрать необходимые данные или произвести обработку входящих данных. Это ее конек. Однако, модель как правило не имеет представления о том, в каком контексте ее вызвали, в ответ на какую команду, что происходит в системе в целом. Более того, модель не может предполагать, понадобятся ли системе для обработки команды дополнительные данные, или нет. Очевидно, что собирать все мыслимые данные на «всякий случай» ни одна модель ни в состоянии.

Попытка собрать все данные в одном представлении может привести и к многократному дублированию кода. Например, данные профиля пользователя некого сайта могут понадобиться в рамках самых разных задач (от редактирования профиля пользователем, до указания ссылки на автора опубликованного материала. Если каждая модель будет самостоятельно запрашивать такие данные, сложность и дублирование кода будет возрастать экспоненциально.
Если представление сможет запрашивать данные порциями, определяя каждый раз кто может эти данные предоставить, жизнь программиста станет легка и безоблачна, так как можно будет сосредоточиться на решении отдельной задачи, без необходимости держать в голове весь проект в целом. Однако, это предполагает необходимость организации иерархической обработки представлений. Если мы сможем из какого-то места шаблона запросить данные другого контроллера и получить готовый фрагмент выходного кода (например уже отформатированный html) нам станет легче. Однако, для этого нам в какой-то момент потребуется приостановить обработку текущего потока и передать управление другому обработчику. Проблема в том, что с такой задачей PHP справляется, мягко говоря, не блестяще. Данные через интерпретатор проходят только однократно и конструкции типа echo('<p><?php echo($msg'); ?></p>'); работать не будут. Внутренне обращение к интерпретатору не пройдет.

Отчасти, проблему блочного построения вывода помогают решить шаблонизаторы. Их достаточно много, разных по возможностям и удобству работы. Однако, они имеют один общий недостаток — они являются надстройкой над PHP и вводят свой язык разметки, требующий изучения. В тоже время, такой функционал в рамках PHP является заведомо избыточным, так как этот язык сам по себе обеспечивает вполне неплохие возможности работы с шаблонами. Думаю, всем приходилось писать конструкции типа:

 <code> <html> ... <?php echo($msg); ?>   или более сложные  <table> <? php foreach($array as $val) { ?> <tr><td>$val</td></tr> <?}?> </table> </code> 

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

Практика

Мы рассмотрим упрощенную версию системы, в которой опущены такие моменты, как работа с ЧПУ (человеко-понятные урл) и настройки файла .htaccess для обеспечения единой точки входа в систему. Эти вопросы широко освещены в сети и смысла повторяться нет. Также не будем рассматривать вопросы старательного раскладывания компонентов системы по каталогам, так как это по сути личное дело каждого. Сосредоточимся на решении проблемы вложенности шаблонов.
Саму систему в действии можно увидеть здесь

Главным компонентом системы и главной точкой входа является файл application.php:

 <code> <?php   	class webApplication 	{ 		protected $dataBuf; 		 		protected static $_classInstance; 		protected $defaultHandler; 		protected $sefRequestParams; //To support SEF technology 		 		private function __construct() 		{ 			$this->dataBuf=""; 			$this->defaultController="mainpage"; 			$currentURL = $_SERVER['REQUEST_URI']; 			$this->sefRequestParams=explode("/",$currentURL); 			 			//It could be a good idea to establish database connection here 		} 		 		private function __clone() 		{     	}     	     	public function getSEFParams() //sef params need to be accessible for any parts of thew app     	{     		return $this->sefRequestParams;     	}     	 		public static function getApp()		 		{         	if (null === self::$_classInstance)              	self::$_classInstance = new self();          	return self::$_classInstance;         } 		 		public function handle($controller,$action) 		{ 			if(!isset($controller) || $controller=="") 				$controller=$this->defaultController; 			$val=$controller.'.php'; 			$res=require_once($val); 			if($res!=1) 			{ 				echo("requested controller not found!"); 				return 0; 			} 			$controlClass=new $controller(); 			if($controlClass==NULL) 			{ 				echo("Controller initialization error!"); 				return 0; 			} 			ob_start(); 			$controlClass->dispatchAction($action,&$this); 			$this->dataBuf=ob_get_contents(); 			ob_end_clean(); 			echo($this->dataBuf); 			return 1; 		} 		 		public function handleHttp() 		{ 			$controller=$_REQUEST['controller']; 			$action=""; 			if(!isset($controller) || $controller=="") //Assume we're using SEF technics 			{ 				$controller=$this->sefRequestParams[0]; 				$action=$this->sefRequestParams[1]; 			} 			else 			{ 				$action=$_REQUEST['action']; 			} 			return $this->handle($controller,$action); 		} 	} 	 	$app=webApplication::getApp(); 	$app->HandleHttp(); ?>  </code> 

Класс webApplication является базовой точкой входа в систему. Как видно из представленного кода, этот класс реализует паттерн Singleton. В рамках работы системы у нас всегда присутствует экземпляр данного класса, причем всегда только один. Такое свойство делает его крайне удобным для хранения всех глобальных данных системы. В данном случае импорт настроек системы и их использование опущены, так как их легко реализовать самостоятельно, в зависимости от своих потребностей.

Ключевой функцией класса является метод handle($controller,$action). Эта функция принимает на вход имя контроллера (первый параметр) и название действия (action), которое надо выполнить. В соответствии с практикой хорошего программирования предполагаем, что имя класса контроллера совпадает с именем файла, в котором он храниться. Разумеется, по желанию, строку $val=$controller.’.php’; можно модифицировать: $val=CONTROLLER_PATH.$controller.’.php’
Важным является то, эта функция позволяет вызвать нужный контроллер по его имени. Для того, чтобы контроллеры могли корректно взаимодействовать с системой, они должны реализовывать интерфейс iHandler, который определен следующим образом:

 <code> ihandler.php:  <?php 	interface iHandler 	{ 		public function dispatchAction($action,&$app); 		public function actionDefault(&$app); 	} ?> </code>  

Этот интерфейс вводит два обязательных метода: dispatchAction($action,&$app) и public function actionDefault(&$app);
Оба метода в качестве одного из параметров принимают ссылку на класс webApplication (параметр &$app). Это сделано для того, чтобы избежать привычки PHP делать полную копию объекта. Да и иметь нужный класс в качестве параметра несколько удобнее, чем писать global $app; $app->…

Вернемся к нашему примеру. В нашем случае будет вызван контроллер welcome (welcome.php):

 <code> <?php require_once("ihandler.php");  class welcome implements iHandler {  	public function __construct() 	{ 		//Nothing to do here	 	} 		 	public function dispatchAction($action,&$app) 	{ 		$this->actionDefault(&$app); 	} 	 	public function actionDefault(&$app) 	{ 		include("welcome.html"); 	} 	 } ?> </code>   

Этот контроллер написан сугубо в рамках примера и можно считать, что не делает вообще ничего. Только загружает некий HTML-файл. Откуда же тогда берутся данные? Можно предположить, что загружаемый файл является шаблоном и как-то их запрашивает. Так ли это? Смотрим код:

 <code> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=utf-8"> <title>Template handler output test page</title> </head>  <body> <p>Test page for template handler</p>   <h3>Starting the test</h3> <?php $app->handle("hello","say");?>  </body> </html>  </code> 

И это действительно так!.. Смотрим на строку <?php $app->handle(«hello»,«say»);?>. Вот он лев! Мы обратились к нашему классу webApplication и попросили вызвать нужный нам контроллер. Причем система обеспечит подготовку и вставку нужного нам HTML автоматически. Никаких возвратов строк. И задействовали мы ту же функцию handle, которую разбирали выше. Мы просим вызвать контроллер hello, который очевидно расположен в hello.php

 <code> <?php require_once("ihandler.php");  class hello implements iHandler {  	public function __construct() 	{ 		//Nothing to do here	 	} 		 	public function dispatchAction($action,&$app) 	{ 		 		switch($action) 		{ 			case 'say': 				$this->actionSay(&$app); 				break; 			default: 				$this->actionDefault(&$app); 			break; 		} 	} 	 	public function actionSay(&$app) 	{ 		require_once("saymodel.php"); 		$model=new sayModel(); 		$model->prepareString($_REQUEST['name']); 		include("hello.tpl"); 	} 	 	public function actionDefault(&$app) 	{ 		//Nothing to do by default 	} 	 } ?> </code>  

В данном контроллере у нас реализован метод, отвечающий за обработку действия «say» — actionSay.
Этот метод выполняет типичную для модели MVC последовательность действий: создает модель, передает ей на обработку данные, загружает представление.

Сначала посмотрим на модель (это только пример, поэтому она крайне проста).

 <code> <?php 	class sayModel 	{ 		public $msg; 		 		public function __construct() 		{ 			$this->msg=""; 		} 		 		public function prepareString($name) 		{ 			$this->msg="Hello $name!"; 		} 	}  ?> </code> 

Понятно, что реальная модель будет намного сложнее. Эта модель предоставляет данные в виде доступной (public) строковой переменной $msg. Как представление hello.tplб загружаемое контроллером использует эти данные? Очень просто:

 hello.tpl <code> <p color="#ff0000"><?php echo($model->msg); ?></p> </code> 

Как видно, данное представление — всего навсего фрагмент HTML кода, со вставкой PHP, обращающейся к данным модели. Эти данные уйдут в поток и после обработки интерпретатором PHP попадут в нужное место первого представления. Понятно, что в целом уровень вложенности и количество вызовов контроллеров не ограничено.

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

ссылка на оригинал статьи http://habrahabr.ru/post/232089/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *