# 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); ```