|
| 1 | +--- |
| 2 | +title: Exception Handling |
| 3 | +sidebarDepth: 2 |
| 4 | +--- |
| 5 | + |
| 6 | +# Exception Handling |
| 7 | + |
| 8 | +Filterable provides a structured and predictable exception-handling system that |
| 9 | +allows engines to decide whether filtering should stop, skip the current filter, |
| 10 | +or continue normally. |
| 11 | +This mechanism was redesigned to offer clearer behavior, improved safety, and |
| 12 | +better extensibility. |
| 13 | + |
| 14 | +The system is built around three main components: |
| 15 | + |
| 16 | +- **Exception types** (how engines signal different situations) |
| 17 | +- **Handlers** (how exceptions are processed) |
| 18 | +- **Configuration** (how strict or lenient the system should behave) |
| 19 | + |
| 20 | +--- |
| 21 | + |
| 22 | +## Exception Flow Overview |
| 23 | + |
| 24 | +During filtering, an engine may encounter invalid, empty, or malformed input. |
| 25 | +Instead of halting the entire process, the engine throws a specific exception |
| 26 | +to indicate what happened. |
| 27 | + |
| 28 | +The handler then decides—based on the exception type and strict configuration— |
| 29 | +whether the exception should be: |
| 30 | + |
| 31 | +- **thrown** (stop filtering), |
| 32 | +- **or skipped** (ignore this filter and continue with the next one). |
| 33 | + |
| 34 | +If a handler returns `false`, the current filter is skipped. |
| 35 | + |
| 36 | +--- |
| 37 | + |
| 38 | +## Exception Types |
| 39 | + |
| 40 | +Filterable defines two fundamental exception categories. |
| 41 | +Each one represents a different kind of failure and implies different behavior. |
| 42 | + |
| 43 | +### **SkipExecution** |
| 44 | + |
| 45 | +`SkipExecution` is used when the engine cannot apply the filter, but the situation |
| 46 | +is not considered critical. |
| 47 | + |
| 48 | +Typical scenarios include: |
| 49 | + |
| 50 | +- empty values when the engine does not accept empty input, |
| 51 | +- unsupported operators, |
| 52 | +- incomplete data structures. |
| 53 | + |
| 54 | +**Behavior:** |
| 55 | + |
| 56 | +- If strict mode is enabled → **the exception is thrown** |
| 57 | +- If strict mode is disabled → **the filter is skipped** |
| 58 | + |
| 59 | +This allows engines to ignore irrelevant or incomplete input without failing the |
| 60 | +whole filtering pipeline. |
| 61 | + |
| 62 | +--- |
| 63 | + |
| 64 | +### **StrictnessException** |
| 65 | + |
| 66 | +`StrictnessException` represents invalid or unsafe input. |
| 67 | +This type signals that the engine cannot proceed safely with the given data. |
| 68 | + |
| 69 | +Examples include: |
| 70 | + |
| 71 | +- corrupted or malformed values, |
| 72 | +- invalid structure or types, |
| 73 | +- contradictory or logically impossible conditions. |
| 74 | + |
| 75 | +**Behavior:** |
| 76 | + |
| 77 | +- strict mode enabled → **always thrown** |
| 78 | +- strict mode disabled → handler may return `false` to skip, but the exception |
| 79 | + indicates a more serious issue |
| 80 | + |
| 81 | +This class of exceptions enforces higher input correctness. |
| 82 | + |
| 83 | +--- |
| 84 | + |
| 85 | +## Exception Handlers |
| 86 | + |
| 87 | +Handlers determine what happens when an exception is thrown. |
| 88 | +They receive both the exception and the engine instance. |
| 89 | + |
| 90 | +Returning `false` means: |
| 91 | +**"Skip this filter and continue."** |
| 92 | + |
| 93 | +Throwing the exception stops filtering immediately. |
| 94 | + |
| 95 | +### **ExceptionHandlerInterface** |
| 96 | + |
| 97 | +Every handler must implement: |
| 98 | + |
| 99 | +```php |
| 100 | +interface ExceptionHandlerInterface |
| 101 | +{ |
| 102 | + public function handle(\Throwable $exception, Engine $engine): bool; |
| 103 | +} |
| 104 | +``` |
| 105 | + |
| 106 | +This gives full control to define how exceptions are processed. |
| 107 | + |
| 108 | +--- |
| 109 | + |
| 110 | +## Helper Base Class: FilterableExceptionHandler |
| 111 | + |
| 112 | +`FilterableExceptionHandler` provides shared logic that custom handlers |
| 113 | +can use to simplify implementation. |
| 114 | + |
| 115 | +Key helper methods: |
| 116 | + |
| 117 | +- `isStrictThrowing()` |
| 118 | + Checks whether global strict mode is enabled via config. |
| 119 | + |
| 120 | +- `hasSkipping($exception)` |
| 121 | + Detects `SkipExecution`. |
| 122 | + |
| 123 | +- `isStrictness($exception)` |
| 124 | + Detects strictness-related exceptions. |
| 125 | + |
| 126 | +Custom handlers may extend this abstract class to avoid duplicating logic. |
| 127 | + |
| 128 | +```php |
| 129 | +abstract class FilterableExceptionHandler implements ExceptionHandlerInterface |
| 130 | +{ |
| 131 | + abstract public function handle(\Throwable $exception, Engine $engine): bool; |
| 132 | + |
| 133 | + protected function isStrictThrowing(): bool |
| 134 | + { |
| 135 | + return config('filterable.exception.strict', false); |
| 136 | + } |
| 137 | + |
| 138 | + protected function hasSkipping($exception): bool |
| 139 | + { |
| 140 | + return $exception instanceof SkipExecution; |
| 141 | + } |
| 142 | + |
| 143 | + protected function isStrictness($exception): bool |
| 144 | + { |
| 145 | + return $exception instanceof StrictnessException; |
| 146 | + } |
| 147 | +} |
| 148 | +``` |
| 149 | + |
| 150 | +--- |
| 151 | + |
| 152 | +## DefaultHandler Behavior |
| 153 | + |
| 154 | +The default handler implements the standard strategy for both exception types: |
| 155 | + |
| 156 | +```php |
| 157 | +class DefaultHandler extends FilterableExceptionHandler |
| 158 | +{ |
| 159 | + public function handle(\Throwable|SkipExecution $exception, Engine $engine): bool |
| 160 | + { |
| 161 | + // SkipExecution: skip if not strict |
| 162 | + if ($this->hasSkipping($exception)) { |
| 163 | + if ($engine->isStrict() || $this->isStrictThrowing()) { |
| 164 | + throw $exception; |
| 165 | + } |
| 166 | + return false; // skip current filter |
| 167 | + } |
| 168 | + |
| 169 | + // StrictnessException: throw when strict |
| 170 | + if ($this->isStrictness($exception) || $this->isStrictThrowing()) { |
| 171 | + throw $exception; |
| 172 | + } |
| 173 | + |
| 174 | + return false; // default: skip non-critical cases |
| 175 | + } |
| 176 | +} |
| 177 | +``` |
| 178 | + |
| 179 | +### Summary of Behavior |
| 180 | + |
| 181 | +| Exception Type | Strict Mode | Behavior | |
| 182 | +| ------------------- | ----------- | --------------- | |
| 183 | +| SkipExecution | Enabled | Throw exception | |
| 184 | +| SkipExecution | Disabled | Skip filter | |
| 185 | +| StrictnessException | Enabled | Throw exception | |
| 186 | +| StrictnessException | Disabled | Skip filter | |
| 187 | + |
| 188 | +--- |
| 189 | + |
| 190 | +## Configuration |
| 191 | + |
| 192 | +Exception handling is defined in the `filterable.exceptions` config section: |
| 193 | + |
| 194 | +```php |
| 195 | +'exceptions' => [ |
| 196 | + |
| 197 | + 'handler' => Kettasoft\Filterable\Exceptions\Handlers\DefaultHandler::class, |
| 198 | + |
| 199 | + 'strict' => env('FILTERABLE_EXCEPTION_STRICT', false), |
| 200 | +] |
| 201 | +``` |
| 202 | + |
| 203 | +### `handler` |
| 204 | + |
| 205 | +Defines the class responsible for handling exceptions. |
| 206 | + |
| 207 | +Must implement: |
| 208 | +`ExceptionHandlerInterface`. |
| 209 | + |
| 210 | +### `strict` |
| 211 | + |
| 212 | +When enabled: |
| 213 | + |
| 214 | +- exceptions are always thrown, |
| 215 | +- skipping behavior is disabled, |
| 216 | +- engine-level strict settings are overridden. |
| 217 | + |
| 218 | +--- |
| 219 | + |
| 220 | +## How Filter Skipping Works |
| 221 | + |
| 222 | +If the handler returns `false`, the current filter is skipped and the next filter |
| 223 | +is processed. |
| 224 | + |
| 225 | +Example: |
| 226 | + |
| 227 | +Filters: **status**, **name**, **is_active** |
| 228 | +Suppose: |
| 229 | + |
| 230 | +- `status` receives empty data, |
| 231 | +- engine does not accept empty values → throws `SkipExecution`. |
| 232 | + |
| 233 | +If strict mode is disabled: |
| 234 | + |
| 235 | +- `status` is skipped, |
| 236 | +- filtering continues with `name` then `is_active`. |
| 237 | + |
| 238 | +This allows the filtering pipeline to continue gracefully without failing |
| 239 | +because of optional or incomplete input. |
| 240 | + |
| 241 | +--- |
| 242 | + |
| 243 | +## Creating Custom Handlers |
| 244 | + |
| 245 | +To implement your own rules: |
| 246 | + |
| 247 | +```php |
| 248 | +class MyCustomHandler extends FilterableExceptionHandler |
| 249 | +{ |
| 250 | + public function handle(\Throwable $exception, Engine $engine): bool |
| 251 | + { |
| 252 | + // custom logic |
| 253 | + } |
| 254 | +} |
| 255 | +``` |
| 256 | + |
| 257 | +Register it in the config: |
| 258 | + |
| 259 | +```php |
| 260 | +'exceptions' => [ |
| 261 | + 'handler' => App\Filters\Handlers\MyCustomHandler::class, |
| 262 | +] |
| 263 | +``` |
| 264 | + |
| 265 | +--- |
| 266 | + |
| 267 | +## Conclusion |
| 268 | + |
| 269 | +This unified exception-handling pipeline provides: |
| 270 | + |
| 271 | +- clear distinction between skip-level and failure-level issues, |
| 272 | +- configurable strictness, |
| 273 | +- customizable handlers, |
| 274 | +- consistent engine behavior, |
| 275 | +- predictable filter skipping. |
| 276 | + |
| 277 | +It enables robust and flexible filtering without breaking existing APIs. |
0 commit comments