Advanced Routing
Advanced routing in Canvas covers wildcard matching, controller prefixes, request access, config-driven paths, and priority resolution.
Wildcard Routes
* matches exactly one segment; ** or {name:**} matches zero or more segments and captures the value.
/** @Route("/files/*") */
public function singleFile(): Response {
// /files/image.jpg ✓ /files/a/b.jpg ✗
}
/** @Route("/files/{path:**}") */
public function nestedFiles(string $path): Response {
// /files/docs/2024/report.pdf → $path = "docs/2024/report.pdf"
}
Route Prefixes
Apply @RoutePrefix to a controller to prepend a segment to every route it contains. Prefixes stack through inheritance.
/** @RoutePrefix("/api") */
class ApiController {}
/** @RoutePrefix("/v1") */
class ApiV1Controller extends ApiController {
/** @Route("/users") */
public function users(): Response {
// Resolves to: /api/v1/users
}
}
Config-Based Routes
Use file::key syntax to resolve a route path from a config file instead of hardcoding it. The prefix before :: determines which file Canvas reads from /config/; the suffix is the key within that file.
// /config/payment.php
return ['mollie_webhook' => '/webhooks/mollie'];
/** @Route("payment::mollie_webhook") */
public function mollieWebhook(): Response {}
/** @Route("payment::mollie_webhook", fallback="/webhooks/mollie") */
public function mollieWebhook(): Response {}
Without a fallback, a missing file or unknown key throws a RuntimeException. For routes that belong to a single action and won't change, an inline path is simpler.
Route Matching Priority
When multiple routes match, Canvas picks the most specific one (highest → lowest):
- Static —
/users/active - Constrained parameter —
/users/{id:int} - Unconstrained parameter —
/users/{name} - Wildcard —
/users/*or/users/**
/** @Route("/users/active") */ // hits first for GET /users/active
public function activeUsers(): Response {}
/** @Route("/users/{id:int}") */ // hits for /users/42, not /users/active
public function userById(int $id): Response {}
/** @Route("/users/{name}") */ // fallback for any other single segment
public function userByName(string $name): Response {}