始めに
- Laraveladminを使っていると必ず出てくるGridクラス内のcolumnメソッドで色々ハマった
- その代わりに色々知れたことをまとめる
その1
columnメソッド内でwith(Eager Loading)の処理をしている
- Eager Loadingの成立確認すべく、get()をしてみた
$grid = new Grid(new Order());
$grid->model()->query()
->with(['orderable' => function (MorphTo $morphTo) {
$morphTo->morphWith([
User::class => ['userAttribute'],
]);
}])->get();
$grid->column('id', 'ID');
...
- すると、以下エラーが発生
Method Illuminate\Database\Eloquent\Collection::with does not exist.
- Eager Loadingは正常に機能(写真の通り)
- ここでいう
Collection
とは$grid->model()->...->get()
の結果であると推測できるので、それ以降の処理でwithメソッドが使われていると推測 - columnメソッドを深くたどると、やはりwithを使用していた
※columnメソッド→addRelationColumnメソッド
vendor\encore\laravel-admin\src\Grid.php
protected function addRelationColumn($name, $label = '')
{
// 1
list($relation, $column) = explode('.', $name);
$model = $this->model()->eloquent();
...
$name = ($this->shouldSnakeAttributes() ? Str::snake($relation) : $relation).'.'.$column;
// 2
$this->model()->with($relation); // ココ!!!
return $this->addColumn($name, $label)->setRelation($relation, $column);
}
- // 2にて、$gridのモデルに対して、withを実行
- 今回ではOrderモデル(
$this->model()
)に対して、'orderable'をEager Loadingしている - column内でEager Loadingを作っているとは思わなかった
- 今回ではOrderモデル(
その2
columnメソッドでリレーションを張れる(Eager Loadingができる)のは1モデルだけ
- 上記ソースコード内の//1でリレーション先のモデルと、取得するカラムを分割している
例えば、こうなる
example.php
$grid->column('orderableWithTrashed.deleted_at', '退会日時');
↓
$relation(取得先リレーション名) = 'orderableWithTrashed'
$column(取得先カラム名) = 'deleted_at'
- 要は columnメソッドでリレーションを張れる(Eager Loadingができる)のは1モデルだけ ということ
- 二つ目以降の.(ドット)は切り捨てられる
その3
columnメソッド以前にEager Loadingしたリレーションは上書きされる
- columnメソッドの第1引数でリレーションを張った場合、columnメソッド以前にEager Loadingしたリレーションは上書きされる
- 以下は
userAttribute
でのEager Loadingが上書きされ、displayメソッド内でuserAttribute
メソッドによるアクセスをすると、N+1が発生する例
- 以下は
sample.php
$grid = new Grid(new Order());
// Eager Loding...希望通りEagerLoading
$grid->model()->query()
->with(['orderable' => function (MorphTo $morphTo) {
$morphTo->morphWith([
User::class => ['userAttribute'],
]);
}]);
// columnメソッドで値取得=Eager Loadingの上書きしてる...
$grid->column('orderable.age', __('年齢'))
->display(
function ($adCode) {
// ゲストの時
if($this->orderable_type === 'GuestUser'){
return $this->orderable->age;
}
// ユーザーの時
return $this->orderable->userAttribute->age;
}
);
その4
同じgridの中で一つでもcolumnメソッドの引数でEager Loadingすれば、他のcolumnメソッドでEagerLodingしなくてよい
↓例
- 以下3つのカラムをgridで取得
1, Orderモデルid
2, Userモデル/Guestモデルad_code
3, Userモデル/Guestモデルname
※2, 3, についてはorderableメソッドによるポリモーフィックで各モデルにアクセス
↓成功例①
$grid = new Grid(new Order());
$grid->column('id', __('admin.app.features.order.id'));
// リレーションを張るメソッドを引数に追加
$grid->column('orderable.ad_code', __('ad_code'));
// リレーションを張るメソッドを引数に追加
$grid->column('orderable.name', __('name'));
- これが以下でも成功する。
↓成功例②
$grid = new Grid(new Order());
$grid->column('id', __('admin.app.features.order.id'));
// リレーションを張るメソッドを引数に追加
$grid->column('orderable.ad_code', __('ad_code'));
// リレーションを張るメソッドを削除
$grid->column('name', __('name'));
- 理由は以下の通り
-
ad_code
のcolumnメソッド処理時にcolumnメソッド内部でwith(=Eager Loding)が実行 - 他のcolumnメソッド内でも上記EagerLodingを踏襲して利用できるため
-
- 試しに以下ソースコードのようにorderableメソッドを全て削除するとN+1が発生
↓失敗例②...N+1が発生
$grid = new Grid(new Order());
$grid->column('id', __('admin.app.features.order.id'));
// リレーションを張るメソッドを削除
$grid->column('ad_code', __('ad_code'));
// リレーションを張るメソッドを削除
$grid->column('name', __('name'));
所見
- なかなか曲者のcolumnメソッドでした…