chore: complete update

This commit is contained in:
2025-07-17 16:24:20 +02:00
parent 899227b0a4
commit 64a7051137
1300 changed files with 85570 additions and 2756 deletions

View File

@@ -0,0 +1,8 @@
<?php
namespace App\Framework\CommandBus;
interface CommandBus
{
public function dispatch(object $command): mixed;
}

View 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);
}
}

View File

@@ -0,0 +1,12 @@
<?php
declare(strict_types=1);
namespace App\Framework\CommandBus;
use Attribute;
#[Attribute(\Attribute::TARGET_METHOD)]
final class CommandHandler
{
}

View 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;
}
}

View 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'],
);
}
}

View 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(),
];
}
}

View 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;
}
}

View 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();
}
}

View 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"
);
}
}

View File

@@ -0,0 +1,8 @@
<?php
namespace App\Framework\CommandBus;
interface Middleware
{
public function handle(object $command, callable $next): mixed;
}

View File

@@ -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);
}
}

View 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;
}
}

View File

@@ -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;
}
}

View 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 …)
}