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()
andPluginDefinitionInterface::mergeOverrideDefinition()
, and loops (for which the deprecated methodArrayPluginDefinition::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
- #2458769: Derivative plugin definitions contain base plugin IDs instead of derivative IDs
- #2485513: DefaultFactory cannot deal with objects as plugin definitions
Beta phase evaluation
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. |
Comment | File | Size | Author |
---|---|---|---|
#127 | interdiff-124-127.txt | 3.05 KB | martin107 |
#127 | drupal_2458789_127.patch | 400.24 KB | martin107 |
Comments
Comment #1
XanoWith
\ArrayAccess
for backwards-compatibility, as discussed with @EclipseGc.Comment #2
XanoAdded issues that would benefit from this approach.
Comment #3
XanoSo that was a first stab. I'll add testing coverage and a testing implementation using all the defaults from
DefaultPluginManager
.Comment #4
dawehnerWhy is this a task and not something like a feature request?
Comment #5
XanoI 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).
Comment #6
XanoI 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 overrideEntityType::setClass()
with a different type check.Comment #7
XanoThe override is not a clean implementation of
PluginDefinitionInterface
, but it doesn't change the current behavior. I added the check forEntityInterface
to make sure there is no confusion aboutEntityType::setClass()
and the super method.Comment #9
XanoComment #10
XanoComment #11
neclimdultype hint?
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
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.
Comment #12
XanoScalar type hints are not possible in PHP <7 (the parameter is a class name and not an object instance).
Good point. I'm not sure yet. We would also have to introduce an interface for the label then.
Comment #13
Xano#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 byDerivativeDiscoveryDecorator
.Comment #14
Xano#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?
Comment #17
XanoRe-roll.
Comment #18
lauriiiOops :)
Comment #19
lauriiiComment #20
tstoecklerDidn't get all the way through, but here we go:
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.I think
label
andderiver
should be dropped, as they are not used for all plugin types. A lot of the Views plugins in particular extend thePluginId
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.
Unfortunately this won't fly. (Missing one-line docs)
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.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.
Comment #21
XanoIt'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.
The department of redundancy department ;-)
PluginDefinitionInterface
is part of\Drupal\Component
, so we can't specifically mention anything that belongs to\Drupal\Core
.IMHO it should be required, but seeing as entities are an exception to this convention, that's enough precedence to drop this requirement.
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.Comment #22
XanoNow with amazeballs interdiff!
Comment #23
jibranIf @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.
@var :)
Theses should be one line 80 char limit doesn't apply to description.
We are not typecasting here so would that fine?
Comment #24
XanoWe could potentially drop
\ArrayAccess
support fromPluginDefinitionInterface
by shipping with anArrayPluginDefinitionDecorator
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 implementPluginDefinitionInterface
.They are
@var
descriptions, so not limited to one line.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
.Comment #26
XanoAnother 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.
Comment #27
XanoComment #28
lauriiiI would like to see
PluginDefinitionInterface
not implement\ArrayAccess
. It allows us to abstract thePluginDefinitionInterface
into smaller interfaces without causing major DX regression. I like the idea of usingArrayPluginDefinitionDecorator
to still support Array type of plugin definitions.Comment #29
XanoIdeally we'll remove
/ArrayAccess
fromPluginDefinitionInterface
. 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.
Comment #30
Fabianx CreditAttribution: Fabianx as a volunteer commentedThis 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.
Comment #31
XanoThank you, @fabianx, for your review and advice!
This patch removes
\ArrayAccess
fromPluginDefinitionInterface
, splits upPluginDefinitionInterface
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.Comment #32
XanoAnd now an interdiff with moves.
Comment #33
XanoI now realize I forgot to update the plugin system classes that handle arbitrary plugin definitions. Will do that tomorrow.
Comment #35
XanoThis 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 themodule
key for array definitions instead of theprovider
key.\Drupal\Component\Plugin\Factory\DefaultFactory
depended on plugin providers, which are a core concept and do not existing in the plugin component.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.Comment #36
XanoI'm now exploring the options of continuing with #24. It seems
\Drupal\Core\Annotation\ContextDefinition
extends thePlugin
annotation class, but it's not actually a plugin annotation. Confusing. And a bug.Comment #37
XanoThis 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.
\Drupal\Core\Annotation\ContextDefinition
extended thePlugin
annotation class. This was incorrect and the annotation now extendsAnnotationBase
.Plugin
annotation classes (both core and contrib) now return all definitions as objects by default.\Drupal\Component\Plugin\Exception\InvalidDeriverException
was thrown when derivative decorators tried to instantiate derivers that were invalid. However, as this validation is now performed when setting deriver classes on plugin definitions, this type of error is now handled by plugin definitions throwing\InvalidArgumentException
.Comment #39
XanoRe-roll.
Comment #41
XanoArrayPluginDefinition
, 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.Comment #43
XanoComment #46
XanoComment #48
XanoComment #51
xjmJust 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.Comment #52
lauriiiCongrats
Comment #53
XanoI 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
andTypedDataManager
failures locally, and will upload a new patch once these problems are fixed, as they contributed to the testbot malfunctions.Comment #54
XanoComment #55
XanoComment #61
XanoComment #63
XanoComment #65
XanoComment #67
XanoComment #69
XanoComment #71
XanoComment #73
dawehnerMh I'm not sure why the generic one should care about contexts, given that most plugin definition give a shit about context
Comment #74
XanoIt 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.
Comment #75
XanoI 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.
Comment #77
XanoI think I fixed all bugs except the one that breaks Views.
Comment #79
XanoThis 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).
Comment #81
XanoFixed 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.Comment #82
XanoNote 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.
Comment #83
lauriiiDidn'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
No newline at end of file
Empty docblock
Comment #84
XanoThanks for the quick review!
This patch addresses the feedback from #83 and removes some debugging code.
Comment #85
XanoI 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:
\Drupal\Core\Annotation\Plugin
base class.new ArrayPluginDefinition()
.Comment #86
tstoecklerOnly got about half way through, but here's what I got:
We now allow
@inheritdoc
with additional stuff, so I think that should stay.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 ofmixed[]
that would additionally reinforce that this is an array.I think in other places we're calling this
toArray()
and that would make sense here as well, IMO.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)
andmergeOverrides($Overrides)
If this is required it proves that IMO we're doing something wrong. Suggestion:
1. Mark
getArrayDefinition()
/toArray()
no longer deprecated and havemergeDefaultArrayDefinition()
/mergeDefaults()
andmergeOverrideArrayDefinition()
mergeOverrides()
use that in case they are passed aPluginDefinitionInterface
(and remove thearray
typehints). Then document that both arrays andPluginDefinitionInterface
s are accepted with a@todo D9
to remove array support.Should this not be abstract?
Also
ArrayPluginDefinition
redeclares those as public, which is a bit strange.IMHO this is over-abstraction. I would rather have this logic in the two implementing classes by proper type-hinting, see my suggestion above.
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.
Why is this method override needed?
I don't think we have to do this API change, if we have
ArrayPluginDefinition
implementIteratorAggregate
and then dogetIterator { return new \ArrayIterator($this->arrayDefinition); }
The
_merged
business at the very least needs a code comment if we can't get around it somehow.Seems a bit over-powered to introduce a whole method for that, no?
Can't
ConfigEntityType
andContentEntityType
just override createDefinitionFromArray()?Seems like
validateClass()
should be overridden here?Leftover debug?
If this is the only use of
mergeDefaultArrayDefinition()
why can we not donew ArrayPluginDefinition($this->defaults)
first and then do a proper merge?Comment #87
XanoThanks 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 just wanted to be explicit and consistent with
::getArrayDefinition()
. I'm fine changing the name. The type documentation should stay atmixed[]
, as it is more specific thanarray
, while both are correct.It's less consistent with the internal property and class names, but we could do it.
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.
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.
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.
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.
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.They can. I don't like duplication.
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.Yes. The last patch already removed that (it seems you started your review before I uploaded that patch).
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.
Comment #88
XanoThis 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 onlyAnnotatedClassDiscovery
andYamlDiscovery
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.Comment #89
XanoThis patch makes
\ArrayAccess
optional, introduces iteration and count support forArrayPluginDefinition
, and splits up bothPluginDefinitionInterface
s 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!
Comment #91
XanoComment #92
XanoI 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.
Comment #95
XanoRe-roll.
Comment #96
XanoAny 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.
Comment #97
XanoComment #98
lauriiiI 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.
This is missing the param comment
Missing space before use statement
Missing line before use statement
Missing line before use statement
Indenting looks inconsistent
Probably xtest for debugging?
Comment #101
dawehnerThanks.
Yeah I think this is really not something we can still do, but sure, feel free to try to fight this.
:)
Comment #102
Xano@dawehner: I understand you see this change as more disruptive than the bugs it fixes. Could you please be more precise about why, though?
Comment #103
xjm@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.
Comment #104
daffie CreditAttribution: daffie commentedI am sorry for you Xano. You have put a lot of effort into this issue.
Comment #105
jibranWhat about #2458769: Derivative plugin definitions contain base plugin IDs instead of derivative IDs & #2485513: DefaultFactory cannot deal with objects as plugin definitions now? Those are bugs so how are we planning to fix those?
Comment #106
Xano@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?
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.
Comment #107
tstoecklerBefore 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
necessaryI 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.
Comment #108
tstoecklerHere'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 plusCountable
andTraversable
, 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 implementingPluginDefinitionInterface
(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 ofDrupal\Component\Annotation
, which was assumingly done to keep the code inDrupal\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
toDrupal\Core\Plugin\Annotation
. We could avoid this by simply putting the mentioned code inDrupal\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.!
Comment #109
Fabianx CreditAttribution: Fabianx as a volunteer commentedRe-adding tag.
Comment #110
xjmMany 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.
Comment #111
xjmComment #112
catchThanks 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.
Comment #113
XanoThis 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.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 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.
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.
Indeed. The menu system also required more work than any other subsystem, but tests and Views were most needy.
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()
andPluginDefinitionInterface::mergeOverrideDefinition()
.We'd still break a few things. There are some leftover array typehints, and the previously mentioned array merges.
Comment #114
XanoI 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.
Comment #115
XanoRe-roll, so we can continue to evaluate the patch. I will incorporate recent feedback in the next patch.
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.
Comment #116
Xano*drum roll*
Now with a Brand New Improved™ patch.
Comment #118
XanoThis patch addresses the feedback given in #98 and fixes the test failures.
Comment #120
XanoComment #122
XanoWe 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.
Comment #124
martin107 CreditAttribution: martin107 commentedReroll.
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.
Comment #125
martin107 CreditAttribution: martin107 commentedDoh
Comment #127
martin107 CreditAttribution: martin107 commentedIs 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.
Comment #128
daffie CreditAttribution: daffie commentedComment #130
XanoSeveral 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.
Comment #131
tim.plunkettWell 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.
Comment #132
XanoThanks, 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.Comment #133
xjm@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.
Comment #134
jibranSeems 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.
Comment #135
tim.plunkettPDI now exists, I'm closing this. If we want to bring in more of the Plugin.module functionality, that can go in targeted issues.