chore: complete update
This commit is contained in:
8
src/Framework/CommandBus/CommandBus.php
Normal file
8
src/Framework/CommandBus/CommandBus.php
Normal file
@@ -0,0 +1,8 @@
|
||||
<?php
|
||||
|
||||
namespace App\Framework\CommandBus;
|
||||
|
||||
interface CommandBus
|
||||
{
|
||||
public function dispatch(object $command): mixed;
|
||||
}
|
||||
32
src/Framework/CommandBus/CommandBusInitializer.php
Normal file
32
src/Framework/CommandBus/CommandBusInitializer.php
Normal file
@@ -0,0 +1,32 @@
|
||||
<?php
|
||||
|
||||
namespace App\Framework\CommandBus;
|
||||
|
||||
use App\Framework\Context\ExecutionContext;
|
||||
use App\Framework\DI\DefaultContainer;
|
||||
use App\Framework\DI\Initializer;
|
||||
use App\Framework\Discovery\Results\DiscoveryResults;
|
||||
|
||||
final readonly class CommandBusInitializer
|
||||
{
|
||||
public function __construct(
|
||||
private DefaultContainer $container,
|
||||
private DiscoveryResults $results,
|
||||
private ExecutionContext $executionContext,
|
||||
) {}
|
||||
|
||||
#[Initializer]
|
||||
public function __invoke(): CommandBus
|
||||
{
|
||||
$this->results->get(CommandHandler::class);
|
||||
|
||||
$handlers = [];
|
||||
foreach($this->results->get(CommandHandler::class) as $handler) {
|
||||
|
||||
$handlers[] = CommandHandlerDescriptor::fromHandlerArray($handler);
|
||||
}
|
||||
$handlersCollection = new CommandHandlersCollection(...$handlers);
|
||||
|
||||
return new DefaultCommandBus($handlersCollection, $this->container, $this->executionContext);
|
||||
}
|
||||
}
|
||||
12
src/Framework/CommandBus/CommandHandler.php
Normal file
12
src/Framework/CommandBus/CommandHandler.php
Normal file
@@ -0,0 +1,12 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Framework\CommandBus;
|
||||
|
||||
use Attribute;
|
||||
|
||||
#[Attribute(\Attribute::TARGET_METHOD)]
|
||||
final class CommandHandler
|
||||
{
|
||||
|
||||
}
|
||||
43
src/Framework/CommandBus/CommandHandlerCompiler.php
Normal file
43
src/Framework/CommandBus/CommandHandlerCompiler.php
Normal file
@@ -0,0 +1,43 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Framework\CommandBus;
|
||||
|
||||
use App\Framework\Core\AttributeCompiler;
|
||||
|
||||
final readonly class CommandHandlerCompiler implements AttributeCompiler
|
||||
{
|
||||
public function getAttributeClass(): string
|
||||
{
|
||||
return CommandHandler::class;
|
||||
}
|
||||
|
||||
public function compile(array $handlers): array
|
||||
{
|
||||
$compiledHandlers = [];
|
||||
|
||||
foreach ($handlers as $handler) {
|
||||
$className = $handler['class'];
|
||||
$methodName = $handler['method'];
|
||||
|
||||
// Ermittle den Command-Typ aus dem ersten Parameter der Methode
|
||||
$methodInfo = new \ReflectionMethod($className, $methodName);
|
||||
$parameters = $methodInfo->getParameters();
|
||||
|
||||
if (count($parameters) > 0) {
|
||||
$paramType = $parameters[0]->getType();
|
||||
if ($paramType instanceof \ReflectionNamedType) {
|
||||
$commandClass = $paramType->getName();
|
||||
|
||||
// Speichere den Handler für diesen Command-Typ
|
||||
$compiledHandlers[$commandClass] = [
|
||||
'class' => $className,
|
||||
'method' => $methodName,
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $compiledHandlers;
|
||||
}
|
||||
}
|
||||
25
src/Framework/CommandBus/CommandHandlerDescriptor.php
Normal file
25
src/Framework/CommandBus/CommandHandlerDescriptor.php
Normal file
@@ -0,0 +1,25 @@
|
||||
<?php
|
||||
|
||||
namespace App\Framework\CommandBus;
|
||||
|
||||
/**
|
||||
* Kapselt Handler-Information für einen Command
|
||||
*/
|
||||
|
||||
final readonly class CommandHandlerDescriptor
|
||||
{
|
||||
public function __construct(
|
||||
public string $class,
|
||||
public string $method,
|
||||
public string $command,
|
||||
) {}
|
||||
|
||||
public static function fromHandlerArray(array $handler): self
|
||||
{
|
||||
return new self(
|
||||
class: $handler['class'],
|
||||
method: $handler['method'],
|
||||
command: $handler['command'],
|
||||
);
|
||||
}
|
||||
}
|
||||
33
src/Framework/CommandBus/CommandHandlerMapper.php
Normal file
33
src/Framework/CommandBus/CommandHandlerMapper.php
Normal file
@@ -0,0 +1,33 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Framework\CommandBus;
|
||||
|
||||
use App\Framework\Core\AttributeMapper;
|
||||
use ReflectionMethod;
|
||||
use RuntimeException;
|
||||
|
||||
final class CommandHandlerMapper implements AttributeMapper
|
||||
{
|
||||
public function getAttributeClass(): string
|
||||
{
|
||||
return CommandHandler::class;
|
||||
}
|
||||
|
||||
public function map(object $reflectionTarget, object $attributeInstance): ?array
|
||||
{
|
||||
if (!$reflectionTarget instanceof ReflectionMethod || !$attributeInstance instanceof CommandHandler) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if(empty($reflectionTarget->getParameters()[0])) {
|
||||
throw new RuntimeException('Missing Command-Type');
|
||||
}
|
||||
|
||||
return [
|
||||
'class' => $reflectionTarget->getDeclaringClass()->getName(),
|
||||
'method' => $reflectionTarget->getName(),
|
||||
'command' => $reflectionTarget->getParameters()[0]->getType()->getName(),
|
||||
];
|
||||
}
|
||||
}
|
||||
58
src/Framework/CommandBus/CommandHandlersCollection.php
Normal file
58
src/Framework/CommandBus/CommandHandlersCollection.php
Normal file
@@ -0,0 +1,58 @@
|
||||
<?php
|
||||
|
||||
namespace App\Framework\CommandBus;
|
||||
|
||||
use ArrayIterator;
|
||||
use Countable;
|
||||
use IteratorAggregate;
|
||||
use Traversable;
|
||||
|
||||
final readonly class CommandHandlersCollection implements IteratorAggregate, Countable
|
||||
{
|
||||
private array $handlers;
|
||||
public function __construct(
|
||||
CommandHandlerDescriptor ...$handlers
|
||||
#private array $handlers = []
|
||||
){
|
||||
$handlerArray = [];
|
||||
foreach ($handlers as $handler) {
|
||||
|
||||
$handlerArray[$handler->command] = $handler;
|
||||
}
|
||||
$this->handlers = $handlerArray;
|
||||
}
|
||||
|
||||
/*private function add(string $messageClass, object $handler): void
|
||||
{
|
||||
$this->handlers[$messageClass] = $handler;
|
||||
}*/
|
||||
|
||||
/**
|
||||
* @param class-string $commandClass
|
||||
* @return CommandHandlerDescriptor|null
|
||||
*/
|
||||
public function get(string $commandClass): ?CommandHandlerDescriptor
|
||||
{
|
||||
return $this->handlers[$commandClass] ?? null;
|
||||
}
|
||||
|
||||
public function all(): array
|
||||
{
|
||||
return $this->toArray();
|
||||
}
|
||||
|
||||
public function getIterator(): Traversable
|
||||
{
|
||||
return new ArrayIterator($this->handlers);
|
||||
}
|
||||
|
||||
public function count(): int
|
||||
{
|
||||
return count($this->handlers);
|
||||
}
|
||||
|
||||
public function toArray(): array
|
||||
{
|
||||
return $this->handlers;
|
||||
}
|
||||
}
|
||||
103
src/Framework/CommandBus/DefaultCommandBus.php
Normal file
103
src/Framework/CommandBus/DefaultCommandBus.php
Normal file
@@ -0,0 +1,103 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Framework\CommandBus;
|
||||
|
||||
use App\Application\Auth\LoginUser;
|
||||
use App\Framework\CommandBus\Exceptions\NoHandlerFound;
|
||||
use App\Framework\CommandBus\Middleware\DatabaseTransactionMiddleware;
|
||||
use App\Framework\CommandBus\Middleware\PerformanceMonitoringMiddleware;
|
||||
use App\Framework\Context\ExecutionContext;
|
||||
use App\Framework\Context\ContextType;
|
||||
use App\Framework\DI\Container;
|
||||
use App\Framework\DI\DefaultContainer;
|
||||
use App\Framework\Queue\FileQueue;
|
||||
use App\Framework\Queue\Queue;
|
||||
use App\Framework\Queue\RedisQueue;
|
||||
|
||||
final readonly class DefaultCommandBus implements CommandBus
|
||||
{
|
||||
public function __construct(
|
||||
private CommandHandlersCollection $commandHandlers,
|
||||
private Container $container,
|
||||
private ExecutionContext $executionContext,
|
||||
private Queue $queue = new RedisQueue(host: 'redis'),
|
||||
private array $middlewares = [
|
||||
PerformanceMonitoringMiddleware::class,
|
||||
DatabaseTransactionMiddleware::class,
|
||||
],
|
||||
) {}
|
||||
|
||||
public function dispatch(object $command): mixed
|
||||
{
|
||||
$contextType = $this->executionContext->getType();
|
||||
error_log("CommandBus Debug: Execution context: {$contextType->value}");
|
||||
error_log("CommandBus Debug: Context metadata: " . json_encode($this->executionContext->getMetadata()));
|
||||
|
||||
// Context-basierte Queue-Entscheidung
|
||||
if ($this->shouldQueueInContext($command, $contextType)) {
|
||||
error_log("CommandBus Debug: Job wird in Queue eingereiht: " . $command::class);
|
||||
$this->queue->push($command);
|
||||
return null;
|
||||
}
|
||||
|
||||
error_log("CommandBus Debug: Job wird direkt ausgeführt: " . $command::class);
|
||||
return $this->executeCommand($command);
|
||||
}
|
||||
|
||||
private function executeCommand(object $command): mixed
|
||||
{
|
||||
$handlerExecutor = function (object $command): mixed {
|
||||
$handler = $this->commandHandlers->get($command::class);
|
||||
|
||||
if($handler === null)
|
||||
{
|
||||
throw NoHandlerFound::forCommand($command::class);
|
||||
}
|
||||
|
||||
// $handler = $this->commandHandlers[get_class($command)];
|
||||
$handlerClass = $this->container->get($handler->class);
|
||||
return $handlerClass->{$handler->method}($command);
|
||||
};
|
||||
|
||||
$pipeline = array_reduce(
|
||||
array_reverse($this->middlewares),
|
||||
function (callable $next, string $middlewareClass): callable {
|
||||
return function (object $command) use ($middlewareClass, $next): mixed {
|
||||
/** @var Middleware $middleware */
|
||||
$middleware = $this->container->get($middlewareClass);
|
||||
return $middleware->handle($command, $next);
|
||||
};
|
||||
},
|
||||
$handlerExecutor
|
||||
);
|
||||
|
||||
return $pipeline($command);
|
||||
}
|
||||
|
||||
private function shouldQueueInContext(object $command, ContextType $context): bool
|
||||
{
|
||||
$ref = new \ReflectionClass($command);
|
||||
$hasQueueAttribute = !empty($ref->getAttributes(ShouldQueue::class));
|
||||
|
||||
if (!$hasQueueAttribute) {
|
||||
return false; // Ohne #[ShouldQueue] Attribut nie queuen
|
||||
}
|
||||
|
||||
// Context-basierte Entscheidung
|
||||
return match($context) {
|
||||
ContextType::WORKER => false, // Worker: NIEMALS queuen
|
||||
ContextType::CONSOLE => false, // Artisan: meist direkt
|
||||
ContextType::TEST => false, // Tests: immer direkt
|
||||
ContextType::CLI_SCRIPT => true, // CLI Scripts: normal queuen
|
||||
ContextType::WEB => true, // Web: Standard Queue-Verhalten
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
|
||||
public function __debugInfo(): ?array
|
||||
{
|
||||
return $this->commandHandlers->toArray();
|
||||
}
|
||||
}
|
||||
16
src/Framework/CommandBus/Exceptions/NoHandlerFound.php
Normal file
16
src/Framework/CommandBus/Exceptions/NoHandlerFound.php
Normal file
@@ -0,0 +1,16 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Framework\CommandBus\Exceptions;
|
||||
|
||||
use App\Framework\Exception\FrameworkException;
|
||||
|
||||
final class NoHandlerFound extends FrameworkException
|
||||
{
|
||||
public static function forCommand(string $commandClass): self
|
||||
{
|
||||
return new self(
|
||||
"No handler found for command: $commandClass"
|
||||
);
|
||||
}
|
||||
}
|
||||
8
src/Framework/CommandBus/Middleware.php
Normal file
8
src/Framework/CommandBus/Middleware.php
Normal file
@@ -0,0 +1,8 @@
|
||||
<?php
|
||||
|
||||
namespace App\Framework\CommandBus;
|
||||
|
||||
interface Middleware
|
||||
{
|
||||
public function handle(object $command, callable $next): mixed;
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
<?php
|
||||
|
||||
namespace App\Framework\CommandBus\Middleware;
|
||||
|
||||
use App\Framework\CommandBus\Middleware;
|
||||
|
||||
final readonly class DatabaseTransactionMiddleware implements Middleware
|
||||
{
|
||||
public function __construct() {}
|
||||
|
||||
public function handle(object $command, callable $next): mixed
|
||||
{
|
||||
#var_dump('DatabaseTransactionMiddleware');
|
||||
|
||||
// Wenn die Validierung erfolgreich ist, wird der nächste Schritt aufgerufen
|
||||
return $next($command);
|
||||
}
|
||||
|
||||
}
|
||||
26
src/Framework/CommandBus/Middleware/LoggingMiddleware.php
Normal file
26
src/Framework/CommandBus/Middleware/LoggingMiddleware.php
Normal file
@@ -0,0 +1,26 @@
|
||||
<?php
|
||||
|
||||
namespace App\Framework\CommandBus\Middleware;
|
||||
|
||||
use App\Framework\CommandBus\Middleware;
|
||||
use App\Framework\Logging\DefaultLogger;
|
||||
use App\Framework\Logging\Logger;
|
||||
|
||||
final readonly class LoggingMiddleware implements Middleware
|
||||
{
|
||||
public function __construct(
|
||||
private Logger $logger
|
||||
) {}
|
||||
|
||||
public function handle(object $command, callable $next): mixed
|
||||
{
|
||||
$commandClass = get_class($command);
|
||||
$this->logger->info("Starting command: $commandClass");
|
||||
|
||||
$result = $next($command);
|
||||
|
||||
$this->logger->info("Finished command: $commandClass");
|
||||
|
||||
return $result;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
<?php
|
||||
|
||||
namespace App\Framework\CommandBus\Middleware;
|
||||
|
||||
use App\Framework\CommandBus\Middleware;
|
||||
use App\Framework\Performance\PerformanceCategory;
|
||||
use App\Framework\Performance\PerformanceMeter;
|
||||
|
||||
final readonly class PerformanceMonitoringMiddleware implements Middleware
|
||||
{
|
||||
public function __construct(
|
||||
private PerformanceMeter $meter
|
||||
) {}
|
||||
|
||||
public function handle(object $command, callable $next): mixed
|
||||
{
|
||||
$this->meter->startMeasure($command::class, PerformanceCategory::SYSTEM);
|
||||
|
||||
$result = $next($command);
|
||||
|
||||
$this->meter->endMeasure($command::class);
|
||||
|
||||
return $result;
|
||||
}
|
||||
}
|
||||
9
src/Framework/CommandBus/ShouldQueue.php
Normal file
9
src/Framework/CommandBus/ShouldQueue.php
Normal file
@@ -0,0 +1,9 @@
|
||||
<?php
|
||||
|
||||
namespace App\Framework\CommandBus;
|
||||
|
||||
#[\Attribute(\Attribute::TARGET_CLASS)]
|
||||
class ShouldQueue
|
||||
{
|
||||
// Hier könnten noch Optionen rein (z. B. Queue-Name, Delay …)
|
||||
}
|
||||
Reference in New Issue
Block a user