fix: DockerSecretsResolver - don't normalize absolute paths like /var/www/html/...
Some checks failed
Deploy Application / deploy (push) Has been cancelled
Some checks failed
Deploy Application / deploy (push) Has been cancelled
This commit is contained in:
328
docs/features/attribute-execution/guide.md
Normal file
328
docs/features/attribute-execution/guide.md
Normal file
@@ -0,0 +1,328 @@
|
||||
# Attribute Execution System Guide
|
||||
|
||||
Das Attribute Execution System ermöglicht die Ausführung von Attributen zur Laufzeit mit drei verschiedenen Patterns. Alle Patterns sind cachebar, testbar und unterstützen Dependency Injection.
|
||||
|
||||
**Wichtig**: Attribute werden primär auf **Command/Query-Handler-Methoden** verwendet, nicht auf Controllern, da die meisten Operationen über den CommandBus laufen.
|
||||
|
||||
## Übersicht
|
||||
|
||||
Das System besteht aus drei Hauptkomponenten:
|
||||
|
||||
1. **CallbackMetadata**: Speichert Metadaten über Callbacks (cachebar)
|
||||
2. **CallbackExecutor**: Führt Callbacks basierend auf Metadata aus
|
||||
3. **AttributeRunner**: Führt ausführbare Attribute aus
|
||||
|
||||
## Die drei Patterns
|
||||
|
||||
### Pattern A: Handler-Klassen
|
||||
|
||||
Handler-Klassen sind dedizierte Klassen die eine `check()`, `handle()` oder `__invoke()` Methode implementieren.
|
||||
|
||||
**Vorteile:**
|
||||
- 100% cachebar (nur Strings/Arrays)
|
||||
- Isoliert testbar
|
||||
- Framework-kompatibel (nutzt Container)
|
||||
|
||||
**Beispiel:**
|
||||
|
||||
```php
|
||||
// Handler-Klasse
|
||||
final readonly class PermissionGuard
|
||||
{
|
||||
public function __construct(
|
||||
private readonly array $permissions
|
||||
) {}
|
||||
|
||||
public function check(AttributeExecutionContext $context): bool
|
||||
{
|
||||
$user = $context->container->get(UserService::class)->getCurrentUser();
|
||||
return $user->hasPermissions(...$this->permissions);
|
||||
}
|
||||
}
|
||||
|
||||
// Verwendung
|
||||
#[Guard(PermissionGuard::class, ['edit_post', 'delete_post'])]
|
||||
class PostController {}
|
||||
```
|
||||
|
||||
### Pattern B: First-Class Callables
|
||||
|
||||
First-Class Callables nutzen statische Methoden mit PHP 8.1+ Syntax.
|
||||
|
||||
**Vorteile:**
|
||||
- Modern und lesbar
|
||||
- Cachebar (class + method als Strings)
|
||||
- Flexibel mit DI
|
||||
|
||||
**Beispiel:**
|
||||
|
||||
```php
|
||||
// Policy-Klasse mit statischen Methoden
|
||||
final readonly class UserPolicies
|
||||
{
|
||||
public static function isAdmin(AttributeExecutionContext $context): bool
|
||||
{
|
||||
$user = $context->container->get(UserService::class)->getCurrentUser();
|
||||
return $user->isAdmin();
|
||||
}
|
||||
|
||||
public static function hasRole(
|
||||
string $role,
|
||||
AttributeExecutionContext $context
|
||||
): bool {
|
||||
$user = $context->container->get(UserService::class)->getCurrentUser();
|
||||
return $user->hasRole($role);
|
||||
}
|
||||
}
|
||||
|
||||
// Verwendung
|
||||
#[Guard(UserPolicies::isAdmin(...))]
|
||||
class AdminController {}
|
||||
|
||||
#[Guard(UserPolicies::hasRole('editor', ...))]
|
||||
class EditorController {}
|
||||
```
|
||||
|
||||
### Pattern C: Closure-Factories
|
||||
|
||||
Closure-Factories erstellen parametrisierte Closures zur Laufzeit.
|
||||
|
||||
**Vorteile:**
|
||||
- Parametrisierbar
|
||||
- Cachebar (nur Factory-Metadaten, nicht die Closure)
|
||||
- Flexibel für komplexe Logik
|
||||
|
||||
**Beispiel:**
|
||||
|
||||
```php
|
||||
// Factory-Klasse
|
||||
final readonly class Policies
|
||||
{
|
||||
public static function requirePermission(string $permission): Closure
|
||||
{
|
||||
return static function (AttributeExecutionContext $context) use ($permission): bool {
|
||||
$user = $context->container->get(UserService::class)->getCurrentUser();
|
||||
return $user->hasPermission($permission);
|
||||
};
|
||||
}
|
||||
|
||||
public static function requireAnyPermission(string ...$permissions): Closure
|
||||
{
|
||||
return static function (AttributeExecutionContext $context) use ($permissions): bool {
|
||||
$user = $context->container->get(UserService::class)->getCurrentUser();
|
||||
foreach ($permissions as $perm) {
|
||||
if ($user->hasPermission($perm)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// Verwendung
|
||||
#[Guard(Policies::requirePermission('edit_post'))]
|
||||
class PostController {}
|
||||
|
||||
#[Guard(Policies::requireAnyPermission('edit_post', 'delete_post'))]
|
||||
class PostAdminController {}
|
||||
```
|
||||
|
||||
## Verfügbare Attribute
|
||||
|
||||
### BeforeExecute
|
||||
|
||||
Führt Logik VOR Handler-Ausführung aus.
|
||||
|
||||
```php
|
||||
final readonly class UpdateUserHandler
|
||||
{
|
||||
#[BeforeExecute(Validators::validateInput(...))]
|
||||
#[BeforeExecute(RateLimiters::perUser(10, 60))]
|
||||
#[CommandHandler]
|
||||
public function handle(UpdateUserCommand $command): void
|
||||
{
|
||||
// Handler-Logic
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### AfterExecute
|
||||
|
||||
Führt Logik NACH erfolgreicher Handler-Ausführung aus.
|
||||
|
||||
```php
|
||||
final readonly class CreateOrderHandler
|
||||
{
|
||||
#[AfterExecute(Notifiers::sendConfirmation(...))]
|
||||
#[AfterExecute(AuditLoggers::logOrderCreated(...))]
|
||||
#[CommandHandler]
|
||||
public function handle(CreateOrderCommand $command): Order
|
||||
{
|
||||
// Handler-Logic
|
||||
return $order;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### OnError
|
||||
|
||||
Führt Logik bei Fehlern aus.
|
||||
|
||||
```php
|
||||
final readonly class CallExternalApiHandler
|
||||
{
|
||||
#[OnError(ErrorHandlers::logAndNotify(...))]
|
||||
#[OnError(RetryHandlers::scheduleRetry(...))]
|
||||
#[CommandHandler]
|
||||
public function handle(CallApiCommand $command): Response
|
||||
{
|
||||
// Handler-Logic
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Guard
|
||||
|
||||
Schützt Methoden/Klassen mit Guards.
|
||||
|
||||
```php
|
||||
#[Guard(PermissionGuard::class, ['edit_post'])]
|
||||
#[Guard(UserPolicies::isAdmin(...))]
|
||||
#[Guard(Policies::requirePermission('edit_post'))]
|
||||
```
|
||||
|
||||
### Validate
|
||||
|
||||
Validiert Property- oder Parameter-Werte.
|
||||
|
||||
```php
|
||||
final readonly class User
|
||||
{
|
||||
#[Validate(EmailValidator::class)]
|
||||
public string $email;
|
||||
|
||||
#[Validate(Validators::minLength(5))]
|
||||
public string $username;
|
||||
}
|
||||
```
|
||||
|
||||
### OnBoot
|
||||
|
||||
Führt Boot-Logik beim Laden einer Klasse aus.
|
||||
|
||||
```php
|
||||
#[OnBoot(ServiceRegistrar::registerServices(...))]
|
||||
final readonly class MyService {}
|
||||
```
|
||||
|
||||
## Verwendung in Command/Query Handlers
|
||||
|
||||
### Command Handler mit Attributen
|
||||
|
||||
```php
|
||||
final readonly class UpdateUserHandler
|
||||
{
|
||||
#[BeforeExecute(Validators::validateInput(...))]
|
||||
#[BeforeExecute(RateLimiters::perUser(10, 60))]
|
||||
#[AfterExecute(Notifiers::sendEmail(...))]
|
||||
#[OnError(ErrorHandlers::logAndNotify(...))]
|
||||
#[CommandHandler]
|
||||
public function handle(UpdateUserCommand $command): void
|
||||
{
|
||||
// Business-Logic hier
|
||||
// Attribute werden automatisch ausgeführt:
|
||||
// 1. BeforeExecute Attribute (vor dieser Methode)
|
||||
// 2. Handler-Ausführung
|
||||
// 3. AfterExecute Attribute (nach erfolgreicher Ausführung)
|
||||
// 4. OnError Attribute (bei Exceptions)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Query Handler mit Attributen
|
||||
|
||||
```php
|
||||
final readonly class GetUserQueryHandler
|
||||
{
|
||||
#[BeforeExecute(CacheValidators::checkCache(...))]
|
||||
#[AfterExecute(CacheStrategies::storeResult(...))]
|
||||
#[QueryHandler]
|
||||
public function handle(GetUserQuery $query): User
|
||||
{
|
||||
// Query-Logic
|
||||
return $user;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Verwendung des AttributeRunners (für manuelle Ausführung)
|
||||
|
||||
### Alle Attribute eines Typs ausführen
|
||||
|
||||
```php
|
||||
$runner = $container->get(AttributeRunner::class);
|
||||
$results = $runner->executeAttributes(Guard::class);
|
||||
```
|
||||
|
||||
### Attribute für eine Klasse ausführen
|
||||
|
||||
```php
|
||||
$className = ClassName::create('MyController');
|
||||
$results = $runner->executeForClass($className, Guard::class);
|
||||
```
|
||||
|
||||
### Attribute für eine Methode ausführen
|
||||
|
||||
```php
|
||||
$className = ClassName::create('MyController');
|
||||
$methodName = MethodName::create('edit');
|
||||
$results = $runner->executeForMethod($className, $methodName, Guard::class);
|
||||
```
|
||||
|
||||
## Cache-Kompatibilität
|
||||
|
||||
Alle drei Patterns sind vollständig cachebar:
|
||||
|
||||
- **Handler**: Nur Klassenname und Argumente werden gecacht
|
||||
- **First-Class Callables**: Nur Klassenname und Methodenname werden gecacht
|
||||
- **Closure-Factories**: Nur Factory-Klasse, Methode und Argumente werden gecacht
|
||||
|
||||
Die Closures selbst werden nicht gecacht, sondern zur Laufzeit aus den Factories erstellt.
|
||||
|
||||
## Best Practices
|
||||
|
||||
1. **Verwende Attribute auf Command/Query-Handlern**, nicht auf Controllern
|
||||
2. **Präferiere Pattern A (Handler)** für komplexe Logik die isoliert getestet werden soll
|
||||
3. **Präferiere Pattern B (First-Class Callables)** für einfache Policy-Checks
|
||||
4. **Präferiere Pattern C (Closure-Factories)** für parametrisierte Policies
|
||||
5. **Vermeide direkte Closures** in Attributen - nutze stattdessen Factories
|
||||
6. **Nutze Dependency Injection** über `AttributeExecutionContext` statt globale Services
|
||||
7. **BeforeExecute** für Validierung, Rate Limiting, etc.
|
||||
8. **AfterExecute** für Notifications, Audit Logs, Cache Updates
|
||||
9. **OnError** für Error Handling, Retry Logic, Logging
|
||||
|
||||
## Performance
|
||||
|
||||
- Callback-Metadata wird gecacht (nicht Closures)
|
||||
- Closures nur zur Laufzeit erstellt
|
||||
- Keine Reflection zur Laufzeit für bekannte Patterns
|
||||
- Handler werden über Container erstellt (unterstützt Lazy Loading)
|
||||
|
||||
## Testing
|
||||
|
||||
Alle Patterns sind testbar:
|
||||
|
||||
```php
|
||||
// Handler testen
|
||||
$handler = new PermissionGuard(['edit_post']);
|
||||
$result = $handler->check($context);
|
||||
|
||||
// Static Method testen
|
||||
$result = UserPolicies::isAdmin($context);
|
||||
|
||||
// Factory testen
|
||||
$closure = Policies::requirePermission('edit_post');
|
||||
$result = $closure($context);
|
||||
```
|
||||
|
||||
Reference in New Issue
Block a user