Give interface-typed entities magic field properties - #1053
Merged
Conversation
EntityFieldsViaMagicReflectionExtension and EntityFieldReflection used implementsInterface() and isSubclassOfClass(), both false for the interface itself, so a value typed ContentEntityInterface had no field properties while a Node did. Both checks now use is(). That alone is not enough. PHPStan only consults property reflection extensions for an interface that allows dynamic properties, and none of the entity interfaces declare __get(). Registering ContentEntityInterface as a universal object crate opens that gate for it and for every interface extending it. As a side effect, @Property tags declared on those interfaces now resolve too. With the extension disabled, entity field access is typed mixed instead of reported as an undefined property. EntityFieldReflection no longer needs a ReflectionProvider, so the extension no longer takes one either. Closes #1048 Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
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.
Closes #1048. First slice of #929, scoped to
ContentEntityInterface. Type inference fix.What changed
A parameter typed
ContentEntityInterface,NodeInterface, or any other interface extendingContentEntityInterfacenow gets the same magic field properties as a concrete entity class. Before,$entity->field_fooon such a value was reported as an undefined property while the same access on aNodeworked.Two layers needed fixing:
EntityFieldsViaMagicReflectionExtensionusedimplementsInterface()andEntityFieldReflectionusedisSubclassOfClass(). Both are false when the reflected class is the interface. Both now useis(), the same change Align legacy rules and reflections with current PHPStan API conventions #1027 made forFieldItemListInterface.__get()declaration, an#[AllowDynamicProperties]attribute, a@phpstan-require-extendstag, or membership inuniversalObjectCratesClasses. Entity interfaces have none of these.extension.neonnow listsDrupal\Core\Entity\ContentEntityInterfaceas a universal object crate, which matches every interface extending it. That is an honest description:ContentEntityBase::__get()accepts any property name.EntityFieldReflectionno longer needs aReflectionProvider, so the extension's constructor no longer takes one.Why not
@phpstan-require-extendsA stub with
@phpstan-require-extends ContentEntityBaseopens the gate forContentEntityInterfaceonly. ForNodeInterface, PHPStan then resolves properties through the required class before consulting the interface's own@propertytags, and$node->booklost itsBookDatatype. The crate list opens the gate for the interface hierarchy itself, so the annotations extension runs first and@propertytags keep winning.Side effects worth knowing
@propertytags on entity interfaces now resolve.NodeInterface::$bookand a project's own@property string $customon an interface extendingContentEntityInterfacewere undefined-property errors before. This is a strict improvement.entityFieldsViaMagicReflection: false, entity field access ismixedinstead of an undefined-property error. The crate registration is what opens the gate, and the crate extension is the fallback when ours is off. The README note under "Disabling extensions" documents this.Testing
Unit cases for
hasProperty()onContentEntityInterface(true) andEntityInterface(false), andgetProperty()onContentEntityInterface. Fixture cases forfield_myfieldandoriginalon aContentEntityInterfaceparameter andfield_myfieldon aNodeInterfaceparameter. The existingbook-modulefixture guards the@propertyprecedence. Full suite, self-analysis, and phpcs are green.🤖 Generated with Claude Code