diff --git a/core/lib/Drupal/Core/Entity/EntityReferenceSelection/SelectionPluginBase.php b/core/lib/Drupal/Core/Entity/EntityReferenceSelection/SelectionPluginBase.php index f8af542..99be1f0 100644 --- a/core/lib/Drupal/Core/Entity/EntityReferenceSelection/SelectionPluginBase.php +++ b/core/lib/Drupal/Core/Entity/EntityReferenceSelection/SelectionPluginBase.php @@ -32,10 +32,6 @@ public function __construct(array $configuration, $plugin_id, $plugin_definition * {@inheritdoc} */ public function defaultConfiguration() { - // Note: The implementations returned default configuration should not - // contain the backward compatibility key 'handler_settings'. That will be - // automatically added in ::setConfiguration(). - // @see \Drupal\Core\Entity\EntityReferenceSelection\SelectionPluginBase::setConfiguration() return [ 'target_type' => NULL, // @todo Remove this key in Drupal 9.0.x. @@ -55,50 +51,17 @@ public function getConfiguration() { * {@inheritdoc} */ public function setConfiguration(array $configuration) { - - // In order to keep backward compatibility, we copy all settings, except - // 'target_type', 'handler' and 'entity' under 'handler_settings', following - // the structure from the field config. If the plugin was instantiated using - // the 'handler_settings' level, those values will be used. In case of - // conflict, the root level settings will take precedence. The backward - // compatibility aware configuration will have the next structure: - // - target_type - // - handler (will be removed in Drupal 9.0.x, it's the plugin id) - // - entity - // - setting_1 - // - setting_2 - // ... - // - setting_N - // - handler_settings: (will be removed in Drupal 9.0.x) - // - setting_1 - // - setting_2 - // ... - // - setting_N - // @todo Remove 'handler' and 'handler_settings' in Drupal 9.0.x. - - $default_config = $this->defaultConfiguration(); - if (isset($default_config['handler_settings'])) { - throw new \InvalidArgumentException("{$this->getPluginDefinition()['class']}::defaultConfiguration() should not contain a 'handler_settings' key. All settings should be placed in the root level."); - } - - // Extract the BC level from passed configuration, if any. - $handler_settings = !empty($configuration['handler_settings']) ? $configuration['handler_settings'] : []; - unset($configuration['handler_settings']); - - // Settings passed in the root level take precedence over BC settings. - $configuration += $handler_settings; + // Resolve backward compatibility level configurations, if any. + $this->resolveBackwardCompatibilityConfiguration($configuration); // Merge in defaults. - $this->configuration = NestedArray::mergeDeep($default_config, $configuration); + $this->configuration = NestedArray::mergeDeep( + $this->defaultConfiguration(), + $configuration + ); - // Synchronize back 'handler_settings'. - // @todo Remove this sync in Drupal 9.0.x - foreach ($this->configuration as $key => $value) { - // Filter out keys that belong strictly to the root level. - if (!in_array($key, ['handler', 'target_type', 'entity', 'handler_settings'])) { - $this->configuration['handler_settings'][$key] = $value; - } - } + // Ensure a backward compatibility level configuration. + $this->ensureBackwardCompatibilityConfiguration(); } /** @@ -130,4 +93,64 @@ public function submitConfigurationForm(array &$form, FormStateInterface $form_s */ public function entityQueryAlter(SelectInterface $query) { } + /** + * Moves the backward compatibility level configurations in the right place. + * + * In order to keep backward compatibility, we copy all settings, except + * 'target_type', 'handler' and 'entity' under 'handler_settings', following + * the structure from the field config. If the plugin was instantiated using + * the 'handler_settings' level, those values will be used. In case of + * conflict, the root level settings will take precedence. The backward + * compatibility aware configuration will have the next structure: + * - target_type + * - handler (will be removed in Drupal 9.0.x, it's the plugin id) + * - entity + * - setting_1 + * - setting_2 + * ... + * - setting_N + * - handler_settings: (will be removed in Drupal 9.0.x) + * - setting_1 + * - setting_2 + * ... + * - setting_N + * + * @param array $configuration + * The configuration array to be altered. + * + * @deprecated Scheduled for removal in Drupal 9.0.x. + */ + protected function resolveBackwardCompatibilityConfiguration(array &$configuration) { + if (isset($this->defaultConfiguration()['handler_settings'])) { + throw new \InvalidArgumentException("{$this->getPluginDefinition()['class']}::defaultConfiguration() should not contain a 'handler_settings' key. All settings should be placed in the root level."); + } + + // Extract the BC level from the passed configuration, if any. + if (array_key_exists('handler_settings', $configuration)) { + if (!is_array($configuration['handler_settings'])) { + throw new \InvalidArgumentException("The setting 'handler_settings' is reserved and cannot be used."); + } + @trigger_error("Providing settings under 'handler_settings' is deprecated since version 8.3.x and will be removed before 9.0.0. Move the settings in the root of the configuration array.", E_USER_DEPRECATED); + + // Settings passed in the root level take precedence over BC settings. + $configuration += $configuration['handler_settings']; + unset($configuration['handler_settings']); + } + } + + /** + * Ensures a backward compatibility level configuration. + * + * @deprecated Scheduled for removal in Drupal 9.0.x. + */ + protected function ensureBackwardCompatibilityConfiguration() { + // Synchronize back 'handler_settings'. + foreach ($this->configuration as $key => $value) { + // Filter out keys that belong strictly to the root level. + if (!in_array($key, ['handler', 'target_type', 'entity', 'handler_settings'])) { + $this->configuration['handler_settings'][$key] = $value; + } + } + } + } diff --git a/core/tests/Drupal/Tests/Core/EntityReferenceSelection/EntityReferenceSelectionUnitTest.php b/core/tests/Drupal/Tests/Core/EntityReferenceSelection/EntityReferenceSelectionUnitTest.php index 0ad56b0..65556c3 100644 --- a/core/tests/Drupal/Tests/Core/EntityReferenceSelection/EntityReferenceSelectionUnitTest.php +++ b/core/tests/Drupal/Tests/Core/EntityReferenceSelection/EntityReferenceSelectionUnitTest.php @@ -15,14 +15,53 @@ class EntityReferenceSelectionUnitTest extends UnitTestCase { /** + * Triggered errors. + * + * @var array + */ + protected $errors = []; + + /** + * {@inheritdoc} + */ + protected function setUp() { + parent::setUp(); + set_error_handler(function($severity, $message, $file, $line, $context) { + $this->errors[] = compact('severity', 'message', 'file', 'line', 'context'); + }); + } + + /** * Tests invalid default configuration. * * @covers ::defaultConfiguration + * @covers ::resolveBackwardCompatibilityConfiguration * @expectedException \InvalidArgumentException + * @expectedExceptionMessage TestSelectionWithInvalidDefaultConfiguration::defaultConfiguration() should not contain a 'handler_settings' key. All settings should be placed in the root level. */ public function testInvalidDefaultConfiguration() { - (new TestSelectionWithInvalidDefaultConfiguration([], 'test_selector', ['class' => 'TestSelectionWithInvalidDefaultConfiguration'])) - ->getConfiguration(); + new TestSelectionWithInvalidDefaultConfiguration( + [], + 'test_selector', + ['class' => 'TestSelectionWithInvalidDefaultConfiguration'] + ); + } + + /** + * Tests the selection handler with malformed 'handler_settings' value. + * + * @covers ::setConfiguration + * @covers ::resolveBackwardCompatibilityConfiguration + * @expectedException \InvalidArgumentException + * @expectedExceptionMessage The setting 'handler_settings' is reserved and cannot be used. + */ + public function testMalformedHandlerSettingsValue() { + new TestSelection( + // The deprecated 'handler_setting' should be an array. + ['handler_settings' => FALSE], + 'test_selector', + ['class' => 'TestSelectionWithInvalidDefaultConfiguration'] + ); } /** @@ -86,6 +125,8 @@ public function providerTestSetConfiguration() { * * @dataProvider providerTestSetConfiguration * @covers ::setConfiguration + * @covers ::resolveBackwardCompatibilityConfiguration + * @covers ::ensureBackwardCompatibilityConfiguration * * @param array $options * The configuration passed to the plugin. @@ -122,6 +163,8 @@ public function testSetConfiguration($options) { * Tests the selection handler plugin BC structure. * * @covers ::setConfiguration + * @covers ::resolveBackwardCompatibilityConfiguration + * @covers ::ensureBackwardCompatibilityConfiguration */ public function testSetConfigurationBcLevel() { $config = [ @@ -157,6 +200,64 @@ public function testSetConfigurationBcLevel() { $this->assertArrayEquals($expected, $selection->getConfiguration()); } + /** + * Tests deprecation error triggering. + * + * @covers ::setConfiguration + * @covers ::resolveBackwardCompatibilityConfiguration + */ + public function testDeprecationErrorTriggering() { + // Configuration with no BC level. + $config = ['setting1' => TRUE]; + new TestSelection($config, 'test_selector', []); + // Check that deprecation error has not been triggered. + $this->assertNoError("Providing settings under 'handler_settings' is deprecated since version 8.3.x and will be removed before 9.0.0. Move the settings in the root of the configuration array.", E_USER_DEPRECATED); + + // Configuration with BC level. + $config = ['handler_settings' => ['setting1' => TRUE]]; + new TestSelection($config, 'test_selector', []); + // Check that deprecation error has been triggered. + $this->assertError("Providing settings under 'handler_settings' is deprecated since version 8.3.x and will be removed before 9.0.0. Move the settings in the root of the configuration array.", E_USER_DEPRECATED); + } + + /** + * Asserts that an error has been triggered. + * + * @param string $message + * The error message. + * @param int $severity + * The error severity. + */ + protected function assertError($message, $severity) { + $assertion_message = "Error '$message' (severity $severity) was triggered."; + foreach ($this->errors as $error) { + if ($error['message'] === $message && $error['severity'] === $severity) { + $this->assertTrue(TRUE, $assertion_message); + return; + } + } + $this->fail($assertion_message); + } + + /** + * Asserts that a specific error has not been triggered. + * + * @param string $message + * The error message. + * @param int $severity + * The error severity. + */ + protected function assertNoError($message, $severity) { + $assertion_message = "Error '$message' (severity $severity) was not triggered."; + foreach ($this->errors as $error) { + if ($error['message'] === $message && $error['severity'] === $severity) { + $this->fail($assertion_message); + return; + } + } + $this->assertTrue(TRUE, $assertion_message); + } + } /**