Skip to content

Drupal entity fields - #564

Open
giuseppe-esposito-87 wants to merge 11 commits into
PHPantom-dev:mainfrom
giuseppe-esposito-87:drupal-dr4
Open

giuseppe-esposito-87 wants to merge 11 commits into
PHPantom-dev:mainfrom
giuseppe-esposito-87:drupal-dr4

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 DR4: a Drupal content entity's fields are typed properties of the entity and of its interfaces, and their names complete and navigate.

function show(NoteInterface $note): string {
    $tags = $note->field_tags;              // EntityReferenceFieldItemList<…, Term>
    $first = $note->field_tags->entity;     // ?Term
    $title = $note->get('title')->value;    // mixed
    return $first?->label() ?? '';
}
  • Base fields come from the entity class's baseFieldDefinitions(), with the fields core adds from the entity keys and from the owner, published and revision log traits.
  • Configurable fields come from field.storage.*.yml files in modules, profiles and the site's exported configuration (config/sync beside the web root).
  • Field properties. A field reads the properties its item class declares (value, target_id, entity, …). entity is typed as the class of the entity type the field points to; stored values stay mixed.
  • Field names in code. The name passed to get(), set(), hasField() and getFieldDefinition() completes, goes to its declaration, shows its field type on hover and finds its references, property reads included. get('title') is typed like ->title.
  • Interfaces. A property read on an entity interface no longer reports "Property not found": a known field is typed, and any other name is mixed, as it is on the entity class. On Drupal core 11.4 this removes about 640 false reports.

Three follow-ups are filed in the roadmap: fields added by hooks (DR29), a warning for unknown field names (DR30), and field names in entity queries (DR31).

How to test: run composer install in examples/drupal, open examples/drupal with this branch's build, then open web/modules/custom/acme/src/NoteSummary.php:

  • Complete after $note-> to see title, id, uid and field_tags. Go to the definition of title (it lands in Note.php) and of field_tags (it lands in config/sync/field.storage.acme_note.field_tags.yml).
  • Hover $tags and $firstTag to see the Term entity reference list and ?Term.
  • In isTagged(), complete inside hasField(''), and find the references of 'field_tags': they include the $note->field_tags reads.

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.

Drupal content entities read their fields and any other stored value through ContentEntityBase::__get(), but the interfaces they are typed with (NodeInterface, ContentEntityInterface) declare no __get(), so every field read on them was reported as an unknown property. In a Drupal project, interfaces that extend FieldableEntityInterface now get the same untyped __get() the class has, so a read on them is mixed, as on the class. Interfaces without fields keep reporting unknown properties.

Also adds the mapping from a fieldable receiver to the entity types it may hold, which the field readers build on.

Assisted-by: claude-code/claude-opus-5-5
A property a framework stores through __set(), such as a Drupal entity field, now counts as a magic write: assigning to it no longer narrows a later read to the assigned value, since the setter stores the value into the field and the read keeps the field's type. Properties declared by @Property tags still narrow as before.

Assisted-by: claude-code/claude-opus-5-5
The entity type reader now also keeps the entity_keys and revision_metadata_keys a declaration writes, whether it is translatable, and whether it is a content entity type, from attributes and from annotations alike. Annotation values written as bare words, such as translatable = TRUE, are now read too.

Assisted-by: claude-code/claude-opus-5-5
The plugin index now keeps the list_class a field type's attribute or annotation names, so a field's item list class can be found from its field type.

Assisted-by: claude-code/claude-opus-5-5
The base fields an entity class's baseFieldDefinitions() declares are now typed properties of the class and of the interfaces it implements: $node->title is a FieldItemList<StringItem>, and a reference field such as $node->uid also names the class of the entity type it points to. Fields taken from the parent class and from core's owner, published and revision log helpers count, under the names the entity type's keys give them, as do the boolean fields Drupal's entity field manager adds itself. An interface several entity classes implement has the fields all of them have. The names complete after ->, go-to-definition lands on the line that declares the field, and writing to a field keeps its type. Editing an entity class or a field type refreshes the fields everywhere they show.

Assisted-by: claude-code/claude-opus-5-5
The fields that field storage files declare are now typed properties of their entity type's class and interfaces, like base fields: $node->field_tags is an EntityReferenceFieldItemList<EntityReferenceItem> that names Term. Field storage files count in extensions' config/install and config/optional, core's profiles included, and anywhere else in the project outside vendor, core and contrib, recipes, and tests, which covers config/sync and config split folders; a test module's file counts while it is open. A field declared by several files is one field, typed by all of them, and go-to-definition lists every file. Adding, editing, or deleting a field storage file refreshes the fields everywhere they show.

Assisted-by: claude-code/claude-opus-5-5
The symbol map now keeps what a field item class's propertyDefinitions() declares: each property it adds to the array it returns, with the data type its definition creates, the properties it unsets, and whether it takes its parent's.

Assisted-by: claude-code/claude-opus-5-5
A field item class now has the properties its propertyDefinitions() declares, with those it takes from its parent's and without those it unsets, and a field reads them on its first item, as Drupal's item lists do: $node->body->format and $node->field_tags->target_id complete and resolve. A reference field's entity property is its target class, nullable since an empty field gives null, or any entity when the field names no target, on the list and on its items alike, and referencedEntities() gives the target class too. A language property is a nullable language, and every stored value stays mixed. Writing a property keeps its type. Editing an item class's propertyDefinitions() refreshes its properties on the item, the classes that extend it, and the fields that hold it.

Assisted-by: claude-code/claude-opus-5-5
Assisted-by: claude-code/claude-opus-5-5
A field name passed to a fieldable entity's get(), set(), hasField() and getFieldDefinition() completes, goes to its declaration, shows its field type on hover, and finds its references, the property reads included. get() with a known field name is typed like reading the field as a property.

Assisted-by: claude-code/claude-opus-5-5
Files the follow-ups: fields added by hooks, warnings for unknown field names, and field names in entity queries.

Assisted-by: claude-code/claude-opus-5-5
@codecov-commenter

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 97.34924% with 80 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
src/symbol_map/extraction/drupal.rs 97.51% 28 Missing ⚠️
...e_engine/call_resolution/return_types/call_arms.rs 12.00% 22 Missing ⚠️
src/drupal/base_fields.rs 97.66% 18 Missing ⚠️
src/drupal/field_items.rs 96.89% 6 Missing ⚠️
src/drupal/fields.rs 99.28% 2 Missing ⚠️
src/blade/typed_receiver.rs 93.33% 1 Missing ⚠️
src/drupal/field_storage.rs 99.45% 1 Missing ⚠️
src/references/dispatch.rs 95.00% 1 Missing ⚠️
src/references/member_scope.rs 97.56% 1 Missing ⚠️

📢 Thoughts on this report? Let us know!

This branch has not been deployed

No deployments
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.

2 participants