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.
This commit is contained in:
202
src/Framework/DI/Analysis/DependencyPathAnalyzer.php
Normal file
202
src/Framework/DI/Analysis/DependencyPathAnalyzer.php
Normal file
@@ -0,0 +1,202 @@
|
||||
<?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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user