Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 13 additions & 13 deletions docs/api/payload.md
Original file line number Diff line number Diff line change
Expand Up @@ -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. |

---

Expand Down Expand Up @@ -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());
Expand Down Expand Up @@ -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
```

---
Expand Down Expand Up @@ -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.
5 changes: 2 additions & 3 deletions docs/engines/invokable.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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.

Expand Down
20 changes: 15 additions & 5 deletions src/Engines/Foundation/Attributes/AttributePipeline.php
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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;
}
}
48 changes: 48 additions & 0 deletions src/Engines/Foundation/Contracts/Outcome.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
<?php

namespace Kettasoft\Filterable\Engines\Foundation\Contracts;

use Closure;

interface Outcome
{
/**
* Register a callback to be executed when the outcome is resolved.
* @param \Closure $closure The callback to execute, which receives the outcome's value as an argument.
* @return self
*/
public function then(Closure $closure): self;

/**
* 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): self;

/**
* 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): self;

/**
* 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): self;

/**
* Check if the outcome is resolved.
* @return bool True if the outcome is resolved, false otherwise.
*/
public function isResolved(): bool;

/**
* Check if the outcome is rejected.
* @return bool True if the outcome is rejected, false otherwise.
*/
public function isRejected(): bool;
}
82 changes: 82 additions & 0 deletions src/Engines/Foundation/Execution.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
<?php

namespace Kettasoft\Filterable\Engines\Foundation;

use Closure;
use Kettasoft\Filterable\Engines\Foundation\Contracts\Outcome;

class Execution implements Contracts\Outcome
{
/**
* The error that caused the outcome to be rejected.
* @var \Throwable|null
*/
private \Throwable|null $error = null;

/**
* Create a new Execution instance.
* @param \Throwable|null $error The error that caused the outcome to be rejected, or null if the outcome is resolved.
*/
public function then(Closure $closure): Outcome
{
if (! $this->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();
}
}
9 changes: 7 additions & 2 deletions src/Engines/Invokable.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
});
}

/**
Expand Down
2 changes: 1 addition & 1 deletion src/Filterable.php
Original file line number Diff line number Diff line change
Expand Up @@ -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));
Expand Down
18 changes: 9 additions & 9 deletions src/Support/Payload.php
Original file line number Diff line number Diff line change
Expand Up @@ -38,34 +38,34 @@ 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;
}

/**
* Shortcut to create Payload instance.
* @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);
}

/**
Expand All @@ -75,7 +75,7 @@ public static function create($field, $operator, $value, $beforeSanitize): stati
*/
public function raw(): mixed
{
return $this->beforeSanitize;
return $this->rawValue;
}

/**
Expand Down Expand Up @@ -572,7 +572,7 @@ public function toArray()
'field' => $this->field,
'operator' => $this->operator,
'value' => $this->value,
'beforeSanitize' => $this->beforeSanitize,
'rawValue' => $this->rawValue,
];
}

Expand Down
4 changes: 2 additions & 2 deletions tests/Unit/Support/PayloadTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ public function setUp(): void
field: 'name',
operator: '=',
value: 'Filterable',
beforeSanitize: ' Filterable '
rawValue: ' Filterable '
);
}

Expand All @@ -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()
Expand Down