1.htaccess(mod_rewrite)の技法
CakePHPでは、
http://cakeのパス/コントローラ/メソッド/パラメータ
というURLで実行すると、以下の「モデル」「コントローラ」「ビュー」クラスが自動的に読み込まれ、セキュリティに必要な処理が行われます。またデータベースへのアクセスやビジネスロジックの実行、テンプレートを使用した表示などの作業が自動的に行われていきます。
| クラス | 説明 |
| モデル | データベーステーブルへのアクセスクラス |
| コントローラ | URLで指定されたコントローラ(メソッドの内容が記述されている)クラス |
| ビュー | コントローラで処理されたデータを埋め込む、テンプレートHTMLファイル |
例えば
http://cake/samples/view/3
というURLの場合(標準的な動作)、以下の通りとなります。この処理はどのような流れで実現されているのでしょうか。
| クラス | ファイル | 読み込まれるクラス名、説明 |
| モデル | sample.php | Sampleクラス(DBのsamplesテーブルとその関連テーブルにアクセス)。 |
| コントローラ | samples_controller.php | SamplesControllerクラス(その中のview($id)メソッドが実行される。$idは3になる)。 |
| ビュー | view/samples/view.thtml | データは単なるHTML。コントローラからのデータをこのテンプレートに読み込んで表示。 |
htaccess(mod_rewrite)の基礎知識
Apacheの「mod_rewrite」とは、「要求されたURLをリアルタイムで書き換えるための、ルールベースの書き換えエンジン」で、「URLを操作するためのスイス製のアーミーナイフ」ともマニュアルに書かれています。つまり、PHPが処理する前にURLを必要に応じて柔軟に書き換えることができる、アーミーナイフのように便利なモジュールです。指示する内容は「httpd.conf」に書くことができますが、「.htaccess」というファイルで設定することもできるようになっています。CakePHPの中には、
- 「CakePHP」ディレクトリを展開した直下に「.htaccess」がひとつ
- 「app」ディレクトリに「.htaccess」
- 「app/webroot」ディレクトリに「.htaccess」
という3カ所に存在しています。なお、単に解凍してそのまま配置しただけなのに、「文字は表示されたけど画像が出ない」とか、「index.php?url=という形ではアクセスできるけど、/コントローラ/メソッド、という形ではうまく表示されない」などのトラブルが起きる場合、その大半は、Apacheの「mod_rewrite」が正しく動いていないことが原因です。phpinfo()などで、「mod_rewrite」モジュールが正しく読み込まれていることを確認してください。
| オプション | 説明 | 備考 |
| RewriteEngine On | URL書き換えエンジンをONにする。 | |
| RewriteCond | 指定した条件に当てはまる場合に、次行のRewriteRuleを実行する。 | -f 指定されたパス、ファイルが存在していればtrue。%{REQUEST_FILENAME}リクエストされたファイル名。 |
| RewriteBase | CakePHPには指定がありませんが、サーバのディレクトリがAliasなどを使用して設定されている場合、物理的なファイル名のパスの指定をこれで追加してください。 | |
| RewriteRule | 正規表現で書き換えルールを設定します。 | [L](last) 指定条件にマッチすると、その後に続くRewriteRuleは評価されません。[QSA](query string append) 正規表現で置換文字列を置き換えるのではなく、クエリー文字列部分を追加します。 |
| 記号 | 説明 |
| ^ | 行頭 |
| $ | 行末 |
| . | 任意の1文字 |
| [abcd] | カッコ内の文字(a,b,c,d)が該当 |
| [^abcd] | カッコ内の文字以外(a,b,c,d以外)が該当 |
| A|B | AかB |
| + | 直前の文字の1回以上の繰り返し |
| ? | 直前の文字の0回または1回の繰り返し |
| * | 直前の文字の0回以上の繰り返し |
| $1 | パターンの()で囲まれた最初の部分を$1で参照できます |
それでは、CakePHPの「.htaccess」ファイルを見てみましょう。
- 「CakePHP」を展開した直下にある「.htaccess:」
- 「app」フォルダにある「.htaccess:」
- 「app/webroot」フォルダにある「.htaccess:」
<IfModule mod_rewrite.c> RewriteEngine on RewriteRule ^$ app/webroot/ [L] RewriteRule (.*) app/webroot/$1 [L] </IfModule>
<IfModule mod_rewrite.c> RewriteEngine on RewriteRule ^$ webroot/ [L] RewriteRule (.*) webroot/$1 [L] </IfModule>
<IfModule mod_rewrite.c> RewriteEngine On RewriteCond %{REQUEST_FILENAME} !-d RewriteCond %{REQUEST_FILENAME} !-f RewriteRule ^(.*)$ index.php?url=$1 [QSA,L] </IfModule>
上記の3つを解説すると以下の通りになります。
1.Cakeの親ディレクトリへのアクセス
- RewriteEngineをONにする。
- コントローラの指定のないURL(つまり、http://cakeのパスで呼ばれた場合)には、「app/webroot/」とする(処理終了)。
- 何か指定されていれば、それをすべて、「app/webroot/」の後に追加。(処理終了)
http://cake/コントローラ/メソッド/パラメータ
http://cake/app/webroot/コントローラ/メソッド/パラメータ
2.Cakeのappフォルダへのアクセス
- RewriteEngineをONにする。
- コントローラの指定のないURL(つまり、http://cake/appのパスで呼ばれた場合)には、「webroot/」とする(処理終了)。
- 何か指定されていれば、それをすべて、「webroot/」の後に追加。(処理終了)
http://cake/app/コントローラ/メソッド/パラメータ
http://cake/app/webroot/コントローラ/メソッド/パラメータ
3.Cakeの「app/webroot」フォルダへのアクセス
- RewriteEngineをONにする。
- URLで指定されたディレクトリが存在している場合には処理を中止。
- URLで指定されたファイルが存在している場合には処理を中止。
- 指定されている文字列を
index.php?url=の形にして追加。処理中止。
http://cake/app/webroot/コントローラ/メソッド/パラメータ
http://cake/app/webroot/index.php?url=コントローラ/メソッド/パラメータ
ここまでをまとめると、
http://Cakeのパス/コントローラ/メソッド/パラメータ
という形でリクエストがあった場合、Cakeの親ディレクトリと「app」ディレクトリのどちらからでも、最終的には、
http://Cakeのパス/appディレクトリ/webroot/index.php?url=コントローラ/メソッド/パラメータ
という形に変換されることになります。ためしに、index.php?url=で直接リクエストしても、Cakeのプロジェクトはそのまま動きます。この後、Dispacherクラスのdispachメソッドがこのパラメータを分解して必要なデータをそろえます。
http://cakeのパス/index.php/コントローラ/メソッド/パラメータ
dispatchの動作
dispatchメソッドの中では、まず、URLを分解します。
function dispatch($url, $additionalParams=array()) { $params = array_merge($this->parseParams($url), $additionalParams);
ControllerのURL部分から、コントローラ名を作成します。camelizeとは、アンダーバーを取り除き、各単語の先頭文字を大文字にする処理です。
$ctrlName = Inflector::camelize($params['controller']); $ctrlClass = $ctrlName.'Controller';
Actionの指定がない場合には、indexアクションが実行されます。また、__で始まっているアクションはプライベートなものとして扱われます。
if (empty($params['action'])) { $params['action'] = 'index'; } if((in_array($params['action'], $classMethods) || in_array(strtolower($params['action']), $classMethods)) && strpos($params['action'], '_', 0) === 0) { $privateAction = true; }
このようにして、コントローラとアクションが決まると、処理は、Controllerクラスのコードが実行されます。Controllerクラスのコンストラクタではモデル名とビューのパスを決定します。Inflector::classifyは、「posts」から、「Post」を作り出すメソッドです。「inflector.php」ファイルの中には、命名規則にそった名前を作り出す関数が色々とあります。
if ($this->viewPath == null) {
$this->viewPath = Inflector::underscore($this->name);
}
$this->modelClass = Inflector::classify($this->name);
$this->modelKey = Inflector::underscore($this->modelClass);
その後、dispachは、$controller->constructClasses();として、Controllerクラスの下記のコードを呼び出します。これで必要なモデルが読み込まれていきます。loadModel()は、「cake\basics.php」の中にあります。
if ($this->uses) { $uses = is_array($this->uses) ? $this->uses : array($this->uses); foreach($uses as $modelClass) { : :省略 : if(!class_exists($modelClass)){ loadModel($modelClass); } : :省略 : } return true; }
いかがだったでしょうか。フレームワークは「想定内」の作業では非常にスムーズに動きますが、フレームワーク「想定外」の作業をさせようとすると、そこからは誰も体験したことのない未知の世界です。オープンソースプロジェクトの場合は自分の疑問に対して誰かがすべて答えてくれるわけではありませんので、自力で原因を追究し解決するスキルも磨かなければなりません。そこから新たなテクニックが見つかる場合もあります。フレームワークの改善案を考えたら報告してみましょう。フレームワークの発展を助けることができるかもしれません。そして、記事を書いてみると自分の理解も深めることができます。

/webroot/index.php?url=コントローラ/メソッド/パラメータ
