Allow decoration of plugins

Created on 3 April 2018, about 7 years ago
Updated 6 June 2025, 6 days ago

Problem/Motivation

If people want to tweak existing plugins the only way at the moment is to extend and add a new plugin with a different functionality.
For example:
- It's impossible to tweak the default schema of a field being generated by a core field plug-in.
- It's impossible to tweak UI for such customization.

In the end people are just doing separate field type plugins that result in non-semantic distribution of fields, for example:
- String
- String with index.
- String with unique index.
- Short string.
- ... etc.

It is an awful DX, because once you define a new field type, there is no way to reuse existing widgets and formatters for it. You need to define new ones as well (even if they are dummy ones - empty class bodies).

This can be extended, as we can have more granular and single-purpose decorators for whole plug-in types. Imagine the whole logging related to a plugin hierarchy can be standardized by a single decorator implemented by the module maintainer.

Proposed resolution

It will be awesome to have a mechanism that will allow for concrete plugins to be decorated (somehow) based on the interface they expose. This should work in a similar fashion to the services decoration provided by the container.

Plugin decorators should be also decoratable.

Remaining tasks

Discussion, architectural idea for implementation patches etc...

User interface changes

None expected out o this change.
Contributed code will have better capability to customize core's behaviors.

API changes

API addition on the plug-in system.
I am imagining a standard trait to be added to plug-in managers that will decorate plugins after they are fetched by ID.

Data model changes

TBD.

✨ Feature request
Status

Active

Version

11.0 πŸ”₯

Component

plugin system

Created by

πŸ‡§πŸ‡¬Bulgaria ndobromirov

Live updates comments and jobs are added and updated live.
Sign in to follow issues

Comments & Activities

Not all content is available!

