Удобная и быстрая система маршрутизации на PHP

в 11:19, , рубрики: mvc, php, метки: , ,

Подходя к вопросу статического роутинга мы сталкиваемся с дилемой скорость или интуитивность.
Объектом стал сам роутер, а средвом маршруты.
Возможные варианты хранения:

YAML

+ Интуитивый
— Требуется кеширования (для повышения производительности)
— Требуется библиотека для парсинга (минус, поскольку в случаях лицензирования могут возникнуть трудности)

ini

+ Быстрый парсинг php
+ Отсутствие потребности в библиотеках
— Желательно все-же кешировать
— Далеко не интуитивный

PHP-file

Вот мы и подошли к самому важному. Но вопрос все же остается. Как хранить его?

$routes = array();
$routes['routeName'] = array(
'pattern' => '/my/home/page',
'controller' => 'homepage',
'action' => 'show'
);

Вариант? Но ведь не интуитивно. И не удобно считывать будет с такого файла, и многомерные массивы.
Да и вдруг кто-то случайно напишет не controller, a controIler. (Маленькая l и большая I соответственно). Ну случайно. Допустим. Ну ведь не будет работать.

Или вариант, который я встречал не реже:

$router = new Router();

$route = new Route('RouteName', array('controller' => 'mycontroller', 'action' => 'myaction'));

$router->addRoute($route);

Находим файл router.php и берем оттуда $router. Удобно? Я сомневаюсь.

Решение

Моя идея заключается в создании класса конфигурации в стиле PHP5.

Пример:

<?php

namespace Сonfig;

use RoutingRoute;

class Routes
{
    function welcome() { //название функции и есть названием маршрута
        $a = new Route;
        $a->setPattern('/')
          ->setController('hello')
          ->setAction('welcome')
          ->setFormat('html');
        
        return $a;
    }

    function main() {
        $a = new Route;
        $a->setPattern('/home/{user}')
          ->setController('controller')
          ->setAction('list')
          ->setFormat('html');
        
        return $a;
    }
}
RoutingRoute.php

<?php

namespace Routing;

class Route 
{
    
    private $pattern    = null,
            $controller = null,
            $action     = null,
            $parameters = array(),
            $regexp     = null,
            $format     = 'html';
    
    public function setPattern($pattern) {
        $this->regexp = str_replace('/', '/', $pattern);
        $this->regexp = preg_replace('/{w+}/', '(w+)', $this->regexp);
        $this->regexp = "/^{$this->regexp}$/i";
        
        $this->pattern = $pattern;
        return $this;
    }
    public function getPattern() {
        return $this->pattern;
    }
    public function setController($controller) {
        $this->controller = $controller;
        return $this;
    }
    public function getController() {
        return $this->controller;
    }
    public function setAction($action) {
        $this->action = $action;
        return $this;
    }
    public function getAction() {
        return $this->action;
    }
    public function addParameter($name, $value) {
        $this->parameters[$name] = $value;
        return $this;
    }
    public function getParameters() {
        return $this->parameters;
    }
    
    public function getRegexp() {
        return $this->regexp;
    }
    
    public function setFormat($format) {
        $this->format = $format;
        return $this;
    }
    public function getFormat() {
        return $this->format;
    }
    
    public function isValid() {
        
        if(is_null($this->getPattern())
         ||is_null($this->getController())
         ||is_null($this->getAction())
       ) return false;
        
        return true;
    }
    
}


Для обработки взятых паттернов с маршрута я использую регулярные выражения.

Собственно RoutingRouter

<?php

namespace Routing;

use RoutingExceptionRoute as RouteException;

class Router
{
    
    protected $routes  = array(),
              $address = null;
    
    public function __construct($str){
        $this->address = $str;
    }
    
    public function init(){
        $routes = new ConfigRoutes;
        
        $resRoute = null;
        foreach(get_class_methods($routes) as $route) {
            if(!is_callable(array($routes, $route))) {
                throw new RouteException('All routes must be public.');
            }
            
            $route = call_user_func(array($routes, "$route"));
            
            if(!$route->isValid()) {
                throw new RouteException('Invalid route found.');
            }
            
            $params = array();
            if(preg_match($route->getRegexp(), $this->address, $params)) {
                
                if(count($params) == 0) {
                    $resRoute = $route;
                    break;
                }
                
                $input = array();
                preg_match('/{(w+)}/', $route->getPattern(), $input);
                
                for($i=1; $i< count($input); $i++) {
                    $route->addParameter($input[$i], $params[$i]);
                }
                $resRoute = $route;
                break;
            }
        }
        
        if(is_null($resRoute)) {
            throw new RouteException('404', 404);
        }
        
        return $resRoute;
    }
    
}

В результате алгоритм работы получается такой:

  • Получаем текущую строку адресу
  • Ищем шаблон, который подходит под данный адрес
  • В случае если шаблон содержит параметры, добавляем их в маршрут
  • Возвращаем маршрут

Сейчас меня не устраивает только парсинг паттернов в regexp'ы.

Буду рад услышать ваши идеи и критику.
Спасибо.

Автор: spein

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js