CakePHP ガイド 入門編
目次
本レポートでは CakePHP の入門記事を掲載します。
まずは日本語のドキュメントをご一読ください。
【公式】 CakePHP プログラマーズ リファレンスガイド
最後まで問題なく理解できれば本レポートは出番がありません。もし、理解に苦しむ表現がありましたら、本レポートからヒントを得ることができるかも知れませんので、再び当サイトを閲覧ください。
最速で理解したい方でも次の章は必ず目を通してください。それ以降はドキュメントと当サイトを平行して読み進めてください。
CakePHP は Web システム開発でもっとも実績のある MVC パターンを採用しています。
開発者はモデル、ビュー、コントローラの各オブジェクトをプログラミングすることが Web システム構築における大部分の開発サイクルとなります。
MVC パターンの概要と CakePHP の各オブジェクトが MVC 中のどの役割を担っているかを解説します。
MVC パターン
MVCパターンとは次のようなWebシステムの構成を意味します。

モデル - Model
【公式】 http://cakephp.jp/doc/models.html
モデルはビジネスロジックとそれに付随するデータの入出力を担当します。また、たとえば「会員を退会扱いにする」場合などにdelete_flag を false から true へと変更するビジネスロジックはモデルが担当します。
主な役割は次のようなケースが考えられます。
■ビジネスロジックをメソッドとして提供する
例)会員のデータ一覧を取得する
例)会員を新規登録する
例)パスワード再設定用のURLを会員にメールで送信する
例)delete_flagをtrueにして削除扱いにする
■データベースからデータを取得する
プライマリキーや条件からデータを1件取得する(SELECT)
条件に合致する複数件のデータを取得する(SELECT)
リレーションを理解して再帰的にデータを取得する(SELECT)
■データベースへデータを保存する
新規登録(INSERT)
更新(UPDATE)
■データを検証する(バリデーション)
入力データを検証する
データベースの整合性(一意属性など)を検証する
コントローラ - Controller
【公式】 http://cakephp.jp/doc/controllers.html
コントローラはモデルのメソッドを使いこなして目的を達成し、ビューへとデータを渡して、その結果をブラウザへ返す役割を担います。
主な役割は次のようなケースが考えられます。
■HTTPリクエストからアクションを実行する
モデルのメソッドを使いこなして目的を達する
画面を遷移するロジックを実行する
ビューへデータを渡す
ビュー - View
【公式】 http://cakephp.jp/doc/views.html
ビューはコントローラから渡ってきたデータを駆使して HTML 、または XML などの表現を構成します。
主な役割は次のようなケースが考えられます。
■コントローラから渡ってきたデータを駆使してHTMLを返す
■コントローラから渡ってきたデータを駆使してXMLを返す
フレームワークともなるとクラス名を決める際にも規約があるものです。それは CakePHP とて例外ではありません。
次のリンクではシンボルの命名規約が表になっています。ほんの少しがんばって覚えていただけると、後になって当時の自分が賞賛に値するほど、ソースコードの高い保守性が実現するかも知れません。
CakePHP におけるシンボルの命名規約
CakePHP には、 MVC 間のデータ交換や、モデルを取り出したり保存したりする際に用いられる基本的なデータの形式があります。配列で表現するこのデータ形式は次のようなイメージになります。
<?php
$data = array("モデル名" => array("フィールド名" => "値",
"フィールド名" => "値",
…),
"モデル名" => array("フィールド名" => "値",
"フィールド名" => "値",
…),
…);
?>
前述の通り、この形式のデータは CakePHP の随所で見つけることができます。便宜上、当サイトではこれを CakePHP 式データと呼びます。
モデル内での使われ方
データベースからデータを取得する際に Model::find() メソッドや Model::read() メソッドを用いますが、それらのメソッドが返す値は CakePHP 配列です。返値にモデル名も含まれている点に注意してください。
また、モデルのデータをテーブルに追加・更新する際に Model::save() というメソッドを用いますが、このメソッドは引数として CakePHP 配列でデータを渡す必要があります。ここでもモデル名も含んで渡すという点に注意してください。
最後ですが、モデルにはコールバック関数があります。たとえば Model::beforeSave() は Model::save() の前に呼ばれるコールバック関数です。データベースへ保存される前にデータを修正したい場合などにこのメソッドをオーバーライドしますが、その際に対象となるデータは $Model::data というメンバ配列変数に CakePHP 配列として入っています。やはりそこでもモデル名を含んでいる点に注意してください。
コントローラ内での使われ方
CakePHP では POST 送信によって渡されたパラメータが自動的に $Controller::data メンバ配列変数へ格納されます。この形式が CakePHP 配列になっています。
ただし、自動的に格納されるのは HTML ヘルパーを使って出力した form 関連要素(inputタグなど)のみです。とはいえ CakePHP ではほとんどの場合 form 関連要素を HTML ヘルパーで出力しますので、事実上、全ての POST データが $Controller::data に自動的に格納されると考えて差し支えありません。
ビュー( HTML )内での使われ方
ビュー( HTML )で form 関連要素( input タグなど)を書く場合、 CakePHP では HTML ヘルパーを利用します。その理由のひとつがコントローラとのデータ交換です。 HTML ヘルパーで書かれた form の要素を POST 送信すると、送信先のコントローラの $Controller::data というメンバ配列変数に自動的に格納されます。
また、 HTML ヘルパーは form 関連要素を書く際に、コントローラ内の $Controller::data メンバ配列変数を参照して、これから出力しようとしている要素と同じ名前のデータがあった場合、そのデータを初期値として HTML を出力します。この仕様によって、たとえばデータ登録画面で入力項目にエラーがあった場合でも、今までに入力していたデータが消失してしまうのを防げるようになっています。
CakePHP には、ビヘイビア、コンポーネント、ヘルパーといった3つのライブラリがあります。ライブラリの役割は独立性の高いコードを再利用する手助けをすることです。ライブラリは自分で作ることができますし、他人のライブラリを使うことも可能です。逆にそれができないコードはライブラリではありません。
ビヘイビア - behavior
ビヘイビアはモデルに関する再利用性の高いメソッドをまとめたクラスです。
利用するためには次のように書きます。
<?php
class SomeModel extends AppModel
{
var $actsAs = array("Hoge" , "Foo");
function someMethod()
{
$this->hogesMethod();
}
}
?>
たとえば、パソコンやゲーム機のような電子機器は、電力をコンセントから得るという点で共通しています。そこで、 PowerSouce というビヘイビアを作って PowerSource::insertToConcentricPlug() というメソッドを書けば、別の電子機器が登場しても、再びコンセント接続のコードを書かなくてすみます。
なお、ビヘイビアを利用できるのは CakePHP 1.2.x.x. 以降です。
コンポーネント - component
コンポーネントは、ビヘイビアと同様に、コントローラに関する再利用性の高いメソッドをまとめたクラスです。
【公式】 コンポーネント
利用するためには次のように書きます。
<?php
class SomeController extends AppModel
{
var $components = array("Hoge" , "Foo");
function someMethod()
{
$this->Hoge->hogeMethod();
}
}
?>
たとえば今後、ログインとログアウトをシームレスに Ajax で実装することが大流行するかもしれません。ログインとログアウトといった機能は Web アプリケーションの必須機能ですので、コンポーネントの利用を検討すべきです。
ヘルパー - helper
ヘルパーは HTML のテンプレートの記述に関する再利用性の高いメソッドをまとめたクラスです。
【公式】 ヘルパー
利用するには、まず…
<?php
class SomeController extends AppModel
{
var $helpers = array("Hoge" , "Foo");
}
?>
…と宣言し、テンプレートの中で…
<body>
<p><?php echo $hoge->somePrint() ?></p>
</body>
…と書きます。 SomeController::helpers 配列のヘルパーは、コントローラの中で宣言して、テンプレートの中で変数としてアクセスする点に注意してください。
たとえば、今日を基準とした3年前から3年後の西暦を要素に持つ select タグを出力するといったコードは多くの人がヘルパーで実現したいかもしれません。
CakePHP にはアソシエーションという機能があります。
【公式】 アソシエーション
アソシエーションとはモデル間の依存関係のことで、通常はモデルを設計するタイミングで指定します。次の例は User モデルには Profile モデルと一対一の関係があることを設定しています。
<?php
class User extends AppModel
{
var $name = "User";
var $hasOne = array("Profile" =>
array("className" => "Profile",
"conditions" => "",
"order" => "",
"dependent" => true,
"foreignKey" => "user_id"
)
);
}
?>
従来は User モデルと対応する Profile モデルを取得する際には SQL の JOIN が必要でした。しかし、アソシエーションを利用することで、 $appModelInstance->find() の1回で User モデルと Profile モデルを取得できるようになります。
しかし、これほど有用なアソシエーションが枷になる場合があります。アソシエーションは関連するモデルを一度に取得できますが、逆に言うと関連するモデルまでもを一度に取得してしまうのです。いうまでもなくデータ件数が多い場合にはレスポンスが悪くなります。
このような場合、キャッシュで対応するという手段もあるのですが、ここでは動的なアソシエーションを紹介します。
前述したように、アソシエーションはモデルの設計時に設定します。しかし、パフォーマンスの向上などの理由から、必要に応じてアソシエーションを設定、または解除することが可能です。このような動的なアソシエーションは次のメソッドで実現します。
- アソシエーションを動的に設定する
Model::bindModel( $params )
Bind model associations on the fly.
Parameters:
array $params
- アソシエーションを動的に解除する
Model::unbindModel( $params )
Turn off associations on the fly.
Parameters:
array $params
最後に例を掲載します。
<?php
$assoc = array("Profile" =>
array("className" => "Profile",
"conditions" => "",
"order" => "",
"dependent" => true,
"foreignKey" => "user_id"
)
);
$this->User->bindModel(array("hasOne" => $assoc));
?>
ひとつ重要な事実があります。
実は CakePHP は、データの永続化( INSERT, UPDATE ) に際して PreparedStatement を使用していません。
PreparedStatement はインジェクションに代表される SQL をターゲットとする攻撃を防止する最も簡単にして強力で確実な技術でした。しかし CakePHP では PreparedStatement を使用せず、攻撃やメタ文字に注意しながら、 SQL の全文を自分( CakePHP の DB ライブラリ)が作ってサーバに投げています。明確な理由はわかりませんが、デバッグ情報として実行した SQL の全文を出力したいのかもしれません。
このように、 CakePHP ではデータを暗黙のうちにサニタイズしています。タイミングは次の2カ所です。
- モデルとデータベース間
前述の通り CakePHP では各 RDBMS ごとに、もっとも好ましいであろう手段でデータをサニタイズします。たとえば MySQL では、データを mysql_escape_string 関数に通してから SQL 文を作ってサーバへ送信します。
- HTML ヘルパー
データ入力画面などのテンプレート HTML を出力する際には、 HTML ヘルパーを用いて form タグを出力すると思われます。このような用途を見越してか HTML ヘルパーでは form 関連タグの value 属性の値を htmlspecialchars() 関数に通してから出力します。また HtmlHelper::tagValue() メソッドでも htmlspecialchars() 関数を通してから値を出力します。この振る舞いは入力データの確認画面で恩恵が得られることと思います。( tagValue メソッドは第二引数を true に設定しないとエスケープしなくなりました)
オープンソースのプログラムを利用すると、ドキュメントに無い仕様を理解するためにソースコードの読み解きが必要になる可能性があります。それは CakePHP も例外ではありません。幸いなことに CakePHP は PHP で書かれていますし、覚えておくと便利なコードは、まだそれほど多くありません。
コントローラ - Controller
- Controller::cleanUpFields()
- HTML ヘルパーで書かれた年月日や時刻のデータを結合して文字列にします。たとえば、入力画面を作る際、テーブル上では Date 型と定義されているフィールドの HTML を HTML ヘルパーを用いて書くと、年と月と日とでそれぞれ3つの select 要素が出力されます。
<select name="some_date_year">
…
<option>1980</option>
<option>1981</option>
<option>1982</option>
…
</select>年
<select name="some_date_month">
…
<option>3</option>
<option>4</option>
<option>5</option>
…
</select>月
<select name="some_date_day">
…
<option>13</option>
<option>14</option>
<option>15</option>
…
</select>日
各 select 要素の name 属性が some_date をベースとして some_date_year, some_date_month, some_date_day と修飾されている点に注目してください。Controller::cleanUpFields() は、このようなデータがコントローラ( Controller::data )に入っているとき、年月日を結合して 1981-4-14 のようにデータベースが理解しやすい値に変換したうえ、名前を some_date として Controller::data にデータを登録します。
モデル - Model
モデルには値の検証(バリデーション)に関する、名前がよく似ているメソッドがいくつかあります。 Model::validates(), Model::invalidFields(), Model::invalidate() の3つで、このメソッドを理解することで、モデルへの理解がいっそう深まります。
- Model::invalidFields()
- Model::validate に基づき、データ( Model::data )を検証して、エラーが発生した
件数フィード名のリストを返します。名前からは想像しづらいのですが、このメソッドが検証(バリデーション)の中心で、検証のアルゴリズムはここに書かれています。
- Model::validates()
- Model::invalidFields() に基づき、すべてのデータが検証にパスしたかの真偽値を返します。このメソッドは Model::save() の中で自動的に呼ばれ、すべてのデータが検証にパスできなかった場合は Model::save() を中止します(強制的に save() する場合は引数でその旨を指定する)。
- Model::invalidate()
- モデルの中の指定したフィールドに、検証失敗のフラグを立てます。 Model::invalidFields() が、検証(バリデーション)にパスできなかったフィールドを記憶するためのメソッドですが、 Model::beforeValidate() の中で使用することで、独自の検証(バリデーション)の後に CakePHP の検証(バリデーション)を連続で実行することができます( Model::beforeValidate() は独自の検証ルールを記述するための場所ですが、通常、このメソッドを使用して検証を失敗させると CakePHP の検証が実行されません)。
HTML ヘルパー - HtmlHelper
- HtmlHelper::setFormTag()
- たとえば “SomeModel/SomeField” のように、”モデル名/フィールド名”という形式になっている文字列から、モデル名とフィールド名をパースして HtmlHelper のインスタンスに格納します。格納されたモデル名とフィールド名は HtmlHelper の各所で使われますので、このメソッドは多用されています。
- HtmlHelper::selectTag()
- select タグ( option タグを含む)を出力します。公式マニュアルに載っていないのは、今後 HtmlHelper で select タグを出力するという常識が無くなる可能性があるためかと思われます( 2007年6月5日現在、 1.2.x.x には HtmlHelper::selectTag() はありません)。ちなみにインタフェースは次のようになります。
HtmlHelper::selectTag(
$fieldName,
$optionElements,
$selected = null,
$selectAttr = array(),
$optionAttr = null,
$showEmpty = true,
$return = false
)
パラメータ
- string $fieldName
- タグの name 属性
- array $optionElements
- option タグの内容( ’value’ => ’text’ のペア配列)
- mixed $selected
- 初期選択の option の value
- array $selectAttr
- select タグの属性( style 属性など)
- array $optionAttr
- 各 option タグの属性( style 属性など)
- boolean $show_empty
- 先頭に空の option タグを追加するか?
- boolean $return
- 結果を出力するか値を返すのか?
返値
select タグの HTML
グローバル関数 - basics.php
loadModel() という関数はモデルを任意のタイミングで読み込むことができます。基本的に利用するケースはありませんが、弊社では CakePHP の 1.1.x.x で 1.2.x.x のビヘイビアをエミュレートする場合などに使用しています。
最後に CakePHP をより具体的に理解できるように、実際に動く scaffolding のサンプルを紹介します。しかし scaffold してしまうと PHP のソースが見えないため bake.php というソースジェネレータを使って scaffolding と同等のコードを出力しました。
完成品のデモサイトを作りましたので、まずイメージをつかんでください
http://www.blueocean.bz/blog/cakephp.demo/
イメージがつかめましたでしょうか?次に仕様を説明します。
本サンプルは、中古品の売買マッチングシステムのマスタ管理画面という設定にしました。ユーザマスタ管理画面とユーザが所持している商品マスタ管理画面が実際に動作します(補足:中古品の売買ですので、本来は商品は管理者ではなくユーザ自身が管理する方が自然なのですが、サンプルの簡略化のため、このような仕様にしました)。
ここで作るサンプルが使うデータベースの構造は次のようになります。 RDBMS には MySQL を採用しました。

