Problem/Motivation

Follow-up to #2560795: Source plugins have a hidden dependency on migrate_drupal.

With the move to discovery by attributes, dealing with multiple "providers" across all plugin types was necessitated since discovery uses reflection and PHP has to parse the class files. With missing providers, this can result in exceptions and/or fatal errors during discovery, and this has largely been handled by #3502913: Add a fallback classloader that can handle missing traits for attribute discovery. Remaining part to do for attribute discovery is to filter out plugin definitions that were cached in APCu filecache when the providers (modules) were previously installed but now aren't. Since annotations are being deprecated, the BC layer for annotation multiple providers for migrate source will be kept as is.

Steps to reproduce

An example:

  • Module layout_builder has class InlineBlock implementing a Block plugin
  • InlineBlock uses trait RefinableDependentAccessTrait from module block_content
  • block_content is not a dependency of layout_builder, so it is possible to install layout_builder with block_content uninstalled
  • With layout_builder installed and block_content uninstalled, during Block plugin discovery, parsing InlineBlock by Reflection to read the attribute data would result in a fatal error because RefinableDependentAccessTrait does not exist. Handling of this fatal error has been addressed in #3502913: Add a fallback classloader that can handle missing traits for attribute discovery and other issues
  • Once block_content is installed, InlineBlock can be reflected and the inline_block plugin should be discovered. This has also been fixed in previous issues making sure that error handling did not mistakenly cache InlineBlock as providing no plugin definition in the APCu FileCache
  • However, once block_content is uninstalled again, the definition has been cached and the plugin stays discovered. The work to remove the plugin definition from discovery in this scenario is addressed by the work for this issue*

*Note that there may need to be a follow up to tie the uninstall of implicit dependencies like block_content in this example to dependency calculations for configurations using the plugin.

