first commit
This commit is contained in:
202
app/Helpers/QueryHelper.php
Normal file
202
app/Helpers/QueryHelper.php
Normal file
@@ -0,0 +1,202 @@
|
||||
<?php
|
||||
|
||||
namespace App\Helpers;
|
||||
|
||||
use Exception;
|
||||
use Filter;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\Relation;
|
||||
use Illuminate\Contracts\Database\Query\Builder;
|
||||
use Illuminate\Pagination\AbstractPaginator;
|
||||
use Illuminate\Support\Collection;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
use RuntimeException;
|
||||
|
||||
class QueryHelper
|
||||
{
|
||||
private const RESERVED_REQUEST_KEYWORDS = [
|
||||
'with',
|
||||
'withCount',
|
||||
'paginate',
|
||||
'perPage',
|
||||
'page',
|
||||
'with_deleted',
|
||||
'search',
|
||||
];
|
||||
|
||||
/**
|
||||
* @throws Exception
|
||||
*/
|
||||
public static function apply(Builder $query, Model $model, array $filter = [], bool $first = true): void
|
||||
{
|
||||
$table = $model->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',
|
||||
];
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user