MVC; Произвольные уровни и параметры пути маршрутизации - PullRequest
1 голос
/ 08 марта 2011

Я работаю над ( о нет, не другой ) платформой MVC на PHP, в первую очередь для образования, но также для развлечения и получения прибыли.

В любом случае, у меня возникли некоторые проблемы с моим Router, в частности, с маршрутизацией на правильные пути, с правильными параметрами. Сейчас я смотрю на маршрутизатор, который (используя __autoload()) допускает произвольно длинные пути маршрутизации:

"path/to/controller/action"
"also/a/path/to/a/controller/action"

Маршрутизация начинается с каталога application, и путь маршрутизации по существу параллелен пути к файловой системе:

"/framework/application/path/to/controller.class.php" => "action()"
    class Path_To_Controller{
        public function action(){}
    }

"/framework/application/also/a/path/to/a/controller.class.php" => "action()"
    class Also_A_Path_To_A_Controller{
        public function action(){}
    }

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

"path/to/controller/action/key1/param1/key2/param2"

Очевидно, будет искать файл:

"/framework/application/path/to/controller/action/key1/param1/key2.class.php"
    => 'param2()'
//no class or method by this name can be found

Это не хорошо. Теперь это, конечно, пахнет проблемой дизайна, но я уверен, что должен быть чистый способ обойти эту проблему.

Сначала я хотел проверить каждый уровень пути маршрутизации на наличие каталога / файла.

  • Если он попадает в каталоги 1+, за которыми следует файл, дополнительные компоненты пути представляют собой действие, за которым следуют параметры.
  • Если он попадает в каталоги 1+ и файл не найден, 404.

Однако, это все еще подвержено ошибочному поиску файлов. Конечно, это можно облегчить благодаря более строгим соглашениям об именах и резервированию определенных слов, но я бы хотел этого избежать, если это возможно.

Я не знаю, является ли это лучшим подходом. Кто-нибудь изящно решил такую ​​проблему?

1 Ответ

1 голос
/ 09 марта 2011

Ну, чтобы ответить на мой собственный вопрос с моим собственным предложением:

// split routePath and set base path
$routeParts = explode('/', $routePath);
$classPath = 'Flooid/Application';
do{
    // append part to path and check if file exists
    $classPath .= '/' . array_shift($routeParts);
    if(is_file(FLOOID_PATH_BASE . '/' . $classPath . '.class.php')){
        // transform to class name and check if method exists
        $className = str_replace('/', '_', $classPath);
        if(method_exists($className, $action = array_shift($routeParts))){
            // build param key => value array
            do{
                $routeParams[current($routeParts)] = next($routeParts);
            }while(next($routeParts));
            // controller instance with params passed to __construct and break
            $controller = new $className($routeParams);
            break;
        }
    }
}while(!empty($routeParts));
// if controller exists call action else 404
if(isset($controller)){
    $controller->{$action}();
}else{
    throw new Flooid_System_ResponseException(404);
}

Мой автозагрузчик настолько же прост, как и выглядит:

function __autoload($className){
    require_once FLOOID_PATH_BASE . '/' . str_replace('_', '/', $className) . '.class.php';
}

Это работает, на удивление хорошо. Я еще не выполнил некоторые проверки, такие как проверка того, что запрошенный контроллер фактически идет от моего Flooid_System_ControllerAbstract, но на данный момент это то, с чем я работаю.

Несмотря на это, я чувствую, что этот подход мог бы выиграть от критики, если бы не полномасштабный капитальный ремонт.


С тех пор я пересмотрел этот подход, хотя в конечном итоге он выполняет те же функции. Вместо того, чтобы создавать экземпляр контроллера, он возвращает имя класса контроллера, имя метода и массив параметров. Все это в getVerifiedRouteData(), verifyRouteParts() и createParamArray(). Я думаю, что хочу реорганизовать или обновить этот класс, хотя. Я ищу понимание того, где я могу оптимизировать удобочитаемость и удобство использования. :

class Flooid_Core_Router {

    protected $_routeTable = array();

    public function getRouteTable() {
        return !empty($this->_routeTable)
            ? $this->_routeTable
            : null;
    }

    public function setRouteTable(Array $routeTable, $mergeTables = true) {
        $this->_routeTable = $mergeTables
            ? array_merge($this->_routeTable, $routeTable)
            : $routeTable;
        return $this;
    }

    public function getRouteRule($routeFrom) {
        return isset($this->_routeTable[$routeFrom])
            ? $this->_routeTable[$routeFrom]
            : null;
    }

    public function setRouteRule($routeFrom, $routeTo, Array $routeParams = null) {
        $this->_routeTable[$routeFrom] = is_null($routeParams)
            ? $routeTo
            : array($routeTo, $routeParams);
        return $this;
    }

    public function unsetRouteRule($routeFrom) {
        if(isset($this->_routeTable[$routeFrom])){
            unset($this->_routeTable[$routeFrom]);
        }
        return $this;
    }

    public function getResolvedRoutePath($routePath, $strict = false) {
        // iterate table
        foreach($this->_routeTable as $routeFrom => $routeData){
            // if advanced rule
            if(is_array($routeData)){
                // build rule
                list($routeTo, $routeParams) = each($routeData);
                foreach($routeParams as $paramName => $paramRule){
                    $routeFrom = str_replace("{{$paramName}}", "(?<{$paramName}>{$paramRule})", $routeFrom);
                }
            // if !advanced rule
            }else{
                // set rule
                $routeTo = $routeData;
            }
            // if path matches rule
            if(preg_match("#^{$routeFrom}$#Di", $routePath, $paramMatch)){
                // check for and iterate rule param matches
                if(is_array($paramMatch)){
                    foreach($paramMatch as $paramKey => $paramValue){
                        $routeTo = str_replace("{{$paramName}}", $paramValue, $routeTo);
                    }
                }
                // return resolved path
                return $routeTo;
            }
        }
        // if !strict return original path
        return !$strict
            ? $routePath
            : false;
    }

    public function createParamArray(Array $routeParts) {
        $params = array();
        if(!empty($routeParts)){
            // iterate indexed array, use odd elements as keys
            do{
                $params[current($routeParts)] = next($routeParts);
            }while(next($routeParts));
        }
        return $params;
    }

    public function verifyRouteParts($className, $methodName) {
        if(!is_subclass_of($className, 'Flooid_Core_Controller_Abstract')){
            return false;
        }
        if(!method_exists($className, $methodName)){
            return false;
        }
        return true;
    }

    public function getVerfiedRouteData($routePath) {
        $classParts = $routeParts = explode('/', $routePath);
        // iterate class parts
        do{
            // get parts
            $classPath  = 'Flooid/Application/' . implode('/', $classParts);
            $className  = str_replace('/', '_', $classPath);
            $methodName = isset($routeParts[count($classParts)]);
            // if verified parts
            if(is_file(FLOOID_PATH_BASE . '/' . $classPath . '.class.php') && $this->verifyRouteParts($className, $methodName)){
                // return data array on verified
                return array(
                    'className'
                        => $className,
                    'methodName'
                        => $methodName,
                    'params'
                        => $this->createParamArray(array_slice($routeParts, count($classParts) + 1)),
                );
            }
            // if !verified parts, slide back class/method/params
            $classParts = array_slice($classParts, 0, count($classParts) - 1);
        }while(!empty($classParts));
        // return false on not verified
        return false;
    }

}
...