Proposed resolution

  • When using reflection to parse class attributes, also use reflection to get a list of all the classes, interfaces, and traits in the class's hierarchy and collect them as dependencies
  • Cache parsed content including dependencies in the file cache
  • When definitions are retrieved from the file cache, using two-level namespace comparison to determine whether all the dependencies are available (i.e. if the dependency is Drupal\other_module\Something\MyTrait, check whether Drupal\other_module namespace is in a list of two-level namespaces (extracted from root namespaces). This is to get around calling class|interface|trait_exists() functions on every dependency
  • If any of a class's dependencies is missing, exclude the plugin from discovery
  • Add property/methods to set and get dependencies on plugin attribute object so that it is included in the definition content. This is so modules such as migrate_source have access to the list of dependencies for each plugin in other processing
  • Remove AttributeClassDiscoveryAutomatedProvider, MultipleProviderAttributeInterface, and all other multiple provider handling specific to migrate source

With these changes, it is also necessary to add any implicit plugin dependency modules to Kernel tests' $modules property. For example, to be able to use the Node entity type in a Kernel test, both node and user need to be included in the tests's $modules property, because Kernel tests do not install module dependencies.

Remaining tasks

User interface changes

Introduced terminology

API changes

New methods getDependencies() and setDependencies() addeed to AttributeInterface. See draft CR

Data model changes

Release notes snippet

CommentFileSizeAuthor
#30 2786355-nr-bot.txt91 bytesneeds-review-queue-bot

Issue fork drupal-2786355

Command icon Show commands

Start within a Git clone of the project using the version control instructions.

Or, if you do not have SSH keys set up on git.drupalcode.org:

Comments

alexpott created an issue. See original summary.

mikeryan’s picture

Category: Bug report » Task
Priority: Major » Normal
Issue tags: -neworleans2016, -Migrate critical, -DevDaysMilan, -blocker

Task or feature request? I don't think this element qualifies as a bug report...

jibran’s picture

Version: 8.2.x-dev » 8.3.x-dev
heddn’s picture

Version: 8.3.x-dev » 8.4.x-dev

Drupal 8.3.0-alpha1 will be released the week of January 30, 2017, which means new developments and disruptive changes should now be targeted against the 8.4.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

neclimdul’s picture

Stumbled into this from the @todo in MigrationPluginManager. How can a plugin have multiple providers? Doing so breaks the idea of what a provider is, a single source module which owns the plugin. To change that would fundamentally change a contract of the plugin design.

Version: 8.4.x-dev » 8.5.x-dev

Drupal 8.4.0-alpha1 will be released the week of July 31, 2017, which means new developments and disruptive changes should now be targeted against the 8.5.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

cburschka’s picture

Also stumbled into this while investigating a surprisingly tricky SO question: https://drupal.stackexchange.com/questions/249721/why-is-my-migrate-sour...

I kind of agree with @neclimdul. Providers, as they currently are handled, aren't dependencies; they're a single and obvious source that is immediately apparent (because a plugin is inside its provider module's folder). This is why the plugin manager silently discards plugin definitions from non-existent providers. If a plugin doesn't exist, it's because you didn't enable the module.

But when there are multiple providers, it's hard to see what they are. The plugin manager doesn't tell you "The plugin you want is unavailable because you didn't enable the modules X, Y and Z"; it simply skips the definition on discovery.

What the migrate module really needs is a way for a plugin to have module dependencies, which are checked individually on instantiation (MissingDependencyException) rather than at discovery time. Currently, you can only get this by putting the plugin in its own module and adding the dependency there, which would effectively require an extra migrate_source_X module for every X module; the multi-provider logic is basically a hack to get around this, which results in hard-to-diagnose problems like the above.

cburschka’s picture

Addendum: After reading the original issue that this follows up on, I have to point out: The undefined-class exception fixed by #2560795: Source plugins have a hidden dependency on migrate_drupal would ironically have been easier to understand than the invisible dependency checking that is happening now. This is an example where failing loudly is better.

alexpott’s picture

@cburschka dependencies can not work in this situation. Because for plugins they don't determine whether a plugin exists - they are calculated by the plugin at runtime.

cburschka’s picture

Yeah, runtime would be too late as the plugin must already be instantiated (which requires the parent class). But do we really only have a choice between a runtime check and filtering out the definition silently? Couldn't the check happen at the time you try to load the plugin, just before it actually calls the constructor?

As it is, if I try to load a multi-provider plugin and it fails, I have no idea whether I spelled it wrong, or forgot to enable a module (and if so, which module). The plugin just doesn't exist.

(Maybe an alternative would be to move the provider check into ::createInstance() rather than ::findDefinitions(), and throwing InvalidPluginDefinition instead of a PluginNotFoundException. This would distinguish between a plugin that never existed at all, and a plugin that got discovered, but can't be loaded because of missing providers.)

Version: 8.5.x-dev » 8.6.x-dev

Drupal 8.5.0-alpha1 will be released the week of January 17, 2018, which means new developments and disruptive changes should now be targeted against the 8.6.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

Version: 8.6.x-dev » 8.7.x-dev

Drupal 8.6.0-alpha1 will be released the week of July 16, 2018, which means new developments and disruptive changes should now be targeted against the 8.7.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

Version: 8.7.x-dev » 8.8.x-dev

Drupal 8.7.0-alpha1 will be released the week of March 11, 2019, which means new developments and disruptive changes should now be targeted against the 8.8.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

Version: 8.8.x-dev » 8.9.x-dev

Drupal 8.8.0-alpha1 will be released the week of October 14th, 2019, which means new developments and disruptive changes should now be targeted against the 8.9.x-dev branch. (Any changes to 8.9.x will also be committed to 9.0.x in preparation for Drupal 9’s release, but some changes like significant feature additions will be deferred to 9.1.x.). For more information see the Drupal 8 and 9 minor version schedule and the Allowed changes during the Drupal 8 and 9 release cycles.

Version: 8.9.x-dev » 9.1.x-dev

Drupal 8.9.0-beta1 was released on March 20, 2020. 8.9.x is the final, long-term support (LTS) minor release of Drupal 8, which means new developments and disruptive changes should now be targeted against the 9.1.x-dev branch. For more information see the Drupal 8 and 9 minor version schedule and the Allowed changes during the Drupal 8 and 9 release cycles.

Version: 9.1.x-dev » 9.2.x-dev

Drupal 9.1.0-alpha1 will be released the week of October 19, 2020, which means new developments and disruptive changes should now be targeted for the 9.2.x-dev branch. For more information see the Drupal 9 minor version schedule and the Allowed changes during the Drupal 9 release cycle.

quietone’s picture

Issue tags: +migration

Version: 9.2.x-dev » 9.3.x-dev

Drupal 9.2.0-alpha1 will be released the week of May 3, 2021, which means new developments and disruptive changes should now be targeted for the 9.3.x-dev branch. For more information see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

Version: 9.3.x-dev » 9.4.x-dev

Drupal 9.3.0-rc1 was released on November 26, 2021, which means new developments and disruptive changes should now be targeted for the 9.4.x-dev branch. For more information see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

Version: 9.4.x-dev » 9.5.x-dev

Drupal 9.4.0-alpha1 was released on May 6, 2022, which means new developments and disruptive changes should now be targeted for the 9.5.x-dev branch. For more information see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

Version: 9.5.x-dev » 10.1.x-dev

Drupal 9.5.0-beta2 and Drupal 10.0.0-beta2 were released on September 29, 2022, which means new developments and disruptive changes should now be targeted for the 10.1.x-dev branch. For more information see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

Version: 10.1.x-dev » 11.x-dev

Drupal core is moving towards using a “main” branch. As an interim step, a new 11.x branch has been opened, as Drupal.org infrastructure cannot currently fully support a branch named main. New developments and disruptive changes should now be targeted for the 11.x branch, which currently accepts only minor-version allowed changes. For more information, see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

godotislate made their first commit to this issue’s fork.

godotislate’s picture

Status: Active » Needs work

With the move to discovery by attributes, dealing with multiple "providers" generally was necessitated since discovery uses reflection and PHP has to parse the class files. With missing providers, this can result in exceptions and/or fatal errors, and this has largely been handled by #3502913: Add a fallback classloader that can handle missing traits for attribute discovery. Remaining part to do for attribute discovery is to filter out plugin definitions that were cached in APCu filecache when the providers (modules) were previously installed but now aren't.

Put up MR with some of this work pulled from attempts in other issues to see what tests pass. Will revisit and address failing tests and check for any outstanding issues later when time permits.

Annotation discovery will be left intact with multiple providers until it is ultimately deprecated and removed.

godotislate’s picture

Issue tags: +Needs tests

OK, got all existing tests passing.

Quick summary of work done here (will update the IS later when I'm less tired):
I ran with the idea of plugins (via attribute discovery) having "dependencies" instead of multiple providers as first mentioned in #8. This led to introducing new methods on the plugin system's AttributeInterface and AttributeBase to get and set the dependencies.

Because there's an existing division between Component AttributeClassDiscovery and Core AttributeClassDiscovery, in that the core class handles the concept of "providers", I split dependency detection between the two. So the dependencies found in the component discovery class are the names of classes, interfaces, and traits in the plugin class's hierarchy. Then in the core discovery class, the providers for classes, interfaces, and traits are derived. The end result is that plugins that have missing provider dependencies (in other words, they rely on assets in uninstalled modules) will be excluded from discovery unless the providers are installed. Keeping track of the classes/traits/interfaces also allows for the flexibility to handle if a plugin class has a non-Drupal class in its hierarchy that is optionally in the codebase. This is not checked now, to avoid having to load classes into memory to check, but it could be done if needed.

The methods on the Attribute class to get/set dependencies makes the dependency information available within its definition for processes that need it, such as migrate_drupal detecting whether a plugin has a dependency on it before requiring a source_module property. Will probably need a change record if the changes to AttributeInterface go through.

Remaining to do:

  • A test that confirms that installing a module causes plugins that have a dependency on it to be discovered (I actually think this exists), but then after that, uninstalling the module then removes the plugin from discovery
godotislate’s picture

Issue summary: View changes
Status: Needs work » Needs review
Issue tags: -Needs issue summary update, -Needs tests +Needs change record
godotislate’s picture

Not sure it can make it, but it would be nice to get in for 11.2 so that a couple classes being removed could be removed clean.

needs-review-queue-bot’s picture

Status: Needs review » Needs work
StatusFileSize
new91 bytes

The Needs Review Queue Bot tested this issue. It no longer applies to Drupal core. Therefore, this issue status is now "Needs work".

This does not mean that the patch necessarily needs to be re-rolled or the MR rebased. Read the Issue Summary, the issue tags and the latest discussion here to determine what needs to be done.

Consult the Drupal Contributor Guide to find step-by-step guides for working with issues.

godotislate’s picture

Status: Needs work » Needs review

Rebased. Back to NR.

neclimdul’s picture

Title: Move multiple provider plugin work from migrate into core's plugin system » Convert multiple provider plugin work from migrate into plugin dependencies in core

I was really confused to see this being worked on but I see its replacing the multiple providers with dependencies which does seem like a good move. Not an actual review, just the approach makes a lot more sense.

Updating title to clarify the new approach and match updated IS.

nicxvan’s picture

I did a first pass here, nothing jumped out immediately, it's pretty clever, I definitely need to run through some things to better understand some bits though.

godotislate’s picture

Issue tags: -Needs change record

Added CR https://www.drupal.org/node/3523753.

One thought that came up is that there maybe should be a follow up to get the module dependencies found here to be added to dependency calculations for plugin class instances used in config.

Also mentioned this in a comment in #3502913: Add a fallback classloader that can handle missing traits for attribute discovery (comment #72), but the MR here does address a small bug from that issue. It might makes to create another issue to address that bug specifically with a subset of work done here.

godotislate’s picture

Adding 11.2.0 release priority tag for #29 and bug in #34.

godotislate’s picture

Issue summary: View changes
godotislate’s picture

Issue summary: View changes
godotislate’s picture

Issue summary: View changes
nicxvan’s picture

Status: Needs review » Reviewed & tested by the community

The example actually helped make this click.

We are now caching the dependencies of given plugins and skipping loading them if they are missing, and if I understand correctly it is now handling the uninstall case.

The only concern I have is how much will end up in these dependency trains, but I don't see a better way to do this, and it does seem pretty straightforward all things considered.

I've also reviewed the CR, I think it's clear but the committers can confirm as well.

I've asked @godotislate to create a follow up for

*Note that there may need to be a follow up to tie the uninstall of implicit dependencies like block_content in this example to dependency calculations for configurations using the plugin.

but I'm not going to block this on that, I'll keep an eye on it and follow up if it's not created shortly.

godotislate’s picture

Note that there may need to be a follow up to tie the uninstall of implicit dependencies like block_content in this example to dependency calculations for configurations using the plugin.

Follow up for this: #3524219: Add plugin class module dependencies to dependency calculation

quietone’s picture

I read the MR comments and made some suggestion and left a question.

quietone’s picture

Has anyone reviewed the change record?

godotislate’s picture

Has anyone reviewed the change record?

@nicxvan reviewed and asked me for some revisions. If there are still things that need clarifying, I can make additional changes.

godotislate’s picture

I think I addressed or answered the outstanding MR comments.

quietone’s picture

@godotislate, thanks. I resolved all the threads.

larowlan’s picture

Issue tags: +Needs followup

Saving credits

Both the issue summary and #39 mention the need for a follow up but I don't see one in related issues - can someone add that please?

godotislate’s picture

The follow up should be #3524219: Add plugin class module dependencies to dependency calculation. It's a child issue, but I'll add it as a related issue as well.

larowlan’s picture

Status: Reviewed & tested by the community » Needs review

Left a couple of questions/comments on the MR

larowlan’s picture

thanks @godotislate

godotislate’s picture

Applied the suggestion and answered the Qs on the MR.

godotislate’s picture

Removing an unnecessary conditional gate revealed a couple bugs it was hiding. Push commits to fix. This is ready to be looked at again.

larowlan’s picture

Status: Needs review » Reviewed & tested by the community

Back to RTBC thanks

larowlan’s picture

catch made their first commit to this issue’s fork.

  • catch committed ccbcf312 on 11.x
    Issue #2786355 by godotislate, larowlan, quietone, alexpott, cburschka,...
catch’s picture

Status: Reviewed & tested by the community » Fixed

This looks good, I had very minor comments nits that I made suggestions for and applied before commit.

I briefly confused myself looking for recursion to get the class hierarchy and not seeing it, it's because that's handled by this while loop, not recursion https://git.drupalcode.org/project/drupal/-/merge_requests/11960/diffs?f...

Committed/pushed to 11.x, thanks!

Status: Fixed » Closed (fixed)

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

m.stenta’s picture

For anyone who runs into this after updating to 11.3 and needs help figuring out which dependencies are missing from your test, I found the easiest thing to do is add a breakpoint to the following line before running the test:

https://git.drupalcode.org/project/drupal/-/blob/f4830b5037084b5330e22ce...

The $dependency variable will show the missing dependency that prevented the plugin from being discoverable.

Hope that helps!

dpi’s picture

Had a bizarre error.

Call to a member function set() on null
/data/app/core/modules/system/tests/modules/entity_test/src/Hook/EntityTestHooks.php:72

Thankfully I have weekly pipelines and was able to trace the issue to the change from this issue.

My fix was to installing the formal dependencies of entity_test, particularly, user.module.

The clue from @m.stenta helped a lot.

Too bad there isnt a more obvious way of discovering this issue. Im sure it will cause a lot of lost cycles.

geek-merlin’s picture

Crosslinking regression