Skip to content

Commit 1f37b40

Browse files
authored
Merge pull request #11 from rslsystems/sortable-by-configure
Sortable by configure
2 parents 18baa6f + 5add1f3 commit 1f37b40

File tree

3 files changed

+162
-17
lines changed

3 files changed

+162
-17
lines changed

README.md

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,27 @@ By default, if no operator is specified, it will default to `eq` so `name=Bob` w
6363

6464
## Sorting
6565

66+
You can set the fields that can be sortable when setting up your model.
67+
You can pass an array of fields as the second parameter of configure.
68+
It does not have to match the filterable, allowing total control.
69+
70+
```php
71+
<?php
72+
73+
class Pet extends Model implements Searchable
74+
{
75+
public function sieve(Sieve $sieve)
76+
{
77+
$sieve->configure(fn ($filter) => [
78+
'name' => $filter->string(),
79+
'breed' => $filter->enum(['Beagle', 'Tiger']),
80+
],
81+
['name']
82+
);
83+
}
84+
}
85+
```
86+
6687
Sieve will also allow consumers of your API to specify sort order. You can do this by `sort=property:direction`
6788

6889
* `sort=age:asc`

src/Sieve.php

Lines changed: 35 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -8,20 +8,26 @@ class Sieve
88
{
99
protected $filters = [];
1010

11+
protected $sortable = [];
12+
1113
protected $request;
1214

13-
protected $defaultSort;
15+
protected $defaultSort = null;
1416

1517
public function __construct(Request $request)
1618
{
1719
$this->request = $request;
1820
}
1921

20-
public function configure($callback)
22+
public function configure($callback, array $sortable = [])
2123
{
2224
foreach ($callback(new FilterBuilder) as $prop => $filter) {
2325
$this->addFilter($prop, $filter);
2426
}
27+
28+
foreach ($sortable as $sort) {
29+
$this->addSort($sort);
30+
}
2531
}
2632

2733
public function addFilter($property, $filter)
@@ -46,8 +52,6 @@ public function apply($queryBuilder)
4652
$filter = $sieveFilter['filter'];
4753
$property = $sieveFilter['property'];
4854

49-
$sort = $this->request->get("sort");
50-
5155
foreach ($filter->operators() as $operator) {
5256
$eqFilter = $operator == 'eq' && $this->request->has($property);
5357
if (!$this->request->has("$property:$operator") && !$eqFilter) {
@@ -62,27 +66,43 @@ public function apply($queryBuilder)
6266
$search = new SearchTerm($property, $operator, $property, $term);
6367
$filter->modifyQuery($queryBuilder, $search);
6468
}
69+
}
6570

66-
if ($this->getSort() == "$property:desc") {
67-
$queryBuilder->orderBy($property, "desc");
68-
}
69-
70-
if ($this->getSort() == "$property:asc") {
71-
$queryBuilder->orderBy($property, "asc");
72-
}
71+
if ($sort = $this->getSort()) {
72+
$queryBuilder->orderBy($sort['sortBy'], $sort['sortDirection']);
7373
}
7474

7575
return $this;
7676
}
7777

78-
public function setDefaultSort($property='id', $direction='asc'): Sieve
78+
public function setDefaultSort($property = 'id', $direction = 'asc'): Sieve
7979
{
80-
$this->defaultSort = $property.':'.$direction;
80+
$this->sortable[] = $property;
81+
$this->defaultSort = $property . ':' . $direction;
82+
8183
return $this;
8284
}
8385

84-
public function getSort(): ?string
86+
public function addSort(string $sort)
87+
{
88+
$this->sortable[] = $sort;
89+
}
90+
91+
public function getSort(): ?array
92+
{
93+
$sort = $this->request->get("sort") ?? $this->defaultSort ?? ':';
94+
95+
list($sortBy, $sortDirection) = explode(':', $sort, 2);
96+
97+
if ((in_array(strtolower($sortDirection), ['asc', 'desc'])) && (in_array($sortBy, $this->sortable))) {
98+
return compact('sortBy', 'sortDirection');
99+
}
100+
101+
return null;
102+
}
103+
104+
public function getSortable(): array
85105
{
86-
return $this->request->get("sort") ?? $this->defaultSort;
106+
return $this->sortable;
87107
}
88108
}

