Skip to content

Commit

Permalink
Component/Dependencies: Find Cycles in Dependency Tree
Browse files Browse the repository at this point in the history
  • Loading branch information
klees committed Nov 27, 2023
1 parent 8f2056e commit 32d2376
Show file tree
Hide file tree
Showing 3 changed files with 102 additions and 0 deletions.
5 changes: 5 additions & 0 deletions components/ILIAS/Component/src/Dependencies/Out.php
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,11 @@ public function addDependency(In $in): void
$this->dependencies[(string) $in] = $in;
}

public function getDependencies(): array
{
return $this->dependencies;
}

public function addResolves(In $in): void
{
$this->resolves[] = $in;
Expand Down
50 changes: 50 additions & 0 deletions components/ILIAS/Component/src/Dependencies/Resolver.php
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,23 @@ public function resolveDependencies(array $disambiguation, OfComponent ...$compo
}
}

$cycles = iterator_to_array($this->findCycles(...$components));
if (!empty($cycles)) {
throw new \LogicException(
"Detected Cycles in Dependency Tree: " .
join("\n", array_map(
fn($cycle) => join(
" <- ",
array_map(
fn($v) => "{$v[0]->getComponentName()} ({$v[1]})",
$cycle
)
),
$cycles
))
);
}

return $components;
}

Expand Down Expand Up @@ -157,4 +174,37 @@ protected function disambiguate(OfComponent $component, array &$disambiguation,
}
return null;
}

/**
* @var Generator<array<OfComponent, Dependency>>
*/
protected function findCycles(OfComponent ...$components): \Generator
{
foreach ($components as $component) {
foreach ($component->getInDependencies() as $in) {
foreach ($this->findCyclesWith([], $component, $in) as $cycle) {
yield $cycle;
}
}
}
}

protected function findCyclesWith(array $visited, OfComponent $component, In $in): \Generator
{
if (!empty($visited) && $visited[0][0] === $component && $visited[0][1] == $in) {
yield $visited;
return;
}

array_push($visited, [$component, $in]);
foreach ($in->getResolvedBy() as $out) {
$other = $out->getComponent();
array_push($visited, [$component, $out]);
foreach ($out->getDependencies() as $next) {
yield from $this->findCyclesWith($visited, $out->getComponent(), $next);
}
array_pop($visited);
}
array_pop($visited);
}
}
47 changes: 47 additions & 0 deletions components/ILIAS/Component/tests/Dependencies/ResolverTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -270,4 +270,51 @@ public function testUseDisambiguateDuplicateGeneric(): void

$this->assertEquals([$c1, $c2, $c3], $result);
}

public function testFindSimpleCycle(): void
{
$this->expectException(\LogicException::class);

$component = $this->createMock(Component::class);

$name = TestInterface::class;
$name2 = TestInterface2::class;

$pull = new D\In(D\InType::PULL, $name);
$provide = new D\Out(D\OutType::PROVIDE, $name2, "Some\\Class", [$pull], []);
$c1 = new D\OfComponent($component, $pull, $provide);

$pull = new D\In(D\InType::PULL, $name2);
$provide = new D\Out(D\OutType::PROVIDE, $name, "Some\\OtherClass", [$pull], []);
$c2 = new D\OfComponent($component, $pull, $provide);


$result = $this->resolver->resolveDependencies([], $c1, $c2);
}

public function testFindLongerCycle(): void
{
$this->expectException(\LogicException::class);

$component = $this->createMock(Component::class);

$name = TestInterface::class;
$name2 = TestInterface2::class;
$name3 = TestInterface3::class;

$pull = new D\In(D\InType::PULL, $name);
$provide = new D\Out(D\OutType::PROVIDE, $name2, "Some\\Class", [$pull], []);
$c1 = new D\OfComponent($component, $pull, $provide);

$pull = new D\In(D\InType::PULL, $name2);
$provide = new D\Out(D\OutType::PROVIDE, $name3, "Some\\OtherClass", [$pull], []);
$c2 = new D\OfComponent($component, $pull, $provide);

$pull = new D\In(D\InType::PULL, $name3);
$provide = new D\Out(D\OutType::PROVIDE, $name, "Some\\OtherOtherClass", [$pull], []);
$c3 = new D\OfComponent($component, $pull, $provide);


$result = $this->resolver->resolveDependencies([], $c1, $c2, $c3);
}
}

0 comments on commit 32d2376

Please sign in to comment.