Some checks failed
Deploy Application / deploy (push) Has been cancelled
349 lines
12 KiB
PHP
349 lines
12 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace Tests\Framework\CommandBus;
|
|
|
|
use App\Framework\Attributes\AfterExecute;
|
|
use App\Framework\Attributes\BeforeExecute;
|
|
use App\Framework\Attributes\Execution\AttributeExecutionInitializer;
|
|
use App\Framework\Attributes\Execution\AttributeRunner;
|
|
use App\Framework\Attributes\Execution\CallbackExecutor;
|
|
use App\Framework\Attributes\Execution\HandlerAttributeExecutor;
|
|
use App\Framework\Attributes\OnError;
|
|
use App\Framework\CommandBus\CommandBus;
|
|
use App\Framework\CommandBus\CommandHandler;
|
|
use App\Framework\CommandBus\CommandHandlersCollection;
|
|
use App\Framework\CommandBus\CommandHandlerDescriptor;
|
|
use App\Framework\CommandBus\DefaultCommandBus;
|
|
use App\Framework\Context\ContextType;
|
|
use App\Framework\Context\ExecutionContext;
|
|
use App\Framework\DI\Container;
|
|
use App\Framework\DI\DefaultContainer;
|
|
use App\Framework\DI\MethodInvoker;
|
|
use App\Framework\Discovery\Results\AttributeRegistry;
|
|
use App\Framework\Discovery\Results\DiscoveryRegistry;
|
|
use App\Framework\Discovery\ValueObjects\AttributeTarget;
|
|
use App\Framework\Discovery\ValueObjects\DiscoveredAttribute;
|
|
use App\Framework\Logging\Logger;
|
|
use App\Framework\Performance\Contracts\PerformanceCollectorInterface;
|
|
use App\Framework\Performance\PerformanceCategory;
|
|
use App\Framework\Performance\PerformanceMetric;
|
|
use App\Framework\Queue\Queue;
|
|
use App\Framework\Reflection\SimpleReflectionService;
|
|
use PHPUnit\Framework\TestCase;
|
|
|
|
final class AttributeExecutionIntegrationTest extends TestCase
|
|
{
|
|
private Container $container;
|
|
private CommandBus $commandBus;
|
|
private \ArrayObject $beforeExecuted;
|
|
private \ArrayObject $afterExecuted;
|
|
private \ArrayObject $onErrorExecuted;
|
|
|
|
protected function setUp(): void
|
|
{
|
|
$this->container = new DefaultContainer();
|
|
$this->container->instance(MethodInvoker::class, new MethodInvoker(
|
|
$this->container,
|
|
new SimpleReflectionService()
|
|
));
|
|
|
|
// Mock Logger
|
|
$logger = $this->createMock(Logger::class);
|
|
$this->container->instance(Logger::class, $logger);
|
|
|
|
// Mock Queue
|
|
$queue = $this->createMock(Queue::class);
|
|
$this->container->instance(Queue::class, $queue);
|
|
|
|
// Mock PerformanceCollectorInterface (optional - wird nur verwendet wenn verfügbar)
|
|
// Wir registrieren es nicht, damit die Middleware es als optional behandelt
|
|
|
|
// Mock ExecutionContext
|
|
$executionContext = new ExecutionContext(ContextType::TEST);
|
|
$this->container->instance(ExecutionContext::class, $executionContext);
|
|
|
|
// Reset execution tracking
|
|
$this->beforeExecuted = new \ArrayObject();
|
|
$this->afterExecuted = new \ArrayObject();
|
|
$this->onErrorExecuted = new \ArrayObject();
|
|
}
|
|
|
|
public function testBeforeExecuteAttributeIsExecuted(): void
|
|
{
|
|
$command = new TestCommand('test');
|
|
|
|
$handler = new TestHandlerWithBeforeAttribute($this->beforeExecuted);
|
|
|
|
$this->setupCommandBus($handler::class, 'handle', TestCommand::class);
|
|
|
|
$this->commandBus->dispatch($command);
|
|
|
|
$this->assertContains('before', (array)$this->beforeExecuted, 'BeforeExecute should have been executed');
|
|
}
|
|
|
|
public function testAfterExecuteAttributeIsExecuted(): void
|
|
{
|
|
$command = new TestCommand('test');
|
|
|
|
$handler = new TestHandlerWithAfterAttribute($this->afterExecuted);
|
|
|
|
$this->setupCommandBus($handler::class, 'handle', TestCommand::class);
|
|
|
|
$result = $this->commandBus->dispatch($command);
|
|
|
|
$this->assertContains('after', (array)$this->afterExecuted);
|
|
$this->assertEquals('result', $result);
|
|
}
|
|
|
|
public function testOnErrorAttributeIsExecuted(): void
|
|
{
|
|
$command = new TestCommand('test');
|
|
|
|
$handler = new TestHandlerWithOnErrorAttribute($this->onErrorExecuted);
|
|
|
|
$this->setupCommandBus($handler::class, 'handle', TestCommand::class);
|
|
|
|
try {
|
|
$this->commandBus->dispatch($command);
|
|
$this->fail('Expected exception was not thrown');
|
|
} catch (\RuntimeException $e) {
|
|
$this->assertEquals('Test error', $e->getMessage());
|
|
}
|
|
|
|
$this->assertContains('error', (array)$this->onErrorExecuted);
|
|
}
|
|
|
|
private function setupCommandBus(string $handlerClass, string $handlerMethod, string $commandClass): void
|
|
{
|
|
// Erstelle Handler-Descriptor
|
|
$handlerDescriptor = new CommandHandlerDescriptor(
|
|
class: $handlerClass,
|
|
method: $handlerMethod,
|
|
command: $commandClass
|
|
);
|
|
|
|
$handlersCollection = new CommandHandlersCollection($handlerDescriptor);
|
|
$this->container->instance(CommandHandlersCollection::class, $handlersCollection);
|
|
|
|
// Erstelle DiscoveryRegistry mit Handler-Attribut
|
|
$discoveryRegistry = new DiscoveryRegistry(
|
|
attributes: new AttributeRegistry()
|
|
);
|
|
|
|
// Füge CommandHandler-Attribut hinzu
|
|
$commandHandlerAttribute = new DiscoveredAttribute(
|
|
className: \App\Framework\Core\ValueObjects\ClassName::create($handlerClass),
|
|
attributeClass: CommandHandler::class,
|
|
target: AttributeTarget::METHOD,
|
|
methodName: \App\Framework\Core\ValueObjects\MethodName::create($handlerMethod),
|
|
arguments: [],
|
|
additionalData: [
|
|
'class' => $handlerClass,
|
|
'method' => $handlerMethod,
|
|
'command' => $commandClass,
|
|
]
|
|
);
|
|
$discoveryRegistry->attributes->add(CommandHandler::class, $commandHandlerAttribute);
|
|
|
|
// Entdecke BeforeExecute, AfterExecute, OnError Attribute für den Handler
|
|
$reflectionClass = new \ReflectionClass($handlerClass);
|
|
$reflectionMethod = $reflectionClass->getMethod($handlerMethod);
|
|
|
|
foreach ($reflectionMethod->getAttributes(BeforeExecute::class) as $attribute) {
|
|
$beforeAttribute = new DiscoveredAttribute(
|
|
className: \App\Framework\Core\ValueObjects\ClassName::create($handlerClass),
|
|
attributeClass: BeforeExecute::class,
|
|
target: AttributeTarget::METHOD,
|
|
methodName: \App\Framework\Core\ValueObjects\MethodName::create($handlerMethod),
|
|
arguments: $attribute->getArguments(),
|
|
additionalData: []
|
|
);
|
|
$discoveryRegistry->attributes->add(BeforeExecute::class, $beforeAttribute);
|
|
}
|
|
|
|
foreach ($reflectionMethod->getAttributes(AfterExecute::class) as $attribute) {
|
|
$afterAttribute = new DiscoveredAttribute(
|
|
className: \App\Framework\Core\ValueObjects\ClassName::create($handlerClass),
|
|
attributeClass: AfterExecute::class,
|
|
target: AttributeTarget::METHOD,
|
|
methodName: \App\Framework\Core\ValueObjects\MethodName::create($handlerMethod),
|
|
arguments: $attribute->getArguments(),
|
|
additionalData: []
|
|
);
|
|
$discoveryRegistry->attributes->add(AfterExecute::class, $afterAttribute);
|
|
}
|
|
|
|
foreach ($reflectionMethod->getAttributes(OnError::class) as $attribute) {
|
|
$onErrorAttribute = new DiscoveredAttribute(
|
|
className: \App\Framework\Core\ValueObjects\ClassName::create($handlerClass),
|
|
attributeClass: OnError::class,
|
|
target: AttributeTarget::METHOD,
|
|
methodName: \App\Framework\Core\ValueObjects\MethodName::create($handlerMethod),
|
|
arguments: $attribute->getArguments(),
|
|
additionalData: []
|
|
);
|
|
$discoveryRegistry->attributes->add(OnError::class, $onErrorAttribute);
|
|
}
|
|
|
|
$this->container->instance(\App\Framework\Discovery\Results\DiscoveryRegistry::class, $discoveryRegistry);
|
|
|
|
// Initialisiere Attribute Execution System
|
|
$callbackExecutor = new CallbackExecutor($this->container);
|
|
$this->container->instance(CallbackExecutor::class, $callbackExecutor);
|
|
|
|
$attributeRunner = new AttributeRunner(
|
|
discoveryRegistry: $discoveryRegistry,
|
|
container: $this->container,
|
|
callbackExecutor: $callbackExecutor
|
|
);
|
|
$this->container->instance(AttributeRunner::class, $attributeRunner);
|
|
|
|
// Registriere Test-Handler im Container
|
|
$handlerInstance = new $handlerClass(...$this->getHandlerDependencies($handlerClass));
|
|
$this->container->instance($handlerClass, $handlerInstance);
|
|
|
|
// Registriere Test-Attribute-Handler mit korrekten Referenzen
|
|
// WICHTIG: Wir müssen die ArrayObjects direkt übergeben, nicht über Closures
|
|
if (str_contains($handlerClass, 'Before')) {
|
|
$this->container->instance(TestBeforeHandler::class, new TestBeforeHandler($this->beforeExecuted));
|
|
}
|
|
if (str_contains($handlerClass, 'After')) {
|
|
$this->container->instance(TestAfterHandler::class, new TestAfterHandler($this->afterExecuted));
|
|
}
|
|
if (str_contains($handlerClass, 'OnError')) {
|
|
$this->container->instance(TestOnErrorHandler::class, new TestOnErrorHandler($this->onErrorExecuted));
|
|
}
|
|
|
|
$handlerAttributeExecutor = new HandlerAttributeExecutor(
|
|
discoveryRegistry: $discoveryRegistry,
|
|
container: $this->container,
|
|
attributeRunner: $attributeRunner
|
|
);
|
|
$this->container->instance(HandlerAttributeExecutor::class, $handlerAttributeExecutor);
|
|
|
|
// Erstelle CommandBus
|
|
$executionContext = $this->container->get(ExecutionContext::class);
|
|
$queue = $this->container->get(Queue::class);
|
|
$logger = $this->container->get(Logger::class);
|
|
|
|
$this->commandBus = new DefaultCommandBus(
|
|
commandHandlers: $handlersCollection,
|
|
container: $this->container,
|
|
executionContext: $executionContext,
|
|
queue: $queue,
|
|
logger: $logger
|
|
);
|
|
}
|
|
|
|
/**
|
|
* @return array<mixed>
|
|
*/
|
|
private function getHandlerDependencies(string $handlerClass): array
|
|
{
|
|
if (str_contains($handlerClass, 'Before')) {
|
|
return [$this->beforeExecuted];
|
|
}
|
|
if (str_contains($handlerClass, 'After')) {
|
|
return [$this->afterExecuted];
|
|
}
|
|
if (str_contains($handlerClass, 'OnError')) {
|
|
return [$this->onErrorExecuted];
|
|
}
|
|
return [];
|
|
}
|
|
}
|
|
|
|
// Test Command
|
|
final readonly class TestCommand
|
|
{
|
|
public function __construct(
|
|
public string $data
|
|
) {
|
|
}
|
|
}
|
|
|
|
// Test Handler mit BeforeExecute Attribute
|
|
final class TestHandlerWithBeforeAttribute
|
|
{
|
|
public function __construct(
|
|
private \ArrayObject $beforeExecuted
|
|
) {}
|
|
|
|
#[BeforeExecute(TestBeforeHandler::class)]
|
|
#[CommandHandler]
|
|
public function handle(TestCommand $command): void
|
|
{
|
|
// Handler logic
|
|
}
|
|
}
|
|
|
|
// Test Handler mit AfterExecute Attribute
|
|
final class TestHandlerWithAfterAttribute
|
|
{
|
|
public function __construct(
|
|
private \ArrayObject $afterExecuted
|
|
) {}
|
|
|
|
#[AfterExecute(TestAfterHandler::class)]
|
|
#[CommandHandler]
|
|
public function handle(TestCommand $command): string
|
|
{
|
|
return 'result';
|
|
}
|
|
}
|
|
|
|
// Test Handler mit OnError Attribute
|
|
final class TestHandlerWithOnErrorAttribute
|
|
{
|
|
public function __construct(
|
|
private \ArrayObject $onErrorExecuted
|
|
) {}
|
|
|
|
#[OnError(TestOnErrorHandler::class)]
|
|
#[CommandHandler]
|
|
public function handle(TestCommand $command): void
|
|
{
|
|
throw new \RuntimeException('Test error');
|
|
}
|
|
}
|
|
|
|
// Test Handler-Klassen für Attribute
|
|
final class TestBeforeHandler
|
|
{
|
|
public function __construct(
|
|
private \ArrayObject $beforeExecuted
|
|
) {}
|
|
|
|
public function __invoke($ctx): void
|
|
{
|
|
$this->beforeExecuted[] = 'before';
|
|
}
|
|
}
|
|
|
|
final class TestAfterHandler
|
|
{
|
|
public function __construct(
|
|
private \ArrayObject $afterExecuted
|
|
) {}
|
|
|
|
public function __invoke($ctx): void
|
|
{
|
|
$this->afterExecuted[] = 'after';
|
|
}
|
|
}
|
|
|
|
final class TestOnErrorHandler
|
|
{
|
|
public function __construct(
|
|
private \ArrayObject $onErrorExecuted
|
|
) {}
|
|
|
|
public function __invoke($ctx): void
|
|
{
|
|
$this->onErrorExecuted[] = 'error';
|
|
}
|
|
}
|
|
|