diff --git a/core/lib/Drupal/Core/Plugin/DefaultPluginManager.php b/core/lib/Drupal/Core/Plugin/DefaultPluginManager.php
index e1d0731..c9d4680 100644
--- a/core/lib/Drupal/Core/Plugin/DefaultPluginManager.php
+++ b/core/lib/Drupal/Core/Plugin/DefaultPluginManager.php
@@ -9,6 +9,7 @@
use Drupal\Component\Plugin\Discovery\CachedDiscoveryInterface;
use Drupal\Component\Plugin\Discovery\DiscoveryCachedTrait;
+use Drupal\Component\Plugin\Exception\PluginException;
use Drupal\Component\Plugin\Exception\PluginNotFoundException;
use Drupal\Core\Plugin\Discovery\ContainerDerivativeDiscoveryDecorator;
use Drupal\Component\Plugin\PluginManagerBase;
@@ -20,6 +21,7 @@
use Drupal\Core\Language\LanguageManagerInterface;
use Drupal\Core\Plugin\Discovery\AnnotatedClassDiscovery;
use Drupal\Core\Plugin\Factory\ContainerFactory;
+use \Symfony\Component\Debug\Exception\FlattenException;
/**
* Base class for plugin managers.
@@ -83,6 +85,13 @@ class DefaultPluginManager extends PluginManagerBase implements PluginManagerInt
protected $defaults = array();
/**
+ * Defines a fallback ID in case the plugin instance could not be created.
+ *
+ * @var string
+ */
+ protected $fallbackPluginId;
+
+ /**
* Creates the discovery object.
*
* @param string|bool $subdir
@@ -198,7 +207,6 @@ protected function setCachedDefinitions($definitions) {
$this->definitions = $definitions;
}
-
/**
* Performs extra processing on plugin definitions.
*
@@ -212,6 +220,7 @@ public function processDefinition(&$definition, $plugin_id) {
}
}
+
/**
* Finds plugin definitions.
*
@@ -241,4 +250,50 @@ protected function findDefinitions() {
return $definitions;
}
+ /**
+ * {@inheritdoc}
+ */
+ public function createInstance($plugin_id, array $configuration = array()) {
+ try {
+ $instance = $this->factory->createInstance($plugin_id, $configuration);
+ }
+ catch (PluginException $exception) {
+ if ($fallback_plugin_id = $this->getFallbackPluginId($plugin_id, $configuration)) {
+ // Allow implementations show the exception message.
+ $configuration['_exception'] = new FlattenException($exception);
+ $instance = $this->factory->createInstance($this->getFallbackPluginId($plugin_id, $configuration), $configuration);
+ }
+ else {
+ throw $exception;
+ }
+ }
+ return $instance;
+ }
+
+ /**
+ * Returns the fallback plugin ID to be used.
+ *
+ * @param string $original_plugin_id
+ * The plugin ID of the non-instantiable plugin.
+ * @param array $configuration
+ * The original configuration of the plugin.
+ *
+ * @return string|null
+ * The plugin ID of the fallback instance or NULL if there is no fallback
+ * set.
+ */
+ protected function getFallbackPluginId($original_plugin_id, array $configuration) {
+ return $this->fallbackPluginId;
+ }
+
+ /**
+ * Sets the fallback plugin ID.
+ *
+ * @param string $fallback_plugin_id
+ * The plugin ID to fall back to.
+ */
+ public function setFallbackPluginId($fallback_plugin_id) {
+ $this->fallbackPluginId = $fallback_plugin_id;
+ }
+
}
diff --git a/core/modules/block_content/src/Plugin/Block/BlockContentBlock.php b/core/modules/block_content/src/Plugin/Block/BlockContentBlock.php
index 342d0cc..0ad4ca4 100644
--- a/core/modules/block_content/src/Plugin/Block/BlockContentBlock.php
+++ b/core/modules/block_content/src/Plugin/Block/BlockContentBlock.php
@@ -154,9 +154,8 @@ public function build() {
}
else {
return array(
- '#markup' => t('Block with uuid %uuid does not exist. Add custom block.', array(
+ '#markup' => t('Block with uuid %uuid does not exist.', array(
'%uuid' => $uuid,
- '!url' => url('block/add')
)),
'#access' => $this->account->hasPermission('administer blocks')
);
diff --git a/core/modules/block_content/src/Tests/BlockContentPageViewTest.php b/core/modules/block_content/src/Tests/BlockContentPageViewTest.php
index 9d57528..ed9eee7 100644
--- a/core/modules/block_content/src/Tests/BlockContentPageViewTest.php
+++ b/core/modules/block_content/src/Tests/BlockContentPageViewTest.php
@@ -22,7 +22,7 @@ class BlockContentPageViewTest extends BlockContentTestBase {
public static $modules = array('block', 'block_content', 'block_content_test');
/**
- * Checks block edit functionality.
+ * Checks block edit and fallback functionality.
*/
public function testPageEdit() {
$this->drupalLogin($this->adminUser);
@@ -33,6 +33,15 @@ public function testPageEdit() {
// Assert response was '200' and not '403 Access denied'.
$this->assertResponse('200', 'User was able the view the block');
- }
+
+ $this->drupalGet('');
+ $expected_text = t('Block with uuid %uuid does not exist.', array(
+ '%uuid' => 'foobar_gorilla',
+ ));
+ $this->assertRaw($expected_text);
+
+ $this->drupalLogout();
+ $this->assertNoRaw($expected_text);
+ }
}
diff --git a/core/modules/block_content/tests/modules/block_content_test/config/install/block.block.foobar.yml b/core/modules/block_content/tests/modules/block_content_test/config/install/block.block.foobar.yml
new file mode 100644
index 0000000..939779c
--- /dev/null
+++ b/core/modules/block_content/tests/modules/block_content_test/config/install/block.block.foobar.yml
@@ -0,0 +1,35 @@
+id: foobar
+weight: null
+status: true
+langcode: en
+dependencies:
+ module:
+ - custom_block
+ theme:
+ - stark
+theme: stark
+region: sidebar_first
+plugin: 'custom_block:foobar_gorilla'
+settings:
+ label: Foobar
+ provider: custom_block
+ label_display: visible
+ cache:
+ max_age: -1
+ contexts: { }
+ status: true
+ info: ''
+ view_mode: default
+ admin_label: ''
+ custom_block:
+ view_mode: default
+visibility:
+ path:
+ visibility: 0
+ pages: ''
+ role:
+ roles: { }
+ node_type:
+ types:
+ article: '0'
+ page: '0'
diff --git a/core/modules/views/src/Plugin/ViewsHandlerManager.php b/core/modules/views/src/Plugin/ViewsHandlerManager.php
index 7b7b76e..1cdaa33 100644
--- a/core/modules/views/src/Plugin/ViewsHandlerManager.php
+++ b/core/modules/views/src/Plugin/ViewsHandlerManager.php
@@ -11,6 +11,7 @@
use Drupal\Core\Cache\CacheBackendInterface;
use Drupal\Core\Extension\ModuleHandlerInterface;
use Drupal\Core\Plugin\DefaultPluginManager;
+use Drupal\views\Plugin\views\HandlerBase;
use Drupal\views\ViewsData;
use Symfony\Component\DependencyInjection\Container;
@@ -55,6 +56,7 @@ public function __construct($handler_type, \Traversable $namespaces, ViewsData $
parent::__construct("Plugin/views/$handler_type", $namespaces, $module_handler, $plugin_definition_annotation_name);
$this->setCacheBackend($cache_backend, "views:$handler_type", array('extension' => array(TRUE, 'views')));
+ $this->setFallbackPluginId('broken');
$this->viewsData = $views_data;
$this->handlerType = $handler_type;
@@ -97,26 +99,25 @@ public function getHandler($item, $override = NULL) {
}
}
}
-
- // @todo This is crazy. Find a way to remove the override functionality.
- $plugin_id = $override ? : $definition['id'];
- // Try to use the overridden handler.
- try {
- return $this->createInstance($plugin_id, $definition);
- }
- catch (PluginException $e) {
- // If that fails, use the original handler.
- try {
- return $this->createInstance($definition['id'], $definition);
- }
- catch (PluginException $e) {
- // Deliberately empty, this case is handled generically below.
- }
- }
+ }
+ else {
+ $definition = $item;
+ $definition['id'] = 'non_existing';
}
- // Finally, use the 'broken' handler.
- return $this->createInstance('broken', array('original_configuration' => $item));
+ // @todo This is crazy. Find a way to remove the override functionality.
+ $plugin_id = $override ?: $definition['id'];
+ // Try to use the overridden or the original handler.
+ try {
+ return $this->createInstance($plugin_id, $definition);
+ }
+ catch (PluginException $e) {
+ // If that fails, use the original handler or the broken fallback.
+ $this->setFallbackPluginId('broken');
+ $result = $this->createInstance($definition['id'], $definition);
+ $this->setFallbackPluginId(NULL);
+ return $result;
+ }
}
/**
diff --git a/core/modules/views/src/Plugin/views/BrokenHandlerTrait.php b/core/modules/views/src/Plugin/views/BrokenHandlerTrait.php
index 4bd9948..0ecb10e 100644
--- a/core/modules/views/src/Plugin/views/BrokenHandlerTrait.php
+++ b/core/modules/views/src/Plugin/views/BrokenHandlerTrait.php
@@ -21,7 +21,7 @@
*/
public function adminLabel($short = FALSE) {
$args = array(
- '@module' => $this->definition['original_configuration']['provider'],
+ '@module' => $this->definition['provider'],
);
return t('Broken/missing handler (Module: @module) …', $args);
}
@@ -61,9 +61,9 @@ public function buildOptionsForm(&$form, FormStateInterface $form_state) {
$description_top = t('The handler for this item is broken or missing. The following details are available:');
$items = array(
- t('Module: @module', array('@module' => $this->definition['original_configuration']['provider'])),
- t('Table: @table', array('@table' => $this->definition['original_configuration']['table'])),
- t('Field: @field', array('@field' => $this->definition['original_configuration']['field'])),
+ t('Module: @module', array('@module' => $this->definition['provider'])),
+ t('Table: @table', array('@table' => $this->definition['table'])),
+ t('Field: @field', array('@field' => $this->definition['field'])),
);
$description_bottom = t('Enabling the appropriate module will may solve this issue. Otherwise, check to see if there is a module update available.');
diff --git a/core/tests/Drupal/Tests/Core/Plugin/DefaultPluginManagerTest.php b/core/tests/Drupal/Tests/Core/Plugin/DefaultPluginManagerTest.php
index 7f7d3ac..56bec56 100644
--- a/core/tests/Drupal/Tests/Core/Plugin/DefaultPluginManagerTest.php
+++ b/core/tests/Drupal/Tests/Core/Plugin/DefaultPluginManagerTest.php
@@ -7,12 +7,18 @@
namespace Drupal\Tests\Core\Plugin;
+use Drupal\Component\Plugin\Exception\PluginException;
+use Drupal\plugin_test\Plugin\plugin_test\fruit\Apple;
use Drupal\Tests\UnitTestCase;
+use Symfony\Component\Debug\Exception\FlattenException;
/**
* Tests the DefaultPluginManager.
*
* @group Plugin
+ * @group Drupal
+ *
+ * @coversDefaultClass Drupal\Core\Plugin\DefaultPluginManager
*/
class DefaultPluginManagerTest extends UnitTestCase {
@@ -58,6 +64,8 @@ protected function setUp() {
/**
* Tests the plugin manager with a disabled module.
+ *
+ * @covers ::getDefinition
*/
public function testDefaultPluginManagerWithDisabledModule() {
$definitions = $this->expectedDefinitions;
@@ -108,6 +116,9 @@ public function testDefaultPluginManagerWithObjects() {
/**
* Tests the plugin manager with no cache and altering.
+ *
+ * @covers ::getDefinitions
+ * @covers ::getDefinition
*/
public function testDefaultPluginManager() {
$plugin_manager = new TestPluginManager($this->namespaces, $this->expectedDefinitions);
@@ -117,6 +128,9 @@ public function testDefaultPluginManager() {
/**
* Tests the plugin manager with no cache and altering.
+ *
+ * @covers ::getDefinitions
+ * @covers ::getDefinition
*/
public function testDefaultPluginManagerWithAlter() {
$module_handler = $this->getMockBuilder('Drupal\Core\Extension\ModuleHandler')
@@ -137,6 +151,9 @@ public function testDefaultPluginManagerWithAlter() {
/**
* Tests the plugin manager with caching and altering.
+ *
+ * @covers ::getDefinitions
+ * @covers ::getDefinition
*/
public function testDefaultPluginManagerWithEmptyCache() {
$cid = $this->randomMachineName();
@@ -162,6 +179,8 @@ public function testDefaultPluginManagerWithEmptyCache() {
/**
* Tests the plugin manager with caching and altering.
+ *
+ * @covers ::getDefinitions
*/
public function testDefaultPluginManagerWithFilledCache() {
$cid = $this->randomMachineName();
@@ -185,6 +204,9 @@ public function testDefaultPluginManagerWithFilledCache() {
/**
* Tests the plugin manager cache clear with tags.
+ *
+ * @covers ::getFallbackPluginId
+ * @covers ::createInstance
*/
public function testCacheClearWithTags() {
$cid = $this->randomMachineName();
@@ -207,6 +229,46 @@ public function testCacheClearWithTags() {
$plugin_manager->clearCachedDefinitions();
}
+ /**
+ * Tests the plugin manager without a fallback plugin.
+ *
+ * @expectedException \Drupal\Component\Plugin\Exception\PluginException
+ */
+ public function testCreateInstanceWithoutFallback() {
+ $factory = $this->getMock('\Drupal\Component\Plugin\Factory\FactoryInterface');
+ $factory->expects($this->once())
+ ->method('createInstance')
+ ->with('non_existing', array())
+ ->will($this->throwException(new PluginException()));
+
+ $plugin_manager = new TestPluginManager($this->namespaces, $this->expectedDefinitions, NULL, NULL);
+ $plugin_manager->setFactory($factory);
+ $plugin_manager->createInstance('non_existing');
+ }
+
+ /**
+ * Tests the plugin manager with a fallback plugin.
+ */
+ public function testCreateInstanceWithFallback() {
+ $factory = $this->getMock('\Drupal\Component\Plugin\Factory\FactoryInterface');
+ $apple = new Apple();
+ $exception = new PluginException();
+ $factory->expects($this->at(0))
+ ->method('createInstance')
+ ->with('non_existing', array('key' => 'value'))
+ ->will($this->throwException($exception));
+ $factory->expects($this->at(1))
+ ->method('createInstance')
+ ->with('apple', array('_exception' => new FlattenException($exception), 'key' => 'value'))
+ ->will($this->returnValue($apple));
+
+ $plugin_manager = new TestPluginManager($this->namespaces, $this->expectedDefinitions);
+ $plugin_manager->setFallbackPluginId('apple');
+ $plugin_manager->setFactory($factory);
+ $plugin = $plugin_manager->createInstance('non_existing', array('key' => 'value'));
+ $this->assertSame($apple, $plugin);
+ }
+
}
if (!defined('DRUPAL_ROOT')) {
diff --git a/core/tests/Drupal/Tests/Core/Plugin/TestPluginManager.php b/core/tests/Drupal/Tests/Core/Plugin/TestPluginManager.php
index a7ea79d..4619640 100644
--- a/core/tests/Drupal/Tests/Core/Plugin/TestPluginManager.php
+++ b/core/tests/Drupal/Tests/Core/Plugin/TestPluginManager.php
@@ -8,6 +8,7 @@
namespace Drupal\Tests\Core\Plugin;
use Drupal\Component\Plugin\Discovery\StaticDiscovery;
+use Drupal\Component\Plugin\Factory\FactoryInterface;
use Drupal\Core\Extension\ModuleHandlerInterface;
use Drupal\Core\Plugin\DefaultPluginManager;
@@ -28,6 +29,8 @@ class TestPluginManager extends DefaultPluginManager {
* (optional) The module handler to invoke the alter hook with.
* @param string $alter_hook
* (optional) Name of the alter hook.
+ * @param string $fallback_plugin_id
+ * (optional) The fallback plugin ID.
*/
public function __construct(\Traversable $namespaces, array $definitions, ModuleHandlerInterface $module_handler = NULL, $alter_hook = NULL) {
// Create the object that can be used to return definitions for all the
@@ -48,4 +51,14 @@ public function __construct(\Traversable $namespaces, array $definitions, Module
}
}
+ /**
+ * Sets the plugin factory.
+ *
+ * @param \Drupal\Component\Plugin\Factory\FactoryInterface $factory
+ * (optional) The plugin factory.
+ */
+ public function setFactory(FactoryInterface $factory) {
+ $this->factory = $factory;
+ }
+
}