diff --git a/Dockerfile b/Dockerfile index 1d8651f9..06ec0fdb 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,14 +1,20 @@ FROM composer:2.7.7 FROM php:8.3.9-cli-alpine AS base -FROM base as builder +FROM base AS builder RUN apk update && apk add git +RUN apk add --update linux-headers +RUN apk add --no-cache $PHPIZE_DEPS \ + && pecl install xdebug-3.3.2 \ + && docker-php-ext-enable xdebug +COPY ./docker/php/xdebug.ini /usr/local/etc/php/conf.d/docker-php-ext-xdebug.ini +COPY ./docker/php/error_reporting.ini /usr/local/etc/php/conf.d/error_reporting.ini COPY --from=composer /usr/bin/composer /usr/bin/composer COPY ./ /mozart/ WORKDIR /mozart/ RUN composer install -FROM builder as packager +FROM builder AS packager RUN rm -rf vendor RUN composer install --no-dev -o diff --git a/composer.json b/composer.json index eda74610..05f06aa5 100644 --- a/composer.json +++ b/composer.json @@ -12,7 +12,8 @@ "prefer-stable": true, "license": "MIT", "require": { - "php": "^8.0" + "php": "^8.0", + "netresearch/jsonmapper": "^4.4" }, "autoload": { "psr-4": { diff --git a/docker-compose.yml b/docker-compose.yml index 8835bc8f..a8d243c1 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,4 +1,3 @@ -version: '3.4' services: builder: build: diff --git a/docker/php/error_reporting.ini b/docker/php/error_reporting.ini new file mode 100644 index 00000000..7e566f46 --- /dev/null +++ b/docker/php/error_reporting.ini @@ -0,0 +1 @@ +error_reporting=E_ALL diff --git a/docker/php/xdebug.ini b/docker/php/xdebug.ini new file mode 100644 index 00000000..9c37b9c1 --- /dev/null +++ b/docker/php/xdebug.ini @@ -0,0 +1,6 @@ +zend_extension=xdebug + +[xdebug] +xdebug.mode=develop,debug +xdebug.client_host=host.docker.internal +xdebug.start_with_request=yes diff --git a/phpcs.xml.dist b/phpcs.xml.dist index 27d5af45..a83ef6c4 100644 --- a/phpcs.xml.dist +++ b/phpcs.xml.dist @@ -5,7 +5,7 @@ - + ./src diff --git a/src/Composer/Autoload/Autoloader.php b/src/Composer/Autoload/Autoloader.php index 9aa6b218..15f190a9 100644 --- a/src/Composer/Autoload/Autoloader.php +++ b/src/Composer/Autoload/Autoloader.php @@ -4,6 +4,9 @@ interface Autoloader { - public function processConfig($autoloadConfig); - public function getSearchNamespace(); + /** + * @param mixed $autoloadConfig + */ + public function processConfig($autoloadConfig): void; + public function getSearchNamespace(): string; } diff --git a/src/Composer/Autoload/Classmap.php b/src/Composer/Autoload/Classmap.php deleted file mode 100644 index 981df843..00000000 --- a/src/Composer/Autoload/Classmap.php +++ /dev/null @@ -1,36 +0,0 @@ -files, $value); - } else { - array_push($this->paths, $value); - } - } - } - - /** - * @throws \Exception - * - * @return void - */ - public function getSearchNamespace() - { - throw new \Exception('Classmap autoloaders do not contain a namespace and this method can not be used.'); - } -} diff --git a/src/Composer/Autoload/NamespaceAutoloader.php b/src/Composer/Autoload/NamespaceAutoloader.php index f8393995..62ef6328 100644 --- a/src/Composer/Autoload/NamespaceAutoloader.php +++ b/src/Composer/Autoload/NamespaceAutoloader.php @@ -12,7 +12,7 @@ abstract class NamespaceAutoloader implements Autoloader * * e.g. src/ * - * @var string[] + * @var array */ public $paths = []; @@ -20,29 +20,29 @@ abstract class NamespaceAutoloader implements Autoloader * A package's composer.json config autoload key's value, where $key is `psr-1`|`psr-4`|`classmap`. * * @param $autoloadConfig - * - * @return void */ - public function processConfig($autoloadConfig) + public function processConfig($autoloadConfig): void { - foreach ($autoloadConfig as $key => $value) { - $this->namespace = $key; - array_push($this->paths, $value); + if (is_array($autoloadConfig)) { + foreach ($autoloadConfig as $path) { + array_push($this->paths, $path); + } + } else { + array_push($this->paths, $autoloadConfig); } } - /** - * @return string - */ - public function getSearchNamespace() + public function getNamespace(): string { - return $this->namespace; + return rtrim($this->namespace, '\\') . '\\'; } - /** - * @return string - */ - public function getNamespacePath() + public function getSearchNamespace(): string + { + return rtrim($this->namespace, '\\'); + } + + public function getNamespacePath(): string { return ''; } diff --git a/src/Composer/Autoload/Psr0.php b/src/Composer/Autoload/Psr0.php deleted file mode 100644 index 2a009821..00000000 --- a/src/Composer/Autoload/Psr0.php +++ /dev/null @@ -1,7 +0,0 @@ -namespace, '\\'); - } - - /** - * @return string - */ - public function getNamespacePath() - { - return str_replace('\\', DIRECTORY_SEPARATOR, $this->namespace); - } -} diff --git a/src/Composer/Package.php b/src/Composer/Package.php deleted file mode 100644 index 78800fe8..00000000 --- a/src/Composer/Package.php +++ /dev/null @@ -1,69 +0,0 @@ -path = $path; - $this->config = json_decode(file_get_contents($this->path . '/composer.json')); - - if (isset($overrideAutoload)) { - $this->config->autoload = $overrideAutoload; - } - } - - /** - * @return void - */ - public function findAutoloaders() - { - $namespace_autoloaders = array( - 'psr-0' => 'CoenJacobs\Mozart\Composer\Autoload\Psr0', - 'psr-4' => 'CoenJacobs\Mozart\Composer\Autoload\Psr4', - 'classmap' => 'CoenJacobs\Mozart\Composer\Autoload\Classmap', - ); - - if (! isset($this->config->autoload)) { - return; - } - - foreach ($namespace_autoloaders as $key => $value) { - if (! isset($this->config->autoload->$key)) { - continue; - } - - $autoloadConfig = (array)$this->config->autoload->$key; - - /** @var Autoloader $autoloader */ - $autoloader = new $value(); - $autoloader->processConfig($autoloadConfig); - - array_push($this->autoloaders, $autoloader); - } - } -} diff --git a/src/Config/Autoload.php b/src/Config/Autoload.php new file mode 100644 index 00000000..55fd1aae --- /dev/null +++ b/src/Config/Autoload.php @@ -0,0 +1,67 @@ + */ + public array $autoloaders = []; + + public function setupAutoloaders(stdClass $autoloadData): void + { + $autoloaders = []; + + if (isset($autoloadData->{'psr-4'})) { + $psr4Autoloaders = (array) $autoloadData->{'psr-4'}; + foreach ($psr4Autoloaders as $key => $value) { + $autoloader = new Psr4(); + $autoloader->namespace = $key; + $autoloader->processConfig($value); + $autoloaders[] = $autoloader; + } + } + + if (isset($autoloadData->{'psr-0'})) { + $psr0Autoloaders = (array) $autoloadData->{'psr-0'}; + foreach ($psr0Autoloaders as $key => $value) { + $autoloader = new Psr0(); + $autoloader->namespace = $key; + $autoloader->processConfig($value); + $autoloaders[] = $autoloader; + } + } + + if (isset($autoloadData->classmap)) { + $autoloader = new Classmap(); + $autoloader->processConfig($autoloadData->classmap); + $autoloaders[] = $autoloader; + } + + $this->setAutoloaders($autoloaders); + } + + /** + * @param array $autoloaders + */ + public function setAutoloaders(array $autoloaders): void + { + foreach ($autoloaders as $autoloader) { + if (! $autoloader instanceof Autoloader) { + continue; + } + + array_push($this->autoloaders, $autoloader); + } + } + + /** + * @return Autoloader[] + */ + public function getAutoloaders(): array + { + return $this->autoloaders; + } +} diff --git a/src/Config/Classmap.php b/src/Config/Classmap.php new file mode 100644 index 00000000..c53529ed --- /dev/null +++ b/src/Config/Classmap.php @@ -0,0 +1,37 @@ +files, $value); + } else { + array_push($this->paths, $value); + } + } + } + + /** + * @throws Exception + */ + public function getSearchNamespace(): string + { + throw new Exception('Classmap autoloaders do not contain a namespace and this method can not be used.'); + } +} diff --git a/src/Config/Extra.php b/src/Config/Extra.php new file mode 100644 index 00000000..327482b3 --- /dev/null +++ b/src/Config/Extra.php @@ -0,0 +1,13 @@ +mozart; + } +} diff --git a/src/Config/Mozart.php b/src/Config/Mozart.php new file mode 100644 index 00000000..a07c5b5b --- /dev/null +++ b/src/Config/Mozart.php @@ -0,0 +1,134 @@ +packages; + } + + /** + * @param string[] $packages + */ + public function setPackages(array $packages): void + { + $this->packages = $packages; + } + + /** + * @param string[] $excluded_packages + */ + public function setExcludedPackages(array $excluded_packages): void + { + $this->excluded_packages = $excluded_packages; + } + + public function setOverrideAutoload(stdClass $object): void + { + $this->override_autoload = new OverrideAutoload($object); + } + + public function isValidMozartConfig(): bool + { + $required = [ 'dep_namespace', 'dep_directory', 'classmap_directory', 'classmap_prefix' ]; + + foreach ($required as $requiredProp) { + if (empty($this->$requiredProp)) { + return false; + } + } + + return true; + } + + public function isExcludedPackage(Package $package): bool + { + return in_array($package->getName(), $this->getExcludedPackages()); + } + + /** + * Returns the configured dependency directory, with an appended directory + * separator, if one isn't at the end of the configured string yet. + */ + public function getDepDirectory(): string + { + return rtrim($this->dep_directory, DIRECTORY_SEPARATOR).DIRECTORY_SEPARATOR; + } + + public function getClassmapDirectory(): string + { + return rtrim($this->classmap_directory, DIRECTORY_SEPARATOR).DIRECTORY_SEPARATOR; + } + + public function getDeleteVendorDirectories(): bool + { + return $this->delete_vendor_directories; + } + + public function getDependencyNamespace(): string + { + $namespace = preg_replace("/\\\{2,}$/", "\\", $this->dep_namespace."\\"); + + if (empty($namespace)) { + throw new Exception('Could not get target dependency namespace'); + } + + return $namespace; + } + + public function getClassmapPrefix(): string + { + return $this->classmap_prefix; + } + + public function getOverrideAutoload(): OverrideAutoload + { + return $this->override_autoload; + } + + /** + * @return string[] + */ + 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/OverrideAutoload.php b/src/Config/OverrideAutoload.php new file mode 100644 index 00000000..42967cb5 --- /dev/null +++ b/src/Config/OverrideAutoload.php @@ -0,0 +1,34 @@ + + */ +class OverrideAutoload extends ArrayObject +{ + public function __construct(stdClass $objects) + { + $objects = (array) $objects; + + $storage = []; + + foreach ($objects as $key => $object) { + $storage[$key] = $object; + } + + parent::__construct($storage); + } + + public function getByKey(string $key): mixed + { + if (! isset($this[$key])) { + return null; + } + + return $this[$key]; + } +} diff --git a/src/Config/Package.php b/src/Config/Package.php new file mode 100644 index 00000000..c466ee9f --- /dev/null +++ b/src/Config/Package.php @@ -0,0 +1,117 @@ +setupAutoloaders($data); + $this->autoload = $autoload; + } + + public function getExtra(): ?Extra + { + return $this->extra; + } + + public function isValidMozartConfig(): bool + { + if (empty($this->getExtra())) { + return false; + } + + if (empty($this->getExtra()->getMozart())) { + return false; + } + + return $this->getExtra()->getMozart()->isValidMozartConfig(); + } + + public function getName(): string + { + return $this->name; + } + + /** + * @return Autoloader[] + */ + public function getAutoloaders(): array + { + if (empty($this->autoload)) { + return array(); + } + + return $this->autoload->getAutoloaders(); + } + + /** + * @return string[] + */ + public function getRequire(): array + { + return array_keys($this->require); + } + + /** + * @return Package[] + */ + public function getDependencies(): array + { + return $this->dependencies; + } + + public function loadDependencies(): void + { + $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 registerDependencies(array $packages): void + { + foreach ($packages as $package) { + $this->registerDependency($package); + } + } +} diff --git a/src/Config/Psr0.php b/src/Config/Psr0.php new file mode 100644 index 00000000..6f664d22 --- /dev/null +++ b/src/Config/Psr0.php @@ -0,0 +1,9 @@ +namespace, '\\'); + } + + public function getNamespacePath(): string + { + return str_replace('\\', DIRECTORY_SEPARATOR, $this->namespace); + } +} diff --git a/src/Config/ReadsConfig.php b/src/Config/ReadsConfig.php new file mode 100644 index 00000000..4e7f3d30 --- /dev/null +++ b/src/Config/ReadsConfig.php @@ -0,0 +1,69 @@ + $config + */ + public static function loadFromArray(array $config): self + { + $encoded = json_encode($config); + + if (! $encoded) { + throw new Exception('Could not read config from provided array.'); + } + + $config = json_decode($encoded, false); + + if (! $config) { + throw new Exception('Could not read config from provided array.'); + } + + return self::loadFromStdClass($config); + } + + public static function loadFromStdClass(stdClass $config): self + { + $mapper = new JsonMapper(); + $mapper->bEnforceMapType = false; + $object = $mapper->map($config, self::class); + + if (! $object instanceof self) { + throw new Exception('Could not read config from provided array.'); + } + + return $object; + } + + public static function loadFromString(string $config): self + { + $config = json_decode($config); + + $mapper = new JsonMapper(); + $mapper->bEnforceMapType = false; + $object = $mapper->map($config, self::class); + + if (! $object instanceof self) { + throw new Exception('Could not read config from provided array.'); + } + + return $object; + } +} diff --git a/src/Console/Commands/Compose.php b/src/Console/Commands/Compose.php index dd9a567e..aad21e6a 100644 --- a/src/Console/Commands/Compose.php +++ b/src/Console/Commands/Compose.php @@ -2,31 +2,31 @@ namespace CoenJacobs\Mozart\Console\Commands; -use CoenJacobs\Mozart\Composer\Package; +use CoenJacobs\Mozart\Config\Mozart; use CoenJacobs\Mozart\Mover; +use CoenJacobs\Mozart\PackageFactory; +use CoenJacobs\Mozart\PackageFinder; use CoenJacobs\Mozart\Replacer; +use Exception; use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\OutputInterface; 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 */ - private $config; + public function __construct() + { + $this->workingDir = getcwd(); + parent::__construct(); + } - /** - * @return void - */ - protected function configure() + protected function configure(): void { $this->setName('compose'); $this->setDescription('Composes all dependencies as a package inside a WordPress plugin.'); @@ -35,205 +35,55 @@ protected function configure() protected function execute(InputInterface $input, OutputInterface $output): int { - $workingDir = getcwd(); - $this->workingDir = $workingDir; + if (! $this->workingDir) { + throw new Exception('Could not determine working directory.'); + } - $composerFile = $workingDir . DIRECTORY_SEPARATOR. 'composer.json'; - if (!file_exists($composerFile)) { - $output->write('No composer.json found at current directory: ' . $workingDir); + $composerFile = $this->workingDir . DIRECTORY_SEPARATOR. 'composer.json'; + try { + $package = PackageFactory::createPackage($composerFile, null, false); + } catch (Exception $e) { + $output->write('Unable to read the composer.json file'); return 1; } - $composer = json_decode(file_get_contents($composerFile)); - // If the json was malformed. - if (!is_object($composer)) { - $output->write('Unable to parse composer.json read at: ' . $workingDir); + if (! $package->isValidMozartConfig() || empty($package->getExtra())) { + $output->write('Mozart config not readable in composer.json at extra->mozart'); return 1; } - // if `extra` is missing or not an object or if it does not have a `mozart` key which is an object. - if (!isset($composer->extra) || !is_object($composer->extra) - || !isset($composer->extra->mozart) || !is_object($composer->extra->mozart)) { + $config = $package->getExtra()->getMozart(); + + if (empty($config)) { $output->write('Mozart config not readable in composer.json at extra->mozart'); return 1; } - $config = $composer->extra->mozart; - - $config->dep_namespace = preg_replace("/\\\{2,}$/", "\\", "$config->dep_namespace\\"); $this->config = $config; + $this->config->setWorkingDir($this->workingDir); - $require = array(); - if (isset($config->packages) && is_array($config->packages)) { - $require = $config->packages; - } elseif (isset($composer->require) && is_object($composer->require)) { - $require = array_keys(get_object_vars($composer->require)); + $require = $this->config->getPackages(); + if (empty($require)) { + $require = $package->getRequire(); } - $packagesByName = $this->findPackages($require); - $excludedPackagesNames = isset($config->excluded_packages) ? $config->excluded_packages : []; - $packagesToMoveByName = array_diff_key($packagesByName, array_flip($excludedPackagesNames)); - $packages = array_values($packagesToMoveByName); + $this->finder = PackageFinder::instance(); + $this->finder->setConfig($this->config); - foreach ($packages as $package) { - $package->dependencies = array_diff_key($package->dependencies, array_flip($excludedPackagesNames)); - } + $package->loadDependencies(); - $this->mover = new Mover($workingDir, $config); - $this->replacer = new Replacer($workingDir, $config); + $packages = $this->finder->getPackagesBySlugs($require); + $packages = $this->finder->findPackages($packages); + + $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->replacer->replaceParentClassesInDirectory($this->config->classmap_directory); + $this->mover->movePackages($packages); + $this->replacer->replacePackages($packages); + $this->replacer->replaceParentInTree($packages); + $this->replacer->replaceParentClassesInDirectory($this->config->getClassmapDirectory()); return 0; } - - /** - * @param $workingDir - * @param $config - * @param array $packages - * - * @return void - */ - protected function movePackages($packages): void - { - foreach ($packages as $package) { - $this->movePackage($package); - } - - $this->mover->deleteEmptyDirs(); - } - - /** - * @param $workingDir - * @param $config - * @param array $packages - * - * @return void - */ - 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. - * - * @return void - */ - public function movePackage($package): void - { - if (! empty($package->dependencies)) { - foreach ($package->dependencies 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. - * - * @return void - */ - public function replacePackage($package): void - { - if (! empty($package->dependencies)) { - foreach ($package->dependencies 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; - if (isset($this->config->override_autoload) && isset($this->config->override_autoload->$package_slug)) { - $autoloaders = $this->config->override_autoload->$package_slug; - } - - $package = new Package($packageDir, $autoloaders); - $package->findAutoloaders(); - - $config = json_decode(file_get_contents($packageDir . 'composer.json')); - - $dependencies = []; - if (isset($config->require)) { - $dependencies = array_keys((array)$config->require); - } - - $package->dependencies = $this->findPackages($dependencies); - $packages[$package_slug] = $package; - } - - return $packages; - } - - /** - * Get an array containing all the dependencies and dependencies - * @param Package $package - * @param array $dependencies - * @return array - */ - private function getAllDependenciesOfPackage(Package $package, $dependencies = []): array - { - if (empty($package->dependencies)) { - return $dependencies; - } - - /** @var Package $dependency */ - foreach ($package->dependencies as $dependency) { - $dependencies[] = $dependency; - } - - foreach ($package->dependencies as $dependency) { - $dependencies = $this->getAllDependenciesOfPackage($dependency, $dependencies); - } - - return $dependencies; - } - - /** - * @param array $packages - */ - private function replaceParentInTree(array $packages): void - { - /** @var Package $package */ - foreach ($packages as $package) { - $dependencies = $this->getAllDependenciesOfPackage($package); - - /** @var Package $dependency */ - foreach ($dependencies as $dependency) { - $this->replacer->replaceParentPackage($dependency, $package); - } - - $this->replaceParentInTree($package->dependencies); - } - } } diff --git a/src/Mover.php b/src/Mover.php index 42a78a45..4f3d65d6 100644 --- a/src/Mover.php +++ b/src/Mover.php @@ -3,11 +3,12 @@ namespace CoenJacobs\Mozart; use CoenJacobs\Mozart\Composer\Autoload\Autoloader; -use CoenJacobs\Mozart\Composer\Autoload\Classmap; use CoenJacobs\Mozart\Composer\Autoload\NamespaceAutoloader; -use CoenJacobs\Mozart\Composer\Autoload\Psr0; -use CoenJacobs\Mozart\Composer\Autoload\Psr4; -use CoenJacobs\Mozart\Composer\Package; +use CoenJacobs\Mozart\Config\Classmap; +use CoenJacobs\Mozart\Config\Mozart; +use CoenJacobs\Mozart\Config\Package; +use CoenJacobs\Mozart\Config\Psr0; +use CoenJacobs\Mozart\Config\Psr4; use League\Flysystem\Local\LocalFilesystemAdapter; use League\Flysystem\Filesystem; use Symfony\Component\Finder\Finder; @@ -21,20 +22,20 @@ class Mover /** @var string */ protected $targetDir; - /** @var \stdClass */ + /** @var Mozart */ protected $config; /** @var Filesystem */ protected $filesystem; - /** @var array */ + /** @var array */ protected $movedPackages = []; - public function __construct($workingDir, $config) + public function __construct(string $workingDir, Mozart $config) { - $this->workingDir = $workingDir; - $this->targetDir = $config->dep_directory; $this->config = $config; + $this->workingDir = $workingDir; + $this->targetDir = $this->config->getDepDirectory(); $adapter = new LocalFilesystemAdapter( $this->workingDir @@ -48,14 +49,11 @@ public function __construct($workingDir, $config) * Create the required `dep_directory` and `classmap_directory` and delete targetDirs of packages about to be moved. * * @param Package[] $packages The packages that, in the next step, will be moved. - * - * @return void */ public function deleteTargetDirs($packages): void { - $this->filesystem->createDirectory($this->config->dep_directory); - - $this->filesystem->createDirectory($this->config->classmap_directory); + $this->filesystem->createDirectory($this->config->getDepDirectory()); + $this->filesystem->createDirectory($this->config->getClassmapDirectory()); foreach ($packages as $package) { $this->deleteDepTargetDirs($package); @@ -66,63 +64,73 @@ public function deleteTargetDirs($packages): void * Delete the directories about to be used for packages earmarked for Mozart namespacing. * * @visibility private to allow recursion through packages and subpackages. - * - * @param Package $package - * - * @return void */ - private function deleteDepTargetDirs($package): void + private function deleteDepTargetDirs(Package $package): void { - foreach ($package->autoloaders as $packageAutoloader) { + foreach ($package->getAutoloaders() as $packageAutoloader) { $autoloaderType = get_class($packageAutoloader); switch ($autoloaderType) { case Psr0::class: case Psr4::class: - $outputDir = $this->config->dep_directory . $packageAutoloader->namespace; + $outputDir = $this->config->getDepDirectory() . $packageAutoloader->namespace; $outputDir = str_replace('\\', DIRECTORY_SEPARATOR, $outputDir); $this->filesystem->deleteDirectory($outputDir); break; case Classmap::class: - $outputDir = $this->config->classmap_directory . $package->config->name; + $outputDir = $this->config->getClassmapDirectory() . $package->getName(); $outputDir = str_replace('\\', DIRECTORY_SEPARATOR, $outputDir); $this->filesystem->deleteDirectory($outputDir); break; } } - foreach ($package->dependencies as $subPackage) { + foreach ($package->getDependencies() as $subPackage) { $this->deleteDepTargetDirs($subPackage); } } public function deleteEmptyDirs(): void { - if (count($this->filesystem->listContents($this->config->dep_directory, true)->toArray()) === 0) { - $this->filesystem->deleteDirectory($this->config->dep_directory); + if (count($this->filesystem->listContents($this->config->getDepDirectory(), true)->toArray()) === 0) { + $this->filesystem->deleteDirectory($this->config->getDepDirectory()); } - if (count($this->filesystem->listContents($this->config->classmap_directory, true)->toArray()) === 0) { - $this->filesystem->deleteDirectory($this->config->classmap_directory); + if (count($this->filesystem->listContents($this->config->getClassmapDirectory(), true)->toArray()) === 0) { + $this->filesystem->deleteDirectory($this->config->getClassmapDirectory()); } } /** - * @return void + * @param Package[] $packages */ - public function movePackage(Package $package) + 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->config->name, $this->movedPackages)) { + if (in_array($package->getName(), $this->movedPackages)) { return; } - foreach ($package->autoloaders as $autoloader) { + if ($this->config->isExcludedPackage($package)) { + return; + } + + foreach ($package->getAutoloaders() as $autoloader) { if ($autoloader instanceof NamespaceAutoloader) { $finder = new Finder(); foreach ($autoloader->paths as $path) { $source_path = $this->workingDir . DIRECTORY_SEPARATOR . 'vendor' . DIRECTORY_SEPARATOR - . $package->config->name . DIRECTORY_SEPARATOR . $path; + . $package->getName() . DIRECTORY_SEPARATOR . $path; $source_path = str_replace('/', DIRECTORY_SEPARATOR, $source_path); @@ -139,7 +147,7 @@ public function movePackage(Package $package) foreach ($autoloader->files as $file) { $source_path = $this->workingDir . DIRECTORY_SEPARATOR . 'vendor' - . DIRECTORY_SEPARATOR . $package->config->name; + . DIRECTORY_SEPARATOR . $package->getName(); $finder->files()->name($file)->in($source_path); foreach ($finder as $foundFile) { @@ -152,7 +160,7 @@ public function movePackage(Package $package) foreach ($autoloader->paths as $path) { $source_path = $this->workingDir . DIRECTORY_SEPARATOR . 'vendor' - . DIRECTORY_SEPARATOR . $package->config->name . DIRECTORY_SEPARATOR . $path; + . DIRECTORY_SEPARATOR . $package->getName() . DIRECTORY_SEPARATOR . $path; $finder->files()->in($source_path); @@ -167,40 +175,33 @@ public function movePackage(Package $package) } } - if (!in_array($package->config->name, $this->movedPackages)) { - $this->movedPackages[] = $package->config->name; + if (!in_array($package->getName(), $this->movedPackages)) { + $this->movedPackages[] = $package->getName(); } } - if (!isset($this->config->delete_vendor_directories) || $this->config->delete_vendor_directories === true) { + if ($this->config->getDeleteVendorDirectories()) { $this->deletePackageVendorDirectories(); } } - /** - * @param Package $package - * @param Autoloader $autoloader - * @param SplFileInfo $file - * @param string $path - * @return string - */ - public function moveFile(Package $package, $autoloader, $file, $path = '') + public function moveFile(Package $package, Autoloader $autoloader, SplFileInfo $file, string $path = ''): string { if ($autoloader instanceof NamespaceAutoloader) { $namespacePath = $autoloader->getNamespacePath(); - $replaceWith = $this->config->dep_directory . $namespacePath; + $replaceWith = $this->config->getDepDirectory() . $namespacePath; $targetFile = str_replace($this->workingDir, $replaceWith, $file->getPathname()); - $packageVendorPath = DIRECTORY_SEPARATOR . 'vendor' . DIRECTORY_SEPARATOR . $package->config->name + $packageVendorPath = DIRECTORY_SEPARATOR . 'vendor' . DIRECTORY_SEPARATOR . $package->getName() . DIRECTORY_SEPARATOR . $path; $packageVendorPath = str_replace('/', DIRECTORY_SEPARATOR, $packageVendorPath); $targetFile = str_replace($packageVendorPath, '', $targetFile); } else { - $namespacePath = $package->config->name; - $replaceWith = $this->config->classmap_directory . DIRECTORY_SEPARATOR . $namespacePath; + $namespacePath = $package->getName(); + $replaceWith = $this->config->getClassmapDirectory() . $namespacePath; $targetFile = str_replace($this->workingDir, $replaceWith, $file->getPathname()); - $packageVendorPath = DIRECTORY_SEPARATOR . 'vendor' . DIRECTORY_SEPARATOR . $package->config->name + $packageVendorPath = DIRECTORY_SEPARATOR . 'vendor' . DIRECTORY_SEPARATOR . $package->getName() . DIRECTORY_SEPARATOR; $packageVendorPath = str_replace('/', DIRECTORY_SEPARATOR, $packageVendorPath); $targetFile = str_replace($packageVendorPath, DIRECTORY_SEPARATOR, $targetFile); @@ -218,8 +219,6 @@ public function moveFile(Package $package, $autoloader, $file, $path = '') * Deletes all the packages that are moved from the /vendor/ directory to * prevent packages that are prefixed/namespaced from being used or * influencing the output of the code. They just need to be gone. - * - * @return void */ protected function deletePackageVendorDirectories(): void { diff --git a/src/PackageFactory.php b/src/PackageFactory.php new file mode 100644 index 00000000..da67d61e --- /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 00000000..9f162c63 --- /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/Replace/BaseReplacer.php b/src/Replace/BaseReplacer.php index 442551e8..3298c5d6 100644 --- a/src/Replace/BaseReplacer.php +++ b/src/Replace/BaseReplacer.php @@ -11,9 +11,8 @@ abstract class BaseReplacer implements Replacer /** * @param Autoloader $autoloader - * @return void */ - public function setAutoloader(Autoloader $autoloader) + public function setAutoloader(Autoloader $autoloader): void { $this->autoloader = $autoloader; } diff --git a/src/Replace/ClassmapReplacer.php b/src/Replace/ClassmapReplacer.php index 2cc297ed..41323fa3 100644 --- a/src/Replace/ClassmapReplacer.php +++ b/src/Replace/ClassmapReplacer.php @@ -17,8 +17,6 @@ class ClassmapReplacer extends BaseReplacer /** * @param false|string $contents - * - * @return string */ public function replace($contents): string { @@ -29,16 +27,16 @@ public function replace($contents): string return preg_replace_callback( " / # Start the pattern - namespace\s+[a-zA-Z0-9_\x7f-\xff\\\\]+[;{\s\n]{1}.*?(?=namespace|$) - # Look for a preceeding namespace declaration, up until + namespace\s+[a-zA-Z0-9_\x7f-\xff\\\\]+[;{\s\n]{1}.*?(?=namespace|$) + # Look for a preceeding namespace declaration, up until # a potential second namespace declaration - | # if found, match that much before repeating the search + | # if found, match that much before repeating the search # on the remainder of the string (?:abstract\sclass|class|interface)\s+ # Look behind for class, abstract class, interface - ([a-zA-Z0-9_\x7f-\xff]+) # Match the word until the first + ([a-zA-Z0-9_\x7f-\xff]+) # Match the word until the first # non-classname-valid character \s? # Allow a space after - (?:{|extends|implements|\n) # Class declaration can be followed by {, extends, + (?:{|extends|implements|\n) # Class declaration can be followed by {, extends, # implements, or a new line /sx", // # dot matches newline, ignore whitespace in regex. function ($matches) use ($contents) { diff --git a/src/Replace/NamespaceReplacer.php b/src/Replace/NamespaceReplacer.php index 711ec086..680fb413 100644 --- a/src/Replace/NamespaceReplacer.php +++ b/src/Replace/NamespaceReplacer.php @@ -14,10 +14,8 @@ class NamespaceReplacer extends BaseReplacer /** * @param string $contents The text to make replacements in. * @param null $file Only used in ClassmapReplacer (for recording which files were changed). - * - * @return string The updated text. */ - public function replace($contents, $file = null) + public function replace($contents, $file = null): string { $searchNamespace = preg_quote($this->autoloader->getSearchNamespace(), '/'); $dependencyNamespace = preg_quote($this->dep_namespace, '/'); diff --git a/src/Replacer.php b/src/Replacer.php index 57bd4e29..bc2668b2 100644 --- a/src/Replacer.php +++ b/src/Replacer.php @@ -3,9 +3,10 @@ namespace CoenJacobs\Mozart; use CoenJacobs\Mozart\Composer\Autoload\Autoloader; -use CoenJacobs\Mozart\Composer\Autoload\Classmap; use CoenJacobs\Mozart\Composer\Autoload\NamespaceAutoloader; -use CoenJacobs\Mozart\Composer\Package; +use CoenJacobs\Mozart\Config\Classmap; +use CoenJacobs\Mozart\Config\Mozart; +use CoenJacobs\Mozart\Config\Package; use CoenJacobs\Mozart\Replace\ClassmapReplacer; use CoenJacobs\Mozart\Replace\NamespaceReplacer; use League\Flysystem\Local\LocalFilesystemAdapter; @@ -21,7 +22,7 @@ class Replacer /** @var string */ protected $targetDir; - /** @var \stdClass */ + /** @var Mozart */ protected $config; /** @var array */ @@ -30,11 +31,11 @@ class Replacer /** @var Filesystem */ protected $filesystem; - public function __construct($workingDir, $config) + public function __construct(string $workingDir, Mozart $config) { $this->workingDir = $workingDir; - $this->targetDir = $config->dep_directory; - $this->config = $config; + $this->config = $config; + $this->targetDir = $this->config->getDepDirectory(); $adapter = new LocalFilesystemAdapter( $this->workingDir @@ -44,20 +45,25 @@ public function __construct($workingDir, $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->autoloaders as $autoloader) { + foreach ($package->getAutoloaders() as $autoloader) { $this->replacePackageByAutoloader($package, $autoloader); } } - /** - * @param $targetFile - * @param $autoloader - * - * @return void - */ - public function replaceInFile($targetFile, Autoloader $autoloader): void + public function replaceInFile(string $targetFile, Autoloader $autoloader): void { $targetFile = str_replace($this->workingDir, '', $targetFile); try { @@ -66,16 +72,16 @@ public function replaceInFile($targetFile, Autoloader $autoloader): void return; } - if (empty($contents) || false === $contents) { + if (!$contents) { return; } if ($autoloader instanceof NamespaceAutoloader) { $replacer = new NamespaceReplacer(); - $replacer->dep_namespace = $this->config->dep_namespace; + $replacer->dep_namespace = $this->config->getDependencyNamespace(); } else { $replacer = new ClassmapReplacer(); - $replacer->classmap_prefix = $this->config->classmap_prefix; + $replacer->classmap_prefix = $this->config->getClassmapPrefix(); } $replacer->setAutoloader($autoloader); @@ -88,23 +94,19 @@ public function replaceInFile($targetFile, Autoloader $autoloader): void $this->filesystem->write($targetFile, $contents); } - /** - * @param Package $package - * @param $autoloader - * - * @return void - */ - public function replacePackageByAutoloader(Package $package, Composer\Autoload\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(); - $source_path = $this->workingDir . $this->config->classmap_directory . DIRECTORY_SEPARATOR - . $package->config->name; + $source_path = $this->workingDir . $this->config->getClassmapDirectory() . $package->getName(); $finder->files()->in($source_path); foreach ($finder as $foundFile) { @@ -117,12 +119,6 @@ public function replacePackageByAutoloader(Package $package, Composer\Autoload\A } } - /** - * @param $autoloader - * @param $directory - * - * @return void - */ public function replaceParentClassesInDirectory(string $directory): void { if (count($this->replacedClasses)===0) { @@ -145,7 +141,7 @@ public function replaceParentClassesInDirectory(string $directory): void continue; } - if (empty($contents) || false === $contents) { + if (!$contents) { continue; } @@ -171,12 +167,6 @@ function ($matches) use ($replacement) { } } - /** - * @param $autoloader - * @param $directory - * - * @return void - */ public function replaceInDirectory(NamespaceAutoloader $autoloader, string $directory): void { $finder = new Finder(); @@ -195,19 +185,18 @@ public function replaceInDirectory(NamespaceAutoloader $autoloader, string $dire * Replace everything in parent package, based on the dependency package. * This is done to ensure that package A (which requires package B), is also * updated with the replacements being made in package B. - * - * @param Package $package - * @param Package $parent - * - * @return void */ public function replaceParentPackage(Package $package, Package $parent): void { - foreach ($parent->autoloaders as $parentAutoloader) { - foreach ($package->autoloaders as $autoloader) { + if ($this->config->isExcludedPackage($package)) { + return; + } + + foreach ($parent->getAutoloaders() as $parentAutoloader) { + foreach ($package->getAutoloaders() as $autoloader) { if ($parentAutoloader instanceof NamespaceAutoloader) { $namespace = str_replace('\\', DIRECTORY_SEPARATOR, $parentAutoloader->namespace); - $directory = $this->workingDir . $this->config->dep_directory . $namespace + $directory = $this->workingDir . $this->config->getDepDirectory() . $namespace . DIRECTORY_SEPARATOR; if ($autoloader instanceof NamespaceAutoloader) { @@ -217,7 +206,8 @@ public function replaceParentPackage(Package $package, Package $parent): void $this->replaceParentClassesInDirectory($directory); } } else { - $directory = $this->workingDir . $this->config->classmap_directory . $parent->config->name; + $directory = $this->workingDir . + $this->config->getClassmapDirectory() . $parent->getName(); if ($autoloader instanceof NamespaceAutoloader) { $this->replaceInDirectory($autoloader, $directory); @@ -229,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 new file mode 100644 index 00000000..42e1ed7a --- /dev/null +++ b/tests/Config/ConfigMapperTest.php @@ -0,0 +1,23 @@ +assertInstanceOf(Package::class, $package); + $this->assertInstanceOf(Mozart::class, $package->getExtra()->getMozart()); + $this->assertCount(4, $package->autoload->getAutoloaders()); + } +} diff --git a/tests/Config/config-mapper-test.json b/tests/Config/config-mapper-test.json new file mode 100644 index 00000000..b043f34b --- /dev/null +++ b/tests/Config/config-mapper-test.json @@ -0,0 +1,30 @@ +{ + "require": { + }, + "autoload": { + "psr-0": { + "Mozart\\RandomDir\\": "old_files/", + "Mozart\\Multiples\\": [ "another_dir", "more_dirs" ] + }, + "psr-4": { + "Mozart\\TestProject\\": "src/", + "Mozart\\MultipleDirs\\": [ "packages/test/src/", "packages/more/src/" ] + } + }, + "extra": { + "mozart": { + "dep_namespace": "Mozart\\TestProject\\Dependencies", + "dep_directory": "/src/dependencies", + "classmap_directory": "/classes/", + "classmap_prefix": "MozartDependency_", + "packages": [ + "pimple/pimple" + ], + "excluded_packages": [ + ], + "override_autoload": { + }, + "delete_vendor_directories": true + } + } + } diff --git a/tests/Console/Commands/ComposeTest.php b/tests/Console/Commands/ComposeTest.php index ea283cfc..325ea3bc 100644 --- a/tests/Console/Commands/ComposeTest.php +++ b/tests/Console/Commands/ComposeTest.php @@ -39,7 +39,6 @@ public function setUp(): void #[Test] public function it_fails_gracefully_when_composer_json_absent(): void { - $inputInterfaceMock = $this->createMock(InputInterface::class); $outputInterfaceMock = $this->createMock(OutputInterface::class); @@ -66,7 +65,6 @@ public function __construct($inputInterfaceMock, $outputInterfaceMock) #[Test] public function it_handles_malformed_json_with_grace(): void { - $badComposerJson = '{ "name": "coenjacobs/mozart", }'; file_put_contents(__DIR__ . '/composer.json', $badComposerJson); @@ -97,7 +95,6 @@ public function __construct($inputInterfaceMock, $outputInterfaceMock) #[Test] public function it_handles_absent_extra_config_with_grace(): void { - $badComposerJson = '{ "name": "coenjacobs/mozart" }'; file_put_contents(__DIR__ . '/composer.json', $badComposerJson); @@ -129,7 +126,6 @@ public function __construct($inputInterfaceMock, $outputInterfaceMock) #[Test] public function it_handles_malformed_extra_config_with_grace(): void { - $badComposerJson = '{ "name": "coenjacobs/mozart", "extra": [] }'; file_put_contents(__DIR__ . '/composer.json', $badComposerJson); @@ -160,7 +156,6 @@ public function __construct($inputInterfaceMock, $outputInterfaceMock) #[Test] public function it_handles_absent_mozart_config_with_grace(): void { - $badComposerJson = '{ "name": "coenjacobs/mozart", "extra": { "moozart": {} } }'; file_put_contents(__DIR__ . '/composer.json', $badComposerJson); @@ -193,7 +188,6 @@ public function __construct($inputInterfaceMock, $outputInterfaceMock) #[Test] public function it_handles_malformed_mozart_config__with_grace(): void { - $badComposerJson = '{ "name": "coenjacobs/mozart", "extra": { "mozart": [] } }'; file_put_contents(__DIR__ . '/composer.json', $badComposerJson); diff --git a/tests/Integration/ExcludedPackagesTest.php b/tests/Integration/ExcludedPackagesTest.php new file mode 100644 index 00000000..646f5dba --- /dev/null +++ b/tests/Integration/ExcludedPackagesTest.php @@ -0,0 +1,109 @@ +testsWorkingDir = __DIR__ . '/temptestdir'; + if (!file_exists($this->testsWorkingDir)) { + mkdir($this->testsWorkingDir); + } + } + + /** + * Verifies that the explicitely excluded packages from the Mozart config + * are _not_ being moved to the provided dependency directory and the files + * will stay present in the vendor directory. At the same time, the other + * package is being moved to the dependency directory and after that the + * originating directory in the vendor directory is deleted (as the + * `delete_vendor_directories` parameter is set to `true`). + * + * @test + */ + #[Test] + public function it_excludes_moving_specified_packages(): void + { + copy(__DIR__ . '/excluded-packages.json', $this->testsWorkingDir . '/composer.json'); + + chdir($this->testsWorkingDir); + + exec('composer update'); + + $inputInterfaceMock = $this->createMock(InputInterface::class); + $outputInterfaceMock = $this->createMock(OutputInterface::class); + + $mozartCompose = new Compose(); + + $result = $mozartCompose->run($inputInterfaceMock, $outputInterfaceMock); + $this->assertEquals(0, $result); + + $this->assertDirectoryDoesNotExist($this->testsWorkingDir . '/vendor/pimple/pimple'); + $this->assertDirectoryExists($this->testsWorkingDir . '/src/dependencies/Pimple'); + + $this->assertDirectoryExists($this->testsWorkingDir . '/vendor/psr/container'); + $this->assertDirectoryDoesNotExist($this->testsWorkingDir . '/src/dependencies/Psr'); + } + + /** + * Verifies that the excluded package `psr/container` is _not_ having its + * classes replaced in the implementing `pimple/pimple` package when the + * former is explicitely excluded and the latter is added to the list of + * packages for Mozart to rewrite. + * + * @test + */ + #[Test] + public function it_excludes_replacing_classes_from_specified_packages(): void + { + copy(__DIR__ . '/excluded-packages.json', $this->testsWorkingDir . '/composer.json'); + + chdir($this->testsWorkingDir); + + exec('composer update'); + + $inputInterfaceMock = $this->createMock(InputInterface::class); + $outputInterfaceMock = $this->createMock(OutputInterface::class); + + $mozartCompose = new Compose(); + + $result = $mozartCompose->run($inputInterfaceMock, $outputInterfaceMock); + $this->assertEquals(0, $result); + + $testFile = file_get_contents($this->testsWorkingDir . '/src/dependencies/Pimple/Psr11/Container.php'); + $this->assertStringContainsString('namespace Mozart\TestProject\Dependencies\Pimple\Psr11;', $testFile); + $this->assertStringContainsString('use Mozart\TestProject\Dependencies\Pimple\Container as PimpleContainer;', $testFile); + $this->assertStringContainsString('use Psr\Container\ContainerInterface;', $testFile); + } + + public function tearDown(): void + { + parent::tearDown(); + + $dir = $this->testsWorkingDir; + + $it = new RecursiveDirectoryIterator($dir, RecursiveDirectoryIterator::SKIP_DOTS); + $files = new RecursiveIteratorIterator( + $it, + RecursiveIteratorIterator::CHILD_FIRST + ); + foreach ($files as $file) { + if ($file->isDir()) { + rmdir($file->getRealPath()); + } else { + unlink($file->getRealPath()); + } + } + rmdir($dir); + chdir(__DIR__); + } +} diff --git a/tests/Integration/excluded-packages.json b/tests/Integration/excluded-packages.json new file mode 100644 index 00000000..f0165703 --- /dev/null +++ b/tests/Integration/excluded-packages.json @@ -0,0 +1,26 @@ +{ + "require": { + "pimple/pimple": "^3.5" + }, + "autoload": { + "psr-4": { + "Mozart\\TestProject\\": "src/" + } + }, + "extra": { + "mozart": { + "dep_namespace": "Mozart\\TestProject\\Dependencies", + "dep_directory": "/src/dependencies", + "classmap_directory": "/classes/", + "classmap_prefix": "MozartDependency_", + "packages": [ + "pimple/pimple" + ], + "excluded_packages": [ + "psr/container" + ], + "override_autoload": {}, + "delete_vendor_directories": true + } + } +} diff --git a/tests/MoverTest.php b/tests/MoverTest.php index 4577cc90..8945f49e 100644 --- a/tests/MoverTest.php +++ b/tests/MoverTest.php @@ -1,7 +1,8 @@ extra->mozart settings * - * @var stdClass + * @var Mozart */ protected $config; @@ -38,23 +38,23 @@ public function setUp(): void mkdir($this->testsWorkingDir); } - $config = new class() { - }; - $config->dep_directory = "/dep_directory/"; - $config->classmap_directory = "/classmap_directory/"; - $config->packages = array( - "pimple/pimple", - "ezyang/htmlpurifier" - ); - $pimpleAutoload = json_decode("{ \"psr-0\" : { \"Pimple\" : [ \"src/\" ] } }"); $htmlpurifierAutoload = json_decode("{ \"classmap\" : { \"Pimple\" => [ \"library/\" ] } }"); - $config->override_autoload = array(); - $config->override_autoload["pimple/pimple"] = $pimpleAutoload; - $config->override_autoload["ezyang/htmlpurifier"] = $htmlpurifierAutoload; + $configArgs = array( + 'dep_directory' => "/dep_directory/", + 'classmap_directory' => "/classmap_directory/", + 'packages' => array( + "pimple/pimple", + "ezyang/htmlpurifier", + ), + 'override_autoload' => array( + 'pimple/pimple' => $pimpleAutoload, + 'ezyang/htmlpurifier' => $htmlpurifierAutoload, + ), + ); - $this->config = $config; + $this->config = Mozart::loadFromString( json_encode($configArgs) ); } /** @@ -72,9 +72,9 @@ public function it_creates_absent_dirs(): void $mover->deleteTargetDirs($packages); $this->assertTrue(file_exists($this->testsWorkingDir . DIRECTORY_SEPARATOR - . $this->config->dep_directory)); + . $this->config->getDepDirectory())); $this->assertTrue(file_exists($this->testsWorkingDir . DIRECTORY_SEPARATOR - . $this->config->classmap_directory)); + . $this->config->getClassmapDirectory())); } /** @@ -87,15 +87,15 @@ public function it_is_unpertrubed_by_existing_dirs(): void { $mover = new Mover($this->testsWorkingDir, $this->config); - if (!file_exists($this->testsWorkingDir . $this->config->dep_directory)) { - mkdir($this->testsWorkingDir . $this->config->dep_directory); + if (!file_exists($this->testsWorkingDir . $this->config->getDepDirectory())) { + mkdir($this->testsWorkingDir . $this->config->getDepDirectory()); } - if (!file_exists($this->testsWorkingDir . $this->config->classmap_directory)) { - mkdir($this->testsWorkingDir . $this->config->classmap_directory); + if (!file_exists($this->testsWorkingDir . $this->config->getClassmapDirectory())) { + mkdir($this->testsWorkingDir . $this->config->getClassmapDirectory()); } - $this->assertDirectoryExists($this->testsWorkingDir . $this->config->dep_directory); - $this->assertDirectoryExists($this->testsWorkingDir . $this->config->classmap_directory); + $this->assertDirectoryExists($this->testsWorkingDir . $this->config->getDepDirectory()); + $this->assertDirectoryExists($this->testsWorkingDir . $this->config->getClassmapDirectory()); $packages = array(); @@ -117,16 +117,14 @@ public function it_is_unpertrubed_by_existing_dirs(): void #[Test] public function it_deletes_subdirs_for_packages_about_to_be_moved(): void { - $mover = new Mover($this->testsWorkingDir, $this->config); - - mkdir($this->testsWorkingDir . DIRECTORY_SEPARATOR . $this->config->dep_directory); - mkdir($this->testsWorkingDir . DIRECTORY_SEPARATOR . $this->config->classmap_directory); + mkdir($this->testsWorkingDir . DIRECTORY_SEPARATOR . $this->config->getDepDirectory()); + mkdir($this->testsWorkingDir . DIRECTORY_SEPARATOR . $this->config->getClassmapDirectory()); - mkdir($this->testsWorkingDir . DIRECTORY_SEPARATOR . $this->config->dep_directory . 'Pimple'); - mkdir($this->testsWorkingDir . DIRECTORY_SEPARATOR . $this->config->classmap_directory . 'ezyang'); + mkdir($this->testsWorkingDir . DIRECTORY_SEPARATOR . $this->config->getDepDirectory() . 'Pimple'); + mkdir($this->testsWorkingDir . DIRECTORY_SEPARATOR . $this->config->getClassmapDirectory() . 'ezyang'); $packages = array(); - foreach ($this->config->packages as $packageString) { + foreach ($this->config->getPackages() as $packageString) { $testDummyComposerDir = $this->testsWorkingDir . DIRECTORY_SEPARATOR . 'vendor' . DIRECTORY_SEPARATOR . $packageString; @mkdir($testDummyComposerDir, 0777, true); @@ -134,15 +132,20 @@ public function it_deletes_subdirs_for_packages_about_to_be_moved(): void $testDummyComposerContents = json_encode(new stdClass()); file_put_contents($testDummyComposerPath, $testDummyComposerContents); - $parsedPackage = new Package($testDummyComposerDir, $this->config->override_autoload[$packageString]); - $parsedPackage->findAutoloaders(); + + $overrideAutoload = $this->config->getOverrideAutoload(); + if ( ! empty( $overrideAutoload ) ) { + $overrideAutoload = $overrideAutoload->getByKey( $packageString ); + } + $parsedPackage = PackageFactory::createPackage($testDummyComposerPath, $overrideAutoload); $packages[] = $parsedPackage; } + $mover = new Mover($this->testsWorkingDir, $this->config); $mover->deleteTargetDirs($packages); - $this->assertDirectoryDoesNotExist($this->testsWorkingDir . $this->config->dep_directory . 'Pimple'); - $this->assertDirectoryDoesNotExist($this->testsWorkingDir . $this->config->dep_directory . 'ezyang'); + $this->assertDirectoryDoesNotExist($this->testsWorkingDir . $this->config->getDepDirectory() . 'Pimple'); + $this->assertDirectoryDoesNotExist($this->testsWorkingDir . $this->config->getDepDirectory() . 'ezyang'); } /** diff --git a/tests/replacers/NamespaceReplacerTest.php b/tests/replacers/NamespaceReplacerTest.php index f5856ede..b0651bc7 100644 --- a/tests/replacers/NamespaceReplacerTest.php +++ b/tests/replacers/NamespaceReplacerTest.php @@ -1,7 +1,7 @@