Problem/Motivation
RegisterEventSubscribersPass will throw an exception on container rebuild if an event subscriber doesn't implement the right interface:
$interface = EventSubscriberInterface::class;
if (!is_subclass_of($class, $interface)) {
throw new \InvalidArgumentException(sprintf('Service "%s" must implement interface "%s".', $id, $interface));
}
But the check for is_subclass_of() will also fail if the class wasn't found at all.
This means that for example, if you mess up the class name or the namespace, but your code implements the interface, you have a confusing error message.
Steps to reproduce
Proposed resolution
Add class_exists() check and exception first.
Remaining tasks
User interface changes
Introduced terminology
API changes
Data model changes
Release notes snippet
Comments
Comment #2
akhilsoni commentedI was able to reproduce the confusing error described in the issue. When an `event_subscriber` service points to a class that does not exist, `is_subclass_of()` returns FALSE, so the exception says the service must implement `EventSubscriberInterface` even though the real problem is a missing or misspelled class.
I added a preflight check in `RegisterEventSubscribersPass` before delegating to Symfony’s `RegisterListenersPass`. If an `event_subscriber` service has a missing class, Drupal now throws a clearer exception naming both the service ID and the missing class.
Added unit test coverage for the missing-class case in:
`core/tests/Drupal/Tests/Core/DependencyInjection/Compiler/RegisterEventSubscribersPassTest.php`
Tested locally:
`php -l core/lib/Drupal/Core/DependencyInjection/Compiler/RegisterEventSubscribersPass.php`
`php -l core/tests/Drupal/Tests/Core/DependencyInjection/Compiler/RegisterEventSubscribersPassTest.php`
`../vendor/bin/phpunit --configuration phpunit.xml.dist tests/Drupal/Tests/Core/DependencyInjection/Compiler/RegisterEventSubscribersPassTest.php`
`../vendor/bin/phpunit --configuration phpunit.xml.dist tests/Drupal/Tests/Core/DependencyInjection/Compiler`
Result:
`OK (27 tests, 109 assertions)`
Also ran PHPCS on the changed files:
`vendor/bin/phpcs --standard=core/phpcs.xml.dist core/lib/Drupal/Core/DependencyInjection/Compiler/RegisterEventSubscribersPass.php core/tests/Drupal/Tests/Core/DependencyInjection/Compiler/RegisterEventSubscribersPassTest.php`
The patch is ready for review.
Comment #3
akhilsoni commentedComment #4
joachim commentedHi. Thanks for the patch.
It's really overkill though -- we just need a class_exists() condition.
What's this check for:
> if ($container->hasDefinition('event_dispatcher') || $container->hasAlias('event_dispatcher')) {
Comment #5
akhilsoni commentedThanks, good point.
I removed the extra `event_dispatcher` check and simplified the patch. It now only checks services tagged with `event_subscriber` and verifies that the configured class exists before passing the container to Symfony's `RegisterListenersPass`.
I reran the tests after the update:
`../vendor/bin/phpunit --configuration phpunit.xml.dist tests/Drupal/Tests/Core/DependencyInjection/Compiler/RegisterEventSubscribersPassTest.php`
`../vendor/bin/phpunit --configuration phpunit.xml.dist tests/Drupal/Tests/Core/DependencyInjection/Compiler`
Result:
`OK (27 tests, 109 assertions)`
Comment #6
charlliequadros commentedHi @joachim, @akhilsoni
Could you please check whether I performed any of the steps incorrectly or missed any relevant scenario?
In the first test, I configured the service with an existing class that does not implement the required interface to be registered as an event subscriber. As shown in the image below, Symfony identifies the problem and displays an appropriate error message:
In the second test, I configured the service with a class that does not exist. In this case, Symfony also identifies the problem and displays the expected error message:
Therefore, based on my tests, both scenarios already appear to be handled by Symfony.
If I understood the purpose of this issue correctly, I believe it can be closed, since the error messages appear to be correct and sufficiently clear.
Please let me know what you think.
Cheers!
Charllie
Comment #7
charlliequadros commentedHi @akhilsoni
If what I mentioned above is correct, the validation may be being implemented in the wrong place. In that case, the check to confirm that the class exists should be added here.
To include this scenario in the testing steps, we would need to create a service that references a non-existent class and add the `logger_aware` tag to that service.
The test would then verify the behaviour of a service tagged with `logger_aware` whose configured class does not exist.
In any case, let’s wait @joachim's confirmation before proceeding.
Comment #8
akhilsoni commentedThanks for checking this.
Your examples make sense. I was looking at Drupal's wrapper before the tag is passed to Symfony, but if current 11.x already reports the missing class clearly through Symfony's
RegisterListenersPass, then the patch probably does not need to be inRegisterEventSubscribersPass.The
LoggerAwarePasscase does look closer to the original problem pattern, since it callsis_subclass_of()directly on the configured service class. A missing class there could still produce the misleading “must implement interface” message.I agree, let's wait for @joachim to confirm the intended scope before rerolling this. If the issue is really about
logger_aware, I can update the patch in that direction with a test for alogger_awareservice using a non-existent class.Comment #9
jacobupal commentedSeems like we're outside of the novice aspect of this work, current state is "Needs review" which would be better for a more-than-novice person. Removing the Novice tag now to help move things forwards (here in Issue Triage at DrupalCon Rotterdam 2026).