イントロダクション
Eloquent ORMはLaravelに含まれており、美しくシンプルなアクティブレコードによるデーター操作の実装です。それぞれのデータベーステーブルは関連する「モデル」と結びついています。
使用を始める前に、app/cofig/database.php
で設定を確実に済ませてください。
基本的な使用法
使用開始するには、Eloquentモデルを作成します。通常、モデルはapp/model
ディレクトリーに置かれますが、どこにでも自由に設置し、composer.json
ファイルでオートロードするように指定することもできます。
Eloquentモデルを定義する
class User extends Eloquent {}
User
モデルにどのテーブルを使用するのかをEloquentに指定していないことに注意してください。クラス名を小文字の複数形にしたものが、テーブル名として使用されます。他の名前を使用したい場合はそれを指定します。ですからこの場合、EloquentはUser
モデルをusers
テーブルに保存します。モデルのtable
プロパティを定義し、テーブルを指定することもできます。
class User extends Eloquent {
protected $table = 'my_users';
}
注目:Eloquentは更にテーブルの主キーとして
id
というカラムであると仮定しています。この規約をオーバーライドする場合はprimaryKey
プロパティを定義してください。同様に、connection
プロパティを定義することで、そのモデルを取り扱うデータベース接続をオーバーライドすることもできます。
一度モデルを定義したら、テーブルのレコードを取得したり、作成したりする準備は整います。デフォルトではupdated_at
とcreated_at
カラムをテーブルに用意しておく必要があることに注意してください。もしこれらのカラムが自動的に更新されたくないのでしたら、$timestamps
をfalse
に設定してください。
全モデルを取得する
$users = User::all();
主キーで1レコードを取得する
$user = User::find(1);
var_dump($user->name);
クエリービルダーの全メソッドは、Eloquentモデルを使ってクエリーする場合に使用できます。
主キーでモデルを取得するか、例外を投げる
モデルが見つからない場合、例外を投げたいこともあることでしょう。例外はApp::error
ハンドラーを使用して捕捉し、404ページを表示することが可能です。
$model = User::findOrFail(1);
$model = User::where('votes', '>', 100)->firstOrFail();
エラーハンドラーを登録するには、ModelNotFoundException
をリッスンしてください。
use Illuminate\Database\Eloquent\ModelNotFoundException;
App::error(function(ModelNotFoundException $e)
{
return Response::make('Not Found', 404);
});
Eloquentモデルを使用し、クエリーを行う
$users = User::where('votes', '>', 100)->take(10)->get();
foreach ($users as $user)
{
var_dump($user->name);
}
Eloquentを使った集計
もちろんクエリービルダーの集計関数も使用できます。
$count = User::where('votes', '>', 100)->count();
Fluentインターフェイスで必要なクエリーを生成できない場合、whereRaw
を使用してください。
$users = User::whereRaw('age > ? and votes = 100', array(25))->get();
結果を分割する
大きな(数千の)Eloquentレコードを処理する必要がある場合、RAMが食いつぶされないように、chunk
コマンドを利用してください。
User::chunk(200, function($users)
{
foreach ($users as $user)
{
//
}
});
最初の引数には「チャンク(塊)」ごとにいくつのレコードを処理するかを渡します。2番めの引数にはクロージャーを渡し、そのデータベースからの結果をチャンクごとに処理するコードを記述します。
クエリー接続の指定
Eloquentクエリーの実行時に、データベース接続を指定することができます。on
メソッドを使用してください。
$user = User::on('connection-name')->find(1);
複数代入
新しいモデルを作成する時、モデルのコンストラクターに属性の配列を渡します。それらの属性は複数代入としてモデルに結び付けられます。これは便利ですが、ユーザーの入力を闇雲にモデルに渡してしまうことは、重大なセキュリティーの欠陥になり得ます。何でもかんでもユーザー入力をモデルに渡せば、ユーザーは何でもモデルの全属性を変更できるのです。 このため、全てのEloquentモデルはデフォルトで複数代入されないように保護されています。
複数代入を使用するにはfillable
かguarded
プロパティをモデルに設定してください。
モデルに複数代入可能な属性を定義する
fillable
プロパティーは複数代入を許す属性です。クラスとインスタンスの両レベルで設定できます。
class User extends Eloquent {
protected $fillable = array('first_name', 'last_name', 'email');
}
この例では、リストされている3属性のみ、複数代入されます。
モデルで保護する属性を定義する
このfillable
の反対がguarded
です。「ホワイトリスト」ではなく、「ブラックリスト」として利用します。
class User extends Eloquent {
protected $guarded = array('id', 'password');
}
注目:
guarded
を使用する場合、守られて(guarded)いないカラムが更新されてしまうため、Input::get()
や、ユーザーがコントロールできる配列をそのまま、save
やupdate
メソッドには渡すべきではありません。
全属性を複数代入から保護する
この例ではid
とpassword
属性が複数代入の対象外となります。その他の属性は複数代入されます。全属性を複数代入から保護するには、guardプロパティーを使用してください。
protected $guarded = array('*');
INSERT、UPDATE、DELETE
モデルから新しいレコードを作成するには、モデルの新しいインスタンスを作成し、save
メソッドを呼び出します。
新しいモデルを保存する
$user = new User;
$user->name = 'John';
$user->save();
注目: 典型的にEloquentモデルは自動的にキーを増加させ使用します。しかしながら自分でキーを設定したい場合は、モデルの
incrementing
プロパティをfalse
にセットしてください。
もしくは一行で新しいモデルを保存するためにcreate
メソッドを使用することも可能です。メソッドから挿入されたモデルのインスタンスがリターンされます。しかしながら全Eloquentモデルは複数代入から保護されているため、これを使用する前に操作対象のモデルに対しfillable
かguarded
プロパティのどちらかを指定しておく必要があります。
IDの自動増分を設定しているモデルを保存、もしくは新しく作成した後、IDを取得したい場合は、オブジェクトのid
属性にアクセスしてください。
$insertedId = $user->id;
モデルに保護された属性を設定する
class User extends Eloquent {
protected $guarded = array('id', 'account_id');
}
モデルのcreateメソッドを使用する
// データベースに新しいユーザーを作成する…
$user = User::create(array('name' => 'John'));
// ユーザーを属性で取得する。存在しない場合は、作成する…
$user = User::firstOrCreate(array('name' => 'John'));
// ユーザーを属性で取得する。存在しない場合は、インスタンス化する…
$user = User::firstOrNew(array('name' => 'John'));
取得したモデルを更新する
モデルを更新する場合、初めに取得し、その内容を変更し、それからsave
メソッドを使用します。
$user = User::find(1);
$user->email = 'john@foo.com';
$user->save();
モデルと関連を保存する
時々、モデルだけでなく、全部の関連を保存したい場合もあるでしょう。そんな場合は、push
メソッドを使用してください。
$user->push();
複数のモデルに対しクエリーで更新することもできます。
$affectedRows = User::where('votes', '>', 100)->update(array('status' => 2));
注意:上記のように一連のモデルをまとめてEloquentクエリービルダーで更新する場合、モデルのイベントは発行されません。
存在しているモデルを削除する
削除するには、シンプルにインスタンスに対しdelete
メソッドを使用してください。
$user = User::find(1);
$user->delete();
存在しているモデルをキーで削除する
User::destroy(1);
User::destroy(array(1, 2, 3));
User::destroy(1, 2, 3);
もちろん、deleteクエリーで複数のモデルを削除できます。
$affectedRows = User::where('votes', '>', 100)->delete();
モデルのタイムスタンプだけをアップデートする
モデルのタイムスタンプをただアップデートしたい場合は、touch
メソッドを使用します。
$user->touch();
ソフトデリート
モデルをソフトデリートする場合、データーベースから実際に削除されるわけではありません。代わりにレコードへdeleted_at
タイムスタンプをセットします。モデルのソフトでリートを有効にするためには、そのモデルに対しSoftDeletingTrait
を適用してください。
use Illuminate\Database\Eloquent\SoftDeletingTrait;
class User extends Eloquent {
use SoftDeletingTrait;
protected $dates = ['deleted_at'];
}
deleted_at
カラムをテーブルに追加するには、マイグレーションでsoftDeletes
メソッドを使用してください。
$table->softDeletes();
これでモデルに対しdelete
メソッドを使用すれば、deleted_at
カラムに現在の時間がセットされます。ソフトデリートされたモデルに対しクエリーされても「削除済み」のモデルはクエリー結果に含まれません。
ソフトデリートされたモデルも結果に含めるように強制する
ソフトデリートされたモデルも結果に含めたい場合、withTrashed
メソッドをクエリーに使用してください。
$users = User::withTrashed()->where('account_id', 1)->get();
定義済みの関連付けに対して、withTrashed
メソッドを使用することができます。
$user->posts()->withTrashed()->get();
もしソフトデリートされたモデルのみに対しクエリーしたい場合はonlyTrashed
メソッドを使用します。
$users = User::onlyTrashed()->where('account_id', 1)->get();
ソフトデリートされたモデルを有効状態へ復活させるにはrestore
メソッドを使ってください。
$user->restore();
restore
メソッドはクエリーと共に使用することもできます。
User::withTrashed()->where('account_id', 1)->restore();
withTrashed
と同様に、restore
メソッドも、関連付けに対して使用することができます。
$user->posts()->restore();
モデルを本当にデータベースから削除したい場合は、forceDelete
メソッドを使用してください。
$user->forceDelete();
これも関連付けに対して使用することもできます。
$user->posts()->forceDelete();
指定したモデルのインスタンスがソフトデリートされたものか判断するには、trashed
メソッドを使用します。
if ($user->trashed())
{
//
}
タイムスタンプ
デフォルトでEloquentはデータベースのcreated_at
(作成日時)とupdated_at
(更新日時)カラムを自動的に更新します。上記のタイムスタンプカラムをテーブルに追加するだけで、Eloquentは残りの面倒を見てくれます。Eloquentによる2つのカラムのメンテナンスがご希望でなければ、モデルに以下のプロパティを追加してください。
自動タイムスタンプを無効にする
class User extends Eloquent {
protected $table = 'users';
public $timestamps = false;
}
カスタムタイムスタンプフォーマットを指定する
タイムスタンプのフォーマットをカスタマイズしたい場合は、getDateFormat
メソッドをモデルの中でオーバーライドしてください。
class User extends Eloquent {
protected function getDateFormat()
{
return 'U';
}
}
クエリースコープ
クエリースコープを定義する
スコープはモデルに対するクエリーのロジックを簡単に再使用できるようにしてくれます。スコープを定義するには、モデルのメソッド名にscope
をプレフィックスとして付けてください。
class User extends Eloquent {
public function scopePopular($query)
{
return $query->where('votes', '>', 100);
}
public function scopeWomen($query)
{
return $query->whereGender('W');
}
}
クエリースコープを使用する
$users = User::popular()->women()->orderBy('created_at')->get();
動的スコープ
スコープでパラメーターを受け取りたい場合もあるでしょう。スコープのメソッドにパラメーターを追加するだけです。
class User extends Eloquent {
public function scopeOfType($query, $type)
{
return $query->whereType($type);
}
}
スコープの呼び出し時に、パラメーターを渡してください。
$users = User::ofType('member')->get();
グローバルスコープ
モデルの全クエリーに適用させるスコープを定義したいこともあるでしょう。実際に、Eloquentの「ソフトデリート」機能はこれを使用しています。グローバルスコープは、PHPのトレイトとIlluminate\Database\Eloquent\ScopeInterface
の実装を組み合わせ、定義します。
最初にトレイトを定義します。例として、Laravelに組み込まれているSoftDeletingTrait
を使いましょう。
trait SoftDeletingTrait {
/**
* モデルのソフトデリートを起動する。
*
* @return void
*/
public static function bootSoftDeletingTrait()
{
static::addGlobalScope(new SoftDeletingScope);
}
}
Eloquentモデルにbootトレイトの名前Trait
という命名規則に一致するメソッドが存在するならば、Eloquentモデルの起動時に、グローバールスコープを登録するか、他の皆さんが行いたいことを行う機会を作るために、そのトレイトメソッドは呼び出されます。スコープはapply
とremove
メソッドの2つを持つ、ScopeInterface
を実装しなくてはなりません。
apply
メソッドは、Illuminate\Database\Eloquent\Builder
クエリービルダーオブジェクトを受け取り、そのスコープで付け加えたいwhere
節を追加してから、返す責任があります。remove
メソッドも「ビルダー」オブジェクトを受け取り、apply
により付け加えられたアクションを取り消してから、返す必要があります。言い換えれば、remove
は追加されたwhere
節(もしくは他の節)を削除しなくてはなりません。これにより、SoftDeletingScope
のメソッドは、次のようになります。
/**
* 指定されたEloquentクエリービルダーにスコープを適用する。
*
* @param \Illuminate\Database\Eloquent\Builder $builder
* @return void
*/
public function apply(Builder $builder)
{
$model = $builder->getModel();
$builder->whereNull($model->getQualifiedDeletedAtColumn());
}
/**
* 指定されたEloquentクエリービルダーからスコープを取り除く。
*
* @param \Illuminate\Database\Eloquent\Builder $builder
* @return void
*/
public function remove(Builder $builder)
{
$column = $builder->getModel()->getQualifiedDeletedAtColumn();
$query = $builder->getQuery();
foreach ((array) $query->wheres as $key => $where)
{
// where節がソフトデリートのデータ制約によるものであれば、クエリーから削除し
// whereのキーをリセットする。これにより開発者は削除済みモデルを関連付けに
// 含めることができ、遅延ロードされた結果をセットできる。
if ($this->isSoftDeleteConstraint($where, $column))
{
unset($query->wheres[$key]);
$query->wheres = array_values($query->wheres);
}
}
}
リレーション
もちろん、データベーステーブルは他のテーブルと関連があることでしょう。例えば、ブログポストは多くのコメントを持つでしょうし、注文は購入したユーザーと関連しているでしょう。Eloquentはこれらの関連を簡単に操作できるように管理します。Laravelは多くの関連をサポートしています。
1対1
1対1関係を定義する
1対1関係は基本です。例えば、User
モデルはPhone
モデルを一つ持つとしましょう。この関係をEloquentで定義します。
class User extends Eloquent {
public function phone()
{
return $this->hasOne('Phone');
}
}
hasOne
メソッドの最初の引数は関係するモデルの名前です。関連を定義したら、Eloquentの動的プロパティを使用し取得できます。
$phone = User::find(1)->phone;
この文は以下のSQLとして動作します。
select * from users where id = 1
select * from phones where user_id = 1
Eloquentはモデル名を元に関連の外部キーを決めることに注意してください。この場合、Phone
モデルはuser_id
外部キーを使用しようとします。この規約をオーバーライドしたい場合、hasOne
メソッドの第2引数を指定してください。さらにその関連で使用されるべきローカルカラムを指定するために、メソッドの第3引数を渡すこともできます。
return $this->hasOne('Phone', 'foreign_key');
return $this->hasOne('Phone', 'foreign_key', 'local_key');
逆の関連を定義する
Phone
モデルの関連を逆に定義するには、belongsTo
メソッドを使います。
class Phone extends Eloquent {
public function user()
{
return $this->belongsTo('User');
}
}
上の例で、Eloquentは、phones
テーブルのuser_id
カラムを探します。もし、他の外部キーを定義したい場合は、belongsTo
メソッドの第2引数に渡してください。
class Phone extends Eloquent {
public function user()
{
return $this->belongsTo('User', 'local_key');
}
}
さらに、親のテーブルの関連するカラム名を第3引数として指定することもできます。
class Phone extends Eloquent {
public function user()
{
return $this->belongsTo('User', 'local_key', 'parent_key');
}
}
1対多
1対多の関連の例には、「プログポストは多くのコメントを持つ(has many)」があります。このように関係をモデルに定義します。
class Post extends Eloquent {
public function comments()
{
return $this->hasMany('Comment');
}
}
これで動的プロパティを使用し、ポストのコメントへアクセスできます。
$comments = Post::find(1)->comments;
取得するコメントを制限する必要がある場合、comments
メソッドを呼び出し条件をチェーンでつなげることができます。
$comments = Post::find(1)->comments()->where('title', '=', 'foo')->first();
ここでも、hasMany
メソッドの第2引数を指定し、外部キーの規約をオーバーライドできます。また、hasOne
リレーションと同様に、ローカルカラムも指定できます。
return $this->hasMany('Comment', 'foreign_key');
return $this->hasMany('Comment', 'foreign_key', 'local_key');
逆の関連を定義する
Comment
モデルに逆の関係を定義するには、belongsTo
を使用します。
class Comment extends Eloquent {
public function post()
{
return $this->belongsTo('Post');
}
}
多対多
多対多の関係はもっと複雑なリレーションタイプです。このような関係として、ユーザ(user)が多くの役目(roles)を持ち、役目(role)も大勢のユーザー(users)に共有されるという例が挙げられます。例えば、多くのユーザーは"管理者"の役目を持っているとしましょう。users
、roles
、role_user
の3テーブルがこの関係には必要です。role_user
テーブルは関係するモデル名をアルファベット順に並べたもので、user_id
とrole_id
を持つ必要があります。
多対多の関係はbelongsToMany
メソッドで定義します。
class User extends Eloquent {
public function roles()
{
return $this->belongsToMany('Role');
}
}
では、User
モデルを通して、役割を取得しましょう。
$roles = User::find(1)->roles;
もしピボットテーブル名に規約から外れた名前を使用したい場合は、belongsToMany
メソッドの第2引数に渡してください。
return $this->belongsToMany('Role', 'user_roles');
更に関係するキー名の規約をオーバーライドするには:
return $this->belongsToMany('Role', 'user_roles', 'user_id', 'foo_id');
もちろん、Role
モデルに対し逆のリレーションを定義することもできます。
class Role extends Eloquent {
public function users()
{
return $this->belongsToMany('User');
}
}
他テーブルを経由した多対多
「〜経由の多対多(has many
through)」リレーションは、中間テーブルを介する離れた関連付けへアクセスするための、便利な近道を提供します。例えば、Country
モデルはUsers
モデルを経由して、多くのPosts
を所有することでしょう。
countries
id - 整数
name - 文字列
users
id - 整数
country_id - 整数
name - 文字列
posts
id - 整数
user_id - 整数
title - 文字列
例えposts
テーブルにcountry_id
が存在しなくても、hasManyThrough
リレーションでは、CountryのPostへ、$country->posts
によりアクセスできます。関連を定義付けましょう。
class Country extends Eloquent {
public function posts()
{
return $this->hasManyThrough('Post', 'User');
}
}
関連付けのキーを手動で指定したい場合、メソッドの第3引数と、第4引数として渡して下さい。
class Country extends Eloquent {
public function posts()
{
return $this->hasManyThrough('Post', 'User', 'country_id', 'user_id');
}
}
多様対応関係
多様対応(polymorphic)関係はあるモデルを一つの関係だけで、複数のモデルに所属させるものです。例えば写真モデルがあり、スタッフモデルと注文モデルの両方に所属しているとしましょう。この関係を次のように定義できます。
class Photo extends Eloquent {
public function imageable()
{
return $this->morphTo();
}
}
class Staff extends Eloquent {
public function photos()
{
return $this->morphMany('Photo', 'imageable');
}
}
class Order extends Eloquent {
public function photos()
{
return $this->morphMany('Photo', 'imageable');
}
}
多様対応関係で取得する
では、スタッフと注文を通して、写真を取得してみましょう。
$staff = Staff::find(1);
foreach ($staff->photos as $photo)
{
//
}
多様対応関連の所有者を取得する
しかし、本当の「多様対応関係」マジックは、スタッフと注文を写真('Photo')モデルを通じてアクセスする場合に現れます。
$photo = Photo::find(1);
$imageable = $photo->imageable;
Photo
モデルのimageable
関係は写真を所有しているモデルのタイプにより、Staff
もしくはOrder
どちらかのインスタンスをリターンします。
多様対応関係のテーブル構造
これがどの様に動作するのか理解を助けるために、多様対応関係のデータベース構造を説明しましょう。
staff
id - 整数
name - 文字列
orders
id - 整数
price - 整数
photos
id - 整数
path - 文字列
imageable_id - 整数
imageable_type - 文字列
注目すべき鍵となるフィールドは、photos
テーブルのimageable_id
とimageable_type
です。この例の場合、IDは所有しているスタッフか注文のIDで、タイプは所有しているモデルのクラス名です。これにより、ORMが所有しているモデルのタイプを決定し、imageable
関係でアクセスした時にそのモデルをリターンする仕組みです。
多対多 多様対応関係
多対多 多様対応関係のテーブル構造
伝統的な多様対応関係に加え、多対多-多様対応関係も指定することができます。例えば、ブログのPost
とVideo
モデルは、Tag
モデルに対する多様対応関係を共有できます。最初に、テーブル構造を確認しましょう。
posts
id - 整数
name - 文字列
videos
id - 整数
name - 文字列
tags
id - 整数
name - 文字列
taggables
tag_id - 整数
taggable_id - 整数
taggable_type - 文字列
次に、モデルにその関係を用意しましょう。Post
とVideo
モデルは、tags
メソッドを通じ、両方共にmorphToMany
関係を持ちます。
class Post extends Eloquent {
public function tags()
{
return $this->morphToMany('Tag', 'taggable');
}
}
Tag
モデルでは、それぞれの関係を表すメソッドを定義します。
class Tag extends Eloquent {
public function posts()
{
return $this->morphedByMany('Post', 'taggable');
}
public function videos()
{
return $this->morphedByMany('Video', 'taggable');
}
}
リレーションの問い合わせ
SELECT時にリレーションを問い合わせる
モデルのレコードにアクセスする場合、関連付けたモデルのレコードに基づいて制限したい場合もあると思います。例えば、最低でも一つのコメントを持つ、全ブログポストを取得したい場合です。これを行うためにはhas
メソッドを使用します。
$posts = Post::has('comments')->get();
演算子とレコード数も指定できます。
$posts = Post::has('comments', '>=', 3)->get();
もっと強い力がほしいなら、has
の問い合わせに、"where"で条件をつけるために、whereHas
やorWhereHas
を利用して下さい。
$posts = Post::whereHas('comments', function($q)
{
$q->where('content', 'like', 'foo%');
})->get();
動的プロパティ
Eloquentは動的プロパティーを使用して関係にアクセスする方法を提供しています。Eloquentは自動的にその関係をロードし、賢いことにget
(1対多関係)メソッドとfirst
(1対1関係)メソッドを使い分けます。動的プロパティーでアクセスするにはその関係名をメソッド名として使います。例えば、次のPhone
モデルをご覧ください。
class Phone extends Eloquent {
public function user()
{
return $this->belongsTo('User');
}
}
$phone = Phone::find(1);
以下のようにメールアドレスをechoします。
echo $phone->user()->first()->email;
その代わりに、もっとシンプルで短く記述できます。
echo $phone->user->email;
複数の結果をリターンするリレーションは、
Illuminate\Database\Eloquent\Collection
クラスのインスタンスを返します。
Eagerローディング
EagerローディングはN+1クエリー問題を軽減するために存在しています。例えば、Book
モデルがAuthor
モデルと関連していると考えてください。関係はこのように定義されます。
class Book extends Eloquent {
public function author()
{
return $this->belongsTo('Author');
}
}
では、次のコードを考察してみましょう。
foreach (Book::all() as $book)
{
echo $book->author->name;
}
このループでは、まず全ての本をテーブルから取得するために1クエリー実行され、それから著者をそれぞれの本について取得します。ですから、25冊あるならば、このループで26クエリーが発生します。
ありがたいことに、クエリーの数を徹底的に減らすためにEagerローディングを使うことができます。with
メソッドを使い、指定してください。
foreach (Book::with('author')->get() as $book)
{
echo $book->author->name;
}
上のループでは、2つだけしかクエリーが実行されません。
select * from books
select * from authors where id in (1, 2, 3, 4, 5, ...)
賢くEagerローディングを使用することで、アプリケーションのパフォーマンスを大幅に向上させることができます。
もちろん、一度に複数の関係をEagerローディングすることもできます。
$books = Book::with('author', 'publisher')->get();
ネストした関連でさえ、Eagerローディングできます。
$books = Book::with('author.contacts')->get();
上記では、author
関係がEagerローディングされ、それからauthorのcontacts
関係がEagerローディングされます。
Eagerローディングの条件付け
関係をEagerローディングする時に、条件を付けたい場合もあります。一例をご覧ください。
$users = User::with(array('posts' => function($query)
{
$query->where('title', 'like', '%first%');
}))->get();
この例では、ユーザーの写真をEagerローディングしていますが、写真のタイトルが"first"という単語で構成されているものだけが取得されます。
もちろん、Eagerローディングクロージャーは、「検索条件」指定だけに限定されていません。ソート順も指定できます。
$users = User::with(array('posts' => function($query)
{
$query->orderBy('created_at', 'desc');
}))->get();
遅延Eagerローディング
既に存在しているモデルコレクションから直接関係をEagerローディングすることも可能です。これは関係するモデルをロードするかどうかを動的に決める場合や、キャッシュと結びつける場合に便利です。
$books = Book::all();
$books->load('author', 'publisher');
関連したモデルの挿入
関連するモデルを追加する
関連するモデルを新しく挿入する機会も多いと思います。例えば、ポストに対して新しいコメントを挿入する場合です。モデルに外部キーとしてpost_id
をセットする代わりに、新しいコメントを親のPost
モデルから直接挿入できます。
$comment = new Comment(array('message' => 'A new comment.'));
$post = Post::find(1);
$comment = $post->comments()->save($comment);
この例では、post_id
フィールドは自動的にセットされ、コメントが挿入されます。
関連するモデルを一度に追加することもできます。
$comments = array(
new Comment(array('message' => 'A new comment.')),
new Comment(array('message' => 'Another comment.')),
new Comment(array('message' => 'The latest comment.'))
);
$post = Post::find(1);
$post->comments()->saveMany($comments);
関係づけられているモデル(所属)
belongsTo
関係を更新する場合、associate
メソッドを使用できます。このメソッドは子供のモデルに外部キーをセットします。
$account = Account::find(10);
$user->account()->associate($account);
$user->save();
関連するモデルの挿入(多対多)
多対多の関連を操作する場合も、関連するモデルを挿入したい場合があるでしょう。User
とRole
の例を使い、続けて説明します。attach
メソッドを使用し、新しい役目をユーザーに追加することができます。
多対多モデルを追加する
$user = User::find(1);
$user->roles()->attach(1);
その関係に対してピボットテーブルに保存する属性の配列を渡すこともできます。
$user->roles()->attach(1, array('expires' => $expires));
もちろんattach
の反対はdetach
です。
$user->roles()->detach(1);
attach
とdetach
の両方共に、IDの配列を指定することができます。
$user = User::find(1);
$user->roles()->detach([1, 2, 3]);
$user->roles()->attach([1 => ['attribute1' => 'value1'], 2, 3]);
多対多モデルの追加にSyncを使用する
さらにsync
メソッドで関連するモデルを追加することもできます。sync
メソッドはピボットテーブルに設置するIDの配列を渡します。これによる操作が終了すると、そのモデルに対する中間テーブルは配列で指定されたIDだけになります。
$user->roles()->sync(array(1, 2, 3));
Syncを使い、ピボットデーターを追加する
IDを指定して他のピボットテーブル値を関連付けることもできます。
$user->roles()->sync(array(1 => array('expires' => true)));
時にはひとつのコマンドで、関連するモデルを作成し、追加したいこともあるでしょう。その場合、save
メソッドを使用してください。
$role = new Role(array('name' => 'Editor'));
User::find(1)->roles()->save($role);
この例では、新しいRole
モデルは保存され、ユーザーモデルに結び付けられます。更に以下の操作で、関連するテーブルに設定する属性の配列を渡すことができます。
User::find(1)->roles()->save($role, array('expires' => $expires));
親のタイムスタンプの更新
例えばComment
がPost
に所属しているような場合、子供のモデルが変更された場合、所属している(belongsTo)親のタイムスタンプも変更されると便利です。Comment
モデルが更新されたら、Post
が持っているupdated_at
タイムスタンプも自動的に更新したい場合などです。Eloquentでは簡単に実現できます。子供のモデルでtouches
プロパティーに、関連名を付け加えてください。
class Comment extends Eloquent {
protected $touches = array('post');
public function post()
{
return $this->belongsTo('Post');
}
}
これで、Post
に所有されているComment
が更新されると、Post
のupdated_at
カラムも更新されるようになります。
$comment = Comment::find(1);
$comment->text = 'Edit to this comment!';
$comment->save();
ピボットテーブルの操作
既に学んだように、多対多の関連を操作するためには、中間テーブルが必要です。Eloquentはこのテーブルを操作するために役に立つ手法を提供しています。例えば、User
オブジェクトが多くの関連するRole
オブジェクトを所有しているとしましょう。この関連にアクセスした後に、モデルに対するピボット(pivot
)テーブルにもアクセスできます。
$user = User::find(1);
foreach ($user->roles as $role)
{
echo $role->pivot->created_at;
}
取得されたRole
モデルそれぞれは、自動的にpivot
属性を与えられていることに注目してください。この属性は中間テーブルを表すモデルで、他のEloquentモデルと同様に使用できます。
デフォルトでは、pivot
オブジェクトにはキーしかありません。もし、あなたのピボットテーブルにその他の属性があるのでしたら、関連を定義する時にそれを指定する必要があります。
return $this->belongsToMany('Role')->withPivot('foo', 'bar');
これでRole
モデルのpivot
オブジェクトで、foo
とbar
属性はアクセス可能になります。
もしピボットテーブルでも自動的にcreated_at
とupdated_at
タイムスタンプを更新したい場合は、関係の定義に対してwithTimestamps
メソッドを使用してください。
return $this->belongsToMany('Role')->withTimestamps();
Pivotテーブルのレコードを削除する
モデルのピボットテーブルの全レコードを削除したい場合は、detach
メソッドを使用してください。
User::find(1)->roles()->detach();
この操作はroles
テーブルからレコードを削除せず、ピボットテーブルに対してだけ操作されることに注目してください。
ピボットテーブル上のレコードを更新する
場合により、ピボットテーブルを削除せずに、更新する必要があることもあるでしょう。存在するピボットテーブルを更新したい場合は、updateExistingPivot
メソッドが使用できます。
User::find(1)->roles()->updateExistingPivot($roleId, $attributes);
カスタムピボットモデルを定義する
Laravelではカスタムピボットモデルを定義することさえできます。カスタムモデルを定義するためには、最初にEloquent
を拡張したBase
モデルクラスを作成します。そして他のEloqeuntモデルでは、デフォルトのEloquent
の代わりに、このカスタムベースモデルを拡張しましょう。ベースモデルの中に、カスタムピボットモデルのインスタンスを返す、次のような関数を追加してください。
public function newPivot(Model $parent, array $attributes, $table, $exists)
{
return new YourCustomPivot($parent, $attributes, $table, $exists);
}
コレクション
get
メソッドであれ、関連によるものであれ、Eloquentが複数のレコードをリターンする場合、Eloquent
Collection
オブジェクトが返されます。このオブジェクトはIteratorAggregate
PHPインターフェイスの実装で、そのため配列のように操作できます。更に、結果を操作するためのバラエティーに富んだ他のパワフルなメソッドも持っています。
特定のキーをコレクションに含んでいるか調べる。
例えば、contains
メソッドを使用し、指定した主キーを結果に含んでいるか調べることができます。
$roles = User::find(1)->roles;
if ($roles->contains(2))
{
//
}
コレクションは配列やJSONに変換することもできます。
$roles = User::find(1)->roles->toArray();
$roles = User::find(1)->roles->toJson();
コレクションを文字列にキャストすると、JSONがリターンされます。
$roles = (string) User::find(1)->roles;
コレクションの反復処理
Eloquentコレクションは、中に含んでいるアイテムをループしたり、フィルタリングしたりする便利なメソッドも持っています。
$roles = $user->roles->each(function($role)
{
//
});
コレクションのフィルタリング
コレクションをフィルタリングする場合、指定されたコールバックは、array_filterのコールバックとして利用されます。
$users = $users->filter(function($user)
{
return $user->isAdmin();
});
注目:コレクションをフィルタリングするか、JSONに変換する場合、
values
メソッドを最初に呼び出し、配列のキーをリセットしてください。
コレクションの各オブジェクトにコールバックを適用する
$roles = User::find(1)->roles;
$roles->each(function($role)
{
//
});
値でコレクションをソートする
$roles = $roles->sortBy(function($role)
{
return $role->created_at;
});
値でコレクションをソートする
$roles = $roles->sortBy('created_at');
カスタムコレクションタイプをリターンする
カスタムメソッドをつけた、カスタムコレクションオブジェクトをリターンしたい場合もあるでしょう。EloquentモデルのnewCollection
メソッドをオーバーライドしてください。
class User extends Eloquent {
public function newCollection(array $models = array())
{
return new CustomCollection($models);
}
}
アクセサーとミューテーター
アクセサーの定義
Eloquentはモデルの属性を設定したり取得したりする時に、内容を変更する便利な方法を提供しています。モデルのアクセサーを宣言するには、getFooAttribute
メソッドを定義するだけです。データベースのカラムがスネークケースであったとしても、メソッドはキャメルケースにしなくてはならないことに注意してください。
class User extends Eloquent {
public function getFirstNameAttribute($value)
{
return ucfirst($value);
}
}
上記の例は、first_name
カラムのアクセサーです。アクセサーに属性の値を渡していることに注目してください。
ミューテーターを定義する
ミューテーターも同じやり方で定義します。
class User extends Eloquent {
public function setFirstNameAttribute($value)
{
$this->attributes['first_name'] = strtolower($value);
}
}
日付ミューテーター
デフォルトでEloquentはcreated_at、updated_atカラムを Carbonのインスタンスに変換します。Carbonは便利なメソッドが揃っており、PHPネイティブのDateTime
クラスを拡張しています。
これらのフィールドの自動変換をカスタマイズ、またはこの変換を完全に無効にすることもモデルのgetDates
メソッドをオーバーライドすることで可能です。
public function getDates()
{
return array('created_at');
}
日付項目にはUNIXタイムスタンプ、日付文字列(Y-m-d
)、日付時間文字列、そしてもちろんDateTime
やCarbon
のインスタンスを設定することができます。
日付ミューテーターを完全に無効にしたい場合は、getDates
メソッドから空配列をリターンしてください。
public function getDates()
{
return array();
}
モデルイベント
Eloquentモデルは多くのイベントを発行します。以降のメソッドを利用し、モデルのライフサイクルの様々な時点をフックすることができます。:creating
、created
、updating
、updated
、saving
、saved
、deleting
、deleted
、restoring
、restored
いつでも新しいアイテムが最初に保存される場合に、creating
とcreated
イベントが発行されます。新しくないアイテムにsave
メソッドが呼び出されると、updating
とupdated
イベントが発行されます。どちらの場合にも、saving
とsaved
イベントは発行されます。
イベントにより保存操作をキャンセルする
もし、creating
、updating
、saving
、deleting
イベントでfalse
がリターンされると、そのアクションはキャンセルされます。
User::creating(function($user)
{
if ( ! $user->isValid()) return false;
});
モデルへbootメソッドを設定する
Eloquentモデルはstaticなboot
メソッドを持っており、イベントフックを登録するため便利に使用できます。
class User extends Eloquent {
public static function boot()
{
parent::boot();
// イベントのバインドを準備する…
}
}
モデルオブザーバー
モデルのイベントを取りまとめるにはモデルオブザーバーを登録してください。オブザーバークラスは対応するそれぞれのモデルイベントのメソッドを用意します。例えば、監視するのがcreating
、updating
、saving
イベントなら、その名前のメソッドを用意します。もちろん他のモデルイベント名も追加できます。
そのため、モデルオブザーバーは次のような形式になります。
class UserObserver {
public function saving($model)
{
//
}
public function saved($model)
{
//
}
}
オブザーバーのインスタンスはobserve
メソッドを使いモデルに登録してください。
User::observe(new UserObserver);
配列とJSONへの変換
モデルを配列に変換する
JSONでAPIを作成する場合、モデルと関連をしばしば配列やJSONに変換する必要が起きます。そのため、Eloquentはこれを行うメソッドを含んでいます。モデルとそれにロードされている関連を配列に変換するには、toArray
メソッドが使用できます。
$user = User::with('roles')->first();
return $user->toArray();
モデルの全コレクションを配列に変換することさえできることに注目してください。
return User::all()->toArray();
モデルをJSONに変換する
モデルをJSONに変換するには、toJson
メソッドを使用します。
return User::find(1)->toJson();
モデルをルートでリターンする
モデルやコレクションを文字列に変換する場合、JSON形式に変換されることに注目してください。これが意味するのはEloquentオブジェクトを直接アプリケーションのルートからリターンできるということです!
Route::get('users', function()
{
return User::all();
});
配列とJSONへの変換から特定の属性を除く
パスワードのように、モデルの配列やJSON形式に含める属性を制限したい属性もあります。そのためにはhidden
プロパティーをモデルで定義してください。
class User extends Eloquent {
protected $hidden = array('password');
}
注意:リレーションを隠している場合、動的なアクセサー名を使用せずに、リレーションのメソッド名を使用してください。
反対に、visible
プロパティでホワイトリストを定義することもできます。
protected $visible = array('first_name', 'last_name');
通常、データーベースのカラムに対応しない属性を追加する必要もあるでしょう。これを行うには、先ずシンプルに値を返すアクセサーを定義してください。
public function getIsAdminAttribute()
{
return $this->attributes['admin'] == 'yes';
}
アクセサーを作成したら、モデルのappends
プロパティーにその値を追加してください。
protected $appends = array('is_admin');
一度属性をappends
リストに追加したら、モデルの配列とJSON形式の両方へ、含まれるようになります。appends
配列の属性は、モデル中のvisible
とhidden
設定の影響を受けます。