Added Capable plugins for a more future-proof Plugin API
Plugins can now present their capabilities to the PluginManager, through which it can act accordingly, thus making Plugin API more flexible, BC-friendly and decoupled.pull/4124/head
parent
7d7b3ccb2a
commit
2051d74774
|
@ -0,0 +1,24 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of Composer.
|
||||||
|
*
|
||||||
|
* (c) Nils Adermann <naderman@naderman.de>
|
||||||
|
* Jordi Boggiano <j.boggiano@seld.be>
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view the LICENSE
|
||||||
|
* file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Composer\Plugin\Capability;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Marker interface for Plugin capabilities.
|
||||||
|
* Every new Capability which is added to the Plugin API must implement this interface.
|
||||||
|
*
|
||||||
|
* @api
|
||||||
|
* @since Plugin API 1.1
|
||||||
|
*/
|
||||||
|
interface Capability
|
||||||
|
{
|
||||||
|
}
|
|
@ -0,0 +1,48 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of Composer.
|
||||||
|
*
|
||||||
|
* (c) Nils Adermann <naderman@naderman.de>
|
||||||
|
* Jordi Boggiano <j.boggiano@seld.be>
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view the LICENSE
|
||||||
|
* file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Composer\Plugin;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Plugins which need to expose various implementations
|
||||||
|
* of the Composer Plugin Capabilities must have their
|
||||||
|
* declared Plugin class implementing this interface.
|
||||||
|
*
|
||||||
|
* @api
|
||||||
|
* @since Plugin API 1.1
|
||||||
|
*/
|
||||||
|
interface Capable
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Method by which a Plugin announces its API implementations, through an array
|
||||||
|
* with a special structure.
|
||||||
|
*
|
||||||
|
* The key must be a string, representing a fully qualified class/interface name
|
||||||
|
* which Composer Plugin API exposes - named "API class".
|
||||||
|
* The value must be a string as well, representing the fully qualified class name
|
||||||
|
* of the API class - named "SPI class".
|
||||||
|
*
|
||||||
|
* Every SPI must implement their API class.
|
||||||
|
*
|
||||||
|
* Every SPI will be passed a single array parameter via their constructor.
|
||||||
|
*
|
||||||
|
* Example:
|
||||||
|
* // API as key, SPI as value
|
||||||
|
* return array(
|
||||||
|
* 'Composer\Plugin\Capability\CommandProvider' => 'My\CommandProvider',
|
||||||
|
* 'Composer\Plugin\Capability\Validator' => 'My\Validator',
|
||||||
|
* );
|
||||||
|
*
|
||||||
|
* @return string[]
|
||||||
|
*/
|
||||||
|
public function getCapabilities();
|
||||||
|
}
|
|
@ -23,14 +23,14 @@ use Composer\IO\IOInterface;
|
||||||
interface PluginInterface
|
interface PluginInterface
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* Version number of the fake composer-plugin-api package
|
* Version number of the internal composer-plugin-api package
|
||||||
*
|
*
|
||||||
* @var string
|
* @var string
|
||||||
*/
|
*/
|
||||||
const PLUGIN_API_VERSION = '1.0.0';
|
const PLUGIN_API_VERSION = '1.1.0';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Apply plugin modifications to composer
|
* Apply plugin modifications to Composer
|
||||||
*
|
*
|
||||||
* @param Composer $composer
|
* @param Composer $composer
|
||||||
* @param IOInterface $io
|
* @param IOInterface $io
|
||||||
|
|
|
@ -23,6 +23,7 @@ use Composer\Package\PackageInterface;
|
||||||
use Composer\Package\Link;
|
use Composer\Package\Link;
|
||||||
use Composer\Semver\Constraint\Constraint;
|
use Composer\Semver\Constraint\Constraint;
|
||||||
use Composer\DependencyResolver\Pool;
|
use Composer\DependencyResolver\Pool;
|
||||||
|
use Composer\Plugin\Capability\Capability;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Plugin manager
|
* Plugin manager
|
||||||
|
@ -185,16 +186,6 @@ class PluginManager
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the version of the internal composer-plugin-api package.
|
|
||||||
*
|
|
||||||
* @return string
|
|
||||||
*/
|
|
||||||
protected function getPluginApiVersion()
|
|
||||||
{
|
|
||||||
return PluginInterface::PLUGIN_API_VERSION;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Adds a plugin, activates it and registers it with the event dispatcher
|
* Adds a plugin, activates it and registers it with the event dispatcher
|
||||||
*
|
*
|
||||||
|
@ -299,4 +290,57 @@ class PluginManager
|
||||||
|
|
||||||
return $this->globalComposer->getInstallationManager()->getInstallPath($package);
|
return $this->globalComposer->getInstallationManager()->getInstallPath($package);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the version of the internal composer-plugin-api package.
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
protected function getPluginApiVersion()
|
||||||
|
{
|
||||||
|
return PluginInterface::PLUGIN_API_VERSION;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param PluginInterface $plugin
|
||||||
|
* @param string $capability
|
||||||
|
* @return bool|string The fully qualified class of the implementation or false if none was provided
|
||||||
|
*/
|
||||||
|
protected function getCapabilityImplementationClassName(PluginInterface $plugin, $capability)
|
||||||
|
{
|
||||||
|
if (!($plugin instanceof Capable)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
$capabilities = (array) $plugin->getCapabilities();
|
||||||
|
|
||||||
|
if (empty($capabilities[$capability]) || !is_string($capabilities[$capability])) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return trim($capabilities[$capability]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param PluginInterface $plugin
|
||||||
|
* @param string $capability The fully qualified name of the API interface which the plugin may provide
|
||||||
|
* an implementation.
|
||||||
|
* @param array $ctorArgs Arguments passed to Capability's constructor.
|
||||||
|
* Keeping it an array will allow future values to be passed w\o changing the signature.
|
||||||
|
* @return Capability|boolean Bool false if the Plugin has no implementation of the requested Capability.
|
||||||
|
*/
|
||||||
|
public function getPluginCapability(PluginInterface $plugin, $capability, array $ctorArgs = array())
|
||||||
|
{
|
||||||
|
if ($capabilityClass = $this->getCapabilityImplementationClassName($plugin, $capability)) {
|
||||||
|
if (class_exists($capabilityClass)) {
|
||||||
|
$capabilityObj = new $capabilityClass($ctorArgs);
|
||||||
|
if ($capabilityObj instanceof Capability &&
|
||||||
|
$capabilityObj instanceof $capability
|
||||||
|
) {
|
||||||
|
return $capabilityObj;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,23 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of Composer.
|
||||||
|
*
|
||||||
|
* (c) Nils Adermann <naderman@naderman.de>
|
||||||
|
* Jordi Boggiano <j.boggiano@seld.be>
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view the LICENSE
|
||||||
|
* file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Composer\Test\Plugin\Mock;
|
||||||
|
|
||||||
|
class Capability implements \Composer\Plugin\Capability\Capability
|
||||||
|
{
|
||||||
|
public $args;
|
||||||
|
|
||||||
|
public function __construct(array $args)
|
||||||
|
{
|
||||||
|
$this->args = $args;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,20 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of Composer.
|
||||||
|
*
|
||||||
|
* (c) Nils Adermann <naderman@naderman.de>
|
||||||
|
* Jordi Boggiano <j.boggiano@seld.be>
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view the LICENSE
|
||||||
|
* file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Composer\Test\Plugin\Mock;
|
||||||
|
|
||||||
|
use Composer\Plugin\Capable;
|
||||||
|
use Composer\Plugin\PluginInterface;
|
||||||
|
|
||||||
|
interface CapablePluginInterface extends PluginInterface, Capable
|
||||||
|
{
|
||||||
|
}
|
|
@ -314,4 +314,67 @@ class PluginInstallerTest extends TestCase
|
||||||
$this->setPluginApiVersionWithPlugins('5.5.0', $pluginWithApiConstraint);
|
$this->setPluginApiVersionWithPlugins('5.5.0', $pluginWithApiConstraint);
|
||||||
$this->assertCount(0, $this->pm->getPlugins());
|
$this->assertCount(0, $this->pm->getPlugins());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function testIncapablePluginIsCorrectlyDetected()
|
||||||
|
{
|
||||||
|
$plugin = $this->getMockBuilder('Composer\Plugin\PluginInterface')
|
||||||
|
->getMock();
|
||||||
|
|
||||||
|
$this->assertFalse($this->pm->getPluginCapability($plugin, 'Fake\Ability'));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testCapabilityImplementsComposerPluginApiClassAndIsConstructedWithArgs()
|
||||||
|
{
|
||||||
|
$capabilityApi = 'Composer\Plugin\Capability\Capability';
|
||||||
|
$capabilitySpi = 'Composer\Test\Plugin\Mock\Capability';
|
||||||
|
|
||||||
|
$plugin = $this->getMockBuilder('Composer\Test\Plugin\Mock\CapablePluginInterface')
|
||||||
|
->getMock();
|
||||||
|
|
||||||
|
$plugin->expects($this->once())
|
||||||
|
->method('getCapabilities')
|
||||||
|
->will($this->returnCallback(function() use ($capabilitySpi, $capabilityApi) {
|
||||||
|
return array($capabilityApi => $capabilitySpi);
|
||||||
|
}));
|
||||||
|
|
||||||
|
$capability = $this->pm->getPluginCapability($plugin, $capabilityApi, array('a' => 1, 'b' => 2));
|
||||||
|
|
||||||
|
$this->assertInstanceOf($capabilityApi, $capability);
|
||||||
|
$this->assertInstanceOf($capabilitySpi, $capability);
|
||||||
|
$this->assertSame(array('a' => 1, 'b' => 2), $capability->args);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function invalidSpiValues()
|
||||||
|
{
|
||||||
|
return array(
|
||||||
|
array(null),
|
||||||
|
array(""),
|
||||||
|
array(0),
|
||||||
|
array(1000),
|
||||||
|
array(" "),
|
||||||
|
array(array(1)),
|
||||||
|
array(array()),
|
||||||
|
array(new \stdClass()),
|
||||||
|
array("NonExistentClassLikeMiddleClass"),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @dataProvider invalidSpiValues
|
||||||
|
*/
|
||||||
|
public function testInvalidCapabilitySpiDeclarationsAreDisregarded($invalidSpi)
|
||||||
|
{
|
||||||
|
$capabilityApi = 'Composer\Plugin\Capability\Capability';
|
||||||
|
|
||||||
|
$plugin = $this->getMockBuilder('Composer\Test\Plugin\Mock\CapablePluginInterface')
|
||||||
|
->getMock();
|
||||||
|
|
||||||
|
$plugin->expects($this->once())
|
||||||
|
->method('getCapabilities')
|
||||||
|
->will($this->returnCallback(function() use ($invalidSpi, $capabilityApi) {
|
||||||
|
return array($capabilityApi => $invalidSpi);
|
||||||
|
}));
|
||||||
|
|
||||||
|
$this->assertFalse($this->pm->getPluginCapability($plugin, $capabilityApi));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue