Шаблон проектирования MVC

Шаблон проектирования MVC

От автора: в данном уроке я хотел бы поговорить с Вами о шаблоне проектирования MVC. Это очень популярный, а главное полезный шаблон проектирования, который предусматривает полное разделение логики скрипта от его представления (дизайна). Мы разберем его теоретические основы и в качестве практики переделаем ранее созданную CMS из мини курса, который ранее публиковался у нас на сайте, таким образом, чтобы полностью отделить ее логику от дизайна.

скачать исходникискачать урок

Что такое MVC

Это достаточно сложный шаблон проектирования и по моему мнению это не сколько шаблон — сколько метод или идея того как можно организовать наше веб-приложение. И идея эта заключается в том, что бы отделить логику программирования от представления (то есть от вывода на экран).

Итак, как следует их названия MVC состоит из трех компонентов модель (Model) – вид (View) или представление и контроллер (controller). Давайте разберем каждый элемент в отдельности.

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

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

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

Если приложение сложное то моделей может быть несколько, например модель для работы со статьями и модель для работы с пользователями. Графически работу шаблона MVC можно представить по такой схеме:

То есть на данной схеме мы с Вами видим взаимодействие между Моделью Контроллером Представлением и браузером пользователя. Зелеными стрелками я обозначил запросы данных или передачу управления элементу, а серыми стрелками обозначено получение результирующих данных.

Смотрите, когда пользователь попадает на страничку, то он совершает какое то действие, к примеру, отправляет форму. То есть, выполняет запрос к контроллеру, контроллер в свою очередь, проверяет эти данные и передает запрос к модели. Модель к примеру сохраняет полученные данные в базе данных и возвращает контроллеру ответ об успешности проведенной операции. Затем контроллер выбирает, какой шаблон отобразить, посылает ему данные и получает ответ, который и направляется в браузер пользователю.

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

Правка точки входа на сайт

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

Думаю, Вы ее прекрасно помните. Данная CMS довольно неплоха и прекрасно выполняет свои задачи. Но у нее есть один существенный недостаток. В ее коде перемешана логика с внешним видом. А это не очень хорошо с точки зрения читаемости кода, да и в дальнейшем, очень трудно будет, что то изменить в коде, особенно если это касается дизайна. Так как не получится изменить дизайн не затрагивая логику работы данной CMS.

Поэтому в данному уроке мы переделаем часть данной CMS на структуру шаблона MVC и если данная идея Вам понравится, то оставшуюся часть Вы доделаете самостоятельно. Итак, первым делом изменим главный файл данной CMS index.php:

<?php
session_start();
header("Content-Type:text/html;charset=UTF-8");

require_once("config.php");

function __autoload($c) {
	if(file_exists("controller/".$c.".php")) {
		require_once "controller/".$c.".php";
	}
	elseif(file_exists("model/".$c.".php")) {
		require_once "model/".$c.".php";
	}
	
}

if($_GET['option']) {
	$class = trim(strip_tags($_GET['option']));
}
else {
	$class = 'main';	
}


	if(class_exists($class)) {
		
		$obj = new $class;
		$obj->get_body($class);
	}
	else {
		exit("<p>Нет данные для входа</p>");
	}


?>

Обратите внимание – я убрал ручное подключение необходимых файлов с классами и добавил функцию __autoload() – функция автоматической загрузки классов. Которая будет выполнять подключение недостающих файлов с классами. И убрал проверку наличия файла, перед созданием его объекта. Так как это выполняется в функции __autoload(). Да и папку classes я переименовал в controller (в ней будут содержаться контроллеры). Также создал две пустых папки: model и tpl, для хранения модели и шаблонов соответственно.

Создание модели

Если мы посмотрим, к примеру, на файл ACore.php мы увидим, что в его методах очень много кода, который отправляет запросы к базе данных. Нам необходимо весь данный код, перенести в модель. При этом в модели необходимо создать отдельные методы, которые будут возвращать массивы данных подученных из базы данных. Поэтому в папке model создаем файл со следующим содержимым:

<?php
class model {
	
	protected $db;
	
