From 6cf4b13d0ce5dbe6a7648bc74dccb8bd76530e5c Mon Sep 17 00:00:00 2001 From: Coen Jacobs Date: Wed, 11 Sep 2024 16:33:23 +0200 Subject: [PATCH] Rewritten logic to load packages and their dependencies --- src/Composer/Autoload/NamespaceAutoloader.php | 7 +- src/Config/Mozart.php | 12 ++ src/Config/Package.php | 37 +++- src/Config/PackageFactory.php | 20 -- src/Console/Commands/Compose.php | 197 +++--------------- src/Mover.php | 17 ++ src/PackageFactory.php | 35 ++++ src/PackageFinder.php | 93 +++++++++ src/Replacer.php | 65 +++++- tests/Config/ConfigMapperTest.php | 2 +- tests/Config/config-mapper-test.json | 1 - tests/MoverTest.php | 2 +- 12 files changed, 285 insertions(+), 203 deletions(-) delete mode 100644 src/Config/PackageFactory.php create mode 100644 src/PackageFactory.php create mode 100644 src/PackageFinder.php diff --git a/src/Composer/Autoload/NamespaceAutoloader.php b/src/Composer/Autoload/NamespaceAutoloader.php index 955d8ab..62ef632 100644 --- a/src/Composer/Autoload/NamespaceAutoloader.php +++ b/src/Composer/Autoload/NamespaceAutoloader.php @@ -32,9 +32,14 @@ public function processConfig($autoloadConfig): void } } + public function getNamespace(): string + { + return rtrim($this->namespace, '\\') . '\\'; + } + public function getSearchNamespace(): string { - return $this->namespace; + return rtrim($this->namespace, '\\'); } public function getNamespacePath(): string diff --git a/src/Config/Mozart.php b/src/Config/Mozart.php index 8335881..a07c5b5 100644 --- a/src/Config/Mozart.php +++ b/src/Config/Mozart.php @@ -25,6 +25,8 @@ class Mozart public OverrideAutoload $override_autoload; public bool $delete_vendor_directories; + public string $workingDir = ''; + /** * @return string[] */ @@ -119,4 +121,14 @@ public function getExcludedPackages(): array { return $this->excluded_packages; } + + public function setWorkingDir(string $workingDir): void + { + $this->workingDir = $workingDir; + } + + public function getWorkingDir(): string + { + return $this->workingDir; + } } diff --git a/src/Config/Package.php b/src/Config/Package.php index a34d85c..c466ee9 100644 --- a/src/Config/Package.php +++ b/src/Config/Package.php @@ -6,6 +6,8 @@ use CoenJacobs\Mozart\Config\Autoload; use CoenJacobs\Mozart\Config\Extra; use CoenJacobs\Mozart\Config\ReadsConfig; +use CoenJacobs\Mozart\PackageFinder; +use Exception; use stdClass; class Package @@ -13,12 +15,12 @@ class Package use ReadsConfig; /** @var Package[] */ - public $requirePackages = []; + public $dependencies = []; public string $name; /** @var string[] */ - public array $require; + public array $require = []; public ?Autoload $autoload = null; public ?Extra $extra = null; @@ -68,9 +70,9 @@ public function getAutoloaders(): array /** * @return string[] */ - public function getPackages(): array + public function getRequire(): array { - return $this->require; + return array_keys($this->require); } /** @@ -78,21 +80,38 @@ public function getPackages(): array */ public function getDependencies(): array { - return $this->requirePackages; + return $this->dependencies; } - public function registerRequirePackage(Package $package): void + public function loadDependencies(): void { - array_push($this->requirePackages, $package); + $finder = PackageFinder::instance(); + if ($this->isValidMozartConfig() && !empty($this->getExtra())) { + $mozart = $this->getExtra()->getMozart(); + + if (empty($mozart)) { + throw new Exception("Couldn't load dependencies because config not set."); + } + $finder->setConfig($mozart); + } + + $dependencies = $finder->getPackagesBySlugs($this->getRequire()); + + $this->registerDependencies($dependencies); + } + + public function registerDependency(Package $package): void + { + array_push($this->dependencies, $package); } /** * @param Package[] $packages */ - public function registerRequirePackages(array $packages): void + public function registerDependencies(array $packages): void { foreach ($packages as $package) { - $this->registerRequirePackage($package); + $this->registerDependency($package); } } } diff --git a/src/Config/PackageFactory.php b/src/Config/PackageFactory.php deleted file mode 100644 index e84adf3..0000000 --- a/src/Config/PackageFactory.php +++ /dev/null @@ -1,20 +0,0 @@ -setAutoload($overrideAutoload); - } - - return $package; - } -} diff --git a/src/Console/Commands/Compose.php b/src/Console/Commands/Compose.php index 11ae219..aad21e6 100644 --- a/src/Console/Commands/Compose.php +++ b/src/Console/Commands/Compose.php @@ -3,9 +3,9 @@ namespace CoenJacobs\Mozart\Console\Commands; use CoenJacobs\Mozart\Config\Mozart; -use CoenJacobs\Mozart\Config\Package; -use CoenJacobs\Mozart\Config\PackageFactory; use CoenJacobs\Mozart\Mover; +use CoenJacobs\Mozart\PackageFactory; +use CoenJacobs\Mozart\PackageFinder; use CoenJacobs\Mozart\Replacer; use Exception; use Symfony\Component\Console\Command\Command; @@ -14,17 +14,17 @@ class Compose extends Command { - /** @var Mover */ - private $mover; + private Mover $mover; + private Replacer $replacer; + private Mozart $config; + private PackageFinder $finder; + private string $workingDir; - /** @var Replacer */ - private $replacer; - - /** @var string */ - private $workingDir; - - /** @var Mozart */ - private $config; + public function __construct() + { + $this->workingDir = getcwd(); + parent::__construct(); + } protected function configure(): void { @@ -35,17 +35,13 @@ protected function configure(): void protected function execute(InputInterface $input, OutputInterface $output): int { - $workingDir = getcwd(); - - if (! $workingDir) { + if (! $this->workingDir) { throw new Exception('Could not determine working directory.'); } - $this->workingDir = $workingDir; - - $composerFile = $workingDir . DIRECTORY_SEPARATOR. 'composer.json'; + $composerFile = $this->workingDir . DIRECTORY_SEPARATOR. 'composer.json'; try { - $package = PackageFactory::createPackage($composerFile); + $package = PackageFactory::createPackage($composerFile, null, false); } catch (Exception $e) { $output->write('Unable to read the composer.json file'); return 1; @@ -64,165 +60,30 @@ protected function execute(InputInterface $input, OutputInterface $output): int } $this->config = $config; + $this->config->setWorkingDir($this->workingDir); $require = $this->config->getPackages(); if (empty($require)) { - $require = $package->getPackages(); + $require = $package->getRequire(); } - $this->mover = new Mover($workingDir, $this->config); - $this->replacer = new Replacer($workingDir, $this->config); + $this->finder = PackageFinder::instance(); + $this->finder->setConfig($this->config); + + $package->loadDependencies(); + + $packages = $this->finder->getPackagesBySlugs($require); + $packages = $this->finder->findPackages($packages); - $packages = $this->findPackages($require); + $this->mover = new Mover($this->workingDir, $this->config); + $this->replacer = new Replacer($this->workingDir, $this->config); $this->mover->deleteTargetDirs($packages); - $this->movePackages($packages); - $this->replacePackages($packages); - $this->replaceParentInTree($packages); + $this->mover->movePackages($packages); + $this->replacer->replacePackages($packages); + $this->replacer->replaceParentInTree($packages); $this->replacer->replaceParentClassesInDirectory($this->config->getClassmapDirectory()); return 0; } - - /** - * @param Package[] $packages - */ - protected function movePackages($packages): void - { - foreach ($packages as $package) { - $this->movePackage($package); - } - - $this->mover->deleteEmptyDirs(); - } - - /** - * @param Package[] $packages - */ - protected function replacePackages($packages): void - { - foreach ($packages as $package) { - $this->replacePackage($package); - } - } - - /** - * Move all the packages over, one by one, starting on the deepest level of dependencies. - */ - public function movePackage(Package $package): void - { - if ($this->config->isExcludedPackage($package)) { - return; - } - - if (! empty($package->dependencies)) { - foreach ($package->getDependencies() as $dependency) { - $this->movePackage($dependency); - } - } - - $this->mover->movePackage($package); - } - - /** - * Replace contents of all the packages, one by one, starting on the deepest level of dependencies. - */ - public function replacePackage(Package $package): void - { - if ($this->config->isExcludedPackage($package)) { - return; - } - - if (! empty($package->dependencies)) { - foreach ($package->getDependencies() as $dependency) { - $this->replacePackage($dependency); - } - } - - $this->replacer->replacePackage($package); - } - - /** - * Loops through all dependencies and their dependencies and so on... - * will eventually return a list of all packages required by the full tree. - * - * @param ((int|string)|mixed)[] $slugs - * - * @return Package[] - */ - private function findPackages(array $slugs): array - { - $packages = []; - - foreach ($slugs as $package_slug) { - $packageDir = $this->workingDir . DIRECTORY_SEPARATOR . 'vendor' - . DIRECTORY_SEPARATOR . $package_slug . DIRECTORY_SEPARATOR; - - if (! is_dir($packageDir)) { - continue; - } - - $autoloaders = null; - $override_autoload = $this->config->getOverrideAutoload(); - if ($override_autoload !== false && isset($override_autoload->$package_slug)) { - $autoloaders = $override_autoload->$package_slug; - } - - $package = PackageFactory::createPackage($packageDir . 'composer.json', $autoloaders); - - if ($this->config->isExcludedPackage($package)) { - continue; - } - - $dependencies = $package->getDependencies(); - - $package->registerRequirePackages($this->findPackages($dependencies)); - $packages[$package_slug] = $package; - } - - return $packages; - } - - /** - * Get an array containing all the dependencies and dependencies - * @param Package $package - * @param Package[] $dependencies - * @return Package[] - */ - private function getAllDependenciesOfPackage(Package $package, $dependencies = []): array - { - if ($this->config->isExcludedPackage($package)) { - return $dependencies; - } - - if (empty($package->getDependencies())) { - return $dependencies; - } - - foreach ($package->getDependencies() as $dependency) { - $dependencies[] = $dependency; - } - - foreach ($package->getDependencies() as $dependency) { - $dependencies = $this->getAllDependenciesOfPackage($dependency, $dependencies); - } - - return $dependencies; - } - - /** - * @param Package[] $packages - */ - private function replaceParentInTree(array $packages): void - { - foreach ($packages as $package) { - $dependencies = $this->getAllDependenciesOfPackage($package); - - foreach ($dependencies as $dependency) { - $this->replacer->replaceParentPackage($dependency, $package); - } - - $this->replaceParentInTree($package->getDependencies()); - } - } } diff --git a/src/Mover.php b/src/Mover.php index f0d4518..4f3d65d 100644 --- a/src/Mover.php +++ b/src/Mover.php @@ -101,12 +101,29 @@ public function deleteEmptyDirs(): void } } + /** + * @param Package[] $packages + */ + public function movePackages($packages): void + { + foreach ($packages as $package) { + $this->movePackages($package->getDependencies()); + $this->movePackage($package); + } + + $this->deleteEmptyDirs(); + } + public function movePackage(Package $package): void { if (in_array($package->getName(), $this->movedPackages)) { return; } + if ($this->config->isExcludedPackage($package)) { + return; + } + foreach ($package->getAutoloaders() as $autoloader) { if ($autoloader instanceof NamespaceAutoloader) { $finder = new Finder(); diff --git a/src/PackageFactory.php b/src/PackageFactory.php new file mode 100644 index 0000000..da67d61 --- /dev/null +++ b/src/PackageFactory.php @@ -0,0 +1,35 @@ + */ + public static array $cache = []; + + public static function createPackage( + string $path, + stdClass $overrideAutoload = null, + bool $loadDependencies = true + ): Package { + if (isset(self::$cache[$path])) { + return self::$cache[$path]; + } + + $package = Package::loadFromFile($path); + + if (! empty($overrideAutoload)) { + $package->setAutoload($overrideAutoload); + } + + if ($loadDependencies) { + $package->loadDependencies(); + } + + self::$cache[$path] = $package; + return $package; + } +} diff --git a/src/PackageFinder.php b/src/PackageFinder.php new file mode 100644 index 0000000..9f162c6 --- /dev/null +++ b/src/PackageFinder.php @@ -0,0 +1,93 @@ +config = $config; + } + + public function getPackageBySlug(string $slug): ?Package + { + /** + * This case prevents issues where the requirements array can contain + * non-package like lines, for example: php or extensions. + */ + if (!strstr($slug, '/')) { + return null; + } + + if (empty($this->config)) { + throw new Exception("Config not set to find packages"); + } + + $packageDir = $this->config->getWorkingDir() . DIRECTORY_SEPARATOR . 'vendor' + . DIRECTORY_SEPARATOR . $slug . DIRECTORY_SEPARATOR; + + if (! is_dir($packageDir)) { + throw new Exception("Couldn't load package based on provided slug: " . $slug); + } + + $autoloaders = null; + $override_autoload = $this->config->getOverrideAutoload(); + if ($override_autoload !== false && isset($override_autoload->$slug)) { + $autoloaders = $override_autoload->$slug; + } + + return PackageFactory::createPackage($packageDir . 'composer.json', $autoloaders, true); + } + + /** + * @param string[] $slugs + * @return Package[] + */ + public function getPackagesBySlugs(array $slugs): array + { + $packages = array_map(function (string $slug) { + return $this->getPackageBySlug($slug); + }, $slugs); + + return array_filter($packages, function ($package) { + return $package instanceof Package; + }); + } + + /** + * Loops through all dependencies and their dependencies and so on... + * will eventually return a list of all packages required by the full tree. + * + * @param Package[] $packages + * + * @return Package[] + */ + public function findPackages(array $packages): array + { + foreach ($packages as $package) { + $dependencies = $package->getDependencies(); + + $package->registerDependencies($this->findPackages($dependencies)); + $packages[$package->getName()] = $package; + } + + return $packages; + } +} diff --git a/src/Replacer.php b/src/Replacer.php index 95d8259..bc2668b 100644 --- a/src/Replacer.php +++ b/src/Replacer.php @@ -45,6 +45,17 @@ public function __construct(string $workingDir, Mozart $config) $this->filesystem = new Filesystem($adapter); } + /** + * @param Package[] $packages + */ + public function replacePackages($packages): void + { + foreach ($packages as $package) { + $this->replacePackages($package->getDependencies()); + $this->replacePackage($package); + } + } + public function replacePackage(Package $package): void { foreach ($package->getAutoloaders() as $autoloader) { @@ -85,10 +96,13 @@ public function replaceInFile(string $targetFile, Autoloader $autoloader): void public function replacePackageByAutoloader(Package $package, Autoloader $autoloader): void { + if ($this->config->isExcludedPackage($package)) { + return; + } + if ($autoloader instanceof NamespaceAutoloader) { $source_path = $this->workingDir . $this->targetDir - . str_replace('\\', DIRECTORY_SEPARATOR, $autoloader->namespace) - . DIRECTORY_SEPARATOR; + . str_replace('\\', DIRECTORY_SEPARATOR, $autoloader->getNamespace()); $this->replaceInDirectory($autoloader, $source_path); } elseif ($autoloader instanceof Classmap) { $finder = new Finder(); @@ -174,6 +188,10 @@ public function replaceInDirectory(NamespaceAutoloader $autoloader, string $dire */ public function replaceParentPackage(Package $package, Package $parent): void { + if ($this->config->isExcludedPackage($package)) { + return; + } + foreach ($parent->getAutoloaders() as $parentAutoloader) { foreach ($package->getAutoloaders() as $autoloader) { if ($parentAutoloader instanceof NamespaceAutoloader) { @@ -201,4 +219,47 @@ public function replaceParentPackage(Package $package, Package $parent): void } } } + + /** + * Get an array containing all the dependencies and dependencies + * @param Package $package + * @param Package[] $dependencies + * @return Package[] + */ + private function getAllDependenciesOfPackage(Package $package, $dependencies = []): array + { + if (empty($package->getDependencies())) { + return $dependencies; + } + + foreach ($package->getDependencies() as $dependency) { + $dependencies[] = $dependency; + } + + foreach ($package->getDependencies() as $dependency) { + $dependencies = $this->getAllDependenciesOfPackage($dependency, $dependencies); + } + + return $dependencies; + } + + /** + * @param Package[] $packages + */ + public function replaceParentInTree(array $packages): void + { + foreach ($packages as $package) { + if ($this->config->isExcludedPackage($package)) { + continue; + } + + $dependencies = $this->getAllDependenciesOfPackage($package); + + foreach ($dependencies as $dependency) { + $this->replaceParentPackage($dependency, $package); + } + + $this->replaceParentInTree($package->getDependencies()); + } + } } diff --git a/tests/Config/ConfigMapperTest.php b/tests/Config/ConfigMapperTest.php index 846ae24..42e1ed7 100644 --- a/tests/Config/ConfigMapperTest.php +++ b/tests/Config/ConfigMapperTest.php @@ -4,7 +4,7 @@ use CoenJacobs\Mozart\Config\Mozart; use CoenJacobs\Mozart\Config\Package; -use CoenJacobs\Mozart\Config\PackageFactory; +use CoenJacobs\Mozart\PackageFactory; use PHPUnit\Framework\TestCase; class ConfigMapperTest extends TestCase diff --git a/tests/Config/config-mapper-test.json b/tests/Config/config-mapper-test.json index 1f53c4a..b043f34 100644 --- a/tests/Config/config-mapper-test.json +++ b/tests/Config/config-mapper-test.json @@ -1,6 +1,5 @@ { "require": { - "pimple/pimple": "^3.5" }, "autoload": { "psr-0": { diff --git a/tests/MoverTest.php b/tests/MoverTest.php index 01890c7..8945f49 100644 --- a/tests/MoverTest.php +++ b/tests/MoverTest.php @@ -2,7 +2,7 @@ declare(strict_types=1); use CoenJacobs\Mozart\Config\Mozart; -use CoenJacobs\Mozart\Config\PackageFactory; +use CoenJacobs\Mozart\PackageFactory; use CoenJacobs\Mozart\Console\Commands\Compose; use CoenJacobs\Mozart\Mover; use PHPUnit\Framework\TestCase;