tests/SieveTest.php

Lines changed: 106 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,45 @@
99

1010
class SieveTest extends TestCase
1111
{
12+
/**
13+
* @test
14+
*/
15+
public function filters_and_sorts()
16+
{
17+
$request = Request::create('/', 'GET', [
18+
'name:in' => 'Snoopy,Hobbes',
19+
'sort' => 'name:desc'
20+
]);
21+
22+
$seive = new Sieve($request);
23+
$seive->addFilter('name', new StringFilter);
24+
$seive->addSort('name');
25+
26+
/** @var Builder */
27+
$builder = $this->app->make(Builder::class);
28+
29+
$seive->apply($builder);
30+
31+
$this->assertEquals(1, count($builder->wheres));
32+
33+
$where = $builder->wheres[0];
34+
35+
$this->assertEquals('In', $where['type']);
36+
$this->assertEquals('name', $where['column']);
37+
$this->assertEquals([
38+
'Snoopy',
39+
'Hobbes',
40+
], $where['values']);
41+
$this->assertEquals('and', $where['boolean']);
42+
43+
$this->assertEquals(1, count($builder->orders));
44+
45+
$order = $builder->orders[0];
46+
47+
$this->assertEquals('name', $order['column']);
48+
$this->assertEquals('desc', $order['direction']);
49+
}
50+
1251
/**
1352
* @test
1453
*/
@@ -47,8 +86,73 @@ public function set_default_sort_filter()
4786
$request = Request::create('/');
4887

4988
$sieve = new Sieve($request);
50-
$sieve->setDefaultSort('name','desc');
89+
$sieve->setDefaultSort('name', 'desc');
90+
91+
$this->assertEquals($sieve->getSort(), ['sortBy' => 'name', 'sortDirection' => 'desc']);
92+
}
93+
94+
/**
95+
* @test
96+
*/
97+
public function applies_sieve_sorts_to_a_query_builder()
98+
{
99+
$request = Request::create('/', 'GET', [
100+
'sort' => 'name:desc',
101+
]);
102+
103+
$seive = new Sieve($request);
104+
$seive->addSort('name');
105+
106+
/** @var Builder */
107+
$builder = $this->app->make(Builder::class);
108+
109+
$seive->apply($builder);
110+
111+
$this->assertEquals(1, count($builder->orders));
112+
113+
$order = $builder->orders[0];
114+
115+
$this->assertEquals('name', $order['column']);
116+
$this->assertEquals('desc', $order['direction']);
117+
}
118+
119+
/**
120+
* @test
121+
*/
122+
public function ignores_undefined_sort()
123+
{
124+
$request = Request::create('/', 'GET', [
125+
'sort' => 'name:desc',
126+
]);
127+
128+
$seive = new Sieve($request);
129+
$seive->addSort('id');
130+
131+
/** @var Builder */
132+
$builder = $this->app->make(Builder::class);
133+
134+
$seive->apply($builder);
135+
136+
$this->assertEquals(null, $builder->orders);
137+
}
138+
139+
/**
140+
* @test
141+
*/
142+
public function ignores_invalid_sort_direction()
143+
{
144+
$request = Request::create('/', 'GET', [
145+
'sort' => 'name:foo',
146+
]);
147+
148+
$seive = new Sieve($request);
149+
$seive->addSort('name');
150+
151+
/** @var Builder */
152+
$builder = $this->app->make(Builder::class);
153+
154+
$seive->apply($builder);
51155

52-
$this->assertEquals($sieve->getSort(), 'name:desc');
156+
$this->assertEquals(null, $builder->orders);
53157
}
54158
}

0 commit comments

Comments
 (0)