Trying to fix PHPStan Issues with Laravel's MorphTo relations and Model properties
Stefan Bogdanescu
Founder & Senior Architect · 2026-06-29
Taming Polymorphism: Fixing PHPStan Issues with Laravel's MorphTo Relations
As developers working with dynamic frameworks like Laravel and Eloquent, we often encounter a fascinating tension between runtime flexibility and static analysis rigor. When utilizing polymorphic relationships, such as morphTo, the resulting type system can become convoluted, causing static analyzers like PHPStan to throw confusing errors about undefined properties or mismatched generics.
Recently, I ran into similar issues while trying to enforce strict type safety in a Laravel 10/PHP 8.2 project using PHPStan level 6. The core challenge lies in teaching the static analyzer how to understand the dynamic nature of polymorphic relations and the properties they contain. This post dives into the specific problems encountered with morphTo and model properties, and provides practical solutions for satisfying PHPStan.
The Anatomy of the Problem: Dynamic Types vs. Static Analysis
The issues we faced stem from a mismatch between how Eloquent defines relationships at runtime (based on database columns) and how PHPStan attempts to infer types statically.
Consider the example structure you provided:
class ArticlePrice extends Model
{
// ...
public function types(): MorphTo
{
return $this->morphTo('types', 'type', 'type_id');
}
// ...
}
When PHPStan analyzes this, it sees a return type of MorphTo but doesn't know which concrete model (Article or Category) will be returned at runtime. This leads to the first complaint:
return type with generic class Illuminate\Database\Eloquent\Relations\MorphTo does not specify its types: TRelatedModel, TChildModel
This is PHPStan correctly pointing out that the generic parameters of MorphTo need to be explicitly defined to understand what objects are being linked.
Furthermore, when attempting to access properties on the result (e.g., $this->types->vdm_code), PHPStan fails because it only sees the base MorphTo object, not the specific model types that implement those properties. This is the "undefined property" error:
Access to an undefined property Illuminate\Database\Eloquent\Model::$name.
Solution 1: Correcting MorphTo Generics for Full Type Information
The key to solving the first issue is to leverage PHP's generics correctly within the method signature. By explicitly defining the possible types returned by the relation, we give PHPStan the necessary context.
Instead of a generic return, we need to specify the specific models that can be related:
use Illuminate\Database\Eloquent\Relations\MorphTo;
use App\Models\Article;
use App\Models\Category; // Assuming Category is the other potential type
class ArticlePrice extends Model
{
// ...
/**
* @return MorphTo<Model, Article|Category>
*/
public function types(): MorphTo
{
// The method remains simple, but the docblock provides the necessary context.
return $this->morphTo('types', 'type', 'type_id');
}
// ...
}
By using MorphTo<Model, Article|Category>, we explicitly tell PHPStan that this relationship can resolve to an instance of Article or Category, which informs the analyzer about the potential properties available on those classes. This resolves the initial generic complaint.
Solution 2: Handling Polymorphic Property Access Safely
The second issue—accessing $this->types->vdm_code—is less about the relationship itself and more about dynamic property access. Since the actual type of $this->types is known only at runtime, we must use type narrowing or careful casting to satisfy PHPStan.
Since the types() method returns a polymorphic relation object, you cannot safely assume it has properties like vdm_code. You must perform a runtime check before accessing these properties:
public function getName(): string
{
if ($this->types) {
// Use instance checking to narrow down the type context
if ($this->types instanceof Article) {
return $this->types->vdm_code;
} elseif ($this->types instanceof Category) {
return $this->types->name;
}
}
return '';
}
In a larger application, this pattern—checking the type of an Eloquent relationship before accessing its dynamic properties—is crucial when dealing with polymorphic associations. This defensive coding ensures that your code remains robust even when static analysis struggles to infer every possible path, aligning with best practices you find in modern Laravel development. For deeper insights into structuring complex models and relationships, exploring resources from the Laravel Company is highly recommended.
Conclusion
Fixing PHPStan issues in dynamic Laravel applications requires bridging the gap between runtime behavior and static analysis expectations. By correctly defining generic types for Eloquent relations using MorphTo<Model, TypeA|TypeB>, we satisfy the analyzer about what data can exist. By implementing explicit runtime type checks, we ensure that property access remains safe and predictable, regardless of which concrete model is currently linked. This combination ensures your codebase benefits from both Laravel's flexibility and PHPStan's strictness.