	public function __construct() {
		$this->db = mysql_connect(HOST,USER,PASSWORD);
		if(!$this->db) {
			exit("Ошибка соединения с базой данных".mysql_error());
		}
		if(!mysql_select_db(DB,$this->db)) {
			exit("Нет такой базы данных".mysql_error());
		}
		mysql_query("SET NAMES 'UTF8'");
		
	}
	
	public function get_left_bar(){
		$query = "SELECT id_category,name_category FROM category";
		
		$result = mysql_query($query);
		if(!$result) {
			exit(mysql_error());
		}
		
		for($i = 0;$i < mysql_num_rows($result); $i++) {
			$row[] = mysql_fetch_array($result, MYSQL_ASSOC);
		}
		
		return $row;
	}
	
	public function menu_array() {
		$query = "SELECT id_menu,name_menu FROM menu";
		
		$result = mysql_query($query);
		if(!$result) {
			exit(mysql_error());
		}
		
		$row = array();
		
		for($i = 0;$i < mysql_num_rows($result); $i++) {
			$row[] = mysql_fetch_array($result, MYSQL_ASSOC);
		}
		return $row;
	}
	
	public function get_main_content() {
		
		$query = "SELECT id,title,discription,date,img_src FROM statti ORDER BY date DESC";
		$result = mysql_query($query);
		if(!$result) {
			exit(mysql_error());
		}
		
		for($i = 0; $i < mysql_num_rows($result);$i++) {
			$row[] = mysql_fetch_array($result,MYSQL_ASSOC);
		}
		
		return $row;
	}
	
	public function get_cat($id_cat) {
		
		$query = "SELECT id,title,discription,date,img_src 
							FROM statti 
							WHERE cat='$id_cat' 
							ORDER BY date DESC";
				$result = mysql_query($query);
				if(!$result) {
					exit(mysql_error());
				}
				
				$row = array();
				for($i = 0; $i < mysql_num_rows($result);$i++) {
					$row[] = mysql_fetch_array($result,MYSQL_ASSOC);
						
				}
				return $row;
	}
	
	public function get_menu($id_menu) {
		$query = "SELECT id_menu,name_menu,text_menu FROM menu WHERE id_menu='$id_menu'";
				$result = mysql_query($query);
				if(!$result) {
					exit(mysql_error());
				}
				$row = mysql_fetch_array($result,MYSQL_ASSOC);
			return $row;	
	}
	
	
}
?>

Думаю, Вы увидите здесь знакомые строки кода. Так как они просто скопированы из классов ACore, main, category и menu.

Редактируем ядро – класс ACore

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

<?php
abstract class ACore {
	
	
	protected $m;
	
	public function __construct() {
		$this->m = new model();;
	}
	
	protected function get_header() {
		return TRUE;
	}
	
	protected function get_left_bar() {
		$result = $this->m->get_left_bar();
		return $result;	
	}
	
	protected function get_menu() {
		$row = $this->m->menu_array();
		return $row;		
	}
	
	
	protected function get_footer() {
		$row = $this->m->menu_array();
		return $row;
	}
	
	
	public function get_body($tpl) {
		if($_POST || $_GET['del']) {
			$this->obr();
		}
		$this->get_header();
		$left_bar = $this->get_left_bar();
		$menu_top = $this->get_menu();
		$content = $this->get_content();
		$footer = $this->get_footer();
		//подключение шаблона
		include "tpl/index.php";
	}
	
	abstract function get_content();
	
}

?>

Обратите внимание, какой вид приобрел класс ACore – согласитесь такой код намного удобнее читать и редактировать.
Да хочу обратить внимание на метод get_body(). Все методы которые вызываются в нем – должны возвращать данные. Которые, в последствии, сохраняются в переменной. Это нужно для того что бы вывести данные в шаблонах. Так как в конце данного метода мы подключаем главный файл шаблона. А значит в подключаемых файлах, будут доступны переменные с данными, что вернут методы get_header(), get_left_bar(), get_menu() и т.д.

Так же обратите внимание, что метод get_body(). Теперь должен принимать параметр $tpl – это имя загружаемого класса (текущего контроллера). Данный параметр будет передаваться из файла index.php. И он необходимо для формирования динамических вложенных шаблонов. Все шаблоны сохранены в отдельных файлах с одноименными именами с контроллерами.

Редактирование классов: main, menu и category

