EloqeuentはLaravelの持つORM(データベースをマッピングしたモデル)です。これには他テーブルとの関係性をモデル内に定義するためのリレーションという仕組みが備わっています。
Eloquent:リレーション 6.x Laravel
例えば、次の様になります
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class User extends Model
{
/**
* ユーザーに関連する電話レコードを取得
*/
public function phone()
{
return $this->hasOne('App\Phone');
}
}
// 別ファイルで
/** @var App\Phone $phone ユーザに関連する電話レコードインスタンス */
$phone = User::find($id)->phone;
上記の様にプロパティとして呼び出すだけで適切なSQLが発行され、関連したレコードを元にしたモデルインスタンスを得られます。
この呼び出しだけでも便利なのですが保存でも活躍します。詳しくは次リンクの”関連したモデルの挿入/更新”です。
Eloquent:リレーション 6.x Laravel
例としては次になります。
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class User extends Model
{
/**
* ユーザーに関連する電話レコードを取得
*/
public function phone()
{
return $this->hasOne('App\Phone');
}
}
// 別ファイルで
$phone = new Phone($validatedRequest);
// user_idカラムの様なリレーションに使うカラムの値が自動で代入される保存処理
User::find($id)->phone()->save($phone);
リレーション用のidの代入が不要な保存です。かなり便利です。当然の様に1対多の保存も備わっています。
$post = App\Post::find(1);
$post->comments()->saveMany([
new App\Comment(['message' => 'A new comment.']),
new App\Comment(['message' => 'Another comment.']),
]);
話は変わってEloquentにはpushというメソッドもあります。これはリレーションを使った再帰保存です。具体的には次の様になります。
$post = App\Post::find(1); $post->comments[0]->message = 'Message'; $post->comments[0]->author->name = 'Author Name'; $post->push(); // postに関連するcommentテーブルのレコード一つ目のmessageカラムを更新して保存 // postに関連するcommentテーブルのレコード一つ目に関連するauthorテーブルのレコードのnameカラムを更新して保存
トランザクションを書いた後長々とsaveを書き続ける必要はありません。pushで事足ります。
pushメソッドにはもの足りない部分があります。それは先述した外部キーの自動割り当てです。これがないため、外部キーを手動で割り当てた後にpushかひとつづつrelation()->save($model)をするかの二択になりがちです。いいとこどりをしたいものです。いいとこどりをするには例えば次の様なメソッドが考えられます。既存のpushメソッドになかったrelation()->save($model)を増やしました。
<?php
namespace App\Models\Eloquents;
use App\Models\Eloquents\Contracts\HasRulesContract;
use App\Models\Eloquents\Traits\HasRules;
use Eloquent as Model;
use Illuminate\Database\Eloquent\Collection;
/**
* 認証機能持ち以外のモデルのベース
*/
abstract class BaseEloquent extends Model
{
/**
* リレーションを参照した自動キー割り当て機能を搭載したpushメソッド.
* 再帰によって呼び出し済みのリレーション先全てをsaveする.
* @param bool $skipSave このループでsaveをスキップするならばtrue. 主に一つ前のループでリレーションを元にした保存をした時に使用
* @return bool
*/
public function push($skipSave = false)
{
if (! $skipSave && ! $this->save()) {
return false;
}
// すべての関係をデータベースに同期するために単にスピンスルーします
// 関係を作成し、このpushメソッドを介して各モデルを保存します。
// モデルインスタンスのこれらのネストされたリレーションすべてに再帰します。
foreach ($this->getRelations() as $relationName => $models) {
$models = $models instanceof Collection ? $models->all() : [$models];
foreach (array_filter($models) as $model) {
// HasOneOrManyがあった上で保存に失敗したらreturn false
if (method_exists($this, $relationName) && $this->$relationName() instanceof HasOneOrMany) {
if ($this->$relationName()->save($model)) {
$successSave = true;
} else {
return false;
}
}
// 再帰
if (! $model->push($successSave ?? false)) {
return false;
}
}
}
return true;
}
}