docs: consolidate documentation into organized structure

- Move 12 markdown files from root to docs/ subdirectories
- Organize documentation by category:
  • docs/troubleshooting/ (1 file)  - Technical troubleshooting guides
  • docs/deployment/      (4 files) - Deployment and security documentation
  • docs/guides/          (3 files) - Feature-specific guides
  • docs/planning/        (4 files) - Planning and improvement proposals

Root directory cleanup:
- Reduced from 16 to 4 markdown files in root
- Only essential project files remain:
  • CLAUDE.md (AI instructions)
  • README.md (Main project readme)
  • CLEANUP_PLAN.md (Current cleanup plan)
  • SRC_STRUCTURE_IMPROVEMENTS.md (Structure improvements)

This improves:
 Documentation discoverability
 Logical organization by purpose
 Clean root directory
 Better maintainability
This commit is contained in:
2025-10-05 11:05:04 +02:00
parent 887847dde6
commit 5050c7d73a
36686 changed files with 196456 additions and 12398919 deletions

View File

@@ -0,0 +1,80 @@
<?php
declare(strict_types=1);
namespace App\Framework\Discovery\Analysis;
use App\Framework\Discovery\ValueObjects\DependencyGraph;
final readonly class DependencyAnalysisResult
{
/**
* @param array<array<string>> $circularDependencies
* @param array<string, mixed> $statistics
*/
public function __construct(
private DependencyGraph $graph,
private array $circularDependencies,
private array $statistics
) {}
/**
* Get the dependency graph
*/
public function getGraph(): DependencyGraph
{
return $this->graph;
}
/**
* Get circular dependencies
*
* @return array<array<string>>
*/
public function getCircularDependencies(): array
{
return $this->circularDependencies;
}
/**
* Get analysis statistics
*
* @return array<string, mixed>
*/
public function getStatistics(): array
{
return $this->statistics;
}
/**
* Check if there are any circular dependencies
*/
public function hasCircularDependencies(): bool
{
return !empty($this->circularDependencies);
}
/**
* Get the number of circular dependency cycles
*/
public function getCircularDependencyCount(): int
{
return count($this->circularDependencies);
}
/**
* Convert to array for serialization
*
* @return array<string, mixed>
*/
public function toArray(): array
{
return [
'statistics' => $this->statistics,
'circular_dependencies' => $this->circularDependencies,
'has_circular_dependencies' => $this->hasCircularDependencies(),
'circular_dependency_count' => $this->getCircularDependencyCount(),
'graph' => $this->graph->toArray(),
];
}
}

View File

@@ -0,0 +1,456 @@
<?php
declare(strict_types=1);
namespace App\Framework\Discovery\Analysis;
use App\Framework\Core\ValueObjects\ClassName;
use App\Framework\Discovery\ValueObjects\DependencyEdge;
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\WrappedReflectionClass;
use ReflectionClass;
use ReflectionMethod;
use ReflectionParameter;
use ReflectionProperty;
use ReflectionException;
use Throwable;
final readonly class DependencyAnalyzer
{
public function __construct(
private ReflectionProvider $reflectionProvider,
private ?Logger $logger = null
) {}
/**
* Analyze dependencies for a list of classes
*
* @param array<string> $classNames
*/
public function analyze(array $classNames): DependencyGraph
{
$this->logger?->info('Starting dependency analysis', LogContext::withData([
'class_count' => count($classNames),
]));
$graph = DependencyGraph::empty();
$processedClasses = [];
foreach ($classNames as $className) {
try {
if (isset($processedClasses[$className])) {
continue;
}
$graph = $this->analyzeClass($className, $graph);
$processedClasses[$className] = true;
} catch (Throwable $e) {
$this->logger?->warning('Failed to analyze class dependencies', LogContext::withData([
'class' => $className,
'error' => $e->getMessage(),
]));
}
}
$this->logger?->info('Dependency analysis completed', LogContext::withData([
'nodes' => $graph->getNodeCount(),
'edges' => $graph->getEdgeCount(),
'circular_dependencies' => count($graph->findCircularDependencies()),
]));
return $graph;
}
/**
* Analyze dependencies for a single class
*/
public function analyzeClass(string $className, DependencyGraph $graph): DependencyGraph
{
try {
$reflectionClass = $this->reflectionProvider->getClass(ClassName::create($className));
// Create or update the node for this class
$dependencyType = $this->determineDependencyType($reflectionClass);
$node = DependencyNode::create(ClassName::create($className), $dependencyType);
$graph = $graph->addNode($node);
$this->logger?->debug('Analyzing class', ['class' => $className, 'type' => $dependencyType->value]);
// Analyze inheritance
$graph = $this->analyzeInheritance($reflectionClass, $graph);
// Analyze interfaces
$graph = $this->analyzeInterfaces($reflectionClass, $graph);
// Analyze traits
$graph = $this->analyzeTraits($reflectionClass, $graph);
// Analyze constructor dependencies
$graph = $this->analyzeConstructorDependencies($reflectionClass, $graph);
// Analyze method dependencies
$graph = $this->analyzeMethodDependencies($reflectionClass, $graph);
// Analyze property dependencies
$graph = $this->analyzePropertyDependencies($reflectionClass, $graph);
} catch (ReflectionException $e) {
$this->logger?->warning('Reflection failed for class', LogContext::withData([
'class' => $className,
'error' => $e->getMessage(),
]));
}
return $graph;
}
/**
* Determine the dependency type of a class
*/
private function determineDependencyType(WrappedReflectionClass $class): DependencyType
{
return DependencyType::fromClassName(
$class->getName(),
$class->isInterface(),
$class->isAbstract(),
$class->isTrait(),
$class->isEnum()
);
}
/**
* Analyze inheritance relationships
*/
private function analyzeInheritance(WrappedReflectionClass $class, DependencyGraph $graph): DependencyGraph
{
$parentClass = $class->getParentClass();
if ($parentClass === false) {
return $graph;
}
$edge = DependencyEdge::create(
ClassName::create($class->getName()),
ClassName::create($parentClass->getName()),
DependencyRelation::EXTENDS,
10 // High weight for inheritance
);
return $graph->addEdge($edge);
}
/**
* Analyze interface implementations
*/
private function analyzeInterfaces(WrappedReflectionClass $class, DependencyGraph $graph): DependencyGraph
{
$interfaces = $class->getInterfaceNames();
foreach ($interfaces as $interfaceName) {
$edge = DependencyEdge::create(
ClassName::create($class->getName()),
ClassName::create($interfaceName),
DependencyRelation::IMPLEMENTS,
8 // High weight for interface implementation
);
$graph = $graph->addEdge($edge);
}
return $graph;
}
/**
* Analyze trait usage
*/
private function analyzeTraits(WrappedReflectionClass $class, DependencyGraph $graph): DependencyGraph
{
$traits = $class->getTraitNames();
foreach ($traits as $traitName) {
$edge = DependencyEdge::create(
ClassName::create($class->getName()),
ClassName::create($traitName),
DependencyRelation::USES_TRAIT,
6 // Medium weight for trait usage
);
$graph = $graph->addEdge($edge);
}
return $graph;
}
/**
* Analyze constructor dependencies
*/
private function analyzeConstructorDependencies(WrappedReflectionClass $class, DependencyGraph $graph): DependencyGraph
{
$constructor = $class->getConstructor();
if ($constructor === null) {
return $graph;
}
$parameters = $constructor->getParameters();
foreach ($parameters as $parameter) {
$type = $parameter->getType();
if ($type === null || $type->isBuiltin()) {
continue;
}
$typeNames = $this->extractTypeNames($type);
foreach ($typeNames as $typeName) {
$edge = DependencyEdge::create(
ClassName::create($class->getName()),
ClassName::create($typeName),
DependencyRelation::CONSTRUCTOR_INJECTION,
10 // Highest weight for constructor injection
);
$graph = $graph->addEdge($edge);
}
}
return $graph;
}
/**
* Analyze method parameter dependencies
*/
private function analyzeMethodDependencies(WrappedReflectionClass $class, DependencyGraph $graph): DependencyGraph
{
$methods = $class->getMethodsRaw(ReflectionMethod::IS_PUBLIC | ReflectionMethod::IS_PROTECTED);
foreach ($methods as $method) {
// Skip constructor (already analyzed)
if ($method->isConstructor()) {
continue;
}
// Analyze parameters
$graph = $this->analyzeMethodParameters($class, $method, $graph);
// Analyze return type
$graph = $this->analyzeMethodReturnType($class, $method, $graph);
}
return $graph;
}
/**
* Analyze method parameters
*/
private function analyzeMethodParameters(WrappedReflectionClass $class, ReflectionMethod $method, DependencyGraph $graph): DependencyGraph
{
$parameters = $method->getParameters();
foreach ($parameters as $parameter) {
$type = $parameter->getType();
if ($type === null || $type->isBuiltin()) {
continue;
}
$typeNames = $this->extractTypeNames($type);
foreach ($typeNames as $typeName) {
$edge = DependencyEdge::create(
ClassName::create($class->getName()),
ClassName::create($typeName),
DependencyRelation::METHOD_PARAMETER,
5 // Medium weight for method parameters
);
$graph = $graph->addEdge($edge);
}
}
return $graph;
}
/**
* Analyze method return types
*/
private function analyzeMethodReturnType(WrappedReflectionClass $class, ReflectionMethod $method, DependencyGraph $graph): DependencyGraph
{
$returnType = $method->getReturnType();
if ($returnType === null || $returnType->isBuiltin()) {
return $graph;
}
$typeNames = $this->extractTypeNames($returnType);
foreach ($typeNames as $typeName) {
$edge = DependencyEdge::create(
ClassName::create($class->getName()),
ClassName::create($typeName),
DependencyRelation::RETURN_TYPE,
3 // Lower weight for return types
);
$graph = $graph->addEdge($edge);
}
return $graph;
}
/**
* Analyze property type dependencies
*/
private function analyzePropertyDependencies(WrappedReflectionClass $class, DependencyGraph $graph): DependencyGraph
{
$properties = $class->getPropertiesRaw(ReflectionProperty::IS_PUBLIC | ReflectionProperty::IS_PROTECTED | ReflectionProperty::IS_PRIVATE);
foreach ($properties as $property) {
$type = $property->getType();
if ($type === null || $type->isBuiltin()) {
continue;
}
$typeNames = $this->extractTypeNames($type);
foreach ($typeNames as $typeName) {
$edge = DependencyEdge::create(
ClassName::create($class->getName()),
ClassName::create($typeName),
DependencyRelation::PROPERTY_TYPE,
4 // Medium-low weight for property types
);
$graph = $graph->addEdge($edge);
}
}
return $graph;
}
/**
* Analyze dependencies with circular detection
*
* @param array<string> $classNames
*/
public function analyzeWithCircularDetection(array $classNames): DependencyAnalysisResult
{
$graph = $this->analyze($classNames);
$circularDependencies = $graph->findCircularDependencies();
$statistics = $graph->getStatistics();
return new DependencyAnalysisResult(
graph: $graph,
circularDependencies: $circularDependencies,
statistics: $statistics
);
}
/**
* Get dependency recommendations
*
* @return array<string, mixed>
*/
public function getRecommendations(DependencyGraph $graph): array
{
$recommendations = [];
$statistics = $graph->getStatistics();
// Check for high complexity nodes
$highComplexityNodes = [];
foreach ($graph->getNodes() as $node) {
$expectedRange = $node->getType()->getExpectedComplexityRange();
if ($node->getComplexityScore() > $expectedRange['max']) {
$highComplexityNodes[] = [
'class' => $node->getClassName()->getShortName(),
'current' => $node->getComplexityScore(),
'expected_max' => $expectedRange['max'],
'suggestion' => 'Consider breaking down this class into smaller components',
];
}
}
if (!empty($highComplexityNodes)) {
$recommendations['high_complexity'] = $highComplexityNodes;
}
// Check for circular dependencies
$circularDependencies = $graph->findCircularDependencies();
if (!empty($circularDependencies)) {
$recommendations['circular_dependencies'] = [
'count' => count($circularDependencies),
'cycles' => $circularDependencies,
'suggestion' => 'Break circular dependencies using interfaces, events, or dependency inversion',
];
}
// Check for highly coupled classes
$highDependencyNodes = $graph->getHighestDependencyNodes(5);
if (!empty($highDependencyNodes)) {
$recommendations['high_dependencies'] = array_map(
fn(DependencyNode $node) => [
'class' => $node->getClassName()->getShortName(),
'dependency_count' => $node->getDependencyCount(),
'suggestion' => 'Consider using dependency injection or factory patterns',
],
$highDependencyNodes
);
}
// Check for unused classes (leaf nodes with no dependents)
$unusedClasses = array_filter(
$graph->getLeafNodes(),
fn(DependencyNode $node) => $node->getDependentCount() === 0
);
if (!empty($unusedClasses)) {
$recommendations['potentially_unused'] = array_map(
fn(DependencyNode $node) => [
'class' => $node->getClassName()->getShortName(),
'suggestion' => 'Consider removing if truly unused',
],
array_slice($unusedClasses, 0, 10)
);
}
return $recommendations;
}
/**
* Extract type names from reflection type, handling union types
*
* @return array<string>
*/
private function extractTypeNames(\ReflectionType $type): array
{
if ($type instanceof \ReflectionNamedType) {
return [$type->getName()];
}
if ($type instanceof \ReflectionUnionType) {
$types = [];
foreach ($type->getTypes() as $subType) {
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()) {
$types[] = $subType->getName();
}
}
return $types;
}
// Fallback for unknown types
return [];
}
}

View File

@@ -52,4 +52,22 @@ final readonly class DiscoveryCacheIdentifiers
{
return self::discoveryKey($paths, ScanType::INCREMENTAL, $context);
}
/**
* Cache key for lazy components that are loaded on-demand
*/
public static function lazyComponentsKey(): CacheKey
{
return CacheKey::fromString('discovery:lazy_components');
}
/**
* Cache key for directory last modified timestamps (used for cache validation)
*/
public static function lastModifiedKey(string $path): CacheKey
{
$pathHash = md5($path);
return CacheKey::fromString("discovery:lastmod_{$pathHash}");
}
}

View File

@@ -0,0 +1,43 @@
<?php
declare(strict_types=1);
namespace App\Framework\Discovery;
use App\Framework\Context\ContextType;
use App\Framework\DI\Initializer;
use App\Framework\Discovery\Results\AttributeRegistry;
use App\Framework\Discovery\Results\DiscoveryRegistry;
use App\Framework\Discovery\Results\InterfaceRegistry;
use App\Framework\Discovery\Results\TemplateRegistry;
use App\Framework\Discovery\Runtime\DiscoveryLoader;
/**
* Lädt Discovery-Daten aus gespeicherten Dateien und erstellt DiscoveryRegistry
*
* Dieser Initializer ersetzt den alten UnifiedDiscoveryService und lädt
* die vorher gescannten Discovery-Daten aus storage/discovery/
*/
final readonly class DiscoveryRegistryInitializer
{
public function __construct(
private DiscoveryLoader $discoveryLoader
) {
}
#[Initializer(ContextType::ALL)]
public function __invoke(): DiscoveryRegistry
{
// Lade alle Discovery-Daten aus Storage
$attributes = $this->discoveryLoader->loadAttributes() ?? new AttributeRegistry();
$interfaces = $this->discoveryLoader->loadInterfaces() ?? new InterfaceRegistry();
$templates = $this->discoveryLoader->loadTemplates() ?? new TemplateRegistry();
// Erstelle und returne DiscoveryRegistry mit geladenen Daten
return new DiscoveryRegistry(
attributes: $attributes,
interfaces: $interfaces,
templates: $templates
);
}
}

View File

@@ -55,52 +55,33 @@ final readonly class DiscoveryServiceBootstrapper
$cachedItem = $cache->get($cacheKey);
error_log("DiscoveryServiceBootstrapper: Cache lookup for key: " . $cacheKey->toString() .
" - Hit: " . ($cachedItem->isHit ? "YES" : "NO"));
if ($cachedItem->isHit) {
error_log("DiscoveryServiceBootstrapper: Cache hit for key: " . $cacheKey->toString());
// Ensure DiscoveryRegistry class is loaded before attempting deserialization
if (! class_exists(DiscoveryRegistry::class, true)) {
error_log("DiscoveryServiceBootstrapper: Could not load DiscoveryRegistry class, skipping cache");
$cachedRegistry = null;
} else {
// Versuche die gecachten Daten zu laden
$cachedRegistry = null;
error_log("DiscoveryServiceBootstrapper: Cached value type: " . gettype($cachedItem->value) .
" - Class: " . (is_object($cachedItem->value) ? get_class($cachedItem->value) : 'not object'));
try {
// Skip incomplete classes - they indicate autoloader issues
if (is_object($cachedItem->value) && get_class($cachedItem->value) === '__PHP_Incomplete_Class') {
error_log("DiscoveryServiceBootstrapper: Skipping __PHP_Incomplete_Class cache entry");
$cachedRegistry = null;
} elseif ($cachedItem->value instanceof DiscoveryRegistry) {
$cachedRegistry = $cachedItem->value;
error_log("DiscoveryServiceBootstrapper: Using cached DiscoveryRegistry directly");
} elseif (is_array($cachedItem->value)) {
$cachedRegistry = DiscoveryRegistry::fromArray($cachedItem->value);
error_log("DiscoveryServiceBootstrapper: Deserialized from array");
} elseif (is_string($cachedItem->value)) {
$cachedRegistry = DiscoveryRegistry::fromArray(json_decode($cachedItem->value, true, 512, JSON_THROW_ON_ERROR));
error_log("DiscoveryServiceBootstrapper: Deserialized from JSON string");
} else {
error_log("DiscoveryServiceBootstrapper: Unsupported cache value type: " . gettype($cachedItem->value));
$cachedRegistry = null;
}
} catch (\Throwable $e) {
error_log("DiscoveryServiceBootstrapper: Failed to deserialize cached data: " . $e->getMessage());
$cachedRegistry = null;
}
}
if ($cachedRegistry !== null && ! $cachedRegistry->isEmpty()) {
$consoleCommandsInCache = count($cachedRegistry->attributes->get(\App\Framework\Console\ConsoleCommand::class));
error_log("DiscoveryServiceBootstrapper: Loaded cached registry with " .
$consoleCommandsInCache . " console commands (total items: " . count($cachedRegistry) . ")");
$this->container->singleton(DiscoveryRegistry::class, $cachedRegistry);
// Initializer-Verarbeitung für gecachte Registry
@@ -108,25 +89,14 @@ final readonly class DiscoveryServiceBootstrapper
$initializerProcessor->processInitializers($cachedRegistry);
return $cachedRegistry;
} else {
error_log("DiscoveryServiceBootstrapper: Cached registry is " .
($cachedRegistry === null ? "null" : "empty") .
", falling back to full discovery");
}
}
// Fallback: Vollständige Discovery durchführen
error_log("DiscoveryServiceBootstrapper: No valid cache found, performing full discovery");
// Test: Ist DemoCommand verfügbar?
$demoCommandExists = class_exists(\App\Framework\Console\DemoCommand::class, true);
error_log("DiscoveryServiceBootstrapper: DemoCommand class exists: " . ($demoCommandExists ? "YES" : "NO"));
$results = $this->performBootstrap($pathProvider, $cache, $discoveryConfig);
// Nach der Discovery explizit in unserem eigenen Cache-Format speichern
$consoleCommandCount = count($results->attributes->get(\App\Framework\Console\ConsoleCommand::class));
error_log("DiscoveryServiceBootstrapper: Discovery completed with " . $consoleCommandCount . " console commands");
// Only cache if we found meaningful results
// An empty discovery likely indicates initialization timing issues
@@ -140,8 +110,6 @@ final readonly class DiscoveryServiceBootstrapper
);
$cache->set($cacheItem);
} else {
error_log("DiscoveryServiceBootstrapper: Skipping cache - empty or no console commands found (likely timing issue)");
}
return $results;

