You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
ModelPropertyAccessorHandler registers magic properties for Eloquent attribute access only when a get{Name}Attribute() (legacy) or new-style Attribute cast method exists. It does not read protected $appends = [...].
When a model declares an attribute in $appends but resolves it through a dynamic mechanism (overridden __get/getAttribute, a trait, or magic value resolution), there is no accessor method to detect, so the plugin falls through and Psalm emits UndefinedMagicPropertyFetch.
class Attachment extends Post
{
use Aliases;
protected$appends = ['title', 'url', 'type', 'description', 'caption', 'alt'];
protectedstatic$aliases = [
'title' => 'post_title',
'url' => 'guid',
// ...
];
}
The Aliases trait overrides getAttribute() to look entries up in $aliases. No getUrlAttribute() method exists.
Corcel\Model\Meta\ThumbnailMeta::size():
return$this->attachment->url;
emits:
UndefinedMagicPropertyFetch: Magic instance property Corcel\Model\Attachment::$url is not defined
Real-app benchmark (psalm-app-benchmark against corcel @ v3.10.2) produces 4 such gaps on Attachment ($url ×3, $guid ×1 — $guid is the raw target column, separate concern).
Proposed fix
In ModelPropertyAccessorHandler::doesPropertyExist / isPropertyVisible / getPropertyType, after the existing accessor checks fail:
Read the default value of protected $appends on the model's class storage.
If $propertyName is in that list, return true for existence/visibility.
For type: defer to accessor handlers (already runs earlier); if no accessor matches, fall back to mixed.
Sketch:
if (self::isInAppends($codebase, $fqcn, $propertyName)) {
returntrue;
}
isInAppends reads $classStorage->properties['appends'] default value via the AST or suggested_type.
Scope notes
Generic: ships for every Laravel app declaring $appends without @property PHPDoc, not just corcel.
Does not address the related class of issues where the column itself (e.g. Attachment::$guid, Term::$name) is a raw DB column on a schema-less model (corcel reads WordPress tables, no migrations exist). That needs a separate "permissive fallback for schema-less Eloquent models" issue.
Acceptance
New type test in tests/Type/tests/ covering a model with $appends + no accessor → property resolves as mixed, no UndefinedMagicPropertyFetch.
Existing test where $appends entry has an accessor still uses the accessor's return type (current behavior unchanged).
Real-app benchmark on corcel: UndefinedMagicPropertyFetch count drops by the appends-resolved entries.
Problem
ModelPropertyAccessorHandlerregisters magic properties for Eloquent attribute access only when aget{Name}Attribute()(legacy) or new-styleAttributecast method exists. It does not readprotected $appends = [...].When a model declares an attribute in
$appendsbut resolves it through a dynamic mechanism (overridden__get/getAttribute, a trait, or magic value resolution), there is no accessor method to detect, so the plugin falls through and Psalm emitsUndefinedMagicPropertyFetch.Reproducer
corcel/corcelv9.0 (Corcel\Model\Attachment) declares:The
Aliasestrait overridesgetAttribute()to look entries up in$aliases. NogetUrlAttribute()method exists.Corcel\Model\Meta\ThumbnailMeta::size():emits:
Real-app benchmark (
psalm-app-benchmarkagainst corcel @ v3.10.2) produces 4 such gaps onAttachment($url×3,$guid×1 —$guidis the raw target column, separate concern).Proposed fix
In
ModelPropertyAccessorHandler::doesPropertyExist/isPropertyVisible/getPropertyType, after the existing accessor checks fail:protected $appendson the model's class storage.$propertyNameis in that list, returntruefor existence/visibility.mixed.Sketch:
isInAppendsreads$classStorage->properties['appends']default value via the AST orsuggested_type.Scope notes
$appendswithout@propertyPHPDoc, not just corcel.$appendsentries have corresponding accessors #694 (validate$appendsentries have accessors). Validate$appendsentries have corresponding accessors #694 is a write-side lint; this is a read-side resolver. They share the$appendsparser.Attachment::$guid,Term::$name) is a raw DB column on a schema-less model (corcel reads WordPress tables, no migrations exist). That needs a separate "permissive fallback for schema-less Eloquent models" issue.Acceptance
tests/Type/tests/covering a model with$appends+ no accessor → property resolves asmixed, noUndefinedMagicPropertyFetch.$appendsentry has an accessor still uses the accessor's return type (current behavior unchanged).UndefinedMagicPropertyFetchcount drops by the appends-resolved entries.