Всем доброго времени суток. Сегодня мы с Вами начнем делать роутинг системы RS-MINI. Что такое роутинг? В понимании нашей CMS — это алгоритм позволяющий по данным, переданным через адресную строку, найти в таблице rs_map (которую мы создали в предыдущем посте) открываемую страницу. В случае если такая страница была не найдена, выдать пользователю ошибку 404 (пока очень примитивную). Если же такая страница есть в базе данных, то необходимо будет определить какие именно контроллеры подключены к данной странице, собрать их и запустить (это мы будем делать не сегодня, это так, для справки )
Для начала нам понадобится сам алгоритм поиска открытой страницы (map.class.php). Так же мы напишем небольшой метод в классе application.class.php выдачи 404 ошибки (как уже говорил очень примитивной)
Давайте начнем.
Нам понадобится метод __call() (про него мы уже говорили, и надеюсь понимаем как он работает ) и два метода
Методы само собой задокументированы, но пояснить, в двух словах, логику считаю обязательным. Но для начала давайте я выложу Вам код, а ниже поясню некоторые моменты. (файл лежит в папке /rs-mini/core/ и называется map.class.php)
<?php
/*
* @package RS-MINI
* @copyright (c) 2015 Alexey Glumov aka Rio-Shaman (support@rio-shaman.ru)
* @license GNU General Public License version 2; see LICENSE.txt
*
*/
namespace core;
if(!defined('RS-MINI')) die();
/*
* map v 1.0
*
* класс работает с структурой сайта (роутинг)
*
*/
class map
{
/*
* хранит значение посланные в метод __call()
*
* @var - массив
* @access - private
*/
private $call = array();
/*
* массив c страницами
*
* @var - array
* @access - public
*/
public $pageList;
/*
* метод позволяем вызвать несуществующие методы класса
*
* @access - public
* @return - сам себя или сохраненное значение (все зависит от
* того set или get метод был вызван)
*
* @param string name - имя вызываемого метода
* @param array arguments - присланные аргументы в вызываемый метод
*/
public function __call($name, $arguments)
{
$name = mb_strtolower($name);
$action = mb_substr($name, 0, 3);
$argument = mb_substr($name, 3, mb_strlen($name)-1);
switch($action) {
case 'get':
return (isset($this->call[$argument])) ? $this->call[$argument] : null;
break;
case 'set':
$this->call[$argument] = $arguments[0];
return $this;
break;
}
}
/*
* получаем шаблон, все контроллеры и их параметры открываемой страницы
*
* @access - public
* @return - массив с данными или false если ничего не найдено
*/
public function getPage()
{
// получаем адрес страницы
$uri = request::getHttpServer('REQUEST_URI')->toUri();
// если адрес равен null выкидываем false
if (is_null($uri))
return FALSE;
// просто бьем строку на массив
$uri = explode('/', $uri);
// удаляем последний элемент массива, так как он всегда пустой
unset($uri[(count($uri) - 1)]);
// получаем все открываемые узлы
// родитель по умолчанию NULL (главная страница)
$parent = NULL;
foreach ($uri as $key => $alias) {
// получаем страницу
if (!$page = $this->setAlias($alias)->setParent($parent)->getNode())
return FALSE;
// запоминаем ID страницы, это наш родитель в следующей итерации
$parent = $page['map_id'];
// создаем объект страницы
$this->pageList[] = new page($page);
}
return TRUE;
}
/*
* метод получения узла определенной ветки
*
* @access - private
* @return - массив с структурой сайта или false если ничего не найдено
*
* @param integer getParent() - родитель предшествующего узла
* @param string getAlias() - алиас узла
*/
private function getNode()
{
// с начало пытаемся получить страницу как статичную
$sql = ''
. 'SELECT'
. ' m.map_id AS map_id,'
. ' m.map_parent AS map_parent,'
. ' m.map_alias AS map_alias,'
. ' m.map_name AS map_name,'
. ' m.map_view AS map_view,'
. ' m.map_system AS map_system,'
. ' m.map_type AS map_type'
. ' FROM'
. ' rs_map AS m'
. ' WHERE'
. ' m.map_parent ' . ( (is_null($this->getParent())) ? 'IS NULL' : '= \'' . $this->getParent() . '\'' )
. ' AND m.map_alias ' . ( ($this->getAlias() == '') ? 'IS NULL' : '= \'' . $this->getAlias() . '\'' )
. ' AND m.map_type = \'static\''
;
// если выборка дала результат
if ($resultList = sql::query($sql)->fetch()) {
// запоминаем алиас динамичного узла.
// так как узел статичный, то мы записываем туда NULL
$resultList[0]['dynamic_alias'] = NULL;
// возвращаем результат
return $resultList[0];
}
// если статичный узел был не найден, пытаемся найти динамичный
$sql = ''
. 'SELECT'
. ' m.map_id AS map_id,'
. ' m.map_parent AS map_parent,'
. ' m.map_alias AS map_alias,'
. ' m.map_name AS map_name,'
. ' m.map_view AS map_view,'
. ' m.map_system AS map_system,'
. ' m.map_type AS map_type'
. ' FROM'
. ' rs_map AS m'
. ' WHERE'
. ' m.map_parent ' . ( (is_null($this->getParent())) ? 'IS NULL' : '= \'' . $this->getParent() . '\'' )
. ' AND m.map_type = \'dynamic\''
;
// если выборка не дала результата
if (!$resultList = sql::query($sql)->fetch())
// вертаем false
return FALSE;
// запоминаем динамичный алиас (реально записанный в БД)
$resultList[0]['dynamic_alias'] = $resultList[0]['map_alias'];
// перезаписываем псевдоним на тот что в адресной строке
$resultList[0]['map_alias'] = $this->getAlias();
// возвращаем результат
return $resultList[0];
}
}
Давайте начнем с метода getPage(). В общем-то портянка кода не имеет ничего экстраординарного. Давайте разберем на пример открытия страницы /about/feedback/. При этом заведома уточним что все эти страницы есть в базе и у
Приступим. Первая строчка кода получит нам вот такие данные (запишется в переменную uri):
/about/feedback/
Вы могли обратить внимания что эти данные получаются через класс request метод toUri(). Если помните там были проверки на корректность урла, поэтому беспокоится о каких либо переданных опасных символах тут нам не приходится если было передано что-то запрещенное, то метод toUri() вернет нам NULL, а если в переменно uri будет NULL, то метод getPage() закончит свою работу вернув false (а этот результат спровоцирует показ 404-ой ошибки)
Далее значение в переменно uri бьется на массив, тем самым мы получаем вот такие данные
array(4) {
[0] => string(0) ""
[1] => string(5) "about"
[2] => string(8) "feedback"
[3] => string(0) ""
}
Как видите в начале пустой элемент массива и в конце. Это из-за того что и в начале и в конце есть слеши. От последнего элемента массива мы принудительно избавляемся, так как наши урлы по задумке всегда заканчиваются на слеш, а значит последний элемент массива всегда пуст. Получаем вот такой массив
array(3) {
[0] => string(0) ""
[1] => string(5) "about"
[2] => string(8) "feedback"
}
Далее мы назначаем родителя страницы по умолчанию. У главной страницы ведь нет родителя, и в базе данных поле map_parent будет иметь значение NULL, стало быть стартовое значение родителя должно быть тоже NULL.
Теперь запускаем цикл, и листаем псевдонимы страниц переданные нам через адресную строку.
В нашем примере первый псевдоним будет пустота (смотрите первый элемент массива uri) У главной странице псевдонима тоже нет, так что передовая в метод getNode() (о нем чуть ниже) родителя с значением NULL и псевдоним с значением «пусто» мы должны получить главную страницу сайта. После успешно запроса мы сохраняем в переменной parent id только что полученной страницы и поднимем объект страницы (класс page.class.php) в свойстве pageList
В следующей итерации псевдоним уже будет равен about а id родителя единицы. Такая страница у нас тоже имеется, поэтому результат будет утвердительным. Сохраняться новые данные (родитель теперь должен равняться id страницы about, то есть двум), и запустится последняя итерация с псевдонимом feedback.
Если хотя бы в одной из итераций страница не будет найдена, это приведет к 404-ой ошибке.
Этот метод получает из базы данных страницу с переданными параметрами. Тут имеются два скуль запроса, почему именно два? Объясняется это тем, что у нас имеются два вида старниц (мы о них уже говорили)
По приоритету запрашивается с начало статичная страница, используя все переданные параметры (и родитель и псевдоним). Если запрос был удачным то метод возвращает выборку в метод getPage() но перед этим создает еще один элемент массива — dynamic_alias. При первом удачном запросе этот элемент будет содержать NULL.
Если же запрос прошел не удачно, система попробует найти первую попавшуюся динамичную страницу. При этом будет использован лишь родитель, опуская при этом значение псевдонима. При успешном втором запросе, в элемент массива map_alias бедет передан запрашиваемый псевдоним (взятый из адресной строки) а в элемент dynamic_alias будет сохранен реальный псевдоним. Очень сложно объяснить зачем это все нужно, но я все же попробую.
Я уже не раз говорил что динамичные страницы понадобятся нам в основном для реализации редакторов. А именно что бы по адресу /post/123/edit/ нам открывалась статья чей id равен 123. Как контроллер узнает что ему нужно вытащить именно статью с таким id, а не с другим? Он должен запросить значение 123 из адресной строки. Вот тут возникает технический вопрос, а как именно он это сделает?
Как раз на этот случай и необходим этот самый элемент массива dynamic_alias. Значение хранящиеся тут будет передано в объект класса page.class.php, а уже после в параметры контроллера который должен отработать на странице edit. То есть контроллеру будут заранее известны все динамические страницы которые есть в адресной строке (в нашем примере такая страница только одна)
Запрашивать id из строки контроллер сможет по реальному псевдониму динамичной страницы. То есть если в базе есть три вложенных узла
а в адресной строке запрашивалась страницы /post/123/edit/ то под ключом post_id в спец массиве будет лежать значение 123. Надеюсь понятно о чем я (ну если не понятно, мы еще вернемся к этому вопросу)
Ну что же, нам осталось подключить новый класс к приложению, и вызвать из него (из класса map.class.php) метод getPage(). Так же нам понадобится метод вывода 404-ой ошибки который будет содержать всего пару строк кода. Это временный метод, позднее мы будем подгружать специально подготовленную страничку. В общем вот новый код файла application.class.php (файл если не помните лежит в папке /rs-mini/core/)
<?php
/*
* @package RS-MINI
* @copyright (c) 2015 Alexey Glumov aka Rio-Shaman (support@rio-shaman.ru)
* @license GNU General Public License version 2; see LICENSE.txt
*
*/
namespace core;
if(!defined('RS-MINI')) die();
/*
* application v 1.0
*
* Класс стартует систему
*
*/
class application
{
/*
* объект с картой сайта
*
* @var - object
* @access - private
*/
private static $map;
/*
* объект дома
*
* @var - object
* @access - private
*/
private static $dom;
/*
* главный метод. запуск системы
*
* @access - public
*
*/
public static function run()
{
// поднимаем некоторые классы ядра
// объект по работе с структурой сайта
self::$map = new map();
// объект прослойка между контроллерами и вьюшкой
self::$dom = new dom();
// стартуем подключение к БД
sql::setConnect();
// получаем открываемую страницу
if (!self::$map->getPage())
self::notFound();
// тест работы роутинга
// просматриваем массив объектов страниц
?><xmp><?
var_dump(self::$map->pageList);
?></xmp><?
}
/*
* метод выводит 404 ошибку
*
* @access - private
*/
private static function notFound()
{
header('HTTP/1.0 404 Not Fount');
die('404 error');
}
}
В методе run() я разместил небольшой тест, он позволит увидеть массив с поднятыми объектами класса page.class.php.
Сейчас если запустить главную страницу нашего сайта, мы увидим ошибку 404. Почему так происходит? Все очень просто, у нас в базе данных нет ни одной созданной страницы, поэтому для того что бы протестировать нашу проделанную работу, необходимо создать хотя бы пару узлов.
Давайте протестируем оба типа страниц (я про статичные и динамичные типы). Для этого создадим главную (статичную) и вложим внутрь динамичную страницу. Вот sql запрос для этого:
INSERT INTO `rs_map`
(
`map_id`,
`map_parent`,
`map_alias`,
`map_name`,
`map_view`,
`map_system`,
`map_type`
)
VALUES
(
NULL,
NULL,
NULL,
'Главная',
'',
'true',
'static'
),
(
NULL,
'1',
'test_alias',
'Тест',
'',
'true',
'dynamic'
)
;
Теперь, если мы запустим главную страницу, то увидим вот такую картинку: (один поднятый объект класса page.class.php)
Если же перейти вот по такому http://mini.test.ru/123/, или такому адресу http://mini.test.ru/chtonibud/ (тут без разницы, ведь страница динамическая) то мы увидим уже два созданных объекта page, это говорит нам о том, что открыто две страницы.
Разницы между этими объектами пока никакой, но это пока
Надеюсь материал не очень сложный для восприятия. В конце статьи Вы можете скачать проделанную работу вместе с дампом базы данных.
Если у Вас возникли какие либо вопросы, то задавайте их в комментариях, постараюсь на них ответить
Всего Вам наилучшего, на сегодня все!
Не хочет присваивать значение, как быть?
У меня почему то поле в массив не хочет добавляться,
Вы переписывали класс /rs-mini/core/sql.class.php? У Вас явно результат функции mysqli_fetch_array() а не mysqli_fetch_assoc()
К тому же класс sql возвращает многомерный массив следующий структуры:
а у Вас
На главной странице у меня отражаются 2 поднятых объекта класса page. Вероятно потому что
Но почему то при
Я скачал ваши исходники и абсолютно ничего не менял, все равно выкидывает в localhost
Но раньше почему - то перекидывал на уровень выше, хотя редиректа нигда нету, и использовал я ваши файлы