- 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.
203 lines
6.5 KiB
PHP
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;
|
|
}
|
|
}
|
|
}
|
|
|