HTTP Router Contract (v1.0 Freeze)¶
Bamboo routes requests through Bamboo\Core\Router
, a thin wrapper around FastRoute tuned for long-lived OpenSwoole workers. The router API is part of the v1.0 public contract; helpers, middleware semantics, and error payloads will remain stable across the 1.x series.
Route registration and helpers¶
Applications and modules obtain the router from the container ($app->get('router')
) or inject it into service providers. Routes can be registered at bootstrap time or cached to disk.
$router->get('/hello/{name}', [GreetingController::class, 'show']);
$router->post('/jobs', RouteDefinition::forHandler([
JobController::class, 'enqueue'],
middlewareGroups: ['queue'],
));
$router->addRoute('DELETE', '/cache/{key}', function (ServerRequestInterface $request, array $vars) {
return new JsonResponse(['deleted' => $vars['key']]);
});
Supported methods¶
Router::get(string $path, callable|array|RouteDefinition $handler, array $middleware = [], array $groups = [])
Router::post(...)
Router::addRoute(string $method, string $path, ...)
addRoute
accepts any valid HTTP verb and stores the definition internally with a normalised signature. Custom helpers can wrap addRoute
when additional verbs (such as PUT, PATCH, DELETE) are required.
Route definitions¶
Handlers can be expressed as:
- A PHP callable (
function (...) { ... }
). - A controller tuple
[ClassName::class, 'method']
. - An associative array with
handler
,middleware
,middleware_groups
, or legacy keys (uses
,action
). - A
Bamboo\Core\RouteDefinition
instance for explicit signatures.
RouteDefinition::signature
overrides the derived signature used for cache keys and request context metadata. When omitted, the router uses "<METHOD> <PATH>"
.
Parameter tokens¶
Paths follow FastRoute syntax. {name}
captures a single path segment using the [^/]+
regex. Inline constraints are supported: {id:\d+}
restricts the token to digits, {slug:[a-z0-9\-]+}
enforces slug-friendly characters. Matched variables are passed to the handler as part of the $vars
array or injected via argument names ($id
, $slug
) when the callable signature includes them.
Closures and controller methods may type-hint the PSR-7 request or the application container; the router performs best-effort argument matching. The following invocation strategies are part of the contract:
$router->get('/demo/{id}', function (ServerRequestInterface $request, array $vars, Application $app) {
// $request === the incoming request
// $vars === ['id' => '123']
// $app === Bamboo\Core\Application instance
});
Middleware composition¶
Middleware ordering is deterministic:
- Global middleware –
etc/middleware.php['global']
. - Group middleware – merged for each named group listed by the route.
- Route middleware – entries supplied directly to the route definition.
Aliases declared under etc/middleware.php['aliases']
are resolved before the pipeline executes. Modules may contribute additional entries via ModuleInterface::middleware
; contributions are merged with the same ordering rules. See tests/Core/ApplicationPipelineTest.php
for a thorough ordering specification and terminable middleware guarantees.
Error handling¶
The router produces canonical JSON payloads for framework-level errors:
- 404 Not Found –
{"error":"Not Found"}
- 405 Method Not Allowed –
{"error":"Method Not Allowed"}
with anAllow
header listing permitted verbs. - 500 Routing failure –
{"error":"Routing failure"}
emitted when an unexpected dispatcher status occurs.
These responses inherit headers required for Prometheus and tracing middleware, ensuring monitoring remains consistent even when no route matches. Application middleware can override or extend the behaviour by catching responses upstream.
Route caching contract¶
Router::cacheTo($file)
exports the in-memory map to PHP, enabling instant boot without re-registering routes. Because PHP cannot serialise closures safely, the cache routine rejects any definition that contains a closure and throws a RuntimeException
. Operators should run php bin/bamboo routes.cache
as part of build pipelines once the application route table stabilises.
Deprecation policy¶
- Existing helper methods (
get
,post
,addRoute
,gatherMiddleware
) remain stable throughout the 1.x line. New helpers may be added in minor releases. - Future helper deprecations must trigger
E_USER_DEPRECATED
notices while keeping the old API functional for at least one minor release. - Parameter token semantics and the default error payload schema are frozen; any changes require a new major release with upgrade-guide choreography.
Contract validation¶
tests/Http/ApplicationRoutesTest.php
exercises built-in routes, parameter injection, and the Prometheus/metrics
exporter.tests/Core/ApplicationPipelineTest.php
covers middleware ordering, alias resolution, and terminable middleware behaviour.tests/Roadmap/V0_4/TimeoutMiddlewareTest.php
andtests/Roadmap/V0_4/CircuitBreakerMiddlewareTest.php
ensure resilience middleware integrates correctly with router-driven signatures.tests/Console/RoutesShowCommandTest.php
andtests/Core/ApplicationModulesTest.php
guard the cached route format and module contributions.
Together with the new config.validate
command, these suites guarantee the router contract is exercised in CI before the v1.0 release is tagged.
HTTP Router Contract (v1.0 Freeze)¶
Bamboo's HTTP router exposes the framework's primary public surface area. This reference freezes the v1.x contract so that applications can rely on routing, middleware, and error semantics remaining stable across the major series.
Supported HTTP methods and helpers¶
Registration APIs¶
Bamboo\Core\Router
normalises every route into an internal map keyed by the uppercased HTTP method and URI template.【F:src/Core/Router.php†L11-L148】 Routes can be registered through the following helpers:
addRoute($method, $path, $action, array $middleware = [], array $middlewareGroups = [])
is the generic entry-point. It accepts any RFC 7231 token (e.g.PUT
,PATCH
,DELETE
,OPTIONS
) and should be used for verbs other than GET/POST.【F:src/Core/Router.php†L14-L31】get($path, $action, array $middleware = [], array $middlewareGroups = [])
andpost(...)
delegate toaddRoute()
with a fixed method string.【F:src/Core/Router.php†L19-L25】RouteDefinition::forHandler(...)
is an ergonomic factory when you need to bundle middleware, middleware groups, or a custom signature with the handler metadata.【F:src/Core/RouteDefinition.php†L4-L19】
Handlers may be:
- A callable (closure, invokable object, or function name).
- A
[ControllerClass::class, 'method']
pair; the router will instantiate the controller, pass it the currentApplication
, and invoke the method.【F:src/Core/Router.php†L101-L108】 - An associative array with a
handler
key plus optionalmiddleware
,middleware_groups
/middlewareGroups
/groups
, andsignature
overrides; the router normalises these shapes so downstream code receives a consistent structure.【F:src/Core/Router.php†L120-L148】
The dispatcher reflects handler arguments and automatically injects the PSR-7 request, the route variable array, and the running Application
instance when a parameter's type-hint or name matches one of those services.【F:src/Core/Router.php†L167-L239】
Middleware attachments¶
Each route may supply inline middleware aliases and middleware groups. At normalisation time the router flattens these lists, merges any values provided by RouteDefinition
or associative array metadata, and stores them separately so they can be combined with the configured global and group stacks during request handling.【F:src/Core/Router.php†L120-L148】【F:src/Core/Application.php†L42-L57】
Parameter tokens and URI patterns¶
Bamboo delegates path compilation and matching to FastRoute, so URI templates use FastRoute's token syntax. The router passes method/path pairs directly to FastRoute\RouteCollector
, which means:
- Curly-brace placeholders capture single path segments (e.g.
/hello/{name}
binds aname
variable).【F:routes/http.php†L25-L30】【F:src/Core/Router.php†L63-L114】 - Placeholders may specify a custom regular expression using the
{token:pattern}
form. Because Bamboo does not alter the definition before it reaches FastRoute, the framework inherits FastRoute's default of[^/]+
for unspecified patterns.【F:src/Core/Router.php†L63-L83】 - Optional segments and nested groups follow FastRoute's square-bracket syntax. Since definitions are forwarded verbatim, any template supported by FastRoute's parser is accepted by Bamboo.【F:src/Core/Router.php†L63-L83】
Canonical route examples¶
The default application registers routes using the helpers described above. These examples double as integration tests for the router contract:
$router->get('/metrics', RouteDefinition::forHandler([
Bamboo\Web\Controller\MetricsController::class, 'index'],
));
$router->get('/hello/{name}', RouteDefinition::forHandler(
function ($request, $vars) {
return new Nyholm\Psr7\Response(200, ['Content-Type' => 'text/plain'], "Hello, {$vars['name']}!\n");
},
middleware: [Bamboo\Web\Middleware\SignatureHeader::class],
));
$router->post('/api/echo', function ($request) {
$body = (string) $request->getBody();
return new Nyholm\Psr7\Response(200, ['Content-Type' => 'application/json'], $body ?: '{}');
}, [Bamboo\Web\Middleware\SignatureHeader::class]);
Error handling behaviour¶
Dispatcher outcomes¶
Router::match()
wraps FastRoute's dispatcher and returns a structured array containing the outcome, allowed methods (for 405 responses), and extracted route variables. This structure feeds directly into the HTTP response generator used by Application::handle()
.【F:src/Core/Router.php†L63-L117】【F:src/Core/Application.php†L37-L83】
JSON responses¶
The router standardises three error payloads when a request cannot be fulfilled by application code:
Status | Body | Notes |
---|---|---|
404 | { "error": "Not Found" } | Returned when no route matches the URI.【F:src/Core/Router.php†L95-L99】 |
405 | { "error": "Method Not Allowed" } | Returned when the URI exists but does not accept the request method. The match() result still includes an allowed list for callers that need it.【F:src/Core/Router.php†L74-L115】 |
500 | { "error": "Routing failure" } | Defensive fallback if FastRoute returns an unknown status code.【F:src/Core/Router.php†L95-L118】 |
Each response is emitted with a Content-Type: application/json
header. Because routing happens inside the middleware pipeline, global middleware such as the request ID generator still run and attach correlation headers even for 4xx/5xx responses.【F:src/Core/Application.php†L55-L83】【F:src/Web/Middleware/RequestId.php†L7-L22】
Logging and correlation expectations¶
The request ID middleware stores the generated or propagated identifier, HTTP method, and route signature in RequestContext
. That context is merged into every Monolog record so operators receive correlation IDs alongside error reports and 404/405 traces.【F:src/Web/Middleware/RequestId.php†L7-L22】【F:src/Core/Application.php†L31-L52】【F:src/Provider/AppProvider.php†L10-L27】 Regression tests assert both header propagation and structured logging metadata for matched and unmatched routes.【F:tests/Http/RequestIdMiddlewareTest.php†L31-L85】
Regression coverage¶
ApplicationPipelineTest::testUnmatchedRoutesShareKernelCacheEntry
exercises repeated 404 responses and ensures the middleware stack executes with the expected context for missing routes.【F:tests/Core/ApplicationPipelineTest.php†L230-L263】- The request ID middleware tests listed above confirm correlation IDs surface in responses and logs, covering the observable contract for error conditions.
Middleware ordering guarantees¶
Pipeline composition¶
The application bootstraps a Kernel
that expands middleware aliases and groups from configuration. For each request it concatenates the fully-expanded global stack with route-provided middleware (aliases and groups are recursively resolved). The resulting list is cached per-route signature so subsequent requests reuse the same ordering.【F:src/Core/Application.php†L42-L83】【F:src/Web/Kernel.php†L6-L123】
Middleware are invoked in the order produced by the kernel, while "after" logic runs in the reverse order as the stack unwinds. Terminable middleware are queued and executed after a response is produced, preserving the same request instance that reached the termination hook.【F:src/Core/Application.php†L55-L83】
Deterministic ordering guarantees¶
- Global middleware always execute first, in declaration order, followed by expanded middleware groups, then route-scoped middleware aliases.【F:src/Web/Kernel.php†L21-L123】
- Group references and aliases are flattened depth-first. Circular references are detected and rejected to avoid infinite recursion.【F:src/Web/Kernel.php†L81-L107】
- The kernel invalidates its cache when the middleware configuration or the route cache file timestamp changes, ensuring stale stacks are never reused across deployments.【F:src/Web/Kernel.php†L36-L54】
Regression coverage¶
tests/Core/ApplicationPipelineTest.php
captures the execution order contract:
testMiddlewarePipelineResolvesConfiguredOrder
asserts the global → group → route ordering, header mutations, and middleware cache reuse.【F:tests/Core/ApplicationPipelineTest.php†L180-L228】testKernelCacheInvalidatesWhenConfigurationChanges
andtestKernelCacheInvalidatesWhenRouteCacheTimestampChanges
ensure the kernel's cache invalidation semantics stay in sync with configuration updates and route cache refreshes.【F:tests/Core/ApplicationPipelineTest.php†L265-L319】testKernelCachesSingleEntryForUnmatchedRoutes
verifies unmatched requests share a__global__
cache entry while still recording the active route in the request context.【F:tests/Core/ApplicationPipelineTest.php†L321-L361】testTerminableMiddlewareRunsAfterResponseIsProduced
exercises the termination hooks and guarantees the recorded events and response headers appear in the expected order.【F:tests/Core/ApplicationPipelineTest.php†L364-L404】
Deprecation policy¶
Router-facing helpers (addRoute
, verb-specific helpers, RouteDefinition
constructors, and middleware utilities) follow Bamboo's deprecation rules:
- Deprecations must trigger
E_USER_DEPRECATED
notices viatrigger_error()
as soon as an alternative exists. Notices include the planned removal version and migration guidance. - Deprecated helpers remain available for at least one subsequent minor release (e.g. deprecated in v1.2, removed no earlier than v1.3) so applications have a full release cycle to migrate.
- Documentation and upgrade notes describe migration strategies, including suggested codemod patterns when a mechanical transformation exists (such as replacing
post()
withaddRoute('POST', ...)
).
Contract validation¶
The following regression suites cover the router contract:
Concern | Test coverage |
---|---|
Route caching and closure safeguards | tests/Core/RouterCacheTest.php 【F:tests/Core/RouterCacheTest.php†L20-L91】 |
Middleware ordering, caching, and terminators | tests/Core/ApplicationPipelineTest.php 【F:tests/Core/ApplicationPipelineTest.php†L180-L404】 |
Request ID propagation and logging context | tests/Http/RequestIdMiddlewareTest.php 【F:tests/Http/RequestIdMiddlewareTest.php†L31-L85】 |
Default application routes | routes/http.php (serves as the canonical example suite)【F:routes/http.php†L8-L65】 |
Future router-specific PHPUnit namespaces will live under tests/Router/
; until then the existing suites above serve as the authoritative regression coverage for this contract.