View File

@@ -0,0 +1,481 @@
<?php
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\DependencyGraph;
use App\Framework\Discovery\Enhanced\ValueObjects\DependencyNode;
use App\Framework\Discovery\Enhanced\ValueObjects\DependencyEdge;
use App\Framework\Discovery\Enhanced\ValueObjects\ComponentType;
/**
* Dependency Graph Analyzer
*
* Analyzes component dependencies and creates comprehensive dependency graphs
* with intelligent caching and performance optimization.
*/
final readonly class DependencyGraphAnalyzer
{
public function __construct(
private IntelligentMcpCacheManager $cacheManager
) {}
/**
* Analyze component dependencies and create dependency graph
*/
public function analyze(array $components): DependencyGraph
{
return $this->cacheManager->remember(
'dependency_analyzer',
'create_graph',
['components_hash' => $this->hashComponents($components)],
fn() => $this->createDependencyGraph($components),
CacheStrategy::LONG
);
}
/**
* Analyze circular dependencies
*/
public function findCircularDependencies(DependencyGraph $graph): array
{
return $this->cacheManager->remember(
'dependency_analyzer',
'circular_dependencies',
['graph_hash' => $graph->getHash()],
fn() => $this->detectCycles($graph),
CacheStrategy::MEDIUM
);
}
/**
* Get dependency depth analysis
*/
public function analyzeDependencyDepth(DependencyGraph $graph): array
{
return $this->cacheManager->remember(
'dependency_analyzer',
'depth_analysis',
['graph_hash' => $graph->getHash()],
fn() => $this->calculateDepths($graph),
CacheStrategy::MEDIUM
);
}
/**
* Find component clusters (strongly connected components)
*/
public function findComponentClusters(DependencyGraph $graph): array
{
return $this->cacheManager->remember(
'dependency_analyzer',
'component_clusters',
['graph_hash' => $graph->getHash()],
fn() => $this->findStronglyConnectedComponents($graph),
CacheStrategy::LONG
);
}
/**
* Analyze dependency impact (what depends on what)
*/
public function analyzeImpact(DependencyGraph $graph, string $componentName): array
{
return $this->cacheManager->remember(
'dependency_analyzer',
'impact_analysis',
['graph_hash' => $graph->getHash(), 'component' => $componentName],
fn() => $this->calculateImpact($graph, $componentName),
CacheStrategy::SHORT
);
}
/**
* Get optimal loading order based on dependencies
*/
public function getOptimalLoadingOrder(DependencyGraph $graph): array
{
return $this->cacheManager->remember(
'dependency_analyzer',
'loading_order',
['graph_hash' => $graph->getHash()],
fn() => $this->topologicalSort($graph),
CacheStrategy::LONG
);
}
/**
* Create dependency graph from components
*/
private function createDependencyGraph(array $components): DependencyGraph
{
$nodes = [];
$edges = [];
// Create nodes for each component
foreach ($components as $component) {
$nodes[] = $this->createDependencyNode($component);
}
// Analyze dependencies between components
foreach ($components as $component) {
$dependencies = $this->extractDependencies($component);
foreach ($dependencies as $dependency) {
if ($this->componentExists($dependency, $components)) {
$edges[] = new DependencyEdge(
from: $component['name'] ?? $component['class'] ?? 'unknown',
to: $dependency,
type: $this->determineDependencyType($component, $dependency),
strength: $this->calculateDependencyStrength($component, $dependency)
);
}
}
}
return new DependencyGraph($nodes, $edges);
}
/**
* Create dependency node from component
*/
private function createDependencyNode(array $component): DependencyNode
{
return new DependencyNode(
id: $component['name'] ?? $component['class'] ?? uniqid(),
name: $component['name'] ?? $component['class'] ?? 'Unknown',
type: $this->determineComponentType($component),
metadata: $component['metadata'] ?? [],
weight: $this->calculateComponentWeight($component)
);
}
/**
* Extract dependencies from component
*/
private function extractDependencies(array $component): array
{
$dependencies = [];
// Extract from constructor dependencies
if (isset($component['constructor_dependencies'])) {
$dependencies = array_merge($dependencies, $component['constructor_dependencies']);
}
// Extract from method dependencies
if (isset($component['method_dependencies'])) {
$dependencies = array_merge($dependencies, $component['method_dependencies']);
}
// Extract from attribute dependencies
if (isset($component['attributes'])) {
foreach ($component['attributes'] as $attribute) {
if (isset($attribute['dependencies'])) {
$dependencies = array_merge($dependencies, $attribute['dependencies']);
}
}
}
return array_unique($dependencies);
}
/**
* Determine component type from component data
*/
private function determineComponentType(array $component): ComponentType
{
$class = $component['class'] ?? '';
$name = $component['name'] ?? '';
return match (true) {
str_contains($class, 'Controller') => ComponentType::CONTROLLER,
str_contains($class, 'Service') => ComponentType::SERVICE,
str_contains($class, 'Repository') => ComponentType::REPOSITORY,
str_contains($class, 'Middleware') => ComponentType::MIDDLEWARE,
str_contains($class, 'Command') => ComponentType::COMMAND,
str_contains($class, 'Event') => ComponentType::EVENT,
str_contains($class, 'Exception') => ComponentType::EXCEPTION,
str_contains($class, 'ValueObject') || str_contains($class, 'VO') => ComponentType::VALUE_OBJECT,
str_contains($class, 'Interface') => ComponentType::INTERFACE,
default => ComponentType::UNKNOWN
};
}
/**
* Determine dependency type
*/
private function determineDependencyType(array $from, string $to): string
{
// Analyze how the dependency is used
return match (true) {
$this->isConstructorDependency($from, $to) => 'constructor',
$this->isMethodDependency($from, $to) => 'method',
$this->isAttributeDependency($from, $to) => 'attribute',
default => 'unknown'
};
}
/**
* Calculate dependency strength (0.0 - 1.0)
*/
private function calculateDependencyStrength(array $from, string $to): float
{
$strength = 0.0;
// Constructor dependency = highest strength
if ($this->isConstructorDependency($from, $to)) {
$strength += 0.5;
}
// Multiple usage points increase strength
$usageCount = $this->countUsages($from, $to);
$strength += min(0.4, $usageCount * 0.1);
// Interface dependency = lower strength than concrete dependency
if ($this->isInterfaceDependency($to)) {
$strength *= 0.8;
}
return min(1.0, $strength);
}
/**
* Calculate component weight for graph layout
*/
private function calculateComponentWeight(array $component): float
{
$weight = 1.0;
// Controllers and services have higher weight
$type = $this->determineComponentType($component);
$weight *= match ($type) {
ComponentType::CONTROLLER => 1.5,
ComponentType::SERVICE => 1.3,
ComponentType::REPOSITORY => 1.2,
ComponentType::MIDDLEWARE => 1.1,
default => 1.0
};
// Components with many dependencies have higher weight
$dependencyCount = count($this->extractDependencies($component));
$weight += $dependencyCount * 0.1;
return $weight;
}
/**
* Detect circular dependencies using DFS
*/
private function detectCycles(DependencyGraph $graph): array
{
$visited = [];
$recursionStack = [];
$cycles = [];
foreach ($graph->getNodes() as $node) {
if (!isset($visited[$node->id])) {
$this->dfsDetectCycle($graph, $node->id, $visited, $recursionStack, $cycles, []);
}
}
return $cycles;
}
/**
* DFS helper for cycle detection
*/
private function dfsDetectCycle(
DependencyGraph $graph,
string $nodeId,
array &$visited,
array &$recursionStack,
array &$cycles,
array $path
): bool {
$visited[$nodeId] = true;
$recursionStack[$nodeId] = true;
$path[] = $nodeId;
foreach ($graph->getEdgesFrom($nodeId) as $edge) {
$targetId = $edge->to;
if (!isset($visited[$targetId])) {
if ($this->dfsDetectCycle($graph, $targetId, $visited, $recursionStack, $cycles, $path)) {
return true;
}
} elseif (isset($recursionStack[$targetId]) && $recursionStack[$targetId]) {
// Found cycle
$cycleStart = array_search($targetId, $path);
$cycles[] = array_slice($path, $cycleStart);
return true;
}
}
$recursionStack[$nodeId] = false;
return false;
}
/**
* Calculate dependency depths
*/
private function calculateDepths(DependencyGraph $graph): array
{
$depths = [];
$visited = [];
foreach ($graph->getNodes() as $node) {
if (!isset($visited[$node->id])) {
$this->calculateNodeDepth($graph, $node->id, $depths, $visited, []);
}
}
return $depths;
}
/**
* Calculate depth for a single node
*/
private function calculateNodeDepth(
DependencyGraph $graph,
string $nodeId,
array &$depths,
array &$visited,
array $path
): int {
if (isset($depths[$nodeId])) {
return $depths[$nodeId];
}
if (in_array($nodeId, $path)) {
// Circular dependency detected, return arbitrary depth
return 0;
}
$visited[$nodeId] = true;
$path[] = $nodeId;
$maxDepth = 0;
foreach ($graph->getEdgesFrom($nodeId) as $edge) {
$targetDepth = $this->calculateNodeDepth($graph, $edge->to, $depths, $visited, $path);
$maxDepth = max($maxDepth, $targetDepth + 1);
}
$depths[$nodeId] = $maxDepth;
return $maxDepth;
}
/**
* Topological sort for optimal loading order
*/
private function topologicalSort(DependencyGraph $graph): array
{
$inDegree = [];
$queue = [];
$result = [];
// Calculate in-degrees
foreach ($graph->getNodes() as $node) {
$inDegree[$node->id] = 0;
}
foreach ($graph->getEdges() as $edge) {
$inDegree[$edge->to]++;
}
// Add nodes with no incoming edges to queue
foreach ($inDegree as $nodeId => $degree) {
if ($degree === 0) {
$queue[] = $nodeId;
}
}
// Process queue
while (!empty($queue)) {
$nodeId = array_shift($queue);
$result[] = $nodeId;
foreach ($graph->getEdgesFrom($nodeId) as $edge) {
$inDegree[$edge->to]--;
if ($inDegree[$edge->to] === 0) {
$queue[] = $edge->to;
}
}
}
return $result;
}
/**
* Helper methods for dependency analysis
*/
private function componentExists(string $dependency, array $components): bool
{
foreach ($components as $component) {
if (($component['name'] ?? $component['class'] ?? '') === $dependency) {
return true;
}
}
return false;
}
private function isConstructorDependency(array $component, string $dependency): bool
{
return in_array($dependency, $component['constructor_dependencies'] ?? []);
}
private function isMethodDependency(array $component, string $dependency): bool
{
return in_array($dependency, $component['method_dependencies'] ?? []);
}
private function isAttributeDependency(array $component, string $dependency): bool
{
foreach ($component['attributes'] ?? [] as $attribute) {
if (in_array($dependency, $attribute['dependencies'] ?? [])) {
return true;
}
}
return false;
}
private function isInterfaceDependency(string $dependency): bool
{
return str_contains($dependency, 'Interface') || str_ends_with($dependency, 'Contract');
}
private function countUsages(array $component, string $dependency): int
{
$count = 0;
if ($this->isConstructorDependency($component, $dependency)) $count++;
if ($this->isMethodDependency($component, $dependency)) $count++;
if ($this->isAttributeDependency($component, $dependency)) $count++;
return $count;
}
private function hashComponents(array $components): string
{
return md5(serialize($components));
}
private function findStronglyConnectedComponents(DependencyGraph $graph): array
{
// Simplified implementation - in practice would use Tarjan's algorithm
return [];
}
private function calculateImpact(DependencyGraph $graph, string $componentName): array
{
// Find all components that depend on the given component
$impact = [];
foreach ($graph->getEdges() as $edge) {
if ($edge->to === $componentName) {
$impact[] = $edge->from;
}
}
return $impact;
}
}

View File

@@ -0,0 +1,277 @@
<?php
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\Results\DiscoveryRegistry;
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
*
* Advanced discovery system that leverages:
* - MCP caching for intelligent result storage
* - Performance monitoring for optimization
* - Pattern recognition for component classification
* - Predictive discovery for proactive component loading
* - Dependency graph analysis for better understanding
*/
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(
private UnifiedDiscoveryService $baseDiscoveryService,
private Cache $cache,
private array $configuration = []
) {
$this->initializeEnhancedComponents();
}
/**
* Enhanced discovery with intelligent caching and optimization
*/
public function discover(
array $paths = [],
array $attributes = [],
?DiscoveryPlan $plan = null
): DiscoveryResult {
return $this->performanceMonitor->monitor(
'intelligent_discovery',
'discover',
function() use ($paths, $attributes, $plan) {
return $this->executeEnhancedDiscovery($paths, $attributes, $plan);
},
[
'paths_count' => count($paths),
'attributes_count' => count($attributes),
'has_plan' => $plan !== null
]
);
}
/**
* Discover components with predictive loading
*/
public function discoverWithPrediction(array $paths = []): DiscoveryResult
{
// Get prediction of what components will be needed
$predictions = $this->predictor->predictRequiredComponents($paths);
// Create optimized discovery plan
$plan = $this->optimizer->createOptimalPlan($paths, $predictions);
return $this->discover($paths, [], $plan);
}
/**
* Analyze component dependencies and create dependency graph
*/
public function analyzeDependencies(array $components = []): DependencyGraph
{
return $this->cacheManager->remember(
'dependency_analyzer',
'analyze_dependencies',
['components' => md5(serialize($components))],
fn() => $this->dependencyAnalyzer->analyze($components),
CacheStrategy::LONG
);
}
/**
* Get component performance profiles
*/
public function getComponentProfiles(array $componentNames = []): array
{
return $this->cacheManager->remember(
'profile_analyzer',
'get_component_profiles',
['components' => md5(serialize($componentNames))],
fn() => $this->profileAnalyzer->analyzeComponents($componentNames),
CacheStrategy::MEDIUM
);
}
/**
* Recognize patterns in discovered components
*/
public function recognizePatterns(DiscoveryRegistry $registry): PatternAnalysisResult
{
return $this->patternEngine->recognizePatterns($registry);
}
/**
* Optimize discovery process based on historical data
*/
public function optimizeDiscovery(array $historicalMetrics): OptimizationResult
{
return $this->optimizer->optimize($historicalMetrics);
}
/**
* Get enhanced discovery metrics
*/
public function getDiscoveryMetrics(): DiscoveryMetrics
{
$cacheMetrics = $this->cacheManager->getMetrics();
$performanceMetrics = $this->performanceMonitor->getAggregatedMetrics('intelligent_discovery');
return new DiscoveryMetrics(
cacheHitRate: $cacheMetrics->hitRate,
averageDiscoveryTime: $performanceMetrics->averageExecutionTime,
componentsDiscovered: $this->getDiscoveredComponentsCount(),
patternMatchRate: $this->patternEngine->getMatchRate(),
predictionAccuracy: $this->predictor->getAccuracy(),
memoryEfficiency: $this->calculateMemoryEfficiency(),
optimizationImpact: $this->optimizer->getOptimizationImpact()
);
}
/**
* Clear enhanced discovery caches
*/
public function clearEnhancedCaches(): void
{
$this->cacheManager->invalidateTool('intelligent_discovery');
$this->cacheManager->invalidateTool('dependency_analyzer');
$this->cacheManager->invalidateTool('profile_analyzer');
$this->patternEngine->clearPatternCache();
$this->predictor->clearPredictionCache();
}
/**
* Execute enhanced discovery with all optimizations
*/
private function executeEnhancedDiscovery(
array $paths,
array $attributes,
?DiscoveryPlan $plan
): DiscoveryResult {
// Use plan or create default optimization strategy
$actualPlan = $plan ?? $this->optimizer->createDefaultPlan($paths, $attributes);
// Execute base discovery with optimizations
$baseResult = $this->executeOptimizedDiscovery($actualPlan);
// Enhance results with pattern recognition
$patterns = $this->recognizePatterns($baseResult->getRegistry());
// Analyze dependencies
$dependencies = $this->analyzeDependencies($baseResult->getComponents());
// Create enhanced result
return new DiscoveryResult(
registry: $baseResult->getRegistry(),
patterns: $patterns,
dependencies: $dependencies,
metrics: $this->getDiscoveryMetrics(),
plan: $actualPlan
);
}
/**
* Execute discovery with optimization strategies
*/
private function executeOptimizedDiscovery(DiscoveryPlan $plan): BaseDiscoveryResult
{
// Apply memory optimization
$this->optimizer->applyMemoryOptimizations();
// Execute with performance monitoring
return $this->performanceMonitor->monitor(
'base_discovery',
'execute_optimized',
function() use ($plan) {
// Use base discovery service with plan optimizations
return $this->baseDiscoveryService->discover(
$plan->getPaths(),
$plan->getAttributes(),
$plan->getDiscoveryContext()
);
},
$plan->getMetadata()
);
}
/**
* Initialize enhanced discovery components
*/
private function initializeEnhancedComponents(): void
{
// Initialize MCP-integrated cache manager
$this->cacheManager = new IntelligentMcpCacheManager($this->cache, [
'discovery_cache' => [
'default_ttl' => 3600,
'compression_threshold' => 100 * 1024, // 100KB
'max_memory_usage' => 50 * 1024 * 1024 // 50MB
]
]);
// Initialize performance monitor
$this->performanceMonitor = new McpPerformanceMonitor($this->cache);
// Initialize analysis components
$this->dependencyAnalyzer = new DependencyGraphAnalyzer($this->cacheManager);
$this->profileAnalyzer = new PerformanceProfileAnalyzer($this->performanceMonitor);
$this->patternEngine = new PatternRecognitionEngine($this->cacheManager);
$this->optimizer = new DiscoveryOptimizer($this->performanceMonitor);
$this->predictor = new DiscoveryPredictor($this->cacheManager, $this->patternEngine);
}
/**
* Get count of discovered components
*/
private function getDiscoveredComponentsCount(): int
{
return $this->cacheManager->remember(
'discovery_stats',
'component_count',
[],
function() {
// Count components from base discovery service
// This would integrate with the actual discovery registry
return 0; // Placeholder
},
CacheStrategy::SHORT
);
}
/**
* Calculate memory efficiency metrics
*/
private function calculateMemoryEfficiency(): float
{
$metrics = $this->cacheManager->getMetrics();
// Calculate efficiency based on cache compression ratio and memory usage
$compressionRatio = $metrics->compressionRatio;
$memoryUsage = $metrics->memoryUsage;
// 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

@@ -0,0 +1,128 @@
<?php
declare(strict_types=1);
namespace App\Framework\Discovery\Enhanced\ValueObjects;
/**
* Component Type enumeration for discovery classification
*/
enum ComponentType: string
{
case CONTROLLER = 'controller';
case SERVICE = 'service';
case REPOSITORY = 'repository';
case MIDDLEWARE = 'middleware';
case COMMAND = 'command';
case EVENT = 'event';
case EXCEPTION = 'exception';
case VALUE_OBJECT = 'value_object';
case INTERFACE = 'interface';
case TRAIT = 'trait';
case ENUM = 'enum';
case FACTORY = 'factory';
case BUILDER = 'builder';
case DECORATOR = 'decorator';
case ADAPTER = 'adapter';
case UNKNOWN = 'unknown';
/**
* Get component type description
*/
public function getDescription(): string
{
return match ($this) {
self::CONTROLLER => 'HTTP request controller',
self::SERVICE => 'Business logic service',
self::REPOSITORY => 'Data access repository',
self::MIDDLEWARE => 'HTTP middleware',
self::COMMAND => 'Console command',
self::EVENT => 'Domain event',
self::EXCEPTION => 'Exception class',
self::VALUE_OBJECT => 'Value object or DTO',
self::INTERFACE => 'Interface contract',
self::TRAIT => 'Reusable trait',
self::ENUM => 'Enumeration',
self::FACTORY => 'Object factory',
self::BUILDER => 'Object builder',
self::DECORATOR => 'Decorator pattern',
self::ADAPTER => 'Adapter pattern',
self::UNKNOWN => 'Unknown component type'
};
}
/**
* Get component weight for dependency analysis
*/
public function getWeight(): float
{
return match ($this) {
self::CONTROLLER => 1.5,
self::SERVICE => 1.3,
self::REPOSITORY => 1.2,
self::MIDDLEWARE => 1.1,
self::COMMAND => 1.0,
self::EVENT => 0.8,
self::FACTORY => 1.1,
self::BUILDER => 1.0,
self::DECORATOR => 0.9,
self::ADAPTER => 0.9,
self::VALUE_OBJECT => 0.7,
self::INTERFACE => 0.6,
self::TRAIT => 0.5,
self::ENUM => 0.4,
self::EXCEPTION => 0.3,
self::UNKNOWN => 0.5
};
}
/**
* Get typical dependency patterns for this component type
*/
public function getTypicalDependencies(): array
{
return match ($this) {
self::CONTROLLER => ['service', 'repository', 'validator'],
self::SERVICE => ['repository', 'event_dispatcher', 'value_object'],
self::REPOSITORY => ['connection', 'entity_manager', 'cache'],
self::MIDDLEWARE => ['request', 'response', 'next'],
self::COMMAND => ['service', 'input', 'output'],
self::EVENT => ['value_object'],
self::FACTORY => ['configuration', 'dependencies'],
self::BUILDER => ['value_object'],
default => []
};
}
/**
* Check if this component type typically has dependencies
*/
public function hasDependencies(): bool
{
return match ($this) {
self::CONTROLLER,
self::SERVICE,
self::REPOSITORY,
self::MIDDLEWARE,
self::COMMAND,
self::FACTORY => true,
default => false
};
}
/**
* Get performance impact level
*/
public function getPerformanceImpact(): string
{
return match ($this) {
self::CONTROLLER => 'high',
self::SERVICE => 'medium',
self::REPOSITORY => 'high',
self::MIDDLEWARE => 'medium',
self::COMMAND => 'low',
self::EVENT => 'low',
default => 'very_low'
};
}
}

View File

@@ -0,0 +1,267 @@
<?php
declare(strict_types=1);
namespace App\Framework\Discovery\Enhanced\ValueObjects;
/**
* Dependency Edge representation
*
* Represents a dependency relationship between two components.
*/
final readonly class DependencyEdge
{
public function __construct(
public string $from,
public string $to,
public string $type,
public float $strength = 1.0,
public array $metadata = []
) {}
/**
* Get edge weight for graph algorithms
*/
public function getWeight(): float
{
return $this->strength;
}
/**
* Check if this is a strong dependency
*/
public function isStrong(): bool
{
return $this->strength > 0.7;
}
/**
* Check if this is a weak dependency
*/
public function isWeak(): bool
{
return $this->strength < 0.3;
}
/**
* Get dependency description
*/
public function getDescription(): string
{
return match ($this->type) {
'constructor' => 'Constructor dependency (injected)',
'method' => 'Method parameter dependency',
'attribute' => 'Attribute-based dependency',
'inheritance' => 'Inheritance relationship',
'composition' => 'Composition relationship',
'aggregation' => 'Aggregation relationship',
'usage' => 'Usage dependency',
'event' => 'Event-based dependency',
default => 'Unknown dependency type'
};
}
/**
* Get dependency criticality level
*/
public function getCriticality(): string
{
return match ($this->type) {
'constructor' => 'critical',
'inheritance' => 'critical',
'composition' => 'high',
'method' => 'medium',
'attribute' => 'medium',
'aggregation' => 'low',
'usage' => 'low',
'event' => 'very_low',
default => 'unknown'
};
}
/**
* Check if dependency can be easily removed
*/
public function isRemovable(): bool
{
return match ($this->type) {
'constructor', 'inheritance' => false,
'composition' => false,
'method' => $this->strength < 0.5,
'attribute' => true,
'aggregation' => true,
'usage' => true,
'event' => true,
default => false
};
}
/**
* Get performance impact of this dependency
*/
public function getPerformanceImpact(): string
{
$impact = match ($this->type) {
'constructor' => 'startup',
'method' => 'runtime',
'attribute' => 'initialization',
'inheritance' => 'startup',
'composition' => 'startup',
'aggregation' => 'runtime',
'usage' => 'runtime',
'event' => 'event_driven',
default => 'unknown'
};
$severity = match (true) {
$this->strength > 0.8 => 'high',
$this->strength > 0.5 => 'medium',
default => 'low'
};
return "{$severity}_{$impact}";
}
/**
* Check if this dependency creates a potential circular reference
*/
public function isCircularRisk(): bool
{
return in_array($this->type, ['composition', 'aggregation']) && $this->strength > 0.5;
}
/**
* Get refactoring suggestions for this dependency
*/
public function getRefactoringSuggestions(): array
{
$suggestions = [];
if ($this->strength > 0.8 && $this->type === 'method') {
$suggestions[] = [
'type' => 'extract_interface',
'description' => 'Consider extracting an interface to reduce coupling',
'priority' => 'medium'
];
}
if ($this->type === 'usage' && $this->strength > 0.6) {
$suggestions[] = [
'type' => 'dependency_injection',
'description' => 'Consider using dependency injection instead of direct usage',
'priority' => 'high'
];
}
if ($this->isCircularRisk()) {
$suggestions[] = [
'type' => 'break_circular',
'description' => 'This dependency may cause circular references. Consider using events or interfaces.',
'priority' => 'critical'
];
}
if ($this->strength < 0.2) {
$suggestions[] = [
'type' => 'remove_dependency',
'description' => 'This dependency is very weak and might be removable',
'priority' => 'low'
];
}
return $suggestions;
}
/**
* Convert to array representation
*/
public function toArray(): array
{
return [
'from' => $this->from,
'to' => $this->to,
'type' => $this->type,
'strength' => round($this->strength, 3),
'weight' => round($this->getWeight(), 3),
'description' => $this->getDescription(),
'criticality' => $this->getCriticality(),
'is_strong' => $this->isStrong(),
'is_weak' => $this->isWeak(),
'is_removable' => $this->isRemovable(),
'performance_impact' => $this->getPerformanceImpact(),
'is_circular_risk' => $this->isCircularRisk(),
'refactoring_suggestions' => $this->getRefactoringSuggestions(),
'metadata' => $this->metadata
];
}
/**
* Create from array data
*/
public static function fromArray(array $data): self
{
return new self(
from: $data['from'],
to: $data['to'],
type: $data['type'],
strength: $data['strength'] ?? 1.0,
metadata: $data['metadata'] ?? []
);
}
/**
* Create a strong dependency edge
*/
public static function strong(string $from, string $to, string $type): self
{
return new self($from, $to, $type, 0.9);
}
/**
* Create a weak dependency edge
*/
public static function weak(string $from, string $to, string $type): self
{
return new self($from, $to, $type, 0.2);
}
/**
* Create a constructor dependency (always strong)
*/
public static function constructor(string $from, string $to): self
{
return new self($from, $to, 'constructor', 0.95);
}
/**
* Create a method dependency
*/
public static function method(string $from, string $to, float $strength = 0.6): self
{
return new self($from, $to, 'method', $strength);
}
/**
* Create an inheritance relationship (always critical)
*/
public static function inheritance(string $from, string $to): self
{
return new self($from, $to, 'inheritance', 1.0);
}
/**
* Create a composition relationship
*/
public static function composition(string $from, string $to): self
{
return new self($from, $to, 'composition', 0.8);
}
/**
* Create an event-based dependency (usually weak)
*/
public static function event(string $from, string $to): self
{
return new self($from, $to, 'event', 0.3);
}
}

View File

@@ -0,0 +1,318 @@
<?php
declare(strict_types=1);
namespace App\Framework\Discovery\Enhanced\ValueObjects;
/**
* Dependency Graph representation
*
* Represents the complete dependency graph of discovered components.
*/
final readonly class DependencyGraph
{
public function __construct(
private array $nodes,
private array $edges
) {}
/**
* Get all nodes in the graph
*/
public function getNodes(): array
{
return $this->nodes;
}
/**
* Get all edges in the graph
*/
public function getEdges(): array
{
return $this->edges;
}
/**
* Get node by ID
*/
public function getNode(string $nodeId): ?DependencyNode
{
foreach ($this->nodes as $node) {
if ($node->id === $nodeId) {
return $node;
}
}
return null;
}
/**
* Get edges originating from a specific node
*/
public function getEdgesFrom(string $nodeId): array
{
return array_filter($this->edges, fn($edge) => $edge->from === $nodeId);
}
/**
* Get edges pointing to a specific node
*/
public function getEdgesTo(string $nodeId): array
{
return array_filter($this->edges, fn($edge) => $edge->to === $nodeId);
}
/**
* Get direct dependencies of a node
*/
public function getDependencies(string $nodeId): array
{
$edges = $this->getEdgesFrom($nodeId);
return array_map(fn($edge) => $edge->to, $edges);
}
/**
* Get nodes that depend on the given node
*/
public function getDependents(string $nodeId): array
{
$edges = $this->getEdgesTo($nodeId);
return array_map(fn($edge) => $edge->from, $edges);
}
/**
* Check if there's a path between two nodes
*/
public function hasPath(string $from, string $to): bool
{
if ($from === $to) {
return true;
}
$visited = [];
return $this->dfsHasPath($from, $to, $visited);
}
/**
* Get all paths between two nodes
*/
public function getAllPaths(string $from, string $to): array
{
$paths = [];
$visited = [];
$currentPath = [];
$this->dfsAllPaths($from, $to, $visited, $currentPath, $paths);
return $paths;
}
/**
* Get shortest path between two nodes
*/
public function getShortestPath(string $from, string $to): ?array
{
if ($from === $to) {
return [$from];
}
$queue = [[$from]];
$visited = [$from => true];
while (!empty($queue)) {
$path = array_shift($queue);
$node = end($path);
foreach ($this->getDependencies($node) as $neighbor) {
if ($neighbor === $to) {
return array_merge($path, [$neighbor]);
}
if (!isset($visited[$neighbor])) {
$visited[$neighbor] = true;
$queue[] = array_merge($path, [$neighbor]);
}
}
}
return null;
}
/**
* Calculate graph statistics
*/
public function getStatistics(): array
{
$nodeCount = count($this->nodes);
$edgeCount = count($this->edges);
// Calculate in-degree and out-degree distributions
$inDegrees = [];
$outDegrees = [];
foreach ($this->nodes as $node) {
$inDegrees[$node->id] = count($this->getEdgesTo($node->id));
$outDegrees[$node->id] = count($this->getEdgesFrom($node->id));
}
return [
'node_count' => $nodeCount,
'edge_count' => $edgeCount,
'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,
'isolated_nodes' => $this->countIsolatedNodes(),
'strongly_connected' => $this->isStronglyConnected()
];
}
/**
* Get graph hash for caching
*/
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)
]));
}
/**
* Convert to array representation
*/
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()
];
}
/**
* Create subgraph containing only specified nodes
*/
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) =>
in_array($edge->from, $nodeIds) && in_array($edge->to, $nodeIds)
);
return new self(array_values($subNodes), array_values($subEdges));
}
/**
* Merge with another graph
*/
public function mergeWith(self $other): self
{
$mergedNodes = array_merge($this->nodes, $other->getNodes());
$mergedEdges = array_merge($this->edges, $other->getEdges());
// Remove duplicates
$uniqueNodes = [];
$uniqueEdges = [];
$seenNodeIds = [];
$seenEdgeKeys = [];
foreach ($mergedNodes as $node) {
if (!in_array($node->id, $seenNodeIds)) {
$uniqueNodes[] = $node;
$seenNodeIds[] = $node->id;
}
}
foreach ($mergedEdges as $edge) {
$edgeKey = $edge->from . '->' . $edge->to;
if (!in_array($edgeKey, $seenEdgeKeys)) {
$uniqueEdges[] = $edge;
$seenEdgeKeys[] = $edgeKey;
}
}
return new self($uniqueNodes, $uniqueEdges);
}
/**
* DFS helper for path checking
*/
private function dfsHasPath(string $current, string $target, array &$visited): bool
{
if ($current === $target) {
return true;
}
$visited[$current] = true;
foreach ($this->getDependencies($current) as $neighbor) {
if (!isset($visited[$neighbor]) && $this->dfsHasPath($neighbor, $target, $visited)) {
return true;
}
}
return false;
}
/**
* DFS helper for finding all paths
*/
private function dfsAllPaths(
string $current,
string $target,
array &$visited,
array &$currentPath,
array &$paths
): void {
$visited[$current] = true;
$currentPath[] = $current;
if ($current === $target) {
$paths[] = $currentPath;
} else {
foreach ($this->getDependencies($current) as $neighbor) {
if (!isset($visited[$neighbor])) {
$this->dfsAllPaths($neighbor, $target, $visited, $currentPath, $paths);
}
}
}
array_pop($currentPath);
unset($visited[$current]);
}
/**
* Count isolated nodes (no incoming or outgoing edges)
*/
private function countIsolatedNodes(): int
{
$count = 0;
foreach ($this->nodes as $node) {
if (empty($this->getEdgesFrom($node->id)) && empty($this->getEdgesTo($node->id))) {
$count++;
}
}
return $count;
}
/**
* Check if graph is strongly connected
*/
private function isStronglyConnected(): bool
{
if (count($this->nodes) <= 1) {
return true;
}
// Simplified check - in practice would use proper SCC algorithm
$firstNode = $this->nodes[0]->id;
foreach ($this->nodes as $node) {
if ($node->id !== $firstNode && !$this->hasPath($firstNode, $node->id)) {
return false;
}
}
return true;
}
}

View File

@@ -0,0 +1,226 @@
<?php
declare(strict_types=1);
namespace App\Framework\Discovery\Enhanced\ValueObjects;
/**
* Dependency Node representation
*
* Represents a single component in the dependency graph.
*/
final readonly class DependencyNode
{
public function __construct(
public string $id,
public string $name,
public ComponentType $type,
public array $metadata = [],
public float $weight = 1.0
) {}
/**
* Get node importance score based on type and metadata
*/
public function getImportanceScore(): float
{
$score = $this->type->getWeight() * $this->weight;
// Adjust based on metadata
if (isset($this->metadata['usage_count'])) {
$score += min(2.0, $this->metadata['usage_count'] * 0.1);
}
if (isset($this->metadata['is_core']) && $this->metadata['is_core']) {
$score *= 1.5;
}
if (isset($this->metadata['is_deprecated']) && $this->metadata['is_deprecated']) {
$score *= 0.5;
}
return $score;
}
/**
* Check if this node represents a critical component
*/
public function isCritical(): bool
{
return $this->getImportanceScore() > 2.0 ||
($this->metadata['is_core'] ?? false) ||
in_array($this->type, [ComponentType::CONTROLLER, ComponentType::SERVICE]);
}
/**
* Get component category for grouping
*/
public function getCategory(): string
{
return match ($this->type) {
ComponentType::CONTROLLER => 'presentation',
ComponentType::SERVICE, ComponentType::FACTORY, ComponentType::BUILDER => 'business',
ComponentType::REPOSITORY => 'data',
ComponentType::MIDDLEWARE => 'infrastructure',
ComponentType::COMMAND => 'console',
ComponentType::EVENT => 'events',
ComponentType::VALUE_OBJECT, ComponentType::ENUM => 'domain',
ComponentType::INTERFACE, ComponentType::TRAIT => 'contracts',
ComponentType::EXCEPTION => 'exceptions',
ComponentType::DECORATOR, ComponentType::ADAPTER => 'patterns',
default => 'other'
};
}
/**
* Get estimated memory usage of this component
*/
public function getEstimatedMemoryUsage(): int
{
$baseUsage = match ($this->type) {
ComponentType::CONTROLLER => 50 * 1024, // 50KB
ComponentType::SERVICE => 30 * 1024, // 30KB
ComponentType::REPOSITORY => 40 * 1024, // 40KB
ComponentType::MIDDLEWARE => 20 * 1024, // 20KB
ComponentType::COMMAND => 25 * 1024, // 25KB
ComponentType::EVENT => 5 * 1024, // 5KB
ComponentType::VALUE_OBJECT => 2 * 1024, // 2KB
ComponentType::ENUM => 1 * 1024, // 1KB
ComponentType::INTERFACE => 500, // 500B
ComponentType::TRAIT => 3 * 1024, // 3KB
ComponentType::FACTORY => 15 * 1024, // 15KB
ComponentType::BUILDER => 10 * 1024, // 10KB
ComponentType::DECORATOR => 8 * 1024, // 8KB
ComponentType::ADAPTER => 12 * 1024, // 12KB
ComponentType::EXCEPTION => 2 * 1024, // 2KB
default => 10 * 1024 // 10KB
};
// Adjust based on metadata
if (isset($this->metadata['complexity_score'])) {
$baseUsage = (int)($baseUsage * (1 + $this->metadata['complexity_score'] * 0.5));
}
if (isset($this->metadata['line_count'])) {
$baseUsage += (int)($this->metadata['line_count'] * 100); // 100 bytes per line
}
return $baseUsage;
}
/**
* Get performance impact level
*/
public function getPerformanceImpact(): string
{
return $this->type->getPerformanceImpact();
}
/**
* Check if node has specific metadata
*/
public function hasMetadata(string $key): bool
{
return isset($this->metadata[$key]);
}
/**
* Get metadata value with default
*/
public function getMetadata(string $key, mixed $default = null): mixed
{
return $this->metadata[$key] ?? $default;
}
/**
* Convert to array representation
*/
public function toArray(): array
{
return [
'id' => $this->id,
'name' => $this->name,
'type' => $this->type->value,
'type_description' => $this->type->getDescription(),
'weight' => $this->weight,
'importance_score' => round($this->getImportanceScore(), 2),
'is_critical' => $this->isCritical(),
'category' => $this->getCategory(),
'estimated_memory_usage' => $this->getEstimatedMemoryUsage(),
'performance_impact' => $this->getPerformanceImpact(),
'metadata' => $this->metadata
];
}
/**
* Create from array data
*/
public static function fromArray(array $data): self
{
return new self(
id: $data['id'],
name: $data['name'],
type: ComponentType::from($data['type'] ?? 'unknown'),
metadata: $data['metadata'] ?? [],
weight: $data['weight'] ?? 1.0
);
}
/**
* Create a simple node with minimal data
*/
public static function simple(string $id, string $name, ComponentType $type): self
{
return new self($id, $name, $type);
}
/**
* Create a critical node with high importance
*/
public static function critical(string $id, string $name, ComponentType $type): self
{
return new self(
id: $id,
name: $name,
type: $type,
metadata: ['is_core' => true],
weight: 2.0
);
}
/**
* Create node with complexity metadata
*/
public static function withComplexity(
string $id,
string $name,
ComponentType $type,
float $complexity
): self {
return new self(
id: $id,
name: $name,
type: $type,
metadata: ['complexity_score' => $complexity],
weight: 1.0 + ($complexity * 0.5)
);
}
/**
* Create node with usage statistics
*/
public static function withUsage(
string $id,
string $name,
ComponentType $type,
int $usageCount
): self {
return new self(
id: $id,
name: $name,
type: $type,
metadata: ['usage_count' => $usageCount],
weight: 1.0 + min(1.0, $usageCount * 0.1)
);
}
}

View File

