Laravel で 一括でjsonカラムもwhereできるクエリスコープを作成する

#やりたいこと
配列形式で渡されてきたリクエストでjsonカラムを検索したい

具体的には
viewがわで`name="hoge[piyo]" value=100`のようなものをリクエストすれば下記のようになる

```php
[
  'hoge' => [
    'piyo' => 100
  ]
]

```

これをjsonのwhereの方法で取得する

```php
User::where('hoge->piyo','=',100)->get();
```
のようなものをカラム名が変わっても取れるクエリスコープを作成する

#やりかた

##微妙なコード

```php:HogeModel

    protected $casts = [
        'hoge' => 'json',
    ];

    /**
     * jsonキャストするカラム名のみを取得する
     * @return array
     */
    private function getJsonColumns()
    {
        return array_keys($this->casts, 'json');
    }


    /**
     * jsonカラムを検索するクエリスコープ
     * attributesにjsonの連想配列を渡す
     *
     * @param  \Illuminate\Database\Eloquent\Builder  $query
     * @param  array  $attributes
     * @param  string $operation
     * @return \Illuminate\Database\Eloquent\Builder
     */
    public function scopeJsonSearch(Builder $query, array $attributes, string $operation = '=')
    {
        $json_columns = $this->getJsonColumns();
        // jsonのみ抽出
        $json_attributes = array_filter($attributes, function ($value, $key) use ($json_columns) {
            return in_array($key, $json_columns) && !is_null($value);
        }, ARRAY_FILTER_USE_BOTH);

        if (!$json_attributes) {
            return $query;
        }

        foreach ($json_attributes as $column => $arr) {
            foreach ($arr as $key => $value) {
                if (is_null($value)) {
                    continue;
                }
                $query->where("${column}->${key}", $operation, $value);
            }
        }
        return $query;
    }
```

当初自分が書いたのはこちらです
jsonで代入したりするのにcastが不可欠なので、castの中からjsonカラムを抽出します
渡されてた配列にjsonカラムが存在したらループしてクエリを作成しています
これの微妙なのは連想配列のネストが二重までなところです
再起してもよいのですが、もっと簡単に凡庸性高くしたい...

##最終的
```php:HogeModel

use Illuminate\Support\Arr;

class Hoge extends Model
{
    protected $casts = [
        'hoge' => 'json',
    ];

    /**
     * jsonキャストするカラム名のみを取得する
     * @return array
     */
    private function getJsonColumns()
    {
        return array_keys($this->casts, 'json');
    }


    /**
     * jsonカラムを検索するクエリスコープ
     * attributesにjsonの連想配列を渡す
     *
     * @param  \Illuminate\Database\Eloquent\Builder  $query
     * @param  array  $attributes
     * @param  string $operation
     * @return \Illuminate\Database\Eloquent\Builder
     */
    public function scopeJsonSearch(Builder $query, array $attributes, string $operation = '=')
    {
        $json_columns = $this->getJsonColumns();
        // jsonのみ抽出
        $json_attributes = array_filter($attributes, function ($value, $key) use ($json_columns) {
            return in_array($key, $json_columns) && !is_null($value);
        }, ARRAY_FILTER_USE_BOTH);

        if (!$json_attributes) {
            return $query;
        }

        $json_attributes = Arr::dot($json_attributes);
        foreach ($json_attributes as $column => $value) {
            $column = str_replace('.', '->', $column);
            if (!is_null($value)) {
                $query->where($column, $operation, $value);
            }
        }
        return $query;
    }
```
jsonのみ抽出するところは変えず、`Arr::dot`で`hoge.piyo=>100`のような形式に直します。
仮にネストが深くなっても、`dot`は再帰的に展開してくれます。
ネストが深くても`hoge.piyo.fuga=>100`のようにドットでつながったkey、valueになるので
ドットをアローに置き換えてやれば、jsonでの`where`が行なえます


##json以外も対応

ちなみにjsonの判定部分を消してあげれば、通常のwhere検索でも動作します
これで、jsonや通常のカラム検索も一括で指定できるようになりました

```php:HogeModel

use Illuminate\Support\Arr;

class Hoge extends Model
{
    /**
     * カラムを検索するクエリスコープ
     * attributesに配列を渡す
     *
     * @param  \Illuminate\Database\Eloquent\Builder  $query
     * @param  array  $attributes
     * @param  string $operation
     * @return \Illuminate\Database\Eloquent\Builder
     */
    public function scopeSearch(Builder $query, array $attributes, string $operation = '=')
    {
        $attributes = Arr::dot($attributes);
        foreach ($attributes as $column => $value) {
            $column = str_replace('.', '->', $column);
            if (!is_null($value)) {
                $query->where($column, $operation, $value);
            }
        }
        return $query;
    }
```

idが1で`hoge`jsonカラムの`piyo`が100のものを取得

```php
Hoge::search([
    'id' => 1,
    'hoge' => [
        'piyo' => 100
    ] 
])->get()
```

便利