Some checks failed
Deploy Application / deploy (push) Has been cancelled
276 lines
11 KiB
PHP
276 lines
11 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace App\Framework\Console\Commands;
|
|
|
|
use App\Framework\Console\CommandGroup;
|
|
use App\Framework\Console\ConsoleColor;
|
|
use App\Framework\Console\ConsoleCommand;
|
|
use App\Framework\Console\ConsoleInput;
|
|
use App\Framework\Console\ConsoleOutputInterface;
|
|
use App\Framework\Console\ExitCode;
|
|
use App\Framework\Core\ValueObjects\ReturnTypeValue;
|
|
use App\Framework\DI\Exceptions\InitializerCycleException;
|
|
use App\Framework\DI\Initializer;
|
|
use App\Framework\DI\InitializerDependencyAnalyzer;
|
|
use App\Framework\DI\InitializerDependencyGraph;
|
|
use App\Framework\Discovery\Results\DiscoveryRegistry;
|
|
use App\Framework\Discovery\ValueObjects\DiscoveredAttribute;
|
|
use App\Framework\Reflection\ReflectionService;
|
|
|
|
#[CommandGroup(
|
|
name: 'Framework',
|
|
description: 'Framework diagnostics and health checks',
|
|
icon: '🔧',
|
|
priority: 90
|
|
)]
|
|
final readonly class InitializersCheckCommand
|
|
{
|
|
public function __construct(
|
|
private DiscoveryRegistry $discoveryRegistry,
|
|
private ReflectionService $reflectionService,
|
|
private InitializerDependencyAnalyzer $dependencyAnalyzer,
|
|
private ConsoleOutputInterface $output
|
|
) {
|
|
}
|
|
|
|
#[ConsoleCommand('initializers:check', 'Check all initializers for problems (dependencies, cycles, etc.)')]
|
|
public function check(ConsoleInput $input, ConsoleOutputInterface $output): ExitCode
|
|
{
|
|
$jsonOutput = $input->hasOption('json');
|
|
|
|
$output->writeLine('Checking initializers...', ConsoleColor::BRIGHT_CYAN);
|
|
$output->newLine();
|
|
|
|
$initializerResults = $this->discoveryRegistry->attributes->get(Initializer::class);
|
|
$totalInitializers = count($initializerResults);
|
|
|
|
if ($totalInitializers === 0) {
|
|
$output->writeLine('⚠️ No initializers found in discovery registry.', ConsoleColor::YELLOW);
|
|
return ExitCode::FAILURE;
|
|
}
|
|
|
|
$output->writeLine("Found {$totalInitializers} initializer(s)", ConsoleColor::WHITE);
|
|
$output->newLine();
|
|
|
|
// Build dependency graph
|
|
$dependencyGraph = new InitializerDependencyGraph(
|
|
$this->reflectionService,
|
|
$this->dependencyAnalyzer
|
|
);
|
|
|
|
$problems = [];
|
|
$warnings = [];
|
|
$info = [];
|
|
|
|
// Phase 1: Analyze each initializer
|
|
foreach ($initializerResults as $discoveredAttribute) {
|
|
$initializer = $discoveredAttribute->createAttributeInstance();
|
|
if ($initializer === null) {
|
|
$problems[] = [
|
|
'type' => 'error',
|
|
'message' => "Failed to instantiate Initializer attribute",
|
|
'class' => (string) $discoveredAttribute->className,
|
|
'method' => (string) ($discoveredAttribute->methodName ?? 'unknown'),
|
|
];
|
|
continue;
|
|
}
|
|
|
|
$methodName = $discoveredAttribute->methodName ?? \App\Framework\Core\ValueObjects\MethodName::invoke();
|
|
$returnTypeString = $discoveredAttribute->additionalData['return'] ?? null;
|
|
|
|
try {
|
|
$returnType = ReturnTypeValue::fromString($returnTypeString, $discoveredAttribute->className);
|
|
|
|
// Skip setup initializers (void return)
|
|
if ($returnType->hasNoReturn()) {
|
|
continue;
|
|
}
|
|
|
|
$concreteReturnType = $returnType->isSelf()
|
|
? $returnType->toClassName()
|
|
: $returnType->toClassName();
|
|
|
|
// Analyze dependencies
|
|
$analysis = $this->dependencyAnalyzer->analyze($discoveredAttribute->className->getFullyQualified());
|
|
|
|
// Check for missing dependencies
|
|
$missingDeps = [];
|
|
foreach (array_merge($analysis['constructorDeps'], $analysis['containerGetDeps']) as $dep) {
|
|
// Skip Container itself
|
|
if (\App\Framework\DI\InitializerDependencyAnalyzer::isContainerClass($dep)) {
|
|
continue;
|
|
}
|
|
|
|
// Check if dependency has an initializer or is a concrete class
|
|
if (!class_exists($dep) && !interface_exists($dep)) {
|
|
$missingDeps[] = $dep;
|
|
}
|
|
}
|
|
|
|
if (!empty($missingDeps)) {
|
|
$warnings[] = [
|
|
'type' => 'warning',
|
|
'message' => "Potential missing dependencies",
|
|
'class' => (string) $discoveredAttribute->className,
|
|
'method' => (string) $methodName,
|
|
'return_type' => $concreteReturnType->getFullyQualified(),
|
|
'missing_dependencies' => $missingDeps,
|
|
];
|
|
}
|
|
|
|
// Add to graph
|
|
// Extrahiere explizite Dependencies und Priority aus additionalData
|
|
$explicitDependencies = $discoveredAttribute->additionalData['dependencies'] ?? null;
|
|
$priority = (int) ($discoveredAttribute->additionalData['priority'] ?? 0);
|
|
|
|
$dependencyGraph->addInitializer(
|
|
$concreteReturnType->getFullyQualified(),
|
|
$discoveredAttribute->className,
|
|
$methodName,
|
|
$explicitDependencies,
|
|
$priority
|
|
);
|
|
|
|
$info[] = [
|
|
'type' => 'info',
|
|
'class' => (string) $discoveredAttribute->className,
|
|
'method' => (string) $methodName,
|
|
'return_type' => $concreteReturnType->getFullyQualified(),
|
|
'dependencies' => array_merge($analysis['constructorDeps'], $analysis['containerGetDeps']),
|
|
];
|
|
} catch (\Throwable $e) {
|
|
$problems[] = [
|
|
'type' => 'error',
|
|
'message' => "Failed to analyze initializer: {$e->getMessage()}",
|
|
'class' => (string) $discoveredAttribute->className,
|
|
'method' => (string) $methodName,
|
|
'exception' => get_class($e),
|
|
];
|
|
}
|
|
}
|
|
|
|
// Phase 2: Check for cycles
|
|
try {
|
|
$executionOrder = $dependencyGraph->getExecutionOrder();
|
|
} catch (InitializerCycleException $e) {
|
|
$cycles = $e->getCycles();
|
|
$paths = $e->getDependencyPaths();
|
|
|
|
foreach ($cycles as $index => $cycle) {
|
|
$path = $paths[$index] ?? $cycle;
|
|
$problems[] = [
|
|
'type' => 'error',
|
|
'message' => 'Circular dependency detected',
|
|
'cycle' => $cycle,
|
|
'dependency_path' => $path,
|
|
];
|
|
}
|
|
}
|
|
|
|
// Phase 3: Output results
|
|
if ($jsonOutput) {
|
|
$this->outputJson($output, [
|
|
'total' => $totalInitializers,
|
|
'problems' => $problems,
|
|
'warnings' => $warnings,
|
|
'info' => $info,
|
|
]);
|
|
} else {
|
|
$this->outputHumanReadable($output, $problems, $warnings, $info, $dependencyGraph);
|
|
}
|
|
|
|
// Return exit code based on problems
|
|
if (!empty($problems)) {
|
|
return ExitCode::FAILURE;
|
|
}
|
|
|
|
if (!empty($warnings)) {
|
|
return ExitCode::PARTIAL_SUCCESS;
|
|
}
|
|
|
|
$output->writeLine('✅ All initializers are valid', ConsoleColor::GREEN);
|
|
return ExitCode::SUCCESS;
|
|
}
|
|
|
|
private function outputHumanReadable(
|
|
ConsoleOutputInterface $output,
|
|
array $problems,
|
|
array $warnings,
|
|
array $info,
|
|
InitializerDependencyGraph $graph
|
|
): void {
|
|
// Show problems
|
|
if (!empty($problems)) {
|
|
$output->writeLine('❌ Problems:', ConsoleColor::BRIGHT_RED);
|
|
foreach ($problems as $problem) {
|
|
$output->writeLine(" • {$problem['message']}", ConsoleColor::RED);
|
|
if (isset($problem['class'])) {
|
|
$output->writeLine(" Class: {$problem['class']}", ConsoleColor::GRAY);
|
|
}
|
|
if (isset($problem['cycle'])) {
|
|
$cycleStr = implode(' → ', $problem['cycle']) . ' → ' . ($problem['cycle'][0] ?? '');
|
|
$output->writeLine(" Cycle: {$cycleStr}", ConsoleColor::GRAY);
|
|
}
|
|
if (isset($problem['dependency_path'])) {
|
|
$pathStr = implode(' → ', $problem['dependency_path']);
|
|
$output->writeLine(" Path: {$pathStr}", ConsoleColor::GRAY);
|
|
}
|
|
}
|
|
$output->newLine();
|
|
}
|
|
|
|
// Show warnings
|
|
if (!empty($warnings)) {
|
|
$output->writeLine('⚠️ Warnings:', ConsoleColor::BRIGHT_YELLOW);
|
|
foreach ($warnings as $warning) {
|
|
$output->writeLine(" • {$warning['message']}", ConsoleColor::YELLOW);
|
|
$output->writeLine(" Class: {$warning['class']}::{$warning['method']}", ConsoleColor::GRAY);
|
|
if (isset($warning['missing_dependencies'])) {
|
|
$depsStr = implode(', ', $warning['missing_dependencies']);
|
|
$output->writeLine(" Missing: {$depsStr}", ConsoleColor::GRAY);
|
|
}
|
|
}
|
|
$output->newLine();
|
|
}
|
|
|
|
// Show summary
|
|
$output->writeLine('📊 Summary:', ConsoleColor::BRIGHT_CYAN);
|
|
$output->writeLine(" Total initializers: " . count($info), ConsoleColor::WHITE);
|
|
$output->writeLine(" Problems: " . count($problems), ConsoleColor::RED);
|
|
$output->writeLine(" Warnings: " . count($warnings), ConsoleColor::YELLOW);
|
|
|
|
// Show dependency graph visualization
|
|
$output->newLine();
|
|
$output->writeLine('🔗 Dependency Graph:', ConsoleColor::BRIGHT_CYAN);
|
|
try {
|
|
$executionOrder = $graph->getExecutionOrder();
|
|
foreach ($executionOrder as $index => $returnType) {
|
|
$node = $graph->getNode($returnType);
|
|
if ($node === null) {
|
|
continue;
|
|
}
|
|
|
|
$deps = empty($node->dependencies)
|
|
? 'no dependencies'
|
|
: 'depends on: ' . implode(', ', $node->dependencies);
|
|
|
|
$output->writeLine(
|
|
sprintf(' %d. %s', $index + 1, $node->toString()),
|
|
ConsoleColor::WHITE
|
|
);
|
|
}
|
|
} catch (InitializerCycleException $e) {
|
|
$output->writeLine(' ⚠️ Cannot show graph due to circular dependencies', ConsoleColor::YELLOW);
|
|
}
|
|
}
|
|
|
|
private function outputJson(ConsoleOutputInterface $output, array $data): void
|
|
{
|
|
$json = json_encode($data, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES);
|
|
$output->writeLine($json);
|
|
}
|
|
}
|
|
|