Problem/Motivation
Plugin discovery allows retrieval of plugin definitions.
Most plugin types support an alter on that list, but the result of those alters is cached and can only be used to fully remove a single plugin.
If the list of plugins needs to be filtered at runtime, it is currently up to the code that uses the definitions to support their own filtering.
Proposed resolution
Provide a unified mechanism for retrieving a filtered list of plugin definitions.
Remaining tasks
User interface changes
N/A
API changes
API addition
Deprecate \Drupal\Core\Plugin\Context\ContextAwarePluginManagerInterface::getDefinitionsForContexts()
Data model changes
N/A
Comments
Comment #2
tim.plunkettRough first approach.
Currently includes code from #2946227: Provide ability to alter blocks presented in ChooseBlockController
Comment #4
tim.plunkettComment #5
eclipsegc commentedTim and I discussed a bunch of this code earlier and we has a use_case kind of string instead of a $type/consumer. I still think that's probably a good approach and reduces the number of parameters.
That said I 10000000% support the addition of this to core. We've needed plugin filtering for a long time, and while I've attempted to do this at times in other code, this is a much better total approach.
Eclipse
Comment #6
eclipsegc commentedTim and I discussed this, and he convinced me that my objection isn't warranted. On top of that, the test looks good but there are a lot of docblocks that need addressing yet.
Let's do
Need docs
This isn't really a repository. It's more of a "Filterer" but I'm not sure that's a good name either. I know I used "Mutator" for anything that changes the list of definitions in my plugin rewrite. I'm not certain that's good either. PluginDefinitionFilterCollector? I dunno
This doesn't look right, but I've not messed with an api file in a while.
Comment #7
samuel.mortensonI addressed #6. I ended up renaming "PluginDefinitionRepository" to "DiscoveryFilterer", since to me that accurately described what was going on. What do people think of that?
Comment #8
tim.plunkettI like the name better.
The idea of introducing a new hook was bugging me. I think it would work fine, but wanted to try something else out.
Here's a tagged services approach.
Any preferences for this over hooks?
Comment #10
samuel.mortensonI think to answer this we need to think of the 80% use case - what's the most common operation for filtering plugin definitions? In my experience I've maintained a hard-coded list of definitions I want to hide (unset from the list), but others may have more complex filtering needs. Can we think of examples of complex filtering use cases that would do better as services?
Comment #11
johnwebdev commentedFrom a DX-perspective I think this is better than a hook.
Comment #12
swentel commentedI have to admit I like the hook version as well, looking from a themer experience. Themes can implement this in the theme_name.theme and optionally maybe use theme settings to configure the visibility without having to create a module for it. But I'm fine with whatever solution goes in, as long something goes in :)
Comment #13
tim.plunkett#11
You can always delegate your hook implementations to a class (see
layout_discovery_theme()as an example).Your final two points can be (horribly) accomplished via
hook_module_implements_alter(), though definitely not ideal.#12
Allowing themes to participate in this is a huge plus for hooks, not sure why I didn't think of that.
I think we should pursue the hook-based approach for now.
Reuploading the patch from #7
Comment #14
tedbowSpelling definitins
s/$hook/$hooks
Should also be sending the
$contextto the callables?Then a module could use the same callable but have logic based on the context.
I am not real clear why the hook was written this way.
It seems weird to me provide a hook so that modules can provide an "array of callables". If we are going to use hooks why not call the hook after the definitions are collected and then the hooks themselves would alter the list?
Is the pattern in the current patch used in core elsewhere I could look at?
Spelling definitins
Spelling definitins
Since *.api.php files are good way to show examples of how hooks could be used should we use a different example here from above?
Is there reason we aren't passing
$deltaand$regionin$context?It seems this would give more flexibility to contrib especially to not allow certain blocks in certain regions. Not sure about delta.
We should also have generic test for the
DiscoveryFiltererclass working with the hooks.Comment #15
tim.plunkett#12
Well, let's actually add the code that allows themes to participate :)
#14
1) Fixed
2) Fixed
3) Yep, agreed. Let's also send the $consumer, so that the generic hook can do it's own logic based on that.
4) I kinda agree. It was originally written to support ContextFilter, but I think context is central enough of a concept to include on its own.
5) Fixed
6) Fixed
7) Expanded the first example, and clarified the second example.
8) Good idea! Added region
9) Agreed, added.
Also moved the original test added in the patch from 2946227 to be in an existing test.
1fdf47dd4f fix typos
56306d1a3d Get rid of ContextFilter in favor of default filtering by contexts
6068f8157d Expand implementors
fb495dc1d3 adjust the hooks to filter directly
52573e6eeb expand the API docs
14336d8d3c Expand to themes
0567c9f7c8 include region as part of the extra context
93cd0a3ad3 fixup! Expand to themes
78bbbd984d fix tests
https://github.com/timplunkett/d8-layouts/tree/2949177-plugin_filter
Comment #17
tim.plunkettFixed BlockFormTest
Comment #18
jibranHere are some observations.
I don't think we use __ pattern in core anywhere.
Let's explain TYPE a little with example.
Let's explain TYPE and CONSUMER a little with example.
Comment #19
tim.plunkettWe don't concatenate two other strings to make one hook in other places either.
The double underscore is to avoid clashes.
Leaving the other two points for tomorrow, unless someone else beats me to it.
Comment #20
mark_fullmerThe attached explains the hooks' usage of TYPE & CONSUMER with examples.
Comment #21
tedbowShould we also deprecate
\Drupal\Core\Plugin\Context\ContextAwarePluginManagerTrait?Should we say "allows modules and themes"? Not sure if this is just assumed.
I am not sure where the definitions are getting sorted.
Neither
\Drupal\Component\Plugin\Discovery\DiscoveryInterface::getDefinitionsnor\Drupal\Core\Plugin\Context\ContextHandlerInterface::filterPluginDefinitionsByContextsmention sorting in the docs.We should add something here about if the contexts are provide they will used to filter.
I like that change in hook allows the example hooks to be much simpler.
Further down in this method we have
$region = $request->query->get('region');It seems like it would make sense to move up so that
$regionif available could be added to$extraThis would allow a module disallow certain block within certain regions on the main block admin page.
I was going to suggest that we pass
$deltain$extrahere with idea that it would allow say restricting a "header" layout to only be added at the top of the page.But then I realized you can still reorder the sections(correct?) once they are placed so would not be effective.
So unless anyone else can think of another reason for passing
$deltait seems find the way it is.I know this is just test code but it seems overly complicated for the test.
Test modules also can serve as ways to learn the system and this seem to bring unnecessary complexity that makes it harder to under what is possible.
It could be simplified as
Comment #22
tim.plunkettThanks @mark_fullmer!
#21
1) Yep! Also used the correct deprecation message format.
2) Adjusted.
3) Fixed
4) Changed this code, since filtering by an empty set of contexts could be needed.
5) Thanks
6) Good idea!
7) I also thought about adding delta, but I similarly can't see a use case.
8) That example is pretty advanced, but it's also pretty handy. People could use it as a real example of how to maintain a whitelist/blacklist of field names...
I'm going to keep it for now.
Also renamed from DiscoveryFilterer to PluginDefinitionFilterer, and added an interface.
Comment #23
tim.plunkettHere's an interdiff but without the rename.
Comment #24
tedbowLooks good to me!
We should change record about the new hooks.
Comment #26
tim.plunkettGood idea, wrote https://www.drupal.org/node/2961820
Also fixed up one failure, had to open #2961822: Support object-based plugin definitions in ContextHandler as a follow-up.
Comment #27
tedbow@tim.plunkett thanks for the CR
Assuming the tests pass I am +1 for RTBC
Assigning to @EclipseGc as he said he was going to take a look.
Comment #28
eclipsegc commenteduhhh yes! Tim and I walked through this together, and plugin filtering is a thing we've needed since like... day 2 of D8, so yeah this is good to have in general and gives us some really amazing possibilities for Layout Builder as the patch is right now.
RTBC
Eclipse
Comment #29
tim.plunkettOh, before this is committed: @tedbow provided *significant* feedback and direction on the final approach, please credit him.
Comment #30
eclipsegc commentedComment #31
andypostCR needs to mention about deprecated methods and trait
That could be done in follow-up but we have policy for that https://www.drupal.org/core/deprecation
Comment #32
tedbowMaybe this could be follow ups but...
@seeto\Drupal\Component\Plugin\Discovery\DiscoveryInterface::getDefinitions()that points to\Drupal\Core\Plugin\PluginDefinitionFilterer::get()that indicates that if you displaying the plugins to the UI you should usually usePluginDefinitionFilterer::get()Otherwise I wondering how coders would find this new functionality.
If am a developer and a write plugin types that I am going expose in a form and if the plugin type should be filtered by context I will find the new functionality because
\Drupal\Core\Plugin\Context\ContextAwarePluginManagerInterface::getDefinitionsForContextshas been deprecated and points toPluginDefinitionFiltererInterfaceOn the other hand if I am writing a plugin type that is not filtered by context, like image effects, then I would use
\Drupal\Component\Plugin\Discovery\DiscoveryInterface::getDefinitionsto retrieve the definitions when I want to display a list of plugins.For instance in
\Drupal\image\Form\ImageStyleEditForm::formhasIf a module wanted to limited the list available plugins here, for instance to only only instance of scaling plugin per image style, then they would have use a form alter. But if the code used
PluginDefinitionFilterer::get()then the new hooks could be used to filter in this situation(provided enough metadata was passed in$extra.\Drupal\Component\Plugin\Discovery\DiscoveryInterface::getDefinitions()with\Drupal\Core\Plugin\PluginDefinitionFilterer::get()One example would be the
ImageStyleEditFormexample above.Comment #33
tim.plunkett1)
Based on my attempts in #2946227: Provide ability to alter blocks presented in ChooseBlockController I'm reconsidering the deprecation.
There is at least one use-case for filtering by context but not invoking the alter: building a UI that provides the data for the hook itself.
Switching from @deprecated to @see for that, and adding it to getDefinitions()
2)
Great idea! Opened #2962421: Use the new PluginDefinitionFilterer in any UI code that presents available plugins.
No longer doing #31 due to my change for #32.1
After further discussion with @samuel.mortenson and @tedbow, it became clear that while it was nice to have a standalone service, it broke with the standard convention of "ask the plugin manager for things". While composition over inheritance is a good principle, in this case it just resulted in hiding the feature where no one would ever find it.
Furthermore, having to pass in a string declaring which type of plugin you're retrieving, as well as the plugin manager itself, made very little sense.
What would happen if you passed in BlockManager but told the hook they were layouts?
So instead, those plugin managers wishing to support filtered definitions will need to properly declare their plugin type as a string.
Comment #35
tim.plunkettPHP's trait collision handling is abysmal. Oh well, this should fix it.
Comment #37
tim.plunkettSloppy work on my part :(
Comment #38
andypostwhy not to use class variable?
why not to add protected vars for static cache?
Comment #39
tim.plunkett1) Only by using an abstract method can we enforce that the user of the trait actually specifies a type.
2) PHP 5.5 and 5.6 have issues with the properties clashing between traits and implementations.
If one wanted to use proper dependency injection, they can override these methods and reference the property
Comment #40
tedbowLooking really good! I like the changes seems like it is obvious on how to use the new logic.
Setting to "Needs work" but only for comment changes I mention below. But also could probably be talked out of them if others think they aren't needed.
I was confused as why this patch need this change.
Seems that changes to
\Drupal\layout_builder\Controller\ChooseSectionController::build()made this necessary.But isn't strictly related to the new interface because we would have run into the same problem even without the patch if LayoutPluginManagerInterface extended ContextAwarePluginManagerInterface.
Correct?
Should be put @see here or anywhere in this interface about the new hooks? Others reading this you might assume the filter is only context filter and not take into consideration the hooks.
Looking back at #21.2 it looks like we had a comment of "which is useful for tasks" has to why you would want to get filtered plugins. which I am not sure is anywhere in the new version.
Should we say "usually a module or theme" or is it always? I guess I profile could implement this too? An extension short name?
Comment #41
tim.plunkett1) Correct, this change is indeed because of layout plugins. Layouts and Entity Types are the only two plugins so far that utilize object-based definitions (as opposed to the legacy approach of using arrays).
I'd prefer to introduce this workaround and @todo here, but if that's what holds this issue up we can remove the change in ChooseSectionController
2) In the refactor I accidentally dropped those @see's and the extra docs. Restored them.
3) The consumer is sometimes neither of those things. It can be any string, really. For example, to better differentiate "block" from "block" (from "block" from "block") I made the consumer string
block_uifor the two calls within the Block UI.Comment #42
tedbowThis looks good to me now. I am +1 for RTBC.
Re #31
The patch no longer deprecates these.
@EclipseGc RTBC'ed in #28 but the change is very different now so maybe he should take another look.
Comment #43
eclipsegc commentedThis looks really good actually. My only misgiving is that we're hard-coding consumer ids, which will make it difficult to build new UIs later for limiting this stuff in a UI, but I'm unconvinced that's a real use case given the nuance in this code, and also seems like the sort of thing we could change at some later date.
I'm rtbcing this.
Eclipse
Comment #44
tedbowGreat
Nit should this function have the argument
array $extraeven if it isn't used?Comment #45
tim.plunkettPersonal preference, I'm one for leaving out the extra arguments
Comment #46
larowlanthis is a BC break, should we instead just have BlockManager implement it direct? Then there is no BC break.
I know its unlikely that someone has an implementation of a block manager that doesn't extend the existing one, but it is allowed, so unless I'm mistaken, I think we have to honour it.
Because most plugin managers will extend from DefaultPluginManager, should we check for presence of $this->moduleHandler first here and avoid the call to the container?
Comment #47
tim.plunkett1)
- https://www.drupal.org/core/d8-bc-policy#interfaces
This certainly applies here.
It would be prohibitively awkward to have to instanceof check within classes that typehint on BlockManageInterface but want to use the new method.
2)
Fair enough. Did it for theme manager too, even though that probably won't be available.
Comment #48
tedbowAgree with #47.1 that it seem like we are able add methods via adding the interface.
#47.2 also seems like good fix.
Comment #49
larowlanI'll discuss the impact of the new method, in particular whether it impact ability to backport, with the other framework managers
Comment #50
larowlanConsensus was this isn't eligible for backport, because of the new methods etc.
Adding review credits
Comment #51
larowlanCommitted e91d969 and pushed to 8.6.x
Published change record