It's likely this issue predates Contrib.social: some issue and comment data are missing.

  • πŸ‡¬πŸ‡§United Kingdom joachim

    That contrib project https://www.drupal.org/project/plugindecorator β†’ now says it's obsolete, but there's no explanation of why or whether something else has replaced it.

  • πŸ‡¬πŸ‡§United Kingdom joachim

    Revisiting this a little.

    I think one of the problems with decorating plugins is that plugin classes tend to have a LOT of methods. Services that get decorated are fairly slim. When we have a stack of services, they're often classes with only 1 or 2 methods. We have some with more methods -- e.g. WorkspacesEntityRepository, which has to have 5 methods which just relay the call to $this->inner.

    But plugin classes often have FAR more functions.

    BlockBase has over 20.
    Views' PluginBase has 32, and that's only generic base -- FilterPluginBase has 57!

    That's a TON of boilerplate to add to a decorator class.

    There's the technique of adding a magic __call(), which some services do, e.g. AnnotationBridgeDecorator.

    But magic call is slow -- according to this, 5x slower on php 8: https://gist.github.com/bwaidelich/7334680

    So we'd probably want each plugin type to provide a trait which is all of the base class's methods, calling $this->inner, for decorator classes to use so they can just implement the methods they care about.

    Or we look at #2754071: Introduce plugin handlers β†’ , and make plugins smaller, more focussed classes, which would therefore be easier to decorate.

  • πŸ‡©πŸ‡ͺGermany mxh Offenburg

    Another factor of hidden cost: Majority of plugins can be instantiated multiple times per request, whereas services on the other hand often are "shared" which means they are being instantiated once per request. So for "singleton" services this pattern may not have much of an impact, but for instantiating plugins this might be noticeable.

    Many oversee this hidden cost of the decoration pattern: once the parent class implements another method or logic, the decorating class must be at least checked and refactored when needed. Many times this is not really a justified tradeoff, especially when a class provides many methods.

    Even in the service workspace many modules don't follow this pattern. Instead, the class definition is being swapped out. One example is the contrib Token module: it replaces the token service with its own implementation by simply exchanging the class. So even if we have a general API provided to decorate plugins, still most modules probably won't follow up on it.

    Based on these findings I don't think it's worth to introduce another API layer for decorating. Also it seems the usage of the linked contrib module wasn't that high, so this may be of interest only for very few developers.

    Sometimes I wonder why Plugin API is not leveraging Symfony's dependency injection component by using service collectors for plugins and reinvents the wheel at some point.

    There are already ways of decorating a plugin if you really want to. But since there are multiple ways, everyone is doing it different. Maybe we can agree on a possible "non-intrusive" general way and just document it how to decorate a plugin?

    An important thing I miss since we replace Doctrine Annotations by PHP attributes is, that one is not able to add an arbitrary definition key anymore. One example are action plugins, where the contrib ECA module is adding its own meta information needed for properly grouping and documenting plugin definitions. We could think of introducing an "extra" key into PHP attributes for plugins, using a similar concept such as third_party_settings. Then we'd be able to put in class definitions that are being decorated by the current class definition of a plugin.

  • πŸ‡¬πŸ‡§United Kingdom joachim

    > Sometimes I wonder why Plugin API is not leveraging Symfony's dependency injection component by using service collectors for plugins and reinvents the wheel at some point.

    My feeling about that is that the service container becomes a bigger and bigger dumping ground.
    And services need to obtained by anything, potentially. Whereas plugins have a dedicated manager you ask for the plugin. I could envisage each plugin manager having a dumped PHP helper which handles instantiation and DI.

    > There are already ways of decorating a plugin if you really want to

    What methods are out there in the wild?

    > An important thing I miss since we replace Doctrine Annotations by PHP attributes is, that one is not able to add an arbitrary definition key anymore

    See ✨ Allow attribute-based plugins to discover supplemental attributes from other modules Active .

  • πŸ‡©πŸ‡ͺGermany mxh Offenburg

    Thanks for linking to the issue about supplemental attributes.

    What methods are out there in the wild?

    Only custom ways I guess. For example:

    • Implement the alter hook for the plugin definition. There, swap out the "class" definition by your decorator class, put the original class being decorated somewhere else - for example into a "myplugin_inner": $original_class (as mentioned this is possible with annotations but not yet with PHP Attributes). When being instantiated, the decorator then manually reads out the original class name from the plugin definition, instantiates an object of this original class definition and sets it as "inner" property.
    • Alternatively, decorate the plugin manager if needed, introduce an interface such as "DecoratorPluginInterface" for the plugin implementation. When instantiating, the plugin manager checks whether current class is implementing this interface and if so, looksup into the plugin definition for "myplugin_inner".
    • ... Or in the way the contrib plugindecorator module does it.
  • πŸ‡©πŸ‡ͺGermany rgpublic DΓΌsseldorf πŸ‡©πŸ‡ͺ πŸ‡ͺπŸ‡Ί

    The problem with most custom ways is that they are usually not easily "stackable". If you have two modules that need to these alterations then... pandemonium frequently ensues :-)

    One reason for the low usage of the contrib module might also have been that you introduce a dependency on sth. that looks a bit "hacky" exactly because it's not a solid part of the core. There's not really a reason why you wouldn't use one of the "custom ways" directly if you really needed that functionality in one of your own modules.

    I still think it's a valid feature request. Drupal's strength is its best-in-class adaptability. Not being able to decorate plugins easily is really a sore point IMHO. I'm not seeing the trouble with performance either. I could cite the famous "Premature optimization is the root of all evil", but instead I'd like to make the following two arguments:

    1) If it really turns to out to be problematic in reality, at the very least the decorator mechanism could be implemented in such a way that it only kicks into gear in case sth. really gets decorated. That would eliminate most of the overhead.

    2) We have many otger parts in Drupal that could also be seen as just overhead from a performance POV, but are necessary to keep Drupal that wonderfully flexible CMS that it is. I wouldn't be overly worried only for those plugins.

Production build 0.71.5 2024