From 81f33e9739abd03ece2f57f6d54c1624226f552c Mon Sep 17 00:00:00 2001 From: Bilal Amarni Date: Mon, 2 May 2016 09:19:40 +0200 Subject: [PATCH 1/5] document plugin capabilities (closes #5262) --- doc/articles/plugins.md | 72 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 72 insertions(+) diff --git a/doc/articles/plugins.md b/doc/articles/plugins.md index b4997d22d..9af6103c7 100644 --- a/doc/articles/plugins.md +++ b/doc/articles/plugins.md @@ -183,6 +183,76 @@ class AwsPlugin implements PluginInterface, EventSubscriberInterface } ``` + +## Plugin capabilities + +Composer defines a standard set of capabilities which may be implemented by plugins +through the [`Composer\Plugin\Capable`][8] interface. + +### Command provider + +The [`Composer\Plugin\Capability\CommandProvider`][9] capability allows to register +additional commands for Composer : + +```php +setName('custom-plugin-command'); + } + + protected function execute(InputInterface $input, OutputInterface $output) + { + $output->writeln('Executing'); + } +} +``` + +Once the plugin exposes this capability, `custom-plugin-command` is +available alongside Composer commands : + +```php + 'My\Composer\CommandProvider', + ); + } +} +``` + ## Using Plugins Plugin packages are automatically loaded as soon as they are installed and will @@ -202,3 +272,5 @@ local project plugins are loaded. [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 [7]: ../01-basic-usage.md#package-versions +[8]: https://github.com/composer/composer/blob/master/src/Composer/Plugin/Capable.php +[9]: https://github.com/composer/composer/blob/master/src/Composer/Plugin/Capability/CommandProvider.php From c1bc50dd2acca9351803abd5fbf1af86b1874dfd Mon Sep 17 00:00:00 2001 From: Bilal Amarni Date: Mon, 2 May 2016 12:50:25 +0200 Subject: [PATCH 2/5] add missing interface --- doc/articles/plugins.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/doc/articles/plugins.md b/doc/articles/plugins.md index 9af6103c7..e7ecf97e2 100644 --- a/doc/articles/plugins.md +++ b/doc/articles/plugins.md @@ -237,8 +237,9 @@ namespace My\Composer; use Composer\Composer; use Composer\IO\IOInterface; use Composer\Plugin\PluginInterface; +use Composer\Plugin\Capable; -class Plugin implements PluginInterface +class Plugin implements PluginInterface, Capable { public function activate(Composer $composer, IOInterface $io) { From 253a30793d4a77354f5259f424adbcd87c757d88 Mon Sep 17 00:00:00 2001 From: Bilal Amarni Date: Tue, 3 May 2016 08:22:54 +0200 Subject: [PATCH 3/5] updates --- doc/articles/plugins.md | 66 +++++++++++++++++++++++------------------ 1 file changed, 37 insertions(+), 29 deletions(-) diff --git a/doc/articles/plugins.md b/doc/articles/plugins.md index e7ecf97e2..bf72aa235 100644 --- a/doc/articles/plugins.md +++ b/doc/articles/plugins.md @@ -183,11 +183,42 @@ class AwsPlugin implements PluginInterface, EventSubscriberInterface } ``` - ## Plugin capabilities -Composer defines a standard set of capabilities which may be implemented by plugins -through the [`Composer\Plugin\Capable`][8] interface. +Composer defines a standard set of capabilities which may be implemented by plugins. +Their goal is to make the plugin ecosystem more stable as it reduces the need to mess +with [`Composer\Composer`][4]'s internal state, by providing explicit extension points +for common plugin requirements. + +Capable Plugins classes must implement the [`Composer\Plugin\Capable`][8] interface +and declare their capabilities in the `getCapabilities()` method. +This method must return an array, with the _key_ as a Composer Capability class name, +and the _value_ as the Plugin's own implementation class name of said Capability: + +```php + 'My\Composer\CommandProvider', + ); + } +} +``` ### Command provider @@ -226,33 +257,9 @@ class Command extends BaseCommand } ``` -Once the plugin exposes this capability, `custom-plugin-command` is -available alongside Composer commands : - -```php - 'My\Composer\CommandProvider', - ); - } -} -``` +> _Composer commands are based on the [Symfony Console Component][10]._ ## Using Plugins @@ -275,3 +282,4 @@ local project plugins are loaded. [7]: ../01-basic-usage.md#package-versions [8]: https://github.com/composer/composer/blob/master/src/Composer/Plugin/Capable.php [9]: https://github.com/composer/composer/blob/master/src/Composer/Plugin/Capability/CommandProvider.php +[10]: http://symfony.com/doc/current/components/console/introduction.html From 92207da83ad1fac1b43e710cf6cd519d16b54f36 Mon Sep 17 00:00:00 2001 From: Bilal Amarni Date: Tue, 3 May 2016 08:26:14 +0200 Subject: [PATCH 4/5] add isProxyCommand() to BaseCommand --- src/Composer/Command/BaseCommand.php | 12 +++++++++ src/Composer/Command/GlobalCommand.php | 8 ++++++ src/Composer/Command/OutdatedCommand.php | 8 ++++++ src/Composer/Console/Application.php | 32 +++++++++++++----------- tests/Composer/Test/ApplicationTest.php | 5 ++++ 5 files changed, 51 insertions(+), 14 deletions(-) diff --git a/src/Composer/Command/BaseCommand.php b/src/Composer/Command/BaseCommand.php index 2a70b584d..365733d85 100644 --- a/src/Composer/Command/BaseCommand.php +++ b/src/Composer/Command/BaseCommand.php @@ -79,6 +79,18 @@ abstract class BaseCommand extends Command $this->getApplication()->resetComposer(); } + /** + * Whether or not this command is meant to call another command. + * + * This is mainly needed to avoid duplicated warnings messages. + * + * @return bool + */ + public function isProxyCommand() + { + return false; + } + /** * @return IOInterface */ diff --git a/src/Composer/Command/GlobalCommand.php b/src/Composer/Command/GlobalCommand.php index 9aa48706b..beeca5b77 100644 --- a/src/Composer/Command/GlobalCommand.php +++ b/src/Composer/Command/GlobalCommand.php @@ -80,4 +80,12 @@ EOT return $this->getApplication()->run($input, $output); } + + /** + * {@inheritDoc} + */ + public function isProxyCommand() + { + return true; + } } diff --git a/src/Composer/Command/OutdatedCommand.php b/src/Composer/Command/OutdatedCommand.php index d89434951..b3cf566df 100644 --- a/src/Composer/Command/OutdatedCommand.php +++ b/src/Composer/Command/OutdatedCommand.php @@ -71,4 +71,12 @@ EOT return $this->getApplication()->run($input, $output); } + + /** + * {@inheritDoc} + */ + public function isProxyCommand() + { + return true; + } } diff --git a/src/Composer/Console/Application.php b/src/Composer/Console/Application.php index 60ac50508..a03327df3 100644 --- a/src/Composer/Console/Application.php +++ b/src/Composer/Console/Application.php @@ -55,6 +55,8 @@ class Application extends BaseApplication /_/ '; + private $hasPluginCommands = false; + public function __construct() { static $shutdownRegistered = false; @@ -107,17 +109,29 @@ class Application extends BaseApplication $io = $this->io = new ConsoleIO($input, $output, $this->getHelperSet()); ErrorHandler::register($io); - // determine command name to be executed + if (!$input->hasParameterOption('--no-plugins') && !$this->hasPluginCommands) { + foreach ($this->getPluginCommands() as $command) { + if ($this->has($command->getName())) { + $io->writeError('Plugin command '.$command->getName().' ('.get_class($command).') would override a Composer command and has been skipped'); + } else { + $this->add($command); + } + } + $this->hasPluginCommands = true; + } + + // determine command name to be executed, and if it's a proxy command $commandName = ''; + $isProxyCommand = false; if ($name = $this->getCommandName($input)) { try { - $commandName = $this->find($name)->getName(); + $command = $this->find($name); + $commandName = $command->getName(); + $isProxyCommand = ($command instanceof Command\BaseCommand && $command->isProxyCommand()); } catch (\InvalidArgumentException $e) { } } - $isProxyCommand = $commandName === 'global' || $commandName === 'outdated'; - if (!$isProxyCommand) { $io->writeError(sprintf( 'Running %s (%s) with %s on %s', @@ -196,16 +210,6 @@ class Application extends BaseApplication $this->io->enableDebugging($startTime); } - if (!$input->hasParameterOption('--no-plugins') && !$isProxyCommand) { - foreach ($this->getPluginCommands() as $command) { - if ($this->has($command->getName())) { - $io->writeError('Plugin command '.$command->getName().' ('.get_class($command).') would override a Composer command and has been skipped'); - } else { - $this->add($command); - } - } - } - $result = parent::doRun($input, $output); if (isset($oldWorkingDir)) { diff --git a/tests/Composer/Test/ApplicationTest.php b/tests/Composer/Test/ApplicationTest.php index 4e843bfa6..823c2b4d1 100644 --- a/tests/Composer/Test/ApplicationTest.php +++ b/tests/Composer/Test/ApplicationTest.php @@ -25,6 +25,11 @@ class ApplicationTest extends TestCase $inputMock = $this->getMock('Symfony\Component\Console\Input\InputInterface'); $outputMock = $this->getMock('Symfony\Component\Console\Output\OutputInterface'); + $inputMock->expects($this->once()) + ->method('hasParameterOption') + ->with($this->equalTo('--no-plugins')) + ->will($this->returnValue(true)); + $inputMock->expects($this->once()) ->method('getFirstArgument') ->will($this->returnValue('list')); From 591cbcee12546ec1b86efa36779b7da99295d285 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Thu, 5 May 2016 14:06:04 +0100 Subject: [PATCH 5/5] Avoiding defining plugin commands using the local project plugins, refs #5277 --- src/Composer/Console/Application.php | 14 +++++++++++--- tests/Composer/Test/ApplicationTest.php | 6 +++--- 2 files changed, 14 insertions(+), 6 deletions(-) diff --git a/src/Composer/Console/Application.php b/src/Composer/Console/Application.php index a03327df3..c6b9bd377 100644 --- a/src/Composer/Console/Application.php +++ b/src/Composer/Console/Application.php @@ -109,7 +109,16 @@ class Application extends BaseApplication $io = $this->io = new ConsoleIO($input, $output, $this->getHelperSet()); ErrorHandler::register($io); - if (!$input->hasParameterOption('--no-plugins') && !$this->hasPluginCommands) { + // determine command name to be executed without including plugin commands + $commandName = ''; + if ($name = $this->getCommandName($input)) { + try { + $commandName = $this->find($name)->getName(); + } catch (\InvalidArgumentException $e) { + } + } + + if (!$input->hasParameterOption('--no-plugins') && !$this->hasPluginCommands && 'global' !== $commandName) { foreach ($this->getPluginCommands() as $command) { if ($this->has($command->getName())) { $io->writeError('Plugin command '.$command->getName().' ('.get_class($command).') would override a Composer command and has been skipped'); @@ -120,8 +129,7 @@ class Application extends BaseApplication $this->hasPluginCommands = true; } - // determine command name to be executed, and if it's a proxy command - $commandName = ''; + // determine command name to be executed incl plugin commands, and check if it's a proxy command $isProxyCommand = false; if ($name = $this->getCommandName($input)) { try { diff --git a/tests/Composer/Test/ApplicationTest.php b/tests/Composer/Test/ApplicationTest.php index 823c2b4d1..b977b78e0 100644 --- a/tests/Composer/Test/ApplicationTest.php +++ b/tests/Composer/Test/ApplicationTest.php @@ -25,12 +25,12 @@ class ApplicationTest extends TestCase $inputMock = $this->getMock('Symfony\Component\Console\Input\InputInterface'); $outputMock = $this->getMock('Symfony\Component\Console\Output\OutputInterface'); - $inputMock->expects($this->once()) + $inputMock->expects($this->any()) ->method('hasParameterOption') ->with($this->equalTo('--no-plugins')) ->will($this->returnValue(true)); - $inputMock->expects($this->once()) + $inputMock->expects($this->any()) ->method('getFirstArgument') ->will($this->returnValue('list')); @@ -73,7 +73,7 @@ class ApplicationTest extends TestCase $inputMock = $this->getMock('Symfony\Component\Console\Input\InputInterface'); $outputMock = $this->getMock('Symfony\Component\Console\Output\OutputInterface'); - $inputMock->expects($this->once()) + $inputMock->expects($this->any()) ->method('getFirstArgument') ->will($this->returnValue($command));