Skip to content

Commit 42e4c91

Browse files
authored
Merge pull request #24 from kettasoft/hotfix/invokable
Refactor Filter Engine: Integrate Payload, ClauseFactory & Dissector for cleaner filter execution
2 parents 0e38208 + 4fcf266 commit 42e4c91

6 files changed

Lines changed: 133 additions & 48 deletions

File tree

config/filterable.php

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -236,6 +236,31 @@
236236
|
237237
*/
238238
'normalize_keys' => false,
239+
240+
/*
241+
|--------------------------------------------------------------------------
242+
| Allowed SQL Operators
243+
|--------------------------------------------------------------------------
244+
|
245+
| List of supported SQL operators you want to allow when parsing
246+
| the expressions.
247+
|
248+
*/
249+
'allowed_operators' => [
250+
'eq' => '=',
251+
'neq' => '!=',
252+
'gt' => '>',
253+
'lt' => '<',
254+
'gte' => '>=',
255+
'lte' => '<=',
256+
'like' => 'like',
257+
'nlike' => 'not like',
258+
'in' => 'in',
259+
'nin' => 'not in',
260+
'null' => 'is null',
261+
'notnull' => 'is not null',
262+
'between' => 'between',
263+
],
239264
],
240265

241266
/*

src/Engines/Foundation/Clause.php

Lines changed: 22 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
use Illuminate\Contracts\Support\Arrayable;
99
use Kettasoft\Filterable\Engines\Foundation\Parsers\Dissector;
1010
use Kettasoft\Filterable\Engines\Foundation\Resolvers\RelationResolver;
11+
use Kettasoft\Filterable\Support\Payload;
1112

1213
class Clause implements Arrayable, Jsonable
1314
{
@@ -38,26 +39,25 @@ class Clause implements Arrayable, Jsonable
3839

3940
/**
4041
* Clause constructor.
41-
* @param \Kettasoft\Filterable\Foundation\Resources $resources
42-
* @param mixed $field
43-
* @param mixed $dissector
42+
*
43+
* @param Payload $payload
4444
*/
45-
public function __construct($field, $operator, $value)
45+
public function __construct(protected Payload $payload)
4646
{
47-
$this->field = $field;
48-
$this->operator = $operator;
49-
$this->value = $value;
47+
$this->field = $payload->field;
48+
$this->operator = $payload->operator;
49+
$this->value = $payload->value;
5050
}
5151

5252
/**
53-
* Create Dissector instance.
54-
* @param mixed $field
55-
* @param mixed $dissector
53+
* Create Clause instance.
54+
*
55+
* @param Payload $payload
5656
* @return Clause
5757
*/
58-
public static function make($field, $operator, $value)
58+
public static function make(Payload $payload)
5959
{
60-
return new self($field, $operator, $value);
60+
return new self($payload);
6161
}
6262

