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

CommentFileSizeAuthor
#47 2949177-plugin_filter-47.patch26.63 KBtim.plunkett
#47 2949177-plugin_filter-47-interdiff.txt1.29 KBtim.plunkett
#41 2949177-plugin_filter-41.patch26.23 KBtim.plunkett
#41 2949177-plugin_filter-41-interdiff.txt1.14 KBtim.plunkett
#37 2949177-plugin_filter-37.patch25.95 KBtim.plunkett
#37 2949177-plugin_filter-37-interdiff.txt633 bytestim.plunkett
#35 2949177-plugin_filter-35.patch25.94 KBtim.plunkett
#35 2949177-plugin_filter-35-interdiff.txt3.56 KBtim.plunkett
#33 2949177-plugin_filter-33.patch26.02 KBtim.plunkett
#33 2949177-plugin_filter-33-interdiff.txt32.18 KBtim.plunkett
#26 2949177-plugin_filter-26.patch29.33 KBtim.plunkett
#26 2949177-plugin_filter-26-interdiff.txt908 bytestim.plunkett
#23 2949177-plugin_filter-22-partial-interdiff.txt7.82 KBtim.plunkett
#22 2949177-plugin_filter-22.patch28.44 KBtim.plunkett
#22 2949177-plugin_filter-22-interdiff.txt24.87 KBtim.plunkett
#20 2949177-plugin_filter-20.patch25.24 KBmark_fullmer
#20 2949177-plugin_filter-20-interdiff.txt1.62 KBmark_fullmer
#17 2949177-plugin_filter-17.patch24.48 KBtim.plunkett
#17 2949177-plugin_filter-17-interdiff.txt1.22 KBtim.plunkett
#15 2949177-plugin_filter-15.patch23.26 KBtim.plunkett
#15 2949177-plugin_filter-15-interdiff.txt24.78 KBtim.plunkett
#13 2949177-plugin_filter-13.patch15.45 KBtim.plunkett
#8 2949177-plugin_filter-8-interdiff.txt10.92 KBtim.plunkett
#8 2949177-plugin_filter-8.patch15.97 KBtim.plunkett
#7 interdiff-2949177-2-7.txt11.57 KBsamuel.mortenson
#7 2949177-7.patch15.38 KBsamuel.mortenson
#2 2949177-plugin_filter-2.patch14.69 KBtim.plunkett

Comments

tim.plunkett created an issue. See original summary.

tim.plunkett’s picture

Status: Active » Needs review
StatusFileSize
new14.69 KB

Rough first approach.
Currently includes code from #2946227: Provide ability to alter blocks presented in ChooseBlockController

tim.plunkett’s picture

Status: Needs work » Needs review
eclipsegc’s picture

