From 84f0f191122af73039555a3a9381a1a408131422 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Fri, 18 Feb 2022 14:45:08 +0100 Subject: [PATCH] Split Composer into PartialComposer & Composer classes to avoid nullable properties on Composer for non-fully-loaded instances, add types to Composer\Factory --- src/Composer/Command/InstallCommand.php | 2 +- src/Composer/Composer.php | 181 ++---------------- src/Composer/Console/Application.php | 2 +- .../EventDispatcher/EventDispatcher.php | 16 +- src/Composer/Factory.php | 118 ++++-------- src/Composer/Installer/LibraryInstaller.php | 30 +-- src/Composer/Installer/PluginInstaller.php | 26 +-- src/Composer/PartialComposer.php | 143 ++++++++++++++ src/Composer/Plugin/PluginManager.php | 24 +-- .../Test/Json/JsonValidationExceptionTest.php | 6 +- tests/Composer/Test/Mock/FactoryMock.php | 5 +- 11 files changed, 264 insertions(+), 289 deletions(-) diff --git a/src/Composer/Command/InstallCommand.php b/src/Composer/Command/InstallCommand.php index b992758bb..df1edf062 100644 --- a/src/Composer/Command/InstallCommand.php +++ b/src/Composer/Command/InstallCommand.php @@ -98,7 +98,7 @@ EOT $composer = $this->requireComposer(); - if ((!$composer->getLocker() || !$composer->getLocker()->isLocked()) && !HttpDownloader::isCurlEnabled()) { + if (!$composer->getLocker()->isLocked() && !HttpDownloader::isCurlEnabled()) { $io->writeError('Composer is operating significantly slower than normal because you do not have the PHP curl extension enabled.'); } diff --git a/src/Composer/Composer.php b/src/Composer/Composer.php index 6bdc54908..8913397b6 100644 --- a/src/Composer/Composer.php +++ b/src/Composer/Composer.php @@ -12,15 +12,10 @@ namespace Composer; -use Composer\Package\RootPackageInterface; use Composer\Package\Locker; use Composer\Pcre\Preg; -use Composer\Util\Loop; -use Composer\Repository\RepositoryManager; -use Composer\Installer\InstallationManager; use Composer\Plugin\PluginManager; use Composer\Downloader\DownloadManager; -use Composer\EventDispatcher\EventDispatcher; use Composer\Autoload\AutoloadGenerator; use Composer\Package\Archiver\ArchiveManager; @@ -29,7 +24,7 @@ use Composer\Package\Archiver\ArchiveManager; * @author Konstantin Kudryashiv * @author Nils Adermann */ -class Composer +class Composer extends PartialComposer { /* * Examples of the following constants in the various configurations they can be in @@ -87,220 +82,76 @@ class Composer } /** - * @var RootPackageInterface + * @var Locker */ - private $package; + private $locker; /** - * @var Locker|null + * @var Downloader\DownloadManager */ - private $locker = null; + private $downloadManager; /** - * @var Loop + * @var Plugin\PluginManager */ - private $loop; + private $pluginManager; /** - * @var Repository\RepositoryManager + * @var Autoload\AutoloadGenerator */ - private $repositoryManager; + private $autoloadGenerator; /** - * @var Downloader\DownloadManager|null + * @var ArchiveManager */ - private $downloadManager = null; + private $archiveManager; - /** - * @var Installer\InstallationManager - */ - private $installationManager; - - /** - * @var Plugin\PluginManager|null - */ - private $pluginManager = null; - - /** - * @var Config - */ - private $config; - - /** - * @var EventDispatcher - */ - private $eventDispatcher; - - /** - * @var Autoload\AutoloadGenerator|null - */ - private $autoloadGenerator = null; - - /** - * @var ArchiveManager|null - */ - private $archiveManager = null; - - /** - * @return void - */ - public function setPackage(RootPackageInterface $package): void - { - $this->package = $package; - } - - /** - * @return RootPackageInterface - */ - public function getPackage(): RootPackageInterface - { - return $this->package; - } - - /** - * @return void - */ - public function setConfig(Config $config): void - { - $this->config = $config; - } - - /** - * @return Config - */ - public function getConfig(): Config - { - return $this->config; - } - - /** - * @return void - */ public function setLocker(Locker $locker): void { $this->locker = $locker; } - /** - * @return ?Locker - */ - public function getLocker(): ?Locker + public function getLocker(): Locker { return $this->locker; } - /** - * @return void - */ - public function setLoop(Loop $loop): void - { - $this->loop = $loop; - } - - /** - * @return Loop - */ - public function getLoop(): Loop - { - return $this->loop; - } - - /** - * @return void - */ - public function setRepositoryManager(RepositoryManager $manager): void - { - $this->repositoryManager = $manager; - } - - /** - * @return RepositoryManager - */ - public function getRepositoryManager(): RepositoryManager - { - return $this->repositoryManager; - } - - /** - * @return void - */ public function setDownloadManager(DownloadManager $manager): void { $this->downloadManager = $manager; } - public function getDownloadManager(): ?DownloadManager + public function getDownloadManager(): DownloadManager { return $this->downloadManager; } - /** - * @return void - */ public function setArchiveManager(ArchiveManager $manager): void { $this->archiveManager = $manager; } - public function getArchiveManager(): ?ArchiveManager + public function getArchiveManager(): ArchiveManager { return $this->archiveManager; } - /** - * @return void - */ - public function setInstallationManager(InstallationManager $manager): void - { - $this->installationManager = $manager; - } - - /** - * @return InstallationManager - */ - public function getInstallationManager(): InstallationManager - { - return $this->installationManager; - } - - /** - * @return void - */ public function setPluginManager(PluginManager $manager): void { $this->pluginManager = $manager; } - public function getPluginManager(): ?PluginManager + public function getPluginManager(): PluginManager { return $this->pluginManager; } - /** - * @return void - */ - public function setEventDispatcher(EventDispatcher $eventDispatcher): void - { - $this->eventDispatcher = $eventDispatcher; - } - - /** - * @return EventDispatcher - */ - public function getEventDispatcher(): EventDispatcher - { - return $this->eventDispatcher; - } - - /** - * @return void - */ public function setAutoloadGenerator(AutoloadGenerator $autoloadGenerator): void { $this->autoloadGenerator = $autoloadGenerator; } - public function getAutoloadGenerator(): ?AutoloadGenerator + public function getAutoloadGenerator(): AutoloadGenerator { return $this->autoloadGenerator; } diff --git a/src/Composer/Console/Application.php b/src/Composer/Console/Application.php index 24811b34b..0d1b19ce1 100644 --- a/src/Composer/Console/Application.php +++ b/src/Composer/Console/Application.php @@ -560,7 +560,7 @@ class Application extends BaseApplication $composer = $this->getComposer(false, false); if (null === $composer) { - $composer = Factory::createGlobal($this->io); + $composer = Factory::createGlobal($this->io, $this->disablePluginsByDefault, $this->disableScriptsByDefault); } if (null !== $composer) { diff --git a/src/Composer/EventDispatcher/EventDispatcher.php b/src/Composer/EventDispatcher/EventDispatcher.php index 704c1e403..ea6d51792 100644 --- a/src/Composer/EventDispatcher/EventDispatcher.php +++ b/src/Composer/EventDispatcher/EventDispatcher.php @@ -12,10 +12,12 @@ namespace Composer\EventDispatcher; +use Composer\Autoload\AutoloadGenerator; use Composer\DependencyResolver\Transaction; use Composer\Installer\InstallerEvent; use Composer\IO\IOInterface; use Composer\Composer; +use Composer\PartialComposer; use Composer\Pcre\Preg; use Composer\Util\Platform; use Composer\DependencyResolver\Operation\OperationInterface; @@ -44,7 +46,7 @@ use Symfony\Component\Process\ExecutableFinder; */ class EventDispatcher { - /** @var Composer */ + /** @var PartialComposer */ protected $composer; /** @var IOInterface */ protected $io; @@ -62,11 +64,11 @@ class EventDispatcher /** * Constructor. * - * @param Composer $composer The composer instance + * @param PartialComposer $composer The composer instance * @param IOInterface $io The IOInterface instance * @param ProcessExecutor $process */ - public function __construct(Composer $composer, IOInterface $io, ProcessExecutor $process = null) + public function __construct(PartialComposer $composer, IOInterface $io, ProcessExecutor $process = null) { $this->composer = $composer; $this->io = $io; @@ -116,6 +118,8 @@ class EventDispatcher */ public function dispatchScript($eventName, $devMode = false, $additionalArgs = array(), $flags = array()) { + assert($this->composer instanceof Composer, new \LogicException('This should only be reached with a fully loaded Composer')); + return $this->doDispatch(new Script\Event($eventName, $this->composer, $this->io, $devMode, $additionalArgs, $flags)); } @@ -133,6 +137,8 @@ class EventDispatcher */ public function dispatchPackageEvent($eventName, $devMode, RepositoryInterface $localRepo, array $operations, OperationInterface $operation) { + assert($this->composer instanceof Composer, new \LogicException('This should only be reached with a fully loaded Composer')); + return $this->doDispatch(new PackageEvent($eventName, $this->composer, $this->io, $devMode, $localRepo, $operations, $operation)); } @@ -149,6 +155,8 @@ class EventDispatcher */ public function dispatchInstallerEvent($eventName, $devMode, $executeOperations, Transaction $transaction) { + assert($this->composer instanceof Composer, new \LogicException('This should only be reached with a fully loaded Composer')); + return $this->doDispatch(new InstallerEvent($eventName, $this->composer, $this->io, $devMode, $executeOperations, $transaction)); } @@ -486,6 +494,8 @@ class EventDispatcher return array(); } + assert($this->composer instanceof Composer, new \LogicException('This should only be reached with a fully loaded Composer')); + if ($this->loader) { $this->loader->unregister(); } diff --git a/src/Composer/Factory.php b/src/Composer/Factory.php index 4f7c6fb2d..e6ef47563 100644 --- a/src/Composer/Factory.php +++ b/src/Composer/Factory.php @@ -28,6 +28,7 @@ use Composer\Util\Loop; use Composer\Util\Silencer; use Composer\Plugin\PluginEvents; use Composer\EventDispatcher\Event; +use Phar; use Seld\JsonLint\DuplicateKeyException; use Symfony\Component\Console\Formatter\OutputFormatter; use Symfony\Component\Console\Formatter\OutputFormatterStyle; @@ -39,6 +40,7 @@ use Composer\Downloader\TransportException; use Composer\Json\JsonValidationException; use Composer\Repository\InstalledRepositoryInterface; use Seld\JsonLint\JsonParser; +use ZipArchive; /** * Creates a configured instance of composer. @@ -52,9 +54,8 @@ class Factory { /** * @throws \RuntimeException - * @return string */ - protected static function getHomeDir() + protected static function getHomeDir(): string { $home = Platform::getEnv('COMPOSER_HOME'); if ($home) { @@ -95,11 +96,7 @@ class Factory return $dirs[0]; } - /** - * @param string $home - * @return string - */ - protected static function getCacheDir($home) + protected static function getCacheDir(string $home): string { $cacheDir = Platform::getEnv('COMPOSER_CACHE_DIR'); if ($cacheDir) { @@ -144,11 +141,7 @@ class Factory return $home . '/cache'; } - /** - * @param string $home - * @return string - */ - protected static function getDataDir($home) + protected static function getDataDir(string $home): string { $homeEnv = Platform::getEnv('COMPOSER_HOME'); if ($homeEnv) { @@ -169,12 +162,7 @@ class Factory return $home; } - /** - * @param string|null $cwd - * - * @return Config - */ - public static function createConfig(IOInterface $io = null, $cwd = null) + public static function createConfig(IOInterface $io = null, ?string $cwd = null): Config { $cwd = $cwd ?: (string) getcwd(); @@ -243,20 +231,12 @@ class Factory return $config; } - /** - * @return string - */ - public static function getComposerFile() + public static function getComposerFile(): string { return trim((string) Platform::getEnv('COMPOSER')) ?: './composer.json'; } - /** - * @param string $composerFile - * - * @return string - */ - public static function getLockFile($composerFile) + public static function getLockFile(string $composerFile): string { return "json" === pathinfo($composerFile, PATHINFO_EXTENSION) ? substr($composerFile, 0, -4).'lock' @@ -266,7 +246,7 @@ class Factory /** * @return array{highlight: OutputFormatterStyle, warning: OutputFormatterStyle} */ - public static function createAdditionalStyles() + public static function createAdditionalStyles(): array { return array( 'highlight' => new OutputFormatterStyle('red'), @@ -274,12 +254,7 @@ class Factory ); } - /** - * Creates a ConsoleOutput instance - * - * @return ConsoleOutput - */ - public static function createOutput() + public static function createOutput(): ConsoleOutput { $styles = self::createAdditionalStyles(); $formatter = new OutputFormatter(false, $styles); @@ -299,9 +274,9 @@ class Factory * @param bool $fullLoad Whether to initialize everything or only main project stuff (used when loading the global composer) * @throws \InvalidArgumentException * @throws \UnexpectedValueException - * @return Composer + * @return Composer|PartialComposer Composer if $fullLoad is true, otherwise PartialComposer */ - public function createComposer(IOInterface $io, $localConfig = null, $disablePlugins = false, $cwd = null, $fullLoad = true, $disableScripts = false) + public function createComposer(IOInterface $io, $localConfig = null, bool $disablePlugins = false, ?string $cwd = null, bool $fullLoad = true, bool $disableScripts = false) { $cwd = $cwd ?: (string) getcwd(); @@ -363,7 +338,7 @@ class Factory $vendorDir = $config->get('vendor-dir'); // initialize composer - $composer = new Composer(); + $composer = $fullLoad ? new Composer() : new PartialComposer(); $composer->setConfig($config); if ($fullLoad) { @@ -410,7 +385,7 @@ class Factory $im = $this->createInstallationManager($loop, $io, $dispatcher); $composer->setInstallationManager($im); - if ($fullLoad) { + if ($composer instanceof Composer) { // initialize download manager $dm = $this->createDownloadManager($io, $config, $httpDownloader, $process, $dispatcher); $composer->setDownloadManager($dm); @@ -427,7 +402,7 @@ class Factory // add installers to the manager (must happen after download manager is created since they read it out of $composer) $this->createDefaultInstallers($im, $composer, $io, $process); - if ($fullLoad) { + if ($composer instanceof Composer) { $globalComposer = null; if (realpath($config->get('home')) !== $cwd) { $globalComposer = $this->createGlobalComposer($io, $config, $disablePlugins, $disableScripts); @@ -440,7 +415,7 @@ class Factory } // init locker if possible - if ($fullLoad && isset($composerFile)) { + if ($composer instanceof Composer && isset($composerFile)) { $lockFile = self::getLockFile($composerFile); $locker = new Package\Locker($io, new JsonFile($lockFile, null, $io), $im, file_get_contents($composerFile), $process); @@ -460,16 +435,17 @@ class Factory } /** - * @param IOInterface $io IO instance * @param bool $disablePlugins Whether plugins should not be loaded * @param bool $disableScripts Whether scripts should not be executed - * @return Composer|null */ - public static function createGlobal(IOInterface $io, $disablePlugins = false, $disableScripts = false) + public static function createGlobal(IOInterface $io, bool $disablePlugins = false, bool $disableScripts = false): ?Composer { $factory = new static(); - return $factory->createGlobalComposer($io, static::createConfig($io), $disablePlugins, $disableScripts, true); + $composer = $factory->createGlobalComposer($io, static::createConfig($io), $disablePlugins, $disableScripts, true); + assert(null === $composer || $composer instanceof Composer); + + return $composer; } /** @@ -478,7 +454,7 @@ class Factory * * @return void */ - protected function addLocalRepository(IOInterface $io, RepositoryManager $rm, $vendorDir, RootPackageInterface $rootPackage, ProcessExecutor $process = null) + protected function addLocalRepository(IOInterface $io, RepositoryManager $rm, $vendorDir, RootPackageInterface $rootPackage, ProcessExecutor $process = null): void { $fs = null; if ($process) { @@ -489,13 +465,9 @@ class Factory } /** - * @param bool $disablePlugins - * @param bool $disableScripts - * @param bool $fullLoad - * - * @return Composer|null + * @return PartialComposer|Composer|null By default PartialComposer, but Composer if $fullLoad is set to true */ - protected function createGlobalComposer(IOInterface $io, Config $config, $disablePlugins, $disableScripts, $fullLoad = false) + protected function createGlobalComposer(IOInterface $io, Config $config, bool $disablePlugins, bool $disableScripts, bool $fullLoad = false): ?PartialComposer { $composer = null; try { @@ -513,7 +485,7 @@ class Factory * @param EventDispatcher $eventDispatcher * @return Downloader\DownloadManager */ - public function createDownloadManager(IOInterface $io, Config $config, HttpDownloader $httpDownloader, ProcessExecutor $process, EventDispatcher $eventDispatcher = null) + public function createDownloadManager(IOInterface $io, Config $config, HttpDownloader $httpDownloader, ProcessExecutor $process, EventDispatcher $eventDispatcher = null): Downloader\DownloadManager { $cache = null; if ($config->get('cache-files-ttl') > 0) { @@ -566,20 +538,20 @@ class Factory public function createArchiveManager(Config $config, Downloader\DownloadManager $dm, Loop $loop) { $am = new Archiver\ArchiveManager($dm, $loop); - $am->addArchiver(new Archiver\ZipArchiver); - $am->addArchiver(new Archiver\PharArchiver); + if (class_exists(ZipArchive::class)) { + $am->addArchiver(new Archiver\ZipArchiver); + } + if (class_exists(Phar::class)) { + $am->addArchiver(new Archiver\PharArchiver); + } return $am; } /** - * @param IOInterface $io - * @param Composer $composer - * @param Composer $globalComposer - * @param bool $disablePlugins * @return Plugin\PluginManager */ - protected function createPluginManager(IOInterface $io, Composer $composer, Composer $globalComposer = null, $disablePlugins = false) + protected function createPluginManager(IOInterface $io, Composer $composer, PartialComposer $globalComposer = null, bool $disablePlugins = false): Plugin\PluginManager { return new Plugin\PluginManager($io, $composer, $globalComposer, $disablePlugins); } @@ -587,7 +559,7 @@ class Factory /** * @return Installer\InstallationManager */ - public function createInstallationManager(Loop $loop, IOInterface $io, EventDispatcher $eventDispatcher = null) + public function createInstallationManager(Loop $loop, IOInterface $io, EventDispatcher $eventDispatcher = null): Installer\InstallationManager { return new Installer\InstallationManager($loop, $io, $eventDispatcher); } @@ -595,7 +567,7 @@ class Factory /** * @return void */ - protected function createDefaultInstallers(Installer\InstallationManager $im, Composer $composer, IOInterface $io, ProcessExecutor $process = null) + protected function createDefaultInstallers(Installer\InstallationManager $im, PartialComposer $composer, IOInterface $io, ProcessExecutor $process = null): void { $fs = new Filesystem($process); $binaryInstaller = new Installer\BinaryInstaller($io, rtrim($composer->getConfig()->get('bin-dir'), '/'), $composer->getConfig()->get('bin-compat'), $fs, rtrim($composer->getConfig()->get('vendor-dir'), '/')); @@ -608,10 +580,8 @@ class Factory /** * @param InstalledRepositoryInterface $repo repository to purge packages from * @param Installer\InstallationManager $im manager to check whether packages are still installed - * - * @return void */ - protected function purgePackages(InstalledRepositoryInterface $repo, Installer\InstallationManager $im) + protected function purgePackages(InstalledRepositoryInterface $repo, Installer\InstallationManager $im): void { foreach ($repo->getPackages() as $package) { if (!$im->isPackageInstalled($repo, $package)) { @@ -620,10 +590,7 @@ class Factory } } - /** - * @return Package\Loader\RootPackageLoader - */ - protected function loadRootPackage(RepositoryManager $rm, Config $config, VersionParser $parser, VersionGuesser $guesser, IOInterface $io) + protected function loadRootPackage(RepositoryManager $rm, Config $config, VersionParser $parser, VersionGuesser $guesser, IOInterface $io): Package\Loader\RootPackageLoader { return new Package\Loader\RootPackageLoader($rm, $config, $parser, $guesser, $io); } @@ -636,11 +603,14 @@ class Factory * @param bool $disableScripts Whether scripts should not be run * @return Composer */ - public static function create(IOInterface $io, $config = null, $disablePlugins = false, $disableScripts = false) + public static function create(IOInterface $io, $config = null, bool $disablePlugins = false, bool $disableScripts = false): Composer { $factory = new static(); - return $factory->createComposer($io, $config, $disablePlugins, null, true, $disableScripts); + $composer = $factory->createComposer($io, $config, $disablePlugins, null, true, $disableScripts); + assert($composer instanceof Composer); + + return $composer; } /** @@ -651,7 +621,7 @@ class Factory * @param mixed[] $options Array of options passed directly to HttpDownloader constructor * @return HttpDownloader */ - public static function createHttpDownloader(IOInterface $io, Config $config, $options = array()) + public static function createHttpDownloader(IOInterface $io, Config $config, $options = array()): HttpDownloader { static $warned = false; $disableTls = false; @@ -693,9 +663,6 @@ class Factory return $httpDownloader; } - /** - * @return bool - */ private static function useXdg(): bool { foreach (array_keys($_SERVER) as $key) { @@ -713,7 +680,6 @@ class Factory /** * @throws \RuntimeException - * @return string */ private static function getUserDir(): string { diff --git a/src/Composer/Installer/LibraryInstaller.php b/src/Composer/Installer/LibraryInstaller.php index 7490e11a5..8fff84d46 100644 --- a/src/Composer/Installer/LibraryInstaller.php +++ b/src/Composer/Installer/LibraryInstaller.php @@ -14,6 +14,7 @@ namespace Composer\Installer; use Composer\Composer; use Composer\IO\IOInterface; +use Composer\PartialComposer; use Composer\Pcre\Preg; use Composer\Repository\InstalledRepositoryInterface; use Composer\Package\PackageInterface; @@ -31,11 +32,11 @@ use Composer\Downloader\DownloadManager; */ class LibraryInstaller implements InstallerInterface, BinaryPresenceInterface { - /** @var Composer */ + /** @var PartialComposer */ protected $composer; /** @var string */ protected $vendorDir; - /** @var DownloadManager */ + /** @var DownloadManager|null */ protected $downloadManager; /** @var IOInterface */ protected $io; @@ -50,15 +51,15 @@ class LibraryInstaller implements InstallerInterface, BinaryPresenceInterface * Initializes library installer. * * @param IOInterface $io - * @param Composer $composer + * @param PartialComposer $composer * @param string|null $type * @param Filesystem $filesystem * @param BinaryInstaller $binaryInstaller */ - public function __construct(IOInterface $io, Composer $composer, $type = 'library', Filesystem $filesystem = null, BinaryInstaller $binaryInstaller = null) + public function __construct(IOInterface $io, PartialComposer $composer, $type = 'library', Filesystem $filesystem = null, BinaryInstaller $binaryInstaller = null) { $this->composer = $composer; - $this->downloadManager = $composer->getDownloadManager(); + $this->downloadManager = $composer instanceof Composer ? $composer->getDownloadManager() : null; $this->io = $io; $this->type = $type; @@ -101,7 +102,7 @@ class LibraryInstaller implements InstallerInterface, BinaryPresenceInterface $this->initializeVendorDir(); $downloadPath = $this->getInstallPath($package); - return $this->downloadManager->download($package, $downloadPath, $prevPackage); + return $this->getDownloadManager()->download($package, $downloadPath, $prevPackage); } /** @@ -112,7 +113,7 @@ class LibraryInstaller implements InstallerInterface, BinaryPresenceInterface $this->initializeVendorDir(); $downloadPath = $this->getInstallPath($package); - return $this->downloadManager->prepare($type, $package, $downloadPath, $prevPackage); + return $this->getDownloadManager()->prepare($type, $package, $downloadPath, $prevPackage); } /** @@ -123,7 +124,7 @@ class LibraryInstaller implements InstallerInterface, BinaryPresenceInterface $this->initializeVendorDir(); $downloadPath = $this->getInstallPath($package); - return $this->downloadManager->cleanup($type, $package, $downloadPath, $prevPackage); + return $this->getDownloadManager()->cleanup($type, $package, $downloadPath, $prevPackage); } /** @@ -266,7 +267,7 @@ class LibraryInstaller implements InstallerInterface, BinaryPresenceInterface { $downloadPath = $this->getInstallPath($package); - return $this->downloadManager->install($package, $downloadPath); + return $this->getDownloadManager()->install($package, $downloadPath); } /** @@ -295,7 +296,7 @@ class LibraryInstaller implements InstallerInterface, BinaryPresenceInterface $this->filesystem->rename($initialDownloadPath, $targetDownloadPath); } - return $this->downloadManager->update($initial, $target, $targetDownloadPath); + return $this->getDownloadManager()->update($initial, $target, $targetDownloadPath); } /** @@ -305,7 +306,7 @@ class LibraryInstaller implements InstallerInterface, BinaryPresenceInterface { $downloadPath = $this->getPackageBasePath($package); - return $this->downloadManager->remove($package, $downloadPath); + return $this->getDownloadManager()->remove($package, $downloadPath); } /** @@ -316,4 +317,11 @@ class LibraryInstaller implements InstallerInterface, BinaryPresenceInterface $this->filesystem->ensureDirectoryExists($this->vendorDir); $this->vendorDir = realpath($this->vendorDir); } + + protected function getDownloadManager(): DownloadManager + { + assert($this->downloadManager instanceof DownloadManager, new \LogicException(self::class.' should be initialized with a fully loaded Composer instance to be able to install/... packages')); + + return $this->downloadManager; + } } diff --git a/src/Composer/Installer/PluginInstaller.php b/src/Composer/Installer/PluginInstaller.php index e4d0f0404..ce4c75933 100644 --- a/src/Composer/Installer/PluginInstaller.php +++ b/src/Composer/Installer/PluginInstaller.php @@ -14,8 +14,10 @@ namespace Composer\Installer; use Composer\Composer; use Composer\IO\IOInterface; +use Composer\PartialComposer; use Composer\Repository\InstalledRepositoryInterface; use Composer\Package\PackageInterface; +use Composer\Plugin\PluginManager; use Composer\Util\Filesystem; use Composer\Util\Platform; use React\Promise\PromiseInterface; @@ -28,13 +30,7 @@ use React\Promise\PromiseInterface; */ class PluginInstaller extends LibraryInstaller { - /** - * Initializes Plugin installer. - * - * @param IOInterface $io - * @param Composer $composer - */ - public function __construct(IOInterface $io, Composer $composer, Filesystem $fs = null, BinaryInstaller $binaryInstaller = null) + public function __construct(IOInterface $io, PartialComposer $composer, Filesystem $fs = null, BinaryInstaller $binaryInstaller = null) { parent::__construct($io, $composer, 'composer-plugin', $fs, $binaryInstaller); } @@ -73,7 +69,7 @@ class PluginInstaller extends LibraryInstaller return $promise->then(function () use ($package, $repo) { try { Platform::workaroundFilesystemIssues(); - $this->composer->getPluginManager()->registerPackage($package, true); + $this->getPluginManager()->registerPackage($package, true); } catch (\Exception $e) { $this->rollbackInstall($e, $repo, $package); } @@ -93,8 +89,8 @@ class PluginInstaller extends LibraryInstaller return $promise->then(function () use ($initial, $target, $repo) { try { Platform::workaroundFilesystemIssues(); - $this->composer->getPluginManager()->deactivatePackage($initial); - $this->composer->getPluginManager()->registerPackage($target, true); + $this->getPluginManager()->deactivatePackage($initial); + $this->getPluginManager()->registerPackage($target, true); } catch (\Exception $e) { $this->rollbackInstall($e, $repo, $target); } @@ -103,7 +99,7 @@ class PluginInstaller extends LibraryInstaller public function uninstall(InstalledRepositoryInterface $repo, PackageInterface $package) { - $this->composer->getPluginManager()->uninstallPackage($package); + $this->getPluginManager()->uninstallPackage($package); return parent::uninstall($repo, $package); } @@ -114,4 +110,12 @@ class PluginInstaller extends LibraryInstaller parent::uninstall($repo, $package); throw $e; } + + protected function getPluginManager(): PluginManager + { + assert($this->composer instanceof Composer, new \LogicException(self::class.' should be initialized with a fully loaded Composer instance.')); + $pluginManager = $this->composer->getPluginManager(); + + return $pluginManager; + } } diff --git a/src/Composer/PartialComposer.php b/src/Composer/PartialComposer.php index fead62258..891175066 100644 --- a/src/Composer/PartialComposer.php +++ b/src/Composer/PartialComposer.php @@ -1,8 +1,151 @@ + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + namespace Composer; +use Composer\Package\RootPackageInterface; +use Composer\Util\Loop; +use Composer\Repository\RepositoryManager; +use Composer\Installer\InstallationManager; +use Composer\EventDispatcher\EventDispatcher; + +/** + * @author Jordi Boggiano + */ class PartialComposer { + /** + * @var RootPackageInterface + */ + private $package; + /** + * @var Loop + */ + private $loop; + + /** + * @var Repository\RepositoryManager + */ + private $repositoryManager; + + /** + * @var Installer\InstallationManager + */ + private $installationManager; + + /** + * @var Config + */ + private $config; + + /** + * @var EventDispatcher + */ + private $eventDispatcher; + + /** + * @return void + */ + public function setPackage(RootPackageInterface $package): void + { + $this->package = $package; + } + + /** + * @return RootPackageInterface + */ + public function getPackage(): RootPackageInterface + { + return $this->package; + } + + /** + * @return void + */ + public function setConfig(Config $config): void + { + $this->config = $config; + } + + /** + * @return Config + */ + public function getConfig(): Config + { + return $this->config; + } + + /** + * @return void + */ + public function setLoop(Loop $loop): void + { + $this->loop = $loop; + } + + /** + * @return Loop + */ + public function getLoop(): Loop + { + return $this->loop; + } + + /** + * @return void + */ + public function setRepositoryManager(RepositoryManager $manager): void + { + $this->repositoryManager = $manager; + } + + /** + * @return RepositoryManager + */ + public function getRepositoryManager(): RepositoryManager + { + return $this->repositoryManager; + } + + /** + * @return void + */ + public function setInstallationManager(InstallationManager $manager): void + { + $this->installationManager = $manager; + } + + /** + * @return InstallationManager + */ + public function getInstallationManager(): InstallationManager + { + return $this->installationManager; + } + + /** + * @return void + */ + public function setEventDispatcher(EventDispatcher $eventDispatcher): void + { + $this->eventDispatcher = $eventDispatcher; + } + + /** + * @return EventDispatcher + */ + public function getEventDispatcher(): EventDispatcher + { + return $this->eventDispatcher; + } } diff --git a/src/Composer/Plugin/PluginManager.php b/src/Composer/Plugin/PluginManager.php index 296fb06f4..b849de107 100644 --- a/src/Composer/Plugin/PluginManager.php +++ b/src/Composer/Plugin/PluginManager.php @@ -13,6 +13,7 @@ namespace Composer\Plugin; use Composer\Composer; +use Composer\Autoload\AutoloadGenerator; use Composer\EventDispatcher\EventSubscriberInterface; use Composer\Installer\InstallerInterface; use Composer\IO\IOInterface; @@ -20,6 +21,7 @@ use Composer\Package\BasePackage; use Composer\Package\CompletePackage; use Composer\Package\Package; use Composer\Package\Version\VersionParser; +use Composer\PartialComposer; use Composer\Pcre\Preg; use Composer\Repository\RepositoryInterface; use Composer\Repository\InstalledRepository; @@ -42,7 +44,7 @@ class PluginManager protected $composer; /** @var IOInterface */ protected $io; - /** @var ?Composer */ + /** @var PartialComposer|null */ protected $globalComposer; /** @var VersionParser */ protected $versionParser; @@ -67,15 +69,7 @@ class PluginManager /** @var int */ private static $classCounter = 0; - /** - * Initializes plugin manager - * - * @param IOInterface $io - * @param Composer $composer - * @param Composer $globalComposer - * @param bool $disablePlugins - */ - public function __construct(IOInterface $io, Composer $composer, Composer $globalComposer = null, $disablePlugins = false) + public function __construct(IOInterface $io, Composer $composer, PartialComposer $globalComposer = null, bool $disablePlugins = false) { $this->io = $io; $this->composer = $composer; @@ -118,10 +112,9 @@ class PluginManager } $repo = $this->composer->getRepositoryManager()->getLocalRepository(); - $globalRepo = $this->globalComposer ? $this->globalComposer->getRepositoryManager()->getLocalRepository() : null; $this->deactivateRepository($repo, false); - if ($globalRepo) { - $this->deactivateRepository($globalRepo, true); + if ($this->globalComposer !== null) { + $this->deactivateRepository($this->globalComposer->getRepositoryManager()->getLocalRepository(), true); } } @@ -137,10 +130,8 @@ class PluginManager /** * Gets global composer or null when main composer is not fully loaded - * - * @return Composer|null */ - public function getGlobalComposer(): ?Composer + public function getGlobalComposer(): ?PartialComposer { return $this->globalComposer; } @@ -558,6 +549,7 @@ class PluginManager return $this->composer->getInstallationManager()->getInstallPath($package); } + assert(null !== $this->globalComposer); return $this->globalComposer->getInstallationManager()->getInstallPath($package); } diff --git a/tests/Composer/Test/Json/JsonValidationExceptionTest.php b/tests/Composer/Test/Json/JsonValidationExceptionTest.php index 0d6feb050..7f959ae93 100644 --- a/tests/Composer/Test/Json/JsonValidationExceptionTest.php +++ b/tests/Composer/Test/Json/JsonValidationExceptionTest.php @@ -19,10 +19,10 @@ class JsonValidationExceptionTest extends TestCase { /** * @dataProvider errorProvider - * @param string|null $message - * @param string[]|null $errors + * @param string[] $errors + * @param string[] $expectedErrors */ - public function testGetErrors($message, $errors, $expectedMessage, $expectedErrors): void + public function testGetErrors(?string $message, array $errors, string $expectedMessage, array $expectedErrors): void { $object = new JsonValidationException($message, $errors); $this->assertSame($expectedMessage, $object->getMessage()); diff --git a/tests/Composer/Test/Mock/FactoryMock.php b/tests/Composer/Test/Mock/FactoryMock.php index 127720666..8db24fe11 100644 --- a/tests/Composer/Test/Mock/FactoryMock.php +++ b/tests/Composer/Test/Mock/FactoryMock.php @@ -17,6 +17,7 @@ use Composer\Package\Loader\RootPackageLoader; use Composer\Composer; use Composer\Config; use Composer\Factory; +use Composer\PartialComposer; use Composer\Repository\RepositoryManager; use Composer\Package\Version\VersionGuesser; use Composer\Package\Version\VersionParser; @@ -32,7 +33,7 @@ use Composer\Util\ProcessExecutor; class FactoryMock extends Factory { - public static function createConfig(IOInterface $io = null, $cwd = null): Config + public static function createConfig(IOInterface $io = null, ?string $cwd = null): Config { $config = new Config(true, $cwd); @@ -59,7 +60,7 @@ class FactoryMock extends Factory return new InstallationManagerMock(); } - protected function createDefaultInstallers(InstallationManager $im, Composer $composer, IOInterface $io, ProcessExecutor $process = null): void + protected function createDefaultInstallers(InstallationManager $im, PartialComposer $composer, IOInterface $io, ProcessExecutor $process = null): void { }