1

我正在研究一個(哦不,不是另一個)PHP中的MVC框架,主要用於教育,同時也是趣味和利潤。MVC;任意路由路徑級別和參數

無論如何,我在使用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

好了,回答我的問題,我自己的建議:

// 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; 
    } 

}