# Routing Value Objects Das Framework unterstützt **parallele Routing-Ansätze** für maximale Flexibilität und Typsicherheit. ## Überblick ```php // ✅ Traditioneller String-Ansatz (schnell & gewohnt) #[Route(path: '/api/users/{id}')] // ✅ Value Object-Ansatz (typsicher & framework-konform) #[Route(path: RoutePath::fromElements('api', 'users', Placeholder::fromString('id')))] ``` Beide Ansätze sind **vollständig kompatibel** und können parallel verwendet werden. ## String-Basierte Routen **Einfach und gewohnt** für schnelle Entwicklung: ```php final readonly class UserController { #[Route(path: '/api/users')] public function index(): JsonResult { /* ... */ } #[Route(path: '/api/users/{id}')] public function show(int $id): JsonResult { /* ... */ } #[Route(path: '/api/users/{id}/posts/{postId}')] public function showPost(int $id, int $postId): JsonResult { /* ... */ } #[Route(path: '/files/{path*}', method: Method::GET)] public function downloadFile(string $path): StreamResult { /* ... */ } } ``` ## Value Object-Basierte Routen **Typsicher und framework-konform** für Production-Code: ### Basis-Syntax ```php use App\Framework\Router\ValueObjects\RoutePath; use App\Framework\Router\ValueObjects\Placeholder; final readonly class ImageController { #[Route(path: RoutePath::fromElements('images', Placeholder::fromString('filename')))] public function show(string $filename): ImageResult { return new ImageResult($this->imageService->getImage($filename)); } } ``` ### Typisierte Parameter ```php #[Route(path: RoutePath::fromElements( 'api', 'users', Placeholder::typed('userId', 'uuid'), 'posts', Placeholder::typed('postId', 'int') ))] public function getUserPost(string $userId, int $postId): JsonResult { // userId wird automatisch als UUID validiert // postId wird automatisch als Integer validiert } ``` ### Verfügbare Parameter-Typen | Typ | Regex Pattern | Beispiel | |-----|---------------|----------| | `int` | `(\d+)` | `/users/{id}` → 123 | | `uuid` | `([0-9a-f]{8}-...)` | `/users/{id}` → `550e8400-e29b-...` | | `slug` | `([a-z0-9\-]+)` | `/posts/{slug}` → `my-blog-post` | | `alpha` | `([a-zA-Z]+)` | `/category/{name}` → `Technology` | | `alphanumeric` | `([a-zA-Z0-9]+)` | `/code/{id}` → `ABC123` | | `filename` | `([a-zA-Z0-9._\-]+)` | `/files/{name}` → `image.jpg` | ### Wildcard-Parameter ```php #[Route(path: RoutePath::fromElements('files', Placeholder::wildcard('path')))] public function serveFile(string $path): StreamResult { // Matched: /files/uploads/2024/image.jpg // $path = "uploads/2024/image.jpg" } ``` ## Fluent Builder API **Expressiver Builder** für komplexe Routen: ```php final readonly class ApiController { #[Route(path: RoutePath::create() ->segment('api') ->segment('v1') ->segment('users') ->typedParameter('userId', 'uuid') ->segment('posts') ->typedParameter('postId', 'int') ->build() )] public function getUserPost(string $userId, int $postId): JsonResult { /* ... */ } // Quick Helper für häufige Patterns #[Route(path: RoutePath::create()->segments('api', 'users')->uuid())] public function showUser(string $id): JsonResult { /* ... */ } } ``` ## Praktische Beispiele ### RESTful API Routes ```php final readonly class ProductController { // String-Ansatz für einfache Routen #[Route(path: '/api/products', method: Method::GET)] public function index(): JsonResult { } #[Route(path: '/api/products', method: Method::POST)] public function create(CreateProductRequest $request): JsonResult { } // Value Object-Ansatz für komplexe Routen #[Route(path: RoutePath::fromElements( 'api', 'products', Placeholder::typed('productId', 'uuid'), 'reviews', Placeholder::typed('reviewId', 'int') ), method: Method::GET)] public function getProductReview(string $productId, int $reviewId): JsonResult { } } ``` ### File Serving ```php final readonly class FileController { // Static files (string) #[Route(path: '/assets/{type}/{filename}')] public function staticAsset(string $type, string $filename): StreamResult { } // Dynamic file paths (Value Object mit Wildcard) #[Route(path: RoutePath::fromElements( 'uploads', Placeholder::wildcard('path') ))] public function uploadedFile(string $path): StreamResult { } } ``` ### Admin Routes ```php final readonly class AdminController { // String für bekannte Admin-Pfade #[Route(path: '/admin/dashboard')] #[Auth(strategy: 'ip', allowedIps: ['127.0.0.1'])] public function dashboard(): ViewResult { } // Value Objects für dynamische Admin-Actions #[Route(path: RoutePath::fromElements( 'admin', 'users', Placeholder::typed('userId', 'uuid'), 'actions', Placeholder::fromString('action') ))] #[Auth(strategy: 'session', roles: ['admin'])] public function userAction(string $userId, string $action): JsonResult { } } ``` ## Migration & Kompatibilität ### Bestehende Routen bleiben unverändert ```php // ✅ Weiterhin gültig und funktional #[Route(path: '/api/users/{id}')] public function show(int $id): JsonResult { } ``` ### Schrittweise Migration ```php final readonly class UserController { // Phase 1: Strings für einfache Routen #[Route(path: '/users')] public function index(): ViewResult { } // Phase 2: Value Objects für neue komplexe Routen #[Route(path: RoutePath::fromElements( 'users', Placeholder::typed('userId', 'uuid'), 'preferences', Placeholder::fromString('section') ))] public function userPreferences(string $userId, string $section): JsonResult { } } ``` ### Konsistenz-Check ```php // Beide Ansätze sind äquivalent $stringRoute = new Route(path: '/api/users/{id}'); $objectRoute = new Route(path: RoutePath::fromElements('api', 'users', Placeholder::fromString('id'))); $stringRoute->getPathAsString() === $objectRoute->getPathAsString(); // true ``` ## Best Practices ### Wann String verwenden - **Prototyping**: Schnelle Route-Erstellung - **Einfache Routen**: Statische oder 1-Parameter-Routen - **Legacy-Kompatibilität**: Bestehende Routen beibehalten ```php // ✅ Gut für einfache Fälle #[Route(path: '/health')] #[Route(path: '/api/status')] #[Route(path: '/users/{id}')] ``` ### Wann Value Objects verwenden - **Production-Code**: Maximale Typsicherheit - **Komplexe Routen**: Mehrere Parameter mit Validierung - **API-Endpoints**: Starke Typisierung für externe Schnittstellen - **Framework-Konsistenz**: Vollständige Value Object-Nutzung ```php // ✅ Gut für komplexe Fälle #[Route(path: RoutePath::fromElements( 'api', 'v2', 'organizations', Placeholder::typed('orgId', 'uuid'), 'projects', Placeholder::typed('projectId', 'slug'), 'files', Placeholder::wildcard('filePath') ))] ``` ### Hybrid-Ansatz (Empfohlen) ```php final readonly class ProjectController { // String für einfache Routen #[Route(path: '/projects')] public function index(): ViewResult { } // Value Objects für komplexe/kritische Routen #[Route(path: RoutePath::fromElements( 'api', 'projects', Placeholder::typed('projectId', 'uuid'), 'members', Placeholder::typed('memberId', 'uuid') ))] public function getProjectMember(string $projectId, string $memberId): JsonResult { } } ``` ## Router-Integration Das Framework konvertiert automatisch zwischen beiden Ansätzen: ```php // Route-Attribute bietet einheitliche Interface public function getPathAsString(): string // Immer String für Router public function getRoutePath(): RoutePath // Immer RoutePath-Objekt // RouteCompiler verwendet automatisch getPathAsString() $path = $routeAttribute->getPathAsString(); // Funktioniert für beide ``` ## Fazit - **String-Routen**: Schnell, gewohnt, ideal für einfache Fälle - **Value Object-Routen**: Typsicher, framework-konform, ideal für komplexe Fälle - **Vollständige Kompatibilität**: Beide Ansätze parallel nutzbar - **Keine Breaking Changes**: Bestehender Code funktioniert weiterhin - **Schrittweise Adoption**: Migration nach Bedarf möglich **Empfehlung**: Hybrid-Ansatz mit Strings für einfache und Value Objects für komplexe Routen.