ちょっとURLルーティングを自作
どんな風に動いているか興味があったのでやってみた。
めちゃ車輪の再発明だけどw
- routes.php
<?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 } }
- url.php
<?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」は整数だけ受け取るとかできます。
- test.php
<?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" } }