Skip to content

Commit 7a1208a

Browse files
authored
Search classes
Introduction of "Search" classes
2 parents 6cad519 + 34b4a2c commit 7a1208a

File tree

5 files changed

+272
-45
lines changed

5 files changed

+272
-45
lines changed

readme.md

Lines changed: 81 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ abstract class Resource extends NovaResource
2323

2424
## Usage
2525

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

2929
```php
@@ -37,6 +37,22 @@ public static $searchRelations = [
3737
];
3838
```
3939

40+
Alternatively, you may add a static `searchableRelations()` method to return an array of searchable relations.
41+
42+
```php
43+
/**
44+
* Get the searchable columns for the resource.
45+
*
46+
* @return array
47+
*/
48+
public static function searchableRelations(): array
49+
{
50+
return [
51+
'user' => ['username', 'email'],
52+
];
53+
}
54+
```
55+
4056
## Global search
4157

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

55-
You may disable the global search for relationships by defining the `$globalSearchRelations` property with an empty array.
71+
Alternatively, you may add a static `globallySearchableRelations()` method to return an array of globally searchable relations.
72+
73+
```php
74+
/**
75+
* Get the searchable columns for the resource.
76+
*
77+
* @return array
78+
*/
79+
public static function globallySearchableRelations(): array
80+
{
81+
return [
82+
'user' => ['email'],
83+
];
84+
}
85+
```
86+
87+
---
88+
#### Disabling global search for relationships
89+
90+
You may disable the global relationship search by declaring `$globalSearchRelations` with an empty array.
5691

5792
```php
5893
/**
@@ -88,3 +123,47 @@ public static $searchRelations = [
88123
'user.country' => ['code'],
89124
];
90125
```
126+
127+
## Extending Search
128+
129+
You may apply custom search logic for the specified relations by retuning a class implementing a `Search` interface.
130+
131+
```php
132+
/**
133+
* Get the searchable columns for the resource.
134+
*
135+
* @return array
136+
*/
137+
public static function searchableRelations(): array
138+
{
139+
return [
140+
'country' => new LocationSearch(['USA', 'UK']),
141+
];
142+
}
143+
```
144+
145+
Your custom search class must implement a simple `Search` interface that has a single method which accepts
146+
the current query `$query`, a relationship name `$relation` and a search input `$search`.
147+
148+
```php
149+
<?php
150+
151+
namespace Titasgailius\SearchRelations\Contracts;
152+
153+
use Illuminate\Database\Eloquent\Builder;
154+
155+
interface Search
156+
{
157+
/**
158+
* Apply search for the given relation.
159+
*
160+
* @param \Illuminate\Database\Eloquent\Builder $query
161+
* @param string $relation
162+
* @param string $search
163+
* @return \Illuminate\Database\Eloquent\Builder
164+
*/
165+
public function apply(Builder $query, string $relation, string $search): Builder;
166+
}
167+
```
168+
169+
You may take a look at the `Titasgailius\SearchRelations\Searches\RelationSearch` class as an example.

src/Contracts/Search.php

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
<?php
2+
3+
namespace Titasgailius\SearchRelations\Contracts;
4+
5+
use Illuminate\Database\Eloquent\Builder;
6+
7+
interface Search
8+
{
9+
/**
10+
* Apply search for the given relation.
11+
*
12+
* @param \Illuminate\Database\Eloquent\Builder $query
13+
* @param string $relation
14+
* @param string $search
15+
* @return \Illuminate\Database\Eloquent\Builder
16+
*/
17+
public function apply(Builder $query, string $relation, string $search): Builder;
18+
}

