Skip to content

Drupal event subscribers - #566

Merged
AJenbo merged 6 commits into
PHPantom-dev:mainfrom
giuseppe-esposito-87:drupal-dr16
Nov 12, 2026
Merged

AJenbo merged 6 commits into
PHPantom-dev:mainfrom
giuseppe-esposito-87:drupal-dr16

Conversation

@giuseppe-esposito-87

Copy link
Copy Markdown
Contributor

🤖 This was written by an AI agent on behalf of @giuseppe-esposito-87 (claude-code/claude-opus-5-5).

Adds DR16: PHPantom now reads event subscribers in Drupal sites, as it already does in Symfony projects. Listener methods navigate and are checked, and event names complete and navigate in the event dispatcher's calls.

final class GreetingEvents {
    public const GREETED = 'acme.greeted';                    // the event's definition
}

public static function getSubscribedEvents(): array {
    $events[KernelEvents::RESPONSE][] = ['onResponse', 10];   // goes to onResponse(), reported if missing
    $events[GreetingEvents::GREETED][] = ['onGreeted'];       // subscribes to 'acme.greeted'
    return $events;
}

\Drupal::service('event_dispatcher')->dispatch(new Event(), 'acme.greeted');
// completes, goes to GREETED and to the subscription

In Drupal sites

  • Listener methods: go to definition, hover, Find References, rename, and the unknown-method warning.
  • Event names in dispatch() and the dispatcher's other calls that take one:
    • they complete with every name the site's subscribers declare, core's included;
    • hover shows how many listeners subscribe;
    • go to definition offers the constant that holds the name first, then each subscription.
  • One event, two spellings: Find References on 'acme.greeted' or on GREETED lists the same places: the constant, its uses, the subscriptions and the dispatches. Renaming the constant never rewrites an event string.
  • #[AsEventListener] stays Symfony-only, because Drupal's container ignores it.

This also changes Symfony projects

Symfony and Drupal share the subscriber reader, so Symfony projects get these changes too:

  • Maps built by assignment are read, not only returned arrays: $events[KernelEvents::REQUEST][] = …; return $events;. Most of Drupal core writes its maps this way.
  • Constant keys declare their event name: KernelEvents::REQUEST => … now declares kernel.request, an inherited constant included. That constant also becomes the event's definition for navigation and Find References. A Foo::class key still declares nothing.

Checked

  • analyze on Drupal core 11.4 and on the redirect and flag modules reports exactly what main reports.
  • The Drupal completion benchmark gains an event-name case. Its generated site now also holds an events class and a subscriber per module; on that larger site the other cases run 4% to 12% slower than main.

Roadmap

  • DR37 is filed: complete a subscriber's methods inside its map, and warn on a private or protected listener method.
  • F45 is trimmed to what it still lacks.

How to test

Run composer install in examples/drupal, open examples/drupal with this branch's build, and follow the "Try:" comments:

  • web/modules/custom/acme/src/EventSubscriber/GreetingSubscriber.php: go to the definition of 'onResponse', rename onResponse() (the string follows), and misspell 'onConfigSave' to see the warning.
  • web/modules/custom/acme/acme.module, acme_announce_greeting(): go to the definition of 'acme.greeted', find its references, and clear it to complete an event name such as kernel.request.
  • web/modules/custom/acme/src/Event/GreetingEvents.php: find the references of GREETED; the dispatch in acme.module is among them.

Checklist

If applicable:

  • I have updated CHANGELOG.md
  • I have updated the documentation (README.md, docs/, examples/)
  • I have updated the config schema (config-schema.json). Not applicable: this PR adds or changes no setting.
  • I have added/updated tests to cover my changes
  • If I used AI tools, I have reviewed what they produced and can explain
    the changes and why they are needed.
  • I have read through any AI-generated text, especially documentation,
    the changelog, and other user-facing content, and removed or corrected
    anything inaccurate, unclear, or unnecessary.

@codecov-commenter

codecov-commenter commented Nov 12, 2026 •

Copy link
Copy Markdown

⚠️ Please install the 'codecov app svg image' to ensure uploads and comments are reliably processed by Codecov.

Codecov Report

❌ Patch coverage is 95.93985% with 27 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
src/symbol_map/extraction/symfony.rs 95.02% 12 Missing ⚠️
src/references/events.rs 95.72% 8 Missing ⚠️
src/symfony/event_constants.rs 95.69% 4 Missing ⚠️
src/symfony/mod.rs 96.34% 3 Missing ⚠️

📢 Thoughts on this report? Let us know!

A subscriber's getSubscribedEvents() is read when it builds the map by
assigning to the variable it returns ($events[KEY][] = ..., $events[KEY] =
..., $events = [...]), anywhere in the body, as well as when it returns
a literal.

Assisted-by: claude-code/claude-opus-5-5
A Drupal site reads each subscriber's getSubscribedEvents() as a Symfony
project does, so the listener methods its map names go to their
definition, hover, take part in references and rename, and are reported
when the class has no such method. Event names become a kind both
frameworks read. #[AsEventListener] stays Symfony-only, in the symbol map
and in completion, since Drupal never honours it.

Assisted-by: claude-code/claude-opus-5-5
A subscription keyed by a class constant (KernelEvents::REQUEST,
self::X, static::X) declares the constant's value when it is a plain
string literal, read with what the class inherits from its parents and
interfaces. A subscriber keyed only by constants now counts as declaring
event names, so dispatcher calls complete, navigate to and count its
subscriptions. An X::class key and a constant built any other way
declare nothing.

Assisted-by: claude-code/claude-opus-5-5
In a Drupal site, the event name a dispatcher call takes completes with
every name the project's subscribers declare, goes to each subscription,
and shows how many listeners subscribe. This works on the contracts and
component interfaces and on \Drupal::service('event_dispatcher'). The
name a listener is added for in code completes too, but declares nothing.

Assisted-by: claude-code/claude-opus-5-5
A class constant a subscriber keys its map by is the event name it holds
in another spelling. Go to definition on an event string offers the
constant first, then every subscription, and Find References on the
string, on the constant, or on the string the constant is initialized
with lists the constant, its uses, and the dispatches as one event. A
constant no subscriber keys by stays a plain constant, and renaming one
never rewrites an event string.

Assisted-by: claude-code/claude-opus-5-5
Assisted-by: claude-code/claude-opus-5-5
@AJenbo
AJenbo merged commit bcaf872 into PHPantom-dev:main Nov 12, 2026
13 of 15 checks passed
@AJenbo

AJenbo commented Nov 12, 2026

Copy link
Copy Markdown
Contributor

Thanks

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants