feat(Production): Complete production deployment infrastructure

- Add comprehensive health check system with multiple endpoints
- Add Prometheus metrics endpoint
- Add production logging configurations (5 strategies)
- Add complete deployment documentation suite:
  * QUICKSTART.md - 30-minute deployment guide
  * DEPLOYMENT_CHECKLIST.md - Printable verification checklist
  * DEPLOYMENT_WORKFLOW.md - Complete deployment lifecycle
  * PRODUCTION_DEPLOYMENT.md - Comprehensive technical reference
  * production-logging.md - Logging configuration guide
  * ANSIBLE_DEPLOYMENT.md - Infrastructure as Code automation
  * README.md - Navigation hub
  * DEPLOYMENT_SUMMARY.md - Executive summary
- Add deployment scripts and automation
- Add DEPLOYMENT_PLAN.md - Concrete plan for immediate deployment
- Update README with production-ready features

All production infrastructure is now complete and ready for deployment.
This commit is contained in:
2025-10-25 19:18:37 +02:00
parent caa85db796
commit fc3d7e6357
83016 changed files with 378904 additions and 20919 deletions

View File

@@ -16,7 +16,8 @@ final readonly class DependencyAnalysisResult
private DependencyGraph $graph,
private array $circularDependencies,
private array $statistics
) {}
) {
}
/**
* Get the dependency graph
@@ -51,7 +52,7 @@ final readonly class DependencyAnalysisResult
*/
public function hasCircularDependencies(): bool
{
return !empty($this->circularDependencies);
return ! empty($this->circularDependencies);
}
/**
@@ -77,4 +78,4 @@ final readonly class DependencyAnalysisResult
'graph' => $this->graph->toArray(),
];
}
}
}

View File

@@ -10,15 +10,13 @@ use App\Framework\Discovery\ValueObjects\DependencyGraph;
use App\Framework\Discovery\ValueObjects\DependencyNode;
use App\Framework\Discovery\ValueObjects\DependencyRelation;
use App\Framework\Discovery\ValueObjects\DependencyType;
use App\Framework\Reflection\ReflectionProvider;
use App\Framework\Logging\Logger;
use App\Framework\Logging\ValueObjects\LogContext;
use App\Framework\Reflection\ReflectionProvider;
use App\Framework\Reflection\WrappedReflectionClass;
use ReflectionClass;
use ReflectionMethod;
use ReflectionParameter;
use ReflectionProperty;
use ReflectionException;
use ReflectionMethod;
use ReflectionProperty;
use Throwable;
final readonly class DependencyAnalyzer
@@ -26,7 +24,8 @@ final readonly class DependencyAnalyzer
public function __construct(
private ReflectionProvider $reflectionProvider,
private ?Logger $logger = null
) {}
) {
}
/**
* Analyze dependencies for a list of classes
@@ -373,13 +372,13 @@ final readonly class DependencyAnalyzer
}
}
if (!empty($highComplexityNodes)) {
if (! empty($highComplexityNodes)) {
$recommendations['high_complexity'] = $highComplexityNodes;
}
// Check for circular dependencies
$circularDependencies = $graph->findCircularDependencies();
if (!empty($circularDependencies)) {
if (! empty($circularDependencies)) {
$recommendations['circular_dependencies'] = [
'count' => count($circularDependencies),
'cycles' => $circularDependencies,
@@ -389,9 +388,9 @@ final readonly class DependencyAnalyzer
// Check for highly coupled classes
$highDependencyNodes = $graph->getHighestDependencyNodes(5);
if (!empty($highDependencyNodes)) {
if (! empty($highDependencyNodes)) {
$recommendations['high_dependencies'] = array_map(
fn(DependencyNode $node) => [
fn (DependencyNode $node) => [
'class' => $node->getClassName()->getShortName(),
'dependency_count' => $node->getDependencyCount(),
'suggestion' => 'Consider using dependency injection or factory patterns',
@@ -403,12 +402,12 @@ final readonly class DependencyAnalyzer
// Check for unused classes (leaf nodes with no dependents)
$unusedClasses = array_filter(
$graph->getLeafNodes(),
fn(DependencyNode $node) => $node->getDependentCount() === 0
fn (DependencyNode $node) => $node->getDependentCount() === 0
);
if (!empty($unusedClasses)) {
if (! empty($unusedClasses)) {
$recommendations['potentially_unused'] = array_map(
fn(DependencyNode $node) => [
fn (DependencyNode $node) => [
'class' => $node->getClassName()->getShortName(),
'suggestion' => 'Consider removing if truly unused',
],
@@ -433,24 +432,26 @@ final readonly class DependencyAnalyzer
if ($type instanceof \ReflectionUnionType) {
$types = [];
foreach ($type->getTypes() as $subType) {
if ($subType instanceof \ReflectionNamedType && !$subType->isBuiltin()) {
if ($subType instanceof \ReflectionNamedType && ! $subType->isBuiltin()) {
$types[] = $subType->getName();
}
}
return $types;
}
if ($type instanceof \ReflectionIntersectionType) {
$types = [];
foreach ($type->getTypes() as $subType) {
if ($subType instanceof \ReflectionNamedType && !$subType->isBuiltin()) {
if ($subType instanceof \ReflectionNamedType && ! $subType->isBuiltin()) {
$types[] = $subType->getName();
}
}
return $types;
}
// Fallback for unknown types
return [];
}
}
}

View File

@@ -5,12 +5,15 @@ declare(strict_types=1);
namespace App\Framework\Discovery\Commands;
use App\Framework\Cache\Cache;
use App\Framework\Cache\CacheKey;
use App\Framework\Console\ConsoleCommand;
use App\Framework\Console\ConsoleInput;
use App\Framework\Console\ConsoleOutput;
use App\Framework\Core\PathProvider;
use App\Framework\Core\ValueObjects\Duration;
use App\Framework\DateTime\Clock;
use App\Framework\Discovery\Storage\DiscoveryCacheManager;
use App\Framework\Filesystem\Storage;
/**
* Unified console commands for clearing discovery-related caches
@@ -27,7 +30,8 @@ final readonly class ClearDiscoveryCache
private ConsoleOutput $output,
private Cache $cache,
private Clock $clock,
private PathProvider $pathProvider
private PathProvider $pathProvider,
private Storage $fileSystem
) {
}
@@ -69,8 +73,8 @@ final readonly class ClearDiscoveryCache
}
// Clear legacy cache keys from old Core command
$this->cache->forget('discovery_service');
$this->cache->forget('unified_discovery_results');
$this->cache->forget(CacheKey::fromString('discovery_service'));
$this->cache->forget(CacheKey::fromString('unified_discovery_results'));
$this->output->writeLine('✓ Legacy discovery cache keys cleared');
// Clear routes cache
@@ -81,6 +85,10 @@ final readonly class ClearDiscoveryCache
$success = false;
}
// Clear ALL file cache entries (since we can't selectively delete by prefix due to MD5 hashing)
$deletedFiles = $this->clearAllFileCache();
$this->output->writeLine("✓ Cleared {$deletedFiles} file cache entries");
if ($success) {
$this->output->writeLine('<success>All discovery caches cleared successfully!</success>');
@@ -119,4 +127,49 @@ final readonly class ClearDiscoveryCache
return unlink($routesCachePath);
}
/**
* Clear ALL file cache entries
*
* Note: We clear all cache files because FileCache uses MD5 hashing for filenames,
* making it impossible to selectively delete by cache key prefix.
* This is necessary until we implement cache tags or a better storage mechanism.
*/
private function clearAllFileCache(): int
{
$cacheDir = $this->pathProvider->resolvePath('/storage/cache');
$deletedCount = 0;
try {
// Use DirectoryIterator for large directories (handles 80K+ files)
$iterator = new \DirectoryIterator($cacheDir);
foreach ($iterator as $fileInfo) {
if ($fileInfo->isDot() || ! $fileInfo->isFile()) {
continue;
}
$filename = $fileInfo->getFilename();
// Only delete .cache.php files (keep .lock files for now)
if (! str_ends_with($filename, '.cache.php')) {
continue;
}
try {
$this->fileSystem->delete($fileInfo->getPathname());
$deletedCount++;
} catch (\Throwable $e) {
// Continue with next file if deletion fails
continue;
}
}
} catch (\Throwable $e) {
// If directory iteration fails, return 0
return 0;
}
return $deletedCount;
}
}

