324
common/Database/Paginator.php
Executable file
324
common/Database/Paginator.php
Executable file
@@ -0,0 +1,324 @@
|
||||
<?php namespace Common\Database;
|
||||
|
||||
use Cache;
|
||||
use Carbon\Carbon;
|
||||
use Closure;
|
||||
use Illuminate\Container\Container;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Illuminate\Database\Eloquent\Collection;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Pagination\LengthAwarePaginator;
|
||||
use Illuminate\Support\Arr;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
class Paginator
|
||||
{
|
||||
/**
|
||||
* @var Builder
|
||||
*/
|
||||
private $query;
|
||||
|
||||
/**
|
||||
* @var Model
|
||||
*/
|
||||
private $model;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $defaultOrderColumn = 'updated_at';
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $defaultOrderDirection = 'desc';
|
||||
|
||||
/**
|
||||
* @var Closure
|
||||
*/
|
||||
public $secondaryOrderCallback;
|
||||
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
public $defaultPerPage = 15;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
public $searchColumn = 'name';
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
public $filterColumns = [];
|
||||
|
||||
/**
|
||||
* @var Closure
|
||||
*/
|
||||
public $searchCallback;
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
private $params;
|
||||
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
public $dontSort;
|
||||
|
||||
public function __construct($model, array $params)
|
||||
{
|
||||
$this->model = $model;
|
||||
$this->params = $this->toCamelCase($params);
|
||||
$this->query = $model->newQuery();
|
||||
}
|
||||
|
||||
public function paginate(): LengthAwarePaginator
|
||||
{
|
||||
$with = array_filter(explode(',', $this->param('with', '')));
|
||||
$withCount = array_filter(explode(',', $this->param('withCount', '')));
|
||||
$searchTerm = $this->param('query');
|
||||
$order = $this->getOrder();
|
||||
$perPage = $this->param('perPage', $this->defaultPerPage);
|
||||
$page = (int) $this->param('page', 1);
|
||||
|
||||
// load specified relations and counts
|
||||
if (!empty($with)) {
|
||||
$this->query->with($with);
|
||||
}
|
||||
if (!empty($withCount)) {
|
||||
$this->query->withCount($withCount);
|
||||
}
|
||||
|
||||
// search
|
||||
if ($searchTerm) {
|
||||
if ($this->searchCallback) {
|
||||
call_user_func(
|
||||
$this->searchCallback,
|
||||
$this->query,
|
||||
$searchTerm,
|
||||
);
|
||||
} else {
|
||||
$this->query->where(
|
||||
$this->searchColumn,
|
||||
'like',
|
||||
"$searchTerm%",
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
$this->applyFilters();
|
||||
|
||||
// order
|
||||
if (!$this->dontSort) {
|
||||
if ($this->isRawOrder($order['col'])) {
|
||||
$this->query->orderByRaw($order['col']);
|
||||
} else {
|
||||
$this->query->orderBy($order['col'], $order['dir']);
|
||||
}
|
||||
|
||||
if ($this->secondaryOrderCallback) {
|
||||
call_user_func(
|
||||
$this->secondaryOrderCallback,
|
||||
$this->query,
|
||||
$order['col'],
|
||||
$order['dir'],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
$countCacheKeyQuery = $this->query
|
||||
->toBase()
|
||||
->cloneWithout(['columns', 'orders', 'limit', 'offset']);
|
||||
$countCacheKye = base64_encode(
|
||||
Str::replaceArray(
|
||||
'?',
|
||||
$countCacheKeyQuery->getBindings(),
|
||||
$countCacheKeyQuery->toSql(),
|
||||
),
|
||||
);
|
||||
$total = null;
|
||||
if ($countCacheKye) {
|
||||
$total = Cache::get($countCacheKye);
|
||||
}
|
||||
if (is_null($total)) {
|
||||
$total = $this->query->toBase()->getCountForPagination();
|
||||
if ($total > 500000) {
|
||||
Cache::put($countCacheKye, $total, Carbon::now()->addDay());
|
||||
}
|
||||
}
|
||||
|
||||
$items = $total
|
||||
? $this->query->forPage($page, $perPage)->get()
|
||||
: new Collection();
|
||||
|
||||
return Container::getInstance()->makeWith(LengthAwarePaginator::class, [
|
||||
'items' => $items,
|
||||
'total' => $total,
|
||||
'perPage' => $perPage,
|
||||
'page' => $page,
|
||||
]);
|
||||
}
|
||||
|
||||
public function param(string $name, $default = null)
|
||||
{
|
||||
return Arr::get($this->params, Str::camel($name)) ?: $default;
|
||||
}
|
||||
|
||||
public function setParam(string $name, $value): self
|
||||
{
|
||||
$this->params[$name] = $value;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Builder
|
||||
*/
|
||||
public function query()
|
||||
{
|
||||
return $this->query;
|
||||
}
|
||||
|
||||
/**
|
||||
* Load specified relation counts with paginator items.
|
||||
*
|
||||
* @param mixed $relations
|
||||
* @return $this
|
||||
*/
|
||||
public function withCount($relations)
|
||||
{
|
||||
$this->query->withCount($relations);
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Load specified relations of paginated items.
|
||||
*
|
||||
* @param mixed $relations
|
||||
* @return $this
|
||||
*/
|
||||
public function with($relations)
|
||||
{
|
||||
$this->query->with($relations);
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $column
|
||||
* @param null $operator
|
||||
* @param null $value
|
||||
* @param string $boolean
|
||||
* @return $this
|
||||
*/
|
||||
public function where(
|
||||
$column,
|
||||
$operator = null,
|
||||
$value = null,
|
||||
$boolean = 'and'
|
||||
) {
|
||||
$this->query->where($column, $operator, $value, $boolean);
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function setDefaultOrderColumns($column, $direction = 'desc'): self
|
||||
{
|
||||
$this->defaultOrderColumn = $column;
|
||||
$this->defaultOrderDirection = $direction;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function getOrder()
|
||||
{
|
||||
// order provided as single string: "column|direction"
|
||||
if ($specifiedOrder = $this->param('order')) {
|
||||
$parts = preg_split('(\||:)', $specifiedOrder);
|
||||
$orderCol = Arr::get($parts, 0, $this->defaultOrderColumn);
|
||||
$orderDir = Arr::get($parts, 1, $this->defaultOrderDirection);
|
||||
} else {
|
||||
$orderCol = $this->param('orderBy', $this->defaultOrderColumn);
|
||||
$orderDir = $this->param('orderDir', $this->defaultOrderDirection);
|
||||
}
|
||||
|
||||
return [
|
||||
'dir' => Str::snake($orderDir),
|
||||
'col' => !$this->isRawOrder($orderCol)
|
||||
? Str::snake($orderCol)
|
||||
: $orderCol,
|
||||
];
|
||||
}
|
||||
|
||||
private function toCamelCase($params)
|
||||
{
|
||||
return collect($params)
|
||||
->keyBy(function ($value, $key) {
|
||||
return Str::camel($key);
|
||||
})
|
||||
->toArray();
|
||||
}
|
||||
|
||||
private function applyFilters()
|
||||
{
|
||||
foreach ($this->filterColumns as $column => $callback) {
|
||||
$column = is_int($column) ? $callback : $column;
|
||||
$column = Str::camel($column);
|
||||
if (isset($this->params[$column])) {
|
||||
$value = $this->params[$column];
|
||||
$column = Str::snake($column);
|
||||
|
||||
// user specified callback
|
||||
if (is_callable($callback)) {
|
||||
$callback($this->query, $value);
|
||||
|
||||
// boolean filter
|
||||
} elseif ($value === 'false' || $value === 'true') {
|
||||
$this->applyBooleanFilter($column, $value);
|
||||
|
||||
// filter by between date
|
||||
} elseif (
|
||||
\Str::contains($column, '_at') &&
|
||||
\Str::contains($value, ':')
|
||||
) {
|
||||
$this->query()->whereBetween($column, explode(':', $value));
|
||||
|
||||
// filter by specified column value
|
||||
} else {
|
||||
$this->query()->where($column, $value);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $column
|
||||
* @param string $value
|
||||
*/
|
||||
private function applyBooleanFilter($column, $value)
|
||||
{
|
||||
// cast "true" or "false" to boolean
|
||||
$value = filter_var($value, FILTER_VALIDATE_BOOLEAN);
|
||||
$casts = $this->model->getCasts();
|
||||
|
||||
// column is a simple boolean type
|
||||
if (Arr::get($casts, $column) === 'boolean') {
|
||||
$this->query()->where($column, $value);
|
||||
|
||||
// column has actual value, test whether it's null or not by default
|
||||
} else {
|
||||
if ($value) {
|
||||
$this->query()->whereNotNull($column);
|
||||
} else {
|
||||
$this->query()->whereNull($column);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function isRawOrder(string $order): bool
|
||||
{
|
||||
return Str::contains($order, ['(', ')']);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user