From bc48921a51031e5446d3326cad3b4f77fea43c9e Mon Sep 17 00:00:00 2001 From: kettasoft Date: Fri, 27 Feb 2026 06:32:59 +0200 Subject: [PATCH 1/4] refactor(Payload): rename 'beforeSanitize' to 'rawValue' for clarity and consistency --- docs/api/payload.md | 26 +++++++++++++------------- docs/engines/invokable.md | 5 ++--- src/Support/Payload.php | 18 +++++++++--------- tests/Unit/Support/PayloadTest.php | 4 ++-- 4 files changed, 26 insertions(+), 27 deletions(-) diff --git a/docs/api/payload.md b/docs/api/payload.md index a28adab..8b19ff6 100644 --- a/docs/api/payload.md +++ b/docs/api/payload.md @@ -25,12 +25,12 @@ class PostFilter extends Filterable ## Properties -| Property | Type | Description | -| ----------------- | -------- | -------------------------------------- | -| `$field` | `string` | The field passed from the request. | -| `$operator` | `string` | The operator passed from the request. | -| `$value` | `mixed` | The raw value passed from the request. | -| `$beforeSanitize` | `mixed` | The original value before sanitizing. | +| Property | Type | Description | +| ----------- | -------- | -------------------------------------- | +| `$field` | `string` | The field passed from the request. | +| `$operator` | `string` | The operator passed from the request. | +| `$value` | `mixed` | The raw value passed from the request. | +| `$rawValue` | `mixed` | The original value before sanitizing. | --- @@ -247,9 +247,9 @@ $payload->asSlug("_"); // "my_sample_value" Wrap the value with `%` for SQL `LIKE` queries. -- `both` → `%value%` -- `left` → `%value` -- `right` → `value%` +- `both` → `%value%` +- `left` → `%value` +- `right` → `value%` ```php $this->builder->where('title', 'like', $payload->asLike()); @@ -293,7 +293,7 @@ $payload->split(); // ['one', 'two', 'three'] Get the original unmodified value. ```php -$payload->raw(); // equivalent to $payload->beforeSanitize +$payload->raw(); // equivalent to $payload->rawValue ``` --- @@ -472,6 +472,6 @@ protected function meta(Payload $payload) ## Summary -- `Payload` standardizes how filter values are processed. -- It provides helper methods (`asLike`, `asBoolean`, `isJson`, etc.). -- This reduces repetitive code inside filter classes. +- `Payload` standardizes how filter values are processed. +- It provides helper methods (`asLike`, `asBoolean`, `isJson`, etc.). +- This reduces repetitive code inside filter classes. diff --git a/docs/engines/invokable.md b/docs/engines/invokable.md index 3fe08ba..370375e 100644 --- a/docs/engines/invokable.md +++ b/docs/engines/invokable.md @@ -92,7 +92,7 @@ You can access not only the raw value but also the parsed operator (e.g. =, like - `field` – the column name - `operator` – the parsed operator (from your ruleset or SQL‐expression config) - `value` – the sanitized filter value -- `beforeSanitize` – the original raw input +- `rawValue` – the original raw input ## Mapping Request Keys @@ -193,8 +193,7 @@ This ensures that the filter system remains dynamic and flexible whether or not 2. $request->only([...]) extracts relevent filters. 3. Filter class loops over keys. 4. For each key: - - - if a method named **`$key`** exists and registered in **`$filters`** property, is is executed with the value. + - if a method named **`$key`** exists and registered in **`$filters`** property, is is executed with the value. 5. Modified Eloquent query is returned. diff --git a/src/Support/Payload.php b/src/Support/Payload.php index bf2ee3f..1cc61c0 100644 --- a/src/Support/Payload.php +++ b/src/Support/Payload.php @@ -38,21 +38,21 @@ class Payload implements \Stringable, Arrayable, Jsonable * Value before sanitizing. * @var mixed */ - public mixed $beforeSanitize; + public mixed $rawValue; /** * Create new Payload instance. * @param string $field * @param string $operator * @param mixed $value - * @param mixed $beforeSanitize + * @param mixed $rawValue */ - public function __construct(string $field, string $operator, mixed $value, mixed $beforeSanitize) + public function __construct(string $field, string $operator, mixed $value, mixed $rawValue) { $this->field = $field; $this->operator = $operator; $this->value = $value; - $this->beforeSanitize = $beforeSanitize; + $this->rawValue = $rawValue; } /** @@ -60,12 +60,12 @@ public function __construct(string $field, string $operator, mixed $value, mixed * @param mixed $field * @param mixed $operator * @param mixed $value - * @param mixed $beforeSanitize + * @param mixed $rawValue * @return Payload */ - public static function create($field, $operator, $value, $beforeSanitize): static + public static function create($field, $operator, $value, $rawValue): static { - return new static($field, $operator, $value, $beforeSanitize); + return new static($field, $operator, $value, $rawValue); } /** @@ -75,7 +75,7 @@ public static function create($field, $operator, $value, $beforeSanitize): stati */ public function raw(): mixed { - return $this->beforeSanitize; + return $this->rawValue; } /** @@ -572,7 +572,7 @@ public function toArray() 'field' => $this->field, 'operator' => $this->operator, 'value' => $this->value, - 'beforeSanitize' => $this->beforeSanitize, + 'rawValue' => $this->rawValue, ]; } diff --git a/tests/Unit/Support/PayloadTest.php b/tests/Unit/Support/PayloadTest.php index 8d373c6..567e6c6 100644 --- a/tests/Unit/Support/PayloadTest.php +++ b/tests/Unit/Support/PayloadTest.php @@ -17,7 +17,7 @@ public function setUp(): void field: 'name', operator: '=', value: 'Filterable', - beforeSanitize: ' Filterable ' + rawValue: ' Filterable ' ); } @@ -27,7 +27,7 @@ public function test_it_can_be_instantiated() $this->assertEquals('name', $this->payload->field); $this->assertEquals('=', $this->payload->operator); $this->assertEquals('Filterable', $this->payload->value); - $this->assertEquals(' Filterable ', $this->payload->beforeSanitize); + $this->assertEquals(' Filterable ', $this->payload->rawValue); } public function test_static_create_method() From 5362fa46f2ae96bdbdee152504bfa84dd3bca467 Mon Sep 17 00:00:00 2001 From: kettasoft Date: Fri, 27 Feb 2026 06:38:53 +0200 Subject: [PATCH 2/4] fix(Filterable): update return statement to use 'finally' method for consistency --- src/Filterable.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Filterable.php b/src/Filterable.php index 4413c2e..b90903d 100644 --- a/src/Filterable.php +++ b/src/Filterable.php @@ -360,7 +360,7 @@ public function apply(Builder|null $builder = null): Invoker|Builder ]); if ($this instanceof ShouldReturnQueryBuilder || $this->shouldReturnQueryBuilder) { - return $builder; + return $this->finally($builder); } $invoker = new Invoker($this->finally($builder)); From d379765fb7cd5b395bc327b35d68da41dd65b05d Mon Sep 17 00:00:00 2001 From: kettasoft Date: Sun, 1 Mar 2026 01:12:48 +0200 Subject: [PATCH 3/4] feat(Execution): implement Outcome interface with methods for handling resolved and rejected outcomes --- src/Engines/Foundation/Contracts/Outcome.php | 48 ++++++++++++ src/Engines/Foundation/Execution.php | 82 ++++++++++++++++++++ 2 files changed, 130 insertions(+) create mode 100644 src/Engines/Foundation/Contracts/Outcome.php create mode 100644 src/Engines/Foundation/Execution.php diff --git a/src/Engines/Foundation/Contracts/Outcome.php b/src/Engines/Foundation/Contracts/Outcome.php new file mode 100644 index 0000000..20eb68c --- /dev/null +++ b/src/Engines/Foundation/Contracts/Outcome.php @@ -0,0 +1,48 @@ +isRejected()) { + $closure(); + } + + return $this; + } + + /** + * Register a callback to be executed when the outcome is rejected. + * @param \Closure $closure The callback to execute, which receives the reason for rejection as an argument. + * @return self + */ + public function catch(Closure $closure): Outcome + { + if ($this->isRejected()) { + $closure($this->error); + } + + return $this; + } + + /** + * Register a callback to be executed when the outcome is settled (either resolved or rejected). + * @param \Closure $closure The callback to execute, which receives no arguments. + * @return self + */ + public function finally(Closure $closure): Outcome + { + $closure(); + return $this; + } + + /** + * Mark the outcome as failed with the given error. + * @param \Throwable $error The error that caused the outcome to be rejected. + * @return self + */ + public function fail(\Throwable $error): Outcome + { + $this->error = $error; + return $this; + } + + /** + * Check if the outcome is resolved. + * @return bool True if the outcome is resolved, false otherwise. + */ + public function isResolved(): bool + { + return $this->error === null; + } + + /** + * Check if the outcome is rejected. + * @return bool True if the outcome is rejected, false otherwise. + */ + public function isRejected(): bool + { + return !$this->isResolved(); + } +} From 3409739a217989c8e26bbbe795aaf0bc543d2242 Mon Sep 17 00:00:00 2001 From: kettasoft Date: Sun, 1 Mar 2026 01:14:18 +0200 Subject: [PATCH 4/4] feat(AttributePipeline): update process method to return Outcome and handle exceptions --- .../Attributes/AttributePipeline.php | 20 ++++++++++++++----- src/Engines/Invokable.php | 9 +++++++-- 2 files changed, 22 insertions(+), 7 deletions(-) diff --git a/src/Engines/Foundation/Attributes/AttributePipeline.php b/src/Engines/Foundation/Attributes/AttributePipeline.php index 4f18aab..27e6bf5 100644 --- a/src/Engines/Foundation/Attributes/AttributePipeline.php +++ b/src/Engines/Foundation/Attributes/AttributePipeline.php @@ -2,6 +2,8 @@ namespace Kettasoft\Filterable\Engines\Foundation\Attributes; +use Kettasoft\Filterable\Engines\Foundation\Contracts\Outcome; +use Kettasoft\Filterable\Engines\Foundation\Execution; use Kettasoft\Filterable\Filterable; class AttributePipeline @@ -18,14 +20,22 @@ public function __construct(protected AttributeRegistry $registry, protected Att * Process the attributes for the given target and method. * * @param object|string $target - * @return void + * @return \Kettasoft\Filterable\Engines\Foundation\Contracts\Outcome */ - public function process(Filterable $target, string $method): void + public function process(Filterable $target, string $method): Outcome { - $handlers = $this->registry->getHandlersForMethod($target, $method); + $execution = new Execution(); - foreach ($handlers as [$handler, $attributeInstance]) { - (new $handler)->handle($this->context, $attributeInstance); + try { + $handlers = $this->registry->getHandlersForMethod($target, $method); + + foreach ($handlers as [$handler, $attributeInstance]) { + (new $handler)->handle($this->context, $attributeInstance); + } + } catch (\Exception $e) { + $execution->fail($e); } + + return $execution; } } diff --git a/src/Engines/Invokable.php b/src/Engines/Invokable.php index 9fc3351..fdb8562 100644 --- a/src/Engines/Invokable.php +++ b/src/Engines/Invokable.php @@ -86,9 +86,14 @@ protected function applyFilterMethod(string $key, string $method, Payload $paylo ); $pipeline = new AttributePipeline(new AttributeRegistry(), $attrContext); - $pipeline->process($this->context, $method); + $process = $pipeline->process($this->context, $method); - $this->forwardCallTo($this->context, $method, [$payload]); + $process->then(function () use ($method, $payload) { + $this->forwardCallTo($this->context, $method, [$payload]); + }) + ->catch(function ($e) { + throw $e; + }); } /**