モデル - Model_Baseクラス
モデル - Model_Baseクラス
Section titled “モデル - Model_Baseクラス”ここでは、モデルの基底クラスについて説明いたします
なぜフルスクラッチで書いたのか
Section titled “なぜフルスクラッチで書いたのか”FuelPHPには、Model_CrudやOrm\Modelといったモデルクラスが提供されています
しかし、Model_Baseは、それらのクラスを継承しておりません
その理由をまず説明いたします
Model_CrudやOrm\Modelは、
プロパティの取得や設定など、高頻度でアクセスされるプロパティにマジックメソッド(__get, __set)を使用しています
マジックメソッドは動作が遅い1ため、コードではなく「動作速度を」軽量にしたいことと、
後述のいくつかの制約や機能を加えたかったため、Model_CrudもOrmパッケージも継承しない基底クラスModel_Baseを実装しました
完全にゼロベースではなく、FuelのModel_Crudにあるメソッドを一部組み込んでおります
Model_Baseクラスは、
モデルクラスの名前とテーブル名の関係に命名規約があるなど、いくつか制約を加えています
1. モデルクラス名とテーブル名の命名規約
Section titled “1. モデルクラス名とテーブル名の命名規約”Model_Baseを継承するクラスの名前と、DB上のテーブル名は対応させなければなりません
代わりに、命名規約に則りさえすれば、楽にモデルのコードを書くことができます
テーブル名とモデルクラス名の関係は、
- テーブル名:複数形
- モデルクラス名:単数形
とする必要がありますいくつか具体例を挙げます
| テーブル名 | 単数形 | モデルクラス名 |
|---|---|---|
users | user | Model_User |
companies | company | Model_Company |
people | person | Model_Person |
この対応関係を作るために、内部でFuelPHPのInflector::pluralizeメソッド2を使用しています
単数形の単語を与えると、複数形にした単語を返すメソッドです
そのため、oil consoleコマンドで手軽に対応関係を確認することができます
$ cd /path/to/sample-fuelphp$ php oil consoleFuel 1.7.1 - PHP 5.4.28 (cli) (Jun 19 2014 14:31:57) [Darwin][]>>> Inflector::pluralize('man')men # この場合テーブル名は`Model_Men`とする>>> Inflector::pluralize('monky')monkies # この場合テーブル名は`Model_Monkies`とする>>> Inflector::pluralize('person')people # この場合テーブル名は`Model_People`とするしかし、oil generate modelコマンドで生成されたマイグレーションが生成するテーブルは、
モデルクラス名の複数形になるため、さほど意識する必要はありません
※ Moel_Baseクラス自身をインスタンス化する必要がないため、abstractにしています
2. プロパティ名とカラム名の命名規約
Section titled “2. プロパティ名とカラム名の命名規約”DBのカラム名と、モデルクラスのプロパティ名にも命名規約があります
カラム名とプロパティ名は一致させる
Section titled “カラム名とプロパティ名は一致させる”モデルの設定や、Model_Baseの継承クラスの処理を簡潔するための規約です
DBのカラム名と、モデルクラスのプロパティ名は一致させる必要があります
また、モデルで定義されたプロパティは、publicで、かつDBに存在する必要があります
class Model_User extends Model_Base { public $company_id = null; public $uid = null; public $next_engine_id = null; public $email = null; public $access_token = null; public $refresh_token = null;}例えば、はじめに3で説明したusersテーブルを使用したModel_Userの場合ですと、
以下の様な定義になります
なお、id, created_at, updated_atはModel_Baseに定義されているため、再定義する必要はありません
これら3つのカラムは、全てのテーブルが持っていると制約しています
しかし、全てのプロパティが強制的に保存されてしまうと柔軟性に欠けるため、
保存対象から除外するカラムを指定する設定を設けています
保存処理から除外したいプロパティはignoreSaveKeyへ
Section titled “保存処理から除外したいプロパティはignoreSaveKeyへ”保存対象から除外するカラムを指定する、ignoreSaveKeyというプロパティがあります
class Model_Hoge extends Model_Base { public $hoge = null; public $foo = null; public $bar = null;
protected $ignoreSaveKey = array('hoge', 'foo');}
$model = new Model_Hoge();$model->bar = 'fizz';
// hogeとfooは除外指定されているため、barのみが保存される$model->save(); // INSERT INTO `hoges` SET `bar`='fizz'上記のように、プロパティ名の配列を指定します
- DBに存在しないカラムをプロパティとして使用したい、
- PHPからDBに保存したくないプロパティ
などを保存対象から除外することができます
DBに存在するカラムとモデルのみに存在するプロパティを分ける
Section titled “DBに存在するカラムとモデルのみに存在するプロパティを分ける”DBに存在しないカラムをプロパティとして定義することができるため、
第三者がコードを見た際に、DBに存在するプロパティなのか見間違うリスクが生まれました
そのため、DBに保存するカラム名(プロパティ名)と、
DBに保存しない、モデルクラスにのみ記述されたプロパティ名を分けるための命名規約を設けます
| 種類 | 命名規則 |
|---|---|
| DBに保存するプロパティ | スネークケース(hoge, hoge_foo_bar) |
| モデルにのみ存在するプロパティ | キャメルケース(hoge, hogeFooBar) |
上記の表のように、命名を分けて下さい
Model_Baseで言えば、id, created_at, updated_atがカラム名(スネークケース)
validationErrors, primaryKey, ignoreSaveKeyがモデルのプロパティ(キャメルケース)です
主キーの変更
Section titled “主キーの変更”主キーは、primaryKeyプロパティで指定します
デフォルトではidと指定されていますが、任意の値へ変更することができます
// 主キーを変更するには$primaryKeyプロパティの値を変更するclass Model_Map extends Model_Base { public $foo_id = null; public $hoge_id = null;
protected static $primaryKey = 'foo_id';}
// idではなく、カラムfoo_idの値で検索をかけることができる$map = Model_Map::find('hoge-foo-bar');現状、主キーは単一の指定のみ対応しております
複合主キーには対応しておりません
基本的な機能
Section titled “基本的な機能”次は、Model_Baseで使える基本的な機能について説明いたします
※ このドキュメントには、細かな仕様を記述しておりません
細かい挙動や仕様については、fuel/app/classes/model/base.phpを御覧下さい
インスタンスの生成
Section titled “インスタンスの生成”__construct(array $data = array())
引数
array $data プロパティに与える初期値を連想配列で指定
class Model_Test extends Model_Base { public $hoge = null;}
$model = new Model_Test();$model->hoge; // null
$model = new Model_Test(array('hoge' => 1));$model->hoge; // 1データの読み取りには、find, findAll, findBy, findLikeを用意しています
find($id)
Section titled “find($id)”findは、主キーを用いて検索を行います
引数 mixed $id 取得したいモデルのIDを指定(文字列か数値を想定) 戻り値 指定されたIDに対応するデータが有れば、自身のインスタンス、なければnull
$model = Model_Test::find(1); // id値で検索
$exist = is_null($model) ? 'ない' : 'ある';findAll()
Section titled “findAll()”引数 なし 戻り値 モデルクラスと対応するテーブル内の全件のデータ
$models = Model_Test::findAll(); // 全件取得
$found = (count($models) > 0) ? '1件以上ある' : '1件もない'findBy($column, $value, $operator = ’=’)
Section titled “findBy($column, $value, $operator = ’=’)”引数
string $column 比較するカラム名
mixed $value 比較する値
string $operator 比較に用いる演算子(デフォルトは=)
戻り値
比較条件にマッチしたモデルのインスタンスの配列、1件もマッチしなければ空配列
$models = Model_Test::findBy('hoge', 'foo'); // カラムhogeの値がfooなモデルを取得$models = Model_Test::findBy('age' 10, '>='); // 比較に>=を使用
$found = (count($models) > 0) ? '1件以上ある' : '1件もない'findLike($column, $value)
Section titled “findLike($column, $value)”引数 string $column 検索に使用するカラム mixed $value 検索に使用する値 戻り値 比較条件にマッチしたモデルのインスタンスの配列、1件もマッチしなければ空配列
$models = Model_Test::findLike('description', 'Geek'); // LIKEを使用して部分一致を検索
$found = (count($models) > 0) ? '1件以上ある' : '1件もない'上記のような、汎用的で簡素な検索・取得メソッドしか用意しておりません
複雑な検索クエリを書かなければならない場合は、検索用のメソッドを実装して下さい
また、件数のカウントにはcountを用意しています
countについてはModel_Crudのcountメソッドをそのまま移植しています
使い方はそちらを御覧下さい
Model_Crud - クラス - FuelPHP ドキュメント
http://fuelphp.jp/docs/1.7/classes/model_crud/methods.html#/method_count
$row_count = Model_Test::count(); // テーブルの全行数をカウント保存系の処理には、insert, update, save, createを用意しています
insert($insert_ignore = false)
Section titled “insert($insert_ignore = false)”insertメソッドは、第一引数にINSERT IGNORE...を使用するか否かのフラグを受け取ります
しかし、INSERT IGNOREを使用するとINSERTに使用する処理時間が若干増えます
デフォルトの動作はなるべく早く軽くしておきたいため、引数を省略した場合は通常のINSERTを行います
引数 boolean $insert_ignore trueが渡されるとINSERT IGNORE句を使用する(デフォルトは使用しない) 戻り値 挿入に成功すればtrue
挿入に失敗(INSERT IGNOREで挿入が起きなかった場合も)したらfalse
また、保存に成功した場合、挿入されたidがidプロパティに反映されます
// まだDBに未挿入のデータはinsertを用いる$model = new Model_Test(array('hoge' => 'foo'));$model->insert(); // DBに挿入される$model->id; // 挿入されたIDが格納されている
$model->insert(); // そのまま再INSERTしようとするとキー重複で例外発生$model->insert(true); // trueを渡すとINSERT IGNOREになり、キー重複の例外が発生しないupdate()
Section titled “update()”引数 なし 戻り値 更新に成功したらtrue、更新に失敗したらfalse
// DBから取得したデータならupdateを用いる$model = Model_Test::find(1);$model->content = 'rewrite from PHP';$model->update(); // DBの値が更新されるsave()
Section titled “save()”引数 なし 戻り値 保存に成功すればtrue、失敗すればfalse
saveメソッドはINSERT ... ON DUPLICATE KEY UPDATE ...を使用します
DBにUNIQUE等の制約がかかっているカラムがあるとしたときに、
重複する値がDBに存在している場合は、モデルの値でDBを更新します
重複する値がDBに存在していない場合は、挿入を行います
// fuel/packages/nextengine/classes/nextengine/api/client/router.phpの内部
private function _createUser($company_id) { $user_info = parent::apiExecute('/api_v1_login_user/info'); $user_info = $user_info['data'][0];
$user = new \Model_User(); $user->company_id = $company_id; $user->uid = $user_info['uid']; $user->next_engine_id = $user_info['pic_ne_id']; $user->email = $user_info['pic_mail_address']; $user->access_token = $this->_access_token; $user->refresh_token = $this->_refresh_token; $user->created_at = \DB::expr('NOW()');
$user->save(); // INSERT or UPDATE
return $user; }create(array $columns, array $values) {
Section titled “create(array $columns, array $values) {”createメソッドはstaticメソッドとして提供されており、
引数に特定の形式で配列を与えると、DBに挿入を行い、そのデータをモデルクラスのインスタンスとして返します
引数 string[] $columns 挿入するカラム名の配列 array[] $values 値の配列、指定する順序は$columnsの順序と対応している必要がある 戻り値 挿入されたデータのインスタンスの配列
Model_Test::create( array( 'username', 'password', ), array( array('hogehoge', 'hugahuga', 'foooooooooo'), // usernameに挿入する値の配列 array('foobar', 'hige', 'fizzbuzz'), // passwordに挿入する値の配列 ));削除処理は、deleteを用意しています
delete()
Section titled “delete()”deleteメソッドは物理削除を行います
論理削除は今のところ実装していません
引数 なし 戻り値 削除に成功すればtrue、失敗したらfalse
$models = Model_Test::findAll();foreach($models as $model) { $model->delete(); // DBから物理削除する(findAllと組み合わせて全件削除)}バルクインサート
Section titled “バルクインサート”大量のデータを外部から取得し、素早く一度にDBへ挿入する際に便利ですが、
現状、実装する予定はありません
モデルを扱う上での注意点
Section titled “モデルを扱う上での注意点”created_atとupdated_atについて
Section titled “created_atとupdated_atについて”MySQLの仕様
Section titled “MySQLの仕様”ネクストエンジンアプリ基盤ではcreated_atのデフォルト値にCURRENT_TIMESTAMP、
updated_atのデフォルト値にCURRENT_TIMESTAMP on UPDATE CURRENT_TIMESTAMPが指定されています
これらの指定が前提となっているため、ネクストエンジンアプリ基盤(PHPコード)からこれらのカラムについて一切操作を行いません
ここで気をつけるべき事項は、
既存のDBの内容と全く同じ内容でupdateを実行した場合、成功はするがupdated_atは更新されないことです
何か値を変更して更新した場合にはupdated_atは更新されます
この挙動にご注意下さい
insert, update, save時のプロパティの更新
Section titled “insert, update, save時のプロパティの更新”挿入や保存をした際に、
DBにはその日時が反映されていますが、
モデルのプロパティのcreated_atとupdated_atは更新されません
これらの情報をモデルのプロパティにも反映させたい場合、
挿入や更新後に、一度SELECT文を発行せざるを得ないため、パフォーマンスの観点から実装していません
具体的なコードを上げます
<?php$model = Model_Hoge::find(1);$model->hoge = 1;$model->save();
$model->updated_at; // 更新されてない
/////////////////////////////////////////
$model = new Model_Hoge();$model->hoge = 1;
$model->save();
$model->created_at; // 更新されてない$model->updated_at; // 更新されてないcreated_atやupdated_atを処理に使用したい場合には、挿入および保存の挙動にご注意下さい
これら日時に関するプロパティを更新したいケースのためにフックを用意しています
そちらに日付更新の処理を実装して下さい
各種DBとの通信するメソッドの前後に挟める各種フックを用意しています
| フック名 | フックが呼び出されるタイミング | メソッドに渡される引数 |
|---|---|---|
before_save | save(), insert(), update() | before_save() |
after_save | save(), insert(), update() | after_save(boolean $success) |
before_insert | insert() | before_insert() |
after_insert | insert() | after_insert(boolean $success) |
before_update | update() | before_update() |
after_update | update() | after_update(boolean $success) |
before_delete | delete() | before_delete() |
after_delete | delete() | after_delete(boolean $success) |
before_validate | validate() | before_validate() |
after_validate | validate() | after_validate(boolean $success) |
after_find | find(), findAll(), findBy(), findLike() | after_find(array $record) |
ほぼすべてのメソッドがインスタンスメソッドです
メソッド内部で$thisを用いればインスタンスにアクセスすることができます
ですが、find系のメソッドは全てstaticメソッドです
そのためafter_findのみstaticメソッドとなっていますご注意下さい
具体的なコードを挙げます
class Model_Admin extends Model_Base { public $username; public $password;
// 挿入前にパスワードをハッシュ化 // @override public function before_insert($query) { $this->password = password_hash($this->password); }
// ユーザ名とパスワードを渡し、それにマッチするユーザが居ればtrueを、いなければfalseを返す public static function verify($username, $raw_password) { $admin = self::findBy('username', $username); if(is_null($admin)) return false;
$admin = $admin[0]; if(password_verify($raw_password, $admin->password)) { return true; } else { return false; } }}- 2015/02/17: モデル - Model_Baseクラスページ作成
ベンチマーク - マジックメソッドを使用したプロパティアクセス ↩
Inflector - クラス - FuelPHP ドキュメント
http://fuelphp.jp/docs/1.7/classes/inflector.html#/method_pluralize ↩
はじめに ↩