Problem/Motivation

In the Image Effects module, we have been providing a set of image operations for both the 'gd' and the 'imagemagick' toolkits.

There never was a formal dependency to the ImageMagick module, though. If the module is installed, it benefits from the operations; if it isn't, the operations just stay there silently.

In converting the plugins to use attributes in place of annotations, though, I came across the fact that if the ImageMagick module is not installed, all the GD image toolkit operations fail with

The image toolkit 'gd' failed processing 'scale' for image 'core/modules/image/sample.png'. Reported error: Error - Class "Drupal\imagemagick\Plugin\ImageToolkit\Operation\imagemagick\ImagemagickImageToolkitOperationBase" not found

I think this is occurring during plugin discovery, and is probably due to class reflection actually trying to load the extended class.

Proposed resolution

If reflection failed on the class, it could be due to the class implementing or extending from interfaces/classes that are not available to the classloader. This can happen in contrib when a class in a module extends from a class in another module that is not installed.

In that case, just skip the plugin.

Drupal\Component\Plugin\Discovery\AttributeClassDiscovery no longer uses FileCache to store results of plugin class parsing.

Differently that discovery via annotations, discovery via attributes is subject to PHP runtime checks that may fail due to circumstances outside of the class itself (for instance, if a plugin class extends from an uninstalled module class). Caching the result leads to errors due if the external circumstances changes (i.e. the module that is missing is installed, or an installed module is uninstalled).

Remaining tasks

User interface changes

API changes

Data model changes

Release notes snippet

CommentFileSizeAuthor
#32 3458177-nr-bot.txt2.39 KBneeds-review-queue-bot

Issue fork drupal-3458177

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

mondrake created an issue. See original summary.

mondrake’s picture

mondrake’s picture

Issue summary: View changes
mondrake’s picture

Status: Active » Needs review
mondrake’s picture

Component: image system » plugin system

It's not just about image related plugins, it's about ALL plugins.

cmlara’s picture

longwave’s picture

Status: Needs review » Reviewed & tested by the community

I think this makes sense and I can't see a simpler or better way to do it.

However let's open a followup to discuss that return value of parseClass() because I agree the structure with NULLs is strange, perhaps it should throw an exception in the failure case instead? The reason we do it is:

              ['id' => $id, 'content' => $content] = $this->parseClass($class, $fileinfo);

so the return value is always expected in that structure. I think this is all internal enough that we can change it without BC implications, but the followup can help us decide that.

mondrake’s picture

longwave’s picture

Status: Reviewed & tested by the community » Needs work

Ugh, yeah, there is caching bug there, but not sure how to work around it.

Also, how about logging something if we encounter an exception, instead of swallowing it - it might help developers if they have typo'd something or similar that prevents discovery from working?

mondrake’s picture

Not much into the plugin system, so this is naive…

can we just drop caching to filecache?

any module install/uninstall can potentially add/remove plugin types and actual plugins, so full scan is needed anyway on discovery?

mondrake’s picture

If caching to filecache is due to considerations on reflection performance, then I think that needs to be reconsidered at the light of improvements in PHP 8.

https://gist.github.com/mindplay-dk/3359812 has benchmarks - and concludes

there is no performance-gain from caching reflection-objects
mondrake’s picture

Status: Needs work » Needs review
longwave’s picture

File cache was probably much more important when we were parsing for annotations? But maybe we can move it to that layer and drop it for attributes, though it would be good to do some benchmarking if possible.

mondrake’s picture

Added deprecations and a draft CR.

File cache was probably much more important when we were parsing for annotations? But maybe we can move it to that layer and drop it for attributes

AFAICS, Drupal\Component\Annotation\Plugin\Discovery\AnnotatedClassDiscovery is already using file caching.

it would be good to do some benchmarking if possible

This is above me ATM

mondrake’s picture

Issue summary: View changes
mondrake’s picture

Can benchmarking per #14 be done in a follow up? It would be a bummer to release D11 without this fix.

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

catch’s picture

I don't think we should get rid of the file cache here without performance numbers - but even worse we don't know what attribute discovery even looks like on a large site yet because not all of core and contrib is converted yet, so any numbers we did get would be artificially better than they might otherwise be.

However, I think I found a way to fix the error, avoid writing to cache just in this case, and leave the file cache for everything that currently would be successfully found and cached. Because this is an alternate approach, have made the change in a separate MR. I restored the test that was removed here, and made the minimal changes to the new test so that it still parses - I think it might be enough but maybe we need to explictly test the missing attribute class with file caching too. I initially did the fix wrong and the test found the error though at least.

mondrake’s picture

@catch thought about this earlier, what was preventing me to go in this direction is the fact that you might end up with info cached for files that no longer exist (i.e. if you uninstall a module + composer remove it). I am not fully in details of plugin discovery, but if that's not a problem, I think it's a great compromise.

catch’s picture

Plugin discovery caches should be cleared on module install/uninstall, so that part ought to be OK I think?

mondrake’s picture

So on module (un)install all the plugin files will be scanned in the directories, but only those missing from the file cache effectively also parsed? Sounds good - the file cache will have more but will not be retrieved.

Closed the initial MR, cannot really RTBC @catch's one, needs someone else's review.

catch’s picture

So on module (un)install all the plugin files will be scanned in the directories, but only those missing from the file cache effectively also parsed? Sounds good - the file cache will have more but will not be retrieved.

