getTable(); $relations = []; if (isset($filter['limit'])) { $query->limit((int)$filter['limit']); unset($filter['limit']); } if (isset($filter['offset'])) { $query->offset((int)$filter['offset']); unset($filter['offset']); } if (isset($filter['orderBy'])) { $order_by = $filter['orderBy']; [$column, $dir] = isset($order_by[1]) ? array_values($order_by) : [$order_by[0], 'asc']; if (Schema::hasColumn($table, $column)) { $query->orderBy($column, $dir); } unset($filter['orderBy']); } if (isset($filter['with'])) { $query->with(self::getRelationsFilter($filter['with'])); } if (isset($filter['withCount'])) { $query->withCount($filter['withCount']); } if (isset($filter['withSum'])) { foreach ($filter['withSum'] as $withSum) { $query->withSum(...$withSum); } } if (isset($filter['search']['query'], $filter['search']['fields'])) { $query = self::buildSearchQuery($query, $filter['search']['query'], $filter['search']['fields']); } if (isset($filter['where'])) { foreach ($filter['where'] as $key => $param) { if (str_contains($key, '.')) { $params = explode('.', $key); $domain = array_shift($params); $filterParam = implode('.', $params); if (!isset($relations[$domain])) { $relations[$domain] = []; } $relations[$domain][$filterParam] = $param; } elseif (Schema::hasColumn($table, $key) && !in_array($key, static::RESERVED_REQUEST_KEYWORDS, true) && !in_array($key, $model->getHidden(), true) ) { [$operator, $value] = is_array($param) ? array_values($param) : ['=', $param]; if (is_array($value) && $operator !== 'in') { if ($operator === '=') { $query->whereIn("$table.$key", $value); } elseif ($operator === 'between' && count($value) >= 2) { $query->whereBetween("$table.$key", [$value[0], $value[1]]); } } elseif ($operator === 'in') { $inArgs = is_array($value) ? $value : [$value]; $query->whereIn("$table.$key", $inArgs); } else { $query->where("$table.$key", $operator, $value); } } } } if (!empty($relations)) { foreach ($relations as $domain => $filters) { if (!method_exists($model, $domain)) { $cls = get_class($model); throw new RuntimeException("Unknown relation $cls::$domain()"); } /** @var Relation $relationQuery */ $relationQuery = $model->{$domain}(); $query->whereHas($domain, static function ($q) use ($filters, $relationQuery, $first) { QueryHelper::apply($q, $relationQuery->getModel(), ['where' => $filters], $first); }); } } } /** * @throws Exception */ private static function getRelationsFilter(array $filter): array { $key = array_search('can', $filter, true); if ($key !== false) { array_splice($filter, $key, 1); Filter::listen(Filter::getActionFilterName(), static function ($data) { if ($data instanceof Model) { $data->append('can'); return $data; } if ($data instanceof Collection) { return $data->map(static fn(Model $el) => $el->append('can')); } if ($data instanceof AbstractPaginator) { $data->setCollection($data->getCollection()->map(static fn(Model $el) => $el->append('can'))); return $data; } return $data; }); } return $filter; } /** * @param Builder $query * @param string $search * @param string[] $fields * * @return Builder */ private static function buildSearchQuery(Builder $query, string $search, array $fields): Builder { $value = "%$search%"; return $query->where(static function ($query) use ($value, $fields) { $field = array_shift($fields); if (str_contains($field, '.')) { [$relation, $relationField] = explode('.', $field); $query->whereHas($relation, static fn(Builder $query) => $query->where($relationField, 'like', $value) ); } else { $query->where($field, 'like', $value); } foreach ($fields as $field) { if (str_contains($field, '.')) { [$relation, $relationField] = explode('.', $field); $query->orWhereHas($relation, static fn(Builder $query) => $query->where($relationField, 'like', $value) ); } else { $query->orWhere($field, 'like', $value); } } }); } public static function getValidationRules(): array { return [ 'limit' => 'sometimes|int', 'offset' => 'sometimes|int', 'orderBy' => 'sometimes|array', 'with.*' => 'sometimes|string', 'withCount.*' => 'sometimes|string', 'withSum.*.*' => 'sometimes|string', 'search.query' => 'sometimes|string|nullable', 'search.fields.*' => 'sometimes|string', 'where' => 'sometimes|array', ]; } }