From 3a31c8d7d8f9591a070b5c6d350a148768bde2ec Mon Sep 17 00:00:00 2001 From: Richard Klees Date: Wed, 13 Sep 2023 09:47:12 +0200 Subject: [PATCH] Core/Dependencies: start working on resolver --- components/ILIAS/Core/src/Dependencies/In.php | 11 ++ .../Core/src/Dependencies/OfComponent.php | 14 ++ .../ILIAS/Core/src/Dependencies/Out.php | 5 + .../ILIAS/Core/src/Dependencies/Resolver.php | 79 ++++++++++ .../tests/Dependencies/OfComponentTest.php | 61 ++++++++ .../Core/tests/Dependencies/ResolverTest.php | 143 ++++++++++++++++++ 6 files changed, 313 insertions(+) create mode 100644 components/ILIAS/Core/src/Dependencies/Resolver.php create mode 100644 components/ILIAS/Core/tests/Dependencies/OfComponentTest.php create mode 100644 components/ILIAS/Core/tests/Dependencies/ResolverTest.php diff --git a/components/ILIAS/Core/src/Dependencies/In.php b/components/ILIAS/Core/src/Dependencies/In.php index 60d0132937c5..7d5e02d19a7b 100644 --- a/components/ILIAS/Core/src/Dependencies/In.php +++ b/components/ILIAS/Core/src/Dependencies/In.php @@ -43,8 +43,19 @@ public function __toString(): string return $this->type->value . ": " . $this->name; } + public function getName(): Name + { + return $this->name; + } + + public function getType(): InType + { + return $this->type; + } + public function addDependant(Out $out) { $this->dependant[(string) $out] = $out; + $out->addDependency($this); } } diff --git a/components/ILIAS/Core/src/Dependencies/OfComponent.php b/components/ILIAS/Core/src/Dependencies/OfComponent.php index 32a9c42f8bea..6b8f4009c295 100644 --- a/components/ILIAS/Core/src/Dependencies/OfComponent.php +++ b/components/ILIAS/Core/src/Dependencies/OfComponent.php @@ -41,6 +41,20 @@ public function getComponent(): Component return $this->component; } + public function getComponentName(): string + { + return get_class($this->getComponent()); + } + + public function getInDependencies(): \Iterator + { + foreach ($this->dependencies as $d) { + if ($d instanceof In) { + yield $d; + } + } + } + // ArrayAccess public function offsetExists($dependency_description): bool diff --git a/components/ILIAS/Core/src/Dependencies/Out.php b/components/ILIAS/Core/src/Dependencies/Out.php index 1d8f8b3f46c1..f996ebcb0b5a 100644 --- a/components/ILIAS/Core/src/Dependencies/Out.php +++ b/components/ILIAS/Core/src/Dependencies/Out.php @@ -46,4 +46,9 @@ public function __toString(): string { return $this->type->value . ": " . $this->name; } + + public function addDependency(In $in) + { + $this->dependencies[(string) $in] = $in; + } } diff --git a/components/ILIAS/Core/src/Dependencies/Resolver.php b/components/ILIAS/Core/src/Dependencies/Resolver.php new file mode 100644 index 000000000000..81c6e19fa7ef --- /dev/null +++ b/components/ILIAS/Core/src/Dependencies/Resolver.php @@ -0,0 +1,79 @@ +getInDependencies() as $d) { + switch ($d->getType()) { + case InType::PULL: + $this->resolvePull($d, $components); + break; + case InType::SEEK: + $this->resolveSeek($d, $components); + break; + } + } + } + + return $components; + } + + protected function resolvePull(In $in, array &$others): void + { + $candidate = null; + + foreach ($others as $other) { + if ($other->offsetExists("PROVIDE: " . $in->getName())) { + if (!is_null($candidate)) { + throw new \LogicException( + "Dependency {$in->getName()} is provided (at least) twice. " . + "Once by {$candidate->getComponentName()} " . + "and by {$other->getComponentName()} " + ); + } + $candidate = $other; + } + } + + if (is_null($candidate)) { + throw new \LogicException("Could not resolve dependency for: " . (string) $in); + } + + $in->addDependant($candidate["PROVIDE: " . $in->getName()]); + } + + protected function resolveSeek(In $in, array &$others): void + { + foreach ($others as $other) { + if ($other->offsetExists("CONTRIBUTE: " . $in->getName())) { + $in->addDependant($other["CONTRIBUTE: " . $in->getName()]); + } + } + } +} diff --git a/components/ILIAS/Core/tests/Dependencies/OfComponentTest.php b/components/ILIAS/Core/tests/Dependencies/OfComponentTest.php new file mode 100644 index 000000000000..7b6a9deff0a2 --- /dev/null +++ b/components/ILIAS/Core/tests/Dependencies/OfComponentTest.php @@ -0,0 +1,61 @@ +component = $this->createMock(Component::class); + $this->of_component = new D\OfComponent( + $this->component + ); + } + + public function testGetComponent(): void + { + $this->assertEquals($this->component, $this->of_component->getComponent()); + } + + public function testInDependencies(): void + { + $name = TestInterface::class; + + $out = new D\Out(D\OutType::PROVIDE, $name, []); + $in = new D\In(D\InType::PULL, $name); + + $of_component = new D\OfComponent( + $this->component, + $in, + $out + ); + + $result = iterator_to_array($of_component->getInDependencies()); + + $this->assertEquals([$in], $result); + } +} diff --git a/components/ILIAS/Core/tests/Dependencies/ResolverTest.php b/components/ILIAS/Core/tests/Dependencies/ResolverTest.php new file mode 100644 index 000000000000..ce2703a7657c --- /dev/null +++ b/components/ILIAS/Core/tests/Dependencies/ResolverTest.php @@ -0,0 +1,143 @@ +resolver = new Resolver(); + } + + public function testEmptyComponentSet(): void + { + $result = $this->resolver->resolveDependencies(); + + $this->assertEquals([], $result); + } + + public function testResolvePull(): void + { + $component = $this->createMock(Component::class); + + $name = TestInterface::class; + + $pull = new D\In(D\InType::PULL, $name); + $provide = new D\Out(D\OutType::PROVIDE, $name, []); + + $c1 = new D\OfComponent($component, $pull); + $c2 = new D\OfComponent($component, $provide); + + $result = $this->resolver->resolveDependencies($c1, $c2); + + $pull = new D\In(D\InType::PULL, $name); + $provide = new D\Out(D\OutType::PROVIDE, $name, [$pull]); + + $c1 = new D\OfComponent($component, $pull); + $c2 = new D\OfComponent($component, $provide); + + $this->assertEquals([$c1, $c2], $result); + } + + public function testPullFailsNotExistent(): void + { + $this->expectException(\LogicException::class); + + $component = $this->createMock(Component::class); + + $name = TestInterface::class; + + $pull = new D\In(D\InType::PULL, $name); + + $c1 = new D\OfComponent($component, $pull); + + $this->resolver->resolveDependencies($c1); + } + + public function testPullFailsDuplicate(): void + { + $this->expectException(\LogicException::class); + + $component = $this->createMock(Component::class); + + $name = TestInterface::class; + + $pull = new D\In(D\InType::PULL, $name); + $provide1 = new D\Out(D\OutType::PROVIDE, $name, []); + $provide2 = new D\Out(D\OutType::PROVIDE, $name, []); + + $c1 = new D\OfComponent($component, $pull); + $c2 = new D\OfComponent($component, $provide1); + $c3 = new D\OfComponent($component, $provide2); + + $this->resolver->resolveDependencies($c1, $c2, $c3); + } + + public function testEmptySeek(): void + { + $component = $this->createMock(Component::class); + + $name = TestInterface::class; + + $seek = new D\In(D\InType::SEEK, $name); + + $c1 = new D\OfComponent($component, $seek); + + $result = $this->resolver->resolveDependencies($c1); + + $this->assertEquals([$c1], $result); + } + + public function testResolveSeek(): void + { + $component = $this->createMock(Component::class); + + $name = TestInterface::class; + + $seek = new D\In(D\InType::SEEK, $name); + $contribute1 = new D\Out(D\OutType::CONTRIBUTE, $name, []); + $contribute2 = new D\Out(D\OutType::CONTRIBUTE, $name, []); + + $c1 = new D\OfComponent($component, $seek); + $c2 = new D\OfComponent($component, $contribute1); + $c3 = new D\OfComponent($component, $contribute2); + + $result = $this->resolver->resolveDependencies($c1, $c2, $c3); + + + $seek = new D\In(D\InType::SEEK, $name); + $contribute1 = new D\Out(D\OutType::CONTRIBUTE, $name, [$seek]); + $contribute2 = new D\Out(D\OutType::CONTRIBUTE, $name, [$seek]); + + $c1 = new D\OfComponent($component, $seek); + $c2 = new D\OfComponent($component, $contribute1); + $c3 = new D\OfComponent($component, $contribute2); + + $this->assertEquals([$c1, $c2, $c3], $result); + } +}