はじめに
生SQLからEloquent Modelを作りたいと思ったことはないでしょうか?
3つほど方法があるので、ご紹介します。
DBでCREATE VIEWしてそのEloquent Modelを作る
以下のように、VIEWを生成し、そのVIEWに対するEloquent Modelを作ります。
CREATE VIEW view_user_with_groups AS (
SELECT
u.id,
u.email,
u.group_id,
g.name AS group_name
FROM users u
LEFT OUTER JOIN groups g ON u.group_id = g.id
);
use Illuminate\Database\Eloquent\Model;
class ViewUserWithGroup extends Model
{
}
hydrateを使ってクエリ結果をEloquent Modelにマッピングする
以下のように、クエリ結果をEloquent Modelにマッピングします。
$sql = <<< SQL
SELECT
u.id,
u.email,
u.group_id,
g.name AS group_name
FROM users u
LEFT OUTER JOIN groups g ON u.group_id = g.id
WHERE u.id = :id
SQL;
$user = DB::select(DB::raw($sql), ['id' => auth()->id()]);
$userWithGroup = UserWithGroup::query()->hydrate($user);
use Illuminate\Database\Eloquent\Model;
class UserWithGroup extends Model
{
}
fromSubを使って、Eloquent Modelのfromにサブクエリをセットする
以下のように、Eloquent Modelのfrom`にサブクエリをセットします。
use Illuminate\Database\Eloquent\Model;
class UserWithGroup extends Model
{
protected static function boot()
{
parent::boot();
static::addGlobalScope('default', function (Builder $builder) {
$sql = <<< SQL
SELECT
u.id,
u.email,
u.group_id,
g.name AS group_name
FROM users u
LEFT OUTER JOIN groups g ON u.group_id = g.id
SQL;
$builder->fromSub($sql, 'user_with_groups');
});
}
}
$userWithGroup = UserWithGroup::find(auth()->id());
ポイントは、fromSubの第2引数aliasをModel::getTable()の戻り値と一致させておくことです。
protected $table = '{table_name}';とテーブル名を直接指定してもOKです。
namespace Illuminate\Database\Eloquent;
abstract class Model implements ArrayAccess, Arrayable, Jsonable, JsonSerializable, QueueableEntity, UrlRoutable
{
public function getTable()
{
if (! isset($this->table)) {
return str_replace(
'\\', '', Str::snake(Str::plural(class_basename($this)))
);
}
return $this->table;
}
どういうときにどの方法を選ぶか
それぞれの方法のメリットとデメリットを列挙すると以下のようになります。
-
DBでCREATE VIEWしてそのEloquent Modelを作る- SQLと
Eloquent Modelが分離されるので、コードがシンプルになる -
CREATE VIEWもしくはALTER VIEWするマイグレーションスクリプトが必要になる
- SQLと
-
hydrateを使ってクエリ結果をEloquent Modelにマッピングする-
hydrateするタイミングで動的にモデルを生成できる -
hydrateしないことには何もできない(findなどクエリ系のメソッドは一切使えない)
-
-
fromSubを使って、Eloquent Modelのfromにサブクエリをセットする-
findなどクエリ系のメソッドが使える(更新系は除く) - サブクエリの結果に対して、クエリを発行することになるので、SQLが重くなる可能性がある
-
おわりに
3つの方法それぞれにメリットとデメリットがありますが、いずれにしても、肥大化するEloquent Modelを分割することにはなるので、それぞれに合った方法を採用してみてはいかがでしょうか?
個人的には、クエリが十分に早ければ、fromSubを使う方法がマイグレーションも必要ないし、採用しやすいのではないかと思います。
ではでは。