Создание сайтов Эксклюзив Простая система роутинга для сайта на PHP

goodmice

Новорег
1
0
31 Май 2019
Всем доброго времени суток!
Хочу рассказать о принципах написания собственного простого роутинга на PHP сайте. Пример может быть не сильно эффективен, но сделан лишь для того, чтобы показать принцип.

Итак, роутинг происходит в несколько этапов:
  1. Пересылка запрашиваемого URL в файл-роутер
  2. Разбиение URL на части
  3. Сравнение путей роутинга с базовой частью URL
  4. Обработка остальных частей внутри блока с нужным путём (с п.2)
  5. Выдача требуемой страницы
На практике, в первую очередь необходимо определиться со структурой проекта, в моём случае это выглядит как-то так:
  • /config/config.php
  • /routes/index.php
  • /routes/login.php
  • /index.php
Сначала я приведу пример кода, а затем объясню его для того. чтобы тем, кому нужен только код не было скучно.
PHP:
<?php
return (object) [
    'ROOT' => 'здесь путь к рут директории',
];
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();
?>
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>
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
Подключается нужная страница с аргументами