+++ b/core/lib/Drupal/Core/Plugin/PluginDefinitionRepository.php
@@ -0,0 +1,60 @@
+  public function get($type, $consumer, DiscoveryInterface $discovery, array $filters = [], array $context = []) {
+    $hook[] = "plugin_filter_{$type}";
+    $hook[] = "plugin_filter_{$type}__{$consumer}";

Tim 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

eclipsegc’s picture

Status: Needs review » Needs work

Tim 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.

  1. +++ b/core/lib/Drupal/Core/Plugin/Context/ContextFilter.php
    @@ -0,0 +1,34 @@
    + * @todo.
    

    Let's do

  2. +++ b/core/lib/Drupal/Core/Plugin/Context/ContextFilter.php
    @@ -0,0 +1,34 @@
    +  public static function getFilter(array $contexts) {
    ...
    +  public function filter(array $definitions) {
    
    +++ b/core/lib/Drupal/Core/Plugin/PluginDefinitionRepository.php
    @@ -0,0 +1,60 @@
    +/**
    + * @todo.
    + */
    

    Need docs

  3. +++ b/core/lib/Drupal/Core/Plugin/PluginDefinitionRepository.php
    @@ -0,0 +1,60 @@
    +class PluginDefinitionRepository {
    

    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

  4. +++ b/core/lib/Drupal/Core/Plugin/plugin.api.php
    @@ -0,0 +1,45 @@
    +/**
    + * @addtogroup hooks
    + * @{
    + */
    

    This doesn't look right, but I've not messed with an api file in a while.

samuel.mortenson’s picture

Status: Needs work » Needs review
StatusFileSize
new15.38 KB
new11.57 KB

I 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?

tim.plunkett’s picture

I 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?

samuel.mortenson’s picture

Here's a tagged services approach.
Any preferences for this over hooks?

I 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?

johnwebdev’s picture

From a DX-perspective I think this is better than a hook.

  • It's object-oriented, well organised code v.s. a 900+ line .module file)
  • You can write unit tests easier
  • It's more reusable
  • You can override existing discovery filters
  • You can give filters priority, this is useful if you would like to keep definitions organised in a certain way and you need to ensure other filters are run first, though the name filterer can be a bit misleading, if you are allowed to move the definitions around (or even add new ones)
swentel’s picture

I 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 :)

tim.plunkett’s picture

Status: Needs work » Needs review
StatusFileSize
new15.45 KB

#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

tedbow’s picture

Status: Needs review » Needs work
  1. +++ b/core/lib/Drupal/Core/Plugin/DiscoveryFilterer.php
    @@ -0,0 +1,66 @@
    +   *   provided by the code requesting the filtered definitins.
    

    Spelling definitins

  2. +++ b/core/lib/Drupal/Core/Plugin/DiscoveryFilterer.php
    @@ -0,0 +1,66 @@
    +    $hook[] = "plugin_filter_{$type}";
    +    $hook[] = "plugin_filter_{$type}__{$consumer}";
    +    $this->moduleHandler->alter($hook, $filters, $context);
    

    s/$hook/$hooks

  3. +++ b/core/lib/Drupal/Core/Plugin/DiscoveryFilterer.php
    @@ -0,0 +1,66 @@
    +      $definitions = call_user_func($filter, $definitions);
    

    Should also be sending the $context to the callables?

    Then a module could use the same callable but have logic based on the context.

  4. +++ b/core/lib/Drupal/Core/Plugin/plugin.api.php
    @@ -0,0 +1,49 @@
    + * @param Callable[] $filters
    + *   An array of callables to filter the definitions.
    

    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?

  5. +++ b/core/lib/Drupal/Core/Plugin/plugin.api.php
    @@ -0,0 +1,49 @@
    + *   requesting the filtered definitins.
    

    Spelling definitins

  6. +++ b/core/lib/Drupal/Core/Plugin/plugin.api.php
    @@ -0,0 +1,49 @@
    + *   requesting the filtered definitins.
    

    Spelling definitins

  7. +++ b/core/lib/Drupal/Core/Plugin/plugin.api.php
    @@ -0,0 +1,49 @@
    +  $filters[] = function ($definitions) {
    +    // Explicitly remove the "Help" blocks from the list.
    +    unset($definitions['help_block']);
    +    return $definitions;
    

    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?

  8. +++ b/core/modules/layout_builder/src/Controller/ChooseBlockController.php
    @@ -42,7 +54,8 @@ public function __construct(BlockManagerInterface $block_manager) {
    @@ -63,8 +76,10 @@ public function build(SectionStorageInterface $section_storage, $delta, $region)
    
    @@ -63,8 +76,10 @@ public function build(SectionStorageInterface $section_storage, $delta, $region)
         $build['#type'] = 'container';
         $build['#attributes']['class'][] = 'block-categories';
     
    -    $definitions = $this->blockManager->getDefinitionsForContexts($this->getAvailableContexts($section_storage));
    -    foreach ($this->blockManager->getGroupedDefinitions($definitions) as $category => $blocks) {
    +    $filters[] = ContextFilter::getFilter($this->getAvailableContexts($section_storage));
    +    $definitions = $this->discoveryFilterer->get('block', 'layout_builder', $this->blockManager, $filters, ['section_storage' => $section_storage]);
    

    Is there reason we aren't passing $delta and $region in $context?

    It seems this would give more flexibility to contrib especially to not allow certain blocks in certain regions. Not sure about delta.

  9. +++ b/core/modules/layout_builder/tests/src/Functional/LayoutBuilderAPITest.php
    @@ -0,0 +1,75 @@
    +class LayoutBuilderAPITest extends BrowserTestBase {
    

    We should also have generic test for the DiscoveryFilterer class working with the hooks.

tim.plunkett’s picture

Status: Needs work » Needs review
StatusFileSize
new24.78 KB
new23.26 KB

#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

tim.plunkett’s picture

Status: Needs work » Needs review
StatusFileSize
new1.22 KB
new24.48 KB

Fixed BlockFormTest

jibran’s picture

Here are some observations.

  1. +++ b/core/lib/Drupal/Core/Plugin/DiscoveryFilterer.php
    @@ -0,0 +1,90 @@
    +    $hooks[] = "plugin_filter_{$type}__{$consumer}";
    

    I don't think we use __ pattern in core anywhere.

  2. +++ b/core/lib/Drupal/Core/Plugin/plugin.api.php
    @@ -0,0 +1,55 @@
    + * Alter the filtering of plugin definitions for a specific type.
    

    Let's explain TYPE a little with example.

  3. +++ b/core/lib/Drupal/Core/Plugin/plugin.api.php
    @@ -0,0 +1,55 @@
    + * Alter the filtering of plugin definitions for a specific type and consumer.
    

    Let's explain TYPE and CONSUMER a little with example.

tim.plunkett’s picture

We 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.

mark_fullmer’s picture

The attached explains the hooks' usage of TYPE & CONSUMER with examples.

tedbow’s picture

Status: Needs review » Needs work
  1. +++ b/core/lib/Drupal/Core/Plugin/Context/ContextAwarePluginManagerInterface.php
    @@ -20,6 +20,8 @@
    +   * @deprecated Use \Drupal\Core\Plugin\DiscoveryFilterer instead.
        */
       public function getDefinitionsForContexts(array $contexts = []);
    

    Should we also deprecate \Drupal\Core\Plugin\Context\ContextAwarePluginManagerTrait?

  2. +++ b/core/lib/Drupal/Core/Plugin/DiscoveryFilterer.php
    @@ -0,0 +1,90 @@
    + * This allows modules to alter plugin definitions, which is useful for tasks
    

    Should we say "allows modules and themes"? Not sure if this is just assumed.

  3. +++ b/core/lib/Drupal/Core/Plugin/DiscoveryFilterer.php
    @@ -0,0 +1,90 @@
    +   * Gets the plugin definitions for a given type and sorts and filters them.
    

    I am not sure where the definitions are getting sorted.

    Neither \Drupal\Component\Plugin\Discovery\DiscoveryInterface::getDefinitions nor \Drupal\Core\Plugin\Context\ContextHandlerInterface::filterPluginDefinitionsByContexts mention sorting in the docs.

  4. +++ b/core/lib/Drupal/Core/Plugin/DiscoveryFilterer.php
    @@ -0,0 +1,90 @@
    +   *   (optional) An array of contexts.
    

    We should add something here about if the contexts are provide they will used to filter.

  5. +++ b/core/lib/Drupal/Core/Plugin/plugin.api.php
    @@ -0,0 +1,68 @@
    +function hook_plugin_filter_TYPE_alter(array &$definitions, array $extra, $consumer) {
    +  // Remove the "Help" block from the Block UI list.
    +  if ($consumer == 'block_ui') {
    +    unset($definitions['help_block']);
    +  }
    

    I like that change in hook allows the example hooks to be much simpler.

  6. +++ b/core/modules/block/src/Controller/BlockLibraryController.php
    @@ -102,7 +114,7 @@ public function listBlocks(Request $request, $theme) {
    -    $definitions = $this->blockManager->getDefinitionsForContexts($this->contextRepository->getAvailableContexts());
    +    $definitions = $this->discoveryFilterer->get('block', 'block_ui', $this->blockManager, $this->contextRepository->getAvailableContexts(), ['theme' => $theme]);
    

    Further down in this method we have
    $region = $request->query->get('region');

    It seems like it would make sense to move up so that $region if available could be added to $extra

    This would allow a module disallow certain block within certain regions on the main block admin page.

  7. +++ b/core/modules/layout_builder/src/Controller/ChooseSectionController.php
    @@ -43,7 +54,8 @@ public function __construct(LayoutPluginManagerInterface $layout_manager) {
    @@ -62,7 +74,8 @@ public function build(SectionStorageInterface $section_storage, $delta) {
    
    @@ -62,7 +74,8 @@ public function build(SectionStorageInterface $section_storage, $delta) {
         $output['#title'] = $this->t('Choose a layout');
     
         $items = [];
    -    foreach ($this->layoutManager->getDefinitions() as $plugin_id => $definition) {
    +    $definitions = $this->discoveryFilterer->get('layout', 'layout_builder', $this->layoutManager, [], ['section_storage' => $section_storage]);
    

    I was going to suggest that we pass $delta in $extra here 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 $delta it seems find the way it is.

  8. +++ b/core/modules/layout_builder/tests/modules/layout_builder_test/layout_builder_test.module
    @@ -0,0 +1,29 @@
    +  foreach ($definitions as $plugin_id => $definition) {
    +    // Field block IDs are in the form 'field_block:{entity}:{bundle}:{name}',
    +    // for example 'field_block:node:article:revision_timestamp'.
    +    preg_match('/field_block:.*:.*:(.*)/', $plugin_id, $parts);
    +    if (isset($parts[1]) && in_array($parts[1], $disallowed_fields, TRUE)) {
    +      // Unset any field blocks that match our predefined list.
    +      unset($definitions[$plugin_id]);
    +    }
    +  }
    

    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

      $disallowed_plugins = [
        'field_block:node:bundle_with_section_field:sticky',
        'help_block',
      ];
    
      foreach ($disallowed_plugins as $disallowed_plugin) {
        unset($definitions[$disallowed_plugin]);
      }
    
tim.plunkett’s picture

Issue summary: View changes
Status: Needs work » Needs review
StatusFileSize
new24.87 KB
new28.44 KB

Thanks @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.

tim.plunkett’s picture

Here's an interdiff but without the rename.

tedbow’s picture

Issue tags: +Needs change record

Looks good to me!

We should change record about the new hooks.

tim.plunkett’s picture

Status: Needs work » Needs review
Issue tags: -Needs change record
StatusFileSize
new908 bytes
new29.33 KB

Good 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.

tedbow’s picture

Assigned: Unassigned » eclipsegc

@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.

eclipsegc’s picture

Status: Needs review » Reviewed & tested by the community

uhhh 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

tim.plunkett’s picture

Oh, before this is committed: @tedbow provided *significant* feedback and direction on the final approach, please credit him.

eclipsegc’s picture

Assigned: eclipsegc » Unassigned
andypost’s picture

Status: Reviewed & tested by the community » Needs work

CR 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

tedbow’s picture

Maybe this could be follow ups but...

  1. I am wondering if we want to add a @see to \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 use PluginDefinitionFilterer::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::getDefinitionsForContexts has been deprecated and points to PluginDefinitionFiltererInterface

    On 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::getDefinitions to retrieve the definitions when I want to display a list of plugins.

    For instance in \Drupal\image\Form\ImageStyleEditForm::form has

    $effects = $this->imageEffectManager->getDefinitions();
        uasort($effects, function ($a, $b) {
          return Unicode::strcasecmp($a['label'], $b['label']);
        });
        foreach ($effects as $effect => $definition) {
          $new_effect_options[$effect] = $definition['label'];
        }
    

    If 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.

  2. Should we have a follow up to determine where core should replace calls to \Drupal\Component\Plugin\Discovery\DiscoveryInterface::getDefinitions() with \Drupal\Core\Plugin\PluginDefinitionFilterer::get()
    One example would be the ImageStyleEditForm example above.
tim.plunkett’s picture

Status: Needs work » Needs review
StatusFileSize
new32.18 KB
new26.02 KB

1)
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.

tim.plunkett’s picture

Status: Needs work » Needs review
StatusFileSize
new3.56 KB
new25.94 KB

PHP's trait collision handling is abysmal. Oh well, this should fix it.

tim.plunkett’s picture

Status: Needs work » Needs review
StatusFileSize
new633 bytes
new25.95 KB

Sloppy work on my part :(

andypost’s picture

  1. +++ b/core/lib/Drupal/Core/Block/BlockManager.php
    @@ -37,10 +37,17 @@ class BlockManager extends DefaultPluginManager implements BlockManagerInterface
    +  protected function getType() {
    +    return 'block';
    
    +++ b/core/lib/Drupal/Core/Condition/ConditionManager.php
    @@ -43,6 +44,13 @@ public function __construct(\Traversable $namespaces, CacheBackendInterface $cac
    +  protected function getType() {
    +    return 'condition';
    
    +++ b/core/lib/Drupal/Core/Layout/LayoutPluginManager.php
    @@ -42,8 +45,16 @@ public function __construct(\Traversable $namespaces, CacheBackendInterface $cac
    +  protected function getType() {
    +    return 'layout';
    

    why not to use class variable?

  2. +++ b/core/lib/Drupal/Core/Plugin/FilteredPluginManagerTrait.php
    @@ -0,0 +1,65 @@
    +    return \Drupal::service('module_handler');
    ...
    +    return \Drupal::service('theme.manager');
    

    why not to add protected vars for static cache?

tim.plunkett’s picture

1) 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

tedbow’s picture

Status: Needs review » Needs work

Looking 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.

  1. +++ b/core/lib/Drupal/Core/Plugin/Context/ContextHandler.php
    @@ -17,7 +17,9 @@ class ContextHandler implements ContextHandlerInterface {
    -      if (!isset($plugin_definition['context'])) {
    +      // @todo Support object-based plugin definitions in
    +      //   https://www.drupal.org/project/drupal/issues/2961822.
    +      if (!is_array($plugin_definition) || !isset($plugin_definition['context'])) {
    

    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?

  2. +++ b/core/lib/Drupal/Core/Plugin/FilteredPluginManagerInterface.php
    @@ -0,0 +1,29 @@
    +  /**
    +   * Gets the plugin definitions for a given type and consumer and filters them.
    +   *
    +   * @param string $consumer
    +   *   A string identifying the consumer of these plugin definitions.
    +   * @param \Drupal\Component\Plugin\Context\ContextInterface[]|null $contexts
    +   *   (optional) Either an array of contexts to use for filtering, or NULL to
    +   *   not filter by contexts.
    +   * @param mixed[] $extra
    +   *   (optional) An associative array containing additional information
    +   *   provided by the code requesting the filtered definitions.
    +   *
    +   * @return \Drupal\Component\Plugin\Definition\PluginDefinitionInterface[]|array[]
    +   *   An array of plugin definitions that are filtered.
    +   */
    +  public function getFilteredDefinitions($consumer, $contexts = NULL, array $extra = []);
    

    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.

  3. +++ b/core/lib/Drupal/Core/Plugin/FilteredPluginManagerInterface.php
    @@ -0,0 +1,29 @@
    +   * @param string $consumer
    +   *   A string identifying the consumer of these plugin definitions.
    

    Should we say "usually a module or theme" or is it always? I guess I profile could implement this too? An extension short name?

tim.plunkett’s picture

Status: Needs work » Needs review
StatusFileSize
new1.14 KB
new26.23 KB

1) 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_ui for the two calls within the Block UI.

tedbow’s picture

Assigned: Unassigned » eclipsegc

This looks good to me now. I am +1 for RTBC.

Re #31

CR needs to mention about deprecated methods and trait

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.

eclipsegc’s picture

Status: Needs review » Reviewed & tested by the community

This 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

tedbow’s picture

Assigned: eclipsegc » Unassigned

Great

+++ b/core/modules/layout_builder/tests/modules/layout_builder_test/layout_builder_test.module
@@ -0,0 +1,29 @@
+function layout_builder_test_plugin_filter_block__layout_builder_alter(array &$definitions) {

Nit should this function have the argument array $extra even if it isn't used?

tim.plunkett’s picture

Personal preference, I'm one for leaving out the extra arguments

larowlan’s picture

Status: Reviewed & tested by the community » Needs review
  1. +++ b/core/lib/Drupal/Core/Block/BlockManagerInterface.php
    @@ -4,10 +4,11 @@
    +interface BlockManagerInterface extends ContextAwarePluginManagerInterface, CategorizingPluginManagerInterface, FilteredPluginManagerInterface {
    

    this 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.

  2. +++ b/core/lib/Drupal/Core/Plugin/FilteredPluginManagerTrait.php
    @@ -0,0 +1,65 @@
    +    return \Drupal::service('module_handler');
    

    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?

tim.plunkett’s picture

1)

# Interfaces within non-experimental, non-test modules not tagged with either @api or @internal

Interfaces that are not tagged with either @api or @internal can be safely used as type hints. No methods will be changed or removed from these interface in a breaking way.
However, we reserve the ability to add methods to these interfaces in minor releases to support new features. When implementing the interface, module authors are encouraged to either:
- Where there is a 1-1 relationship between a class and an interface, inherit from the class. Where a base class or trait is provided, use those. This way your class should inherit new methods from the class or trait when they're added.
- Where the interface is implemented directly for other reasons, be prepared to add support for new methods in minor releases

- 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.

tedbow’s picture

Status: Needs review » Reviewed & tested by the community

Agree with #47.1 that it seem like we are able add methods via adding the interface.

#47.2 also seems like good fix.

larowlan’s picture

I'll discuss the impact of the new method, in particular whether it impact ability to backport, with the other framework managers

larowlan’s picture

Consensus was this isn't eligible for backport, because of the new methods etc.

Adding review credits

larowlan’s picture

Status: Reviewed & tested by the community » Fixed

Committed e91d969 and pushed to 8.6.x

Published change record

  • larowlan committed e91d969 on 8.6.x
    Issue #2949177 by tim.plunkett, samuel.mortenson, mark_fullmer, tedbow,...

Status: Fixed » Closed (fixed)

Automatically closed - issue fixed for 2 weeks with no activity.