diff --git a/core/lib/Drupal/Component/Plugin/Factory/DefaultFactory.php b/core/lib/Drupal/Component/Plugin/Factory/DefaultFactory.php index d049814..cc64d17 100644 --- a/core/lib/Drupal/Component/Plugin/Factory/DefaultFactory.php +++ b/core/lib/Drupal/Component/Plugin/Factory/DefaultFactory.php @@ -31,6 +31,13 @@ class DefaultFactory implements FactoryInterface { protected $discovery; /** + * Stores the interface the plugin should have. + * + * @var string + */ + protected $interface; + + /** * Constructs a Drupal\Component\Plugin\Factory\DefaultFactory object. */ public function __construct(DiscoveryInterface $discovery) { @@ -38,15 +45,53 @@ public function __construct(DiscoveryInterface $discovery) { } /** + * Set the interface used for the plugin. + * + * @param string $interface + * The interface the plugin should have. + * + * @throws \Drupal\Component\Plugin\Exception\PluginException + * Thrown when the interface does not exist. + */ + public function setInterface($interface) { + if (!interface_exists($interface)) { + throw new PluginException('Invalid/non existing interface passed to setInterface'); + } + $this->interface = $interface; + } + + /** * Implements Drupal\Component\Plugin\Factory\FactoryInterface::createInstance(). */ public function createInstance($plugin_id, array $configuration) { $plugin_definition = $this->discovery->getDefinition($plugin_id); $plugin_class = static::getPluginClass($plugin_id, $plugin_definition); + $this->validateInterface($plugin_class, $plugin_id); + return new $plugin_class($configuration, $plugin_id, $plugin_definition); } /** + * Validates the plugin class against the set interface. + * + * @param string $plugin_class + * The class of the plugin to validate. + * @param string $plugin_id + * The plugin ID of the plugin to validate. + * + * @throws \Drupal\Component\Plugin\Exception\PluginException + * Thrown when the plugin class does not match with the set interface. + */ + protected function validateInterface($plugin_class, $plugin_id) { + // Validate the interface of the class. + if (isset($this->interface)) { + if (!is_subclass_of($plugin_class, $this->interface)) { + throw new PluginException(sprintf('Plugin (%s) instance class "%s" has to implement interface %s', $plugin_id, $plugin_class, $this->interface)); + } + } + } + + /** * Finds the class relevant for a given plugin. * * @param string $plugin_id diff --git a/core/lib/Drupal/Component/Plugin/Factory/ReflectionFactory.php b/core/lib/Drupal/Component/Plugin/Factory/ReflectionFactory.php index 01f300e..9196310 100644 --- a/core/lib/Drupal/Component/Plugin/Factory/ReflectionFactory.php +++ b/core/lib/Drupal/Component/Plugin/Factory/ReflectionFactory.php @@ -6,6 +6,7 @@ namespace Drupal\Component\Plugin\Factory; +use Drupal\Component\Plugin\Exception\PluginException; use ReflectionClass; /** @@ -22,6 +23,7 @@ class ReflectionFactory extends DefaultFactory { public function createInstance($plugin_id, array $configuration) { $plugin_definition = $this->discovery->getDefinition($plugin_id); $plugin_class = static::getPluginClass($plugin_id, $plugin_definition); + $this->validateInterface($plugin_class, $plugin_id); // Lets figure out of there's a constructor for this class and pull // arguments from the $options array if so to populate it. diff --git a/core/lib/Drupal/Core/Plugin/Factory/ContainerFactory.php b/core/lib/Drupal/Core/Plugin/Factory/ContainerFactory.php index 0990565..5fcaab0 100644 --- a/core/lib/Drupal/Core/Plugin/Factory/ContainerFactory.php +++ b/core/lib/Drupal/Core/Plugin/Factory/ContainerFactory.php @@ -6,6 +6,7 @@ namespace Drupal\Core\Plugin\Factory; +use Drupal\Component\Plugin\Exception\PluginException; use Drupal\Component\Plugin\Factory\DefaultFactory; /** @@ -19,11 +20,7 @@ class ContainerFactory extends DefaultFactory { public function createInstance($plugin_id, array $configuration) { $plugin_definition = $this->discovery->getDefinition($plugin_id); $plugin_class = static::getPluginClass($plugin_id, $plugin_definition); - - // If the plugin provides a factory method, pass the container to it. - if (is_subclass_of($plugin_class, 'Drupal\Core\Plugin\ContainerFactoryPluginInterface')) { - return $plugin_class::create(\Drupal::getContainer(), $configuration, $plugin_id, $plugin_definition); - } + $this->validateInterface($plugin_class, $plugin_id); // Otherwise, create the plugin directly. return new $plugin_class($configuration, $plugin_id, $plugin_definition); diff --git a/core/modules/field/lib/Drupal/field/Plugin/Type/Widget/WidgetFactory.php b/core/modules/field/lib/Drupal/field/Plugin/Type/Widget/WidgetFactory.php index 4b7cb6b..94d9682 100644 --- a/core/modules/field/lib/Drupal/field/Plugin/Type/Widget/WidgetFactory.php +++ b/core/modules/field/lib/Drupal/field/Plugin/Type/Widget/WidgetFactory.php @@ -7,6 +7,7 @@ namespace Drupal\field\Plugin\Type\Widget; +use Drupal\Component\Plugin\Exception\PluginException; use Drupal\Component\Plugin\Factory\DefaultFactory; /** @@ -20,6 +21,8 @@ class WidgetFactory extends DefaultFactory { public function createInstance($plugin_id, array $configuration) { $plugin_definition = $this->discovery->getDefinition($plugin_id); $plugin_class = static::getPluginClass($plugin_id, $plugin_definition); + $this->validateInterface($plugin_class, $plugin_id); + return new $plugin_class($plugin_id, $plugin_definition, $configuration['field_definition'], $configuration['settings']); } } diff --git a/core/modules/system/tests/modules/plugin_test/lib/Drupal/plugin_test/Plugin/plugin_test/fruit/Apple.php b/core/modules/system/tests/modules/plugin_test/lib/Drupal/plugin_test/Plugin/plugin_test/fruit/Apple.php index fd3fb2f..6a59327 100644 --- a/core/modules/system/tests/modules/plugin_test/lib/Drupal/plugin_test/Plugin/plugin_test/fruit/Apple.php +++ b/core/modules/system/tests/modules/plugin_test/lib/Drupal/plugin_test/Plugin/plugin_test/fruit/Apple.php @@ -16,5 +16,7 @@ * color = "green" * ) */ -class Apple {} +class Apple implements FruitInterface { + +} diff --git a/core/modules/system/tests/modules/plugin_test/lib/Drupal/plugin_test/Plugin/plugin_test/fruit/Banana.php b/core/modules/system/tests/modules/plugin_test/lib/Drupal/plugin_test/Plugin/plugin_test/fruit/Banana.php index 55f1d12..71f5bb1 100644 --- a/core/modules/system/tests/modules/plugin_test/lib/Drupal/plugin_test/Plugin/plugin_test/fruit/Banana.php +++ b/core/modules/system/tests/modules/plugin_test/lib/Drupal/plugin_test/Plugin/plugin_test/fruit/Banana.php @@ -20,6 +20,6 @@ * } * ) */ -class Banana { +class Banana implements FruitInterface { } diff --git a/core/modules/system/tests/modules/plugin_test/lib/Drupal/plugin_test/Plugin/plugin_test/fruit/FruitInterface.php b/core/modules/system/tests/modules/plugin_test/lib/Drupal/plugin_test/Plugin/plugin_test/fruit/FruitInterface.php new file mode 100644 index 0000000..a55efc7 --- /dev/null +++ b/core/modules/system/tests/modules/plugin_test/lib/Drupal/plugin_test/Plugin/plugin_test/fruit/FruitInterface.php @@ -0,0 +1,15 @@ + 'Default plugin factory', + 'description' => 'Tests the default plugin factory.', + 'group' => 'Plugin', + ); + } + + /** + * {@inheritdoc} + */ + protected function setUp() { + $this->discovery = $this->getMock('Drupal\Component\Plugin\Discovery\DiscoveryInterface'); + $this->defaultFactory = new DefaultFactory($this->discovery); + } + + /** + * Tests the createInstance method. + * + * @see \Drupal\Component\Plugin\DefaultFactory::getPluginClass + * @see \Drupal\Component\Plugin\DefaultFactory::createInstance + * + * @dataProvider providerTestCreateInstance + */ + public function testCreateInstance($plugin_id, $plugin_definition) { + $this->discovery->expects($this->once()) + ->method('getDefinition') + ->with($plugin_id) + ->will($this->returnValue($plugin_definition)); + $this->defaultFactory->setInterface('\Drupal\plugin_test\Plugin\plugin_test\fruit\FruitInterface'); + $instance = $this->defaultFactory->createInstance($plugin_id, array()); + + $this->assertInstanceOf($plugin_definition['class'], $instance); + $this->assertInstanceOf('\Drupal\plugin_test\Plugin\plugin_test\fruit\FruitInterface', $instance); + } + + /** + * Provides plugin_id/plugin_definition which are valid. + * + * @return array + * An array of plugin IDs and plugin definitions. + * + * @see self::testCreateInstanceWithInvalidInput + */ + public function providerTestCreateInstance() { + return array( + array('apple', array('class' => '\Drupal\plugin_test\Plugin\plugin_test\fruit\Apple')), + array('banana', array('class' => '\Drupal\plugin_test\Plugin\plugin_test\fruit\Banana')), + ); + } + + /** + * Tests the getPluginClass method with invalid input. + * + * @expectedException \Drupal\Component\Plugin\Exception\PluginException + * @dataProvider providerTestCreateInstanceWithInvalidInput + */ + public function testCreateInstanceWithInvalidInput($plugin_id, $plugin_definition) { + $this->discovery->expects($this->once()) + ->method('getDefinition') + ->with($plugin_id) + ->will($this->returnValue($plugin_definition)); + $this->defaultFactory->setInterface('\Drupal\plugin_test\Plugin\plugin_test\fruit\FruitInterface'); + $this->defaultFactory->createInstance($plugin_id, array()); + } + + /** + * Provides plugin_id/plugin_definitions which are invalid. + * + * @return array + * An array of plugin IDs and plugin definitions. + * + * @see self::testCreateInstanceWithInvalidInput + */ + public function providerTestCreateInstanceWithInvalidInput() { + return array( + // Provide no class definition. + array('rasperry', array()), + // Provide a non existing class. + array('rasperry', array('class' => '\Drupal\plugin_test\Plugin\plugin_test\fruit\Rasperry')), + // Provide a plugin without FruitInterface. + array('orange', array('class' => '\Drupal\plugin_test\Plugin\plugin_test\fruit\Orange')), + ); + } + + /** + * Tests the setInterface method with an invalid interface.. + * + * @expectedException \Drupal\Component\Plugin\Exception\PluginException + */ + public function testSetInterface() { + $this->defaultFactory->setInterface('\Drupal\plugin_test\Plugin\plugin_test\fruit\VegetablesInterface'); + } + +}