Создаем прототип микрофреймворка на PHP. Часть 2: реализация роутинга, разделение видов

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

ride-the-wheels
В предыдущей статье мы построили простейший MVC каркас и убедились в легкости и элегантности этого архитектурного шаблона. У нас был всего один контроллер — контроллер главной страницы с единственным действием (метод index) для отрисовки вида. В этой статье мы добавим класс для работы с маршрутами и модифицируем класс Load для разделения вида на общий для всех страниц шаблон и шаблон контента.

Реализация роутинга

Первый шаг, который нам нужно сделать, для создания удобной и практичной маршрутизации – записать следующий код в .htaccess:

RewriteEngine On
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule .* index.php [L]

Этот код перенаправит обработку всех страниц на index.php, что нам и нужно. Помните в первой части мы говорили о Front Controller?!

Маршрутизацию мы поместим в отдельный файл routing.php в корневой директории (application).
В этом файле опишем следующий класс, который будет запускать методы контроллеров, отрисовывающие вид страниц.

<?php

class Routing
{
	private $default_controller = 'Main';
	private $default_action = 'index';
	
	private $controller_prefix = 'Controller_';
	private $action_prefix = 'action_';
	private $model_prefix = 'Model_';
	
	function __construct ()
	{
		//echo "route: ".$_SERVER['REQUEST_URI']."<br>";
		$this->routs = explode('/', $_SERVER['REQUEST_URI']);		

		if (count($this->routs)>3) die('Непредусмотренный маршрут!');
		
		// получаем имя контроллера
		if ( !empty($this->routs[1]) )
		{	
			$this->controller_name = $this->routs[1];
		}
		else
		{
			$this->controller_name = $this->default_controller;
		}
		
		// получаем имя экшена
		if ( !empty($this->routs[2]) )
		{
			$this->action_name = $this->routs[2];	// vj;tv
		}
		else
		{
			$this->action_name = $this->default_action;
		}
		
		// добавляем префиксы
		$this->model_name = $this->model_prefix . $this->controller_name;
		$this->controller_name = $this->controller_prefix . $this->controller_name;
		$this->action_name = $this->action_prefix.$this->action_name;

		$this->run();
	}

	function run()
	{
		// подцепляем файл с классом модели
		$model_file = strtolower($this->model_name).'.php';
		$model_path = "application/models/".$model_file;
		if(file_exists($model_path)) include "models/".$model_file;
		// файла модели может и не быть
		
		// подцепляем файл с классом контроллера
		$controller_file = strtolower($this->controller_name).'.php';
		$controller_path = "application/controllers/".$controller_file;
		if(file_exists($controller_path)) include "controllers/".$controller_file;
		else die('Такого контроллера не существует!'); // здесь можно кинуть исключение
		
		// создаем контроллер и вызываем экшн
		$controller = new $this->controller_name;
		$action = $this->action_name;
		
		if(method_exists($controller, $action))
			$controller->$action();
		else die('Такого экшена не существует!'); // здесь можно кинуть исключение
	}

}
?>

Несмотря на объемный код, логика класса очень проста. В элементе глобального массива $_SERVER['REQUEST_URI'] содержится полный адрес по которому обратился пользователь.
Например: tinymvc.ru/contacts/feedback

В конструкторе, c помощью функции explode производится разделение адреса на составлющие. В результате мы получаем имя контроллера, для приведенного примера, это контроллер contacts и имя действия, в нашем случае — feedback.

После этого добавляются необходимые префиксы, описанные в свойствах класса и запускается метод run, в котором подключается файл модели (модель может отсутствовать) и файл контроллера.

Наконец, создается экземпляр контроллера и вызывается действие, если такое было описано в классе контроллера.

Как видите, это очень упрощенная реализация, но в качестве тренировочного примера думаю вполне подходит. Примеры маршрутизации показаны в разделе «Результат».

Модификация видов

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

Сказано — сделано. Файл общего шаблона template_view.php, содержит следующий код:

<!DOCTYPE html>

<html lang="ru">
<head>
	<meta charset="utf-8">
	<title>Главная</title>
</head>
<body>
	<?php include 'application/views/'.$content_file; ?>
</body>
</html>

Как мы видим, в общий шаблон встраивается некий контент.
Создадим вид для его отображения — файл contacts_view.php

<h1>Контакты</h1>
<p>
Email: <a href="mailto:yourname@company.com">yourname@company.com</a><br/>
ICQ: 199199538<br/>
</p>

Хорошо, теперь нужно подправить файл load.php, содержащий метод view для формирования видов.
Его код будет таким:

<?php
class Load {
	public $template;
	
	function view($content_file, $template_file, $data = null)
	{
		// динамически подключаем шаблон отображения (вид) и добавляем контент
		include 'application/views/'.$template_file;
	}
}
?>

В методе view подключается файл общего шаблона template_view.php, внутри которого подключается вид контента, ранее созданный файл contacts_view.php. Имена файлов передаются, как значения параметров метода view.

Ну что же, осталось написать контроллер, который «рулит» всем этим.
Класс контроллера будет следующим (файл controller_contacts):

<?php
class Controller_Contacts {
	
	public $load;
	public $model;
	
	function __construct()
	{
		$this->load = new Load();
	}
	
	function action_index()
	{
		$this->load->view('contacts_view.php', 'template_view.php');
	}
}
?>

Здесь я думаю все понятно. В метод view экземпляра класса Load передаются названия общего вида и вида c контентом страницы. После чего контент будет встроен в общий вид и отображен пользователю.
Помимо индексного действия мы можем описать в контроллере, скажем действие feedback, вызвав вид с формой обратной связи.

Теперь при обращении по адресу: tinymvc.ru/contacts/
или tinymvc.ru/contacts/index
пользователь увидит следующую страницу:

contacts

При обращении к несуществующим контроллерам или видам пользователь получит соответствующие уведомления. Например, при обращении по адресу tinymvc.ru/ufo пользователь увидит это:

ufo

Эту логику мы задавали в файле routing.php и позже мы сделаем редирект на 404 страницу.

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

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

folders

Результат

А вот, что получилось в результате:

results

Создание прототипа мы закончим в следующей статье.

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

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

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

Автор: vitalyswipe

  1. Варильян:

    мне кажется слишком большая привязка к структуре

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


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