fix: Gitea Traefik routing and connection pool optimization
Some checks failed
🚀 Build & Deploy Image / Determine Build Necessity (push) Failing after 10m14s
🚀 Build & Deploy Image / Build Runtime Base Image (push) Has been skipped
🚀 Build & Deploy Image / Build Docker Image (push) Has been skipped
🚀 Build & Deploy Image / Run Tests & Quality Checks (push) Has been skipped
🚀 Build & Deploy Image / Auto-deploy to Staging (push) Has been skipped
🚀 Build & Deploy Image / Auto-deploy to Production (push) Has been skipped
Security Vulnerability Scan / Check for Dependency Changes (push) Failing after 11m25s
Security Vulnerability Scan / Composer Security Audit (push) Has been cancelled
Some checks failed
🚀 Build & Deploy Image / Determine Build Necessity (push) Failing after 10m14s
🚀 Build & Deploy Image / Build Runtime Base Image (push) Has been skipped
🚀 Build & Deploy Image / Build Docker Image (push) Has been skipped
🚀 Build & Deploy Image / Run Tests & Quality Checks (push) Has been skipped
🚀 Build & Deploy Image / Auto-deploy to Staging (push) Has been skipped
🚀 Build & Deploy Image / Auto-deploy to Production (push) Has been skipped
Security Vulnerability Scan / Check for Dependency Changes (push) Failing after 11m25s
Security Vulnerability Scan / Composer Security Audit (push) Has been cancelled
- Remove middleware reference from Gitea Traefik labels (caused routing issues) - Optimize Gitea connection pool settings (MAX_IDLE_CONNS=30, authentication_timeout=180s) - Add explicit service reference in Traefik labels - Fix intermittent 504 timeouts by improving PostgreSQL connection handling Fixes Gitea unreachability via git.michaelschiemer.de
This commit is contained in:
306
docs/features/routing/value-objects.md
Normal file
306
docs/features/routing/value-objects.md
Normal file
@@ -0,0 +1,306 @@
|
||||
# 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.
|
||||
Reference in New Issue
Block a user