やりたいこと
配列形式で渡されてきたリクエストでjsonカラムを検索したい
具体的には
viewがわでname="hoge[piyo]" value=100
のようなものをリクエストすれば下記のようになる
[
'hoge' => [
'piyo' => 100
]
]
これをjsonのwhereの方法で取得する
User::where('hoge->piyo','=',100)->get();
のようなものをカラム名が変わっても取れるクエリスコープを作成する
やりかた
微妙なコード
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カラムが存在したらループしてクエリを作成しています
これの微妙なのは連想配列のネストが二重までなところです
再起してもよいのですが、もっと簡単に凡庸性高くしたい...
最終的
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や通常のカラム検索も一括で指定できるようになりました
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のものを取得
Hoge::search([
'id' => 1,
'hoge' => [
'piyo' => 100
]
])->get()
便利