Skip to content

Instantly share code, notes, and snippets.

@oddvalue
Created May 3, 2022 14:34
Show Gist options
  • Select an option

  • Save oddvalue/6b129bf80b72b07419360abeed9b3179 to your computer and use it in GitHub Desktop.

Select an option

Save oddvalue/6b129bf80b72b07419360abeed9b3179 to your computer and use it in GitHub Desktop.
Laravel rest API support for filtering and sorting as defined by https://jsonapi.org/recommendations/#filtering
<?php
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Http\Request;
abstract class ApiSearchQuery
{
/**
* @var string The model class for the query.
*/
protected string $model;
/**
* @var string[] The fields that can be used for sorting
*/
protected array $sortable = [];
/**
* @var string[] The fields that can be used for filtering
*/
protected array $filterable = [];
/**
* @var Request
*/
protected Request $request;
/**
* @var Builder
*/
protected Builder $query;
public function __construct(Request $request)
{
$this->request = $request;
$this->query = $this->getModel()->newQuery();
}
protected function getModel(): Model
{
return (new $this->model);
}
protected function canSort(string $key): bool
{
return in_array($key, $this->getSortableKeys());
}
protected function canFilter(string $key): bool
{
return in_array($key, $this->getFilterableKeys());
}
protected function getSortableKeys(): array
{
return $this->sortable;
}
protected function getFilterableKeys(): array
{
return $this->filterable;
}
public function sort(): static
{
collect(explode(',', $this->request->sort))->map(function ($sort) {
@list($key, $direction) = explode(':', $sort);
return [
'key' => $key,
'direction' => $direction === 'desc' ? 'desc' : 'asc',
];
})->filter(function ($sort) {
return $this->canSort($sort['key']);
})->map(function ($sort) {
$this->query->orderBy($sort['key'], $sort['direction']);
});
return $this;
}
public function filter(): static
{
collect($this->request->filter)->map(function ($filter, $key) {
@list($operationOrValue, $value) = explode(':', $filter);
if ($value) {
$queryOperation = $operationOrValue;
} else {
$queryOperation = null;
$value = $operationOrValue;
}
switch ($queryOperation) {
case 'in':
$operation = 'in';
$value = explode(',', $value);
break;
case 'nin':
$operation = 'not in';
$value = explode(',', $value);
break;
case 'neq':
$operation = '!=';
break;
case 'gt':
$operation = '>';
break;
case 'gte':
$operation = '>=';
break;
case 'lt':
$operation = '<';
break;
case 'lte':
$operation = '<=';
break;
case 'like':
$operation = 'like';
break;
default:
$operation = '=';
break;
}
return [
'key' => $key,
'operation' => $operation,
'value' => $value,
];
})->filter(function ($filter) {
return $this->canFilter($filter['key']);
})->map(function ($filter) {
switch ($filter['value']) {
case 'null':
return $this->query->whereNull($filter['key']);
case 'notnull':
return $this->query->whereNotNull($filter['key']);
}
switch ($filter['operation']) {
case 'in':
return $this->query->whereIn($filter['key'], $filter['value']);
case 'not in':
return $this->query->whereNotIn($filter['key'], $filter['value']);
default:
return $this->query->where($filter['key'], $filter['operation'], $filter['value']);
}
});
return $this;
}
public function __call(string $method, array $params): static
{
$query = call_user_func_array([$this->query, $method], $params);
if (!$query instanceof Builder) {
return $query;
}
return $this;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment