Skip to content

Commit

Permalink
Core/Dependencies: Resolver for Dependencies
Browse files Browse the repository at this point in the history
  • Loading branch information
klees committed Nov 17, 2023
1 parent f90b29d commit 0e0a3e3
Show file tree
Hide file tree
Showing 4 changed files with 444 additions and 1 deletion.
11 changes: 11 additions & 0 deletions components/ILIAS/Core/src/Dependencies/In.php
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
}
7 changes: 6 additions & 1 deletion components/ILIAS/Core/src/Dependencies/Out.php
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ class Out implements Dependency
public function __construct(
protected OutType $type,
string $name,
protected ?string $class,
public readonly ?string $class,
array $dependencies
) {
if ($type !== OutType::INTERNAL) {
Expand All @@ -52,4 +52,9 @@ public function __toString(): string
{
return $this->type->value . ": " . $this->name;
}

public function addDependency(In $in)
{
$this->dependencies[(string) $in] = $in;
}
}
160 changes: 160 additions & 0 deletions components/ILIAS/Core/src/Dependencies/Resolver.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
<?php

/**
* This file is part of ILIAS, a powerful learning management system
* published by ILIAS open source e-Learning e.V.
*
* ILIAS is licensed with the GPL-3.0,
* see https://www.gnu.org/licenses/gpl-3.0.en.html
* You should have received a copy of said license along with the
* source code, too.
*
* If this is not the case or you just want to try ILIAS, you'll find
* us at:
* https://www.ilias.de
* https://github.com/ILIAS-eLearning
*
*********************************************************************/

declare(strict_types=1);

namespace ILIAS\Core\Dependencies;

class Resolver
{
/**
* Resolves dependencies of all components. This is unambigous for all types of
* dependencies but the use/implement-pair. If there would be ambiguities, these
* can be disambiguated by the first argument.
*
* The structure of the first argument is as such: keys are components that use
* services ("dependant") that need disambiguation, value for each dependant is
* an array where the key is the definition ("dependency") and the value is the
* implementation ("implementation") to be used.
*
* The entry "*" for the dependant will define fallbacks to be used for all
* components that have no explicit disambiguation.
*
* So, the array might look as such:
*
* [
* "*" => [
* "ILIAS\Logger\Logger" => ILIAS\Logger\DBLogger
* ],
* "ILIAS\Database\DB" => [
* "ILIAS\Logger\Logger" => ILIAS\Logger\StdErrLogger
* ]
* ]
*
* @param array<string, array<string, string>> $disambiguation
* @param OfComponent[]
* @return OfComponent[]
*/
public function resolveDependencies(array $disambiguation, OfComponent ...$components): array
{
foreach ($components as $component) {
foreach ($component->getInDependencies() as $d) {
switch ($d->getType()) {
case InType::PULL:
$this->resolvePull($d, $components);
break;
case InType::SEEK:
$this->resolveSeek($d, $components);
break;
case InType::USE:
$this->resolveUse($component, $disambiguation, $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."
);
}
// For PROVIDEd dependencies, there only ever is one implementation.
$candidate = $other["PROVIDE: " . $in->getName()][0];
}
}

if (is_null($candidate)) {
throw new \LogicException("Could not resolve dependency for: " . (string) $in);
}

$in->addDependant($candidate);
}

protected function resolveSeek(In $in, array &$others): void
{
foreach ($others as $other) {
if ($other->offsetExists("CONTRIBUTE: " . $in->getName())) {
// For CONTRIBUTEd, we just use all contributions.
foreach ($other["CONTRIBUTE: " . $in->getName()] as $o) {
$in->addDependant($o);
}
}
}
}

protected function resolveUse(OfComponent $component, array &$disambiguation, In $in, array &$others): void
{
$candidates = [];

foreach ($others as $other) {
if ($other->offsetExists("IMPLEMENT: " . $in->getName())) {
// For IMPLEMENTed dependencies, we need to make choice.
$candidates[] = $other["IMPLEMENT: " . $in->getName()];
}
}

$candidates = array_merge(...$candidates);

if (empty($candidates)) {
throw new \LogicException("Could not resolve dependency for: " . (string) $in);
}

if (count($candidates) === 1) {
$in->addDependant($candidates[0]);
return;
}

$preferred_class = $this->disambiguate($component, $disambiguation, $in);
if (is_null($preferred_class)) {
throw new \LogicException(
"Dependency {$in->getName()} is provided (at least) twice, " .
"no disambiguation for {$component->getComponentName()}."
);
}
foreach ($candidates as $candidate) {
if ($candidate->class === $preferred_class) {
$in->addDependant($candidate);
return;
}
}
throw new \LogicException(
"Dependency $preferred_class for service {$in->getName()} " .
"for {$component->getComponentName()} could not be located."
);
}

protected function disambiguate(OfComponent $component, array &$disambiguation, In $in): ?string
{
$service_name = (string) $in->getName();
foreach ([$component->getComponentName(), "*"] as $c) {
if (isset($disambiguation[$c]) && isset($disambiguation[$c][$service_name])) {
return $disambiguation[$c][$service_name];
}
}
return null;
}
}
Loading

0 comments on commit 0e0a3e3

Please sign in to comment.