Skip to content

Commit 1988a87

Browse files
committed
Add filters
1 parent 21d2bd1 commit 1988a87

File tree

1 file changed

+115
-3
lines changed

1 file changed

+115
-3
lines changed

docs/05. Best practices.md

Lines changed: 115 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -177,9 +177,6 @@ custom repository, that will create the query, and wrap it under a paginator:
177177
First, your service now delegate to the repository:
178178

179179
```php
180-
use DoctrineModule\Paginator\Adapter\Selectable as SelectableAdapter;
181-
use Zend\Paginator\Paginator;
182-
183180
class TweetService
184181
{
185182
private $tweetRepository;
@@ -225,6 +222,121 @@ filtering by first name, last name or age when retrieving a list of users).
225222
* You have more complex filtering needs, when you can possibly also filter by properties of associations (for instance,
226223
you want to filter by author first name when retrieving a list of tweets).
227224

225+
### Simple filtering
226+
227+
For filtering fields on the same entity, you can use a Criteria object. The Criteria object is a Doctrine abstraction
228+
that allows to filtering collections (it also works on repository) efficiently. For instance, let's say you want
229+
to filter users by first name and last name. To do that, you allow `first_name` and `last_name` query params:
230+
231+
```php
232+
use Doctrine\Common\Collections\Criteria;
233+
234+
class UserListController extends AbstractRestfulController
235+
{
236+
private $userService;
237+
238+
public function get()
239+
{
240+
// Get all the query params
241+
$queryParams = $this->params()->fromQuery();
242+
243+
// Create the criteria object
244+
$criteria = new Criteria();
245+
$builder = $criteria->expr();
246+
247+
foreach ($queryParams as $key => $value) {
248+
switch ($key) {
249+
case 'first_name':
250+
$criteria->andWhere($builder->eq('firstName', $value));
251+
break;
252+
253+
case 'last_name':
254+
$criteria->andWhere($builder->eq('lastName', $value));
255+
break;
256+
}
257+
}
258+
259+
$users = $this->userService->getAllByCriteria($criteria);
260+
261+
return new ResourceViewModel(['users' => $users]);
262+
}
263+
}
264+
```
265+
266+
With your `getAllByCriteria` creating a paginator, but with the Criteria object to further filtering the data set:
267+
268+
```php
269+
use DoctrineModule\Paginator\Adapter\Selectable as SelectableAdapter;
270+
use Zend\Paginator\Paginator;
271+
272+
class UserService
273+
{
274+
private $userRepository;
275+
276+
public function getAllByCriteria(Criteria $criteria)
277+
{
278+
return new Paginator(new SelectableAdapter($this->tweetRepository, $criteria));
279+
}
280+
}
281+
```
282+
283+
> As you can see, we can easily combine filtering and pagination thanks to the power of the Criteria API!
284+
285+
However, there is a problem with this approach: our controller is now polluted with code that is difficult to
286+
test in isolation, and cannot be reused elsewhere. To solve this problem, we introduce a new kind of objects: the
287+
Criteria objects.
288+
289+
Those objects simply extend the base Criteria, but know how to build the criteria object. For instance, here is
290+
a UserCriteria object:
291+
292+
```php
293+
class UserCriteria extends Criteria
294+
{
295+
public function __construct(array $filters = [])
296+
{
297+
$builder = $this->expr();
298+
299+
foreach ($queryParams as $key => $value) {
300+
switch ($key) {
301+
case 'first_name':
302+
$this->andWhere($builder->eq('firstName', $value));
303+
break;
304+
305+
case 'last_name':
306+
$this->andWhere($builder->eq('lastName', $value));
307+
break;
308+
}
309+
}
310+
}
311+
}
312+
```
313+
314+
Your controller now become:
315+
316+
```php
317+
use Doctrine\Common\Collections\Criteria;
318+
319+
class UserListController extends AbstractRestfulController
320+
{
321+
private $userService;
322+
323+
public function get()
324+
{
325+
$criteria = new UserCriteria($this->params()->fromQuery(null, []));
326+
$users = $this->userService->getAllByCriteria($criteria);
327+
328+
return new ResourceViewModel(['users' => $users]);
329+
}
330+
}
331+
```
332+
333+
Much cleaner!
334+
335+
### Complex filtering
336+
337+
This works well when filtering on fields that belong to the entity. However, the Criteria API is a rather limited API,
338+
and does not allow things such as filtering on association using joins. We therefore need to resolve to a similar
339+
approach, but without using the Criteria API itself.
228340

229341
* Back to [**Using HTTP exceptions for reporting errors**](/docs/04. Using HTTP exceptions for reporting errors.md)
230342
* Back to [the Index](/docs/README.md)

0 commit comments

Comments
 (0)