Annotations Reference
Reference for Canvas framework annotations. Annotations configure routing, aspect-oriented programming, and controller configuration.
Routing Annotations
@Route
Purpose: Defines HTTP routes in controller methods.
/**
* @Route("/users/{id}", methods={"GET", "PUT", "DELETE"}, name="user.edit")
*/
public function edit(int $id) { ... }
Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
| path | string | Yes | URL pattern with optional {placeholders} |
| methods | array | No | HTTP methods (default: ["GET"]) |
| name | string | No | Named route identifier for URL generation |
@RoutePrefix
Purpose: Automatically prefixes all routes in a controller class with a common path segment. Useful for API versioning or grouping related endpoints.
Use When: Multiple routes in a controller share a common path prefix (e.g., "/api", "/v1", "/admin").
/**
* @RoutePrefix("/api/v1")
*/
class ProductController extends BaseController {
/**
* @Route("/products")
* Final route: /api/v1/products
*/
public function index() { ... }
/**
* @Route("/products/{id}")
* Final route: /api/v1/products/{id}
*/
public function show(int $id) { ... }
}
Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
| value | string | Yes | Path prefix to prepend to all routes in the controller (leading/trailing slashes are automatically normalized) |
Aspect-Oriented Programming
@InterceptWith
Purpose: Applies cross-cutting concerns (caching, auth, logging) to controller methods or entire classes. Multiple aspects can be stacked.
/**
* @InterceptWith(CacheAspect::class, ttl=300, tags={"reports", "admin"})
* @InterceptWith(RequireAuthAspect::class)
*/
public function reports() { ... }
Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
| class | string | Yes | Fully qualified aspect class name (e.g., CacheAspect::class) |
| ...parameters | mixed | No | Additional named parameters passed to aspect constructor |
Common Aspect-Specific Parameters:
| Parameter | Type | Used By | Description |
|---|---|---|---|
| ttl | int | CacheAspect | Cache time-to-live in seconds |
| tags | array | CacheAspect | Cache invalidation tags for grouped clearing |
| limit | int | RateLimitAspect | Maximum requests allowed in time window |
| window | int | RateLimitAspect | Time window in seconds for rate limiting |
| permission | string | RequirePermissionAspect | Required authorization permission string |
Cache Configuration
@CacheContext
Purpose: Configures cache behavior for methods that inject CacheInterface. Allows per-method customization of cache driver, namespace, and connection parameters without modifying aspect configuration.
Use When: Different methods need different cache backends (e.g., Redis for sessions, file cache for temporary data) or isolated cache namespaces.
class ProductService {
/**
* Uses Redis cache with 'products' namespace
* @CacheContext(driver="redis", namespace="products")
*/
public function getProducts(CacheInterface $cache): array {
return $cache->remember('all_products', 3600, function() {
return $this->repository->findAll();
});
}
/**
* Uses file cache with custom namespace
* @CacheContext(driver="file", namespace="temp_reports")
*/
public function generateReport(CacheInterface $cache): string {
return $cache->get('report_123');
}
/**
* Uses Redis with custom connection config
* @CacheContext(driver="redis", namespace="sessions", host="session-redis.local", port=6380)
*/
public function getSession(CacheInterface $cache, string $sessionId): ?array {
return $cache->get("session_{$sessionId}");
}
}
Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
| driver | string | No | Cache driver name (e.g., "file", "redis", "memcached"). Must match a driver configured in config/cache.php. Falls back to default driver if not specified. |
| namespace | string | No | Cache namespace for key isolation (default: "default"). Useful for separating cache concerns (e.g., "sessions", "products", "temp"). |
| ...custom | mixed | No | Driver-specific configuration parameters (e.g., host, port, database for Redis; servers for Memcached). These override config/cache.php settings for this method. |
Configuration Priority:
Cache configuration is resolved in the following order (later sources override earlier ones):
- Default driver configuration from config/cache.php
- @CacheContext annotation parameters
- Runtime parameters passed to Container::get(CacheInterface::class, $params)
Complete Controller Example
/**
* API v1 Product Controller
* All routes automatically prefixed with /api/v1
*
* @RoutePrefix("/api/v1")
* @InterceptWith(RequireAuthAspect::class)
* @InterceptWith(AuditLogAspect::class)
*/
class ProductController extends BaseController {
/**
* List all products (cached for 5 minutes)
* Final route: GET /api/v1/products
*
* @Route("/products", methods={"GET"}, name="products.index")
* @InterceptWith(CacheAspect::class, ttl=300, namespace="products")
*/
public function index() {
return $this->productRepository->findAll();
}
/**
* View single product (cached for 10 minutes)
* Final route: GET /api/v1/products/{id}
*
* @Route("/products/{id}", methods={"GET"}, name="products.show")
* @InterceptWith(CacheAspect::class, ttl=600, namespace="products")
*/
public function show(int $id) {
return $this->productRepository->find($id);
}
/**
* Create new product (requires permission, wrapped in transaction)
* Final route: POST /api/v1/products
*
* @Route("/products", methods={"POST"}, name="products.create")
* @InterceptWith(RequirePermissionAspect::class, permission="products.create")
* @InterceptWith(TransactionAspect::class)
*/
public function create() {
$product = $this->productRepository->create($this->request->all());
return $this->created($product);
}
/**
* Update product (requires permission, wrapped in transaction, rate limited)
* Final route: PUT /api/v1/products/{id}
*
* @Route("/products/{id}", methods={"PUT"}, name="products.update")
* @InterceptWith(RequirePermissionAspect::class, permission="products.edit")
* @InterceptWith(TransactionAspect::class)
* @InterceptWith(RateLimitAspect::class, limit=60, window=3600)
*/
public function update(int $id) {
$product = $this->productRepository->update($id, $this->request->all());
return $this->success($product);
}
/**
* Delete product (requires permission, wrapped in transaction)
* Final route: DELETE /api/v1/products/{id}
*
* @Route("/products/{id}", methods={"DELETE"}, name="products.delete")
* @InterceptWith(RequirePermissionAspect::class, permission="products.delete")
* @InterceptWith(TransactionAspect::class)
*/
public function delete(int $id) {
$this->productRepository->delete($id);
return $this->noContent();
}
}