Advanced Routing

Advanced routing features in Canvas provide powerful pattern matching, route organization, and request handling capabilities for complex applications.

Wildcard Routes

Match variable numbers of URL segments using wildcards:

Single-Segment Wildcards

Use * to match exactly one URL segment:

/**
 * @Route("/files/*")
 */
public function singleFile(Request $request): Response {
    // Matches: /files/image.jpg
    // Matches: /files/document.pdf
    // Does not match: /files/folder/image.jpg (multiple segments)
}

Multi-Segment Wildcards

Use ** or {name:**} to match multiple URL segments (zero or more):

/**
 * @Route("/api/**")
 */
public function apiCatchAll(Request $request): Response {
    // Matches: /api/v1/users/123
    // Matches: /api/admin/settings/cache
}

/**
 * @Route("/files/{path:**}")
 */
public function nestedFiles(string $path): Response {
    // Matches: /files/docs/reports/2024/summary.pdf
    // $path = "docs/reports/2024/summary.pdf"

    // Matches: /files/image.jpg
    // $path = "image.jpg"
}

Route Prefixes

Apply a prefix to all routes in a controller using @RoutePrefix:

<?php
namespace App\Controllers;

use Quellabs\Canvas\Annotations\Route;
use Quellabs\Canvas\Annotations\RoutePrefix;
use Quellabs\Canvas\Controllers\BaseController;

/**
 * @RoutePrefix("/admin")
 */
class AdminController {

    /**
     * @Route("/users")
     */
    public function users(): Response {
        // Full path: /admin/users
    }

    /**
     * @Route("/settings")
     */
    public function settings(): Response {
        // Full path: /admin/settings
    }
}

Inherited Route Prefixes

Route prefixes stack through inheritance (parent to child order):

/**
 * @RoutePrefix("/api")
 */
class ApiController {}

/**
 * @RoutePrefix("/v1")
 */
class ApiV1Controller {

    /**
     * @Route("/users")
     */
    public function users(): Response {
        // Full path: /api/v1/users
        // Prefixes combine: /api + /v1 + /users
    }
}

Route Matching Priority

When multiple routes could match a URL, Canvas automatically prioritizes more specific patterns over generic ones. If multiple routes match, the highest priority route wins.

Priority order (highest to lowest):

  1. Static routes: Fully static paths like /users/active
  2. Constrained parameters: Routes with type constraints like /users/{id:int}
  3. Unconstrained parameters: Routes with simple parameters like /users/{name}
  4. Wildcards: Single-segment * or multi-segment ** patterns
/**
 * @Route("/users/active")
 */
public function activeUsers(): Response {
    // Static route - highest priority
}

/**
 * @Route("/users/{id:int}")
 */
public function showUserById(int $id): Response {
    // Constrained parameter - won't match /users/active (fails int constraint)
}

/**
 * @Route("/users/{slug:slug}")
 */
public function showUserBySlug(string $slug): Response {
    // Could match /users/active depending on route order
}

/**
 * @Route("/users/{name}")
 */
public function showUserByName(string $name): Response {
    // Unconstrained parameter - matches anything
}

Accessing Request Data

Inject Symfony's Request object to access HTTP request data:

use Symfony\Component\HttpFoundation\Request;

/**
 * @Route("/search", methods={"GET", "POST"})
 */
public function search(Request $request): Response {
    // Get query parameters
    $query = $request->query->get('q');

    // Get POST data
    $name = $request->request->get('name');

    // Get cookies
    $sessionId = $request->cookies->get('session_id');

    // Get headers
    $userAgent = $request->headers->get('User-Agent');

    // Check HTTP method
    if ($request->isMethod('POST')) {
        // Handle POST
    }

    // Get full URL
    $url = $request->getUri();

    // Get path
    $path = $request->getPathInfo();
}