Files
michaelschiemer/src/Framework/DI/Analysis/DependencyPathAnalyzer.php
Michael Schiemer a93a086ee4 refactor(di): add analysis components for dependency parsing and resolution
- Introduce `CodeParser` to extract dependencies from `container->get()` calls and `return new` statements.
- Add `DependencyPathAnalyzer` for recursive analysis of dependency paths with cycle detection.
- Implement `InitializerFinder` to locate initializers based on naming conventions.
- Include `InterfaceResolver` to determine interface implementations using introspection and initializers.
- Add `NamespaceResolver` for resolving class names from use statements and namespaces.
- Introduce `ReturnTypeAnalyzer` for method and closure return type analysis.
2025-11-03 22:38:06 +01:00

203 lines
6.5 KiB
PHP

<?php
declare(strict_types=1);
namespace App\Framework\DI\Analysis;
use App\Framework\Core\ValueObjects\ClassName;
use App\Framework\DI\Container;
/**
* Rekursive Analyse von Dependency-Pfaden
*/
final readonly class DependencyPathAnalyzer
{
private const MAX_RECURSION_DEPTH = 4;
public function __construct(
private InterfaceResolver $interfaceResolver,
private CodeParser $codeParser
) {
}
/**
* Finde rekursiv den Pfad von einer Dependency bis zum Interface
*
* @param ClassName $dependencyClass Die Dependency-Klasse
* @param ClassName $targetInterface Das gesuchte Interface
* @param array<ClassName> $visited Bereits besuchte Klassen (für Cycle-Detection)
* @param array<ClassName> $currentPath Aktueller Pfad
* @param int $depth Aktuelle Rekursionstiefe
* @return array<ClassName>|null Vollständiger Pfad [Dep1, Dep2, ..., Interface] oder null
*/
public function findPathToInterface(
ClassName $dependencyClass,
ClassName $targetInterface,
array $visited = [],
array $currentPath = [],
int $depth = 0
): ?array {
// Max. Rekursionstiefe erreicht
if ($depth >= self::MAX_RECURSION_DEPTH) {
return null;
}
// Cycle-Detection: Vermeide Endlosschleifen
foreach ($visited as $visitedClass) {
if ($visitedClass->equals($dependencyClass)) {
return null;
}
}
// Prüfe ob diese Klasse direkt das Interface benötigt
if ($this->dependencyNeedsInterface($dependencyClass, $targetInterface)) {
return array_merge($currentPath, [$dependencyClass, $targetInterface]);
}
// Rekursiv: Analysiere Dependencies dieser Klasse
$dependencies = $this->getClassDependencies($dependencyClass);
if (empty($dependencies)) {
return null;
}
$newVisited = array_merge($visited, [$dependencyClass]);
$newPath = array_merge($currentPath, [$dependencyClass]);
foreach ($dependencies as $subDependency) {
// Überspringe Container selbst (würde alle Dependencies auflisten)
if ($subDependency->toString() === Container::class || $subDependency->toString() === 'App\Framework\DI\Container') {
continue;
}
$path = $this->findPathToInterface(
$subDependency,
$targetInterface,
$newVisited,
$newPath,
$depth + 1
);
if ($path !== null) {
return $path;
}
}
return null;
}
/**
* Hole Dependencies einer Klasse (Constructor-Parameter)
*
* @return array<ClassName>
*/
public function getClassDependencies(ClassName $className): array
{
try {
if (!$className->exists()) {
return [];
}
$reflection = new \ReflectionClass($className->toString());
// Wenn es ein Interface ist, versuche die Implementierung zu finden
if ($reflection->isInterface()) {
$implClass = $this->interfaceResolver->findImplementation($className);
if ($implClass !== null && $implClass->exists()) {
$reflection = new \ReflectionClass($implClass->toString());
} else {
// Kann keine Dependencies für Interfaces ohne Implementierung finden
return [];
}
}
$constructor = $reflection->getConstructor();
if ($constructor === null) {
return [];
}
$dependencies = [];
foreach ($constructor->getParameters() as $parameter) {
$type = $parameter->getType();
if ($type instanceof \ReflectionNamedType && !$type->isBuiltin()) {
try {
$depClassName = ClassName::create($type->getName());
$dependencies[] = $depClassName;
} catch (\InvalidArgumentException) {
// Ungültiger Klassenname - überspringe
continue;
}
}
}
return $dependencies;
} catch (\Throwable) {
return [];
}
}
/**
* Prüfe ob eine Klasse ein Interface benötigt (über Reflection)
*/
private function dependencyNeedsInterface(ClassName $dependencyClass, ClassName $interface): bool
{
try {
if (!$dependencyClass->exists()) {
return false;
}
$reflection = new \ReflectionClass($dependencyClass->toString());
$constructor = $reflection->getConstructor();
if ($constructor === null) {
return false;
}
// Prüfe alle Constructor-Parameter
foreach ($constructor->getParameters() as $parameter) {
$type = $parameter->getType();
if ($type === null) {
continue;
}
// Direkter NamedType
if ($type instanceof \ReflectionNamedType && !$type->isBuiltin()) {
if ($type->getName() === $interface->toString()) {
return true;
}
}
// Union Types (PHP 8.0+)
if ($type instanceof \ReflectionUnionType) {
foreach ($type->getTypes() as $subType) {
if ($subType instanceof \ReflectionNamedType && !$subType->isBuiltin()) {
if ($subType->getName() === $interface->toString()) {
return true;
}
}
}
}
// Intersection Types (PHP 8.1+)
if ($type instanceof \ReflectionIntersectionType) {
foreach ($type->getTypes() as $subType) {
if ($subType instanceof \ReflectionNamedType && !$subType->isBuiltin()) {
if ($subType->getName() === $interface->toString()) {
return true;
}
}
}
}
}
return false;
} catch (\Throwable) {
return false;
}
}
}