@@ -0,0 +1,198 @@
<?php
declare(strict_types=1);
namespace App\Framework\Discovery\Enhanced\ValueObjects;
/**
* Enhanced Discovery Metrics
*
* Comprehensive metrics for the enhanced discovery system performance.
*/
final readonly class DiscoveryMetrics
{
public function __construct(
public float $cacheHitRate,
public float $averageDiscoveryTime,
public int $componentsDiscovered,
public float $patternMatchRate,
public float $predictionAccuracy,
public float $memoryEfficiency,
public float $optimizationImpact,
public array $detailedMetrics = []
) {}
/**
* Calculate overall efficiency score (0.0 - 1.0)
*/
public function getEfficiencyScore(): float
{
return (
$this->cacheHitRate * 0.2 +
$this->patternMatchRate * 0.2 +
$this->predictionAccuracy * 0.2 +
$this->memoryEfficiency * 0.2 +
$this->optimizationImpact * 0.2
);
}
/**
* Get performance rating
*/
public function getPerformanceRating(): string
{
$score = $this->getEfficiencyScore();
return match (true) {
$score >= 0.9 => 'excellent',
$score >= 0.8 => 'very_good',
$score >= 0.7 => 'good',
$score >= 0.6 => 'average',
$score >= 0.5 => 'below_average',
default => 'poor'
};
}
/**
* Get components per second rate
*/
public function getComponentsPerSecond(): float
{
return $this->averageDiscoveryTime > 0
? $this->componentsDiscovered / $this->averageDiscoveryTime
: 0.0;
}
/**
* Get improvement suggestions based on metrics
*/
public function getImprovementSuggestions(): array
{
$suggestions = [];
if ($this->cacheHitRate < 0.7) {
$suggestions[] = [
'type' => 'cache_optimization',
'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
];
}
if ($this->patternMatchRate < 0.6) {
$suggestions[] = [
'type' => 'pattern_improvement',
'priority' => 'medium',
'description' => 'Pattern match rate is low. Update pattern recognition algorithms.',
'current_value' => $this->patternMatchRate,
'target_value' => 0.75
];
}
if ($this->predictionAccuracy < 0.7) {
$suggestions[] = [
'type' => 'prediction_training',
'priority' => 'medium',
'description' => 'Prediction accuracy needs improvement. Consider retraining prediction models.',
'current_value' => $this->predictionAccuracy,
'target_value' => 0.8
];
}
if ($this->memoryEfficiency < 0.6) {
$suggestions[] = [
'type' => 'memory_optimization',
'priority' => 'high',
'description' => 'Memory efficiency is low. Enable streaming mode or increase compression.',
'current_value' => $this->memoryEfficiency,
'target_value' => 0.75
];
}
if ($this->averageDiscoveryTime > 5.0) {
$suggestions[] = [
'type' => 'performance_optimization',
'priority' => 'high',
'description' => 'Discovery time is too high. Consider parallel processing or result caching.',
'current_value' => $this->averageDiscoveryTime,
'target_value' => 3.0
];
}
return $suggestions;
}
/**
* Convert to array representation
*/
public function toArray(): array
{
return [
'cache_hit_rate' => round($this->cacheHitRate, 3),
'average_discovery_time' => round($this->averageDiscoveryTime, 3),
'components_discovered' => $this->componentsDiscovered,
'pattern_match_rate' => round($this->patternMatchRate, 3),
'prediction_accuracy' => round($this->predictionAccuracy, 3),
'memory_efficiency' => round($this->memoryEfficiency, 3),
'optimization_impact' => round($this->optimizationImpact, 3),
'efficiency_score' => round($this->getEfficiencyScore(), 3),
'performance_rating' => $this->getPerformanceRating(),
'components_per_second' => round($this->getComponentsPerSecond(), 2),
'improvement_suggestions' => $this->getImprovementSuggestions(),
'detailed_metrics' => $this->detailedMetrics
];
}
/**
* Create metrics from basic values
*/
public static function basic(
float $cacheHitRate = 0.0,
float $discoveryTime = 0.0,
int $componentsFound = 0
): self {
return new self(
cacheHitRate: $cacheHitRate,
averageDiscoveryTime: $discoveryTime,
componentsDiscovered: $componentsFound,
patternMatchRate: 0.0,
predictionAccuracy: 0.0,
memoryEfficiency: 0.5,
optimizationImpact: 0.0
);
}
/**
* Create empty metrics instance
*/
public static function empty(): self
{
return new self(
cacheHitRate: 0.0,
averageDiscoveryTime: 0.0,
componentsDiscovered: 0,
patternMatchRate: 0.0,
predictionAccuracy: 0.0,
memoryEfficiency: 0.0,
optimizationImpact: 0.0
);
}
/**
* Merge with other metrics (for aggregation)
*/
public function mergeWith(self $other): self
{
return new self(
cacheHitRate: ($this->cacheHitRate + $other->cacheHitRate) / 2,
averageDiscoveryTime: ($this->averageDiscoveryTime + $other->averageDiscoveryTime) / 2,
componentsDiscovered: $this->componentsDiscovered + $other->componentsDiscovered,
patternMatchRate: ($this->patternMatchRate + $other->patternMatchRate) / 2,
predictionAccuracy: ($this->predictionAccuracy + $other->predictionAccuracy) / 2,
memoryEfficiency: ($this->memoryEfficiency + $other->memoryEfficiency) / 2,
optimizationImpact: ($this->optimizationImpact + $other->optimizationImpact) / 2,
detailedMetrics: array_merge($this->detailedMetrics, $other->detailedMetrics)
);
}
}

View File

@@ -0,0 +1,182 @@
<?php
declare(strict_types=1);
namespace App\Framework\Discovery\Enhanced\ValueObjects;
use App\Framework\Discovery\ValueObjects\DiscoveryContext;
/**
* Discovery Plan for optimized component discovery
*
* Defines the strategy and parameters for enhanced discovery execution.
*/
final readonly class DiscoveryPlan
{
public function __construct(
private array $paths,
private array $attributes,
private DiscoveryContext $discoveryContext,
private PlanStrategy $strategy,
private array $optimizations = [],
private array $predictions = [],
private array $metadata = []
) {}
public function getPaths(): array
{
return $this->paths;
}
public function getAttributes(): array
{
return $this->attributes;
}
public function getDiscoveryContext(): DiscoveryContext
{
return $this->discoveryContext;
}
public function getStrategy(): PlanStrategy
{
return $this->strategy;
}
public function getOptimizations(): array
{
return $this->optimizations;
}
public function getPredictions(): array
{
return $this->predictions;
}
public function getMetadata(): array
{
return $this->metadata;
}
/**
* Create an optimized plan for performance
*/
public static function optimized(
array $paths,
array $attributes = [],
array $optimizations = []
): self {
return new self(
paths: $paths,
attributes: $attributes,
discoveryContext: DiscoveryContext::default(),
strategy: PlanStrategy::PERFORMANCE_OPTIMIZED,
optimizations: array_merge([
'memory_efficient' => true,
'cache_aggressive' => true,
'parallel_processing' => true
], $optimizations)
);
}
/**
* Create a comprehensive plan for thorough discovery
*/
public static function comprehensive(
array $paths,
array $attributes = []
): self {
return new self(
paths: $paths,
attributes: $attributes,
discoveryContext: DiscoveryContext::comprehensive(),
strategy: PlanStrategy::COMPREHENSIVE,
optimizations: [
'deep_analysis' => true,
'dependency_mapping' => true,
'pattern_recognition' => true
]
);
}
/**
* Create a minimal plan for basic discovery
*/
public static function minimal(array $paths): self
{
return new self(
paths: $paths,
attributes: [],
discoveryContext: DiscoveryContext::minimal(),
strategy: PlanStrategy::MINIMAL,
optimizations: [
'cache_only' => true,
'skip_analysis' => true
]
);
}
/**
* Check if a specific optimization is enabled
*/
public function hasOptimization(string $optimization): bool
{
return isset($this->optimizations[$optimization]) &&
$this->optimizations[$optimization] === true;
}
/**
* Get optimization value
*/
public function getOptimization(string $key, mixed $default = null): mixed
{
return $this->optimizations[$key] ?? $default;
}
/**
* Create a new plan with additional optimizations
*/
public function withOptimizations(array $additionalOptimizations): self
{
return new self(
paths: $this->paths,
attributes: $this->attributes,
discoveryContext: $this->discoveryContext,
strategy: $this->strategy,
optimizations: array_merge($this->optimizations, $additionalOptimizations),
predictions: $this->predictions,
metadata: $this->metadata
);
}
/**
* Create a new plan with predictions
*/
public function withPredictions(array $predictions): self
{
return new self(
paths: $this->paths,
attributes: $this->attributes,
discoveryContext: $this->discoveryContext,
strategy: $this->strategy,
optimizations: $this->optimizations,
predictions: $predictions,
metadata: $this->metadata
);
}
/**
* Get plan as array for serialization
*/
public function toArray(): array
{
return [
'paths' => $this->paths,
'attributes' => $this->attributes,
'strategy' => $this->strategy->value,
'optimizations' => $this->optimizations,
'predictions' => $this->predictions,
'metadata' => $this->metadata
];
}
}

View File

@@ -0,0 +1,185 @@
<?php
declare(strict_types=1);
namespace App\Framework\Discovery\Enhanced\ValueObjects;
/**
* Discovery Plan Strategy enumeration
*
* Defines different approaches to discovery execution.
*/
enum PlanStrategy: string
{
case PERFORMANCE_OPTIMIZED = 'performance_optimized';
case COMPREHENSIVE = 'comprehensive';
case MINIMAL = 'minimal';
case MEMORY_EFFICIENT = 'memory_efficient';
case CACHE_FIRST = 'cache_first';
case REAL_TIME = 'real_time';
/**
* Get strategy description
*/
public function getDescription(): string
{
return match ($this) {
self::PERFORMANCE_OPTIMIZED => 'Optimized for maximum performance with intelligent caching',
self::COMPREHENSIVE => 'Thorough discovery with complete analysis and dependency mapping',
self::MINIMAL => 'Minimal discovery with basic component detection',
self::MEMORY_EFFICIENT => 'Memory-optimized discovery with streaming and chunking',
self::CACHE_FIRST => 'Cache-first approach with minimal fresh discovery',
self::REAL_TIME => 'Real-time discovery with immediate processing'
};
}
/**
* Get default optimizations for this strategy
*/
public function getDefaultOptimizations(): array
{
return match ($this) {
self::PERFORMANCE_OPTIMIZED => [
'cache_aggressive' => true,
'parallel_processing' => true,
'memory_efficient' => true,
'pattern_recognition' => true
],
self::COMPREHENSIVE => [
'deep_analysis' => true,
'dependency_mapping' => true,
'pattern_recognition' => true,
'performance_profiling' => true
],
self::MINIMAL => [
'cache_only' => true,
'skip_analysis' => true,
'basic_patterns_only' => true
],
self::MEMORY_EFFICIENT => [
'streaming_mode' => true,
'chunk_processing' => true,
'memory_monitoring' => true,
'aggressive_cleanup' => true
],
self::CACHE_FIRST => [
'cache_preference' => 100,
'fresh_discovery_timeout' => 100,
'fallback_minimal' => true
],
self::REAL_TIME => [
'immediate_processing' => true,
'skip_caching' => true,
'minimal_analysis' => true
]
};
}
/**
* Get expected performance characteristics
*/
public function getPerformanceProfile(): array
{
return match ($this) {
self::PERFORMANCE_OPTIMIZED => [
'execution_time' => 'fast',
'memory_usage' => 'moderate',
'accuracy' => 'high',
'cache_efficiency' => 'high'
],
self::COMPREHENSIVE => [
'execution_time' => 'slow',
'memory_usage' => 'high',
'accuracy' => 'maximum',
'cache_efficiency' => 'moderate'
],
self::MINIMAL => [
'execution_time' => 'very_fast',
'memory_usage' => 'low',
'accuracy' => 'basic',
'cache_efficiency' => 'high'
],
self::MEMORY_EFFICIENT => [
'execution_time' => 'moderate',
'memory_usage' => 'very_low',
'accuracy' => 'high',
'cache_efficiency' => 'moderate'
],
self::CACHE_FIRST => [
'execution_time' => 'very_fast',
'memory_usage' => 'low',
'accuracy' => 'depends_on_cache',
'cache_efficiency' => 'maximum'
],
self::REAL_TIME => [
'execution_time' => 'immediate',
'memory_usage' => 'moderate',
'accuracy' => 'good',
'cache_efficiency' => 'none'
]
};
}
/**
* Check if strategy supports a specific feature
*/
public function supports(string $feature): bool
{
$features = match ($this) {
self::PERFORMANCE_OPTIMIZED => [
'caching', 'parallel_processing', 'pattern_recognition', 'dependency_analysis'
],
self::COMPREHENSIVE => [
'caching', 'deep_analysis', 'dependency_mapping', 'pattern_recognition', 'performance_profiling'
],
self::MINIMAL => [
'caching', 'basic_discovery'
],
self::MEMORY_EFFICIENT => [
'streaming', 'chunking', 'memory_monitoring', 'cleanup'
],
self::CACHE_FIRST => [
'caching', 'fallback_discovery'
],
self::REAL_TIME => [
'immediate_processing', 'basic_discovery'
]
};
return in_array($feature, $features, true);
}
/**
* Get recommended strategy based on context
*/
public static function recommendFor(array $context): self
{
$pathCount = $context['path_count'] ?? 0;
$memoryLimit = $context['memory_limit'] ?? 128 * 1024 * 1024; // 128MB
$timeLimit = $context['time_limit'] ?? 30; // 30 seconds
$accuracyRequired = $context['accuracy'] ?? 'high';
// Large codebase with memory constraints
if ($pathCount > 1000 || $memoryLimit < 64 * 1024 * 1024) {
return self::MEMORY_EFFICIENT;
}
// Real-time requirements
if ($timeLimit < 5) {
return self::REAL_TIME;
}
// High accuracy requirements
if ($accuracyRequired === 'maximum') {
return self::COMPREHENSIVE;
}
// Basic requirements
if ($accuracyRequired === 'basic' || $pathCount < 50) {
return self::MINIMAL;
}
// Default to performance optimized
return self::PERFORMANCE_OPTIMIZED;
}
}

View File

