В этой статье мы напишем «каркас» нашего проекта. Под словом «каркас» я подразумеваю рабочий код, который будет иметь в своей основе MVC подход, то есть будет иметь четкое разделение логики на контролеры, экшены, шаблоны (представления) и модели.
И так начнем. Паттерн MVC подразумевает одну точку входа – index.php, через это скрипт будут проходить все запросы, через него будет работать вся логика проекта. Для того чтобы реализовать такой подход необходимо настроить сервер, подразумевается, что сайт работает на сервере apache, поэтому нам достаточно создать файл .htaccess, в котором мы укажем правила маршрутизации URL. Помимо определения точки входа, маршрутизация позволяет создавать ЧПУ(человеко-понятные урлы). То есть после правильной настройки, адреса страниц буду выглядеть вот так site.ru/article/new. Для начала, давайте составим .htaccess, который перенаправит обработку всех страниц на скрипт index.php. Код выглядит вот так:
RewriteEngine on
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^(.*)$ index.php?route=$1 [L,QSA]
Файл .htaccess должен лежать в корневой папке сайта, тут же необходимо создать скрипт index.php, который является точкой входа. Давайте запишем в index.php одну строку, для проверки работы перенаправления:
echo "test";
Теперь можно проверять работу перенаправления, введите любой адрес и посмотрите, что получиться: test-mvc.web/sdf/sdf/ или test-mvc.web/sdf/sdf/2342/не важно, на экране в любом случае, должно появиться «Test». Если Вы увидели эту надпись, значит, у нас все получилось. Продолжим, давайте для удобства создадим в корне сайта файл config.php, в котором будем задавать различные константы, облегчающие своим существование настройку сайта. Это могут быть различные пути к скриптам, подступы к базе данных и так далее. Сейчас в конфиге давайте зададим следующее:
// Задаем константы:
define ('DS', DIRECTORY_SEPARATOR); // разделитель для путей к файлам
$sitePath = realpath(dirname(__FILE__) . DS);
define ('SITE_PATH', $sitePath); // путь к корневой папке сайта
// для подключения к бд
define('DB_USER', 'root');
define('DB_PASS', '');
define('DB_HOST', 'localhost');
define('DB_NAME', 'blog_mvc');
Для того, чтобы константы и другие данные конфига мы могли использовать во всем проекте, в файле index.php необходимо подключить скрипт config.php. Помимо подключения файла с настройками, в index.php нужно создать подключение к базе данных, подключить скрипт с ядром сайта и запустить роутер, в котором будет происходить маршрутизация. Теперь по порядку, создание соединения с базой данных будет находиться в index.php для того, чтобы соединение открывалось только один раз. Единожды открыв соединение, мы сможем использовать его во всех контроллерах и моделях, но об этом чуть позже. Сейчас просто создадим соединение с базой. Для работы с бд я решил использовать PDO. Подробнее почитать про PDO можно тут. Ядро сайта расположим в папке core и назовем скрипт core.php, тут мы напишем функцию, которая будет сама подключать, необходимы для работы классы. Такая функция очень облегчит и упростит нам работу с контролерами, моделями и тд. Поскольку, забегая вперед скажу, что каждый контролер и каждая модель будут представлять собой отдельный класс. Помимо авто подключения классов, добавим в ядро создания хранилища (реестра), в котором будем хранить все необходимые объекты и переменные, которые могут пригодиться в любом месте проекта. Роутер тоже подключим в индексном файле, он будет анализировать URL и подключать необходимый контроллер и экшен. Что такое контролер я писал в предыдущей статье, а информацию про экшен я пропустил умышленно, не став нагружать лишней информацией. Так что же такое экшен? Контролер это класс, в котором заключены различные методы, при MVC подходе каждый метод будет являться экшеном. То есть экшен(action) – это метод класса, который будет обрабатывать данные и передавать их в представление (в шаблон). Может быть, пока не совсем понятно, но после примера все станет на свои места. На данном этапе теории достаточно, давайте перейдем к практике. Приведу код файлов, работу которых, я описывал выше. Код скрипта index.php:
// включим отображение всех ошибок
error_reporting (E_ALL);
// подключаем конфиг
include ('/config.php');
// Соединяемся с БД
$dbObject = new PDO('mysql:host=' . DB_HOST . ';dbname=' . DB_NAME, DB_USER, DB_PASS);
// подключаем ядро сайта
include (SITE_PATH . DS . 'core' . DS . 'core.php');
// Загружаем router
$router = new Router($registry);
// записываем данные в реестр
$registry->set ('router', $router);
// задаем путь до папки контроллеров.
$router->setPath (SITE_PATH . 'controllers');
// запускаем маршрутизатор
$router->start();
Скрипт core.php:
// Загрузка классов "на лету"
function __autoload($className) {
$filename = strtolower($className) . '.php';
// определяем класс и находим для него путь
$expArr = explode('_', $className);
if(empty($expArr[1]) OR $expArr[1] == 'Base'){
$folder = 'classes';
}else{
switch(strtolower($expArr[0])){
case 'controller':
$folder = 'controllers';
break;
case 'model':
$folder = 'models';
break;
default:
$folder = 'classes';
break;
}
}
// путь до класса
$file = SITE_PATH . $folder . DS . $filename;
// проверяем наличие файла
if (file_exists($file) == false) {
return false;
}
// подключаем файл с классом
include ($file);
}
// запускаем реестр (хранилище)
$registry = new Registry;
Класс хранилища Registry.php, будет находиться в папке /classes/
// Класс хранилища
Class Registry {
private $vars = array();
// запись данных
public function set($key, $var) {
if (isset($this->vars[$key]) == true) {
throw new Exception('Unable to set var `' . $key . '`. Already set.');
}
$this->vars[$key] = $var;
return true;
}
// получение данных
public function get($key) {
if (isset($this->vars[$key]) == false) {
return null;
}
return $this->vars[$key];
}
// удаление данных
public function remove($key) {
unset($this->vars[$key]);
}
}
Код файла router.php, который находиться в папке /classes/
// класс роутера
Class Router {
private $registry;
private $path;
private $args = array();
// получаем хранилище
public function __construct($registry) {
$this->registry = $registry;
}
// задаем путь до папки с контроллерами
public function setPath($path) {
$path = trim($path, '/\\');
$path .= DS;
// если путь не существует, сигнализируем об этом
if (is_dir($path) == false) {
throw new Exception('Invalid controller path: `' . $path . '`');
}
$this->path = $path;
}
// определение контроллера и экшена из урла
private function getController(&$file, &$controller, &$action, &$args) {
$route = (empty($_GET['route'])) ? '' : $_GET['route'];
unset($_GET['route']);
if (empty($route)) {
$route = 'index';
}
// Получаем части урла
$route = trim($route, '/\\');
$parts = explode('/', $route);
// Находим контроллер
$cmd_path = $this->path;
foreach ($parts as $part) {
$fullpath = $cmd_path . $part;
// Проверка существования папки
if (is_dir($fullpath)) {
$cmd_path .= $part . DS;
array_shift($parts);
continue;
}
// Находим файл
if (is_file($fullpath . '.php')) {
$controller = $part;
array_shift($parts);
break;
}
}
// Если в URL не указан контролер, то испольлзуем по умолчанию index
if (empty($controller)) {
$controller = 'index';
}
// Получаем экшен
$action = array_shift($parts);
if (empty($action)) {
$action = 'index';
}
$file = $cmd_path . $controller . '.php';
$args = $parts;
}
public function start() {
// Анализируем путь
$this->getController($file, $controller, $action, $args);
// Проверка существования файла, иначе 404
if (is_readable($file) == false) {
die('404 Not Found');
}
// Подключаем файл
include ($file);
// Создаём экземпляр контроллера
$class = 'Controller_' . $controller;
$controller = new $class($this->registry);
// Если экшен не существует - 404
if (is_callable(array($controller, $action)) == false) {
die('404 Not Found');
}
// Выполняем экшен
$controller->$action();
}
}
Теперь необходимо создать папки для хранения контроллеров, шаблонов и моделей – в корне создадим три папки controllers, views и models. И создадим несколько тестовых файлов /controllers/index.php, /views/index/index.php и /models/model_users.php, а теперь заполним файлы: Для контроллера:
// контролер
Class Controller_Index extends Controller_Base {
// шаблон
public $layouts = "first_layouts";
// экшен
public function index() {
$model = new Model_Users();
$userInfo = $model->getUser();
$this->template->vars('userInfo', $userInfo);
$this->template->view('index');
}
}
Для отображения(/views/index/index.php)
Test view <br/>
id: <?=$userInfo['id'];?><br/>
name: <?=$userInfo['name'];?>
И модель:
// модель
Class Model_Users {
public function getUser() {
return array('id' => 1, 'name' => 'test_name');
}
}
Как вы могли заметить, класс контролера наследуется от родительского класса Controller_Base. Это сделано, для того, чтобы упростить класс контролера. Поскольку нам еще необходимо подключать класс для работы с шаблонами, его подключение вынесено в Controller_Base. Приведу его код, он расположен в папке /classes/ и называется controller_base.php :
// абстрактый класс контроллера
Abstract Class Controller_Base {
protected $registry;
protected $template;
protected $layouts; // шаблон
public $vars = array();
// в конструкторе подключаем шаблоны
public function __construct($registry) {
$this->registry = $registry;
// шаблоны
$this->template = new Template($this->layouts, get_class($this));
}
abstract function index();
}
Теперь осталось только разобраться с шаблонами. В абстрактном классе Controller_Base мы вызываем класс Template и передаем ему имя шаблона и имя контроллера. Код класса Template, который лежит тут /classes/ и называется template.php
// класс для подключения шаблонов и передачи данных в отображение
Class Template {
private $template;
private $controller;
private $layouts;
private $vars = array();
public function __construct($layouts, $controllerName) {
$this->layouts = $layouts;
$arr = explode('_', $controllerName);
$this->controller = strtolower($arr[1]);
}
// установка переменных, для отображения
public function vars($varname, $value) {
if (isset($this->vars[$varname]) == true) {
trigger_error('Unable to set var `' . $varname . '`. Already set, and overwrite not allowed.', E_USER_NOTICE);
return false;
}
$this->vars[$varname] = $value;
return true;
}
// отображение
public function view($name) {
$pathLayout = SITE_PATH . 'views' . DS . 'layouts' . DS . $this->layouts . '.php';
$contentPage = SITE_PATH . 'views' . DS . $this->controller . DS . $name . '.php';
if (file_exists($pathLayout) == false) {
trigger_error('Layout `' . $this->layouts . '` does not exist.', E_USER_NOTICE);
return false;
}
if (file_exists($contentPage) == false) {
trigger_error('Template `' . $name . '` does not exist.', E_USER_NOTICE);
return false;
}
foreach ($this->vars as $key => $value) {
$$key = $value;
}
include ($pathLayout);
}
}
Если вы внимательно прочитали код, то наверняка поняли, что для отображения на страницах у нас используется шаблон first_layouts и вьюха(отображение) index.php – ее код я приводил чуть выше. Все что нам осталось, это создать файл шаблона first_layouts. Расположим его в папке /views/layouts/first_layouts.php Шаблон будет содержать вот такой код:
<h1> header </h1>
<?php
include ($contentPage);
?>
<h1> footer </h1>
Вот и все, на этом создание «каркаса» закончено. Сейчас у нас получилась самая простая структура, использующая в своей основе паттерн MVC.
Оригинал статьи: vk-book.ru
|