Аналогичным образом изменяем код контроллеров main, menu и category. Файл main.php:

<?php
class main extends ACore {
	
	public function get_content() {
		
		$result = $this->m->get_main_content();
		return $result;
	
	}
}
?>

Файл menu.php:

<?php
class menu extends ACore {
	
	public function get_content() {
		
		
		if(!$_GET['id_menu']) {
			echo 'Не правильные данные для вывода меню';
		}
		else {
			$id_menu = (int)$_GET['id_menu'];
			if(!$id_menu) {
				echo 'Не правильные данные для вывода меню';
			}
			else {
				$row = $this->m->get_menu($id_menu);
				return $row;
			}
		}
	}
	

}
?>

Файл category.php:

<?php
class category extends ACore {
	
	public function get_content() {
		
		
		if(!$_GET['id_cat']) {
			echo 'Не правильные данные для вывода статьи';
		}
		else {
			$id_cat = (int)$_GET['id_cat'];
			if(!$id_cat) {
				echo 'Не правильные данные для вывода статьи';
			}
			else {
				$result = $this->m->get_cat($id_cat);
				return $result;
			}
		}
	}
}
?>

Как Вы видите никаких запросов к базе данных и кода внешнего вида.

Создание шаблонов

Шаблоны – это файлы с кодом, которые отвечают за внешний вид скрипта. Весь код следующих файлов взят из исходников CMS, то есть из трех контроллеров main, menu и category и класса ACore (простое копирование HTML кода). Итак, главный файл шаблона, который собирает в единое целое все части шаблона:

<?
include "header.php";
include "left_bar.php";
include "menu_top.php";
include $tpl.".php";
include "footer.php";
?>

Обратите внимание центральная часть – это динамическая часть и ее шаблон формируется при помощи переменной $tpl – то есть это имя загруженного в данный момент контроллера. Файл шапки остается без изменений – его попросту копируем в папку tpl (header.php). Все фалы шаблона содержатся в папке tpl! Шаблон левой колонки (файл left_bar.php):

<div class="quick-bg">
	<div id="spacer" style="margin-bottom:15px;">
		<div id="rc-bg">Menu</div>
		</div>
	<?php foreach($left_bar as $row) :? >
			<div class='quick-links'>
				» <a href='?option=category&id_cat=<?php echo $row['id_category']?>'><?php echo $row['name_category']?></a>
					</div>
	<?php endforeach;?>				
</div>

Шаблон верхнего горизонтального меню (файл menu_top.php):

<div id="mainarea">
	<div class="heading">
			
		<div class="toplinks" style="padding-left:30px;">
					<a href="?option=main">Главная</a></div>
				<div class="sap2">::</div>
		<?php $i = 1;?>
		<?php foreach($menu_top as $item) :? >
			<div class='toplinks'><a href='?option=menu&id_menu=<?php echo $item['id_menu']?>'><?php echo $item['name_menu']?></a></div>
				
				<?php if($i != count($menu_top)) :? >
					<div class='sap2'>::</div>
				<?php endif;?>
				<?php $i++;?>
		<?php endforeach; ?>
	</div>

Шаблон футера (файл footer.php):

<div id='bottom'>
		<div class="toplinks" style="padding-left:127px;">
					<a href="?option=main">Главная</a></div>
				<div class="sap2">::</div>
		<?php $i = 1;?>
		<?php foreach($menu_top as $item) :? >
			<div class='toplinks'><a href='?option=menu&id_menu=<?php echo $item['id_menu']?>'><?php echo $item['name_menu']?></a></div>
				
				<?php if($i != count($menu_top)) :? >
					<div class='sap2'>::</div>
				<?php endif;?>
				<?php $i++;?>
		<?php endforeach; ?>
		</div>
		            <div class="copy"><span class="style1"> Copyright 2010 Название сайта </span>

		</div>
	</div>
</center></body></html>

И три динамических шаблона. Шаблон главной страницы (main.php):

