add nova
This commit is contained in:
51
nova/src/Query/ApplyFilter.php
Normal file
51
nova/src/Query/ApplyFilter.php
Normal file
@@ -0,0 +1,51 @@
|
||||
<?php
|
||||
|
||||
namespace Laravel\Nova\Query;
|
||||
|
||||
use Laravel\Nova\Http\Requests\NovaRequest;
|
||||
|
||||
class ApplyFilter
|
||||
{
|
||||
/**
|
||||
* The filter instance.
|
||||
*
|
||||
* @var \Laravel\Nova\Filters\Filter
|
||||
*/
|
||||
public $filter;
|
||||
|
||||
/**
|
||||
* The value of the filter.
|
||||
*
|
||||
* @var mixed
|
||||
*/
|
||||
public $value;
|
||||
|
||||
/**
|
||||
* Create a new invokable filter applier.
|
||||
*
|
||||
* @param \Laravel\Nova\Filters\Filter $filter
|
||||
* @param mixed $value
|
||||
* @return void
|
||||
*/
|
||||
public function __construct($filter, $value)
|
||||
{
|
||||
$this->value = $value;
|
||||
$this->filter = $filter;
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply the filter to the given query.
|
||||
*
|
||||
* @param \Laravel\Nova\Http\Requests\NovaRequest $request
|
||||
* @param \Illuminate\Database\Eloquent\Builder $query
|
||||
* @return \Illuminate\Database\Eloquent\Builder
|
||||
*/
|
||||
public function __invoke(NovaRequest $request, $query)
|
||||
{
|
||||
$this->filter->apply(
|
||||
$request, $query, $this->value
|
||||
);
|
||||
|
||||
return $query;
|
||||
}
|
||||
}
|
||||
26
nova/src/Query/ApplySoftDeleteConstraint.php
Normal file
26
nova/src/Query/ApplySoftDeleteConstraint.php
Normal file
@@ -0,0 +1,26 @@
|
||||
<?php
|
||||
|
||||
namespace Laravel\Nova\Query;
|
||||
|
||||
use Laravel\Nova\TrashedStatus;
|
||||
|
||||
class ApplySoftDeleteConstraint
|
||||
{
|
||||
/**
|
||||
* Apply the trashed state constraint to the query.
|
||||
*
|
||||
* @param \Illuminate\Database\Eloquent\Builder|\Laravel\Scout\Builder $query
|
||||
* @param string $withTrashed
|
||||
* @return \Illuminate\Database\Eloquent\Builder|\Laravel\Scout\Builder
|
||||
*/
|
||||
public function __invoke($query, $withTrashed)
|
||||
{
|
||||
if ($withTrashed == TrashedStatus::WITH) {
|
||||
$query = $query->withTrashed();
|
||||
} elseif ($withTrashed == TrashedStatus::ONLY) {
|
||||
$query = $query->onlyTrashed();
|
||||
}
|
||||
|
||||
return $query;
|
||||
}
|
||||
}
|
||||
394
nova/src/Query/Builder.php
Normal file
394
nova/src/Query/Builder.php
Normal file
@@ -0,0 +1,394 @@
|
||||
<?php
|
||||
|
||||
namespace Laravel\Nova\Query;
|
||||
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
|
||||
use Illuminate\Database\Eloquent\Relations\MorphToMany;
|
||||
use Illuminate\Pagination\Paginator;
|
||||
use Laravel\Nova\Contracts\QueryBuilder;
|
||||
use Laravel\Nova\Http\Requests\NovaRequest;
|
||||
use Laravel\Nova\TrashedStatus;
|
||||
use Laravel\Scout\Builder as ScoutBuilder;
|
||||
use Laravel\Scout\Contracts\PaginatesEloquentModels;
|
||||
use RuntimeException;
|
||||
|
||||
class Builder implements QueryBuilder
|
||||
{
|
||||
/**
|
||||
* The resource class.
|
||||
*
|
||||
* @var class-string<\Laravel\Nova\Resource>
|
||||
*/
|
||||
protected $resourceClass;
|
||||
|
||||
/**
|
||||
* The original query builder instance.
|
||||
*
|
||||
* @var \Illuminate\Database\Eloquent\Builder|\Illuminate\Database\Eloquent\Relations\Relation|null
|
||||
*/
|
||||
protected $originalQueryBuilder;
|
||||
|
||||
/**
|
||||
* The query builder instance.
|
||||
*
|
||||
* @var \Laravel\Scout\Builder|\Illuminate\Database\Eloquent\Builder|\Illuminate\Database\Eloquent\Relations\Relation|null
|
||||
*/
|
||||
protected $queryBuilder;
|
||||
|
||||
/**
|
||||
* Optional callbacks before model query execution.
|
||||
*
|
||||
* @var array<int, callable(\Illuminate\Database\Eloquent\Builder|\Illuminate\Database\Eloquent\Relations\Relation):void>
|
||||
*/
|
||||
protected $queryCallbacks = [];
|
||||
|
||||
/**
|
||||
* Determine query callbacks has been applied.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
protected $appliedQueryCallbacks = false;
|
||||
|
||||
/**
|
||||
* Construct a new query builder for a resource.
|
||||
*
|
||||
* @param class-string<\Laravel\Nova\Resource> $resourceClass
|
||||
* @return void
|
||||
*/
|
||||
public function __construct($resourceClass)
|
||||
{
|
||||
$this->resourceClass = $resourceClass;
|
||||
}
|
||||
|
||||
/**
|
||||
* Build a "whereKey" query for the given resource.
|
||||
*
|
||||
* @param \Illuminate\Database\Eloquent\Builder|\Illuminate\Database\Eloquent\Relations\Relation $query
|
||||
* @param string $key
|
||||
* @return $this
|
||||
*/
|
||||
public function whereKey($query, $key)
|
||||
{
|
||||
$this->setOriginalQueryBuilder($this->queryBuilder = $query);
|
||||
|
||||
$this->tap(function ($query) use ($key) {
|
||||
$query->whereKey($key);
|
||||
});
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Build a "search" query for the given resource.
|
||||
*
|
||||
* @param \Laravel\Nova\Http\Requests\NovaRequest $request
|
||||
* @param \Illuminate\Database\Eloquent\Builder|\Illuminate\Database\Eloquent\Relations\Relation $query
|
||||
* @param string|null $search
|
||||
* @param array<int, \Laravel\Nova\Query\ApplyFilter> $filters
|
||||
* @param array<string, string> $orderings
|
||||
* @param string $withTrashed
|
||||
* @return $this
|
||||
*/
|
||||
public function search(NovaRequest $request, $query, $search = null,
|
||||
array $filters = [], array $orderings = [],
|
||||
$withTrashed = TrashedStatus::DEFAULT)
|
||||
{
|
||||
$this->setOriginalQueryBuilder($query);
|
||||
|
||||
$hasSearchKeyword = ! empty(trim($search ?? ''));
|
||||
$hasOrderings = collect($orderings)->filter()->isNotEmpty();
|
||||
|
||||
if ($this->resourceClass::usesScout()) {
|
||||
if ($hasSearchKeyword) {
|
||||
$this->queryBuilder = $this->resourceClass::buildIndexQueryUsingScout($request, $search, $withTrashed);
|
||||
$search = '';
|
||||
|
||||
if ($query instanceof MorphToMany || $query instanceof BelongsToMany) {
|
||||
$this->tap(function ($queryBuilder) use ($query) {
|
||||
/** @var \Illuminate\Database\Eloquent\Builder|\Illuminate\Database\Eloquent\Relations\Relation $queryBuilder */
|
||||
$queryBuilder->whereIn(
|
||||
$this->resourceClass::newModel()->getQualifiedKeyName(),
|
||||
$query->allRelatedIds()
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (! $hasSearchKeyword && ! $hasOrderings) {
|
||||
$this->tap(function ($query) {
|
||||
/** @var \Illuminate\Database\Eloquent\Builder|\Illuminate\Database\Eloquent\Relations\Relation $query */
|
||||
$this->resourceClass::defaultOrderings($query);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (! isset($this->queryBuilder)) {
|
||||
$this->queryBuilder = $query;
|
||||
}
|
||||
|
||||
$this->tap(function ($query) use ($request, $search, $filters, $orderings, $withTrashed) {
|
||||
/** @var \Illuminate\Database\Eloquent\Builder|\Illuminate\Database\Eloquent\Relations\Relation $query */
|
||||
$this->resourceClass::buildIndexQuery(
|
||||
$request, $query, $search, $filters, $orderings, $withTrashed
|
||||
);
|
||||
});
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Pass the query to a given callback.
|
||||
*
|
||||
* @param callable(\Illuminate\Database\Eloquent\Builder|\Illuminate\Database\Eloquent\Relations\Relation):void $callback
|
||||
* @return $this
|
||||
*/
|
||||
public function tap($callback)
|
||||
{
|
||||
$this->queryCallbacks[] = $callback;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the "take" directly to Scout or Eloquent builder.
|
||||
*
|
||||
* @param int $limit
|
||||
* @return $this
|
||||
*/
|
||||
public function take($limit)
|
||||
{
|
||||
$this->queryBuilder->take($limit);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Defer setting a "limit" using query callback and only executed via Eloquent builder.
|
||||
*
|
||||
* @param int $limit
|
||||
* @return $this
|
||||
*/
|
||||
public function limit($limit)
|
||||
{
|
||||
return $this->tap(function ($query) use ($limit) {
|
||||
/** @var \Illuminate\Database\Eloquent\Builder|\Illuminate\Database\Eloquent\Relations\Relation $query */
|
||||
$query->limit($limit);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the results of the search.
|
||||
*
|
||||
* @return \Illuminate\Database\Eloquent\Collection
|
||||
*/
|
||||
public function get()
|
||||
{
|
||||
return $this->applyQueryCallbacks($this->queryBuilder)->get();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a lazy collection for the given query by chunks of the given size.
|
||||
*
|
||||
* @param int $chunkSize
|
||||
* @return \Illuminate\Support\LazyCollection
|
||||
*/
|
||||
public function lazy($chunkSize = 1000)
|
||||
{
|
||||
if (! method_exists($this->queryBuilder, 'lazy')) {
|
||||
return $this->cursor();
|
||||
}
|
||||
|
||||
return $this->applyQueryCallbacks($this->queryBuilder)->lazy($chunkSize);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a lazy collection for the given query.
|
||||
*
|
||||
* @return \Illuminate\Support\LazyCollection
|
||||
*/
|
||||
public function cursor()
|
||||
{
|
||||
$queryBuilder = $this->applyQueryCallbacks($this->queryBuilder);
|
||||
|
||||
if (
|
||||
method_exists($queryBuilder, 'cursor')
|
||||
&& (! $queryBuilder instanceof ScoutBuilder && empty($queryBuilder->getEagerLoads()))
|
||||
) {
|
||||
return $queryBuilder->cursor();
|
||||
}
|
||||
|
||||
return $queryBuilder->get()->lazy();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the paginated results of the query.
|
||||
*
|
||||
* @param int $perPage
|
||||
* @return array{0: \Illuminate\Contracts\Pagination\Paginator, 1: int|null, 2: bool}
|
||||
*/
|
||||
public function paginate($perPage)
|
||||
{
|
||||
$queryBuilder = $this->applyQueryCallbacks($this->queryBuilder);
|
||||
|
||||
if (! $queryBuilder instanceof ScoutBuilder) {
|
||||
return [
|
||||
$queryBuilder->simplePaginate($perPage),
|
||||
$this->getCountForPagination(),
|
||||
true,
|
||||
];
|
||||
}
|
||||
|
||||
return $this->paginateFromScout($queryBuilder, $perPage);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the paginated results of the Scout query.
|
||||
*
|
||||
* @param \Laravel\Scout\Builder $queryBuilder
|
||||
* @param int $perPage
|
||||
* @return array{0: \Illuminate\Contracts\Pagination\Paginator, 1: int|null, 2: false}
|
||||
*/
|
||||
protected function paginateFromScout(ScoutBuilder $queryBuilder, $perPage)
|
||||
{
|
||||
$originalQueryBuilder = clone $this->originalQueryBuilder;
|
||||
|
||||
[$sql, $bindings] = [$originalQueryBuilder->toSql(), $originalQueryBuilder->getBindings()];
|
||||
|
||||
$modelQueryBuilder = $this->handleQueryCallbacks($originalQueryBuilder);
|
||||
|
||||
if ($sql === $modelQueryBuilder->toSql() && array_diff($bindings, $modelQueryBuilder->getBindings()) === []) {
|
||||
/** @var \Illuminate\Pagination\LengthAwarePaginator $paginated */
|
||||
$paginated = $queryBuilder->paginate($perPage);
|
||||
|
||||
$items = $paginated->items();
|
||||
|
||||
$hasMorePages = ($paginated->perPage() * $paginated->currentPage()) < $paginated->total();
|
||||
|
||||
return [
|
||||
app()->makeWith(Paginator::class, [
|
||||
'items' => $items,
|
||||
'perPage' => $paginated->perPage(),
|
||||
'currentPage' => $paginated->currentPage(),
|
||||
'options' => $paginated->getOptions(),
|
||||
])->hasMorePagesWhen($hasMorePages),
|
||||
$paginated->total(),
|
||||
false,
|
||||
];
|
||||
}
|
||||
|
||||
/** @var array<int, string|int> $scoutResultKeys */
|
||||
$scoutResultKeys = $queryBuilder->keys()->all();
|
||||
|
||||
/** @var \Illuminate\Database\Eloquent\Model&\Laravel\Scout\Searchable $model */
|
||||
$model = $this->resourceClass::newModel();
|
||||
|
||||
$paginated = tap($model->queryScoutModelsByIds(
|
||||
$queryBuilder, $scoutResultKeys
|
||||
), function ($query) {
|
||||
/** @var \Illuminate\Database\Eloquent\Builder $query */
|
||||
$this->originalQueryBuilder = $query;
|
||||
})->simplePaginate($perPage);
|
||||
|
||||
if (! $model->searchableUsing() instanceof PaginatesEloquentModels) {
|
||||
/** @var array<int|string, int> $objectIdPositions */
|
||||
$objectIdPositions = collect($scoutResultKeys)->values()->flip()->all();
|
||||
|
||||
$paginated->setCollection(
|
||||
$paginated->getCollection()
|
||||
->sortBy(function ($model) use ($objectIdPositions) {
|
||||
return $objectIdPositions[$model->getScoutKey()];
|
||||
}, SORT_NUMERIC)->values()
|
||||
);
|
||||
}
|
||||
|
||||
return [$paginated, $this->getCountForPagination(), false];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the count of the total records for the paginator.
|
||||
*
|
||||
* @return int|null
|
||||
*/
|
||||
public function getCountForPagination()
|
||||
{
|
||||
return $this->toBaseQueryBuilder()->getCountForPagination();
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert the query builder to an Eloquent query builder (skip using Scout).
|
||||
*
|
||||
* @return \Illuminate\Database\Eloquent\Builder|\Illuminate\Database\Eloquent\Relations\Relation
|
||||
*/
|
||||
public function toBase()
|
||||
{
|
||||
return $this->applyQueryCallbacks($this->originalQueryBuilder);
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert the query builder to an fluent query builder (skip using Scout).
|
||||
*
|
||||
* @return \Illuminate\Database\Query\Builder
|
||||
*/
|
||||
public function toBaseQueryBuilder()
|
||||
{
|
||||
return $this->toBase()->toBase();
|
||||
}
|
||||
|
||||
/**
|
||||
* Set original query builder instance.
|
||||
*
|
||||
* @param \Illuminate\Database\Eloquent\Builder|\Illuminate\Database\Eloquent\Relations\Relation $queryBuilder
|
||||
* @return void
|
||||
*/
|
||||
protected function setOriginalQueryBuilder($queryBuilder)
|
||||
{
|
||||
if (isset($this->originalQueryBuilder)) {
|
||||
throw new RuntimeException('Unable to override $originalQueryBuilder, please create a new '.self::class);
|
||||
}
|
||||
|
||||
$this->originalQueryBuilder = $queryBuilder;
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply any query callbacks to the query builder.
|
||||
*
|
||||
* @param \Laravel\Scout\Builder|\Illuminate\Database\Eloquent\Builder|\Illuminate\Database\Eloquent\Relations\Relation $queryBuilder
|
||||
* @return \Laravel\Scout\Builder|\Illuminate\Database\Eloquent\Builder|\Illuminate\Database\Eloquent\Relations\Relation
|
||||
*/
|
||||
protected function applyQueryCallbacks($queryBuilder)
|
||||
{
|
||||
if (! $this->appliedQueryCallbacks) {
|
||||
$this->handleQueryCallbacks($queryBuilder);
|
||||
|
||||
$this->appliedQueryCallbacks = true;
|
||||
}
|
||||
|
||||
return $queryBuilder;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle any query callbacks to the query builder.
|
||||
*
|
||||
* @param \Laravel\Scout\Builder|\Illuminate\Database\Eloquent\Builder|\Illuminate\Database\Eloquent\Relations\Relation $queryBuilder
|
||||
* @return \Laravel\Scout\Builder|\Illuminate\Database\Eloquent\Builder|\Illuminate\Database\Eloquent\Relations\Relation
|
||||
*/
|
||||
protected function handleQueryCallbacks($queryBuilder)
|
||||
{
|
||||
$callback = function ($query) {
|
||||
/** @var \Illuminate\Database\Eloquent\Builder|\Illuminate\Database\Eloquent\Relations\Relation $query */
|
||||
collect($this->queryCallbacks)
|
||||
->filter()
|
||||
->each(function ($callback) use ($query) {
|
||||
call_user_func($callback, $query);
|
||||
});
|
||||
};
|
||||
|
||||
if ($queryBuilder instanceof ScoutBuilder) {
|
||||
$queryBuilder->query($callback);
|
||||
} else {
|
||||
$queryBuilder->tap($callback);
|
||||
}
|
||||
|
||||
return $queryBuilder;
|
||||
}
|
||||
}
|
||||
52
nova/src/Query/Mixin/BelongsToMany.php
Normal file
52
nova/src/Query/Mixin/BelongsToMany.php
Normal file
@@ -0,0 +1,52 @@
|
||||
<?php
|
||||
|
||||
namespace Laravel\Nova\Query\Mixin;
|
||||
|
||||
use Illuminate\Database\Eloquent\Relations\MorphToMany;
|
||||
|
||||
class BelongsToMany
|
||||
{
|
||||
/**
|
||||
* Get default pivot attributes using mixin.
|
||||
*
|
||||
* @return \Closure
|
||||
*/
|
||||
public function getDefaultPivotAttributes()
|
||||
{
|
||||
return function () {
|
||||
return collect($this->pivotValues)->mapWithKeys(function ($pivot) {
|
||||
return [$pivot['column'] => $pivot['value']];
|
||||
})->all();
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply default pivot query using mixin.
|
||||
*
|
||||
* @return \Closure
|
||||
*/
|
||||
public function applyDefaultPivotQuery()
|
||||
{
|
||||
return function ($query) {
|
||||
$query->from($this->table);
|
||||
|
||||
if ($this instanceof MorphToMany) {
|
||||
$query->where($this->qualifyPivotColumn($this->morphType), $this->morphClass);
|
||||
}
|
||||
|
||||
foreach ($this->pivotWheres as $arguments) {
|
||||
$query->where(...$arguments);
|
||||
}
|
||||
|
||||
foreach ($this->pivotWhereIns as $arguments) {
|
||||
$query->whereIn(...$arguments);
|
||||
}
|
||||
|
||||
foreach ($this->pivotWhereNulls as $arguments) {
|
||||
$query->whereNull(...$arguments);
|
||||
}
|
||||
|
||||
return $query->where($this->getQualifiedForeignPivotKeyName(), $this->parent->{$this->parentKey});
|
||||
};
|
||||
}
|
||||
}
|
||||
59
nova/src/Query/Search.php
Normal file
59
nova/src/Query/Search.php
Normal file
@@ -0,0 +1,59 @@
|
||||
<?php
|
||||
|
||||
namespace Laravel\Nova\Query;
|
||||
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Laravel\Nova\Query\Search\Column;
|
||||
|
||||
class Search
|
||||
{
|
||||
/**
|
||||
* The Eloquent Query Builder instance.
|
||||
*
|
||||
* @var \Illuminate\Database\Eloquent\Builder
|
||||
*/
|
||||
public $queryBuilder;
|
||||
|
||||
/**
|
||||
* The search keyword.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $searchKeyword;
|
||||
|
||||
/**
|
||||
* Create a new search builder instance.
|
||||
*
|
||||
* @param \Illuminate\Database\Eloquent\Builder|\Illuminate\Database\Eloquent\Relations\Relation $queryBuilder
|
||||
* @param string $searchKeyword
|
||||
* @return void
|
||||
*/
|
||||
public function __construct($queryBuilder, $searchKeyword)
|
||||
{
|
||||
$this->queryBuilder = $queryBuilder;
|
||||
$this->searchKeyword = $searchKeyword;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the raw results of the search.
|
||||
*
|
||||
* @param class-string<\Laravel\Nova\Resource> $resourceClass
|
||||
* @param array<int, string|\Laravel\Nova\Query\Search\Column> $searchColumns
|
||||
* @return mixed
|
||||
*/
|
||||
public function handle($resourceClass, array $searchColumns)
|
||||
{
|
||||
return $this->queryBuilder->where(function ($query) use ($searchColumns) {
|
||||
$connectionType = $query->getModel()->getConnection()->getDriverName();
|
||||
|
||||
collect($searchColumns)
|
||||
->each(function ($column) use ($query, $connectionType) {
|
||||
if ($column instanceof Column || (! is_string($column) && is_callable($column))) {
|
||||
$column($query, $this->searchKeyword, $connectionType);
|
||||
} else {
|
||||
Column::from($column)->__invoke($query, $this->searchKeyword, $connectionType);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
89
nova/src/Query/Search/Column.php
Normal file
89
nova/src/Query/Search/Column.php
Normal file
@@ -0,0 +1,89 @@
|
||||
<?php
|
||||
|
||||
namespace Laravel\Nova\Query\Search;
|
||||
|
||||
use Illuminate\Database\Query\Expression;
|
||||
|
||||
class Column
|
||||
{
|
||||
/**
|
||||
* The search column.
|
||||
*
|
||||
* @var \Illuminate\Database\Query\Expression|string
|
||||
*/
|
||||
public $column;
|
||||
|
||||
/**
|
||||
* Construct a new search.
|
||||
*
|
||||
* @param \Illuminate\Database\Query\Expression|string $column
|
||||
* @return void
|
||||
*/
|
||||
public function __construct($column)
|
||||
{
|
||||
$this->column = $column;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create Column instance for raw expression value.
|
||||
*
|
||||
* @param string $column
|
||||
* @return mixed
|
||||
*/
|
||||
public static function raw($column)
|
||||
{
|
||||
return new static(new Expression($column));
|
||||
}
|
||||
|
||||
/**
|
||||
* Create Column instance from raw expression or fluent string.
|
||||
*
|
||||
* @param \Illuminate\Database\Query\Expression|string $column
|
||||
* @return mixed
|
||||
*/
|
||||
public static function from($column)
|
||||
{
|
||||
if ($column instanceof Expression) {
|
||||
return new static($column);
|
||||
}
|
||||
|
||||
if (strpos($column, '->') !== false) {
|
||||
return new SearchableJson($column);
|
||||
} elseif (strpos($column, '.') !== false) {
|
||||
[$relation, $columnName] = explode('.', $column, 2);
|
||||
|
||||
return new SearchableRelation($relation, $columnName);
|
||||
}
|
||||
|
||||
return new static($column);
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply the search.
|
||||
*
|
||||
* @param \Illuminate\Database\Eloquent\Builder|\Illuminate\Database\Eloquent\Relations\Relation $query
|
||||
* @param string $search
|
||||
* @param string $connectionType
|
||||
* @param string $whereOperator
|
||||
* @return \Illuminate\Database\Eloquent\Builder
|
||||
*/
|
||||
public function __invoke($query, $search, string $connectionType, string $whereOperator = 'orWhere')
|
||||
{
|
||||
return $query->{$whereOperator}(
|
||||
$this->columnName($query),
|
||||
$connectionType == 'pgsql' ? 'ilike' : 'like',
|
||||
"%{$search}%"
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the column name.
|
||||
*
|
||||
* @param \Illuminate\Database\Eloquent\Builder|\Illuminate\Database\Eloquent\Relations\Relation $query
|
||||
* @return string
|
||||
*/
|
||||
protected function columnName($query)
|
||||
{
|
||||
return $this->column instanceof Expression ? $this->column : $query->qualifyColumn($this->column);
|
||||
}
|
||||
}
|
||||
50
nova/src/Query/Search/PrimaryKey.php
Normal file
50
nova/src/Query/Search/PrimaryKey.php
Normal file
@@ -0,0 +1,50 @@
|
||||
<?php
|
||||
|
||||
namespace Laravel\Nova\Query\Search;
|
||||
|
||||
class PrimaryKey extends Column
|
||||
{
|
||||
/**
|
||||
* Max primary key size.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
protected $maxPrimaryKeySize;
|
||||
|
||||
/**
|
||||
* Construct a new search.
|
||||
*
|
||||
* @param \Illuminate\Database\Query\Expression|string $column
|
||||
* @param int $maxPrimaryKeySize
|
||||
* @return void
|
||||
*/
|
||||
public function __construct($column, $maxPrimaryKeySize = PHP_INT_MAX)
|
||||
{
|
||||
$this->column = $column;
|
||||
$this->maxPrimaryKeySize = $maxPrimaryKeySize;
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply the search.
|
||||
*
|
||||
* @param \Illuminate\Database\Eloquent\Builder|\Illuminate\Database\Eloquent\Relations\Relation $query
|
||||
* @param string|int $search
|
||||
* @param string $connectionType
|
||||
* @param string $whereOperator
|
||||
* @return \Illuminate\Database\Eloquent\Builder
|
||||
*/
|
||||
public function __invoke($query, $search, string $connectionType, string $whereOperator = 'orWhere')
|
||||
{
|
||||
$model = $query->getModel();
|
||||
|
||||
$canSearchPrimaryKey = ctype_digit($search) &&
|
||||
in_array($model->getKeyType(), ['int', 'integer']) &&
|
||||
($connectionType != 'pgsql' || $search <= $this->maxPrimaryKeySize);
|
||||
|
||||
if (! $canSearchPrimaryKey) {
|
||||
return parent::__invoke($query, $search, $connectionType, $whereOperator);
|
||||
}
|
||||
|
||||
return $query->{$whereOperator}($model->getQualifiedKeyName(), $search);
|
||||
}
|
||||
}
|
||||
47
nova/src/Query/Search/SearchableJson.php
Normal file
47
nova/src/Query/Search/SearchableJson.php
Normal file
@@ -0,0 +1,47 @@
|
||||
<?php
|
||||
|
||||
namespace Laravel\Nova\Query\Search;
|
||||
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
class SearchableJson extends Column
|
||||
{
|
||||
/**
|
||||
* The search JSON seletor path.
|
||||
*
|
||||
* @var \Illuminate\Database\Query\Expression|string
|
||||
*/
|
||||
public $jsonSelectorPath;
|
||||
|
||||
/**
|
||||
* Construct a new search.
|
||||
*
|
||||
* @param \Illuminate\Database\Query\Expression|string $jsonSelectorPath
|
||||
* @return void
|
||||
*/
|
||||
public function __construct($jsonSelectorPath)
|
||||
{
|
||||
$this->jsonSelectorPath = $jsonSelectorPath;
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply the search.
|
||||
*
|
||||
* @param \Illuminate\Database\Eloquent\Builder|\Illuminate\Database\Eloquent\Relations\Relation $query
|
||||
* @param string $search
|
||||
* @param string $connectionType
|
||||
* @param string $whereOperator
|
||||
* @return \Illuminate\Database\Eloquent\Builder
|
||||
*/
|
||||
public function __invoke($query, $search, string $connectionType, string $whereOperator = 'orWhere')
|
||||
{
|
||||
$path = $query->getGrammar()->wrap($this->jsonSelectorPath);
|
||||
$likeOperator = $connectionType == 'pgsql' ? 'ilike' : 'like';
|
||||
|
||||
if (in_array($connectionType, ['pgsql', 'sqlite'])) {
|
||||
return $query->{$whereOperator}($this->jsonSelectorPath, $likeOperator, "%{$search}%");
|
||||
}
|
||||
|
||||
return $query->{$whereOperator.'Raw'}("lower({$path}) {$likeOperator} ?", ['%'.Str::lower($search).'%']);
|
||||
}
|
||||
}
|
||||
63
nova/src/Query/Search/SearchableMorphToRelation.php
Normal file
63
nova/src/Query/Search/SearchableMorphToRelation.php
Normal file
@@ -0,0 +1,63 @@
|
||||
<?php
|
||||
|
||||
namespace Laravel\Nova\Query\Search;
|
||||
|
||||
class SearchableMorphToRelation extends SearchableRelation
|
||||
{
|
||||
/**
|
||||
* The available morph types.
|
||||
*
|
||||
* @var array<int, class-string<\Illuminate\Database\Eloquent\Model|\Laravel\Nova\Resource>|string>
|
||||
*/
|
||||
public $types = [];
|
||||
|
||||
/**
|
||||
* Construct a new search.
|
||||
*
|
||||
* @param string $relation
|
||||
* @param \Illuminate\Database\Query\Expression|string $column
|
||||
* @param array<int, class-string<\Illuminate\Database\Eloquent\Model|\Laravel\Nova\Resource>|string> $types
|
||||
* @return void
|
||||
*/
|
||||
public function __construct(string $relation, $column, array $types = [])
|
||||
{
|
||||
$this->types = $types;
|
||||
|
||||
parent::__construct($relation, $column);
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply the search.
|
||||
*
|
||||
* @param \Illuminate\Database\Eloquent\Builder|\Illuminate\Database\Eloquent\Relations\Relation $query
|
||||
* @param string $search
|
||||
* @param string $connectionType
|
||||
* @param string $whereOperator
|
||||
* @return \Illuminate\Database\Eloquent\Builder
|
||||
*/
|
||||
public function __invoke($query, $search, string $connectionType, string $whereOperator = 'orWhere')
|
||||
{
|
||||
return $query->{$whereOperator.'HasMorph'}($this->relation, $this->morphTypes(), function ($query) use ($search, $connectionType) {
|
||||
return Column::from($this->column)->__invoke(
|
||||
$query, $search, $connectionType, 'where'
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Get available morph types.
|
||||
*
|
||||
* @return array<int, class-string<\Illuminate\Database\Eloquent\Model>|string>|string
|
||||
*/
|
||||
protected function morphTypes()
|
||||
{
|
||||
if (empty($this->types)) {
|
||||
return '*';
|
||||
}
|
||||
|
||||
return collect($this->types)
|
||||
->map(function ($resource) {
|
||||
return $resource::$model ?? $resource;
|
||||
})->all();
|
||||
}
|
||||
}
|
||||
45
nova/src/Query/Search/SearchableRelation.php
Normal file
45
nova/src/Query/Search/SearchableRelation.php
Normal file
@@ -0,0 +1,45 @@
|
||||
<?php
|
||||
|
||||
namespace Laravel\Nova\Query\Search;
|
||||
|
||||
class SearchableRelation extends Column
|
||||
{
|
||||
/**
|
||||
* The relationship name.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $relation;
|
||||
|
||||
/**
|
||||
* Construct a new search.
|
||||
*
|
||||
* @param string $relation
|
||||
* @param \Illuminate\Database\Query\Expression|string $column
|
||||
* @return void
|
||||
*/
|
||||
public function __construct(string $relation, $column)
|
||||
{
|
||||
$this->relation = $relation;
|
||||
|
||||
parent::__construct($column);
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply the search.
|
||||
*
|
||||
* @param \Illuminate\Database\Eloquent\Builder|\Illuminate\Database\Eloquent\Relations\Relation $query
|
||||
* @param string $search
|
||||
* @param string $connectionType
|
||||
* @param string $whereOperator
|
||||
* @return \Illuminate\Database\Eloquent\Builder
|
||||
*/
|
||||
public function __invoke($query, $search, string $connectionType, string $whereOperator = 'orWhere')
|
||||
{
|
||||
return $query->{$whereOperator.'Has'}($this->relation, function ($query) use ($search, $connectionType) {
|
||||
return Column::from($this->column)->__invoke(
|
||||
$query, $search, $connectionType, 'where'
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
26
nova/src/Query/Search/SearchableText.php
Normal file
26
nova/src/Query/Search/SearchableText.php
Normal file
@@ -0,0 +1,26 @@
|
||||
<?php
|
||||
|
||||
namespace Laravel\Nova\Query\Search;
|
||||
|
||||
class SearchableText extends Column
|
||||
{
|
||||
/**
|
||||
* Apply the search.
|
||||
*
|
||||
* @param \Illuminate\Database\Eloquent\Builder|\Illuminate\Database\Eloquent\Relations\Relation $query
|
||||
* @param string $search
|
||||
* @param string $connectionType
|
||||
* @param string $whereOperator
|
||||
* @return \Illuminate\Database\Eloquent\Builder
|
||||
*/
|
||||
public function __invoke($query, $search, string $connectionType, string $whereOperator = 'orWhere')
|
||||
{
|
||||
if (in_array($connectionType, ['mysql', 'pgsql'])) {
|
||||
$query->{$whereOperator.'FullText'}(
|
||||
$this->columnName($query), $search
|
||||
);
|
||||
}
|
||||
|
||||
return $query;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user