Repository navigation
Drupal entity types and typed entity storage - #534
Merged
Merged
Conversation
This was referenced Nov 5, 2026
Closed
|
Codecov Report❌ Patch coverage is 📢 Thoughts on this report? Let us know! |
An entity type ID declared by an entity class's attribute or annotation completes, jumps to its declaration, and lists its references wherever the entity type manager is asked for it. getStorage() returns the entity type's storage handler tagged with its entity class, so load(), loadMultiple(), create(), and the other loads return that class, chained or through a variable. Test modules' entity types count only while open. Assisted-by: claude-code/claude-opus-5-5
The demo loads its note entity through the entity type manager, and the assertions check the entity type's class and storage with Drupal's own attribute. DR3 leaves the backlog. Assisted-by: claude-code/claude-opus-5-5
…ed type Hovering a method name shows the method as declared, so the inferred types of a container lookup and an entity load are shown on the variables that receive them. Assisted-by: claude-code/claude-opus-5-5
A load or getStorage() chained with no variable in between is typed like an assigned one, and getStorage(entity_type_id: 'x') is read like the positional form. A nested annotation's own pairs no longer override the entity type's ID or storage, a load on a union with other classes keeps their types, and looking an entity type up no longer runs the type engine over every declaring file. The Drupal hooks check the method name and the project before reading arguments, and reading entity types allocates nothing for classes that declare none. Assisted-by: claude-code/claude-opus-5-5
The mixed workspace also checks that a Symfony container builder's getDefinition() still names a service, and a unit test checks that every entity type manager method Drupal reads reaches the manager's target, so a same-named arm placed before Drupal's fails it. Assisted-by: claude-code/claude-opus-5-5
\Drupal::entityQuery(), \Drupal::entityQueryAggregate(), and a tagged storage's getQuery() start a query on an entity type, whose ID completes, navigates, and lists among its references. The query is tagged with the entity classes the way a storage is, and execute() gives that type's IDs, keyed by revision ID for a content entity and by ID for a configuration entity, or int<0, max> once count() was called, as phpstan-drupal types them. An aggregate query keeps core's type. DR3 leaves the backlog. Assisted-by: claude-code/claude-opus-5-5
loadOverrideFree(), loadRevisionUnchanged(), createWithSampleValues(), loadMultipleRevisions() and loadMultipleOverrideFree() on a typed storage now return the entity class, which removes false reports such as grantPermission() on a role loaded override-free in user.module. The playground no longer claims acme_note's query IDs are keyed by revision ID (it has no revisions), and its assertion checks the entity type definition instead of only the demo's source text. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
AJenbo
enabled auto-merge (squash)
November 5, 2026 14:48
AJenbo
disabled auto-merge
November 5, 2026 14:48
Contributor
|
Thank you for working on this, saves me a lot of work and probably gives a better result as well. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Adds DR3: entity type IDs map to their entity class and storage, so loaded entities have their real type.
#[ContentEntityType]/#[ConfigEntityType]attribute or the older annotation complete, jump to their declaration and list their references wherever the entity type manager is asked for them.getStorage()is typed as that entity type's storage, soload(),loadMultiple(),create()and the other loads return the entity class.\Drupal::entityQuery()and a storage'sgetQuery()give the IDs of that entity type, or their number aftercount(). An entity class PHPantom cannot load keeps core'sint|array.On
drupal/redirectanddrupal/flag, 17 false positives are gone, and one remaining report is a real mistake inflag(a method returns aFlagwhereboolis declared). Time and memory are unchanged.Why this PR comes before DR5 and DR6
Sprint 8 in
docs/todo.mdschedules DR1, DR2, DR5 and DR6, but not DR3. Two of those need entity types:view(Note $acme_note)), which needs to know which classacme_noteis.hook_node_insert(), which needs the list of entity types.So DR3 comes first, and the DR5 and DR6 PRs build on it.
Same-named methods on different classes
How a call is read today. The extractor first maps a method's name (and argument position) to one receiver group, and only then checks the receiver's class against that group. The first matching arm wins:
The collision. Drupal's entity type manager also has
getDefinition('node')andhasDefinition('node'), taking an entity type ID. The name maps to theContainerBuildergroup, the entity type manager is not in it, so the Drupal call was silently never read.What this PR does. The two names get a shared group that lists both classes, each with its own kind of name, the same way upstream already shares
generate()between Symfony and Yii. Symfony behaves as before:The general case. Any two frameworks with a same-named method will collide the same way, and nothing reports it. A table keyed by class and method would avoid that, since the receiver's class picks the row:
It is a larger change to shared code, so it is not in this PR. I'd be glad to discuss it separately.
Builds on #533.
Checklist
If applicable:
CHANGELOG.mdREADME.md,docs/,examples/)config-schema.json). Not applicable: this PR adds or changes no setting.the changes and why they are needed.
the changelog, and other user-facing content, and removed or corrected
anything inaccurate, unclear, or unnecessary.