Problem/Motivation

The goal of #2844302: Move Field Layout data model and API directly into \Drupal\Core\Entity\EntityDisplayBase is to move the functionality of Field Layout directly into the EntityDisplay system and Field UI.

In order to accomplish that move, several changes must be made to existing core code, this is one of them.


DefaultSingleLazyPluginCollection is used to maintain a single plugin instance and its configuration.
The benefit is that callers don't know that it's different from DefaultLazyPluginCollection, which contains multiple plugins.

Plugin collections can be iterated over.

Consider the following code:

$collection = new DefaultSingleLazyPluginCollection($manager, 'apple', $apple_configuration);
foreach ($collection as $id => $instance) {
  var_dump($id);
}
$collection->addInstanceId('banana', $banana_configuration);
foreach ($collection as $id => $instance) {
  var_dump($id);
}

Expected:
apple
banana
Actual:
apple
apple
banana

Even though it is intended to only represent a single plugin, iterating over the collection will return any instance ID it ever contained.

Proposed resolution

Reset the list of instance IDs when a new one is added.

Remaining tasks

N/A

User interface changes

N/A

API changes

N/A

Data model changes

N/A

Support from Acquia helps fund testing for Drupal Acquia logo

Comments

tim.plunkett created an issue. See original summary.

tim.plunkett’s picture

Status: Active » Needs review
FileSize
2.3 KB
dawehner’s picture

  1. +++ b/core/lib/Drupal/Core/Plugin/DefaultSingleLazyPluginCollection.php
    @@ -52,10 +52,7 @@ class DefaultSingleLazyPluginCollection extends LazyPluginCollection {
       public function __construct(PluginManagerInterface $manager, $instance_id, array $configuration) {
         $this->manager = $manager;
    -    $this->instanceId = $instance_id;
    -    // This is still needed by the parent LazyPluginCollection class.
    -    $this->instanceIDs = array($instance_id => $instance_id);
    -    $this->configuration = $configuration;
    +    $this->addInstanceId($instance_id, $configuration);
       }
    

    It is always nice to not bypass APIs in constructors. The only tricky bit is that this might cause BC problems. Imagine the following: A subclass having a constructor with one additional dependency. This dependency is used in the addInstanceId method. Now this new call leads to a call to NULL

  2. +++ b/core/tests/Drupal/Tests/Core/Plugin/DefaultSingleLazyPluginCollectionTest.php
    @@ -58,6 +58,17 @@ public function testAddInstanceId() {
    +  /**
    +   * @covers ::getInstanceIds
    +   */
    +  public function testGetInstanceIds() {
    +    $this->setupPluginCollection($this->any());
    +    $this->assertEquals(['apple' => 'apple'], $this->defaultPluginCollection->getInstanceIds());
    +
    +    $this->defaultPluginCollection->addInstanceId('banana', ['id' => 'banana', 'key' => 'other_value']);
    +    $this->assertEquals(['banana' => 'banana'], $this->defaultPluginCollection->getInstanceIds());
    +  }
    

    Nice and good to understand test coverage.

Status: Needs review » Needs work

The last submitted patch, 2: 2851635-singleplugincollectiong-2.patch, failed testing.

tim.plunkett’s picture

In general, I agree. But I think this is a special case and it is okay. Nothing overrides this method in any contrib or custom module I've seen.

Fixing plugins that already merge their defaults correctly, but just not in the correct place.

tim.plunkett’s picture

Status: Needs work » Needs review

Status: Needs review » Needs work

The last submitted patch, 5: 2851635-singleplugincollectiong-5.patch, failed testing.

tim.plunkett’s picture

Status: Needs review » Needs work

The last submitted patch, 8: 2851635-singleplugincollectiong-8.patch, failed testing.

tim.plunkett’s picture

PHP 5 is the worst. That's not a problem in 7!
https://3v4l.org/hUrTd

Interdiff against #2, just doing the bare minimum now.

jibran’s picture

Status: Needs review » Reviewed & tested by the community

New test made the review easier. Is it worth it to add test only patch?

tim.plunkett’s picture

Reuploading last patch and a fail version.
But that's not a new test, it was in the original patch.

The last submitted patch, 12: 2851635-singleplugincollection-12-FAIL.patch, failed testing.

alexpott’s picture

+++ b/core/modules/search/src/Plugin/ConfigurableSearchPluginBase.php
@@ -23,7 +23,7 @@
   public function __construct(array $configuration, $plugin_id, $plugin_definition) {
     parent::__construct($configuration, $plugin_id, $plugin_definition);
 
-    $this->configuration = NestedArray::mergeDeep($this->defaultConfiguration(), $this->configuration);
+    $this->setConfiguration($configuration);
   }
 
   /**
@@ -44,7 +44,7 @@ public function getConfiguration() {

@@ -44,7 +44,7 @@ public function getConfiguration() {
    * {@inheritdoc}
    */
   public function setConfiguration(array $configuration) {
-    $this->configuration = $configuration;
+    $this->configuration = NestedArray::mergeDeep($this->defaultConfiguration(), $configuration);
   }

What impacts will this have on contrib?

alexpott’s picture

tim.plunkett’s picture

TL;DR, none.


Most plugins, search plugins included, blindly assume that $this->configuration/$this->getConfiguration() always contain a value.
For the change above, two things are now different:

1) Before, if you called $search_plugin->setConfiguration() and did not include a value for each key, you would start triggering "undefined index" errors. Now you will not.

2) Now there is no way to purposefully cause undefined index errors. Which I don't consider a loss of functionality. If you don't like the default values, add 'the_key_you_do_not_want' => NULL to the array before you pass to setConfiguration().

