PHP MVC
Контроллер и Router
КОНТРОЛЛЕР (class NewsController)
Типичная задача веб-программирования - информационный раздел (статьи, новости, книги и др.)
Этот раздел состоит из двух частей:
1 - список записей,
2 - просмотр одной записи.
Расмотрим эти две задачи, для этого - добавим два экшена в наш контроллер, который будет обрабатывать эти страницы.
Допустим это контроллер - NewsController.php и экшены - actionIndex и actionView.
Файл NewsController.php
-- файл NewsController.php --
<?php
class
{
public function ()
{
echo 'Список новостей';
return true;
}
public function ()
{
echo 'Просмотр одной новости';
return true;
{
}
?>
Для того, чтобы эти экшены заработали, нам нужно сделать определенные записи в роуты (routes.php)
первая запись (просмотр одной новости):
'news/77' => 'news/view'
'news/15' => 'news/view'
вторая запись (список новостей):
'news' => 'news/index', // actionIndex в NewsController
Так, как наш класс Router по очереди идет по этому массиву, и применяет первое правило в котором совпадает запрос, правила 'news/77' => 'news/view' и 'news/15' => 'news/view'
- должны расположить выше, для того, чтобы они могли заработать.
Новостей на сайте может быть сотни и тысячи, поэтому писать правила для каждой новости нецелесообразно.
Для решения этой задачи нужны регулярные выражения.
Вместо индификатора новости напишем регулярное выражение. Правило будет выглядеть примерно вот так:
'news/([0-9]+)' => 'news/view'
В нашем actionView мы собираемся получить одну новость из БД и отобразить ее на странице.
Для этого нам нужен ее индификатор, нам нужно его получить, и для этого нам нужно модифицировать наш роутер (Router.php).
Передача параметров при использовании ЧПУ ( Человеко-понятный URL).
Не ЧПУ:
Ранее мы передавали параметры в строке запроса, при этом на сервере они попадали в суперглобальный массив $_GET:
http://test2/news/?id-1235&category=sport -> $_GET
и мы могли их получить по ключам:
$_GET['id']
$_GET['category']
ЧПУ:
Особенностью использования ЧПУ состоит в том, что мы будем использовать адреса такого вида:
http://test2/news/sport/1235
Они красивее и понятней для пользователя, но менее удобней для программистов.
При такой записи строки запроса данные не будут попадать в массив $_GET.
Будем использовать функции работы со строками, чтобы получить данные из строки запроса.
Расмотрим этот процесс на примере адреса : http://test2/news/sport/1235
Для него запишем определенный маршрут в наших роутах:
'news/([a-z]+)/([0-9]+)' => 'news/view/$1/$2',
В этом адресе есть два параметра: ([a-z]+) и ([0-9]+), которые соответствуют категории и индификатору .
Мы получим эти параметры и передадим их методу класса (экшену).
Все это мы сделаем в классе Router, так как именно там происходит обработка запроса.
Для этого нужно выполнить следующие три шага:
Шаг первый:
Нам нужно из нашего запроса, к примеру :
// news/sport/114 ,
вырезать два параметра
sport и 114
и подставить на места ссылок
$1 и $2,
это нужно для того, чтобы передать их на нужный экшен.
Это сделаем при помощи кода в классе Router:
-- файл Router.php --
// Получаем внутренний путь из внешнего согласно правилу
$internalRoute = preg_replace("~$uriPattern~", $path, $uri);
echo '<br><br>Нужно сформировать: ' . $internalRoute;
- При помощи функции preg_replace:
в строке запроса $uri (news/sport/114) мы ищем параметры -
- sport и 114 по определенному шаблону $uriPattern,
который у нас содержится в routes.php:
- ([a-z]+)/([0-9]+);
Если мы находим эти параметры, то в строку $path , которая описывается таким образом:
- $1/$2,
мы их подставляем, ($internalRoute вместо $path)
в итоге мы получим, так называемый, внутренний маршрут:
news/view/sport/114
Используя этот внутренний путь, мы можем узнать:
Контроллер: news
Экшен: view
И получить из строки параметры: sport и 114,
которые потом мы будем передавать в экшен
Шаг второй:
Определяем контроллер, action, параметры
Контроллер и экшен мы определяем из старого кода:
-- файл Router.php --
// Если есть совпадение, определить какой контроллер и action обрабатывают запрос
$segments = explode('/', $internalRoute);// $internalRoute вместо $path
// получаем имя контроллера
$controllerName = array_shift($segments) . 'Controller';
$controllerName = ucfirst($controllerName);
// получаем название экшена
$actionName = 'action' . ucfirst(array_shift($segments));
Далее, в массиве ($segments) останутся параметры.
Мы специально использовали функцию array_shift(), которая получает из массива название контроллера и экшена, и удаляет его.
В итоге, в массиве ($segments) останутся только параметры:
-- файл Router.php --
echo '<br>controller name: ' . $controllerName ;
echo '<br>action name: ' . $actionName ;
$parameters = $segments;// массив с параметрами
echo '<pre>';
print_r($parameters);
die ;
Шаг третий:
Вызвать экшен с параметрами.
Для этого есть все необходимые данные:
название контроллера: $controllerName
название экшена: $actionName
и массив с параметрами: $parameters
Этот массив ($parameters) мы можем просто передать нашему экшену, используя ранее написаный код:
-- файл Router.php --
// передаем массив с параметрами($parameters) - нашему экшену
$result = $controllerObject -> $actionName($parameters);
При этом, для того, чтобы получить параметры в нашем экшене (actionView() из класса NewsController)-
- мы также будем использовать массив $params , обращаясь к его элементам:
-- файл NewsController.php --
public function actionView($params)// параметром - массив $params
{
echo '<br><br>' . $params[0];
echo '<br>' . $params[1];
echo '<br>' . $params[2];
// - если в строке запроса добавить еще некоторые некоторые элементы,
// они бы тоже появились в нашем массиве и мы могли бы их распечатать
return true;
}
- соответственно номер элемента в массиве, будет отвечать его позиции в запросе.
Если здесь мы добавили еще некоторые элементы в запросе, они бы тоже появились в нашем массиве и мы могли бы их распечатать
Это один способ.
Здесь совершенно не нравятся названия параметров(какой-то массив($params), с каким-то индексом [0]).
Если вдруг забыть порядок следования параметров в запросе, то не будем знать к какому элементу обратиться.
Поэтому в роутере (Router.php) можно использовать функцию: call_user_func_array():
- $result = call_user_func_array(array($controllerObject, $actionName), $parameters );
Эта функция вызывает экшен с именем, которое содержится в переменной $actionName,
у объекта - $controllerObject, при этом передает ему массив с параметрами ($parameters)
В принципе, практически, одно и то-же.
Разница состоит лишь в том, как эти параметры будут переданы в наш экшен.
В этом случае они будут переданы как переменные и доступ к ним мы можем получать через переменные:
-- файл NewsController.php --
// при использовании функции: call_user_func_array() в классе Router
public function actionView($category, $id)
{
echo '<br>' . $category;
echo '<br>' . $id;
return true;
}
Проверим этот способ - тоже работает, но теперь мы можем использовать красивые, нормальные имена для переменных.
Файл routes.php
-- файл routes.php --
<?php
// маршруты
return array
(
'news/([a-z]+)/([0-9]+)' => 'news/view/$1/$2',
'news/([0-9]+)' => 'news/view', // actionView в NewsController
'news' => 'news/index', // actionIndex в NewsController
'products' => 'product/list', // actionList в ProductController
);
// - где 'news' - строка запроса
// 'news/index' - имя контроллера и экшена для обработки этого запроса (путь обработчика)
?>
Файл NewsController.php
-- файл NewsController.php --
<?php
class
{
public function ()
{
echo 'Список новостей';
return true;
}
/*
public function actionView($params)
{
// echo '<br><br>Просмотр одной новости';
echo '<br><br>' . $params[0];
echo '<br>' . $params[1];
echo '<br>' . $params[2];
// - если в строке запроса добавить еще некоторые некоторые элементы
// они бы тоже появились в нашем массиве и мы могли бы их распечатать
return true;
}
*/
public function ($category, $id)
{
echo '<br>' . $category;;
echo '<br>' . $id ;
return true;
}
}
?>
Router
Файл Router.php
-- файл Router.php --
<?php
class
{
// массив маршрутов
private $routes;
public function ()
{
// указываем путь к роутам (ROOT - путь к базовой дериктории,
// '/config/routes.php' - путь к созданному файлу с роутами
$routesPath = . '/config/routes.php';
// здесь мы присваиваем свойству $this->routes массив,
// который хранится в файле routes.php, при помощи - include
$this->routes = include($routesPath);
}
// 1. Получаем строку запроса
// метод возвращает строку запроса
private function ()
{
if(!empty($_SERVER['REQUEST_URI']))
{
return ($_SERVER['REQUEST_URI'], '/');
}
}
// метод будет принимать управление от фронтконтроллера
// получаем значение URI
public function ()
{
// проверяем наше свойство $this->routes
// выведет массив: Array ( [news] => news/index [products] => products/list )
//print_r($this->routes);
// проверка (после запуска из файла index.php получим:
// Class Router, method ran)
//echo 'Class Router, method ran';
// Получаем строку запроса:
//if(!empty($_SERVER['REQUEST_URI']))
//{
// $uri = trim($_SERVER['REQUEST_URI'], '/');
//}
// проверка (в строке запроса добавим: /news)
// echo $uri;
// обратимся к методу getURI() (этот метод возвращает строку запроса)
$uri = $this-> ();
//echo $uri;
// 2. Ищем строку запроса($uri) в наших маршрутах в цикле foreach:
foreach($this->routes as $uriPattern => $path)
{
//проверка: echo "<br>$uriPattern -> $path";
// Сравниваем строку запроса ($uri) и данные,
// которые содержатся в роутах ($uriPattern)
// для этого передаем в preg_match() строку запроса $uri и
// данные из наших роутов ('news' или 'products')
if( ("~$uriPattern~", $uri))
{
/*
// 11111111111111111111
// проверка: если есть совпадение, то выведет +
//echo '+';
// проверка: если есть совпадение, то выведет путь из routes.php
//echo $path;
// 3. определяем какой контроллер и action обрабатывают запрос
$segments = explode('/', $path);
// echo '<pre>';
// print_r($segments);
// echo '</pre>';
// 3 - 1. Получаем имя контроллера:
// Получаем имя контроллера:
$controllerName = array_shift($segments) . 'Controller';
// делает первую букву строки заглавной
$controllerName = ucfirst($controllerName);
//echo $controllerName;
// 3 - 2. Точно также получаем название экшена:
// Точно также получаем название экшена:
$actionName ='action' . ucfirst(array_shift($segments));
//echo '<br>' . $actionName;
// в итоге получаем:
// echo '<br>Класс: ' . $controllerName; // имя класса
// echo '<br>Метод: ' . $actionName; // имя метода
// 4. Подключаем файл класса-контроллера:
// Определяем путь к файлу, который нужно подключить:
$controllerFile = ROOT . '/controllers/' . $controllerName .'.php';
// проверяем: если такой файл существует, то подключаем его
if(file_exists($controllerFile))
{
include_once($controllerFile);
}
// 5. Создаем объект и вызваем метод (т.е. action)
// создаем объект класса контроллера
$controllerObject = new $controllerName;
// для этого объекта мы вызываем метод
$result = $controllerObject -> $actionName();
// если метод сработал, то мы можем об этом узнать,
// следовательно - оборвать цикл foreach()
if($result != null)
{
break ;
}
// 11111111111111111111
*/
// 22222222222222222222
// echo '<br>Где ищем (запрос, который набрал пользователь): ' . '$uri';
// echo '<br>Что ищем (совпадение из правила):' . '$uriPattern';
// echo '<br>Кто обрабатывает :' . '$path';
// Получаем внутренний путь из внешнего согласно правилу
$internalRoute = ("~$uriPattern,~", $path, $uri);
// echo '<br><br>Нужно сформировать: ' . $internalRoute;
// Определяем контроллер, action, параметры
// Контроллер и action мы определяем с помощью старого кода
// ($internalRoute вместо $path)
// Если есть совпадение, определить какой контроллер и
// action обрабатывают запрос
$segments = ('/', $internalRoute);
// Получаем имя контроллера:
$controllerName = ($segments) . 'Controller';
// делает первую букву строки заглавной
$controllerName = ($controllerName);
// Точно также получаем название экшена:
$actionName = 'action' . ( ($segments));
// в итоге получаем:
// имя класса:
// echo '<br><br>Controller name: ' . $controllerName;
// имя метода
// echo '<br>action name: ' . $actionName;
// В итоге, в массиве ($segments) останутся только параметры
// массив с параметрами:
$parameters = $segments;
// echo '<pre>';
// print_r($parameters);
// Подключаем файл класса - контроллера
// Определяем путь к файлу, который нужно подключить:
$controllerFile = . '/controllers/' . $controllerName . '.php';
// проверяем: если такой файл существует, то подключаем его
if( ($controllerFile))
{
include_once ($controllerFile);
}
// создаем объект класса контроллера
$controllerObject = new $controllerName;
// для этого объекта мы вызываем метод (action)
// массив ($parameters) мы можем просто передать нашему экшену,
// используя ранее написаный код:
//$result = $controllerObject -> $actionName($parameters);
// используем функцию: call_user_func_array()
// Эта функция вызывает экшен с именем,
// которое содержится в переменной $actionName,
// у объекта - $controllerObject,
// при этом передает ему массив с параметрами ($parameters)
$result = (array($controllerObject, $actionName), $parameters );
(array($controllerObject, $actionName));
die;
// 22222222222222222222
}
}
}
}
?>
Наверх Наверх