@@ -13,6 +13,7 @@ use App\Framework\DI\Initializer;
use App\Framework\DI\InitializerDependencyGraph;
use App\Framework\DI\ValueObjects\DependencyGraphNode;
use App\Framework\Discovery\Results\DiscoveryRegistry;
use App\Framework\Logging\Logger;
use App\Framework\Reflection\ReflectionProvider;
/**
@@ -38,8 +39,12 @@ final readonly class InitializerProcessor
*/
public function processInitializers(DiscoveryRegistry $results): void
{
// Safe Logger resolution - use if available, otherwise rely on error_log
$logger = $this->container->has(Logger::class) ? $this->container->get(Logger::class) : null;
$initializerResults = $results->attributes->get(Initializer::class);
$logger?->debug("InitializerProcessor: Processing " . count($initializerResults) . " initializers");
$dependencyGraph = new InitializerDependencyGraph($this->reflectionProvider);
// Phase 1: Setup-Initializer sofort ausführen & Service-Initializer zum Graph hinzufügen
@@ -48,10 +53,9 @@ final readonly class InitializerProcessor
/** @var Initializer $initializer */
$initializer = $discoveredAttribute->createAttributeInstance();
// Get mapped data from additionalData (was mappedData in AttributeMapping)
// The actual initializer data is in the additionalData from the InitializerMapper
$initializerData = $discoveredAttribute->additionalData;
// The actual initializer data is in the additionalData from the InitializerMapper
if (! $initializerData) {
continue;
}
@@ -63,7 +67,6 @@ final readonly class InitializerProcessor
continue;
}
$methodName = $discoveredAttribute->methodName ?? MethodName::invoke();
// The return type is directly in the additionalData from the InitializerMapper
$returnType = $initializerData['return'] ?? null;
@@ -78,6 +81,8 @@ final readonly class InitializerProcessor
$dependencyGraph->addInitializer($returnType, $discoveredAttribute->className, $methodName);
}
} catch (\Throwable $e) {
// Skip failed initializers
}
}
@@ -95,9 +100,13 @@ final readonly class InitializerProcessor
$executionOrder = $graph->getExecutionOrder();
foreach ($executionOrder as $returnType) {
if ($graph->hasNode($returnType)) {
/** @var DependencyGraphNode $node */
$node = $graph->getNode($returnType);
$this->registerLazyService(
$returnType,
$node->getClassName(),
@@ -148,7 +157,9 @@ final readonly class InitializerProcessor
*/
private function registerLazyService(string $returnType, string $className, string $methodName): void
{
$factory = function ($container) use ($className, $methodName, $returnType) {
$instance = $container->invoker->invoke(ClassName::create($className), $methodName);
// Wenn das ein Interface ist, registriere auch die konkrete Klasse automatisch
@@ -162,7 +173,12 @@ final readonly class InitializerProcessor
return $instance;
};
// Registriere den Return-Type (Interface oder konkrete Klasse)
$this->container->singleton($returnType, $factory);
try {
// Registriere den Return-Type (Interface oder konkrete Klasse)
$this->container->singleton($returnType, $factory);
} catch (\Throwable $e) {
// Service registration failed - continue
}
}
}

View File

@@ -0,0 +1,176 @@
<?php
declare(strict_types=1);
namespace App\Framework\Discovery\Memory;
use App\Framework\Core\ValueObjects\Byte;
/**
* Memory-efficient circular buffer for tracking memory usage history
*
* Replaces inefficient array operations with fixed-size circular buffer
* for constant O(1) memory usage regardless of operation count.
*/
final class CircularMemoryBuffer
{
private array $buffer;
private int $head = 0;
private int $count = 0;
public function __construct(
private readonly int $maxSize = 50
) {
$this->buffer = array_fill(0, $maxSize, null);
}
/**
* Add memory usage sample to buffer
*/
public function add(Byte $usage): void
{
$this->buffer[$this->head] = $usage;
$this->head = ($this->head + 1) % $this->maxSize;
if ($this->count < $this->maxSize) {
$this->count++;
}
}
/**
* Get all current samples in chronological order
* @return Byte[]
*/
public function getSamples(): array
{
if ($this->count === 0) {
return [];
}
$samples = [];
// If buffer isn't full yet, return from start to head
if ($this->count < $this->maxSize) {
for ($i = 0; $i < $this->count; $i++) {
$samples[] = $this->buffer[$i];
}
} else {
// Buffer is full, return in correct chronological order
for ($i = 0; $i < $this->maxSize; $i++) {
$index = ($this->head + $i) % $this->maxSize;
$samples[] = $this->buffer[$index];
}
}
return $samples;
}
/**
* Get most recent N samples
* @return Byte[]
*/
public function getRecentSamples(int $n): array
{
if ($n <= 0 || $this->count === 0) {
return [];
}
$samples = [];
$samplesToGet = min($n, $this->count);
for ($i = 0; $i < $samplesToGet; $i++) {
// Calculate index for the i-th most recent sample
$index = ($this->head - 1 - $i + $this->maxSize) % $this->maxSize;
$samples[] = $this->buffer[$index];
}
// Reverse to get chronological order (oldest first)
return array_reverse($samples);
}
/**
* Get latest (most recent) sample
*/
public function getLatest(): ?Byte
{
if ($this->count === 0) {
return null;
}
$latestIndex = ($this->head - 1 + $this->maxSize) % $this->maxSize;
return $this->buffer[$latestIndex];
}
/**
* Check if buffer has minimum number of samples for analysis
*/
public function hasMinimumSamples(int $minimum): bool
{
return $this->count >= $minimum;
}
/**
* Get current number of samples in buffer
*/
public function getCount(): int
{
return $this->count;
}
/**
* Get maximum buffer size
*/
public function getMaxSize(): int
{
return $this->maxSize;
}
/**
* Check if buffer is full
*/
public function isFull(): bool
{
return $this->count >= $this->maxSize;
}
/**
* Clear all samples from buffer
*/
public function clear(): void
{
$this->buffer = array_fill(0, $this->maxSize, null);
$this->head = 0;
$this->count = 0;
}
/**
* Get buffer statistics for debugging
*/
public function getStatistics(): array
{
if ($this->count === 0) {
return [
'count' => 0,
'max_size' => $this->maxSize,
'is_full' => false,
'memory_usage_bytes' => $this->maxSize * 8, // Rough estimate
];
}
$samples = $this->getSamples();
$usageBytes = array_map(fn (Byte $b) => $b->toBytes(), $samples);
return [
'count' => $this->count,
'max_size' => $this->maxSize,
'is_full' => $this->isFull(),
'min_usage' => min($usageBytes),
'max_usage' => max($usageBytes),
'avg_usage' => (int) (array_sum($usageBytes) / count($usageBytes)),
'memory_usage_bytes' => $this->maxSize * 8, // Fixed size
];
}
}

View File

@@ -13,6 +13,7 @@ use App\Framework\Discovery\Events\MemoryPressureEvent;
use App\Framework\Discovery\Events\MemoryStrategyChangedEvent;
use App\Framework\Discovery\ValueObjects\MemoryStrategy;
use App\Framework\Logging\Logger;
use App\Framework\Logging\ValueObjects\LogContext;
use App\Framework\Performance\MemoryMonitor;
/**
@@ -197,7 +198,7 @@ final readonly class DiscoveryMemoryManager
));
}
$this->logger?->debug('Memory cleanup performed', [
$this->logger?->debug('Memory cleanup performed', LogContext::withData([
'before_usage' => $beforeUsage->toHumanReadable(),
'after_usage' => $afterUsage->toHumanReadable(),
'memory_freed' => $memoryFreed->toHumanReadable(),
@@ -205,22 +206,22 @@ final readonly class DiscoveryMemoryManager
'strategy' => $this->strategy->value,
'was_emergency' => $isEmergency,
'trigger_reason' => $triggerReason,
]);
]));
return $result;
}
/**
* Check for potential memory leaks and emit leak event if detected
* Check for potential memory leaks using CircularMemoryBuffer
*/
public function checkForMemoryLeaks(array $memoryHistory, ?string $context = null): ?MemoryLeakInfo
public function checkForMemoryLeaks(CircularMemoryBuffer $memoryBuffer, ?string $context = null): ?MemoryLeakInfo
{
if (count($memoryHistory) < $this->leakDetectionWindow) {
if (! $memoryBuffer->hasMinimumSamples($this->leakDetectionWindow)) {
return null;
}
// Take recent measurements
$recentHistory = array_slice($memoryHistory, -$this->leakDetectionWindow);
// Get recent measurements from circular buffer (O(1) operation)
$recentHistory = $memoryBuffer->getRecentSamples($this->leakDetectionWindow);
$usageValues = array_map(fn (Byte $usage) => $usage->toBytes(), $recentHistory);
// Calculate trend using linear regression
@@ -353,13 +354,13 @@ final readonly class DiscoveryMemoryManager
));
}
$this->logger?->info('Memory strategy changed', [
$this->logger?->info('Memory strategy changed', LogContext::withData([
'previous_strategy' => $previousStrategy->value,
'new_strategy' => $newStrategy->value,
'change_reason' => $changeReason,
'trigger_metrics' => $triggerMetrics,
'context' => $context,
]);
]));
}
/**

View File

@@ -14,7 +14,7 @@ use App\Framework\Core\ValueObjects\Byte;
*/
final class MemoryGuard
{
private array $memoryHistory = [];
private CircularMemoryBuffer $memoryHistory;
private int $checkCounter = 0;
@@ -26,6 +26,7 @@ final class MemoryGuard
private readonly DiscoveryMemoryManager $memoryManager,
?callable $emergencyCallback = null
) {
$this->memoryHistory = new CircularMemoryBuffer(maxSize: 100);
$this->emergencyCallback = $emergencyCallback;
}
@@ -37,13 +38,8 @@ final class MemoryGuard
$this->checkCounter++;
$memoryStatus = $this->memoryManager->getMemoryStatus();
// Track memory history for leak detection
$this->memoryHistory[] = $memoryStatus->currentUsage;
// Keep only recent history to prevent memory growth
if (count($this->memoryHistory) > 100) {
$this->memoryHistory = array_slice($this->memoryHistory, -50);
}
// Track memory history for leak detection using CircularMemoryBuffer
$this->memoryHistory->add($memoryStatus->currentUsage);
$actions = [];
@@ -73,7 +69,7 @@ final class MemoryGuard
}
}
// Check for memory leaks periodically
// Check for memory leaks periodically using CircularMemoryBuffer
if ($this->checkCounter % 20 === 0) {
$leakInfo = $this->memoryManager->checkForMemoryLeaks($this->memoryHistory);
if ($leakInfo !== null) {
@@ -121,13 +117,15 @@ final class MemoryGuard
{
$memoryStatus = $this->memoryManager->getMemoryStatus();
$historyCount = count($this->memoryHistory);
$samples = $this->memoryHistory->getSamples();
$historyCount = $this->memoryHistory->getCount();
$averageUsage = $historyCount > 0
? Byte::fromBytes((int) (array_sum(array_map(fn ($byte) => $byte->toBytes(), $this->memoryHistory)) / $historyCount))
? Byte::fromBytes((int) (array_sum(array_map(fn (Byte $byte) => $byte->toBytes(), $samples)) / $historyCount))
: Byte::zero();
$peakUsage = $historyCount > 0
? array_reduce($this->memoryHistory, fn ($max, $current) => $current->greaterThan($max ?? Byte::zero()) ? $current : $max, Byte::zero())
? array_reduce($samples, fn ($max, $current) => $current->greaterThan($max ?? Byte::zero()) ? $current : $max, Byte::zero())
: Byte::zero();
return new GuardStatistics(
@@ -145,7 +143,7 @@ final class MemoryGuard
*/
public function reset(): void
{
$this->memoryHistory = [];
$this->memoryHistory->clear();
$this->checkCounter = 0;
$this->emergencyMode = false;
}
@@ -192,8 +190,8 @@ final class MemoryGuard
break;
}
// Check for potential leaks
if (count($this->memoryHistory) > 10) {
// Check for potential leaks using CircularMemoryBuffer
if ($this->memoryHistory->getCount() > 10) {
$leakInfo = $this->memoryManager->checkForMemoryLeaks($this->memoryHistory);
if ($leakInfo !== null) {
$recommendations[] = "Memory leak detected ({$leakInfo->severity->value}): Review object retention";

View File

@@ -0,0 +1,214 @@
<?php
declare(strict_types=1);
namespace App\Framework\Discovery\Performance;
use App\Framework\Cache\Cache;
use App\Framework\Cache\CacheItem;
use App\Framework\Core\ValueObjects\Duration;
use App\Framework\Discovery\Cache\DiscoveryCacheIdentifiers;
use App\Framework\Discovery\Results\DiscoveryRegistry;
/**
* Performance-Optimierungen für das Discovery-System
*
* Reduziert Discovery-Zeit von mehreren Sekunden auf < 100ms durch:
* - Intelligente Verzeichnis-Filterung
* - Optimierte Caching-Strategien
* - Lazy Loading für nicht-kritische Komponenten
*/
final readonly class DiscoveryPerformanceOptimizer
{
public function __construct(
private Cache $cache
) {
}
/**
* Optimiert Discovery-Paths durch Filterung unwichtiger Verzeichnisse
*/
public function getOptimizedPaths(array $basePaths): array
{
$optimizedPaths = [];
foreach ($basePaths as $basePath) {
// Nur relevante Verzeichnisse für Web-Requests scannen
$optimizedPaths = array_merge($optimizedPaths, [
$basePath . '/Application', // Controllers und Services
$basePath . '/Domain', // Business Logic
// Framework nur bei CLI Commands oder wenn explizit gebraucht
...$this->getFrameworkPaths($basePath),
]);
}
return array_filter($optimizedPaths, 'is_dir');
}
/**
* Framework-Paths nur bei Bedarf laden
*/
private function getFrameworkPaths(string $basePath): array
{
$context = $_SERVER['SCRIPT_NAME'] ?? 'unknown';
// Framework-Discovery nur für CLI oder spezielle Routes
if (str_contains($context, 'console.php') ||
str_contains($context, 'admin') ||
$this->isFrameworkRequired()) {
return [
$basePath . '/Framework/Console', // Commands
$basePath . '/Framework/Http', // Route Handlers
$basePath . '/Framework/Mcp', // MCP Tools
$basePath . '/Framework/Attributes', // Framework Attributes
];
}
// Minimal Framework Discovery für Web-Requests
return [
$basePath . '/Framework/Attributes', // Route/Auth Attributes
$basePath . '/Framework/Http/Middlewares', // Essential Middleware
];
}
/**
* Prüft ob Framework-Discovery für aktuellen Request notwendig ist
*/
private function isFrameworkRequired(): bool
{
// Framework wird für Admin-Routes und Console Commands benötigt
$requestUri = $_SERVER['REQUEST_URI'] ?? '';
return str_starts_with($requestUri, '/admin') ||
str_starts_with($requestUri, '/api/system') ||
str_contains($requestUri, 'mcp');
}
/**
* Erstellt Performance-optimierte Discovery Registry mit Lazy Loading
*/
public function createOptimizedRegistry(DiscoveryRegistry $fullRegistry): DiscoveryRegistry
{
// Nur kritische Komponenten sofort laden
$criticalAttributes = [
\App\Framework\Attributes\Route::class,
\App\Framework\Auth\Attributes\IpAuth::class,
\App\Framework\Http\Middlewares\MiddlewarePriorityAttribute::class,
];
// Andere Komponenten lazy loaden
$optimizedRegistry = $fullRegistry->filterByCriticalAttributes($criticalAttributes);
// Cache für später benötigte Komponenten
$this->cacheLazyComponents($fullRegistry, $optimizedRegistry);
return $optimizedRegistry;
}
/**
* Cache für Lazy Loading von nicht-kritischen Komponenten
*/
private function cacheLazyComponents(DiscoveryRegistry $full, DiscoveryRegistry $optimized): void
{
$lazyComponents = $full->diff($optimized);
$cacheKey = DiscoveryCacheIdentifiers::lazyComponentsKey();
$cacheItem = CacheItem::forSet(
key: $cacheKey,
value: $lazyComponents->toArray(),
ttl: Duration::fromHours(2)
);
$this->cache->set($cacheItem);
}
/**
* Lädt Lazy Components bei Bedarf nach
*/
public function loadLazyComponents(string $attributeClass): array
{
$cacheKey = DiscoveryCacheIdentifiers::lazyComponentsKey();
$cached = $this->cache->get($cacheKey);
if ($cached->isHit && is_array($cached->value)) {
$lazyRegistry = DiscoveryRegistry::fromArray($cached->value);
return $lazyRegistry->attributes->get($attributeClass) ?? [];
}
return [];
}
/**
* Prüft ob Discovery-Cache noch aktuell ist (basierend auf Dateisystem-Änderungen)
*/
public function isCacheStale(string $sourcePath): bool
{
$cacheKey = DiscoveryCacheIdentifiers::lastModifiedKey($sourcePath);
$cached = $this->cache->get($cacheKey);
if (! $cached->isHit) {
return true; // Kein Cache vorhanden
}
$cachedTimestamp = $cached->value;
$currentTimestamp = $this->getDirectoryLastModified($sourcePath);
return $currentTimestamp > $cachedTimestamp;
}
/**
* Ermittelt letzte Änderungszeit eines Verzeichnisses (für Cache-Validation)
*/
private function getDirectoryLastModified(string $path): int
{
$latest = 0;
// Nur relevante Dateien prüfen (PHP-Dateien in wichtigen Verzeichnissen)
$relevantDirs = ['Application', 'Domain', 'Framework/Attributes', 'Framework/Http'];
foreach ($relevantDirs as $dir) {
$fullPath = $path . '/' . $dir;
if (is_dir($fullPath)) {
$latest = max($latest, $this->getDirectoryTimestamp($fullPath));
}
}
return $latest;
}
/**
* Rekursive Ermittlung der letzten Änderungszeit
*/
private function getDirectoryTimestamp(string $directory): int
{
$latest = filemtime($directory);
$iterator = new \RecursiveIteratorIterator(
new \RecursiveDirectoryIterator($directory, \RecursiveDirectoryIterator::SKIP_DOTS)
);
foreach ($iterator as $file) {
if ($file->isFile() && $file->getExtension() === 'php') {
$latest = max($latest, $file->getMTime());
}
}
return $latest;
}
/**
* Speichert aktuellen Timestamp für Cache-Validation
*/
public function updateCacheTimestamp(string $sourcePath): void
{
$cacheKey = DiscoveryCacheIdentifiers::lastModifiedKey($sourcePath);
$cacheItem = CacheItem::forSet(
key: $cacheKey,
value: $this->getDirectoryLastModified($sourcePath),
ttl: Duration::fromHours(6)
);
$this->cache->set($cacheItem);
}
}

View File

@@ -0,0 +1,169 @@
<?php
declare(strict_types=1);
namespace App\Framework\Discovery\Plugins;
use App\Framework\Discovery\Analysis\DependencyAnalysisResult;
use App\Framework\Discovery\Analysis\DependencyAnalyzer;
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\Logging\Logger;
use App\Framework\Logging\ValueObjects\LogContext;
use App\Framework\Reflection\ReflectionProvider;
final readonly class DependencyAnalysisPlugin implements DiscoveryPlugin
{
private DependencyAnalyzer $analyzer;
public function __construct(
private ReflectionProvider $reflectionProvider,
private ?Logger $logger = null
) {
$this->analyzer = new DependencyAnalyzer($this->reflectionProvider, $this->logger);
}
/**
* Get the name of this plugin
*/
public function getName(): string
{
return 'dependency_analysis';
}
/**
* Check if this plugin can process the given file
*/
public function canProcess(FilePath $file, FileContext $context): bool
{
// Process all PHP files that contain classes
return $file->hasExtension('php') && !empty($context->getClassNames());
}
/**
* Process a file and collect dependency information
*/
public function processFile(FilePath $file, FileContext $context, DiscoveryDataCollector $collector): void
{
$classNames = $context->getClassNames();
foreach ($classNames as $className) {
// Create a discovered attribute for each class to track it
$attribute = DiscoveredAttribute::create(
className: $className,
attributeType: 'DependencyNode',
instance: null, // Will be filled during analysis
metadata: [
'file' => $file->toString(),
'discovered_at' => date('Y-m-d H:i:s'),
]
);
$collector->addAttribute($attribute);
}
$this->logger?->debug('Processed file for dependency analysis', LogContext::withData([
'file' => $file->toString(),
'classes_found' => count($classNames),
]));
}
/**
* Post-process the discovery results to perform dependency analysis
*/
public function postProcess(DiscoveryRegistry $registry): DiscoveryRegistry
{
$this->logger?->info('Starting post-processing dependency analysis');
// Extract all class names from the discovered attributes
$classNames = [];
foreach ($registry->getByType('DependencyNode') as $attribute) {
$classNames[] = $attribute->className;
}
if (empty($classNames)) {
$this->logger?->warning('No classes found for dependency analysis');
return $registry;
}
// Perform dependency analysis
$analysisResult = $this->analyzer->analyzeWithCircularDetection($classNames);
// Add analysis result to the registry
$analysisAttribute = DiscoveredAttribute::create(
className: 'DependencyAnalysisResult',
attributeType: 'DependencyAnalysis',
instance: $analysisResult,
metadata: [
'analysis_timestamp' => date('Y-m-d H:i:s'),
'classes_analyzed' => count($classNames),
'circular_dependencies' => $analysisResult->getCircularDependencyCount(),
'statistics' => $analysisResult->getStatistics(),
]
);
$updatedRegistry = $registry->add($analysisAttribute);
// Update individual class attributes with dependency information
$graph = $analysisResult->getGraph();
foreach ($registry->getByType('DependencyNode') as $attribute) {
$node = $graph->getNode(\App\Framework\Core\ValueObjects\ClassName::create($attribute->className));
if ($node !== null) {
$updatedAttribute = DiscoveredAttribute::create(
className: $attribute->className,
attributeType: 'DependencyNode',
instance: $node,
metadata: array_merge($attribute->metadata, [
'dependency_count' => $node->getDependencyCount(),
'dependent_count' => $node->getDependentCount(),
'complexity_score' => $node->getComplexityScore(),
'type' => $node->getType()->value,
])
);
$updatedRegistry = $updatedRegistry->replace($attribute, $updatedAttribute);
}
}
$this->logger?->info('Dependency analysis completed', LogContext::withData([
'classes_analyzed' => count($classNames),
'nodes_created' => $graph->getNodeCount(),
'edges_created' => $graph->getEdgeCount(),
'circular_dependencies' => $analysisResult->getCircularDependencyCount(),
]));
return $updatedRegistry;
}
/**
* Get the analysis result from the registry
*/
public function getAnalysisResult(DiscoveryRegistry $registry): ?DependencyAnalysisResult
{
$analysisAttributes = $registry->getByType('DependencyAnalysis');
if (empty($analysisAttributes)) {
return null;
}
$attribute = reset($analysisAttributes);
return $attribute->instance instanceof DependencyAnalysisResult ? $attribute->instance : null;
}
/**
* Get recommendations based on the analysis
*
* @return array<string, mixed>
*/
public function getRecommendations(DiscoveryRegistry $registry): array
{
$analysisResult = $this->getAnalysisResult($registry);
if ($analysisResult === null) {
return ['error' => 'No dependency analysis results found'];
}
return $this->analyzer->getRecommendations($analysisResult->getGraph());
}
}

View File

@@ -0,0 +1,34 @@
<?php
declare(strict_types=1);
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;
interface DiscoveryPlugin
{
/**
* Get the name of this plugin
*/
public function getName(): string;
/**
* Check if this plugin can process the given file
*/
public function canProcess(FilePath $file, FileContext $context): bool;
/**
* Process a file and collect discovery data
*/
public function processFile(FilePath $file, FileContext $context, DiscoveryDataCollector $collector): void;
/**
* Post-process the discovery results (optional)
* This is called after all files have been processed
*/
public function postProcess(DiscoveryRegistry $registry): DiscoveryRegistry;
}

View File

@@ -18,6 +18,7 @@ use App\Framework\Discovery\Memory\MemoryStatus;
use App\Framework\Discovery\ValueObjects\MemoryStrategy;
use App\Framework\Filesystem\File;
use App\Framework\Logging\Logger;
use App\Framework\Logging\ValueObjects\LogContext;
/**
* Adaptive chunking system for Discovery operations
@@ -64,13 +65,13 @@ final class AdaptiveChunker
// Create chunks with adaptive sizing
$chunks = $this->createAdaptiveChunks($files, $fileAnalysis, $batchParams);
$this->logger?->debug('Created adaptive chunks', [
$this->logger?->debug('Created adaptive chunks', LogContext::withData([
'total_files' => count($files),
'chunk_count' => count($chunks),
'average_chunk_size' => $this->calculateAverageChunkSize($chunks),
'strategy' => $batchParams->strategy->value,
'estimated_memory_per_chunk' => $batchParams->estimatedMemoryPerBatch->toHumanReadable(),
]);
]));
return new ChunkCollection($chunks);
}
@@ -108,11 +109,11 @@ final class AdaptiveChunker
if (! $this->memoryGuard->isSafeToProcess() &&
$guardResult->memoryStatus->status === MemoryStatus::CRITICAL &&
$chunkIndex > 0) {
$this->logger?->warning('Stopping chunk processing due to CRITICAL memory constraints', [
$this->logger?->warning('Stopping chunk processing due to CRITICAL memory constraints', LogContext::withData([
'chunk_index' => $chunkIndex,
'processed_files' => $processedFiles,
'memory_status' => $guardResult->memoryStatus->toArray(),
]);
]));
break;
}
@@ -167,11 +168,11 @@ final class AdaptiveChunker
}
} catch (\Throwable $e) {
$this->logger?->error('Chunk processing failed', [
$this->logger?->error('Chunk processing failed', LogContext::withData([
'chunk_index' => $chunkIndex,
'chunk_size' => $adjustedChunk->getFileCount(),
'error' => $e->getMessage(),
]);
]));
// Emit chunk failed event
$this->emitChunkEvent(
@@ -194,10 +195,10 @@ final class AdaptiveChunker
triggerReason: 'Scheduled chunk cleanup',
context: "chunk_{$chunkIndex}"
);
$this->logger?->debug('Performed chunk cleanup', [
$this->logger?->debug('Performed chunk cleanup', LogContext::withData([
'memory_freed' => $cleanupResult->memoryFreed->toHumanReadable(),
'collected_cycles' => $cleanupResult->collectedCycles,
]);
]));
}
}
@@ -333,11 +334,11 @@ final class AdaptiveChunker
if ($targetSize < count($files)) {
$adjustedFiles = array_slice($files, 0, $targetSize);
$this->logger?->info('Adjusted chunk size due to memory pressure', [
$this->logger?->info('Adjusted chunk size due to memory pressure', LogContext::withData([
'original_size' => count($files),
'adjusted_size' => $targetSize,
'memory_status' => $guardResult->memoryStatus->status->value,
]);
]));
return ProcessingChunk::create($adjustedFiles);
}

View File

@@ -30,6 +30,13 @@ final readonly class ClassExtractor
{
try {
$content = $this->fileSystemService->readFile($file);
// Skip files without PHP tags (e.g., pure HTML templates)
// This prevents HTML tags from being misinterpreted as PHP classes
if (!$this->containsPhpCode($content)) {
return [];
}
$classes = $this->tokenizer->extractClasses($content);
return array_map(
@@ -43,6 +50,15 @@ final readonly class ClassExtractor
}
}
/**
* Check if content contains actual PHP code
*/
private function containsPhpCode(string $content): bool
{
$trimmed = trim($content);
return str_starts_with($trimmed, '<?php') || str_starts_with($trimmed, '<?=');
}
/**
* Extract detailed class information with metadata
* @return array<array{type: string, name: string, namespace: ?string, fqn: string, line: int}>
@@ -52,6 +68,11 @@ final readonly class ClassExtractor
try {
$content = $this->fileSystemService->readFile($file);
// Skip files without PHP tags
if (!$this->containsPhpCode($content)) {
return [];
}
return $this->tokenizer->extractClasses($content);
} catch (Throwable) {
@@ -68,6 +89,11 @@ final readonly class ClassExtractor
try {
$content = $this->fileSystemService->readFile($file);
// Skip files without PHP tags
if (!$this->containsPhpCode($content)) {
return [];
}
return $this->tokenizer->extractFunctions($content);
} catch (Throwable) {
@@ -84,6 +110,11 @@ final readonly class ClassExtractor
try {
$content = $this->fileSystemService->readFile($file);
// Skip files without PHP tags
if (!$this->containsPhpCode($content)) {
return [];
}
return $this->tokenizer->extractAttributes($content);
} catch (Throwable) {
@@ -100,6 +131,11 @@ final readonly class ClassExtractor
try {
$content = $this->fileSystemService->readFile($file);
// Skip files without PHP tags
if (!$this->containsPhpCode($content)) {
return [];
}
return $this->tokenizer->extractUseStatements($content);
} catch (Throwable) {
@@ -141,6 +177,16 @@ final readonly class ClassExtractor
try {
$content = $this->fileSystemService->readFile($file);
// Skip files without PHP tags
if (!$this->containsPhpCode($content)) {
return [
'classes' => [],
'functions' => [],
'attributes' => [],
'uses' => [],
];
}
return [
'classes' => $this->tokenizer->extractClasses($content),
'functions' => $this->tokenizer->extractFunctions($content),

View File

@@ -38,6 +38,8 @@ final readonly class FileStreamProcessor
): int {
$totalFiles = 0;
error_log("FileStreamProcessor: Processing directories: " . implode(', ', $directories));
foreach ($directories as $directory) {
$directoryPath = FilePath::create($directory);
@@ -64,6 +66,7 @@ final readonly class FileStreamProcessor
$this->processingContext->maybeCollectGarbage($totalFiles);
} catch (\Throwable $e) {
// Only log errors, not every processed file
$this->logger?->warning(
"Failed to process file {$file->getPath()->toString()}: {$e->getMessage()}"
);
@@ -74,6 +77,8 @@ final readonly class FileStreamProcessor
}
}
error_log("FileStreamProcessor: Total files processed: " . $totalFiles);
return $totalFiles;
}

View File

@@ -59,14 +59,16 @@ final class ProcessingContext
// Get new reflection
try {
if ($className->exists()) {
$this->currentReflection = $this->reflectionProvider->getClass($className);
$this->currentClassName = $className;
// IMPORTANT: Skip exists() check completely - it would trigger autoloading
// which could fail if constructor dependencies aren't available yet.
// Instead, directly try to get reflection and catch any errors.
$this->currentReflection = $this->reflectionProvider->getClass($className);
$this->currentClassName = $className;
return $this->currentReflection;
}
return $this->currentReflection;
} catch (\Throwable) {
// Class can't be reflected
// Class can't be reflected (missing, parse error, autoload failure, etc.)
// This is fine during discovery - we skip it and continue
}
return null;

View File

@@ -11,6 +11,7 @@ use App\Framework\Discovery\DiscoveryDataCollector;
use App\Framework\Discovery\ValueObjects\AttributeTarget;
use App\Framework\Discovery\ValueObjects\DiscoveredAttribute;
use App\Framework\Discovery\ValueObjects\FileContext;
use App\Framework\Discovery\ValueObjects\InterfaceMapping;
use App\Framework\Discovery\ValueObjects\TemplateMapping;
use App\Framework\Filesystem\File;
use App\Framework\Http\Method;
@@ -54,13 +55,13 @@ final class VisitorCoordinator
FileContext $fileContext,
DiscoveryDataCollector $collector
): void {
// Process each class in the file
// Process templates FIRST (before potential exceptions in class processing)
$this->processTemplates($file, $collector);
// Then process classes
foreach ($fileContext->getClassNames() as $className) {
$this->processClass($className, $fileContext, $collector);
}
// Process templates (file-based)
$this->processTemplates($file, $collector);
}
/**
@@ -166,10 +167,14 @@ final class VisitorCoordinator
): void {
foreach ($this->targetInterfaces as $targetInterface) {
if ($reflection->implementsInterface($targetInterface)) {
$collector->getInterfaceRegistry()->add(
$targetInterface,
$className->getFullyQualified()
// Create InterfaceMapping object as expected by InterfaceRegistry
$mapping = InterfaceMapping::create(
interface: $targetInterface,
implementation: $className->getFullyQualified(),
file: $reflection->getFileName()
);
$collector->getInterfaceRegistry()->add($mapping);
}
}
}
@@ -182,7 +187,8 @@ final class VisitorCoordinator
$filePath = $file->getPath()->toString();
if (str_ends_with($filePath, '.view.php') || str_contains($filePath, '/views/')) {
$templateName = basename($filePath, '.php');
// Extract clean template name: dashboard.view.php → dashboard
$templateName = basename($filePath, '.view.php');
$mapping = TemplateMapping::create($templateName, $filePath);
$collector->getTemplateRegistry()->add($mapping);
}

View File

@@ -58,6 +58,10 @@ final class AttributeRegistry implements Countable
try {
$mappings[$attributeClass][] = DiscoveredAttribute::fromArray($mappingArray);
} catch (\Throwable $e) {
// Log error for debugging
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

@@ -200,4 +200,46 @@ final readonly class DiscoveryRegistry implements Countable
{
return $this->attributes;
}
/**
* Filter registry by critical attributes (for performance optimization)
* @param array<class-string> $criticalAttributes
*/
public function filterByCriticalAttributes(array $criticalAttributes): self
{
$filteredAttributes = new AttributeRegistry();
foreach ($criticalAttributes as $attributeClass) {
$items = $this->attributes->get($attributeClass);
if (! empty($items)) {
foreach ($items as $item) {
$filteredAttributes->add($attributeClass, $item);
}
}
}
return new self($filteredAttributes, $this->interfaces, $this->templates);
}
/**
* Get difference between two registries (for lazy loading)
*/
public function diff(self $other): self
{
$diffAttributes = new AttributeRegistry();
// Find attributes that are in this registry but not in the other
foreach ($this->attributes->getAllTypes() as $attributeClass) {
$thisItems = $this->attributes->get($attributeClass);
$otherItems = $other->attributes->get($attributeClass);
foreach ($thisItems as $item) {
if (! in_array($item, $otherItems, true)) {
$diffAttributes->add($attributeClass, $item);
}
}
}
return new self($diffAttributes, $this->interfaces, $this->templates);
}
}

View File

@@ -32,8 +32,14 @@ final class InterfaceRegistry implements Countable
*/
public function toArray(): array
{
// Serialize InterfaceMapping objects properly to avoid incomplete object issues
$mappingsData = [];
foreach ($this->mappings as $mapping) {
$mappingsData[] = $mapping->toArray();
}
return [
'mappings' => $this->mappings,
'mappings' => $mappingsData,
];
}
@@ -44,7 +50,19 @@ final class InterfaceRegistry implements Countable
public static function fromArray(array $data): self
{
$registry = new self();
$registry->mappings = $data['mappings'] ?? [];
// Properly deserialize InterfaceMapping objects from array data
$mappings = [];
foreach ($data['mappings'] ?? [] as $mappingData) {
if (is_array($mappingData)) {
$mappings[] = InterfaceMapping::fromArray($mappingData);
} else {
// Legacy support for direct InterfaceMapping objects (should not happen with new serialization)
$mappings[] = $mappingData;
}
}
$registry->mappings = $mappings;
$registry->implementationsByInterface = []; // Don't restore cache, will be rebuilt
$registry->isOptimized = false; // Always force re-optimization for cache data integrity
@@ -57,17 +75,49 @@ final class InterfaceRegistry implements Countable
*/
public function get(string $interface): array
{
if (! isset($this->implementationsByInterface[$interface])) {
$this->implementationsByInterface[$interface] = [];
// TEMPORARY FIX: Protect against incomplete object access
error_log("InterfaceRegistry::get() called for interface: $interface");
foreach ($this->mappings as $mapping) {
if ($mapping->interface->getFullyQualified() === $interface) {
$this->implementationsByInterface[$interface][] = $mapping->implementation;
try {
if (! isset($this->implementationsByInterface[$interface])) {
$this->implementationsByInterface[$interface] = [];
foreach ($this->mappings as $mapping) {
// Check if mapping is complete before accessing properties
if (! is_object($mapping)) {
error_log("InterfaceRegistry::get() - Mapping is not an object: " . gettype($mapping));
continue;
}
if (get_class($mapping) === '__PHP_Incomplete_Class') {
error_log("InterfaceRegistry::get() - Found incomplete InterfaceMapping object, skipping");
continue;
}
// Safe property access
if (isset($mapping->interface) && isset($mapping->implementation)) {
if ($mapping->interface->getFullyQualified() === $interface) {
$this->implementationsByInterface[$interface][] = $mapping->implementation;
}
} else {
error_log("InterfaceRegistry::get() - Mapping missing interface or implementation properties");
}
}
}
}
return $this->implementationsByInterface[$interface];
error_log("InterfaceRegistry::get() returning " . count($this->implementationsByInterface[$interface]) . " implementations for $interface");
return $this->implementationsByInterface[$interface];
} catch (\Throwable $e) {
error_log("InterfaceRegistry::get() caught exception: " . $e->getMessage());
error_log("InterfaceRegistry::get() exception trace: " . $e->getTraceAsString());
// Return empty array on error to prevent fatal errors
return [];
}
}
/**

View File

@@ -0,0 +1,86 @@
<?php
declare(strict_types=1);
namespace App\Framework\Discovery\Runtime;
use App\Framework\Discovery\Results\AttributeRegistry;
use App\Framework\Discovery\Results\DiscoveryRegistry;
use App\Framework\Discovery\Results\InterfaceRegistry;
use App\Framework\Discovery\Results\TemplateRegistry;
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
{
public function __construct(
private DiscoveryStorageService $storage
) {
}
/**
* Load all pre-discovered data for runtime use
*/
public function loadAll(): DiscoveryRegistry
{
$attributes = $this->storage->loadAttributes() ?? new AttributeRegistry();
$templates = $this->storage->loadTemplates() ?? new TemplateRegistry();
$interfaces = $this->storage->loadInterfaces() ?? new InterfaceRegistry();
return new DiscoveryRegistry(
attributes: $attributes,
templates: $templates,
interfaces: $interfaces
);
}
/**
* Load only attributes (fastest for web requests)
*/
public function loadAttributes(): AttributeRegistry
{
return $this->storage->loadAttributes() ?? new AttributeRegistry();
}
/**
* Load only templates (for template engine)
*/
public function loadTemplates(): TemplateRegistry
{
return $this->storage->loadTemplates() ?? new TemplateRegistry();
}
/**
* Load only interfaces (for DI container)
*/
public function loadInterfaces(): InterfaceRegistry
{
return $this->storage->loadInterfaces() ?? new InterfaceRegistry();
}
/**
* Check if pre-discovered data exists
*/
public function hasPreDiscoveredData(): bool
{
return $this->storage->exists('attributes')
&& $this->storage->exists('templates')
&& $this->storage->exists('interfaces');
}
/**
* Get storage timestamps for cache invalidation
*/
public function getStorageTimestamps(): array
{
return [
'attributes' => $this->storage->getLastModified('attributes'),
'templates' => $this->storage->getLastModified('templates'),
'interfaces' => $this->storage->getLastModified('interfaces')
];
}
}

View File

@@ -26,6 +26,7 @@ use App\Framework\Discovery\ValueObjects\DiscoveryContext;
use App\Framework\Filesystem\FilePath;
use App\Framework\Filesystem\FileSystemService;
use App\Framework\Logging\Logger;
use App\Framework\Logging\ValueObjects\LogContext;
/**
* Enhanced Discovery cache manager with memory-aware and tiered caching strategies
@@ -85,10 +86,10 @@ final class DiscoveryCacheManager
$cached = $this->processRetrievedData($item->value);
if (! $cached instanceof DiscoveryRegistry) {
$this->logger?->warning('Invalid cache data type', [
$this->logger?->warning('Invalid cache data type', LogContext::withData([
'key' => $key->toString(),
'type' => gettype($cached),
]);
]));
$this->emitCacheMissEvent($key->toString(), 'corrupted');
return null;
@@ -96,9 +97,9 @@ final class DiscoveryCacheManager
// Validate cache freshness
if ($this->isStale($context, $cached)) {
$this->logger?->info('Discovery cache is stale', [
$this->logger?->info('Discovery cache is stale', LogContext::withData([
'key' => $key->toString(),
]);
]));
$this->invalidate($context);
$this->emitCacheMissEvent($key->toString(), 'expired');
@@ -160,7 +161,7 @@ final class DiscoveryCacheManager
$this->updateCacheMetrics($key->toString(), $dataSize, $cacheLevel, 'store');
$this->trackAccess($key->toString());
$this->logger?->info('Discovery results cached with memory awareness', [
$this->logger?->info('Discovery results cached with memory awareness', LogContext::withData([
'key' => $key->toString(),
'items' => count($registry),
'cache_level' => $cacheLevel->value,
@@ -168,7 +169,7 @@ final class DiscoveryCacheManager
'memory_pressure' => $memoryStatus->memoryPressure->toDecimal(),
'data_size' => $dataSize->toHumanReadable(),
'ttl' => $adjustedTtl->toHours(),
]);
]));
}
return $success;
@@ -191,11 +192,11 @@ final class DiscoveryCacheManager
$success = $this->cache->set($cacheItem);
if ($success) {
$this->logger?->info('Discovery results cached (standard)', [
$this->logger?->info('Discovery results cached (standard)', LogContext::withData([
'key' => $key->toString(),
'items' => count($registry),
'ttl' => $ttl->toHours(),
]);
]));
}
return $success;

View File

@@ -1,70 +0,0 @@
<?php
declare(strict_types=1);
namespace App\Framework\Discovery\Storage;
use App\Framework\Filesystem\FileMetadata;
use App\Framework\Filesystem\Storage;
use SplFileInfo;
/**
* Spezialisiertes Storage-Interface für Discovery-Operationen
* Erweitert das Standard-Storage-Interface mit Discovery-spezifischen Methoden
*/
interface DiscoveryStorage extends Storage
{
/**
* Findet geänderte Dateien seit dem letzten Scan
*
* @param string $directory Zu durchsuchendes Verzeichnis
* @param array<string, array> $fileMetadata Vorhandene Metadaten [path => metadata]
* @return array<SplFileInfo> Liste geänderter Dateien
*/
public function findChangedFiles(string $directory, array $fileMetadata): array;
/**
* Führt einen inkrementellen Scan durch
*
* @param string $directory Zu durchsuchendes Verzeichnis
* @param array<string, array> $fileMetadata Vorhandene Metadaten [path => metadata]
* @return array<string, array> Aktualisierte Metadaten [path => metadata]
*/
public function incrementalScan(string $directory, array $fileMetadata): array;
/**
* Findet alle Dateien mit einem bestimmten Muster
*
* @param string $directory Zu durchsuchendes Verzeichnis
* @param string $pattern Suchmuster (z.B. '*.php')
* @return array<SplFileInfo> Liste gefundener Dateien
*/
public function findFiles(string $directory, string $pattern): array;
/**
* Erstellt Metadaten für eine Datei
*
* @param string $filePath Pfad zur Datei
* @param bool $calculateChecksum Ob eine Prüfsumme berechnet werden soll
* @return FileMetadata Metadaten der Datei
*/
public function getDiscoveryMetadata(string $filePath, bool $calculateChecksum = true): FileMetadata;
/**
* Erstellt Metadaten für mehrere Dateien parallel
*
* @param array<string> $filePaths Liste von Dateipfaden
* @param bool $calculateChecksum Ob Prüfsummen berechnet werden sollen
* @return array<string, FileMetadata> Metadaten [path => metadata]
*/
public function getDiscoveryMetadataMultiple(array $filePaths, bool $calculateChecksum = true): array;
/**
* Registriert Dateisystem-Events für Änderungserkennung
*
* @param string $directory Zu überwachendes Verzeichnis
* @param callable $callback Callback-Funktion für Events (string $path, string $event)
* @return bool Erfolg der Registrierung
*/
public function registerFileSystemEvents(string $directory, callable $callback): bool;
}

View File

@@ -0,0 +1,180 @@
<?php
declare(strict_types=1);
namespace App\Framework\Discovery\Storage;
use App\Framework\Core\PathProvider;
use App\Framework\Core\ValueObjects\Timestamp;
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\FileStorage;
/**
* Storage for pre-discovered data using Filesystem module
*
* Stores discovery results as PHP files for fast loading at runtime
*/
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()) {
$this->storageDir->create();
}
}
/**
* Store attributes registry
*/
public function storeAttributes(AttributeRegistry $registry): void
{
$data = $registry->toArray();
$this->writePhpFile('attributes', $data);
}
/**
* Load attributes registry
*/
public function loadAttributes(): ?AttributeRegistry
{
$data = $this->readPhpFile('attributes');
if ($data === null) {
return null;
}
return AttributeRegistry::fromArray($data);
}
/**
* Store templates registry
*/
public function storeTemplates(TemplateRegistry $registry): void
{
$data = $registry->toArray();
$this->writePhpFile('templates', $data);
}
/**
* Load templates registry
*/
public function loadTemplates(): ?TemplateRegistry
{
$data = $this->readPhpFile('templates');
if ($data === null) {
return null;
}
return TemplateRegistry::fromArray($data);
}
/**
* Store interfaces registry
*/
public function storeInterfaces(InterfaceRegistry $registry): void
{
$data = $registry->toArray();
$this->writePhpFile('interfaces', $data);
}
/**
* Load interfaces registry
*/
public function loadInterfaces(): ?InterfaceRegistry
{
$data = $this->readPhpFile('interfaces');
if ($data === null) {
return null;
}
return InterfaceRegistry::fromArray($data);
}
/**
* Get last modification time for a storage type
*/
public function getLastModified(string $type): ?Timestamp
{
$filePath = $this->getFilePath($type);
if (!$this->storage->exists($filePath->toString())) {
return null;
}
$metadata = $this->storage->getMetadata($filePath->toString());
return Timestamp::fromInt((int)$metadata->modifiedAt->format('U'));
}
/**
* Check if storage exists for a type
*/
public function exists(string $type): bool
{
return $this->storage->exists($this->getFilePath($type)->toString());
}
/**
* Clear all discovery storage
*/
public function clear(): void
{
$files = $this->storageDir->getFiles();
foreach ($files as $file) {
$this->storage->delete($file->getPath()->toString());
}
}
/**
* Write data as serialized PHP file for fast loading
*/
private function writePhpFile(string $type, array $data): void
{
$filePath = $this->getFilePath($type);
// Use serialize instead of var_export to handle Value Objects
$serialized = serialize($data);
$phpCode = "<?php\n\nreturn unserialize(" . var_export($serialized, true) . ");\n";
$this->storage->put($filePath->toString(), $phpCode);
}
/**
* Read PHP file data
*/
private function readPhpFile(string $type): ?array
{
$filePath = $this->getFilePath($type);
if (!$this->storage->exists($filePath->toString())) {
return null;
}
$data = require $filePath->toString();
return is_array($data) ? $data : null;
}
/**
* Get file path for storage type
*/
private function getFilePath(string $type): FilePath
{
return FilePath::create($this->storageDir->getPathString() . '/' . $type . '.php');
}
}

View File

@@ -37,6 +37,7 @@ use App\Framework\Filesystem\ValueObjects\FilePattern;
use App\Framework\Filesystem\ValueObjects\ScannerMemoryUsage;
use App\Framework\Filesystem\ValueObjects\ScannerMetrics;
use App\Framework\Logging\Logger;
use App\Framework\Logging\ValueObjects\LogContext;
use App\Framework\Performance\EnhancedPerformanceCollector;
use App\Framework\Performance\MemoryMonitor;
use App\Framework\Performance\PerformanceCategory;
@@ -141,7 +142,9 @@ final readonly class UnifiedDiscoveryService
$this->clock,
$fileSystemService,
$this->logger,
$cacheTimeoutHours
$cacheTimeoutHours,
$this->memoryManager,
$this->eventDispatcher
);
// Initialize adaptive chunker with memory management
@@ -183,9 +186,9 @@ final readonly class UnifiedDiscoveryService
// Create memory guard with emergency callback
$this->memoryGuard = $this->memoryManager->createMemoryGuard(
emergencyCallback: function () {
$this->logger?->critical('Emergency memory cleanup triggered during discovery', [
$this->logger?->critical('Emergency memory cleanup triggered during discovery', LogContext::withData([
'memory_status' => $this->memoryManager->getMemoryStatus()->toArray(),
]);
]));
// Force cleanup of processing context caches
$this->processingContext?->clearCaches();
@@ -195,11 +198,11 @@ final readonly class UnifiedDiscoveryService
}
);
$this->logger?->info('Memory management initialized', [
$this->logger?->info('Memory management initialized', LogContext::withData([
'strategy' => $strategy->value,
'memory_limit' => $memoryLimit->toHumanReadable(),
'pressure_threshold' => $this->configuration->memoryPressureThreshold,
]);
]));
}
/**
@@ -426,6 +429,94 @@ final readonly class UnifiedDiscoveryService
return 0;
}
/**
* Get cache status including hit rates, size, and health metrics
*/
public function getCacheStatus(): array
{
try {
$healthStatus = $this->cacheManager->getHealthStatus();
$cacheMetrics = $this->cacheManager->getCacheMetrics();
return [
'health' => $healthStatus,
'metrics' => $cacheMetrics?->toArray() ?? [],
'status' => 'available',
];
} catch (\Throwable $e) {
$this->logger?->error('Failed to get cache status', [
'error' => $e->getMessage(),
]);
return [
'error' => $e->getMessage(),
'status' => 'unavailable',
];
}
}
/**
* Warm cache for specified paths or default framework paths
* @param array<int, string>|null $paths
*/
public function warmCache(?array $paths = null): array
{
$startTime = microtime(true);
if ($paths === null) {
$paths = [
$this->pathProvider->src(),
];
}
$warmedCount = 0;
$errors = [];
foreach ($paths as $path) {
try {
// Trigger discovery - cache wird automatisch befüllt
$this->discover();
$warmedCount++;
} catch (\Throwable $e) {
$errors[] = "Failed to warm cache for path '{$path}': {$e->getMessage()}";
$this->logger?->error('Cache warm failed', [
'path' => $path,
'error' => $e->getMessage(),
]);
}
}
$duration = microtime(true) - $startTime;
return [
'warmed_paths' => $warmedCount,
'total_paths' => count($paths),
'duration_ms' => round($duration * 1000, 2),
'errors' => $errors,
'cache_status' => $this->getCacheStatus(),
];
}
/**
* Clear all discovery cache entries
*/
public function clearCache(): bool
{
try {
$cleared = $this->cacheManager->clearAll();
$this->logger?->info('Discovery cache cleared successfully');
return $cleared;
} catch (\Throwable $e) {
$this->logger?->error('Failed to clear cache', [
'error' => $e->getMessage(),
]);
return false;
}
}
/**
* Perform the actual discovery using new architecture with memory management
*/
@@ -435,10 +526,10 @@ final readonly class UnifiedDiscoveryService
// Check initial memory status
$initialMemoryStatus = $this->memoryManager->getMemoryStatus();
$this->logger?->debug('Starting discovery with memory management', [
$this->logger?->debug('Starting discovery with memory management', LogContext::withData([
'initial_memory' => $initialMemoryStatus->toArray(),
'paths' => $context->paths,
]);
]));
// Use adaptive chunked processing if memory management is enabled
if ($this->configuration->enableMemoryMonitoring) {
@@ -453,20 +544,20 @@ final readonly class UnifiedDiscoveryService
// Final memory status and cleanup
$finalMemoryStatus = $this->memoryManager->getMemoryStatus();
$this->logger?->debug('Discovery completed', [
$this->logger?->debug('Discovery completed', LogContext::withData([
'final_memory' => $finalMemoryStatus->toArray(),
'registry_size' => count($registry),
]);
]));
// Cleanup
$collector->clear();
$cleanupResult = $this->memoryManager->performCleanup();
if ($cleanupResult->wasEffective()) {
$this->logger?->debug('Final cleanup performed', [
$this->logger?->debug('Final cleanup performed', LogContext::withData([
'memory_freed' => $cleanupResult->memoryFreed->toHumanReadable(),
'collected_cycles' => $cleanupResult->collectedCycles,
]);
]));
}
return $registry;
@@ -488,11 +579,11 @@ final readonly class UnifiedDiscoveryService
// Create adaptive chunks
$chunks = $this->adaptiveChunker->createChunks($allFiles);
$this->logger?->info('Created adaptive chunks for memory-managed processing', [
$this->logger?->info('Created adaptive chunks for memory-managed processing', LogContext::withData([
'total_files' => count($allFiles),
'chunk_count' => $chunks->count(),
'total_size' => $chunks->getTotalSize()->toHumanReadable(),
]);
]));
// Process chunks with memory monitoring
$processingResult = $this->adaptiveChunker->processChunks(
@@ -509,11 +600,11 @@ final readonly class UnifiedDiscoveryService
if (! $this->memoryGuard->isSafeToProcess() &&
$guardResult->memoryStatus->status === \App\Framework\Discovery\Memory\MemoryStatus::CRITICAL &&
$processedInChunk > 0) {
$this->logger?->warning('Stopping chunk processing due to CRITICAL memory constraints', [
$this->logger?->warning('Stopping chunk processing due to CRITICAL memory constraints', LogContext::withData([
'file' => $file->getPath(),
'processed_in_chunk' => $processedInChunk,
'memory_status' => $guardResult->memoryStatus->toArray(),
]);
]));
break;
}
@@ -533,16 +624,16 @@ final readonly class UnifiedDiscoveryService
return true; // Indicate successful processing
},
progressCallback: function ($processedFiles, $totalFiles, $chunkIndex, $totalChunks) {
$this->logger?->debug('Processing progress', [
$this->logger?->debug('Processing progress', LogContext::withData([
'processed_files' => $processedFiles,
'total_files' => $totalFiles,
'chunk' => "{$chunkIndex}/{$totalChunks}",
'progress' => round(($processedFiles / $totalFiles) * 100, 1) . '%',
]);
]));
}
);
$this->logger?->info('Memory-managed processing completed', $processingResult->toArray());
$this->logger?->info('Memory-managed processing completed', LogContext::withData($processingResult->toArray()));
}
/**
@@ -550,14 +641,22 @@ final readonly class UnifiedDiscoveryService
*/
private function performStandardDiscovery(DiscoveryContext $context, DiscoveryDataCollector $collector): void
{
error_log("UnifiedDiscoveryService: performStandardDiscovery starting with paths: " . implode(', ', $context->paths));
// Process files with the standard file processor and visitor coordinator
$this->fileProcessor->processDirectories(
$context->paths,
$collector,
function ($file, $fileContext, $collector) {
// Debug: Log files being processed, specifically looking for RequestFactory
if (str_contains($file->getPath()->toString(), 'RequestFactory.php')) {
error_log("UnifiedDiscoveryService: Processing RequestFactory file: " . $file->getPath()->toString());
}
$this->visitorCoordinator->processFile($file, $fileContext, $collector);
}
);
error_log("UnifiedDiscoveryService: performStandardDiscovery completed");
}
/**

View File

@@ -0,0 +1,141 @@
<?php
declare(strict_types=1);
namespace App\Framework\Discovery\ValueObjects;
use App\Framework\Core\ValueObjects\ClassName;
final readonly class DependencyEdge
{
public function __construct(
private ClassName $source,
private ClassName $target,
private DependencyRelation $relation,
private int $weight = 1
) {}
/**
* Create a dependency edge
*/
public static function create(
ClassName $source,
ClassName $target,
DependencyRelation $relation,
int $weight = 1
): self {
return new self($source, $target, $relation, $weight);
}
/**
* Get the source class (the one that depends on target)
*/
public function getSource(): ClassName
{
return $this->source;
}
/**
* Get the target class (the dependency)
*/
public function getTarget(): ClassName
{
return $this->target;
}
/**
* Get the type of dependency relationship
*/
public function getRelation(): DependencyRelation
{
return $this->relation;
}
/**
* Get the weight/importance of this dependency
*/
public function getWeight(): int
{
return $this->weight;
}
/**
* Check if this is a circular dependency (source == target)
*/
public function isCircular(): bool
{
return $this->source->equals($this->target);
}
/**
* Check if this is a strong dependency (constructor injection)
*/
public function isStrong(): bool
{
return $this->relation === DependencyRelation::CONSTRUCTOR_INJECTION;
}
/**
* Check if this is a weak dependency (method parameter, property)
*/
public function isWeak(): bool
{
return in_array($this->relation, [
DependencyRelation::METHOD_PARAMETER,
DependencyRelation::PROPERTY_TYPE,
DependencyRelation::RETURN_TYPE,
]);
}
/**
* Create an inverted edge (target -> source)
*/
public function invert(): self
{
return new self(
$this->target,
$this->source,
$this->relation->invert(),
$this->weight
);
}
/**
* Convert to array for serialization
*
* @return array<string, mixed>
*/
public function toArray(): array
{
return [
'source' => $this->source->toString(),
'target' => $this->target->toString(),
'relation' => $this->relation->value,
'weight' => $this->weight,
'is_circular' => $this->isCircular(),
'is_strong' => $this->isStrong(),
'is_weak' => $this->isWeak(),
];
}
/**
* Convert to string representation
*/
public function toString(): string
{
$arrow = $this->isStrong() ? ' => ' : ' -> ';
return sprintf(
'%s%s%s (%s, weight: %d)',
$this->source->getShortName(),
$arrow,
$this->target->getShortName(),
$this->relation->value,
$this->weight
);
}
public function __toString(): string
{
return $this->toString();
}
}