CREATE TABLE users (
id integer NOT NULL auto_increment,
name varchar(50) NOT NULL,
note varchar(255),
PRIMARY KEY (id)
);
CREATE TABLE items (
id integer NOT NULL auto_increment,
user_id integer NOT NULL,
title varchar(100) NOT NULL,
note varchar(255),
PRIMARY KEY (id)
);
データベースが決まりましたら、最初にモデルを作成します。
User モデル - /add/models/user.php
<?php
class User extends AppModel
{
var $name = 'User';
var $displayField = 'name';
var $hasMany = array('Item' =>
array('className' => 'Item',
'foreignKey' => 'user_id',
'conditions' => '',
'fields' => '',
'order' => '',
'limit' => '',
'offset' => '',
'dependent' => '',
'exclusive' => '',
'finderQuery' => '',
'counterQuery' => ''
),
);
}
?>
Item モデル - /app/models/item.php
<?php
class Item extends AppModel
{
var $name = 'Item';
var $displayField = 'title';
var $belongsTo = array('User' =>
array('className' => 'User',
'foreignKey' => 'user_id',
'conditions' => '',
'fields' => '',
'order' => '',
'counterCache' => ''
),
);
}
?>
モデルの次はコントローラを作成します。手書き、自動生成、どちらでもかまいません。
Users コントローラ - /app/controllers/users_controller.php
<?php
class UsersController extends AppController
{
var $name = 'Users';
var $uses = array('User', 'Item');
var $helpers = array('Html', 'Form' );
function index() {
$this->User->recursive = 0;
$this->set('users', $this->User->findAll());
}
function view($id = null) {
if(!$id) {
$this->flash('Invalid id for User', '/users/index');
}
$this->set('user', $this->User->read(null, $id));
}
function add() {
if(empty($this->data)) { $this->render();
} else { $this->cleanUpFields();
if($this->User->save($this->data)) {
$this->flash('User saved.', '/users/index');
} else {
}
}
}
function edit($id = null) {
if(empty($this->data)) { if(!$id) {
$this->flash('Invalid id for User', '/users/index');
}
$this->data = $this->User->read(null, $id);
} else { $this->cleanUpFields();
if($this->User->save($this->data)) {
$this->flash('User saved.', '/users/index');
} else {
}
}
}
function delete($id = null) {
if(!$id) {
$this->flash('Invalid id for User', '/users/index');
}
if($this->User->del($id)) {
$this->flash('User deleted: id '.$id.'.', '/users/index');
}
}
}
?>
Items コントローラ - /app/controllers/items_controller.php
<?php
class ItemsController extends AppController
{
var $name = 'Items';
var $uses = array('Item', 'User');
var $helpers = array('Html', 'Form' );
function index() {
$this->Item->recursive = 0;
$this->set('items', $this->Item->findAll());
}
function view($id = null) {
if(!$id) {
$this->flash('Invalid id for Item', '/items/index');
}
$this->set('item', $this->Item->read(null, $id));
}
function add() {
if(empty($this->data)) { $this->set('users', $this->Item->User->generateList());
$this->render();
} else { $this->cleanUpFields();
if($this->Item->save($this->data)) {
$this->flash('Item saved.', '/items/index');
} else {
$this->set('users', $this->Item->User->generateList());
}
}
}
function edit($id = null) {
if(empty($this->data)) { if(!$id) {
$this->flash('Invalid id for Item', '/items/index');
}
$this->data = $this->Item->read(null, $id);
$this->set('users', $this->Item->User->generateList());
} else { $this->cleanUpFields();
if($this->Item->save($this->data)) {
$this->flash('Item saved.', '/items/index');
} else {
$this->set('users', $this->Item->User->generateList());
}
}
}
function delete($id = null) {
if(!$id) {
$this->flash('Invalid id for Item', '/items/index');
}
if($this->Item->del($id)) {
$this->flash('Item deleted: id '.$id.'.', '/items/index');
}
}
}
?>
最後にビューを作成します。手書きでもかまいませんが bake.php を利用した方が楽です。ビューはファイル数が大きくなってしまいますので、読み解くためのポイントだけ掲載します。
-
<?php echo $html->input('User/name',
array('size' => '60'));?>
- User モデルの name に対する input タグを作っています。第2引数の配列はタグの属性になります。この場合 <input name="data[User][name]" size="6" /> となります。
-
<?php echo $html->tagErrorMsg('User/name',
'名前は必ず入力してください');?>
- User モデルの name に対するエラーメッセージを書いています。エラーがあった場合は「名前は必ず入力してください」が出力されます。エラーがなかった場合は何も表示しません。
-
<?php echo $html->url('/users/add'); ?>
- CakePHP 特有の URL を作っています。 CakePHP は様々な環境で動作しますので URL を書く際にはこのメソッドを使用します。
-
<?php echo $html->link('List Users', '/users/index')?>
- List Users という文字を含む a タグを作っています。前述の $html->url() メソッドと同様に、環境の違いを吸収するためのメソッドです。
-
- ユーザ( $user )にひもづく商品( [’Item’] )の情報を参照しています。アソシエーションを利用して取得したデータはこのように配置されます。
これでユーザと商品に対する一覧・詳細・登録・編集・削除が可能になりました。しかし実際には入力画面と登録完了画面との間に確認画面を挟みたくなると思います(海外と日本との文化の違いでしょうか?)。その際には Controller::add() や Controller::edit() に数行の追記が必要です。
参考
確認画面を挟む(CakePHPまとめ@Wiki)
トップページへ戻る /
前のページへ戻る