【PHP】フロントコントローラとルーティングの話 その2

おはようございます。

今日は前回の続きで、「フロントコントローラとルーティング」のパート2です。

ルーティング

前回までで、URLからPATH_INFOを抜き出すRequestクラスについて説明していきました。

この取得したPATH_INFOからコントローラとアクションを特定する処理を「ルーティング」と呼びます。
つまり、アクセスされたURLを自動的に判別して、そのURLから自ら定義したルールに沿ってコントローラとアクションを呼び出すということです。

具体的には、次のような連想配列の形でルーティングを定義していきます

<?php
array(
  '/' => array(
    'controller' => 'home',
    'action' => 'index'
  ),
  '/user/edit' => array(
    'controller' => 'user',
    'action' => 'edit'
  ),
);
?>

ドメイン直下であれば、「home」コントローラ、「index」アクション
http:hogehoge.com/user/editというようなURLだったら、「user」コントローラ、「index」アクションを呼び出すように定義されています。

動的ルーティング

ここまでは結構簡単に理解を進めることができるかと思いますが、次がちょっと難しい正規表現を利用して動的にルーティングを定義していきます。

たとえば、「user?id=1」というURLなどはたびたび利用していると思います。
それを「user/1」といったような形で扱えるように制御していくのが動的なルーティングとなります。

パーフェクトPHPでは「:」(コロン)で始まる文字列を指定すると、その部分を動的なパラメータとして扱えるように規定しています。

<?php
//動的ルーティング
//"/user?id=1"というURLを"/user/1"として扱う・・・①
array(
  '/user/:id' => array(
    'controller' => 'user',
    'action' => 'show'
  ),
);
//動的コントローラ/アクションの指定・・・②
array(
  '/:controller' => array(
    'action' => 'index'
  ),
  '/item/:action' => array(
    'controller' => 'item'
  ),
);
?>

動的ルーティングを実装するために「preg_match()」という関数を用います。
この関数は第3匹数に変数を指定するとキャプチャした値を取得することができます。
【参考URL】
http://php.net/manual/ja/function.preg-match.php

また、②のようにGETパラメータだけでなく、アクションの指定も動的に行うことができます。

<?php
class Router{

  protected $routes;

  public function __construct($definitions){
    $this->routes = $this->compileRoutes($definitions);
  }

  public function compileRoutes($definitions){

    $routes = array();

    foreach($definitions as $url => $params){
      $tokens = explode('/',ltrim($url, '/'));
      foreach($tokens as $i => $token){
        //動的パラメータか判定
        if(0 === strpos($token, ':')){
          $name = substr($token, 1);
          $token = '(?P<'.$name.'>[^/]+)';
        }
        $tokens[$i] = $token;
      }
      $pattern = '/'.implode('/', $tokens);
      $routes[$pattern] = $params;
    }
    return $routes;
  }

  public function resolve($path_info){
    if('/' !== substr($path_info, 0, 1)){
      $path_info = '/'.$path_info;
    }

    foreach($this->routes as $pattern => $params){
      if(preg_match('#^'.$pattern.'$#', $path_info, $matches)){
        //コントローラー・アクション・ルーティングパラメータを合体させる
        $params = array_merge($params, $matches);

        return $params;
      }
    }
    return false;
  }

}
/*Routerをインスタンス化するときに、ルーティングを定義する
 *Router->resolve()にPATH_INFOの値を与えるとマッチングした結果を返す
 *返ってきた結果は、指定した動的パラメータ名で取得できる
 * */
?>

「compileRoutes()」
ルーティング定義配列を変換する関数。
実装には、ルーティング定義配列をコンストラクタのパラメータとして受け取り、変換したものを「routes」プロパティとして設定します。
※URLを「/」で区切って、動的パラメータがあるか判定して、加工後再度「/」でつなげて「routes」に格納する。

「resolve()」
ルーティングのマッチングをする関数。
compileRoutes()で変換したルーティング定義配列を利用して、マッチングを行う。
マッチした場合は、コントローラー・アクション・ルーティングパラメータを合体させて「params」として変数を返しています。

/*————————————————————————————————————*/
この辺りで何度も挫折していたのですが、何度も読み返すのと、返される値をひたすらvar_dump()することで実際の挙動がわかってきました。
(まだまだ、正規表現の部分が苦手ですが。)

「パーフェクトPHP」では動的パラメータを「:」で制御していましたが、他のフレームワークでは違う方法で定義されてるかもしれないです。
このあたりは、もっと他のフレームワークのコードを読んで色んなパターンに触れて知識を深めていきたいですね。

フレームワークは勉強すればするほど、便利な要素が詰まっているので、今まで自己流で書いていたコードの改良する部分がどんどん見えてきますね。

フレームワークの使い方を知るだけじゃなくて、フレームワークの後ろでどんな処理が行われているのかをもっと勉強して、いつか自分もオープンソースプロジェクトに参加できるようになりたいと思っている今日この頃です。

今は「パーフェクトPHP」と「FuelPHP」を目下勉強中!!


ご一読頂きまして、ありがとうございました。

【参考資料】
パーフェクトPHP

パーフェクトPHP (PERFECT SERIES 3)

パーフェクトPHP (PERFECT SERIES 3)