Yes - it'll find all the files, but only actually parse the ones that aren't in the cache (which my MR should stop them going in), or whose mtime has changed.

I think I realised more what you meant in #21 though, scenario would be something like this:

1. Module A has a plugin that relies on an an attribute defined by an uninstalled module B -> doesn't enter into FileCache or discovery.
2. Module B is installed - now the plugin is in both FileCache and discovery.
3. Module B is uninstalled again - the discovery cache will be cleared, but the FileCache won't.

So now we have a file in FileCache that uses an attribute defined by an uninstalled module.

I guess that leaves a couple of questions:

1. Should we be clearing the file cache for attributes on module uninstall (just uninstall, since the MR should cover install).

2. Or is it OK, given that this is how annotations used to work anyway.

mondrake’s picture

Well my gut feeling is that’s ok for now. When annotations will be eventually gone, then it will make sense the benchmark to see if file caching performs better than running reflection every time. We may have surprises…

godotislate’s picture

Made one comment on the MR: there's been discussion on #3421014: Convert MigrateSource plugin discovery to attributes not to just eat all exceptions, so I put in a suggestion to target the specific error based on pending work over there.

There's also discussion on how to deal with missing traits in that issue as well, but the solution is more complicated.

mondrake’s picture

preg_match('/(Class|Interface) .* not found$/', $e->getMessage()) isn't the exception message (potentially) localised in PHP?

Just asking.

godotislate’s picture

Status: Needs review » Needs work
mondrake’s picture

Status: Needs work » Needs review

Applied @godotislate suggestion, and rebased.

needs-review-queue-bot’s picture

Status: Needs review » Needs work
StatusFileSize
new2.39 KB

The Needs Review Queue Bot tested this issue. It fails the Drupal core commit checks. 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.

catch’s picture

Status: Needs work » Needs review

Rebased.

mondrake’s picture

Green now.

mondrake’s picture

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

godotislate’s picture

Status: Needs review » Reviewed & tested by the community

lgtm.

xjm’s picture

Status: Reviewed & tested by the community » Needs work
Issue tags: +Needs followup

Posted some comments on the MR. There are some docs minutiae that seem out of scope, or if they're in scope, I don't understand why and so clarification of the docs changes would therefore be needed. :)

catch’s picture

I think I've addressed everything except the two comment changes that may or may not be out of scope, and the $file_path_2 naming which I don't have a strong opinion on.

mondrake’s picture

Status: Needs work » Needs review

Addressed remaining three points.

mondrake’s picture

Retracted the draft CR as no longer relevant.

catch’s picture

Status: Needs review » Reviewed & tested by the community

OK I think that's enough to move this back to RTBC.

  • larowlan committed 7a331d50 on 11.x
    Issue #3458177 by mondrake, catch, godotislate, longwave, xjm: Changing...

  • larowlan committed 27a40bef on 10.3.x
    Issue #3458177 by mondrake, catch, godotislate, longwave, xjm: Changing...

  • larowlan committed 7aacebf3 on 10.4.x
    Issue #3458177 by mondrake, catch, godotislate, longwave, xjm: Changing...

  • larowlan committed e8aec68a on 11.0.x
    Issue #3458177 by mondrake, catch, godotislate, longwave, xjm: Changing...

larowlan’s picture

Version: 11.x-dev » 10.3.x-dev
Status: Reviewed & tested by the community » Fixed

Committed to 11.x and then backported to 11.0.x/10.4.x/10.3.x after confirming with catch

Thanks folks 🚀

catch’s picture

Looks like this is breaking pipelines on 10.3.x somehow (seen in performance test pipeline, not sure about anywhere else) https://git.drupalcode.org/project/drupal/-/jobs/2310094

catch’s picture

Version: 10.3.x-dev » 10.4.x-dev
Status: Fixed » Needs work

OK this is because phpunit test discovery is different in 10.x, and the test class is being picked up as if it's a test. run-tests.sh doesn't use the suites, so it's mainly a problem for local phpunit test runs + performance tests.

Two options I think:

1. Just remove the test coverage in 10.x, rely on it being in 11.x

2. Move the intentionally broken class somewhere else - a test module, fixture directory etc., where it won't be mistaken for a test class.

mondrake’s picture

Let's try option 2. Should not be too difficult.

mondrake’s picture

Status: Needs work » Needs review

Let's see MR!9039.

alexpott’s picture

I was just going to comment that the only way to fix this is to move the classes under core/tests/fixtures ... 1 small nit on the MR. +1 for the fix.

godotislate’s picture

Status: Needs review » Reviewed & tested by the community

Tests are green, #54 was addressed, so lgtm.

  • catch committed eb14e0bd on 10.3.x
    Issue #3458177 by mondrake, catch, quietone, godotislate, longwave,...

  • catch committed 7ef2982a on 10.4.x
    Issue #3458177 by mondrake, catch, quietone, godotislate, longwave,...

  • catch committed d7b5f9e3 on 11.0.x
    Issue #3458177 by mondrake, catch, quietone, godotislate, longwave,...

  • catch committed 618aeb60 on 11.x
    Issue #3458177 by mondrake, catch, quietone, godotislate, longwave,...
catch’s picture

Version: 10.4.x-dev » 10.3.x-dev
Status: Reviewed & tested by the community » Fixed

Committed/pushed to 11.x and cherry-picked back through to 10.3.x, thanks!

Status: Fixed » Closed (fixed)

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

xjm’s picture

Amending attribution.