【CakePHP】CakePHP Advent Calendar2010(day 7) : Tips for Routes
CakePHP Advent Calendar2010 の当番が回ってきました。
本来はもっとかる~い内容でいくつもりでしたが、皆さんがっつりとやられているので、慌てて秘蔵のネタ(?)を引っ張り出すことにしました。まあとはいえ実際はそんなでもないので軽く読んでいただければ(笑)。
さて、Modelにまつわる話が多いようなので今回も違う話題にしましょう。Modelは今までTipsが出にくい仕組みの一つだったのかもしれないのですが、同じくなかなか情報として出回らないRoutes関連でも取り上げてみようと思います。
CakePHPであることを隠蔽する (CakePHP Advent Calendar 2010 2日目) でも取り上げられていましたが、CakeぽくないURLを作成するためにはRouteを変更することで自由なURLにコントローラとアクションを振り分けることができます。しかし多くの場合「/:controller/:action/*」という標準のルート指定で問題がないため、修正する機会はなかなかないのではないかと思います。そこで2ステップでRoutes活用法をご紹介します。
ステップ1:パラメータチェックをRoutesで済ませてしまう
Routesの設定は上記のようにURL変更というのも活用法のひとつかもしれませんが、もう一つとても便利な使い方があります。それは、URL中にパラメータを含む場合に、そのパラメータチェックをRoutes内で行うことができる点です。
たとえば
http://www.example.com/foo/hoge/0123456789
というURLを想定するとしましょう。fooコントローラ内のhogeアクションで1つの値を入手したい場合です。この場合、デフォルトですとコントローラ内の「$this->params['pass']['0']」に値が入ります。そこで一般的に、次のようなコントローラを書くでしょう。
class FooController extends AppController {
var $name = 'Foo';
var $uses = array();
function hoge(){
//まずパラメータチェック
//
if(!preg_match('/^[0-9A-Za-z]+$/', $this->params['pass'][0])){
$this->flash('正しくないURLです', '/');
return;
}
//以下、必要な処理
//
}
}
しかし、Routesを活用することで、コントローラにチェックを書かずにチェックを行うことができます。「app/config/routes.php」に次のように記述します。
Router::connect('/foo/hoge/:viewerId', array('controller' => 'foo', 'action' => 'hoge'), array('viewerId' => '[0-9A-Za-z]+'));
Router::connect('/foo/hoge/*', array('controller' => 'foo', 'action' => 'error'));
この場合は$this->params['viewerId']にて値を受け取ることができます。条件に満たさない「/foo/hoge」はすべてerrorアクションに流れます。Router::connect()は先着順でルーティングを決定しますので、一番最後に条件を満たさない場合のルートを書いておけば、デフォルトである「/:controller/:action/*」に流れることはありません。
メリットとしては、まずアクション内でパラメータチェックが要らなくなる点で、fat controllerのスリム化に貢献できると思います。そして不要なアクションがコントローラ内に渡ってこない点にあります。そもそもURLから渡されるパラメータはコントローラに渡ってくる時点で正しいものであるべきかと思いますので、Routesでチェックすることは理にかなっていると言えます。
ステップ2:正規表現で表せないルーティングはどうするか?
上記ルーティングは、あくまでも正規表現で表される場合であり、複雑な条件分岐を伴ったりモデルを参照したい場合などはうまく表現できません。
しかしCakePHP1.3からは「カスタムルーティング」という機能が備わり、その中でチェックしたりデフォルト値を追加したりすることができます。
たとえば、既にモデルに登録されている値のみをルーティングし、登録されていない値はアクセスできないようなルーティングを表現してみましょう。まず、カスタムルーティングのコードを「app/libs」に記述します。コードを置く場所はどこでもよいのですが、Cake用のコードなのでvendorsではなくlibsがいいでしょう。ここでは「app/libs/viewer_route.php」とします。
class ViewerRoute extends CakeRoute {
//Userモデル内のviewerIdをチェックし、userIdを返す
//
function __checkViewerId($id){
$model =& ClassRegistry::init('User');
$result = $model->find('first', array(
'conditions' => array(
'viewerid' => $id,
),
));
if(empty($result)){
return false;
}
return $result[$model->alias]['id'];
}
//パース処理(URL => array())
//
function parse($url){
//ここはお決まりの処理
//
$result = parent::parse($url);
if(empty($result)){
return false;
}
//viewerIdが登録されているかをチェック
//userIdをルーティングパラメータとして追加
//
if(isset($result['viewerId'])){
$userId = $this->__checkViewerId(h($result['viewerId']));
if($userId === false){
return false;
}
$result['userId'] = $userId;
}else{
return false;
}
return $result;
}
//マッチ処理(array() => URL)
//
function match($url){
//ここに、URL生成に必要な処理を記述する(今回はなし)
//
//以下お決まりの処理
//
$result = parent::match($url);
return $result;
}
}
カスタムルーティングでは、parse()とmatch()を記述する事で、それぞれ正引き(Router::connect())と逆引き(Router::url())で独自の処理を行う事ができます。それ以外のコード(上記では__checkViewerId())は独自メソッドです。
ClassRegistry::init()を用いると、ルーティング処理内でもモデルを扱うことができます。ただし、parse()の段階ではまだコントローラ等は初期化が済んでいませんから、その範囲内での処理しかできないことを留意しておきます。match()は逆引きで用いられるので、タイミング的にcontrollerの初期化が終わっているのでこの限りではありません。
次に、「app/config/routes.php」にカスタムルーティングを適用します。第3引数の連想配列の中にある「routeClass」パラメータに作成したカスタムルート名を指定します。また、このクラスは自動的に読み込まれないので、事前に「App::import()」等で読み込んでおきます。
App::import('Lib', 'ViewerRoute');
Router::connect('/foo/hoge/:viewerId', array('controller' => 'foo', 'action' => 'hoge'), array('viewerId' => '[0-9A-Za-z]+', 'routeClass' => 'ViewerRoute'));
Router::connect('/foo/hoge/*', array('controller' => 'foo', 'action' => 'error'));
これで、viewerIdというパラメータで特定ユーザのみを許可するルーティングが実現できます。また、「ルーティングパラメータに存在しないパラメータを後から追加できる」ことも注目したい点です。上記サンプルでは、モデルから取得したuserIdを追加しています。このパラメータはコントローラ内で「$this->params['userId']」として受け取ることができます。
CakePHPは1.2でも十分に実用的ですが、1.3になってよりいろいろなことができるようになりました。今回の「カスタムルーティング」も使い方次第では大変に便利な機能といえます。また1.2を含めて、まだまだ便利な機能がCakePHPには備わっています。当方の執筆しました「Pocket詳解 CakePHP辞典」では、1.2/1.3の違いを含めコアクラス内に収録されているプロパティ・メソッドを盛りだくさんで紹介しています。こちらのほうもぜひご利用いただけますと幸いです。
次の担当は kanonji さんです。
どんなCake話が聞けるか楽しみですね!:D
ちょっと気が早いかもしれませんがMerry Xmas!!
【追記】(2010.12.07)
カスタムルーティングについての解説を若干足しました





