Skip to content

Commit

Permalink
Search classes
Browse files Browse the repository at this point in the history
Introduction of "Search" classes
  • Loading branch information
TitasGailius committed Oct 15, 2020
2 parents 6cad519 + 34b4a2c commit 7a1208a
Show file tree
Hide file tree
Showing 5 changed files with 272 additions and 45 deletions.
83 changes: 81 additions & 2 deletions readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ abstract class Resource extends NovaResource

## Usage

Simply add `public static $searchRelations` array to any of your Nova resources.
Simply add `public static $searchRelations` variable to any of your Nova resources.
This array accepts a relationship name as a key and an array of searchable columns as a value.

```php
Expand All @@ -37,6 +37,22 @@ public static $searchRelations = [
];
```

Alternatively, you may add a static `searchableRelations()` method to return an array of searchable relations.

```php
/**
* Get the searchable columns for the resource.
*
* @return array
*/
public static function searchableRelations(): array
{
return [
'user' => ['username', 'email'],
];
}
```

## Global search

You may customize the rules of your searchable relationships for global search by defining the `$globalSearchRelations` property.
Expand All @@ -52,7 +68,26 @@ public static $globalSearchRelations = [
];
```

You may disable the global search for relationships by defining the `$globalSearchRelations` property with an empty array.
Alternatively, you may add a static `globallySearchableRelations()` method to return an array of globally searchable relations.

```php
/**
* Get the searchable columns for the resource.
*
* @return array
*/
public static function globallySearchableRelations(): array
{
return [
'user' => ['email'],
];
}
```

---
#### Disabling global search for relationships

You may disable the global relationship search by declaring `$globalSearchRelations` with an empty array.

```php
/**
Expand Down Expand Up @@ -88,3 +123,47 @@ public static $searchRelations = [
'user.country' => ['code'],
];
```

## Extending Search

You may apply custom search logic for the specified relations by retuning a class implementing a `Search` interface.

```php
/**
* Get the searchable columns for the resource.
*
* @return array
*/
public static function searchableRelations(): array
{
return [
'country' => new LocationSearch(['USA', 'UK']),
];
}
```

Your custom search class must implement a simple `Search` interface that has a single method which accepts
the current query `$query`, a relationship name `$relation` and a search input `$search`.

```php
<?php

namespace Titasgailius\SearchRelations\Contracts;

use Illuminate\Database\Eloquent\Builder;

