6.3 KiB
Attribute-Based Routing
The Foundation framework provides a modern attribute-based routing system that automatically discovers and registers routes when controllers are registered in the dependency injection container.
Overview
Instead of manually registering routes in service providers, controllers can use PHP 8+ attributes to define their routes directly on controller methods. Routes are automatically processed when controllers are registered in the container.
Key Components
ControllerInterface
Controllers must implement Foundation\Core\Infrastructure\ControllerInterface to have their route attributes
processed:
use Foundation\Core\Infrastructure\ControllerInterface;
class MyController implements ControllerInterface
{
// Route attributes will be processed for this controller
}
Route Attributes
Basic Route Attributes
#[Get('/path')]- GET requests#[Post('/path')]- POST requests#[Put('/path')]- PUT requests#[Delete('/path')]- DELETE requests#[Route('/path', ['GET', 'POST'])]- Custom HTTP methods
Group Attribute
Use #[Group('/prefix')] on controller classes to add a common prefix to all routes:
#[Group('/api/users')]
class UserController implements ControllerInterface
{
#[Get('')] // Route: GET /api/users
#[Get('/{id}')] // Route: GET /api/users/{id}
#[Post('')] // Route: POST /api/users
}
Route Options
All route attributes support additional options:
#[Get('/path', name: 'route.name', middleware: [AuthMiddleware::class])]
#[Group('/api', middleware: [RateLimitMiddleware::class])]
Examples
Basic Controller
<?php
namespace App\Infrastructure\Web;
use Foundation\Core\Infrastructure\ControllerInterface;
use Foundation\Core\Routing\Attributes\Get;
use Foundation\Core\Routing\Attributes\Post;
class ProductController implements ControllerInterface
{
#[Get('/products')]
public function index(): ResponseInterface
{
// Handle GET /products
}
#[Get('/products/{id}')]
public function show(string $id): ResponseInterface
{
// Handle GET /products/{id}
}
#[Post('/products')]
public function create(): ResponseInterface
{
// Handle POST /products
}
}
API Controller with Groups
<?php
namespace App\Infrastructure\Api;
use Foundation\Core\Infrastructure\ControllerInterface;
use Foundation\Core\Routing\Attributes\Get;
use Foundation\Core\Routing\Attributes\Post;
use Foundation\Core\Routing\Attributes\Group;
#[Group('/api/v1/products')]
class ProductApiController implements ControllerInterface
{
#[Get('')]
public function index(): ResponseInterface
{
// Handle GET /api/v1/products
}
#[Get('/{id}')]
public function show(string $id): ResponseInterface
{
// Handle GET /api/v1/products/{id}
}
#[Post('')]
public function create(): ResponseInterface
{
// Handle POST /api/v1/products
}
}
Routes with Middleware
<?php
namespace App\Infrastructure\Api;
use Foundation\Core\Infrastructure\ControllerInterface;
use Foundation\Core\Routing\Attributes\Get;
use Foundation\Core\Routing\Attributes\Post;
use Foundation\Core\Routing\Attributes\Group;
use App\Middleware\AuthMiddleware;
use App\Middleware\AdminMiddleware;
#[Group('/admin', middleware: [AuthMiddleware::class, AdminMiddleware::class])]
class AdminController implements ControllerInterface
{
#[Get('/dashboard')]
public function dashboard(): ResponseInterface
{
// Handle GET /admin/dashboard
// AuthMiddleware and AdminMiddleware applied
}
#[Post('/users', middleware: [ValidateUserMiddleware::class])]
public function createUser(): ResponseInterface
{
// Handle POST /admin/users
// AuthMiddleware, AdminMiddleware, and ValidateUserMiddleware applied
}
}
How It Works
Automatic Processing
-
Container Registration: When a controller is registered in the DI container using
$container->register(MyController::class), the system automatically checks if it implementsControllerInterface -
Attribute Discovery: If the controller implements the interface, the
AttributeRouteProcessorscans the controller for route attributes -
Route Registration: Found route attributes are immediately registered with the Slim application
-
No Bootstrap Phase: Routes are registered during container setup, not during application bootstrap
Processing Flow
Controller Registration
↓
ControllerInterface Check
↓
Route Attribute Discovery
↓
Slim Route Registration
Architecture Benefits
Container-Driven Processing
- Only processes controllers that are explicitly registered in the container
- No filesystem scanning or discovery phase
- Routes registered immediately when controllers are registered
Type Safety
- Explicit interface contract (
ControllerInterface) - Clear intent - controllers must opt-in to route processing
- IDE support and autocompletion
Extensible Design
- Built on generic
AttributeProcessorInterface - Framework ready for other attribute types (validation, caching, etc.)
- Modular and maintainable architecture
Best Practices
Controller Organization
- Keep controllers focused and single-purpose
- Use groups for logical route prefixes
- Implement
ControllerInterfaceonly on actual controllers
Route Design
- Use descriptive route paths
- Group related routes using
#[Group] - Apply middleware at the appropriate level (group vs individual routes)
Module Structure
src/Modules/{ModuleName}/Infrastructure/
├── Api/ # API controllers with /api groups
├── Web/ # Web controllers for HTML responses
└── Console/ # Console commands (no route attributes)
Troubleshooting
Routes Not Registered
- Ensure controller implements
ControllerInterface - Verify controller is registered in a service provider
- Check attribute syntax and imports
Route Conflicts
- Use unique route paths
- Check for overlapping group prefixes
- Verify HTTP method combinations
Middleware Issues
- Ensure middleware classes exist and are registered
- Check middleware order (group middleware runs before route middleware)
- Verify middleware implements correct interface