src/Searches/ColumnSearch.php

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
<?php
2+
3+
namespace Titasgailius\SearchRelations\Searches;
4+
5+
use Illuminate\Database\Eloquent\Builder;
6+
use Titasgailius\SearchRelations\Contracts\Search;
7+
8+
class ColumnSearch implements Search
9+
{
10+
/**
11+
* Searchable columns.
12+
*
13+
* @var array
14+
*/
15+
protected $columns;
16+
17+
/**
18+
* Instantiate a new search query.
19+
*
20+
* @param array $columns
21+
*/
22+
public function __construct(array $columns)
23+
{
24+
$this->columns = $columns;
25+
}
26+
27+
/**
28+
* Apply search for the given relation.
29+
*
30+
* @param \Illuminate\Database\Eloquent\Builder $query
31+
* @param string $relation
32+
* @param string $search
33+
* @return \Illuminate\Database\Eloquent\Builder
34+
*/
35+
public function apply(Builder $query, string $relation, string $search): Builder
36+
{
37+
return $query->where(function ($query) use ($search) {
38+
return $this->applySearchQuery($query, $search);
39+
});
40+
}
41+
42+
/**
43+
* Apply search query.
44+
*
45+
* @param \Illuminate\Database\Eloquent\Builder $query
46+
* @param string $search
47+
* @return \Illuminate\Database\Eloquent\Builder
48+
*/
49+
protected function applySearchQuery(Builder $query, string $search): Builder
50+
{
51+
$model = $query->getModel();
52+
$operator = $this->operator($query);
53+
54+
foreach ($this->columns as $column) {
55+
$query->orWhere($model->qualifyColumn($column), $operator, '%'.$search.'%');
56+
}
57+
58+
return $query;
59+
}
60+
61+
/**
62+
* Get the like operator for the given query.
63+
*
64+
* @param \Illuminate\Database\Eloquent\Builder $query
65+
* @return string
66+
*/
67+
protected function operator(Builder $query): string
68+
{
69+
if ($query->getModel()->getConnection()->getDriverName() === 'pgsql') {
70+
return 'ILIKE';
71+
}
72+
73+
return 'LIKE';
74+
}
75+
}

src/Searches/RelationSearch.php

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
<?php
2+
3+
namespace Titasgailius\SearchRelations\Searches;
4+
5+
use Illuminate\Database\Eloquent\Builder;
6+
use Titasgailius\SearchRelations\Contracts\Search;
7+
use Illuminate\Database\Eloquent\RelationNotFoundException;
8+
9+
class RelationSearch implements Search
10+
{
11+
/**
12+
* Searchable columns.
13+
*
14+
* @var array
15+
*/
16+
protected $columns;
17+
18+
/**
19+
* Instantiate a new search query instance.
20+
*
21+
* @param array $columns
22+
*/
23+
public function __construct(array $columns)
24+
{
25+
$this->columns = $columns;
26+
}
27+
28+
/**
29+
* Apply search for the given relation.
30+
*
31+
* @param \Illuminate\Database\Eloquent\Builder $query
32+
* @param string $relation
33+
* @param string $search
34+
* @return \Illuminate\Database\Eloquent\Builder
35+
*/
36+
public function apply(Builder $query, string $relation, string $search): Builder
37+
{
38+
$this->ensureRelationshipExists($query, $relation);
39+
40+
$query->orWhereHas($relation, function ($query) use ($relation, $search) {
41+
return $this->columnSearch()->apply($query, $relation, $search);
42+
});
43+
44+
return $query;
45+
}
46+
47+
/**
48+
* Ensure that the specified relationship exists.
49+
*
50+
* @param \Illuminate\Database\Eloquent\Builder $query
51+
* @param string $relation
52+
* @return void
53+
*/
54+
protected function ensureRelationshipExists(Builder $query, string $relation)
55+
{
56+
$query->getRelation($relation);
57+
}
58+
59+
/**
60+
* Apply column search.
61+
*
62+
* @return \Titasgailius\SearchRelations\Contracts\Search
63+
*/
64+
protected function columnSearch(): Search
65+
{
66+
return new ColumnSearch($this->columns);
67+
}
68+
}

src/SearchesRelations.php

Lines changed: 30 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,10 @@
22

33
namespace Titasgailius\SearchRelations;
44

