Routing

Canvas uses annotation-based routing. Routes are defined directly in controller methods using the @Route annotation, eliminating the need for separate route configuration files.

explanation

Request Lifecycle

When a request arrives, Canvas checks its own routes first. If a match is found, any @InterceptWith aspects run before and around the controller method. The controller executes and returns a response, after which After aspects run. If no Canvas route matches, the request falls through to the Legacy Bridge. If an exception is thrown at any point, the error handling system takes over.

Canvas request lifecycle diagram

Route Discovery

Canvas automatically discovers routes by scanning your src/Controllers directory for classes with @Route annotations. The discovered routes are compiled and cached for performance.

Controller Structure

Routes must be defined in controller classes:

<?php
namespace App\Controllers;

use Quellabs\Canvas\Annotations\Route;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;

class UserController {

    /**
     * @Route("/api/users")
     */
    public function index(): Response {
        return new Response('User list');
    }
}

HTTP Methods

Routes default to GET requests when no methods parameter is specified. Use the methods parameter to support additional HTTP methods:

/**
 * @Route("/api/users")
 */
public function index(): Response {
    // Only accepts GET requests (default)
}

/**
 * @Route("/api/users", methods={"POST"})
 */
public function create(): Response {
    // Only accepts POST requests
}

/**
 * @Route("/api/users", methods={"GET", "POST"})
 */
public function manage(): Response {
    // Accepts both GET and POST
}

Route Parameters

Capture URL segments as method parameters. Parameters are injected by name - the parameter name in your method must match the placeholder name in the route:

/**
 * @Route("/users/{id}/posts/{postId}")
 */
public function userPost(string $id, string $postId): Response {
    // URL: /users/123/posts/456
    // $id = "123", $postId = "456"
    // Parameter names must match route placeholders
}

Route Parameter Validation

Validate route parameters with type constraints. Routes with parameters that fail validation will not match, allowing the router to continue searching for other matching routes:

/**
 * @Route("/users/{id:int}")
 */
public function show(int $id): Response {
    // Matches: /users/123
    // Doesn't match: /users/abc (fails int validation, router continues)
}

Available Validation Patterns

  • {x:int} - Integer values only (digits)
  • {x:alpha} - Alphabetic characters only (a-z, A-Z)
  • {x:alnum} - Alphanumeric characters only (a-z, A-Z, 0-9)
  • {x:slug} - URL-friendly slugs (lowercase letters, digits, hyphens)
  • {x:uuid} - UUID format (8-4-4-4-12 hex digits)
  • {x:email} - Valid email address format

Accessing Request Data

Inject Symfony's Request as a controller method parameter to read query strings, POST body, headers, cookies, and session data. See Request & Response for the full reference.