【Laravel admin】columnメソッドの注意点

## 始めに
* [Laraveladmin](https://laravel-admin.org/docs/en)を使っていると必ず出てくるGridクラス内のcolumnメソッドで色々ハマった
* その代わりに色々知れたことをまとめる

## その１
### columnメソッド内でwith(Eager Loading)の処理をしている

* Eager Loadingの成立確認すべく、get()をしてみた

```php
$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を作っているとは思わなかった

## その２
###  columnメソッドでリレーションを張れる（Eager Loadingができる）のは１モデルだけ
* 上記ソースコード内の//1でリレーション先のモデルと、取得するカラムを分割している

例えば、こうなる

```example.php
$grid->column('orderableWithTrashed.deleted_at', '退会日時');
↓
$relation(取得先リレーション名) = 'orderableWithTrashed'
$column(取得先カラム名) = 'deleted_at'
```
* 要は **columnメソッドでリレーションを張れる（Eager Loadingができる）のは１モデルだけ** ということ
  * 二つ目以降の.（ドット）は切り捨てられる

## その３
### 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;
        }
    );
```

## その４
### 同じgridの中で一つでもcolumnメソッドの引数でEager Loadingすれば、他のcolumnメソッドでEagerLodingしなくてよい

↓例
* 以下3つのカラムをgridで取得
  1, Orderモデル `id`
  2, Userモデル／Guestモデル `ad_code`
  3, Userモデル／Guestモデル `name`
※2, 3, については**orderableメソッドによるポリモーフィック**で各モデルにアクセス

↓成功例①

```php
$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'));
```

* これが以下でも成功する。

↓成功例②

```php
$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が発生

```php
$grid = new Grid(new Order());
$grid->column('id', __('admin.app.features.order.id'));
// リレーションを張るメソッドを削除
$grid->column('ad_code', __('ad_code'));
// リレーションを張るメソッドを削除
$grid->column('name', __('name'));
```

### 所見
* なかなか曲者のcolumnメソッドでした…