From 283ccfc95163dce8972c71548473e12fd6e5027c Mon Sep 17 00:00:00 2001 From: Robin Wieschendorf Date: Mon, 6 Mar 2023 22:07:28 +0100 Subject: [PATCH 01/37] chore: improve DependencyBuilder --- src/Classes/DependencyBuilder.php | 229 -------------- .../DependencyManager/DependencyBuilder.php | 293 ++++++++++++++++++ .../DependencyBuilderTest.php | 10 +- 3 files changed, 297 insertions(+), 235 deletions(-) delete mode 100644 src/Classes/DependencyBuilder.php create mode 100644 src/Classes/DependencyManager/DependencyBuilder.php rename tests/{unused_unit_tests => unit/DependencyManager}/DependencyBuilderTest.php (63%) 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/DependencyBuilder.php b/src/Classes/DependencyManager/DependencyBuilder.php
new file mode 100644
index 00000000..b3656052
--- /dev/null
+++ b/src/Classes/DependencyManager/DependencyBuilder.php
@@ -0,0 +1,293 @@
+
+ *
+ * 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 DependencyBuilder
+{
+    public function test()
+    {
+        $moduleLoader = ModuleLoader::getModuleLoader();
+        $module = $moduleLoader->loadLatestVersionByArchiveName('firstweb/multi-order');
+
+
+        // $modules = [];
+        // $this->getModuleList($modules, $module);
+        // file_put_contents(__DIR__ . '/debug-log-modules.txt', print_r($modules, true));
+        // file_put_contents(__DIR__ . '/debug-log-modules.json', json_encode($modules, JSON_PRETTY_PRINT));
+        // return;
+
+        // $tree = $this->buildTreeByModuleRecursive($module);
+        // file_put_contents(__DIR__ . '/debug-log-tree.txt', print_r($tree, true));
+        // file_put_contents(__DIR__ . '/debug-log-tree.json', json_encode($tree, JSON_PRETTY_PRINT));
+
+        $tree = $this->buildTreeByModuleRecursiveConstraint($module);
+        file_put_contents(__DIR__ . '/debug-log-tree-constraint.txt', print_r($tree, true));
+        file_put_contents(__DIR__ . '/debug-log-tree-constraint.json', json_encode($tree, JSON_PRETTY_PRINT));
+
+        $flatt = [];
+        $this->flattenTreeNew($tree, $flatt);
+        file_put_contents(__DIR__ . '/debug-log-flatt.txt', print_r($flatt, true));
+        file_put_contents(__DIR__ . '/debug-log-flatt.json', json_encode($flatt, JSON_PRETTY_PRINT));
+
+        $combinations = [];
+        $this->allCombinations($flatt, $combinations, 0);
+        file_put_contents(__DIR__ . '/debug-log-combinations.txt', print_r($combinations, true));
+        file_put_contents(__DIR__ . '/debug-log-combinations.json', json_encode($combinations, JSON_PRETTY_PRINT));
+
+        $combinations = array_reverse($combinations);
+        foreach ($combinations as $combination) {
+            $combination['composer/autoload'] = '1.0.0';
+            $result = $this->satisfiesComination($tree, $combination);
+            if ($result) {
+                var_dump($combination);
+                var_dump($result);
+                break;
+            }
+        }
+    }
+
+
+    private function allCombinations(&$flatt, &$combinations, int $index, $combination = [])
+    {
+        $entry = array_values($flatt)[$index] ?? [];
+
+        if (!$entry) {
+            $combinations[] = $combination;
+            return;
+        }
+
+        foreach ($entry['versions'] as $version) {
+            $newCombination = array_merge($combination, [$entry['archiveName'] => $version]);
+            $this->allCombinations($flatt, $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, &$flatt)
+    {
+        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'], $flatt);
+            }
+            $flatt[$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/tests/unused_unit_tests/DependencyBuilderTest.php b/tests/unit/DependencyManager/DependencyBuilderTest.php
similarity index 63%
rename from tests/unused_unit_tests/DependencyBuilderTest.php
rename to tests/unit/DependencyManager/DependencyBuilderTest.php
index eb03d828..f6625881 100644
--- a/tests/unused_unit_tests/DependencyBuilderTest.php
+++ b/tests/unit/DependencyManager/DependencyBuilderTest.php
@@ -11,19 +11,17 @@
  * file that was distributed with this source code.
  */
 
-namespace RobinTheHood\ModifiedModuleLoaderClient\Tests\Unit;
+namespace RobinTheHood\ModifiedModuleLoaderClient\Tests\Unit\DependencyManager;
 
 use PHPUnit\Framework\TestCase;
+use RobinTheHood\ModifiedModuleLoaderClient\DependencyManager\DependencyBuilder;
 
-class DependencyBuilderTest //extends TestCase
+class DependencyBuilderTest extends TestCase
 {
-
-    /*
-    public function invokeDependencyTest()
+    public function testInvokeDependency()
     {
         $dpb = new DependencyBuilder();
         $dpb->test();
         die('TEST DONE');
     }
-    */
 }

From e12d3ed5860cc2f34df369eba885260d2600f0fe Mon Sep 17 00:00:00 2001
From: Robin Wieschendorf 
Date: Tue, 7 Mar 2023 00:29:20 +0100
Subject: [PATCH 02/37] chore: improve DependencyManager

---
 .../DependencyManager/DependencyBuilder.php   | 351 +++++++-----------
 .../DependencyBuilderOld.php                  | 292 +++++++++++++++
 .../DependencyManager/ModuleCombination.php   |  19 +
 .../DependencyManager/ModuleFlatEntry.php     |  23 ++
 .../DependencyManager/ModuleTreeNode.php      |  26 ++
 5 files changed, 494 insertions(+), 217 deletions(-)
 create mode 100644 src/Classes/DependencyManager/DependencyBuilderOld.php
 create mode 100644 src/Classes/DependencyManager/ModuleCombination.php
 create mode 100644 src/Classes/DependencyManager/ModuleFlatEntry.php
 create mode 100644 src/Classes/DependencyManager/ModuleTreeNode.php

diff --git a/src/Classes/DependencyManager/DependencyBuilder.php b/src/Classes/DependencyManager/DependencyBuilder.php
index b3656052..25bad5ee 100644
--- a/src/Classes/DependencyManager/DependencyBuilder.php
+++ b/src/Classes/DependencyManager/DependencyBuilder.php
@@ -13,6 +13,7 @@
 
 use RobinTheHood\ModifiedModuleLoaderClient\Loader\ModuleLoader;
 use RobinTheHood\ModifiedModuleLoaderClient\Module;
+use RobinTheHood\ModifiedModuleLoaderClient\ModuleSorter;
 
 class DependencyBuilder
 {
@@ -21,161 +22,47 @@ public function test()
         $moduleLoader = ModuleLoader::getModuleLoader();
         $module = $moduleLoader->loadLatestVersionByArchiveName('firstweb/multi-order');
 
-
-        // $modules = [];
-        // $this->getModuleList($modules, $module);
-        // file_put_contents(__DIR__ . '/debug-log-modules.txt', print_r($modules, true));
-        // file_put_contents(__DIR__ . '/debug-log-modules.json', json_encode($modules, JSON_PRETTY_PRINT));
-        // return;
-
-        // $tree = $this->buildTreeByModuleRecursive($module);
-        // file_put_contents(__DIR__ . '/debug-log-tree.txt', print_r($tree, true));
-        // file_put_contents(__DIR__ . '/debug-log-tree.json', json_encode($tree, JSON_PRETTY_PRINT));
-
-        $tree = $this->buildTreeByModuleRecursiveConstraint($module);
-        file_put_contents(__DIR__ . '/debug-log-tree-constraint.txt', print_r($tree, true));
-        file_put_contents(__DIR__ . '/debug-log-tree-constraint.json', json_encode($tree, JSON_PRETTY_PRINT));
-
-        $flatt = [];
-        $this->flattenTreeNew($tree, $flatt);
-        file_put_contents(__DIR__ . '/debug-log-flatt.txt', print_r($flatt, true));
-        file_put_contents(__DIR__ . '/debug-log-flatt.json', json_encode($flatt, JSON_PRETTY_PRINT));
-
-        $combinations = [];
-        $this->allCombinations($flatt, $combinations, 0);
-        file_put_contents(__DIR__ . '/debug-log-combinations.txt', print_r($combinations, true));
-        file_put_contents(__DIR__ . '/debug-log-combinations.json', json_encode($combinations, JSON_PRETTY_PRINT));
-
-        $combinations = array_reverse($combinations);
-        foreach ($combinations as $combination) {
-            $combination['composer/autoload'] = '1.0.0';
-            $result = $this->satisfiesComination($tree, $combination);
-            if ($result) {
-                var_dump($combination);
-                var_dump($result);
-                break;
-            }
-        }
-    }
-
-
-    private function allCombinations(&$flatt, &$combinations, int $index, $combination = [])
-    {
-        $entry = array_values($flatt)[$index] ?? [];
-
-        if (!$entry) {
-            $combinations[] = $combination;
-            return;
-        }
-
-        foreach ($entry['versions'] as $version) {
-            $newCombination = array_merge($combination, [$entry['archiveName'] => $version]);
-            $this->allCombinations($flatt, $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, &$flatt)
-    {
-        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'], $flatt);
-            }
-            $flatt[$entry['archiveName']] = $moduleEntry;
-        }
+        $this->satisfiesContraints(
+            $module,
+            [
+                //"composer/autoload" => ['1.2.0'],
+                "robinthehood/modified-std-module" => ['0.6.0'],
+                //"robinthehood/modified-orm" => ['1.7.0']
+                "robinthehood/pdf-bill" => ['0.10.0']
+            ]
+        );
     }
 
-    private function getModuleList(array &$modules, Module $module, int $depth = 0)
+    public function satisfiesContraints($module, $contraints)
     {
-        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;
-        }
+        $moduleTreeNodes = $this->buildModuleTreeByConstraints($module);
+        file_put_contents(__DIR__ . '/debug-log-tree-constraint-obj.txt', print_r($moduleTreeNodes, true));
+        file_put_contents(__DIR__ . '/debug-log-tree-constraint-obj.json', json_encode($moduleTreeNodes, JSON_PRETTY_PRINT));
 
-        $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);
-            }
-        }
-    }
+        $moduleFlatEntries = [];
+        $this->flattenModuleTreeNodes($moduleTreeNodes, $moduleFlatEntries);
+        file_put_contents(__DIR__ . '/debug-log-flat-obj.txt', print_r($moduleFlatEntries, true));
+        file_put_contents(__DIR__ . '/debug-log-flat-obj.json', json_encode($moduleFlatEntries, JSON_PRETTY_PRINT));
 
-    private function containsModule($moduleA, $modules)
-    {
-        foreach ($modules as $moduleB) {
-            if ($moduleA['archiveName'] !== $moduleB['archiveName']) {
-                continue;
-            }
+        $moduleFlatEntries = $this->removeModuleFlatEnties($moduleFlatEntries, $contraints);
+        file_put_contents(__DIR__ . '/debug-log-flat-filtered-obj.txt', print_r($moduleFlatEntries, true));
+        file_put_contents(__DIR__ . '/debug-log-flat-filtered-obj.json', json_encode($moduleFlatEntries, JSON_PRETTY_PRINT));
 
-            if ($moduleA['version'] !== $moduleB['version']) {
-                continue;
-            }
+        $combinations = [];
+        $moduleFlatEntries = array_values($moduleFlatEntries);
+        $this->buildAllCombinations($moduleFlatEntries, $combinations);
+        file_put_contents(__DIR__ . '/debug-log-combinations-obj.txt', print_r($combinations, true));
+        file_put_contents(__DIR__ . '/debug-log-combinations-obj.json', json_encode($combinations, JSON_PRETTY_PRINT));
 
-            return true;
-        }
-        return false;
+        $this->satisfiesCominations($moduleTreeNodes, $combinations);
     }
 
