Skip to content

Commit fedc56f

Browse files
authored
Merge pull request #39 from kettasoft/v2
Fix attribute execution control and finalize lifecycle
2 parents e80edfe + 3409739 commit fedc56f

9 files changed

Lines changed: 179 additions & 35 deletions

File tree

docs/api/payload.md

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -25,12 +25,12 @@ class PostFilter extends Filterable
2525

2626
## Properties
2727

28-
| Property | Type | Description |
29-
| ----------------- | -------- | -------------------------------------- |
30-
| `$field` | `string` | The field passed from the request. |
31-
| `$operator` | `string` | The operator passed from the request. |
32-
| `$value` | `mixed` | The raw value passed from the request. |
33-
| `$beforeSanitize` | `mixed` | The original value before sanitizing. |
28+
| Property | Type | Description |
29+
| ----------- | -------- | -------------------------------------- |
30+
| `$field` | `string` | The field passed from the request. |
31+
| `$operator` | `string` | The operator passed from the request. |
32+
| `$value` | `mixed` | The raw value passed from the request. |
33+
| `$rawValue` | `mixed` | The original value before sanitizing. |
3434

3535
---
3636

@@ -247,9 +247,9 @@ $payload->asSlug("_"); // "my_sample_value"
247247

248248
Wrap the value with `%` for SQL `LIKE` queries.
249249

250-
- `both``%value%`
251-
- `left``%value`
252-
- `right``value%`
250+
- `both``%value%`
251+
- `left``%value`
252+
- `right``value%`
253253

