ルーティングとはリクエストされたURLから実行すべきコントローラーとアクションを割り出し、振り分けるための処理です。
MVCでのURLについて
MVCでは通常のURLとは考え方が違います。そもそものURLはサーバー上のディレクトリのパスにそのまま対応していますが、MVCでは実際にはディレクトリが存在しません。また、通常ではファイルの拡張しに「.php」などが憑きますがMVCでは拡張子もつきません。
URLは、web上のリソースを一意に識別するためのものです。拡張子がついているとURLを表しているのはPHPファイルそのものということになります。しかし、MVCのビューはページを作るための部品であり、状況に応じて動的にページを生成します。
URLに含めるパラメーター
ブラウザーからサーバーへデータを送信するときはPOSTかGETを使っていますが、GETの場合URLの末尾にデータをくっつけて送信するので、長くなりがちです。また、どんなパラメーターを送信しているのが丸見えです。こんな感じにmodeがbrowseでIDが何番とか。
http://example.com/blog.php?mode=browse&id=123
上のURLをMVCにするとこんな感じになります。
http://example.com/blog/browse?id=123
blogがコントローラーでbrowseがアクションです。コントローラーごとにアクションメソッドを設定します。
でも、idというパラメーターが必要だと、GET送信では?を使用して末尾に付けなければいけませんが、これではMVCを利用してるのが台無しになります。そこで、パラメーターの無くし方を説明していきます。
まず、MVCではドメイン部分以降をGETパラメーターとして処理します。そして、ドメインよりあとの部分を「/」で区切って、1つ目をコントローラー、2つ目をアクションという決まりを作ります。id=123はどうするかといいますと、3つ目のパラメーターも「/」で区切って、次のようにURLに含めます。
http://example/blog/browse/123
このように123をユーザーIDとして取得すればいいです。ですが、これはblogコントローラーに限ったことなので、共通の仕組みとして3つ目はユーザーIDとすることができませんので、プログラムぐぁで最後の「123」を「id」というキーで取得する仕組みを作ることが必要です。
ここでは1つの例として示しましたが、つまりはこのような考え方でURLを扱うこととしていきます。
ルーティングの定義をアプリケーション側で行う
URLとコントローラーの対応付けはアプリによって異なるので、ルーティングの定義はアプリ側で行います。
ここではgetRouteDid()メソッドとして定義し、このメソッドを呼び出せばルーティング定義が戻り値として返すようにします。
public function getRouteDif(){
return array(
//フロントコントローラーへのアクセス
'/' =>array('controller' => 'blog', 'action' => 'index'),
//status/postへのアクセス
'/status/post' => array('controller' => 'blog', 'action' =>'post'),
//user/:user_nameへのアクセス
'/user/:user_name' => array('controller' => 'blog', 'action' =>'user')
);
}
連想配列のキーにパス情報を指定します。値にはコントローラーとアクションを連想配列として設定します。
各コントローラーはクラスとして定義するので先頭文字は大文字になりますが、URLに不規則に大文字が含まれるのはよしとしないので、コントローラー名はすべて小文字にしておき、コントローラークラスを呼び出す際に先頭文字を大文字に変換します。
Routerクラスを定義する
Requestクラスでは、ユーザーのリクエストからベースとなるURLと、ベースURLよりあとの部分をパス情報として取り出す処理を実装しました。フロントコントローラーから各コントローラーやアクションへの振り分けはパス情報を使って行います。この対応付けして振り分ける処理をルーティングと呼びます。
以降は、各メソッドを定義する際に上記のルーティング定義を例にしてどのように処理を行うのか説明していきます。
コンストラクターを用意する
アプリ側から渡されるルーティング定義を利用するには、中身を解析するなどの処理が必要になります。
ですので、Routerクラスのコンストラクターでは、解析専用のメソッドを呼び出して解析後のデータをプロパティに格納します。
protected $_converteRoutes;
public function __construct($routedif){
$this->_converteRoutes = $this->routeConverter($routedif);
}
routeConverter()メソッドでルーティング定義を内部用に変換
このメソッドでは以下のような処理を行います。
- ルーティングを定義する配列$routedifのパス情報キーを$urlに格納する
- $urlに格納されたパス情報キーの先頭の「/」を除去
- $urlに格納されているパス情報を「/」ごとに分割する
- $contertsに格納された要素を取り出す
- $convertに格納された文字列の先頭が「:」であれば除去して$barに格納
- 「:user_name」の「user_name」をサブパターンの名前にする
public function routeConverter($routedif){
$converted = array();
foreach($routedif as $url => $param){
$converts = explode('/', ltrim($url, '/'));
foreach($converts as $i => $convert){
if( strpos($convert, ':') === 0){
$bar = substr($convert, 1);
$convert = '(?<' .$bar .'>[^/]+)';
}
$converts[$i] = $convert;
}
$pattern = '/' .implode('/', $converts);
$converted[$pattern] = $param;
}
return $converted;
//この時点の$convertedの中身
/*
Array(
[/] => Array([controller] => blog,
[action] => index),
[/] => Array([controller] => blog,
[action] => post),
[/] => Array([controller] => blog,
[action] => user),
)
*/
}
getRouteParams()メソッドでルーティング定義にマッチするか確認
リクエストのURLから取り出したパス情報が変換済みのルーティング定義に存在すれば、ルーティング定義のパス情報の値として追加するメソッドを定義する。
getRouteParams()メソッドを定義する
このメソッドの処理手順は以下の通りです。
- リクエストされたURLのパス情報の先頭が「/」でない場合に「/」を付ける
- $_convertedRoutesプロパティに格納されているルーティング定義の要素を取り出す
- $pathのパス情報が$patternと一致するかを調べる
- $matchesに格納されている要素を$paramの要素として追加する
public function getRouteParams($path){
if('/') !== substr($path, 0 , 1){
$path = '/' . $path;
}
foreach($this->_converedRoutes as $pattern => $param){
if(preg_match('#^' . $pattern . '$#', $path, $pMatch)){
$param = array_merge($param, $pMatch);
return $param;
}
}
return false;
}
Routerクラスのコードまとめ
Router.phpは以下のようなコードになります。
<?php
class Router{
//ルーティング情報を保持する
protected $_converteRoutes;
public function __construct($routedif){
//$routedifを変換し、プロパティに格納
$this->_converteRoutes = $this->routeConverter($routedif);
}
public function getRouteDif(){
return array(
//フロントコントローラーへのアクセス
'/' =>array('controller' => 'blog', 'action' => 'index'),
//status/postへのアクセス
'/status/post' => array('controller' => 'blog', 'action' =>'post'),
//user/:user_nameへのアクセス
'/user/:user_name' => array('controller' => 'blog', 'action' =>'user')
);
}
public function routeConverter($routedif){
$converted = array();
foreach($routedif as $url => $param){
$converts = explode('/', ltrim($url, '/'));
foreach($converts as $i => $convert){
if( strpos($convert, ':') === 0){
$bar = substr($convert, 1);
$convert = '(?<' .$bar .'>[^/]+)';
}
$converts[$i] = $convert;
}
$pattern = '/' .implode('/', $converts);
$converted[$pattern] = $param;
}
return $converted;
//この時点の$convertedの中身
/*
Array(
[/] => Array([controller] => blog,
[action] => index),
[/] => Array([controller] => blog,
[action] => post),
[/] => Array([controller] => blog,
[action] => user),
)
*/
}
public function getRouteParams($path){
if('/') !== substr($path, 0 , 1){
$path = '/' . $path;
}
foreach($this->_converedRoutes as $pattern => $param){
if(preg_match('#^' . $pattern . '$#', $path, $pMatch)){
$param = array_merge($param, $pMatch);
return $param;
}
}
return false;
}
}
?>
コメント