interface Search
{
/**
* Apply search for the given relation.
*
* @param \Illuminate\Database\Eloquent\Builder $query
* @param string $relation
* @param string $search
* @return \Illuminate\Database\Eloquent\Builder
*/
public function apply(Builder $query, string $relation, string $search): Builder;
}
```

You may take a look at the `Titasgailius\SearchRelations\Searches\RelationSearch` class as an example.
18 changes: 18 additions & 0 deletions src/Contracts/Search.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<?php

namespace Titasgailius\SearchRelations\Contracts;

use Illuminate\Database\Eloquent\Builder;

interface Search
{
/**
* Apply search for the given relation.
*
* @param \Illuminate\Database\Eloquent\Builder $query
* @param string $relation
* @param string $search
* @return \Illuminate\Database\Eloquent\Builder
*/
public function apply(Builder $query, string $relation, string $search): Builder;
}
75 changes: 75 additions & 0 deletions src/Searches/ColumnSearch.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
<?php

namespace Titasgailius\SearchRelations\Searches;

use Illuminate\Database\Eloquent\Builder;
use Titasgailius\SearchRelations\Contracts\Search;

class ColumnSearch implements Search
{
/**
* Searchable columns.
*
* @var array
*/
protected $columns;

/**
* Instantiate a new search query.
*
* @param array $columns
*/
public function __construct(array $columns)
{
$this->columns = $columns;
}

/**
* Apply search for the given relation.
*
* @param \Illuminate\Database\Eloquent\Builder $query
* @param string $relation
* @param string $search
* @return \Illuminate\Database\Eloquent\Builder
*/
public function apply(Builder $query, string $relation, string $search): Builder
{
return $query->where(function ($query) use ($search) {
return $this->applySearchQuery($query, $search);
});
}

/**
* Apply search query.
*
* @param \Illuminate\Database\Eloquent\Builder $query
* @param string $search
* @return \Illuminate\Database\Eloquent\Builder
*/
protected function applySearchQuery(Builder $query, string $search): Builder
{
$model = $query->getModel();
$operator = $this->operator($query);

foreach ($this->columns as $column) {
$query->orWhere($model->qualifyColumn($column), $operator, '%'.$search.'%');
}

return $query;
}

/**
* Get the like operator for the given query.
*
* @param \Illuminate\Database\Eloquent\Builder $query
* @return string
*/
protected function operator(Builder $query): string
{
if ($query->getModel()->getConnection()->getDriverName() === 'pgsql') {
return 'ILIKE';
}

return 'LIKE';
}
}
68 changes: 68 additions & 0 deletions src/Searches/RelationSearch.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
<?php

namespace Titasgailius\SearchRelations\Searches;

use Illuminate\Database\Eloquent\Builder;
use Titasgailius\SearchRelations\Contracts\Search;
use Illuminate\Database\Eloquent\RelationNotFoundException;

class RelationSearch implements Search
{
/**
* Searchable columns.
*
* @var array
*/
protected $columns;

/**
* Instantiate a new search query instance.
*
* @param array $columns
*/
public function __construct(array $columns)
{
$this->columns = $columns;
}

/**
* Apply search for the given relation.
*
* @param \Illuminate\Database\Eloquent\Builder $query
* @param string $relation
* @param string $search
* @return \Illuminate\Database\Eloquent\Builder
*/
public function apply(Builder $query, string $relation, string $search): Builder
{
$this->ensureRelationshipExists($query, $relation);

$query->orWhereHas($relation, function ($query) use ($relation, $search) {
return $this->columnSearch()->apply($query, $relation, $search);
});

return $query;
}

/**
* Ensure that the specified relationship exists.
*
* @param \Illuminate\Database\Eloquent\Builder $query
* @param string $relation
* @return void
*/
protected function ensureRelationshipExists(Builder $query, string $relation)
{
$query->getRelation($relation);
}

/**
* Apply column search.
*
* @return \Titasgailius\SearchRelations\Contracts\Search
*/
protected function columnSearch(): Search
{
return new ColumnSearch($this->columns);
}
}
73 changes: 30 additions & 43 deletions src/SearchesRelations.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,10 @@

namespace Titasgailius\SearchRelations;

use Closure;
use InvalidArgumentException;
use Illuminate\Database\Eloquent\Builder;
use Titasgailius\SearchRelations\Contracts\Search;
use Titasgailius\SearchRelations\Searches\RelationSearch;

trait SearchesRelations
{
Expand All @@ -14,7 +16,8 @@ trait SearchesRelations
*/
public static function searchable()
{
return parent::searchable() || !empty(static::$searchRelations);
return parent::searchable()
|| ! empty(static::resolveSearchableRelations());
}

/**
Expand All @@ -24,10 +27,6 @@ public static function searchable()
*/
public static function searchableRelations(): array
{
if (static::isGlobalSearch()) {
return static::globallySearchableRelations();
}

return static::$searchRelations ?? [];
}

Expand All @@ -42,22 +41,23 @@ public static function globallySearchableRelations(): array
return static::$globalSearchRelations;
}

if (static::globalSearchDisabledForRelations()) {
return [];
if (static::$searchRelationsGlobally ?? true) {
return static::searchableRelations();
}

return static::$searchRelations ?? [];
return [];
}

/**
* Determine if a global search is disabled for the relationships.
* Resolve searchable relations for the current request.
*
* @return boolean
* @return array
*/
protected static function globalSearchDisabledForRelations(): bool
protected static function resolveSearchableRelations(): array
{
return isset(static::$searchRelationsGlobally)
&& ! static::$searchRelationsGlobally;
return static::isGlobalSearch()
? static::globallySearchableRelations()
: static::searchableRelations();
}

/**
Expand Down Expand Up @@ -94,46 +94,33 @@ protected static function applySearch($query, $search)
*/
protected static function applyRelationSearch(Builder $query, string $search): Builder
{
foreach (static::searchableRelations() as $relation => $columns) {
$query->orWhereHas($relation, function ($query) use ($columns, $search) {
$query->where(static::searchQueryApplier($columns, $search));
});
foreach (static::resolveSearchableRelations() as $relation => $columns) {
static::parseSearch($relation, $columns)->apply($query, $relation, $search);
}

return $query;
}

/**
* Returns a Closure that applies a search query for a given columns.
* Parse search.
*
* @param array $columns
* @param string $search
* @return \Closure
* @param string $relation
* @param mixed $columns
* @return \Titasgailius\SearchRelations\Contracts\Search
*/
protected static function searchQueryApplier(array $columns, string $search): Closure
protected static function parseSearch($relation, $columns): Search
{
return function ($query) use ($columns, $search) {
$model = $query->getModel();
$operator = static::operator($query);

foreach ($columns as $column) {
$query->orWhere($model->qualifyColumn($column), $operator, '%'.$search.'%');
}
};
}
if ($columns instanceof Search) {
return $columns;
}

/**
* Resolve the query operator.
*
* @param \Illuminate\Database\Eloquent\Builder $query
* @return string
*/
protected static function operator(Builder $query): string
{
if ($query->getModel()->getConnection()->getDriverName() === 'pgsql') {
return 'ILIKE';
if (is_array($columns)) {
return new RelationSearch($columns);
}

return 'LIKE';
throw new InvalidArgumentException(sprintf(
'Unsupported search configuration in [%s] resource for [%s] relationship.',
static::class, $relation
));
}
}

0 comments on commit 7a1208a

Please sign in to comment.