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
19 changes: 10 additions & 9 deletions docs/engines/invokable/annotations/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -199,16 +199,17 @@ protected function search(Payload $payload)

The `AttributeContext` object passed to each annotation's `handle()` method contains:

| Property | Type | Description |
| --------- | ------- | ------------------------------------------------- |
| `query` | `mixed` | The Eloquent query builder instance |
| `payload` | `mixed` | The `Payload` object with the filter value |
| `state` | `array` | Shared state array (`method`, `key`, custom data) |
| Property | Type | Description |
| --------- | --------- | ---------------------------------------------- |
| `engine` | `Engine` | The active engine and its `Filterable` context |
| `payload` | `Payload` | The current filter payload |

You can read and write to `state` for inter-attribute communication:
Use the engine to access the current filter or builder. Transform annotations
can update the payload for attributes that run later in the pipeline:

```php
$context->set('my_flag', true);
$context->get('my_flag'); // true
$context->has('my_flag'); // true
$filter = $context->engine->getContext();
$builder = $filter->getBuilder();

$context->payload->setValue(trim($context->payload->value));
```
21 changes: 11 additions & 10 deletions docs/engines/invokable/custom-annotations.md
Original file line number Diff line number Diff line change
Expand Up @@ -57,18 +57,19 @@ public static function stage(): int

`handle()` receives an `AttributeContext` with:

| Property | Type | Description |
| --------- | --------- | --------------------------------------------------------- |
| `query` | `mixed` | The Eloquent query builder |
| `payload` | `Payload` | The filter payload (field, operator, value, rawValue) |
| `state` | `array` | Shared state between annotations in the same pipeline run |
| Property | Type | Description |
| --------- | --------- | ----------------------------------------------------- |
| `engine` | `Engine` | The active engine and its `Filterable` context |
| `payload` | `Payload` | The filter payload (field, operator, value, raw value) |

You can read and write to `state` for inter-annotation communication:
Use the engine to reach the active filter or query builder, and mutate the
payload when a downstream annotation should receive a transformed value:

```php
$context->set('my_flag', true);
$context->get('my_flag'); // true
$context->has('my_flag'); // true
$filter = $context->engine->getContext();
$builder = $filter->getBuilder();

$context->payload->setValue(trim($context->payload->value));
```

---
Expand Down Expand Up @@ -230,4 +231,4 @@ protected function salary(Payload $payload)
- Use `Stage::VALIDATE` for constraints that should run after the value is already cleaned.
- Use `SkipExecution` for optional filters, `StrictnessException` for required ones.
- Add `Attribute::IS_REPEATABLE` to `#[Attribute(...)]` if the annotation should be stackable on the same method.
- Store computed values in `$context->state` if a downstream annotation (in the same pipeline run) needs them.
- Write transformed values to `$context->payload` when a downstream annotation needs them.
2 changes: 1 addition & 1 deletion src/Engines/Foundation/Attributes/Annotations/Required.php
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ public function handle(\Kettasoft\Filterable\Engines\Foundation\Attributes\Attri
$payload = $context->payload;

if ($payload && ($payload->isEmpty() || $payload->isNull())) {
throw new StrictnessException(sprintf($this->message, $context->state['key']));
throw new StrictnessException(sprintf($this->message, $payload->field));
}
}
}
8 changes: 2 additions & 6 deletions src/Engines/Foundation/Attributes/Annotations/Scope.php
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,8 @@ public static function stage(): int
*/
public function handle(\Kettasoft\Filterable\Engines\Foundation\Attributes\AttributeContext $context): void
{
/** @var \Illuminate\Contracts\Eloquent\Builder $query */
$query = $context->query;
/** @var \Illuminate\Database\Eloquent\Builder $query */
$query = $context->engine->getContext()->getBuilder();

/** @var \Kettasoft\Filterable\Support\Payload $payload */
$payload = $context->payload;
Expand All @@ -47,9 +47,5 @@ public function handle(\Kettasoft\Filterable\Engines\Foundation\Attributes\Attri
}

$query->{$scope}($payload->value);

// Set a flag in context to indicate the scope was applied,
// allowing the engine to optionally skip the filter method execution.
$context->set('scope_applied', true);
}
}
48 changes: 7 additions & 41 deletions src/Engines/Foundation/Attributes/AttributeContext.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@

namespace Kettasoft\Filterable\Engines\Foundation\Attributes;

use Kettasoft\Filterable\Engines\Foundation\Engine;
use Kettasoft\Filterable\Support\Payload;

/**
* The context in which attributes are processed.
*
Expand All @@ -12,48 +15,11 @@ class AttributeContext
/**
* Create a new attribute context instance.
*
* @param mixed $query
* @param mixed $payload
* @param array $state
* @param Engine $engine
* @param Payload $payload
*/
public function __construct(
public mixed $query = null,
public mixed $payload = null,
public array $state = []
public Engine $engine,
public Payload $payload
) {}

/**
* Set a value in the context state.
*
* @param string $key
* @param mixed $value
* @return void
*/
public function set(string $key, mixed $value): void
{
$this->state[$key] = $value;
}

/**
* Get a value from the context state.
*
* @param string $key
* @param mixed $default
* @return mixed
*/
public function get(string $key, mixed $default = null): mixed
{
return $this->state[$key] ?? $default;
}

/**
* Check if a key exists in the context state.
*
* @param string $key
* @return bool
*/
public function has(string $key): bool
{
return array_key_exists($key, $this->state);
}
}
1 change: 0 additions & 1 deletion src/Engines/Foundation/Attributes/AttributePipeline.php
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@ class AttributePipeline
/**
* Create a new attribute pipeline instance.
*
* @param AttributeRegistry $registry
* @param AttributeContext $context
*/
public function __construct(protected AttributeContext $context)
Expand Down

This file was deleted.

28 changes: 0 additions & 28 deletions src/Engines/Foundation/Attributes/Handlers/DefaultValueHandler.php

This file was deleted.

29 changes: 0 additions & 29 deletions src/Engines/Foundation/Attributes/Handlers/RequiredHandler.php

This file was deleted.

6 changes: 1 addition & 5 deletions src/Engines/Invokable.php
Original file line number Diff line number Diff line change
Expand Up @@ -79,11 +79,7 @@ protected function applyFilterMethod(string $key, string $method, Payload $paylo
return;
}

$attrContext = new AttributeContext(
$this->builder,
$payload,
state: ['method' => $method, 'key' => $key]
);
$attrContext = new AttributeContext($this, $payload);

$pipeline = new AttributePipeline($attrContext);
$process = $pipeline->process($this->context, $method);
Expand Down
2 changes: 2 additions & 0 deletions tests/Feature/Engines/Attributes/CastAttributeTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -218,6 +218,7 @@ public function test_cast_attribute_handle_method_directly()
{
$payload = Payload::create('views', '=', '42', '42');
$context = new \Kettasoft\Filterable\Engines\Foundation\Attributes\AttributeContext(
engine: $this->createMock(\Kettasoft\Filterable\Engines\Foundation\Engine::class),
payload: $payload
);

Expand All @@ -234,6 +235,7 @@ public function test_cast_attribute_handle_throws_for_invalid_type()

$payload = Payload::create('status', '=', 'active', 'active');
$context = new \Kettasoft\Filterable\Engines\Foundation\Attributes\AttributeContext(
engine: $this->createMock(\Kettasoft\Filterable\Engines\Foundation\Engine::class),
payload: $payload
);

Expand Down
Loading