5-
use Closure;
5+
use InvalidArgumentException;
66
use Illuminate\Database\Eloquent\Builder;
7+
use Titasgailius\SearchRelations\Contracts\Search;
8+
use Titasgailius\SearchRelations\Searches\RelationSearch;
79

810
trait SearchesRelations
911
{
@@ -14,7 +16,8 @@ trait SearchesRelations
1416
*/
1517
public static function searchable()
1618
{
17-
return parent::searchable() || !empty(static::$searchRelations);
19+
return parent::searchable()
20+
|| ! empty(static::resolveSearchableRelations());
1821
}
1922

2023
/**
@@ -24,10 +27,6 @@ public static function searchable()
2427
*/
2528
public static function searchableRelations(): array
2629
{
27-
if (static::isGlobalSearch()) {
28-
return static::globallySearchableRelations();
29-
}
30-
3130
return static::$searchRelations ?? [];
3231
}
3332

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

45-
if (static::globalSearchDisabledForRelations()) {
46-
return [];
44+
if (static::$searchRelationsGlobally ?? true) {
45+
return static::searchableRelations();
4746
}
4847

49-
return static::$searchRelations ?? [];
48+
return [];
5049
}
5150

5251
/**
53-
* Determine if a global search is disabled for the relationships.
52+
* Resolve searchable relations for the current request.
5453
*
55-
* @return boolean
54+
* @return array
5655
*/
57-
protected static function globalSearchDisabledForRelations(): bool
56+
protected static function resolveSearchableRelations(): array
5857
{
59-
return isset(static::$searchRelationsGlobally)
60-
&& ! static::$searchRelationsGlobally;
58+
return static::isGlobalSearch()
59+
? static::globallySearchableRelations()
60+
: static::searchableRelations();
6161
}
6262

6363
/**
@@ -94,46 +94,33 @@ protected static function applySearch($query, $search)
9494
*/
9595
protected static function applyRelationSearch(Builder $query, string $search): Builder
9696
{
97-
foreach (static::searchableRelations() as $relation => $columns) {
98-
$query->orWhereHas($relation, function ($query) use ($columns, $search) {
99-
$query->where(static::searchQueryApplier($columns, $search));
100-
});
97+
foreach (static::resolveSearchableRelations() as $relation => $columns) {
98+
static::parseSearch($relation, $columns)->apply($query, $relation, $search);
10199
}
102100

103101
return $query;
104102
}
105103

106104
/**
107-
* Returns a Closure that applies a search query for a given columns.
105+
* Parse search.
108106
*
109-
* @param array $columns
110-
* @param string $search
111-
* @return \Closure
107+
* @param string $relation
108+
* @param mixed $columns
109+
* @return \Titasgailius\SearchRelations\Contracts\Search
112110
*/
113-
protected static function searchQueryApplier(array $columns, string $search): Closure
111+
protected static function parseSearch($relation, $columns): Search
114112
{
115-
return function ($query) use ($columns, $search) {
116-
$model = $query->getModel();
117-
$operator = static::operator($query);
118-
119-
foreach ($columns as $column) {
120-
$query->orWhere($model->qualifyColumn($column), $operator, '%'.$search.'%');
121-
}
122-
};
123-
}
113+
if ($columns instanceof Search) {
114+
return $columns;
115+
}
124116

125-
/**
126-
* Resolve the query operator.
127-
*
128-
* @param \Illuminate\Database\Eloquent\Builder $query
129-
* @return string
130-
*/
131-
protected static function operator(Builder $query): string
132-
{
133-
if ($query->getModel()->getConnection()->getDriverName() === 'pgsql') {
134-
return 'ILIKE';
117+
if (is_array($columns)) {
118+
return new RelationSearch($columns);
135119
}
136120

137-
return 'LIKE';
121+
throw new InvalidArgumentException(sprintf(
122+
'Unsupported search configuration in [%s] resource for [%s] relationship.',
123+
static::class, $relation
124+
));
138125
}
139126
}

0 commit comments

Comments
 (0)