alexpott’s picture

Status: Reviewed & tested by the community » Needs work
Issue tags: +Needs change record

Discussed about the implications for contrib with @tim.plunkett in iRC. I think we need a change record to inform contrib about the need to make changes like:

+++ b/core/lib/Drupal/Core/Action/ConfigurableActionBase.php
@@ -17,7 +17,7 @@
   public function __construct(array $configuration, $plugin_id, $plugin_definition) {
     parent::__construct($configuration, $plugin_id, $plugin_definition);
 
-    $this->configuration += $this->defaultConfiguration();
+    $this->setConfiguration($configuration);
   }
 
   /**
@@ -38,7 +38,7 @@ public function getConfiguration() {

@@ -38,7 +38,7 @@ public function getConfiguration() {
    * {@inheritdoc}
    */
   public function setConfiguration(array $configuration) {
-    $this->configuration = $configuration;
+    $this->configuration = $configuration + $this->defaultConfiguration();
   }

If we get this early into 8.4.x then contrib has plenty of time to apply and these changes will work for 8.3.x we should be all good. According to @tim.plunkett if they don;t make the changes then the worst that will occur is some PHP notices but the site should not be broken.

tim.plunkett’s picture

Status: Needs work » Needs review
Issue tags: -Needs change record
+++ b/core/modules/search/src/Plugin/ConfigurableSearchPluginBase.php
@@ -23,7 +23,7 @@
   public function __construct(array $configuration, $plugin_id, $plugin_definition) {
     parent::__construct($configuration, $plugin_id, $plugin_definition);
 
-    $this->configuration = NestedArray::mergeDeep($this->defaultConfiguration(), $this->configuration);
+    $this->setConfiguration($configuration);
   }

@@ -44,7 +44,7 @@ public function getConfiguration() {
   public function setConfiguration(array $configuration) {
-    $this->configuration = $configuration;
+    $this->configuration = NestedArray::mergeDeep($this->defaultConfiguration(), $configuration);
   }

This is actually the preferred way to do it, I opted to keep Action working with the += approach instead of switching to NestedArray to stay within scope.

Wrote https://www.drupal.org/node/2852190

dawehner’s picture

I'm wondering whether we should have a protected function called setConfigurationNested and allow contrib modules to use it in their setConfiguration implementation. This would then allow all contrib modules to have the new method already and then we could maybe switch over at some point.

tim.plunkett’s picture

We don't have a trait yet (tried to add one but failed, see #8 above), and no generic "ConfigurablePluginBase" or anything...

Also we need them to call it from their __construct too.

jibran’s picture

Status: Needs review » Reviewed & tested by the community

#17 is answered in #18 so back to RTBC

alexpott’s picture

Issue tags: +Needs followup

Can we get a followup to investigate adding a trait for \Drupal\Component\Plugin\ConfigurablePluginInterface and for all core base plugin classes to use it where possible. The differences between ConfigurableSearchPluginBase and others confuse me and probably others.

Once the followup exists this looks ready to commit to 8.4.x.

alexpott’s picture

alexpott’s picture

Status: Reviewed & tested by the community » Fixed

Committed 037932d and pushed to 8.4.x. Thanks!

  • alexpott committed 037932d on 8.4.x
    Issue #2851635 by tim.plunkett, alexpott, dawehner:...
tim.plunkett’s picture

slashsrm points out what seems like a gross oversight: by doing this, a plugin collection full of configurable plugins is no longer lazy.
DefaultSingleLazyPluginCollection::setConfiguration() instantiates the plugin.

I'm not honestly sure how important this is, since usually the single plugin collection isn't instantiated unless the plugin will be too. It really only exists to mimic DefaultLazyPluginCollection, so that code like ConfigEntityBase can have one way to handle plugins...

Status: Fixed » Closed (fixed)

Automatically closed - issue fixed for 2 weeks with no activity.

bojanz’s picture

The fact that DefaultLazyPluginCollection is no longer lazy broke Commerce hard.
Caught and fixed (need to start testing against future branches), but it feels like the core BC process was unusually lax here.

amateescu’s picture

This broke Entityqueue as well, it needed the same fix as Commerce: #2903149: Fatal error when updating to Drupal 8.4