ちょっとURLルーティングを自作

どんな風に動いているか興味があったのでやってみた。
めちゃ車輪の再発明だけどw

<?php
/* */
class Routes {
  public $routes = array();
  
  public function __construct() {}
  
  public static function &getInstance() {
    static $instance = array();
    if(!isset($instance[0]) || !$instance[0]) {
      $instance[0] =& new Routes();
    }
    return $instance[0];
  }
  
  public static function connect($path, $default = array(), $rules = array()) {
    $route = new URL($path, $default, $rules);
    self::getInstance()->addRoute($route);
    return $route;
  }
 
  private function addRoute($route) {
    array_push($this->routes, $route);
  }
  
  public static function getRoute() {
    $self = self::getInstance();
    $controller = "";
    $action = "";
    $params = array();
    
    $route = $self->routes[$self->matchPattern(PATH)]; // PATHは$_SERVER["PATH_INFO"]とかあらかじめ定数で入れておく?
    $values = $route->getValues();
    $defaults = $route->getDefaults();
    
    $segments = split("/", ltrim(PATH, "/"));
    if(is_null($values["controller"])) {
      $controller = $defaults["controller"];
    } else {
      $controller = $segments[$values["controller"]];
    }
    if(is_null($values["action"])) {
      $action = $defaults["action"];
    } else {
      $action = $segments[$values["action"]];
    }
    if(count($values["params"]) > 0) {
      foreach($values["params"] as $key => $value) {
        $params[$key] = $segments[$value];
      }
    }
    
    return array("controller"=>$controller, "action"=>$action, "params"=>$params);
  }
  
  private function matchPattern($path) {
    for($i = 0; $i < count($this->routes); $i++) {
      $pattern = $this->routes[$i]->getPattern();
      if(preg_match($pattern, $path)) {
        return $i;
      }
    }
    // exception
  }
}
<?php
/* */
class URL {
  private $pattern;
  private $values;
  private $path = "";
  private $defaults = array();
  private $rules = array();
  
  public function __construct($path = "", $defaults = array(), $rules = array()) {
    $this->path = $path;
    $this->defaults = $defaults;
    $this->rules = $rules;
    
    try {
      $this->parse();
    } catch(Exception $e) {
      // exception
    }
  }
  
  private function parse() {
    $this->pattern= "/";
    $segments = split("/", ltrim($this->path, "/"));
    for($i = 0; $i < count($segments); $i++) {
      if(preg_match("/^:[a-zA-Z0-9_]+/i", $segments[$i], $match)) {
        $s = str_replace(":", "", $match[0]);

        if($s === "controller") {
          $this->values["controller"] = $i;
        } else if($s === "action") {
          $this->values["action"] = $i;
        } else {
          $this->values["params"][$s] = $i;
        }
        if(!is_null($this->rules[$s])) {
          $this->pattern .= "(\/" . $this->rules[$s] . ")";
        } else {
          $this->pattern .= "(\/[a-zA-Z0-9_]+)";
        }
      } else {
        $this->pattern .= "(\/" . $segments[$i] . ")";
      }
    }
    $this->pattern .= "/";
  }
  
  /**
   * getter
   */
  public function getPattern() {
    return $this->pattern;
  }
  
  public function getValues() {
    return $this->values;
  }
  
  public function getDefaults() {
    return $this->defaults;
  }
  
  public function getRules() {
    return $this->rules;
  }
}

「/」で分割してパラメータを正規表現に変換しつつ、セグメント分割した何番目の値がコントローラやアクションなのかを保存。
で、URIのindex.php以下の「/foo/bar/3」とかにマッチするパスがあれば、コントローラとアクションのパラメータにして返す感じにした。
mod_rewrite使う場合は、$_SERVER["PATH_INFO"]とか使えばいいのかな?
(ちなみに「// exception」と書いてあるところは後で例外処理を入れる予定です。。。)

これで実行するアクションメソッドでは$params["id"]とかで値を受け取れます。


こんな感じでRailsライクにマッチさせるパスを指定できます。
パラメータ指定するものは正規表現でルールを書けるようにもしてみた。
「:id」は整数だけ受け取るとかできます。

<?php
/* */
  require_once "routes.php";
  require_once "url.php";
  define("PATH", "/foo/bar/3");

  Routes::connect("/:controller/bar/:id",
                  array("action"=>"index"),
                  array("id" => "[0-9]+$"));
  Routes::connect("/:controller/:action/:id",
                  array(),
                  array("id" => "[0-9]+$"));
  Routes::connect("/:controller/:action");

  $values = Routes::getRoute();
  var_dump($values);
?>
array(3) {
  ["controller"]=>
  string(3) "foo"
  ["action"]=>
  string(5) "index"
  ["params"]=>
  array(1) {
    ["id"]=>
    string(1) "3"
  }
}