View File

@@ -0,0 +1,435 @@
<?php
declare(strict_types=1);
namespace App\Framework\Discovery\ValueObjects;
use App\Framework\Core\ValueObjects\ClassName;
use InvalidArgumentException;
final readonly class DependencyGraph
{
/** @var array<string, DependencyNode> */
private array $nodes;
/** @var array<DependencyEdge> */
private array $edges;
/**
* @param array<string, DependencyNode> $nodes
* @param array<DependencyEdge> $edges
*/
public function __construct(array $nodes = [], array $edges = [])
{
$this->nodes = $nodes;
$this->edges = $edges;
}
/**
* Create an empty dependency graph
*/
public static function empty(): self
{
return new self();
}
/**
* Add a node to the graph
*/
public function addNode(DependencyNode $node): self
{
$nodes = $this->nodes;
$nodes[$node->getClassName()->toString()] = $node;
return new self($nodes, $this->edges);
}
/**
* Add an edge to the graph
*/
public function addEdge(DependencyEdge $edge): self
{
// Ensure both nodes exist first
$graph = $this->ensureNodesExist($edge->getSource(), $edge->getTarget());
$edges = $graph->edges;
$edges[] = $edge;
return new self($graph->nodes, $edges);
}
/**
* Get a node by class name
*/
public function getNode(ClassName $className): ?DependencyNode
{
return $this->nodes[$className->toString()] ?? null;
}
/**
* Check if a node exists
*/
public function hasNode(ClassName $className): bool
{
return isset($this->nodes[$className->toString()]);
}
/**
* Get all nodes
*
* @return array<string, DependencyNode>
*/
public function getNodes(): array
{
return $this->nodes;
}
/**
* Get all edges
*
* @return array<DependencyEdge>
*/
public function getEdges(): array
{
return $this->edges;
}
/**
* Get the number of nodes
*/
public function getNodeCount(): int
{
return count($this->nodes);
}
/**
* Get the number of edges
*/
public function getEdgeCount(): int
{
return count($this->edges);
}
/**
* Find circular dependencies in the graph
*
* @return array<array<string>>
*/
public function findCircularDependencies(): array
{
$visited = [];
$recursionStack = [];
$cycles = [];
foreach ($this->nodes as $node) {
$className = $node->getClassName()->toString();
if (!isset($visited[$className])) {
$this->dfsForCycles($node, $visited, $recursionStack, $cycles, []);
}
}
return $cycles;
}
/**
* Get nodes with highest dependency count
*
* @return array<DependencyNode>
*/
public function getHighestDependencyNodes(int $limit = 10): array
{
$nodes = array_values($this->nodes);
usort($nodes, fn($a, $b) => $b->getDependencyCount() <=> $a->getDependencyCount());
return array_slice($nodes, 0, $limit);
}
/**
* Get nodes with highest dependent count (most used)
*
* @return array<DependencyNode>
*/
public function getMostUsedNodes(int $limit = 10): array
{
$nodes = array_values($this->nodes);
usort($nodes, fn($a, $b) => $b->getDependentCount() <=> $a->getDependentCount());
return array_slice($nodes, 0, $limit);
}
/**
* Get leaf nodes (no dependencies)
*
* @return array<DependencyNode>
*/
public function getLeafNodes(): array
{
return array_filter($this->nodes, fn(DependencyNode $node) => $node->isLeaf());
}
/**
* Get root nodes (no dependents)
*
* @return array<DependencyNode>
*/
public function getRootNodes(): array
{
return array_filter($this->nodes, fn(DependencyNode $node) => $node->isRoot());
}
/**
* Get nodes by type
*
* @return array<DependencyNode>
*/
public function getNodesByType(DependencyType $type): array
{
return array_filter(
$this->nodes,
fn(DependencyNode $node) => $node->getType() === $type
);
}
/**
* Calculate dependency depth for a node
*/
public function getDependencyDepth(ClassName $className): int
{
$node = $this->getNode($className);
if ($node === null) {
return 0;
}
$visited = [];
return $this->calculateDepth($node, $visited);
}
/**
* Get dependency path from one node to another
*
* @return array<string>|null
*/
public function getDependencyPath(ClassName $from, ClassName $to): ?array
{
$fromNode = $this->getNode($from);
$toNode = $this->getNode($to);
if ($fromNode === null || $toNode === null) {
return null;
}
$visited = [];
$path = [];
if ($this->findPath($fromNode, $toNode, $visited, $path)) {
return $path;
}
return null;
}
/**
* Get graph statistics
*
* @return array<string, mixed>
*/
public function getStatistics(): array
{
$totalComplexity = 0;
$typeDistribution = [];
$circularDependencies = $this->findCircularDependencies();
foreach ($this->nodes as $node) {
$totalComplexity += $node->getComplexityScore();
$type = $node->getType()->value;
$typeDistribution[$type] = ($typeDistribution[$type] ?? 0) + 1;
}
$avgComplexity = count($this->nodes) > 0 ? $totalComplexity / count($this->nodes) : 0;
return [
'node_count' => count($this->nodes),
'edge_count' => count($this->edges),
'circular_dependencies' => count($circularDependencies),
'leaf_nodes' => count($this->getLeafNodes()),
'root_nodes' => count($this->getRootNodes()),
'average_complexity' => round($avgComplexity, 2),
'total_complexity' => round($totalComplexity, 2),
'type_distribution' => $typeDistribution,
'max_dependency_count' => $this->getMaxDependencyCount(),
'max_dependent_count' => $this->getMaxDependentCount(),
];
}
/**
* Convert to array for serialization
*
* @return array<string, mixed>
*/
public function toArray(): array
{
return [
'statistics' => $this->getStatistics(),
'nodes' => array_map(
fn(DependencyNode $node) => $node->toArray(),
array_values($this->nodes)
),
'edges' => array_map(
fn(DependencyEdge $edge) => $edge->toArray(),
$this->edges
),
'circular_dependencies' => $this->findCircularDependencies(),
];
}
/**
* Ensure both source and target nodes exist in the graph
*/
private function ensureNodesExist(ClassName $source, ClassName $target): self
{
$graph = $this;
if (!$this->hasNode($source)) {
$sourceType = DependencyType::fromClassName($source->toString());
$sourceNode = DependencyNode::create($source, $sourceType);
$graph = $graph->addNode($sourceNode);
}
if (!$this->hasNode($target)) {
$targetType = DependencyType::fromClassName($target->toString());
$targetNode = DependencyNode::create($target, $targetType);
$graph = $graph->addNode($targetNode);
}
return $graph;
}
/**
* DFS algorithm for cycle detection
*
* @param array<string, bool> $visited
* @param array<string, bool> $recursionStack
* @param array<array<string>> $cycles
* @param array<string> $currentPath
*/
private function dfsForCycles(
DependencyNode $node,
array &$visited,
array &$recursionStack,
array &$cycles,
array $currentPath
): void {
$className = $node->getClassName()->toString();
$visited[$className] = true;
$recursionStack[$className] = true;
$currentPath[] = $className;
foreach ($node->getDependencies() as $edge) {
$targetClassName = $edge->getTarget()->toString();
if (isset($recursionStack[$targetClassName]) && $recursionStack[$targetClassName]) {
// Found a cycle
$cycleStart = array_search($targetClassName, $currentPath);
if ($cycleStart !== false) {
$cycles[] = array_slice($currentPath, $cycleStart);
}
} elseif (!isset($visited[$targetClassName])) {
$targetNode = $this->getNode($edge->getTarget());
if ($targetNode !== null) {
$this->dfsForCycles($targetNode, $visited, $recursionStack, $cycles, $currentPath);
}
}
}
$recursionStack[$className] = false;
array_pop($currentPath);
}
/**
* Calculate dependency depth recursively
*
* @param array<string, bool> $visited
*/
private function calculateDepth(DependencyNode $node, array &$visited): int
{
$className = $node->getClassName()->toString();
if (isset($visited[$className])) {
return 0; // Avoid infinite recursion
}
$visited[$className] = true;
$maxDepth = 0;
foreach ($node->getDependencies() as $edge) {
$dependencyNode = $this->getNode($edge->getTarget());
if ($dependencyNode !== null) {
$depth = 1 + $this->calculateDepth($dependencyNode, $visited);
$maxDepth = max($maxDepth, $depth);
}
}
unset($visited[$className]);
return $maxDepth;
}
/**
* Find path between two nodes
*
* @param array<string, bool> $visited
* @param array<string> $path
*/
private function findPath(
DependencyNode $from,
DependencyNode $to,
array &$visited,
array &$path
): bool {
$fromClassName = $from->getClassName()->toString();
$toClassName = $to->getClassName()->toString();
$visited[$fromClassName] = true;
$path[] = $fromClassName;
if ($fromClassName === $toClassName) {
return true;
}
foreach ($from->getDependencies() as $edge) {
$nextClassName = $edge->getTarget()->toString();
if (!isset($visited[$nextClassName])) {
$nextNode = $this->getNode($edge->getTarget());
if ($nextNode !== null && $this->findPath($nextNode, $to, $visited, $path)) {
return true;
}
}
}
array_pop($path);
return false;
}
/**
* Get maximum dependency count among all nodes
*/
private function getMaxDependencyCount(): int
{
$max = 0;
foreach ($this->nodes as $node) {
$max = max($max, $node->getDependencyCount());
}
return $max;
}
/**
* Get maximum dependent count among all nodes
*/
private function getMaxDependentCount(): int
{
$max = 0;
foreach ($this->nodes as $node) {
$max = max($max, $node->getDependentCount());
}
return $max;
}
}