-    private function buildTreeByModuleRecursiveConstraint(Module $module, int $depth = 0): array
+    /**
+     * @param Module $Module
+     * @param int $depth
+     * @return ModuleTreeNode[]
+     */
+    private function buildModuleTreeByConstraints(Module $module, int $depth = 0): array
     {
         if ($depth >= 10) {
             return [];
@@ -183,111 +70,141 @@ private function buildTreeByModuleRecursiveConstraint(Module $module, int $depth
 
         $require = $module->getRequire();
 
-        $requireModulesTree = [];
+        $moduleTreeNodes = [];
         foreach ($require as $archiveName => $versionConstraint) {
             // Modules to Entry
-            $entry = [];
-            $entry['archiveName'] = $archiveName;
-            $entry['versionConstraint'] = $versionConstraint;
+            $moduleTreeNode = new ModuleTreeNode();
+            $moduleTreeNode->archiveName = $archiveName;
+            $moduleTreeNode->versionConstraint = $versionConstraint;
 
             // Versions
             $moduleLoader = ModuleLoader::getModuleLoader();
             $modules = $moduleLoader->loadAllByArchiveNameAndConstraint($archiveName, $versionConstraint);
+            $modules = ModuleSorter::sortByVersion($modules);
             foreach ($modules as $module) {
-                $entry['versions'][$module->getVersion()] = [
-                    'value' => false,
-                    'requireExpanded' => $this->buildTreeByModuleRecursiveConstraint($module, $depth + 1)
-                ];
+                $versionStr = $module->getVersion();
+                $moduleTreeNode->versions[$versionStr] = $this->buildModuleTreeByConstraints($module, $depth + 1);
             }
 
-
-            $requireModulesTree[] = $entry;
+            $moduleTreeNodes[] = $moduleTreeNode;
         }
 
-        return $requireModulesTree;
+        return $moduleTreeNodes;
     }
 
-    private function buildTreeByModuleRecursive(Module $module, int $depth = 0): array
+    /**
+     * @param ModuleTreeNode[] $moduleTreeNodes
+     * @param ModuleFlatEntry[] $moduleFlatEntries
+     */
+    private function flattenModuleTreeNodes(array $moduleTreeNodes, array &$moduleFlatEntries): void
     {
-        if ($depth >= 10) {
-            return [];
+        if (!$moduleTreeNodes) {
+            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);
+        foreach ($moduleTreeNodes as $moduleTreeNode) {
+            $moduleFlatEntry = new ModuleFlatEntry();
+            $moduleFlatEntry->archiveName = $moduleTreeNode->archiveName;
+            foreach ($moduleTreeNode->versions as $versionStr => $subModuleTreeNodes) {
+                $moduleFlatEntry->versions[] = $versionStr;
+                /** @var ModuleTreeNode[] $subModuleTreeNodes */
+                $this->flattenModuleTreeNodes($subModuleTreeNodes, $moduleFlatEntries);
             }
-
-
-            $requireModulesTree[] = $entry;
+            $moduleFlatEntries[$moduleTreeNode->archiveName] = $moduleFlatEntry;
         }
-
-        return $requireModulesTree;
     }
 
-
-    private function buildTreeByModuleRecursiveOld(Module $module, int $depth = 0): array
+    private function removeModuleFlatEnties(array $moduleFlatTreeEntries, $contraints): array
     {
-        if ($depth >= 5) {
-            return [];
+        foreach ($contraints as $archiveName => $versions) {
+            $moduleFlatTreeEntries = $this->removeModuleFlatEnty($moduleFlatTreeEntries, $archiveName, $versions);
         }
+        return $moduleFlatTreeEntries;
+    }
 
-        $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);
+    private function removeModuleFlatEnty(array $moduleFlatTreeEntries, string $archiveName, array $versions): array
+    {
+        $filteredModuleFlatTreeEntries = [];
+        foreach ($moduleFlatTreeEntries as $moduleFlatTreeEntry) {
+            if ($moduleFlatTreeEntry->archiveName !== $archiveName) {
+                $filteredModuleFlatTreeEntries[$moduleFlatTreeEntry->archiveName] = $moduleFlatTreeEntry;
+                continue;
+            }
 
-                if ($requireModules) {
-                    $entry['require'] = $requireModules;
+            $fileredVersions = [];
+            foreach ($moduleFlatTreeEntry->versions as $versionStr) {
+                if (!in_array($versionStr, $versions)) {
+                    continue;
                 }
-
-                $requireModulesTree[] = $entry;
+                $fileredVersions[] = $versionStr;
             }
+            $newModuleFlatTreeEntry = new ModuleFlatEntry();
+            $newModuleFlatTreeEntry->archiveName = $moduleFlatTreeEntry->archiveName;
+            $newModuleFlatTreeEntry->versions = $fileredVersions;
+            $filteredModuleFlatTreeEntries[$moduleFlatTreeEntry->archiveName] = $newModuleFlatTreeEntry;
         }
-
-        return $requireModulesTree;
+        return $filteredModuleFlatTreeEntries;
     }
 
-    public function flattenTree($moduleTree, &$modules = null)
+    /**
+     * @param ModuleFlatEntry[] $moduleFlatEntries
+     * @param array $moduleFlatEntries
+     * @param int $index
+     * @param string[] $versionList
+     */
+    private function buildAllCombinations(array &$moduleFlatEntries, array &$combinations, int $index = 0, array $versionList = [])
     {
-        if (!$moduleTree) {
+        /** @var ModuleFlatEntry*/
+        $moduleFlatEntry = $moduleFlatEntries[$index] ?? [];
+
+        if (!$moduleFlatEntry) {
+            $combinations[] = $versionList;
             return;
         }
 
-        foreach ($moduleTree as $entry) {
-            $modules[] = [
-                'module' => $entry['module'],
-                'requestedVersion' => $entry['requestedVersion'],
-                'selectedVersion' => $entry['selectedVersion']
+        foreach ($moduleFlatEntry->versions as $versionStr) {
+            $version = [
+                $moduleFlatEntry->archiveName => $versionStr
             ];
-            $this->flattenTree($entry['require'], $modules);
+            $newVersionList = array_merge($versionList, $version);
+            $this->buildAllCombinations($moduleFlatEntries, $combinations, $index + 1, $newVersionList);
+        }
+    }
+
+    private function satisfiesCominations(array $moduleTreeNodes, array $combinations)
+    {
+        foreach ($combinations as $combination) {
+            $combination['robinthehood/modified-orm'] = '1.7.0';
+            $result = $this->satisfiesComination($moduleTreeNodes, $combination);
+            if ($result) {
+                var_dump($combination);
+                var_dump($result);
+                break;
+            }
         }
     }
+
+    private function satisfiesComination(array $moduleTreeNodes, array $combination): bool
+    {
+        // Expanded
+        $moduleResult = true;
+        foreach ($moduleTreeNodes as $moduleTreeNode) {
+            // Module
+            /** @var ModuleTreeNode $moduleTreeNode */
+            $archiveName = $moduleTreeNode->archiveName;
+            $moduleVersions = $moduleTreeNode->versions;
+            $selectedVersion = $combination[$archiveName];
+            $versionResult = false;
+            foreach ($moduleVersions as $version => $subModuleTreeNodes) {
+                // Version
+                /** @var ModuleTreeNode[] $subModuleTreeNodes */
+                if ($version === $selectedVersion) {
+                    $versionResult = $this->satisfiesComination($subModuleTreeNodes, $combination);
+                    break;
+                }
+            }
+            $moduleResult = $moduleResult && $versionResult;
+        }
+        return $moduleResult;
+    }
 }
diff --git a/src/Classes/DependencyManager/DependencyBuilderOld.php b/src/Classes/DependencyManager/DependencyBuilderOld.php
new file mode 100644
index 00000000..ecd3b4eb
--- /dev/null
+++ b/src/Classes/DependencyManager/DependencyBuilderOld.php
@@ -0,0 +1,292 @@
+
+ *
+ * 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
+{
+    public function test()
+    {
+        $moduleLoader = ModuleLoader::getModuleLoader();
+        $module = $moduleLoader->loadLatestVersionByArchiveName('firstweb/multi-order');
+
+        $modules = [];
+        $this->getModuleList($modules, $module);
+        file_put_contents(__DIR__ . '/debug-log-modules.txt', print_r($modules, true));
+        file_put_contents(__DIR__ . '/debug-log-modules.json', json_encode($modules, JSON_PRETTY_PRINT));
+
+        $tree = $this->buildTreeByModuleRecursive($module);
+        file_put_contents(__DIR__ . '/debug-log-tree.txt', print_r($tree, true));
+        file_put_contents(__DIR__ . '/debug-log-tree.json', json_encode($tree, JSON_PRETTY_PRINT));
+
+        $tree = $this->buildTreeByModuleRecursiveConstraint($module);
+        file_put_contents(__DIR__ . '/debug-log-tree-constraint.txt', print_r($tree, true));
+        file_put_contents(__DIR__ . '/debug-log-tree-constraint.json', json_encode($tree, JSON_PRETTY_PRINT));
+
+        $flat = [];
+        $this->flattenTreeNew($tree, $flat);
+        file_put_contents(__DIR__ . '/debug-log-flat.txt', print_r($flat, true));
+        file_put_contents(__DIR__ . '/debug-log-flat.json', json_encode($flat, JSON_PRETTY_PRINT));
+
+        $combinations = [];
+        $this->allCombinations($flat, $combinations, 0);
+        file_put_contents(__DIR__ . '/debug-log-combinations.txt', print_r($combinations, true));
+        file_put_contents(__DIR__ . '/debug-log-combinations.json', json_encode($combinations, JSON_PRETTY_PRINT));
+
+        $combinations = array_reverse($combinations);
+        foreach ($combinations as $combination) {
+            $combination['composer/autoload'] = '1.0.0';
+            $result = $this->satisfiesComination($tree, $combination);
+            if ($result) {
+                var_dump($combination);
+                var_dump($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/DependencyManager/ModuleCombination.php b/src/Classes/DependencyManager/ModuleCombination.php
new file mode 100644
index 00000000..681c17a8
--- /dev/null
+++ b/src/Classes/DependencyManager/ModuleCombination.php
@@ -0,0 +1,19 @@
+
+ *
+ * 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 ModuleCombination
+{
+    public $moduleVersion = [];
+}
diff --git a/src/Classes/DependencyManager/ModuleFlatEntry.php b/src/Classes/DependencyManager/ModuleFlatEntry.php
new file mode 100644
index 00000000..57027a30
--- /dev/null
+++ b/src/Classes/DependencyManager/ModuleFlatEntry.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 ModuleFlatEntry
+{
+    /** @var string */
+    public $archiveName;
+
+    /** @var array version strings */
+    public $versions;
+}
diff --git a/src/Classes/DependencyManager/ModuleTreeNode.php b/src/Classes/DependencyManager/ModuleTreeNode.php
new file mode 100644
index 00000000..4c01d49e
--- /dev/null
+++ b/src/Classes/DependencyManager/ModuleTreeNode.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 ModuleTreeNode
+{
+    /** @var string */
+    public $archiveName = '';
+
+    /** @var string version constraint string */
+    public $versionConstraint = '';
+
+    /** @var ModuleTreeNode[] version string as key */
+    public $versions = [];
+}

From 386b94786bcd6a5e8293ac9ae2683ce3456cfb2e Mon Sep 17 00:00:00 2001
From: Robin Wieschendorf 
Date: Tue, 7 Mar 2023 10:23:48 +0100
Subject: [PATCH 03/37] refactor: make code more readable

---
 .../DependencyManager/DependencyBuilder.php   | 33 ++++++++++---------
 .../DependencyManager/ModuleTreeNode.php      |  4 +--
 .../DependencyManager/ModuleVersion.php       | 23 +++++++++++++
 3 files changed, 42 insertions(+), 18 deletions(-)
 create mode 100644 src/Classes/DependencyManager/ModuleVersion.php

diff --git a/src/Classes/DependencyManager/DependencyBuilder.php b/src/Classes/DependencyManager/DependencyBuilder.php
index 25bad5ee..1e987f4d 100644
--- a/src/Classes/DependencyManager/DependencyBuilder.php
+++ b/src/Classes/DependencyManager/DependencyBuilder.php
@@ -77,13 +77,17 @@ private function buildModuleTreeByConstraints(Module $module, int $depth = 0): a
             $moduleTreeNode->archiveName = $archiveName;
             $moduleTreeNode->versionConstraint = $versionConstraint;
 
-            // Versions
+            // Fetch Versions
             $moduleLoader = ModuleLoader::getModuleLoader();
             $modules = $moduleLoader->loadAllByArchiveNameAndConstraint($archiveName, $versionConstraint);
             $modules = ModuleSorter::sortByVersion($modules);
+
+            // VersionList
             foreach ($modules as $module) {
-                $versionStr = $module->getVersion();
-                $moduleTreeNode->versions[$versionStr] = $this->buildModuleTreeByConstraints($module, $depth + 1);
+                $moduleVersion = new ModuleVersion();
+                $moduleVersion->version = $module->getVersion();
+                $moduleVersion->require = $this->buildModuleTreeByConstraints($module, $depth + 1);
+                $moduleTreeNode->moduleVersions[$moduleVersion->version] = $moduleVersion;
             }
 
             $moduleTreeNodes[] = $moduleTreeNode;
@@ -105,10 +109,9 @@ private function flattenModuleTreeNodes(array $moduleTreeNodes, array &$moduleFl
         foreach ($moduleTreeNodes as $moduleTreeNode) {
             $moduleFlatEntry = new ModuleFlatEntry();
             $moduleFlatEntry->archiveName = $moduleTreeNode->archiveName;
-            foreach ($moduleTreeNode->versions as $versionStr => $subModuleTreeNodes) {
-                $moduleFlatEntry->versions[] = $versionStr;
-                /** @var ModuleTreeNode[] $subModuleTreeNodes */
-                $this->flattenModuleTreeNodes($subModuleTreeNodes, $moduleFlatEntries);
+            foreach ($moduleTreeNode->moduleVersions as $moduleVersion) {
+                $moduleFlatEntry->versions[] = $moduleVersion->version;
+                $this->flattenModuleTreeNodes($moduleVersion->require, $moduleFlatEntries);
             }
             $moduleFlatEntries[$moduleTreeNode->archiveName] = $moduleFlatEntry;
         }
@@ -186,23 +189,21 @@ private function satisfiesCominations(array $moduleTreeNodes, array $combination
 
     private function satisfiesComination(array $moduleTreeNodes, array $combination): bool
     {
-        // Expanded
+        // Context: Expanded
         $moduleResult = true;
         foreach ($moduleTreeNodes as $moduleTreeNode) {
-            // Module
-            /** @var ModuleTreeNode $moduleTreeNode */
+            // Context: Module
             $archiveName = $moduleTreeNode->archiveName;
-            $moduleVersions = $moduleTreeNode->versions;
             $selectedVersion = $combination[$archiveName];
             $versionResult = false;
-            foreach ($moduleVersions as $version => $subModuleTreeNodes) {
-                // Version
-                /** @var ModuleTreeNode[] $subModuleTreeNodes */
-                if ($version === $selectedVersion) {
-                    $versionResult = $this->satisfiesComination($subModuleTreeNodes, $combination);
+            foreach ($moduleTreeNode->moduleVersions as $moduleVersion) {
+                // Context: Version
+                if ($moduleVersion->version === $selectedVersion) {
+                    $versionResult = $this->satisfiesComination($moduleVersion->require, $combination);
                     break;
                 }
             }
+
             $moduleResult = $moduleResult && $versionResult;
         }
         return $moduleResult;
diff --git a/src/Classes/DependencyManager/ModuleTreeNode.php b/src/Classes/DependencyManager/ModuleTreeNode.php
index 4c01d49e..b675932e 100644
--- a/src/Classes/DependencyManager/ModuleTreeNode.php
+++ b/src/Classes/DependencyManager/ModuleTreeNode.php
@@ -21,6 +21,6 @@ class ModuleTreeNode
     /** @var string version constraint string */
     public $versionConstraint = '';
 
-    /** @var ModuleTreeNode[] version string as key */
-    public $versions = [];
+    /** @var ModuleVersion[] */
+    public $moduleVersions = [];
 }
diff --git a/src/Classes/DependencyManager/ModuleVersion.php b/src/Classes/DependencyManager/ModuleVersion.php
new file mode 100644
index 00000000..c3c65728
--- /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 ModuleTreeNode[] **/
+    public $require = [];
+}

From 23b458019de1960b4a9f820aea07eef349d415d2 Mon Sep 17 00:00:00 2001
From: Robin Wieschendorf 
Date: Tue, 7 Mar 2023 14:28:02 +0100
Subject: [PATCH 04/37] refactor: add new methods and make code more readable

---
 .../DependencyManager/DependencyBuilder.php   | 159 ++++++++++++++++--
 .../DependencyManager/ModuleFlatEntryList.php |  33 ++++
 2 files changed, 177 insertions(+), 15 deletions(-)
 create mode 100644 src/Classes/DependencyManager/ModuleFlatEntryList.php

diff --git a/src/Classes/DependencyManager/DependencyBuilder.php b/src/Classes/DependencyManager/DependencyBuilder.php
index 1e987f4d..4f33c7c6 100644
--- a/src/Classes/DependencyManager/DependencyBuilder.php
+++ b/src/Classes/DependencyManager/DependencyBuilder.php
@@ -19,11 +19,12 @@ class DependencyBuilder
 {
     public function test()
     {
-        $moduleLoader = ModuleLoader::getModuleLoader();
-        $module = $moduleLoader->loadLatestVersionByArchiveName('firstweb/multi-order');
+        // $moduleLoader = ModuleLoader::getModuleLoader();
+        // $module = $moduleLoader->loadLatestVersionByArchiveName('firstweb/multi-order');
 
         $this->satisfiesContraints(
-            $module,
+            'firstweb/multi-order',
+            '^1.13.0',
             [
                 //"composer/autoload" => ['1.2.0'],
                 "robinthehood/modified-std-module" => ['0.6.0'],
@@ -33,28 +34,45 @@ public function test()
         );
     }
 
-    public function satisfiesContraints($module, $contraints)
+    public function satisfiesContraints(string $archiveName, string $constraint, array $contraints): void
     {
-        $moduleTreeNodes = $this->buildModuleTreeByConstraints($module);
-        file_put_contents(__DIR__ . '/debug-log-tree-constraint-obj.txt', print_r($moduleTreeNodes, true));
-        file_put_contents(__DIR__ . '/debug-log-tree-constraint-obj.json', json_encode($moduleTreeNodes, JSON_PRETTY_PRINT));
+        // $moduleTreeNodes = $this->buildModuleTreeByConstraints($module);
+        // file_put_contents(__DIR__ . '/debug-log-tree-constraint-obj.txt', print_r($moduleTreeNodes, true));
+        // file_put_contents(__DIR__ . '/debug-log-tree-constraint-obj.json', json_encode($moduleTreeNodes, JSON_PRETTY_PRINT));
+
+
+        $moduleTreeNode = $this->buildModuleTreeByConstraintsNew($archiveName, $constraint);
+        file_put_contents(__DIR__ . '/debug-log-tree-constraint-obj-new.txt', print_r($moduleTreeNode, true));
+        file_put_contents(__DIR__ . '/debug-log-tree-constraint-obj-new.json', json_encode($moduleTreeNode, JSON_PRETTY_PRINT));
+
+        // $moduleFlatEntries = [];
+        // $this->flattenModuleTreeNodes($moduleTreeNodes, $moduleFlatEntries);
+        // file_put_contents(__DIR__ . '/debug-log-flat-obj.txt', print_r($moduleFlatEntries, true));
+        // file_put_contents(__DIR__ . '/debug-log-flat-obj.json', json_encode($moduleFlatEntries, JSON_PRETTY_PRINT));
 
         $moduleFlatEntries = [];
-        $this->flattenModuleTreeNodes($moduleTreeNodes, $moduleFlatEntries);
-        file_put_contents(__DIR__ . '/debug-log-flat-obj.txt', print_r($moduleFlatEntries, true));
-        file_put_contents(__DIR__ . '/debug-log-flat-obj.json', json_encode($moduleFlatEntries, JSON_PRETTY_PRINT));
+        $this->flattenModuleTreeNodeNew($moduleTreeNode, $moduleFlatEntries);
+        file_put_contents(__DIR__ . '/debug-log-flat-obj-new.txt', print_r($moduleFlatEntries, true));
+        file_put_contents(__DIR__ . '/debug-log-flat-obj-new.json', json_encode($moduleFlatEntries, JSON_PRETTY_PRINT));
 
         $moduleFlatEntries = $this->removeModuleFlatEnties($moduleFlatEntries, $contraints);
         file_put_contents(__DIR__ . '/debug-log-flat-filtered-obj.txt', print_r($moduleFlatEntries, true));
         file_put_contents(__DIR__ . '/debug-log-flat-filtered-obj.json', json_encode($moduleFlatEntries, JSON_PRETTY_PRINT));
 
+        // $combinations = [];
+        // $moduleFlatEntries = array_values($moduleFlatEntries);
+        // $this->buildAllCombinations($moduleFlatEntries, $combinations);
+        // file_put_contents(__DIR__ . '/debug-log-combinations-obj.txt', print_r($combinations, true));
+        // file_put_contents(__DIR__ . '/debug-log-combinations-obj.json', json_encode($combinations, JSON_PRETTY_PRINT));
+
         $combinations = [];
-        $moduleFlatEntries = array_values($moduleFlatEntries);
-        $this->buildAllCombinations($moduleFlatEntries, $combinations);
-        file_put_contents(__DIR__ . '/debug-log-combinations-obj.txt', print_r($combinations, true));
-        file_put_contents(__DIR__ . '/debug-log-combinations-obj.json', json_encode($combinations, JSON_PRETTY_PRINT));
+        $moduleFlatEntryList = new ModuleFlatEntryList($moduleFlatEntries);
+        $this->buildAllCombinationsNew($moduleFlatEntryList, $combinations);
+        file_put_contents(__DIR__ . '/debug-log-combinations-obj-new.txt', print_r($combinations, true));
+        file_put_contents(__DIR__ . '/debug-log-combinations-obj-new.json', json_encode($combinations, JSON_PRETTY_PRINT));
 
-        $this->satisfiesCominations($moduleTreeNodes, $combinations);
+        //$this->satisfiesCominations($moduleTreeNodes, $combinations);
+        $this->satisfiesCominationsNew($moduleTreeNode, $combinations);
     }
 
     /**
@@ -96,6 +114,41 @@ private function buildModuleTreeByConstraints(Module $module, int $depth = 0): a
         return $moduleTreeNodes;
     }
 
+    /**
+     * @param string $archiveName
+     * @param string $versionConstraint
+     * @param int $depth
+     */
+    private function buildModuleTreeByConstraintsNew(string $archiveName, string $versionConstraint, int $depth = 0): ModuleTreeNode
+    {
+        $moduleTreeNode = new ModuleTreeNode();
+        $moduleTreeNode->archiveName = $archiveName;
+        $moduleTreeNode->versionConstraint = $versionConstraint;
+
+        $moduleLoader = ModuleLoader::getModuleLoader();
+        $modules = $moduleLoader->loadAllByArchiveNameAndConstraint($archiveName, $versionConstraint);
+        $modules = ModuleSorter::sortByVersion($modules);
+
+        $moduleVersions = [];
+        foreach ($modules as $module) {
+            // Context: Module
+            $moduleVersion = new ModuleVersion();
+            $moduleVersion->version = $module->getVersion();
+
+            if ($depth < 10) {
+                $require = $module->getRequire();
+                foreach ($require as $archiveName => $versionConstraint) {
+                    // Context: require
+                    $moduleVersion->require[] = $this->buildModuleTreeByConstraintsNew($archiveName, $versionConstraint, $depth + 1);
+                }
+            }
+            $moduleVersions[] = $moduleVersion;
+        }
+        $moduleTreeNode->moduleVersions = $moduleVersions;
+
+        return $moduleTreeNode;
+    }
+
     /**
      * @param ModuleTreeNode[] $moduleTreeNodes
      * @param ModuleFlatEntry[] $moduleFlatEntries
@@ -117,6 +170,23 @@ private function flattenModuleTreeNodes(array $moduleTreeNodes, array &$moduleFl
         }
     }
 
+    /**
+     * @param ModuleTreeNode $moduleTreeNode
+     * @param ModuleFlatEntry[] $moduleFlatEntries
+     */
+    private function flattenModuleTreeNodeNew(ModuleTreeNode $moduleTreeNode, array &$moduleFlatEntries): void
+    {
+        $moduleFlatEntry = new ModuleFlatEntry();
+        $moduleFlatEntry->archiveName = $moduleTreeNode->archiveName;
+        $moduleFlatEntries[$moduleTreeNode->archiveName] = $moduleFlatEntry;
+        foreach ($moduleTreeNode->moduleVersions as $moduleVersion) {
+            $moduleFlatEntry->versions[] = $moduleVersion->version;
+            foreach ($moduleVersion->require as $moduleTreeNode) {
+                $this->flattenModuleTreeNodeNew($moduleTreeNode, $moduleFlatEntries);
+            }
+        }
+    }
+
     private function removeModuleFlatEnties(array $moduleFlatTreeEntries, $contraints): array
     {
         foreach ($contraints as $archiveName => $versions) {
@@ -174,6 +244,29 @@ private function buildAllCombinations(array &$moduleFlatEntries, array &$combina
         }
     }
 
+    /**
+     * @param ModuleFlatEntryList $moduleFlatEntryList
+     * @param array $combinations [compination, compination, compination ...]
+     * @param string[] $compination [archiveName => version]
+     * @param int $index
+     */
+    private function buildAllCombinationsNew(ModuleFlatEntryList $moduleFlatEntryList, array &$combinations, array $compination = [], int $index = 0)
+    {
+        /** @var ModuleFlatEntry*/
+        $moduleFlatEntry = $moduleFlatEntryList->get($index);
+
+        if (!$moduleFlatEntry) {
+            $combinations[] = $compination;
+            return;
+        }
+
+        foreach ($moduleFlatEntry->versions as $versionStr) {
+            $version = [$moduleFlatEntry->archiveName => $versionStr];
+            $newCombination = array_merge($compination, $version);
+            $this->buildAllCombinationsNew($moduleFlatEntryList, $combinations, $newCombination, $index + 1);
+        }
+    }
+
     private function satisfiesCominations(array $moduleTreeNodes, array $combinations)
     {
         foreach ($combinations as $combination) {
@@ -208,4 +301,40 @@ private function satisfiesComination(array $moduleTreeNodes, array $combination)
         }
         return $moduleResult;
     }
+
+    private function satisfiesCominationsNew(ModuleTreeNode $moduleTreeNode, array $combinations)
+    {
+        foreach ($combinations as $combination) {
+            $result = $this->satisfiesCominationNew($moduleTreeNode, $combination);
+            if ($result) {
+                var_dump($combination);
+                var_dump($result);
+                break;
+            }
+        }
+    }
+
+    public function satisfiesCominationNew(ModuleTreeNode $moduleTreeNode, array $combination): bool
+    {
+        // Context: Module
+        $archiveName = $moduleTreeNode->archiveName;
+        $selectedVersion = $combination[$archiveName];
+        foreach ($moduleTreeNode->moduleVersions as $moduleVersion) {
+            // Context: Version
+            if ($moduleVersion->version === $selectedVersion) {
+                return $this->satisfiesCominationNew2($moduleVersion->require, $combination);
+            }
+        }
+        return false;
+    }
+
+    private function satisfiesCominationNew2(array $moduleTreeNodes, array $combination): bool
+    {
+        // Context: Expanded
+        $moduleResult = true;
+        foreach ($moduleTreeNodes as $moduleTreeNode) {
+            $moduleResult = $moduleResult && $this->satisfiesCominationNew($moduleTreeNode, $combination);
+        }
+        return $moduleResult;
+    }
 }
diff --git a/src/Classes/DependencyManager/ModuleFlatEntryList.php b/src/Classes/DependencyManager/ModuleFlatEntryList.php
new file mode 100644
index 00000000..056569b0
--- /dev/null
+++ b/src/Classes/DependencyManager/ModuleFlatEntryList.php
@@ -0,0 +1,33 @@
+
+ *
+ * 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 ModuleFlatEntryList
+{
+    /** @var ModuleFlatEntry[] */
+    private $moduleFlatEntries = [];
+
+    /**
+     * @param ModuleFlatEntry[] $moduleFlatEntries
+     */
+    public function __construct(array $moduleFlatEntries)
+    {
+        $this->moduleFlatEntries = array_values($moduleFlatEntries);
+    }
+
+    public function get(int $index): ?ModuleFlatEntry
+    {
+        return $this->moduleFlatEntries[$index] ?? null;
+    }
+}

From e9035a0e7af79a67c8ebe86214e28e5a3d397a0d Mon Sep 17 00:00:00 2001
From: Robin Wieschendorf 
Date: Tue, 7 Mar 2023 16:43:35 +0100
Subject: [PATCH 05/37] chore: add class Counter and CounterTest

---
 src/Classes/DependencyManager/Counter.php    | 67 ++++++++++++++++++++
 tests/unit/DependencyManager/CounterTest.php | 61 ++++++++++++++++++
 2 files changed, 128 insertions(+)
 create mode 100644 src/Classes/DependencyManager/Counter.php
 create mode 100644 tests/unit/DependencyManager/CounterTest.php

diff --git a/src/Classes/DependencyManager/Counter.php b/src/Classes/DependencyManager/Counter.php
new file mode 100644
index 00000000..924c53d0
--- /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->counterMaxValues 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/tests/unit/DependencyManager/CounterTest.php b/tests/unit/DependencyManager/CounterTest.php
new file mode 100644
index 00000000..a42267ef
--- /dev/null
+++ b/tests/unit/DependencyManager/CounterTest.php
@@ -0,0 +1,61 @@
+
+ *
+ * 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());
+    }
+}

From 3411401d44ce382eb87b0070c9a7c0031b9a8831 Mon Sep 17 00:00:00 2001
From: Robin Wieschendorf 
Date: Tue, 7 Mar 2023 16:44:02 +0100
Subject: [PATCH 06/37] chore: add class CombinationIterator

---
 .../DependencyManager/CombinationIterator.php | 75 +++++++++++++++++++
 1 file changed, 75 insertions(+)
 create mode 100644 src/Classes/DependencyManager/CombinationIterator.php

diff --git a/src/Classes/DependencyManager/CombinationIterator.php b/src/Classes/DependencyManager/CombinationIterator.php
new file mode 100644
index 00000000..c002d804
--- /dev/null
+++ b/src/Classes/DependencyManager/CombinationIterator.php
@@ -0,0 +1,75 @@
+
+ *
+ * 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 ModuleFlatEntry[] */
+    private $moduleFlatEntries;
+
+    /** @var int[] */
+
+    /** @var Counter $counter */
+    private $counter;
+
+    /**
+     * @param ModuleFlatEntry[] $moduleFlatEntries
+     */
+    public function __construct(array $moduleFlatEntries)
+    {
+        $this->moduleFlatEntries = $moduleFlatEntries;
+
+        foreach ($moduleFlatEntries as $moduleFlatEntry) {
+            $counterMaxValues[] = count($moduleFlatEntry->versions) - 1;
+        }
+
+        $this->counter = new Counter($counterMaxValues);
+    }
+
+    public function current(): array
+    {
+        return $this->combinationFromCounter($this->moduleFlatEntries, $this->counter);
+    }
+
+    public function next(): array
+    {
+        $this->counter->next();
+        return $this->combinationFromCounter($this->moduleFlatEntries, $this->counter);
+    }
+
+    public function isStart(): bool
+    {
+        return $this->counter->isStart();
+    }
+
+    /**
+     * @param ModuleFlatEntry[] $moduleFlatEntries
+     */
+    private function combinationFromCounter(array $moduleFlatEntries, Counter $counter): array
+    {
+        $counter = $counter->current();
+
+        $counterIndex = 0;
+        $combination = [];
+        foreach ($moduleFlatEntries as $moduleFlatEntry) {
+            $versionIndex = $counter[$counterIndex];
+            $versionStr = $moduleFlatEntry->versions[$versionIndex];
+            $version = [$moduleFlatEntry->archiveName => $versionStr];
+            $combination = array_merge($combination, $version);
+            $counterIndex++;
+        }
+
+        return $combination;
+    }
+}

From e80e665f5f091c55f2f29ec2d38d4cfcd426d681 Mon Sep 17 00:00:00 2001
From: Robin Wieschendorf 
Date: Tue, 7 Mar 2023 16:44:32 +0100
Subject: [PATCH 07/37] chore: use CombinationIterator

---
 .../DependencyManager/DependencyBuilder.php   | 79 +++++++++++++++----
 .../DependencyManager/ModuleFlatEntryList.php |  5 ++
 2 files changed, 70 insertions(+), 14 deletions(-)

diff --git a/src/Classes/DependencyManager/DependencyBuilder.php b/src/Classes/DependencyManager/DependencyBuilder.php
index 4f33c7c6..fb7f80d5 100644
--- a/src/Classes/DependencyManager/DependencyBuilder.php
+++ b/src/Classes/DependencyManager/DependencyBuilder.php
@@ -13,10 +13,14 @@
 
 use RobinTheHood\ModifiedModuleLoaderClient\Loader\ModuleLoader;
 use RobinTheHood\ModifiedModuleLoaderClient\Module;
+use RobinTheHood\ModifiedModuleLoaderClient\ModuleFilter;
 use RobinTheHood\ModifiedModuleLoaderClient\ModuleSorter;
 
 class DependencyBuilder
 {
+    /** @var Module[] */
+    private $moduleCache = [];
+
     public function test()
     {
         // $moduleLoader = ModuleLoader::getModuleLoader();
@@ -24,12 +28,12 @@ public function test()
 
         $this->satisfiesContraints(
             'firstweb/multi-order',
-            '^1.13.0',
+            '^1.0.0',
             [
                 //"composer/autoload" => ['1.2.0'],
-                "robinthehood/modified-std-module" => ['0.6.0'],
+                "robinthehood/modified-std-module" => ['0.1.0'],
                 //"robinthehood/modified-orm" => ['1.7.0']
-                "robinthehood/pdf-bill" => ['0.10.0']
+                //"robinthehood/pdf-bill" => ['0.10.0']
             ]
         );
     }
@@ -42,7 +46,7 @@ public function satisfiesContraints(string $archiveName, string $constraint, arr
 
 
         $moduleTreeNode = $this->buildModuleTreeByConstraintsNew($archiveName, $constraint);
-        file_put_contents(__DIR__ . '/debug-log-tree-constraint-obj-new.txt', print_r($moduleTreeNode, true));
+        // file_put_contents(__DIR__ . '/debug-log-tree-constraint-obj-new.txt', print_r($moduleTreeNode, true));
         file_put_contents(__DIR__ . '/debug-log-tree-constraint-obj-new.json', json_encode($moduleTreeNode, JSON_PRETTY_PRINT));
 
         // $moduleFlatEntries = [];
@@ -52,7 +56,7 @@ public function satisfiesContraints(string $archiveName, string $constraint, arr
 
         $moduleFlatEntries = [];
         $this->flattenModuleTreeNodeNew($moduleTreeNode, $moduleFlatEntries);
-        file_put_contents(__DIR__ . '/debug-log-flat-obj-new.txt', print_r($moduleFlatEntries, true));
+        // file_put_contents(__DIR__ . '/debug-log-flat-obj-new.txt', print_r($moduleFlatEntries, true));
         file_put_contents(__DIR__ . '/debug-log-flat-obj-new.json', json_encode($moduleFlatEntries, JSON_PRETTY_PRINT));
 
         $moduleFlatEntries = $this->removeModuleFlatEnties($moduleFlatEntries, $contraints);
@@ -65,14 +69,42 @@ public function satisfiesContraints(string $archiveName, string $constraint, arr
         // file_put_contents(__DIR__ . '/debug-log-combinations-obj.txt', print_r($combinations, true));
         // file_put_contents(__DIR__ . '/debug-log-combinations-obj.json', json_encode($combinations, JSON_PRETTY_PRINT));
 
-        $combinations = [];
-        $moduleFlatEntryList = new ModuleFlatEntryList($moduleFlatEntries);
-        $this->buildAllCombinationsNew($moduleFlatEntryList, $combinations);
-        file_put_contents(__DIR__ . '/debug-log-combinations-obj-new.txt', print_r($combinations, true));
-        file_put_contents(__DIR__ . '/debug-log-combinations-obj-new.json', json_encode($combinations, JSON_PRETTY_PRINT));
+
+        $combinationIterator = new CombinationIterator($moduleFlatEntries);
+        // for ($i = 0; $i < 10; $i++) {
+        //     $combination = $combinationIterator->next();
+        //     var_dump($combination);
+        // }
+        // die();
+
+        // $combinations = [];
+        // $moduleFlatEntryList = new ModuleFlatEntryList($moduleFlatEntries);
+        // $this->buildAllCombinationsNew($moduleFlatEntryList, $combinations);
+        // file_put_contents(__DIR__ . '/debug-log-combinations-obj-new.json', json_encode($combinations, JSON_PRETTY_PRINT));
+
 
         //$this->satisfiesCominations($moduleTreeNodes, $combinations);
-        $this->satisfiesCominationsNew($moduleTreeNode, $combinations);
+        //$this->satisfiesCominationsNew($moduleTreeNode, $combinations);
+
+        $this->satisfiesCominationsNewNew($moduleTreeNode, $combinationIterator);
+    }
+
+    /**
+     * @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);
     }
 
     /**
@@ -125,9 +157,10 @@ private function buildModuleTreeByConstraintsNew(string $archiveName, string $ve
         $moduleTreeNode->archiveName = $archiveName;
         $moduleTreeNode->versionConstraint = $versionConstraint;
 
-        $moduleLoader = ModuleLoader::getModuleLoader();
-        $modules = $moduleLoader->loadAllByArchiveNameAndConstraint($archiveName, $versionConstraint);
-        $modules = ModuleSorter::sortByVersion($modules);
+        // $moduleLoader = ModuleLoader::getModuleLoader();
+        // $modules = $moduleLoader->loadAllByArchiveNameAndConstraint($archiveName, $versionConstraint);
+        // $modules = ModuleSorter::sortByVersion($modules);
+        $modules = $this->loadAllByArchiveNameAndConstraint($archiveName, $versionConstraint);
 
         $moduleVersions = [];
         foreach ($modules as $module) {
@@ -314,6 +347,24 @@ private function satisfiesCominationsNew(ModuleTreeNode $moduleTreeNode, array $
         }
     }
 
+    private function satisfiesCominationsNewNew(ModuleTreeNode $moduleTreeNode, CombinationIterator $combinationIterator)
+    {
+        while (true) {
+            $combination = $combinationIterator->current();
+            $result = $this->satisfiesCominationNew($moduleTreeNode, $combination);
+            if ($result) {
+                var_dump($combination);
+                var_dump($result);
+                return;
+            }
+
+            $combinationIterator->next();
+            if ($combinationIterator->isStart()) {
+                return;
+            }
+        }
+    }
+
     public function satisfiesCominationNew(ModuleTreeNode $moduleTreeNode, array $combination): bool
     {
         // Context: Module
diff --git a/src/Classes/DependencyManager/ModuleFlatEntryList.php b/src/Classes/DependencyManager/ModuleFlatEntryList.php
index 056569b0..da122b5d 100644
--- a/src/Classes/DependencyManager/ModuleFlatEntryList.php
+++ b/src/Classes/DependencyManager/ModuleFlatEntryList.php
@@ -30,4 +30,9 @@ public function get(int $index): ?ModuleFlatEntry
     {
         return $this->moduleFlatEntries[$index] ?? null;
     }
+
+    public function getAll(): array
+    {
+        return $this->moduleFlatEntries;
+    }
 }

From 374f40f1998c8c8b167552e01b41051852d54cdc Mon Sep 17 00:00:00 2001
From: Robin Wieschendorf 
Date: Tue, 7 Mar 2023 17:48:58 +0100
Subject: [PATCH 08/37] chore: improve code

---
 .../CombinationSatisfyer.php                  | 103 +++++++++++
 .../DependencyManager/DependencyBuilder.php   | 171 ++++++++++--------
 2 files changed, 202 insertions(+), 72 deletions(-)
 create mode 100644 src/Classes/DependencyManager/CombinationSatisfyer.php

diff --git a/src/Classes/DependencyManager/CombinationSatisfyer.php b/src/Classes/DependencyManager/CombinationSatisfyer.php
new file mode 100644
index 00000000..f1864371
--- /dev/null
+++ b/src/Classes/DependencyManager/CombinationSatisfyer.php
@@ -0,0 +1,103 @@
+
+ *
+ * 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 CombinationSatisfyer
+{
+    public function satisfiesCominationsFromModuleTreeNodes(array $moduleTreeNodes, array $combinations): array
+    {
+        foreach ($combinations as $combination) {
+            $result = $this->satisfiesCominationFromModuleTreeNodes($moduleTreeNodes, $combination);
+            if ($result) {
+                return $combination;
+            }
+        }
+        return [];
+    }
+
+    public function satisfiesCominationsFromModuleTreeNode(ModuleTreeNode $moduleTreeNode, array $combinations): array
+    {
+        foreach ($combinations as $combination) {
+            $result = $this->satisfiesCominationFromModuleTreeNode($moduleTreeNode, $combination);
+            if ($result) {
+                return $combination;
+            }
+        }
+        return [];
+    }
+
+    public function satisfiesCominationsFromModuleWithIterator(
+        ModuleTreeNode $moduleTreeNode,
+        CombinationIterator $combinationIterator
+    ): array {
+        while (true) {
+            $combination = $combinationIterator->current();
+            $result = $this->satisfiesCominationFromModuleTreeNode($moduleTreeNode, $combination);
+            if ($result) {
+                return $combination;
+            }
+
+            $combinationIterator->next();
+            if ($combinationIterator->isStart()) {
+                return [];
+            }
+        }
+    }
+
+    public function satisfiesCominationFromModuleTreeNode(ModuleTreeNode $moduleTreeNode, array $combination): bool
+    {
+        // Context: Module
+        $archiveName = $moduleTreeNode->archiveName;
+        $selectedVersion = $combination[$archiveName];
+        foreach ($moduleTreeNode->moduleVersions as $moduleVersion) {
+            // Context: Version
+            if ($moduleVersion->version === $selectedVersion) {
+                return $this->satisfiesCominationFromModuleTreeNodes($moduleVersion->require, $combination);
+            }
+        }
+        return false;
+    }
+
+    public function satisfiesCominationFromModuleTreeNodes(array $moduleTreeNodes, array $combination): bool
+    {
+        // Context: Expanded
+        $moduleResult = true;
+        foreach ($moduleTreeNodes as $moduleTreeNode) {
+            $moduleResult =
+                $moduleResult && $this->satisfiesCominationFromModuleTreeNode($moduleTreeNode, $combination);
+        }
+        return $moduleResult;
+    }
+
+    // public function satisfiesCominationFromModuleTreeNodes(array $moduleTreeNodes, array $combination): bool
+    // {
+    //     // Context: Expanded
+    //     $moduleResult = true;
+    //     foreach ($moduleTreeNodes as $moduleTreeNode) {
+    //         // Context: Module
+    //         $archiveName = $moduleTreeNode->archiveName;
+    //         $selectedVersion = $combination[$archiveName];
+    //         $versionResult = false;
+    //         foreach ($moduleTreeNode->moduleVersions as $moduleVersion) {
+    //             // Context: Version
+    //             if ($moduleVersion->version === $selectedVersion) {
+    //                 $versionResult = $this->satisfiesCominationFromModuleTreeNodes($moduleVersion->require, $combination);
+    //                 break;
+    //             }
+    //         }
+    //         $moduleResult = $moduleResult && $versionResult;
+    //     }
+    //     return $moduleResult;
+    // }
+}
diff --git a/src/Classes/DependencyManager/DependencyBuilder.php b/src/Classes/DependencyManager/DependencyBuilder.php
index fb7f80d5..9503d37e 100644
--- a/src/Classes/DependencyManager/DependencyBuilder.php
+++ b/src/Classes/DependencyManager/DependencyBuilder.php
@@ -23,70 +23,103 @@ class DependencyBuilder
 
     public function test()
     {
-        // $moduleLoader = ModuleLoader::getModuleLoader();
-        // $module = $moduleLoader->loadLatestVersionByArchiveName('firstweb/multi-order');
+        $moduleLoader = ModuleLoader::getModuleLoader();
+        $module = $moduleLoader->loadLatestVersionByArchiveName('firstweb/multi-order');
+
+        $constraints = [
+            //"composer/autoload" => ['1.2.0'],
+            //"robinthehood/modified-std-module" => ['0.1.0'],
+            //"robinthehood/modified-orm" => ['1.7.0']
+            //"robinthehood/pdf-bill" => ['0.10.0']
+        ];
+
+        var_dump('TEST: satisfiesContraints1');
+        $this->satisfiesContraints1(
+            $module,
+            $constraints
+        );
+
+        var_dump('TEST: satisfiesContraints2');
+        $this->satisfiesContraints2(
+            'firstweb/multi-order',
+            '^1.13.0',
+            $constraints
+        );
 
-        $this->satisfiesContraints(
+        var_dump('TEST: satisfiesContraints3');
+        $this->satisfiesContraints3(
             'firstweb/multi-order',
             '^1.0.0',
-            [
-                //"composer/autoload" => ['1.2.0'],
-                "robinthehood/modified-std-module" => ['0.1.0'],
-                //"robinthehood/modified-orm" => ['1.7.0']
-                //"robinthehood/pdf-bill" => ['0.10.0']
-            ]
+            $constraints
         );
     }
 
-    public function satisfiesContraints(string $archiveName, string $constraint, array $contraints): void
+    public function satisfiesContraints1(Module $module, array $contraints): void
     {
-        // $moduleTreeNodes = $this->buildModuleTreeByConstraints($module);
-        // file_put_contents(__DIR__ . '/debug-log-tree-constraint-obj.txt', print_r($moduleTreeNodes, true));
-        // file_put_contents(__DIR__ . '/debug-log-tree-constraint-obj.json', json_encode($moduleTreeNodes, JSON_PRETTY_PRINT));
+        $moduleTreeNodes = $this->buildModuleTreesByConstraints($module);
+        file_put_contents(__DIR__ . '/debug-log-tree-constraint-obj.json', json_encode($moduleTreeNodes, JSON_PRETTY_PRINT));
 
+        $moduleFlatEntries = [];
+        $this->buildModuleFlatEntriesByModuleTreeNodes($moduleTreeNodes, $moduleFlatEntries);
+        file_put_contents(__DIR__ . '/debug-log-flat-obj.json', json_encode($moduleFlatEntries, JSON_PRETTY_PRINT));
+
+        $moduleFlatEntries = $this->removeModuleFlatEnties($moduleFlatEntries, $contraints);
+        file_put_contents(__DIR__ . '/debug-log-flat-filtered-obj.json', json_encode($moduleFlatEntries, JSON_PRETTY_PRINT));
+
+        $combinations = [];
+        $moduleFlatEntries = array_values($moduleFlatEntries);
+        $this->buildAllCombinationsFromModuleFlatEntries($moduleFlatEntries, $combinations);
+        file_put_contents(__DIR__ . '/debug-log-combinations-obj.json', json_encode($combinations, JSON_PRETTY_PRINT));
+
+        //$value = $this->satisfiesCominationsFromModuleTreeNodes($moduleTreeNodes, $combinations);
+        $combinationSatisfyer = new CombinationSatisfyer();
+        $array = $combinationSatisfyer->satisfiesCominationsFromModuleTreeNodes($moduleTreeNodes, $combinations);
+        var_dump($array);
+    }
 
-        $moduleTreeNode = $this->buildModuleTreeByConstraintsNew($archiveName, $constraint);
-        // file_put_contents(__DIR__ . '/debug-log-tree-constraint-obj-new.txt', print_r($moduleTreeNode, true));
-        file_put_contents(__DIR__ . '/debug-log-tree-constraint-obj-new.json', json_encode($moduleTreeNode, JSON_PRETTY_PRINT));
 
-        // $moduleFlatEntries = [];
-        // $this->flattenModuleTreeNodes($moduleTreeNodes, $moduleFlatEntries);
-        // file_put_contents(__DIR__ . '/debug-log-flat-obj.txt', print_r($moduleFlatEntries, true));
-        // file_put_contents(__DIR__ . '/debug-log-flat-obj.json', json_encode($moduleFlatEntries, JSON_PRETTY_PRINT));
+    public function satisfiesContraints2(string $archiveName, string $constraint, array $contraints): void
+    {
+        $moduleTreeNode = $this->buildModuleTreeByConstraints($archiveName, $constraint);
+        file_put_contents(__DIR__ . '/debug-log-tree-constraint-obj-new.json', json_encode($moduleTreeNode, JSON_PRETTY_PRINT));
 
         $moduleFlatEntries = [];
-        $this->flattenModuleTreeNodeNew($moduleTreeNode, $moduleFlatEntries);
-        // file_put_contents(__DIR__ . '/debug-log-flat-obj-new.txt', print_r($moduleFlatEntries, true));
+        $this->buildModuleFlatEntriesByModuleTreeNode($moduleTreeNode, $moduleFlatEntries);
         file_put_contents(__DIR__ . '/debug-log-flat-obj-new.json', json_encode($moduleFlatEntries, JSON_PRETTY_PRINT));
 
         $moduleFlatEntries = $this->removeModuleFlatEnties($moduleFlatEntries, $contraints);
-        file_put_contents(__DIR__ . '/debug-log-flat-filtered-obj.txt', print_r($moduleFlatEntries, true));
         file_put_contents(__DIR__ . '/debug-log-flat-filtered-obj.json', json_encode($moduleFlatEntries, JSON_PRETTY_PRINT));
 
-        // $combinations = [];
-        // $moduleFlatEntries = array_values($moduleFlatEntries);
-        // $this->buildAllCombinations($moduleFlatEntries, $combinations);
-        // file_put_contents(__DIR__ . '/debug-log-combinations-obj.txt', print_r($combinations, true));
-        // file_put_contents(__DIR__ . '/debug-log-combinations-obj.json', json_encode($combinations, JSON_PRETTY_PRINT));
+        $combinations = [];
+        $moduleFlatEntryList = new ModuleFlatEntryList($moduleFlatEntries);
+        $this->buildAllCombinationsFromModuleFlatEntryList($moduleFlatEntryList, $combinations);
+        file_put_contents(__DIR__ . '/debug-log-combinations-obj-new.json', json_encode($combinations, JSON_PRETTY_PRINT));
 
+        //$this->satisfiesCominationsFromModuleTreeNode($moduleTreeNode, $combinations);
+        $combinationSatisfyer = new CombinationSatisfyer();
+        $array = $combinationSatisfyer->satisfiesCominationsFromModuleTreeNode($moduleTreeNode, $combinations);
+        var_dump($array);
+    }
 
-        $combinationIterator = new CombinationIterator($moduleFlatEntries);
-        // for ($i = 0; $i < 10; $i++) {
-        //     $combination = $combinationIterator->next();
-        //     var_dump($combination);
-        // }
-        // die();
 
-        // $combinations = [];
-        // $moduleFlatEntryList = new ModuleFlatEntryList($moduleFlatEntries);
-        // $this->buildAllCombinationsNew($moduleFlatEntryList, $combinations);
-        // file_put_contents(__DIR__ . '/debug-log-combinations-obj-new.json', json_encode($combinations, JSON_PRETTY_PRINT));
+    public function satisfiesContraints3(string $archiveName, string $constraint, array $contraints): void
+    {
+        $moduleTreeNode = $this->buildModuleTreeByConstraints($archiveName, $constraint);
+        file_put_contents(__DIR__ . '/debug-log-tree-constraint-obj-new.json', json_encode($moduleTreeNode, JSON_PRETTY_PRINT));
 
+        $moduleFlatEntries = [];
+        $this->buildModuleFlatEntriesByModuleTreeNode($moduleTreeNode, $moduleFlatEntries);
+        file_put_contents(__DIR__ . '/debug-log-flat-obj-new.json', json_encode($moduleFlatEntries, JSON_PRETTY_PRINT));
 
-        //$this->satisfiesCominations($moduleTreeNodes, $combinations);
-        //$this->satisfiesCominationsNew($moduleTreeNode, $combinations);
+        $moduleFlatEntries = $this->removeModuleFlatEnties($moduleFlatEntries, $contraints);
+        file_put_contents(__DIR__ . '/debug-log-flat-filtered-obj.json', json_encode($moduleFlatEntries, JSON_PRETTY_PRINT));
+
+        $combinationIterator = new CombinationIterator($moduleFlatEntries);
+        //$this->satisfiesCominationsFromModuleWithIterator($moduleTreeNode, $combinationIterator);
 
-        $this->satisfiesCominationsNewNew($moduleTreeNode, $combinationIterator);
+        $combinationSatisfyer = new CombinationSatisfyer();
+        $array = $combinationSatisfyer->satisfiesCominationsFromModuleWithIterator($moduleTreeNode, $combinationIterator);
+        var_dump($array);
     }
 
     /**
@@ -112,7 +145,7 @@ private function loadAllByArchiveNameAndConstraint(string $archiveName, string $
      * @param int $depth
      * @return ModuleTreeNode[]
      */
-    private function buildModuleTreeByConstraints(Module $module, int $depth = 0): array
+    private function buildModuleTreesByConstraints(Module $module, int $depth = 0): array
     {
         if ($depth >= 10) {
             return [];
@@ -128,15 +161,13 @@ private function buildModuleTreeByConstraints(Module $module, int $depth = 0): a
             $moduleTreeNode->versionConstraint = $versionConstraint;
 
             // Fetch Versions
-            $moduleLoader = ModuleLoader::getModuleLoader();
-            $modules = $moduleLoader->loadAllByArchiveNameAndConstraint($archiveName, $versionConstraint);
-            $modules = ModuleSorter::sortByVersion($modules);
+            $modules = $this->loadAllByArchiveNameAndConstraint($archiveName, $versionConstraint);
 
             // VersionList
             foreach ($modules as $module) {
                 $moduleVersion = new ModuleVersion();
                 $moduleVersion->version = $module->getVersion();
-                $moduleVersion->require = $this->buildModuleTreeByConstraints($module, $depth + 1);
+                $moduleVersion->require = $this->buildModuleTreesByConstraints($module, $depth + 1);
                 $moduleTreeNode->moduleVersions[$moduleVersion->version] = $moduleVersion;
             }
 
@@ -151,15 +182,12 @@ private function buildModuleTreeByConstraints(Module $module, int $depth = 0): a
      * @param string $versionConstraint
      * @param int $depth
      */
-    private function buildModuleTreeByConstraintsNew(string $archiveName, string $versionConstraint, int $depth = 0): ModuleTreeNode
+    private function buildModuleTreeByConstraints(string $archiveName, string $versionConstraint, int $depth = 0): ModuleTreeNode
     {
         $moduleTreeNode = new ModuleTreeNode();
         $moduleTreeNode->archiveName = $archiveName;
         $moduleTreeNode->versionConstraint = $versionConstraint;
 
-        // $moduleLoader = ModuleLoader::getModuleLoader();
-        // $modules = $moduleLoader->loadAllByArchiveNameAndConstraint($archiveName, $versionConstraint);
-        // $modules = ModuleSorter::sortByVersion($modules);
         $modules = $this->loadAllByArchiveNameAndConstraint($archiveName, $versionConstraint);
 
         $moduleVersions = [];
@@ -172,7 +200,7 @@ private function buildModuleTreeByConstraintsNew(string $archiveName, string $ve
                 $require = $module->getRequire();
                 foreach ($require as $archiveName => $versionConstraint) {
                     // Context: require
-                    $moduleVersion->require[] = $this->buildModuleTreeByConstraintsNew($archiveName, $versionConstraint, $depth + 1);
+                    $moduleVersion->require[] = $this->buildModuleTreeByConstraints($archiveName, $versionConstraint, $depth + 1);
                 }
             }
             $moduleVersions[] = $moduleVersion;
@@ -186,7 +214,7 @@ private function buildModuleTreeByConstraintsNew(string $archiveName, string $ve
      * @param ModuleTreeNode[] $moduleTreeNodes
      * @param ModuleFlatEntry[] $moduleFlatEntries
      */
-    private function flattenModuleTreeNodes(array $moduleTreeNodes, array &$moduleFlatEntries): void
+    private function buildModuleFlatEntriesByModuleTreeNodes(array $moduleTreeNodes, array &$moduleFlatEntries): void
     {
         if (!$moduleTreeNodes) {
             return;
@@ -197,7 +225,7 @@ private function flattenModuleTreeNodes(array $moduleTreeNodes, array &$moduleFl
             $moduleFlatEntry->archiveName = $moduleTreeNode->archiveName;
             foreach ($moduleTreeNode->moduleVersions as $moduleVersion) {
                 $moduleFlatEntry->versions[] = $moduleVersion->version;
-                $this->flattenModuleTreeNodes($moduleVersion->require, $moduleFlatEntries);
+                $this->buildModuleFlatEntriesByModuleTreeNodes($moduleVersion->require, $moduleFlatEntries);
             }
             $moduleFlatEntries[$moduleTreeNode->archiveName] = $moduleFlatEntry;
         }
@@ -207,7 +235,7 @@ private function flattenModuleTreeNodes(array $moduleTreeNodes, array &$moduleFl
      * @param ModuleTreeNode $moduleTreeNode
      * @param ModuleFlatEntry[] $moduleFlatEntries
      */
-    private function flattenModuleTreeNodeNew(ModuleTreeNode $moduleTreeNode, array &$moduleFlatEntries): void
+    private function buildModuleFlatEntriesByModuleTreeNode(ModuleTreeNode $moduleTreeNode, array &$moduleFlatEntries): void
     {
         $moduleFlatEntry = new ModuleFlatEntry();
         $moduleFlatEntry->archiveName = $moduleTreeNode->archiveName;
@@ -215,7 +243,7 @@ private function flattenModuleTreeNodeNew(ModuleTreeNode $moduleTreeNode, array
         foreach ($moduleTreeNode->moduleVersions as $moduleVersion) {
             $moduleFlatEntry->versions[] = $moduleVersion->version;
             foreach ($moduleVersion->require as $moduleTreeNode) {
-                $this->flattenModuleTreeNodeNew($moduleTreeNode, $moduleFlatEntries);
+                $this->buildModuleFlatEntriesByModuleTreeNode($moduleTreeNode, $moduleFlatEntries);
             }
         }
     }
@@ -258,7 +286,7 @@ private function removeModuleFlatEnty(array $moduleFlatTreeEntries, string $arch
      * @param int $index
      * @param string[] $versionList
      */
-    private function buildAllCombinations(array &$moduleFlatEntries, array &$combinations, int $index = 0, array $versionList = [])
+    private function buildAllCombinationsFromModuleFlatEntries(array &$moduleFlatEntries, array &$combinations, int $index = 0, array $versionList = [])
     {
         /** @var ModuleFlatEntry*/
         $moduleFlatEntry = $moduleFlatEntries[$index] ?? [];
@@ -273,7 +301,7 @@ private function buildAllCombinations(array &$moduleFlatEntries, array &$combina
                 $moduleFlatEntry->archiveName => $versionStr
             ];
             $newVersionList = array_merge($versionList, $version);
-            $this->buildAllCombinations($moduleFlatEntries, $combinations, $index + 1, $newVersionList);
+            $this->buildAllCombinationsFromModuleFlatEntries($moduleFlatEntries, $combinations, $index + 1, $newVersionList);
         }
     }
 
@@ -283,7 +311,7 @@ private function buildAllCombinations(array &$moduleFlatEntries, array &$combina
      * @param string[] $compination [archiveName => version]
      * @param int $index
      */
-    private function buildAllCombinationsNew(ModuleFlatEntryList $moduleFlatEntryList, array &$combinations, array $compination = [], int $index = 0)
+    private function buildAllCombinationsFromModuleFlatEntryList(ModuleFlatEntryList $moduleFlatEntryList, array &$combinations, array $compination = [], int $index = 0)
     {
         /** @var ModuleFlatEntry*/
         $moduleFlatEntry = $moduleFlatEntryList->get($index);
@@ -296,15 +324,14 @@ private function buildAllCombinationsNew(ModuleFlatEntryList $moduleFlatEntryLis
         foreach ($moduleFlatEntry->versions as $versionStr) {
             $version = [$moduleFlatEntry->archiveName => $versionStr];
             $newCombination = array_merge($compination, $version);
-            $this->buildAllCombinationsNew($moduleFlatEntryList, $combinations, $newCombination, $index + 1);
+            $this->buildAllCombinationsFromModuleFlatEntryList($moduleFlatEntryList, $combinations, $newCombination, $index + 1);
         }
     }
 
-    private function satisfiesCominations(array $moduleTreeNodes, array $combinations)
+    private function satisfiesCominationsFromModuleTreeNodes(array $moduleTreeNodes, array $combinations)
     {
         foreach ($combinations as $combination) {
-            $combination['robinthehood/modified-orm'] = '1.7.0';
-            $result = $this->satisfiesComination($moduleTreeNodes, $combination);
+            $result = $this->satisfiesCominationFromModuleTreeNodes($moduleTreeNodes, $combination);
             if ($result) {
                 var_dump($combination);
                 var_dump($result);
@@ -313,7 +340,7 @@ private function satisfiesCominations(array $moduleTreeNodes, array $combination
         }
     }
 
-    private function satisfiesComination(array $moduleTreeNodes, array $combination): bool
+    private function satisfiesCominationFromModuleTreeNodes(array $moduleTreeNodes, array $combination): bool
     {
         // Context: Expanded
         $moduleResult = true;
@@ -325,7 +352,7 @@ private function satisfiesComination(array $moduleTreeNodes, array $combination)
             foreach ($moduleTreeNode->moduleVersions as $moduleVersion) {
                 // Context: Version
                 if ($moduleVersion->version === $selectedVersion) {
-                    $versionResult = $this->satisfiesComination($moduleVersion->require, $combination);
+                    $versionResult = $this->satisfiesCominationFromModuleTreeNodes($moduleVersion->require, $combination);
                     break;
                 }
             }
@@ -335,10 +362,10 @@ private function satisfiesComination(array $moduleTreeNodes, array $combination)
         return $moduleResult;
     }
 
-    private function satisfiesCominationsNew(ModuleTreeNode $moduleTreeNode, array $combinations)
+    private function satisfiesCominationsFromModuleTreeNode(ModuleTreeNode $moduleTreeNode, array $combinations)
     {
         foreach ($combinations as $combination) {
-            $result = $this->satisfiesCominationNew($moduleTreeNode, $combination);
+            $result = $this->satisfiesCominationFromModuleTreeNode($moduleTreeNode, $combination);
             if ($result) {
                 var_dump($combination);
                 var_dump($result);
@@ -347,11 +374,11 @@ private function satisfiesCominationsNew(ModuleTreeNode $moduleTreeNode, array $
         }
     }
 
-    private function satisfiesCominationsNewNew(ModuleTreeNode $moduleTreeNode, CombinationIterator $combinationIterator)
+    private function satisfiesCominationsFromModuleWithIterator(ModuleTreeNode $moduleTreeNode, CombinationIterator $combinationIterator)
     {
         while (true) {
             $combination = $combinationIterator->current();
-            $result = $this->satisfiesCominationNew($moduleTreeNode, $combination);
+            $result = $this->satisfiesCominationFromModuleTreeNode($moduleTreeNode, $combination);
             if ($result) {
                 var_dump($combination);
                 var_dump($result);
@@ -365,7 +392,7 @@ private function satisfiesCominationsNewNew(ModuleTreeNode $moduleTreeNode, Comb
         }
     }
 
-    public function satisfiesCominationNew(ModuleTreeNode $moduleTreeNode, array $combination): bool
+    public function satisfiesCominationFromModuleTreeNode(ModuleTreeNode $moduleTreeNode, array $combination): bool
     {
         // Context: Module
         $archiveName = $moduleTreeNode->archiveName;
@@ -373,18 +400,18 @@ public function satisfiesCominationNew(ModuleTreeNode $moduleTreeNode, array $co
         foreach ($moduleTreeNode->moduleVersions as $moduleVersion) {
             // Context: Version
             if ($moduleVersion->version === $selectedVersion) {
-                return $this->satisfiesCominationNew2($moduleVersion->require, $combination);
+                return $this->satisfiesCominationFromModuleTreeNode2($moduleVersion->require, $combination);
             }
         }
         return false;
     }
 
-    private function satisfiesCominationNew2(array $moduleTreeNodes, array $combination): bool
+    private function satisfiesCominationFromModuleTreeNode2(array $moduleTreeNodes, array $combination): bool
     {
         // Context: Expanded
         $moduleResult = true;
         foreach ($moduleTreeNodes as $moduleTreeNode) {
-            $moduleResult = $moduleResult && $this->satisfiesCominationNew($moduleTreeNode, $combination);
+            $moduleResult = $moduleResult && $this->satisfiesCominationFromModuleTreeNode($moduleTreeNode, $combination);
         }
         return $moduleResult;
     }

From 12fe8323b2249d8c0f6535f2947c48ee1c52ac3d Mon Sep 17 00:00:00 2001
From: Robin Wieschendorf 
Date: Tue, 7 Mar 2023 17:51:31 +0100
Subject: [PATCH 09/37] chore: improve code

---
 .../DependencyManager/DependencyBuilder.php   | 92 -------------------
 1 file changed, 92 deletions(-)

diff --git a/src/Classes/DependencyManager/DependencyBuilder.php b/src/Classes/DependencyManager/DependencyBuilder.php
index 9503d37e..39ef97db 100644
--- a/src/Classes/DependencyManager/DependencyBuilder.php
+++ b/src/Classes/DependencyManager/DependencyBuilder.php
@@ -71,7 +71,6 @@ public function satisfiesContraints1(Module $module, array $contraints): void
         $this->buildAllCombinationsFromModuleFlatEntries($moduleFlatEntries, $combinations);
         file_put_contents(__DIR__ . '/debug-log-combinations-obj.json', json_encode($combinations, JSON_PRETTY_PRINT));
 
-        //$value = $this->satisfiesCominationsFromModuleTreeNodes($moduleTreeNodes, $combinations);
         $combinationSatisfyer = new CombinationSatisfyer();
         $array = $combinationSatisfyer->satisfiesCominationsFromModuleTreeNodes($moduleTreeNodes, $combinations);
         var_dump($array);
@@ -95,7 +94,6 @@ public function satisfiesContraints2(string $archiveName, string $constraint, ar
         $this->buildAllCombinationsFromModuleFlatEntryList($moduleFlatEntryList, $combinations);
         file_put_contents(__DIR__ . '/debug-log-combinations-obj-new.json', json_encode($combinations, JSON_PRETTY_PRINT));
 
-        //$this->satisfiesCominationsFromModuleTreeNode($moduleTreeNode, $combinations);
         $combinationSatisfyer = new CombinationSatisfyer();
         $array = $combinationSatisfyer->satisfiesCominationsFromModuleTreeNode($moduleTreeNode, $combinations);
         var_dump($array);
@@ -115,8 +113,6 @@ public function satisfiesContraints3(string $archiveName, string $constraint, ar
         file_put_contents(__DIR__ . '/debug-log-flat-filtered-obj.json', json_encode($moduleFlatEntries, JSON_PRETTY_PRINT));
 
         $combinationIterator = new CombinationIterator($moduleFlatEntries);
-        //$this->satisfiesCominationsFromModuleWithIterator($moduleTreeNode, $combinationIterator);
-
         $combinationSatisfyer = new CombinationSatisfyer();
         $array = $combinationSatisfyer->satisfiesCominationsFromModuleWithIterator($moduleTreeNode, $combinationIterator);
         var_dump($array);
@@ -327,92 +323,4 @@ private function buildAllCombinationsFromModuleFlatEntryList(ModuleFlatEntryList
             $this->buildAllCombinationsFromModuleFlatEntryList($moduleFlatEntryList, $combinations, $newCombination, $index + 1);
         }
     }
-
-    private function satisfiesCominationsFromModuleTreeNodes(array $moduleTreeNodes, array $combinations)
-    {
-        foreach ($combinations as $combination) {
-            $result = $this->satisfiesCominationFromModuleTreeNodes($moduleTreeNodes, $combination);
-            if ($result) {
-                var_dump($combination);
-                var_dump($result);
-                break;
-            }
-        }
-    }
-
-    private function satisfiesCominationFromModuleTreeNodes(array $moduleTreeNodes, array $combination): bool
-    {
-        // Context: Expanded
-        $moduleResult = true;
-        foreach ($moduleTreeNodes as $moduleTreeNode) {
-            // Context: Module
-            $archiveName = $moduleTreeNode->archiveName;
-            $selectedVersion = $combination[$archiveName];
-            $versionResult = false;
-            foreach ($moduleTreeNode->moduleVersions as $moduleVersion) {
-                // Context: Version
-                if ($moduleVersion->version === $selectedVersion) {
-                    $versionResult = $this->satisfiesCominationFromModuleTreeNodes($moduleVersion->require, $combination);
-                    break;
-                }
-            }
-
-            $moduleResult = $moduleResult && $versionResult;
-        }
-        return $moduleResult;
-    }
-
-    private function satisfiesCominationsFromModuleTreeNode(ModuleTreeNode $moduleTreeNode, array $combinations)
-    {
-        foreach ($combinations as $combination) {
-            $result = $this->satisfiesCominationFromModuleTreeNode($moduleTreeNode, $combination);
-            if ($result) {
-                var_dump($combination);
-                var_dump($result);
-                break;
-            }
-        }
-    }
-
-    private function satisfiesCominationsFromModuleWithIterator(ModuleTreeNode $moduleTreeNode, CombinationIterator $combinationIterator)
-    {
-        while (true) {
-            $combination = $combinationIterator->current();
-            $result = $this->satisfiesCominationFromModuleTreeNode($moduleTreeNode, $combination);
-            if ($result) {
-                var_dump($combination);
-                var_dump($result);
-                return;
-            }
-
-            $combinationIterator->next();
-            if ($combinationIterator->isStart()) {
-                return;
-            }
-        }
-    }
-
-    public function satisfiesCominationFromModuleTreeNode(ModuleTreeNode $moduleTreeNode, array $combination): bool
-    {
-        // Context: Module
-        $archiveName = $moduleTreeNode->archiveName;
-        $selectedVersion = $combination[$archiveName];
-        foreach ($moduleTreeNode->moduleVersions as $moduleVersion) {
-            // Context: Version
-            if ($moduleVersion->version === $selectedVersion) {
-                return $this->satisfiesCominationFromModuleTreeNode2($moduleVersion->require, $combination);
-            }
-        }
-        return false;
-    }
-
-    private function satisfiesCominationFromModuleTreeNode2(array $moduleTreeNodes, array $combination): bool
-    {
-        // Context: Expanded
-        $moduleResult = true;
-        foreach ($moduleTreeNodes as $moduleTreeNode) {
-            $moduleResult = $moduleResult && $this->satisfiesCominationFromModuleTreeNode($moduleTreeNode, $combination);
-        }
-        return $moduleResult;
-    }
 }

From ebd767e913d6f55d81af0764cdc64dcbf4870198 Mon Sep 17 00:00:00 2001
From: Robin Wieschendorf 
Date: Tue, 7 Mar 2023 20:28:15 +0100
Subject: [PATCH 10/37] chore: improve code

---
 src/Classes/DependencyManager/Combination.php |  48 +++
 .../DependencyManager/CombinationBuilder.php  |  59 ++++
 .../DependencyManager/CombinationIterator.php |  36 +--
 .../CombinationSatisfyer.php                  |  77 +++--
 .../DependencyManager/DependencyBuilder.php   | 291 +++---------------
 .../{ModuleFlatEntry.php => FlatEntry.php}    |   2 +-
 .../DependencyManager/FlatEntryBuilder.php    | 113 +++++++
 .../DependencyManager/ModuleCombination.php   |  19 --
 .../DependencyManager/ModuleFlatEntryList.php |  38 ---
 .../{ModuleTreeNode.php => ModuleTree.php}    |   2 +-
 .../DependencyManager/ModuleTreeBuilder.php   | 113 +++++++
 .../DependencyManager/ModuleVersion.php       |   2 +-
 12 files changed, 436 insertions(+), 364 deletions(-)
 create mode 100644 src/Classes/DependencyManager/Combination.php
 create mode 100644 src/Classes/DependencyManager/CombinationBuilder.php
 rename src/Classes/DependencyManager/{ModuleFlatEntry.php => FlatEntry.php} (95%)
 create mode 100644 src/Classes/DependencyManager/FlatEntryBuilder.php
 delete mode 100644 src/Classes/DependencyManager/ModuleCombination.php
 delete mode 100644 src/Classes/DependencyManager/ModuleFlatEntryList.php
 rename src/Classes/DependencyManager/{ModuleTreeNode.php => ModuleTree.php} (96%)
 create mode 100644 src/Classes/DependencyManager/ModuleTreeBuilder.php

diff --git a/src/Classes/DependencyManager/Combination.php b/src/Classes/DependencyManager/Combination.php
new file mode 100644
index 00000000..9c4c1e09
--- /dev/null
+++ b/src/Classes/DependencyManager/Combination.php
@@ -0,0 +1,48 @@
+
+ *
+ * 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 Exception;
+
+class Combination
+{
+    /** @var array */
+    private $combinations = [];
+
+    public function add(string $archiveName, string $version)
+    {
+        if (array_key_exists($archiveName, $this->combinations)) {
+            throw new Exception($archiveName . ' is already set.');
+        }
+
+        $this->combinations[$archiveName] = $version;
+    }
+
+    public function getVersion(string $archiveName): string
+    {
+        if (!array_key_exists($archiveName, $this->combinations)) {
+            throw new Exception('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;
+    }
+}
diff --git a/src/Classes/DependencyManager/CombinationBuilder.php b/src/Classes/DependencyManager/CombinationBuilder.php
new file mode 100644
index 00000000..701418f8
--- /dev/null
+++ b/src/Classes/DependencyManager/CombinationBuilder.php
@@ -0,0 +1,59 @@
+
+ *
+ * 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 Exception;
+
+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 array $flatEntries
+     * @param int $index
+     * @param Combination $combination
+     */
+    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
index c002d804..d5181b61 100644
--- a/src/Classes/DependencyManager/CombinationIterator.php
+++ b/src/Classes/DependencyManager/CombinationIterator.php
@@ -15,8 +15,8 @@
 
 class CombinationIterator
 {
-    /** @var ModuleFlatEntry[] */
-    private $moduleFlatEntries;
+    /** @var FlatEntry[] */
+    private $flatEntries;
 
     /** @var int[] */
 
@@ -24,28 +24,28 @@ class CombinationIterator
     private $counter;
 
     /**
-     * @param ModuleFlatEntry[] $moduleFlatEntries
+     * @param FlatEntry[] $flatEntries
      */
-    public function __construct(array $moduleFlatEntries)
+    public function __construct(array $flatEntries)
     {
-        $this->moduleFlatEntries = $moduleFlatEntries;
+        $this->flatEntries = $flatEntries;
 
-        foreach ($moduleFlatEntries as $moduleFlatEntry) {
-            $counterMaxValues[] = count($moduleFlatEntry->versions) - 1;
+        foreach ($flatEntries as $flatEntry) {
+            $counterMaxValues[] = count($flatEntry->versions) - 1;
         }
 
         $this->counter = new Counter($counterMaxValues);
     }
 
-    public function current(): array
+    public function current(): Combination
     {
-        return $this->combinationFromCounter($this->moduleFlatEntries, $this->counter);
+        return $this->combinationFromCounter($this->flatEntries, $this->counter);
     }
 
-    public function next(): array
+    public function next(): Combination
     {
         $this->counter->next();
-        return $this->combinationFromCounter($this->moduleFlatEntries, $this->counter);
+        return $this->combinationFromCounter($this->flatEntries, $this->counter);
     }
 
     public function isStart(): bool
@@ -54,19 +54,19 @@ public function isStart(): bool
     }
 
     /**
-     * @param ModuleFlatEntry[] $moduleFlatEntries
+     * @param FlatEntry[] $flatEntries
+     * @param Counter Counter
      */
-    private function combinationFromCounter(array $moduleFlatEntries, Counter $counter): array
+    private function combinationFromCounter(array $flatEntries, Counter $counter): Combination
     {
         $counter = $counter->current();
 
         $counterIndex = 0;
-        $combination = [];
-        foreach ($moduleFlatEntries as $moduleFlatEntry) {
+        $combination = new Combination();
+        foreach ($flatEntries as $flatEntry) {
             $versionIndex = $counter[$counterIndex];
-            $versionStr = $moduleFlatEntry->versions[$versionIndex];
-            $version = [$moduleFlatEntry->archiveName => $versionStr];
-            $combination = array_merge($combination, $version);
+            $versionStr = $flatEntry->versions[$versionIndex];
+            $combination->add($flatEntry->archiveName, $versionStr);
             $counterIndex++;
         }
 
diff --git a/src/Classes/DependencyManager/CombinationSatisfyer.php b/src/Classes/DependencyManager/CombinationSatisfyer.php
index f1864371..49c0da3c 100644
--- a/src/Classes/DependencyManager/CombinationSatisfyer.php
+++ b/src/Classes/DependencyManager/CombinationSatisfyer.php
@@ -15,89 +15,88 @@
 
 class CombinationSatisfyer
 {
-    public function satisfiesCominationsFromModuleTreeNodes(array $moduleTreeNodes, array $combinations): array
+    /**
+     * @param ModuleTree[] $moduleTrees
+     * @param Combination[] $combinations
+     *
+     * @return Combination
+     */
+    public function satisfiesCominationsFromModuleTrees(array $moduleTrees, array $combinations): ?Combination
     {
         foreach ($combinations as $combination) {
-            $result = $this->satisfiesCominationFromModuleTreeNodes($moduleTreeNodes, $combination);
+            $result = $this->satisfiesCominationFromModuleTrees($moduleTrees, $combination);
             if ($result) {
                 return $combination;
             }
         }
-        return [];
+        return null;
     }
 
-    public function satisfiesCominationsFromModuleTreeNode(ModuleTreeNode $moduleTreeNode, array $combinations): array
+    /**
+     * @param ModuleTree $moduleTree
+     * @param Combination[] $combinations
+     *
+     * @return Combination
+     */
+    public function satisfiesCominationsFromModuleTree(ModuleTree $moduleTree, array $combinations): ?Combination
     {
         foreach ($combinations as $combination) {
-            $result = $this->satisfiesCominationFromModuleTreeNode($moduleTreeNode, $combination);
+            $result = $this->satisfiesCominationFromModuleTree($moduleTree, $combination);
             if ($result) {
                 return $combination;
             }
         }
-        return [];
+        return null;
     }
 
     public function satisfiesCominationsFromModuleWithIterator(
-        ModuleTreeNode $moduleTreeNode,
+        ModuleTree $moduleTree,
         CombinationIterator $combinationIterator
-    ): array {
+    ): ?Combination {
         while (true) {
             $combination = $combinationIterator->current();
-            $result = $this->satisfiesCominationFromModuleTreeNode($moduleTreeNode, $combination);
+            $result = $this->satisfiesCominationFromModuleTree($moduleTree, $combination);
             if ($result) {
                 return $combination;
             }
 
             $combinationIterator->next();
             if ($combinationIterator->isStart()) {
-                return [];
+                return null;
             }
         }
     }
 
-    public function satisfiesCominationFromModuleTreeNode(ModuleTreeNode $moduleTreeNode, array $combination): bool
+    /**
+     * @param ModuleTree $moduleTree
+     * @param Combination $combination
+     */
+    public function satisfiesCominationFromModuleTree(ModuleTree $moduleTree, Combination $combination): bool
     {
         // Context: Module
-        $archiveName = $moduleTreeNode->archiveName;
-        $selectedVersion = $combination[$archiveName];
-        foreach ($moduleTreeNode->moduleVersions as $moduleVersion) {
+        $archiveName = $moduleTree->archiveName;
+        $selectedVersion = $combination->getVersion($archiveName);
+        foreach ($moduleTree->moduleVersions as $moduleVersion) {
             // Context: Version
             if ($moduleVersion->version === $selectedVersion) {
-                return $this->satisfiesCominationFromModuleTreeNodes($moduleVersion->require, $combination);
+                return $this->satisfiesCominationFromModuleTrees($moduleVersion->require, $combination);
             }
         }
         return false;
     }
 
-    public function satisfiesCominationFromModuleTreeNodes(array $moduleTreeNodes, array $combination): bool
+    /**
+     * @param ModuleTree[] $moduleTrees
+     * @param Combination $combination
+     */
+    public function satisfiesCominationFromModuleTrees(array $moduleTrees, Combination $combination): bool
     {
         // Context: Expanded
         $moduleResult = true;
-        foreach ($moduleTreeNodes as $moduleTreeNode) {
+        foreach ($moduleTrees as $moduleTree) {
             $moduleResult =
-                $moduleResult && $this->satisfiesCominationFromModuleTreeNode($moduleTreeNode, $combination);
+                $moduleResult && $this->satisfiesCominationFromModuleTree($moduleTree, $combination);
         }
         return $moduleResult;
     }
-
-    // public function satisfiesCominationFromModuleTreeNodes(array $moduleTreeNodes, array $combination): bool
-    // {
-    //     // Context: Expanded
-    //     $moduleResult = true;
-    //     foreach ($moduleTreeNodes as $moduleTreeNode) {
-    //         // Context: Module
-    //         $archiveName = $moduleTreeNode->archiveName;
-    //         $selectedVersion = $combination[$archiveName];
-    //         $versionResult = false;
-    //         foreach ($moduleTreeNode->moduleVersions as $moduleVersion) {
-    //             // Context: Version
-    //             if ($moduleVersion->version === $selectedVersion) {
-    //                 $versionResult = $this->satisfiesCominationFromModuleTreeNodes($moduleVersion->require, $combination);
-    //                 break;
-    //             }
-    //         }
-    //         $moduleResult = $moduleResult && $versionResult;
-    //     }
-    //     return $moduleResult;
-    // }
 }
diff --git a/src/Classes/DependencyManager/DependencyBuilder.php b/src/Classes/DependencyManager/DependencyBuilder.php
index 39ef97db..a6fb50c3 100644
--- a/src/Classes/DependencyManager/DependencyBuilder.php
+++ b/src/Classes/DependencyManager/DependencyBuilder.php
@@ -13,13 +13,15 @@
 
 use RobinTheHood\ModifiedModuleLoaderClient\Loader\ModuleLoader;
 use RobinTheHood\ModifiedModuleLoaderClient\Module;
-use RobinTheHood\ModifiedModuleLoaderClient\ModuleFilter;
-use RobinTheHood\ModifiedModuleLoaderClient\ModuleSorter;
 
 class DependencyBuilder
 {
-    /** @var Module[] */
-    private $moduleCache = [];
+    private function log($value, $file)
+    {
+        @mkdir(__DIR__ . '/logs/');
+        $path = __DIR__ . '/logs/' . $file;
+        file_put_contents($path, json_encode($value, JSON_PRETTY_PRINT));
+    }
 
     public function test()
     {
@@ -28,7 +30,7 @@ public function test()
 
         $constraints = [
             //"composer/autoload" => ['1.2.0'],
-            //"robinthehood/modified-std-module" => ['0.1.0'],
+            "robinthehood/modified-std-module" => ['0.1.0'],
             //"robinthehood/modified-orm" => ['1.7.0']
             //"robinthehood/pdf-bill" => ['0.10.0']
         ];
@@ -56,271 +58,66 @@ public function test()
 
     public function satisfiesContraints1(Module $module, array $contraints): void
     {
-        $moduleTreeNodes = $this->buildModuleTreesByConstraints($module);
-        file_put_contents(__DIR__ . '/debug-log-tree-constraint-obj.json', json_encode($moduleTreeNodes, JSON_PRETTY_PRINT));
+        $moduleTreeBuilder = new ModuleTreeBuilder();
+        $moduleTrees = $moduleTreeBuilder->buildListByConstraints($module);
+        $this->log($moduleTrees, '1-moduleTrees.json');
 
-        $moduleFlatEntries = [];
-        $this->buildModuleFlatEntriesByModuleTreeNodes($moduleTreeNodes, $moduleFlatEntries);
-        file_put_contents(__DIR__ . '/debug-log-flat-obj.json', json_encode($moduleFlatEntries, JSON_PRETTY_PRINT));
+        $flatEntryBuilder = new FlatEntryBuilder();
+        $flatEntries = $flatEntryBuilder->buildListFromModuleTrees($moduleTrees);
+        $this->log($flatEntries, '1-flatEntries.json');
 
-        $moduleFlatEntries = $this->removeModuleFlatEnties($moduleFlatEntries, $contraints);
-        file_put_contents(__DIR__ . '/debug-log-flat-filtered-obj.json', json_encode($moduleFlatEntries, JSON_PRETTY_PRINT));
+        $flatEntries = $flatEntryBuilder->removeFlatEntriesByContrains($flatEntries, $contraints);
+        $this->log($flatEntries, '1-flatEntries-removed.json');
 
-        $combinations = [];
-        $moduleFlatEntries = array_values($moduleFlatEntries);
-        $this->buildAllCombinationsFromModuleFlatEntries($moduleFlatEntries, $combinations);
-        file_put_contents(__DIR__ . '/debug-log-combinations-obj.json', json_encode($combinations, JSON_PRETTY_PRINT));
+        $combinationBuilder = new CombinationBuilder();
+        $combinations = $combinationBuilder->buildAllFromModuleFlatEntries($flatEntries);
+        $this->log($combinations, '1-combinations.json');
 
         $combinationSatisfyer = new CombinationSatisfyer();
-        $array = $combinationSatisfyer->satisfiesCominationsFromModuleTreeNodes($moduleTreeNodes, $combinations);
-        var_dump($array);
+        $combination = $combinationSatisfyer->satisfiesCominationsFromModuleTrees($moduleTrees, $combinations);
+        var_dump($combination);
     }
 
 
     public function satisfiesContraints2(string $archiveName, string $constraint, array $contraints): void
     {
-        $moduleTreeNode = $this->buildModuleTreeByConstraints($archiveName, $constraint);
-        file_put_contents(__DIR__ . '/debug-log-tree-constraint-obj-new.json', json_encode($moduleTreeNode, JSON_PRETTY_PRINT));
+        $moduleTreeBuilder = new ModuleTreeBuilder();
+        $moduleTree = $moduleTreeBuilder->buildByConstraints($archiveName, $constraint);
+        $this->log($moduleTree, '2-moduleTrees.json');
 
-        $moduleFlatEntries = [];
-        $this->buildModuleFlatEntriesByModuleTreeNode($moduleTreeNode, $moduleFlatEntries);
-        file_put_contents(__DIR__ . '/debug-log-flat-obj-new.json', json_encode($moduleFlatEntries, JSON_PRETTY_PRINT));
+        $flatEntryBuilder = new FlatEntryBuilder();
+        $flatEntries = $flatEntryBuilder->buildListFromModuleTree($moduleTree);
+        $this->log($flatEntries, '2-flatEntries.json');
 
-        $moduleFlatEntries = $this->removeModuleFlatEnties($moduleFlatEntries, $contraints);
-        file_put_contents(__DIR__ . '/debug-log-flat-filtered-obj.json', json_encode($moduleFlatEntries, JSON_PRETTY_PRINT));
+        $flatEntries = $flatEntryBuilder->removeFlatEntriesByContrains($flatEntries, $contraints);
+        $this->log($flatEntries, '2-flatEntries-removed.json');
 
-        $combinations = [];
-        $moduleFlatEntryList = new ModuleFlatEntryList($moduleFlatEntries);
-        $this->buildAllCombinationsFromModuleFlatEntryList($moduleFlatEntryList, $combinations);
-        file_put_contents(__DIR__ . '/debug-log-combinations-obj-new.json', json_encode($combinations, JSON_PRETTY_PRINT));
+        $combinationBuilder = new CombinationBuilder();
+        $combinations = $combinationBuilder->buildAllFromModuleFlatEntries($flatEntries);
+        $this->log($combinations, '2-combinations.json');
 
         $combinationSatisfyer = new CombinationSatisfyer();
-        $array = $combinationSatisfyer->satisfiesCominationsFromModuleTreeNode($moduleTreeNode, $combinations);
-        var_dump($array);
+        $combination = $combinationSatisfyer->satisfiesCominationsFromModuleTree($moduleTree, $combinations);
+        var_dump($combination);
     }
 
 
     public function satisfiesContraints3(string $archiveName, string $constraint, array $contraints): void
     {
-        $moduleTreeNode = $this->buildModuleTreeByConstraints($archiveName, $constraint);
-        file_put_contents(__DIR__ . '/debug-log-tree-constraint-obj-new.json', json_encode($moduleTreeNode, JSON_PRETTY_PRINT));
+        $moduleTreeBuilder = new ModuleTreeBuilder();
+        $moduleTree = $moduleTreeBuilder->buildByConstraints($archiveName, $constraint);
+        $this->log($moduleTree, '3-moduleTrees.json');
 
-        $moduleFlatEntries = [];
-        $this->buildModuleFlatEntriesByModuleTreeNode($moduleTreeNode, $moduleFlatEntries);
-        file_put_contents(__DIR__ . '/debug-log-flat-obj-new.json', json_encode($moduleFlatEntries, JSON_PRETTY_PRINT));
+        $flatEntryBuilder = new FlatEntryBuilder();
+        $flatEntries = $flatEntryBuilder->buildListFromModuleTree($moduleTree);
+        $this->log($flatEntries, '3-flatEntries.json');
 
-        $moduleFlatEntries = $this->removeModuleFlatEnties($moduleFlatEntries, $contraints);
-        file_put_contents(__DIR__ . '/debug-log-flat-filtered-obj.json', json_encode($moduleFlatEntries, JSON_PRETTY_PRINT));
+        $flatEntries = $flatEntryBuilder->removeFlatEntriesByContrains($flatEntries, $contraints);
+        $this->log($flatEntries, '3-flatEntries-removed.json');
 
-        $combinationIterator = new CombinationIterator($moduleFlatEntries);
+        $combinationIterator = new CombinationIterator($flatEntries);
         $combinationSatisfyer = new CombinationSatisfyer();
-        $array = $combinationSatisfyer->satisfiesCominationsFromModuleWithIterator($moduleTreeNode, $combinationIterator);
-        var_dump($array);
-    }
-
-    /**
-     * @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 ModuleTreeNode[]
-     */
-    private function buildModuleTreesByConstraints(Module $module, int $depth = 0): array
-    {
-        if ($depth >= 10) {
-            return [];
-        }
-
-        $require = $module->getRequire();
-
-        $moduleTreeNodes = [];
-        foreach ($require as $archiveName => $versionConstraint) {
-            // Modules to Entry
-            $moduleTreeNode = new ModuleTreeNode();
-            $moduleTreeNode->archiveName = $archiveName;
-            $moduleTreeNode->versionConstraint = $versionConstraint;
-
-            // Fetch Versions
-            $modules = $this->loadAllByArchiveNameAndConstraint($archiveName, $versionConstraint);
-
-            // VersionList
-            foreach ($modules as $module) {
-                $moduleVersion = new ModuleVersion();
-                $moduleVersion->version = $module->getVersion();
-                $moduleVersion->require = $this->buildModuleTreesByConstraints($module, $depth + 1);
-                $moduleTreeNode->moduleVersions[$moduleVersion->version] = $moduleVersion;
-            }
-
-            $moduleTreeNodes[] = $moduleTreeNode;
-        }
-
-        return $moduleTreeNodes;
-    }
-
-    /**
-     * @param string $archiveName
-     * @param string $versionConstraint
-     * @param int $depth
-     */
-    private function buildModuleTreeByConstraints(string $archiveName, string $versionConstraint, int $depth = 0): ModuleTreeNode
-    {
-        $moduleTreeNode = new ModuleTreeNode();
-        $moduleTreeNode->archiveName = $archiveName;
-        $moduleTreeNode->versionConstraint = $versionConstraint;
-
-        $modules = $this->loadAllByArchiveNameAndConstraint($archiveName, $versionConstraint);
-
-        $moduleVersions = [];
-        foreach ($modules as $module) {
-            // Context: Module
-            $moduleVersion = new ModuleVersion();
-            $moduleVersion->version = $module->getVersion();
-
-            if ($depth < 10) {
-                $require = $module->getRequire();
-                foreach ($require as $archiveName => $versionConstraint) {
-                    // Context: require
-                    $moduleVersion->require[] = $this->buildModuleTreeByConstraints($archiveName, $versionConstraint, $depth + 1);
-                }
-            }
-            $moduleVersions[] = $moduleVersion;
-        }
-        $moduleTreeNode->moduleVersions = $moduleVersions;
-
-        return $moduleTreeNode;
-    }
-
-    /**
-     * @param ModuleTreeNode[] $moduleTreeNodes
-     * @param ModuleFlatEntry[] $moduleFlatEntries
-     */
-    private function buildModuleFlatEntriesByModuleTreeNodes(array $moduleTreeNodes, array &$moduleFlatEntries): void
-    {
-        if (!$moduleTreeNodes) {
-            return;
-        }
-
-        foreach ($moduleTreeNodes as $moduleTreeNode) {
-            $moduleFlatEntry = new ModuleFlatEntry();
-            $moduleFlatEntry->archiveName = $moduleTreeNode->archiveName;
-            foreach ($moduleTreeNode->moduleVersions as $moduleVersion) {
-                $moduleFlatEntry->versions[] = $moduleVersion->version;
-                $this->buildModuleFlatEntriesByModuleTreeNodes($moduleVersion->require, $moduleFlatEntries);
-            }
-            $moduleFlatEntries[$moduleTreeNode->archiveName] = $moduleFlatEntry;
-        }
-    }
-
-    /**
-     * @param ModuleTreeNode $moduleTreeNode
-     * @param ModuleFlatEntry[] $moduleFlatEntries
-     */
-    private function buildModuleFlatEntriesByModuleTreeNode(ModuleTreeNode $moduleTreeNode, array &$moduleFlatEntries): void
-    {
-        $moduleFlatEntry = new ModuleFlatEntry();
-        $moduleFlatEntry->archiveName = $moduleTreeNode->archiveName;
-        $moduleFlatEntries[$moduleTreeNode->archiveName] = $moduleFlatEntry;
-        foreach ($moduleTreeNode->moduleVersions as $moduleVersion) {
-            $moduleFlatEntry->versions[] = $moduleVersion->version;
-            foreach ($moduleVersion->require as $moduleTreeNode) {
-                $this->buildModuleFlatEntriesByModuleTreeNode($moduleTreeNode, $moduleFlatEntries);
-            }
-        }
-    }
-
-    private function removeModuleFlatEnties(array $moduleFlatTreeEntries, $contraints): array
-    {
-        foreach ($contraints as $archiveName => $versions) {
-            $moduleFlatTreeEntries = $this->removeModuleFlatEnty($moduleFlatTreeEntries, $archiveName, $versions);
-        }
-        return $moduleFlatTreeEntries;
-    }
-
-    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 ModuleFlatEntry();
-            $newModuleFlatTreeEntry->archiveName = $moduleFlatTreeEntry->archiveName;
-            $newModuleFlatTreeEntry->versions = $fileredVersions;
-            $filteredModuleFlatTreeEntries[$moduleFlatTreeEntry->archiveName] = $newModuleFlatTreeEntry;
-        }
-        return $filteredModuleFlatTreeEntries;
-    }
-
-    /**
-     * @param ModuleFlatEntry[] $moduleFlatEntries
-     * @param array $moduleFlatEntries
-     * @param int $index
-     * @param string[] $versionList
-     */
-    private function buildAllCombinationsFromModuleFlatEntries(array &$moduleFlatEntries, array &$combinations, int $index = 0, array $versionList = [])
-    {
-        /** @var ModuleFlatEntry*/
-        $moduleFlatEntry = $moduleFlatEntries[$index] ?? [];
-
-        if (!$moduleFlatEntry) {
-            $combinations[] = $versionList;
-            return;
-        }
-
-        foreach ($moduleFlatEntry->versions as $versionStr) {
-            $version = [
-                $moduleFlatEntry->archiveName => $versionStr
-            ];
-            $newVersionList = array_merge($versionList, $version);
-            $this->buildAllCombinationsFromModuleFlatEntries($moduleFlatEntries, $combinations, $index + 1, $newVersionList);
-        }
-    }
-
-    /**
-     * @param ModuleFlatEntryList $moduleFlatEntryList
-     * @param array $combinations [compination, compination, compination ...]
-     * @param string[] $compination [archiveName => version]
-     * @param int $index
-     */
-    private function buildAllCombinationsFromModuleFlatEntryList(ModuleFlatEntryList $moduleFlatEntryList, array &$combinations, array $compination = [], int $index = 0)
-    {
-        /** @var ModuleFlatEntry*/
-        $moduleFlatEntry = $moduleFlatEntryList->get($index);
-
-        if (!$moduleFlatEntry) {
-            $combinations[] = $compination;
-            return;
-        }
-
-        foreach ($moduleFlatEntry->versions as $versionStr) {
-            $version = [$moduleFlatEntry->archiveName => $versionStr];
-            $newCombination = array_merge($compination, $version);
-            $this->buildAllCombinationsFromModuleFlatEntryList($moduleFlatEntryList, $combinations, $newCombination, $index + 1);
-        }
+        $combination = $combinationSatisfyer->satisfiesCominationsFromModuleWithIterator($moduleTree, $combinationIterator);
+        var_dump($combination);
     }
 }
diff --git a/src/Classes/DependencyManager/ModuleFlatEntry.php b/src/Classes/DependencyManager/FlatEntry.php
similarity index 95%
rename from src/Classes/DependencyManager/ModuleFlatEntry.php
rename to src/Classes/DependencyManager/FlatEntry.php
index 57027a30..822ac974 100644
--- a/src/Classes/DependencyManager/ModuleFlatEntry.php
+++ b/src/Classes/DependencyManager/FlatEntry.php
@@ -13,7 +13,7 @@
 
 namespace RobinTheHood\ModifiedModuleLoaderClient\DependencyManager;
 
-class ModuleFlatEntry
+class FlatEntry
 {
     /** @var string */
     public $archiveName;
diff --git a/src/Classes/DependencyManager/FlatEntryBuilder.php b/src/Classes/DependencyManager/FlatEntryBuilder.php
new file mode 100644
index 00000000..1b014567
--- /dev/null
+++ b/src/Classes/DependencyManager/FlatEntryBuilder.php
@@ -0,0 +1,113 @@
+
+ *
+ * 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 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);
+            }
+            $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;
+        $flatEntries[$moduleTree->archiveName] = $flatEntry;
+        foreach ($moduleTree->moduleVersions as $moduleVersion) {
+            $flatEntry->versions[] = $moduleVersion->version;
+            foreach ($moduleVersion->require as $moduleTree) {
+                $this->buildModuleFlatEntriesByModuleTree($moduleTree, $flatEntries);
+            }
+        }
+    }
+
+    public function removeFlatEntriesByContrains(array $moduleFlatTreeEntries, $contraints): array
+    {
+        foreach ($contraints as $archiveName => $versions) {
+            $moduleFlatTreeEntries = $this->removeModuleFlatEnty($moduleFlatTreeEntries, $archiveName, $versions);
+        }
+        return $moduleFlatTreeEntries;
+    }
+
+    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/ModuleCombination.php b/src/Classes/DependencyManager/ModuleCombination.php
deleted file mode 100644
index 681c17a8..00000000
--- a/src/Classes/DependencyManager/ModuleCombination.php
+++ /dev/null
@@ -1,19 +0,0 @@
-
- *
- * 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 ModuleCombination
-{
-    public $moduleVersion = [];
-}
diff --git a/src/Classes/DependencyManager/ModuleFlatEntryList.php b/src/Classes/DependencyManager/ModuleFlatEntryList.php
deleted file mode 100644
index da122b5d..00000000
--- a/src/Classes/DependencyManager/ModuleFlatEntryList.php
+++ /dev/null
@@ -1,38 +0,0 @@
-
- *
- * 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 ModuleFlatEntryList
-{
-    /** @var ModuleFlatEntry[] */
-    private $moduleFlatEntries = [];
-
-    /**
-     * @param ModuleFlatEntry[] $moduleFlatEntries
-     */
-    public function __construct(array $moduleFlatEntries)
-    {
-        $this->moduleFlatEntries = array_values($moduleFlatEntries);
-    }
-
-    public function get(int $index): ?ModuleFlatEntry
-    {
-        return $this->moduleFlatEntries[$index] ?? null;
-    }
-
-    public function getAll(): array
-    {
-        return $this->moduleFlatEntries;
-    }
-}
diff --git a/src/Classes/DependencyManager/ModuleTreeNode.php b/src/Classes/DependencyManager/ModuleTree.php
similarity index 96%
rename from src/Classes/DependencyManager/ModuleTreeNode.php
rename to src/Classes/DependencyManager/ModuleTree.php
index b675932e..03950c3c 100644
--- a/src/Classes/DependencyManager/ModuleTreeNode.php
+++ b/src/Classes/DependencyManager/ModuleTree.php
@@ -13,7 +13,7 @@
 
 namespace RobinTheHood\ModifiedModuleLoaderClient\DependencyManager;
 
-class ModuleTreeNode
+class ModuleTree
 {
     /** @var string */
     public $archiveName = '';
diff --git a/src/Classes/DependencyManager/ModuleTreeBuilder.php b/src/Classes/DependencyManager/ModuleTreeBuilder.php
new file mode 100644
index 00000000..4850bac3
--- /dev/null
+++ b/src/Classes/DependencyManager/ModuleTreeBuilder.php
@@ -0,0 +1,113 @@
+
+ *
+ * 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 Module[] */
+    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 = [];
+        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) {
+                $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;
+    }
+}
diff --git a/src/Classes/DependencyManager/ModuleVersion.php b/src/Classes/DependencyManager/ModuleVersion.php
index c3c65728..619bdfcb 100644
--- a/src/Classes/DependencyManager/ModuleVersion.php
+++ b/src/Classes/DependencyManager/ModuleVersion.php
@@ -18,6 +18,6 @@ class ModuleVersion
     /** @var string */
     public $version = '';
 
-    /** @var ModuleTreeNode[] **/
+    /** @var ModuleTree[] **/
     public $require = [];
 }

From d480dbbe4d529adca9366ef55ff902e9b252eefa Mon Sep 17 00:00:00 2001
From: Robin Wieschendorf 
Date: Tue, 7 Mar 2023 22:35:27 +0100
Subject: [PATCH 11/37] chore: handle modified compatibility

---
 src/Classes/DependencyManager/Combination.php |  2 +-
 .../CombinationSatisfyer.php                  |  4 +++
 .../DependencyManager/DependencyBuilder.php   | 11 +++---
 src/Classes/DependencyManager/FlatEntry.php   | 15 +++++++-
 .../DependencyManager/FlatEntryBuilder.php    | 36 +++++++++++++++----
 .../DependencyManager/ModuleTreeBuilder.php   | 22 ++++++++++++
 6 files changed, 76 insertions(+), 14 deletions(-)

diff --git a/src/Classes/DependencyManager/Combination.php b/src/Classes/DependencyManager/Combination.php
index 9c4c1e09..e41de30f 100644
--- a/src/Classes/DependencyManager/Combination.php
+++ b/src/Classes/DependencyManager/Combination.php
@@ -18,7 +18,7 @@
 class Combination
 {
     /** @var array */
-    private $combinations = [];
+    public $combinations = [];
 
     public function add(string $archiveName, string $version)
     {
diff --git a/src/Classes/DependencyManager/CombinationSatisfyer.php b/src/Classes/DependencyManager/CombinationSatisfyer.php
index 49c0da3c..0c65fb60 100644
--- a/src/Classes/DependencyManager/CombinationSatisfyer.php
+++ b/src/Classes/DependencyManager/CombinationSatisfyer.php
@@ -78,10 +78,14 @@ public function satisfiesCominationFromModuleTree(ModuleTree $moduleTree, Combin
         $selectedVersion = $combination->getVersion($archiveName);
         foreach ($moduleTree->moduleVersions as $moduleVersion) {
             // Context: Version
+            //var_dump($archiveName . " {$moduleVersion->version} == {$selectedVersion}");
             if ($moduleVersion->version === $selectedVersion) {
+                //var_dump('x');
                 return $this->satisfiesCominationFromModuleTrees($moduleVersion->require, $combination);
             }
         }
+        // var_dump('xxx');
+        // var_dump($moduleTree);
         return false;
     }
 
diff --git a/src/Classes/DependencyManager/DependencyBuilder.php b/src/Classes/DependencyManager/DependencyBuilder.php
index a6fb50c3..43443bb7 100644
--- a/src/Classes/DependencyManager/DependencyBuilder.php
+++ b/src/Classes/DependencyManager/DependencyBuilder.php
@@ -29,10 +29,11 @@ public function test()
         $module = $moduleLoader->loadLatestVersionByArchiveName('firstweb/multi-order');
 
         $constraints = [
-            //"composer/autoload" => ['1.2.0'],
-            "robinthehood/modified-std-module" => ['0.1.0'],
-            //"robinthehood/modified-orm" => ['1.7.0']
-            //"robinthehood/pdf-bill" => ['0.10.0']
+            // "modified" => ['2.0.4.2'],
+            "composer/autoload" => ['1.3.0'],
+            // "robinthehood/modified-std-module" => ['0.9.0'],
+            // "robinthehood/modified-orm" => ['1.8.1'],
+            // "robinthehood/pdf-bill" => ['0.17.0']
         ];
 
         var_dump('TEST: satisfiesContraints1');
@@ -94,7 +95,7 @@ public function satisfiesContraints2(string $archiveName, string $constraint, ar
 
         $combinationBuilder = new CombinationBuilder();
         $combinations = $combinationBuilder->buildAllFromModuleFlatEntries($flatEntries);
-        $this->log($combinations, '2-combinations.json');
+        // $this->log($combinations, '2-combinations.json');
 
         $combinationSatisfyer = new CombinationSatisfyer();
         $combination = $combinationSatisfyer->satisfiesCominationsFromModuleTree($moduleTree, $combinations);
diff --git a/src/Classes/DependencyManager/FlatEntry.php b/src/Classes/DependencyManager/FlatEntry.php
index 822ac974..b62e1569 100644
--- a/src/Classes/DependencyManager/FlatEntry.php
+++ b/src/Classes/DependencyManager/FlatEntry.php
@@ -13,11 +13,24 @@
 
 namespace RobinTheHood\ModifiedModuleLoaderClient\DependencyManager;
 
+use Exception;
+
 class FlatEntry
 {
     /** @var string */
     public $archiveName;
 
     /** @var array version strings */
-    public $versions;
+    public $versions = [];
+
+    public function combine(FlatEntry $flatEntry)
+    {
+        if ($this->archiveName !== $flatEntry->archiveName) {
+            throw new Exception("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
index 1b014567..c35a09b4 100644
--- a/src/Classes/DependencyManager/FlatEntryBuilder.php
+++ b/src/Classes/DependencyManager/FlatEntryBuilder.php
@@ -37,6 +37,20 @@ public function buildListFromModuleTree(ModuleTree $moduleTree): array
         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
@@ -54,14 +68,10 @@ private function buildModuleFlatEntriesByModuleTrees(array $moduleTrees, array &
                 $flatEntry->versions[] = $moduleVersion->version;
                 $this->buildModuleFlatEntriesByModuleTrees($moduleVersion->require, $flatEntries);
             }
-            $flatEntries[$moduleTree->archiveName] = $flatEntry;
+            $this->addFlatEntry($flatEntries, $moduleTree->archiveName, $flatEntry);
         }
     }
 
-
-
-
-
     /**
      * @param ModuleTree $moduleTree
      * @param FlatEntry[] $flatEntries
@@ -70,16 +80,21 @@ private function buildModuleFlatEntriesByModuleTree(ModuleTree $moduleTree, arra
     {
         $flatEntry = new FlatEntry();
         $flatEntry->archiveName = $moduleTree->archiveName;
-        $flatEntries[$moduleTree->archiveName] = $flatEntry;
         foreach ($moduleTree->moduleVersions as $moduleVersion) {
             $flatEntry->versions[] = $moduleVersion->version;
             foreach ($moduleVersion->require as $moduleTree) {
                 $this->buildModuleFlatEntriesByModuleTree($moduleTree, $flatEntries);
             }
         }
+        $this->addFlatEntry($flatEntries, $flatEntry->archiveName, $flatEntry);
     }
 
-    public function removeFlatEntriesByContrains(array $moduleFlatTreeEntries, $contraints): array
+    /**
+     * @param FlatEntry[] $moduleFlatTreeEntries
+     * @param array $contraints
+     * @return FlatEntry[]
+     */
+    public function removeFlatEntriesByContrains(array $moduleFlatTreeEntries, array $contraints): array
     {
         foreach ($contraints as $archiveName => $versions) {
             $moduleFlatTreeEntries = $this->removeModuleFlatEnty($moduleFlatTreeEntries, $archiveName, $versions);
@@ -87,6 +102,13 @@ public function removeFlatEntriesByContrains(array $moduleFlatTreeEntries, $cont
         return $moduleFlatTreeEntries;
     }
 
+    /**
+     * @param FlatEntry[] $moduleFlatTreeEntries
+     * @param string $archiveName
+     * @param string[] $versions
+     *
+     * @return FlatEntry[]
+     */
     private function removeModuleFlatEnty(array $moduleFlatTreeEntries, string $archiveName, array $versions): array
     {
         $filteredModuleFlatTreeEntries = [];
diff --git a/src/Classes/DependencyManager/ModuleTreeBuilder.php b/src/Classes/DependencyManager/ModuleTreeBuilder.php
index 4850bac3..9a1ce312 100644
--- a/src/Classes/DependencyManager/ModuleTreeBuilder.php
+++ b/src/Classes/DependencyManager/ModuleTreeBuilder.php
@@ -55,6 +55,7 @@ public function buildListByConstraints(Module $module, int $depth = 0): array
         $require = $module->getRequire();
 
         $moduleTrees = [];
+        $moduleTrees[] = $this->createModifed($module);
         foreach ($require as $archiveName => $versionConstraint) {
             // Modules to Entry
             $moduleTree = new ModuleTree();
@@ -69,6 +70,7 @@ public function buildListByConstraints(Module $module, int $depth = 0): array
                 $moduleVersion = new ModuleVersion();
                 $moduleVersion->version = $module->getVersion();
                 $moduleVersion->require = $this->buildListByConstraints($module, $depth + 1);
+                //$moduleVersion->require[] = $this->createModifed($module);
                 $moduleTree->moduleVersions[$moduleVersion->version] = $moduleVersion;
             }
 
@@ -78,6 +80,26 @@ public function buildListByConstraints(Module $module, int $depth = 0): array
         return $moduleTrees;
     }
 
+    /**
+     * @return ModuleTree[]
+     */
+    private function createModifed(Module $module): ModuleTree
+    {
+        $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);
+
+        return $moduleTree;
+    }
     /**
      * @param string $archiveName
      * @param string $versionConstraint

From 64b104e9ebe8f110dee777d641150a7dabc026f8 Mon Sep 17 00:00:00 2001
From: Robin Wieschendorf 
Date: Thu, 9 Mar 2023 15:27:48 +0100
Subject: [PATCH 12/37] chore: handle php version

---
 .../DependencyManager/CombinationIterator.php |  6 ++
 .../CombinationSatisfyer.php                  | 18 +++++
 src/Classes/DependencyManager/Counter.php     |  2 +-
 .../DependencyManager/DependencyBuilder.php   | 51 ++++++++-----
 .../DependencyManager/FlatEntryBuilder.php    | 74 ++++++++++++++++++-
 .../DependencyManager/ModuleTreeBuilder.php   | 73 ++++++++++++------
 src/Classes/DependencyManager/SystemSet.php   | 19 +++++
 tests/unit/DependencyManager/CounterTest.php  | 18 +++++
 8 files changed, 215 insertions(+), 46 deletions(-)
 create mode 100644 src/Classes/DependencyManager/SystemSet.php

diff --git a/src/Classes/DependencyManager/CombinationIterator.php b/src/Classes/DependencyManager/CombinationIterator.php
index d5181b61..a9e27a0e 100644
--- a/src/Classes/DependencyManager/CombinationIterator.php
+++ b/src/Classes/DependencyManager/CombinationIterator.php
@@ -31,6 +31,9 @@ public function __construct(array $flatEntries)
         $this->flatEntries = $flatEntries;
 
         foreach ($flatEntries as $flatEntry) {
+            if (!$flatEntry->versions) {
+                continue;
+            }
             $counterMaxValues[] = count($flatEntry->versions) - 1;
         }
 
@@ -64,6 +67,9 @@ private function combinationFromCounter(array $flatEntries, Counter $counter): C
         $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);
diff --git a/src/Classes/DependencyManager/CombinationSatisfyer.php b/src/Classes/DependencyManager/CombinationSatisfyer.php
index 0c65fb60..994d44da 100644
--- a/src/Classes/DependencyManager/CombinationSatisfyer.php
+++ b/src/Classes/DependencyManager/CombinationSatisfyer.php
@@ -13,8 +13,19 @@
 
 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
@@ -76,6 +87,13 @@ public function satisfiesCominationFromModuleTree(ModuleTree $moduleTree, Combin
         // Context: Module
         $archiveName = $moduleTree->archiveName;
         $selectedVersion = $combination->getVersion($archiveName);
+
+        // Es gibt keine weiteren Untermodule
+        if (!$moduleTree->moduleVersions) {
+            //var_dump($selectedVersion . ' == ' . $moduleTree->versionConstraint);
+            return $this->comparator->satisfiesOr($selectedVersion, $moduleTree->versionConstraint);
+        }
+
         foreach ($moduleTree->moduleVersions as $moduleVersion) {
             // Context: Version
             //var_dump($archiveName . " {$moduleVersion->version} == {$selectedVersion}");
diff --git a/src/Classes/DependencyManager/Counter.php b/src/Classes/DependencyManager/Counter.php
index 924c53d0..1821451a 100644
--- a/src/Classes/DependencyManager/Counter.php
+++ b/src/Classes/DependencyManager/Counter.php
@@ -39,7 +39,7 @@ public function next(): array
 
     public function isStart(): bool
     {
-        foreach ($this->counterMaxValues as $value) {
+        foreach ($this->counterValues as $value) {
             if ($value !== 0) {
                 return false;
             }
diff --git a/src/Classes/DependencyManager/DependencyBuilder.php b/src/Classes/DependencyManager/DependencyBuilder.php
index 43443bb7..438e98a3 100644
--- a/src/Classes/DependencyManager/DependencyBuilder.php
+++ b/src/Classes/DependencyManager/DependencyBuilder.php
@@ -28,36 +28,38 @@ public function test()
         $moduleLoader = ModuleLoader::getModuleLoader();
         $module = $moduleLoader->loadLatestVersionByArchiveName('firstweb/multi-order');
 
-        $constraints = [
-            // "modified" => ['2.0.4.2'],
-            "composer/autoload" => ['1.3.0'],
-            // "robinthehood/modified-std-module" => ['0.9.0'],
-            // "robinthehood/modified-orm" => ['1.8.1'],
-            // "robinthehood/pdf-bill" => ['0.17.0']
+        $systemSet = new SystemSet();
+        $systemSet->systems = [
+            "modified" => '2.0.4.2',
+            //"php" => '7.3.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'
         ];
 
         var_dump('TEST: satisfiesContraints1');
         $this->satisfiesContraints1(
             $module,
-            $constraints
+            $systemSet
         );
 
         var_dump('TEST: satisfiesContraints2');
         $this->satisfiesContraints2(
             'firstweb/multi-order',
-            '^1.13.0',
-            $constraints
+            '^1.0.0',
+            $systemSet
         );
 
         var_dump('TEST: satisfiesContraints3');
         $this->satisfiesContraints3(
             'firstweb/multi-order',
             '^1.0.0',
-            $constraints
+            $systemSet
         );
     }
 
-    public function satisfiesContraints1(Module $module, array $contraints): void
+    public function satisfiesContraints1(Module $module, SystemSet $systemSet): void
     {
         $moduleTreeBuilder = new ModuleTreeBuilder();
         $moduleTrees = $moduleTreeBuilder->buildListByConstraints($module);
@@ -67,8 +69,11 @@ public function satisfiesContraints1(Module $module, array $contraints): void
         $flatEntries = $flatEntryBuilder->buildListFromModuleTrees($moduleTrees);
         $this->log($flatEntries, '1-flatEntries.json');
 
-        $flatEntries = $flatEntryBuilder->removeFlatEntriesByContrains($flatEntries, $contraints);
-        $this->log($flatEntries, '1-flatEntries-removed.json');
+        // $flatEntries = $flatEntryBuilder->removeFlatEntriesBySystemSet($flatEntries, $systemSet);
+        // $this->log($flatEntries, '1-flatEntries-removed.json');
+
+        $flatEntries = $flatEntryBuilder->fitSystemSet($flatEntries, $systemSet);
+        $this->log($flatEntries, '1-flatEntries-fit.json');
 
         $combinationBuilder = new CombinationBuilder();
         $combinations = $combinationBuilder->buildAllFromModuleFlatEntries($flatEntries);
@@ -80,7 +85,7 @@ public function satisfiesContraints1(Module $module, array $contraints): void
     }
 
 
-    public function satisfiesContraints2(string $archiveName, string $constraint, array $contraints): void
+    public function satisfiesContraints2(string $archiveName, string $constraint, SystemSet $systemSet): void
     {
         $moduleTreeBuilder = new ModuleTreeBuilder();
         $moduleTree = $moduleTreeBuilder->buildByConstraints($archiveName, $constraint);
@@ -90,12 +95,15 @@ public function satisfiesContraints2(string $archiveName, string $constraint, ar
         $flatEntries = $flatEntryBuilder->buildListFromModuleTree($moduleTree);
         $this->log($flatEntries, '2-flatEntries.json');
 
-        $flatEntries = $flatEntryBuilder->removeFlatEntriesByContrains($flatEntries, $contraints);
-        $this->log($flatEntries, '2-flatEntries-removed.json');
+        // $flatEntries = $flatEntryBuilder->removeFlatEntriesBySystemSet($flatEntries, $systemSet);
+        // $this->log($flatEntries, '2-flatEntries-removed.json');
+
+        $flatEntries = $flatEntryBuilder->fitSystemSet($flatEntries, $systemSet);
+        $this->log($flatEntries, '2-flatEntries-fit.json');
 
         $combinationBuilder = new CombinationBuilder();
         $combinations = $combinationBuilder->buildAllFromModuleFlatEntries($flatEntries);
-        // $this->log($combinations, '2-combinations.json');
+        $this->log($combinations, '2-combinations.json');
 
         $combinationSatisfyer = new CombinationSatisfyer();
         $combination = $combinationSatisfyer->satisfiesCominationsFromModuleTree($moduleTree, $combinations);
@@ -103,7 +111,7 @@ public function satisfiesContraints2(string $archiveName, string $constraint, ar
     }
 
 
-    public function satisfiesContraints3(string $archiveName, string $constraint, array $contraints): void
+    public function satisfiesContraints3(string $archiveName, string $constraint, SystemSet $systemSet): void
     {
         $moduleTreeBuilder = new ModuleTreeBuilder();
         $moduleTree = $moduleTreeBuilder->buildByConstraints($archiveName, $constraint);
@@ -113,8 +121,11 @@ public function satisfiesContraints3(string $archiveName, string $constraint, ar
         $flatEntries = $flatEntryBuilder->buildListFromModuleTree($moduleTree);
         $this->log($flatEntries, '3-flatEntries.json');
 
-        $flatEntries = $flatEntryBuilder->removeFlatEntriesByContrains($flatEntries, $contraints);
-        $this->log($flatEntries, '3-flatEntries-removed.json');
+        // $flatEntries = $flatEntryBuilder->removeFlatEntriesBySystemSet($flatEntries, $systemSet);
+        // $this->log($flatEntries, '3-flatEntries-removed.json');
+
+        $flatEntries = $flatEntryBuilder->fitSystemSet($flatEntries, $systemSet);
+        $this->log($flatEntries, '2-flatEntries-fit.json');
 
         $combinationIterator = new CombinationIterator($flatEntries);
         $combinationSatisfyer = new CombinationSatisfyer();
diff --git a/src/Classes/DependencyManager/FlatEntryBuilder.php b/src/Classes/DependencyManager/FlatEntryBuilder.php
index c35a09b4..075e85c2 100644
--- a/src/Classes/DependencyManager/FlatEntryBuilder.php
+++ b/src/Classes/DependencyManager/FlatEntryBuilder.php
@@ -89,14 +89,82 @@ private function buildModuleFlatEntriesByModuleTree(ModuleTree $moduleTree, arra
         $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->systems 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 array $contraints
+     * @param SystemSet $systemSet
      * @return FlatEntry[]
      */
-    public function removeFlatEntriesByContrains(array $moduleFlatTreeEntries, array $contraints): array
+    public function removeFlatEntriesBySystemSet(array $moduleFlatTreeEntries, SystemSet $systemSet): array
     {
-        foreach ($contraints as $archiveName => $versions) {
+        foreach ($systemSet->systems as $archiveName => $version) {
+            $versions = [$version];
             $moduleFlatTreeEntries = $this->removeModuleFlatEnty($moduleFlatTreeEntries, $archiveName, $versions);
         }
         return $moduleFlatTreeEntries;
diff --git a/src/Classes/DependencyManager/ModuleTreeBuilder.php b/src/Classes/DependencyManager/ModuleTreeBuilder.php
index 9a1ce312..24214d41 100644
--- a/src/Classes/DependencyManager/ModuleTreeBuilder.php
+++ b/src/Classes/DependencyManager/ModuleTreeBuilder.php
@@ -41,6 +41,50 @@ private function loadAllByArchiveNameAndConstraint(string $archiveName, string $
         return ModuleFilter::filterByVersionConstrain($modules, $versionConstraint);
     }
 
+    /**
+     * @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 int $depth
@@ -55,7 +99,10 @@ public function buildListByConstraints(Module $module, int $depth = 0): array
         $require = $module->getRequire();
 
         $moduleTrees = [];
-        $moduleTrees[] = $this->createModifed($module);
+
+        $this->addTreeModified($module, $moduleTrees);
+        $this->addTreePhp($module, $moduleTrees);
+
         foreach ($require as $archiveName => $versionConstraint) {
             // Modules to Entry
             $moduleTree = new ModuleTree();
@@ -70,7 +117,6 @@ public function buildListByConstraints(Module $module, int $depth = 0): array
                 $moduleVersion = new ModuleVersion();
                 $moduleVersion->version = $module->getVersion();
                 $moduleVersion->require = $this->buildListByConstraints($module, $depth + 1);
-                //$moduleVersion->require[] = $this->createModifed($module);
                 $moduleTree->moduleVersions[$moduleVersion->version] = $moduleVersion;
             }
 
@@ -80,26 +126,6 @@ public function buildListByConstraints(Module $module, int $depth = 0): array
         return $moduleTrees;
     }
 
-    /**
-     * @return ModuleTree[]
-     */
-    private function createModifed(Module $module): ModuleTree
-    {
-        $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);
-
-        return $moduleTree;
-    }
     /**
      * @param string $archiveName
      * @param string $versionConstraint
@@ -120,6 +146,9 @@ public function buildByConstraints(string $archiveName, string $versionConstrain
             $moduleVersion->version = $module->getVersion();
 
             if ($depth < 10) {
+                $this->addTreeModified($module, $moduleVersion->require);
+                $this->addTreePhp($module, $moduleVersion->require);
+
                 $require = $module->getRequire();
                 foreach ($require as $archiveName => $versionConstraint) {
                     // Context: require
diff --git a/src/Classes/DependencyManager/SystemSet.php b/src/Classes/DependencyManager/SystemSet.php
new file mode 100644
index 00000000..da7ba951
--- /dev/null
+++ b/src/Classes/DependencyManager/SystemSet.php
@@ -0,0 +1,19 @@
+
+ *
+ * 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
+{
+    public $systems = [];
+}
diff --git a/tests/unit/DependencyManager/CounterTest.php b/tests/unit/DependencyManager/CounterTest.php
index a42267ef..74daa199 100644
--- a/tests/unit/DependencyManager/CounterTest.php
+++ b/tests/unit/DependencyManager/CounterTest.php
@@ -58,4 +58,22 @@ public function testNext()
         $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());
+    }
 }

From bfb5d50ddf80b50edc67edbff1c4ee85df92450e Mon Sep 17 00:00:00 2001
From: Robin Wieschendorf 
Date: Thu, 9 Mar 2023 15:41:07 +0100
Subject: [PATCH 13/37] chore: handle mmlc version

---
 .../DependencyManager/DependencyBuilder.php   |  12 +-
 .../DependencyManager/ModuleTreeBuilder.php   | 123 +++++++++++-------
 2 files changed, 77 insertions(+), 58 deletions(-)

diff --git a/src/Classes/DependencyManager/DependencyBuilder.php b/src/Classes/DependencyManager/DependencyBuilder.php
index 438e98a3..c9d4e84c 100644
--- a/src/Classes/DependencyManager/DependencyBuilder.php
+++ b/src/Classes/DependencyManager/DependencyBuilder.php
@@ -31,7 +31,8 @@ public function test()
         $systemSet = new SystemSet();
         $systemSet->systems = [
             "modified" => '2.0.4.2',
-            //"php" => '7.3.0',
+            "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',
@@ -69,9 +70,6 @@ public function satisfiesContraints1(Module $module, SystemSet $systemSet): void
         $flatEntries = $flatEntryBuilder->buildListFromModuleTrees($moduleTrees);
         $this->log($flatEntries, '1-flatEntries.json');
 
-        // $flatEntries = $flatEntryBuilder->removeFlatEntriesBySystemSet($flatEntries, $systemSet);
-        // $this->log($flatEntries, '1-flatEntries-removed.json');
-
         $flatEntries = $flatEntryBuilder->fitSystemSet($flatEntries, $systemSet);
         $this->log($flatEntries, '1-flatEntries-fit.json');
 
@@ -95,9 +93,6 @@ public function satisfiesContraints2(string $archiveName, string $constraint, Sy
         $flatEntries = $flatEntryBuilder->buildListFromModuleTree($moduleTree);
         $this->log($flatEntries, '2-flatEntries.json');
 
-        // $flatEntries = $flatEntryBuilder->removeFlatEntriesBySystemSet($flatEntries, $systemSet);
-        // $this->log($flatEntries, '2-flatEntries-removed.json');
-
         $flatEntries = $flatEntryBuilder->fitSystemSet($flatEntries, $systemSet);
         $this->log($flatEntries, '2-flatEntries-fit.json');
 
@@ -121,9 +116,6 @@ public function satisfiesContraints3(string $archiveName, string $constraint, Sy
         $flatEntries = $flatEntryBuilder->buildListFromModuleTree($moduleTree);
         $this->log($flatEntries, '3-flatEntries.json');
 
-        // $flatEntries = $flatEntryBuilder->removeFlatEntriesBySystemSet($flatEntries, $systemSet);
-        // $this->log($flatEntries, '3-flatEntries-removed.json');
-
         $flatEntries = $flatEntryBuilder->fitSystemSet($flatEntries, $systemSet);
         $this->log($flatEntries, '2-flatEntries-fit.json');
 
diff --git a/src/Classes/DependencyManager/ModuleTreeBuilder.php b/src/Classes/DependencyManager/ModuleTreeBuilder.php
index 24214d41..e1b9a96f 100644
--- a/src/Classes/DependencyManager/ModuleTreeBuilder.php
+++ b/src/Classes/DependencyManager/ModuleTreeBuilder.php
@@ -41,50 +41,6 @@ private function loadAllByArchiveNameAndConstraint(string $archiveName, string $
         return ModuleFilter::filterByVersionConstrain($modules, $versionConstraint);
     }
 
-    /**
-     * @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 int $depth
@@ -100,8 +56,7 @@ public function buildListByConstraints(Module $module, int $depth = 0): array
 
         $moduleTrees = [];
 
-        $this->addTreeModified($module, $moduleTrees);
-        $this->addTreePhp($module, $moduleTrees);
+        $this->addExtraDependencies($module, $moduleTrees);
 
         foreach ($require as $archiveName => $versionConstraint) {
             // Modules to Entry
@@ -146,8 +101,7 @@ public function buildByConstraints(string $archiveName, string $versionConstrain
             $moduleVersion->version = $module->getVersion();
 
             if ($depth < 10) {
-                $this->addTreeModified($module, $moduleVersion->require);
-                $this->addTreePhp($module, $moduleVersion->require);
+                $this->addExtraDependencies($module, $moduleVersion->require);
 
                 $require = $module->getRequire();
                 foreach ($require as $archiveName => $versionConstraint) {
@@ -161,4 +115,77 @@ public function buildByConstraints(string $archiveName, string $versionConstrain
 
         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;
+    }
 }

From c745c4385099abd735bbdccf004b37d634667009 Mon Sep 17 00:00:00 2001
From: Robin Wieschendorf 
Date: Thu, 9 Mar 2023 20:45:23 +0100
Subject: [PATCH 14/37] chore: add and use CombinationSatisfyerResult

---
 .../CombinationSatisfyer.php                  | 76 +++++++++++++------
 .../CombinationSatisfyerResult.php            | 28 +++++++
 .../DependencyManager/DependencyBuilder.php   | 43 +++++------
 .../DependencyBuilderTest.php                 | 53 ++++++++++++-
 4 files changed, 153 insertions(+), 47 deletions(-)
 create mode 100644 src/Classes/DependencyManager/CombinationSatisfyerResult.php

diff --git a/src/Classes/DependencyManager/CombinationSatisfyer.php b/src/Classes/DependencyManager/CombinationSatisfyer.php
index 994d44da..eba2fb36 100644
--- a/src/Classes/DependencyManager/CombinationSatisfyer.php
+++ b/src/Classes/DependencyManager/CombinationSatisfyer.php
@@ -30,50 +30,82 @@ public function __construct()
      * @param ModuleTree[] $moduleTrees
      * @param Combination[] $combinations
      *
-     * @return Combination
+     * @return CombinationSatisfyerResult
      */
-    public function satisfiesCominationsFromModuleTrees(array $moduleTrees, array $combinations): ?Combination
+    public function satisfiesCominationsFromModuleTrees(array $moduleTrees, array $combinations): CombinationSatisfyerResult
     {
-        foreach ($combinations as $combination) {
-            $result = $this->satisfiesCominationFromModuleTrees($moduleTrees, $combination);
+        $foundCombination = new Combination();
+
+        foreach ($combinations as $testCombination) {
+            $foundCombination = new Combination();
+            $result = $this->satisfiesCominationFromModuleTrees($moduleTrees, $testCombination, $foundCombination);
             if ($result) {
-                return $combination;
+                $combinationSatisfyerResult = new CombinationSatisfyerResult(
+                    $testCombination,
+                    $foundCombination
+                );
+                return $combinationSatisfyerResult;
             }
         }
-        return null;
+        $combinationSatisfyerResult = new CombinationSatisfyerResult(
+            null,
+            null
+        );
+        return $combinationSatisfyerResult;
     }
 
     /**
      * @param ModuleTree $moduleTree
      * @param Combination[] $combinations
      *
-     * @return Combination
+     * @return CombinationSatisfyerResult
      */
-    public function satisfiesCominationsFromModuleTree(ModuleTree $moduleTree, array $combinations): ?Combination
+    public function satisfiesCominationsFromModuleTree(ModuleTree $moduleTree, array $combinations): CombinationSatisfyerResult
     {
-        foreach ($combinations as $combination) {
-            $result = $this->satisfiesCominationFromModuleTree($moduleTree, $combination);
+        $foundCombination = new Combination();
+
+        foreach ($combinations as $testCombination) {
+            $result = $this->satisfiesCominationFromModuleTree($moduleTree, $testCombination, $foundCombination);
             if ($result) {
-                return $combination;
+                $combinationSatisfyerResult = new CombinationSatisfyerResult(
+                    $testCombination,
+                    $foundCombination
+                );
+                return $combinationSatisfyerResult;
             }
         }
-        return null;
+        $combinationSatisfyerResult = new CombinationSatisfyerResult(
+            null,
+            null
+        );
+        return $combinationSatisfyerResult;
     }
 
     public function satisfiesCominationsFromModuleWithIterator(
         ModuleTree $moduleTree,
         CombinationIterator $combinationIterator
-    ): ?Combination {
+    ): CombinationSatisfyerResult {
+        $foundCombination = new Combination();
+
         while (true) {
-            $combination = $combinationIterator->current();
-            $result = $this->satisfiesCominationFromModuleTree($moduleTree, $combination);
+            $testCombination = $combinationIterator->current();
+            $result = $this->satisfiesCominationFromModuleTree($moduleTree, $testCombination, $foundCombination);
+
             if ($result) {
-                return $combination;
+                $combinationSatisfyerResult = new CombinationSatisfyerResult(
+                    $testCombination,
+                    $foundCombination
+                );
+                return $combinationSatisfyerResult;
             }
 
             $combinationIterator->next();
             if ($combinationIterator->isStart()) {
-                return null;
+                $combinationSatisfyerResult = new CombinationSatisfyerResult(
+                    null,
+                    null
+                );
+                return $combinationSatisfyerResult;
             }
         }
     }
@@ -82,7 +114,7 @@ public function satisfiesCominationsFromModuleWithIterator(
      * @param ModuleTree $moduleTree
      * @param Combination $combination
      */
-    public function satisfiesCominationFromModuleTree(ModuleTree $moduleTree, Combination $combination): bool
+    public function satisfiesCominationFromModuleTree(ModuleTree $moduleTree, Combination $combination, Combination &$foundCombination): bool
     {
         // Context: Module
         $archiveName = $moduleTree->archiveName;
@@ -98,8 +130,8 @@ public function satisfiesCominationFromModuleTree(ModuleTree $moduleTree, Combin
             // Context: Version
             //var_dump($archiveName . " {$moduleVersion->version} == {$selectedVersion}");
             if ($moduleVersion->version === $selectedVersion) {
-                //var_dump('x');
-                return $this->satisfiesCominationFromModuleTrees($moduleVersion->require, $combination);
+                $foundCombination->overwrite($archiveName, $moduleVersion->version);
+                return $this->satisfiesCominationFromModuleTrees($moduleVersion->require, $combination, $foundCombination);
             }
         }
         // var_dump('xxx');
@@ -111,13 +143,13 @@ public function satisfiesCominationFromModuleTree(ModuleTree $moduleTree, Combin
      * @param ModuleTree[] $moduleTrees
      * @param Combination $combination
      */
-    public function satisfiesCominationFromModuleTrees(array $moduleTrees, Combination $combination): bool
+    public function satisfiesCominationFromModuleTrees(array $moduleTrees, Combination $combination, Combination &$foundCombination): bool
     {
         // Context: Expanded
         $moduleResult = true;
         foreach ($moduleTrees as $moduleTree) {
             $moduleResult =
-                $moduleResult && $this->satisfiesCominationFromModuleTree($moduleTree, $combination);
+                $moduleResult && $this->satisfiesCominationFromModuleTree($moduleTree, $combination, $foundCombination);
         }
         return $moduleResult;
     }
diff --git a/src/Classes/DependencyManager/CombinationSatisfyerResult.php b/src/Classes/DependencyManager/CombinationSatisfyerResult.php
new file mode 100644
index 00000000..cda52b92
--- /dev/null
+++ b/src/Classes/DependencyManager/CombinationSatisfyerResult.php
@@ -0,0 +1,28 @@
+
+ *
+ * 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
+{
+    /** @var Combination */
+    public $testCombination = null;
+    /** @var Combination */
+    public $foundCombination = null;
+
+    public function __construct(?Combination $testCombination, ?Combination $foundCombination)
+    {
+        $this->testCombination = $testCombination;
+        $this->foundCombination = $foundCombination;
+    }
+}
diff --git a/src/Classes/DependencyManager/DependencyBuilder.php b/src/Classes/DependencyManager/DependencyBuilder.php
index c9d4e84c..aa23df3b 100644
--- a/src/Classes/DependencyManager/DependencyBuilder.php
+++ b/src/Classes/DependencyManager/DependencyBuilder.php
@@ -40,27 +40,19 @@ public function test()
         ];
 
         var_dump('TEST: satisfiesContraints1');
-        $this->satisfiesContraints1(
-            $module,
-            $systemSet
-        );
+        $combinationSatisfyerResult = $this->satisfiesContraints1($module, $systemSet);
+        var_dump($combinationSatisfyerResult);
 
         var_dump('TEST: satisfiesContraints2');
-        $this->satisfiesContraints2(
-            'firstweb/multi-order',
-            '^1.0.0',
-            $systemSet
-        );
+        $combinationSatisfyerResult = $this->satisfiesContraints2('firstweb/multi-order', '^1.0.0', $systemSet);
+        var_dump($combinationSatisfyerResult);
 
         var_dump('TEST: satisfiesContraints3');
-        $this->satisfiesContraints3(
-            'firstweb/multi-order',
-            '^1.0.0',
-            $systemSet
-        );
+        $combinationSatisfyerResult = $this->satisfies('firstweb/multi-order', '^1.0.0', $systemSet);
+        var_dump($combinationSatisfyerResult);
     }
 
-    public function satisfiesContraints1(Module $module, SystemSet $systemSet): void
+    public function satisfiesContraints1(Module $module, SystemSet $systemSet): CombinationSatisfyerResult
     {
         $moduleTreeBuilder = new ModuleTreeBuilder();
         $moduleTrees = $moduleTreeBuilder->buildListByConstraints($module);
@@ -78,12 +70,13 @@ public function satisfiesContraints1(Module $module, SystemSet $systemSet): void
         $this->log($combinations, '1-combinations.json');
 
         $combinationSatisfyer = new CombinationSatisfyer();
-        $combination = $combinationSatisfyer->satisfiesCominationsFromModuleTrees($moduleTrees, $combinations);
-        var_dump($combination);
+        $combinationSatisfyerResult = $combinationSatisfyer->satisfiesCominationsFromModuleTrees($moduleTrees, $combinations);
+
+        return $combinationSatisfyerResult;
     }
 
 
-    public function satisfiesContraints2(string $archiveName, string $constraint, SystemSet $systemSet): void
+    public function satisfiesContraints2(string $archiveName, string $constraint, SystemSet $systemSet): CombinationSatisfyerResult
     {
         $moduleTreeBuilder = new ModuleTreeBuilder();
         $moduleTree = $moduleTreeBuilder->buildByConstraints($archiveName, $constraint);
@@ -101,12 +94,13 @@ public function satisfiesContraints2(string $archiveName, string $constraint, Sy
         $this->log($combinations, '2-combinations.json');
 
         $combinationSatisfyer = new CombinationSatisfyer();
-        $combination = $combinationSatisfyer->satisfiesCominationsFromModuleTree($moduleTree, $combinations);
-        var_dump($combination);
+        $combinationSatisfyerResult = $combinationSatisfyer->satisfiesCominationsFromModuleTree($moduleTree, $combinations);
+
+        return $combinationSatisfyerResult;
     }
 
 
-    public function satisfiesContraints3(string $archiveName, string $constraint, SystemSet $systemSet): void
+    public function satisfies(string $archiveName, string $constraint, SystemSet $systemSet): CombinationSatisfyerResult
     {
         $moduleTreeBuilder = new ModuleTreeBuilder();
         $moduleTree = $moduleTreeBuilder->buildByConstraints($archiveName, $constraint);
@@ -117,11 +111,12 @@ public function satisfiesContraints3(string $archiveName, string $constraint, Sy
         $this->log($flatEntries, '3-flatEntries.json');
 
         $flatEntries = $flatEntryBuilder->fitSystemSet($flatEntries, $systemSet);
-        $this->log($flatEntries, '2-flatEntries-fit.json');
+        $this->log($flatEntries, '3-flatEntries-fit.json');
 
         $combinationIterator = new CombinationIterator($flatEntries);
         $combinationSatisfyer = new CombinationSatisfyer();
-        $combination = $combinationSatisfyer->satisfiesCominationsFromModuleWithIterator($moduleTree, $combinationIterator);
-        var_dump($combination);
+        $combinationSatisfyerResult = $combinationSatisfyer->satisfiesCominationsFromModuleWithIterator($moduleTree, $combinationIterator);
+
+        return $combinationSatisfyerResult;
     }
 }
diff --git a/tests/unit/DependencyManager/DependencyBuilderTest.php b/tests/unit/DependencyManager/DependencyBuilderTest.php
index f6625881..f7eea4e1 100644
--- a/tests/unit/DependencyManager/DependencyBuilderTest.php
+++ b/tests/unit/DependencyManager/DependencyBuilderTest.php
@@ -15,10 +15,61 @@
 
 use PHPUnit\Framework\TestCase;
 use RobinTheHood\ModifiedModuleLoaderClient\DependencyManager\DependencyBuilder;
+use RobinTheHood\ModifiedModuleLoaderClient\DependencyManager\SystemSet;
 
 class DependencyBuilderTest extends TestCase
 {
-    public function testInvokeDependency()
+    public function testSatisfies()
+    {
+        $dependencyBuilder = new DependencyBuilder();
+        $systemSet = new SystemSet();
+
+        $systemSet->systems = [
+            "modified" => '2.0.4.2',
+            "php" => '7.4.0',
+            "mmlc" => '1.18.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',
+            "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.18.0',
+                "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->combinations
+        );
+
+        $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->combinations
+        );
+    }
+
+    public function atestInvokeDependency()
     {
         $dpb = new DependencyBuilder();
         $dpb->test();

From 6602c2240390cbda086f11424d8769c969c2c76e Mon Sep 17 00:00:00 2001
From: Robin Wieschendorf 
Date: Thu, 9 Mar 2023 20:46:03 +0100
Subject: [PATCH 15/37] chore: add overwrite and strip methods to Combination

---
 src/Classes/DependencyManager/Combination.php | 21 +++++++++++++++++++
 1 file changed, 21 insertions(+)

diff --git a/src/Classes/DependencyManager/Combination.php b/src/Classes/DependencyManager/Combination.php
index e41de30f..69da19a1 100644
--- a/src/Classes/DependencyManager/Combination.php
+++ b/src/Classes/DependencyManager/Combination.php
@@ -29,6 +29,11 @@ public function add(string $archiveName, string $version)
         $this->combinations[$archiveName] = $version;
     }
 
+    public function overwrite(string $archiveName, string $version)
+    {
+        $this->combinations[$archiveName] = $version;
+    }
+
     public function getVersion(string $archiveName): string
     {
         if (!array_key_exists($archiveName, $this->combinations)) {
@@ -45,4 +50,20 @@ public function clone(): Combination
         $newCombination->combinations = $combinations;
         return $newCombination;
     }
+
+    /**
+     * Liefert eine neues Combinations Obj zurück, in der nur echte Module enthalten sind.
+     */
+    public function strip(): Combination
+    {
+        $combination = new Combination();
+        foreach ($this->combinations as $archiveName => $version) {
+            if (strpos($archiveName, '/') === false) { // Überspringe Einträge wie modified, php, mmlc ...
+                continue;
+            }
+
+            $combination->add($archiveName, $version);
+        }
+        return $combination;
+    }
 }

From d23e445ad773af75ab443b706db27deebe2bca3f Mon Sep 17 00:00:00 2001
From: Robin Wieschendorf 
Date: Thu, 9 Mar 2023 20:46:50 +0100
Subject: [PATCH 16/37] chore: add resetCache methods for ModuleLoader and
 RemoteLoaderLoader

---
 src/Classes/Loader/ModuleLoader.php       | 17 +++++++++++++++++
 src/Classes/Loader/RemoteModuleLoader.php | 11 +++++++++++
 2 files changed, 28 insertions(+)

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.
      *

From b4b910cf2f668488669387d246b3b6b96d065a31 Mon Sep 17 00:00:00 2001
From: Robin Wieschendorf 
Date: Thu, 9 Mar 2023 20:47:30 +0100
Subject: [PATCH 17/37] chore: add class SystemSetFactory

---
 .../DependencyManager/SystemSetFactory.php    | 36 +++++++++++++++++++
 1 file changed, 36 insertions(+)
 create mode 100644 src/Classes/DependencyManager/SystemSetFactory.php

diff --git a/src/Classes/DependencyManager/SystemSetFactory.php b/src/Classes/DependencyManager/SystemSetFactory.php
new file mode 100644
index 00000000..280a3038
--- /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->systems['modified'] = ModifiedModuleLoaderClientShopInfo::getModifiedVersion();
+        $systemSet->systems['php'] = phpversion();
+        $systemSet->systems['mmlc'] = App::getMmlcVersion();
+
+        $moduleLoader = LocalModuleLoader::getModuleLoader();
+        $modules = $moduleLoader->loadAllInstalledVersions();
+        foreach ($modules as $module) {
+            $systemSet->systems[$module->getArchiveName()] = $module->getVersion();
+        }
+        return $systemSet;
+    }
+}

From eade87ec00cce461e719a32c89f21d9eef836b4d Mon Sep 17 00:00:00 2001
From: Robin Wieschendorf 
Date: Thu, 9 Mar 2023 20:50:19 +0100
Subject: [PATCH 18/37] chore: use new DependencyManager

---
 .../DependencyException.php                   |   2 +-
 .../DependencyManager/DependencyManager.php   | 121 ++++++++++++++++++
 .../DependencyManagerOld.php}                 |  68 ++++++++--
 src/Classes/IndexController.php               |   6 +-
 src/Classes/Module.php                        |   2 +-
 src/Classes/ModuleInstaller.php               |  56 ++++++--
 6 files changed, 224 insertions(+), 31 deletions(-)
 rename src/Classes/{ => DependencyManager}/DependencyException.php (83%)
 create mode 100644 src/Classes/DependencyManager/DependencyManager.php
 rename src/Classes/{DependencyManager.php => DependencyManager/DependencyManagerOld.php} (77%)

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..21b37c23
--- /dev/null
+++ b/src/Classes/DependencyManager/DependencyManager.php
@@ -0,0 +1,121 @@
+
+ *
+ * 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\DependencyManager\DependencyException;
+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()->combinations 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
+    {
+        $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 module {$module->getArchiveName()} in version {$module->getVersion()}");
+        }
+
+        $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..423667a9 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()->combinations 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) {
+            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/IndexController.php b/src/Classes/IndexController.php
index eb95ebd2..5f405676 100644
--- a/src/Classes/IndexController.php
+++ b/src/Classes/IndexController.php
@@ -23,6 +23,7 @@
 use RobinTheHood\ModifiedModuleLoaderClient\Category;
 use RobinTheHood\ModifiedModuleLoaderClient\SendMail;
 use RobinTheHood\ModifiedModuleLoaderClient\Config;
+use RobinTheHood\ModifiedModuleLoaderClient\DependencyManager\DependencyException;
 
 class IndexController extends Controller
 {
@@ -508,8 +509,9 @@ public function invokeLoadAndInstall()
 
         try {
             $moduleInstaller = new ModuleInstaller();
-            $moduleInstaller->install($module);
-            $moduleInstaller->installDependencies($module);
+            // $moduleInstaller->install($module);
+            // $moduleInstaller->installDependencies($module);
+            $moduleInstaller->installWithDependencies($module);
         } catch (DependencyException $e) {
             Notification::pushFlashMessage([
                 'text' => $e->getMessage(),
diff --git a/src/Classes/Module.php b/src/Classes/Module.php
index 4ef15c5c..2c7e9d44 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;
diff --git a/src/Classes/ModuleInstaller.php b/src/Classes/ModuleInstaller.php
index d8724dd6..fa2b2686 100644
--- a/src/Classes/ModuleInstaller.php
+++ b/src/Classes/ModuleInstaller.php
@@ -17,8 +17,9 @@
 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;
@@ -133,35 +134,58 @@ public function revertChanges(Module $module): void
         $this->install($module, true);
     }
 
-    public function installDependencies(Module $module): void
+    public function installDependencies(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 (!$module->isLoaded()) {
+                $this->pull($module);
             }
         }
 
-        $modules = $dependencyManager->getAllModules($module);
-        foreach ($modules as $depModule) {
-            $this->uninstall($depModule);
-            if ($depModule->isLoaded() && !$depModule->isInstalled()) {
-                $this->install($depModule);
+        $modules = $dependencyManager->getAllModulesFromCombination($combination);
+        foreach ($modules as $module) {
+            $this->uninstall($module);
+            if ($module->isLoaded() && !$module->isInstalled()) {
+                $this->install($module);
             }
         }
 
         $this->createAutoloadFile();
     }
 
+    // public function installDependenciesOld(Module $module): void
+    // {
+    //     $dependencyManager = new DependencyManager();
+    //     $modules = $dependencyManager->getAllModules($module);
+
+    //     foreach ($modules as $depModule) {
+    //         if (!$depModule->isLoaded()) {
+    //             $this->pull($depModule);
+    //         }
+    //     }
+
+    //     $modules = $dependencyManager->getAllModules($module);
+    //     foreach ($modules as $depModule) {
+    //         $this->uninstall($depModule);
+    //         if ($depModule->isLoaded() && !$depModule->isInstalled()) {
+    //             $this->install($depModule);
+    //         }
+    //     }
+
+    //     $this->createAutoloadFile();
+    // }
+
     public function installWithDependencies(Module $module): void
     {
         $dependencyManager = new DependencyManager();
-        $dependencyManager->canBeInstalled($module);
+        $combinationSatisfyerResult = $dependencyManager->canBeInstalled($module);
 
         $this->install($module);
-        $this->installDependencies($module);
+        $this->installDependencies($combinationSatisfyerResult->foundCombination);
+        //$this->installDependenciesOld($module);
     }
 
     public function createAutoloadFile(): void
@@ -259,6 +283,7 @@ public function update(Module $module): ?Module
         // Da nach $newModule->pull() das Modul noch nicht lokal inistailisiert
         // sein kann, wird es noch einmal geladen.
         $moduleLoader = new LocalModuleLoader();
+        $moduleLoader->resetCache();
         $newModule = $moduleLoader->loadByArchiveNameAndVersion($newModule->getArchiveName(), $newModule->getVersion());
 
         if (!$newModule) {
@@ -272,12 +297,15 @@ public function update(Module $module): ?Module
 
     public function updateWithDependencies(Module $module): ?Module
     {
+        $dependencyManager = new DependencyManager();
+        $combinationSatisfyerResult = $dependencyManager->canBeInstalled($module);
+
         $newModule = $this->update($module);
         if (!$newModule) {
             return null; //TODO: Better return not nullable type Module and thorw an exception
         }
 
-        $this->installDependencies($newModule);
+        $this->installDependencies($combinationSatisfyerResult->foundCombination);
         return $newModule;
     }
 

From 52d693fa7a178b61dd3be3ca368b061a7a23deb5 Mon Sep 17 00:00:00 2001
From: Robin Wieschendorf 
Date: Thu, 9 Mar 2023 21:43:16 +0100
Subject: [PATCH 19/37] fix: errros

---
 .../DependencyManager/CombinationBuilder.php  |  4 +-
 .../DependencyManager/CombinationIterator.php |  1 +
 .../CombinationSatisfyerResult.php            |  4 +-
 .../DependencyManager/DependencyBuilder.php   | 45 +++++++++++--------
 .../DependencyBuilderOld.php                  | 35 ++++++++++-----
 .../DependencyManagerOld.php                  |  2 +-
 .../DependencyManager/ModuleTreeBuilder.php   |  2 +-
 src/Classes/ModuleInstaller.php               |  8 ++++
 .../DependencyBuilderTest.php                 |  4 +-
 9 files changed, 67 insertions(+), 38 deletions(-)

diff --git a/src/Classes/DependencyManager/CombinationBuilder.php b/src/Classes/DependencyManager/CombinationBuilder.php
index 701418f8..7263baba 100644
--- a/src/Classes/DependencyManager/CombinationBuilder.php
+++ b/src/Classes/DependencyManager/CombinationBuilder.php
@@ -32,9 +32,9 @@ public function buildAllFromModuleFlatEntries($flatEntries): array
 
     /**
      * @param FlatEntry[] $flatEntries
-     * @param array $flatEntries
-     * @param int $index
+     * @param Combination[] $combinations
      * @param Combination $combination
+     * @param int $index
      */
     private function buildAllCombinationsFromModuleFlatEntries(
         array &$flatEntries,
diff --git a/src/Classes/DependencyManager/CombinationIterator.php b/src/Classes/DependencyManager/CombinationIterator.php
index a9e27a0e..84683748 100644
--- a/src/Classes/DependencyManager/CombinationIterator.php
+++ b/src/Classes/DependencyManager/CombinationIterator.php
@@ -30,6 +30,7 @@ public function __construct(array $flatEntries)
     {
         $this->flatEntries = $flatEntries;
 
+        $counterMaxValues = [];
         foreach ($flatEntries as $flatEntry) {
             if (!$flatEntry->versions) {
                 continue;
diff --git a/src/Classes/DependencyManager/CombinationSatisfyerResult.php b/src/Classes/DependencyManager/CombinationSatisfyerResult.php
index cda52b92..3b0bb60f 100644
--- a/src/Classes/DependencyManager/CombinationSatisfyerResult.php
+++ b/src/Classes/DependencyManager/CombinationSatisfyerResult.php
@@ -15,9 +15,9 @@
 
 class CombinationSatisfyerResult
 {
-    /** @var Combination */
+    /** @var ?Combination */
     public $testCombination = null;
-    /** @var Combination */
+    /** @var ?Combination */
     public $foundCombination = null;
 
     public function __construct(?Combination $testCombination, ?Combination $foundCombination)
diff --git a/src/Classes/DependencyManager/DependencyBuilder.php b/src/Classes/DependencyManager/DependencyBuilder.php
index aa23df3b..ac1b17cd 100644
--- a/src/Classes/DependencyManager/DependencyBuilder.php
+++ b/src/Classes/DependencyManager/DependencyBuilder.php
@@ -16,18 +16,27 @@
 
 class DependencyBuilder
 {
-    private function log($value, $file)
+    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->systems = [
             "modified" => '2.0.4.2',
@@ -39,35 +48,35 @@ public function test()
             "robinthehood/pdf-bill" => '0.17.0'
         ];
 
-        var_dump('TEST: satisfiesContraints1');
+        $this->log('TEST: satisfiesContraints1');
         $combinationSatisfyerResult = $this->satisfiesContraints1($module, $systemSet);
-        var_dump($combinationSatisfyerResult);
+        $this->log($combinationSatisfyerResult);
 
-        var_dump('TEST: satisfiesContraints2');
+        $this->log('TEST: satisfiesContraints2');
         $combinationSatisfyerResult = $this->satisfiesContraints2('firstweb/multi-order', '^1.0.0', $systemSet);
-        var_dump($combinationSatisfyerResult);
+        $this->log($combinationSatisfyerResult);
 
-        var_dump('TEST: satisfiesContraints3');
+        // var_dump('TEST: satisfiesContraints3');
         $combinationSatisfyerResult = $this->satisfies('firstweb/multi-order', '^1.0.0', $systemSet);
-        var_dump($combinationSatisfyerResult);
+        $this->log($combinationSatisfyerResult);
     }
 
     public function satisfiesContraints1(Module $module, SystemSet $systemSet): CombinationSatisfyerResult
     {
         $moduleTreeBuilder = new ModuleTreeBuilder();
         $moduleTrees = $moduleTreeBuilder->buildListByConstraints($module);
-        $this->log($moduleTrees, '1-moduleTrees.json');
+        $this->logFile($moduleTrees, '1-moduleTrees.json');
 
         $flatEntryBuilder = new FlatEntryBuilder();
         $flatEntries = $flatEntryBuilder->buildListFromModuleTrees($moduleTrees);
-        $this->log($flatEntries, '1-flatEntries.json');
+        $this->logFile($flatEntries, '1-flatEntries.json');
 
         $flatEntries = $flatEntryBuilder->fitSystemSet($flatEntries, $systemSet);
-        $this->log($flatEntries, '1-flatEntries-fit.json');
+        $this->logFile($flatEntries, '1-flatEntries-fit.json');
 
         $combinationBuilder = new CombinationBuilder();
         $combinations = $combinationBuilder->buildAllFromModuleFlatEntries($flatEntries);
-        $this->log($combinations, '1-combinations.json');
+        $this->logFile($combinations, '1-combinations.json');
 
         $combinationSatisfyer = new CombinationSatisfyer();
         $combinationSatisfyerResult = $combinationSatisfyer->satisfiesCominationsFromModuleTrees($moduleTrees, $combinations);
@@ -80,18 +89,18 @@ public function satisfiesContraints2(string $archiveName, string $constraint, Sy
     {
         $moduleTreeBuilder = new ModuleTreeBuilder();
         $moduleTree = $moduleTreeBuilder->buildByConstraints($archiveName, $constraint);
-        $this->log($moduleTree, '2-moduleTrees.json');
+        $this->logFile($moduleTree, '2-moduleTrees.json');
 
         $flatEntryBuilder = new FlatEntryBuilder();
         $flatEntries = $flatEntryBuilder->buildListFromModuleTree($moduleTree);
-        $this->log($flatEntries, '2-flatEntries.json');
+        $this->logFile($flatEntries, '2-flatEntries.json');
 
         $flatEntries = $flatEntryBuilder->fitSystemSet($flatEntries, $systemSet);
-        $this->log($flatEntries, '2-flatEntries-fit.json');
+        $this->logFile($flatEntries, '2-flatEntries-fit.json');
 
         $combinationBuilder = new CombinationBuilder();
         $combinations = $combinationBuilder->buildAllFromModuleFlatEntries($flatEntries);
-        $this->log($combinations, '2-combinations.json');
+        $this->logFile($combinations, '2-combinations.json');
 
         $combinationSatisfyer = new CombinationSatisfyer();
         $combinationSatisfyerResult = $combinationSatisfyer->satisfiesCominationsFromModuleTree($moduleTree, $combinations);
@@ -104,14 +113,14 @@ public function satisfies(string $archiveName, string $constraint, SystemSet $sy
     {
         $moduleTreeBuilder = new ModuleTreeBuilder();
         $moduleTree = $moduleTreeBuilder->buildByConstraints($archiveName, $constraint);
-        $this->log($moduleTree, '3-moduleTrees.json');
+        $this->logFile($moduleTree, '3-moduleTrees.json');
 
         $flatEntryBuilder = new FlatEntryBuilder();
         $flatEntries = $flatEntryBuilder->buildListFromModuleTree($moduleTree);
-        $this->log($flatEntries, '3-flatEntries.json');
+        $this->logFile($flatEntries, '3-flatEntries.json');
 
         $flatEntries = $flatEntryBuilder->fitSystemSet($flatEntries, $systemSet);
-        $this->log($flatEntries, '3-flatEntries-fit.json');
+        $this->logFile($flatEntries, '3-flatEntries-fit.json');
 
         $combinationIterator = new CombinationIterator($flatEntries);
         $combinationSatisfyer = new CombinationSatisfyer();
diff --git a/src/Classes/DependencyManager/DependencyBuilderOld.php b/src/Classes/DependencyManager/DependencyBuilderOld.php
index ecd3b4eb..dd228936 100644
--- a/src/Classes/DependencyManager/DependencyBuilderOld.php
+++ b/src/Classes/DependencyManager/DependencyBuilderOld.php
@@ -16,41 +16,52 @@
 
 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);
-        file_put_contents(__DIR__ . '/debug-log-modules.txt', print_r($modules, true));
-        file_put_contents(__DIR__ . '/debug-log-modules.json', json_encode($modules, JSON_PRETTY_PRINT));
+        $this->logFile($modules, 'debug-log-modules.json');
 
         $tree = $this->buildTreeByModuleRecursive($module);
-        file_put_contents(__DIR__ . '/debug-log-tree.txt', print_r($tree, true));
-        file_put_contents(__DIR__ . '/debug-log-tree.json', json_encode($tree, JSON_PRETTY_PRINT));
+        $this->logFile($tree, 'debug-log-tree.json');
 
         $tree = $this->buildTreeByModuleRecursiveConstraint($module);
-        file_put_contents(__DIR__ . '/debug-log-tree-constraint.txt', print_r($tree, true));
-        file_put_contents(__DIR__ . '/debug-log-tree-constraint.json', json_encode($tree, JSON_PRETTY_PRINT));
+        $this->logFile($tree, 'debug-log-tree-constraint.json');
 
         $flat = [];
         $this->flattenTreeNew($tree, $flat);
-        file_put_contents(__DIR__ . '/debug-log-flat.txt', print_r($flat, true));
-        file_put_contents(__DIR__ . '/debug-log-flat.json', json_encode($flat, JSON_PRETTY_PRINT));
+        $this->logFile($flat, 'debug-log-flat.json');
 
         $combinations = [];
         $this->allCombinations($flat, $combinations, 0);
-        file_put_contents(__DIR__ . '/debug-log-combinations.txt', print_r($combinations, true));
-        file_put_contents(__DIR__ . '/debug-log-combinations.json', json_encode($combinations, JSON_PRETTY_PRINT));
+        $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) {
-                var_dump($combination);
-                var_dump($result);
+                $this->log($combination);
+                $this->log($result);
                 break;
             }
         }
diff --git a/src/Classes/DependencyManager/DependencyManagerOld.php b/src/Classes/DependencyManager/DependencyManagerOld.php
index 423667a9..4556b415 100644
--- a/src/Classes/DependencyManager/DependencyManagerOld.php
+++ b/src/Classes/DependencyManager/DependencyManagerOld.php
@@ -99,7 +99,7 @@ public function canBeInstalled(Module $module): CombinationSatisfyerResult
 
         $dependencyBuilder = new DependencyBuilder();
         $combinationSatisfyerResult = $dependencyBuilder->satisfies($module->getArchiveName(), $module->getVersion(), $systemSet);
-        if (!$combinationSatisfyerResult) {
+        if (!$combinationSatisfyerResult->foundCombination) {
             throw new DependencyException("Can not install modul");
         }
 
diff --git a/src/Classes/DependencyManager/ModuleTreeBuilder.php b/src/Classes/DependencyManager/ModuleTreeBuilder.php
index e1b9a96f..d13a771c 100644
--- a/src/Classes/DependencyManager/ModuleTreeBuilder.php
+++ b/src/Classes/DependencyManager/ModuleTreeBuilder.php
@@ -20,7 +20,7 @@
 
 class ModuleTreeBuilder
 {
-    /** @var Module[] */
+    /** @var array */
     private $moduleCache = [];
 
     /**
diff --git a/src/Classes/ModuleInstaller.php b/src/Classes/ModuleInstaller.php
index fa2b2686..001ab13f 100644
--- a/src/Classes/ModuleInstaller.php
+++ b/src/Classes/ModuleInstaller.php
@@ -183,6 +183,10 @@ public function installWithDependencies(Module $module): void
         $dependencyManager = new DependencyManager();
         $combinationSatisfyerResult = $dependencyManager->canBeInstalled($module);
 
+        if (!$combinationSatisfyerResult->foundCombination) {
+            return;
+        }
+
         $this->install($module);
         $this->installDependencies($combinationSatisfyerResult->foundCombination);
         //$this->installDependenciesOld($module);
@@ -305,6 +309,10 @@ public function updateWithDependencies(Module $module): ?Module
             return null; //TODO: Better return not nullable type Module and thorw an exception
         }
 
+        if (!$combinationSatisfyerResult->foundCombination) {
+            return null;
+        }
+
         $this->installDependencies($combinationSatisfyerResult->foundCombination);
         return $newModule;
     }
diff --git a/tests/unit/DependencyManager/DependencyBuilderTest.php b/tests/unit/DependencyManager/DependencyBuilderTest.php
index f7eea4e1..f8523914 100644
--- a/tests/unit/DependencyManager/DependencyBuilderTest.php
+++ b/tests/unit/DependencyManager/DependencyBuilderTest.php
@@ -27,7 +27,7 @@ public function testSatisfies()
         $systemSet->systems = [
             "modified" => '2.0.4.2',
             "php" => '7.4.0',
-            "mmlc" => '1.18.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',
@@ -41,7 +41,7 @@ public function testSatisfies()
             [
                 "modified" => '2.0.4.2',
                 "php" => '7.4.0',
-                "mmlc" => '1.18.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',

From 8db755fac9a9e60e5acede97c89091e23d7427f0 Mon Sep 17 00:00:00 2001
From: Robin Wieschendorf 
Date: Thu, 9 Mar 2023 21:43:51 +0100
Subject: [PATCH 20/37] chore: set default mmlc version

---
 src/Classes/Module.php            | 5 +----
 src/Classes/ModuleFactory.php     | 2 +-
 src/Templates/ModuleInfo.tmpl.php | 2 +-
 3 files changed, 3 insertions(+), 6 deletions(-)

diff --git a/src/Classes/Module.php b/src/Classes/Module.php
index 2c7e9d44..b8d32f5e 100644
--- a/src/Classes/Module.php
+++ b/src/Classes/Module.php
@@ -519,9 +519,6 @@ public function isCompatibleWithPhp(): bool
         return $comparator->satisfiesOr($phpVersionInstalled, $phpVersionContraint);
     }
 
-    /**
-     * Version 1.20.0 is the last version without isCompatibleWithMmlc()
-     */
     public function isCompatibleWithMmlc(): bool
     {
         $mmlcVersionInstalled = App::getMmlcVersion();
@@ -530,7 +527,7 @@ public function isCompatibleWithMmlc(): bool
         }
 
         $mmlc = $this->getMmlc();
-        $mmlcVersionContraint = $mmlc['version'] ?? '^1.20.0';
+        $mmlcVersionContraint = $mmlc['version'] ?? '';
         if (!$mmlcVersionContraint) {
             return true;
         }
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/Templates/ModuleInfo.tmpl.php b/src/Templates/ModuleInfo.tmpl.php
index 7b653cf7..b7e878d1 100644
--- a/src/Templates/ModuleInfo.tmpl.php
+++ b/src/Templates/ModuleInfo.tmpl.php
@@ -289,7 +289,7 @@
                                             Kompatibel mit MMLC
                                             
                                                 getMmlc()) { ?>
-                                                    getMmlc()['version'] ?? '^1.20.0') as $version) { ?>
+                                                    getMmlc()['version'] ?? '') as $version) { ?>
                                                         
                                                     
                                                 

From 545447b25bf37419f51afe6a8da753b297be08d3 Mon Sep 17 00:00:00 2001
From: Robin Wieschendorf 
Date: Thu, 9 Mar 2023 23:48:33 +0100
Subject: [PATCH 21/37] test: add CombinationTest

---
 src/Classes/DependencyManager/Combination.php | 23 ++++-
 .../DependencyManager/CombinationTest.php     | 91 +++++++++++++++++++
 tests/unit/RemoteModuleLoaderTest.php         |  2 +
 3 files changed, 114 insertions(+), 2 deletions(-)
 create mode 100644 tests/unit/DependencyManager/CombinationTest.php

diff --git a/src/Classes/DependencyManager/Combination.php b/src/Classes/DependencyManager/Combination.php
index 69da19a1..50ee265d 100644
--- a/src/Classes/DependencyManager/Combination.php
+++ b/src/Classes/DependencyManager/Combination.php
@@ -52,13 +52,17 @@ public function clone(): Combination
     }
 
     /**
-     * Liefert eine neues Combinations Obj zurück, in der nur echte Module enthalten sind.
+     * 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 (strpos($archiveName, '/') === false) { // Überspringe Einträge wie modified, php, mmlc ...
+            if (!$this->isArchiveName($archiveName)) {
                 continue;
             }
 
@@ -66,4 +70,19 @@ public function strip(): Combination
         }
         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;
+    }
 }
diff --git a/tests/unit/DependencyManager/CombinationTest.php b/tests/unit/DependencyManager/CombinationTest.php
new file mode 100644
index 00000000..a9b5f405
--- /dev/null
+++ b/tests/unit/DependencyManager/CombinationTest.php
@@ -0,0 +1,91 @@
+
+ *
+ * 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->combinations);
+    }
+
+    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->combinations);
+    }
+
+    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->combinations
+        );
+
+        $this->assertEquals(
+            [
+                'foo/bar' => '2.0.0'
+            ],
+            $stripedCombination->combinations
+        );
+    }
+}
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();

From b4eb6be2b64e46ac457ad1cde0f568b4030ff4ac Mon Sep 17 00:00:00 2001
From: Robin Wieschendorf 
Date: Sun, 26 Mar 2023 20:44:21 +0200
Subject: [PATCH 22/37] feat: add Comparator:satisfiesAnd() and refactor
 Comparator:satisfies()

---
 .../CombinationSatisfyer.php                  |  2 +-
 src/Classes/Module.php                        |  4 +-
 src/Classes/Semver/Comparator.php             | 50 ++++++++++++++++---
 src/Classes/Semver/Filter.php                 |  2 +-
 .../unit/SemverTests/SemverComparatorTest.php | 10 +++-
 5 files changed, 54 insertions(+), 14 deletions(-)

diff --git a/src/Classes/DependencyManager/CombinationSatisfyer.php b/src/Classes/DependencyManager/CombinationSatisfyer.php
index eba2fb36..e6fbd129 100644
--- a/src/Classes/DependencyManager/CombinationSatisfyer.php
+++ b/src/Classes/DependencyManager/CombinationSatisfyer.php
@@ -123,7 +123,7 @@ public function satisfiesCominationFromModuleTree(ModuleTree $moduleTree, Combin
         // Es gibt keine weiteren Untermodule
         if (!$moduleTree->moduleVersions) {
             //var_dump($selectedVersion . ' == ' . $moduleTree->versionConstraint);
-            return $this->comparator->satisfiesOr($selectedVersion, $moduleTree->versionConstraint);
+            return $this->comparator->satisfies($selectedVersion, $moduleTree->versionConstraint);
         }
 
         foreach ($moduleTree->moduleVersions as $moduleVersion) {
diff --git a/src/Classes/Module.php b/src/Classes/Module.php
index afeae49a..bf9a9a02 100644
--- a/src/Classes/Module.php
+++ b/src/Classes/Module.php
@@ -516,7 +516,7 @@ public function isCompatibleWithPhp(): bool
 
         $phpVersionInstalled = phpversion();
         $comparator = new Comparator(new Parser());
-        return $comparator->satisfiesOr($phpVersionInstalled, $phpVersionContraint);
+        return $comparator->satisfies($phpVersionInstalled, $phpVersionContraint);
     }
 
     public function isCompatibleWithMmlc(): bool
@@ -533,7 +533,7 @@ public function isCompatibleWithMmlc(): bool
         }
 
         $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/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/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/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'));
     }
 }

From 9a17abd2eb66f396a956fa3850ae8f92608a49bb Mon Sep 17 00:00:00 2001
From: Robin Wieschendorf 
Date: Sun, 26 Mar 2023 20:45:41 +0200
Subject: [PATCH 23/37] feat: add 3 new methods: createConstraint(),
 createConstraintFromConstraints(), getModuleByArchiveNameAndVersion()

---
 .../DependencyManager/DependencyBuilder.php   | 41 +++++++++++++++++++
 1 file changed, 41 insertions(+)

diff --git a/src/Classes/DependencyManager/DependencyBuilder.php b/src/Classes/DependencyManager/DependencyBuilder.php
index ac1b17cd..64fc4ba7 100644
--- a/src/Classes/DependencyManager/DependencyBuilder.php
+++ b/src/Classes/DependencyManager/DependencyBuilder.php
@@ -128,4 +128,45 @@ public function satisfies(string $archiveName, string $constraint, SystemSet $sy
 
         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] ?? '';
+    }
 }

From 8d97dcfabb178a2b10c29a03ba9323d5af7842d5 Mon Sep 17 00:00:00 2001
From: Robin Wieschendorf 
Date: Sun, 26 Mar 2023 20:47:08 +0200
Subject: [PATCH 24/37] feat: add new methods and tests
 SystemSet::getArchiveNames() and SystemSet::getArchives()

---
 src/Classes/DependencyManager/SystemSet.php   | 27 ++++++++++++
 .../unit/DependencyManager/SystemSetTest.php  | 41 +++++++++++++++++++
 2 files changed, 68 insertions(+)
 create mode 100644 tests/unit/DependencyManager/SystemSetTest.php

diff --git a/src/Classes/DependencyManager/SystemSet.php b/src/Classes/DependencyManager/SystemSet.php
index da7ba951..c42cc0a2 100644
--- a/src/Classes/DependencyManager/SystemSet.php
+++ b/src/Classes/DependencyManager/SystemSet.php
@@ -15,5 +15,32 @@
 
 class SystemSet
 {
+    /** @var array */
     public $systems = [];
+
+    /**
+     * @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 getArchives(): array
+    {
+        $archives = [];
+        foreach ($this->systems as $name => $version) {
+            if (!strpos($name, '/')) {
+                continue;
+            }
+            $archives[$name] = $version;
+        }
+        return $archives;
+    }
 }
diff --git a/tests/unit/DependencyManager/SystemSetTest.php b/tests/unit/DependencyManager/SystemSetTest.php
new file mode 100644
index 00000000..4fbe9503
--- /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->systems = $systems;
+
+        // Act
+        $result = $systemSet->getArchiveNames();
+
+        // Assert
+        $expected = ['systemB/1.2', 'systemC/2.0', 'systemD/2.1'];
+        $this->assertEquals($expected, $result);
+    }
+}

From e4d6c201b8b24afd0ab4c89f5f0a904ac5c84445 Mon Sep 17 00:00:00 2001
From: Robin Wieschendorf 
Date: Sun, 26 Mar 2023 20:47:53 +0200
Subject: [PATCH 25/37] feat: add class and test Comstraint

---
 src/Classes/Semver/Constraint.php         |  57 +++++++++++
 tests/unit/SemverTests/ConstraintTest.php | 112 ++++++++++++++++++++++
 2 files changed, 169 insertions(+)
 create mode 100644 src/Classes/Semver/Constraint.php
 create mode 100644 tests/unit/SemverTests/ConstraintTest.php

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/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));
+    }
+}

From b9ce5ec717fea9d991acdfd0b012c150110cbb6e Mon Sep 17 00:00:00 2001
From: Robin Wieschendorf 
Date: Sun, 26 Mar 2023 20:48:19 +0200
Subject: [PATCH 26/37] style: improve coding style

---
 src/Classes/DependencyManager/FlatEntry.php      | 2 +-
 tests/unit/DependencyManager/CombinationTest.php | 1 -
 2 files changed, 1 insertion(+), 2 deletions(-)

diff --git a/src/Classes/DependencyManager/FlatEntry.php b/src/Classes/DependencyManager/FlatEntry.php
index b62e1569..0cc7be65 100644
--- a/src/Classes/DependencyManager/FlatEntry.php
+++ b/src/Classes/DependencyManager/FlatEntry.php
@@ -23,7 +23,7 @@ class FlatEntry
     /** @var array version strings */
     public $versions = [];
 
-    public function combine(FlatEntry $flatEntry)
+    public function combine(FlatEntry $flatEntry): void
     {
         if ($this->archiveName !== $flatEntry->archiveName) {
             throw new Exception("Cant combine FlatEntry {$this->archiveName} and {$flatEntry->archiveName}");
diff --git a/tests/unit/DependencyManager/CombinationTest.php b/tests/unit/DependencyManager/CombinationTest.php
index a9b5f405..4cf0a777 100644
--- a/tests/unit/DependencyManager/CombinationTest.php
+++ b/tests/unit/DependencyManager/CombinationTest.php
@@ -19,7 +19,6 @@
 
 class CombinationTest extends TestCase
 {
-
     public function testAdd()
     {
         $combination = new Combination();

From 497a01c995e522347ea2404ad4d9670169f2bc8a Mon Sep 17 00:00:00 2001
From: Robin Wieschendorf 
Date: Sun, 26 Mar 2023 20:49:53 +0200
Subject: [PATCH 27/37] feat: check whether the version to be installed is
 compatible with the modules already installed

---
 src/Classes/DependencyManager/DependencyBuilder.php | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/src/Classes/DependencyManager/DependencyBuilder.php b/src/Classes/DependencyManager/DependencyBuilder.php
index 64fc4ba7..306fe82d 100644
--- a/src/Classes/DependencyManager/DependencyBuilder.php
+++ b/src/Classes/DependencyManager/DependencyBuilder.php
@@ -108,9 +108,10 @@ public function satisfiesContraints2(string $archiveName, string $constraint, Sy
         return $combinationSatisfyerResult;
     }
 
-
     public function satisfies(string $archiveName, string $constraint, SystemSet $systemSet): CombinationSatisfyerResult
     {
+        $constraint = $this->createConstraint($archiveName, $constraint, $systemSet);
+
         $moduleTreeBuilder = new ModuleTreeBuilder();
         $moduleTree = $moduleTreeBuilder->buildByConstraints($archiveName, $constraint);
         $this->logFile($moduleTree, '3-moduleTrees.json');

From 88352799cded2a4f6978146db56df53a4bcab915 Mon Sep 17 00:00:00 2001
From: Robin Wieschendorf 
Date: Mon, 27 Mar 2023 11:55:54 +0200
Subject: [PATCH 28/37] refactor: use DependencyException instead of Exception

---
 src/Classes/DependencyManager/Combination.php     |  6 ++----
 .../DependencyManager/CombinationBuilder.php      |  2 --
 .../DependencyManager/CombinationSatisfyer.php    |  6 +++++-
 .../DependencyManager/DependencyManager.php       | 15 ++++++++++++---
 src/Classes/DependencyManager/FlatEntry.php       |  4 +---
 5 files changed, 20 insertions(+), 13 deletions(-)

diff --git a/src/Classes/DependencyManager/Combination.php b/src/Classes/DependencyManager/Combination.php
index 50ee265d..fdc92a2d 100644
--- a/src/Classes/DependencyManager/Combination.php
+++ b/src/Classes/DependencyManager/Combination.php
@@ -13,8 +13,6 @@
 
 namespace RobinTheHood\ModifiedModuleLoaderClient\DependencyManager;
 
-use Exception;
-
 class Combination
 {
     /** @var array */
@@ -23,7 +21,7 @@ class Combination
     public function add(string $archiveName, string $version)
     {
         if (array_key_exists($archiveName, $this->combinations)) {
-            throw new Exception($archiveName . ' is already set.');
+            throw new DependencyException($archiveName . ' is already set.');
         }
 
         $this->combinations[$archiveName] = $version;
@@ -37,7 +35,7 @@ public function overwrite(string $archiveName, string $version)
     public function getVersion(string $archiveName): string
     {
         if (!array_key_exists($archiveName, $this->combinations)) {
-            throw new Exception('Version of ' . $archiveName . ' not found.');
+            throw new DependencyException('Version of ' . $archiveName . ' not found.');
         }
 
         return $this->combinations[$archiveName];
diff --git a/src/Classes/DependencyManager/CombinationBuilder.php b/src/Classes/DependencyManager/CombinationBuilder.php
index 7263baba..ee4eedbb 100644
--- a/src/Classes/DependencyManager/CombinationBuilder.php
+++ b/src/Classes/DependencyManager/CombinationBuilder.php
@@ -13,8 +13,6 @@
 
 namespace RobinTheHood\ModifiedModuleLoaderClient\DependencyManager;
 
-use Exception;
-
 class CombinationBuilder
 {
     /**
diff --git a/src/Classes/DependencyManager/CombinationSatisfyer.php b/src/Classes/DependencyManager/CombinationSatisfyer.php
index e6fbd129..41b9e77c 100644
--- a/src/Classes/DependencyManager/CombinationSatisfyer.php
+++ b/src/Classes/DependencyManager/CombinationSatisfyer.php
@@ -118,7 +118,11 @@ public function satisfiesCominationFromModuleTree(ModuleTree $moduleTree, Combin
     {
         // Context: Module
         $archiveName = $moduleTree->archiveName;
-        $selectedVersion = $combination->getVersion($archiveName);
+        try {
+            $selectedVersion = $combination->getVersion($archiveName);
+        } catch (DependencyException $e) {
+            return false;
+        }
 
         // Es gibt keine weiteren Untermodule
         if (!$moduleTree->moduleVersions) {
diff --git a/src/Classes/DependencyManager/DependencyManager.php b/src/Classes/DependencyManager/DependencyManager.php
index 21b37c23..5a6cf1a8 100644
--- a/src/Classes/DependencyManager/DependencyManager.php
+++ b/src/Classes/DependencyManager/DependencyManager.php
@@ -17,7 +17,6 @@
 use RobinTheHood\ModifiedModuleLoaderClient\DependencyManager\CombinationSatisfyerResult;
 use RobinTheHood\ModifiedModuleLoaderClient\DependencyManager\DependencyBuilder;
 use RobinTheHood\ModifiedModuleLoaderClient\DependencyManager\SystemSetFactory;
-use RobinTheHood\ModifiedModuleLoaderClient\DependencyManager\DependencyException;
 use RobinTheHood\ModifiedModuleLoaderClient\Module;
 use RobinTheHood\ModifiedModuleLoaderClient\Loader\ModuleLoader;
 use RobinTheHood\ModifiedModuleLoaderClient\Semver\Comparator;
@@ -86,9 +85,19 @@ public function canBeInstalled(Module $module): CombinationSatisfyerResult
         $systemSet = $systemSetFactory->getSystemSet();
 
         $dependencyBuilder = new DependencyBuilder();
-        $combinationSatisfyerResult = $dependencyBuilder->satisfies($module->getArchiveName(), $module->getVersion(), $systemSet);
+        $combinationSatisfyerResult = $dependencyBuilder->satisfies(
+            $module->getArchiveName(),
+            $module->getVersion(),
+            $systemSet
+        );
+
         if (!$combinationSatisfyerResult->foundCombination) {
-            throw new DependencyException("Can not install module {$module->getArchiveName()} in version {$module->getVersion()}");
+            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."
+            );
         }
 
         $modules = $this->getAllModulesFromCombination($combinationSatisfyerResult->foundCombination);
diff --git a/src/Classes/DependencyManager/FlatEntry.php b/src/Classes/DependencyManager/FlatEntry.php
index 0cc7be65..9aa3aa75 100644
--- a/src/Classes/DependencyManager/FlatEntry.php
+++ b/src/Classes/DependencyManager/FlatEntry.php
@@ -13,8 +13,6 @@
 
 namespace RobinTheHood\ModifiedModuleLoaderClient\DependencyManager;
 
-use Exception;
-
 class FlatEntry
 {
     /** @var string */
@@ -26,7 +24,7 @@ class FlatEntry
     public function combine(FlatEntry $flatEntry): void
     {
         if ($this->archiveName !== $flatEntry->archiveName) {
-            throw new Exception("Cant combine FlatEntry {$this->archiveName} and {$flatEntry->archiveName}");
+            throw new DependencyException("Cant combine FlatEntry {$this->archiveName} and {$flatEntry->archiveName}");
         }
 
         $this->versions = array_merge($this->versions, $flatEntry->versions);

From 905c0b2547271d60d2ee58676d2dce8df9e781b0 Mon Sep 17 00:00:00 2001
From: Robin Wieschendorf 
Date: Mon, 27 Mar 2023 11:56:30 +0200
Subject: [PATCH 29/37] feat: add ban icon to error message

---
 src/Classes/ViewModels/NotificationViewModel.php | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/Classes/ViewModels/NotificationViewModel.php b/src/Classes/ViewModels/NotificationViewModel.php
index 6e8bc623..c45f42f4 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 '';
         }

From 13df422ab9c23002011dc96b4600a0cb6076bd73 Mon Sep 17 00:00:00 2001
From: Robin Wieschendorf 
Date: Mon, 27 Mar 2023 11:58:04 +0200
Subject: [PATCH 30/37] fix: make installed version updateable again

---
 src/Classes/DependencyManager/DependencyBuilder.php | 1 +
 src/Classes/DependencyManager/SystemSet.php         | 7 +++++++
 2 files changed, 8 insertions(+)

diff --git a/src/Classes/DependencyManager/DependencyBuilder.php b/src/Classes/DependencyManager/DependencyBuilder.php
index 306fe82d..ee8ac7da 100644
--- a/src/Classes/DependencyManager/DependencyBuilder.php
+++ b/src/Classes/DependencyManager/DependencyBuilder.php
@@ -110,6 +110,7 @@ public function satisfiesContraints2(string $archiveName, string $constraint, Sy
 
     public function satisfies(string $archiveName, string $constraint, SystemSet $systemSet): CombinationSatisfyerResult
     {
+        $systemSet->removeByArchiveName($archiveName);
         $constraint = $this->createConstraint($archiveName, $constraint, $systemSet);
 
         $moduleTreeBuilder = new ModuleTreeBuilder();
diff --git a/src/Classes/DependencyManager/SystemSet.php b/src/Classes/DependencyManager/SystemSet.php
index c42cc0a2..08e63556 100644
--- a/src/Classes/DependencyManager/SystemSet.php
+++ b/src/Classes/DependencyManager/SystemSet.php
@@ -43,4 +43,11 @@ public function getArchives(): array
         }
         return $archives;
     }
+
+    public function removeByArchiveName(string $archiveName): void
+    {
+        if (array_key_exists($archiveName, $this->systems)) {
+            unset($this->systems[$archiveName]);
+        }
+    }
 }

From 7375bc26825103426573fead7756756499ac22c9 Mon Sep 17 00:00:00 2001
From: Robin Wieschendorf 
Date: Mon, 27 Mar 2023 11:59:09 +0200
Subject: [PATCH 31/37] fix: add missing use statement

---
 src/Classes/DependencyManager/DependencyBuilder.php | 1 +
 1 file changed, 1 insertion(+)

diff --git a/src/Classes/DependencyManager/DependencyBuilder.php b/src/Classes/DependencyManager/DependencyBuilder.php
index ee8ac7da..0d54be76 100644
--- a/src/Classes/DependencyManager/DependencyBuilder.php
+++ b/src/Classes/DependencyManager/DependencyBuilder.php
@@ -13,6 +13,7 @@
 
 use RobinTheHood\ModifiedModuleLoaderClient\Loader\ModuleLoader;
 use RobinTheHood\ModifiedModuleLoaderClient\Module;
+use RobinTheHood\ModifiedModuleLoaderClient\Semver\Constraint;
 
 class DependencyBuilder
 {

From fb3e2425465050192c09c899a54e9b5ecedc2c2d Mon Sep 17 00:00:00 2001
From: Robin Wieschendorf 
Date: Tue, 28 Mar 2023 18:57:51 +0200
Subject: [PATCH 32/37] feat: add class FailLog

---
 src/Classes/DependencyManager/FailLog.php | 98 +++++++++++++++++++++++
 1 file changed, 98 insertions(+)
 create mode 100644 src/Classes/DependencyManager/FailLog.php

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); + } +} From ed36342857888851d11bc4d4a8588098f5e1d980 Mon Sep 17 00:00:00 2001 From: Robin Wieschendorf Date: Tue, 28 Mar 2023 18:59:14 +0200 Subject: [PATCH 33/37] feat: add method __toString() to Combination --- src/Classes/DependencyManager/Combination.php | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/Classes/DependencyManager/Combination.php b/src/Classes/DependencyManager/Combination.php index fdc92a2d..e104f2fe 100644 --- a/src/Classes/DependencyManager/Combination.php +++ b/src/Classes/DependencyManager/Combination.php @@ -83,4 +83,14 @@ private function isArchiveName(string $archiveName): bool return true; } + + + public function __toString(): string + { + $strings = []; + foreach ($this->combinations as $archiveName => $version) { + $strings[] = "$archiveName v$version"; + } + return implode(', ', $strings); + } } From 10ac4ae47bd4c60af04e36ce11e3a5d81f1406da Mon Sep 17 00:00:00 2001 From: Robin Wieschendorf Date: Tue, 28 Mar 2023 19:06:38 +0200 Subject: [PATCH 34/37] feat: add method getAll() to Combination --- src/Classes/DependencyManager/Combination.php | 16 ++++++++++++---- .../DependencyManager/DependencyManager.php | 2 +- .../DependencyManager/FlatEntryBuilder.php | 4 ++-- tests/unit/DependencyManager/CombinationTest.php | 8 ++++---- 4 files changed, 19 insertions(+), 11 deletions(-) diff --git a/src/Classes/DependencyManager/Combination.php b/src/Classes/DependencyManager/Combination.php index e104f2fe..0621d779 100644 --- a/src/Classes/DependencyManager/Combination.php +++ b/src/Classes/DependencyManager/Combination.php @@ -15,10 +15,18 @@ class Combination { - /** @var array */ - public $combinations = []; + /** @var array */ + private $combinations = []; - public function add(string $archiveName, string $version) + /** + * @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.'); @@ -27,7 +35,7 @@ public function add(string $archiveName, string $version) $this->combinations[$archiveName] = $version; } - public function overwrite(string $archiveName, string $version) + public function overwrite(string $archiveName, string $version): void { $this->combinations[$archiveName] = $version; } diff --git a/src/Classes/DependencyManager/DependencyManager.php b/src/Classes/DependencyManager/DependencyManager.php index 5a6cf1a8..d72b170d 100644 --- a/src/Classes/DependencyManager/DependencyManager.php +++ b/src/Classes/DependencyManager/DependencyManager.php @@ -64,7 +64,7 @@ public function getAllModulesFromCombination(Combination $combination): array $moduleLoader->resetCache(); $modules = []; - foreach ($combination->strip()->combinations as $archiveName => $version) { + 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); diff --git a/src/Classes/DependencyManager/FlatEntryBuilder.php b/src/Classes/DependencyManager/FlatEntryBuilder.php index 075e85c2..5aa31c83 100644 --- a/src/Classes/DependencyManager/FlatEntryBuilder.php +++ b/src/Classes/DependencyManager/FlatEntryBuilder.php @@ -111,7 +111,7 @@ public function fitSystemSet(array $flatEntries, SystemSet $systemSet): array */ public function addFlatEntriesBySystemSet(array $flatEntries, SystemSet $systemSet): array { - foreach ($systemSet->systems as $archiveName => $version) { + foreach ($systemSet->getAll() as $archiveName => $version) { if ($this->findFatEntryByArchiveName($archiveName, $flatEntries)) { continue; } @@ -163,7 +163,7 @@ public function removeFlatEntriesWithNoVersion(array $flatEntries): array */ public function removeFlatEntriesBySystemSet(array $moduleFlatTreeEntries, SystemSet $systemSet): array { - foreach ($systemSet->systems as $archiveName => $version) { + foreach ($systemSet->getAll() as $archiveName => $version) { $versions = [$version]; $moduleFlatTreeEntries = $this->removeModuleFlatEnty($moduleFlatTreeEntries, $archiveName, $versions); } diff --git a/tests/unit/DependencyManager/CombinationTest.php b/tests/unit/DependencyManager/CombinationTest.php index 4cf0a777..f1ba2fa3 100644 --- a/tests/unit/DependencyManager/CombinationTest.php +++ b/tests/unit/DependencyManager/CombinationTest.php @@ -24,7 +24,7 @@ public function testAdd() $combination = new Combination(); $combination->add('foo/bar', '1.0.0'); - $this->assertEquals(['foo/bar' => '1.0.0'], $combination->combinations); + $this->assertEquals(['foo/bar' => '1.0.0'], $combination->getAll()); } public function testCanNotAddTwice() @@ -42,7 +42,7 @@ public function testOverwrite() $combination->add('foo/bar', '1.0.0'); $combination->overwrite('foo/bar', '2.0.0'); - $this->assertEquals(['foo/bar' => '2.0.0'], $combination->combinations); + $this->assertEquals(['foo/bar' => '2.0.0'], $combination->getAll()); } public function testGetVersion() @@ -77,14 +77,14 @@ public function testStrip() 'foo' => '1.0.0', 'foo/bar' => '2.0.0' ], - $combination->combinations + $combination->getAll() ); $this->assertEquals( [ 'foo/bar' => '2.0.0' ], - $stripedCombination->combinations + $stripedCombination->getAll() ); } } From 6ef066fd310699817b7a71cd0f2c072b39a8a1a2 Mon Sep 17 00:00:00 2001 From: Robin Wieschendorf Date: Tue, 28 Mar 2023 19:25:09 +0200 Subject: [PATCH 35/37] chore: rebase changes --- .../CombinationSatisfyer.php | 143 ++++++++--- .../CombinationSatisfyerResult.php | 20 +- .../DependencyManager/DependencyBuilder.php | 29 ++- .../DependencyManager/DependencyManager.php | 16 +- .../DependencyManagerOld.php | 2 +- src/Classes/DependencyManager/SystemSet.php | 42 +++- .../DependencyManager/SystemSetFactory.php | 8 +- src/Classes/IndexController.php | 32 ++- src/Classes/ModuleInstaller.php | 238 ++++++++++-------- .../DependencyBuilderTest.php | 8 +- .../unit/DependencyManager/SystemSetTest.php | 2 +- 11 files changed, 376 insertions(+), 164 deletions(-) diff --git a/src/Classes/DependencyManager/CombinationSatisfyer.php b/src/Classes/DependencyManager/CombinationSatisfyer.php index 41b9e77c..b3a417f7 100644 --- a/src/Classes/DependencyManager/CombinationSatisfyer.php +++ b/src/Classes/DependencyManager/CombinationSatisfyer.php @@ -32,25 +32,41 @@ public function __construct() * * @return CombinationSatisfyerResult */ - public function satisfiesCominationsFromModuleTrees(array $moduleTrees, array $combinations): 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); + $result = $this->satisfiesCominationFromModuleTrees( + $moduleTrees, + $testCombination, + $foundCombination, + $failLog + ); + if ($result) { $combinationSatisfyerResult = new CombinationSatisfyerResult( + CombinationSatisfyerResult::RESULT_COMBINATION_FOUND, $testCombination, - $foundCombination + $foundCombination, + $failLog ); return $combinationSatisfyerResult; } } + $combinationSatisfyerResult = new CombinationSatisfyerResult( - null, - null + CombinationSatisfyerResult::RESULT_COMBINATION_NOT_FOUND, + $testCombination, + $foundCombination, + $failLog ); + return $combinationSatisfyerResult; } @@ -60,24 +76,40 @@ public function satisfiesCominationsFromModuleTrees(array $moduleTrees, array $c * * @return CombinationSatisfyerResult */ - public function satisfiesCominationsFromModuleTree(ModuleTree $moduleTree, array $combinations): 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); + $result = $this->satisfiesCominationFromModuleTree( + $moduleTree, + $testCombination, + $foundCombination, + $failLog + ); + if ($result) { $combinationSatisfyerResult = new CombinationSatisfyerResult( + CombinationSatisfyerResult::RESULT_COMBINATION_FOUND, $testCombination, - $foundCombination + $foundCombination, + $failLog ); return $combinationSatisfyerResult; } } + $combinationSatisfyerResult = new CombinationSatisfyerResult( - null, - null + CombinationSatisfyerResult::RESULT_COMBINATION_NOT_FOUND, + $testCombination, + $foundCombination, + $failLog ); + return $combinationSatisfyerResult; } @@ -88,13 +120,21 @@ public function satisfiesCominationsFromModuleWithIterator( $foundCombination = new Combination(); while (true) { + $failLog = new FailLog(); $testCombination = $combinationIterator->current(); - $result = $this->satisfiesCominationFromModuleTree($moduleTree, $testCombination, $foundCombination); + $result = $this->satisfiesCominationFromModuleTree( + $moduleTree, + $testCombination, + $foundCombination, + $failLog + ); if ($result) { $combinationSatisfyerResult = new CombinationSatisfyerResult( + CombinationSatisfyerResult::RESULT_COMBINATION_FOUND, $testCombination, - $foundCombination + $foundCombination, + $failLog ); return $combinationSatisfyerResult; } @@ -102,8 +142,10 @@ public function satisfiesCominationsFromModuleWithIterator( $combinationIterator->next(); if ($combinationIterator->isStart()) { $combinationSatisfyerResult = new CombinationSatisfyerResult( - null, - null + CombinationSatisfyerResult::RESULT_COMBINATION_NOT_FOUND, + $testCombination, + $foundCombination, + $failLog ); return $combinationSatisfyerResult; } @@ -113,9 +155,19 @@ public function satisfiesCominationsFromModuleWithIterator( /** * @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): bool - { + public function satisfiesCominationFromModuleTree( + ModuleTree $moduleTree, + Combination $combination, + Combination &$foundCombination, + FailLog &$failLog, + array $moduleTreeChain = [] + ): bool { // Context: Module $archiveName = $moduleTree->archiveName; try { @@ -126,34 +178,69 @@ public function satisfiesCominationFromModuleTree(ModuleTree $moduleTree, Combin // Es gibt keine weiteren Untermodule if (!$moduleTree->moduleVersions) { - //var_dump($selectedVersion . ' == ' . $moduleTree->versionConstraint); - return $this->comparator->satisfies($selectedVersion, $moduleTree->versionConstraint); + $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 - //var_dump($archiveName . " {$moduleVersion->version} == {$selectedVersion}"); if ($moduleVersion->version === $selectedVersion) { $foundCombination->overwrite($archiveName, $moduleVersion->version); - return $this->satisfiesCominationFromModuleTrees($moduleVersion->require, $combination, $foundCombination); + + $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); } - // var_dump('xxx'); - // var_dump($moduleTree); + 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): bool - { + public function satisfiesCominationFromModuleTrees( + array $moduleTrees, + Combination $combination, + Combination &$foundCombination, + FailLog &$failLog, + array $moduleTreeChain = [] + ): bool { // Context: Expanded $moduleResult = true; foreach ($moduleTrees as $moduleTree) { - $moduleResult = - $moduleResult && $this->satisfiesCominationFromModuleTree($moduleTree, $combination, $foundCombination); + $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 index 3b0bb60f..df6cad59 100644 --- a/src/Classes/DependencyManager/CombinationSatisfyerResult.php +++ b/src/Classes/DependencyManager/CombinationSatisfyerResult.php @@ -15,14 +15,30 @@ 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; - public function __construct(?Combination $testCombination, ?Combination $foundCombination) - { + /** @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/DependencyBuilder.php b/src/Classes/DependencyManager/DependencyBuilder.php index 0d54be76..934cffc4 100644 --- a/src/Classes/DependencyManager/DependencyBuilder.php +++ b/src/Classes/DependencyManager/DependencyBuilder.php @@ -39,7 +39,7 @@ public function test() } $systemSet = new SystemSet(); - $systemSet->systems = [ + $systemSet->set([ "modified" => '2.0.4.2', "php" => '7.4.0', "mmlc" => '1.19.0', @@ -47,7 +47,7 @@ public function test() "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); @@ -80,14 +80,20 @@ public function satisfiesContraints1(Module $module, SystemSet $systemSet): Comb $this->logFile($combinations, '1-combinations.json'); $combinationSatisfyer = new CombinationSatisfyer(); - $combinationSatisfyerResult = $combinationSatisfyer->satisfiesCominationsFromModuleTrees($moduleTrees, $combinations); + $combinationSatisfyerResult = $combinationSatisfyer->satisfiesCominationsFromModuleTrees( + $moduleTrees, + $combinations + ); return $combinationSatisfyerResult; } - public function satisfiesContraints2(string $archiveName, string $constraint, SystemSet $systemSet): 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'); @@ -104,14 +110,17 @@ public function satisfiesContraints2(string $archiveName, string $constraint, Sy $this->logFile($combinations, '2-combinations.json'); $combinationSatisfyer = new CombinationSatisfyer(); - $combinationSatisfyerResult = $combinationSatisfyer->satisfiesCominationsFromModuleTree($moduleTree, $combinations); + $combinationSatisfyerResult = $combinationSatisfyer->satisfiesCominationsFromModuleTree( + $moduleTree, + $combinations + ); return $combinationSatisfyerResult; } public function satisfies(string $archiveName, string $constraint, SystemSet $systemSet): CombinationSatisfyerResult { - $systemSet->removeByArchiveName($archiveName); + $systemSet->remove($archiveName); $constraint = $this->createConstraint($archiveName, $constraint, $systemSet); $moduleTreeBuilder = new ModuleTreeBuilder(); @@ -127,8 +136,10 @@ public function satisfies(string $archiveName, string $constraint, SystemSet $sy $combinationIterator = new CombinationIterator($flatEntries); $combinationSatisfyer = new CombinationSatisfyer(); - $combinationSatisfyerResult = $combinationSatisfyer->satisfiesCominationsFromModuleWithIterator($moduleTree, $combinationIterator); - + $combinationSatisfyerResult = $combinationSatisfyer->satisfiesCominationsFromModuleWithIterator( + $moduleTree, + $combinationIterator + ); return $combinationSatisfyerResult; } diff --git a/src/Classes/DependencyManager/DependencyManager.php b/src/Classes/DependencyManager/DependencyManager.php index d72b170d..f5ef6721 100644 --- a/src/Classes/DependencyManager/DependencyManager.php +++ b/src/Classes/DependencyManager/DependencyManager.php @@ -76,14 +76,19 @@ public function getAllModulesFromCombination(Combination $combination): array /** * @param Module $module + * @param string[] $doNotCheck * * @return CombinationSatisfyerResult */ - public function canBeInstalled(Module $module): 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(), @@ -91,17 +96,18 @@ public function canBeInstalled(Module $module): CombinationSatisfyerResult $systemSet ); - if (!$combinationSatisfyerResult->foundCombination) { + 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." + . "or there is no compatible combination of dependencies. " + . " The following combination is required: {$combinationSatisfyerResult->failLog}" ); } - $modules = $this->getAllModulesFromCombination($combinationSatisfyerResult->foundCombination); - $this->canBeInstalledTestChanged($module, $modules); + // $modules = $this->getAllModulesFromCombination($combinationSatisfyerResult->foundCombination); + // $this->canBeInstalledTestChanged($module, $modules); return $combinationSatisfyerResult; } diff --git a/src/Classes/DependencyManager/DependencyManagerOld.php b/src/Classes/DependencyManager/DependencyManagerOld.php index 4556b415..bb391a42 100644 --- a/src/Classes/DependencyManager/DependencyManagerOld.php +++ b/src/Classes/DependencyManager/DependencyManagerOld.php @@ -68,7 +68,7 @@ public function getAllModulesFromCombination(Combination $combination): array $moduleLoader->resetCache(); $modules = []; - foreach ($combination->strip()->combinations as $archiveName => $version) { + 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); diff --git a/src/Classes/DependencyManager/SystemSet.php b/src/Classes/DependencyManager/SystemSet.php index 08e63556..b41941c6 100644 --- a/src/Classes/DependencyManager/SystemSet.php +++ b/src/Classes/DependencyManager/SystemSet.php @@ -15,8 +15,30 @@ class SystemSet { - /** @var array */ - public $systems = []; + /** + * 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[] @@ -29,6 +51,14 @@ public function getArchiveNames(): array return $archiveNames; } + /** + * @return array + */ + public function getAll(): array + { + return $this->systems; + } + /** * @return array */ @@ -44,10 +74,12 @@ public function getArchives(): array return $archives; } - public function removeByArchiveName(string $archiveName): void + public function remove(string $name): void { - if (array_key_exists($archiveName, $this->systems)) { - unset($this->systems[$archiveName]); + 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 index 280a3038..14e7b34f 100644 --- a/src/Classes/DependencyManager/SystemSetFactory.php +++ b/src/Classes/DependencyManager/SystemSetFactory.php @@ -22,14 +22,14 @@ class SystemSetFactory public function getSystemSet(): SystemSet { $systemSet = new SystemSet(); - $systemSet->systems['modified'] = ModifiedModuleLoaderClientShopInfo::getModifiedVersion(); - $systemSet->systems['php'] = phpversion(); - $systemSet->systems['mmlc'] = App::getMmlcVersion(); + $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->systems[$module->getArchiveName()] = $module->getVersion(); + $systemSet->add($module->getArchiveName(), $module->getVersion()); } return $systemSet; } diff --git a/src/Classes/IndexController.php b/src/Classes/IndexController.php index 7f522ae5..4c99f36e 100644 --- a/src/Classes/IndexController.php +++ b/src/Classes/IndexController.php @@ -22,6 +22,7 @@ use RobinTheHood\ModifiedModuleLoaderClient\SendMail; use RobinTheHood\ModifiedModuleLoaderClient\Config; use RobinTheHood\ModifiedModuleLoaderClient\DependencyManager\DependencyException; +use RuntimeException; class IndexController extends Controller { @@ -318,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()); @@ -357,7 +361,7 @@ private function invokeRevertChanges() 'text' => $e->getMessage(), 'type' => 'error' ]); - } catch (\RuntimeException $e) { + } catch (RuntimeException $e) { Notification::pushFlashMessage([ 'text' => $e->getMessage(), 'type' => 'error' @@ -393,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()); @@ -426,6 +435,11 @@ public function invokeUpdate() 'text' => $e->getMessage(), 'type' => 'error' ]); + } catch (RuntimeException $e) { + Notification::pushFlashMessage([ + 'text' => $e->getMessage(), + 'type' => 'error' + ]); } if (!$newModule) { @@ -503,14 +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()); @@ -542,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/ModuleInstaller.php b/src/Classes/ModuleInstaller.php index 001ab13f..c4e527fd 100644 --- a/src/Classes/ModuleInstaller.php +++ b/src/Classes/ModuleInstaller.php @@ -23,6 +23,7 @@ use RobinTheHood\ModifiedModuleLoaderClient\Loader\LocalModuleLoader; use RobinTheHood\ModifiedModuleLoaderClient\Helpers\FileHelper; use RobinTheHood\ModifiedModuleLoaderClient\ModuleHasher\ModuleHashFileCreator; +use RuntimeException; class ModuleInstaller { @@ -50,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; } @@ -82,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(); @@ -121,78 +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(Combination $combination): void + private function internalInstallDependencies(Module $parentModule, Combination $combination): void { $dependencyManager = new DependencyManager(); $modules = $dependencyManager->getAllModulesFromCombination($combination); foreach ($modules as $module) { - if (!$module->isLoaded()) { - $this->pull($module); + if ($parentModule->getArchiveName() === $module->getArchiveName()) { + continue; } + $this->internalPullAndInstall($module); } + } - $modules = $dependencyManager->getAllModulesFromCombination($combination); - foreach ($modules as $module) { - $this->uninstall($module); - if ($module->isLoaded() && !$module->isInstalled()) { - $this->install($module); - } - } + public function update(Module $module): ?Module + { + $installedModule = $module->getInstalledVersion(); + $newModule = $module->getNewestVersion(); - $this->createAutoloadFile(); - } + $dependencyManager = new DependencyManager(); + $combinationSatisfyerResult = $dependencyManager->canBeInstalled($module); - // public function installDependenciesOld(Module $module): void - // { - // $dependencyManager = new DependencyManager(); - // $modules = $dependencyManager->getAllModules($module); + if (!$combinationSatisfyerResult->foundCombination) { + throw new RuntimeException( + "Can not update module {$module->getArchiveName()} {$module->getVersion()}. " + . "No possible combination of versions found" + ); + } - // foreach ($modules as $depModule) { - // if (!$depModule->isLoaded()) { - // $this->pull($depModule); - // } - // } + if ($installedModule) { + $this->uninstall($installedModule); + } - // $modules = $dependencyManager->getAllModules($module); - // foreach ($modules as $depModule) { - // $this->uninstall($depModule); - // if ($depModule->isLoaded() && !$depModule->isInstalled()) { - // $this->install($depModule); - // } - // } + $this->pull($newModule); + $newModule = $this->reload($newModule); - // $this->createAutoloadFile(); - // } + $this->internalInstall($newModule); + $this->createAutoloadFile(); - public function installWithDependencies(Module $module): void + return $newModule; + } + + public function updateWithDependencies(Module $module): ?Module { + $installedModule = $module->getInstalledVersion(); + $newModule = $module->getNewestVersion(); + $dependencyManager = new DependencyManager(); $combinationSatisfyerResult = $dependencyManager->canBeInstalled($module); if (!$combinationSatisfyerResult->foundCombination) { - return; + throw new RuntimeException( + "Can not update module {$module->getArchiveName()} {$module->getVersion()} with dependencies. " + . "No possible combination of versions found" + ); } - $this->install($module); - $this->installDependencies($combinationSatisfyerResult->foundCombination); - //$this->installDependenciesOld($module); + if ($installedModule) { + $this->uninstall($installedModule); + } + + $this->pull($newModule); + $newModule = $this->reload($newModule); + + $this->internalInstall($newModule); + $this->internalInstallDependencies($newModule, $combinationSatisfyerResult->foundCombination); + $this->createAutoloadFile(); + + return $newModule; } - public function createAutoloadFile(): void + 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); + } + + private function createAutoloadFile(): void { $localModuleLoader = LocalModuleLoader::getModuleLoader(); $localModuleLoader->resetCache(); @@ -235,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) { @@ -267,57 +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(); - $moduleLoader->resetCache(); - $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 - { - $dependencyManager = new DependencyManager(); - $combinationSatisfyerResult = $dependencyManager->canBeInstalled($module); - - $newModule = $this->update($module); - if (!$newModule) { - return null; //TODO: Better return not nullable type Module and thorw an exception - } - - if (!$combinationSatisfyerResult->foundCombination) { - return null; - } - - $this->installDependencies($combinationSatisfyerResult->foundCombination); - 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; @@ -344,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/tests/unit/DependencyManager/DependencyBuilderTest.php b/tests/unit/DependencyManager/DependencyBuilderTest.php index f8523914..e22896f5 100644 --- a/tests/unit/DependencyManager/DependencyBuilderTest.php +++ b/tests/unit/DependencyManager/DependencyBuilderTest.php @@ -24,7 +24,7 @@ public function testSatisfies() $dependencyBuilder = new DependencyBuilder(); $systemSet = new SystemSet(); - $systemSet->systems = [ + $systemSet->set([ "modified" => '2.0.4.2', "php" => '7.4.0', "mmlc" => '1.20.0-beta.1', @@ -33,7 +33,7 @@ public function testSatisfies() "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); @@ -51,7 +51,7 @@ public function testSatisfies() 'firstweb/multi-order' => '1.13.3', "foo/bar" => '1.2.3' ], - $combinationSatisfyerResult->testCombination->combinations + $combinationSatisfyerResult->testCombination->getAll() ); $this->assertEqualsCanonicalizing( @@ -65,7 +65,7 @@ public function testSatisfies() "robinthehood/tfpdf" => '0.3.0', 'firstweb/multi-order' => '1.13.3', ], - $combinationSatisfyerResult->foundCombination->combinations + $combinationSatisfyerResult->foundCombination->getAll() ); } diff --git a/tests/unit/DependencyManager/SystemSetTest.php b/tests/unit/DependencyManager/SystemSetTest.php index 4fbe9503..28afb96f 100644 --- a/tests/unit/DependencyManager/SystemSetTest.php +++ b/tests/unit/DependencyManager/SystemSetTest.php @@ -29,7 +29,7 @@ public function testGetArchiveNamesReturnsExpectedArray() 'systemD/2.1' => '2.1', ]; $systemSet = new SystemSet(); - $systemSet->systems = $systems; + $systemSet->set($systems); // Act $result = $systemSet->getArchiveNames(); From e5fe5fe376c8ad3de1feec9f70c7356f39a06cf0 Mon Sep 17 00:00:00 2001 From: Robin Wieschendorf Date: Tue, 28 Mar 2023 19:09:01 +0200 Subject: [PATCH 36/37] fix: go to installed version url --- src/Classes/ViewModels/ModuleViewModel.php | 16 +++++++++++++--- src/Templates/ModuleInfo.tmpl.php | 2 +- 2 files changed, 14 insertions(+), 4 deletions(-) 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/Templates/ModuleInfo.tmpl.php b/src/Templates/ModuleInfo.tmpl.php index b7e878d1..9f46f424 100644 --- a/src/Templates/ModuleInfo.tmpl.php +++ b/src/Templates/ModuleInfo.tmpl.php @@ -131,7 +131,7 @@ Installieren (inkompatible Version) hasInstalledVersion()) { ?> - Zur installierten Version + Zur installierten Version isRemote() && $moduleView->isLoaded() && !$moduleView->isInstalled()) { ?> From 92c9a636905019ed47897f31237e1a856757742c Mon Sep 17 00:00:00 2001 From: Robin Wieschendorf Date: Tue, 28 Mar 2023 19:27:33 +0200 Subject: [PATCH 37/37] chore: rebase changes --- .../ViewModels/NotificationViewModel.php | 2 +- src/Templates/ModuleInfo.tmpl.php | 56 +++++++++---------- 2 files changed, 29 insertions(+), 29 deletions(-) diff --git a/src/Classes/ViewModels/NotificationViewModel.php b/src/Classes/ViewModels/NotificationViewModel.php index c45f42f4..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 9f46f424..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) { ?> - - -