Eloquentのちょっと進んだ使い方
2016-09-12 16:28
先日はLaravelのORマッパーであるEloquentを単独でライブラリとして利用する方法について説明させていただきました。
今回は実際にEloquentを使ってモデルを作っていきながら、簡単な保存メソッドや取得メソッド、カスタムコレクションや、スコープ機能、リレーションの構築、Eager Loadingなど、Eloquentの基本的な使い方について説明をしていきたいと思います。
なお、Eloquentの公式マニュアルはこちら。
日本語のマニュアルもありますが、英語のほうがマニュアルとしては使いやすかったです。
また、マニュアルには書かれていない便利なメソッドなどがあったりするので、
EloquentのAPIレファレンスも見ながら開発していくといいと思います。
前回に引き続き”MyLib”というライブラリを開発するという前提で話を進めます。
MyLib/Model/SomeModel.phpというファイルで下記のようなクラスを作ってみましょう。
クラスの骨格と簡単な保存メソッド
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
namespace MyLib\Model; use Illuminate\Database\Eloquent; class SomeModel extends Eloquent\Model { /** @var string 対象テーブル*/ protected $table = 'some_tables'; /** * 簡単な保存メソッド */ public function customSave($key, $val) { $this->$key = $val; return $this->save(); } } |
$tableというプロパティにSomeModelが対応するテーブル名を指定しておきます。
これでSomeModel::customSave(‘foo’,’bar’)でfooカラムにbarという値が保存されます。
実際にはEloquent\Modelから継承しているsave()メソッドを実行しています。
次からはこのクラスに実装するメソッドのみを切り出してご紹介します。
簡単な検索メソッド(とその拡張): get(), where()
1 2 3 4 5 6 7 8 9 10 11 |
/** * statusカラムが1の行だけをSELECTしてくるメソッドです * @param Eloquent\Builder $Builder ベースとなるクエリビルダー */ public static function findActive(Eloquent\Builder $Builder=null) { if(is_null($Builder)){ $Builder = self::query(); } return $Builder->where('status',1)->get(); } |
これでstatusカラムが1の行だけをSELECTしてくれます。
実際はSomeModel::findActive()のようにコールします。
おわかりのとおりEloquentではwhere(foo、bar)で”WHERE foo = bar”句を生成してくれます。
また、Eloquentではget()をコール時にDBへのリクエストが実行されます。
get()ではget([‘id’,’status’])のように取得してくるカラムを指定することができます。
さらにベースとなるクエリビルダーを引き渡せば、処理を拡張できます。
例えば
1 2 |
$Builder = SomeModel::where('group_id',5)->orderBy('created_at','desc')->take(10); $result = SomeModel::findActive($Builder); |
こうすれば、statusカラムが1かつ、group_idが5のものを、created_atカラムを降順に10件取得することができます。
where()についてはこちらをどうぞ。
カスタムコレクション
1 2 3 4 5 6 7 8 9 10 |
/** * カスタムコレクションの登録 * * @param array $models 検索結果の配列 * @return MyLib\Colleciton\SomeModelCollection */ public function newCollection(Array $models = []) { return new SomeModelCollection($models); } |
Eloquentでは検索結果はCollectionオブジェクトという配列をオブジェクト化したものを返します。
ただし、newCollection()メソッドを実装することで、検索結果を独自のコレクションオブジェクトにして返すことができます。
ここではMyLib\Collection\SomeModelCollectionオブジェクトを生成して返すようにしています。
(Mylib/Collection/SomeModelCollection.phpがあり、SomeModelCollectionというクラスが実装されていると仮定します)
こうしておけばSomeModelCollectionクラスで、結果の配列全体に対して特別な操作をしたり処理をするメソッドを個別に実装することが出来てコードがすっきりします。
特にこのメソッドをコール必要はなく、get()が実行されたときにnewCollection()メソッドが実装されていれば、
自動的にコールされて、新しいCollectionで検索結果が返ってきます。
CustomCollectionについてはこちらを参照してください。
スコープ機能
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
/** * some_columnというカラムが$some_dataのレコードだけを取得するwhere句を生成するメソッドです。 * @param Eloquent\Builder $Builder ベースとなるクエリビルダー(勝手についてきます) * @param string|array $some_data some_columnで絞り込みたいデータ */ public function scopeOfSomeKey(Eloquent\Builder $Builder, $some_data) { if(is_array($some_data)){ $method = 'whereIn'; }else{ $method = 'where'; } #クエリビルダに条件を付け足して返します return $Builder->$method('some_column',$some_data); } } |
Eloquentには「スコープ」という機能があります。
scopeOfというプレフィックスで始まるメソッドは、下記のように呼ぶことで具体的な処理をカプセル化できます。
1 2 3 |
$result = SomeModel::ofSomeKey('hoge')->get(); #または $result = SomeModel::ofSomeKey(['hoge','piyo'])->get(); |
ここでは引数である$some_dataが配列だったら、”WHERE key IN(val1, val2)”を生成してくれるwhereIn()メソッドをコールするようにしています。
逆に、引数が配列じゃなかったら”WHERE key = val”を生成するwhere()をコールしています。
この処理をスコープメソッドにまとめることで、外部からは引数が配列でも、配列じゃなくても同じように検索処理を実行することができます。
下記にwhereIn()の使い方とスコープ機能のリファレンスへのリンクを示しておきます。
リレーション
1 2 3 4 5 6 7 8 9 10 |
/** * HogeモデルとhasOneのリレーションを作成します * * @return \Illuminate\Database\Eloquent\Relations\hasOne * */ public function Hoge() { return $this->hasOne('MyLib\Model\Hoge','hoge_id'); } |
Eloquentではリレーションをメソッドで定義・実装します。
ここでは”Hoge”という別のモデルがあり、SomeModelとはhasOneの関係であるとします。
内部ではhasOne()というメソッドをコールしていて、リレーション相手のモデル名と、キーとなるカラムを指定します。
1 2 3 4 |
$result = SomeModel::where('status',1)->get(); foreach($result as $Some){ echo $Some->Hoge->name; } |
これでHogeモデルのnameプロパティを取得することが出来ます。
Eager Loding
先に説明したとおり、Eloquentではリレーションはメソッドをコールすることで実現されます。
検索時にリレーション先のカラムを参照していなかったり、検索後も
リレーション先のカラムを取得していなければEloquentはリレーションのメソッドを
一切実行しません。
必要になったときだけリレーションが構築され関連テーブルのデータを取得しに行きます。
この機能は”Lazy Loading“と呼ばれています。
ただし、取得した100行の結果に対してリレーション先のカラムを取得しようとすると
100回のクエリがリレーション先のテーブルに実行されてしまいます。
どうやらこれを”1+n”の問題と呼ぶらしいです。
最初の一回のクエリに加えて検索結果の分だけ(n回)クエリを実行してしまう問題のことです。
当然検索結果が多ければ多いほどパフォーマンスの問題が深刻になってきます。
そこであらかじめリレーショナルなプログラミングをすることが分かっている場合は、
先にリレーション先のテーブルのデータを取得し、
検索結果の配列に自動的にマージしてくれる機能があります。
これを”Eager Loading“と呼びます。
1 2 3 4 5 6 7 8 9 |
/** * データ取得時に関連するHogeオブジェクトも同時に取得するようにします * @param Eloquent\Builder $Builder * @return Eloquent\Builder */ public function scopeWithHoge(Eloquent\Builder $Builder) { return $Builder->with('Hoge'); } |
Eager LoadingはEloquent\Modelのwith()メソッドを使って実装できます。
引数にはリレーション先のモデル名を渡してあげます。
よく使うEager Loadingであれば上記の例のようにscope機能を使って短縮化しておくこともできます。
scopeを使った場合と使わなかった場合のクライアントコードはこうなります。
1 2 3 4 5 |
//scopeを使わなかった場合 SomeModel::with('Hoge')->get(); //scopeを使った場合 SomeModel::withHoge()->get(); |
そこまで大きい差はありませんが、とあるモデルとのリレーションをとる場合、
決まったORDERやWHEREがある場合、まとめておくとより便利になると思います。
さて、3回に続いたLaravelのEloquentについてのご紹介は
今回で終わりとなります。
簡単に振り返りますと、、、
まず第一回ではPHPの軽量ORMを比較し、Eloquentの簡単な特徴を振り返りました。
第二回ではEloquentをLaravelを使わずにORMのライブラリとして使うやり方を見てみました。
第三回にあたる今回では簡単な保存メソッドや取得メソッド、カスタムコレクションや、スコープ機能、リレーションの構築、Eager Loadingの機能を見ていきました。
弊社では現在Laravelの軽量版フレームワークであるLumenを使って、
ちょっとしたREST API風のアプリケーションを開発しはじめたところです。
非常に拡張性が高く、書いていて面白いフレームワークです。
興味のある方は是非弊社まで!