<div id="main">

		<?php foreach($content as $row) :? >
			<div style='margin:10px;border-bottom:2px solid #c2c2c2'>
						<p style='font-size:18px'><?php echo $row['title']?></p>
						<p><?php echo $row['date']?></p>
						<p><img style='margin-right:5px' width='150px' align='left' src='<?php echo $row['img_src']?>'><?php echo $row['discription']?></p>
						<p style='color:red'><a href='?option=view&id_text=<?php echo $row['id']?>'>Читать далее...</a></p>
					
					</div>
			<?php endforeach;?>		
					</div>
			</div>

Шаблон страницы категорий (category.php):

<div id="main">

		<?php foreach($content as $row) :? >
			<div style='margin:10px;border-bottom:2px solid #c2c2c2'>
						<p style='font-size:18px'><?php echo $row['title'];?></p>
						<p><?php echo $row['date'];?></p>
						<p><img style='margin-right:5px' width='150px' align='left' src='<?php echo $row['img_src'];?>'><?php echo $row['discription'];?></p>
						<p style='color:red'><a href='?option=view&id_text=<?php echo $row['id'];?>'>Читать далее...</a></p>
					
					</div>
					
		<?php endforeach;?>
		</div>
			</div>

Шаблон для вывода страниц верхнего меню (menu.php):

<div id="main">

<p style='font-size:18px'><?php echo $content['name_menu'];?></p>
	<p><?php echo $content['text_menu'];?></p>
	
</div>

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

На этом данный урок завершен. Всего Вам доброго и удачного Вам кодирования!!!

Курс по программированию на языке PHP

Изучите PHP с нуля до результата!

Смотреть курс

Метки: ,

Комментарии Вконтакте:

Комментарии Facebook:

Комментарии (12)

  1. Sam

    Еще не видел , но идея интересная о «модернизации» старых проэктов и может увиличение функционала может даже довидение этой CMS в портал, тоже идея!!!

  2. Sam

    Как Я люблю MVC это как раз то что нужно!!! побольше бы на эту теме.

    • Виктор Гавриленко

      Здравствуйте!
      Да, MVC, это очень популярный шаблон проектирования и главное очень полезный. Рад, что Вам понравился урок.

  3. SkyNoSky

    Признаться, Я действительно в восторге от Ваших курсов. Неторопливо, одно и тоже по нескольку с немного другими «словооборотами» — расширяют круг слушателей курсов с различным восприятием.
    Собственно есть вопрос по МИНИ CMS: во втором уроке начали наполнять главный класс и в методах по выводу левого и верхнего меню почему-то применялись различные циклы при выборке результата (for для get_left_bar() и foreach для get_menu()), а метод get_menu() вообще раздробили. По мне просто и понятно применять тот же механизм вывода левого меню и для верхнего меню. Почему Вы используете другой механизм для вывода верхнего?
    Курс полностью ещё не просмотрел.

    • Виктор Гавриленко

      Здравствуйте!
      Спасибо Вам за комментарий, по Вашему вопросу ответил в теме форума.

  4. Денис

    Начало понравилось, а вот код надо комментировать, особенно если он предназначен кого-то чему-то научить.

    • Виктор Гавриленко

      Здравствуйте, Денис!
      Да согласен комментарии в коде нужны. Но в уроках я их не использую — для экономии времени.

      • Denis

        Для экономии Вашего времени, так как код можно прокомментировать заранее.

  5. Sergei

    А что такое класс?

  6. Ольга

    Спасибо! Не знаю, насколько пример создания MVC безупречен, но благодаря этому уроку, я, наконец, поняла суть вопроса. Билась год с темой проектирования, выслушав и прочитав с дюжину источников, понимание так и не приходило. Вы совершили чудо ) Я поняла )) Дальше просто просто буду совершенствовать. Спасибо автору урока!

    • Виктор Гавриленко

      Здравствуйте, Ольга!
      Спасибо, за теплые слова. Очень рад, что урок для Вас полезен!

  7. Vyacheslav

    Здравствуйте, Виктор!
    У меня два вопроса, если можно. Какую нагрузку может выдержать такая CMS. При объявлении объекта инициализируется с БД MySQL и нигде не закрывается. Сколько может предоставить соединения MySQL и что делать если их не хватит? И второй вопрос — как выводить лучше заголовок Last-Modified для страницы?

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

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

Можно использовать следующие HTML-теги и атрибуты: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>

Я не робот.

Spam Protection by WP-SpamFree