View File

@@ -0,0 +1,227 @@
<?php
declare(strict_types=1);
namespace App\Framework\Discovery\ValueObjects;
use App\Framework\Core\ValueObjects\ClassName;
final readonly class DependencyNode
{
/** @var array<DependencyEdge> */
private array $dependencies;
/** @var array<DependencyEdge> */
private array $dependents;
public function __construct(
private ClassName $className,
private DependencyType $type,
array $dependencies = [],
array $dependents = []
) {
$this->dependencies = $dependencies;
$this->dependents = $dependents;
}
/**
* Create a new dependency node
*/
public static function create(
ClassName $className,
DependencyType $type
): self {
return new self($className, $type);
}
/**
* Get the class name this node represents
*/
public function getClassName(): ClassName
{
return $this->className;
}
/**
* Get the dependency type (service, value object, etc.)
*/
public function getType(): DependencyType
{
return $this->type;
}
/**
* Get all direct dependencies
*
* @return array<DependencyEdge>
*/
public function getDependencies(): array
{
return $this->dependencies;
}
/**
* Get all classes that depend on this class
*
* @return array<DependencyEdge>
*/
public function getDependents(): array
{
return $this->dependents;
}
/**
* Add a dependency to this node
*/
public function withDependency(DependencyEdge $dependency): self
{
return new self(
$this->className,
$this->type,
[...$this->dependencies, $dependency],
$this->dependents
);
}
/**
* Add a dependent to this node
*/
public function withDependent(DependencyEdge $dependent): self
{
return new self(
$this->className,
$this->type,
$this->dependencies,
[...$this->dependents, $dependent]
);
}
/**
* Get the dependency count
*/
public function getDependencyCount(): int
{
return count($this->dependencies);
}
/**
* Get the dependent count
*/
public function getDependentCount(): int
{
return count($this->dependents);
}
/**
* Check if this node has circular dependencies
*/
public function hasCircularDependency(): bool
{
foreach ($this->dependencies as $dependency) {
if ($dependency->getTarget()->equals($this->className)) {
return true;
}
}
return false;
}
/**
* Get complexity score based on dependency and dependent counts
*/
public function getComplexityScore(): float
{
$dependencyWeight = count($this->dependencies) * 0.7;
$dependentWeight = count($this->dependents) * 0.3;
return $dependencyWeight + $dependentWeight;
}
/**
* Check if this is a leaf node (no dependencies)
*/
public function isLeaf(): bool
{
return empty($this->dependencies);
}
/**
* Check if this is a root node (no dependents)
*/
public function isRoot(): bool
{
return empty($this->dependents);
}
/**
* Get dependency names only
*
* @return array<string>
*/
public function getDependencyNames(): array
{
return array_map(
fn(DependencyEdge $edge) => $edge->getTarget()->toString(),
$this->dependencies
);
}
/**
* Get dependent names only
*
* @return array<string>
*/
public function getDependentNames(): array
{
return array_map(
fn(DependencyEdge $edge) => $edge->getSource()->toString(),
$this->dependents
);
}
/**
* Convert to array for serialization
*
* @return array<string, mixed>
*/
public function toArray(): array
{
return [
'class_name' => $this->className->toString(),
'type' => $this->type->value,
'dependency_count' => $this->getDependencyCount(),
'dependent_count' => $this->getDependentCount(),
'complexity_score' => $this->getComplexityScore(),
'is_leaf' => $this->isLeaf(),
'is_root' => $this->isRoot(),
'has_circular_dependency' => $this->hasCircularDependency(),
'dependencies' => array_map(
fn(DependencyEdge $edge) => $edge->toArray(),
$this->dependencies
),
'dependents' => array_map(
fn(DependencyEdge $edge) => $edge->toArray(),
$this->dependents
),
];
}
/**
* Convert to string representation
*/
public function toString(): string
{
return sprintf(
'%s (%s) [deps: %d, dependents: %d, complexity: %.1f]',
$this->className->getShortName(),
$this->type->value,
$this->getDependencyCount(),
$this->getDependentCount(),
$this->getComplexityScore()
);
}
public function __toString(): string
{
return $this->toString();
}
}

