diff --git a/src/Classes/DependencyBuilder.php b/src/Classes/DependencyBuilder.php deleted file mode 100644 index 52b1f0f4..00000000 --- a/src/Classes/DependencyBuilder.php +++ /dev/null @@ -1,229 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace RobinTheHood\ModifiedModuleLoaderClient; - -use RobinTheHood\ModifiedModuleLoaderClient\Loader\ModuleLoader; - -class DependencyBuilder -{ - // protected $comparator; - - // public function __construct() - // { - // $this->comparator = new Comparator(new Parser()); - // } - - // public function getInstalledModules() - // { - // $localModuleLoader = LocalModuleLoader::getModuleLoader(); - // $modules = $localModuleLoader->loadAllVersions(); - // $installedModules = ModuleFilter::filterInstalled($modules); - // return $installedModules; - // } - - // public function getAllModules($module) - // { - // $requireModulesTree = $this->buildTreeByModule($module); - - // $requireModules = []; - // $this->flattenTree($requireModulesTree, $requireModules); - - // $requireModules = $this->getUniqueRequireModules($requireModules); - - // $modules = []; - // foreach ($requireModules as $requireModule) { - // $modules[] = $requireModule['module']; - // } - - // return $modules; - // } - - // public function canBeInstalled($module) - // { - // $modules = $this->getAllModules($module); - // $modules[] = $module; - // foreach ($modules as $module) { - // $this->canBeInstalledTestRequiers($module, $modules); - // $this->canBeInstalledTestSelected($module, $modules); - // $this->canBeInstalledTestInstalled($module); - // } - // } - - // public function canBeInstalledTestInstalled($module) - // { - // $installedModules = $this->getInstalledModules(); - // $this->canBeInstalledTestSelected($module, $installedModules); - // } - - // public function canBeInstalledTestSelected($module, $modules) - // { - // $usedByEntrys = $this->getUsedByEntrys($module, $modules); - // foreach ($usedByEntrys as $usedByEntry) { - // if (!$this->comparator->satisfies($module->getVersion(), $usedByEntry['requiredVersion'])) { - // $a = $module->getArchiveName(); - // $av = $module->getVersion(); - // $b = $usedByEntry['module']->getArchiveName(); - // $bv = $usedByEntry['requiredVersion']; - // die("Module $a version $av can not be installed because module $b requires version $bv"); - // } - // } - // } - - // public function canBeInstalledTestRequiers($module, $modules) - // { - // foreach ($module->getRequire() as $archiveName => $version) { - // $moduleFound = false; - // foreach ($modules as $selectedModule) { - // if ($selectedModule->getArchiveName() != $archiveName) { - // continue; - // } - - // $moduleFound = true; - // if (!$this->comparator->satisfies($selectedModule->getVersion(), $version)) { - // $a = $selectedModule->getArchiveName(); - // $av = $module->getVersion(); - // die("Module $a version $av can not be installed because module $archiveName version $version is required"); - // } - // } - - // if (!$moduleFound) { - // die("Module $archiveName version $version can not be installed because module was not found."); - // } - // } - // } - - // Liefert alle Module aus $selectedModules die das Modul $module verwenden - // inkl. die benötigte Versionsnummer. - // public function getUsedByEntrys($module, $selectedModules) - // { - // $usedByEntrys = []; - // foreach ($selectedModules as $selectedModule) { - // foreach ($selectedModule->getRequire() as $archiveName => $version) { - // if ($archiveName == $module->getArchiveName()) { - // $usedByEntrys[] = [ - // 'module' => $selectedModule, - // 'requiredVersion' => $version - // ]; - // } - // } - // } - // return $usedByEntrys; - // } - - public function flattenTree($moduleTree, &$modules = null) - { - if (!$moduleTree) { - return; - } - - foreach ($moduleTree as $entry) { - $modules[] = [ - 'module' => $entry['module'], - 'requestedVersion' => $entry['requestedVersion'], - 'selectedVersion' => $entry['selectedVersion'] - ]; - $this->flattenTree($entry['require'], $modules); - } - } - - // public function getUniqueRequireModules($requireModules) - // { - // $uniqueModules = []; - // foreach ($requireModules as $requireModule) { - // $index = $requireModule['module']->getArchiveName() . ':' . $requireModule['selectedVersion']; - // $uniqueModules[$index] = [ - // 'module' => $requireModule['module'], - // 'requestedVersion' => $requireModule['requestedVersion'], - // 'selectedVersion' => $requireModule['selectedVersion'] - // ]; - // } - - // return array_values($uniqueModules); - // } - - // private function buildTreeByArchiveName($archiveName, $version) - // { - // $moduleLoader = ModuleLO - // $module = $this->loadModuleByArchiveName($archiveName, $version); - // return $this->buildTreeByModule($module); - // } - - // private function buildTreeByModule($module) - // { - // $requireModulesTree = $this->buildTreeByModuleRecursive($module); - // return $requireModulesTree; - // } - - public function test() - { - $moduleLoader = ModuleLoader::getModuleLoader(); - $module = $moduleLoader->loadLatestVersionByArchiveName('firstweb/multi-order'); - - // var_dump($module); - // die(); - $tree = $this->buildTreeByModuleRecursive($module); - - echo '
';
-        print_r($tree);
-    }
-
-    private function buildTreeByModuleRecursive(Module $module, int $depth = 0): array
-    {
-        if ($depth >= 5) {
-            return false;
-        }
-
-        $require = $module->getRequire();
-
-        $requireModulesTree = [];
-        foreach ($require as $archiveName => $versionConstraint) {
-            $moduleLoader = ModuleLoader::getModuleLoader();
-
-            // An dieser Stelle wird zurzeit immer die neuste Variante ausgewählt.
-            // Eigentlich müssen hier alle Varianten die zu $versionConstraint passen
-            // ausgewählt und weiter verarbeitet werden. Zurzeit wird $versionConstraint
-            // aber nicht beachtet. Das muss bei einer späteren Version verbessert werden.
-            $selectedModule = $moduleLoader->loadLatestVersionByArchiveName($archiveName);
-
-            $entry = [];
-            if ($selectedModule) {
-                // $entry['module'] = $selectedModule;
-                $entry['archiveName'] = $selectedModule->getArchiveName();
-                $entry['requestedVersion'] = $versionConstraint;
-                $entry['selectedVersion'] = $selectedModule->getVersion();
-                $entry['require'] = [];
-                $requireModules = $this->buildTreeByModuleRecursive($selectedModule, ++$depth);
-
-                if ($requireModules) {
-                    $entry['require'] = $requireModules;
-                }
-
-                $requireModulesTree[] = $entry;
-            }
-        }
-
-        return $requireModulesTree;
-    }
-
-    // private function loadLatestVersionByArchiveName(string $archiveName): ?Module
-    // {
-    //     $modules = [];
-    //     $localModuleLoader = LocalModuleLoader::getModuleLoader();
-    //     $modules[] = $localModuleLoader->loadLatestVersionByArchiveName($archiveName);
-
-    //     $remoteModuleLoader = RemoteModuleLoader::getModuleLoader();
-    //     $modules[] = $remoteModuleLoader->loadLatestVersionByArchiveName($archiveName);
-
-    //     $latestVersion = ModuleFilter::getLatestVersion($modules);
-    //     return $latestVersion;
-    // }
-}
diff --git a/src/Classes/DependencyManager/Combination.php b/src/Classes/DependencyManager/Combination.php
new file mode 100644
index 00000000..0621d779
--- /dev/null
+++ b/src/Classes/DependencyManager/Combination.php
@@ -0,0 +1,104 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+declare(strict_types=1);
+
+namespace RobinTheHood\ModifiedModuleLoaderClient\DependencyManager;
+
+class Combination
+{
+    /** @var array */
+    private $combinations = [];
+
+    /**
+     * @return array
+     */
+    public function getAll(): array
+    {
+        return $this->combinations;
+    }
+
+    public function add(string $archiveName, string $version): void
+    {
+        if (array_key_exists($archiveName, $this->combinations)) {
+            throw new DependencyException($archiveName . ' is already set.');
+        }
+
+        $this->combinations[$archiveName] = $version;
+    }
+
+    public function overwrite(string $archiveName, string $version): void
+    {
+        $this->combinations[$archiveName] = $version;
+    }
+
+    public function getVersion(string $archiveName): string
+    {
+        if (!array_key_exists($archiveName, $this->combinations)) {
+            throw new DependencyException('Version of ' . $archiveName . ' not found.');
+        }
+
+        return $this->combinations[$archiveName];
+    }
+
+    public function clone(): Combination
+    {
+        $combinations = $this->combinations; // clones an array
+        $newCombination = new Combination();
+        $newCombination->combinations = $combinations;
+        return $newCombination;
+    }
+
+    /**
+     * Liefert eine neues Combinations Obj zurück, in der nur echte Module enthalten sind
+     *
+     * Einträge, wie modified, php, mmlc werden entfernt.
+     *
+     * @return Combination Liefert ein Combination Obj nur mit echten Modulen
+     */
+    public function strip(): Combination
+    {
+        $combination = new Combination();
+        foreach ($this->combinations as $archiveName => $version) {
+            if (!$this->isArchiveName($archiveName)) {
+                continue;
+            }
+
+            $combination->add($archiveName, $version);
+        }
+        return $combination;
+    }
+
+    /**
+     * Überprüft, ob es sich um einen gültigen ArchvieName handelt
+     *
+     * @param string $archiveName Ein ArchiveName
+     * @return bool Liefert true, wenn $archiveName ein gültiger ArchvieName ist
+     */
+    private function isArchiveName(string $archiveName): bool
+    {
+        if (strpos($archiveName, '/') === false) {
+            return false;
+        }
+
+        return true;
+    }
+
+
+    public function __toString(): string
+    {
+        $strings = [];
+        foreach ($this->combinations as $archiveName => $version) {
+            $strings[] = "$archiveName v$version";
+        }
+        return implode(', ', $strings);
+    }
+}
diff --git a/src/Classes/DependencyManager/CombinationBuilder.php b/src/Classes/DependencyManager/CombinationBuilder.php
new file mode 100644
index 00000000..ee4eedbb
--- /dev/null
+++ b/src/Classes/DependencyManager/CombinationBuilder.php
@@ -0,0 +1,57 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+declare(strict_types=1);
+
+namespace RobinTheHood\ModifiedModuleLoaderClient\DependencyManager;
+
+class CombinationBuilder
+{
+    /**
+     * @param FlatEntry[] $flatEntries
+     *
+     * @return Combination[]
+     */
+    public function buildAllFromModuleFlatEntries($flatEntries): array
+    {
+        $combinations = [];
+        $flatEntries = array_values($flatEntries);
+        $this->buildAllCombinationsFromModuleFlatEntries($flatEntries, $combinations, new Combination(), 0);
+        return $combinations;
+    }
+
+    /**
+     * @param FlatEntry[] $flatEntries
+     * @param Combination[] $combinations
+     * @param Combination $combination
+     * @param int $index
+     */
+    private function buildAllCombinationsFromModuleFlatEntries(
+        array &$flatEntries,
+        array &$combinations,
+        Combination $combination,
+        int $index
+    ): void {
+        /** @var FlatEntry*/
+        $flatEntry = $flatEntries[$index] ?? [];
+
+        if (!$flatEntry) {
+            $combinations[] = $combination;
+            return;
+        }
+
+        foreach ($flatEntry->versions as $versionStr) {
+            $newCombination = $combination->clone();
+            $newCombination->add($flatEntry->archiveName, $versionStr);
+            $this->buildAllCombinationsFromModuleFlatEntries($flatEntries, $combinations, $newCombination, $index + 1);
+        }
+    }
+}
diff --git a/src/Classes/DependencyManager/CombinationIterator.php b/src/Classes/DependencyManager/CombinationIterator.php
new file mode 100644
index 00000000..84683748
--- /dev/null
+++ b/src/Classes/DependencyManager/CombinationIterator.php
@@ -0,0 +1,82 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+declare(strict_types=1);
+
+namespace RobinTheHood\ModifiedModuleLoaderClient\DependencyManager;
+
+class CombinationIterator
+{
+    /** @var FlatEntry[] */
+    private $flatEntries;
+
+    /** @var int[] */
+
+    /** @var Counter $counter */
+    private $counter;
+
+    /**
+     * @param FlatEntry[] $flatEntries
+     */
+    public function __construct(array $flatEntries)
+    {
+        $this->flatEntries = $flatEntries;
+
+        $counterMaxValues = [];
+        foreach ($flatEntries as $flatEntry) {
+            if (!$flatEntry->versions) {
+                continue;
+            }
+            $counterMaxValues[] = count($flatEntry->versions) - 1;
+        }
+
+        $this->counter = new Counter($counterMaxValues);
+    }
+
+    public function current(): Combination
+    {
+        return $this->combinationFromCounter($this->flatEntries, $this->counter);
+    }
+
+    public function next(): Combination
+    {
+        $this->counter->next();
+        return $this->combinationFromCounter($this->flatEntries, $this->counter);
+    }
+
+    public function isStart(): bool
+    {
+        return $this->counter->isStart();
+    }
+
+    /**
+     * @param FlatEntry[] $flatEntries
+     * @param Counter Counter
+     */
+    private function combinationFromCounter(array $flatEntries, Counter $counter): Combination
+    {
+        $counter = $counter->current();
+
+        $counterIndex = 0;
+        $combination = new Combination();
+        foreach ($flatEntries as $flatEntry) {
+            if (!$flatEntry->versions) {
+                continue;
+            }
+            $versionIndex = $counter[$counterIndex];
+            $versionStr = $flatEntry->versions[$versionIndex];
+            $combination->add($flatEntry->archiveName, $versionStr);
+            $counterIndex++;
+        }
+
+        return $combination;
+    }
+}
diff --git a/src/Classes/DependencyManager/CombinationSatisfyer.php b/src/Classes/DependencyManager/CombinationSatisfyer.php
new file mode 100644
index 00000000..b3a417f7
--- /dev/null
+++ b/src/Classes/DependencyManager/CombinationSatisfyer.php
@@ -0,0 +1,247 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+declare(strict_types=1);
+
+namespace RobinTheHood\ModifiedModuleLoaderClient\DependencyManager;
+
+use RobinTheHood\ModifiedModuleLoaderClient\Semver\Comparator;
+use RobinTheHood\ModifiedModuleLoaderClient\Semver\Parser;
+
+class CombinationSatisfyer
+{
+    /** @var Comparator */
+    private $comparator;
+
+    public function __construct()
+    {
+        $this->comparator = new Comparator(new Parser());
+    }
+
+    /**
+     * @param ModuleTree[] $moduleTrees
+     * @param Combination[] $combinations
+     *
+     * @return CombinationSatisfyerResult
+     */
+    public function satisfiesCominationsFromModuleTrees(
+        array $moduleTrees,
+        array $combinations
+    ): CombinationSatisfyerResult {
+        $testCombination = null;
+        $foundCombination = new Combination();
+        $failLog = new FailLog();
+
+        foreach ($combinations as $testCombination) {
+            $foundCombination = new Combination();
+            $result = $this->satisfiesCominationFromModuleTrees(
+                $moduleTrees,
+                $testCombination,
+                $foundCombination,
+                $failLog
+            );
+
+            if ($result) {
+                $combinationSatisfyerResult = new CombinationSatisfyerResult(
+                    CombinationSatisfyerResult::RESULT_COMBINATION_FOUND,
+                    $testCombination,
+                    $foundCombination,
+                    $failLog
+                );
+                return $combinationSatisfyerResult;
+            }
+        }
+
+        $combinationSatisfyerResult = new CombinationSatisfyerResult(
+            CombinationSatisfyerResult::RESULT_COMBINATION_NOT_FOUND,
+            $testCombination,
+            $foundCombination,
+            $failLog
+        );
+
+        return $combinationSatisfyerResult;
+    }
+
+    /**
+     * @param ModuleTree $moduleTree
+     * @param Combination[] $combinations
+     *
+     * @return CombinationSatisfyerResult
+     */
+    public function satisfiesCominationsFromModuleTree(
+        ModuleTree $moduleTree,
+        array $combinations
+    ): CombinationSatisfyerResult {
+        $testCombination = null;
+        $foundCombination = new Combination();
+        $failLog = new FailLog();
+
+        foreach ($combinations as $testCombination) {
+            $result = $this->satisfiesCominationFromModuleTree(
+                $moduleTree,
+                $testCombination,
+                $foundCombination,
+                $failLog
+            );
+
+            if ($result) {
+                $combinationSatisfyerResult = new CombinationSatisfyerResult(
+                    CombinationSatisfyerResult::RESULT_COMBINATION_FOUND,
+                    $testCombination,
+                    $foundCombination,
+                    $failLog
+                );
+                return $combinationSatisfyerResult;
+            }
+        }
+
+        $combinationSatisfyerResult = new CombinationSatisfyerResult(
+            CombinationSatisfyerResult::RESULT_COMBINATION_NOT_FOUND,
+            $testCombination,
+            $foundCombination,
+            $failLog
+        );
+
+        return $combinationSatisfyerResult;
+    }
+
+    public function satisfiesCominationsFromModuleWithIterator(
+        ModuleTree $moduleTree,
+        CombinationIterator $combinationIterator
+    ): CombinationSatisfyerResult {
+        $foundCombination = new Combination();
+
+        while (true) {
+            $failLog = new FailLog();
+            $testCombination = $combinationIterator->current();
+            $result = $this->satisfiesCominationFromModuleTree(
+                $moduleTree,
+                $testCombination,
+                $foundCombination,
+                $failLog
+            );
+
+            if ($result) {
+                $combinationSatisfyerResult = new CombinationSatisfyerResult(
+                    CombinationSatisfyerResult::RESULT_COMBINATION_FOUND,
+                    $testCombination,
+                    $foundCombination,
+                    $failLog
+                );
+                return $combinationSatisfyerResult;
+            }
+
+            $combinationIterator->next();
+            if ($combinationIterator->isStart()) {
+                $combinationSatisfyerResult = new CombinationSatisfyerResult(
+                    CombinationSatisfyerResult::RESULT_COMBINATION_NOT_FOUND,
+                    $testCombination,
+                    $foundCombination,
+                    $failLog
+                );
+                return $combinationSatisfyerResult;
+            }
+        }
+    }
+
+    /**
+     * @param ModuleTree $moduleTree
+     * @param Combination $combination
+     * @param Combination $foundCombination
+     * @param FailLog $failLog
+     * @param ModuleTree[] $moduleTreeChain
+     *
+     * @return bool
+     */
+    public function satisfiesCominationFromModuleTree(
+        ModuleTree $moduleTree,
+        Combination $combination,
+        Combination &$foundCombination,
+        FailLog &$failLog,
+        array $moduleTreeChain = []
+    ): bool {
+        // Context: Module
+        $archiveName = $moduleTree->archiveName;
+        try {
+            $selectedVersion = $combination->getVersion($archiveName);
+        } catch (DependencyException $e) {
+            return false;
+        }
+
+        // Es gibt keine weiteren Untermodule
+        if (!$moduleTree->moduleVersions) {
+            $result = $this->comparator->satisfies($selectedVersion, $moduleTree->versionConstraint);
+            if (!$result) {
+                $failLog->fail($moduleTreeChain, $archiveName, $selectedVersion, $moduleTree->versionConstraint);
+            } else {
+                $failLog->unfail($moduleTreeChain, $archiveName, $selectedVersion, $moduleTree->versionConstraint);
+            }
+            return $result;
+        }
+
+        foreach ($moduleTree->moduleVersions as $moduleVersion) {
+            // Context: Version
+            if ($moduleVersion->version === $selectedVersion) {
+                $foundCombination->overwrite($archiveName, $moduleVersion->version);
+
+                $failLog->unfail(
+                    $moduleTreeChain,
+                    $archiveName,
+                    $moduleVersion->version,
+                    $moduleTree->versionConstraint
+                );
+
+                return $this->satisfiesCominationFromModuleTrees(
+                    $moduleVersion->require,
+                    $combination,
+                    $foundCombination,
+                    $failLog,
+                    array_merge($moduleTreeChain, [$moduleTree])
+                );
+            }
+
+            $failLog->fail($moduleTreeChain, $archiveName, $moduleVersion->version, $moduleTree->versionConstraint);
+        }
+
+        return false;
+    }
+
+    /**
+     * @param ModuleTree[] $moduleTrees
+     * @param Combination $combination
+     * @param Combination $foundCombination
+     * @param FailLog $failLog
+     * @param ModuleTree[] $moduleTreeChain
+     *
+     * @return bool
+     */
+    public function satisfiesCominationFromModuleTrees(
+        array $moduleTrees,
+        Combination $combination,
+        Combination &$foundCombination,
+        FailLog &$failLog,
+        array $moduleTreeChain = []
+    ): bool {
+        // Context: Expanded
+        $moduleResult = true;
+        foreach ($moduleTrees as $moduleTree) {
+            $result = $this->satisfiesCominationFromModuleTree(
+                $moduleTree,
+                $combination,
+                $foundCombination,
+                $failLog,
+                $moduleTreeChain
+            );
+            $moduleResult = $moduleResult && $result;
+        }
+        return $moduleResult;
+    }
+}
diff --git a/src/Classes/DependencyManager/CombinationSatisfyerResult.php b/src/Classes/DependencyManager/CombinationSatisfyerResult.php
new file mode 100644
index 00000000..df6cad59
--- /dev/null
+++ b/src/Classes/DependencyManager/CombinationSatisfyerResult.php
@@ -0,0 +1,44 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+declare(strict_types=1);
+
+namespace RobinTheHood\ModifiedModuleLoaderClient\DependencyManager;
+
+class CombinationSatisfyerResult
+{
+    public const RESULT_COMBINATION_NOT_FOUND = 0;
+    public const RESULT_COMBINATION_FOUND = 1;
+
+    /** @var int */
+    public $result = -1;
+
+    /** @var ?Combination */
+    public $testCombination = null;
+
+    /** @var ?Combination */
+    public $foundCombination = null;
+
+    /** @var ?FailLog */
+    public $failLog = null;
+
+    public function __construct(
+        int $result,
+        ?Combination $testCombination,
+        ?Combination $foundCombination,
+        ?FailLog $failLog
+    ) {
+        $this->result = $result;
+        $this->testCombination = $testCombination;
+        $this->foundCombination = $foundCombination;
+        $this->failLog = $failLog;
+    }
+}
diff --git a/src/Classes/DependencyManager/Counter.php b/src/Classes/DependencyManager/Counter.php
new file mode 100644
index 00000000..1821451a
--- /dev/null
+++ b/src/Classes/DependencyManager/Counter.php
@@ -0,0 +1,67 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+declare(strict_types=1);
+
+namespace RobinTheHood\ModifiedModuleLoaderClient\DependencyManager;
+
+class Counter
+{
+    private $counterMaxValues = [];
+    private $counterValues = [];
+
+    public function __construct(array $maxValues)
+    {
+        $this->counterMaxValues = $maxValues;
+        foreach ($this->counterMaxValues as $counterMaxValue) {
+            $this->counterValues[] = 0;
+        }
+    }
+
+    public function current(): array
+    {
+        return $this->counterValues;
+    }
+
+    public function next(): array
+    {
+        $this->incrementCounterAtIndex(0);
+        return $this->counterValues;
+    }
+
+    public function isStart(): bool
+    {
+        foreach ($this->counterValues as $value) {
+            if ($value !== 0) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    private function incrementCounterAtIndex(int $counterIndex)
+    {
+        if ($counterIndex > count($this->counterMaxValues) - 1) {
+            return true;
+        }
+
+        if ($this->incrementCounterAtIndex($counterIndex + 1)) {
+            $this->counterValues[$counterIndex]++;
+        }
+
+        if ($this->counterValues[$counterIndex] > $this->counterMaxValues[$counterIndex]) {
+            $this->counterValues[$counterIndex] = 0;
+            return true;
+        }
+
+        return false;
+    }
+}
diff --git a/src/Classes/DependencyManager/DependencyBuilder.php b/src/Classes/DependencyManager/DependencyBuilder.php
new file mode 100644
index 00000000..934cffc4
--- /dev/null
+++ b/src/Classes/DependencyManager/DependencyBuilder.php
@@ -0,0 +1,186 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace RobinTheHood\ModifiedModuleLoaderClient\DependencyManager;
+
+use RobinTheHood\ModifiedModuleLoaderClient\Loader\ModuleLoader;
+use RobinTheHood\ModifiedModuleLoaderClient\Module;
+use RobinTheHood\ModifiedModuleLoaderClient\Semver\Constraint;
+
+class DependencyBuilder
+{
+    private function logFile($value, $file)
+    {
+        @mkdir(__DIR__ . '/logs/');
+        $path = __DIR__ . '/logs/' . $file;
+        file_put_contents($path, json_encode($value, JSON_PRETTY_PRINT));
+    }
+
+    public function log($var)
+    {
+        print_r($var);
+    }
+
+    public function test()
+    {
+        $moduleLoader = ModuleLoader::getModuleLoader();
+        $module = $moduleLoader->loadLatestVersionByArchiveName('firstweb/multi-order');
+
+        if (!$module) {
+            die('Can not find base module');
+        }
+
+        $systemSet = new SystemSet();
+        $systemSet->set([
+            "modified" => '2.0.4.2',
+            "php" => '7.4.0',
+            "mmlc" => '1.19.0',
+            "composer/autoload" => '1.3.0',
+            "robinthehood/modified-std-module" => '0.9.0',
+            "robinthehood/modified-orm" => '1.8.1',
+            "robinthehood/pdf-bill" => '0.17.0'
+        ]);
+
+        $this->log('TEST: satisfiesContraints1');
+        $combinationSatisfyerResult = $this->satisfiesContraints1($module, $systemSet);
+        $this->log($combinationSatisfyerResult);
+
+        $this->log('TEST: satisfiesContraints2');
+        $combinationSatisfyerResult = $this->satisfiesContraints2('firstweb/multi-order', '^1.0.0', $systemSet);
+        $this->log($combinationSatisfyerResult);
+
+        // var_dump('TEST: satisfiesContraints3');
+        $combinationSatisfyerResult = $this->satisfies('firstweb/multi-order', '^1.0.0', $systemSet);
+        $this->log($combinationSatisfyerResult);
+    }
+
+    public function satisfiesContraints1(Module $module, SystemSet $systemSet): CombinationSatisfyerResult
+    {
+        $moduleTreeBuilder = new ModuleTreeBuilder();
+        $moduleTrees = $moduleTreeBuilder->buildListByConstraints($module);
+        $this->logFile($moduleTrees, '1-moduleTrees.json');
+
+        $flatEntryBuilder = new FlatEntryBuilder();
+        $flatEntries = $flatEntryBuilder->buildListFromModuleTrees($moduleTrees);
+        $this->logFile($flatEntries, '1-flatEntries.json');
+
+        $flatEntries = $flatEntryBuilder->fitSystemSet($flatEntries, $systemSet);
+        $this->logFile($flatEntries, '1-flatEntries-fit.json');
+
+        $combinationBuilder = new CombinationBuilder();
+        $combinations = $combinationBuilder->buildAllFromModuleFlatEntries($flatEntries);
+        $this->logFile($combinations, '1-combinations.json');
+
+        $combinationSatisfyer = new CombinationSatisfyer();
+        $combinationSatisfyerResult = $combinationSatisfyer->satisfiesCominationsFromModuleTrees(
+            $moduleTrees,
+            $combinations
+        );
+
+        return $combinationSatisfyerResult;
+    }
+
+
+    public function satisfiesContraints2(
+        string $archiveName,
+        string $constraint,
+        SystemSet $systemSet
+    ): CombinationSatisfyerResult {
+        $moduleTreeBuilder = new ModuleTreeBuilder();
+        $moduleTree = $moduleTreeBuilder->buildByConstraints($archiveName, $constraint);
+        $this->logFile($moduleTree, '2-moduleTrees.json');
+
+        $flatEntryBuilder = new FlatEntryBuilder();
+        $flatEntries = $flatEntryBuilder->buildListFromModuleTree($moduleTree);
+        $this->logFile($flatEntries, '2-flatEntries.json');
+
+        $flatEntries = $flatEntryBuilder->fitSystemSet($flatEntries, $systemSet);
+        $this->logFile($flatEntries, '2-flatEntries-fit.json');
+
+        $combinationBuilder = new CombinationBuilder();
+        $combinations = $combinationBuilder->buildAllFromModuleFlatEntries($flatEntries);
+        $this->logFile($combinations, '2-combinations.json');
+
+        $combinationSatisfyer = new CombinationSatisfyer();
+        $combinationSatisfyerResult = $combinationSatisfyer->satisfiesCominationsFromModuleTree(
+            $moduleTree,
+            $combinations
+        );
+
+        return $combinationSatisfyerResult;
+    }
+
+    public function satisfies(string $archiveName, string $constraint, SystemSet $systemSet): CombinationSatisfyerResult
+    {
+        $systemSet->remove($archiveName);
+        $constraint = $this->createConstraint($archiveName, $constraint, $systemSet);
+
+        $moduleTreeBuilder = new ModuleTreeBuilder();
+        $moduleTree = $moduleTreeBuilder->buildByConstraints($archiveName, $constraint);
+        $this->logFile($moduleTree, '3-moduleTrees.json');
+
+        $flatEntryBuilder = new FlatEntryBuilder();
+        $flatEntries = $flatEntryBuilder->buildListFromModuleTree($moduleTree);
+        $this->logFile($flatEntries, '3-flatEntries.json');
+
+        $flatEntries = $flatEntryBuilder->fitSystemSet($flatEntries, $systemSet);
+        $this->logFile($flatEntries, '3-flatEntries-fit.json');
+
+        $combinationIterator = new CombinationIterator($flatEntries);
+        $combinationSatisfyer = new CombinationSatisfyer();
+        $combinationSatisfyerResult = $combinationSatisfyer->satisfiesCominationsFromModuleWithIterator(
+            $moduleTree,
+            $combinationIterator
+        );
+        return $combinationSatisfyerResult;
+    }
+
+    /**
+     * Gehe alle Module durch, die in $systemSet sind und das gleichzeitig Modul $archiveName benötigen.
+     * Gibt ein $constraint zurück, sodass die Anforderungenden der Module in $systemSet erhaltenbleiben.
+     */
+    private function createConstraint(string $archiveName, string $constraint, SystemSet $systemSet): string
+    {
+        /** @var string[] */
+        $requiredConstraints = [$constraint];
+
+        $archives = $systemSet->getArchives();
+        foreach ($archives as $archiveNameB => $version) {
+            $installedModule = $this->getModuleByArchiveNameAndVersion($archiveNameB, $version);
+            if (!$installedModule) {
+                continue;
+            }
+
+            $requiredConstraint = $this->getRequiredConstraint($installedModule, $archiveName);
+            if (!$requiredConstraint) {
+                continue;
+            }
+
+            $requiredConstraints[] = $requiredConstraint;
+        }
+
+        $constraint = Constraint::createConstraintFromConstraints($requiredConstraints);
+
+        return $constraint;
+    }
+
+    private function getModuleByArchiveNameAndVersion(string $archiveName, string $version): ?Module
+    {
+        $moduleLoader = ModuleLoader::getModuleLoader();
+        return $moduleLoader->loadByArchiveNameAndVersion($archiveName, $version);
+    }
+
+    private function getRequiredConstraint(Module $installedModule, string $archiveName): string
+    {
+        $required = $installedModule->getRequire();
+        return $required[$archiveName] ?? '';
+    }
+}
diff --git a/src/Classes/DependencyManager/DependencyBuilderOld.php b/src/Classes/DependencyManager/DependencyBuilderOld.php
new file mode 100644
index 00000000..dd228936
--- /dev/null
+++ b/src/Classes/DependencyManager/DependencyBuilderOld.php
@@ -0,0 +1,303 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace RobinTheHood\ModifiedModuleLoaderClient\DependencyManager;
+
+use RobinTheHood\ModifiedModuleLoaderClient\Loader\ModuleLoader;
+use RobinTheHood\ModifiedModuleLoaderClient\Module;
+
+class DependencyBuilderOld
+{
+    private function logFile($value, $file)
+    {
+        @mkdir(__DIR__ . '/logs/');
+        $path = __DIR__ . '/logs/' . $file;
+        file_put_contents($path, json_encode($value, JSON_PRETTY_PRINT));
+    }
+
+    public function log($var)
+    {
+        print_r($var);
+    }
+
+    public function test()
+    {
+        $moduleLoader = ModuleLoader::getModuleLoader();
+        $module = $moduleLoader->loadLatestVersionByArchiveName('firstweb/multi-order');
+
+        if (!$module) {
+            die('Can not find base module');
+        }
+
+        $modules = [];
+        $this->getModuleList($modules, $module);
+        $this->logFile($modules, 'debug-log-modules.json');
+
+        $tree = $this->buildTreeByModuleRecursive($module);
+        $this->logFile($tree, 'debug-log-tree.json');
+
+        $tree = $this->buildTreeByModuleRecursiveConstraint($module);
+        $this->logFile($tree, 'debug-log-tree-constraint.json');
+
+        $flat = [];
+        $this->flattenTreeNew($tree, $flat);
+        $this->logFile($flat, 'debug-log-flat.json');
+
+        $combinations = [];
+        $this->allCombinations($flat, $combinations, 0);
+        $this->logFile($combinations, 'debug-log-combinations.json');
+
+        $combinations = array_reverse($combinations);
+        foreach ($combinations as $combination) {
+            $combination['composer/autoload'] = '1.0.0';
+            $result = $this->satisfiesComination($tree, $combination);
+            if ($result) {
+                $this->log($combination);
+                $this->log($result);
+                break;
+            }
+        }
+    }
+
+
+    private function allCombinations(&$flat, &$combinations, int $index, $combination = [])
+    {
+        $entry = array_values($flat)[$index] ?? [];
+
+        if (!$entry) {
+            $combinations[] = $combination;
+            return;
+        }
+
+        foreach ($entry['versions'] as $version) {
+            $newCombination = array_merge($combination, [$entry['archiveName'] => $version]);
+            $this->allCombinations($flat, $combinations, $index + 1, $newCombination);
+        }
+    }
+
+    private function satisfiesComination(&$tree, $combination): bool
+    {
+        // Expanded
+        $moduleResult = true;
+        foreach ($tree as &$module) {
+            // Module
+            $archiveName = $module['archiveName'];
+            $moduleVersions = &$module['versions'];
+            $selectedVersion = $combination[$archiveName];
+            $versionResult = false;
+            foreach ($moduleVersions as $version => &$moduleVersion) {
+                // Version
+                if ($version === $selectedVersion) {
+                    $subTree = &$moduleVersion['requireExpanded'];
+                    $versionResult = $this->satisfiesComination($subTree, $combination);
+                    break;
+                }
+            }
+            $moduleResult = $moduleResult && $versionResult;
+        }
+        return $moduleResult;
+    }
+
+    private function flattenTreeNew($tree, &$flat)
+    {
+        if (!$tree) {
+            return;
+        }
+
+        foreach ($tree as $entry) {
+            $moduleEntry = [];
+            $moduleEntry["archiveName"] = $entry['archiveName'];
+            foreach ($entry['versions'] as $version => $entrys) {
+                $moduleEntry["versions"][] = $version;
+                $this->flattenTreeNew($entrys['requireExpanded'], $flat);
+            }
+            $flat[$entry['archiveName']] = $moduleEntry;
+        }
+    }
+
+
+    private function getModuleList(array &$modules, Module $module, int $depth = 0)
+    {
+        if ($depth >= 10) {
+            return;
+        }
+
+        $requireExpanded = [];
+        $require = $module->getRequire();
+        foreach ($require as $archiveName => $versionConstraint) {
+            $moduleLoader = ModuleLoader::getModuleLoader();
+            $moduleVersions = $moduleLoader->loadAllByArchiveNameAndConstraint($archiveName, $versionConstraint);
+            $versions = [];
+            foreach ($moduleVersions as $moduleA) {
+                $versions[] = $archiveName . ' : ' . $moduleA->getVersion();
+            }
+            $requireExpanded[$archiveName] = $versions;
+        }
+
+        $moduleAsArray = [
+            'archiveName' => $module->getArchiveName(),
+            'version' => $module->getVersion(),
+            'require' => $require,
+            'requireExpanded' => $requireExpanded
+        ];
+
+        if (!$this->containsModule($moduleAsArray, $modules)) {
+            $modules[] = $moduleAsArray;
+        }
+
+        $require = $module->getRequire();
+        foreach ($require as $archiveName => $versionConstraint) {
+            $moduleLoader = ModuleLoader::getModuleLoader();
+            $moduleVersions = $moduleLoader->loadAllByArchiveNameAndConstraint($archiveName, $versionConstraint);
+            // var_dump($archiveName);
+            // if ($archiveName == 'robinthehood/modified-std-module') {
+            //     echo 'aaa';
+            //     var_dump($moduleVersions);
+            //     die();
+            // }
+            foreach ($moduleVersions as $moduleA) {
+                $this->getModuleList($modules, $moduleA, $depth + 1);
+            }
+        }
+    }
+
+    private function containsModule($moduleA, $modules)
+    {
+        foreach ($modules as $moduleB) {
+            if ($moduleA['archiveName'] !== $moduleB['archiveName']) {
+                continue;
+            }
+
+            if ($moduleA['version'] !== $moduleB['version']) {
+                continue;
+            }
+
+            return true;
+        }
+        return false;
+    }
+
+
+    private function buildTreeByModuleRecursiveConstraint(Module $module, int $depth = 0): array
+    {
+        if ($depth >= 10) {
+            return [];
+        }
+
+        $require = $module->getRequire();
+
+        $requireModulesTree = [];
+        foreach ($require as $archiveName => $versionConstraint) {
+            // Modules to Entry
+            $entry = [];
+            $entry['archiveName'] = $archiveName;
+            $entry['versionConstraint'] = $versionConstraint;
+
+            // Versions
+            $moduleLoader = ModuleLoader::getModuleLoader();
+            $modules = $moduleLoader->loadAllByArchiveNameAndConstraint($archiveName, $versionConstraint);
+            foreach ($modules as $module) {
+                $entry['versions'][$module->getVersion()] = [
+                    'value' => false,
+                    'requireExpanded' => $this->buildTreeByModuleRecursiveConstraint($module, $depth + 1)
+                ];
+            }
+
+            $requireModulesTree[] = $entry;
+        }
+
+        return $requireModulesTree;
+    }
+
+    private function buildTreeByModuleRecursive(Module $module, int $depth = 0): array
+    {
+        if ($depth >= 10) {
+            return [];
+        }
+
+        $require = $module->getRequire();
+
+        $requireModulesTree = [];
+        foreach ($require as $archiveName => $versionConstraint) {
+            // Modules to Entry
+            $entry = [];
+            $entry['archiveName'] = $archiveName;
+            $entry['versionConstraint'] = $versionConstraint;
+
+            // Versions
+            $moduleLoader = ModuleLoader::getModuleLoader();
+            $modules = $moduleLoader->loadAllVersionsByArchiveName($archiveName);
+            foreach ($modules as $module) {
+                $entry['versions'][$module->getVersion()] = $this->buildTreeByModuleRecursive($module, $depth + 1);
+            }
+
+
+            $requireModulesTree[] = $entry;
+        }
+
+        return $requireModulesTree;
+    }
+
+
+    private function buildTreeByModuleRecursiveOld(Module $module, int $depth = 0): array
+    {
+        if ($depth >= 5) {
+            return [];
+        }
+
+        $require = $module->getRequire();
+
+        $requireModulesTree = [];
+        foreach ($require as $archiveName => $versionConstraint) {
+            $moduleLoader = ModuleLoader::getModuleLoader();
+
+            // An dieser Stelle wird zurzeit immer die neuste Variante ausgewählt.
+            // Eigentlich müssen hier alle Varianten die zu $versionConstraint passen
+            // ausgewählt und weiter verarbeitet werden. Zurzeit wird $versionConstraint
+            // aber nicht beachtet. Das muss bei einer späteren Version verbessert werden.
+            $selectedModule = $moduleLoader->loadLatestVersionByArchiveName($archiveName);
+
+            $entry = [];
+            if ($selectedModule) {
+                // $entry['module'] = $selectedModule;
+                $entry['archiveName'] = $selectedModule->getArchiveName();
+                $entry['requestedVersion'] = $versionConstraint;
+                $entry['selectedVersion'] = $selectedModule->getVersion();
+                $entry['require'] = [];
+                $requireModules = $this->buildTreeByModuleRecursive($selectedModule, ++$depth);
+
+                if ($requireModules) {
+                    $entry['require'] = $requireModules;
+                }
+
+                $requireModulesTree[] = $entry;
+            }
+        }
+
+        return $requireModulesTree;
+    }
+
+    public function flattenTree($moduleTree, &$modules = null)
+    {
+        if (!$moduleTree) {
+            return;
+        }
+
+        foreach ($moduleTree as $entry) {
+            $modules[] = [
+                'module' => $entry['module'],
+                'requestedVersion' => $entry['requestedVersion'],
+                'selectedVersion' => $entry['selectedVersion']
+            ];
+            $this->flattenTree($entry['require'], $modules);
+        }
+    }
+}
diff --git a/src/Classes/DependencyException.php b/src/Classes/DependencyManager/DependencyException.php
similarity index 83%
rename from src/Classes/DependencyException.php
rename to src/Classes/DependencyManager/DependencyException.php
index 62305926..e2f25960 100644
--- a/src/Classes/DependencyException.php
+++ b/src/Classes/DependencyManager/DependencyException.php
@@ -11,7 +11,7 @@
  * file that was distributed with this source code.
  */
 
-namespace RobinTheHood\ModifiedModuleLoaderClient;
+namespace RobinTheHood\ModifiedModuleLoaderClient\DependencyManager;
 
 class DependencyException extends \LogicException
 {
diff --git a/src/Classes/DependencyManager/DependencyManager.php b/src/Classes/DependencyManager/DependencyManager.php
new file mode 100644
index 00000000..f5ef6721
--- /dev/null
+++ b/src/Classes/DependencyManager/DependencyManager.php
@@ -0,0 +1,136 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace RobinTheHood\ModifiedModuleLoaderClient\DependencyManager;
+
+use RobinTheHood\ModifiedModuleLoaderClient\DependencyManager\Combination;
+use RobinTheHood\ModifiedModuleLoaderClient\DependencyManager\CombinationSatisfyerResult;
+use RobinTheHood\ModifiedModuleLoaderClient\DependencyManager\DependencyBuilder;
+use RobinTheHood\ModifiedModuleLoaderClient\DependencyManager\SystemSetFactory;
+use RobinTheHood\ModifiedModuleLoaderClient\Module;
+use RobinTheHood\ModifiedModuleLoaderClient\Loader\ModuleLoader;
+use RobinTheHood\ModifiedModuleLoaderClient\Semver\Comparator;
+use RobinTheHood\ModifiedModuleLoaderClient\Semver\Parser;
+
+class DependencyManager
+{
+    protected $comparator;
+
+    public function __construct()
+    {
+        $this->comparator = new Comparator(new Parser());
+    }
+
+    /**
+     * Liefert eine Liste mit allen Modulen aus $selectedModules, die das Modul
+     * $module verwenden.
+     *
+     * @param Module[] $selectedModules
+    */
+    public function getUsedByEntrys(Module $module, array $selectedModules): array
+    {
+        $usedByEntrys = [];
+        foreach ($selectedModules as $selectedModule) {
+            foreach ($selectedModule->getRequire() as $archiveName => $version) {
+                if ($archiveName == $module->getArchiveName()) {
+                    $usedByEntrys[] = [
+                        'module' => $selectedModule,
+                        'requiredVersion' => $version
+                    ];
+                }
+            }
+        }
+        return $usedByEntrys;
+    }
+
+    /**
+     * @param Combination $combination
+     *
+     * @return Module[]
+     */
+    public function getAllModulesFromCombination(Combination $combination): array
+    {
+        $moduleLoader = ModuleLoader::getModuleLoader();
+        $moduleLoader->resetCache();
+
+        $modules = [];
+        foreach ($combination->strip()->getAll() as $archiveName => $version) {
+            $module = $moduleLoader->loadByArchiveNameAndVersion($archiveName, $version);
+            if (!$module) {
+                throw new DependencyException('Can not find Module ' . $archiveName . ' in version ' . $version);
+            }
+            $modules[] = $module;
+        }
+        return $modules;
+    }
+
+    /**
+     * @param Module $module
+     * @param string[] $doNotCheck
+     *
+     * @return CombinationSatisfyerResult
+     */
+    public function canBeInstalled(Module $module, array $doNotCheck = []): CombinationSatisfyerResult
+    {
+        $systemSetFactory = new SystemSetFactory();
+        $systemSet = $systemSetFactory->getSystemSet();
+
+        foreach ($doNotCheck as $name) {
+            $systemSet->remove($name);
+        }
+
+        $dependencyBuilder = new DependencyBuilder();
+        $combinationSatisfyerResult = $dependencyBuilder->satisfies(
+            $module->getArchiveName(),
+            $module->getVersion(),
+            $systemSet
+        );
+
+        if ($combinationSatisfyerResult->result === CombinationSatisfyerResult::RESULT_COMBINATION_NOT_FOUND) {
+            throw new DependencyException(
+                "Can not install module {$module->getArchiveName()} in version {$module->getVersion()} "
+                . "because there are conflicting version contraints. "
+                . "Perhaps you have installed a module that requires a different version, "
+                . "or there is no compatible combination of dependencies. "
+                . " The following combination is required: {$combinationSatisfyerResult->failLog}"
+            );
+        }
+
+        // $modules = $this->getAllModulesFromCombination($combinationSatisfyerResult->foundCombination);
+        // $this->canBeInstalledTestChanged($module, $modules);
+
+        return $combinationSatisfyerResult;
+    }
+
+    /**
+     * Testet, ob das Modul in $module installiert werden kann, ob das Modul $module
+     * selbst oder eine Abhängigkeit in $modules im Status 'changed' ist.
+     *
+     * @param Module[] $modules
+     */
+    private function canBeInstalledTestChanged(Module $module, array $modules): void
+    {
+        $module = $module->getInstalledVersion();
+        if ($module && $module->isInstalled() && $module->isChanged()) {
+            $a = $module->getArchiveName();
+            throw new DependencyException("Module $a can not be installed because the Module has changes");
+        }
+
+        foreach ($modules as $module) {
+            if ($module && $module->isInstalled() && $module->isChanged()) {
+                $a = $module->getArchiveName();
+                throw new DependencyException("Required Module $a can not be installed because the Module has changes");
+            }
+        }
+    }
+}
diff --git a/src/Classes/DependencyManager.php b/src/Classes/DependencyManager/DependencyManagerOld.php
similarity index 77%
rename from src/Classes/DependencyManager.php
rename to src/Classes/DependencyManager/DependencyManagerOld.php
index 8ccde429..bb391a42 100644
--- a/src/Classes/DependencyManager.php
+++ b/src/Classes/DependencyManager/DependencyManagerOld.php
@@ -11,15 +11,19 @@
  * file that was distributed with this source code.
  */
 
-namespace RobinTheHood\ModifiedModuleLoaderClient;
+namespace RobinTheHood\ModifiedModuleLoaderClient\DependencyManager;
 
+use RobinTheHood\ModifiedModuleLoaderClient\DependencyManager\Combination;
+use RobinTheHood\ModifiedModuleLoaderClient\DependencyManager\CombinationSatisfyerResult;
+use RobinTheHood\ModifiedModuleLoaderClient\DependencyManager\DependencyBuilder;
+use RobinTheHood\ModifiedModuleLoaderClient\DependencyManager\SystemSetFactory;
 use RobinTheHood\ModifiedModuleLoaderClient\Module;
 use RobinTheHood\ModifiedModuleLoaderClient\Loader\LocalModuleLoader;
 use RobinTheHood\ModifiedModuleLoaderClient\Loader\ModuleLoader;
 use RobinTheHood\ModifiedModuleLoaderClient\Semver\Comparator;
 use RobinTheHood\ModifiedModuleLoaderClient\Semver\Parser;
 
-class DependencyManager
+class DependencyManagerOld
 {
     protected $comparator;
 
@@ -53,20 +57,60 @@ public function getAllModules($module): array
         return $modules;
     }
 
-    public function canBeInstalled(Module $module): void
+    /**
+     * @param Combination $combination
+     *
+     * @return Module[]
+     */
+    public function getAllModulesFromCombination(Combination $combination): array
     {
-        $modules = $this->getAllModules($module);
-        $modules[] = $module;
-        foreach ($modules as $module) {
-            $this->canBeInstalledTestRequiers($module, $modules);
-            $this->canBeInstalledTestSelected($module, $modules);
-            $this->canBeInstalledTestInstalled($module);
-            $this->canBeInstalledTestChanged($module, $modules);
+        $moduleLoader = ModuleLoader::getModuleLoader();
+        $moduleLoader->resetCache();
+
+        $modules = [];
+        foreach ($combination->strip()->getAll() as $archiveName => $version) {
+            $module = $moduleLoader->loadByArchiveNameAndVersion($archiveName, $version);
+            if (!$module) {
+                throw new DependencyException('Can not find Module ' . $archiveName . ' in version ' . $version);
+            }
+            $modules[] = $module;
+        }
+        return $modules;
+    }
+
+    /**
+     * @param Module $module
+     *
+     * @return combinationSatisfyerResult
+     */
+    public function canBeInstalled(Module $module): CombinationSatisfyerResult
+    {
+        // $modules = $this->getAllModules($module);
+        // $modules[] = $module;
+        // foreach ($modules as $subModule) {
+        //     // $this->canBeInstalledTestRequiers($module, $modules);
+        //     // $this->canBeInstalledTestSelected($module, $modules);
+        //     // $this->canBeInstalledTestInstalled($module);
+        //     $this->canBeInstalledTestChanged($subModule, $modules);
+        // }
+
+        $systemSetFactory = new SystemSetFactory();
+        $systemSet = $systemSetFactory->getSystemSet();
+
+        $dependencyBuilder = new DependencyBuilder();
+        $combinationSatisfyerResult = $dependencyBuilder->satisfies($module->getArchiveName(), $module->getVersion(), $systemSet);
+        if (!$combinationSatisfyerResult->foundCombination) {
+            throw new DependencyException("Can not install modul");
         }
+
+        $modules = $this->getAllModulesFromCombination($combinationSatisfyerResult->foundCombination);
+        $this->canBeInstalledTestChanged($module, $modules);
+
+        return $combinationSatisfyerResult;
     }
 
     /**
-     * Test ob das Modul in $module installiert werden kann, ob das Modul $module
+     * Testet, ob das Modul in $module installiert werden kann, ob das Modul $module
      * selbst oder eine Abhängigkeit in $modules im Status 'changed' ist.
      *
      * @param Module[] $modules
@@ -155,8 +199,6 @@ private function canBeInstalledTestRequiers(Module $module, array $selectedModul
         }
     }
 
-
-
     /**
      * Liefert eine Liste mit allen Modulen aus $selectedModules, die das Modul
      * $module verwenden.
diff --git a/src/Classes/DependencyManager/FailLog.php b/src/Classes/DependencyManager/FailLog.php
new file mode 100644
index 00000000..79077e81
--- /dev/null
+++ b/src/Classes/DependencyManager/FailLog.php
@@ -0,0 +1,98 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+declare(strict_types=1);
+
+namespace RobinTheHood\ModifiedModuleLoaderClient\DependencyManager;
+
+class FailLog
+{
+    public const TYPE_UNFAIL = 0;
+    public const TYPE_FAIL = 1;
+
+    /** @var array */
+    private $entries = [];
+
+    /**
+     * @param ModuleTree[] $moduleTreeChain
+     */
+    public function unfail(
+        array $moduleTreeChain,
+        string $archiveName,
+        string $version = '',
+        string $constraint = ''
+    ): void {
+        $key = $this->createKey($moduleTreeChain, $archiveName);
+
+        $this->entries[$key] = [
+            'type' => self::TYPE_UNFAIL,
+            'moduleTreeChain' => $moduleTreeChain,
+            'archiveName' => $version,
+            'version' => $version,
+            'constraint' => $constraint
+        ];
+    }
+
+    /**
+     * @param ModuleTree[] $moduleTreeChain
+     */
+    public function fail(
+        array $moduleTreeChain,
+        string $archiveName,
+        string $version = '',
+        string $constraint = ''
+    ): void {
+        $key = $this->createKey($moduleTreeChain, $archiveName);
+
+        if (!array_key_exists($key, $this->entries)) {
+            $this->entries[$key] = [
+                'moduleTreeChain' => $moduleTreeChain,
+                'type' => self::TYPE_FAIL,
+                'archiveName' => $version,
+                'version' => $version,
+                'constraint' => $constraint
+            ];
+        }
+    }
+
+    /**
+     * @param ModuleTree[] $moduleTreeChain
+     */
+    private function createKey(array $moduleTreeChain, string $archiveName): string
+    {
+        $key = '';
+        foreach ($moduleTreeChain as $moduleTree) {
+            if ($key) {
+                $key .= ' > ';
+            }
+            $key .= $moduleTree->archiveName;
+        }
+        return $key . ' > ' . $archiveName;
+    }
+
+    public function __toString(): string
+    {
+        $strings = [];
+        foreach ($this->entries as $key => $entry) {
+            if ($entry['type'] !== self::TYPE_FAIL) {
+                continue;
+            }
+            $constraint = $entry['constraint'];
+            $version = $entry['version'];
+            if ($constraint) {
+                $strings[] = "$key $constraint";
+            } else {
+                $strings[] = "$key $version";
+            }
+        }
+        return implode('
', $strings); + } +} diff --git a/src/Classes/DependencyManager/FlatEntry.php b/src/Classes/DependencyManager/FlatEntry.php new file mode 100644 index 00000000..9aa3aa75 --- /dev/null +++ b/src/Classes/DependencyManager/FlatEntry.php @@ -0,0 +1,34 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace RobinTheHood\ModifiedModuleLoaderClient\DependencyManager; + +class FlatEntry +{ + /** @var string */ + public $archiveName; + + /** @var array version strings */ + public $versions = []; + + public function combine(FlatEntry $flatEntry): void + { + if ($this->archiveName !== $flatEntry->archiveName) { + throw new DependencyException("Cant combine FlatEntry {$this->archiveName} and {$flatEntry->archiveName}"); + } + + $this->versions = array_merge($this->versions, $flatEntry->versions); + $this->versions = array_unique($this->versions); + $this->versions = array_values($this->versions); + } +} diff --git a/src/Classes/DependencyManager/FlatEntryBuilder.php b/src/Classes/DependencyManager/FlatEntryBuilder.php new file mode 100644 index 00000000..5aa31c83 --- /dev/null +++ b/src/Classes/DependencyManager/FlatEntryBuilder.php @@ -0,0 +1,203 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace RobinTheHood\ModifiedModuleLoaderClient\DependencyManager; + +class FlatEntryBuilder +{ + /** + * @param ModuleTree[] $moduleTrees + * + * @return FlatEntry[] + */ + public function buildListFromModuleTrees(array $moduleTrees): array + { + $flatEntries = []; + $this->buildModuleFlatEntriesByModuleTrees($moduleTrees, $flatEntries); + return $flatEntries; + } + + /** + * @param ModuleTree $moduleTree + * + * @return FlatEntry[] + */ + public function buildListFromModuleTree(ModuleTree $moduleTree): array + { + $flatEntries = []; + $this->buildModuleFlatEntriesByModuleTree($moduleTree, $flatEntries); + return $flatEntries; + } + + /** + * @param FlatEntry[] $flatEntries + * @param string $archiveName + * @param FlatEntry $flatEntry + */ + private function addFlatEntry(array &$flatEntries, string $archiveName, FlatEntry $flatEntry): void + { + if (array_key_exists($archiveName, $flatEntries)) { + $flatEntries[$archiveName]->combine($flatEntry); + } else { + $flatEntries[$archiveName] = $flatEntry; + } + } + + /** + * @param ModuleTree[] $moduleTrees + * @param FlatEntry[] $flatEntries + */ + private function buildModuleFlatEntriesByModuleTrees(array $moduleTrees, array &$flatEntries): void + { + if (!$moduleTrees) { + return; + } + + foreach ($moduleTrees as $moduleTree) { + $flatEntry = new FlatEntry(); + $flatEntry->archiveName = $moduleTree->archiveName; + foreach ($moduleTree->moduleVersions as $moduleVersion) { + $flatEntry->versions[] = $moduleVersion->version; + $this->buildModuleFlatEntriesByModuleTrees($moduleVersion->require, $flatEntries); + } + $this->addFlatEntry($flatEntries, $moduleTree->archiveName, $flatEntry); + } + } + + /** + * @param ModuleTree $moduleTree + * @param FlatEntry[] $flatEntries + */ + private function buildModuleFlatEntriesByModuleTree(ModuleTree $moduleTree, array &$flatEntries): void + { + $flatEntry = new FlatEntry(); + $flatEntry->archiveName = $moduleTree->archiveName; + foreach ($moduleTree->moduleVersions as $moduleVersion) { + $flatEntry->versions[] = $moduleVersion->version; + foreach ($moduleVersion->require as $moduleTree) { + $this->buildModuleFlatEntriesByModuleTree($moduleTree, $flatEntries); + } + } + $this->addFlatEntry($flatEntries, $flatEntry->archiveName, $flatEntry); + } + + + + /** + * @param FlatEntry[] $flatEntries + * @param SystemSet $systemSet + * @return FlatEntry[] + */ + public function fitSystemSet(array $flatEntries, SystemSet $systemSet): array + { + $flatEntries = $this->removeFlatEntriesBySystemSet($flatEntries, $systemSet); + $flatEntries = $this->removeFlatEntriesWithNoVersion($flatEntries); + $flatEntries = $this->addFlatEntriesBySystemSet($flatEntries, $systemSet); + return $flatEntries; + } + + /** + * @param FlatEntry[] $flatEntries + * @param SystemSet $systemSet + * @return FlatEntry[] + */ + public function addFlatEntriesBySystemSet(array $flatEntries, SystemSet $systemSet): array + { + foreach ($systemSet->getAll() as $archiveName => $version) { + if ($this->findFatEntryByArchiveName($archiveName, $flatEntries)) { + continue; + } + $flatEntry = new FlatEntry(); + $flatEntry->archiveName = $archiveName; + $flatEntry->versions = [$version]; + $flatEntries[] = $flatEntry; + } + return $flatEntries; + } + + /** + * @param string $archiveName + * @param FlatEntry[] $flatEntries + * + * @return ?FlatEntry + */ + private function findFatEntryByArchiveName(string $archiveName, array $flatEntries): ?FlatEntry + { + foreach ($flatEntries as $flatEntry) { + if ($flatEntry->archiveName === $archiveName) { + return $flatEntry; + } + } + return null; + } + + /** + * @param FlatEntry[] $flatEntries + * + * @return FlatEntry[] + */ + public function removeFlatEntriesWithNoVersion(array $flatEntries): array + { + $filteredFlatEntires = []; + foreach ($flatEntries as $flatEntry) { + if (!$flatEntry->versions) { + continue; + } + $filteredFlatEntires[] = $flatEntry; + } + return $filteredFlatEntires; + } + + /** + * @param FlatEntry[] $moduleFlatTreeEntries + * @param SystemSet $systemSet + * @return FlatEntry[] + */ + public function removeFlatEntriesBySystemSet(array $moduleFlatTreeEntries, SystemSet $systemSet): array + { + foreach ($systemSet->getAll() as $archiveName => $version) { + $versions = [$version]; + $moduleFlatTreeEntries = $this->removeModuleFlatEnty($moduleFlatTreeEntries, $archiveName, $versions); + } + return $moduleFlatTreeEntries; + } + + /** + * @param FlatEntry[] $moduleFlatTreeEntries + * @param string $archiveName + * @param string[] $versions + * + * @return FlatEntry[] + */ + private function removeModuleFlatEnty(array $moduleFlatTreeEntries, string $archiveName, array $versions): array + { + $filteredModuleFlatTreeEntries = []; + foreach ($moduleFlatTreeEntries as $moduleFlatTreeEntry) { + if ($moduleFlatTreeEntry->archiveName !== $archiveName) { + $filteredModuleFlatTreeEntries[$moduleFlatTreeEntry->archiveName] = $moduleFlatTreeEntry; + continue; + } + + $fileredVersions = []; + foreach ($moduleFlatTreeEntry->versions as $versionStr) { + if (!in_array($versionStr, $versions)) { + continue; + } + $fileredVersions[] = $versionStr; + } + $newModuleFlatTreeEntry = new FlatEntry(); + $newModuleFlatTreeEntry->archiveName = $moduleFlatTreeEntry->archiveName; + $newModuleFlatTreeEntry->versions = $fileredVersions; + $filteredModuleFlatTreeEntries[$moduleFlatTreeEntry->archiveName] = $newModuleFlatTreeEntry; + } + return $filteredModuleFlatTreeEntries; + } +} diff --git a/src/Classes/DependencyManager/ModuleTree.php b/src/Classes/DependencyManager/ModuleTree.php new file mode 100644 index 00000000..03950c3c --- /dev/null +++ b/src/Classes/DependencyManager/ModuleTree.php @@ -0,0 +1,26 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace RobinTheHood\ModifiedModuleLoaderClient\DependencyManager; + +class ModuleTree +{ + /** @var string */ + public $archiveName = ''; + + /** @var string version constraint string */ + public $versionConstraint = ''; + + /** @var ModuleVersion[] */ + public $moduleVersions = []; +} diff --git a/src/Classes/DependencyManager/ModuleTreeBuilder.php b/src/Classes/DependencyManager/ModuleTreeBuilder.php new file mode 100644 index 00000000..d13a771c --- /dev/null +++ b/src/Classes/DependencyManager/ModuleTreeBuilder.php @@ -0,0 +1,191 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace RobinTheHood\ModifiedModuleLoaderClient\DependencyManager; + +use RobinTheHood\ModifiedModuleLoaderClient\Loader\ModuleLoader; +use RobinTheHood\ModifiedModuleLoaderClient\Module; +use RobinTheHood\ModifiedModuleLoaderClient\ModuleFilter; +use RobinTheHood\ModifiedModuleLoaderClient\ModuleSorter; + +class ModuleTreeBuilder +{ + /** @var array */ + private $moduleCache = []; + + /** + * @param string $archiveName + * @param string $versionConstraint + * @return Module[] + */ + private function loadAllByArchiveNameAndConstraint(string $archiveName, string $versionConstraint): array + { + $modules = $this->moduleCache[$archiveName] ?? []; + if (!$modules) { + $moduleLoader = ModuleLoader::getModuleLoader(); + $modules = $moduleLoader->loadAllVersionsByArchiveName($archiveName); + $modules = ModuleSorter::sortByVersion($modules); + $this->moduleCache[$archiveName] = $modules; + } + + return ModuleFilter::filterByVersionConstrain($modules, $versionConstraint); + } + + /** + * @param Module $Module + * @param int $depth + * @return ModuleTree[] + */ + public function buildListByConstraints(Module $module, int $depth = 0): array + { + if ($depth >= 10) { + return []; + } + + $require = $module->getRequire(); + + $moduleTrees = []; + + $this->addExtraDependencies($module, $moduleTrees); + + foreach ($require as $archiveName => $versionConstraint) { + // Modules to Entry + $moduleTree = new ModuleTree(); + $moduleTree->archiveName = $archiveName; + $moduleTree->versionConstraint = $versionConstraint; + + // Fetch Versions + $modules = $this->loadAllByArchiveNameAndConstraint($archiveName, $versionConstraint); + + // VersionList + foreach ($modules as $module) { + $moduleVersion = new ModuleVersion(); + $moduleVersion->version = $module->getVersion(); + $moduleVersion->require = $this->buildListByConstraints($module, $depth + 1); + $moduleTree->moduleVersions[$moduleVersion->version] = $moduleVersion; + } + + $moduleTrees[] = $moduleTree; + } + + return $moduleTrees; + } + + /** + * @param string $archiveName + * @param string $versionConstraint + * @param int $depth + */ + public function buildByConstraints(string $archiveName, string $versionConstraint, int $depth = 0): ModuleTree + { + $moduleTree = new ModuleTree(); + $moduleTree->archiveName = $archiveName; + $moduleTree->versionConstraint = $versionConstraint; + + $modules = $this->loadAllByArchiveNameAndConstraint($archiveName, $versionConstraint); + + $moduleVersions = []; + foreach ($modules as $module) { + // Context: Module + $moduleVersion = new ModuleVersion(); + $moduleVersion->version = $module->getVersion(); + + if ($depth < 10) { + $this->addExtraDependencies($module, $moduleVersion->require); + + $require = $module->getRequire(); + foreach ($require as $archiveName => $versionConstraint) { + // Context: require + $moduleVersion->require[] = $this->buildByConstraints($archiveName, $versionConstraint, $depth + 1); + } + } + $moduleVersions[] = $moduleVersion; + } + $moduleTree->moduleVersions = $moduleVersions; + + return $moduleTree; + } + + /** + * @param Module $module + * @param ModuleTree[] $moduleTrees + */ + private function addExtraDependencies(Module $module, array &$moduleTrees): void + { + $this->addTreeModified($module, $moduleTrees); + $this->addTreePhp($module, $moduleTrees); + $this->addTreeMmlc($module, $moduleTrees); + } + + /** + * @param Module $module + * @param ModuleTree[] $moduleTrees + */ + private function addTreeModified(Module $module, array &$moduleTrees): void + { + if (!$module->getModifiedCompatibility()) { + return; + } + + $moduleVersions = []; + foreach ($module->getModifiedCompatibility() as $modifiedVersion) { + $moduleVersion = new ModuleVersion(); + $moduleVersion->version = $modifiedVersion; + $moduleVersion->require = []; + $moduleVersions[] = $moduleVersion; + } + + $moduleTree = new ModuleTree(); + $moduleTree->archiveName = 'modified'; + $moduleTree->versionConstraint = ''; + $moduleTree->moduleVersions = array_reverse($moduleVersions); + + $moduleTrees[] = $moduleTree; + } + + /** + * @param Module $module + * @param ModuleTree[] $moduleTrees + */ + private function addTreePhp(Module $module, array &$moduleTrees): void + { + if (!$module->getPhp()) { + return; + } + + $moduleTree = new ModuleTree(); + $moduleTree->archiveName = 'php'; + $moduleTree->versionConstraint = $module->getPhp()['version'] ?? '^7.4.0 || ^8.0.0'; + $moduleTree->moduleVersions = []; + + $moduleTrees[] = $moduleTree; + } + + /** + * @param Module $module + * @param ModuleTree[] $moduleTrees + */ + private function addTreeMmlc(Module $module, array &$moduleTrees): void + { + if (!$module->getMmlc()) { + return; + } + + $moduleTree = new ModuleTree(); + $moduleTree->archiveName = 'mmlc'; + $moduleTree->versionConstraint = $module->getMmlc()['version'] ?? '^1.19.0'; + $moduleTree->moduleVersions = []; + + $moduleTrees[] = $moduleTree; + } +} diff --git a/src/Classes/DependencyManager/ModuleVersion.php b/src/Classes/DependencyManager/ModuleVersion.php new file mode 100644 index 00000000..619bdfcb --- /dev/null +++ b/src/Classes/DependencyManager/ModuleVersion.php @@ -0,0 +1,23 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace RobinTheHood\ModifiedModuleLoaderClient\DependencyManager; + +class ModuleVersion +{ + /** @var string */ + public $version = ''; + + /** @var ModuleTree[] **/ + public $require = []; +} diff --git a/src/Classes/DependencyManager/SystemSet.php b/src/Classes/DependencyManager/SystemSet.php new file mode 100644 index 00000000..b41941c6 --- /dev/null +++ b/src/Classes/DependencyManager/SystemSet.php @@ -0,0 +1,85 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace RobinTheHood\ModifiedModuleLoaderClient\DependencyManager; + +class SystemSet +{ + /** + * SystemNames: mmlc, modified, php + * + * An array entry looks like + * array + * or + * array + * + * @var array + **/ + private $systems = []; + + /** + * @param array $sytems + */ + public function set(array $sytems): void + { + $this->systems = $sytems; + } + + public function add(string $name, string $versionString): void + { + $this->systems[$name] = $versionString; + } + + /** + * @return string[] + */ + public function getArchiveNames(): array + { + $archiveNames = array_values(array_filter(array_keys($this->systems), function ($name) { + return strpos($name, '/') !== false; + })); + return $archiveNames; + } + + /** + * @return array + */ + public function getAll(): array + { + return $this->systems; + } + + /** + * @return array + */ + public function getArchives(): array + { + $archives = []; + foreach ($this->systems as $name => $version) { + if (!strpos($name, '/')) { + continue; + } + $archives[$name] = $version; + } + return $archives; + } + + public function remove(string $name): void + { + if (!array_key_exists($name, $this->systems)) { + return; + } + + unset($this->systems[$name]); + } +} diff --git a/src/Classes/DependencyManager/SystemSetFactory.php b/src/Classes/DependencyManager/SystemSetFactory.php new file mode 100644 index 00000000..14e7b34f --- /dev/null +++ b/src/Classes/DependencyManager/SystemSetFactory.php @@ -0,0 +1,36 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace RobinTheHood\ModifiedModuleLoaderClient\DependencyManager; + +use RobinTheHood\ModifiedModuleLoaderClient\App; +use RobinTheHood\ModifiedModuleLoaderClient\Loader\LocalModuleLoader; +use RobinTheHood\ModifiedModuleLoaderClient\ShopInfo as ModifiedModuleLoaderClientShopInfo; + +class SystemSetFactory +{ + public function getSystemSet(): SystemSet + { + $systemSet = new SystemSet(); + $systemSet->add('modified', ModifiedModuleLoaderClientShopInfo::getModifiedVersion()); + $systemSet->add('php', phpversion()); + $systemSet->add('mmlc', App::getMmlcVersion()); + + $moduleLoader = LocalModuleLoader::getModuleLoader(); + $modules = $moduleLoader->loadAllInstalledVersions(); + foreach ($modules as $module) { + $systemSet->add($module->getArchiveName(), $module->getVersion()); + } + return $systemSet; + } +} diff --git a/src/Classes/IndexController.php b/src/Classes/IndexController.php index 0721258a..4c99f36e 100644 --- a/src/Classes/IndexController.php +++ b/src/Classes/IndexController.php @@ -21,6 +21,8 @@ use RobinTheHood\ModifiedModuleLoaderClient\Category; use RobinTheHood\ModifiedModuleLoaderClient\SendMail; use RobinTheHood\ModifiedModuleLoaderClient\Config; +use RobinTheHood\ModifiedModuleLoaderClient\DependencyManager\DependencyException; +use RuntimeException; class IndexController extends Controller { @@ -317,14 +319,17 @@ public function invokeInstall() try { $moduleInstaller = new ModuleInstaller(); - //$moduleInstaller->install($module); - //$moduleInstaller->installDependencies($module); $moduleInstaller->installWithDependencies($module); } catch (DependencyException $e) { Notification::pushFlashMessage([ 'text' => $e->getMessage(), 'type' => 'error' ]); + } catch (RuntimeException $e) { + Notification::pushFlashMessage([ + 'text' => $e->getMessage(), + 'type' => 'error' + ]); } return $this->redirectRef($archiveName, $module->getVersion()); @@ -356,7 +361,7 @@ private function invokeRevertChanges() 'text' => $e->getMessage(), 'type' => 'error' ]); - } catch (\RuntimeException $e) { + } catch (RuntimeException $e) { Notification::pushFlashMessage([ 'text' => $e->getMessage(), 'type' => 'error' @@ -392,6 +397,11 @@ public function invokeUninstall() 'text' => $e->getMessage(), 'type' => 'error' ]); + } catch (RuntimeException $e) { + Notification::pushFlashMessage([ + 'text' => $e->getMessage(), + 'type' => 'error' + ]); } return $this->redirectRef($archiveName, $module->getVersion()); @@ -425,6 +435,11 @@ public function invokeUpdate() 'text' => $e->getMessage(), 'type' => 'error' ]); + } catch (RuntimeException $e) { + Notification::pushFlashMessage([ + 'text' => $e->getMessage(), + 'type' => 'error' + ]); } if (!$newModule) { @@ -502,13 +517,17 @@ public function invokeLoadAndInstall() try { $moduleInstaller = new ModuleInstaller(); - $moduleInstaller->install($module); - $moduleInstaller->installDependencies($module); + $moduleInstaller->installWithDependencies($module); } catch (DependencyException $e) { Notification::pushFlashMessage([ 'text' => $e->getMessage(), 'type' => 'error' ]); + } catch (RuntimeException $e) { + Notification::pushFlashMessage([ + 'text' => $e->getMessage(), + 'type' => 'error' + ]); } return $this->redirectRef($archiveName, $module->getVersion()); @@ -540,6 +559,11 @@ public function invokeUnloadLocalModule() 'text' => $e->getMessage(), 'type' => 'error' ]); + } catch (RuntimeException $e) { + Notification::pushFlashMessage([ + 'text' => $e->getMessage(), + 'type' => 'error' + ]); } return $this->redirect('/'); diff --git a/src/Classes/Loader/ModuleLoader.php b/src/Classes/Loader/ModuleLoader.php index 35e5a802..27ff4814 100644 --- a/src/Classes/Loader/ModuleLoader.php +++ b/src/Classes/Loader/ModuleLoader.php @@ -29,6 +29,23 @@ public static function getModuleLoader(): ModuleLoader return self::$moduleLoader; } + /** + * Resets / deletes allready loaded modules data. For examplae because + * during the script runtime the amount of modules or data of modules + * changed and the LocalModuleLoader does not give the latest module + * informations. + */ + public function resetCache() + { + $this->cachedModules = null; + + $localModuleLoader = LocalModuleLoader::getModuleLoader(); + $localModuleLoader->resetCache(); + + $remoteModuleLoader = RemoteModuleLoader::getModuleLoader(); + $remoteModuleLoader->resetCache(); + } + /** * Loads all local module version plus all latest remote module version. * diff --git a/src/Classes/Loader/RemoteModuleLoader.php b/src/Classes/Loader/RemoteModuleLoader.php index 60f4e132..d3e57e44 100644 --- a/src/Classes/Loader/RemoteModuleLoader.php +++ b/src/Classes/Loader/RemoteModuleLoader.php @@ -45,6 +45,17 @@ public static function getModuleLoader(): RemoteModuleLoader return self::$moduleLoader; } + /** + * Resets / deletes allready loaded modules data. For examplae because + * during the script runtime the amount of modules or data of modules + * changed and the LocalModuleLoader does not give the latest module + * informations. + */ + public function resetCache() + { + $this->cachedModules = null; + } + /** * Loads all remote module versions. * diff --git a/src/Classes/Module.php b/src/Classes/Module.php index 5b803f94..bf9a9a02 100644 --- a/src/Classes/Module.php +++ b/src/Classes/Module.php @@ -14,11 +14,11 @@ namespace RobinTheHood\ModifiedModuleLoaderClient; use RobinTheHood\ModifiedModuleLoaderClient\App; +use RobinTheHood\ModifiedModuleLoaderClient\DependencyManager\DependencyManager; use RobinTheHood\ModifiedModuleLoaderClient\ShopInfo; use RobinTheHood\ModifiedModuleLoaderClient\FileInfo; use RobinTheHood\ModifiedModuleLoaderClient\ModuleFilter; use RobinTheHood\ModifiedModuleLoaderClient\ModuleInfo; -use RobinTheHood\ModifiedModuleLoaderClient\DependencyManager; use RobinTheHood\ModifiedModuleLoaderClient\FileHasher\ChangedEntryCollection; use RobinTheHood\ModifiedModuleLoaderClient\Loader\ModuleLoader; use RobinTheHood\ModifiedModuleLoaderClient\Loader\LocalModuleLoader; @@ -516,12 +516,9 @@ public function isCompatibleWithPhp(): bool $phpVersionInstalled = phpversion(); $comparator = new Comparator(new Parser()); - return $comparator->satisfiesOr($phpVersionInstalled, $phpVersionContraint); + return $comparator->satisfies($phpVersionInstalled, $phpVersionContraint); } - /** - * Version 1.20.0 is the last version without isCompatibleWithMmlc() - */ public function isCompatibleWithMmlc(): bool { $mmlcVersionInstalled = App::getMmlcVersion(); @@ -530,13 +527,13 @@ public function isCompatibleWithMmlc(): bool } $mmlc = $this->getMmlc(); - $mmlcVersionContraint = $mmlc['version'] ?? '^1.20.0'; + $mmlcVersionContraint = $mmlc['version'] ?? ''; if (!$mmlcVersionContraint) { return true; } $comparator = new Comparator(new Parser()); - return $comparator->satisfiesOr($mmlcVersionInstalled, $mmlcVersionContraint); + return $comparator->satisfies($mmlcVersionInstalled, $mmlcVersionContraint); } public function getTemplateFiles($file): array diff --git a/src/Classes/ModuleFactory.php b/src/Classes/ModuleFactory.php index f8ad1b29..20691b0a 100644 --- a/src/Classes/ModuleFactory.php +++ b/src/Classes/ModuleFactory.php @@ -94,7 +94,7 @@ public static function createFromArray(array $array): Module $module->setAutoload($autoload); $module->setTags($array['tags'] ?? ''); $module->setPhp($array['php'] ?? []); - $module->setMmlc($array['mmlc'] ?? []); + $module->setMmlc($array['mmlc'] ?? ['version' => App::getMmlcVersion()]); // Module $module->setLocalRootPath($array['localRootPath'] ?? ''); diff --git a/src/Classes/ModuleInstaller.php b/src/Classes/ModuleInstaller.php index d8724dd6..c4e527fd 100644 --- a/src/Classes/ModuleInstaller.php +++ b/src/Classes/ModuleInstaller.php @@ -17,11 +17,13 @@ use RobinTheHood\ModifiedModuleLoaderClient\Archive; use RobinTheHood\ModifiedModuleLoaderClient\Config; use RobinTheHood\ModifiedModuleLoaderClient\FileInfo; -use RobinTheHood\ModifiedModuleLoaderClient\DependencyManager; use RobinTheHood\ModifiedModuleLoaderClient\Api\V1\ApiRequest; +use RobinTheHood\ModifiedModuleLoaderClient\DependencyManager\Combination; +use RobinTheHood\ModifiedModuleLoaderClient\DependencyManager\DependencyManager; use RobinTheHood\ModifiedModuleLoaderClient\Loader\LocalModuleLoader; use RobinTheHood\ModifiedModuleLoaderClient\Helpers\FileHelper; use RobinTheHood\ModifiedModuleLoaderClient\ModuleHasher\ModuleHashFileCreator; +use RuntimeException; class ModuleInstaller { @@ -49,7 +51,7 @@ public function pull(Module $module): bool $archive = Archive::pullArchive($archiveUrl, $module->getArchiveName(), $module->getVersion()); $archive->untarArchive(); return true; - } catch (\RuntimeException $e) { + } catch (RuntimeException $e) { //Can not pull Archive return false; } @@ -81,13 +83,37 @@ public function delete(Module $module) } } - public function install(Module $module, $force = false): void + public function install(Module $module, bool $force = false): void { if (!$force) { $dependencyManager = new DependencyManager(); $dependencyManager->canBeInstalled($module); } + $this->internalInstall($module); + $this->createAutoloadFile(); + } + + public function installWithDependencies(Module $module): void + { + $dependencyManager = new DependencyManager(); + $combinationSatisfyerResult = $dependencyManager->canBeInstalled($module, ['']); + + if (!$combinationSatisfyerResult->foundCombination) { + throw new RuntimeException( + "Can not install module {$module->getArchiveName()} {$module->getVersion()} with dependencies. " + . "No possible combination of versions found" + ); + } + + $this->uninstall($module); + $this->internalInstall($module); + $this->internalInstallDependencies($module, $combinationSatisfyerResult->foundCombination); + $this->createAutoloadFile(); + } + + private function internalInstall(Module $module): void + { // Install Source Files to Shop Root $files = $module->getSrcFilePaths(); @@ -120,51 +146,129 @@ public function install(Module $module, $force = false): void $moduleHashFileCreator = new ModuleHashFileCreator(); $moduleHashFile = $moduleHashFileCreator->createHashFile($module); $moduleHashFile->writeTo($module->getHashPath()); - - $this->createAutoloadFile(); } - public function revertChanges(Module $module): void + private function internalPullAndInstall(Module $module): void { - if (!$module->isInstalled()) { - throw new \RuntimeException('Can not revert changes because ' . $module->getArchiveName() . ' is not installed.'); + if (!$module->isLoaded()) { + $this->pull($module); + } + + $reloaded = $this->reload($module); + + if (!$reloaded->isLoaded()) { + throw new RuntimeException( + "Can not pull and install module {$module->getArchiveName()} {$module->getVersion()}. " + . "Module is not loaded." + ); + } + + if ($reloaded->isInstalled()) { + return; } - $this->install($module, true); + $this->uninstall($module); + $this->internalInstall($module); } - public function installDependencies(Module $module): void + private function internalInstallDependencies(Module $parentModule, Combination $combination): void { $dependencyManager = new DependencyManager(); - $modules = $dependencyManager->getAllModules($module); + $modules = $dependencyManager->getAllModulesFromCombination($combination); - foreach ($modules as $depModule) { - if (!$depModule->isLoaded()) { - $this->pull($depModule); + foreach ($modules as $module) { + if ($parentModule->getArchiveName() === $module->getArchiveName()) { + continue; } + $this->internalPullAndInstall($module); } + } - $modules = $dependencyManager->getAllModules($module); - foreach ($modules as $depModule) { - $this->uninstall($depModule); - if ($depModule->isLoaded() && !$depModule->isInstalled()) { - $this->install($depModule); - } + public function update(Module $module): ?Module + { + $installedModule = $module->getInstalledVersion(); + $newModule = $module->getNewestVersion(); + + $dependencyManager = new DependencyManager(); + $combinationSatisfyerResult = $dependencyManager->canBeInstalled($module); + + if (!$combinationSatisfyerResult->foundCombination) { + throw new RuntimeException( + "Can not update module {$module->getArchiveName()} {$module->getVersion()}. " + . "No possible combination of versions found" + ); } + if ($installedModule) { + $this->uninstall($installedModule); + } + + $this->pull($newModule); + $newModule = $this->reload($newModule); + + $this->internalInstall($newModule); $this->createAutoloadFile(); + + return $newModule; } - public function installWithDependencies(Module $module): void + public function updateWithDependencies(Module $module): ?Module { + $installedModule = $module->getInstalledVersion(); + $newModule = $module->getNewestVersion(); + $dependencyManager = new DependencyManager(); - $dependencyManager->canBeInstalled($module); + $combinationSatisfyerResult = $dependencyManager->canBeInstalled($module); + + if (!$combinationSatisfyerResult->foundCombination) { + throw new RuntimeException( + "Can not update module {$module->getArchiveName()} {$module->getVersion()} with dependencies. " + . "No possible combination of versions found" + ); + } + + if ($installedModule) { + $this->uninstall($installedModule); + } + + $this->pull($newModule); + $newModule = $this->reload($newModule); + + $this->internalInstall($newModule); + $this->internalInstallDependencies($newModule, $combinationSatisfyerResult->foundCombination); + $this->createAutoloadFile(); - $this->install($module); - $this->installDependencies($module); + return $newModule; + } + + private function reload(Module $module): Module + { + $moduleLoader = new LocalModuleLoader(); + $moduleLoader->resetCache(); + $reloadedModule = $moduleLoader->loadByArchiveNameAndVersion( + $module->getArchiveName(), + $module->getVersion() + ); + + if (!$reloadedModule) { + throw new RuntimeException("Can not reload module {$module->getArchiveName()} {$module->getVersion()}"); + } + + return $reloadedModule; + } + + public function revertChanges(Module $module): void + { + if (!$module->isInstalled()) { + throw new RuntimeException( + "Can not revert changes because {$module->getArchiveName()} {$module->getVersion()} is not installed." + ); + } + + $this->internalInstall($module); } - public function createAutoloadFile(): void + private function createAutoloadFile(): void { $localModuleLoader = LocalModuleLoader::getModuleLoader(); $localModuleLoader->resetCache(); @@ -207,18 +311,28 @@ public function createAutoloadFile(): void \file_put_contents(App::getShopRoot() . '/vendor-mmlc/autoload.php', $template); } - //TODO: Better return void type an thorw exception at error - public function uninstall(?Module $module): bool + public function uninstall(Module $module): bool { - if (!$module) { + $installedModule = $module->getInstalledVersion(); + if (!$installedModule) { return false; } - $module = $module->getInstalledVersion(); - if (!$module) { - return false; + if ($installedModule->isChanged()) { + throw new RuntimeException( + "Can not uninstall module {$installedModule->getArchiveName()} {$installedModule->getVersion()} " + . "because the module has changes." + ); } + $this->internalUninstall($installedModule); + $this->createAutoloadFile(); + + return true; + } + + private function internalUninstall(Module $module): void + { // Uninstall from shop-root $files = $module->getSrcFilePaths(); foreach ($files as $file) { @@ -239,49 +353,9 @@ public function uninstall(?Module $module): bool if (file_exists($module->getHashPath())) { unlink($module->getHashPath()); } - - $this->createAutoloadFile(); - - return true; - } - - public function update(Module $module): ?Module - { - $oldModule = $module->getInstalledVersion(); - $newModule = $module->getNewestVersion(); - - $dependencyManager = new DependencyManager(); - $dependencyManager->canBeInstalled($newModule); - - $this->uninstall($oldModule); - $this->pull($newModule); - - // Da nach $newModule->pull() das Modul noch nicht lokal inistailisiert - // sein kann, wird es noch einmal geladen. - $moduleLoader = new LocalModuleLoader(); - $newModule = $moduleLoader->loadByArchiveNameAndVersion($newModule->getArchiveName(), $newModule->getVersion()); - - if (!$newModule) { - return null; //TODO: Better return not nullable type Module and thorw an exception - } - - $this->install($newModule); - - return $newModule; - } - - public function updateWithDependencies(Module $module): ?Module - { - $newModule = $this->update($module); - if (!$newModule) { - return null; //TODO: Better return not nullable type Module and thorw an exception - } - - $this->installDependencies($newModule); - return $newModule; } - public function installFile(string $src, string $dest, bool $overwrite = false): bool + private function installFile(string $src, string $dest, bool $overwrite = false): bool { if (!file_exists($src)) { return false; @@ -308,7 +382,7 @@ public function installFile(string $src, string $dest, bool $overwrite = false): return true; } - public function uninstallFile(string $dest): void + private function uninstallFile(string $dest): void { if (file_exists($dest)) { unlink($dest); diff --git a/src/Classes/Semver/Comparator.php b/src/Classes/Semver/Comparator.php index 86b1598e..4d4d5920 100644 --- a/src/Classes/Semver/Comparator.php +++ b/src/Classes/Semver/Comparator.php @@ -163,22 +163,39 @@ public function isCompatible(string $versionString1, string $versionString2): bo return $this->greaterThanOrEqualTo($versionString1, $versionString2); } - public function satisfies(string $versionString1, string $constrain): bool + public function satisfies(string $versionString1, string $constraint): bool { - if ($constrain[0] == '^') { // Ist Buchstabe an Index 0 = ^ - $versionString2 = str_replace('^', '', $constrain); - return $this->isCompatible($versionString1, $versionString2); - } elseif ($constrain[0] == '<' && $constrain[1] == '=') { - $versionString2 = str_replace('<=', '', $constrain); + if (strpos($constraint, '||')) { + return $this->satisfiesOr($versionString1, $constraint); + } + + if (strpos($constraint, ',')) { + return $this->satisfiesAnd($versionString1, $constraint); + } + + if (strpos($constraint, '<=') === 0) { + $versionString2 = str_replace('<=', '', $constraint); return $this->lessThanOrEqualTo($versionString1, $versionString2); + } elseif (strpos($constraint, '<') === 0) { + $versionString2 = str_replace('<', '', $constraint); + return $this->lessThan($versionString1, $versionString2); + } elseif (strpos($constraint, '>=') === 0) { + $versionString2 = str_replace('>=', '', $constraint); + return $this->greaterThanOrEqualTo($versionString1, $versionString2); + } elseif (strpos($constraint, '>') === 0) { + $versionString2 = str_replace('>', '', $constraint); + return $this->greaterThan($versionString1, $versionString2); + } elseif (strpos($constraint, '^') === 0) { + $versionString2 = str_replace('^', '', $constraint); + return $this->isCompatible($versionString1, $versionString2); } else { - $versionString2 = $constrain; + $versionString2 = $constraint; return $this->equalTo($versionString1, $versionString2); } } /** - * Can satisfy multiple constraints with OR / || + * Can satisfy multiple constraints with OR (||) * * Example: ^7.4 || ^8.0 */ @@ -193,4 +210,21 @@ public function satisfiesOr(string $versionString1, string $constraintOrExpressi } return false; } + + /** + * Can satisfy multiple constraints with AND (,) + * + * Example: ^7.4, ^8.0 + */ + public function satisfiesAnd(string $versionString1, string $constraintOrExpression): bool + { + $constraints = explode(',', $constraintOrExpression); + foreach ($constraints as $constraint) { + $constraint = trim($constraint); + if (!$this->satisfies($versionString1, $constraint)) { + return false; + } + } + return true; + } } diff --git a/src/Classes/Semver/Constraint.php b/src/Classes/Semver/Constraint.php new file mode 100644 index 00000000..36285ff2 --- /dev/null +++ b/src/Classes/Semver/Constraint.php @@ -0,0 +1,57 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace RobinTheHood\ModifiedModuleLoaderClient\Semver; + +class Constraint +{ + public static function createConstraintFromConstraints(array $constraints): string + { + $constraint = ''; + foreach ($constraints as $version) { + if ($constraint === '') { + $constraint = $version; + } else { + $constraint .= ', ' . $version; + } + } + return $constraint; + } + + public static function resolveCaretRange(string $range): string + { + if (preg_match('/^\^(?\d+)(\.(?\d+))?(\.(?\d+))?(?.*)$/', $range, $matches)) { + $major = intval($matches['major']); + $minor = isset($matches['minor']) ? intval($matches['minor']) : 0; + $patch = isset($matches['patch']) ? intval($matches['patch']) : 0; + $suffix = $matches['suffix']; + + $lower = sprintf("%d.%d.%d%s", $major, $minor, $patch, $suffix); + + if ($major == 0) { + $upper = sprintf("%d.%d.%d", $major, $minor + 1, 0); + } else { + $upper = sprintf("%d.%d.%s", $major + 1, 0, '0'); + } + + return ">=$lower,<$upper"; + } + + return $range; + } + + public static function resolve(string $constaint): string + { + return self::resolveCaretRange($constaint); + } +} diff --git a/src/Classes/Semver/Filter.php b/src/Classes/Semver/Filter.php index d89b9abe..3ddbeea9 100644 --- a/src/Classes/Semver/Filter.php +++ b/src/Classes/Semver/Filter.php @@ -52,7 +52,7 @@ public function byConstraint(string $constraint, array $versions): array { $fileredVersions = []; foreach ($versions as $version) { - if ($this->comparator->satisfiesOr($version, $constraint)) { + if ($this->comparator->satisfies($version, $constraint)) { $fileredVersions[] = $version; } } diff --git a/src/Classes/ViewModels/ModuleViewModel.php b/src/Classes/ViewModels/ModuleViewModel.php index 033d9968..55ff2cdd 100644 --- a/src/Classes/ViewModels/ModuleViewModel.php +++ b/src/Classes/ViewModels/ModuleViewModel.php @@ -57,6 +57,12 @@ public function getModuleInfoUrl(string $ref = ''): string return $this->getUrl('moduleInfo', $ref); } + public function getInstalledUrl(string $ref = ''): string + { + $module = $this->module->getInstalledVersion(); + return $this->getUrl('moduleInfo', $ref, $module); + } + public function getLoadModuleUrl(string $ref = ''): string { return $this->getUrl('loadRemoteModule', $ref); @@ -181,12 +187,16 @@ public function isChanged(): bool return $this->module->isChanged(); } - private function getUrl(string $action, string $ref): string + private function getUrl(string $action, string $ref, ?Module $module = null): string { + if (!$module) { + $module = $this->module; + } + return '?action=' . $action . - '&archiveName=' . $this->module->getArchiveName() . - '&version=' . $this->module->getVersion() . + '&archiveName=' . $module->getArchiveName() . + '&version=' . $module->getVersion() . '&ref=' . $ref; } diff --git a/src/Classes/ViewModels/NotificationViewModel.php b/src/Classes/ViewModels/NotificationViewModel.php index 6e8bc623..6717490d 100644 --- a/src/Classes/ViewModels/NotificationViewModel.php +++ b/src/Classes/ViewModels/NotificationViewModel.php @@ -32,7 +32,7 @@ public function renderFlashMessage(string $message, string $type): string if ($type == 'warning') { return ''; } elseif ($type == 'error') { - return ''; + return ''; } elseif ($type == 'success') { return ''; } diff --git a/src/Templates/ModuleInfo.tmpl.php b/src/Templates/ModuleInfo.tmpl.php index 7b653cf7..80bceb3c 100644 --- a/src/Templates/ModuleInfo.tmpl.php +++ b/src/Templates/ModuleInfo.tmpl.php @@ -39,6 +39,34 @@
renderFlashMessages() ?> + isRepairable()) { ?> + + + + isLoadable()) { ?> + + + + isCompatible()) { ?> + getCompatibleStrings() as $string) { ?> + + + +
@@ -58,34 +86,6 @@
- - isRepairable()) { ?> - - - - isLoadable()) { ?> - - - - isCompatible()) { ?> - getCompatibleStrings() as $string) { ?> - - -
@@ -131,7 +131,7 @@ Installieren (inkompatible Version) hasInstalledVersion()) { ?> - Zur installierten Version + Zur installierten Version isRemote() && $moduleView->isLoaded() && !$moduleView->isInstalled()) { ?> @@ -289,7 +289,7 @@ Kompatibel mit MMLC getMmlc()) { ?> - getMmlc()['version'] ?? '^1.20.0') as $version) { ?> + getMmlc()['version'] ?? '') as $version) { ?> diff --git a/tests/unit/DependencyManager/CombinationTest.php b/tests/unit/DependencyManager/CombinationTest.php new file mode 100644 index 00000000..f1ba2fa3 --- /dev/null +++ b/tests/unit/DependencyManager/CombinationTest.php @@ -0,0 +1,90 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace RobinTheHood\ModifiedModuleLoaderClient\Tests\Unit\DependencyManager; + +use Exception; +use PHPUnit\Framework\TestCase; +use RobinTheHood\ModifiedModuleLoaderClient\DependencyManager\Combination; + +class CombinationTest extends TestCase +{ + public function testAdd() + { + $combination = new Combination(); + $combination->add('foo/bar', '1.0.0'); + + $this->assertEquals(['foo/bar' => '1.0.0'], $combination->getAll()); + } + + public function testCanNotAddTwice() + { + $this->expectException(Exception::class); + + $combination = new Combination(); + $combination->add('foo/bar', '1.0.0'); + $combination->add('foo/bar', '2.0.0'); + } + + public function testOverwrite() + { + $combination = new Combination(); + $combination->add('foo/bar', '1.0.0'); + $combination->overwrite('foo/bar', '2.0.0'); + + $this->assertEquals(['foo/bar' => '2.0.0'], $combination->getAll()); + } + + public function testGetVersion() + { + $combination = new Combination(); + $combination->add('foo', '1.0.0'); + $combination->add('foo/bar', '2.0.0'); + + $this->assertEquals('2.0.0', $combination->getVersion('foo/bar')); + } + + public function testGetVersionThrowsException() + { + $this->expectException(Exception::class); + + $combination = new Combination(); + $combination->add('foo', '1.0.0'); + $combination->add('foo/bar', '2.0.0'); + + $combination->getVersion('bar'); + } + + public function testStrip() + { + $combination = new Combination(); + $combination->add('foo', '1.0.0'); + $combination->add('foo/bar', '2.0.0'); + $stripedCombination = $combination->strip(); + + $this->assertEquals( + [ + 'foo' => '1.0.0', + 'foo/bar' => '2.0.0' + ], + $combination->getAll() + ); + + $this->assertEquals( + [ + 'foo/bar' => '2.0.0' + ], + $stripedCombination->getAll() + ); + } +} diff --git a/tests/unit/DependencyManager/CounterTest.php b/tests/unit/DependencyManager/CounterTest.php new file mode 100644 index 00000000..74daa199 --- /dev/null +++ b/tests/unit/DependencyManager/CounterTest.php @@ -0,0 +1,79 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace RobinTheHood\ModifiedModuleLoaderClient\Tests\Unit\DependencyManager; + +use PHPUnit\Framework\TestCase; +use RobinTheHood\ModifiedModuleLoaderClient\DependencyManager\Counter; + +class CounterTest extends TestCase +{ + public function testCurrent() + { + $counter = new Counter([ + 2, 1, 3 + ]); + + $this->assertEquals([0, 0, 0], $counter->current()); + } + + public function testNext() + { + $counter = new Counter([ + 2, 1, 3 + ]); + + $this->assertEquals([0, 0, 1], $counter->next()); + $this->assertEquals([0, 0, 2], $counter->next()); + $this->assertEquals([0, 0, 3], $counter->next()); + $this->assertEquals([0, 1, 0], $counter->next()); + $this->assertEquals([0, 1, 1], $counter->next()); + $this->assertEquals([0, 1, 2], $counter->next()); + $this->assertEquals([0, 1, 3], $counter->next()); + $this->assertEquals([1, 0, 0], $counter->next()); + $this->assertEquals([1, 0, 1], $counter->next()); + $this->assertEquals([1, 0, 2], $counter->next()); + $this->assertEquals([1, 0, 3], $counter->next()); + $this->assertEquals([1, 1, 0], $counter->next()); + $this->assertEquals([1, 1, 1], $counter->next()); + $this->assertEquals([1, 1, 2], $counter->next()); + $this->assertEquals([1, 1, 3], $counter->next()); + $this->assertEquals([2, 0, 0], $counter->next()); + $this->assertEquals([2, 0, 1], $counter->next()); + $this->assertEquals([2, 0, 2], $counter->next()); + $this->assertEquals([2, 0, 3], $counter->next()); + $this->assertEquals([2, 1, 0], $counter->next()); + $this->assertEquals([2, 1, 1], $counter->next()); + $this->assertEquals([2, 1, 2], $counter->next()); + $this->assertEquals([2, 1, 3], $counter->next()); + $this->assertEquals([0, 0, 0], $counter->next()); + } + + public function testStart() + { + $counter = new Counter([ + 2, 1 + ]); + + $this->assertEquals(true, $counter->isStart()); + $this->assertEquals([0, 0], $counter->current()); + $this->assertEquals([0, 1], $counter->next()); + $this->assertEquals(false, $counter->isStart()); + $this->assertEquals([1, 0], $counter->next()); + $this->assertEquals([1, 1], $counter->next()); + $this->assertEquals([2, 0], $counter->next()); + $this->assertEquals([2, 1], $counter->next()); + $this->assertEquals([0, 0], $counter->next()); + $this->assertEquals(true, $counter->isStart()); + } +} diff --git a/tests/unit/DependencyManager/DependencyBuilderTest.php b/tests/unit/DependencyManager/DependencyBuilderTest.php new file mode 100644 index 00000000..e22896f5 --- /dev/null +++ b/tests/unit/DependencyManager/DependencyBuilderTest.php @@ -0,0 +1,78 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace RobinTheHood\ModifiedModuleLoaderClient\Tests\Unit\DependencyManager; + +use PHPUnit\Framework\TestCase; +use RobinTheHood\ModifiedModuleLoaderClient\DependencyManager\DependencyBuilder; +use RobinTheHood\ModifiedModuleLoaderClient\DependencyManager\SystemSet; + +class DependencyBuilderTest extends TestCase +{ + public function testSatisfies() + { + $dependencyBuilder = new DependencyBuilder(); + $systemSet = new SystemSet(); + + $systemSet->set([ + "modified" => '2.0.4.2', + "php" => '7.4.0', + "mmlc" => '1.20.0-beta.1', + "composer/autoload" => '1.3.0', + "robinthehood/modified-std-module" => '0.9.0', + "robinthehood/modified-orm" => '1.8.1', + "robinthehood/pdf-bill" => '0.17.0', + "foo/bar" => '1.2.3' + ]); + + $combinationSatisfyerResult = $dependencyBuilder->satisfies('firstweb/multi-order', '^1.0.0', $systemSet); + + $this->assertEqualsCanonicalizing( + [ + "modified" => '2.0.4.2', + "php" => '7.4.0', + "mmlc" => '1.20.0-beta.1', + "composer/autoload" => '1.3.0', + "robinthehood/modified-std-module" => '0.9.0', + "robinthehood/modified-orm" => '1.8.1', + "robinthehood/modified-ui" => '0.1.0', + "robinthehood/pdf-bill" => '0.17.0', + "robinthehood/tfpdf" => '0.3.0', + 'firstweb/multi-order' => '1.13.3', + "foo/bar" => '1.2.3' + ], + $combinationSatisfyerResult->testCombination->getAll() + ); + + $this->assertEqualsCanonicalizing( + [ + "modified" => '2.0.4.2', + "composer/autoload" => '1.3.0', + "robinthehood/modified-std-module" => '0.9.0', + "robinthehood/modified-orm" => '1.8.1', + "robinthehood/modified-ui" => '0.1.0', + "robinthehood/pdf-bill" => '0.17.0', + "robinthehood/tfpdf" => '0.3.0', + 'firstweb/multi-order' => '1.13.3', + ], + $combinationSatisfyerResult->foundCombination->getAll() + ); + } + + public function atestInvokeDependency() + { + $dpb = new DependencyBuilder(); + $dpb->test(); + die('TEST DONE'); + } +} diff --git a/tests/unit/DependencyManager/SystemSetTest.php b/tests/unit/DependencyManager/SystemSetTest.php new file mode 100644 index 00000000..28afb96f --- /dev/null +++ b/tests/unit/DependencyManager/SystemSetTest.php @@ -0,0 +1,41 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace RobinTheHood\ModifiedModuleLoaderClient\Tests\Unit\DependencyManager; + +use PHPUnit\Framework\TestCase; +use RobinTheHood\ModifiedModuleLoaderClient\DependencyManager\Combination; +use RobinTheHood\ModifiedModuleLoaderClient\DependencyManager\SystemSet; + +class SystemSetTest extends TestCase +{ + public function testGetArchiveNamesReturnsExpectedArray() + { + // Arrange + $systems = [ + 'systemA' => '1.0', + 'systemB/1.2' => '1.2', + 'systemC/2.0' => '2.0', + 'systemD/2.1' => '2.1', + ]; + $systemSet = new SystemSet(); + $systemSet->set($systems); + + // Act + $result = $systemSet->getArchiveNames(); + + // Assert + $expected = ['systemB/1.2', 'systemC/2.0', 'systemD/2.1']; + $this->assertEquals($expected, $result); + } +} diff --git a/tests/unit/RemoteModuleLoaderTest.php b/tests/unit/RemoteModuleLoaderTest.php index 4409ed58..15df5bf5 100644 --- a/tests/unit/RemoteModuleLoaderTest.php +++ b/tests/unit/RemoteModuleLoaderTest.php @@ -20,6 +20,8 @@ class RemoteModuleLoaderTest extends TestCase { + private $loader; + protected function setUp(): void { $this->loader = RemoteModuleLoader::getModuleLoader(); diff --git a/tests/unit/SemverTests/ConstraintTest.php b/tests/unit/SemverTests/ConstraintTest.php new file mode 100644 index 00000000..ca419042 --- /dev/null +++ b/tests/unit/SemverTests/ConstraintTest.php @@ -0,0 +1,112 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace RobinTheHood\ModifiedModuleLoaderClient\Tests\Unit\SemverTests; + +use PHPUnit\Framework\TestCase; +use RobinTheHood\ModifiedModuleLoaderClient\Semver\Constraint; + +class ConstraintTest extends TestCase +{ + public function testResolveCaretRangeWithMajor() + { + $range = '^1.0.0'; + $expected = '>=1.0.0,<2.0.0'; + $this->assertEquals($expected, Constraint::resolveCaretRange($range)); + } + + public function testResolveCaretRangeWithMajorMinor() + { + $range = '^1.2.0'; + $expected = '>=1.2.0,<2.0.0'; + $this->assertEquals($expected, Constraint::resolveCaretRange($range)); + } + + public function testResolveCaretRangeWithMajorMinorPatch() + { + $range = '^1.2.3'; + $expected = '>=1.2.3,<2.0.0'; + $this->assertEquals($expected, Constraint::resolveCaretRange($range)); + } + + public function testResolveCaretRangeWithSuffix() + { + $range = '^1.2.3-alpha'; + $expected = '>=1.2.3-alpha,<2.0.0'; + $this->assertEquals($expected, Constraint::resolveCaretRange($range)); + } + + public function testResolveCaretRangeWithMajorAndTag() + { + $range = '^1.0.0-beta'; + $expected = '>=1.0.0-beta,<2.0.0'; + $this->assertEquals($expected, Constraint::resolveCaretRange($range)); + } + + public function testResolveCaretRangeWithMajorMinorAndTag() + { + $range = '^1.2.0-rc'; + $expected = '>=1.2.0-rc,<2.0.0'; + $this->assertEquals($expected, Constraint::resolveCaretRange($range)); + } + + public function testResolveCaretRangeWithMajorMinorPatchAndTag() + { + $range = '^1.2.3-dev'; + $expected = '>=1.2.3-dev,<2.0.0'; + $this->assertEquals($expected, Constraint::resolveCaretRange($range)); + } + + public function testResolveCaretPreRelease() + { + // Test resolving a range with a caret and a major version of 0 + $this->assertEquals(">=0.2.0,<0.3.0", Constraint::resolveCaretRange("^0.2.0")); + + // Test resolving a range with a caret and a minor version of 0 and a patch version of 1 + $this->assertEquals(">=0.0.1,<0.1.0", Constraint::resolveCaretRange("^0.0.1")); + } + + public function testCreateConstraintFromConstraints() + { + $constraints = [ + '>1.0', '>=2.3,<4.0', + ]; + $expected = '>1.0, >=2.3,<4.0'; + $this->assertEquals($expected, Constraint::createConstraintFromConstraints($constraints)); + } + + public function testCreateConstraintFromConstraintsWithDuplicateVersions() + { + $constraints = [ + '>=2.3,<4.0', '>=2.3,<=3.0', + ]; + $expected = '>=2.3,<4.0, >=2.3,<=3.0'; + $this->assertEquals($expected, Constraint::createConstraintFromConstraints($constraints)); + } + + public function testCreateConstraintFromConstraintsWithOneConstraint() + { + $constraints = [ + '>=1.0', + ]; + $expected = '>=1.0'; + $this->assertEquals($expected, Constraint::createConstraintFromConstraints($constraints)); + } + + public function testCreateConstraintFromConstraintsWithEmptyArray() + { + $constraints = []; + $expected = ''; + $this->assertEquals($expected, Constraint::createConstraintFromConstraints($constraints)); + } +} diff --git a/tests/unit/SemverTests/SemverComparatorTest.php b/tests/unit/SemverTests/SemverComparatorTest.php index 9b4137f7..4ae00d0d 100644 --- a/tests/unit/SemverTests/SemverComparatorTest.php +++ b/tests/unit/SemverTests/SemverComparatorTest.php @@ -130,7 +130,13 @@ public function testThatVersionASatisfiesConstraint() public function testThatVersionASatisfiesOrConstraint() { - $this->assertTrue($this->comparator->satisfiesOr('3.3.3', '^2.2.2 || ^3.3.3')); - $this->assertFalse($this->comparator->satisfiesOr('4.4.4', '^2.2.2 || ^3.3.3')); + $this->assertTrue($this->comparator->satisfies('3.3.3', '^2.2.2 || ^3.3.3')); + $this->assertFalse($this->comparator->satisfies('4.4.4', '^2.2.2 || ^3.3.3')); + } + + public function testThatVersionASatisfiesAndConstraint() + { + $this->assertFalse($this->comparator->satisfies('3.3.3', '^2.2.2, ^3.3.3')); + $this->assertTrue($this->comparator->satisfies('4.4.4', '^4.4.2, ^4.4.3')); } } diff --git a/tests/unused_unit_tests/DependencyBuilderTest.php b/tests/unused_unit_tests/DependencyBuilderTest.php deleted file mode 100644 index eb03d828..00000000 --- a/tests/unused_unit_tests/DependencyBuilderTest.php +++ /dev/null @@ -1,29 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace RobinTheHood\ModifiedModuleLoaderClient\Tests\Unit; - -use PHPUnit\Framework\TestCase; - -class DependencyBuilderTest //extends TestCase -{ - - /* - public function invokeDependencyTest() - { - $dpb = new DependencyBuilder(); - $dpb->test(); - die('TEST DONE'); - } - */ -}