Problem/Motivation

When entity type definitions grew bigger and bigger, they were converted from arrays to objects (#2005716: Promote EntityType to a domain object). Because entity types are technically plugins, part of the plugin system had to be converted to accommodate object-based definitions (#2170775: Remove array typehint from $plugin_definition). Support for old-style array definitions was kept. As this conversion was not done comprehensively, bugs like #2458769: Derivative plugin definitions contain base plugin IDs instead of derivative IDs remained. As that particular bug could not be fixed without this very issue, the feature the bug appeared in was simply removed in #2470924: Entity manager allows entity type derivers, but should not.

Many bugs go unnoticed because type hints are missing for definition parameters (we simply cannot type hint on arrays and objects at the same time) and we have to jump through all kinds of hoops to accommodate both data types. This creates code that is hard to read, hard to test, and hard to maintain and extend, of which #2458769: Derivative plugin definitions contain base plugin IDs instead of derivative IDs is an example.

The plugin system is also a component that was designed and will be promoted for use in non-Drupal projects. Associative arrays are bad design and cannot easily be documented. This decreases reusability outside of Drupal.

Proposed resolution

Introduce PluginDefinitionInterface implements \ArrayAccess and require all definitions to be instances of classes that implement this interface. Array access will remain possible, which means that, with the exception of array operations, logical code can remain largely unchanged.
In follow-up issues, this change will allow us to re-introduce type hints for plugin definitions, and convert array access to method calls to improve the DX of the code base.

Remaining tasks

Implement the interface and make the necessary changes to accommodate it, but no more.

User interface changes

None.

API changes

This change breaks backwards compatibility.

  • Array type hints should all have been removed from core in earlier issues (#2170775: Remove array typehint from $plugin_definition, #2170415: Plugin definitions are documented as arrays, but they don't have to be), but contrib may not have been aware of this change.
  • In addition any array operation on plugin definitions must be changed. This includes merges (for which the patch introduces PluginDefinitionInterface::mergeDefaultDefinition() and PluginDefinitionInterface::mergeOverrideDefinition(), and loops (for which the deprecated method ArrayPluginDefinition::getArrayDefinition() was added.
  • Array access will continue to work as it always has. In practice this means that code outside the plugin system will not be affected by this change.
  • All plugin definitions that are discovered without YAML discovery or annotated class discovery will have to be changed from arrays to instances of \Drupal\Core\Plugin\Definition\ArrayPluginDefinition by simply passing on the original array to the class's constructor. Example:
    // Before
    $definition = [
      'id' => 'foo',
      'label' => 'Foo',
    ];
    // After
    $definition = new ArrayPluginDefinition([
      'id' => 'foo',
      'label' => 'Foo',
    ]);
    

Blocked issues

Beta phase evaluation

Reference: https://www.drupal.org/core/beta-changes
Issue category Bug, because most of the plugin system cannot deal with object definitions
Issue priority Normal
Disruption Disruptive for core, and contributed and custom modules, because plugin definition type hints must be removed.
CommentFileSizeAuthor
#127 interdiff-124-127.txt3.05 KBmartin107
#127 drupal_2458789_127.patch400.24 KBmartin107
#124 drupal_2458789_124.patch400.1 KBmartin107
#122 drupal_2458789_122.patch399.97 KBXano
#122 interdiff.txt2.28 KBXano
#120 drupal_2458789_120.patch399.78 KBXano
#120 interdiff.txt7.14 KBXano
#118 drupal_2458789_118.patch399.36 KBXano
#118 interdiff.txt5.41 KBXano
#116 drupal_2458789_115.patch398.22 KBXano
#95 drupal_2458789_95.patch398.85 KBXano
#92 drupal_2458789_92.patch398.86 KBXano
#92 interdiff.txt48.57 KBXano
#91 drupal_2458789_91.patch386.6 KBXano
#91 interdiff.txt946 bytesXano
#89 drupal_2458789_89.patch386.65 KBXano
#89 interdiff.txt51.93 KBXano
#88 drupal_2458789_88.patch384.22 KBXano
#88 interdiff.txt3.53 KBXano
#84 drupal_2458789_84.patch384.62 KBXano
#84 interdiff.txt3.6 KBXano
#81 drupal_2458789_81.patch385.03 KBXano
#81 interdiff.txt1.71 KBXano
#79 drupal_2458789_79.patch384.41 KBXano
#79 interdiff.txt673 bytesXano
#77 drupal_2458789_77.patch384.38 KBXano
#77 interdiff.txt13.6 KBXano
#75 drupal_2458789_75.patch375.84 KBXano
#75 interdiff.txt50.23 KBXano
#71 drupal_2458789_71.patch361.55 KBXano
#69 drupal_2458789_69.patch363.06 KBXano
#69 interdiff.txt41.51 KBXano
#67 drupal_2458789_67.patch340.25 KBXano
#67 interdiff.txt21.11 KBXano
#65 drupal_2458789_65.patch325.16 KBXano
#65 interdiff.txt10.46 KBXano
#63 drupal_2458789_63.patch315.21 KBXano
#63 interdiff.txt23.91 KBXano
#61 drupal_2458789_61.patch294.67 KBXano
#61 interdiff.txt18.87 KBXano
#54 drupal_2458789_54.patch280.42 KBXano
#54 interdiff.txt35.44 KBXano
#52 D77E135B2F_2015613_155920_leaf_sized.png37.61 KBlauriii
#48 drupal_2458789_48.patch250.07 KBXano
#48 interdiff.txt724 bytesXano
#46 drupal_2458789_46.patch249.62 KBXano
#46 interdiff.txt7.64 KBXano
#43 drupal_2458789_43.patch242.79 KBXano
#43 interdiff.txt21.91 KBXano
#41 drupal_2458789_41.patch222.03 KBXano
#41 interdiff.txt131.63 KBXano
#39 drupal_2458789_39.patch105.02 KBXano
#37 drupal_2458789_37.patch105.08 KBXano
#37 interdiff_since_24.txt112.18 KBXano
#35 drupal_2458789_35.patch75.38 KBXano
#35 interdiff.txt71.87 KBXano
#32 interdiff.txt35.73 KBXano
#31 drupal_2458789_31.patch28.17 KBXano
#31 interdiff.txt35.73 KBXano
#24 drupal_2458789_24.patch18.22 KBXano
#24 interdiff.txt520 bytesXano
#22 interdiff.txt4.01 KBXano
#21 drupal_2458789_21.patch18.23 KBXano
#17 drupal_2458789_17.patch18.73 KBXano
#12 drupal_2458789_12.patch18.61 KBXano
#12 interdiff.txt891 bytesXano
#9 drupal_2458789_9.patch18.34 KBXano
#9 interdiff.txt897 bytesXano
#7 drupal_2458789_7.patch18.27 KBXano
#7 interdiff.txt2.16 KBXano
#5 drupal_2458789_5.patch17.4 KBXano
#5 interdiff.txt8.8 KBXano
#1 drupal_2458789_1.patch10.48 KBXano
Support from Acquia helps fund testing for Drupal Acquia logo

Comments

Xano’s picture

Status: Active » Needs review
FileSize
10.48 KB

With \ArrayAccess for backwards-compatibility, as discussed with @EclipseGc.

Xano’s picture

Xano’s picture

So that was a first stab. I'll add testing coverage and a testing implementation using all the defaults from DefaultPluginManager.

dawehner’s picture

Why is this a task and not something like a feature request?

Xano’s picture

FileSize
8.8 KB
17.4 KB

I just forgot setting it. I'd argue this is a bug report, because it fixes the bug that prevents us from setting derivative plugin IDs on non-array plugin definitions (#2458769: Derivative plugin definitions contain base plugin IDs instead of derivative IDs).

Xano’s picture

I just ran into another inconsistency in how we implemented the plugin system: entities do not implement PluginInspectionInterface. We either have to make sure they do, or override EntityType::setClass() with a different type check.

Xano’s picture

FileSize
2.16 KB
18.27 KB

The override is not a clean implementation of PluginDefinitionInterface, but it doesn't change the current behavior. I added the check for EntityInterface to make sure there is no confusion about EntityType::setClass() and the super method.

Status: Needs review » Needs work

The last submitted patch, 7: drupal_2458789_7.patch, failed testing.

Xano’s picture

Status: Needs work » Needs review
FileSize
897 bytes
18.34 KB
Xano’s picture

Issue summary: View changes
neclimdul’s picture

  1. +++ b/core/lib/Drupal/Component/Plugin/PluginDefinition.php
    @@ -0,0 +1,229 @@
    +  public function setClass($class) {
    +    if (!is_subclass_of($class, 'Drupal\Component\Plugin\PluginInspectionInterface')) {
    ...
    +  public function setDeriverClass($class) {
    +    if (!is_subclass_of($class, 'Drupal\Component\Plugin\Derivative\DeriverInterface')) {
    

    type hint?

  2. +++ b/core/lib/Drupal/Component/Plugin/PluginDefinitionInterface.php
    @@ -0,0 +1,117 @@
    +  /**
    +   * Sets the plugin provider.
    +   *
    +   * @param string $provider
    +   *
    +   * @return $this
    +   */
    +  public function setProvider($provider);
    +
    +  /**
    +   * Gets the plugin provider.
    +   *
    +   * @return string
    +   */
    +  public function getProvider();
    

    If we're going to do this we need to document somewhere inside the plugin system what a provider is. As it stands its a defacto component provided outside of plugins by \Drupal\Component\Annotation and \Drupal\Component\Discovery

  3. +++ b/core/lib/Drupal/Component/Plugin/PluginDefinitionInterface.php
    @@ -0,0 +1,117 @@
    +  /**
    +   * Sets the deriver class.
    +   *
    +   * @param string $class
    +   *   A class that implements
    +   *   \Drupal\Component\Plugin\Derivative\DeriverInterface.
    +   *
    +   * @return $this
    +   *
    +   * @throws \InvalidArgumentException
    +   */
    +  public function setDeriverClass($class);
    +
    +  /**
    +   * Gets the deriver class.
    +   *
    +   * @return string|null
    +   *   A class that implements
    +   *   \Drupal\Component\Plugin\Derivative\DeriverInterface or null.
    +   */
    +  public function getDeriverClass();
    +
    

    Do we want to put this on _all_ plugins? I haven't really thought too hard about the implications but gut reaction I'd vote for its own interface.

Xano’s picture

FileSize
891 bytes
18.61 KB

type hint?

Scalar type hints are not possible in PHP <7 (the parameter is a class name and not an object instance).

Do we want to put this on _all_ plugins? I haven't really thought too hard about the implications but gut reaction I'd vote for its own interface.

Good point. I'm not sure yet. We would also have to introduce an interface for the label then.

Xano’s picture

#2470924: Entity manager allows entity type derivers, but should not proves that derivatives aren't possible for entity types. By adding a ::mergeDefinition() method here we can support derivatives in the future. The method can be used by derivers, but also by DerivativeDiscoveryDecorator.

Xano’s picture

#2485513: DefaultFactory cannot deal with objects as plugin definitions is another example of why we need something like this issue. We keep running into fundamental problems that we're trying to patch up.

Is there any way this issue can be seriously considered for getting into 8.0.0?

Xano queued 12: drupal_2458789_12.patch for re-testing.

Status: Needs review » Needs work

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

Xano’s picture

Status: Needs work » Needs review
FileSize
18.73 KB

Re-roll.

lauriii’s picture

Status: Needs review » Needs work
+++ b/core/lib/Drupal/Core/Entity/EntityTypeInterface.php
@@ -609,6 +585,7 @@ public function getRevisionTable();
+<<<<<<< HEAD

@@ -618,6 +595,9 @@ public function getLabel();
+=======
...
+>>>>>>> Comment 1

Oops :)

lauriii’s picture

tstoeckler’s picture

Category: Task » Bug report

Didn't get all the way through, but here we go:

  1. +++ b/core/lib/Drupal/Component/Plugin/PluginDefinitionInterface.php
    @@ -0,0 +1,123 @@
    + * For backwards compatibility with array-based plugin definitions, this
    + * interface implements \ArrayAccess. The required array keys and their
    ...
    +interface PluginDefinitionInterface extends \ArrayAccess {
    

    I can see why the default implementation implements \ArrayAccess but I don't see any reason for the interface to implement it. Dropping it (from the interface) would make things a bit easier in the future.

  2. +++ b/core/lib/Drupal/Component/Plugin/PluginDefinitionInterface.php
    @@ -0,0 +1,123 @@
    + * - label: static::setLabel() and static::getLabel()
    + * - deriver: static::setDeriverClass() and static::getDeriverClass()
    

    I think label and deriver should be dropped, as they are not used for all plugin types. A lot of the Views plugins in particular extend the PluginId annotation which by design does not have a label. Also we should not enforce having a deriver.

    At the very least let's drop this for now and discuss this in a follow-up.

  3. +++ b/core/lib/Drupal/Component/Plugin/PluginDefinitionInterface.php
    @@ -0,0 +1,123 @@
    +   * @param string $id
    +   *
    ...
    +   * @return string
    +   */
    ...
    +   * @param string $provider
    +   *
    ...
    +   * @return string
    +   */
    ...
    +   * @param string $label
    +   *
    ...
    +   * @return string|null
    +   */
    

    Unfortunately this won't fly. (Missing one-line docs)

  4. +++ b/core/lib/Drupal/Component/Plugin/PluginDefinitionInterface.php
    @@ -0,0 +1,123 @@
    +   *   A class that implements
    +   *   \Drupal\Component\Plugin\PluginInspectionInterface.
    ...
    +   * @throws \InvalidArgumentException
    ...
    +   * @return string
    +   *   A class that implements
    +   *   \Drupal\Component\Plugin\PluginInspectionInterface.
    

    Why is PluginInspectionInterface required? I see why it should be recommended, but we should enforce it. In fact it is one of the primary principles of the plugin system that any class will do.

  5. +++ b/core/lib/Drupal/Component/Plugin/PluginDefinitionInterface.php
    @@ -0,0 +1,123 @@
    +   * The provider is an arbitrary string that identifies the
    +   * package/component/module/extension that provides the plugin.
    

    I think it would be beneficial to mention that with the default plugin manager only module names + core + component are allowed.

Because #2485513: DefaultFactory cannot deal with objects as plugin definitions is blocked on this, this is now a bug.

Xano’s picture

Status: Needs work » Needs review
FileSize
18.23 KB

I can see why the default implementation implements \ArrayAccess but I don't see any reason for the interface to implement it. Dropping it (from the interface) would make things a bit easier in the future.

It's ugly, but it done to limit BC breaks to a minimum. Without it, one could technically create an object definition that wouldn't work with any of the plugin definition handling code that's already been written. I'd love to remove it if everybody is okay with that.

Unfortunately this won't fly. (Missing one-line docs)

The department of redundancy department ;-)

I think it would be beneficial to mention that with the default plugin manager only module names + core + component are allowed.

PluginDefinitionInterface is part of \Drupal\Component, so we can't specifically mention anything that belongs to \Drupal\Core.

Why is PluginInspectionInterface required? I see why it should be recommended, but we should enforce it. In fact it is one of the primary principles of the plugin system that any class will do.

IMHO it should be required, but seeing as entities are an exception to this convention, that's enough precedence to drop this requirement.

I think label and deriver should be dropped, as they are not used for all plugin types. A lot of the Views plugins in particular extend the PluginId annotation which by design does not have a label. Also we should not enforce having a deriver.

At the very least let's drop this for now and discuss this in a follow-up.

That's why the getters for those properties are documented are being able to return NULL as well. We can drop this functionality, but we'd need a bunch of child classes for many (but indeed not all) plugin types to add these functionalities though. I'm not sure about it.

Xano’s picture

FileSize
4.01 KB

Now with amazeballs interdiff!

jibran’s picture

Status: Needs review » Needs work
Issue tags: +Needs issue summary update, +Needs change record

If @EclipseGc and @neclimdul are on board I think this is good to go. Can we upate IS after #20? We probably need beta eval as well. We definitely need a change notice to let contrib know about this change.

  1. +++ b/core/lib/Drupal/Component/Plugin/PluginDefinition.php
    @@ -0,0 +1,229 @@
    +   * @param string
    

    @var :)

  2. +++ b/core/lib/Drupal/Component/Plugin/PluginDefinition.php
    @@ -0,0 +1,229 @@
    +   *   A class that implements
    +   *   \Drupal\Component\Plugin\PluginInspectionInterface.
    ...
    +   *   A class that implements
    +   *   \Drupal\Component\Plugin\Derivative\DeriverInterface or null.
    

    Theses should be one line 80 char limit doesn't apply to description.

  3. +++ b/core/lib/Drupal/Component/Plugin/PluginDefinition.php
    @@ -0,0 +1,229 @@
    +    return $this->label;
    
    +++ b/core/lib/Drupal/Core/Entity/EntityType.php
    @@ -675,13 +645,6 @@ public function getDataTable() {
    -    return (string) $this->label;
    

    We are not typecasting here so would that fine?

Xano’s picture

Status: Needs work » Needs review
FileSize
520 bytes
18.22 KB

We could potentially drop \ArrayAccess support from PluginDefinitionInterface by shipping with an ArrayPluginDefinitionDecorator class that calling code wraps array definitions in. This would be a slight performance decrease, but would keep the API clean and be an incentive for developers to change their array definitions to objects. It's just a brain fart and will probably cause some trouble with the dozens of array definitions in core, but maybe other people can build on this idea. If nothing happens I'll definitely update the exsting array definition mapper in Plugin Selector to implement PluginDefinitionInterface.

Theses should be one line 80 char limit doesn't apply to description.

They are @var descriptions, so not limited to one line.

We are not typecasting here so would that fine?

This relates to what @tstoeckler said in #20. If we split off labels to a separate interface, then we should only return strings. In this particular implementation they are optional, so they can be null|string.

Xano queued 24: drupal_2458789_24.patch for re-testing.

Xano’s picture

Another reason for splitting off the label and deriver functionality to their own interfaces is that it'll be very difficult to provide easy-to-use default implementations of array access for these definition data. Specific plugin definition classes would then have to provide this functionality, which is a major DX regression. At this point I'd rather keep label and deriver functionality in the top-level interface and default class then.

Xano’s picture

Issue summary: View changes
lauriii’s picture

I would like to see PluginDefinitionInterface not implement \ArrayAccess. It allows us to abstract the PluginDefinitionInterface into smaller interfaces without causing major DX regression. I like the idea of using ArrayPluginDefinitionDecorator to still support Array type of plugin definitions.

Xano’s picture

Ideally we'll remove /ArrayAccess from PluginDefinitionInterface. This allows us to split up the interface while keeping DX good. However, it requires all code that works with multiple plugin type definitions to include checks for the definition type (object or array). If that is an approved change, I'd be happy to take it on.

Using a decorator class for array definitions is useful, but we must take care not to use them too lightly, as they introduce minor performance hits. Seeing as (almost) all definitions are cached and the decorators are often needed before caching takes place, the performance decreases are mitigated.

Fabianx’s picture

Priority: Normal » Major

This needs way more justification on the why's and hows, but as much as I understand it, the problem is that object definitions can't be written to, but need to allow writes for certain properties like 'class', 'id', 'provider', etc. at run time.

The idea is to use a common Interface for that.

My thoughts:

- Do not have PluginDefinitionInterface extend ArrayAccess - That is a no go and just will lead straight to hell ;).

- Use a Decorator, but extend it from ArrayObject, which is faster than ArrayAccess - though a little slower than native arrays.

If you _need_ a reference to the original array to be able to change it too, then you need to use exchangeArray, which works by reference. (I used that trick in my original wrap twig arrays in objects patch to get twig into core).

e.g.

new PluginDefinitionDecorator(array $definition);

Or

$definition_object = new PluginDefinitionDecorator();
$definition_object->exchangeArray($definition); // definition was passed by reference, that is a side-effect that however has not changed since ages.

( see http://php.net/manual/de/arrayobject.exchangearray.php)

Then use within the class:

setClass($class) {
$this->offsetSet('class', $class);
}

etc.

Xano’s picture

FileSize
35.73 KB
28.17 KB

Thank you, @fabianx, for your review and advice!

This patch removes \ArrayAccess from PluginDefinitionInterface, splits up PluginDefinitionInterface into multiple smaller interface (note that most of them are in \Drupal\Component, but the code for providers lives in \Drupal\Core), adds traits for the split-off interfaces, and adds the array decorator.

Xano’s picture

FileSize
35.73 KB

And now an interdiff with moves.

Xano’s picture

I now realize I forgot to update the plugin system classes that handle arbitrary plugin definitions. Will do that tomorrow.

Status: Needs review » Needs work

The last submitted patch, 31: drupal_2458789_31.patch, failed testing.

Xano’s picture

FileSize
71.87 KB
75.38 KB

This patch converts most of the plugin system from array access to method access, and fixes the following bugs in the process:

  • \Drupal\Core\Plugin\DefaultPluginManager::findDefinitions() would skip provider validation for object definitions. It now validates providers for all definitions.
  • \Drupal\Core\Plugin\Discovery\HookDiscovery::getDefinitions() would set the module key for array definitions instead of the provider key.
  • \Drupal\Component\Plugin\Factory\DefaultFactory depended on plugin providers, which are a core concept and do not existing in the plugin component.
  • Tests that relied on plugin IDs being used as keys for plugin definition arrays, but not as part of the definitions themselves.

However, I misunderstood how references work in PHP (they are not pointers...), so this approach will have to be changed slightly if we're to continue working on it.

The change is getting really big, though. PluginDefinitionInterface is nice and clean, but it looks like this will require fairly radical changes to the rest of the plugin system (and calling code), which feels like digging a second hole to fill the first one.

Xano’s picture

I'm now exploring the options of continuing with #24. It seems \Drupal\Core\Annotation\ContextDefinition extends the Plugin annotation class, but it's not actually a plugin annotation. Confusing. And a bug.

Xano’s picture

Status: Needs work » Needs review
FileSize
112.18 KB
105.08 KB

This patch builds on the approach that was last used in #24: all definitions are objects that implement /ArrayAccess. It's a little less clean, but allows us to consistently use the same definition type throughout the plugin system without having to refactor the entire system. Most of the system still uses array access on plugin definitions. This will continue to work and can be converted to object access in follow-ups.

All plugin system unit tests pass locally. Let's see what this breaks elsewhere.

Status: Needs review » Needs work

The last submitted patch, 37: drupal_2458789_37.patch, failed testing.

Xano’s picture

Status: Needs work » Needs review
FileSize
105.02 KB

Re-roll.

Status: Needs review » Needs work

The last submitted patch, 39: drupal_2458789_39.patch, failed testing.

Xano’s picture

Status: Needs work » Needs review
FileSize
131.63 KB
222.03 KB
  • All unit tests pass now. Most of the work went into converting array definitions to instances of ArrayPluginDefinition, which required no logical changes.
  • \Drupal\filter\FilterPluginCollection uses definitions of one plugin type as configuration for plugins of another type. This is an architectural problem, but because it's beyond the scope of this issue, I added \Drupal\Component\Plugin\Definition\PluginDefinitionInterface::getArrayDefinition() and deprecated it immediately.
  • \Drupal\views\Plugin\views\PluginBase does a similar thing by merging plugin configuration with definitions.

Status: Needs review » Needs work

The last submitted patch, 41: drupal_2458789_41.patch, failed testing.

Xano’s picture

Status: Needs work » Needs review
FileSize
21.91 KB
242.79 KB

Status: Needs review » Needs work

The last submitted patch, 43: drupal_2458789_43.patch, failed testing.

The last submitted patch, 43: drupal_2458789_43.patch, failed testing.

Xano’s picture

Status: Needs work » Needs review
FileSize
7.64 KB
249.62 KB

Status: Needs review » Needs work

The last submitted patch, 46: drupal_2458789_46.patch, failed testing.

Xano’s picture

Status: Needs work » Needs review
FileSize
724 bytes
250.07 KB

Status: Needs review » Needs work

The last submitted patch, 48: drupal_2458789_48.patch, failed testing.

The last submitted patch, 48: drupal_2458789_48.patch, failed testing.

xjm’s picture

Just a note that this patch is nuking testbots with lots of must implement interface Drupal\Core\Plugin\Definition\PluginDefinitionInterface in the logs, so please avoid retesting the patches above and ideally run some tests locally before uploading a new one. Sorry for the invconvenience; the infrastructure team is also looking into how to make it so the bot is less brittle.

lauriii’s picture

Issue summary: View changes
Status: Needs work » Needs review
FileSize
37.61 KB

Congrats

Xano’s picture

I am honored ;-)

The bug in #2504995: Component DefaultFactory relies on core's concept of plugin providers is also of the type that was hidden by the fact we use array access. Working with interfaces is what caught it.

I am still working on several TypedConfigManager and TypedDataManager failures locally, and will upload a new patch once these problems are fixed, as they contributed to the testbot malfunctions.

Xano’s picture

FileSize
35.44 KB
280.42 KB
Xano’s picture

Issue summary: View changes
Issue tags: -Needs issue summary update

Status: Needs review » Needs work

The last submitted patch, 54: drupal_2458789_54.patch, failed testing.

Status: Needs work » Needs review

Xano queued 54: drupal_2458789_54.patch for re-testing.

Status: Needs review » Needs work

The last submitted patch, 54: drupal_2458789_54.patch, failed testing.

Status: Needs work » Needs review

Xano queued 54: drupal_2458789_54.patch for re-testing.

Status: Needs review » Needs work

The last submitted patch, 54: drupal_2458789_54.patch, failed testing.

Xano’s picture

Status: Needs work » Needs review
FileSize
18.87 KB
294.67 KB

Status: Needs review » Needs work

The last submitted patch, 61: drupal_2458789_61.patch, failed testing.

Xano’s picture

Status: Needs work » Needs review
FileSize
23.91 KB
315.21 KB

Status: Needs review » Needs work

The last submitted patch, 63: drupal_2458789_63.patch, failed testing.

Xano’s picture

Status: Needs work » Needs review
FileSize
10.46 KB
325.16 KB

Status: Needs review » Needs work

The last submitted patch, 65: drupal_2458789_65.patch, failed testing.

Xano’s picture

Status: Needs work » Needs review
FileSize
21.11 KB
340.25 KB

Status: Needs review » Needs work

The last submitted patch, 67: drupal_2458789_67.patch, failed testing.

Xano’s picture

Status: Needs work » Needs review
FileSize
41.51 KB
363.06 KB

Status: Needs review » Needs work

The last submitted patch, 69: drupal_2458789_69.patch, failed testing.

Xano’s picture

Status: Needs work » Needs review
FileSize
361.55 KB

Status: Needs review » Needs work

The last submitted patch, 71: drupal_2458789_71.patch, failed testing.

dawehner’s picture

+++ b/core/lib/Drupal/Component/Plugin/CategorizingPluginManagerInterface.php
index 0000000..c2b58b3
--- /dev/null

--- /dev/null
+++ b/core/lib/Drupal/Component/Plugin/Definition/ArrayPluginDefinition.php

+++ b/core/lib/Drupal/Component/Plugin/Definition/ArrayPluginDefinition.php
@@ -0,0 +1,238 @@
+  /**
+   * {@inheritdoc}
+   */
+  public function setContextDefinitions(array $context_definitions) {
+    $this->validateContextDefinitions($context_definitions);
+
+    $this->arrayDefinition['context'] = $context_definitions;
+
+    return $this;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getContextDefinitions() {
+    return isset($this->arrayDefinition['context']) ? $this->arrayDefinition['context'] : [];
+  }
...
+  /**
+   * {@inheritdoc}
+   */
+  public function setContextDefinition($name, ContextDefinitionInterface $context_definition) {
+    $this->arrayDefinition['context'][$name] = $context_definition;
+
+    return $this;

Mh I'm not sure why the generic one should care about contexts, given that most plugin definition give a shit about context

Xano’s picture

Mh I'm not sure why the generic one should care about contexts, given that most plugin definition give a shit about context

It shouldn't, and it shouldn't care about labels or derivers either. We can split that functionality off after all, and keep support for those properties in default definitions only through array access.

Xano’s picture

Status: Needs work » Needs review
FileSize
50.23 KB
375.84 KB

I could use a little bit of insight into the Views test failures. I'm sure there's a single cause for the numerous failures, but I haven't been able to find it yet.

Status: Needs review » Needs work

The last submitted patch, 75: drupal_2458789_75.patch, failed testing.

Xano’s picture

Status: Needs work » Needs review
FileSize
13.6 KB
384.38 KB

I think I fixed all bugs except the one that breaks Views.

Status: Needs review » Needs work

The last submitted patch, 77: drupal_2458789_77.patch, failed testing.

Xano’s picture

Status: Needs work » Needs review
FileSize
673 bytes
384.41 KB

This should fix (some of) the Views failures.

It also brings me back to something I thought about last week: plugin definition mutability. Maybe we should split off the setters, and introduce some kind of discovery decorator that converts mutable definitions (necessary during the main discovery/build/alter phases) to immutable definitions (if the used plugin definition classes support it).

Status: Needs review » Needs work

The last submitted patch, 79: drupal_2458789_79.patch, failed testing.

Xano’s picture

Status: Needs work » Needs review
FileSize
1.71 KB
385.03 KB

Fixed the last remaining test failure!

I'll now explore the possibilities of splitting up PluginDefinitionInterface to split off mutability and some non-default features like contexts.

Xano’s picture

Issue summary: View changes

Note that the patch is indeed 385KB. Most of this, however, is taken up by conversions from arrays to objects. These conversions are simple and the same across the code base, and do not contain logical changes.

lauriii’s picture

Status: Needs review » Needs work

Didn't really review the approach taken on the patch that much but for me its sane. I did take a look on the code styles though and found only few nits from the patch

  1. +++ b/core/lib/Drupal/Component/Plugin/Definition/PluginDefinition.php
    @@ -0,0 +1,256 @@
    \ No newline at end of file
    +++ b/core/tests/Drupal/Tests/Component/Plugin/Definition/PluginDefinitionTest.php
    @@ -0,0 +1,188 @@
    \ No newline at end of file
    +++ b/core/tests/Drupal/Tests/Core/Plugin/Definition/PluginDefinitionTest.php
    @@ -0,0 +1,98 @@
    \ No newline at end of file
    

    No newline at end of file

  2. +++ b/core/lib/Drupal/Component/Plugin/Definition/PluginDefinitionBase.php
    @@ -0,0 +1,113 @@
    +  /**
    +   *
    +   */
    ...
    +  /**
    +   *
    +   */
    

    Empty docblock

Xano’s picture

Status: Needs work » Needs review
FileSize
3.6 KB
384.62 KB

Thanks for the quick review!

This patch addresses the feedback from #83 and removes some debugging code.

Xano’s picture

I just ran Payment's unit test suite against this patch. It took me about 20 minutes to fix all test failures. Half that time was spent on fixing the tests of the Views plugins Payment provides. The reason I tested Payment is that it provides five plugin types, and uses another two plugin types from other contributed modules, plus several plugin types provided by Drupal core. The module is also reasonably large. The things I had to change were:

  • Type hinting in plugin constructors (there were some legacy array type hints left that should have been removed last year).
  • Type hinting on plugin derivers (this is an actual BC break caused by this patch)
  • Make sure contrib annotations extend the newly introduced \Drupal\Core\Annotation\Plugin base class.
  • Convert the dummy plugin definitions to objects in a dozen or so tests, but that was easy, because all arrays can be wrapped in new ArrayPluginDefinition().
tstoeckler’s picture

Only got about half way through, but here's what I got:

  1. +++ b/core/lib/Drupal/Component/Annotation/Plugin.php
    @@ -73,10 +86,12 @@ protected function parse(array $values) {
    -   * {@inheritdoc}
    +   * Gets the value of this annotation.
    

    We now allow @inheritdoc with additional stuff, so I think that should stay.

  2. +++ b/core/lib/Drupal/Component/Plugin/Definition/ArrayPluginDefinition.php
    @@ -0,0 +1,263 @@
    +  /**
    +   * The array definition.
    +   *
    +   * @var mixed[]
    +   */
    +  protected $arrayDefinition = [];
    

    I think the "array" is not needed. This is the internal property so "The definition" is completely sufficient.

    The same goes for the variable name. I.e. I would suggest $this->definition instead of $this->arrayDefinition

    If you use array instead of mixed[] that would additionally reinforce that this is an array.

  3. +++ b/core/lib/Drupal/Component/Plugin/Definition/ArrayPluginDefinition.php
    @@ -0,0 +1,263 @@
    +  public function getArrayDefinition() {
    

    I think in other places we're calling this toArray() and that would make sense here as well, IMO.

  4. +++ b/core/lib/Drupal/Component/Plugin/Definition/ArrayPluginDefinition.php
    @@ -0,0 +1,263 @@
    +   * Merges another array definition into this one, using the other for defaults.
    ...
    +  public function mergeDefaultArrayDefinition(array $other_definition) {
    ...
    +   * Merges another array definition into this one, using the other for overrides.
    ...
    +  public function mergeOverrideArrayDefinition(array $other_definition) {
    

    I think the verbosity in this case actually makes things less clear, especially what the difference between the two is. I had to actually look at the code.

    I don't know what others think but what about mergeDefaults($defaults) and mergeOverrides($Overrides)

  5. +++ b/core/lib/Drupal/Component/Plugin/Definition/ArrayPluginDefinition.php
    @@ -0,0 +1,263 @@
    +  public function doMergeDefaultDefinition(PluginDefinitionInterface $other_definition) {
    +    /** @var \Drupal\Component\Plugin\Definition\ArrayPluginDefinition $other_definition */
    +    $this->mergeDefaultArrayDefinition($other_definition->arrayDefinition);
    +  }
    ...
    +  public function doMergeOverrideDefinition(PluginDefinitionInterface $other_definition) {
    +    /** @var \Drupal\Component\Plugin\Definition\ArrayPluginDefinition $other_definition */
    +    $this->mergeOverrideArrayDefinition($other_definition->arrayDefinition);
    +  }
    

    If this is required it proves that IMO we're doing something wrong. Suggestion:
    1. Mark getArrayDefinition()/toArray() no longer deprecated and have mergeDefaultArrayDefinition()/mergeDefaults() and mergeOverrideArrayDefinition()mergeOverrides() use that in case they are passed a PluginDefinitionInterface (and remove the array typehints). Then document that both arrays and PluginDefinitionInterfaces are accepted with a @todo D9 to remove array support.

  6. +++ b/core/lib/Drupal/Component/Plugin/Definition/PluginDefinitionBase.php
    @@ -0,0 +1,113 @@
    +  protected function doMergeDefaultDefinition(PluginDefinitionInterface $other_definition) {
    +    // Child classes can override this to perform an actual merge.
    +  }
    ...
    +  protected function doMergeOverrideDefinition(PluginDefinitionInterface $other_definition) {
    +    // Child classes can override this to perform an actual merge.
    +  }
    

    Should this not be abstract?

    Also ArrayPluginDefinition redeclares those as public, which is a bit strange.

  7. +++ b/core/lib/Drupal/Component/Plugin/Definition/PluginDefinitionBase.php
    @@ -0,0 +1,113 @@
    +  protected function isDefinitionCompatible(PluginDefinitionInterface $other_definition) {
    +    if (!($other_definition instanceof $this)) {
    +      throw new \InvalidArgumentException(sprintf('$other_definition must be an instance of %s, but %s was given.', get_class($this), get_class($other_definition)));
    +    }
    +  }
    

    IMHO this is over-abstraction. I would rather have this logic in the two implementing classes by proper type-hinting, see my suggestion above.

  8. +++ b/core/lib/Drupal/Component/Plugin/Definition/PluginDefinitionInterface.php
    @@ -0,0 +1,193 @@
    +interface PluginDefinitionInterface extends \ArrayAccess {
    

    Again, I fail to see how it would be an API change to remove this from the interface to the actual implementing classes.

    Also, I saw in the previous patches that properly splitting up the implementations in terms of optional features such as derivers etc. is a lot of work. But I don't see why we can't at least split up the interface and then have the base implementation implement all the interfaces.

  9. +++ b/core/lib/Drupal/Core/Annotation/PluginID.php
    @@ -0,0 +1,43 @@
    +  /**
    +   * {@inheritdoc}
    +   */
    +  public function get() {
    +    return $this->createDefinitionFromArray(array(
    +      'id' => $this->value,
    +      'class' => $this->class,
    +      'provider' => $this->provider,
    +    ));
    +  }
    

    Why is this method override needed?

  10. +++ b/core/lib/Drupal/Core/Config/TypedConfigManager.php
    @@ -110,12 +115,12 @@ public function buildDataDefinition(array $definition, $value, $name = NULL, $pa
    -    foreach ($definition as $key => $value) {
    +    foreach ($definition->getArrayDefinition() as $key => $value) {
    

    I don't think we have to do this API change, if we have ArrayPluginDefinition implement IteratorAggregate and then do getIterator { return new \ArrayIterator($this->arrayDefinition); }

  11. +++ b/core/lib/Drupal/Core/Config/TypedConfigManager.php
    @@ -141,20 +146,18 @@ public function getDefinition($base_plugin_id, $exception_on_invalid = TRUE) {
    +    if (isset($definition['type']) && !isset($definition->_merged)) {
    +      $definition->_merged = TRUE;
    

    The _merged business at the very least needs a code comment if we can't get around it somehow.

  12. +++ b/core/lib/Drupal/Core/Entity/Annotation/ContentEntityType.php
    @@ -36,10 +36,8 @@ class ContentEntityType extends EntityType {
    -  public function get() {
    ...
    +  protected function preprocessArrayDefinition(array &$definition) {
    

    Seems a bit over-powered to introduce a whole method for that, no?

    Can't ConfigEntityType and ContentEntityType just override createDefinitionFromArray()?

  13. +++ b/core/lib/Drupal/Core/Entity/EntityType.php
    @@ -350,28 +323,26 @@ public function id() {
    +    $this->validateClass($class);
    +    if (!is_subclass_of($class, 'Drupal\Core\Entity\EntityInterface')) {
    +      throw new \InvalidArgumentException('Entity classes must implement \Drupal\Core\Entity\EntityInterface.');
    +    }
    

    Seems like validateClass() should be overridden here?

  14. +++ b/core/lib/Drupal/Core/EventSubscriber/DefaultExceptionSubscriber.php
    @@ -129,6 +129,7 @@ protected function onHtml(GetResponseForExceptionEvent $event) {
    +    $content = $event->getException()->getMessage() . $event->getException()->getFile() . $event->getException()->getLine();
    

    Leftover debug?

  15. +++ b/core/lib/Drupal/Core/Plugin/DefaultPluginManager.php
    @@ -266,10 +268,14 @@ protected function cacheSet($cid, $data, $expire = Cache::PERMANENT, array $tags
    +      $definition->mergeDefaultArrayDefinition($this->defaults);
    

    If this is the only use of mergeDefaultArrayDefinition() why can we not do new ArrayPluginDefinition($this->defaults) first and then do a proper merge?

Xano’s picture

Thanks for the extensive review! I commented on some of your feedback below. If I didn't comment on something, I'll incorporate your feedback about that in the next patch straight away.

I think the "array" is not needed. This is the internal property so "The definition" is completely sufficient.

The same goes for the variable name. I.e. I would suggest $this->definition instead of $this->arrayDefinition

If you use array instead of mixed[] that would additionally reinforce that this is an array.

I just wanted to be explicit and consistent with ::getArrayDefinition(). I'm fine changing the name. The type documentation should stay at mixed[], as it is more specific than array, while both are correct.

I think in other places we're calling this toArray() and that would make sense here as well, IMO.

It's less consistent with the internal property and class names, but we could do it.

If this is required it proves that IMO we're doing something wrong. Suggestion:
1. Mark getArrayDefinition()/toArray() no longer deprecated and have mergeDefaultArrayDefinition()/mergeDefaults() and mergeOverrideArrayDefinition()mergeOverrides() use that in case they are passed a PluginDefinitionInterface (and remove the array typehints). Then document that both arrays and PluginDefinitionInterfaces are accepted with a @todo D9 to remove array support.

Why shouldn't the array methods be deprecated? We should eventually move to a situation where no array definition handling happens anywhere in the code. It's supported, but you shouldn't use these methods ideally. They exist solely for BC.
I am against removing type hints from the primary merge methods. That would worsen documentation, increase the burden of argument validation on implementations. Arrays are deprecated. We shouldn't pretend they're not.

Should this not be abstract?

The methods or the class? We can make the class abstract if it isn't, but I wasn't sure whether we want to force plugin definitions to be mergable. If we cannot come up with any situations in which merging shouldn't be supported, I'd happy to make the methods abstract as well.

IMHO this is over-abstraction. I would rather have this logic in the two implementing classes by proper type-hinting, see my suggestion above.

We can't type hint. The methods are on the interface and whatever type hints they have, implementing classes cannot technically add a more specific type hint, which means the burden of validation lies with the implementation.

Again, I fail to see how it would be an API change to remove this from the interface to the actual implementing classes.

Also, I saw in the previous patches that properly splitting up the implementations in terms of optional features such as derivers etc. is a lot of work. But I don't see why we can't at least split up the interface and then have the base implementation implement all the interfaces.

It's technically doable, but that does mean that if you want to use core plugin functionality with your custom plugin definition, you'll either have to extend the \ArrayAccess-implementing base class or implement the interface yourself, at least until we have converted all core code to use the interface methods instead of array access and that can take a long time. The 'good' thing about the current patch is that it tries to minimize the amount of refactoring that is needed (you made some good suggestions to improve that even more). If we make array access optional, we'll need more checks as type hints will then no longer suffice.
Removing the unconditional support for array access *is* technically a BC break, because it's a change from the current situation in which array access is possible on any and every plugin definition.

Why is this method override needed?

Good question. Providers are a core concept and don't exist in \Drupal\Component, which means that the provider should be removed from the parent annotation, and then it becomes obvious why the override is necessary.

Seems a bit over-powered to introduce a whole method for that, no?

Can't ConfigEntityType and ContentEntityType just override createDefinitionFromArray()?

They can. I don't like duplication.

Seems like validateClass() should be overridden here?

No, as ::validateClass() validates any class and not just the one that is set in that particular method. The base class also uses ::validateClass() to validate deriver classes.

Leftover debug?

Yes. The last patch already removed that (it seems you started your review before I uploaded that patch).

If this is the only use of mergeDefaultArrayDefinition() why can we not do new ArrayPluginDefinition($this->defaults) first and then do a proper merge?

We can do that and in fact that's what the patch did before the array merge methods were introduced. I was hoping this would be better for performance, but maybe @Fabianx can share whether that was a stupid idea of mine or not :P
The code you quoted isn't, however, the only usage of that method. It's used about a dozen times.

Xano’s picture

FileSize
3.53 KB
384.22 KB

This patch addresses some of the feedback from #86.

I've been playing with several of my Drupal 8 contributed modules and they all play nicely with this change. Most of the work has gone into changing definitions in tests and the ones set on StaticDiscovery, as only AnnotatedClassDiscovery and YamlDiscovery automatically return definitions as objects. After converting three modules I can say that the change requires some work, but most modules should be able to be converted within 20 minutes.

Xano’s picture

FileSize
51.93 KB
386.65 KB

This patch makes \ArrayAccess optional, introduces iteration and count support for ArrayPluginDefinition, and splits up both PluginDefinitionInterfaces into smaller, more specific interfaces that are implemented by the default definitions. It also reverts some unnecessary changes that were well-spotted by @tstoeckler.

The array functionality has been moved to ArrayPluginDefinitionInterface, which should make clearer which functionality exists because of BC and which parts are actually part of the new API.

Thanks to @dawehner, @tstoeckler, and @lauriii for the feedback in this issue and on IRC!

Status: Needs review » Needs work

The last submitted patch, 89: drupal_2458789_89.patch, failed testing.

Xano’s picture

Status: Needs work » Needs review
FileSize
946 bytes
386.6 KB
Xano’s picture

FileSize
48.57 KB
398.86 KB

I moved most of the default plugin definition functionality to separate interfaces and traits in order to keep definitions as small as possible. One next step could me to also do this for the merge functionality.

Xano queued 92: drupal_2458789_92.patch for re-testing.

Status: Needs review » Needs work

The last submitted patch, 92: drupal_2458789_92.patch, failed testing.

Xano’s picture

Status: Needs work » Needs review
FileSize
398.85 KB

Re-roll.

Xano’s picture

Any comments on how solid or how disruptive this patch is? If anyone can verify that this patch indeed fixes the bugs mentioned in the issue summary, that would be very much appreciated.

Xano’s picture

Issue summary: View changes
lauriii’s picture

Status: Needs review » Needs work

I tried to review the whole idea of the issue and for it sounds sane to make the plugin definitions more consistent. Internally this is quite an API change and I'm not very good to evaluate whether the disruption which is very high is lower than what we achieve by doing this. Anyway by looking the issue it is very easy to start implementing the new API which is positive thing.

  1. +++ b/core/lib/Drupal/Component/Plugin/Discovery/DerivativeDiscoveryDecorator.php
    @@ -96,6 +91,10 @@ public function getDefinitions() {
    +   * @param \Drupal\Component\Plugin\Definition\PluginDeriverDefinitionInterface[] $base_plugin_definitions
    

    This is missing the param comment

  2. +++ b/core/lib/Drupal/Component/Plugin/Discovery/StaticDiscovery.php
    @@ -6,6 +6,7 @@
     namespace Drupal\Component\Plugin\Discovery;
    +use Drupal\Component\Plugin\Definition\PluginDefinitionInterface;
    

    Missing space before use statement

  3. +++ b/core/lib/Drupal/Core/Config/Schema/Mapping.php
    @@ -6,6 +6,7 @@
     namespace Drupal\Core\Config\Schema;
    +use Drupal\Core\Plugin\Definition\ArrayPluginDefinition;
    
    +++ b/core/lib/Drupal/Core/Config/Schema/Sequence.php
    @@ -6,6 +6,7 @@
     namespace Drupal\Core\Config\Schema;
    +use Drupal\Core\Plugin\Definition\ArrayPluginDefinition;
    

    Missing line before use statement

  4. +++ b/core/lib/Drupal/Core/Menu/MenuTreeStorageInterface.php
    @@ -6,6 +6,7 @@
     namespace Drupal\Core\Menu;
    +use Drupal\Core\Plugin\Definition\PluginDefinitionInterface;
    

    Missing line before use statement

  5. +++ b/core/modules/views/tests/src/Unit/Plugin/views/field/EntityOperationsUnitTest.php
    @@ -7,7 +7,8 @@
    +  use Drupal\Core\Plugin\Definition\ArrayPluginDefinition;
    +  use Drupal\Tests\UnitTestCase;
     use Drupal\views\Plugin\views\field\EntityOperations;
     use Drupal\views\ResultRow;
    

    Indenting looks inconsistent

  6. +++ b/core/tests/Drupal/Tests/Core/Plugin/Discovery/DerivativeDiscoveryDecoratorTest.php
    @@ -53,93 +53,19 @@ public function testGetDerivativeFetcher() {
    +  public function xtestExistingDerivative() {
    

    Probably xtest for debugging?

Status: Needs work » Needs review

lauriii queued 95: drupal_2458789_95.patch for re-testing.

Status: Needs review » Needs work

The last submitted patch, 95: drupal_2458789_95.patch, failed testing.

dawehner’s picture

Thanks.

Yeah I think this is really not something we can still do, but sure, feel free to try to fight this.

:)

Xano’s picture

@dawehner: I understand you see this change as more disruptive than the bugs it fixes. Could you please be more precise about why, though?

xjm’s picture

Version: 8.0.x-dev » 9.x-dev
Category: Bug report » Task
Status: Needs work » Postponed

@alexpott and I discussed this and due to both the BC break and the scope of the change, unfortunately, this issue is not in scope for Drupal 8. We agreed the issue needed to be postponed to Drupal 9.

Thanks @Xano for your continued diligence on this patch. I'm sad that we can't make this change at this point. For future improvements of this scope, I'd recommend getting feedback from both framework and release managers early on, especially when we are not in the development phase of the release cycle.

Also, recategorizing as a major task.

daffie’s picture

I am sorry for you Xano. You have put a lot of effort into this issue.

jibran’s picture

Xano’s picture

@xjm & @alexpott: thank you for discussing this at the D8 accelerate sprint!
As @jibran swifly pointed out, this issue was marked a blocker for several bugs. Did you get the chance to look at those as part of the discussion?
Also, as this blocks other issues that are bugs, should this issue itself not remain a bug as well?

For future improvements of this scope, I'd recommend getting feedback from both framework and release managers early on, especially when we are not in the development phase of the release cycle.

I understand. The scope of the problem and the requirements of the fix were not fully known until some time had already gone into this issue. The problem the patch fixes is a fundamental one and its development even brought more bugs to light by simply creating a more testable system.

tstoeckler’s picture

Version: 9.x-dev » 8.0.x-dev
Category: Task » Bug report
Status: Postponed » Needs work

Before postponing this we need a clear plan how to fix the problems we currently have.

I don't know why this was changed from Bug report to Task but this whole problem space is speckled with bugs all around. Solving those bugs without any API breaks might not be possible in all cases and will certainly come with increased complexity and code debt in comparison to this solution. So before pushing this back we need to actually assess the situation and know what we are buying into with doing this. Anything else would be just lying to ourselves.

To not piss off people any more than necessary I fear is inevitable, let me be absolutely clear: Even though I personally disagree with the decision, it's totally fine if you, @xjm and @alexpott, decide that this should be postponed. And I'm quite certain that you made a informed decision about that. However, #103 completely lacks reasoning when taking into account all the different issues that are involved with this.

Edit: Better choice of words. I of course do not actually want to piss off anyone.

tstoeckler’s picture

Here's an effort to clarify and provide some reasoning for the previous comment.

The situation (as I see it) is as follows (I am trying to put this as objectively as possible, but I am of course aware that at some point things will be arguable):

The plugin system allows for both array-based plugin definitions and object-based plugin definitions and both are used in core. Some code in the plugin system needs to interact generically with plugins, so it cannot know which definitions are array-based and which are object based. There is already some code to account for the two cases but first of all that code is by definition very messy with type-checks, casting, and all sorts of "safety checks" galore. More importantly, as @Xano and others have more or less recently uncovered it is by far insufficient to be able to use the plugin system generically in any meaningful way. This is as simple as retrieving the plugin class from the definition in order to instantiate a plugin instance as discovered in #2485513: DefaultFactory cannot deal with objects as plugin definitions. This alone makes using the plugin system with object-based definitions impossible for many use-cases. Plugin derivatives also do not work because they currently rely on a notion of array merging which does not exist as such for objects. That is why we explicitly removed derivative support from entity types (which use object-based definitions) even though that support was explicitly baked in, to support ECK in Drupal 8. Such a thing is not possible (without hacks) in Drupal 8 at the moment. We do not have a generic plugin mapper implementation in core, but if we did or if someone tries to write one in contrib, she will hit the same wall. In other words everything in the Plugin system but the discovery (i.e. the retrieving of the definitions itself) - namely factories, mappers, and derivatives - are broken in some way (surely not broken as in completely-broken-WSOD-fatal-in-every-single-use-case broken, but still broken in some way). So much for the status quo.

If we were not in release-mode the easy answer to all this would be to simply require definitions to be proper objects implementing a PluginDefinitionInterface wholesale without caring about backwards compatibility. Needless to say, this is not even remotely an option because it would be a huge API change for anyone already using plugins.

Because PHP provides the \ArrayAccess crutch plus Countable and Traversable, however, we are able to do this conversion with a much smaller set of API changes. We just need a base plugin definition class which implements all the "fake array" logic and most of consumer code will not see the difference. One notable exception to this is the merging of arrays, which cannot be "faked" in any way. For that reason explicit methods are provided for this, so code that needs to merge plugin definitions does need to be changed. Because generic code that interacts generically with plugin definitions now expects to receive objects implementing PluginDefinitionInterface (as that is the whole point: to make the input parameters reliable, so the behavior can be reliable and (more importantly) working), code that creates plugin definitions needs to be updated. This is something we cannot avoid, regardless of the specific implementation of this patch. However, most plugin types in core use the generic discovery mechanisms, so this change is is completely transparent to them.

There are also some other API changes in the current patch but I would argue that some of them could be avoided, if we wanted to:
1. The creation of the definition objects from annotations is done in Drupal\Core\Annotation instead of Drupal\Component\Annotation, which was assumingly done to keep the code in Drupal\Component\Annotation self-contained and without too many assumptions. This leads to the fact that many plugin annotation subclasses need to change their base class from e.g. Drupal\Component\Plugin\Annotation to Drupal\Core\Plugin\Annotation. We could avoid this by simply putting the mentioned code in Drupal\Component\Annotation\Plugin (et al.) directly.

2. We could remove some of the PluginDefinitionInterface typehints, so that code that extends the base classes does not need to be updated to comply with the typehints. Specifically, DefaultPluginManager::processDefinition() comes to mind.

3. This might be going a bit far, but we could even do things like check if a plugin deriver returns array definitions, and convert them to objects, when retrieving them in DerivativeDiscoveryDecorator. That way not even derivers would need to be changed and the ugliness would combined in a single place (with a big fat @todo D9) instead of scattered all around core and contrib.

With those 2 or 3 changes I don't really what much there is left in terms of disruption. There might still be some technical API changes, but looking through core and thinking about what I have done in custom modules so far I can't come up with anything right now. Note that this is just me thinking out loud and - needless to say - there might be things that I have missed or that I am understating, but I feel that this sort of discussion (i.e. how can we reduce/limit/avoid disruption, while still reaping (most of) the benefits) was not conducted extensively above and I think it should before giving up on this.

I want to reiterate one more time that by pushing this to D9 we are effectively saying the following:
1. Not only are we accepting the current messiness of various parts of the plugin system but we accept that we have to add more messiness in order to fix bugs such as #2485513: DefaultFactory cannot deal with objects as plugin definitions that only this issue would be able to solve without hacks.
2. Any contrib that interacts generically with plugin (definition)s will have to employ the same messiness.
3. Things like ECK will not be (cleanly) possible in D8. More precisely, derivatives will not work with object-based plugin definitions. Note that this point is not to be underestimated. It is pure coincidence of the D8 development cycle that entity types are the only object-based plugin definitions in core; with the general move to OOP, object-based plugin definitions can in fact be considered preferred over the array-based ones and will likely be very common in contrib.

And finally let me also restate that I will not even be pissed (albeit disappointed) if the decision is made to push this to D9 bearing all the above in mind. It just was not at all evident from the above that there is such an awareness, which is why I resisted to postponing this - to my mind - prematurely.

Hope that helps clarify where I am coming from , and I look forward to everyone but especially @Xano and @xjm correcting me where I am wrong, over-simplified things, missed things, etc.!

Fabianx’s picture

Re-adding tag.

xjm’s picture

Category: Bug report » Task
Status: Needs work » Needs review

Many thanks @tstoeckler for the very detailed comments and thoughtful feedback!

I still feel the risk of this patch is much too high given how close we are to an RC, but I haven't had a chance to follow up on all of @tstoeckler's points yet so I'll hold off on commenting more until we've taken a careful look. The suggestions for reducing the BC breaks are good insights, but I'd suggest not attempting to implement them unless/until the points raised above have been reviewed (specifically by a framework manager; thanks @Fabianx for tagging).

That said, I am still going to set this back as a major task, because it in itself is not a bugfix. Incomplete or faulty API design that may result in bugs is not in itself a bug. Refactoring to reduce fragility and provide a more robust API is a task. This issue is definitely a major one.

For #2485513: DefaultFactory cannot deal with objects as plugin definitions, I actually thought the current patch in that issue (before it was postponed) was an acceptable workaround during this phase of the release cycle.

xjm’s picture

Issue tags: +D8 Accelerate London
catch’s picture

Thanks for the summary in #108.

I didn't review the patch in full, but it looks like the changes outside the plugin system itself are predominantly in Views and test coverage.

Apart from that the component -> core change and interface type hints do stick out. Core/Component is a fairly arbitrary split so I'm meh on that change - we can add a @todo to clean things up later if we have to (i.e. we could add the core class, have it subclass the one in component, deprecate the component one immediately).

However the interface type hint is going to make it easier rather than harder for contrib modules to deal with the API change here I'd expect, so not sure removing that would be worth it. And then if contrib had to update for that change, it could also do the rename for component/core at the same time.

So agreed with @xjm, not worth trying to reduce the footprint of the patch until we've discussed a bit more whether that's worth it.

Are we sure that the only affect on contrib modules will be:
1. The core/component change
2. The interface type hint
3. Array merging - which seems mostly theoretical?

Would be good to get Berdir's opinion since he has the most familiarity with 8.x contrib and maintains some custom modules too.

However on that rename and type hint, if we did remove those changes and did the automatic array to ArrayAccess conversion, then I'm wondering whether the patch would be something we could get into a minor version (i.e. if it did slip 8.0.0, move it to 8.1.x rather than 9.x).

For example I got CacheArray into 7.x as a new API, and converted places like the theme registry and schema cache to use it. While theoretically people could have used array_* methods on those arrays, in practice almost no-one did and those patches went relatively smoothly. I don't think we've discussed array -> ArrayAccess for minor versions at all.

Also I don't see Tim Plunkett or Alex Bronsteing on this issue at all, and EclipseGc and neclimdul only once each, so tagging 'needs subsystem maintainer review'.

So I don't have a strong view on getting this in before RC yet (I would not object to it going in, but I also don't feel very strongly that it must), but it's definitely worth leaving the issue at 8.0.x to figure out ways that we can either do that (as is or with smaller sets of changes) or allow it into a minor release if not.

Xano’s picture

The creation of the definition objects from annotations is done in Drupal\Core\Annotation instead of Drupal\Component\Annotation, which was assumingly done to keep the code in Drupal\Component\Annotation self-contained and without too many assumptions. This leads to the fact that many plugin annotation subclasses need to change their base class from e.g. Drupal\Component\Plugin\Annotation to Drupal\Core\Plugin\Annotation. We could avoid this by simply putting the mentioned code in Drupal\Component\Annotation\Plugin (et al.) directly.

This is done in the Plugin annotation classes, and both in component *and* core, because the definition interfaces for core extend those of component. We cannot avoid this, nor do we want to, because then we'd only fix the problem in core and not in the plugin component itself.

We could remove some of the PluginDefinitionInterface typehints, so that code that extends the base classes does not need to be updated to comply with the typehints. Specifically, DefaultPluginManager::processDefinition() comes to mind.

I only added them where it helped me debug. I did not consistently change/add type hints for all plugin definition parameters. I left that for follow-ups.

This might be going a bit far, but we could even do things like check if a plugin deriver returns array definitions, and convert them to objects, when retrieving them in DerivativeDiscoveryDecorator. That way not even derivers would need to be changed and the ugliness would combined in a single place (with a big fat @todo D9) instead of scattered all around core and contrib.

This would indeed save a lot of (simple changes). The plugin system would become somewhat inconsistent, though, and we'd lose some of the debugging perks that come with typed values.

For #2485513: DefaultFactory cannot deal with objects as plugin definitions: DefaultFactory cannot deal with objects as plugin definitions, I actually thought the current patch in that issue (before it was postponed) was an acceptable workaround during this phase of the release cycle.

That's not a workaround, but a hack that fails the moment someone does something different than what core does. Magic solutions like those only waste people's time when things go wrong, because it makes debugging and refactoring really hard.

I didn't review the patch in full, but it looks like the changes outside the plugin system itself are predominantly in Views and test coverage.

Indeed. The menu system also required more work than any other subsystem, but tests and Views were most needy.

Array merging - which seems mostly theoretical?

Plugin definition arrays are merged quite often. Most notably in derivers, but also elsewhere (Views, again, does this a lot). Check the patch for occurrences of PluginDefinitionInterface::mergeDefaultDefinition() and PluginDefinitionInterface::mergeOverrideDefinition().

However on that rename and type hint, if we did remove those changes and did the automatic array to ArrayAccess conversion, then I'm wondering whether the patch would be something we could get into a minor version (i.e. if it did slip 8.0.0, move it to 8.1.x rather than 9.x).

We'd still break a few things. There are some leftover array typehints, and the previously mentioned array merges.

Xano’s picture

I will be able to work on core full-time for the next few weeks at least. This means that, if this issue gets a tentative green light, I will have enough time to address the feedback and drive this issue home.

As such, I would really appreciate framework managers and subsystem maintainers to address the concerns raised in recent issues and decide whether we can still try to get this in before RC1.

Xano’s picture

Category: Task » Bug report

Re-roll, so we can continue to evaluate the patch. I will incorporate recent feedback in the next patch.

Incomplete or faulty API design that may result in bugs is not in itself a bug. Refactoring to reduce fragility and provide a more robust API is a task. This issue is definitely a major one.

It may not result in bugs. It already does. See the related issues, such as #2458769: Derivative plugin definitions contain base plugin IDs instead of derivative IDs, which this patch fixes out of necessity. That is why I am re-classifying this issue itself as a bug.

Xano’s picture

FileSize
398.22 KB

*drum roll*

Now with a Brand New Improved™ patch.

Status: Needs review » Needs work

The last submitted patch, 116: drupal_2458789_115.patch, failed testing.

Xano’s picture

Status: Needs work » Needs review
FileSize
5.41 KB
399.36 KB

This patch addresses the feedback given in #98 and fixes the test failures.

Status: Needs review » Needs work

The last submitted patch, 118: drupal_2458789_118.patch, failed testing.

Xano’s picture

Status: Needs work » Needs review
FileSize
7.14 KB
399.78 KB

Status: Needs review » Needs work

The last submitted patch, 120: drupal_2458789_120.patch, failed testing.

Xano’s picture

Status: Needs work » Needs review
FileSize
2.28 KB
399.97 KB

We can't easily let base derivatives provide empty arrays to override the non-empty arrays provided by regular derivatives. This has no consequences for core, though.

Status: Needs review » Needs work

The last submitted patch, 122: drupal_2458789_122.patch, failed testing.

martin107’s picture

Status: Needs work » Needs review
Issue tags: +Needs reroll
FileSize
400.1 KB

Reroll.

Lots and lots of automerging, 5 seemingly trivial conflicts to resolve. Limited testing - that site install works and basic pages render is about all I can say.
I will come back of fix any issues testbot finds.

martin107’s picture

Issue tags: -Needs reroll

Doh

Status: Needs review » Needs work

The last submitted patch, 124: drupal_2458789_124.patch, failed testing.

martin107’s picture

Is is possible for this issue to go in before 8.0.0? -- I hope so.

I am not addressing the errors in the comment... I wanted to give the new interfaces a quick look over before they become locked down for a number of years.

Found nothing worth talking about save a couple of minor @throw comments that were missing.

daffie’s picture

Status: Needs work » Needs review

Status: Needs review » Needs work

The last submitted patch, 127: drupal_2458789_127.patch, failed testing.

Xano’s picture

Several people have indicated that this issue will not be going anywhere without feedback from subsystem maintainers (we have 3 of them) and framework maintainers, none of whom have been able to review this issue so far. This essentially means this issue is blocked on the simple fact that at least four (!) required key people do not have enough time to review the concept (not necessarily the patch just yet), which is a very bad thing and should not happen in our issue queue. It's been over a month now, and we'd like to see this issue move forward, in whichever form is the best.

We can potentially fix this in a way that does not break BC. We'll keep an API that is fundamentally flawed, but that will support typed definitions. In short this means that array *and* object definitions will be supported.

tim.plunkett’s picture

Status: Needs work » Needs review
Issue tags: -Needs subsystem maintainer review

Well I can say that this approach is wonderful and sound and I have no problem with it, except that it's August 2015 and it's way too late IMO.
So, let's just be blocked on a framework manager.

Xano’s picture

Thanks, Tim, for stating your position on this. What do you think of the non-BC-breaking solution of supporting both arrays and PluginDefinitionInterface? It won't clean up the API, but it *will* allow us to fix the bugs in core and allow contributed and future core plugins to provide typed definitions.

xjm’s picture

Category: Bug report » Task

@Xano asked me to respond here and expressed concerns that I had not responded further or posted my thought process in more detail. Note that @catch did respond a month ago in #112 and I considered this issue off my plate at that point.

Frankly this is simply a lower priority than managing and fixing the critical issues blocking Drupal 8's release, which is how I spend all of my time. I'd hazard that's why there haven't been further responses from other committers as well. And that is exactly why I think this should be postponed to 8.1.x or later: No matter whether we can make it less disruptive, no matter whether we can break it into smaller BC chunks, the resources required and the risks added by making these changes when we are almost to RC is just not justifiable for me, especially when the other option is to spend that time directly on criticals. This is why we have a beta policy: to help us separate the things that we should do, but aren't worth making 8.0.0 take longer, from the things that we should do that are unlikely to make 8.0.0 take longer or that need to be done even if they do make 8.0.0 take longer. @catch's "I'd be okay with this not going in before 8.0.0" is, for me, a reason in itself to postpone this or any other large non-critical effort, no matter how beneficial.

Note that I am not speaking for the other committers. I still think it should be postponed, even after reviewing @tstoekler's feedback; @catch doesn't think it should be postponed until we've explored less disruptive options, but also isn't worried if it doesn't go in; @alexpott hasn't weighed in again since #108; @effulgentsia hasn't reviewed it at all. If it were a year ago, I'd propose scheduling a meeting with those guys and some of the relevant component maintainers and other stakeholders to hammer out the path forward. But it's not a year ago, and every hour of most of those folks' available time is currently going into trying to get D8 released.

Again, is not a bug; please do not set the category back to that. #2485513: DefaultFactory cannot deal with objects as plugin definitions is the bug, and can be solved without this in a 2K patch rather than a 400K one. It may be ugly and a hack and not the ideal fix, but we are at the phase in the release cycle for ugly, non-ideal hacks, not the phase for thorough and excellent architectural improvements.

Let me reiterate that none of this is meant as criticism of the patch itself. It's great work. It's just too late, and I think it was already too late when the issue was posted. Basically I agree with what @tim.plunkett said in #131.

jibran’s picture

Version: 8.0.x-dev » 9.x-dev
Status: Needs review » Postponed
Issue tags: -Needs change record, -Needs framework manager review

Seems like @Xano is moving forward with #2485513: DefaultFactory cannot deal with objects as plugin definitions and we can also fix #2458769: Derivative plugin definitions contain base plugin IDs instead of derivative IDs without this issue. I think it is safe to move to 9.x now after 22 days of no disagreement. Thanks @xjm for clearing this up. Thanks @Xano for working on this.

We can re-add needs tag when the issue is ready.

tim.plunkett’s picture

Status: Postponed » Closed (duplicate)

PDI now exists, I'm closing this. If we want to bring in more of the Plugin.module functionality, that can go in targeted issues.

Version: 9.x-dev » 9.0.x-dev

The 9.0.x branch will open for development soon, and the placeholder 9.x branch should no longer be used. Only issues that require a new major version should be filed against 9.0.x (for example, removing deprecated code or updating dependency major versions). New developments and disruptive changes that are allowed in a minor version should be filed against 8.9.x, and significant new features will be moved to 9.1.x at committer discretion. For more information see the Allowed changes during the Drupal 8 and 9 release cycles and the Drupal 9.0.0 release plan.