Skip to content

Usage

Construction

final class SlimGuard
{
    public function __construct(
        ResponseFactoryInterface $responseFactory,
        StreamFactoryInterface $streamFactory,
        GuardEngine $engine
    ) { ... }

    public static function forApp(App $app, GuardEngine $engine, ?StreamFactoryInterface $streamFactory = null): self;
    public function middleware(): MiddlewareInterface;
    public function addTo(App $app): App;
    public function addToGroup(RouteGroupInterface $group): RouteGroupInterface;
}

SlimGuard::forApp() builds the guard from an existing Slim App: the response factory is the app's own, and the stream factory resolves from the explicit argument, then a response factory that also implements StreamFactoryInterface, then the default slim/psr7 factory when installed. A LogicException is thrown when none of those apply; pass a StreamFactoryInterface explicitly instead of relying on slim/psr7.

The constructor composes the psr15-guard GuardMiddleware, which calls $engine->initialize(); a GuardRedisException is swallowed only when SecurityConfig.redisFailOpen is true, otherwise construction fails closed.

Attachment

SlimGuard::forApp($app, $engine)->addTo($app);        // app middleware: screens everything, including 404s

$group = $app->group('/admin', function ($group) { ... });
SlimGuard::forApp($groupApp, $adminEngine)->addToGroup($group);
                                                       // group middleware: only screens matched routes

middleware() exposes the composed PSR-15 middleware for hand wiring ($app->add($slimGuard->middleware())) or for any stack that accepts PSR-15 middleware.

Middleware ordering

Slim runs the last-added middleware first. Add the guard AFTER (in code order) any middleware that reads the request body stream: Slim's optional BodyParsingMiddleware casts the stream to a string, which leaves the read pointer at EOF and starves the engine's bounded body scan. The guard should be added last so it executes first.

Verdicts

Block verdicts are translated by the composed psr15-guard middleware and never reach your route callable:

Situation Status Body
Banned IP 403 IP address banned
Suspicious content 400 Suspicious activity detected
Rate limit exceeded 429 Too many requests plus Retry-After
Engine malfunction 500 Security check failed

Bodies can be overridden globally through SecurityConfig.customErrorResponses.

Fail-closed behavior

Inherited from psr15-guard: if the engine check throws, the middleware responds with the engine's fail-closed response (500 Security check failed) rather than letting the request through. A custom 500 body comes from customErrorResponses[500], not from adapter code.

Route-scoped configuration

The engine supports per-route RouteConfig (required headers, per-route rate limits) keyed off a route ID on the request state, and the composed adapter builds its engine request internally, so there is no route-ID hook. The engine-sanctioned equivalent is the customRequestCheck config closure, which runs as the last pipeline check; for Slim-native route scoping, attach a dedicated guard to a route group with addToGroup() and its own engine instance. See the advanced example app.