A filter is middleware that runs after the controller. It receives the response string and can log it, change it, or replace it.
php laika filter:make LogAccess
Filters live in lf-app/Filter/, namespace App\Filter.
namespace App\Filter;
use Laika\Engine\Route\Contracts\FilterInterface;
class LogAccess implements FilterInterface
{
public function terminate(callable $next, ?string $response, array &$params): ?string
{
// ... your code ...
return $next($response); // pass the (possibly changed) response on
}
}
$response — the controller’s return value (or a pipeline’s short-circuit string). null when nothing was returned.$params — the same array the pipelines and controller received, by reference.use Laika\Engine\Route\Url;
Url::get('/', 'HomeController@index')->filter(LogAccess::class);
Url::get('/dashboard', 'DashboardController@index')->filter(['LogAccess', 'Minify']);
// Every route in the application
Url::globalFilter(['LogAccess']);
Short names resolve to App\Filter\...; a fully qualified class name is used as-is.
In terminate() you… |
Remaining filters | Response sent |
|---|---|---|
return $next($response); |
run | the response, as passed along |
return $next($response, false); |
skipped | $response |
return $next('other'); |
run | 'other' replaces the response |
return $next('other', false); |
skipped | 'other' |
return 'other'; (no $next) |
skipped | 'other' |
Log every response:
public function terminate(callable $next, ?string $response, array &$params): ?string
{
error_log(sprintf('%s %s -> %d bytes',
$_SERVER['REQUEST_METHOD'] ?? '-', $_SERVER['REQUEST_URI'] ?? '-', strlen((string) $response)));
return $next($response);
}
Add a header:
use Laika\Engine\Services\Response;
public function terminate(callable $next, ?string $response, array &$params): ?string
{
Response::setHeader('X-Robots-Tag', 'noindex');
return $next($response);
}
Maintenance mode:
public function terminate(callable $next, ?string $response, array &$params): ?string
{
if (option_bool('maintenance')) {
\Laika\Engine\Services\Response::setStatus(503);
return '<h1>Down for maintenance</h1>';
}
return $next($response);
}
Same syntax as pipelines. The values arrive in $params, and are strings:
Url::get('/reports', 'ReportController@index')->filter(['LogAccess|level=info']);
public function terminate(callable $next, ?string $response, array &$params): ?string
{
error_log('level=' . ($params['level'] ?? 'default'));
return $next($response);
}
Filter args are merged when that filter runs, so only later filters see them — never the controller.
Filters are built through the container, so type-hint dependencies in the constructor:
use App\Support\AuditLog;
class LogAccess implements FilterInterface
{
public function __construct(private AuditLog $log) {}
public function terminate(callable $next, ?string $response, array &$params): ?string
{
$this->log->write($params);
return $next($response);
}
}
The rules are the same as for pipelines: concrete classes auto-wire, interfaces must be bound in a relay provider, and relays should be called statically rather than injected.
Handler::registerGroup()) → route ->filter() → group-chained ->filter().$next($response) runs in reverse order.Laika\Engine\Route\Contracts\FilterInterface (any class with a matching terminate() method is accepted).terminate(callable $next, ?string $response, array &$params): ?stringFilterException (status 500).| Command | Description |
|---|---|
php laika filter:make <name> |
Create a filter class |
php laika filter:list |
List filter classes |
php laika filter:remove <name> |
Delete a filter class |
php laika filter:rename --old=<name> --new=<name> |
Rename a filter class |