From 01a08a2ff30ed8c3096ec320420bf92a040c7562 Mon Sep 17 00:00:00 2001 From: Nils Adermann Date: Tue, 13 Aug 2013 13:25:21 +0200 Subject: [PATCH 01/26] As preparation for composer plugins, rename custom installers to plugins --- doc/04-schema.md | 2 +- res/composer-schema.json | 4 ++-- src/Composer/Command/CreateProjectCommand.php | 18 +++++++++--------- src/Composer/Command/InstallCommand.php | 6 +++--- src/Composer/Command/UpdateCommand.php | 6 +++--- src/Composer/Factory.php | 2 +- src/Composer/Installer.php | 14 +++++++------- src/Composer/Installer/InstallationManager.php | 8 ++++---- ...tallerInstaller.php => PluginInstaller.php} | 11 +++++++---- ...t.test => plugins-are-installed-first.test} | 4 ++-- .../Fixtures/installer-v1/composer.json | 2 +- .../Fixtures/installer-v2/composer.json | 2 +- .../Fixtures/installer-v3/composer.json | 2 +- .../Fixtures/installer-v4/composer.json | 2 +- ...stallerTest.php => PluginInstallerTest.php} | 14 +++++++------- 15 files changed, 50 insertions(+), 47 deletions(-) rename src/Composer/Installer/{InstallerInstaller.php => PluginInstaller.php} (86%) rename tests/Composer/Test/Fixtures/installer/{custom-installers-are-installed-first.test => plugins-are-installed-first.test} (90%) rename tests/Composer/Test/Installer/{InstallerInstallerTest.php => PluginInstallerTest.php} (92%) diff --git a/doc/04-schema.md b/doc/04-schema.md index 7b8ef5558..7d2775af1 100644 --- a/doc/04-schema.md +++ b/doc/04-schema.md @@ -99,7 +99,7 @@ Out of the box, composer supports three types: their installation, but contains no files and will not write anything to the filesystem. As such, it does not require a dist or source key to be installable. -- **composer-installer:** A package of type `composer-installer` provides an +- **composer-plugin:** A package of type `composer-plugin` may provide an installer for other packages that have a custom type. Read more in the [dedicated article](articles/custom-installers.md). diff --git a/res/composer-schema.json b/res/composer-schema.json index 328794ef8..eef9aa3d6 100644 --- a/res/composer-schema.json +++ b/res/composer-schema.json @@ -9,7 +9,7 @@ "required": true }, "type": { - "description": "Package type, either 'library' for common packages, 'composer-installer' for custom installers, 'metapackage' for empty packages, or a custom type ([a-z0-9-]+) defined by whatever project this package applies to.", + "description": "Package type, either 'library' for common packages, 'composer-plugin' for plugins, 'metapackage' for empty packages, or a custom type ([a-z0-9-]+) defined by whatever project this package applies to.", "type": "string" }, "target-dir": { @@ -180,7 +180,7 @@ }, "extra": { "type": ["object", "array"], - "description": "Arbitrary extra data that can be used by custom installers, for example, package of type composer-installer must have a 'class' key defining the installer class name.", + "description": "Arbitrary extra data that can be used by plugins, for example, package of type composer-plugin may have a 'class' key defining an installer class name.", "additionalProperties": true }, "autoload": { diff --git a/src/Composer/Command/CreateProjectCommand.php b/src/Composer/Command/CreateProjectCommand.php index 222add8e5..c38f0bec4 100644 --- a/src/Composer/Command/CreateProjectCommand.php +++ b/src/Composer/Command/CreateProjectCommand.php @@ -62,7 +62,7 @@ class CreateProjectCommand extends Command new InputOption('repository-url', null, InputOption::VALUE_REQUIRED, 'Pick a different repository url to look for the package.'), new InputOption('dev', null, InputOption::VALUE_NONE, 'Enables installation of require-dev packages (enabled by default, only present for BC).'), new InputOption('no-dev', null, InputOption::VALUE_NONE, 'Disables installation of require-dev packages.'), - new InputOption('no-custom-installers', null, InputOption::VALUE_NONE, 'Whether to disable custom installers.'), + new InputOption('no-plugins', null, InputOption::VALUE_NONE, 'Whether to disable plugins.'), new InputOption('no-scripts', null, InputOption::VALUE_NONE, 'Whether to prevent execution of all defined scripts in the root package.'), new InputOption('no-progress', null, InputOption::VALUE_NONE, 'Do not output download progress.'), new InputOption('keep-vcs', null, InputOption::VALUE_NONE, 'Whether to prevent deletion vcs folder.'), @@ -127,19 +127,19 @@ EOT $preferDist, !$input->getOption('no-dev'), $input->getOption('repository-url'), - $input->getOption('no-custom-installers'), + $input->getOption('no-plugins'), $input->getOption('no-scripts'), $input->getOption('keep-vcs'), $input->getOption('no-progress') ); } - public function installProject(IOInterface $io, $config, $packageName, $directory = null, $packageVersion = null, $stability = 'stable', $preferSource = false, $preferDist = false, $installDevPackages = false, $repositoryUrl = null, $disableCustomInstallers = false, $noScripts = false, $keepVcs = false, $noProgress = false) + public function installProject(IOInterface $io, $config, $packageName, $directory = null, $packageVersion = null, $stability = 'stable', $preferSource = false, $preferDist = false, $installDevPackages = false, $repositoryUrl = null, $disablePlugins = false, $noScripts = false, $keepVcs = false, $noProgress = false) { $oldCwd = getcwd(); if ($packageName !== null) { - $installedFromVcs = $this->installRootPackage($io, $config, $packageName, $directory, $packageVersion, $stability, $preferSource, $preferDist, $installDevPackages, $repositoryUrl, $disableCustomInstallers, $noScripts, $keepVcs, $noProgress); + $installedFromVcs = $this->installRootPackage($io, $config, $packageName, $directory, $packageVersion, $stability, $preferSource, $preferDist, $installDevPackages, $repositoryUrl, $disablePlugins, $noScripts, $keepVcs, $noProgress); } else { $installedFromVcs = false; } @@ -158,8 +158,8 @@ EOT ->setDevMode($installDevPackages) ->setRunScripts( ! $noScripts); - if ($disableCustomInstallers) { - $installer->disableCustomInstallers(); + if ($disablePlugins) { + $installer->disablePlugins(); } if (!$installer->run()) { @@ -226,7 +226,7 @@ EOT return 0; } - protected function installRootPackage(IOInterface $io, $config, $packageName, $directory = null, $packageVersion = null, $stability = 'stable', $preferSource = false, $preferDist = false, $installDevPackages = false, $repositoryUrl = null, $disableCustomInstallers = false, $noScripts = false, $keepVcs = false, $noProgress = false) + protected function installRootPackage(IOInterface $io, $config, $packageName, $directory = null, $packageVersion = null, $stability = 'stable', $preferSource = false, $preferDist = false, $installDevPackages = false, $repositoryUrl = null, $disablePlugins = false, $noScripts = false, $keepVcs = false, $noProgress = false) { $stability = strtolower($stability); if ($stability === 'rc') { @@ -285,8 +285,8 @@ EOT $io->write('Installing ' . $package->getName() . ' (' . VersionParser::formatVersion($package, false) . ')'); - if ($disableCustomInstallers) { - $io->write('Custom installers have been disabled.'); + if ($disablePlugins) { + $io->write('Plugins have been disabled.'); } if (0 === strpos($package->getPrettyVersion(), 'dev-') && in_array($package->getSourceType(), array('git', 'hg'))) { diff --git a/src/Composer/Command/InstallCommand.php b/src/Composer/Command/InstallCommand.php index 05d3f37d9..90ad2810f 100644 --- a/src/Composer/Command/InstallCommand.php +++ b/src/Composer/Command/InstallCommand.php @@ -35,7 +35,7 @@ class InstallCommand extends Command new InputOption('dry-run', null, InputOption::VALUE_NONE, 'Outputs the operations but will not execute anything (implicitly enables --verbose).'), new InputOption('dev', null, InputOption::VALUE_NONE, 'Enables installation of require-dev packages (enabled by default, only present for BC).'), new InputOption('no-dev', null, InputOption::VALUE_NONE, 'Disables installation of require-dev packages.'), - new InputOption('no-custom-installers', null, InputOption::VALUE_NONE, 'Disables all custom installers.'), + new InputOption('no-plugins', null, InputOption::VALUE_NONE, 'Disables all plugins.'), new InputOption('no-scripts', null, InputOption::VALUE_NONE, 'Skips the execution of all scripts defined in composer.json file.'), new InputOption('no-progress', null, InputOption::VALUE_NONE, 'Do not output download progress.'), new InputOption('verbose', 'v|vv|vvv', InputOption::VALUE_NONE, 'Shows more details including new commits pulled in when updating packages.'), @@ -90,8 +90,8 @@ EOT ->setOptimizeAutoloader($input->getOption('optimize-autoloader')) ; - if ($input->getOption('no-custom-installers')) { - $install->disableCustomInstallers(); + if ($input->getOption('no-plugins')) { + $install->disablePlugins(); } return $install->run() ? 0 : 1; diff --git a/src/Composer/Command/UpdateCommand.php b/src/Composer/Command/UpdateCommand.php index dc103cf99..8d3133b83 100644 --- a/src/Composer/Command/UpdateCommand.php +++ b/src/Composer/Command/UpdateCommand.php @@ -36,7 +36,7 @@ class UpdateCommand extends Command new InputOption('dev', null, InputOption::VALUE_NONE, 'Enables installation of require-dev packages (enabled by default, only present for BC).'), new InputOption('no-dev', null, InputOption::VALUE_NONE, 'Disables installation of require-dev packages.'), new InputOption('lock', null, InputOption::VALUE_NONE, 'Only updates the lock file hash to suppress warning about the lock file being out of date.'), - new InputOption('no-custom-installers', null, InputOption::VALUE_NONE, 'Disables all custom installers.'), + new InputOption('no-plugins', null, InputOption::VALUE_NONE, 'Disables all plugins.'), new InputOption('no-scripts', null, InputOption::VALUE_NONE, 'Skips the execution of all scripts defined in composer.json file.'), new InputOption('no-progress', null, InputOption::VALUE_NONE, 'Do not output download progress.'), new InputOption('verbose', 'v|vv|vvv', InputOption::VALUE_NONE, 'Shows more details including new commits pulled in when updating packages.'), @@ -96,8 +96,8 @@ EOT ->setUpdateWhitelist($input->getOption('lock') ? array('lock') : $input->getArgument('packages')) ; - if ($input->getOption('no-custom-installers')) { - $install->disableCustomInstallers(); + if ($input->getOption('no-plugins')) { + $install->disablePlugins(); } return $install->run() ? 0 : 1; diff --git a/src/Composer/Factory.php b/src/Composer/Factory.php index 82c5084c2..749e1ad88 100644 --- a/src/Composer/Factory.php +++ b/src/Composer/Factory.php @@ -370,7 +370,7 @@ class Factory { $im->addInstaller(new Installer\LibraryInstaller($io, $composer, null)); $im->addInstaller(new Installer\PearInstaller($io, $composer, 'pear-library')); - $im->addInstaller(new Installer\InstallerInstaller($io, $composer)); + $im->addInstaller(new Installer\PluginInstaller($io, $composer)); $im->addInstaller(new Installer\MetapackageInstaller($io)); } diff --git a/src/Composer/Installer.php b/src/Composer/Installer.php index 8f424cc39..1d3ec9255 100644 --- a/src/Composer/Installer.php +++ b/src/Composer/Installer.php @@ -461,7 +461,7 @@ class Installer $this->io->write('Nothing to install or update'); } - $operations = $this->moveCustomInstallersToFront($operations); + $operations = $this->movePluginsToFront($operations); foreach ($operations as $operation) { // collect suggestions @@ -540,7 +540,7 @@ class Installer /** - * Workaround: if your packages depend on custom installers, we must be sure + * Workaround: if your packages depend on plugins, we must be sure * that those are installed / updated first; else it would lead to packages * being installed multiple times in different folders, when running Composer * twice. @@ -552,7 +552,7 @@ class Installer * @param OperationInterface[] $operations * @return OperationInterface[] reordered operation list */ - private function moveCustomInstallersToFront(array $operations) + private function movePluginsToFront(array $operations) { $installerOps = array(); foreach ($operations as $idx => $op) { @@ -564,7 +564,7 @@ class Installer continue; } - if ($package->getRequires() === array() && $package->getType() === 'composer-installer') { + if ($package->getRequires() === array() && ($package->getType() === 'composer-plugin' || $package->getType() === 'composer-installer')) { $installerOps[] = $op; unset($operations[$idx]); } @@ -1055,7 +1055,7 @@ class Installer } /** - * Disables custom installers. + * Disables plugins. * * Call this if you want to ensure that third-party code never gets * executed. The default is to automatically install, and execute @@ -1063,9 +1063,9 @@ class Installer * * @return Installer */ - public function disableCustomInstallers() + public function disablePlugins() { - $this->installationManager->disableCustomInstallers(); + $this->installationManager->disablePlugins(); return $this; } diff --git a/src/Composer/Installer/InstallationManager.php b/src/Composer/Installer/InstallationManager.php index 406ee1166..b26273847 100644 --- a/src/Composer/Installer/InstallationManager.php +++ b/src/Composer/Installer/InstallationManager.php @@ -66,16 +66,16 @@ class InstallationManager } /** - * Disables custom installers. + * Disables plugins. * - * We prevent any custom installers from being instantiated by simply + * We prevent any plugins from being instantiated by simply * deactivating the installer for them. This ensure that no third-party * code is ever executed. */ - public function disableCustomInstallers() + public function disablePlugins() { foreach ($this->installers as $i => $installer) { - if (!$installer instanceof InstallerInstaller) { + if (!$installer instanceof PluginInstaller) { continue; } diff --git a/src/Composer/Installer/InstallerInstaller.php b/src/Composer/Installer/PluginInstaller.php similarity index 86% rename from src/Composer/Installer/InstallerInstaller.php rename to src/Composer/Installer/PluginInstaller.php index a833b68d2..9be0a9155 100644 --- a/src/Composer/Installer/InstallerInstaller.php +++ b/src/Composer/Installer/PluginInstaller.php @@ -23,7 +23,7 @@ use Composer\Package\PackageInterface; * * @author Jordi Boggiano */ -class InstallerInstaller extends LibraryInstaller +class PluginInstaller extends LibraryInstaller { private $installationManager; private static $classCounter = 0; @@ -37,7 +37,7 @@ class InstallerInstaller extends LibraryInstaller */ public function __construct(IOInterface $io, Composer $composer, $type = 'library') { - parent::__construct($io, $composer, 'composer-installer'); + parent::__construct($io, $composer, $type); $this->installationManager = $composer->getInstallationManager(); $repo = $composer->getRepositoryManager()->getLocalRepository(); @@ -45,6 +45,9 @@ class InstallerInstaller extends LibraryInstaller if ('composer-installer' === $package->getType()) { $this->registerInstaller($package); } + if ('composer-plugin' === $package->getType()) { + $this->registerInstaller($package); + } } } @@ -55,7 +58,7 @@ class InstallerInstaller extends LibraryInstaller { $extra = $package->getExtra(); if (empty($extra['class'])) { - throw new \UnexpectedValueException('Error while installing '.$package->getPrettyName().', composer-installer packages should have a class defined in their extra key to be usable.'); + throw new \UnexpectedValueException('Error while installing '.$package->getPrettyName().', composer-plugin packages should have a class defined in their extra key to be usable.'); } parent::install($repo, $package); @@ -69,7 +72,7 @@ class InstallerInstaller extends LibraryInstaller { $extra = $target->getExtra(); if (empty($extra['class'])) { - throw new \UnexpectedValueException('Error while installing '.$target->getPrettyName().', composer-installer packages should have a class defined in their extra key to be usable.'); + throw new \UnexpectedValueException('Error while installing '.$target->getPrettyName().', composer-plugin packages should have a class defined in their extra key to be usable.'); } parent::update($repo, $initial, $target); diff --git a/tests/Composer/Test/Fixtures/installer/custom-installers-are-installed-first.test b/tests/Composer/Test/Fixtures/installer/plugins-are-installed-first.test similarity index 90% rename from tests/Composer/Test/Fixtures/installer/custom-installers-are-installed-first.test rename to tests/Composer/Test/Fixtures/installer/plugins-are-installed-first.test index dd9d26d98..c57a36d35 100644 --- a/tests/Composer/Test/Fixtures/installer/custom-installers-are-installed-first.test +++ b/tests/Composer/Test/Fixtures/installer/plugins-are-installed-first.test @@ -8,8 +8,8 @@ Composer installers are installed first if they have no requirements "package": [ { "name": "pkg", "version": "1.0.0" }, { "name": "pkg2", "version": "1.0.0" }, - { "name": "inst", "version": "1.0.0", "type": "composer-installer" }, - { "name": "inst2", "version": "1.0.0", "type": "composer-installer", "require": { "pkg2": "*" } } + { "name": "inst", "version": "1.0.0", "type": "composer-plugin" }, + { "name": "inst2", "version": "1.0.0", "type": "composer-plugin", "require": { "pkg2": "*" } } ] } ], diff --git a/tests/Composer/Test/Installer/Fixtures/installer-v1/composer.json b/tests/Composer/Test/Installer/Fixtures/installer-v1/composer.json index 2230f4c4c..3ec38c60e 100644 --- a/tests/Composer/Test/Installer/Fixtures/installer-v1/composer.json +++ b/tests/Composer/Test/Installer/Fixtures/installer-v1/composer.json @@ -1,7 +1,7 @@ { "name": "", "version": "1.0.0", - "type": "composer-installer", + "type": "composer-plugin", "autoload": { "psr-0": { "Installer": "" } }, "extra": { "class": "Installer\\Custom" diff --git a/tests/Composer/Test/Installer/Fixtures/installer-v2/composer.json b/tests/Composer/Test/Installer/Fixtures/installer-v2/composer.json index 82daa3d43..974c82b54 100644 --- a/tests/Composer/Test/Installer/Fixtures/installer-v2/composer.json +++ b/tests/Composer/Test/Installer/Fixtures/installer-v2/composer.json @@ -1,7 +1,7 @@ { "name": "", "version": "2.0.0", - "type": "composer-installer", + "type": "composer-plugin", "autoload": { "psr-0": { "Installer": "" } }, "extra": { "class": "Installer\\Custom2" diff --git a/tests/Composer/Test/Installer/Fixtures/installer-v3/composer.json b/tests/Composer/Test/Installer/Fixtures/installer-v3/composer.json index 84c1d8c00..ef2cc278b 100644 --- a/tests/Composer/Test/Installer/Fixtures/installer-v3/composer.json +++ b/tests/Composer/Test/Installer/Fixtures/installer-v3/composer.json @@ -1,7 +1,7 @@ { "name": "", "version": "3.0.0", - "type": "composer-installer", + "type": "composer-plugin", "autoload": { "psr-0": { "Installer": "" } }, "extra": { "class": "Installer\\Custom2" diff --git a/tests/Composer/Test/Installer/Fixtures/installer-v4/composer.json b/tests/Composer/Test/Installer/Fixtures/installer-v4/composer.json index f29258bc6..5ff8d7cb6 100644 --- a/tests/Composer/Test/Installer/Fixtures/installer-v4/composer.json +++ b/tests/Composer/Test/Installer/Fixtures/installer-v4/composer.json @@ -1,7 +1,7 @@ { "name": "", "version": "4.0.0", - "type": "composer-installer", + "type": "composer-plugin", "autoload": { "psr-0": { "Installer": "" } }, "extra": { "class": [ diff --git a/tests/Composer/Test/Installer/InstallerInstallerTest.php b/tests/Composer/Test/Installer/PluginInstallerTest.php similarity index 92% rename from tests/Composer/Test/Installer/InstallerInstallerTest.php rename to tests/Composer/Test/Installer/PluginInstallerTest.php index c61182389..217edb999 100644 --- a/tests/Composer/Test/Installer/InstallerInstallerTest.php +++ b/tests/Composer/Test/Installer/PluginInstallerTest.php @@ -14,13 +14,13 @@ namespace Composer\Test\Installer; use Composer\Composer; use Composer\Config; -use Composer\Installer\InstallerInstaller; +use Composer\Installer\PluginInstaller; use Composer\Package\Loader\JsonLoader; use Composer\Package\Loader\ArrayLoader; use Composer\Package\PackageInterface; use Composer\Autoload\AutoloadGenerator; -class InstallerInstallerTest extends \PHPUnit_Framework_TestCase +class PluginInstallerTest extends \PHPUnit_Framework_TestCase { protected $composer; protected $packages; @@ -81,7 +81,7 @@ class InstallerInstallerTest extends \PHPUnit_Framework_TestCase ->expects($this->once()) ->method('getPackages') ->will($this->returnValue(array())); - $installer = new InstallerInstallerMock($this->io, $this->composer); + $installer = new PluginInstallerMock($this->io, $this->composer); $test = $this; $this->im @@ -101,7 +101,7 @@ class InstallerInstallerTest extends \PHPUnit_Framework_TestCase ->method('getPackages') ->will($this->returnValue(array())); - $installer = new InstallerInstallerMock($this->io, $this->composer); + $installer = new PluginInstallerMock($this->io, $this->composer); $test = $this; @@ -134,7 +134,7 @@ class InstallerInstallerTest extends \PHPUnit_Framework_TestCase ->expects($this->exactly(2)) ->method('hasPackage') ->will($this->onConsecutiveCalls(true, false)); - $installer = new InstallerInstallerMock($this->io, $this->composer); + $installer = new PluginInstallerMock($this->io, $this->composer); $test = $this; $this->im @@ -157,7 +157,7 @@ class InstallerInstallerTest extends \PHPUnit_Framework_TestCase ->expects($this->exactly(2)) ->method('hasPackage') ->will($this->onConsecutiveCalls(true, false)); - $installer = new InstallerInstallerMock($this->io, $this->composer); + $installer = new PluginInstallerMock($this->io, $this->composer); $test = $this; $this->im @@ -171,7 +171,7 @@ class InstallerInstallerTest extends \PHPUnit_Framework_TestCase } } -class InstallerInstallerMock extends InstallerInstaller +class PluginInstallerMock extends PluginInstaller { public function getInstallPath(PackageInterface $package) { From eb966d347fae748a2571927d40a3273af7a6ac5b Mon Sep 17 00:00:00 2001 From: Nils Adermann Date: Tue, 13 Aug 2013 15:55:52 +0200 Subject: [PATCH 02/26] Implement a plugin manager and interface, update installer plugin tests --- src/Composer/Composer.php | 22 ++++++++ src/Composer/Factory.php | 12 +++++ src/Composer/Installer/PluginInstaller.php | 26 ++++++---- src/Composer/Plugin/PluginInterface.php | 30 +++++++++++ src/Composer/Plugin/PluginManager.php | 49 ++++++++++++++++++ .../installer-v1/Installer/Custom.php | 19 ------- .../installer-v1/Installer/Exception.php | 7 --- .../installer-v1/Installer/Plugin.php | 15 ++++++ .../Fixtures/installer-v1/composer.json | 2 +- .../installer-v2/Installer/Custom2.php | 19 ------- .../installer-v2/Installer/Exception.php | 7 --- .../installer-v2/Installer/Plugin2.php | 15 ++++++ .../Fixtures/installer-v2/composer.json | 2 +- .../installer-v3/Installer/Custom2.php | 19 ------- .../installer-v3/Installer/Exception.php | 7 --- .../installer-v3/Installer/Plugin2.php | 15 ++++++ .../Fixtures/installer-v3/composer.json | 2 +- .../installer-v4/Installer/Custom1.php | 20 -------- .../installer-v4/Installer/Custom2.php | 20 -------- .../installer-v4/Installer/Plugin1.php | 16 ++++++ .../installer-v4/Installer/Plugin2.php | 16 ++++++ .../Fixtures/installer-v4/composer.json | 4 +- .../Test/Installer/PluginInstallerTest.php | 50 +++++++++++-------- 23 files changed, 238 insertions(+), 156 deletions(-) create mode 100644 src/Composer/Plugin/PluginInterface.php create mode 100644 src/Composer/Plugin/PluginManager.php delete mode 100644 tests/Composer/Test/Installer/Fixtures/installer-v1/Installer/Custom.php delete mode 100644 tests/Composer/Test/Installer/Fixtures/installer-v1/Installer/Exception.php create mode 100644 tests/Composer/Test/Installer/Fixtures/installer-v1/Installer/Plugin.php delete mode 100644 tests/Composer/Test/Installer/Fixtures/installer-v2/Installer/Custom2.php delete mode 100644 tests/Composer/Test/Installer/Fixtures/installer-v2/Installer/Exception.php create mode 100644 tests/Composer/Test/Installer/Fixtures/installer-v2/Installer/Plugin2.php delete mode 100644 tests/Composer/Test/Installer/Fixtures/installer-v3/Installer/Custom2.php delete mode 100644 tests/Composer/Test/Installer/Fixtures/installer-v3/Installer/Exception.php create mode 100644 tests/Composer/Test/Installer/Fixtures/installer-v3/Installer/Plugin2.php delete mode 100644 tests/Composer/Test/Installer/Fixtures/installer-v4/Installer/Custom1.php delete mode 100644 tests/Composer/Test/Installer/Fixtures/installer-v4/Installer/Custom2.php create mode 100644 tests/Composer/Test/Installer/Fixtures/installer-v4/Installer/Plugin1.php create mode 100644 tests/Composer/Test/Installer/Fixtures/installer-v4/Installer/Plugin2.php diff --git a/src/Composer/Composer.php b/src/Composer/Composer.php index 8c5d0785f..32d48b771 100644 --- a/src/Composer/Composer.php +++ b/src/Composer/Composer.php @@ -16,6 +16,7 @@ use Composer\Package\RootPackageInterface; use Composer\Package\Locker; use Composer\Repository\RepositoryManager; use Composer\Installer\InstallationManager; +use Composer\Plugin\PluginManager; use Composer\Downloader\DownloadManager; use Composer\Script\EventDispatcher; use Composer\Autoload\AutoloadGenerator; @@ -53,6 +54,11 @@ class Composer */ private $installationManager; + /** + * + */ + private $pluginManager; + /** * @var Config */ @@ -165,6 +171,22 @@ class Composer return $this->installationManager; } + /** + * @param Plugin\PluginManager $manager + */ + public function setPluginManager(PluginManager $manager) + { + $this->pluginManager = $manager; + } + + /** + * @return Plugin\PluginManager + */ + public function getPluginManager() + { + return $this->pluginManager; + } + /** * @param Script\EventDispatcher $eventDispatcher */ diff --git a/src/Composer/Factory.php b/src/Composer/Factory.php index 749e1ad88..1dd612b13 100644 --- a/src/Composer/Factory.php +++ b/src/Composer/Factory.php @@ -264,6 +264,10 @@ class Factory $composer->setLocker($locker); } + $pm = $this->createPluginManager($composer); + + $composer->setPluginManager($pm); + return $composer; } @@ -353,6 +357,14 @@ class Factory return $am; } + /** + * @return Plugin\PluginManager + */ + protected function createPluginManager(Composer $composer) + { + return new Plugin\PluginManager($composer); + } + /** * @return Installer\InstallationManager */ diff --git a/src/Composer/Installer/PluginInstaller.php b/src/Composer/Installer/PluginInstaller.php index 9be0a9155..dfcc97669 100644 --- a/src/Composer/Installer/PluginInstaller.php +++ b/src/Composer/Installer/PluginInstaller.php @@ -29,7 +29,7 @@ class PluginInstaller extends LibraryInstaller private static $classCounter = 0; /** - * Initializes Installer installer. + * Initializes Plugin installer. * * @param IOInterface $io * @param Composer $composer @@ -42,11 +42,8 @@ class PluginInstaller extends LibraryInstaller $repo = $composer->getRepositoryManager()->getLocalRepository(); foreach ($repo->getPackages() as $package) { - if ('composer-installer' === $package->getType()) { - $this->registerInstaller($package); - } - if ('composer-plugin' === $package->getType()) { - $this->registerInstaller($package); + if ('composer-plugin' === $package->getType() || 'composer-installer' === $package->getType()) { + $this->registerPlugin($package); } } } @@ -62,7 +59,7 @@ class PluginInstaller extends LibraryInstaller } parent::install($repo, $package); - $this->registerInstaller($package); + $this->registerPlugin($package); } /** @@ -76,11 +73,13 @@ class PluginInstaller extends LibraryInstaller } parent::update($repo, $initial, $target); - $this->registerInstaller($target); + $this->registerPlugin($target); } - private function registerInstaller(PackageInterface $package) + private function registerPlugin(PackageInterface $package) { + $oldInstallerPlugin = ($package->getType() === 'composer-installer'); + $downloadPath = $this->getInstallPath($package); $extra = $package->getExtra(); @@ -100,8 +99,13 @@ class PluginInstaller extends LibraryInstaller self::$classCounter++; } - $installer = new $class($this->io, $this->composer); - $this->installationManager->addInstaller($installer); + $plugin = new $class($this->io, $this->composer); + + if ($oldInstallerPlugin) { + $this->installationManager->addInstaller($installer); + } else { + $this->composer->getPluginManager()->addPlugin($plugin); + } } } } diff --git a/src/Composer/Plugin/PluginInterface.php b/src/Composer/Plugin/PluginInterface.php new file mode 100644 index 000000000..ebbbe0026 --- /dev/null +++ b/src/Composer/Plugin/PluginInterface.php @@ -0,0 +1,30 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Plugin; + +use Composer\Composer; + +/** + * Plugin interface + * + * @author Nils Adermann + */ +interface PluginInterface +{ + /** + * Apply plugin modifications to the passed in composer object + * + * @param Composer $composer + */ + public function activate(Composer $composer); +} diff --git a/src/Composer/Plugin/PluginManager.php b/src/Composer/Plugin/PluginManager.php new file mode 100644 index 000000000..aa08de2ef --- /dev/null +++ b/src/Composer/Plugin/PluginManager.php @@ -0,0 +1,49 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Plugin; + +use Composer\Composer; +use Composer\Package\PackageInterface; + +/** + * Plugin manager + * + * @author Nils Adermann + */ +class PluginManager +{ + protected $composer; + + protected $plugins = array(); + + /** + * Initializes plugin manager + * + * @param Composer $composer + */ + public function __construct(Composer $composer) + { + $this->composer = $composer; + } + + /** + * Adds plugin + * + * @param PluginInterface $plugin plugin instance + */ + public function addPlugin(PluginInterface $plugin) + { + $this->plugins[] = $plugin; + $plugin->activate($this->composer); + } +} diff --git a/tests/Composer/Test/Installer/Fixtures/installer-v1/Installer/Custom.php b/tests/Composer/Test/Installer/Fixtures/installer-v1/Installer/Custom.php deleted file mode 100644 index bfad4a88a..000000000 --- a/tests/Composer/Test/Installer/Fixtures/installer-v1/Installer/Custom.php +++ /dev/null @@ -1,19 +0,0 @@ -disableOriginalConstructor() ->getMock(); + $this->pm = $this->getMockBuilder('Composer\Plugin\PluginManager') + ->disableOriginalConstructor() + ->getMock(); + $this->repository = $this->getMock('Composer\Repository\InstalledRepositoryInterface'); $rm = $this->getMockBuilder('Composer\Repository\RepositoryManager') @@ -64,6 +69,7 @@ class PluginInstallerTest extends \PHPUnit_Framework_TestCase $this->composer->setConfig($config); $this->composer->setDownloadManager($dm); $this->composer->setInstallationManager($this->im); + $this->composer->setPluginManager($this->pm); $this->composer->setRepositoryManager($rm); $this->composer->setAutoloadGenerator($this->autoloadGenerator); @@ -75,7 +81,7 @@ class PluginInstallerTest extends \PHPUnit_Framework_TestCase )); } - public function testInstallNewInstaller() + public function testInstallNewPlugin() { $this->repository ->expects($this->once()) @@ -84,9 +90,9 @@ class PluginInstallerTest extends \PHPUnit_Framework_TestCase $installer = new PluginInstallerMock($this->io, $this->composer); $test = $this; - $this->im + $this->pm ->expects($this->once()) - ->method('addInstaller') + ->method('addPlugin') ->will($this->returnCallback(function ($installer) use ($test) { $test->assertEquals('installer-v1', $installer->version); })); @@ -94,7 +100,7 @@ class PluginInstallerTest extends \PHPUnit_Framework_TestCase $installer->install($this->repository, $this->packages[0]); } - public function testInstallMultipleInstallers() + public function testInstallMultiplePlugins() { $this->repository ->expects($this->once()) @@ -105,20 +111,20 @@ class PluginInstallerTest extends \PHPUnit_Framework_TestCase $test = $this; - $this->im + $this->pm ->expects($this->at(0)) - ->method('addInstaller') - ->will($this->returnCallback(function ($installer) use ($test) { - $test->assertEquals('custom1', $installer->name); - $test->assertEquals('installer-v4', $installer->version); + ->method('addPlugin') + ->will($this->returnCallback(function ($plugin) use ($test) { + $test->assertEquals('plugin1', $plugin->name); + $test->assertEquals('installer-v4', $plugin->version); })); - $this->im + $this->pm ->expects($this->at(1)) - ->method('addInstaller') - ->will($this->returnCallback(function ($installer) use ($test) { - $test->assertEquals('custom2', $installer->name); - $test->assertEquals('installer-v4', $installer->version); + ->method('addPlugin') + ->will($this->returnCallback(function ($plugin) use ($test) { + $test->assertEquals('plugin2', $plugin->name); + $test->assertEquals('installer-v4', $plugin->version); })); $installer->install($this->repository, $this->packages[3]); @@ -137,11 +143,11 @@ class PluginInstallerTest extends \PHPUnit_Framework_TestCase $installer = new PluginInstallerMock($this->io, $this->composer); $test = $this; - $this->im + $this->pm ->expects($this->once()) - ->method('addInstaller') - ->will($this->returnCallback(function ($installer) use ($test) { - $test->assertEquals('installer-v2', $installer->version); + ->method('addPlugin') + ->will($this->returnCallback(function ($plugin) use ($test) { + $test->assertEquals('installer-v2', $plugin->version); })); $installer->update($this->repository, $this->packages[0], $this->packages[1]); @@ -160,11 +166,11 @@ class PluginInstallerTest extends \PHPUnit_Framework_TestCase $installer = new PluginInstallerMock($this->io, $this->composer); $test = $this; - $this->im + $this->pm ->expects($this->once()) - ->method('addInstaller') - ->will($this->returnCallback(function ($installer) use ($test) { - $test->assertEquals('installer-v3', $installer->version); + ->method('addPlugin') + ->will($this->returnCallback(function ($plugin) use ($test) { + $test->assertEquals('installer-v3', $plugin->version); })); $installer->update($this->repository, $this->packages[1], $this->packages[2]); From 3e41977be7f1a6775b5f49f73c4c319c1c813057 Mon Sep 17 00:00:00 2001 From: Nils Adermann Date: Tue, 13 Aug 2013 15:59:26 +0200 Subject: [PATCH 03/26] Plugin tests are no longer strictly installer tests --- .../Fixtures/plugin-v1}/Installer/Plugin.php | 0 .../installer-v1 => Plugin/Fixtures/plugin-v1}/composer.json | 0 .../Fixtures/plugin-v2}/Installer/Plugin2.php | 0 .../installer-v2 => Plugin/Fixtures/plugin-v2}/composer.json | 0 .../Fixtures/plugin-v3}/Installer/Plugin2.php | 0 .../installer-v3 => Plugin/Fixtures/plugin-v3}/composer.json | 0 .../Fixtures/plugin-v4}/Installer/Plugin1.php | 0 .../Fixtures/plugin-v4}/Installer/Plugin2.php | 0 .../installer-v4 => Plugin/Fixtures/plugin-v4}/composer.json | 0 .../Test/{Installer => Plugin}/PluginInstallerTest.php | 4 ++-- 10 files changed, 2 insertions(+), 2 deletions(-) rename tests/Composer/Test/{Installer/Fixtures/installer-v1 => Plugin/Fixtures/plugin-v1}/Installer/Plugin.php (100%) rename tests/Composer/Test/{Installer/Fixtures/installer-v1 => Plugin/Fixtures/plugin-v1}/composer.json (100%) rename tests/Composer/Test/{Installer/Fixtures/installer-v2 => Plugin/Fixtures/plugin-v2}/Installer/Plugin2.php (100%) rename tests/Composer/Test/{Installer/Fixtures/installer-v2 => Plugin/Fixtures/plugin-v2}/composer.json (100%) rename tests/Composer/Test/{Installer/Fixtures/installer-v3 => Plugin/Fixtures/plugin-v3}/Installer/Plugin2.php (100%) rename tests/Composer/Test/{Installer/Fixtures/installer-v3 => Plugin/Fixtures/plugin-v3}/composer.json (100%) rename tests/Composer/Test/{Installer/Fixtures/installer-v4 => Plugin/Fixtures/plugin-v4}/Installer/Plugin1.php (100%) rename tests/Composer/Test/{Installer/Fixtures/installer-v4 => Plugin/Fixtures/plugin-v4}/Installer/Plugin2.php (100%) rename tests/Composer/Test/{Installer/Fixtures/installer-v4 => Plugin/Fixtures/plugin-v4}/composer.json (100%) rename tests/Composer/Test/{Installer => Plugin}/PluginInstallerTest.php (98%) diff --git a/tests/Composer/Test/Installer/Fixtures/installer-v1/Installer/Plugin.php b/tests/Composer/Test/Plugin/Fixtures/plugin-v1/Installer/Plugin.php similarity index 100% rename from tests/Composer/Test/Installer/Fixtures/installer-v1/Installer/Plugin.php rename to tests/Composer/Test/Plugin/Fixtures/plugin-v1/Installer/Plugin.php diff --git a/tests/Composer/Test/Installer/Fixtures/installer-v1/composer.json b/tests/Composer/Test/Plugin/Fixtures/plugin-v1/composer.json similarity index 100% rename from tests/Composer/Test/Installer/Fixtures/installer-v1/composer.json rename to tests/Composer/Test/Plugin/Fixtures/plugin-v1/composer.json diff --git a/tests/Composer/Test/Installer/Fixtures/installer-v2/Installer/Plugin2.php b/tests/Composer/Test/Plugin/Fixtures/plugin-v2/Installer/Plugin2.php similarity index 100% rename from tests/Composer/Test/Installer/Fixtures/installer-v2/Installer/Plugin2.php rename to tests/Composer/Test/Plugin/Fixtures/plugin-v2/Installer/Plugin2.php diff --git a/tests/Composer/Test/Installer/Fixtures/installer-v2/composer.json b/tests/Composer/Test/Plugin/Fixtures/plugin-v2/composer.json similarity index 100% rename from tests/Composer/Test/Installer/Fixtures/installer-v2/composer.json rename to tests/Composer/Test/Plugin/Fixtures/plugin-v2/composer.json diff --git a/tests/Composer/Test/Installer/Fixtures/installer-v3/Installer/Plugin2.php b/tests/Composer/Test/Plugin/Fixtures/plugin-v3/Installer/Plugin2.php similarity index 100% rename from tests/Composer/Test/Installer/Fixtures/installer-v3/Installer/Plugin2.php rename to tests/Composer/Test/Plugin/Fixtures/plugin-v3/Installer/Plugin2.php diff --git a/tests/Composer/Test/Installer/Fixtures/installer-v3/composer.json b/tests/Composer/Test/Plugin/Fixtures/plugin-v3/composer.json similarity index 100% rename from tests/Composer/Test/Installer/Fixtures/installer-v3/composer.json rename to tests/Composer/Test/Plugin/Fixtures/plugin-v3/composer.json diff --git a/tests/Composer/Test/Installer/Fixtures/installer-v4/Installer/Plugin1.php b/tests/Composer/Test/Plugin/Fixtures/plugin-v4/Installer/Plugin1.php similarity index 100% rename from tests/Composer/Test/Installer/Fixtures/installer-v4/Installer/Plugin1.php rename to tests/Composer/Test/Plugin/Fixtures/plugin-v4/Installer/Plugin1.php diff --git a/tests/Composer/Test/Installer/Fixtures/installer-v4/Installer/Plugin2.php b/tests/Composer/Test/Plugin/Fixtures/plugin-v4/Installer/Plugin2.php similarity index 100% rename from tests/Composer/Test/Installer/Fixtures/installer-v4/Installer/Plugin2.php rename to tests/Composer/Test/Plugin/Fixtures/plugin-v4/Installer/Plugin2.php diff --git a/tests/Composer/Test/Installer/Fixtures/installer-v4/composer.json b/tests/Composer/Test/Plugin/Fixtures/plugin-v4/composer.json similarity index 100% rename from tests/Composer/Test/Installer/Fixtures/installer-v4/composer.json rename to tests/Composer/Test/Plugin/Fixtures/plugin-v4/composer.json diff --git a/tests/Composer/Test/Installer/PluginInstallerTest.php b/tests/Composer/Test/Plugin/PluginInstallerTest.php similarity index 98% rename from tests/Composer/Test/Installer/PluginInstallerTest.php rename to tests/Composer/Test/Plugin/PluginInstallerTest.php index d9e1133d6..28a9a768a 100644 --- a/tests/Composer/Test/Installer/PluginInstallerTest.php +++ b/tests/Composer/Test/Plugin/PluginInstallerTest.php @@ -35,7 +35,7 @@ class PluginInstallerTest extends \PHPUnit_Framework_TestCase $loader = new JsonLoader(new ArrayLoader()); $this->packages = array(); for ($i = 1; $i <= 4; $i++) { - $this->packages[] = $loader->load(__DIR__.'/Fixtures/installer-v'.$i.'/composer.json'); + $this->packages[] = $loader->load(__DIR__.'/Fixtures/plugin-v'.$i.'/composer.json'); } $dm = $this->getMockBuilder('Composer\Downloader\DownloadManager') @@ -183,6 +183,6 @@ class PluginInstallerMock extends PluginInstaller { $version = $package->getVersion(); - return __DIR__.'/Fixtures/installer-v'.$version[0].'/'; + return __DIR__.'/Fixtures/plugin-v'.$version[0].'/'; } } From 2f43e9aefb6912610d6279c4c7f623c8f043164e Mon Sep 17 00:00:00 2001 From: Nils Adermann Date: Tue, 13 Aug 2013 19:13:17 +0200 Subject: [PATCH 04/26] Load installed plugins at appropriate time and adapt tests accordingly --- src/Composer/Composer.php | 2 +- src/Composer/Factory.php | 11 ++- src/Composer/Installer/PluginInstaller.php | 53 +++-------- src/Composer/Plugin/PluginManager.php | 75 ++++++++++++++- .../Plugin/Fixtures/plugin-v1/composer.json | 2 +- .../Plugin/Fixtures/plugin-v2/composer.json | 2 +- .../Plugin/Fixtures/plugin-v3/composer.json | 2 +- .../Plugin/Fixtures/plugin-v4/composer.json | 2 +- .../Test/Plugin/PluginInstallerTest.php | 92 ++++++------------- 9 files changed, 122 insertions(+), 119 deletions(-) diff --git a/src/Composer/Composer.php b/src/Composer/Composer.php index 32d48b771..02da7a3f5 100644 --- a/src/Composer/Composer.php +++ b/src/Composer/Composer.php @@ -55,7 +55,7 @@ class Composer private $installationManager; /** - * + * @var Plugin\PluginManager */ private $pluginManager; diff --git a/src/Composer/Factory.php b/src/Composer/Factory.php index 1dd612b13..029833bf1 100644 --- a/src/Composer/Factory.php +++ b/src/Composer/Factory.php @@ -249,6 +249,9 @@ class Factory $generator = new AutoloadGenerator($dispatcher); $composer->setAutoloadGenerator($generator); + $pm = $this->createPluginManager($composer, $io); + $composer->setPluginManager($pm); + // add installers to the manager $this->createDefaultInstallers($im, $composer, $io); @@ -264,9 +267,7 @@ class Factory $composer->setLocker($locker); } - $pm = $this->createPluginManager($composer); - - $composer->setPluginManager($pm); + $pm->loadInstalledPlugins(); return $composer; } @@ -360,9 +361,9 @@ class Factory /** * @return Plugin\PluginManager */ - protected function createPluginManager(Composer $composer) + protected function createPluginManager(Composer $composer, IOInterface $io) { - return new Plugin\PluginManager($composer); + return new Plugin\PluginManager($composer, $io); } /** diff --git a/src/Composer/Installer/PluginInstaller.php b/src/Composer/Installer/PluginInstaller.php index dfcc97669..046aced45 100644 --- a/src/Composer/Installer/PluginInstaller.php +++ b/src/Composer/Installer/PluginInstaller.php @@ -37,15 +37,17 @@ class PluginInstaller extends LibraryInstaller */ public function __construct(IOInterface $io, Composer $composer, $type = 'library') { - parent::__construct($io, $composer, $type); + parent::__construct($io, $composer, 'composer-plugin'); $this->installationManager = $composer->getInstallationManager(); - $repo = $composer->getRepositoryManager()->getLocalRepository(); - foreach ($repo->getPackages() as $package) { - if ('composer-plugin' === $package->getType() || 'composer-installer' === $package->getType()) { - $this->registerPlugin($package); - } - } + } + + /** + * {@inheritDoc} + */ + public function supports($packageType) + { + return $packageType === 'composer-plugin' || $packageType === 'composer-installer'; } /** @@ -59,7 +61,7 @@ class PluginInstaller extends LibraryInstaller } parent::install($repo, $package); - $this->registerPlugin($package); + $this->composer->getPluginManager()->registerPackage($package); } /** @@ -73,39 +75,6 @@ class PluginInstaller extends LibraryInstaller } parent::update($repo, $initial, $target); - $this->registerPlugin($target); - } - - private function registerPlugin(PackageInterface $package) - { - $oldInstallerPlugin = ($package->getType() === 'composer-installer'); - - $downloadPath = $this->getInstallPath($package); - - $extra = $package->getExtra(); - $classes = is_array($extra['class']) ? $extra['class'] : array($extra['class']); - - $generator = $this->composer->getAutoloadGenerator(); - $map = $generator->parseAutoloads(array(array($package, $downloadPath)), new Package('dummy', '1.0.0.0', '1.0.0')); - $classLoader = $generator->createLoader($map); - $classLoader->register(); - - foreach ($classes as $class) { - if (class_exists($class, false)) { - $code = file_get_contents($classLoader->findFile($class)); - $code = preg_replace('{^(\s*)class\s+(\S+)}mi', '$1class $2_composer_tmp'.self::$classCounter, $code); - eval('?>'.$code); - $class .= '_composer_tmp'.self::$classCounter; - self::$classCounter++; - } - - $plugin = new $class($this->io, $this->composer); - - if ($oldInstallerPlugin) { - $this->installationManager->addInstaller($installer); - } else { - $this->composer->getPluginManager()->addPlugin($plugin); - } - } + $this->composer->getPluginManager()->registerPackage($target); } } diff --git a/src/Composer/Plugin/PluginManager.php b/src/Composer/Plugin/PluginManager.php index aa08de2ef..24e4890ac 100644 --- a/src/Composer/Plugin/PluginManager.php +++ b/src/Composer/Plugin/PluginManager.php @@ -13,6 +13,8 @@ namespace Composer\Plugin; use Composer\Composer; +use Composer\Package\Package; +use Composer\IO\IOInterface; use Composer\Package\PackageInterface; /** @@ -23,17 +25,34 @@ use Composer\Package\PackageInterface; class PluginManager { protected $composer; + protected $io; protected $plugins = array(); + private static $classCounter = 0; + /** * Initializes plugin manager * * @param Composer $composer */ - public function __construct(Composer $composer) + public function __construct(Composer $composer, IOInterface $io) { $this->composer = $composer; + $this->io = $io; + } + + public function loadInstalledPlugins() + { + $repo = $this->composer->getRepositoryManager()->getLocalRepository(); + + if ($repo) { + foreach ($repo->getPackages() as $package) { + if ('composer-plugin' === $package->getType() || 'composer-installer' === $package->getType()) { + $this->registerPackage($package); + } + } + } } /** @@ -46,4 +65,58 @@ class PluginManager $this->plugins[] = $plugin; $plugin->activate($this->composer); } + + public function getPlugins() + { + return $this->plugins; + } + + public function registerPackage(PackageInterface $package) + { + $oldInstallerPlugin = ($package->getType() === 'composer-installer'); + + $downloadPath = $this->getInstallPath($package); + + $extra = $package->getExtra(); + if (empty($extra['class'])) { + throw new \UnexpectedValueException('Error while installing '.$package->getPrettyName().', composer-plugin packages should have a class defined in their extra key to be usable.'); + } + $classes = is_array($extra['class']) ? $extra['class'] : array($extra['class']); + + $generator = $this->composer->getAutoloadGenerator(); + $map = $generator->parseAutoloads(array(array($package, $downloadPath)), new Package('dummy', '1.0.0.0', '1.0.0')); + $classLoader = $generator->createLoader($map); + $classLoader->register(); + + foreach ($classes as $class) { + if (class_exists($class, false)) { + $code = file_get_contents($classLoader->findFile($class)); + $code = preg_replace('{^(\s*)class\s+(\S+)}mi', '$1class $2_composer_tmp'.self::$classCounter, $code); + eval('?>'.$code); + $class .= '_composer_tmp'.self::$classCounter; + self::$classCounter++; + } + + $plugin = new $class($this->io, $this->composer); + + if ($oldInstallerPlugin) { + $this->composer->getInstallationManager()->addInstaller($installer); + } else { + $this->addPlugin($plugin); + } + } + } + + public function getInstallPath(PackageInterface $package) + { + $targetDir = $package->getTargetDir(); + + return $this->getPackageBasePath($package) . ($targetDir ? '/'.$targetDir : ''); + } + + protected function getPackageBasePath(PackageInterface $package) + { + $vendorDir = rtrim($this->composer->getConfig()->get('vendor-dir'), '/'); + return ($vendorDir ? $vendorDir.'/' : '') . $package->getPrettyName(); + } } diff --git a/tests/Composer/Test/Plugin/Fixtures/plugin-v1/composer.json b/tests/Composer/Test/Plugin/Fixtures/plugin-v1/composer.json index 969218ebc..996e5ee3e 100644 --- a/tests/Composer/Test/Plugin/Fixtures/plugin-v1/composer.json +++ b/tests/Composer/Test/Plugin/Fixtures/plugin-v1/composer.json @@ -1,5 +1,5 @@ { - "name": "", + "name": "plugin-v1", "version": "1.0.0", "type": "composer-plugin", "autoload": { "psr-0": { "Installer": "" } }, diff --git a/tests/Composer/Test/Plugin/Fixtures/plugin-v2/composer.json b/tests/Composer/Test/Plugin/Fixtures/plugin-v2/composer.json index 8ec717c46..c099da413 100644 --- a/tests/Composer/Test/Plugin/Fixtures/plugin-v2/composer.json +++ b/tests/Composer/Test/Plugin/Fixtures/plugin-v2/composer.json @@ -1,5 +1,5 @@ { - "name": "", + "name": "plugin-v2", "version": "2.0.0", "type": "composer-plugin", "autoload": { "psr-0": { "Installer": "" } }, diff --git a/tests/Composer/Test/Plugin/Fixtures/plugin-v3/composer.json b/tests/Composer/Test/Plugin/Fixtures/plugin-v3/composer.json index fe7aba7a4..3ba04e6f6 100644 --- a/tests/Composer/Test/Plugin/Fixtures/plugin-v3/composer.json +++ b/tests/Composer/Test/Plugin/Fixtures/plugin-v3/composer.json @@ -1,5 +1,5 @@ { - "name": "", + "name": "plugin-v3", "version": "3.0.0", "type": "composer-plugin", "autoload": { "psr-0": { "Installer": "" } }, diff --git a/tests/Composer/Test/Plugin/Fixtures/plugin-v4/composer.json b/tests/Composer/Test/Plugin/Fixtures/plugin-v4/composer.json index e2c2a4e47..10387a021 100644 --- a/tests/Composer/Test/Plugin/Fixtures/plugin-v4/composer.json +++ b/tests/Composer/Test/Plugin/Fixtures/plugin-v4/composer.json @@ -1,5 +1,5 @@ { - "name": "", + "name": "plugin-v4", "version": "4.0.0", "type": "composer-plugin", "autoload": { "psr-0": { "Installer": "" } }, diff --git a/tests/Composer/Test/Plugin/PluginInstallerTest.php b/tests/Composer/Test/Plugin/PluginInstallerTest.php index 28a9a768a..ab5696af8 100644 --- a/tests/Composer/Test/Plugin/PluginInstallerTest.php +++ b/tests/Composer/Test/Plugin/PluginInstallerTest.php @@ -42,14 +42,6 @@ class PluginInstallerTest extends \PHPUnit_Framework_TestCase ->disableOriginalConstructor() ->getMock(); - $this->im = $this->getMockBuilder('Composer\Installer\InstallationManager') - ->disableOriginalConstructor() - ->getMock(); - - $this->pm = $this->getMockBuilder('Composer\Plugin\PluginManager') - ->disableOriginalConstructor() - ->getMock(); - $this->repository = $this->getMock('Composer\Repository\InstalledRepositoryInterface'); $rm = $this->getMockBuilder('Composer\Repository\RepositoryManager') @@ -68,11 +60,12 @@ class PluginInstallerTest extends \PHPUnit_Framework_TestCase $config = new Config(); $this->composer->setConfig($config); $this->composer->setDownloadManager($dm); - $this->composer->setInstallationManager($this->im); - $this->composer->setPluginManager($this->pm); $this->composer->setRepositoryManager($rm); $this->composer->setAutoloadGenerator($this->autoloadGenerator); + $this->pm = new \Composer\Plugin\PluginManager($this->composer, $this->io); + $this->composer->setPluginManager($this->pm); + $config->merge(array( 'config' => array( 'vendor-dir' => __DIR__.'/Fixtures/', @@ -87,17 +80,13 @@ class PluginInstallerTest extends \PHPUnit_Framework_TestCase ->expects($this->once()) ->method('getPackages') ->will($this->returnValue(array())); - $installer = new PluginInstallerMock($this->io, $this->composer); - - $test = $this; - $this->pm - ->expects($this->once()) - ->method('addPlugin') - ->will($this->returnCallback(function ($installer) use ($test) { - $test->assertEquals('installer-v1', $installer->version); - })); + $installer = new PluginInstaller($this->io, $this->composer); + $this->pm->loadInstalledPlugins(); $installer->install($this->repository, $this->packages[0]); + + $plugins = $this->pm->getPlugins(); + $this->assertEquals('installer-v1', $plugins[0]->version); } public function testInstallMultiplePlugins() @@ -106,28 +95,16 @@ class PluginInstallerTest extends \PHPUnit_Framework_TestCase ->expects($this->once()) ->method('getPackages') ->will($this->returnValue(array())); - - $installer = new PluginInstallerMock($this->io, $this->composer); - - $test = $this; - - $this->pm - ->expects($this->at(0)) - ->method('addPlugin') - ->will($this->returnCallback(function ($plugin) use ($test) { - $test->assertEquals('plugin1', $plugin->name); - $test->assertEquals('installer-v4', $plugin->version); - })); - - $this->pm - ->expects($this->at(1)) - ->method('addPlugin') - ->will($this->returnCallback(function ($plugin) use ($test) { - $test->assertEquals('plugin2', $plugin->name); - $test->assertEquals('installer-v4', $plugin->version); - })); + $installer = new PluginInstaller($this->io, $this->composer); + $this->pm->loadInstalledPlugins(); $installer->install($this->repository, $this->packages[3]); + + $plugins = $this->pm->getPlugins(); + $this->assertEquals('plugin1', $plugins[0]->name); + $this->assertEquals('installer-v4', $plugins[0]->version); + $this->assertEquals('plugin2', $plugins[1]->name); + $this->assertEquals('installer-v4', $plugins[1]->version); } public function testUpgradeWithNewClassName() @@ -140,17 +117,13 @@ class PluginInstallerTest extends \PHPUnit_Framework_TestCase ->expects($this->exactly(2)) ->method('hasPackage') ->will($this->onConsecutiveCalls(true, false)); - $installer = new PluginInstallerMock($this->io, $this->composer); - - $test = $this; - $this->pm - ->expects($this->once()) - ->method('addPlugin') - ->will($this->returnCallback(function ($plugin) use ($test) { - $test->assertEquals('installer-v2', $plugin->version); - })); + $installer = new PluginInstaller($this->io, $this->composer); + $this->pm->loadInstalledPlugins(); $installer->update($this->repository, $this->packages[0], $this->packages[1]); + + $plugins = $this->pm->getPlugins(); + $this->assertEquals('installer-v2', $plugins[1]->version); } public function testUpgradeWithSameClassName() @@ -163,26 +136,13 @@ class PluginInstallerTest extends \PHPUnit_Framework_TestCase ->expects($this->exactly(2)) ->method('hasPackage') ->will($this->onConsecutiveCalls(true, false)); - $installer = new PluginInstallerMock($this->io, $this->composer); - - $test = $this; - $this->pm - ->expects($this->once()) - ->method('addPlugin') - ->will($this->returnCallback(function ($plugin) use ($test) { - $test->assertEquals('installer-v3', $plugin->version); - })); + $installer = new PluginInstaller($this->io, $this->composer); + $this->pm->loadInstalledPlugins(); $installer->update($this->repository, $this->packages[1], $this->packages[2]); + + $plugins = $this->pm->getPlugins(); + $this->assertEquals('installer-v3', $plugins[1]->version); } } -class PluginInstallerMock extends PluginInstaller -{ - public function getInstallPath(PackageInterface $package) - { - $version = $package->getVersion(); - - return __DIR__.'/Fixtures/plugin-v'.$version[0].'/'; - } -} From 3960edd64e27e5ba9dfee7bb8b81b37153c4f074 Mon Sep 17 00:00:00 2001 From: Nils Adermann Date: Wed, 14 Aug 2013 17:42:11 +0200 Subject: [PATCH 05/26] Turn EventDispatcher into generic solution handling plugins as well --- src/Composer/Autoload/AutoloadGenerator.php | 6 +- src/Composer/Composer.php | 8 +- src/Composer/Downloader/FileDownloader.php | 25 ++++-- src/Composer/Downloader/ZipDownloader.php | 5 +- src/Composer/EventDispatcher/Event.php | 72 +++++++++++++++++ .../EventDispatcher.php | 72 +++++++++++++++-- src/Composer/Factory.php | 22 ++--- src/Composer/Installer.php | 2 +- src/Composer/Plugin/PluginEvents.php | 31 +++++++ src/Composer/Plugin/PluginManager.php | 4 + .../Plugin/PrepareRemoteFilesystemEvent.php | 80 +++++++++++++++++++ src/Composer/Script/Event.php | 22 +---- .../Test/Autoload/AutoloadGeneratorTest.php | 6 +- .../Test/Downloader/FileDownloaderTest.php | 2 +- .../EventDispatcherTest.php | 25 +++--- tests/Composer/Test/InstallerTest.php | 4 +- .../Test/Plugin/PluginInstallerTest.php | 2 +- 17 files changed, 320 insertions(+), 68 deletions(-) create mode 100644 src/Composer/EventDispatcher/Event.php rename src/Composer/{Script => EventDispatcher}/EventDispatcher.php (72%) create mode 100644 src/Composer/Plugin/PluginEvents.php create mode 100644 src/Composer/Plugin/PrepareRemoteFilesystemEvent.php rename tests/Composer/Test/{Script => EventDispatcher}/EventDispatcherTest.php (83%) diff --git a/src/Composer/Autoload/AutoloadGenerator.php b/src/Composer/Autoload/AutoloadGenerator.php index b0fe9b232..88f0f4e68 100644 --- a/src/Composer/Autoload/AutoloadGenerator.php +++ b/src/Composer/Autoload/AutoloadGenerator.php @@ -13,12 +13,12 @@ namespace Composer\Autoload; use Composer\Config; +use Composer\EventDispatcher\EventDispatcher; use Composer\Installer\InstallationManager; use Composer\Package\AliasPackage; use Composer\Package\PackageInterface; use Composer\Repository\InstalledRepositoryInterface; use Composer\Util\Filesystem; -use Composer\Script\EventDispatcher; use Composer\Script\ScriptEvents; /** @@ -39,7 +39,7 @@ class AutoloadGenerator public function dump(Config $config, InstalledRepositoryInterface $localRepo, PackageInterface $mainPackage, InstallationManager $installationManager, $targetDir, $scanPsr0Packages = false, $suffix = '') { - $this->eventDispatcher->dispatch(ScriptEvents::PRE_AUTOLOAD_DUMP); + $this->eventDispatcher->dispatchScript(ScriptEvents::PRE_AUTOLOAD_DUMP); $filesystem = new Filesystem(); $filesystem->ensureDirectoryExists($config->get('vendor-dir')); @@ -191,7 +191,7 @@ EOF; fclose($targetLoader); unset($sourceLoader, $targetLoader); - $this->eventDispatcher->dispatch(ScriptEvents::POST_AUTOLOAD_DUMP); + $this->eventDispatcher->dispatchScript(ScriptEvents::POST_AUTOLOAD_DUMP); } public function buildPackageMap(InstallationManager $installationManager, PackageInterface $mainPackage, array $packages) diff --git a/src/Composer/Composer.php b/src/Composer/Composer.php index 02da7a3f5..e77316e4c 100644 --- a/src/Composer/Composer.php +++ b/src/Composer/Composer.php @@ -18,7 +18,7 @@ use Composer\Repository\RepositoryManager; use Composer\Installer\InstallationManager; use Composer\Plugin\PluginManager; use Composer\Downloader\DownloadManager; -use Composer\Script\EventDispatcher; +use Composer\EventDispatcher\EventDispatcher; use Composer\Autoload\AutoloadGenerator; /** @@ -65,7 +65,7 @@ class Composer private $config; /** - * @var Script\EventDispatcher + * @var EventDispatcher\EventDispatcher */ private $eventDispatcher; @@ -188,7 +188,7 @@ class Composer } /** - * @param Script\EventDispatcher $eventDispatcher + * @param EventDispatcher\EventDispatcher $eventDispatcher */ public function setEventDispatcher(EventDispatcher $eventDispatcher) { @@ -196,7 +196,7 @@ class Composer } /** - * @return Script\EventDispatcher + * @return EventDispatcher\EventDispatcher */ public function getEventDispatcher() { diff --git a/src/Composer/Downloader/FileDownloader.php b/src/Composer/Downloader/FileDownloader.php index 8ed0712bf..b9852d730 100644 --- a/src/Composer/Downloader/FileDownloader.php +++ b/src/Composer/Downloader/FileDownloader.php @@ -17,6 +17,9 @@ use Composer\Cache; use Composer\IO\IOInterface; use Composer\Package\PackageInterface; use Composer\Package\Version\VersionParser; +use Composer\Plugin\PluginEvents; +use Composer\Plugin\PrepareRemoteFilesystemEvent; +use Composer\EventDispatcher\EventDispatcher; use Composer\Util\Filesystem; use Composer\Util\GitHub; use Composer\Util\RemoteFilesystem; @@ -27,6 +30,7 @@ use Composer\Util\RemoteFilesystem; * @author Kirill chEbba Chebunin * @author Jordi Boggiano * @author François Pluchino + * @author Nils Adermann */ class FileDownloader implements DownloaderInterface { @@ -43,14 +47,16 @@ class FileDownloader implements DownloaderInterface * * @param IOInterface $io The IO instance * @param Config $config The config + * @param EventDispatcher $eventDispatcher The event dispatcher * @param Cache $cache Optional cache instance * @param RemoteFilesystem $rfs The remote filesystem * @param Filesystem $filesystem The filesystem */ - public function __construct(IOInterface $io, Config $config, Cache $cache = null, RemoteFilesystem $rfs = null, Filesystem $filesystem = null) + public function __construct(IOInterface $io, Config $config, EventDispatcher $eventDispatcher = null, Cache $cache = null, RemoteFilesystem $rfs = null, Filesystem $filesystem = null) { $this->io = $io; $this->config = $config; + $this->eventDispatcher = $eventDispatcher; $this->rfs = $rfs ?: new RemoteFilesystem($io); $this->filesystem = $filesystem ?: new Filesystem(); $this->cache = $cache; @@ -88,6 +94,12 @@ class FileDownloader implements DownloaderInterface $processedUrl = $this->processUrl($package, $url); $hostname = parse_url($processedUrl, PHP_URL_HOST); + $prepRfsEvent = new PrepareRemoteFilesystemEvent(PluginEvents::PREPARE_REMOTE_FILESYSTEM, $this->rfs, $processedUrl); + if ($this->eventDispatcher) { + $this->eventDispatcher->dispatch($prepRfsEvent->getName(), $prepRfsEvent); + } + $rfs = $prepRfsEvent->getRemoteFilesystem(); + if (strpos($hostname, '.github.com') === (strlen($hostname) - 11)) { $hostname = 'github.com'; } @@ -103,7 +115,7 @@ class FileDownloader implements DownloaderInterface $retries = 3; while ($retries--) { try { - $this->rfs->copy($hostname, $processedUrl, $fileName, $this->outputProgress); + $rfs->copy($hostname, $processedUrl, $fileName, $this->outputProgress); break; } catch (TransportException $e) { // if we got an http response with a proper code, then requesting again will probably not help, abort @@ -124,15 +136,18 @@ class FileDownloader implements DownloaderInterface $this->io->write(' Loading from cache'); } } catch (TransportException $e) { - if (in_array($e->getCode(), array(404, 403)) && 'github.com' === $hostname && !$this->io->hasAuthentication($hostname)) { + if (!in_array($e->getCode(), array(404, 403, 412))) { + throw $e; + } + if ('github.com' === $hostname && !$this->io->hasAuthentication($hostname)) { $message = "\n".'Could not fetch '.$processedUrl.', enter your GitHub credentials '.($e->getCode() === 404 ? 'to access private repos' : 'to go over the API rate limit'); - $gitHubUtil = new GitHub($this->io, $this->config, null, $this->rfs); + $gitHubUtil = new GitHub($this->io, $this->config, null, $rfs); if (!$gitHubUtil->authorizeOAuth($hostname) && (!$this->io->isInteractive() || !$gitHubUtil->authorizeOAuthInteractively($hostname, $message)) ) { throw $e; } - $this->rfs->copy($hostname, $processedUrl, $fileName, $this->outputProgress); + $rfs->copy($hostname, $processedUrl, $fileName, $this->outputProgress); } else { throw $e; } diff --git a/src/Composer/Downloader/ZipDownloader.php b/src/Composer/Downloader/ZipDownloader.php index 80bc60272..c2394543d 100644 --- a/src/Composer/Downloader/ZipDownloader.php +++ b/src/Composer/Downloader/ZipDownloader.php @@ -14,6 +14,7 @@ namespace Composer\Downloader; use Composer\Config; use Composer\Cache; +use Composer\EventDispatcher\EventDispatcher; use Composer\Util\ProcessExecutor; use Composer\IO\IOInterface; use ZipArchive; @@ -25,10 +26,10 @@ class ZipDownloader extends ArchiveDownloader { protected $process; - public function __construct(IOInterface $io, Config $config, Cache $cache = null, ProcessExecutor $process = null) + public function __construct(IOInterface $io, Config $config, EventDispatcher $eventDispatcher = null, Cache $cache = null, ProcessExecutor $process = null) { $this->process = $process ?: new ProcessExecutor($io); - parent::__construct($io, $config, $cache); + parent::__construct($io, $config, $eventDispatcher, $cache); } protected function extract($file, $path) diff --git a/src/Composer/EventDispatcher/Event.php b/src/Composer/EventDispatcher/Event.php new file mode 100644 index 000000000..c0a887372 --- /dev/null +++ b/src/Composer/EventDispatcher/Event.php @@ -0,0 +1,72 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\EventDispatcher; + +use Composer\Composer; +use Composer\IO\IOInterface; + +/** + * The base event class + * + * @author Nils Adermann + */ +class Event +{ + /** + * @var string This event's name + */ + protected $name; + + /** + * @var boolean Whether the event should not be passed to more listeners + */ + private $propagationStopped = false; + + /** + * Constructor. + * + * @param string $name The event name + */ + public function __construct($name) + { + $this->name = $name; + } + + /** + * Returns the event's name. + * + * @return string The event name + */ + public function getName() + { + return $this->name; + } + + /** + * Checks if stopPropagation has been called + * + * @return boolean Whether propagation has been stopped + */ + public function isPropagationStopped() + { + return $this->propagationStopped; + } + + /** + * Prevents the event from being passed to further listeners + */ + public function stopPropagation() + { + $this->propagationStopped = true; + } +} diff --git a/src/Composer/Script/EventDispatcher.php b/src/Composer/EventDispatcher/EventDispatcher.php similarity index 72% rename from src/Composer/Script/EventDispatcher.php rename to src/Composer/EventDispatcher/EventDispatcher.php index 46d3d94d7..d15ceb212 100644 --- a/src/Composer/Script/EventDispatcher.php +++ b/src/Composer/EventDispatcher/EventDispatcher.php @@ -10,11 +10,14 @@ * file that was distributed with this source code. */ -namespace Composer\Script; +namespace Composer\EventDispatcher; use Composer\IO\IOInterface; use Composer\Composer; use Composer\DependencyResolver\Operation\OperationInterface; +use Composer\Script; +use Composer\Script\CommandEvent; +use Composer\Script\PackageEvent; use Composer\Util\ProcessExecutor; /** @@ -28,6 +31,7 @@ use Composer\Util\ProcessExecutor; * * @author François Pluchino * @author Jordi Boggiano + * @author Nils Adermann */ class EventDispatcher { @@ -51,15 +55,30 @@ class EventDispatcher } /** - * Dispatch a script event. + * Dispatch an event * - * @param string $eventName The constant in ScriptEvents + * @param string $eventName An event name * @param Event $event */ public function dispatch($eventName, Event $event = null) { if (null == $event) { - $event = new Event($eventName, $this->composer, $this->io); + $event = new Event($eventName); + } + + $this->doDispatch($event); + } + + /** + * Dispatch a script event. + * + * @param string $eventName The constant in ScriptEvents + * @param Event $event + */ + public function dispatchScript($eventName, Script\Event $event = null) + { + if (null == $event) { + $event = new Script\Event($eventName, $this->composer, $this->io); } $this->doDispatch($event); @@ -100,7 +119,9 @@ class EventDispatcher $listeners = $this->getListeners($event); foreach ($listeners as $callable) { - if ($this->isPhpScript($callable)) { + if ((is_array($callable) && $is_callable($callable)) || $callable instanceof Closure) { + $callable($event); + } elseif ($this->isPhpScript($callable)) { $className = substr($callable, 0, strpos($callable, '::')); $methodName = substr($callable, strpos($callable, '::') + 2); @@ -127,6 +148,10 @@ class EventDispatcher throw new \RuntimeException('Error Output: '.$this->process->getErrorOutput(), $exitCode); } } + + if ($event->isPropagationStopped()) { + break; + } } } @@ -140,11 +165,46 @@ class EventDispatcher $className::$methodName($event); } + protected function addListener($eventName, $listener, $priority = 0) + { + $this->listeners[$eventName][$priority][] = $listener; + } + + protected function addSubscriber($subscriber) + { + foreach ($subscriber->getSubscribedEvents() as $eventName => $params) { + if (is_string($params)) { + $this->addListener($eventName, array($subscriber, $params)); + } elseif (is_string($params[0])) { + $this->addListener($eventName, array($subscriber, $params[0]), isset($params[1]) ? $params[1] : 0); + } else { + foreach ($params as $listener) { + $this->addListener($eventName, array($subscriber, $listener[0]), isset($listener[1]) ? $listener[1] : 0); + } + } + } + } + + protected function getListeners(Event $event) + { + $scriptListeners = $this->getScriptListeners($event); + + if (!isset($this->listeners[$event->getName()][0])) { + $this->listeners[$event->getName()][0] = array(); + } + krsort($this->listeners[$event->getName()]); + + $listeners = $this->listeners; + $listeners[$event->getName()][0] = array_merge($listeners[$event->getName()][0], $scriptListeners); + + return call_user_func_array('array_merge', $listeners[$event->getName()]); + } + /** * @param Event $event Event object * @return array Listeners */ - protected function getListeners(Event $event) + protected function getScriptListeners(Event $event) { $package = $this->composer->getPackage(); $scripts = $package->getScripts(); diff --git a/src/Composer/Factory.php b/src/Composer/Factory.php index 029833bf1..a7609469a 100644 --- a/src/Composer/Factory.php +++ b/src/Composer/Factory.php @@ -21,7 +21,7 @@ use Composer\Repository\RepositoryManager; use Composer\Util\ProcessExecutor; use Composer\Util\RemoteFilesystem; use Symfony\Component\Console\Formatter\OutputFormatterStyle; -use Composer\Script\EventDispatcher; +use Composer\EventDispatcher\EventDispatcher; use Composer\Autoload\AutoloadGenerator; use Composer\Package\Version\VersionParser; @@ -227,9 +227,6 @@ class Factory $loader = new Package\Loader\RootPackageLoader($rm, $config, $parser, new ProcessExecutor($io)); $package = $loader->load($localConfig); - // initialize download manager - $dm = $this->createDownloadManager($io, $config); - // initialize installation manager $im = $this->createInstallationManager(); @@ -238,11 +235,15 @@ class Factory $composer->setConfig($config); $composer->setPackage($package); $composer->setRepositoryManager($rm); - $composer->setDownloadManager($dm); $composer->setInstallationManager($im); // initialize event dispatcher $dispatcher = new EventDispatcher($composer, $io); + + // initialize download manager + $dm = $this->createDownloadManager($io, $config, $dispatcher); + + $composer->setDownloadManager($dm); $composer->setEventDispatcher($dispatcher); // initialize autoload generator @@ -304,9 +305,10 @@ class Factory /** * @param IO\IOInterface $io * @param Config $config + * @param EventDispatcher $eventDispatcher * @return Downloader\DownloadManager */ - public function createDownloadManager(IOInterface $io, Config $config) + public function createDownloadManager(IOInterface $io, Config $config, EventDispatcher $eventDispatcher = null) { $cache = null; if ($config->get('cache-files-ttl') > 0) { @@ -330,10 +332,10 @@ class Factory $dm->setDownloader('git', new Downloader\GitDownloader($io, $config)); $dm->setDownloader('svn', new Downloader\SvnDownloader($io, $config)); $dm->setDownloader('hg', new Downloader\HgDownloader($io, $config)); - $dm->setDownloader('zip', new Downloader\ZipDownloader($io, $config, $cache)); - $dm->setDownloader('tar', new Downloader\TarDownloader($io, $config, $cache)); - $dm->setDownloader('phar', new Downloader\PharDownloader($io, $config, $cache)); - $dm->setDownloader('file', new Downloader\FileDownloader($io, $config, $cache)); + $dm->setDownloader('zip', new Downloader\ZipDownloader($io, $config, $eventDispatcher, $cache)); + $dm->setDownloader('tar', new Downloader\TarDownloader($io, $config, $eventDispatcher, $cache)); + $dm->setDownloader('phar', new Downloader\PharDownloader($io, $config, $eventDispatcher, $cache)); + $dm->setDownloader('file', new Downloader\FileDownloader($io, $config, $eventDispatcher, $cache)); return $dm; } diff --git a/src/Composer/Installer.php b/src/Composer/Installer.php index 1d3ec9255..aec6e55a7 100644 --- a/src/Composer/Installer.php +++ b/src/Composer/Installer.php @@ -24,6 +24,7 @@ use Composer\DependencyResolver\Rule; use Composer\DependencyResolver\Solver; use Composer\DependencyResolver\SolverProblemsException; use Composer\Downloader\DownloadManager; +use Composer\EventDispatcher\EventDispatcher; use Composer\Installer\InstallationManager; use Composer\Config; use Composer\Installer\NoopInstaller; @@ -41,7 +42,6 @@ use Composer\Repository\InstalledFilesystemRepository; use Composer\Repository\PlatformRepository; use Composer\Repository\RepositoryInterface; use Composer\Repository\RepositoryManager; -use Composer\Script\EventDispatcher; use Composer\Script\ScriptEvents; /** diff --git a/src/Composer/Plugin/PluginEvents.php b/src/Composer/Plugin/PluginEvents.php new file mode 100644 index 000000000..bfdfba3dd --- /dev/null +++ b/src/Composer/Plugin/PluginEvents.php @@ -0,0 +1,31 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Plugin; + +/** + * The Plugin Events. + * + * @author Nils Adermann + */ +class PluginEvents +{ + /** + * The PREPARE_REMOTE_FILESYSTEM event occurs before downloading a file + * + * The event listener method receives a + * Composer\Plugin\PrepareRemoteFilesystemEvent instance. + * + * @var string + */ + const PREPARE_REMOTE_FILESYSTEM = 'prepare-remote-filesystem'; +} diff --git a/src/Composer/Plugin/PluginManager.php b/src/Composer/Plugin/PluginManager.php index 24e4890ac..1d424627a 100644 --- a/src/Composer/Plugin/PluginManager.php +++ b/src/Composer/Plugin/PluginManager.php @@ -64,6 +64,10 @@ class PluginManager { $this->plugins[] = $plugin; $plugin->activate($this->composer); + + if ($plugin instanceof \Symfony\Component\EventDispatcher\EventSubscriberInterface) { + $this->composer->getPluginEventDispatcher()->addSubscriber($plugin); + } } public function getPlugins() diff --git a/src/Composer/Plugin/PrepareRemoteFilesystemEvent.php b/src/Composer/Plugin/PrepareRemoteFilesystemEvent.php new file mode 100644 index 000000000..91b0543f3 --- /dev/null +++ b/src/Composer/Plugin/PrepareRemoteFilesystemEvent.php @@ -0,0 +1,80 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Plugin; + +use Composer\Composer; +use Composer\IO\IOInterface; +use Composer\EventDispatcher\Event; +use Composer\Util\RemoteFilesystem; + +/** + * The Prepare Remote Filesystem Event. + * + * @author Nils Adermann + */ +class PrepareRemoteFilesystemEvent extends Event +{ + /** + * @var RemoteFilesystem + */ + private $rfs; + + /** + * @var string + */ + private $processedUrl; + + /** + * Constructor. + * + * @param string $name The event name + * @param Composer $composer The composer object + * @param IOInterface $io The IOInterface object + * @param boolean $devMode Whether or not we are in dev mode + * @param OperationInterface $operation The operation object + */ + public function __construct($name, RemoteFilesystem $rfs, $processedUrl) + { + parent::__construct($name); + $this->rfs = $rfs; + $this->processedUrl = $processedUrl; + } + + /** + * Returns the remote filesystem + * + * @return OperationInterface + */ + public function getRemoteFilesystem() + { + return $this->rfs; + } + + /** + * Sets the remote filesystem + */ + public function setRemoteFilesystem(RemoteFilesystem $rfs) + { + $this->rfs = $rfs; + } + + /** + * Retrieves the processed URL this remote filesystem will be used for + * + * @return string + */ + public function getProcessedUrl() + { + return $this->processedUrl; + } +} diff --git a/src/Composer/Script/Event.php b/src/Composer/Script/Event.php index cafea2948..40b109b2d 100644 --- a/src/Composer/Script/Event.php +++ b/src/Composer/Script/Event.php @@ -16,17 +16,13 @@ use Composer\Composer; use Composer\IO\IOInterface; /** - * The base event class + * The script event class * * @author François Pluchino + * @author Nils Adermann */ -class Event +class Event extends \Composer\EventDispatcher\Event { - /** - * @var string This event's name - */ - private $name; - /** * @var Composer The composer instance */ @@ -52,22 +48,12 @@ class Event */ public function __construct($name, Composer $composer, IOInterface $io, $devMode = false) { - $this->name = $name; + parent::__construct($name); $this->composer = $composer; $this->io = $io; $this->devMode = $devMode; } - /** - * Returns the event's name. - * - * @return string The event name - */ - public function getName() - { - return $this->name; - } - /** * Returns the composer instance. * diff --git a/tests/Composer/Test/Autoload/AutoloadGeneratorTest.php b/tests/Composer/Test/Autoload/AutoloadGeneratorTest.php index dc1fa529e..9955f314f 100644 --- a/tests/Composer/Test/Autoload/AutoloadGeneratorTest.php +++ b/tests/Composer/Test/Autoload/AutoloadGeneratorTest.php @@ -72,7 +72,7 @@ class AutoloadGeneratorTest extends TestCase })); $this->repository = $this->getMock('Composer\Repository\InstalledRepositoryInterface'); - $this->eventDispatcher = $this->getMockBuilder('Composer\Script\EventDispatcher') + $this->eventDispatcher = $this->getMockBuilder('Composer\EventDispatcher\EventDispatcher') ->disableOriginalConstructor() ->getMock(); @@ -626,12 +626,12 @@ EOF; { $this->eventDispatcher ->expects($this->at(0)) - ->method('dispatch') + ->method('dispatchScript') ->with(ScriptEvents::PRE_AUTOLOAD_DUMP, false); $this->eventDispatcher ->expects($this->at(1)) - ->method('dispatch') + ->method('dispatchScript') ->with(ScriptEvents::POST_AUTOLOAD_DUMP, false); $package = new Package('a', '1.0', '1.0'); diff --git a/tests/Composer/Test/Downloader/FileDownloaderTest.php b/tests/Composer/Test/Downloader/FileDownloaderTest.php index 99dcaab18..c91798120 100644 --- a/tests/Composer/Test/Downloader/FileDownloaderTest.php +++ b/tests/Composer/Test/Downloader/FileDownloaderTest.php @@ -23,7 +23,7 @@ class FileDownloaderTest extends \PHPUnit_Framework_TestCase $config = $config ?: $this->getMock('Composer\Config'); $rfs = $rfs ?: $this->getMockBuilder('Composer\Util\RemoteFilesystem')->disableOriginalConstructor()->getMock(); - return new FileDownloader($io, $config, null, $rfs); + return new FileDownloader($io, $config, null, null, $rfs); } /** diff --git a/tests/Composer/Test/Script/EventDispatcherTest.php b/tests/Composer/Test/EventDispatcher/EventDispatcherTest.php similarity index 83% rename from tests/Composer/Test/Script/EventDispatcherTest.php rename to tests/Composer/Test/EventDispatcher/EventDispatcherTest.php index cd8f8e76f..7a15679d1 100644 --- a/tests/Composer/Test/Script/EventDispatcherTest.php +++ b/tests/Composer/Test/EventDispatcher/EventDispatcherTest.php @@ -10,11 +10,12 @@ * file that was distributed with this source code. */ -namespace Composer\Test\Script; +namespace Composer\Test\EventDispatcher; +use Composer\EventDispatcher\Event; +use Composer\EventDispatcher\EventDispatcher; use Composer\Test\TestCase; -use Composer\Script\Event; -use Composer\Script\EventDispatcher; +use Composer\Script; use Composer\Util\ProcessExecutor; class EventDispatcherTest extends TestCase @@ -26,12 +27,12 @@ class EventDispatcherTest extends TestCase { $io = $this->getMock('Composer\IO\IOInterface'); $dispatcher = $this->getDispatcherStubForListenersTest(array( - "Composer\Test\Script\EventDispatcherTest::call" + "Composer\Test\EventDispatcher\EventDispatcherTest::call" ), $io); $io->expects($this->once()) ->method('write') - ->with('Script Composer\Test\Script\EventDispatcherTest::call handling the post-install-cmd event terminated with an exception'); + ->with('Script Composer\Test\EventDispatcher\EventDispatcherTest::call handling the post-install-cmd event terminated with an exception'); $dispatcher->dispatchCommandEvent("post-install-cmd", false); } @@ -43,7 +44,7 @@ class EventDispatcherTest extends TestCase public function testDispatcherCanExecuteSingleCommandLineScript($command) { $process = $this->getMock('Composer\Util\ProcessExecutor'); - $dispatcher = $this->getMockBuilder('Composer\Script\EventDispatcher') + $dispatcher = $this->getMockBuilder('Composer\EventDispatcher\EventDispatcher') ->setConstructorArgs(array( $this->getMock('Composer\Composer'), $this->getMock('Composer\IO\IOInterface'), @@ -68,7 +69,7 @@ class EventDispatcherTest extends TestCase public function testDispatcherCanExecuteCliAndPhpInSameEventScriptStack() { $process = $this->getMock('Composer\Util\ProcessExecutor'); - $dispatcher = $this->getMockBuilder('Composer\Script\EventDispatcher') + $dispatcher = $this->getMockBuilder('Composer\EventDispatcher\EventDispatcher') ->setConstructorArgs(array( $this->getMock('Composer\Composer'), $this->getMock('Composer\IO\IOInterface'), @@ -86,7 +87,7 @@ class EventDispatcherTest extends TestCase $listeners = array( 'echo -n foo', - 'Composer\\Test\\Script\\EventDispatcherTest::someMethod', + 'Composer\\Test\\EventDispatcher\\EventDispatcherTest::someMethod', 'echo -n bar', ); $dispatcher->expects($this->atLeastOnce()) @@ -95,7 +96,7 @@ class EventDispatcherTest extends TestCase $dispatcher->expects($this->once()) ->method('executeEventPhpScript') - ->with('Composer\Test\Script\EventDispatcherTest', 'someMethod') + ->with('Composer\Test\EventDispatcher\EventDispatcherTest', 'someMethod') ->will($this->returnValue(true)); $dispatcher->dispatchCommandEvent("post-install-cmd", false); @@ -103,7 +104,7 @@ class EventDispatcherTest extends TestCase private function getDispatcherStubForListenersTest($listeners, $io) { - $dispatcher = $this->getMockBuilder('Composer\Script\EventDispatcher') + $dispatcher = $this->getMockBuilder('Composer\EventDispatcher\EventDispatcher') ->setConstructorArgs(array( $this->getMock('Composer\Composer'), $io, @@ -129,7 +130,7 @@ class EventDispatcherTest extends TestCase public function testDispatcherOutputsCommands() { - $dispatcher = $this->getMockBuilder('Composer\Script\EventDispatcher') + $dispatcher = $this->getMockBuilder('Composer\EventDispatcher\EventDispatcher') ->setConstructorArgs(array( $this->getMock('Composer\Composer'), $this->getMock('Composer\IO\IOInterface'), @@ -150,7 +151,7 @@ class EventDispatcherTest extends TestCase public function testDispatcherOutputsErrorOnFailedCommand() { - $dispatcher = $this->getMockBuilder('Composer\Script\EventDispatcher') + $dispatcher = $this->getMockBuilder('Composer\EventDispatcher\EventDispatcher') ->setConstructorArgs(array( $this->getMock('Composer\Composer'), $io = $this->getMock('Composer\IO\IOInterface'), diff --git a/tests/Composer/Test/InstallerTest.php b/tests/Composer/Test/InstallerTest.php index 7c3791791..e3ec2b927 100644 --- a/tests/Composer/Test/InstallerTest.php +++ b/tests/Composer/Test/InstallerTest.php @@ -66,7 +66,7 @@ class InstallerTest extends TestCase $locker = $this->getMockBuilder('Composer\Package\Locker')->disableOriginalConstructor()->getMock(); $installationManager = new InstallationManagerMock(); - $eventDispatcher = $this->getMockBuilder('Composer\Script\EventDispatcher')->disableOriginalConstructor()->getMock(); + $eventDispatcher = $this->getMockBuilder('Composer\EventDispatcher\EventDispatcher')->disableOriginalConstructor()->getMock(); $autoloadGenerator = $this->getMockBuilder('Composer\Autoload\AutoloadGenerator')->disableOriginalConstructor()->getMock(); $installer = new Installer($io, $config, clone $rootPackage, $downloadManager, $repositoryManager, $locker, $installationManager, $eventDispatcher, $autoloadGenerator); @@ -189,7 +189,7 @@ class InstallerTest extends TestCase $locker = new Locker($io, $lockJsonMock, $repositoryManager, $composer->getInstallationManager(), md5(json_encode($composerConfig))); $composer->setLocker($locker); - $eventDispatcher = $this->getMockBuilder('Composer\Script\EventDispatcher')->disableOriginalConstructor()->getMock(); + $eventDispatcher = $this->getMockBuilder('Composer\EventDispatcher\EventDispatcher')->disableOriginalConstructor()->getMock(); $autoloadGenerator = $this->getMock('Composer\Autoload\AutoloadGenerator', array(), array($eventDispatcher)); $composer->setAutoloadGenerator($autoloadGenerator); $composer->setEventDispatcher($eventDispatcher); diff --git a/tests/Composer/Test/Plugin/PluginInstallerTest.php b/tests/Composer/Test/Plugin/PluginInstallerTest.php index ab5696af8..8734993ea 100644 --- a/tests/Composer/Test/Plugin/PluginInstallerTest.php +++ b/tests/Composer/Test/Plugin/PluginInstallerTest.php @@ -53,7 +53,7 @@ class PluginInstallerTest extends \PHPUnit_Framework_TestCase $this->io = $this->getMock('Composer\IO\IOInterface'); - $dispatcher = $this->getMockBuilder('Composer\Script\EventDispatcher')->disableOriginalConstructor()->getMock(); + $dispatcher = $this->getMockBuilder('Composer\EventDispatcher\EventDispatcher')->disableOriginalConstructor()->getMock(); $this->autoloadGenerator = new AutoloadGenerator($dispatcher); $this->composer = new Composer(); From 919a19015336f37b283bc72233d172709d14dd97 Mon Sep 17 00:00:00 2001 From: Nils Adermann Date: Wed, 14 Aug 2013 18:27:54 +0200 Subject: [PATCH 06/26] Add an EventSubscriberInterface which may also be implemented by plugins --- src/Composer/EventDispatcher/Event.php | 3 -- .../EventDispatcher/EventDispatcher.php | 2 +- .../EventSubscriberInterface.php | 48 +++++++++++++++++++ src/Composer/Plugin/PluginManager.php | 5 +- 4 files changed, 52 insertions(+), 6 deletions(-) create mode 100644 src/Composer/EventDispatcher/EventSubscriberInterface.php diff --git a/src/Composer/EventDispatcher/Event.php b/src/Composer/EventDispatcher/Event.php index c0a887372..8a9352653 100644 --- a/src/Composer/EventDispatcher/Event.php +++ b/src/Composer/EventDispatcher/Event.php @@ -12,9 +12,6 @@ namespace Composer\EventDispatcher; -use Composer\Composer; -use Composer\IO\IOInterface; - /** * The base event class * diff --git a/src/Composer/EventDispatcher/EventDispatcher.php b/src/Composer/EventDispatcher/EventDispatcher.php index d15ceb212..f47e77044 100644 --- a/src/Composer/EventDispatcher/EventDispatcher.php +++ b/src/Composer/EventDispatcher/EventDispatcher.php @@ -170,7 +170,7 @@ class EventDispatcher $this->listeners[$eventName][$priority][] = $listener; } - protected function addSubscriber($subscriber) + public function addSubscriber(EventSubscriberInterface $subscriber) { foreach ($subscriber->getSubscribedEvents() as $eventName => $params) { if (is_string($params)) { diff --git a/src/Composer/EventDispatcher/EventSubscriberInterface.php b/src/Composer/EventDispatcher/EventSubscriberInterface.php new file mode 100644 index 000000000..6b0c4ca06 --- /dev/null +++ b/src/Composer/EventDispatcher/EventSubscriberInterface.php @@ -0,0 +1,48 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\EventDispatcher; + +/** + * An EventSubscriber knows which events it is interested in. + * + * If an EventSubscriber is added to an EventDispatcher, the manager invokes + * {@link getSubscribedEvents} and registers the subscriber as a listener for all + * returned events. + * + * @author Guilherme Blanco + * @author Jonathan Wage + * @author Roman Borschel + * @author Bernhard Schussek + */ +interface EventSubscriberInterface +{ + /** + * Returns an array of event names this subscriber wants to listen to. + * + * The array keys are event names and the value can be: + * + * * The method name to call (priority defaults to 0) + * * An array composed of the method name to call and the priority + * * An array of arrays composed of the method names to call and respective + * priorities, or 0 if unset + * + * For instance: + * + * * array('eventName' => 'methodName') + * * array('eventName' => array('methodName', $priority)) + * * array('eventName' => array(array('methodName1', $priority), array('methodName2')) + * + * @return array The event names to listen to + */ + public static function getSubscribedEvents(); +} diff --git a/src/Composer/Plugin/PluginManager.php b/src/Composer/Plugin/PluginManager.php index 1d424627a..74c0081c3 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\EventDispatcher\EventSubscriberInterface; use Composer\Package\Package; use Composer\IO\IOInterface; use Composer\Package\PackageInterface; @@ -65,8 +66,8 @@ class PluginManager $this->plugins[] = $plugin; $plugin->activate($this->composer); - if ($plugin instanceof \Symfony\Component\EventDispatcher\EventSubscriberInterface) { - $this->composer->getPluginEventDispatcher()->addSubscriber($plugin); + if ($plugin instanceof EventSubscriberInterface) { + $this->composer->getEventDispatcher()->addSubscriber($plugin); } } From f00f5113bfc4c1c7035921a2c9dace34f6071e8d Mon Sep 17 00:00:00 2001 From: Nils Adermann Date: Wed, 14 Aug 2013 18:30:09 +0200 Subject: [PATCH 07/26] Fix typo --- src/Composer/EventDispatcher/EventDispatcher.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Composer/EventDispatcher/EventDispatcher.php b/src/Composer/EventDispatcher/EventDispatcher.php index f47e77044..3762529e7 100644 --- a/src/Composer/EventDispatcher/EventDispatcher.php +++ b/src/Composer/EventDispatcher/EventDispatcher.php @@ -119,7 +119,7 @@ class EventDispatcher $listeners = $this->getListeners($event); foreach ($listeners as $callable) { - if ((is_array($callable) && $is_callable($callable)) || $callable instanceof Closure) { + if ((is_array($callable) && is_callable($callable)) || $callable instanceof Closure) { $callable($event); } elseif ($this->isPhpScript($callable)) { $className = substr($callable, 0, strpos($callable, '::')); From 9402a9fb3c69b75b044377b2e0331c1f3118554e Mon Sep 17 00:00:00 2001 From: Nils Adermann Date: Wed, 14 Aug 2013 18:47:25 +0200 Subject: [PATCH 08/26] Plugins receive composer and io objects on construction already --- src/Composer/Plugin/PluginInterface.php | 6 ++---- src/Composer/Plugin/PluginManager.php | 4 ++-- src/Composer/Util/RemoteFilesystem.php | 10 ++++++++++ 3 files changed, 14 insertions(+), 6 deletions(-) diff --git a/src/Composer/Plugin/PluginInterface.php b/src/Composer/Plugin/PluginInterface.php index ebbbe0026..2c38d0f8d 100644 --- a/src/Composer/Plugin/PluginInterface.php +++ b/src/Composer/Plugin/PluginInterface.php @@ -22,9 +22,7 @@ use Composer\Composer; interface PluginInterface { /** - * Apply plugin modifications to the passed in composer object - * - * @param Composer $composer + * Apply plugin modifications to composer */ - public function activate(Composer $composer); + public function activate(); } diff --git a/src/Composer/Plugin/PluginManager.php b/src/Composer/Plugin/PluginManager.php index 74c0081c3..f97900679 100644 --- a/src/Composer/Plugin/PluginManager.php +++ b/src/Composer/Plugin/PluginManager.php @@ -64,7 +64,7 @@ class PluginManager public function addPlugin(PluginInterface $plugin) { $this->plugins[] = $plugin; - $plugin->activate($this->composer); + $plugin->activate(); if ($plugin instanceof EventSubscriberInterface) { $this->composer->getEventDispatcher()->addSubscriber($plugin); @@ -102,7 +102,7 @@ class PluginManager self::$classCounter++; } - $plugin = new $class($this->io, $this->composer); + $plugin = new $class($this->composer, $this->io); if ($oldInstallerPlugin) { $this->composer->getInstallationManager()->addInstaller($installer); diff --git a/src/Composer/Util/RemoteFilesystem.php b/src/Composer/Util/RemoteFilesystem.php index 3db55ab6d..29ed98321 100644 --- a/src/Composer/Util/RemoteFilesystem.php +++ b/src/Composer/Util/RemoteFilesystem.php @@ -76,6 +76,16 @@ class RemoteFilesystem return $this->get($originUrl, $fileUrl, $options, null, $progress); } + /** + * Retrieve the options set in the constructor + * + * @return array Options + */ + public function getOptions() + { + return $this->options; + } + /** * Get file content or copy action. * From 69a028f368ca96d52c6814933ce53d52b55e64fe Mon Sep 17 00:00:00 2001 From: Nils Adermann Date: Wed, 14 Aug 2013 19:13:27 +0200 Subject: [PATCH 09/26] Fix plugin interface usage in tests --- .../Test/Plugin/Fixtures/plugin-v1/Installer/Plugin.php | 2 +- .../Test/Plugin/Fixtures/plugin-v2/Installer/Plugin2.php | 2 +- .../Test/Plugin/Fixtures/plugin-v3/Installer/Plugin2.php | 2 +- .../Test/Plugin/Fixtures/plugin-v4/Installer/Plugin1.php | 2 +- .../Test/Plugin/Fixtures/plugin-v4/Installer/Plugin2.php | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/tests/Composer/Test/Plugin/Fixtures/plugin-v1/Installer/Plugin.php b/tests/Composer/Test/Plugin/Fixtures/plugin-v1/Installer/Plugin.php index 9196523e7..76c6cef09 100644 --- a/tests/Composer/Test/Plugin/Fixtures/plugin-v1/Installer/Plugin.php +++ b/tests/Composer/Test/Plugin/Fixtures/plugin-v1/Installer/Plugin.php @@ -9,7 +9,7 @@ class Plugin implements PluginInterface { public $version = 'installer-v1'; - public function activate(Composer $composer) + public function activate() { } } diff --git a/tests/Composer/Test/Plugin/Fixtures/plugin-v2/Installer/Plugin2.php b/tests/Composer/Test/Plugin/Fixtures/plugin-v2/Installer/Plugin2.php index 5cea4f0d0..72e44a1b5 100644 --- a/tests/Composer/Test/Plugin/Fixtures/plugin-v2/Installer/Plugin2.php +++ b/tests/Composer/Test/Plugin/Fixtures/plugin-v2/Installer/Plugin2.php @@ -9,7 +9,7 @@ class Plugin2 implements PluginInterface { public $version = 'installer-v2'; - public function activate(Composer $composer) + public function activate() { } } diff --git a/tests/Composer/Test/Plugin/Fixtures/plugin-v3/Installer/Plugin2.php b/tests/Composer/Test/Plugin/Fixtures/plugin-v3/Installer/Plugin2.php index 336618e57..e7130a1a6 100644 --- a/tests/Composer/Test/Plugin/Fixtures/plugin-v3/Installer/Plugin2.php +++ b/tests/Composer/Test/Plugin/Fixtures/plugin-v3/Installer/Plugin2.php @@ -9,7 +9,7 @@ class Plugin2 implements PluginInterface { public $version = 'installer-v3'; - public function activate(Composer $composer) + public function activate() { } } diff --git a/tests/Composer/Test/Plugin/Fixtures/plugin-v4/Installer/Plugin1.php b/tests/Composer/Test/Plugin/Fixtures/plugin-v4/Installer/Plugin1.php index 9359d0f0e..826e5d52f 100644 --- a/tests/Composer/Test/Plugin/Fixtures/plugin-v4/Installer/Plugin1.php +++ b/tests/Composer/Test/Plugin/Fixtures/plugin-v4/Installer/Plugin1.php @@ -10,7 +10,7 @@ class Plugin1 implements PluginInterface public $name = 'plugin1'; public $version = 'installer-v4'; - public function activate(Composer $composer) + public function activate() { } } diff --git a/tests/Composer/Test/Plugin/Fixtures/plugin-v4/Installer/Plugin2.php b/tests/Composer/Test/Plugin/Fixtures/plugin-v4/Installer/Plugin2.php index b98b4f393..5716e3fae 100644 --- a/tests/Composer/Test/Plugin/Fixtures/plugin-v4/Installer/Plugin2.php +++ b/tests/Composer/Test/Plugin/Fixtures/plugin-v4/Installer/Plugin2.php @@ -10,7 +10,7 @@ class Plugin2 implements PluginInterface public $name = 'plugin2'; public $version = 'installer-v4'; - public function activate(Composer $composer) + public function activate() { } } From cd66328d68160911692ede4c6805844a8e1ec46d Mon Sep 17 00:00:00 2001 From: Nils Adermann Date: Thu, 15 Aug 2013 15:11:17 +0200 Subject: [PATCH 10/26] Autoload dependencies of plugins using a pool of only the local repo --- src/Composer/Plugin/PluginManager.php | 43 +++++++++++++++++++++++++-- 1 file changed, 40 insertions(+), 3 deletions(-) diff --git a/src/Composer/Plugin/PluginManager.php b/src/Composer/Plugin/PluginManager.php index f97900679..f13c9787d 100644 --- a/src/Composer/Plugin/PluginManager.php +++ b/src/Composer/Plugin/PluginManager.php @@ -17,6 +17,8 @@ use Composer\EventDispatcher\EventSubscriberInterface; use Composer\Package\Package; use Composer\IO\IOInterface; use Composer\Package\PackageInterface; +use Composer\Package\Link; +use Composer\DependencyResolver\Pool; /** * Plugin manager @@ -76,20 +78,55 @@ class PluginManager return $this->plugins; } + protected function collectDependencies(Pool $pool, array $collected, PackageInterface $package) + { + $requires = array_merge( + $package->getRequires(), + $package->getDevRequires() + ); + + foreach ($requires as $requireLink) { + $requiredPackage = $this->lookupInstalledPackage($pool, $requireLink); + if ($requiredPackage && !isset($collected[$requiredPackage->getName()])) { + $collected[$requiredPackage->getName()] = $requiredPackage; + $collected = $this->collectDependencies($pool, $collected, $requiredPackage); + } + } + + return $collected; + } + + protected function lookupInstalledPackage(Pool $pool, Link $link) + { + $packages = $pool->whatProvides($link->getTarget(), $link->getConstraint()); + + return (!empty($packages)) ? $packages[0] : null; + } + public function registerPackage(PackageInterface $package) { $oldInstallerPlugin = ($package->getType() === 'composer-installer'); - $downloadPath = $this->getInstallPath($package); - $extra = $package->getExtra(); if (empty($extra['class'])) { throw new \UnexpectedValueException('Error while installing '.$package->getPrettyName().', composer-plugin packages should have a class defined in their extra key to be usable.'); } $classes = is_array($extra['class']) ? $extra['class'] : array($extra['class']); + $pool = new Pool('dev'); + $pool->addRepository($this->composer->getRepositoryManager()->getLocalRepository()); + + $autoloadPackages = array($package->getName() => $package); + $autoloadPackages = $this->collectDependencies($pool, $autoloadPackages, $package); + $generator = $this->composer->getAutoloadGenerator(); - $map = $generator->parseAutoloads(array(array($package, $downloadPath)), new Package('dummy', '1.0.0.0', '1.0.0')); + $autoloads = array(); + foreach ($autoloadPackages as $autoloadPackage) { + $downloadPath = $this->getInstallPath($autoloadPackage); + $autoloads[] = array($autoloadPackage, $downloadPath); + } + + $map = $generator->parseAutoloads($autoloads, new Package('dummy', '1.0.0.0', '1.0.0')); $classLoader = $generator->createLoader($map); $classLoader->register(); From b83535d2d91890e7f89570f701553d3f2722864a Mon Sep 17 00:00:00 2001 From: Nils Adermann Date: Thu, 15 Aug 2013 16:35:42 +0200 Subject: [PATCH 11/26] Add back --no-custom-installers option with a deprecated warning --- src/Composer/Command/CreateProjectCommand.php | 6 ++++++ src/Composer/Command/InstallCommand.php | 5 +++++ src/Composer/Command/UpdateCommand.php | 5 +++++ 3 files changed, 16 insertions(+) diff --git a/src/Composer/Command/CreateProjectCommand.php b/src/Composer/Command/CreateProjectCommand.php index c38f0bec4..5cc19927a 100644 --- a/src/Composer/Command/CreateProjectCommand.php +++ b/src/Composer/Command/CreateProjectCommand.php @@ -63,6 +63,7 @@ class CreateProjectCommand extends Command new InputOption('dev', null, InputOption::VALUE_NONE, 'Enables installation of require-dev packages (enabled by default, only present for BC).'), new InputOption('no-dev', null, InputOption::VALUE_NONE, 'Disables installation of require-dev packages.'), new InputOption('no-plugins', null, InputOption::VALUE_NONE, 'Whether to disable plugins.'), + new InputOption('no-custom-installers', null, InputOption::VALUE_NONE, 'DEPRECATED: Use no-plugins instead.'), new InputOption('no-scripts', null, InputOption::VALUE_NONE, 'Whether to prevent execution of all defined scripts in the root package.'), new InputOption('no-progress', null, InputOption::VALUE_NONE, 'Do not output download progress.'), new InputOption('keep-vcs', null, InputOption::VALUE_NONE, 'Whether to prevent deletion vcs folder.'), @@ -116,6 +117,11 @@ EOT $preferDist = $input->getOption('prefer-dist'); } + if ($input->getOption('no-custom-installers')) { + $output->writeln('You are using the deprecated option "no-custom-installers". Use "no-plugins" instead.'); + $input->setOption('no-plugins', true); + } + return $this->installProject( $this->getIO(), $config, diff --git a/src/Composer/Command/InstallCommand.php b/src/Composer/Command/InstallCommand.php index 90ad2810f..2a9c6466a 100644 --- a/src/Composer/Command/InstallCommand.php +++ b/src/Composer/Command/InstallCommand.php @@ -36,6 +36,7 @@ class InstallCommand extends Command new InputOption('dev', null, InputOption::VALUE_NONE, 'Enables installation of require-dev packages (enabled by default, only present for BC).'), new InputOption('no-dev', null, InputOption::VALUE_NONE, 'Disables installation of require-dev packages.'), new InputOption('no-plugins', null, InputOption::VALUE_NONE, 'Disables all plugins.'), + new InputOption('no-custom-installers', null, InputOption::VALUE_NONE, 'DEPRECATED: Use no-plugins instead.'), new InputOption('no-scripts', null, InputOption::VALUE_NONE, 'Skips the execution of all scripts defined in composer.json file.'), new InputOption('no-progress', null, InputOption::VALUE_NONE, 'Do not output download progress.'), new InputOption('verbose', 'v|vv|vvv', InputOption::VALUE_NONE, 'Shows more details including new commits pulled in when updating packages.'), @@ -90,6 +91,10 @@ EOT ->setOptimizeAutoloader($input->getOption('optimize-autoloader')) ; + if ($input->getOption('no-custom-installers')) { + $output->writeln('You are using the deprecated option "no-custom-installers". Use "no-plugins" instead.'); + $input->setOption('no-plugins', true); + } if ($input->getOption('no-plugins')) { $install->disablePlugins(); } diff --git a/src/Composer/Command/UpdateCommand.php b/src/Composer/Command/UpdateCommand.php index 8d3133b83..09fa61a9b 100644 --- a/src/Composer/Command/UpdateCommand.php +++ b/src/Composer/Command/UpdateCommand.php @@ -37,6 +37,7 @@ class UpdateCommand extends Command new InputOption('no-dev', null, InputOption::VALUE_NONE, 'Disables installation of require-dev packages.'), new InputOption('lock', null, InputOption::VALUE_NONE, 'Only updates the lock file hash to suppress warning about the lock file being out of date.'), new InputOption('no-plugins', null, InputOption::VALUE_NONE, 'Disables all plugins.'), + new InputOption('no-custom-installers', null, InputOption::VALUE_NONE, 'DEPRECATED: Use no-plugins instead.'), new InputOption('no-scripts', null, InputOption::VALUE_NONE, 'Skips the execution of all scripts defined in composer.json file.'), new InputOption('no-progress', null, InputOption::VALUE_NONE, 'Do not output download progress.'), new InputOption('verbose', 'v|vv|vvv', InputOption::VALUE_NONE, 'Shows more details including new commits pulled in when updating packages.'), @@ -96,6 +97,10 @@ EOT ->setUpdateWhitelist($input->getOption('lock') ? array('lock') : $input->getArgument('packages')) ; + if ($input->getOption('no-custom-installers')) { + $output->writeln('You are using the deprecated option "no-custom-installers". Use "no-plugins" instead.'); + $input->setOption('no-plugins', true); + } if ($input->getOption('no-plugins')) { $install->disablePlugins(); } From b9c575867061d5afdbd62cb16478673f617eeadd Mon Sep 17 00:00:00 2001 From: Nils Adermann Date: Thu, 15 Aug 2013 16:58:48 +0200 Subject: [PATCH 12/26] Make composer/io part of the activate plugin API rather than constructor args --- src/Composer/Plugin/PluginInterface.php | 6 +++++- src/Composer/Plugin/PluginManager.php | 6 +++--- .../Test/Plugin/Fixtures/plugin-v1/Installer/Plugin.php | 3 ++- .../Test/Plugin/Fixtures/plugin-v2/Installer/Plugin2.php | 3 ++- .../Test/Plugin/Fixtures/plugin-v3/Installer/Plugin2.php | 3 ++- .../Test/Plugin/Fixtures/plugin-v4/Installer/Plugin1.php | 3 ++- .../Test/Plugin/Fixtures/plugin-v4/Installer/Plugin2.php | 3 ++- tests/Composer/Test/Plugin/PluginInstallerTest.php | 8 ++++---- 8 files changed, 22 insertions(+), 13 deletions(-) diff --git a/src/Composer/Plugin/PluginInterface.php b/src/Composer/Plugin/PluginInterface.php index 2c38d0f8d..302175a25 100644 --- a/src/Composer/Plugin/PluginInterface.php +++ b/src/Composer/Plugin/PluginInterface.php @@ -13,6 +13,7 @@ namespace Composer\Plugin; use Composer\Composer; +use Composer\IO\IOInterface; /** * Plugin interface @@ -23,6 +24,9 @@ interface PluginInterface { /** * Apply plugin modifications to composer + * + * @param Composer $composer + * @param IOInterface $io */ - public function activate(); + public function activate(Composer $composer, IOInterface $io); } diff --git a/src/Composer/Plugin/PluginManager.php b/src/Composer/Plugin/PluginManager.php index f13c9787d..a6956c24e 100644 --- a/src/Composer/Plugin/PluginManager.php +++ b/src/Composer/Plugin/PluginManager.php @@ -66,7 +66,7 @@ class PluginManager public function addPlugin(PluginInterface $plugin) { $this->plugins[] = $plugin; - $plugin->activate(); + $plugin->activate($this->composer, $this->io); if ($plugin instanceof EventSubscriberInterface) { $this->composer->getEventDispatcher()->addSubscriber($plugin); @@ -139,11 +139,11 @@ class PluginManager self::$classCounter++; } - $plugin = new $class($this->composer, $this->io); - if ($oldInstallerPlugin) { + $installer = new $class($this->composer, $this->io); $this->composer->getInstallationManager()->addInstaller($installer); } else { + $plugin = new $class(); $this->addPlugin($plugin); } } diff --git a/tests/Composer/Test/Plugin/Fixtures/plugin-v1/Installer/Plugin.php b/tests/Composer/Test/Plugin/Fixtures/plugin-v1/Installer/Plugin.php index 76c6cef09..f80acd325 100644 --- a/tests/Composer/Test/Plugin/Fixtures/plugin-v1/Installer/Plugin.php +++ b/tests/Composer/Test/Plugin/Fixtures/plugin-v1/Installer/Plugin.php @@ -3,13 +3,14 @@ namespace Installer; use Composer\Composer; +use Composer\IO\IOInterface; use Composer\Plugin\PluginInterface; class Plugin implements PluginInterface { public $version = 'installer-v1'; - public function activate() + public function activate(Composer $composer, IOInterface $io) { } } diff --git a/tests/Composer/Test/Plugin/Fixtures/plugin-v2/Installer/Plugin2.php b/tests/Composer/Test/Plugin/Fixtures/plugin-v2/Installer/Plugin2.php index 72e44a1b5..db5a4462e 100644 --- a/tests/Composer/Test/Plugin/Fixtures/plugin-v2/Installer/Plugin2.php +++ b/tests/Composer/Test/Plugin/Fixtures/plugin-v2/Installer/Plugin2.php @@ -3,13 +3,14 @@ namespace Installer; use Composer\Composer; +use Composer\IO\IOInterface; use Composer\Plugin\PluginInterface; class Plugin2 implements PluginInterface { public $version = 'installer-v2'; - public function activate() + public function activate(Composer $composer, IOInterface $io) { } } diff --git a/tests/Composer/Test/Plugin/Fixtures/plugin-v3/Installer/Plugin2.php b/tests/Composer/Test/Plugin/Fixtures/plugin-v3/Installer/Plugin2.php index e7130a1a6..861c1679b 100644 --- a/tests/Composer/Test/Plugin/Fixtures/plugin-v3/Installer/Plugin2.php +++ b/tests/Composer/Test/Plugin/Fixtures/plugin-v3/Installer/Plugin2.php @@ -3,13 +3,14 @@ namespace Installer; use Composer\Composer; +use Composer\IO\IOInterface; use Composer\Plugin\PluginInterface; class Plugin2 implements PluginInterface { public $version = 'installer-v3'; - public function activate() + public function activate(Composer $composer, IOInterface $io) { } } diff --git a/tests/Composer/Test/Plugin/Fixtures/plugin-v4/Installer/Plugin1.php b/tests/Composer/Test/Plugin/Fixtures/plugin-v4/Installer/Plugin1.php index 826e5d52f..93bcabc98 100644 --- a/tests/Composer/Test/Plugin/Fixtures/plugin-v4/Installer/Plugin1.php +++ b/tests/Composer/Test/Plugin/Fixtures/plugin-v4/Installer/Plugin1.php @@ -3,6 +3,7 @@ namespace Installer; use Composer\Composer; +use Composer\IO\IOInterface; use Composer\Plugin\PluginInterface; class Plugin1 implements PluginInterface @@ -10,7 +11,7 @@ class Plugin1 implements PluginInterface public $name = 'plugin1'; public $version = 'installer-v4'; - public function activate() + public function activate(Composer $composer, IOInterface $io) { } } diff --git a/tests/Composer/Test/Plugin/Fixtures/plugin-v4/Installer/Plugin2.php b/tests/Composer/Test/Plugin/Fixtures/plugin-v4/Installer/Plugin2.php index 5716e3fae..d946deb89 100644 --- a/tests/Composer/Test/Plugin/Fixtures/plugin-v4/Installer/Plugin2.php +++ b/tests/Composer/Test/Plugin/Fixtures/plugin-v4/Installer/Plugin2.php @@ -3,6 +3,7 @@ namespace Installer; use Composer\Composer; +use Composer\IO\IOInterface; use Composer\Plugin\PluginInterface; class Plugin2 implements PluginInterface @@ -10,7 +11,7 @@ class Plugin2 implements PluginInterface public $name = 'plugin2'; public $version = 'installer-v4'; - public function activate() + public function activate(Composer $composer, IOInterface $io) { } } diff --git a/tests/Composer/Test/Plugin/PluginInstallerTest.php b/tests/Composer/Test/Plugin/PluginInstallerTest.php index 8734993ea..5d21f59db 100644 --- a/tests/Composer/Test/Plugin/PluginInstallerTest.php +++ b/tests/Composer/Test/Plugin/PluginInstallerTest.php @@ -77,7 +77,7 @@ class PluginInstallerTest extends \PHPUnit_Framework_TestCase public function testInstallNewPlugin() { $this->repository - ->expects($this->once()) + ->expects($this->exactly(2)) ->method('getPackages') ->will($this->returnValue(array())); $installer = new PluginInstaller($this->io, $this->composer); @@ -92,7 +92,7 @@ class PluginInstallerTest extends \PHPUnit_Framework_TestCase public function testInstallMultiplePlugins() { $this->repository - ->expects($this->once()) + ->expects($this->exactly(2)) ->method('getPackages') ->will($this->returnValue(array())); $installer = new PluginInstaller($this->io, $this->composer); @@ -110,7 +110,7 @@ class PluginInstallerTest extends \PHPUnit_Framework_TestCase public function testUpgradeWithNewClassName() { $this->repository - ->expects($this->once()) + ->expects($this->exactly(3)) ->method('getPackages') ->will($this->returnValue(array($this->packages[0]))); $this->repository @@ -129,7 +129,7 @@ class PluginInstallerTest extends \PHPUnit_Framework_TestCase public function testUpgradeWithSameClassName() { $this->repository - ->expects($this->once()) + ->expects($this->exactly(3)) ->method('getPackages') ->will($this->returnValue(array($this->packages[1]))); $this->repository From f0b45099c1354e6822d9bf6a7664dde5eb3a6a65 Mon Sep 17 00:00:00 2001 From: Nils Adermann Date: Thu, 15 Aug 2013 17:14:48 +0200 Subject: [PATCH 13/26] Correct authorship info for files I edited --- src/Composer/Command/CreateProjectCommand.php | 1 + src/Composer/Command/InstallCommand.php | 1 + src/Composer/Command/UpdateCommand.php | 1 + src/Composer/Composer.php | 1 + src/Composer/Factory.php | 1 + src/Composer/Installer.php | 1 + src/Composer/Installer/InstallationManager.php | 1 + src/Composer/Installer/PluginInstaller.php | 3 ++- src/Composer/Util/RemoteFilesystem.php | 1 + 9 files changed, 10 insertions(+), 1 deletion(-) diff --git a/src/Composer/Command/CreateProjectCommand.php b/src/Composer/Command/CreateProjectCommand.php index 5cc19927a..308f1d204 100644 --- a/src/Composer/Command/CreateProjectCommand.php +++ b/src/Composer/Command/CreateProjectCommand.php @@ -44,6 +44,7 @@ use Composer\Package\Version\VersionParser; * @author Benjamin Eberlei * @author Jordi Boggiano * @author Tobias Munk + * @author Nils Adermann */ class CreateProjectCommand extends Command { diff --git a/src/Composer/Command/InstallCommand.php b/src/Composer/Command/InstallCommand.php index 2a9c6466a..edf8ae593 100644 --- a/src/Composer/Command/InstallCommand.php +++ b/src/Composer/Command/InstallCommand.php @@ -21,6 +21,7 @@ use Symfony\Component\Console\Output\OutputInterface; * @author Jordi Boggiano * @author Ryan Weaver * @author Konstantin Kudryashov + * @author Nils Adermann */ class InstallCommand extends Command { diff --git a/src/Composer/Command/UpdateCommand.php b/src/Composer/Command/UpdateCommand.php index 09fa61a9b..3ac0e1401 100644 --- a/src/Composer/Command/UpdateCommand.php +++ b/src/Composer/Command/UpdateCommand.php @@ -20,6 +20,7 @@ use Symfony\Component\Console\Output\OutputInterface; /** * @author Jordi Boggiano + * @author Nils Adermann */ class UpdateCommand extends Command { diff --git a/src/Composer/Composer.php b/src/Composer/Composer.php index e77316e4c..20279b5ac 100644 --- a/src/Composer/Composer.php +++ b/src/Composer/Composer.php @@ -24,6 +24,7 @@ use Composer\Autoload\AutoloadGenerator; /** * @author Jordi Boggiano * @author Konstantin Kudryashiv + * @author Nils Adermann */ class Composer { diff --git a/src/Composer/Factory.php b/src/Composer/Factory.php index a7609469a..ae18bee50 100644 --- a/src/Composer/Factory.php +++ b/src/Composer/Factory.php @@ -31,6 +31,7 @@ use Composer\Package\Version\VersionParser; * @author Ryan Weaver * @author Jordi Boggiano * @author Igor Wiedler + * @author Nils Adermann */ class Factory { diff --git a/src/Composer/Installer.php b/src/Composer/Installer.php index aec6e55a7..3e9a9bf6b 100644 --- a/src/Composer/Installer.php +++ b/src/Composer/Installer.php @@ -48,6 +48,7 @@ use Composer\Script\ScriptEvents; * @author Jordi Boggiano * @author Beau Simensen * @author Konstantin Kudryashov + * @author Nils Adermann */ class Installer { diff --git a/src/Composer/Installer/InstallationManager.php b/src/Composer/Installer/InstallationManager.php index b26273847..a43acbbda 100644 --- a/src/Composer/Installer/InstallationManager.php +++ b/src/Composer/Installer/InstallationManager.php @@ -29,6 +29,7 @@ use Composer\Util\StreamContextFactory; * * @author Konstantin Kudryashov * @author Jordi Boggiano + * @author Nils Adermann */ class InstallationManager { diff --git a/src/Composer/Installer/PluginInstaller.php b/src/Composer/Installer/PluginInstaller.php index 046aced45..61c5a2823 100644 --- a/src/Composer/Installer/PluginInstaller.php +++ b/src/Composer/Installer/PluginInstaller.php @@ -19,9 +19,10 @@ use Composer\Repository\InstalledRepositoryInterface; use Composer\Package\PackageInterface; /** - * Installer installation manager. + * Installer for plugin packages * * @author Jordi Boggiano + * @author Nils Adermann */ class PluginInstaller extends LibraryInstaller { diff --git a/src/Composer/Util/RemoteFilesystem.php b/src/Composer/Util/RemoteFilesystem.php index 29ed98321..7c26bd290 100644 --- a/src/Composer/Util/RemoteFilesystem.php +++ b/src/Composer/Util/RemoteFilesystem.php @@ -19,6 +19,7 @@ use Composer\Downloader\TransportException; /** * @author François Pluchino * @author Jordi Boggiano + * @author Nils Adermann */ class RemoteFilesystem { From 3b519e44c44fef5d034ab8c727d15c87bdfa814c Mon Sep 17 00:00:00 2001 From: Nils Adermann Date: Thu, 15 Aug 2013 17:18:10 +0200 Subject: [PATCH 14/26] Rename PrepareRemoteFilesystem event to PreFileDownload --- src/Composer/Downloader/FileDownloader.php | 8 ++++---- src/Composer/Plugin/PluginEvents.php | 6 +++--- ...RemoteFilesystemEvent.php => PreFileDownloadEvent.php} | 4 ++-- 3 files changed, 9 insertions(+), 9 deletions(-) rename src/Composer/Plugin/{PrepareRemoteFilesystemEvent.php => PreFileDownloadEvent.php} (95%) diff --git a/src/Composer/Downloader/FileDownloader.php b/src/Composer/Downloader/FileDownloader.php index b9852d730..40cbe0a12 100644 --- a/src/Composer/Downloader/FileDownloader.php +++ b/src/Composer/Downloader/FileDownloader.php @@ -18,7 +18,7 @@ use Composer\IO\IOInterface; use Composer\Package\PackageInterface; use Composer\Package\Version\VersionParser; use Composer\Plugin\PluginEvents; -use Composer\Plugin\PrepareRemoteFilesystemEvent; +use Composer\Plugin\PreFileDownloadEvent; use Composer\EventDispatcher\EventDispatcher; use Composer\Util\Filesystem; use Composer\Util\GitHub; @@ -94,11 +94,11 @@ class FileDownloader implements DownloaderInterface $processedUrl = $this->processUrl($package, $url); $hostname = parse_url($processedUrl, PHP_URL_HOST); - $prepRfsEvent = new PrepareRemoteFilesystemEvent(PluginEvents::PREPARE_REMOTE_FILESYSTEM, $this->rfs, $processedUrl); + $preFileDownloadEvent = new PreFileDownloadEvent(PluginEvents::PRE_FILE_DOWNLOAD, $this->rfs, $processedUrl); if ($this->eventDispatcher) { - $this->eventDispatcher->dispatch($prepRfsEvent->getName(), $prepRfsEvent); + $this->eventDispatcher->dispatch($preFileDownloadEvent->getName(), $preFileDownloadEvent); } - $rfs = $prepRfsEvent->getRemoteFilesystem(); + $rfs = $preFileDownloadEvent->getRemoteFilesystem(); if (strpos($hostname, '.github.com') === (strlen($hostname) - 11)) { $hostname = 'github.com'; diff --git a/src/Composer/Plugin/PluginEvents.php b/src/Composer/Plugin/PluginEvents.php index bfdfba3dd..cbf9b1148 100644 --- a/src/Composer/Plugin/PluginEvents.php +++ b/src/Composer/Plugin/PluginEvents.php @@ -20,12 +20,12 @@ namespace Composer\Plugin; class PluginEvents { /** - * The PREPARE_REMOTE_FILESYSTEM event occurs before downloading a file + * The PRE_FILE_DOWNLOAD event occurs before downloading a file * * The event listener method receives a - * Composer\Plugin\PrepareRemoteFilesystemEvent instance. + * Composer\Plugin\PreFileDownloadEvent instance. * * @var string */ - const PREPARE_REMOTE_FILESYSTEM = 'prepare-remote-filesystem'; + const PRE_FILE_DOWNLOAD = 'pre-file-download'; } diff --git a/src/Composer/Plugin/PrepareRemoteFilesystemEvent.php b/src/Composer/Plugin/PreFileDownloadEvent.php similarity index 95% rename from src/Composer/Plugin/PrepareRemoteFilesystemEvent.php rename to src/Composer/Plugin/PreFileDownloadEvent.php index 91b0543f3..94621a5c2 100644 --- a/src/Composer/Plugin/PrepareRemoteFilesystemEvent.php +++ b/src/Composer/Plugin/PreFileDownloadEvent.php @@ -18,11 +18,11 @@ use Composer\EventDispatcher\Event; use Composer\Util\RemoteFilesystem; /** - * The Prepare Remote Filesystem Event. + * The pre file download event. * * @author Nils Adermann */ -class PrepareRemoteFilesystemEvent extends Event +class PreFileDownloadEvent extends Event { /** * @var RemoteFilesystem From 3e1519cde09cbbf2e33fa4acce983453f30f1f75 Mon Sep 17 00:00:00 2001 From: Nils Adermann Date: Thu, 15 Aug 2013 18:04:13 +0200 Subject: [PATCH 15/26] Complete missing docblocks and fix incorrect ones --- .../EventDispatcher/EventDispatcher.php | 22 ++++++++ src/Composer/Plugin/PluginManager.php | 51 ++++++++++++++++++- src/Composer/Plugin/PreFileDownloadEvent.php | 12 ++--- 3 files changed, 78 insertions(+), 7 deletions(-) diff --git a/src/Composer/EventDispatcher/EventDispatcher.php b/src/Composer/EventDispatcher/EventDispatcher.php index 3762529e7..acf8d2f2c 100644 --- a/src/Composer/EventDispatcher/EventDispatcher.php +++ b/src/Composer/EventDispatcher/EventDispatcher.php @@ -165,11 +165,25 @@ class EventDispatcher $className::$methodName($event); } + /** + * Add a listener for a particular event + * + * @param string $eventName The event name - typically a constant + * @param Callable $listener A callable expecting an event argument + * @param integer $priority A higher value represents a higher priority + */ protected function addListener($eventName, $listener, $priority = 0) { $this->listeners[$eventName][$priority][] = $listener; } + /** + * Adds object methods as listeners for the events in getSubscribedEvents + * + * @see EventSubscriberInterface + * + * @param EventSubscriberInterface $subscriber + */ public function addSubscriber(EventSubscriberInterface $subscriber) { foreach ($subscriber->getSubscribedEvents() as $eventName => $params) { @@ -185,6 +199,12 @@ class EventDispatcher } } + /** + * Retrieves all listeners for a given event + * + * @param Event $event + * @return array All listeners: callables and scripts + */ protected function getListeners(Event $event) { $scriptListeners = $this->getScriptListeners($event); @@ -201,6 +221,8 @@ class EventDispatcher } /** + * Finds all listeners defined as scripts in the package + * * @param Event $event Event object * @return array Listeners */ diff --git a/src/Composer/Plugin/PluginManager.php b/src/Composer/Plugin/PluginManager.php index a6956c24e..386de461b 100644 --- a/src/Composer/Plugin/PluginManager.php +++ b/src/Composer/Plugin/PluginManager.php @@ -45,6 +45,9 @@ class PluginManager $this->io = $io; } + /** + * Loads all plugins from currently installed plugin packages + */ public function loadInstalledPlugins() { $repo = $this->composer->getRepositoryManager()->getLocalRepository(); @@ -59,7 +62,7 @@ class PluginManager } /** - * Adds plugin + * Adds a plugin, activates it and registers it with the event dispatcher * * @param PluginInterface $plugin plugin instance */ @@ -73,11 +76,25 @@ class PluginManager } } + /** + * Gets all currently active plugin instances + * + * @return array plugins + */ public function getPlugins() { return $this->plugins; } + /** + * Recursively generates a map of package names to packages for all deps + * + * @param Pool $pool Package pool of installed packages + * @param array $collected Current state of the map for recursion + * @param PackageInterface $package The package to analyze + * + * @return array Map of package names to packages + */ protected function collectDependencies(Pool $pool, array $collected, PackageInterface $package) { $requires = array_merge( @@ -96,6 +113,16 @@ class PluginManager return $collected; } + /** + * Resolves a package link to a package in the installed pool + * + * Since dependencies are already installed this should always find one. + * + * @param Pool $pool Pool of installed packages only + * @param Link $link Package link to look up + * + * @return PackageInterface|null The found package + */ protected function lookupInstalledPackage(Pool $pool, Link $link) { $packages = $pool->whatProvides($link->getTarget(), $link->getConstraint()); @@ -103,6 +130,14 @@ class PluginManager return (!empty($packages)) ? $packages[0] : null; } + /** + * Register a plugin package, activate it etc. + * + * If it's of type composer-installer it is registered as an installer + * instead for BC + * + * @param PackageInterface $package + */ public function registerPackage(PackageInterface $package) { $oldInstallerPlugin = ($package->getType() === 'composer-installer'); @@ -149,6 +184,12 @@ class PluginManager } } + /** + * Retrieves the path a package is installed to. + * + * @param PackageInterface $package + * @return string Install path + */ public function getInstallPath(PackageInterface $package) { $targetDir = $package->getTargetDir(); @@ -156,6 +197,14 @@ class PluginManager return $this->getPackageBasePath($package) . ($targetDir ? '/'.$targetDir : ''); } + /** + * Retrieves the base path a package gets installed into. + * + * Does not take targetDir into account. + * + * @param PackageInterface $package + * @return string Base path + */ protected function getPackageBasePath(PackageInterface $package) { $vendorDir = rtrim($this->composer->getConfig()->get('vendor-dir'), '/'); diff --git a/src/Composer/Plugin/PreFileDownloadEvent.php b/src/Composer/Plugin/PreFileDownloadEvent.php index 94621a5c2..847477e10 100644 --- a/src/Composer/Plugin/PreFileDownloadEvent.php +++ b/src/Composer/Plugin/PreFileDownloadEvent.php @@ -37,11 +37,9 @@ class PreFileDownloadEvent extends Event /** * Constructor. * - * @param string $name The event name - * @param Composer $composer The composer object - * @param IOInterface $io The IOInterface object - * @param boolean $devMode Whether or not we are in dev mode - * @param OperationInterface $operation The operation object + * @param string $name The event name + * @param RemoteFilesystem $rfs + * @param string $processedUrl */ public function __construct($name, RemoteFilesystem $rfs, $processedUrl) { @@ -53,7 +51,7 @@ class PreFileDownloadEvent extends Event /** * Returns the remote filesystem * - * @return OperationInterface + * @return RemoteFilesystem */ public function getRemoteFilesystem() { @@ -62,6 +60,8 @@ class PreFileDownloadEvent extends Event /** * Sets the remote filesystem + * + * @param RemoteFilesystem $rfs */ public function setRemoteFilesystem(RemoteFilesystem $rfs) { From 15ac7be6f1115c176dd9849ee3530b8aa8a94d48 Mon Sep 17 00:00:00 2001 From: Nils Adermann Date: Thu, 15 Aug 2013 18:46:17 +0200 Subject: [PATCH 16/26] Fix disabling plugins which has to happen in the factory now --- src/Composer/Command/Command.php | 5 +++-- src/Composer/Command/CreateProjectCommand.php | 2 +- src/Composer/Command/InstallCommand.php | 11 ++++++----- src/Composer/Command/UpdateCommand.php | 11 ++++++----- src/Composer/Console/Application.php | 5 +++-- src/Composer/Factory.php | 12 ++++++++---- src/Composer/Installer/InstallationManager.php | 1 + 7 files changed, 28 insertions(+), 19 deletions(-) diff --git a/src/Composer/Command/Command.php b/src/Composer/Command/Command.php index 6e91ff869..862b54e58 100644 --- a/src/Composer/Command/Command.php +++ b/src/Composer/Command/Command.php @@ -38,16 +38,17 @@ abstract class Command extends BaseCommand /** * @param bool $required + * @param bool $disablePlugins * @throws \RuntimeException * @return Composer */ - public function getComposer($required = true) + public function getComposer($required = true, $disablePlugins = false) { if (null === $this->composer) { $application = $this->getApplication(); if ($application instanceof Application) { /* @var $application Application */ - $this->composer = $application->getComposer($required); + $this->composer = $application->getComposer($required, $disablePlugins); } elseif ($required) { throw new \RuntimeException( 'Could not create a Composer\Composer instance, you must inject '. diff --git a/src/Composer/Command/CreateProjectCommand.php b/src/Composer/Command/CreateProjectCommand.php index 308f1d204..c764c9340 100644 --- a/src/Composer/Command/CreateProjectCommand.php +++ b/src/Composer/Command/CreateProjectCommand.php @@ -151,7 +151,7 @@ EOT $installedFromVcs = false; } - $composer = Factory::create($io); + $composer = Factory::create($io, null, $disablePlugins); if ($noScripts === false) { // dispatch event diff --git a/src/Composer/Command/InstallCommand.php b/src/Composer/Command/InstallCommand.php index edf8ae593..adc2ca595 100644 --- a/src/Composer/Command/InstallCommand.php +++ b/src/Composer/Command/InstallCommand.php @@ -58,7 +58,12 @@ EOT protected function execute(InputInterface $input, OutputInterface $output) { - $composer = $this->getComposer(); + if ($input->getOption('no-custom-installers')) { + $output->writeln('You are using the deprecated option "no-custom-installers". Use "no-plugins" instead.'); + $input->setOption('no-plugins', true); + } + + $composer = $this->getComposer(true, $input->getOption('no-plugins')); $composer->getDownloadManager()->setOutputProgress(!$input->getOption('no-progress')); $io = $this->getIO(); $install = Installer::create($io, $composer); @@ -92,10 +97,6 @@ EOT ->setOptimizeAutoloader($input->getOption('optimize-autoloader')) ; - if ($input->getOption('no-custom-installers')) { - $output->writeln('You are using the deprecated option "no-custom-installers". Use "no-plugins" instead.'); - $input->setOption('no-plugins', true); - } if ($input->getOption('no-plugins')) { $install->disablePlugins(); } diff --git a/src/Composer/Command/UpdateCommand.php b/src/Composer/Command/UpdateCommand.php index 3ac0e1401..728fadd24 100644 --- a/src/Composer/Command/UpdateCommand.php +++ b/src/Composer/Command/UpdateCommand.php @@ -62,7 +62,12 @@ EOT protected function execute(InputInterface $input, OutputInterface $output) { - $composer = $this->getComposer(); + if ($input->getOption('no-custom-installers')) { + $output->writeln('You are using the deprecated option "no-custom-installers". Use "no-plugins" instead.'); + $input->setOption('no-plugins', true); + } + + $composer = $this->getComposer(true, $input->getOption('no-plugins')); $composer->getDownloadManager()->setOutputProgress(!$input->getOption('no-progress')); $io = $this->getIO(); $install = Installer::create($io, $composer); @@ -98,10 +103,6 @@ EOT ->setUpdateWhitelist($input->getOption('lock') ? array('lock') : $input->getArgument('packages')) ; - if ($input->getOption('no-custom-installers')) { - $output->writeln('You are using the deprecated option "no-custom-installers". Use "no-plugins" instead.'); - $input->setOption('no-plugins', true); - } if ($input->getOption('no-plugins')) { $install->disablePlugins(); } diff --git a/src/Composer/Console/Application.php b/src/Composer/Console/Application.php index 0d1e45500..63bb59124 100644 --- a/src/Composer/Console/Application.php +++ b/src/Composer/Console/Application.php @@ -165,14 +165,15 @@ class Application extends BaseApplication /** * @param bool $required + * @param bool $disablePlugins * @throws JsonValidationException * @return \Composer\Composer */ - public function getComposer($required = true) + public function getComposer($required = true, $disablePlugins = false) { if (null === $this->composer) { try { - $this->composer = Factory::create($this->io); + $this->composer = Factory::create($this->io, null, $disablePlugins); } catch (\InvalidArgumentException $e) { if ($required) { $this->io->write($e->getMessage()); diff --git a/src/Composer/Factory.php b/src/Composer/Factory.php index ae18bee50..28b00fd49 100644 --- a/src/Composer/Factory.php +++ b/src/Composer/Factory.php @@ -177,11 +177,12 @@ class Factory * @param IOInterface $io IO instance * @param array|string|null $localConfig either a configuration array or a filename to read from, if null it will * read from the default filename + * @param bool $disablePlugins Whether plugins should not be loaded * @throws \InvalidArgumentException * @throws \UnexpectedValueException * @return Composer */ - public function createComposer(IOInterface $io, $localConfig = null) + public function createComposer(IOInterface $io, $localConfig = null, $disablePlugins = false) { // load Composer configuration if (null === $localConfig) { @@ -269,7 +270,9 @@ class Factory $composer->setLocker($locker); } - $pm->loadInstalledPlugins(); + if (!$disablePlugins) { + $pm->loadInstalledPlugins(); + } return $composer; } @@ -408,12 +411,13 @@ class Factory * @param IOInterface $io IO instance * @param mixed $config either a configuration array or a filename to read from, if null it will read from * the default filename + * @param bool $disablePlugins Whether plugins should not be loaded * @return Composer */ - public static function create(IOInterface $io, $config = null) + public static function create(IOInterface $io, $config = null, $disablePlugins = false) { $factory = new static(); - return $factory->createComposer($io, $config); + return $factory->createComposer($io, $config, $disablePlugins); } } diff --git a/src/Composer/Installer/InstallationManager.php b/src/Composer/Installer/InstallationManager.php index a43acbbda..21b16e2fd 100644 --- a/src/Composer/Installer/InstallationManager.php +++ b/src/Composer/Installer/InstallationManager.php @@ -14,6 +14,7 @@ namespace Composer\Installer; use Composer\Package\PackageInterface; use Composer\Package\AliasPackage; +use Composer\Plugin\PluginInstaller; use Composer\Repository\RepositoryInterface; use Composer\Repository\InstalledRepositoryInterface; use Composer\DependencyResolver\Operation\OperationInterface; From a8c0170a91e927359dd7eb4934e98c502555d1b6 Mon Sep 17 00:00:00 2001 From: Nils Adermann Date: Fri, 16 Aug 2013 15:14:38 +0200 Subject: [PATCH 17/26] Revert constructor arguments to old order for custom installers --- src/Composer/Plugin/PluginManager.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Composer/Plugin/PluginManager.php b/src/Composer/Plugin/PluginManager.php index 386de461b..cfbc804d6 100644 --- a/src/Composer/Plugin/PluginManager.php +++ b/src/Composer/Plugin/PluginManager.php @@ -175,7 +175,7 @@ class PluginManager } if ($oldInstallerPlugin) { - $installer = new $class($this->composer, $this->io); + $installer = new $class($this->io, $this->composer); $this->composer->getInstallationManager()->addInstaller($installer); } else { $plugin = new $class(); From 5867d477bee5d964e6e104a352b208a1e76090ac Mon Sep 17 00:00:00 2001 From: Nils Adermann Date: Fri, 30 Aug 2013 12:51:06 +0200 Subject: [PATCH 18/26] Use call_user_func for PHP < 5.4 compatability and accept __invoke --- src/Composer/EventDispatcher/EventDispatcher.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Composer/EventDispatcher/EventDispatcher.php b/src/Composer/EventDispatcher/EventDispatcher.php index acf8d2f2c..912d4eaea 100644 --- a/src/Composer/EventDispatcher/EventDispatcher.php +++ b/src/Composer/EventDispatcher/EventDispatcher.php @@ -119,8 +119,8 @@ class EventDispatcher $listeners = $this->getListeners($event); foreach ($listeners as $callable) { - if ((is_array($callable) && is_callable($callable)) || $callable instanceof Closure) { - $callable($event); + if (!is_string($callable) && is_callable($callable)) { + call_user_func($callable, $event); } elseif ($this->isPhpScript($callable)) { $className = substr($callable, 0, strpos($callable, '::')); $methodName = substr($callable, strpos($callable, '::') + 2); From c5c180fdd236adb67d8c905991026f393ece32c0 Mon Sep 17 00:00:00 2001 From: Nils Adermann Date: Fri, 30 Aug 2013 14:02:34 +0200 Subject: [PATCH 19/26] Load plugins from global vendor dir too --- src/Composer/Factory.php | 24 +++++++++++++++++++++-- src/Composer/Plugin/PluginManager.php | 28 ++++++++++++++++++++------- 2 files changed, 43 insertions(+), 9 deletions(-) diff --git a/src/Composer/Factory.php b/src/Composer/Factory.php index 28b00fd49..18b4a57f4 100644 --- a/src/Composer/Factory.php +++ b/src/Composer/Factory.php @@ -18,6 +18,7 @@ use Composer\IO\IOInterface; use Composer\Package\Archiver; use Composer\Repository\ComposerRepository; use Composer\Repository\RepositoryManager; +use Composer\Repository\RepositoryInterface; use Composer\Util\ProcessExecutor; use Composer\Util\RemoteFilesystem; use Symfony\Component\Console\Formatter\OutputFormatterStyle; @@ -252,6 +253,7 @@ class Factory $generator = new AutoloadGenerator($dispatcher); $composer->setAutoloadGenerator($generator); + $globalRepository = $this->createGlobalRepository($config, $vendorDir); $pm = $this->createPluginManager($composer, $io); $composer->setPluginManager($pm); @@ -306,6 +308,24 @@ class Factory $rm->setLocalRepository(new Repository\InstalledFilesystemRepository(new JsonFile($vendorDir.'/composer/installed.json'))); } + /** + * @param Config $config + * @param string $vendorDir + */ + protected function createGlobalRepository(Config $config, $vendorDir) + { + if ($config->get('home') == $vendorDir) { + return null; + } + + $path = $config->get('home').'/vendor/composer/installed.json'; + if (!file_exists($path)) { + return null; + } + + return new Repository\InstalledFilesystemRepository(new JsonFile($path)); + } + /** * @param IO\IOInterface $io * @param Config $config @@ -367,9 +387,9 @@ class Factory /** * @return Plugin\PluginManager */ - protected function createPluginManager(Composer $composer, IOInterface $io) + protected function createPluginManager(Composer $composer, IOInterface $io, RepositoryInterface $globalRepository = null) { - return new Plugin\PluginManager($composer, $io); + return new Plugin\PluginManager($composer, $io, $globalRepository); } /** diff --git a/src/Composer/Plugin/PluginManager.php b/src/Composer/Plugin/PluginManager.php index cfbc804d6..0a4cd5583 100644 --- a/src/Composer/Plugin/PluginManager.php +++ b/src/Composer/Plugin/PluginManager.php @@ -14,8 +14,9 @@ namespace Composer\Plugin; use Composer\Composer; use Composer\EventDispatcher\EventSubscriberInterface; -use Composer\Package\Package; use Composer\IO\IOInterface; +use Composer\Package\Package; +use Composer\Repository\RepositoryInterface; use Composer\Package\PackageInterface; use Composer\Package\Link; use Composer\DependencyResolver\Pool; @@ -29,6 +30,7 @@ class PluginManager { protected $composer; protected $io; + protected $globalRepository; protected $plugins = array(); @@ -39,10 +41,11 @@ class PluginManager * * @param Composer $composer */ - public function __construct(Composer $composer, IOInterface $io) + public function __construct(Composer $composer, IOInterface $io, RepositoryInterface $globalRepository = null) { $this->composer = $composer; $this->io = $io; + $this->globalRepository = $globalRepository; } /** @@ -53,11 +56,10 @@ class PluginManager $repo = $this->composer->getRepositoryManager()->getLocalRepository(); if ($repo) { - foreach ($repo->getPackages() as $package) { - if ('composer-plugin' === $package->getType() || 'composer-installer' === $package->getType()) { - $this->registerPackage($package); - } - } + $this->loadRepository($repo); + } + if ($this->globalRepository) { + $this->loadRepository($this->globalRepository); } } @@ -86,6 +88,15 @@ class PluginManager return $this->plugins; } + protected function loadRepository(RepositoryInterface $repo) + { + foreach ($repo->getPackages() as $package) { + if ('composer-plugin' === $package->getType() || 'composer-installer' === $package->getType()) { + $this->registerPackage($package); + } + } + } + /** * Recursively generates a map of package names to packages for all deps * @@ -150,6 +161,9 @@ class PluginManager $pool = new Pool('dev'); $pool->addRepository($this->composer->getRepositoryManager()->getLocalRepository()); + if ($this->globalRepository) { + $pool->addRepository($this->globalRepository); + } $autoloadPackages = array($package->getName() => $package); $autoloadPackages = $this->collectDependencies($pool, $autoloadPackages, $package); From 5993450d5a2cdef4a11c039f339a6fd652df7ca7 Mon Sep 17 00:00:00 2001 From: Nils Adermann Date: Fri, 30 Aug 2013 14:11:20 +0200 Subject: [PATCH 20/26] Load plugin code from global vendor dir correctly --- src/Composer/Factory.php | 2 +- src/Composer/Plugin/PluginManager.php | 21 +++++++++++++++------ 2 files changed, 16 insertions(+), 7 deletions(-) diff --git a/src/Composer/Factory.php b/src/Composer/Factory.php index 18b4a57f4..a3736300c 100644 --- a/src/Composer/Factory.php +++ b/src/Composer/Factory.php @@ -254,7 +254,7 @@ class Factory $composer->setAutoloadGenerator($generator); $globalRepository = $this->createGlobalRepository($config, $vendorDir); - $pm = $this->createPluginManager($composer, $io); + $pm = $this->createPluginManager($composer, $io, $globalRepository); $composer->setPluginManager($pm); // add installers to the manager diff --git a/src/Composer/Plugin/PluginManager.php b/src/Composer/Plugin/PluginManager.php index 0a4cd5583..f331337c0 100644 --- a/src/Composer/Plugin/PluginManager.php +++ b/src/Composer/Plugin/PluginManager.php @@ -160,7 +160,8 @@ class PluginManager $classes = is_array($extra['class']) ? $extra['class'] : array($extra['class']); $pool = new Pool('dev'); - $pool->addRepository($this->composer->getRepositoryManager()->getLocalRepository()); + $localRepo = $this->composer->getRepositoryManager()->getLocalRepository(); + $pool->addRepository($localRepo); if ($this->globalRepository) { $pool->addRepository($this->globalRepository); } @@ -171,7 +172,7 @@ class PluginManager $generator = $this->composer->getAutoloadGenerator(); $autoloads = array(); foreach ($autoloadPackages as $autoloadPackage) { - $downloadPath = $this->getInstallPath($autoloadPackage); + $downloadPath = $this->getInstallPath($autoloadPackage, !$localRepo->hasPackage($autoloadPackage)); $autoloads[] = array($autoloadPackage, $downloadPath); } @@ -202,13 +203,15 @@ class PluginManager * Retrieves the path a package is installed to. * * @param PackageInterface $package + * @param bool $global Whether this is a global package + * * @return string Install path */ - public function getInstallPath(PackageInterface $package) + public function getInstallPath(PackageInterface $package, $global = false) { $targetDir = $package->getTargetDir(); - return $this->getPackageBasePath($package) . ($targetDir ? '/'.$targetDir : ''); + return $this->getPackageBasePath($package, $global) . ($targetDir ? '/'.$targetDir : ''); } /** @@ -217,11 +220,17 @@ class PluginManager * Does not take targetDir into account. * * @param PackageInterface $package + * @param bool $global Whether this is a global package + * * @return string Base path */ - protected function getPackageBasePath(PackageInterface $package) + protected function getPackageBasePath(PackageInterface $package, $global = false) { - $vendorDir = rtrim($this->composer->getConfig()->get('vendor-dir'), '/'); + if ($global) { + $vendorDir = $this->composer->getConfig()->get('home').'/vendor'; + } else { + $vendorDir = rtrim($this->composer->getConfig()->get('vendor-dir'), '/'); + } return ($vendorDir ? $vendorDir.'/' : '') . $package->getPrettyName(); } } From bf080192929320ab5cb043dd3ee933a0b8c9d17c Mon Sep 17 00:00:00 2001 From: Nils Adermann Date: Sat, 31 Aug 2013 16:20:38 +0200 Subject: [PATCH 21/26] Load plugins and installers prior to checking installed packages --- src/Composer/Factory.php | 8 ++++---- src/Composer/Plugin/PluginManager.php | 2 +- tests/Composer/Test/Plugin/PluginInstallerTest.php | 1 + 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/src/Composer/Factory.php b/src/Composer/Factory.php index a3736300c..a1d17232d 100644 --- a/src/Composer/Factory.php +++ b/src/Composer/Factory.php @@ -260,6 +260,10 @@ class Factory // add installers to the manager $this->createDefaultInstallers($im, $composer, $io); + if (!$disablePlugins) { + $pm->loadInstalledPlugins(); + } + // purge packages if they have been deleted on the filesystem $this->purgePackages($rm, $im); @@ -272,10 +276,6 @@ class Factory $composer->setLocker($locker); } - if (!$disablePlugins) { - $pm->loadInstalledPlugins(); - } - return $composer; } diff --git a/src/Composer/Plugin/PluginManager.php b/src/Composer/Plugin/PluginManager.php index f331337c0..c67ea9622 100644 --- a/src/Composer/Plugin/PluginManager.php +++ b/src/Composer/Plugin/PluginManager.php @@ -172,7 +172,7 @@ class PluginManager $generator = $this->composer->getAutoloadGenerator(); $autoloads = array(); foreach ($autoloadPackages as $autoloadPackage) { - $downloadPath = $this->getInstallPath($autoloadPackage, !$localRepo->hasPackage($autoloadPackage)); + $downloadPath = $this->getInstallPath($autoloadPackage, ($this->globalRepository && $this->globalRepository->hasPackage($autoloadPackage))); $autoloads[] = array($autoloadPackage, $downloadPath); } diff --git a/tests/Composer/Test/Plugin/PluginInstallerTest.php b/tests/Composer/Test/Plugin/PluginInstallerTest.php index 5d21f59db..1e67eafe1 100644 --- a/tests/Composer/Test/Plugin/PluginInstallerTest.php +++ b/tests/Composer/Test/Plugin/PluginInstallerTest.php @@ -69,6 +69,7 @@ class PluginInstallerTest extends \PHPUnit_Framework_TestCase $config->merge(array( 'config' => array( 'vendor-dir' => __DIR__.'/Fixtures/', + 'home' => __DIR__.'/Fixtures', 'bin-dir' => __DIR__.'/Fixtures/bin', ), )); From d00ca4bcdb35ad5b0ee243244c7f17fd3b0981ca Mon Sep 17 00:00:00 2001 From: Nils Adermann Date: Thu, 5 Sep 2013 14:30:03 +0200 Subject: [PATCH 22/26] Add a Command event triggered by all comands which load plugins --- src/Composer/Command/DependsCommand.php | 9 +- src/Composer/Command/DiagnoseCommand.php | 5 ++ src/Composer/Command/DumpAutoloadCommand.php | 6 ++ src/Composer/Command/InstallCommand.php | 6 ++ src/Composer/Command/LicensesCommand.php | 6 ++ src/Composer/Command/RequireCommand.php | 6 ++ src/Composer/Command/SearchCommand.php | 7 ++ src/Composer/Command/ShowCommand.php | 7 ++ src/Composer/Command/StatusCommand.php | 6 ++ src/Composer/Command/UpdateCommand.php | 6 ++ src/Composer/Plugin/CommandEvent.php | 87 ++++++++++++++++++++ src/Composer/Plugin/PluginEvents.php | 10 +++ 12 files changed, 160 insertions(+), 1 deletion(-) create mode 100644 src/Composer/Plugin/CommandEvent.php diff --git a/src/Composer/Command/DependsCommand.php b/src/Composer/Command/DependsCommand.php index 5603a17c0..755b40b90 100644 --- a/src/Composer/Command/DependsCommand.php +++ b/src/Composer/Command/DependsCommand.php @@ -13,6 +13,8 @@ namespace Composer\Command; use Composer\DependencyResolver\Pool; +use Composer\Plugin\CommandEvent; +use Composer\Plugin\PluginEvents; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputOption; @@ -50,7 +52,12 @@ EOT protected function execute(InputInterface $input, OutputInterface $output) { - $repo = $this->getComposer()->getRepositoryManager()->getLocalRepository(); + $composer = $this->getComposer(); + + $commandEvent = new CommandEvent(PluginEvents::COMMAND, 'depends', $input, $output); + $composer->getEventDispatcher()->dispatch($commandEvent->getName(), $commandEvent); + + $repo = $composer->getRepositoryManager()->getLocalRepository(); $needle = $input->getArgument('package'); $pool = new Pool(); diff --git a/src/Composer/Command/DiagnoseCommand.php b/src/Composer/Command/DiagnoseCommand.php index 57ed3a003..63ce4ee19 100644 --- a/src/Composer/Command/DiagnoseCommand.php +++ b/src/Composer/Command/DiagnoseCommand.php @@ -15,6 +15,8 @@ namespace Composer\Command; use Composer\Composer; use Composer\Factory; use Composer\Downloader\TransportException; +use Composer\Plugin\CommandEvent; +use Composer\Plugin\PluginEvents; use Composer\Util\ConfigValidator; use Composer\Util\RemoteFilesystem; use Composer\Util\StreamContextFactory; @@ -64,6 +66,9 @@ EOT $composer = $this->getComposer(false); if ($composer) { + $commandEvent = new CommandEvent(PluginEvents::COMMAND, 'diagnose', $input, $output); + $composer->getEventDispatcher()->dispatch($commandEvent->getName(), $commandEvent); + $output->write('Checking composer.json: '); $this->outputResult($output, $this->checkComposerSchema()); } diff --git a/src/Composer/Command/DumpAutoloadCommand.php b/src/Composer/Command/DumpAutoloadCommand.php index 4855a409d..3e1541590 100644 --- a/src/Composer/Command/DumpAutoloadCommand.php +++ b/src/Composer/Command/DumpAutoloadCommand.php @@ -12,6 +12,8 @@ namespace Composer\Command; +use Composer\Plugin\CommandEvent; +use Composer\Plugin\PluginEvents; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Output\OutputInterface; @@ -42,6 +44,10 @@ EOT $output->writeln('Generating autoload files'); $composer = $this->getComposer(); + + $commandEvent = new CommandEvent(PluginEvents::COMMAND, 'dump-autoload', $input, $output); + $composer->getEventDispatcher()->dispatch($commandEvent->getName(), $commandEvent); + $installationManager = $composer->getInstallationManager(); $localRepo = $composer->getRepositoryManager()->getLocalRepository(); $package = $composer->getPackage(); diff --git a/src/Composer/Command/InstallCommand.php b/src/Composer/Command/InstallCommand.php index adc2ca595..6138009a3 100644 --- a/src/Composer/Command/InstallCommand.php +++ b/src/Composer/Command/InstallCommand.php @@ -13,6 +13,8 @@ namespace Composer\Command; use Composer\Installer; +use Composer\Plugin\CommandEvent; +use Composer\Plugin\PluginEvents; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Output\OutputInterface; @@ -66,6 +68,10 @@ EOT $composer = $this->getComposer(true, $input->getOption('no-plugins')); $composer->getDownloadManager()->setOutputProgress(!$input->getOption('no-progress')); $io = $this->getIO(); + + $commandEvent = new CommandEvent(PluginEvents::COMMAND, 'install', $input, $output); + $composer->getEventDispatcher()->dispatch($commandEvent->getName(), $commandEvent); + $install = Installer::create($io, $composer); $preferSource = false; diff --git a/src/Composer/Command/LicensesCommand.php b/src/Composer/Command/LicensesCommand.php index e30e371c2..a927156c4 100644 --- a/src/Composer/Command/LicensesCommand.php +++ b/src/Composer/Command/LicensesCommand.php @@ -15,6 +15,8 @@ namespace Composer\Command; use Composer\Package\PackageInterface; use Composer\Json\JsonFile; use Composer\Package\Version\VersionParser; +use Composer\Plugin\CommandEvent; +use Composer\Plugin\PluginEvents; use Symfony\Component\Console\Helper\TableHelper; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputArgument; @@ -46,6 +48,10 @@ EOT protected function execute(InputInterface $input, OutputInterface $output) { $composer = $this->getComposer(); + + $commandEvent = new CommandEvent(PluginEvents::COMMAND, 'licenses', $input, $output); + $composer->getEventDispatcher()->dispatch($commandEvent->getName(), $commandEvent); + $root = $composer->getPackage(); $repo = $composer->getRepositoryManager()->getLocalRepository(); diff --git a/src/Composer/Command/RequireCommand.php b/src/Composer/Command/RequireCommand.php index fb4f9a9b1..11951dd08 100644 --- a/src/Composer/Command/RequireCommand.php +++ b/src/Composer/Command/RequireCommand.php @@ -21,6 +21,8 @@ use Composer\Installer; use Composer\Json\JsonFile; use Composer\Json\JsonManipulator; use Composer\Package\Version\VersionParser; +use Composer\Plugin\CommandEvent; +use Composer\Plugin\PluginEvents; /** * @author Jérémy Romey @@ -106,6 +108,10 @@ EOT $composer = $this->getComposer(); $composer->getDownloadManager()->setOutputProgress(!$input->getOption('no-progress')); $io = $this->getIO(); + + $commandEvent = new CommandEvent(PluginEvents::COMMAND, 'require', $input, $output); + $composer->getEventDispatcher()->dispatch($commandEvent->getName(), $commandEvent); + $install = Installer::create($io, $composer); $install diff --git a/src/Composer/Command/SearchCommand.php b/src/Composer/Command/SearchCommand.php index a212eb329..b9aaa8d74 100644 --- a/src/Composer/Command/SearchCommand.php +++ b/src/Composer/Command/SearchCommand.php @@ -20,6 +20,8 @@ use Composer\Repository\CompositeRepository; use Composer\Repository\PlatformRepository; use Composer\Repository\RepositoryInterface; use Composer\Factory; +use Composer\Plugin\CommandEvent; +use Composer\Plugin\PluginEvents; /** * @author Robert Schönthal @@ -65,6 +67,11 @@ EOT $repos = new CompositeRepository(array_merge(array($installedRepo), $defaultRepos)); } + if ($composer) { + $commandEvent = new CommandEvent(PluginEvents::COMMAND, 'search', $input, $output); + $composer->getEventDispatcher()->dispatch($commandEvent->getName(), $commandEvent); + } + $onlyName = $input->getOption('only-name'); $flags = $onlyName ? RepositoryInterface::SEARCH_NAME : RepositoryInterface::SEARCH_FULLTEXT; diff --git a/src/Composer/Command/ShowCommand.php b/src/Composer/Command/ShowCommand.php index a54de99c7..50dabd74a 100644 --- a/src/Composer/Command/ShowCommand.php +++ b/src/Composer/Command/ShowCommand.php @@ -18,6 +18,8 @@ use Composer\DependencyResolver\DefaultPolicy; use Composer\Factory; use Composer\Package\CompletePackageInterface; use Composer\Package\Version\VersionParser; +use Composer\Plugin\CommandEvent; +use Composer\Plugin\PluginEvents; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputOption; @@ -94,6 +96,11 @@ EOT $repos = new CompositeRepository(array_merge(array($installedRepo), $defaultRepos)); } + if ($composer) { + $commandEvent = new CommandEvent(PluginEvents::COMMAND, 'show', $input, $output); + $composer->getEventDispatcher()->dispatch($commandEvent->getName(), $commandEvent); + } + // show single package or single version if ($input->getArgument('package') || !empty($package)) { $versions = array(); diff --git a/src/Composer/Command/StatusCommand.php b/src/Composer/Command/StatusCommand.php index 5151d734b..5edf3b61e 100644 --- a/src/Composer/Command/StatusCommand.php +++ b/src/Composer/Command/StatusCommand.php @@ -17,6 +17,8 @@ use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Output\OutputInterface; use Composer\Downloader\ChangeReportInterface; use Composer\Downloader\VcsDownloader; +use Composer\Plugin\CommandEvent; +use Composer\Plugin\PluginEvents; use Composer\Script\ScriptEvents; /** @@ -46,6 +48,10 @@ EOT { // init repos $composer = $this->getComposer(); + + $commandEvent = new CommandEvent(PluginEvents::COMMAND, 'status', $input, $output); + $composer->getEventDispatcher()->dispatch($commandEvent->getName(), $commandEvent); + $installedRepo = $composer->getRepositoryManager()->getLocalRepository(); $dm = $composer->getDownloadManager(); diff --git a/src/Composer/Command/UpdateCommand.php b/src/Composer/Command/UpdateCommand.php index 728fadd24..ceabf7ff4 100644 --- a/src/Composer/Command/UpdateCommand.php +++ b/src/Composer/Command/UpdateCommand.php @@ -13,6 +13,8 @@ namespace Composer\Command; use Composer\Installer; +use Composer\Plugin\CommandEvent; +use Composer\Plugin\PluginEvents; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Input\InputArgument; @@ -70,6 +72,10 @@ EOT $composer = $this->getComposer(true, $input->getOption('no-plugins')); $composer->getDownloadManager()->setOutputProgress(!$input->getOption('no-progress')); $io = $this->getIO(); + + $commandEvent = new CommandEvent(PluginEvents::COMMAND, 'update', $input, $output); + $composer->getEventDispatcher()->dispatch($commandEvent->getName(), $commandEvent); + $install = Installer::create($io, $composer); $preferSource = false; diff --git a/src/Composer/Plugin/CommandEvent.php b/src/Composer/Plugin/CommandEvent.php new file mode 100644 index 000000000..ac2ad2551 --- /dev/null +++ b/src/Composer/Plugin/CommandEvent.php @@ -0,0 +1,87 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Plugin; + +use Composer\IO\IOInterface; +use Composer\EventDispatcher\Event; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Output\OutputInterface; + +/** + * An event for all commands. + * + * @author Nils Adermann + */ +class CommandEvent extends Event +{ + /** + * @var string + */ + private $commandName; + + /** + * @var InputInterface + */ + private $input; + + /** + * @var OutputInterface + */ + private $output; + + /** + * Constructor. + * + * @param string $name The event name + * @param string $commandName The command name + * @param InputInterface $input + * @param OutputInterface $output + */ + public function __construct($name, $commandName, $input, $output) + { + parent::__construct($name); + $this->commandName = $commandName; + $this->input = $input; + $this->output = $output; + } + + /** + * Returns the command input interface + * + * @return InputInterface + */ + public function getInput() + { + return $this->input; + } + + /** + * Retrieves the command output interface + * + * @return OutputInterface + */ + public function getOutput() + { + return $this->output; + } + + /** + * Retrieves the name of the command being run + * + * @return string + */ + public function getCommandName() + { + return $this->commandName; + } +} diff --git a/src/Composer/Plugin/PluginEvents.php b/src/Composer/Plugin/PluginEvents.php index cbf9b1148..ce9efdef2 100644 --- a/src/Composer/Plugin/PluginEvents.php +++ b/src/Composer/Plugin/PluginEvents.php @@ -19,6 +19,16 @@ namespace Composer\Plugin; */ class PluginEvents { + /** + * The COMMAND event occurs as a command begins + * + * The event listener method receives a + * Composer\Plugin\CommandEvent instance. + * + * @var string + */ + const COMMAND = 'command'; + /** * The PRE_FILE_DOWNLOAD event occurs before downloading a file * From 6c2e998e4029cb4a9d6ec433a5b9975cc5e6e8b6 Mon Sep 17 00:00:00 2001 From: Nils Adermann Date: Thu, 5 Sep 2013 14:32:09 +0200 Subject: [PATCH 23/26] Add missing use statement --- tests/Composer/Test/Plugin/PluginInstallerTest.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/Composer/Test/Plugin/PluginInstallerTest.php b/tests/Composer/Test/Plugin/PluginInstallerTest.php index 1e67eafe1..1c2a4cf31 100644 --- a/tests/Composer/Test/Plugin/PluginInstallerTest.php +++ b/tests/Composer/Test/Plugin/PluginInstallerTest.php @@ -18,6 +18,7 @@ use Composer\Installer\PluginInstaller; use Composer\Package\Loader\JsonLoader; use Composer\Package\Loader\ArrayLoader; use Composer\Package\PackageInterface; +use Composer\Plugin\PluginManager; use Composer\Autoload\AutoloadGenerator; class PluginInstallerTest extends \PHPUnit_Framework_TestCase @@ -63,7 +64,7 @@ class PluginInstallerTest extends \PHPUnit_Framework_TestCase $this->composer->setRepositoryManager($rm); $this->composer->setAutoloadGenerator($this->autoloadGenerator); - $this->pm = new \Composer\Plugin\PluginManager($this->composer, $this->io); + $this->pm = new PluginManager($this->composer, $this->io); $this->composer->setPluginManager($this->pm); $config->merge(array( From 92b1ee2f7ad1c4c823acd96b37dd1986f00bdf02 Mon Sep 17 00:00:00 2001 From: Nils Adermann Date: Thu, 5 Sep 2013 15:47:05 +0200 Subject: [PATCH 24/26] Add a composer-plugin-api platform package and plugins must require it --- src/Composer/Plugin/PluginInterface.php | 7 ++++++ src/Composer/Plugin/PluginManager.php | 23 +++++++++++++++++++ .../Repository/PlatformRepository.php | 8 +++++++ 3 files changed, 38 insertions(+) diff --git a/src/Composer/Plugin/PluginInterface.php b/src/Composer/Plugin/PluginInterface.php index 302175a25..3a33672a6 100644 --- a/src/Composer/Plugin/PluginInterface.php +++ b/src/Composer/Plugin/PluginInterface.php @@ -22,6 +22,13 @@ use Composer\IO\IOInterface; */ interface PluginInterface { + /** + * Version number of the fake composer-plugin-api package + * + * @var string + */ + const PLUGIN_API_VERSION = '1.0.0'; + /** * Apply plugin modifications to composer * diff --git a/src/Composer/Plugin/PluginManager.php b/src/Composer/Plugin/PluginManager.php index c67ea9622..dacf9bd57 100644 --- a/src/Composer/Plugin/PluginManager.php +++ b/src/Composer/Plugin/PluginManager.php @@ -16,9 +16,11 @@ use Composer\Composer; use Composer\EventDispatcher\EventSubscriberInterface; use Composer\IO\IOInterface; use Composer\Package\Package; +use Composer\Package\Version\VersionParser; use Composer\Repository\RepositoryInterface; use Composer\Package\PackageInterface; use Composer\Package\Link; +use Composer\Package\LinkConstraint\VersionConstraint; use Composer\DependencyResolver\Pool; /** @@ -31,6 +33,7 @@ class PluginManager protected $composer; protected $io; protected $globalRepository; + protected $versionParser; protected $plugins = array(); @@ -46,6 +49,7 @@ class PluginManager $this->composer = $composer; $this->io = $io; $this->globalRepository = $globalRepository; + $this->versionParser = new VersionParser(); } /** @@ -92,6 +96,25 @@ class PluginManager { foreach ($repo->getPackages() as $package) { if ('composer-plugin' === $package->getType() || 'composer-installer' === $package->getType()) { + $requiresComposer = null; + foreach ($package->getRequires() as $link) { + if ($link->getTarget() == 'composer-plugin-api') { + $requiresComposer = $link->getConstraint(); + } + } + + if (!$requiresComposer) { + throw new \RuntimeException("Plugin ".$package->getName()." is missing a require statement for a version of the composer-plugin-api package."); + } + + if (!$requiresComposer->matches(new VersionConstraint('==', $this->versionParser->normalize(PluginInterface::PLUGIN_API_VERSION)))) { + $this->io->write("The plugin ".$package->getName()." requires a version of composer-plugin-api that does not match your composer installation. You may need to run composer update with the '--no-plugins' option."); + } + + $this->registerPackage($package); + } + // Backward compatability + if ('composer-installer' === $package->getType()) { $this->registerPackage($package); } } diff --git a/src/Composer/Repository/PlatformRepository.php b/src/Composer/Repository/PlatformRepository.php index 3f208aae0..7c4b72673 100644 --- a/src/Composer/Repository/PlatformRepository.php +++ b/src/Composer/Repository/PlatformRepository.php @@ -14,6 +14,7 @@ namespace Composer\Repository; use Composer\Package\CompletePackage; use Composer\Package\Version\VersionParser; +use Composer\Plugin\PluginInterface; /** * @author Jordi Boggiano @@ -28,6 +29,12 @@ class PlatformRepository extends ArrayRepository $versionParser = new VersionParser(); + $prettyVersion = PluginInterface::PLUGIN_API_VERSION; + $version = $versionParser->normalize($prettyVersion); + $composerPluginApi = new CompletePackage('composer-plugin-api', $version, $prettyVersion); + $composerPluginApi->setDescription('The Composer Plugin API'); + parent::addPackage($composerPluginApi); + try { $prettyVersion = PHP_VERSION; $version = $versionParser->normalize($prettyVersion); @@ -36,6 +43,7 @@ class PlatformRepository extends ArrayRepository $version = $versionParser->normalize($prettyVersion); } + $php = new CompletePackage('php', $version, $prettyVersion); $php->setDescription('The PHP interpreter'); parent::addPackage($php); From 98e5eabf759baf103e657e6e098ff48a0f3b55a7 Mon Sep 17 00:00:00 2001 From: Nils Adermann Date: Thu, 5 Sep 2013 20:08:17 +0200 Subject: [PATCH 25/26] Document how to write and use plugins --- doc/articles/custom-installers.md | 70 +++++++++----- doc/articles/plugins.md | 147 ++++++++++++++++++++++++++++++ 2 files changed, 194 insertions(+), 23 deletions(-) create mode 100644 doc/articles/plugins.md diff --git a/doc/articles/custom-installers.md b/doc/articles/custom-installers.md index 1eb55436e..7c7117248 100644 --- a/doc/articles/custom-installers.md +++ b/doc/articles/custom-installers.md @@ -29,8 +29,8 @@ An example use-case would be: > phpDocumentor features Templates that need to be installed outside of the > default /vendor folder structure. As such they have chosen to adopt the -> `phpdocumentor-template` [type][1] and create a Custom Installer to send -> these templates to the correct folder. +> `phpdocumentor-template` [type][1] and create a plugin providing the Custom +> Installer to send these templates to the correct folder. An example composer.json of such a template package would be: @@ -38,23 +38,24 @@ An example composer.json of such a template package would be: "name": "phpdocumentor/template-responsive", "type": "phpdocumentor-template", "require": { - "phpdocumentor/template-installer": "*" + "phpdocumentor/template-installer-plugin": "*" } } > **IMPORTANT**: to make sure that the template installer is present at the > time the template package is installed, template packages should require -> the installer package. +> the plugin package. ## Creating an Installer A Custom Installer is defined as a class that implements the -[`Composer\Installer\InstallerInterface`][3] and is contained in a Composer -package that has the [type][1] `composer-installer`. +[`Composer\Installer\InstallerInterface`][3] and is usually distributed in a +Composer Plugin. -A basic Installer would thus compose of two files: +A basic Installer Plugin would thus compose of three files: 1. the package file: composer.json +2. The Plugin class, e.g.: `My\Project\Composer\Plugin.php`, containing a class that implements `Composer\Plugin\PluginInterface`. 2. The Installer class, e.g.: `My\Project\Composer\Installer.php`, containing a class that implements `Composer\Installer\InstallerInterface`. ### composer.json @@ -62,35 +63,57 @@ A basic Installer would thus compose of two files: The package file is the same as any other package file but with the following requirements: -1. the [type][1] attribute must be `composer-installer`. +1. the [type][1] attribute must be `composer-plugin`. 2. the [extra][2] attribute must contain an element `class` defining the - class name of the installer (including namespace). If a package contains - multiple installers this can be array of class names. + class name of the plugin (including namespace). If a package contains + multiple plugins this can be array of class names. Example: { - "name": "phpdocumentor/template-installer", - "type": "composer-installer", + "name": "phpdocumentor/template-installer-plugin", + "type": "composer-installer-plugin", "license": "MIT", "autoload": { "psr-0": {"phpDocumentor\\Composer": "src/"} }, "extra": { - "class": "phpDocumentor\\Composer\\TemplateInstaller" + "class": "phpDocumentor\\Composer\\TemplateInstallerPlugin" + } + } + +### The Plugin class + +The class defining the Composer plugin must implement the +[`Composer\Plugin\PluginInterface`][3]. It can then register the Custom +Installer in its `activate()` method. + +The class may be placed in any location and have any name, as long as it is +autoloadable and matches the `extra.class` element in the package definition. + +Example: + + namespace phpDocumentor\Composer; + + use Composer\Composer; + use Composer\IO\IOInterface; + use Composer\Plugin\PluginInterface + + class TemplateInstallerPlugin implements PluginInterface + { + public function activate(Composer $composer, IOInterface $io) + { + $installer = new TemplateInstaller($io, $composer); + $composer->getInstallationManager()->addInstaller($installer); } } ### The Custom Installer class The class that executes the custom installation should implement the -[`Composer\Installer\InstallerInterface`][3] (or extend another installer that -implements that interface). - -The class may be placed in any location and have any name, as long as it is -autoloadable and matches the `extra.class` element in the package definition. -It will also define the [type][1] string as it will be recognized by packages -that will use this installer in the `supports()` method. +[`Composer\Installer\InstallerInterface`][4] (or extend another installer that +implements that interface). It defines the [type][1] string as it will be +recognized by packages that will use this installer in the `supports()` method. > **NOTE**: _choose your [type][1] name carefully, it is recommended to follow > the format: `vendor-type`_. For example: `phpdocumentor-template`. @@ -146,7 +169,7 @@ Example: } The example demonstrates that it is quite simple to extend the -[`Composer\Installer\LibraryInstaller`][4] class to strip a prefix +[`Composer\Installer\LibraryInstaller`][5] class to strip a prefix (`phpdocumentor/template-`) and use the remaining part to assemble a completely different installation path. @@ -155,5 +178,6 @@ different installation path. [1]: ../04-schema.md#type [2]: ../04-schema.md#extra -[3]: https://github.com/composer/composer/blob/master/src/Composer/Installer/InstallerInterface.php -[4]: https://github.com/composer/composer/blob/master/src/Composer/Installer/LibraryInstaller.php +[3]: https://github.com/composer/composer/blob/master/src/Composer/Plugin/PluginInterface.php +[4]: https://github.com/composer/composer/blob/master/src/Composer/Installer/InstallerInterface.php +[5]: https://github.com/composer/composer/blob/master/src/Composer/Installer/LibraryInstaller.php diff --git a/doc/articles/plugins.md b/doc/articles/plugins.md new file mode 100644 index 000000000..57296f8fe --- /dev/null +++ b/doc/articles/plugins.md @@ -0,0 +1,147 @@ + + +# Setting up and using plugins + +## Synopsis + +You may wish to alter or expand Composer's functionality with your own. For +example if your environment poses special requirements on the behaviour of +Composer which do not apply to the majority of its users or if you wish to +accomplish something with composer in a way that is not desired by most users. + +In these cases you could consider creating a plugin to handle your +specific logic. + +## Creating a Plugin + +A plugin is a regular composer package which ships its code as part of the +package and may also depend on further packages. + +### Plugin Package + +The package file is the same as any other package file but with the following +requirements: + +1. the [type][1] attribute must be `composer-plugin`. +2. the [extra][2] attribute must contain an element `class` defining the + class name of the plugin (including namespace). If a package contains + multiple plugins this can be array of class names. + +Additionally you must require the special package called `composer-plugin-api` +to define which composer API versions your plugin is compatible with. The +current composer plugin API version is 1.0.0. + +For example + + { + "name": "my/plugin-package", + "type": "composer-plugin", + "require": { + "composer-plugin-api": "1.0.0" + } + } + +### Plugin Class + +Every plugin has to supply a class which implements the +[`Composer\Plugin\PluginInterface`][3]. The `activate()` method of the plugin +is called after the plugin is loaded and receives an instance of +[`Composer\Composer`][4] as well as an instance of +[`Composer\IO\IOInterface`][5]. Using these two objects all configuration can +be read and all internal objects and state can be manipulated as desired. + +Example: + + namespace phpDocumentor\Composer; + + use Composer\Composer; + use Composer\IO\IOInterface; + use Composer\Plugin\PluginInterface + + class TemplateInstallerPlugin implements PluginInterface + { + public function activate(Composer $composer, IOInterface $io) + { + $installer = new TemplateInstaller($io, $composer); + $composer->getInstallationManager()->addInstaller($installer); + } + } + +## Event Handler + +Furthermore plugins may implement the +[`Composer\EventDispatcher\EventSubscriberInterface`][6] in order to have its +event handlers automatically registered with the `EventDispatcher` when the +plugin is loaded. + +The events available for plugins are: + +* **COMMAND**, is called at the beginning of all commands that load plugins. + It provides you with access to the input and output objects of the program. +* **PRE_FILE_DOWNLOAD**, is triggered before files are downloaded and allows + you to manipulate the `RemoteFilesystem` object prior to downloading files + based on the URL to be downloaded. + +Example: + + namespace Naderman\Composer\AWS; + + use Composer\Composer; + use Composer\EventDispatcher\EventSubscriberInterface; + use Composer\IO\IOInterface; + use Composer\Plugin\PluginInterface; + use Composer\Plugin\PluginEvents; + use Composer\Plugin\PreFileDownloadEvent; + + class AwsPlugin implements PluginInterface, EventSubscriberInterface + { + protected $composer; + protected $io; + + public function activate(Composer $composer, IOInterface $io) + { + $this->composer = $composer; + $this->io = $io; + } + + public static function getSubscribedEvents() + { + return array( + PluginEvents::PRE_FILE_DOWNLOAD => array( + array('onPreFileDownload', 0) + ), + ); + } + + public function onPreFileDownload(PreFileDownloadEvent $event) + { + $protocol = parse_url($event->getProcessedUrl(), PHP_URL_SCHEME); + + if ($protocol === 's3') { + $awsClient = new AwsClient($this->io, $this->composer->getConfig()); + $s3RemoteFilesystem = new S3RemoteFilesystem($this->io, $event->getRemoteFilesystem()->getOptions(), $awsClient); + $event->setRemoteFilesystem($s3RemoteFilesystem); + } + } + } + +## Using Plugins + +Plugin packages are automatically loaded as soon as they are installed and will +be loaded when composer starts up if they are found in the current project's +list of installed packages. Additionally all plugin packages installed in the +`COMPOSER_HOME` directory using the composer global command are loaded before +local project plugins are loaded. + +> You may pass the `--no-plugins` option to composer commands to disable all +> installed commands. This may be particularly helpful if any of the plugins +> causes errors and you wish to update or uninstall it. + +[1]: ../04-schema.md#type +[2]: ../04-schema.md#extra +[3]: https://github.com/composer/composer/blob/master/src/Composer/Plugin/PluginInterface.php +[4]: https://github.com/composer/composer/blob/master/src/Composer/Composer.php +[5]: https://github.com/composer/composer/blob/master/src/Composer/IO/IOInterface.php +[6]: https://github.com/composer/composer/blob/master/src/Composer/EventDispatcher/EventSubscriberInterface.php From f249fd804fee4958d25d2f6428419dfc90be6a6d Mon Sep 17 00:00:00 2001 From: Nils Adermann Date: Fri, 6 Sep 2013 13:36:02 +0200 Subject: [PATCH 26/26] Correctly require composer plugin api version in test plugins --- tests/Composer/Test/Plugin/Fixtures/plugin-v1/composer.json | 3 +++ tests/Composer/Test/Plugin/Fixtures/plugin-v2/composer.json | 3 +++ tests/Composer/Test/Plugin/Fixtures/plugin-v3/composer.json | 3 +++ tests/Composer/Test/Plugin/Fixtures/plugin-v4/composer.json | 3 +++ 4 files changed, 12 insertions(+) diff --git a/tests/Composer/Test/Plugin/Fixtures/plugin-v1/composer.json b/tests/Composer/Test/Plugin/Fixtures/plugin-v1/composer.json index 996e5ee3e..efc552956 100644 --- a/tests/Composer/Test/Plugin/Fixtures/plugin-v1/composer.json +++ b/tests/Composer/Test/Plugin/Fixtures/plugin-v1/composer.json @@ -5,5 +5,8 @@ "autoload": { "psr-0": { "Installer": "" } }, "extra": { "class": "Installer\\Plugin" + }, + "require": { + "composer-plugin-api": "1.0.0" } } diff --git a/tests/Composer/Test/Plugin/Fixtures/plugin-v2/composer.json b/tests/Composer/Test/Plugin/Fixtures/plugin-v2/composer.json index c099da413..6947ddd5c 100644 --- a/tests/Composer/Test/Plugin/Fixtures/plugin-v2/composer.json +++ b/tests/Composer/Test/Plugin/Fixtures/plugin-v2/composer.json @@ -5,5 +5,8 @@ "autoload": { "psr-0": { "Installer": "" } }, "extra": { "class": "Installer\\Plugin2" + }, + "require": { + "composer-plugin-api": "1.0.0" } } diff --git a/tests/Composer/Test/Plugin/Fixtures/plugin-v3/composer.json b/tests/Composer/Test/Plugin/Fixtures/plugin-v3/composer.json index 3ba04e6f6..5cb01d019 100644 --- a/tests/Composer/Test/Plugin/Fixtures/plugin-v3/composer.json +++ b/tests/Composer/Test/Plugin/Fixtures/plugin-v3/composer.json @@ -5,5 +5,8 @@ "autoload": { "psr-0": { "Installer": "" } }, "extra": { "class": "Installer\\Plugin2" + }, + "require": { + "composer-plugin-api": "1.0.0" } } diff --git a/tests/Composer/Test/Plugin/Fixtures/plugin-v4/composer.json b/tests/Composer/Test/Plugin/Fixtures/plugin-v4/composer.json index 10387a021..982d34c7b 100644 --- a/tests/Composer/Test/Plugin/Fixtures/plugin-v4/composer.json +++ b/tests/Composer/Test/Plugin/Fixtures/plugin-v4/composer.json @@ -8,5 +8,8 @@ "Installer\\Plugin1", "Installer\\Plugin2" ] + }, + "require": { + "composer-plugin-api": "1.0.0" } }