The PHPStan error you’re seeing is due to how generics and template types are handled in PHPStan, especially with Eloquent relationships. The core of the issue is that PHPStan expects the second template parameter of MorphMany to be the concrete model class (e.g., App\Models\Mod), but your trait and interface use a template (TModel) which resolves to $this in the trait context. PHPStan cannot guarantee that $this is exactly App\Models\Mod due to how traits and generics interact.
Key points:
- PHPStan’s Eloquent stubs define
MorphMany<TRelated, TDeclaringModel>. - The second parameter (
TDeclaringModel) should be the concrete model class, not$thisor a template type. - Traits cannot enforce the concrete type, only the consuming class can.
Solution
1. Use the correct docblocks in the trait
In your trait, you can use static as the declaring model type in the docblock. This is the closest you can get to what PHPStan expects, as static will resolve to the consuming class.
/**
* @return \Illuminate\Database\Eloquent\Relations\MorphMany<\App\Models\Comment, static>
*/
public function comments(): MorphMany
{
return $this->morphMany(Comment::class, 'commentable');
}
/**
* @return \Illuminate\Database\Eloquent\Relations\MorphMany<\App\Models\Comment, static>
*/
public function rootComments(): MorphMany
{
return $this->morphMany(Comment::class, 'commentable')
->whereNull('parent_id')
->whereNull('root_id')
->orderBy('created_at', 'desc');
}
2. In the concrete model, override the docblocks
In your concrete model (Mod), you can override the docblocks to specify the exact types:
use App\Models\Comment;
use Illuminate\Database\Eloquent\Relations\MorphMany;
class Mod extends Model implements Commentable
{
/** @use HasComments<self> */
use HasComments;
/**
* @return MorphMany<Comment, Mod>
*/
public function comments(): MorphMany
{
return parent::comments();
}
/**
* @return MorphMany<Comment, Mod>
*/
public function rootComments(): MorphMany
{
return parent::rootComments();
}
}
3. (Optional) Suppress the error if needed
If you want to avoid duplicating the methods just for docblocks, you can suppress the PHPStan error with an inline ignore:
/** @phpstan-ignore-next-line */
use HasComments;
Or, in your phpstan.neon config, you can ignore this specific error if it’s not critical.
Summary
- Use
staticas the declaring model type in trait docblocks. - Optionally, override the methods in the concrete class to specify the exact types for PHPStan.
- This is a limitation of how PHPStan and PHP generics work with traits and Eloquent relationships.
References:
Final trait example:
/**
* @return \Illuminate\Database\Eloquent\Relations\MorphMany<\App\Models\Comment, static>
*/
public function comments(): MorphMany
{
return $this->morphMany(Comment::class, 'commentable');
}
Final model example:
/**
* @return \Illuminate\Database\Eloquent\Relations\MorphMany<\App\Models\Comment, \App\Models\Mod>
*/
public function comments(): MorphMany
{
return parent::comments();
}
This should resolve the PHPStan error and provide correct type information.