Problem/Motivation

Plugin instances can be added to config entities directly or by using PluginBag. Since content entities must have fields, and especially now the schema is derived from the property definitions and the default storage depends on that, it's become very hard to set plugin instances on content entities as properties.

Proposed resolution

Add a plugin bag field item base class that can be extended for specific plugin types.

Remaining tasks

None.

User interface changes

None.

API changes

None.

Support from Acquia helps fund testing for Drupal Acquia logo

Comments

Xano’s picture

Status: Active » Needs review
FileSize
2.13 KB

This is an outline of what I had in mind. I'll have to dive into typed data a bit more to find out how to return computed values for particular properties (the plugin instances). We probably want to reset the instance and the plugin configuration when the plugin ID is changed.

Status: Needs review » Needs work

The last submitted patch, 1: drupal_2294745_1.patch, failed testing.

Xano’s picture

Oh yeah. I have no idea what data type to use for an arbitrary object instance and if there's any chance we don't have to force developers to make their plugins be typed data as well, I want to take it.

Xano’s picture

Assigned: Unassigned » Xano
tim.plunkett’s picture

Status: Needs work » Closed (won't fix)

Most of this issue was covered in other condition issues, and I actually disagree with the __construct change, since that would make it different from blocks, image effects, filters, and variants...

EDIT: Cross post, I'll let Xano reopen if he chooses.

tim.plunkett’s picture

Status: Closed (won't fix) » Active
Xano’s picture

Status: Active » Needs work
FileSize
2.91 KB
3.48 KB

We probably shouldn't use an actual PluginBag anymore, but make the field item behave in a similar way.

What I'm trying to do is make the plugin_configuration property behave like a map while the plugin has not been instantiated. After the plugin has been instantiated, the plugin_configuration property must proxy getting and setting its value to the actual plugin. I haven't figured out how to do this. Tips are welcome.

I have plenty of time to work on this. If you don't have to work on this issue, please provide feedback instead of writing patches, as this issue to me is a way to understand the typed data API a bit better.

Xano’s picture

FileSize
7.09 KB
6.58 KB

This is still untested, but it's pretty much what I was going for. I do need to find a solution for getTypedDataPluginId() as we obviously cannot have an abstract static method (its caller is static, so that's why it can't be non-static).

I guess now is the time for uncensored feedback to see if this is getting close to a good solution or just a complete mess. Thanks!

tstoeckler’s picture

Have yet to try out the code but from looking at it I think it's about as good as we can have it right now.

Some things:

  1. +++ b/core/lib/Drupal/Core/Field/Plugin/Field/FieldType/PluginBagItemBase.php
    @@ -26,14 +33,123 @@
    +    // @todo Find a way to get the typed data plugin ID through a method
    +    //   without calling $this->getTypedDataPluginId().
    +    $plugin_typed_data_id = 'any';
    

    These property definitions are not only static coincidentally, but by design. I think 'any' is fine for now.

  2. +++ b/core/lib/Drupal/Core/Field/Plugin/Field/FieldType/PluginBagItemBase.php
    @@ -44,28 +160,45 @@ public static function propertyDefinitions(FieldStorageDefinitionInterface $fiel
    +  public function __get($property_name) {
    ...
    +  public function get($property_name) {
    

    As discussed with Xano in IRC this is a problem. One shouldn't have to override both, but that's currently how it is. Also e.g. CommentItem in core does *not* override get() (only __get()) which proves that we need to fix that.

Xano’s picture

Status: Needs work » Needs review
dawehner’s picture

+++ b/core/lib/Drupal/Core/Field/Plugin/Field/FieldType/PluginBagItemBase.php
@@ -0,0 +1,239 @@
+
+  /**
+   * Returns the plugin manager.
+   *
+   * @return \Drupal\Component\Plugin\PluginManagerInterface
+   */
+  abstract protected function getPluginManager();

Did you considered to add a plugin type feature to plugin managers or discoveries or something else? This would allow to write a generic implementation here, without relying on service IDs directly.

Xano’s picture

FileSize
15.36 KB

This patch contains a test that works 25%. Setting and getting the plugin ID on the field does not work yet and I'm not sure why.

Did you considered to add a plugin type feature to plugin managers or discoveries or something else? This would allow to write a generic implementation here, without relying on service IDs directly.

That would be cool. Is there an issue about that we can link to?

Status: Needs review » Needs work

The last submitted patch, 12: drupal_2294745_12.patch, failed testing.

dawehner’s picture

Status: Needs work » Needs review
FileSize
13.47 KB

I am pretty convinced that you want more something like the following.

Status: Needs review » Needs work

The last submitted patch, 14: 2294745.patch, failed testing.

Xano’s picture

  1. +++ b/core/lib/Drupal/Core/Field/Plugin/Field/FieldType/PluginBagItemBase.php
    @@ -0,0 +1,122 @@
    +    if ($property_name == 'plugin_id' || $property_name == 'plugin_configuration') {
    +      $this->set('plugin_instance', NULL, FALSE);
    +    }
    

    The instance should only be rest if the plugin ID changes. If the configuration is changed, it should be proxied to the instance if it is configurable. Similarly, when the plugin configuration property is read, its value should come from the instance, if it exists and it is configurable.

  2. +++ /dev/null
    @@ -1,18 +0,0 @@
    -Place downloaded and custom modules that extend your site functionality beyond
    -Drupal core in this directory to ensure clean separation from core modules and
    -to facilitate safe, self-contained code updates. Contributed modules from the
    -Drupal community may be downloaded at http://drupal.org/project/modules.
    -
    -It is safe to organize modules into subdirectories, such as "contrib" for
    -contributed modules, and "custom" for custom modules. Note that if you move a
    -module to a subdirectory after it has been enabled, you may need to clear the
    -Drupal cache so that it can be found.
    -
    -In multisite configuration, modules found in this directory are available to
    -all sites. In addition to this directory, shared common modules may also be kept
    -in the sites/all/modules directory and will take precedence over modules in this
    -directory. Alternatively, the sites/your_site_name/modules directory pattern may
    -be used to restrict modules to a specific site instance.
    -
    -Refer to the "Developing for Drupal" section of the README.txt in the Drupal
    -root directory for further information on extending Drupal with custom modules.
    

    Oops.

dawehner’s picture

The instance should only be rest if the plugin ID changes. If the configuration is changed, it should be proxied to the instance if it is configurable. Similarly, when the plugin configuration property is read, its value should come from the instance, if it exists and it is configurable.

I don't really get the second usecase, why should you want to get the configuration from the plugin instance, even you already have it available. The same point could be made for the plugin ID.

Xano’s picture

Use case: load a content entity that has plugin data in this field. Some code instantiates the plugin, works with it, and changes its configuration. Entity API then saves the entity, but if the call to the value of the plugin configuration property is not proxied to the plugin instance, the configuration that is retrieved from the entity is the old one that was originally loaded from storage, instead of the up-to-date configuration from the plugin instance.

Proxying the plugin ID is not necessary, because once that changes, the instance must be unset from the field anyway. The instance may continue to exist, but no longer as a value of the field.

Berdir’s picture

Is that really such a huge problem? It's the same for config entities and EntityWithPluginBagsInterface, see ConfigEntityBase::preSave(). If it works there, it's OK to live with for content entities too?

Xano’s picture

Surely it will work for base fields, but it's not really pretty. The patch from #12 shows, I believe, that proxying is not crazy hard to do. Once someone decides to turn this in a configurable field, the preSave() solution will no longer work.

Unless there is a good reason not to try and proxy calls, I think we should implement it.

Berdir’s picture

Once someone decides to turn this in a configurable field, the preSave() solution will no longer work.

Why?

Xano’s picture

Because when a user adds a configurable plugin bag field to a content entity, the preSave() method will not automatically be added/update with code to store the field's instance's values.

Berdir’s picture

field item/type classes can have preSave() method too, and it should be implemented here, then there is no difference between base and configurable fields?

We can't use that interface I mentioned, I was just using that as an example.

Xano’s picture

It's actually not just saving an entity. If you set the field's configuration, then get the instance, then set new configuration, it will not be in sync with the instance that may be used elsewhere.

Xano’s picture

Status: Needs work » Needs review
FileSize
9.26 KB
13.28 KB
Xano’s picture

The tests should pass. I will need to re-evaluate whether we should still expose the instance as typed data if the contained plugin implements TypedDataInterface.

Status: Needs review » Needs work

The last submitted patch, 25: drupal_2294745_25.patch, failed testing.

Xano’s picture

Status: Needs work » Needs review
FileSize
797 bytes
14.05 KB

Xano queued 28: drupal_2294745_28.patch for re-testing.

Xano queued 28: drupal_2294745_28.patch for re-testing.

Status: Needs review » Needs work

The last submitted patch, 28: drupal_2294745_28.patch, failed testing.

andypost’s picture

  1. +++ b/core/lib/Drupal/Core/Field/Plugin/Field/FieldType/PluginBagItemBase.php
    @@ -0,0 +1,229 @@
    +  abstract protected function getPluginManager();
    

    will require protected $pluginManager on class

    So maybe better use field setting to define plugin manager like ER fields needs entity_type setting configured

  2. +++ b/core/lib/Drupal/Core/Field/Plugin/Field/FieldType/PluginBagItemBase.php
    @@ -0,0 +1,229 @@
    +    $this->get('plugin_id')->applyDefaultValue();
    +    $this->get('plugin_configuration')->setValue(array());
    

    So no way to pass plugin specific defaults?

  3. +++ b/core/lib/Drupal/Core/Field/Plugin/Field/FieldType/PluginBagItemBase.php
    @@ -0,0 +1,229 @@
    +  public function setContainedPluginId($plugin_id) {
    +    $this->get('plugin_id')->setValue($plugin_id);
    ...
    +  public function onChange($property_name) {
    +    if ($property_name == 'plugin_id') {
    +      $this->pluginInstance = NULL;
    +      $this->get('plugin_configuration')->setValue(array());
    

    this should chain settings clear!

    maybe check the plugin is configurable and get its default settings

  4. +++ b/core/lib/Drupal/Core/Field/Plugin/Field/FieldType/PluginBagItemBase.php
    @@ -0,0 +1,229 @@
    +    $properties['plugin_configuration'] = MapDataDefinition::create('any')
    

    There's no type for binary blob?

  5. +++ b/core/lib/Drupal/Core/Field/Plugin/Field/FieldType/PluginBagItemBase.php
    @@ -0,0 +1,229 @@
    +  public static function mainPropertyName() {
    +    return NULL;
    

    plugin_id maybe?

  6. +++ b/core/lib/Drupal/Core/Field/Plugin/Field/FieldType/PluginBagItemBase.php
    @@ -0,0 +1,229 @@
    +      'plugin_id' => array(
    ...
    +        'length' => 255,
    

    do we have any constant for that?

  7. +++ b/core/lib/Drupal/Core/Field/Plugin/Field/FieldType/PluginBagItemBase.php
    @@ -0,0 +1,229 @@
    +        'not null' => TRUE,
    

    so no way to leave the field empty?

Xano’s picture

will require protected $pluginManager on class

It won't, because 1) dependency injection is still impossible for typed data and 2) even when it will we, the base field doesn't care about that. It only needs the method.

So maybe better use field setting to define plugin manager like ER fields needs entity_type setting configured

How? We cannot assume all plugin managers can be retrieved from the container, for instance.

So no way to pass plugin specific defaults?

Good point!

this should chain settings clear!

What do you mean?

plugin_id maybe?

I thought of that, but the plugin ID itself does not say much about the contained plugin if it is configurable. I concluded not having a main property name would be the safest way to let people ask for a specific property instead of giving them something that might be useless.

Status: Needs work » Needs review

Xano queued 28: drupal_2294745_28.patch for re-testing.

Xano’s picture

FileSize
837 bytes
14.06 KB

So no way to pass plugin specific defaults?

I implemented this.

Status: Needs review » Needs work

The last submitted patch, 35: drupal_2294745_35.patch, failed testing.

Xano’s picture

@andypost: could you please reply to #33? I would love for this to get into 8.0.0, otherwise I will have to postpone some contrib release to 8.1.0 and that would be a terrible shame.

andypost’s picture

What do you mean?

I mean that setting value mostly causes onChange() to fire, but that could not be a case for field properties.

The trick for changing pluginID is not clear to me. Maybe better to define and interface method to allow set plugin with config as second param. Otherwise it's confusing to change plugin but the settings will stay old...

Other points are explained for me and fine.

Xano’s picture

Xano’s picture

I re-opened #2134761: Convert plugins on payment entities to fields where I continued using the patch from #35. I figured a practical use case would allow for better manual testing. I found many more bugs that I fixed and tested. Once that issue has been fixed, I will try and use the improved code to finish the patch here.

Xano’s picture

This issue is pretty hard to solve because Field API magically creates empty items, ignores field item's default values (applyDefaultValue() and instead sets NULL everywhere), and sets $notify to FALSE in many places for no documented reasons. These behaviors pretty much prevent developers from re-using existing data type plugins if they want any sort of synchronization between properties in a parent field/property.

I know there are issues to remove magic empty field item creation. Am I wrong about the other things and is there actually any documentation for those behaviors?

Xano’s picture

I've been working on this feature in #2134761: Convert plugins on payment entities to fields for the past few weeks. Developing it for a practical use case helped me discover several flaws in the different approaches I tried to use. I now have a working and re-usable solution that will be committed to Payment. I will use this experience to port to code to Drupal core, which, because the actual plugin code is not Payment-specific, should be fairly straightforward.

There will of course be tests for this functionality, but because some aspects need integration testing, I am wondering if there is a part of core that I can convert as part of the patch, both to provide a concrete use case people can learn from and to have integration testing.

Berdir’s picture

We are not using plugins anywhere in content entities AFAIK.

I'm not sure if we can get this into 8.0.x, doubt it. The issue is currently defined as a task, but it is more a feature. So maybe publish it as a standalone contrib module first, then we can then get into core with 8.1.x?

bojanz’s picture

Version: 8.0.x-dev » 8.2.x-dev
Category: Task » Feature request

This is a very worthy core addition. Let's try again for 8.2.x

Xano’s picture

For those who missed it: this idea eventually ended up in the Plugin module, along with some other plugin system improvements.

Xano’s picture

Assigned: Xano » Unassigned

Unassigning myself.

Version: 8.2.x-dev » 8.3.x-dev

Drupal 8.2.0-beta1 was released on August 3, 2016, which means new developments and disruptive changes should now be targeted against the 8.3.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

Version: 8.3.x-dev » 8.4.x-dev

Drupal 8.3.0-alpha1 will be released the week of January 30, 2017, which means new developments and disruptive changes should now be targeted against the 8.4.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

joachim’s picture

> this idea eventually ended up in the Plugin module

There is also an implementation of a plugin reference field in Commerce.

How similar are the two approaches?

Version: 8.4.x-dev » 8.5.x-dev

Drupal 8.4.0-alpha1 will be released the week of July 31, 2017, which means new developments and disruptive changes should now be targeted against the 8.5.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

Version: 8.5.x-dev » 8.6.x-dev

Drupal 8.5.0-alpha1 will be released the week of January 17, 2018, which means new developments and disruptive changes should now be targeted against the 8.6.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

Version: 8.6.x-dev » 8.7.x-dev

Drupal 8.6.0-alpha1 will be released the week of July 16, 2018, which means new developments and disruptive changes should now be targeted against the 8.7.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

Version: 8.7.x-dev » 8.8.x-dev

Drupal 8.7.0-alpha1 will be released the week of March 11, 2019, which means new developments and disruptive changes should now be targeted against the 8.8.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

Version: 8.8.x-dev » 8.9.x-dev

Drupal 8.8.0-alpha1 will be released the week of October 14th, 2019, which means new developments and disruptive changes should now be targeted against the 8.9.x-dev branch. (Any changes to 8.9.x will also be committed to 9.0.x in preparation for Drupal 9’s release, but some changes like significant feature additions will be deferred to 9.1.x.). For more information see the Drupal 8 and 9 minor version schedule and the Allowed changes during the Drupal 8 and 9 release cycles.

Version: 8.9.x-dev » 9.1.x-dev

Drupal 8.9.0-beta1 was released on March 20, 2020. 8.9.x is the final, long-term support (LTS) minor release of Drupal 8, which means new developments and disruptive changes should now be targeted against the 9.1.x-dev branch. For more information see the Drupal 8 and 9 minor version schedule and the Allowed changes during the Drupal 8 and 9 release cycles.

Version: 9.1.x-dev » 9.2.x-dev

Drupal 9.1.0-alpha1 will be released the week of October 19, 2020, which means new developments and disruptive changes should now be targeted for the 9.2.x-dev branch. For more information see the Drupal 9 minor version schedule and the Allowed changes during the Drupal 9 release cycle.

Version: 9.2.x-dev » 9.3.x-dev

Drupal 9.2.0-alpha1 will be released the week of May 3, 2021, which means new developments and disruptive changes should now be targeted for the 9.3.x-dev branch. For more information see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

Version: 9.3.x-dev » 9.4.x-dev

Drupal 9.3.0-rc1 was released on November 26, 2021, which means new developments and disruptive changes should now be targeted for the 9.4.x-dev branch. For more information see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

joachim’s picture

I think as a first step, we need an issue that formalises the concept of a 'plugin type': #3260378: Add a concept of a 'plugin type' ID.

Version: 9.4.x-dev » 9.5.x-dev

Drupal 9.4.0-alpha1 was released on May 6, 2022, which means new developments and disruptive changes should now be targeted for the 9.5.x-dev branch. For more information see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

Version: 9.5.x-dev » 10.1.x-dev

Drupal 9.5.0-beta2 and Drupal 10.0.0-beta2 were released on September 29, 2022, which means new developments and disruptive changes should now be targeted for the 10.1.x-dev branch. For more information see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

Version: 10.1.x-dev » 11.x-dev

Drupal core is moving towards using a “main” branch. As an interim step, a new 11.x branch has been opened, as Drupal.org infrastructure cannot currently fully support a branch named main. New developments and disruptive changes should now be targeted for the 11.x branch, which currently accepts only minor-version allowed changes. For more information, see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.