Skip to content

Commit 837242e

Browse files
author
Abdalrhman Emad Saad
committed
Merge branch 'develop' of github.com:kettasoft/filterable into develop
2 parents 1aef58e + da03750 commit 837242e

19 files changed

+756
-124
lines changed

docs/.vuepress/config.js

Lines changed: 115 additions & 105 deletions
Original file line numberDiff line numberDiff line change
@@ -3,118 +3,128 @@ import { defineUserConfig } from "vuepress/cli";
33
import { viteBundler } from "@vuepress/bundler-vite";
44

55
export default defineUserConfig({
6-
lang: "en-US",
6+
lang: "en-US",
77

8-
title: "Filterable",
9-
description: "Kettasoft Filterable - Powerful Eloquent Filtering Package",
8+
title: "Filterable",
9+
description: "Kettasoft Filterable - Powerful Eloquent Filtering Package",
1010

11-
plugins: [
12-
{
13-
name: "@vuepress/plugin-search",
14-
},
15-
],
11+
plugins: [
12+
{
13+
name: "@vuepress/plugin-search",
14+
},
15+
],
1616

17-
theme: defaultTheme({
18-
repo: "https://github.com/kettasoft/filterable",
19-
// logo: "/docs/logo.png",
17+
theme: defaultTheme({
18+
repo: "https://github.com/kettasoft/filterable",
19+
// logo: "/docs/logo.png",
2020

21-
colorModeSwitch: true,
22-
sidebarDepth: 3,
23-
logoAlt: null,
24-
logo: null,
25-
selectLanguageText: "ar",
26-
selectLanguageName: "ar",
27-
lastUpdated: true,
28-
contributors: true,
29-
// contributorsText: "dasdasd",
21+
colorModeSwitch: true,
22+
sidebarDepth: 3,
23+
logoAlt: null,
24+
logo: null,
25+
selectLanguageText: "ar",
26+
selectLanguageName: "ar",
27+
lastUpdated: true,
28+
contributors: true,
29+
// contributorsText: "dasdasd",
3030

31-
navbar: ["/", "/installation"],
32-
sidebar: [
33-
{
34-
text: "Home",
35-
link: "/",
36-
},
37-
{
38-
text: "Introduction",
39-
link: "/introduction",
40-
},
41-
{
42-
text: "Installation",
43-
link: "/installation",
44-
},
45-
{
46-
text: "How It Works",
47-
link: "how-it-works",
48-
},
49-
{
50-
text: "Engines",
51-
collapsible: true,
52-
children: [
53-
{
54-
text: "Invokable",
55-
link: "engines/invokable",
56-
},
57-
{
58-
text: "Tree",
59-
link: "engines/tree",
60-
},
61-
{
62-
text: "Ruleset",
63-
link: "engines/rule-set",
64-
},
65-
{
66-
text: "Expression",
67-
link: "engines/expression",
68-
},
69-
],
70-
},
71-
{
72-
text: "Features",
73-
collapsible: true,
74-
children: [
75-
{
76-
text: "Header-Driven Filter Mode",
77-
link: "features/header-driven-filter-mode",
78-
},
31+
navbar: ["/", "/installation"],
32+
sidebar: [
33+
{
34+
text: "Home",
35+
link: "/",
36+
},
37+
{
38+
text: "Introduction",
39+
link: "/introduction",
40+
},
41+
{
42+
text: "Installation",
43+
link: "/installation",
44+
},
45+
{
46+
text: "How It Works",
47+
link: "how-it-works",
48+
},
49+
{
50+
text: "Engines",
51+
collapsible: true,
52+
children: [
53+
{
54+
text: "Invokable",
55+
link: "engines/invokable",
56+
},
57+
{
58+
text: "Tree",
59+
link: "engines/tree",
60+
},
61+
{
62+
text: "Ruleset",
63+
link: "engines/rule-set",
64+
},
65+
{
66+
text: "Expression",
67+
link: "engines/expression",
68+
},
69+
],
70+
},
71+
{
72+
text: "Features",
73+
collapsible: true,
74+
children: [
75+
{
76+
text: "Header-Driven Filter Mode",
77+
link: "features/header-driven-filter-mode",
78+
},
7979

80-
{
81-
text: "Auto Register Filterable Macro",
82-
link: "features/auto-register-filterable-macro",
83-
},
84-
{
85-
text: "Conditional Logic",
86-
link: "features/conditional-logic-with-when",
87-
},
88-
{
89-
text: "Filter Aliases",
90-
link: "features/aliasing",
91-
},
92-
{
93-
text: "Through callbacks",
94-
link: "features/through",
95-
},
96-
{
97-
text: "Auto Binding",
98-
link: "features/auto-binding",
99-
},
100-
],
101-
},
102-
{
103-
text: "Authorization",
104-
link: "authorization",
105-
},
106-
{
107-
text: "Validation",
108-
link: "validation",
109-
},
110-
{
111-
text: "Sanitization",
112-
link: "sanitization",
113-
},
80+
{
81+
text: "Auto Register Filterable Macro",
82+
link: "features/auto-register-filterable-macro",
83+
},
84+
{
85+
text: "Conditional Logic",
86+
link: "features/conditional-logic-with-when",
87+
},
88+
{
89+
text: "Filter Aliases",
90+
link: "features/aliasing",
91+
},
92+
{
93+
text: "Through callbacks",
94+
link: "features/through",
95+
},
96+
{
97+
text: "Auto Binding",
98+
link: "features/auto-binding",
99+
},
114100
],
115-
}),
101+
},
102+
{
103+
text: "Execution",
104+
collapsible: true,
105+
children: [
106+
{
107+
text: "Invoker",
108+
link: "execution/invoker",
109+
},
110+
],
111+
},
112+
{
113+
text: "Authorization",
114+
link: "authorization",
115+
},
116+
{
117+
text: "Validation",
118+
link: "validation",
119+
},
120+
{
121+
text: "Sanitization",
122+
link: "sanitization",
123+
},
124+
],
125+
}),
116126

117-
base: "/filterable",
127+
base: "/filterable",
118128

119-
bundler: viteBundler(),
129+
bundler: viteBundler(),
120130
});

docs/execution/invoker.md

Lines changed: 151 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,151 @@
1+
# Invoker – Fluent Control Over Query Execution
2+
3+
## Overview
4+
5+
The `Invoker` class in the Filterable package is a smart execution wrapper that allows you to control the lifecycle of your query after applying filters. It is returned by the `Filterable::apply()` method and supports actions **before**, **after**, or **on error** during query execution.
6+
7+
---
8+
9+
## Purpose
10+
11+
`Invoker` wraps the underlying query builder and enables:
12+
13+
- Executing callbacks **before** the query runs.
14+
- Handling results **after** the query runs.
15+
- Capturing **errors** and providing fallback logic.
16+
- Dispatching the query as a Laravel job.
17+
- Fluent chaining with `when` and `unless` conditions.
18+
19+
---
20+
21+
## Usage Example
22+
23+
```php
24+
$result = Filterable::apply(User::query())
25+
->beforeExecute(function ($builder) {
26+
$builder->where('is_active', true);
27+
})
28+
->afterExecute(function (Collection $result) {
29+
return $result->filter(fn ($user) => $user->isActive());
30+
})
31+
->onError(function ($invoker, $exception) {
32+
report($exception);
33+
return collect(); // fallback
34+
})
35+
->get(); // <-- This will trigger the execution
36+
```
37+
38+
## Public Methods
39+
40+
`Invoker::init(QueryBuilderInterface $builder)`
41+
42+
Create a new Invoker instance manually.
43+
44+
---
45+
46+
### beforeExecute
47+
48+
`->beforeExecute(Closure $callback): static`
49+
Register a callback to be called before the query is executed.
50+
51+
**Parameters**:
52+
53+
- `Closure $callback`: Receives the internal query builder.
54+
55+
---
56+
57+
### afterExecute
58+
59+
`->afterExecute(Closure $callback): static`
60+
Register a callback to process or modify the result after execution.
61+
62+
**Parameters**:
63+
64+
- `Closure $callback`: Receives the result returned by the terminal method.
65+
66+
---
67+
68+
### onError
69+
70+
`->onError(Closure $callback): static`
71+
Register a callback to handle any exceptions that occur during query execution.
72+
73+
**Parameters**:
74+
`Closure $callback`: Receives the Invoker and the thrown exception.
75+
76+
---
77+
78+
### when
79+
80+
`->when(bool $condition, callable $callback): static`
81+
Conditionally apply logic to the Invoker chain.
82+
83+
---
84+
85+
### unless
86+
87+
`->unless(bool $condition, callable $callback): static`
88+
The inverse of when.
89+
90+
---
91+
92+
### asJob
93+
94+
`->asJob(string $jobClass, array $data = [], ?string $queue = null): mixed`
95+
Dispatch the query execution as a Laravel job.
96+
97+
**Parameters**:
98+
99+
- `string $jobClass`: The name of the job class to dispatch.
100+
- `array $data`: Optional additional data.
101+
- `string|null $queue`: Optional queue name.
102+
103+
---
104+
105+
### When Invoker is Skipped
106+
107+
In some advanced use cases, the `Invoker` wrapper will be **skipped**, and the `apply()` method will return the query builder directly.
108+
109+
This happens in two cases:
110+
111+
1. If the target class implements the `ShouldReturnQueryBuilder` interface:
112+
113+
```php
114+
<?php
115+
116+
namespace App\Http\Filters;
117+
118+
use Kettasoft\Filterable\Filterable;
119+
use Kettasoft\Filterable\Foundation\Contracts\ShouldReturnQueryBuilder;
120+
121+
class PostFilter extends Filterable implements ShouldReturnQueryBuilder
122+
{
123+
//
124+
}
125+
```
126+
127+
2. If you explicitly call the `shouldReturnQueryBuilder()` method before calling a terminal method.
128+
129+
```php
130+
$filter = Filterable::create()
131+
->shouldReturnQueryBuilder()
132+
->apply(Post::query()) // <- Returns Query builder directly
133+
```
134+
135+
This is useful when you want to bypass Invoker's control layer and interact with the builder as usual.
136+
137+
---
138+
139+
Notes:
140+
The job class must accept an `invoker` key in its constructor data.
141+
142+
## Summary
143+
144+
`Invoker` is your **last-mile control layer** before the query is executed. It's ideal for:
145+
146+
- Logging
147+
- Result transformation
148+
- Fallbacks
149+
- Background execution
150+
151+
This gives Filterable a clean and powerful **declarative style**.

0 commit comments

Comments
 (0)