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