個人メモのレベルなのでご容赦ください。
Eagerロードの可読性と汎用性問題
Laravel リレーション
https://readouble.com/laravel/6.x/ja/eloquent-relationships.html#has-many-through
リレーションを後付け的にする場合、以下の書き方をしている。
Eagerロードの制約ネストが深くなったり複雑になる場合にこれを使うのが良い。
$user->posts()->where('active', 1)->get();
または、推奨されるケースとは異なるが、load関数も近い役割。
$user->load('posts');
BuilderとRelationの型問題
この際、制約が複雑な場合、リレーション先のモデルのスコープメソッドを使うときに以下のように書けば良いと思われるが、
$user->posts()->active()->get();
// loadの場合・・・結局ネストが深くなってしまうのと、コールバックの保守が少し大変(?)
$user->load(['posts' => function ($query) {
$query->active();
}]);
public function scopeActive(Builder $query): Builder
{
return $query->where('active', 1)
}
この場合にscopeActiveの引数に渡される$queryはHasMany(extends Relationなので、もといRelation)クラスのインスタンスである。
scopeActiveの引数に型制約を入れている場合、expected Builder, class HasMany given.
と怒られてしまう。
(入れていなくても不健全であるし、そもそも動く保証もない。)
ソースコード
Laravelのソースコードを読むと、
class HasMany extends Relation
{
public function __construct(...省略)
{
...省略)
parent::__construct($query, $child);
}
}
abstract class Relation
{
/**
* The Eloquent query builder instance.
*
* @var \Illuminate\Database\Eloquent\Builder
*/
protected $query;
public function __construct(Builder $query, ...省略)
{
$this->query = $query;
...省略
}
...省略...
/**
* Get the underlying query for the relation.
*
* @return \Illuminate\Database\Eloquent\Builder
*/
public function getQuery()
{
return $this->query;
}
}
対処法
RelationクラスのgetQuery関数を使う。(直でqueryアトリビュートへアクセスすることもできそうだが・・・)
$user->posts()->getQuery() // ここでBuilderになる
->active()->get();
// loadの場合・・・結局ネストが深くなってしまうのと、コールバックの保守が少し大変(?)
$user->load(['posts' => function (HasMany $relation) {
$relation->getQuery()->active();
}]);
※ テストはしていない。