diff --git a/components/ILIAS/Component/src/Dependencies/Define.php b/components/ILIAS/Component/src/Dependencies/Define.php index fd606627ad29..9f52be42aaf5 100644 --- a/components/ILIAS/Component/src/Dependencies/Define.php +++ b/components/ILIAS/Component/src/Dependencies/Define.php @@ -31,4 +31,9 @@ public function __toString(): string { return "DEFINE: " . $this->name; } + + public function getName(): string + { + return (string) $this->name; + } } diff --git a/components/ILIAS/Component/src/Dependencies/Dependency.php b/components/ILIAS/Component/src/Dependencies/Dependency.php index 04de5cc9988e..c43629ce30a0 100644 --- a/components/ILIAS/Component/src/Dependencies/Dependency.php +++ b/components/ILIAS/Component/src/Dependencies/Dependency.php @@ -23,4 +23,5 @@ interface Dependency { public function __toString(): string; + public function getName(): string; } diff --git a/components/ILIAS/Component/src/Dependencies/In.php b/components/ILIAS/Component/src/Dependencies/In.php index 4641e348c904..5966ed8488b6 100644 --- a/components/ILIAS/Component/src/Dependencies/In.php +++ b/components/ILIAS/Component/src/Dependencies/In.php @@ -39,14 +39,14 @@ public function __construct( $this->name = $name; } - public function __toString(): string + public function getName(): string { - return $this->type->value . ": " . $this->name; + return (string) $this->name; } - public function getName(): Name + public function __toString(): string { - return $this->name; + return $this->type->value . ": " . $this->name; } public function getType(): InType @@ -70,4 +70,9 @@ public function addResolution(Out $other): void $this->resolved_by[] = $other; $other->addResolves($this); } + + public function getResolvedBy(): array + { + return $this->resolved_by; + } } diff --git a/components/ILIAS/Component/src/Dependencies/NullDIC.php b/components/ILIAS/Component/src/Dependencies/NullDIC.php new file mode 100644 index 000000000000..e384c718c87f --- /dev/null +++ b/components/ILIAS/Component/src/Dependencies/NullDIC.php @@ -0,0 +1,46 @@ +dependencies as $d) { + foreach ($d as $i) { + if ($i instanceof In && $i->getType() === $type) { + yield $i; + } + } + } + } + // ArrayAccess public function offsetExists($dependency_description): bool diff --git a/components/ILIAS/Component/src/Dependencies/Out.php b/components/ILIAS/Component/src/Dependencies/Out.php index eb95e6982afd..b0234c2696b4 100644 --- a/components/ILIAS/Component/src/Dependencies/Out.php +++ b/components/ILIAS/Component/src/Dependencies/Out.php @@ -51,6 +51,11 @@ public function __toString(): string return $this->type->value . ": " . $this->name; } + public function getName(): string + { + return (string) $this->name; + } + public function setComponent(OfComponent $component): void { if (!is_null($this->component)) { diff --git a/components/ILIAS/Component/src/Dependencies/RenamingDIC.php b/components/ILIAS/Component/src/Dependencies/RenamingDIC.php new file mode 100644 index 000000000000..664bc738be9a --- /dev/null +++ b/components/ILIAS/Component/src/Dependencies/RenamingDIC.php @@ -0,0 +1,58 @@ +counter}"; + $this->counter++; + $this->wrapped->offsetSet($id, $value); + } + + public function offsetGet($id): mixed + { + return $this->wrapped->offsetGet($id); + } + + public function offsetExists($id): bool + { + return $this->wrapped->offsetExists($id); + } + + public function offsetUnset($id): void + { + $this->wrapped->offsetUnset($id); + } +} diff --git a/components/ILIAS/Component/src/Dependencies/Renderer.php b/components/ILIAS/Component/src/Dependencies/Renderer.php new file mode 100644 index 000000000000..5a0775369758 --- /dev/null +++ b/components/ILIAS/Component/src/Dependencies/Renderer.php @@ -0,0 +1,148 @@ + $c->getComponentName(), + $components + ) + ); + + return + $this->renderHeader() . + join("\n", array_map( + fn($c) => $this->renderComponent($component_lookup, $c), + $components + )); + } + + protected function renderHeader(): string + { + return <<getComponentName()]; + $use = $this->renderUse($component_lookup, $component); + $seek = $this->renderSeek($component_lookup, $component); + $pull = $this->renderPull($component_lookup, $component); + return <<getComponentName()}(); + +\$implement_$me = new ILIAS\Core\Dependencies\RenamingDIC(new Pimple\Container()); +\$use = new Pimple\Container();{$use} +\$contribute_$me = new Pimple\Container(); +\$seek = new Pimple\Container();{$seek} +\$provide_$me = new Pimple\Container(); +\$pull = new Pimple\Container();{$pull} +\$internal = new Pimple\Container(); + +\$component_{$me}->init(\$null_dic, \$implement_$me, \$use, \$contribute_$me, \$seek, \$provide_$me, \$pull, \$internal); + +PHP; + } + + protected function renderUse(array $component_lookup, OfComponent $component): string + { + $use = ""; + foreach ($component->getInDependenciesOf(InType::USE) as $in) { + $r = $in->getResolvedBy()[0]; + $p = $r->aux["position"]; + $o = $component_lookup[$r->getComponent()->getComponentName()]; + $use .= "\n" . <<getName()}::class] = fn() => \$implement_{$o}[{$r->getName()}::class . "_{$p}"]; +PHP; + } + return $use; + } + + protected function renderSeek(array $component_lookup, OfComponent $component): string + { + $seek = ""; + foreach ($component->getInDependenciesOf(InType::SEEK) as $in) { + $rs = $in->getResolvedBy(); + $u = []; + $a = ""; + foreach ($rs as $r) { + $p = $r->aux["position"]; + $o = $component_lookup[$r->getComponent()->getComponentName()]; + $u[] = "\$contribute_{$o}"; + $a .= "\n" . <<getName()}::class . "_{$p}"], +PHP; + } + $u = join(", ", array_unique($u)); + $seek .= "\n" . <<getName()}::class] = function () use ({$u}) { + return [{$a} + ]; +}; +PHP; + } + return $seek; + } + + protected function renderPull(array $component_lookup, OfComponent $component): string + { + $pull = ""; + foreach ($component->getInDependenciesOf(InType::PULL) as $in) { + $r = $in->getResolvedBy()[0]; + $o = $component_lookup[$r->getComponent()->getComponentName()]; + $pull .= "\n" . <<getName()}::class] = fn() => \$provide_{$o}[{$r->getName()}::class]; +PHP; + } + return $pull; + } +} diff --git a/components/ILIAS/Component/src/Dependencies/Resolver.php b/components/ILIAS/Component/src/Dependencies/Resolver.php index dfd7152113a8..40fb2d8c5ded 100644 --- a/components/ILIAS/Component/src/Dependencies/Resolver.php +++ b/components/ILIAS/Component/src/Dependencies/Resolver.php @@ -136,7 +136,7 @@ protected function resolveUse(OfComponent $component, array &$disambiguation, In ); } foreach ($candidates as $candidate) { - if ($candidate->class === $preferred_class) { + if ($candidate->aux["class"] === $preferred_class) { $in->addResolution($candidate); return; } diff --git a/components/ILIAS/Component/tests/Dependencies/NullDICTest.php b/components/ILIAS/Component/tests/Dependencies/NullDICTest.php new file mode 100644 index 000000000000..c47e26263cd6 --- /dev/null +++ b/components/ILIAS/Component/tests/Dependencies/NullDICTest.php @@ -0,0 +1,33 @@ +assertInstanceOf(\ArrayAccess::class, $dic); + } +} diff --git a/components/ILIAS/Component/tests/Dependencies/RenamingDICTest.php b/components/ILIAS/Component/tests/Dependencies/RenamingDICTest.php new file mode 100644 index 000000000000..a2f7b0ff0ef9 --- /dev/null +++ b/components/ILIAS/Component/tests/Dependencies/RenamingDICTest.php @@ -0,0 +1,57 @@ +data[] = [$id, $value]; + } + + public function offsetGet($id): null + { + } + public function offsetExists($id): false + { + } + public function offsetUnset($id): void + { + } + }; + $wrapper = new RenamingDIC($wrapped); + + $wrapper["Foo"] = "Bar"; + $wrapper["Baz"] = "Bla"; + $wrapper["Foo"] = "Foobar"; + + $expected = [["Foo_0", "Bar"], ["Baz_1", "Bla"], ["Foo_2", "Foobar"]]; + $this->assertEquals($expected, $wrapped->data); + } +} diff --git a/components/ILIAS/Component/tests/Dependencies/RendererTest.php b/components/ILIAS/Component/tests/Dependencies/RendererTest.php new file mode 100644 index 000000000000..9680eb1e8258 --- /dev/null +++ b/components/ILIAS/Component/tests/Dependencies/RendererTest.php @@ -0,0 +1,87 @@ +reader = new Reader(); + $this->resolver = new Resolver(); + $this->renderer = new Renderer(); + } + + /** + * @dataProvider scenarios + */ + public function testScenario($scenario_file, $result_file, $components) + { + require_once(__DIR__ . "/scenarios/$scenario_file"); + + $components = array_map(fn($c) => $this->reader->read(new $c()), $components); + $resolved = $this->resolver->resolveDependencies([], ...$components); + + $result = $this->renderer->render(...$resolved); + + $expected = file_get_contents(__DIR__ . "/scenarios/$result_file"); + $this->assertEquals($expected, $result); + } + + public function scenarios() + { + return [ + "no dependencies" => ["scenario1.php", "result1.php", + [ + \ILIAS\Core\Tests\Dependencies\Scenario1\ComponentA::class + ] + ], + "pull dependency" => ["scenario2.php", "result2.php", + [ + \ILIAS\Core\Tests\Dependencies\Scenario2\ComponentA::class, + \ILIAS\Core\Tests\Dependencies\Scenario2\ComponentB::class + ] + ], + "use dependency" => ["scenario3.php", "result3.php", + [ + \ILIAS\Core\Tests\Dependencies\Scenario3\ComponentA::class, + \ILIAS\Core\Tests\Dependencies\Scenario3\ComponentB::class + ] + ], + "seek dependency" => ["scenario4.php", "result4.php", + [ + \ILIAS\Core\Tests\Dependencies\Scenario4\ComponentA::class, + \ILIAS\Core\Tests\Dependencies\Scenario4\ComponentB::class, + \ILIAS\Core\Tests\Dependencies\Scenario4\ComponentC::class + ] + ] + ]; + } +} diff --git a/components/ILIAS/Component/tests/Dependencies/ResolverTest.php b/components/ILIAS/Component/tests/Dependencies/ResolverTest.php index 94e8dd413d53..672891295e06 100644 --- a/components/ILIAS/Component/tests/Dependencies/ResolverTest.php +++ b/components/ILIAS/Component/tests/Dependencies/ResolverTest.php @@ -48,7 +48,7 @@ public function testResolvePull(): void $name = TestInterface::class; $pull = new D\In(D\InType::PULL, $name); - $provide = new D\Out(D\OutType::PROVIDE, $name, "Some\\Class", []); + $provide = new D\Out(D\OutType::PROVIDE, $name, null, []); $c1 = new D\OfComponent($component, $pull); $c2 = new D\OfComponent($component, $provide); @@ -56,7 +56,7 @@ public function testResolvePull(): void $result = $this->resolver->resolveDependencies([], $c1, $c2); $pull = new D\In(D\InType::PULL, $name); - $provide = new D\Out(D\OutType::PROVIDE, $name, "Some\\Class", []); + $provide = new D\Out(D\OutType::PROVIDE, $name, null, []); $pull->addResolution($provide); $c1 = new D\OfComponent($component, $pull); @@ -89,8 +89,8 @@ public function testPullFailsDuplicate(): void $name = TestInterface::class; $pull = new D\In(D\InType::PULL, $name); - $provide1 = new D\Out(D\OutType::PROVIDE, $name, "Some\\Class", []); - $provide2 = new D\Out(D\OutType::PROVIDE, $name, "Some\\Class", []); + $provide1 = new D\Out(D\OutType::PROVIDE, $name, null, []); + $provide2 = new D\Out(D\OutType::PROVIDE, $name, null, []); $c1 = new D\OfComponent($component, $pull); $c2 = new D\OfComponent($component, $provide1); @@ -121,8 +121,8 @@ public function testResolveSeek(): void $name = TestInterface::class; $seek = new D\In(D\InType::SEEK, $name); - $contribute1 = new D\Out(D\OutType::CONTRIBUTE, $name, "Some\\Class", []); - $contribute2 = new D\Out(D\OutType::CONTRIBUTE, $name, "Some\\Class", []); + $contribute1 = new D\Out(D\OutType::CONTRIBUTE, $name, null, []); + $contribute2 = new D\Out(D\OutType::CONTRIBUTE, $name, null, []); $c1 = new D\OfComponent($component, $seek); $c2 = new D\OfComponent($component, $contribute1); @@ -132,8 +132,8 @@ public function testResolveSeek(): void $seek = new D\In(D\InType::SEEK, $name); - $contribute1 = new D\Out(D\OutType::CONTRIBUTE, $name, "Some\\Class", []); - $contribute2 = new D\Out(D\OutType::CONTRIBUTE, $name, "Some\\Class", []); + $contribute1 = new D\Out(D\OutType::CONTRIBUTE, $name, null, []); + $contribute2 = new D\Out(D\OutType::CONTRIBUTE, $name, null, []); $seek->addResolution($contribute1); $seek->addResolution($contribute2); @@ -151,7 +151,7 @@ public function testResolveUseOneOption(): void $name = TestInterface::class; $use = new D\In(D\InType::USE, $name); - $implement = new D\Out(D\OutType::IMPLEMENT, $name, "Some\\Class", []); + $implement = new D\Out(D\OutType::IMPLEMENT, $name, ["class" => "Some\\Class"], []); $c1 = new D\OfComponent($component, $use); $c2 = new D\OfComponent($component, $implement); @@ -159,7 +159,7 @@ public function testResolveUseOneOption(): void $result = $this->resolver->resolveDependencies([], $c1, $c2); $use = new D\In(D\InType::USE, $name); - $implement = new D\Out(D\OutType::IMPLEMENT, $name, "Some\\Class", []); + $implement = new D\Out(D\OutType::IMPLEMENT, $name, ["class" => "Some\\Class"], []); $use->addResolution($implement); $c1 = new D\OfComponent($component, $use); @@ -192,8 +192,8 @@ public function testUseFailsDuplicate(): void $name = TestInterface::class; $use = new D\In(D\InType::USE, $name); - $implement1 = new D\Out(D\OutType::IMPLEMENT, $name, "Some\\Class", []); - $implement2 = new D\Out(D\OutType::IMPLEMENT, $name, "Some\\Class", []); + $implement1 = new D\Out(D\OutType::IMPLEMENT, $name, ["class" => "Some\\Class"], []); + $implement2 = new D\Out(D\OutType::IMPLEMENT, $name, ["class" => "Some\\Class"], []); $c1 = new D\OfComponent($component, $use); $c2 = new D\OfComponent($component, $implement1); @@ -209,8 +209,8 @@ public function testUseDisambiguateDuplicateSpecific(): void $name = TestInterface::class; $use = new D\In(D\InType::USE, $name); - $implement1 = new D\Out(D\OutType::IMPLEMENT, $name, "Some\\Class", []); - $implement2 = new D\Out(D\OutType::IMPLEMENT, $name, "Some\\OtherClass", []); + $implement1 = new D\Out(D\OutType::IMPLEMENT, $name, ["class" => "Some\\Class"], []); + $implement2 = new D\Out(D\OutType::IMPLEMENT, $name, ["class" => "Some\\OtherClass"], []); $c1 = new D\OfComponent($component, $use); $c2 = new D\OfComponent($component, $implement1); @@ -225,8 +225,8 @@ public function testUseDisambiguateDuplicateSpecific(): void $result = $this->resolver->resolveDependencies($disambiguation, $c1, $c2, $c3); $use = new D\In(D\InType::USE, $name); - $implement1 = new D\Out(D\OutType::IMPLEMENT, $name, "Some\\Class", []); - $implement2 = new D\Out(D\OutType::IMPLEMENT, $name, "Some\\OtherClass", []); + $implement1 = new D\Out(D\OutType::IMPLEMENT, $name, ["class" => "Some\\Class"], []); + $implement2 = new D\Out(D\OutType::IMPLEMENT, $name, ["class" => "Some\\OtherClass"], []); $use->addResolution($implement2); $c1 = new D\OfComponent($component, $use); @@ -243,8 +243,8 @@ public function testUseDisambiguateDuplicateGeneric(): void $name = TestInterface::class; $use = new D\In(D\InType::USE, $name); - $implement1 = new D\Out(D\OutType::IMPLEMENT, $name, "Some\\Class", []); - $implement2 = new D\Out(D\OutType::IMPLEMENT, $name, "Some\\OtherClass", []); + $implement1 = new D\Out(D\OutType::IMPLEMENT, $name, ["class" => "Some\\Class"], []); + $implement2 = new D\Out(D\OutType::IMPLEMENT, $name, ["class" => "Some\\OtherClass"], []); $c1 = new D\OfComponent($component, $use); $c2 = new D\OfComponent($component, $implement1); @@ -259,8 +259,8 @@ public function testUseDisambiguateDuplicateGeneric(): void $result = $this->resolver->resolveDependencies($disambiguation, $c1, $c2, $c3); $use = new D\In(D\InType::USE, $name); - $implement1 = new D\Out(D\OutType::IMPLEMENT, $name, "Some\\Class", []); - $implement2 = new D\Out(D\OutType::IMPLEMENT, $name, "Some\\OtherClass", []); + $implement1 = new D\Out(D\OutType::IMPLEMENT, $name, ["class" => "Some\\Class"], []); + $implement2 = new D\Out(D\OutType::IMPLEMENT, $name, ["class" => "Some\\OtherClass"], []); $use->addResolution($implement2); diff --git a/components/ILIAS/Component/tests/Dependencies/scenarios/result1.php b/components/ILIAS/Component/tests/Dependencies/scenarios/result1.php new file mode 100644 index 000000000000..0578460e2dda --- /dev/null +++ b/components/ILIAS/Component/tests/Dependencies/scenarios/result1.php @@ -0,0 +1,32 @@ +init($null_dic, $implement_0, $use, $contribute_0, $seek, $provide_0, $pull, $internal); diff --git a/components/ILIAS/Component/tests/Dependencies/scenarios/result2.php b/components/ILIAS/Component/tests/Dependencies/scenarios/result2.php new file mode 100644 index 000000000000..a11eda3fc618 --- /dev/null +++ b/components/ILIAS/Component/tests/Dependencies/scenarios/result2.php @@ -0,0 +1,46 @@ +init($null_dic, $implement_0, $use, $contribute_0, $seek, $provide_0, $pull, $internal); + + +$component_1 = new ILIAS\Core\Tests\Dependencies\Scenario2\ComponentB(); + +$implement_1 = new ILIAS\Core\Dependencies\RenamingDIC(new Pimple\Container()); +$use = new Pimple\Container(); +$contribute_1 = new Pimple\Container(); +$seek = new Pimple\Container(); +$provide_1 = new Pimple\Container(); +$pull = new Pimple\Container(); +$pull[ILIAS\Core\Tests\Dependencies\Scenario2\Provides::class] = fn() => $provide_0[ILIAS\Core\Tests\Dependencies\Scenario2\Provides::class]; +$internal = new Pimple\Container(); + +$component_1->init($null_dic, $implement_1, $use, $contribute_1, $seek, $provide_1, $pull, $internal); diff --git a/components/ILIAS/Component/tests/Dependencies/scenarios/result3.php b/components/ILIAS/Component/tests/Dependencies/scenarios/result3.php new file mode 100644 index 000000000000..b242ca98d26e --- /dev/null +++ b/components/ILIAS/Component/tests/Dependencies/scenarios/result3.php @@ -0,0 +1,46 @@ +init($null_dic, $implement_0, $use, $contribute_0, $seek, $provide_0, $pull, $internal); + + +$component_1 = new ILIAS\Core\Tests\Dependencies\Scenario3\ComponentB(); + +$implement_1 = new ILIAS\Core\Dependencies\RenamingDIC(new Pimple\Container()); +$use = new Pimple\Container(); +$use[ILIAS\Core\Tests\Dependencies\Scenario3\Service::class] = fn() => $implement_0[ILIAS\Core\Tests\Dependencies\Scenario3\Service::class . "_0"]; +$contribute_1 = new Pimple\Container(); +$seek = new Pimple\Container(); +$provide_1 = new Pimple\Container(); +$pull = new Pimple\Container(); +$internal = new Pimple\Container(); + +$component_1->init($null_dic, $implement_1, $use, $contribute_1, $seek, $provide_1, $pull, $internal); diff --git a/components/ILIAS/Component/tests/Dependencies/scenarios/result4.php b/components/ILIAS/Component/tests/Dependencies/scenarios/result4.php new file mode 100644 index 000000000000..00efe42172a4 --- /dev/null +++ b/components/ILIAS/Component/tests/Dependencies/scenarios/result4.php @@ -0,0 +1,65 @@ +init($null_dic, $implement_0, $use, $contribute_0, $seek, $provide_0, $pull, $internal); + + +$component_1 = new ILIAS\Core\Tests\Dependencies\Scenario4\ComponentB(); + +$implement_1 = new ILIAS\Core\Dependencies\RenamingDIC(new Pimple\Container()); +$use = new Pimple\Container(); +$contribute_1 = new Pimple\Container(); +$seek = new Pimple\Container(); +$provide_1 = new Pimple\Container(); +$pull = new Pimple\Container(); +$internal = new Pimple\Container(); + +$component_1->init($null_dic, $implement_1, $use, $contribute_1, $seek, $provide_1, $pull, $internal); + + +$component_2 = new ILIAS\Core\Tests\Dependencies\Scenario4\ComponentC(); + +$implement_2 = new ILIAS\Core\Dependencies\RenamingDIC(new Pimple\Container()); +$use = new Pimple\Container(); +$contribute_2 = new Pimple\Container(); +$seek = new Pimple\Container(); +$seek[ILIAS\Core\Tests\Dependencies\Scenario4\Contribution::class] = function () use ($contribute_0, $contribute_1) { + return [ + $contribute_0[ILIAS\Core\Tests\Dependencies\Scenario4\Contribution::class . "_0"], + $contribute_1[ILIAS\Core\Tests\Dependencies\Scenario4\Contribution::class . "_0"], + $contribute_1[ILIAS\Core\Tests\Dependencies\Scenario4\Contribution::class . "_1"], + ]; +}; +$provide_2 = new Pimple\Container(); +$pull = new Pimple\Container(); +$internal = new Pimple\Container(); + +$component_2->init($null_dic, $implement_2, $use, $contribute_2, $seek, $provide_2, $pull, $internal); diff --git a/components/ILIAS/Component/tests/Dependencies/scenarios/scenario1.php b/components/ILIAS/Component/tests/Dependencies/scenarios/scenario1.php new file mode 100644 index 000000000000..190d32fafbe2 --- /dev/null +++ b/components/ILIAS/Component/tests/Dependencies/scenarios/scenario1.php @@ -0,0 +1,36 @@ + new ComponentAProvides(); + } +} + +class ComponentB implements Component +{ + public function init( + array | \ArrayAccess &$define, + array | \ArrayAccess &$implement, + array | \ArrayAccess &$use, + array | \ArrayAccess &$contribute, + array | \ArrayAccess &$seek, + array | \ArrayAccess &$provide, + array | \ArrayAccess &$pull, + array | \ArrayAccess &$internal, + ): void { + $internal["foo"] = fn() => $pull[Provides::class]; + } +} diff --git a/components/ILIAS/Component/tests/Dependencies/scenarios/scenario3.php b/components/ILIAS/Component/tests/Dependencies/scenarios/scenario3.php new file mode 100644 index 000000000000..01fbf46bd700 --- /dev/null +++ b/components/ILIAS/Component/tests/Dependencies/scenarios/scenario3.php @@ -0,0 +1,62 @@ + new ServiceImplementation(); + } +} + +class ComponentB implements Component +{ + public function init( + array | \ArrayAccess &$define, + array | \ArrayAccess &$implement, + array | \ArrayAccess &$use, + array | \ArrayAccess &$contribute, + array | \ArrayAccess &$seek, + array | \ArrayAccess &$provide, + array | \ArrayAccess &$pull, + array | \ArrayAccess &$internal, + ): void { + $internal["foo"] = fn() => $use[Service::class]; + } +} diff --git a/components/ILIAS/Component/tests/Dependencies/scenarios/scenario4.php b/components/ILIAS/Component/tests/Dependencies/scenarios/scenario4.php new file mode 100644 index 000000000000..8e54f073191b --- /dev/null +++ b/components/ILIAS/Component/tests/Dependencies/scenarios/scenario4.php @@ -0,0 +1,78 @@ + new ContributionImplementation(); + } +} + +class ComponentB implements Component +{ + public function init( + array | \ArrayAccess &$define, + array | \ArrayAccess &$implement, + array | \ArrayAccess &$use, + array | \ArrayAccess &$contribute, + array | \ArrayAccess &$seek, + array | \ArrayAccess &$provide, + array | \ArrayAccess &$pull, + array | \ArrayAccess &$internal, + ): void { + $contribute[Contribution::class] = fn() => new ContributionImplementation(); + $contribute[Contribution::class] = fn() => new ContributionImplementation(); + } +} + +class ComponentC implements Component +{ + public function init( + array | \ArrayAccess &$define, + array | \ArrayAccess &$implement, + array | \ArrayAccess &$use, + array | \ArrayAccess &$contribute, + array | \ArrayAccess &$seek, + array | \ArrayAccess &$provide, + array | \ArrayAccess &$pull, + array | \ArrayAccess &$internal, + ): void { + $internal["foo"] = fn() => $seek[Contribution::class]; + } +}