Browse Source

Merge branch '1.1'

Jordi Boggiano 8 years ago
parent
commit
b8974a3e13

+ 81 - 0
doc/articles/plugins.md

@@ -183,6 +183,84 @@ class AwsPlugin implements PluginInterface, EventSubscriberInterface
 }
 ```
 
+## Plugin capabilities
+
+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
+<?php
+
+namespace My\Composer;
+
+use Composer\Composer;
+use Composer\IO\IOInterface;
+use Composer\Plugin\PluginInterface;
+use Composer\Plugin\Capable;
+
+class Plugin implements PluginInterface, Capable
+{
+    public function activate(Composer $composer, IOInterface $io)
+    {
+    }
+
+    public function getCapabilities()
+    {
+        return array(
+            'Composer\Plugin\Capability\CommandProvider' => 'My\Composer\CommandProvider',
+        );
+    }
+}
+```
+
+### Command provider
+
+The [`Composer\Plugin\Capability\CommandProvider`][9] capability allows to register
+additional commands for Composer :
+
+```php
+<?php
+
+namespace My\Composer;
+
+use Composer\Plugin\Capability\CommandProvider as CommandProviderCapability;
+use Symfony\Component\Console\Input\InputInterface;
+use Symfony\Component\Console\Output\OutputInterface;
+use Composer\Command\BaseCommand;
+
+class CommandProvider implements CommandProviderCapability
+{
+    public function getCommands()
+    {
+        return array(new Command);
+    }
+}
+
+class Command extends BaseCommand
+{
+    protected function configure()
+    {
+        $this->setName('custom-plugin-command');
+    }
+
+    protected function execute(InputInterface $input, OutputInterface $output)
+    {
+        $output->writeln('Executing');
+    }
+}
+```
+
+Now the `custom-plugin-command` is available alongside Composer commands.
+
+> _Composer commands are based on the [Symfony Console Component][10]._
+
 ## Using Plugins
 
 Plugin packages are automatically loaded as soon as they are installed and will
@@ -202,3 +280,6 @@ 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
+[10]: http://symfony.com/doc/current/components/console/introduction.html

+ 12 - 0
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
      */

+ 8 - 0
src/Composer/Command/GlobalCommand.php

@@ -80,4 +80,12 @@ EOT
 
         return $this->getApplication()->run($input, $output);
     }
+
+    /**
+     * {@inheritDoc}
+     */
+    public function isProxyCommand()
+    {
+        return true;
+    }
 }

+ 8 - 0
src/Composer/Command/OutdatedCommand.php

@@ -71,4 +71,12 @@ EOT
 
         return $this->getApplication()->run($input, $output);
     }
+
+    /**
+     * {@inheritDoc}
+     */
+    public function isProxyCommand()
+    {
+        return true;
+    }
 }

+ 24 - 12
src/Composer/Console/Application.php

@@ -56,6 +56,8 @@ class Application extends BaseApplication
                     /_/
 ';
 
+    private $hasPluginCommands = false;
+
     public function __construct()
     {
         static $shutdownRegistered = false;
@@ -108,7 +110,7 @@ class Application extends BaseApplication
         $io = $this->io = new ConsoleIO($input, $output, $this->getHelperSet());
         ErrorHandler::register($io);
 
-        // determine command name to be executed
+        // determine command name to be executed without including plugin commands
         $commandName = '';
         if ($name = $this->getCommandName($input)) {
             try {
@@ -117,7 +119,27 @@ class Application extends BaseApplication
             }
         }
 
-        $isProxyCommand = $commandName === 'global' || $commandName === 'outdated';
+        if (!$input->hasParameterOption('--no-plugins') && !$this->hasPluginCommands && 'global' !== $commandName) {
+            foreach ($this->getPluginCommands() as $command) {
+                if ($this->has($command->getName())) {
+                    $io->writeError('<warning>Plugin command '.$command->getName().' ('.get_class($command).') would override a Composer command and has been skipped</warning>');
+                } else {
+                    $this->add($command);
+                }
+            }
+            $this->hasPluginCommands = true;
+        }
+
+        // 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 {
+                $command = $this->find($name);
+                $commandName = $command->getName();
+                $isProxyCommand = ($command instanceof Command\BaseCommand && $command->isProxyCommand());
+            } catch (\InvalidArgumentException $e) {
+            }
+        }
 
         if (!$isProxyCommand) {
             $io->writeError(sprintf(
@@ -197,16 +219,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('<warning>Plugin command '.$command->getName().' ('.get_class($command).') would override a Composer command and has been skipped</warning>');
-                    } else {
-                        $this->add($command);
-                    }
-                }
-            }
-
             $result = parent::doRun($input, $output);
 
             if (isset($oldWorkingDir)) {

+ 7 - 2
tests/Composer/Test/ApplicationTest.php

@@ -25,7 +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->any())
             ->method('getFirstArgument')
             ->will($this->returnValue('list'));
 
@@ -68,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));