Skip to content

Drupal hooks - #536

Merged
AJenbo merged 13 commits into
PHPantom-dev:mainfrom
giuseppe-esposito-87:drupal-dr6
Nov 5, 2026
Merged

AJenbo merged 13 commits into
PHPantom-dev:mainfrom
giuseppe-esposito-87:drupal-dr6

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 DR6: hook implementations link to their documentation and to the calls that fire them, and untyped hook parameters take the documented types.

// acme.module (simplified from the playground)
function acme_form_alter(array &$form, $form_state, string $form_id): void {
  $form_state->disableCache(); // $form_state is FormStateInterface, from hook_form_alter()'s docs
}

// src/Hook/AcmeHooks.php
#[Hook('acme_greeting_alter')]
public function greetingAlter(string &$greeting, $name): void {} // $name is string
  • Implementation to hook. A procedural implementation (<module>_<hook>) or a #[Hook] method jumps to the hook's hook_<name>() documentation in *.api.php, and hover shows it.
  • Hook to its callers and back. An implementation jumps to the calls that fire the hook (invokeAll(), invoke(), alter() and the like), core's computed names included: form_alter reaches FormBuilder's alter() call, and hook_ENTITY_TYPE_insert() covers node_insert. An invocation jumps to the implementations it reaches.
  • Completion. Typing function acme_ in a .module file offers the documented hooks and inserts their signature; hook names complete inside invokeAll(), invoke() and alter().
  • Typed parameters. An implementation's untyped parameters take the types the documentation declares.

A function in a .module file is a hook only when its name matches a documented or invoked hook, so helper functions stay plain functions. Unknown hook names are not reported: any module may invoke any name, often without documentation, so a warning would be mostly noise. DR19 in the backlog records the narrower check that would be safe.

On drupal/redirect and drupal/flag, the diagnostics are unchanged line for line, and time and memory match DR5.

Builds on #535.

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 5, 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.32127% with 75 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
src/symbol_map/extraction/drupal.rs 89.63% 40 Missing ⚠️
src/drupal/hooks.rs 97.02% 21 Missing ⚠️
src/drupal/computed_hooks.rs 93.33% 7 Missing ⚠️
src/completion/drupal_hooks.rs 98.87% 2 Missing ⚠️
src/completion/symfony_string_keys.rs 96.07% 2 Missing ⚠️
src/completion/laravel_string_keys/mod.rs 50.00% 1 Missing ⚠️
src/drupal/mod.rs 97.95% 1 Missing ⚠️
src/virtual_members/laravel/string_keys.rs 50.00% 1 Missing ⚠️

📢 Thoughts on this report? Let us know!

giuseppe-esposito-87 and others added 13 commits November 5, 2026 17:18
The hook_*() functions of *.api.php files document hooks, core's and contributed extensions' included, and a #[Hook] attribute in an extension's src/Hook jumps to that documentation, shows it on hover, and lists with the other implementations in its references. The Drupal walk also reads every extension's *.info.yml for the hooks to come.

Assisted-by: claude-code/claude-opus-5-5
A global function of a module's or theme's procedural file named after an extension implements the hook the rest of its name says, the way Drupal's hook collector reads it: the file's own extension first, then the other extensions of its group, legacy functions skipped, and nothing read after a scan stop. The hook part of the name jumps to the documentation and lists with the other implementations, while the extension part keeps the function's own references.

Assisted-by: claude-code/claude-opus-5-5
A module handler's or theme manager's invokeAll(), invoke(), alter(), and the other invoking calls name the hooks they fire, an alter type naming its _alter hook. Go-to-definition on one lists the implementations it reaches, of its group and of the extension invoke() names, and then the documentation; on an implementation it lists the invocations that reach it. Install-time hooks and update functions only link to their documentation.

Assisted-by: claude-code/claude-opus-5-5
An implementation of a hook core names from parts, such as acme_form_user_login_form_alter or acme_node_presave, reaches the documented pattern it fills in, hook_form_FORM_ID_alter or hook_ENTITY_TYPE_presave, where ENTITY_TYPE has to be a declared entity type. The pattern's references and go-to-definition list the implementations of every hook it stands for.

Assisted-by: claude-code/claude-opus-5-5
Naming a function in a module's or theme's procedural file offers <extension>_<hook> for every documented hook, inserting the documented parameters and a tab stop for each placeholder of a pattern. Hook names complete in #[Hook('…')] and in the module handler's and theme manager's invoking calls, an alter call's without its _alter.

Assisted-by: claude-code/claude-opus-5-5
A parameter a procedural hook implementation or a #[Hook] method leaves without a type or an @PARAM takes the type the hook's documentation gives the parameter at its place, through a documented pattern when the hook's name is computed.

Assisted-by: claude-code/claude-opus-5-5
A table of the core methods that fire hooks under computed names, ported from PHPlumb, lets go-to-definition on an implementation such as acme_form_user_login_form_alter or acme_node_presave list the invoking calls there that build a matching name, of its module or theme group and with the most written-out characters per method, before the documentation. An ENTITY_TYPE row only stands for the entity types its storage stores.

Assisted-by: claude-code/claude-opus-5-5
The playground's module documents, invokes, and implements a hook of its own and implements core's form alter hook procedurally, with Try comments at each site, and the assertions check each claim. DR6 leaves the backlog.

Assisted-by: claude-code/claude-opus-5-5
A function named after its module whose hook nothing documents or invokes is read as the function it is, so its references, definition, and rename work on its whole name. The hook documentation and the entity type IDs are read once until a declaring file changes, and a pattern is checked against the rest of a name before its entity type, so pattern matching and hook parameter typing stay cheap. Of the patterns a hook matches, the one that writes out the most documents it. Hover names the documentation, find references without declarations keeps the invocations, and test modules' invocations are left out.

Assisted-by: claude-code/claude-opus-5-5
A class that declares none of the table's core methods is turned away before its methods are compared with the table.

Assisted-by: claude-code/claude-opus-5-5
A property hook's parameter no longer reads the documentation of a hook implementation that follows it, and only a declaration's own function keyword is read for its name. Finding the hooks a pattern stands for keeps a set of the names seen, and hook completion reuses the pattern's own placeholder scanner.

Assisted-by: claude-code/claude-opus-5-5
The mixed workspace checks that a WordPress hook reaches only the plugin's calls when a Drupal module documents a hook of the same name, and that a module file's methods still complete as usual. A unit test checks that every invoking method of the module handler and the theme manager reaches a receiver offering that class's hooks.

Assisted-by: claude-code/claude-opus-5-5
Resolve a completed hook signature's hints with the *.api.php file's
own imports, so an imported class is written the way the
implementing file reaches it. Type an entity interface parameter of
a hook_ENTITY_TYPE_*() implementation as the entity class of its
type, which removes false unknown-member warnings on core. Drop the
hook index only when a file that documents hooks or declares entity
types changes, rather than on every edit of a file that invokes a
hook. Check a config entity's storage handler with the shared
subtype test instead of a depth-capped parent walk. File B734, a
typed by-reference parameter leaving the caller's variable at its
old type.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@AJenbo
AJenbo merged commit 27ff3c0 into PHPantom-dev:main Nov 5, 2026
15 checks passed
@AJenbo

AJenbo commented Nov 5, 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