3
5

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

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

Posted at

やりたいこと

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

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

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

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

User::where('hoge->piyo','=',100)->get();

のようなものをカラム名が変わっても取れるクエリスコープを作成する

やりかた

微妙なコード

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カラムが存在したらループしてクエリを作成しています
これの微妙なのは連想配列のネストが二重までなところです
再起してもよいのですが、もっと簡単に凡庸性高くしたい...

最終的

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::dothoge.piyo=>100のような形式に直します。
仮にネストが深くなっても、dotは再帰的に展開してくれます。
ネストが深くてもhoge.piyo.fuga=>100のようにドットでつながったkey、valueになるので
ドットをアローに置き換えてやれば、jsonでのwhereが行なえます

json以外も対応

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

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でhogejsonカラムのpiyoが100のものを取得

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

便利

3
5
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
5

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?