Routing¶
Routing in Erlenmeyer follows the same philosophy as the rest of the framework: simplicity and clarity first.
You declare routes directly on the App instance — no separate route files or rigid structures required.
How routing works¶
At its core, routing is managed by the App class.
Each route maps an HTTP method and a URI pattern to a callable handler:
$app->get('/hello', function (Request $req, Response $res) {
$res->withText('Hello world!')->send();
});
Under the hood, routes are stored internally as regular expressions, allowing Erlenmeyer to support dynamic parameters and even fallback routes.
Dynamic parameters¶
Parameters are enclosed in square brackets [], and are automatically converted into named regex groups:
$app->get('/users/[id]', function ($req, $res, $params) {
$res->withJson(['id' => $params->id])->send();
});
You can use as many parameters as you need, and Erlenmeyer will map them into a $params object:
$app->get('/posts/[year]/[slug]', fn($req, $res, $p)
=> $res->withJson($p)->send());
Internally, /users/[id] becomes:
/^\/users\/([a-zA-Z0-9\.\-_]+)$/
Route methods¶
You can register routes for any HTTP verb, or for multiple ones at once:
$app->get('/users', ...);
$app->post('/users', ...);
$app->put('/users/[id]', ...);
$app->delete('/users/[id]', ...);
$app->patch('/users/[id]', ...);
Or combine several in one call:
$app->match(['GET', 'POST'], '/contact', ...);
$app->any('/ping', ...); // handles all methods
Redirects¶
Simple route redirection is built in:
$app->redirect('/old-home', '/new-home');
$app->redirect('/legacy', '/', permanent: true);
The second parameter defines the destination, and the optional permanent flag triggers a 301 redirect instead of 302.
Fallbacks and 404s¶
If no route matches, Erlenmeyer will:
- Try to serve a static file through the
Assetsmanager (if configured); - Otherwise, call the 404 handler.
You can customize the 404 handler at any time:
$app->set404Handler(function ($req, $res) {
$res->setStatusCode(404)->withHtml('<h1>Not found</h1>')->send();
});
Middlewares¶
Each route can have its own middleware chain.
A middleware receives a $next() callback to continue the execution flow:
$auth = function ($req, $res, $next, $params) {
if ($req->getQueryParam('token') !== 'secret') {
$res->withText('Unauthorized')->setStatusCode(401)->send();
return;
}
$next($req, $res, new stdClass());
};
$app->get('/secure', fn($req, $res) => $res->withText('Welcome!')->send(), [$auth]);
You can also register global middlewares:
$app->addMiddleware($auth);
They will be applied to every route automatically.
Under the hood¶
- The
Appclass keeps an internal map of routes by method. - Each route is stored as a regex pattern and a handler.
- When a request arrives,
handle()loops through the registered routes, testing each pattern withpreg_match. - If a match is found, parameters are mapped into an object and passed to the handler.
- If none match, the fallback or 404 handler is invoked.
This simple mechanism allows Erlenmeyer to remain lightweight, predictable, and easy to debug.