6363
/**
@@ -96,6 +96,16 @@ public function relation($bag)
9696
return $instance;
9797
}
9898

99+
/**
100+
* Get the Payload instance.
101+
*
102+
* @return Payload
103+
*/
104+
public function getPayload(): Payload
105+
{
106+
return $this->payload;
107+
}
108+
99109
public function apply(Builder $builder)
100110
{
101111
return $builder->where(

src/Engines/Foundation/ClauseFactory.php

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,8 +43,9 @@ public function make(Payload $payload): Clause
4343

4444
$resolvedField = $this->resolveField($payload);
4545
$resolvedOperator = $this->resolveOperator($payload);
46+
$payload->setField($resolvedField)->setOperator($resolvedOperator);
4647

47-
return (new Clause($resolvedField, $resolvedOperator, $payload->value))
48+
return (new Clause($payload))
4849
->setStatus($valid);
4950
}
5051

src/Engines/Invokeable.php

Lines changed: 17 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,11 @@
88
use Kettasoft\Filterable\Support\Payload;
99
use Illuminate\Support\Traits\ForwardsCalls;
1010
use Kettasoft\Filterable\Engines\Foundation\Engine;
11-
use Kettasoft\Filterable\Support\ConditionNormalizer;
1211
use Kettasoft\Filterable\Engines\Foundation\Attributes\AttributeContext;
1312
use Kettasoft\Filterable\Engines\Foundation\Attributes\AttributePipeline;
13+
use Kettasoft\Filterable\Engines\Foundation\Clause;
14+
use Kettasoft\Filterable\Engines\Foundation\ClauseFactory;
15+
use Kettasoft\Filterable\Engines\Foundation\Parsers\Dissector;
1416

1517
class Invokeable extends Engine
1618
{
@@ -37,40 +39,42 @@ public function execute(Builder $builder): Builder
3739
{
3840
$this->builder = $builder;
3941

42+
// Set allowed fields from $filters property automatically.
43+
$this->context->setAllowedFields($this->context->getFilterAttributes());
44+
4045
foreach ($this->context->getFilterAttributes() as $filter) {
41-
$value = $this->context->getRequest()->get($filter);
4246

43-
if (($this->context->hasIgnoredEmptyValues() || config('filterable.engines.invokable.ignore_empty_values')) && !$value) {
47+
$dissector = Dissector::parse($this->context->getRequest()->get($filter), $this->defaultOperator());
48+
49+
$payload = new Payload($filter, $dissector->operator, $this->sanitizeValue($filter, $dissector->value), $dissector->value);
50+
51+
$clause = (new ClauseFactory($this))->make($payload);
52+
53+
if (($this->context->hasIgnoredEmptyValues() || config('filterable.engines.invokable.ignore_empty_values')) && !$clause->value) {
4454
continue;
4555
}
4656

4757
$method = $this->getMethodName($filter);
4858

49-
$this->initializeFilters($filter, $method, $value);
59+
$this->initializeFilters($filter, $method, $clause->getPayload());
5060
}
5161

5262
return $this->builder;
5363
}
5464

5565
/**
5666
* Initialize the filter methods and resolve value.
67+
* @param string $key
5768
* @param string $method
58-
* @param mixed $value
69+
* @param Payload $payload
5970
* @return void
6071
*/
61-
protected function initializeFilters(string $key, string $method, mixed $value): void
72+
protected function initializeFilters(string $key, string $method, Payload $payload): void
6273
{
63-
$clause = ConditionNormalizer::normalize($value, '=');
64-
65-
$operator = $clause['operator'];
66-
$val = $clause['value'];
67-
6874
if (! method_exists($this->context, $method)) {
6975
return;
7076
}
7177

72-
$payload = new Payload($key, $operator, $this->sanitizeValue($key, $val), $val);
73-
7478
$attrContext = new AttributeContext(
7579
$this->builder,
7680
$payload,

src/Support/Payload.php

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -228,6 +228,50 @@ public function setValue(mixed $value): Payload
228228
return $this;
229229
}
230230

231+
/**
232+
* Set the payload field.
233+
*
234+
* @param string $field
235+
* @return Payload
236+
*/
237+
public function setField(string $field): Payload
238+
{
239+
$this->field = $field;
240+
return $this;
241+
}
242+
243+
/**
244+
* Set the payload operator.
245+
*
246+
* @param string $operator
247+
* @return Payload
248+
*/
249+
public function setOperator(string $operator): Payload
250+
{
251+
$this->operator = $operator;
252+
return $this;
253+
}
254+
255+
/**
256+
* Get the payload field.
257+
*
258+
* @return string
259+
*/
260+
public function getField(): string
261+
{
262+
return $this->field;
263+
}
264+
265+
/**
266+
* Get the payload operator.
267+
*
268+
* @return string
269+
*/
270+
public function getOperator(): string
271+
{
272+
return $this->operator;
273+
}
274+
231275
/**
232276
* Get the payload value as an array.
233277
*

tests/Unit/Engines/InvokableEngineTest.php

Lines changed: 23 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -34,36 +34,37 @@ public function setUp(): void
3434
* It can filter with basic class filter.
3535
* @test
3636
*/
37-
public function it_can_filter_by_request_query_key_name()
37+
public function it_can_test_method_mapping_filter()
3838
{
39-
4039
request()->merge([
41-
'status' => '',
40+
'status' => 'pending'
4241
]);
4342

4443
$filter = new class extends Filterable {
4544
protected $filters = ['status'];
46-
public $ignoreEmptyValues = true;
45+
protected $mentors = [
46+
'status' => 'filterBystatus'
47+
];
4748

48-
public function status(Payload $payload)
49+
public function filterBystatus(Payload $payload)
4950
{
50-
return $this->builder->where('status', $payload->value);
51+
return $this->builder->where('status', $payload);
5152
}
5253
};
5354

5455
$posts = Post::filter($filter)->count();
5556

56-
$this->assertEquals(12, $posts);
57+
$this->assertEquals(5, $posts);
5758
}
5859

5960
/**
6061
* It can filter with basic class filter.
6162
* @test
6263
*/
63-
public function it_can_test_method_mapping_filter()
64+
public function it_filter_with_ignored_null_or_empty_values()
6465
{
6566
request()->merge([
66-
'status' => 'pending'
67+
'status' => ''
6768
]);
6869

6970
$filter = new class extends Filterable {
@@ -74,7 +75,11 @@ public function it_can_test_method_mapping_filter()
7475

7576
public function filterBystatus(Payload $payload)
7677
{
77-
return $this->builder->where('status', $payload);
78+
if ($payload->value) {
79+
return $this->builder->where('status', $payload);
80+
}
81+
82+
return $this->builder->where($payload->field, 'pending');
7883
}
7984
};
8085

@@ -84,28 +89,24 @@ public function filterBystatus(Payload $payload)
8489
}
8590

8691
/**
87-
* It can filter with basic class filter.
92+
* It can filter with field and operator.
8893
* @test
8994
*/
90-
public function it_filter_with_ignored_null_or_empty_values()
95+
public function it_can_filter_with_field_and_operator()
9196
{
9297
request()->merge([
93-
'status' => ''
98+
'status' => [
99+
'operator' => 'eq',
100+
'value' => 'pending'
101+
]
94102
]);
95103

96104
$filter = new class extends Filterable {
97105
protected $filters = ['status'];
98-
protected $mentors = [
99-
'status' => 'filterBystatus'
100-
];
101106

102-
public function filterBystatus(Payload $payload)
107+
public function status(Payload $payload)
103108
{
104-
if ($payload->value) {
105-
return $this->builder->where('status', $payload);
106-
}
107-
108-
return $this->builder->where($payload->field, 'pending');
109+
return $this->builder->where('status', $payload->operator, $payload->value);
109110
}
110111
};
111112

0 commit comments

Comments
 (0)