View File

@@ -0,0 +1,153 @@
<?php
declare(strict_types=1);
namespace App\Framework\Discovery\ValueObjects;
enum DependencyRelation: string
{
case CONSTRUCTOR_INJECTION = 'constructor_injection';
case METHOD_PARAMETER = 'method_parameter';
case PROPERTY_TYPE = 'property_type';
case RETURN_TYPE = 'return_type';
case EXTENDS = 'extends';
case IMPLEMENTS = 'implements';
case USES_TRAIT = 'uses_trait';
case STATIC_CALL = 'static_call';
case INSTANTIATION = 'instantiation';
case TYPE_HINT = 'type_hint';
/**
* Get the strength of this dependency relationship
*/
public function getStrength(): int
{
return match ($this) {
self::CONSTRUCTOR_INJECTION => 10,
self::EXTENDS => 9,
self::IMPLEMENTS => 8,
self::USES_TRAIT => 7,
self::PROPERTY_TYPE => 6,
self::METHOD_PARAMETER => 5,
self::RETURN_TYPE => 4,
self::INSTANTIATION => 3,
self::STATIC_CALL => 2,
self::TYPE_HINT => 1,
};
}
/**
* Check if this is a compile-time dependency
*/
public function isCompileTime(): bool
{
return in_array($this, [
self::EXTENDS,
self::IMPLEMENTS,
self::USES_TRAIT,
self::CONSTRUCTOR_INJECTION,
self::PROPERTY_TYPE,
]);
}
/**
* Check if this is a runtime dependency
*/
public function isRuntime(): bool
{
return in_array($this, [
self::METHOD_PARAMETER,
self::INSTANTIATION,
self::STATIC_CALL,
]);
}
/**
* Check if this creates a strong coupling
*/
public function isStrongCoupling(): bool
{
return in_array($this, [
self::CONSTRUCTOR_INJECTION,
self::EXTENDS,
self::IMPLEMENTS,
]);
}
/**
* Check if this creates a weak coupling
*/
public function isWeakCoupling(): bool
{
return in_array($this, [
self::METHOD_PARAMETER,
self::RETURN_TYPE,
self::TYPE_HINT,
]);
}
/**
* Get the inverted relation (for creating bidirectional edges)
*/
public function invert(): self
{
return match ($this) {
self::CONSTRUCTOR_INJECTION => self::CONSTRUCTOR_INJECTION, // Dependency -> Dependent
self::METHOD_PARAMETER => self::METHOD_PARAMETER,
self::PROPERTY_TYPE => self::PROPERTY_TYPE,
self::RETURN_TYPE => self::RETURN_TYPE,
self::EXTENDS => self::EXTENDS, // Child -> Parent becomes Parent -> Child
self::IMPLEMENTS => self::IMPLEMENTS, // Implementation -> Interface becomes Interface -> Implementation
self::USES_TRAIT => self::USES_TRAIT, // User -> Trait becomes Trait -> User
self::STATIC_CALL => self::STATIC_CALL,
self::INSTANTIATION => self::INSTANTIATION,
self::TYPE_HINT => self::TYPE_HINT,
};
}
/**
* Get the description of this relationship
*/
public function getDescription(): string
{
return match ($this) {
self::CONSTRUCTOR_INJECTION => 'Constructor dependency injection',
self::METHOD_PARAMETER => 'Method parameter type hint',
self::PROPERTY_TYPE => 'Property type declaration',
self::RETURN_TYPE => 'Method return type',
self::EXTENDS => 'Class inheritance',
self::IMPLEMENTS => 'Interface implementation',
self::USES_TRAIT => 'Trait usage',
self::STATIC_CALL => 'Static method call',
self::INSTANTIATION => 'Direct instantiation',
self::TYPE_HINT => 'Type hint usage',
};
}
/**
* Get the impact on container resolution
*/
public function getContainerImpact(): string
{
return match ($this) {
self::CONSTRUCTOR_INJECTION => 'High - Required for instantiation',
self::METHOD_PARAMETER => 'Medium - Required for method calls',
self::PROPERTY_TYPE => 'Low - Static typing only',
self::RETURN_TYPE => 'Low - Return type validation',
self::EXTENDS => 'High - Parent class must be resolved',
self::IMPLEMENTS => 'Medium - Interface binding affects resolution',
self::USES_TRAIT => 'Low - Trait methods are copied',
self::STATIC_CALL => 'Low - No instance required',
self::INSTANTIATION => 'Medium - Direct instantiation bypasses container',
self::TYPE_HINT => 'Low - Documentation only',
};
}
/**
* Convert to string
*/
public function toString(): string
{
return $this->value;
}
}

View File

@@ -0,0 +1,197 @@
<?php
declare(strict_types=1);
namespace App\Framework\Discovery\ValueObjects;
enum DependencyType: string
{
case SERVICE = 'service';
case CONTROLLER = 'controller';
case REPOSITORY = 'repository';
case VALUE_OBJECT = 'value_object';
case ENTITY = 'entity';
case MIDDLEWARE = 'middleware';
case EVENT_HANDLER = 'event_handler';
case COMMAND_HANDLER = 'command_handler';
case INITIALIZER = 'initializer';
case INTERFACE = 'interface';
case ABSTRACT_CLASS = 'abstract_class';
case TRAIT = 'trait';
case ENUM = 'enum';
case EXCEPTION = 'exception';
case UNKNOWN = 'unknown';
/**
* Determine dependency type from class name and characteristics
*/
public static function fromClassName(string $className, bool $isInterface = false, bool $isAbstract = false, bool $isTrait = false, bool $isEnum = false): self
{
if ($isInterface) {
return self::INTERFACE;
}
if ($isAbstract) {
return self::ABSTRACT_CLASS;
}
if ($isTrait) {
return self::TRAIT;
}
if ($isEnum) {
return self::ENUM;
}
// Pattern-based detection
$shortName = basename(str_replace('\\', '/', $className));
if (str_ends_with($shortName, 'Controller')) {
return self::CONTROLLER;
}
if (str_ends_with($shortName, 'Repository')) {
return self::REPOSITORY;
}
if (str_ends_with($shortName, 'Service')) {
return self::SERVICE;
}
if (str_ends_with($shortName, 'Container') || str_contains($shortName, 'Container')) {
return self::SERVICE; // DI containers are services
}
if (str_ends_with($shortName, 'Middleware')) {
return self::MIDDLEWARE;
}
if (str_ends_with($shortName, 'Handler')) {
if (str_contains($shortName, 'Event')) {
return self::EVENT_HANDLER;
}
if (str_contains($shortName, 'Command')) {
return self::COMMAND_HANDLER;
}
}
if (str_ends_with($shortName, 'Initializer')) {
return self::INITIALIZER;
}
if (str_ends_with($shortName, 'Exception')) {
return self::EXCEPTION;
}
// Namespace-based detection
if (str_contains($className, '\\ValueObjects\\') || str_contains($className, '\\ValueObject\\')) {
return self::VALUE_OBJECT;
}
if (str_contains($className, '\\Entity\\') || str_contains($className, '\\Entities\\')) {
return self::ENTITY;
}
if (str_contains($className, '\\Domain\\')) {
return self::VALUE_OBJECT; // Domain classes are often value objects
}
return self::UNKNOWN;
}
/**
* Get the priority for dependency resolution (lower = higher priority)
*/
public function getResolutionPriority(): int
{
return match ($this) {
self::VALUE_OBJECT => 1,
self::ENTITY => 2,
self::INTERFACE => 3,
self::REPOSITORY => 4,
self::SERVICE => 5,
self::CONTROLLER => 6,
self::MIDDLEWARE => 7,
self::EVENT_HANDLER => 8,
self::COMMAND_HANDLER => 9,
self::INITIALIZER => 10,
self::ABSTRACT_CLASS => 11,
self::TRAIT => 12,
self::ENUM => 13,
self::EXCEPTION => 14,
self::UNKNOWN => 15,
};
}
/**
* Check if this type typically has many dependencies
*/
public function isHighDependency(): bool
{
return in_array($this, [
self::CONTROLLER,
self::SERVICE,
self::COMMAND_HANDLER,
self::EVENT_HANDLER,
]);
}
/**
* Check if this type typically has few dependencies
*/
public function isLowDependency(): bool
{
return in_array($this, [
self::VALUE_OBJECT,
self::ENTITY,
self::ENUM,
self::EXCEPTION,
]);
}
/**
* Check if this type is typically a leaf node
*/
public function isTypicallyLeaf(): bool
{
return in_array($this, [
self::VALUE_OBJECT,
self::ENUM,
self::EXCEPTION,
]);
}
/**
* Get the expected complexity score range for this type
*
* @return array{min: float, max: float}
*/
public function getExpectedComplexityRange(): array
{
return match ($this) {
self::VALUE_OBJECT => ['min' => 0.0, 'max' => 2.0],
self::ENTITY => ['min' => 0.0, 'max' => 3.0],
self::ENUM => ['min' => 0.0, 'max' => 1.0],
self::EXCEPTION => ['min' => 0.0, 'max' => 2.0],
self::REPOSITORY => ['min' => 2.0, 'max' => 8.0],
self::SERVICE => ['min' => 3.0, 'max' => 12.0],
self::CONTROLLER => ['min' => 5.0, 'max' => 15.0],
self::MIDDLEWARE => ['min' => 2.0, 'max' => 6.0],
self::EVENT_HANDLER => ['min' => 1.0, 'max' => 5.0],
self::COMMAND_HANDLER => ['min' => 2.0, 'max' => 8.0],
self::INITIALIZER => ['min' => 3.0, 'max' => 10.0],
self::INTERFACE => ['min' => 0.0, 'max' => 1.0],
self::ABSTRACT_CLASS => ['min' => 1.0, 'max' => 8.0],
self::TRAIT => ['min' => 0.0, 'max' => 3.0],
self::UNKNOWN => ['min' => 0.0, 'max' => 20.0],
};
}
/**
* Convert to string
*/
public function toString(): string
{
return $this->value;
}
}

View File

@@ -226,6 +226,12 @@ final readonly class DiscoveredAttribute
$sanitizedAdditionalData[$key] = (string)$value;
} elseif (method_exists($value, 'getFullyQualified')) {
$sanitizedAdditionalData[$key] = $value->getFullyQualified();
} elseif ($value instanceof \BackedEnum) {
// Handle PHP backed enums (like Method::GET)
$sanitizedAdditionalData[$key] = $value->value;
} elseif ($value instanceof \UnitEnum) {
// Handle PHP unit enums
$sanitizedAdditionalData[$key] = $value->name;
} else {
// Skip unsupported objects to prevent serialization issues
continue;

View File

@@ -68,7 +68,7 @@ final class DiscoveryContext
// between WEB and CLI contexts. Discovery results should be the same regardless of
// execution context - the same PHP files should produce the same attributes.
// Context-dependent caching was causing RequestFactory to be missing in WEB context.
return DiscoveryCacheIdentifiers::discoveryKey($this->paths, $this->scanType, null);
}
@@ -81,4 +81,43 @@ final class DiscoveryContext
{
return $this->options->useCache;
}
/**
* Create default discovery context
*/
public static function default(): self
{
return new self(
paths: ['/src'],
scanType: ScanType::FULL,
options: DiscoveryOptions::default(),
startTime: new DateTimeImmutable()
);
}
/**
* Create comprehensive discovery context
*/
public static function comprehensive(): self
{
return new self(
paths: ['/src', '/app'],
scanType: ScanType::FULL,
options: DiscoveryOptions::comprehensive(),
startTime: new DateTimeImmutable()
);
}
/**
* Create minimal discovery context
*/
public static function minimal(): self
{
return new self(
paths: ['/src'],
scanType: ScanType::INCREMENTAL,
options: DiscoveryOptions::minimal(),
startTime: new DateTimeImmutable()
);
}
}

View File

@@ -67,4 +67,46 @@ final readonly class DiscoveryOptions
includePatterns: $this->includePatterns
);
}
/**
* Create default discovery options
*/
public static function default(): self
{
return new self();
}
/**
* Create comprehensive discovery options
*/
public static function comprehensive(): self
{
return new self(
scanType: ScanType::FULL,
paths: ['/src', '/app'],
useCache: true,
parallel: true,
batchSize: 100,
showProgress: true,
excludePatterns: [],
includePatterns: ['*.php']
);
}
/**
* Create minimal discovery options
*/
public static function minimal(): self
{
return new self(
scanType: ScanType::INCREMENTAL,
paths: ['/src'],
useCache: true,
parallel: false,
batchSize: 25,
showProgress: false,
excludePatterns: ['*/test/*', '*/tests/*'],
includePatterns: ['*.php']
);
}
}

View File

@@ -69,4 +69,28 @@ final readonly class InterfaceMapping
return Byte::fromBytes($bytes);
}
/**
* Serialize to array for cache storage
*/
public function toArray(): array
{
return [
'interface' => $this->interface->getFullyQualified(),
'implementation' => $this->implementation->getFullyQualified(),
'file' => $this->file->toString(),
];
}
/**
* Create from array for cache deserialization
*/
public static function fromArray(array $data): self
{
return new self(
interface: ClassName::create($data['interface']),
implementation: ClassName::create($data['implementation']),
file: FilePath::create($data['file'])
);
}
}