View File

@@ -84,6 +84,10 @@ final readonly class DiscoveryServiceBootstrapper
if ($cachedRegistry !== null && ! $cachedRegistry->isEmpty()) {
$this->container->singleton(DiscoveryRegistry::class, $cachedRegistry);
// Process DefaultImplementation attributes first (before Initializers)
$defaultImplProcessor = $this->container->get(\App\Framework\DI\DefaultImplementationProcessor::class);
$defaultImplProcessor->process($cachedRegistry);
// Initializer-Verarbeitung für gecachte Registry
$initializerProcessor = $this->container->get(InitializerProcessor::class);
$initializerProcessor->processInitializers($cachedRegistry);
@@ -131,6 +135,10 @@ final readonly class DiscoveryServiceBootstrapper
$this->container->singleton(DiscoveryRegistry::class, $results);
$this->container->instance(UnifiedDiscoveryService::class, $discoveryService);
// Process DefaultImplementation attributes first (before Initializers, as they may depend on them)
$defaultImplProcessor = $this->container->get(\App\Framework\DI\DefaultImplementationProcessor::class);
$defaultImplProcessor->process($results);
// Initializer-Verarbeitung mit dedizierter Klasse
$initializerProcessor = $this->container->get(InitializerProcessor::class);
$initializerProcessor->processInitializers($results);

View File

@@ -4,12 +4,12 @@ declare(strict_types=1);
namespace App\Framework\Discovery\Enhanced\Analysis;
use App\Framework\Mcp\Core\Services\IntelligentMcpCacheManager;
use App\Framework\Mcp\Core\ValueObjects\CacheStrategy;
use App\Framework\Discovery\Enhanced\ValueObjects\ComponentType;
use App\Framework\Discovery\Enhanced\ValueObjects\DependencyEdge;
use App\Framework\Discovery\Enhanced\ValueObjects\DependencyGraph;
use App\Framework\Discovery\Enhanced\ValueObjects\DependencyNode;
use App\Framework\Discovery\Enhanced\ValueObjects\DependencyEdge;
use App\Framework\Discovery\Enhanced\ValueObjects\ComponentType;
use App\Framework\Mcp\Core\Services\IntelligentMcpCacheManager;
use App\Framework\Mcp\Core\ValueObjects\CacheStrategy;
/**
* Dependency Graph Analyzer
@@ -21,7 +21,8 @@ final readonly class DependencyGraphAnalyzer
{
public function __construct(
private IntelligentMcpCacheManager $cacheManager
) {}
) {
}
/**
* Analyze component dependencies and create dependency graph
@@ -32,7 +33,7 @@ final readonly class DependencyGraphAnalyzer
'dependency_analyzer',
'create_graph',
['components_hash' => $this->hashComponents($components)],
fn() => $this->createDependencyGraph($components),
fn () => $this->createDependencyGraph($components),
CacheStrategy::LONG
);
}
@@ -46,7 +47,7 @@ final readonly class DependencyGraphAnalyzer
'dependency_analyzer',
'circular_dependencies',
['graph_hash' => $graph->getHash()],
fn() => $this->detectCycles($graph),
fn () => $this->detectCycles($graph),
CacheStrategy::MEDIUM
);
}
@@ -60,7 +61,7 @@ final readonly class DependencyGraphAnalyzer
'dependency_analyzer',
'depth_analysis',
['graph_hash' => $graph->getHash()],
fn() => $this->calculateDepths($graph),
fn () => $this->calculateDepths($graph),
CacheStrategy::MEDIUM
);
}
@@ -74,7 +75,7 @@ final readonly class DependencyGraphAnalyzer
'dependency_analyzer',
'component_clusters',
['graph_hash' => $graph->getHash()],
fn() => $this->findStronglyConnectedComponents($graph),
fn () => $this->findStronglyConnectedComponents($graph),
CacheStrategy::LONG
);
}
@@ -88,7 +89,7 @@ final readonly class DependencyGraphAnalyzer
'dependency_analyzer',
'impact_analysis',
['graph_hash' => $graph->getHash(), 'component' => $componentName],
fn() => $this->calculateImpact($graph, $componentName),
fn () => $this->calculateImpact($graph, $componentName),
CacheStrategy::SHORT
);
}
@@ -102,7 +103,7 @@ final readonly class DependencyGraphAnalyzer
'dependency_analyzer',
'loading_order',
['graph_hash' => $graph->getHash()],
fn() => $this->topologicalSort($graph),
fn () => $this->topologicalSort($graph),
CacheStrategy::LONG
);
}
@@ -275,7 +276,7 @@ final readonly class DependencyGraphAnalyzer
$cycles = [];
foreach ($graph->getNodes() as $node) {
if (!isset($visited[$node->id])) {
if (! isset($visited[$node->id])) {
$this->dfsDetectCycle($graph, $node->id, $visited, $recursionStack, $cycles, []);
}
}
@@ -301,7 +302,7 @@ final readonly class DependencyGraphAnalyzer
foreach ($graph->getEdgesFrom($nodeId) as $edge) {
$targetId = $edge->to;
if (!isset($visited[$targetId])) {
if (! isset($visited[$targetId])) {
if ($this->dfsDetectCycle($graph, $targetId, $visited, $recursionStack, $cycles, $path)) {
return true;
}
@@ -309,11 +310,13 @@ final readonly class DependencyGraphAnalyzer
// Found cycle
$cycleStart = array_search($targetId, $path);
$cycles[] = array_slice($path, $cycleStart);
return true;
}
}
$recursionStack[$nodeId] = false;
return false;
}
@@ -326,7 +329,7 @@ final readonly class DependencyGraphAnalyzer
$visited = [];
foreach ($graph->getNodes() as $node) {
if (!isset($visited[$node->id])) {
if (! isset($visited[$node->id])) {
$this->calculateNodeDepth($graph, $node->id, $depths, $visited, []);
}
}
@@ -363,6 +366,7 @@ final readonly class DependencyGraphAnalyzer
}
$depths[$nodeId] = $maxDepth;
return $maxDepth;
}
@@ -392,7 +396,7 @@ final readonly class DependencyGraphAnalyzer
}
// Process queue
while (!empty($queue)) {
while (! empty($queue)) {
$nodeId = array_shift($queue);
$result[] = $nodeId;
@@ -417,6 +421,7 @@ final readonly class DependencyGraphAnalyzer
return true;
}
}
return false;
}
@@ -437,6 +442,7 @@ final readonly class DependencyGraphAnalyzer
return true;
}
}
return false;
}
@@ -449,9 +455,15 @@ final readonly class DependencyGraphAnalyzer
{
$count = 0;
if ($this->isConstructorDependency($component, $dependency)) $count++;
if ($this->isMethodDependency($component, $dependency)) $count++;
if ($this->isAttributeDependency($component, $dependency)) $count++;
if ($this->isConstructorDependency($component, $dependency)) {
$count++;
}
if ($this->isMethodDependency($component, $dependency)) {
$count++;
}
if ($this->isAttributeDependency($component, $dependency)) {
$count++;
}
return $count;
}
@@ -476,6 +488,7 @@ final readonly class DependencyGraphAnalyzer
$impact[] = $edge->from;
}
}
return $impact;
}
}
}

View File

@@ -5,24 +5,18 @@ declare(strict_types=1);
namespace App\Framework\Discovery\Enhanced;
use App\Framework\Cache\Cache;
use App\Framework\Cache\CacheKey;
use App\Framework\Cache\CacheItem;
use App\Framework\Core\ValueObjects\Duration;
use App\Framework\Discovery\UnifiedDiscoveryService;
use App\Framework\Discovery\ValueObjects\DiscoveryContext;
use App\Framework\Discovery\Enhanced\Analysis\DependencyGraphAnalyzer;
use App\Framework\Discovery\Enhanced\Analysis\PerformanceProfileAnalyzer;
use App\Framework\Discovery\Enhanced\Optimization\DiscoveryOptimizer;
use App\Framework\Discovery\Enhanced\Patterns\PatternRecognitionEngine;
use App\Framework\Discovery\Enhanced\Prediction\DiscoveryPredictor;
use App\Framework\Discovery\Enhanced\ValueObjects\DiscoveryMetrics;
use App\Framework\Discovery\Enhanced\ValueObjects\DiscoveryPlan;
use App\Framework\Discovery\Results\DiscoveryRegistry;
use App\Framework\Discovery\UnifiedDiscoveryService;
use App\Framework\Mcp\Core\Services\IntelligentMcpCacheManager;
use App\Framework\Mcp\Core\Services\McpPerformanceMonitor;
use App\Framework\Mcp\Core\ValueObjects\CacheStrategy;
use App\Framework\Discovery\Enhanced\ValueObjects\DiscoveryPlan;
use App\Framework\Discovery\Enhanced\ValueObjects\ComponentProfile;
use App\Framework\Discovery\Enhanced\ValueObjects\DiscoveryMetrics;
use App\Framework\Discovery\Enhanced\ValueObjects\ComponentType;
use App\Framework\Discovery\Enhanced\Analysis\DependencyGraphAnalyzer;
use App\Framework\Discovery\Enhanced\Analysis\PerformanceProfileAnalyzer;
use App\Framework\Discovery\Enhanced\Patterns\PatternRecognitionEngine;
use App\Framework\Discovery\Enhanced\Optimization\DiscoveryOptimizer;
use App\Framework\Discovery\Enhanced\Prediction\DiscoveryPredictor;
/**
* Enhanced Auto-Discovery Service with MCP Integration
@@ -37,11 +31,17 @@ use App\Framework\Discovery\Enhanced\Prediction\DiscoveryPredictor;
final readonly class IntelligentDiscoveryService
{
private IntelligentMcpCacheManager $cacheManager;
private McpPerformanceMonitor $performanceMonitor;
private DependencyGraphAnalyzer $dependencyAnalyzer;
private PerformanceProfileAnalyzer $profileAnalyzer;
private PatternRecognitionEngine $patternEngine;
private DiscoveryOptimizer $optimizer;
private DiscoveryPredictor $predictor;
public function __construct(
@@ -63,13 +63,13 @@ final readonly class IntelligentDiscoveryService
return $this->performanceMonitor->monitor(
'intelligent_discovery',
'discover',
function() use ($paths, $attributes, $plan) {
function () use ($paths, $attributes, $plan) {
return $this->executeEnhancedDiscovery($paths, $attributes, $plan);
},
[
'paths_count' => count($paths),
'attributes_count' => count($attributes),
'has_plan' => $plan !== null
'has_plan' => $plan !== null,
]
);
}
@@ -97,7 +97,7 @@ final readonly class IntelligentDiscoveryService
'dependency_analyzer',
'analyze_dependencies',
['components' => md5(serialize($components))],
fn() => $this->dependencyAnalyzer->analyze($components),
fn () => $this->dependencyAnalyzer->analyze($components),
CacheStrategy::LONG
);
}
@@ -111,7 +111,7 @@ final readonly class IntelligentDiscoveryService
'profile_analyzer',
'get_component_profiles',
['components' => md5(serialize($componentNames))],
fn() => $this->profileAnalyzer->analyzeComponents($componentNames),
fn () => $this->profileAnalyzer->analyzeComponents($componentNames),
CacheStrategy::MEDIUM
);
}
@@ -205,7 +205,7 @@ final readonly class IntelligentDiscoveryService
return $this->performanceMonitor->monitor(
'base_discovery',
'execute_optimized',
function() use ($plan) {
function () use ($plan) {
// Use base discovery service with plan optimizations
return $this->baseDiscoveryService->discover(
$plan->getPaths(),
@@ -227,8 +227,8 @@ final readonly class IntelligentDiscoveryService
'discovery_cache' => [
'default_ttl' => 3600,
'compression_threshold' => 100 * 1024, // 100KB
'max_memory_usage' => 50 * 1024 * 1024 // 50MB
]
'max_memory_usage' => 50 * 1024 * 1024, // 50MB
],
]);
// Initialize performance monitor
@@ -251,7 +251,7 @@ final readonly class IntelligentDiscoveryService
'discovery_stats',
'component_count',
[],
function() {
function () {
// Count components from base discovery service
// This would integrate with the actual discovery registry
return 0; // Placeholder
@@ -274,4 +274,4 @@ final readonly class IntelligentDiscoveryService
// Efficiency score: better compression + lower memory = higher efficiency
return min(1.0, $compressionRatio * 0.7 + (1.0 - min(1.0, $memoryUsage / (50 * 1024 * 1024))) * 0.3);
}
}
}

View File

@@ -125,4 +125,4 @@ enum ComponentType: string
default => 'very_low'
};
}
}
}

View File

@@ -17,7 +17,8 @@ final readonly class DependencyEdge
public string $type,
public float $strength = 1.0,
public array $metadata = []
) {}
) {
}
/**
* Get edge weight for graph algorithms
@@ -141,7 +142,7 @@ final readonly class DependencyEdge
$suggestions[] = [
'type' => 'extract_interface',
'description' => 'Consider extracting an interface to reduce coupling',
'priority' => 'medium'
'priority' => 'medium',
];
}
@@ -149,7 +150,7 @@ final readonly class DependencyEdge
$suggestions[] = [
'type' => 'dependency_injection',
'description' => 'Consider using dependency injection instead of direct usage',
'priority' => 'high'
'priority' => 'high',
];
}
@@ -157,7 +158,7 @@ final readonly class DependencyEdge
$suggestions[] = [
'type' => 'break_circular',
'description' => 'This dependency may cause circular references. Consider using events or interfaces.',
'priority' => 'critical'
'priority' => 'critical',
];
}
@@ -165,7 +166,7 @@ final readonly class DependencyEdge
$suggestions[] = [
'type' => 'remove_dependency',
'description' => 'This dependency is very weak and might be removable',
'priority' => 'low'
'priority' => 'low',
];
}
@@ -191,7 +192,7 @@ final readonly class DependencyEdge
'performance_impact' => $this->getPerformanceImpact(),
'is_circular_risk' => $this->isCircularRisk(),
'refactoring_suggestions' => $this->getRefactoringSuggestions(),
'metadata' => $this->metadata
'metadata' => $this->metadata,
];
}
@@ -264,4 +265,4 @@ final readonly class DependencyEdge
{
return new self($from, $to, 'event', 0.3);
}
}
}

View File

@@ -14,7 +14,8 @@ final readonly class DependencyGraph
public function __construct(
private array $nodes,
private array $edges
) {}
) {
}
/**
* Get all nodes in the graph
@@ -42,6 +43,7 @@ final readonly class DependencyGraph
return $node;
}
}
return null;
}
@@ -50,7 +52,7 @@ final readonly class DependencyGraph
*/
public function getEdgesFrom(string $nodeId): array
{
return array_filter($this->edges, fn($edge) => $edge->from === $nodeId);
return array_filter($this->edges, fn ($edge) => $edge->from === $nodeId);
}
/**
@@ -58,7 +60,7 @@ final readonly class DependencyGraph
*/
public function getEdgesTo(string $nodeId): array
{
return array_filter($this->edges, fn($edge) => $edge->to === $nodeId);
return array_filter($this->edges, fn ($edge) => $edge->to === $nodeId);
}
/**
@@ -67,7 +69,8 @@ final readonly class DependencyGraph
public function getDependencies(string $nodeId): array
{
$edges = $this->getEdgesFrom($nodeId);
return array_map(fn($edge) => $edge->to, $edges);
return array_map(fn ($edge) => $edge->to, $edges);
}
/**
@@ -76,7 +79,8 @@ final readonly class DependencyGraph
public function getDependents(string $nodeId): array
{
$edges = $this->getEdgesTo($nodeId);
return array_map(fn($edge) => $edge->from, $edges);
return array_map(fn ($edge) => $edge->from, $edges);
}
/**
@@ -89,6 +93,7 @@ final readonly class DependencyGraph
}
$visited = [];
return $this->dfsHasPath($from, $to, $visited);
}
@@ -102,6 +107,7 @@ final readonly class DependencyGraph
$currentPath = [];
$this->dfsAllPaths($from, $to, $visited, $currentPath, $paths);
return $paths;
}
@@ -117,7 +123,7 @@ final readonly class DependencyGraph
$queue = [[$from]];
$visited = [$from => true];
while (!empty($queue)) {
while (! empty($queue)) {
$path = array_shift($queue);
$node = end($path);
@@ -126,7 +132,7 @@ final readonly class DependencyGraph
return array_merge($path, [$neighbor]);
}
if (!isset($visited[$neighbor])) {
if (! isset($visited[$neighbor])) {
$visited[$neighbor] = true;
$queue[] = array_merge($path, [$neighbor]);
}
@@ -159,10 +165,10 @@ final readonly class DependencyGraph
'density' => $nodeCount > 1 ? $edgeCount / ($nodeCount * ($nodeCount - 1)) : 0,
'average_in_degree' => $nodeCount > 0 ? array_sum($inDegrees) / $nodeCount : 0,
'average_out_degree' => $nodeCount > 0 ? array_sum($outDegrees) / $nodeCount : 0,
'max_in_degree' => !empty($inDegrees) ? max($inDegrees) : 0,
'max_out_degree' => !empty($outDegrees) ? max($outDegrees) : 0,
'max_in_degree' => ! empty($inDegrees) ? max($inDegrees) : 0,
'max_out_degree' => ! empty($outDegrees) ? max($outDegrees) : 0,
'isolated_nodes' => $this->countIsolatedNodes(),
'strongly_connected' => $this->isStronglyConnected()
'strongly_connected' => $this->isStronglyConnected(),
];
}
@@ -172,8 +178,8 @@ final readonly class DependencyGraph
public function getHash(): string
{
return md5(serialize([
'nodes' => array_map(fn($node) => $node->toArray(), $this->nodes),
'edges' => array_map(fn($edge) => $edge->toArray(), $this->edges)
'nodes' => array_map(fn ($node) => $node->toArray(), $this->nodes),
'edges' => array_map(fn ($edge) => $edge->toArray(), $this->edges),
]));
}
@@ -183,9 +189,9 @@ final readonly class DependencyGraph
public function toArray(): array
{
return [
'nodes' => array_map(fn($node) => $node->toArray(), $this->nodes),
'edges' => array_map(fn($edge) => $edge->toArray(), $this->edges),
'statistics' => $this->getStatistics()
'nodes' => array_map(fn ($node) => $node->toArray(), $this->nodes),
'edges' => array_map(fn ($edge) => $edge->toArray(), $this->edges),
'statistics' => $this->getStatistics(),
];
}
@@ -194,8 +200,10 @@ final readonly class DependencyGraph
*/
public function createSubgraph(array $nodeIds): self
{
$subNodes = array_filter($this->nodes, fn($node) => in_array($node->id, $nodeIds));
$subEdges = array_filter($this->edges, fn($edge) =>
$subNodes = array_filter($this->nodes, fn ($node) => in_array($node->id, $nodeIds));
$subEdges = array_filter(
$this->edges,
fn ($edge) =>
in_array($edge->from, $nodeIds) && in_array($edge->to, $nodeIds)
);
@@ -217,7 +225,7 @@ final readonly class DependencyGraph
$seenEdgeKeys = [];
foreach ($mergedNodes as $node) {
if (!in_array($node->id, $seenNodeIds)) {
if (! in_array($node->id, $seenNodeIds)) {
$uniqueNodes[] = $node;
$seenNodeIds[] = $node->id;
}
@@ -225,7 +233,7 @@ final readonly class DependencyGraph
foreach ($mergedEdges as $edge) {
$edgeKey = $edge->from . '->' . $edge->to;
if (!in_array($edgeKey, $seenEdgeKeys)) {
if (! in_array($edgeKey, $seenEdgeKeys)) {
$uniqueEdges[] = $edge;
$seenEdgeKeys[] = $edgeKey;
}
@@ -246,7 +254,7 @@ final readonly class DependencyGraph
$visited[$current] = true;
foreach ($this->getDependencies($current) as $neighbor) {
if (!isset($visited[$neighbor]) && $this->dfsHasPath($neighbor, $target, $visited)) {
if (! isset($visited[$neighbor]) && $this->dfsHasPath($neighbor, $target, $visited)) {
return true;
}
}
@@ -271,7 +279,7 @@ final readonly class DependencyGraph
$paths[] = $currentPath;
} else {
foreach ($this->getDependencies($current) as $neighbor) {
if (!isset($visited[$neighbor])) {
if (! isset($visited[$neighbor])) {
$this->dfsAllPaths($neighbor, $target, $visited, $currentPath, $paths);
}
}
@@ -292,6 +300,7 @@ final readonly class DependencyGraph
$count++;
}
}
return $count;
}
@@ -308,11 +317,11 @@ final readonly class DependencyGraph
$firstNode = $this->nodes[0]->id;
foreach ($this->nodes as $node) {
if ($node->id !== $firstNode && !$this->hasPath($firstNode, $node->id)) {
if ($node->id !== $firstNode && ! $this->hasPath($firstNode, $node->id)) {
return false;
}
}
return true;
}
}
}

View File

@@ -17,7 +17,8 @@ final readonly class DependencyNode
public ComponentType $type,
public array $metadata = [],
public float $weight = 1.0
) {}
) {
}
/**
* Get node importance score based on type and metadata
@@ -148,7 +149,7 @@ final readonly class DependencyNode
'category' => $this->getCategory(),
'estimated_memory_usage' => $this->getEstimatedMemoryUsage(),
'performance_impact' => $this->getPerformanceImpact(),
'metadata' => $this->metadata
'metadata' => $this->metadata,
];
}
@@ -223,4 +224,4 @@ final readonly class DependencyNode
weight: 1.0 + min(1.0, $usageCount * 0.1)
);
}
}
}

View File

@@ -20,7 +20,8 @@ final readonly class DiscoveryMetrics
public float $memoryEfficiency,
public float $optimizationImpact,
public array $detailedMetrics = []
) {}
) {
}
/**
* Calculate overall efficiency score (0.0 - 1.0)
@@ -76,7 +77,7 @@ final readonly class DiscoveryMetrics
'priority' => 'high',
'description' => 'Cache hit rate is below 70%. Consider increasing cache TTL or improving cache key strategies.',
'current_value' => $this->cacheHitRate,
'target_value' => 0.8
'target_value' => 0.8,
];
}
@@ -86,7 +87,7 @@ final readonly class DiscoveryMetrics
'priority' => 'medium',
'description' => 'Pattern match rate is low. Update pattern recognition algorithms.',
'current_value' => $this->patternMatchRate,
'target_value' => 0.75
'target_value' => 0.75,
];
}
@@ -96,7 +97,7 @@ final readonly class DiscoveryMetrics
'priority' => 'medium',
'description' => 'Prediction accuracy needs improvement. Consider retraining prediction models.',
'current_value' => $this->predictionAccuracy,
'target_value' => 0.8
'target_value' => 0.8,
];
}
@@ -106,7 +107,7 @@ final readonly class DiscoveryMetrics
'priority' => 'high',
'description' => 'Memory efficiency is low. Enable streaming mode or increase compression.',
'current_value' => $this->memoryEfficiency,
'target_value' => 0.75
'target_value' => 0.75,
];
}
@@ -116,7 +117,7 @@ final readonly class DiscoveryMetrics
'priority' => 'high',
'description' => 'Discovery time is too high. Consider parallel processing or result caching.',
'current_value' => $this->averageDiscoveryTime,
'target_value' => 3.0
'target_value' => 3.0,
];
}
@@ -140,7 +141,7 @@ final readonly class DiscoveryMetrics
'performance_rating' => $this->getPerformanceRating(),
'components_per_second' => round($this->getComponentsPerSecond(), 2),
'improvement_suggestions' => $this->getImprovementSuggestions(),
'detailed_metrics' => $this->detailedMetrics
'detailed_metrics' => $this->detailedMetrics,
];
}
@@ -195,4 +196,4 @@ final readonly class DiscoveryMetrics
detailedMetrics: array_merge($this->detailedMetrics, $other->detailedMetrics)
);
}
}
}

View File

@@ -21,7 +21,8 @@ final readonly class DiscoveryPlan
private array $optimizations = [],
private array $predictions = [],
private array $metadata = []
) {}
) {
}
public function getPaths(): array
{
@@ -74,7 +75,7 @@ final readonly class DiscoveryPlan
optimizations: array_merge([
'memory_efficient' => true,
'cache_aggressive' => true,
'parallel_processing' => true
'parallel_processing' => true,
], $optimizations)
);
}
@@ -94,7 +95,7 @@ final readonly class DiscoveryPlan
optimizations: [
'deep_analysis' => true,
'dependency_mapping' => true,
'pattern_recognition' => true
'pattern_recognition' => true,
]
);
}
@@ -111,7 +112,7 @@ final readonly class DiscoveryPlan
strategy: PlanStrategy::MINIMAL,
optimizations: [
'cache_only' => true,
'skip_analysis' => true
'skip_analysis' => true,
]
);
}
@@ -176,7 +177,7 @@ final readonly class DiscoveryPlan
'strategy' => $this->strategy->value,
'optimizations' => $this->optimizations,
'predictions' => $this->predictions,
'metadata' => $this->metadata
'metadata' => $this->metadata,
];
}
}
}

View File

@@ -43,34 +43,34 @@ enum PlanStrategy: string
'cache_aggressive' => true,
'parallel_processing' => true,
'memory_efficient' => true,
'pattern_recognition' => true
'pattern_recognition' => true,
],
self::COMPREHENSIVE => [
'deep_analysis' => true,
'dependency_mapping' => true,
'pattern_recognition' => true,
'performance_profiling' => true
'performance_profiling' => true,
],
self::MINIMAL => [
'cache_only' => true,
'skip_analysis' => true,
'basic_patterns_only' => true
'basic_patterns_only' => true,
],
self::MEMORY_EFFICIENT => [
'streaming_mode' => true,
'chunk_processing' => true,
'memory_monitoring' => true,
'aggressive_cleanup' => true
'aggressive_cleanup' => true,
],
self::CACHE_FIRST => [
'cache_preference' => 100,
'fresh_discovery_timeout' => 100,
'fallback_minimal' => true
'fallback_minimal' => true,
],
self::REAL_TIME => [
'immediate_processing' => true,
'skip_caching' => true,
'minimal_analysis' => true
'minimal_analysis' => true,
]
};
}
@@ -85,37 +85,37 @@ enum PlanStrategy: string
'execution_time' => 'fast',
'memory_usage' => 'moderate',
'accuracy' => 'high',
'cache_efficiency' => 'high'
'cache_efficiency' => 'high',
],
self::COMPREHENSIVE => [
'execution_time' => 'slow',
'memory_usage' => 'high',
'accuracy' => 'maximum',
'cache_efficiency' => 'moderate'
'cache_efficiency' => 'moderate',
],
self::MINIMAL => [
'execution_time' => 'very_fast',
'memory_usage' => 'low',
'accuracy' => 'basic',
'cache_efficiency' => 'high'
'cache_efficiency' => 'high',
],
self::MEMORY_EFFICIENT => [
'execution_time' => 'moderate',
'memory_usage' => 'very_low',
'accuracy' => 'high',
'cache_efficiency' => 'moderate'
'cache_efficiency' => 'moderate',
],
self::CACHE_FIRST => [
'execution_time' => 'very_fast',
'memory_usage' => 'low',
'accuracy' => 'depends_on_cache',
'cache_efficiency' => 'maximum'
'cache_efficiency' => 'maximum',
],
self::REAL_TIME => [
'execution_time' => 'immediate',
'memory_usage' => 'moderate',
'accuracy' => 'good',
'cache_efficiency' => 'none'
'cache_efficiency' => 'none',
]
};
}
@@ -127,22 +127,22 @@ enum PlanStrategy: string
{
$features = match ($this) {
self::PERFORMANCE_OPTIMIZED => [
'caching', 'parallel_processing', 'pattern_recognition', 'dependency_analysis'
'caching', 'parallel_processing', 'pattern_recognition', 'dependency_analysis',
],
self::COMPREHENSIVE => [
'caching', 'deep_analysis', 'dependency_mapping', 'pattern_recognition', 'performance_profiling'
'caching', 'deep_analysis', 'dependency_mapping', 'pattern_recognition', 'performance_profiling',
],
self::MINIMAL => [
'caching', 'basic_discovery'
'caching', 'basic_discovery',
],
self::MEMORY_EFFICIENT => [
'streaming', 'chunking', 'memory_monitoring', 'cleanup'
'streaming', 'chunking', 'memory_monitoring', 'cleanup',
],
self::CACHE_FIRST => [
'caching', 'fallback_discovery'
'caching', 'fallback_discovery',
],
self::REAL_TIME => [
'immediate_processing', 'basic_discovery'
'immediate_processing', 'basic_discovery',
]
};
@@ -182,4 +182,4 @@ enum PlanStrategy: string
// Default to performance optimized
return self::PERFORMANCE_OPTIMIZED;
}
}
}

View File

@@ -4,7 +4,7 @@ declare(strict_types=1);
namespace App\Framework\Discovery;
use App\Framework\Filesystem\FilePath;
use App\Framework\Filesystem\ValueObjects\FilePath;
/**
* Interface für Visitor, die Dateien direkt besuchen wollen

View File

@@ -7,7 +7,7 @@ namespace App\Framework\Discovery;
use App\Framework\Cache\Cache;
use App\Framework\Cache\CacheKey;
use App\Framework\Core\ValueObjects\ClassName;
use App\Framework\Filesystem\FilePath;
use App\Framework\Filesystem\ValueObjects\FilePath;
/**
* Interface für File-Visitor, die beim Scannen von Dateien verwendet werden

View File

@@ -5,7 +5,7 @@ declare(strict_types=1);
namespace App\Framework\Discovery\Interfaces;
use App\Framework\Core\ValueObjects\ClassName;
use App\Framework\Filesystem\FilePath;
use App\Framework\Filesystem\ValueObjects\FilePath;
use App\Framework\Reflection\WrappedReflectionClass;
/**

View File

@@ -4,7 +4,7 @@ declare(strict_types=1);
namespace App\Framework\Discovery\Interfaces;
use App\Framework\Filesystem\FilePath;
use App\Framework\Filesystem\ValueObjects\FilePath;
/**
* Interface for visitors that discover items from files

View File

@@ -10,7 +10,7 @@ use App\Framework\Discovery\DiscoveryDataCollector;
use App\Framework\Discovery\Results\DiscoveryRegistry;
use App\Framework\Discovery\ValueObjects\DiscoveredAttribute;
use App\Framework\Discovery\ValueObjects\FileContext;
use App\Framework\Filesystem\FilePath;
use App\Framework\Filesystem\ValueObjects\FilePath;
use App\Framework\Logging\Logger;
use App\Framework\Logging\ValueObjects\LogContext;
use App\Framework\Reflection\ReflectionProvider;
@@ -40,7 +40,7 @@ final readonly class DependencyAnalysisPlugin implements DiscoveryPlugin
public function canProcess(FilePath $file, FileContext $context): bool
{
// Process all PHP files that contain classes
return $file->hasExtension('php') && !empty($context->getClassNames());
return $file->hasExtension('php') && ! empty($context->getClassNames());
}
/**
@@ -86,6 +86,7 @@ final readonly class DependencyAnalysisPlugin implements DiscoveryPlugin
if (empty($classNames)) {
$this->logger?->warning('No classes found for dependency analysis');
return $registry;
}
@@ -149,6 +150,7 @@ final readonly class DependencyAnalysisPlugin implements DiscoveryPlugin
}
$attribute = reset($analysisAttributes);
return $attribute->instance instanceof DependencyAnalysisResult ? $attribute->instance : null;
}
@@ -166,4 +168,4 @@ final readonly class DependencyAnalysisPlugin implements DiscoveryPlugin
return $this->analyzer->getRecommendations($analysisResult->getGraph());
}
}
}

View File

@@ -7,7 +7,7 @@ namespace App\Framework\Discovery\Plugins;
use App\Framework\Discovery\DiscoveryDataCollector;
use App\Framework\Discovery\Results\DiscoveryRegistry;
use App\Framework\Discovery\ValueObjects\FileContext;
use App\Framework\Filesystem\FilePath;
use App\Framework\Filesystem\ValueObjects\FilePath;
interface DiscoveryPlugin
{
@@ -31,4 +31,4 @@ interface DiscoveryPlugin
* This is called after all files have been processed
*/
public function postProcess(DiscoveryRegistry $registry): DiscoveryRegistry;
}
}

