AI blog · DotApp PHP Framework 2.0
Middleware in DotApp PHP Framework
DotApp has three middleware shapes. Do not mix their callbacks.
A route before / after hook receives the locked $request — returning a Response short-circuits.
Named pipeline middleware is Middleware::register then Middleware::use('name')->group(). That callback is
function ($request, $next) and must call $next($request) to continue.
A module class lives in app/modules/Shop/Middleware/, extends ModuleMiddleware, and is attached as
#Shop:AuthGate@check!.
This article is a complete Shop admin gate: class, named pipeline, and a per-route login hook.
Do not put crcCheck() in a global before-hook if the handler also calls it — the first pass burns the token.
Watch: crcCheck() once per request
The posted envelope carries a one-time CSRF token. A successful crcCheck() invalidates it.
The boolean is not remembered. Call it again on the same request and the second call returns false
(used token) — HTTP 400 — even though the first check passed.
Wrong: Router::before(['POST'], ['/shop/*'], '#Shop:AuthGate@crc!') plus
if (!$request->crcCheck()) in the save method.
Right: one call. Default: the handler. If you insist on a before-hook, that hook is the only call —
handlers must not call crcCheck() again. form() does not re-run CRC.
Canonical: Request lifecycle.
Common mistakes
| Wrong | Right |
|---|---|
function ($request, $next) on ->before() |
Route hooks get ($request) only. Pipeline middleware gets ($request, $next) |
Forget $next($request) in a named middleware |
The group never runs. Return $next($request) |
Shop:AuthGate@check! without # |
#Shop:AuthGate@check! — callable strings |
Global crcCheck() in middleware, then again in the controller |
One crcCheck() per request. The first success burns the token. See crcCheck once |
| Hand-write the middleware class file | php dotapper.php --module=Shop --create-middleware=AuthGate |
Middleware::use('missing') |
Throws. register first |
Put GET/POST routes only inside module.listeners.php |
Application routes stay in initialize(). Listeners may attach a global Router::before |
Three shapes
| Shape | Callback | Attach |
|---|---|---|
| Route hook | function ($request) |
Router::get(...)->before($fn) or Router::before($methods, $paths, $fn) |
| Named pipeline | function ($request, $next) |
Middleware::register then use()->group() |
| Module class | public static function check($request) |
->before('#Shop:AuthGate@check!') |
Returning a Response from a before hook stops the route.
Middleware::use($name)->group(): if a middleware returns a Response, it is sent and the script exits.
Middleware::get($name) throws when the name is unknown.
Official DI chapter also covers hooks: Dependency injection documentation.
Complete Shop admin gate
File: app/modules/Shop/Middleware/AuthGate.php.
<?php
namespace Dotsystems\App\Modules\Shop\Middleware;
use Dotsystems\App\Parts\Auth;
use Dotsystems\App\Parts\Response;
class AuthGate extends \Dotsystems\App\Parts\ModuleMiddleware
{
public static function check($request)
{
if (!Auth::isLogged()) {
return new Response(403, 'Forbidden');
}
if (!Auth::can('Shop.admin')) {
return new Response(403, 'Forbidden');
}
}
}
In initialize() — named pipeline around admin URLs, plus a per-route login hook.
This class does not call crcCheck(). Handlers still do that once.
use Dotsystems\App\Parts\Auth;
use Dotsystems\App\Parts\Middleware;
use Dotsystems\App\Parts\Response;
use Dotsystems\App\Parts\Router;
Middleware::register('is_admin', function ($request, $next) {
if (!Auth::isLogged() || !Auth::can('Shop.admin')) {
return new Response(403, 'Forbidden');
}
return $next($request);
});
$p = Config::module('Shop', 'prefix');
Middleware::use('is_admin')->group(function () use ($p) {
Router::get($p . '/admin/users', 'Shop:Admin@users!', Router::STATIC_ROUTE);
Router::get($p . '/admin/items', 'Shop:Admin@items!', Router::STATIC_ROUTE);
});
Router::get($p . '/account', 'Shop:Account@index!', Router::STATIC_ROUTE)
->before('#Shop:AuthGate@check!');
Do not add Router::before(['POST'], ['/shop/*'], '#Shop:AuthGate@crc!') if Shop save methods already call crcCheck().
That double call is the used-token 400 — crcCheck once.
Auth session test is Auth::isLogged(). Auth::logged() does not exist.
Permissions: Authentication and 2FA.
Boot order: Module initialization.
Named middleware API
| Call | Returns |
|---|---|
Middleware::register / define / set |
Chain object |
Middleware::use($name) / get($name) |
Middleware — throws if undefined |
->group($cb) |
$this; a Response from middleware is sent and the script exits |
->callAllMiddlewares() |
Last return value, or the Response |
->when($cb) / ->true($cb) / ->false($cb) |
$this |
Other grouping: prefix concatenation from config, or Router::onPath('/shop/admin*', function () { ... }).
Rate limits on a route: ->throttle([...]) — without limitExceeded a 429 JSON is sent and the script exits.
FAQ
Is before() an onion stack?
No. before / after are flat hooks. The onion $next stack is the named Middleware:: pipeline.
Can the gate return JSON 401?
Yes: return Response::json(['status' => 0, 'message' => 'Unauthorized'], 401).
Use that for public JSON APIs — JSON endpoints.
Does #Shop:AuthGate@check! inject services?
Trailing ! skips DI. Keep the method as check($request) only.
When do I use after()?
Logging or headers after the handler ran. It does not replace a successful controller return.
HTTP 405?
Not implemented as a framework feature. Disallowed methods on the request object are a different path (405 and exit on the request).
Do not invent a 405 helper on Router.
Can I crcCheck in a global before-hook?
Only if that is the only crcCheck() on the request. Handlers that also call it will 400.
Prefer the handler. See crcCheck once.
May a global Router::before live in listeners?
Yes — module.listeners.php runs first. Use it for auth gates, not for a second CRC.
Do not register the catalog of Shop pages there.
See also
- Request lifecycle in DotApp PHP Framework
- Callable strings in DotApp PHP Framework
- How routing works in DotApp PHP Framework
- How authentication and 2FA work in DotApp PHP Framework
- Dependency injection in DotApp PHP Framework
- Events and listeners in DotApp PHP Framework
- Official DI and middleware documentation