254254
```php
255255
$this->builder->where('title', 'like', $payload->asLike());
@@ -293,7 +293,7 @@ $payload->split(); // ['one', 'two', 'three']
293293
Get the original unmodified value.
294294

295295
```php
296-
$payload->raw(); // equivalent to $payload->beforeSanitize
296+
$payload->raw(); // equivalent to $payload->rawValue
297297
```
298298

299299
---
@@ -472,6 +472,6 @@ protected function meta(Payload $payload)
472472

473473
## Summary
474474

475-
- `Payload` standardizes how filter values are processed.
476-
- It provides helper methods (`asLike`, `asBoolean`, `isJson`, etc.).
477-
- This reduces repetitive code inside filter classes.
475+
- `Payload` standardizes how filter values are processed.
476+
- It provides helper methods (`asLike`, `asBoolean`, `isJson`, etc.).
477+
- This reduces repetitive code inside filter classes.

docs/engines/invokable.md

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,7 @@ You can access not only the raw value but also the parsed operator (e.g. =, like
9292
- `field` – the column name
9393
- `operator` – the parsed operator (from your ruleset or SQL‐expression config)
9494
- `value` – the sanitized filter value
95-
- `beforeSanitize` – the original raw input
95+
- `rawValue` – the original raw input
9696

9797
## Mapping Request Keys
9898

@@ -193,8 +193,7 @@ This ensures that the filter system remains dynamic and flexible whether or not
193193
2. $request->only([...]) extracts relevent filters.
194194
3. Filter class loops over keys.
195195
4. For each key:
196-
197-
- if a method named **`$key`** exists and registered in **`$filters`** property, is is executed with the value.
196+
- if a method named **`$key`** exists and registered in **`$filters`** property, is is executed with the value.
198197

199198
5. Modified Eloquent query is returned.
200199

src/Engines/Foundation/Attributes/AttributePipeline.php

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22

33
namespace Kettasoft\Filterable\Engines\Foundation\Attributes;
44

5+
use Kettasoft\Filterable\Engines\Foundation\Contracts\Outcome;
6+
use Kettasoft\Filterable\Engines\Foundation\Execution;
57
use Kettasoft\Filterable\Filterable;
68

79
class AttributePipeline
@@ -18,14 +20,22 @@ public function __construct(protected AttributeRegistry $registry, protected Att
1820
* Process the attributes for the given target and method.
1921
*
2022
* @param object|string $target
21-
* @return void
23+
* @return \Kettasoft\Filterable\Engines\Foundation\Contracts\Outcome
2224
*/
23-
public function process(Filterable $target, string $method): void
25+
public function process(Filterable $target, string $method): Outcome
2426
{
25-
$handlers = $this->registry->getHandlersForMethod($target, $method);
27+
$execution = new Execution();
2628

27-
foreach ($handlers as [$handler, $attributeInstance]) {
28-
(new $handler)->handle($this->context, $attributeInstance);
29+
try {
30+
$handlers = $this->registry->getHandlersForMethod($target, $method);
31+
32+
foreach ($handlers as [$handler, $attributeInstance]) {
33+
(new $handler)->handle($this->context, $attributeInstance);
34+
}
35+
} catch (\Exception $e) {
36+
$execution->fail($e);
2937
}
38+
39+
return $execution;
3040
}
3141
}
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
<?php
2+
3+
namespace Kettasoft\Filterable\Engines\Foundation\Contracts;
4+
5+
use Closure;
6+
7+
interface Outcome
8+
{
9+
/**
10+
* Register a callback to be executed when the outcome is resolved.
11+
* @param \Closure $closure The callback to execute, which receives the outcome's value as an argument.
12+
* @return self
13+
*/
14+
public function then(Closure $closure): self;
15+
16+
/**
17+
* Register a callback to be executed when the outcome is rejected.
18+
* @param \Closure $closure The callback to execute, which receives the reason for rejection as an argument.
19+
* @return self
20+
*/
21+
public function catch(Closure $closure): self;
22+
23+
/**
24+
* Register a callback to be executed when the outcome is settled (either resolved or rejected).
25+
* @param \Closure $closure The callback to execute, which receives no arguments.
26+
* @return self
27+
*/
28+
public function finally(Closure $closure): self;
29+
30+
/**
31+
* Mark the outcome as failed with the given error.
32+
* @param \Throwable $error The error that caused the outcome to be rejected.
33+
* @return self
34+
*/
35+
public function fail(\Throwable $error): self;
36+
37+
/**
38+
* Check if the outcome is resolved.
39+
* @return bool True if the outcome is resolved, false otherwise.
40+
*/
41+
public function isResolved(): bool;
42+
43+
/**
44+
* Check if the outcome is rejected.
45+
* @return bool True if the outcome is rejected, false otherwise.
46+
*/
47+
public function isRejected(): bool;
48+
}
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
<?php
2+
3+
namespace Kettasoft\Filterable\Engines\Foundation;
4+
5+
use Closure;
6+
use Kettasoft\Filterable\Engines\Foundation\Contracts\Outcome;
7+
8+
class Execution implements Contracts\Outcome
9+
{
10+
/**
11+
* The error that caused the outcome to be rejected.
12+
* @var \Throwable|null
13+
*/
14+
private \Throwable|null $error = null;
15+
16+
/**
17+
* Create a new Execution instance.
18+
* @param \Throwable|null $error The error that caused the outcome to be rejected, or null if the outcome is resolved.
19+
*/
20+
public function then(Closure $closure): Outcome
21+
{
22+
if (! $this->isRejected()) {
23+
$closure();
24+
}
25+
26+
return $this;
27+
}
28+
29+
/**
30+
* Register a callback to be executed when the outcome is rejected.
31+
* @param \Closure $closure The callback to execute, which receives the reason for rejection as an argument.
32+
* @return self
33+
*/
34+
public function catch(Closure $closure): Outcome
35+
{
36+
if ($this->isRejected()) {
37+
$closure($this->error);
38+
}
39+
40+
return $this;
41+
}
42+
43+
/**
44+
* Register a callback to be executed when the outcome is settled (either resolved or rejected).
45+
* @param \Closure $closure The callback to execute, which receives no arguments.
46+
* @return self
47+
*/
48+
public function finally(Closure $closure): Outcome
49+
{
50+
$closure();
51+
return $this;
52+
}
53+
54+
/**
55+
* Mark the outcome as failed with the given error.
56+
* @param \Throwable $error The error that caused the outcome to be rejected.
57+
* @return self
58+
*/
59+
public function fail(\Throwable $error): Outcome
60+
{
61+
$this->error = $error;
62+
return $this;
63+
}
64+
65+
/**
66+
* Check if the outcome is resolved.
67+
* @return bool True if the outcome is resolved, false otherwise.
68+
*/
69+
public function isResolved(): bool
70+
{
71+
return $this->error === null;
72+
}
73+
74+
/**
75+
* Check if the outcome is rejected.
76+
* @return bool True if the outcome is rejected, false otherwise.
77+
*/
78+
public function isRejected(): bool
79+
{
80+
return !$this->isResolved();
81+
}
82+
}

src/Engines/Invokable.php

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -86,9 +86,14 @@ protected function applyFilterMethod(string $key, string $method, Payload $paylo
8686
);
8787

8888
$pipeline = new AttributePipeline(new AttributeRegistry(), $attrContext);
89-
$pipeline->process($this->context, $method);
89+
$process = $pipeline->process($this->context, $method);
9090

91-
$this->forwardCallTo($this->context, $method, [$payload]);
91+
$process->then(function () use ($method, $payload) {
92+
$this->forwardCallTo($this->context, $method, [$payload]);
93+
})
94+
->catch(function ($e) {
95+
throw $e;
96+
});
9297
}
9398

9499
/**

src/Filterable.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -360,7 +360,7 @@ public function apply(Builder|null $builder = null): Invoker|Builder
360360
]);
361361

362362
if ($this instanceof ShouldReturnQueryBuilder || $this->shouldReturnQueryBuilder) {
363-
return $builder;
363+
return $this->finally($builder);
364364
}
365365

366366
$invoker = new Invoker($this->finally($builder));

src/Support/Payload.php

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -38,34 +38,34 @@ class Payload implements \Stringable, Arrayable, Jsonable
3838
* Value before sanitizing.
3939
* @var mixed
4040
*/
41-
public mixed $beforeSanitize;
41+
public mixed $rawValue;
4242

4343
/**
4444
* Create new Payload instance.
4545
* @param string $field
4646
* @param string $operator
4747
* @param mixed $value
48-
* @param mixed $beforeSanitize
48+
* @param mixed $rawValue
4949
*/
50-
public function __construct(string $field, string $operator, mixed $value, mixed $beforeSanitize)
50+
public function __construct(string $field, string $operator, mixed $value, mixed $rawValue)
5151
{
5252
$this->field = $field;
5353
$this->operator = $operator;
5454
$this->value = $value;
55-
$this->beforeSanitize = $beforeSanitize;
55+
$this->rawValue = $rawValue;
5656
}
5757

5858
/**
5959
* Shortcut to create Payload instance.
6060
* @param mixed $field
6161
* @param mixed $operator
6262
* @param mixed $value
63-
* @param mixed $beforeSanitize
63+
* @param mixed $rawValue
6464
* @return Payload
6565
*/
66-
public static function create($field, $operator, $value, $beforeSanitize): static
66+
public static function create($field, $operator, $value, $rawValue): static
6767
{
68-
return new static($field, $operator, $value, $beforeSanitize);
68+
return new static($field, $operator, $value, $rawValue);
6969
}
7070

7171
/**
@@ -75,7 +75,7 @@ public static function create($field, $operator, $value, $beforeSanitize): stati
7575
*/
7676
public function raw(): mixed
7777
{
78-
return $this->beforeSanitize;
78+
return $this->rawValue;
7979
}
8080

8181
/**
@@ -572,7 +572,7 @@ public function toArray()
572572
'field' => $this->field,
573573
'operator' => $this->operator,
574574
'value' => $this->value,
575-
'beforeSanitize' => $this->beforeSanitize,
575+
'rawValue' => $this->rawValue,
576576
];
577577
}
578578

tests/Unit/Support/PayloadTest.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ public function setUp(): void
1717
field: 'name',
1818
operator: '=',
1919
value: 'Filterable',
20-
beforeSanitize: ' Filterable '
20+
rawValue: ' Filterable '
2121
);
2222
}
2323

@@ -27,7 +27,7 @@ public function test_it_can_be_instantiated()
2727
$this->assertEquals('name', $this->payload->field);
2828
$this->assertEquals('=', $this->payload->operator);
2929
$this->assertEquals('Filterable', $this->payload->value);
30-
$this->assertEquals(' Filterable ', $this->payload->beforeSanitize);
30+
$this->assertEquals(' Filterable ', $this->payload->rawValue);
3131
}
3232

3333
public function test_static_create_method()

0 commit comments

Comments
 (0)