Простая система роутинга для сайта на PHP | BLB - Big little Business

Простая система роутинга для сайта на PHP

PAKETA_TELEGRAM

Administrator
Команда форума
Администратор
03.04.2020
337
561
103
rutube.ru
Всем доброго времени суток!
Хочу рассказать о принципах написания собственного простого роутинга на PHP сайте. Пример может быть не сильно эффективен, но сделан лишь для того, чтобы показать принцип.

Итак, роутинг происходит в несколько этапов:
  1. Пересылка запрашиваемого URL в файл-роутер
  2. Разбиение URL на части
  3. Сравнение путей роутинга с базовой частью URL
  4. Обработка остальных частей внутри блока с нужным путём (с п.2)
  5. Выдача требуемой страницы
На практике, в первую очередь необходимо определиться со структурой проекта, в моём случае это выглядит как-то так:
  • /config/config.php
  • /routes/index.php
  • /routes/login.php
  • /index.php
Сначала я приведу пример кода, а затем объясню его для того. чтобы тем, кому нужен только код не было скучно.
/config/config.php
PHP:

<?php
return (object) [
'ROOT' => 'здесь путь к рут директории',
];

/index.php
PHP:
<?php
$CONFIG = require_once('config/config.php');

class Route
{
public string $path;
public var $callback;

public function Route(string $path, $callback)
{
$this->$path = $path;
$this->$callback = $callback;
}

public function Call(array $arg_v = [])
{
if(empty($arg_v))
return $callback();
else
return $callback($arg_v);
}

public function CallIf(string $path)
{
$out = [];
if(!strpos($path, '?'))
{
if($this->$path != $path)
return false;
}else{
$arg_pos = strpos($path, '?');
if($this->path != substr($path, 0, $arg_pos))
return false;
$out = Router::GetArgs($path) || [];
}
return $this->Call($out);
}

public function destroy()
{
unset($this);
}
}

class Router
{
public $routes = [];

public function Route(string $path, $callback)
{
if(isset($routes[$path]))
return false;
$routes[$path] = new Route($path, $callback);
return true;
}

public function Execute(){
if(!isset($_GET['page']))
exit($this->Call('/'));

$page = parse_url(strip_tags($_GET['page']));
exit($this->Call($page['path']));
}


public function Call(string $path)
{
if(isset($routes[$path]))
return $routes[$path]->Call();
foreach ($routes as $route) {
if($ret = $route->CallIf($path))
return $ret;
}
return require_once $CONFIG->ROOT.'/routes/index.php';
}


public static GetArgs(string $path){
$result = [];
preg_match_all(
'/[?&]([^=]+)=([^&]+)/',
substr($path, strpos('?', $path)),
$result
);
$result['out'] = [];
foreach($result[1] as $key => $arg_n)
$result['out'][$arg_n] = $result[2][$key];
return (object) $result['out'];
}

public function destroy(){
foreach ($routes as $route)
$route->destroy();
unset($this);
}
}


$router = new Router();

$router->Route('/', function(){
return require_once($CONFIG->ROOT.'/routes/index.php');
});
$router->Route('/login', function($args = []){
return require_once($CONFIG->ROOT.'/routes/login.php');
});

$router->Execute();
?>
/routes/index.php

HTML:
<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Главная страница</title>
</head>
<body>
<!-- ТЕЛО СТРАНИЦЫ -->
</body>
</html>
/routes/login.php

HTML:
<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Вход на сайт</title>
</head>
<body>
<!-- ТЕЛО СТРАНИЦЫ -->
</body>
</html>
Итак, файл config.php содержит конфигурации, в данном случае только путь к корню папки.
Оба файла в папке routes это страницы, которые отображаются в зависимости от запроса.
И файл index.php содержит класс маршрутизатора (роутера) и маршрута (роута):
PHP:
$CONFIG = require_once('config/config.php');
Эта строка создаёт переменную с конфигурациями из файла config.php
Далее объявляется класс маршрута, он содержит 2 поля: путь и callback.
Первое содержит url путь, необходимый для вызова callback'а, а второй это собственно он и есть.
Помимо конструктора внутри класса объявляется безусловный (Call) и условный (CallIf) вызов callback'а, а так же деструктор.
Call возвращает ответ callback'а с аргументами или без;
CallIf поступает так же, если $path соответствует пути маршрута (за исключением аргументов), иначе возвращает false;
PHP:
if(!strpos($path, '?'))
Проверка на содержание url-запросом get-аргументов
PHP:
if($this->$path != $path)
return false;
Если пути без аргументов не соответствуют, то вернуть false
PHP:
$arg_pos = strpos($path, '?');

if($this->path != substr($path, 0, $arg_pos))
return false;
Если аргументы есть, то сохранить позицию их начала и проверить на соответствие пути без аргументов, при несоответствии вернуть false.
PHP:
$out = Router::GetArgs($path) || [];
Получить аргументы в виде словаря
PHP:
return $this->Call($out);
Вернуть результат выполнения безусловного вызова с аргументами

Далее, в файле описывается класс Router для работы с маршрутами
Поле $routes содержит массив экземпляров класса Route для различных запросов
Функция Route добавляет в список новый Route, если Route с таким же маршрутом не существует и возвращает true, иначе возвращает false.
Функция Execute запускает роутинг, а именно считывает get-аргумент page, содержащий url путь, если он существует и выполняет нужный маршрут, иначе открывает страницу index.php
Функция Call возвращает результат вызова Call или CallIf класса Route, найденного в списке по запрошенному url
Функция GetArgs возвращает get-артибуты в виде словаря

Далее идёт основная часть кода
PHP:
$router = new Router();
Создание нового экземпляра Router'а
PHP:
$router->Route('/', function(){
return require_once($CONFIG->ROOT.'/routes/index.php');
});

$router->Route('/login', function($args = []){
return require_once($CONFIG->ROOT.'/routes/login.php');
});
Добавляем маршруты в список
PHP:
$router->Execute();
Запускаем маршрутизацию

Также, для корректной работы необходимо изменить или создать файл .htaccess
В котором необходимо прописать переадресацию со всех запросов на этот скрипт
Код:
RewriteEngine On
RewriteCond %{REQUEST_URI} !^/some-directory/
RewriteCond %{DOCUMENT_ROOT}/some-directory%{REQUEST_URI} -f [OR]
RewriteCond %{DOCUMENT_ROOT}/some-directory%{REQUEST_URI} -d
RewriteRule ^(.*)$ /index.php?page=$1 [L]
Это должно выглядеть как-то так

Итак, теперь htaccess пересылает запросы вида site.ru/site в site.ru?page=site
index.php подключает конфигурации, настраивает роутинг и запускает его, Router ищет такие же маршруты, если нашёл вызывает Route
Подключается нужная страница с аргументами
 
  • Like
Реакции: Halfenok