View File

@@ -33,7 +33,7 @@ final readonly class ClassExtractor
// Skip files without PHP tags (e.g., pure HTML templates)
// This prevents HTML tags from being misinterpreted as PHP classes
if (!$this->containsPhpCode($content)) {
if (! $this->containsPhpCode($content)) {
return [];
}
@@ -56,6 +56,7 @@ final readonly class ClassExtractor
private function containsPhpCode(string $content): bool
{
$trimmed = trim($content);
return str_starts_with($trimmed, '<?php') || str_starts_with($trimmed, '<?=');
}
@@ -69,7 +70,7 @@ final readonly class ClassExtractor
$content = $this->fileSystemService->readFile($file);
// Skip files without PHP tags
if (!$this->containsPhpCode($content)) {
if (! $this->containsPhpCode($content)) {
return [];
}
@@ -90,7 +91,7 @@ final readonly class ClassExtractor
$content = $this->fileSystemService->readFile($file);
// Skip files without PHP tags
if (!$this->containsPhpCode($content)) {
if (! $this->containsPhpCode($content)) {
return [];
}
@@ -111,7 +112,7 @@ final readonly class ClassExtractor
$content = $this->fileSystemService->readFile($file);
// Skip files without PHP tags
if (!$this->containsPhpCode($content)) {
if (! $this->containsPhpCode($content)) {
return [];
}
@@ -132,7 +133,7 @@ final readonly class ClassExtractor
$content = $this->fileSystemService->readFile($file);
// Skip files without PHP tags
if (!$this->containsPhpCode($content)) {
if (! $this->containsPhpCode($content)) {
return [];
}
@@ -178,7 +179,7 @@ final readonly class ClassExtractor
$content = $this->fileSystemService->readFile($file);
// Skip files without PHP tags
if (!$this->containsPhpCode($content)) {
if (! $this->containsPhpCode($content)) {
return [
'classes' => [],
'functions' => [],

View File

@@ -11,7 +11,7 @@ use App\Framework\Discovery\Results\DiscoveryRegistry;
use App\Framework\Discovery\ValueObjects\DiscoveryContext;
use App\Framework\Discovery\ValueObjects\FileContext;
use App\Framework\Filesystem\File;
use App\Framework\Filesystem\FilePath;
use App\Framework\Filesystem\ValueObjects\FilePath;
use App\Framework\Filesystem\FileScanner;
use App\Framework\Filesystem\FileSystemService;
use App\Framework\Filesystem\ValueObjects\FilePattern;

View File

@@ -7,7 +7,7 @@ namespace App\Framework\Discovery\Processing;
use App\Framework\Discovery\DiscoveryDataCollector;
use App\Framework\Discovery\ValueObjects\FileContext;
use App\Framework\Filesystem\File;
use App\Framework\Filesystem\FilePath;
use App\Framework\Filesystem\ValueObjects\FilePath;
use App\Framework\Filesystem\FileScanner;
use App\Framework\Filesystem\ValueObjects\FilePattern;
use App\Framework\Logging\Logger;

View File

@@ -5,7 +5,7 @@ declare(strict_types=1);
namespace App\Framework\Discovery;
use App\Framework\Core\ValueObjects\ClassName;
use App\Framework\Filesystem\FilePath;
use App\Framework\Filesystem\ValueObjects\FilePath;
use App\Framework\Reflection\WrappedReflectionClass;
/**

View File

@@ -62,6 +62,7 @@ final class AttributeRegistry implements Countable
error_log('⚠️ Failed to deserialize DiscoveredAttribute: ' . $e->getMessage());
error_log(' Attribute Class: ' . $attributeClass);
error_log(' Data keys: ' . implode(', ', array_keys($mappingArray)));
// Skip corrupted cache entries - they'll be recreated on next discovery
continue;
}

View File

@@ -12,7 +12,7 @@ use App\Framework\Discovery\Storage\DiscoveryStorageService;
/**
* Fast runtime loader for pre-discovered data
*
*
* Loads discovery results from storage instead of scanning files during bootstrap
*/
final readonly class DiscoveryLoader
@@ -67,7 +67,7 @@ final readonly class DiscoveryLoader
*/
public function hasPreDiscoveredData(): bool
{
return $this->storage->exists('attributes')
return $this->storage->exists('attributes')
&& $this->storage->exists('templates')
&& $this->storage->exists('interfaces');
}
@@ -80,7 +80,7 @@ final readonly class DiscoveryLoader
return [
'attributes' => $this->storage->getLastModified('attributes'),
'templates' => $this->storage->getLastModified('templates'),
'interfaces' => $this->storage->getLastModified('interfaces')
'interfaces' => $this->storage->getLastModified('interfaces'),
];
}
}

View File

@@ -7,6 +7,7 @@ namespace App\Framework\Discovery\Storage;
use App\Framework\Cache\Cache;
use App\Framework\Cache\CacheItem;
use App\Framework\Cache\CacheKey;
use App\Framework\Cache\CachePrefix;
use App\Framework\Core\Events\EventDispatcher;
use App\Framework\Core\ValueObjects\Byte;
use App\Framework\Core\ValueObjects\Duration;
@@ -23,7 +24,7 @@ use App\Framework\Discovery\ValueObjects\CacheMetrics;
use App\Framework\Discovery\ValueObjects\CacheTier;
use App\Framework\Discovery\ValueObjects\CompressionLevel;
use App\Framework\Discovery\ValueObjects\DiscoveryContext;
use App\Framework\Filesystem\FilePath;
use App\Framework\Filesystem\ValueObjects\FilePath;
use App\Framework\Filesystem\FileSystemService;
use App\Framework\Logging\Logger;
use App\Framework\Logging\ValueObjects\LogContext;
@@ -76,7 +77,8 @@ final class DiscoveryCacheManager
// Fallback to standard cache
$key = $this->buildCacheKey($context);
$item = $this->cache->get($key);
$result = $this->cache->get($key);
$item = $result->getItem($key);
if (! $item->isHit) {
$this->emitCacheMissEvent($key->toString(), 'not_found');
@@ -217,12 +219,11 @@ final class DiscoveryCacheManager
*/
public function clearAll(): bool
{
// This would ideally use cache tags, but for now we'll use pattern matching
// Clear only discovery-prefixed cache entries
$this->logger?->info('Clearing all discovery caches');
// Since we can't iterate cache keys, we'll just return true
// In a real implementation, you'd want cache tags support
return true;
$prefix = CachePrefix::fromString(self::CACHE_PREFIX);
return $this->cache->forget($prefix);
}
/**
@@ -378,7 +379,8 @@ final class DiscoveryCacheManager
// Try each tier in order of preference
foreach (CacheTier::orderedByPriority() as $tier) {
$key = $this->buildTieredCacheKey($context, $tier);
$item = $this->cache->get($key);
$result = $this->cache->get($key);
$item = $result->getItem($key);
if ($item->isHit) {
$data = $this->processRetrievedData($item->value);

View File

@@ -10,7 +10,7 @@ use App\Framework\Discovery\Results\AttributeRegistry;
use App\Framework\Discovery\Results\InterfaceRegistry;
use App\Framework\Discovery\Results\TemplateRegistry;
use App\Framework\Filesystem\Directory;
use App\Framework\Filesystem\FilePath;
use App\Framework\Filesystem\ValueObjects\FilePath;
use App\Framework\Filesystem\FileStorage;
/**
@@ -21,16 +21,17 @@ use App\Framework\Filesystem\FileStorage;
final readonly class DiscoveryStorageService
{
private Directory $storageDir;
private FileStorage $storage;
public function __construct(PathProvider $pathProvider)
{
$this->storage = new FileStorage($pathProvider->getBasePath());
$storagePath = $pathProvider->getBasePath() . '/storage/discovery';
$this->storageDir = new Directory($storagePath, $this->storage);
if (!$this->storageDir->exists()) {
if (! $this->storageDir->exists()) {
$this->storageDir->create();
}
}
@@ -111,12 +112,12 @@ final readonly class DiscoveryStorageService
{
$filePath = $this->getFilePath($type);
if (!$this->storage->exists($filePath->toString())) {
if (! $this->storage->exists($filePath->toString())) {
return null;
}
$metadata = $this->storage->getMetadata($filePath->toString());
return Timestamp::fromInt((int)$metadata->modifiedAt->format('U'));
}
@@ -161,7 +162,7 @@ final readonly class DiscoveryStorageService
{
$filePath = $this->getFilePath($type);
if (!$this->storage->exists($filePath->toString())) {
if (! $this->storage->exists($filePath->toString())) {
return null;
}

View File

@@ -7,9 +7,9 @@ namespace App\Framework\Discovery\Storage;
use App\Framework\Async\FiberManager;
use App\Framework\Filesystem\Directory;
use App\Framework\Filesystem\File;
use App\Framework\Filesystem\FileMetadata;
use App\Framework\Filesystem\PermissionChecker;
use App\Framework\Filesystem\Storage;
use App\Framework\Filesystem\ValueObjects\FileMetadata;
use SplFileInfo;
/**

View File

@@ -30,7 +30,7 @@ use App\Framework\Discovery\ValueObjects\DiscoveryOptions;
use App\Framework\Discovery\ValueObjects\FileContext;
use App\Framework\Discovery\ValueObjects\MemoryStrategy;
use App\Framework\Discovery\ValueObjects\ScanType;
use App\Framework\Filesystem\FilePath;
use App\Framework\Filesystem\ValueObjects\FilePath;
use App\Framework\Filesystem\FileScanner;
use App\Framework\Filesystem\FileSystemService;
use App\Framework\Filesystem\ValueObjects\FilePattern;

View File

@@ -13,7 +13,8 @@ final readonly class DependencyEdge
private ClassName $target,
private DependencyRelation $relation,
private int $weight = 1
) {}
) {
}
/**
* Create a dependency edge
@@ -124,6 +125,7 @@ final readonly class DependencyEdge
public function toString(): string
{
$arrow = $this->isStrong() ? ' => ' : ' -> ';
return sprintf(
'%s%s%s (%s, weight: %d)',
$this->source->getShortName(),
@@ -138,4 +140,4 @@ final readonly class DependencyEdge
{
return $this->toString();
}
}
}

View File

@@ -5,7 +5,6 @@ declare(strict_types=1);
namespace App\Framework\Discovery\ValueObjects;
use App\Framework\Core\ValueObjects\ClassName;
use InvalidArgumentException;
final readonly class DependencyGraph
{
@@ -123,7 +122,7 @@ final readonly class DependencyGraph
foreach ($this->nodes as $node) {
$className = $node->getClassName()->toString();
if (!isset($visited[$className])) {
if (! isset($visited[$className])) {
$this->dfsForCycles($node, $visited, $recursionStack, $cycles, []);
}
}
@@ -139,7 +138,7 @@ final readonly class DependencyGraph
public function getHighestDependencyNodes(int $limit = 10): array
{
$nodes = array_values($this->nodes);
usort($nodes, fn($a, $b) => $b->getDependencyCount() <=> $a->getDependencyCount());
usort($nodes, fn ($a, $b) => $b->getDependencyCount() <=> $a->getDependencyCount());
return array_slice($nodes, 0, $limit);
}
@@ -152,7 +151,7 @@ final readonly class DependencyGraph
public function getMostUsedNodes(int $limit = 10): array
{
$nodes = array_values($this->nodes);
usort($nodes, fn($a, $b) => $b->getDependentCount() <=> $a->getDependentCount());
usort($nodes, fn ($a, $b) => $b->getDependentCount() <=> $a->getDependentCount());
return array_slice($nodes, 0, $limit);
}
@@ -164,7 +163,7 @@ final readonly class DependencyGraph
*/
public function getLeafNodes(): array
{
return array_filter($this->nodes, fn(DependencyNode $node) => $node->isLeaf());
return array_filter($this->nodes, fn (DependencyNode $node) => $node->isLeaf());
}
/**
@@ -174,7 +173,7 @@ final readonly class DependencyGraph
*/
public function getRootNodes(): array
{
return array_filter($this->nodes, fn(DependencyNode $node) => $node->isRoot());
return array_filter($this->nodes, fn (DependencyNode $node) => $node->isRoot());
}
/**
@@ -186,7 +185,7 @@ final readonly class DependencyGraph
{
return array_filter(
$this->nodes,
fn(DependencyNode $node) => $node->getType() === $type
fn (DependencyNode $node) => $node->getType() === $type
);
}
@@ -201,6 +200,7 @@ final readonly class DependencyGraph
}
$visited = [];
return $this->calculateDepth($node, $visited);
}
@@ -271,11 +271,11 @@ final readonly class DependencyGraph
return [
'statistics' => $this->getStatistics(),
'nodes' => array_map(
fn(DependencyNode $node) => $node->toArray(),
fn (DependencyNode $node) => $node->toArray(),
array_values($this->nodes)
),
'edges' => array_map(
fn(DependencyEdge $edge) => $edge->toArray(),
fn (DependencyEdge $edge) => $edge->toArray(),
$this->edges
),
'circular_dependencies' => $this->findCircularDependencies(),
@@ -289,13 +289,13 @@ final readonly class DependencyGraph
{
$graph = $this;
if (!$this->hasNode($source)) {
if (! $this->hasNode($source)) {
$sourceType = DependencyType::fromClassName($source->toString());
$sourceNode = DependencyNode::create($source, $sourceType);
$graph = $graph->addNode($sourceNode);
}
if (!$this->hasNode($target)) {
if (! $this->hasNode($target)) {
$targetType = DependencyType::fromClassName($target->toString());
$targetNode = DependencyNode::create($target, $targetType);
$graph = $graph->addNode($targetNode);
@@ -333,7 +333,7 @@ final readonly class DependencyGraph
if ($cycleStart !== false) {
$cycles[] = array_slice($currentPath, $cycleStart);
}
} elseif (!isset($visited[$targetClassName])) {
} elseif (! isset($visited[$targetClassName])) {
$targetNode = $this->getNode($edge->getTarget());
if ($targetNode !== null) {
$this->dfsForCycles($targetNode, $visited, $recursionStack, $cycles, $currentPath);
@@ -370,6 +370,7 @@ final readonly class DependencyGraph
}
unset($visited[$className]);
return $maxDepth;
}
@@ -397,7 +398,7 @@ final readonly class DependencyGraph
foreach ($from->getDependencies() as $edge) {
$nextClassName = $edge->getTarget()->toString();
if (!isset($visited[$nextClassName])) {
if (! isset($visited[$nextClassName])) {
$nextNode = $this->getNode($edge->getTarget());
if ($nextNode !== null && $this->findPath($nextNode, $to, $visited, $path)) {
return true;
@@ -406,6 +407,7 @@ final readonly class DependencyGraph
}
array_pop($path);
return false;
}
@@ -418,6 +420,7 @@ final readonly class DependencyGraph
foreach ($this->nodes as $node) {
$max = max($max, $node->getDependencyCount());
}
return $max;
}
@@ -430,6 +433,7 @@ final readonly class DependencyGraph
foreach ($this->nodes as $node) {
$max = max($max, $node->getDependentCount());
}
return $max;
}
}
}

View File

@@ -122,6 +122,7 @@ final readonly class DependencyNode
return true;
}
}
return false;
}
@@ -160,7 +161,7 @@ final readonly class DependencyNode
public function getDependencyNames(): array
{
return array_map(
fn(DependencyEdge $edge) => $edge->getTarget()->toString(),
fn (DependencyEdge $edge) => $edge->getTarget()->toString(),
$this->dependencies
);
}
@@ -173,7 +174,7 @@ final readonly class DependencyNode
public function getDependentNames(): array
{
return array_map(
fn(DependencyEdge $edge) => $edge->getSource()->toString(),
fn (DependencyEdge $edge) => $edge->getSource()->toString(),
$this->dependents
);
}
@@ -195,11 +196,11 @@ final readonly class DependencyNode
'is_root' => $this->isRoot(),
'has_circular_dependency' => $this->hasCircularDependency(),
'dependencies' => array_map(
fn(DependencyEdge $edge) => $edge->toArray(),
fn (DependencyEdge $edge) => $edge->toArray(),
$this->dependencies
),
'dependents' => array_map(
fn(DependencyEdge $edge) => $edge->toArray(),
fn (DependencyEdge $edge) => $edge->toArray(),
$this->dependents
),
];
@@ -224,4 +225,4 @@ final readonly class DependencyNode
{
return $this->toString();
}
}
}

View File

@@ -150,4 +150,4 @@ enum DependencyRelation: string
{
return $this->value;
}
}
}

View File

@@ -194,4 +194,4 @@ enum DependencyType: string
{
return $this->value;
}
}
}

View File

@@ -7,7 +7,7 @@ namespace App\Framework\Discovery\ValueObjects;
use App\Framework\Core\ValueObjects\Byte;
use App\Framework\Core\ValueObjects\ClassName;
use App\Framework\Core\ValueObjects\MethodName;
use App\Framework\Filesystem\FilePath;
use App\Framework\Filesystem\ValueObjects\FilePath;
use Attribute;
/**

View File

@@ -6,7 +6,7 @@ namespace App\Framework\Discovery\ValueObjects;
use App\Framework\Core\ValueObjects\ClassName;
use App\Framework\Filesystem\File;
use App\Framework\Filesystem\FilePath;
use App\Framework\Filesystem\ValueObjects\FilePath;
/**
* Context information for file processing

View File

@@ -6,7 +6,7 @@ namespace App\Framework\Discovery\ValueObjects;
use App\Framework\Core\ValueObjects\Byte;
use App\Framework\Core\ValueObjects\ClassName;
use App\Framework\Filesystem\FilePath;
use App\Framework\Filesystem\ValueObjects\FilePath;
/**
* Immutable value object for interface implementation mappings

View File

@@ -5,7 +5,7 @@ declare(strict_types=1);
namespace App\Framework\Discovery\ValueObjects;
use App\Framework\Core\ValueObjects\ClassName;
use App\Framework\Filesystem\FilePath;
use App\Framework\Filesystem\ValueObjects\FilePath;
use App\Framework\Reflection\WrappedReflectionClass;
/**

View File

@@ -5,7 +5,7 @@ declare(strict_types=1);
namespace App\Framework\Discovery\ValueObjects;
use App\Framework\Core\ValueObjects\Byte;
use App\Framework\Filesystem\FilePath;
use App\Framework\Filesystem\ValueObjects\FilePath;
use ArrayIterator;
use Countable;
use IteratorAggregate;

View File

@@ -5,7 +5,7 @@ declare(strict_types=1);
namespace App\Framework\Discovery\ValueObjects;
use App\Framework\Core\ValueObjects\Byte;
use App\Framework\Filesystem\FilePath;
use App\Framework\Filesystem\ValueObjects\FilePath;
/**
* Immutable value object for template mappings