Skip to content

Commit 0e13e6f

Browse files
authored
Feature/Add support for multiple filter groups. (#16)
1 parent 17ed6ae commit 0e13e6f

File tree

7 files changed

+285
-141
lines changed

7 files changed

+285
-141
lines changed

README.md

Lines changed: 46 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -12,31 +12,60 @@ interface.
1212
Just install the package from Packagist using composer:
1313

1414
```bash
15-
composer require complexheart/php-criteria
15+
composer require complex-heart/criteria
1616
```
1717

1818
## Usage
1919

20-
Just import the class:
20+
Import the class and use the fluent interface:
2121

2222
```php
23-
namespace ComplexHeart\Test\Domain\Criteria;
24-
25-
$criteria = Criteria::createDefault()
26-
->addFilterEqual('name', 'Vincent')
27-
->addFilterNotEqual('surname', 'Winnfield')
28-
->addFilterGreaterThan('money', '10000')
29-
->addFilterGreaterOrEqualThan('age', '35')
30-
->addFilterLessThan('cars', '2')
31-
->addFilterLessOrEqualThan('houses', '2')
32-
->addFilterLike('favoriteMeal', 'pork')
33-
->addFilterIn('boss', ['marcellus', 'mia'])
34-
->addFilterNotIn('hates', ['ringo', 'yolanda'])
35-
->withOrder(Order::createDescBy('name'))
23+
namespace ComplexHeart\Domain\Criteria;
24+
25+
// Match the users with status active and more than 7k followers and from Spain and France
26+
$g1 = FilterGroup::create()
27+
->addFilterEqual('status', 1)
28+
->addFilterGreaterThan('followers', 7000)
29+
->addFilterIn('country', ['es', 'fr']);
30+
31+
$criteria = Criteria::default()
32+
->withFilterGroup($g1)
33+
->withOrderBy('surname')
34+
->withOrderType(Order::TYPE_ASC)
35+
->withPageLimit(10)
36+
->withPageOffset(5);
37+
38+
$users = $repository->match($criteria);
39+
40+
// alternative, same as above
41+
$criteria = Criteria::default()
42+
->withFilterGroup(FilterGroup::create()
43+
->addFilterEqual('status', 1)
44+
->addFilterGreaterThan('followers', 7000)
45+
->addFilterIn('country', ['es', 'fr']))
3646
->withOrderBy('surname')
3747
->withOrderType(Order::TYPE_ASC)
3848
->withPageLimit(10)
39-
->withPageOffset(5)
49+
->withPageOffset(5);
50+
51+
// In SQL, we may have something like:
52+
// WHERE status = 1 AND followers >= 700 AND country in ('es', 'fr')
4053

41-
$customers = $customerRepository->match($criteria);
54+
$users = $repository->match($criteria);
55+
```
56+
57+
A `FilterGroup` is a set of filters or conditions that must match all together (`AND`). To match one group or another
58+
(OR), just add more `FilterGroup`.
59+
60+
```php
61+
62+
// Match articles with given term in title, or in tagline, or in content.
63+
$criteria = Criteria::default()
64+
->withFilterGroup(FilterGroup::create()->addFilterContains('title', $term))
65+
->withFilterGroup(FilterGroup::create()->addFilterContains('tagline', $term))
66+
->withFilterGroup(FilterGroup::create()->addFilterContains('content', $term))
67+
->withOrderBy('created_at')
68+
->withOrderType(Order::TYPE_ASC)
69+
->withPageLimit(10)
70+
->withPageOffset(5);
4271
```

src/Criteria.php

Lines changed: 55 additions & 81 deletions
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,17 @@
44

55
namespace ComplexHeart\Domain\Criteria;
66

7+
use Closure;
78
use ComplexHeart\Domain\Contracts\Model\ValueObject;
89
use ComplexHeart\Domain\Model\IsValueObject;
910

11+
use function Lambdish\Phunctional\map;
12+
1013
/**
1114
* Class Criteria
1215
*
1316
* @author Unay Santisteban <usantisteban@othercode.io>
14-
* @package ComplexHeart\SDK\Domain\Criteria
17+
* @package ComplexHeart\Domain\Criteria
1518
*/
1619
final class Criteria implements ValueObject
1720
{
@@ -20,130 +23,97 @@ final class Criteria implements ValueObject
2023
/**
2124
* Criteria constructor.
2225
*
23-
* @param FilterGroup<Filter> $filters
26+
* @param array<FilterGroup<Filter>> $groups
2427
* @param Order $order
2528
* @param Page $page
2629
*/
2730
public function __construct(
28-
private readonly FilterGroup $filters,
31+
private readonly array $groups,
2932
private readonly Order $order,
3033
private readonly Page $page,
3134
) {
32-
$this->check();
3335
}
3436

35-
public static function create(FilterGroup $filters, Order $order, Page $page): self
37+
/**
38+
* @param array<FilterGroup<Filter>> $groups
39+
* @param Order $order
40+
* @param Page $page
41+
* @return Criteria
42+
*/
43+
public static function create(array $groups, Order $order, Page $page): self
3644
{
37-
return new self($filters, $order, $page);
45+
return new self($groups, $order, $page);
3846
}
3947

40-
public static function createDefault(): self
48+
public static function default(): self
4149
{
42-
return self::create(FilterGroup::create(), Order::none(), Page::create());
50+
return self::create([], Order::none(), Page::create());
4351
}
4452

45-
public function withFilters(FilterGroup $filters): self
53+
/**
54+
* Returns a new instance of the criteria with the given FilterGroups.
55+
*
56+
* @param array<FilterGroup<Filter>> $groups
57+
* @return Criteria
58+
*/
59+
public function withFilterGroups(array $groups): self
4660
{
47-
return new self($filters, $this->order, $this->page);
61+
return self::create($groups, $this->order, $this->page);
62+
}
63+
64+
/**
65+
* Returns a new instance of the criteria adding the given FilterGroup.
66+
*
67+
* @param FilterGroup|Closure $group
68+
* @return $this
69+
*/
70+
public function withFilterGroup(FilterGroup|Closure $group): self
71+
{
72+
if (is_callable($group)) {
73+
$group = $group(new FilterGroup());
74+
}
75+
76+
return $this->withFilterGroups(array_merge($this->groups, [$group]));
4877
}
4978

5079
public function withOrder(Order $order): self
5180
{
52-
return new self($this->filters, $order, $this->page);
81+
return self::create($this->groups, $order, $this->page);
5382
}
5483

5584
public function withOrderBy(string $field): self
5685
{
57-
return new self($this->filters, Order::create($field, $this->orderType()), $this->page);
86+
return self::create($this->groups, Order::create($field, $this->orderType()), $this->page);
5887
}
5988

6089
public function withOrderType(string $type): self
6190
{
62-
return new self($this->filters, Order::create($this->orderBy(), $type), $this->page);
91+
return self::create($this->groups, Order::create($this->orderBy(), $type), $this->page);
6392
}
6493

6594
public function withPage(Page $page): self
6695
{
67-
return new self($this->filters, $this->order, $page);
96+
return self::create($this->groups, $this->order, $page);
6897
}
6998

7099
public function withPageOffset(int $offset): self
71100
{
72-
return new self($this->filters, $this->order, Page::create($this->pageLimit(), $offset));
101+
return self::create($this->groups, $this->order, Page::create($this->pageLimit(), $offset));
73102
}
74103

75104
public function withPageLimit(int $limit): self
76105
{
77-
return new self($this->filters, $this->order, Page::create($limit, $this->pageOffset()));
78-
}
79-
80-
public function filters(): FilterGroup
81-
{
82-
return $this->filters;
83-
}
84-
85-
public function addFilterEqual(string $field, mixed $value): self
86-
{
87-
$this->filters->addFilter(Filter::createEqual($field, $value));
88-
return $this;
89-
}
90-
91-
public function addFilterNotEqual(string $field, mixed $value): self
92-
{
93-
$this->filters->addFilter(Filter::createNotEqual($field, $value));
94-
return $this;
95-
}
96-
97-
public function addFilterGreaterThan(string $field, string $value): self
98-
{
99-
$this->filters->addFilter(Filter::createGreaterThan($field, $value));
100-
return $this;
101-
}
102-
103-
public function addFilterGreaterOrEqualThan(string $field, string $value): self
104-
{
105-
$this->filters->addFilter(Filter::createGreaterOrEqualThan($field, $value));
106-
return $this;
107-
}
108-
109-
public function addFilterLessThan(string $field, string $value): self
110-
{
111-
$this->filters->addFilter(Filter::createLessThan($field, $value));
112-
return $this;
113-
}
114-
115-
public function addFilterLessOrEqualThan(string $field, string $value): self
116-
{
117-
$this->filters->addFilter(Filter::createLessOrEqualThan($field, $value));
118-
return $this;
106+
return self::create($this->groups, $this->order, Page::create($limit, $this->pageOffset()));
119107
}
120108

121109
/**
122-
* @param string $field
123-
* @param array<scalar> $value
124-
* @return $this
125-
*/
126-
public function addFilterIn(string $field, array $value): self
127-
{
128-
$this->filters->addFilter(Filter::createIn($field, $value));
129-
return $this;
130-
}
131-
132-
/**
133-
* @param string $field
134-
* @param array<scalar> $value
135-
* @return $this
110+
* Returns the list of group filters.
111+
*
112+
* @return array<FilterGroup<Filter>>
136113
*/
137-
public function addFilterNotIn(string $field, array $value): self
138-
{
139-
$this->filters->addFilter(Filter::createNotIn($field, $value));
140-
return $this;
141-
}
142-
143-
public function addFilterLike(string $field, string $value): self
114+
public function groups(): array
144115
{
145-
$this->filters->addFilter(Filter::createLike($field, $value));
146-
return $this;
116+
return $this->groups;
147117
}
148118

149119
public function order(): Order
@@ -178,6 +148,10 @@ public function pageLimit(): int
178148

179149
public function __toString(): string
180150
{
181-
return sprintf('%s#%s#%s', $this->filters, $this->order, $this->page);
151+
$groups = join('||', map(fn(FilterGroup $group): string => $group->__toString(), $this->groups));
152+
$order = $this->order->__toString();
153+
$page = $this->page->__toString();
154+
155+
return sprintf('%s#%s#%s', $groups, $order, $page);
182156
}
183157
}

src/Filter.php

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,21 @@ public static function createLike(string $field, mixed $value): self
113113
return self::create($field, Operator::like(), $value);
114114
}
115115

116+
public static function createNotLike(string $field, mixed $value): self
117+
{
118+
return self::create($field, Operator::notLike(), $value);
119+
}
120+
121+
public static function createContains(string $field, mixed $value): self
122+
{
123+
return self::create($field, Operator::contains(), $value);
124+
}
125+
126+
public static function createNotContains(string $field, mixed $value): self
127+
{
128+
return self::create($field, Operator::notContains(), $value);
129+
}
130+
116131
/**
117132
* Retrieve the filter field value object.
118133
*

0 commit comments

Comments
 (0)