phplaraveleloquentphpstan

Trying to fix PHPStan Issues with Laravel's MorphTo relations and Model properties


We recently started using PHPStan 1.10 at level 6 in a Laravel 10 PHP 8.2 project. It reports a few remaining issues in the following model that I would like to fix.

class ArticlePrice extends Model
{

    protected $fillable = ['cost_type_id', 'type', 'type_id', 'amount'];

    /**
     * @var array<string> $with
     */
    protected $with = ['types'];

    /**
     * @return MorphTo
     */
    public function types(): MorphTo
    {
        return $this->morphTo('types', 'type', 'type_id');
    }

    /**
     * @return string
     */
    public function getName(): string
    {
        if ($this->types) {
            return $this->type === Article::class ? $this->types->vdm_code : $this->types->name;
        }

        return '';
    }
}

For the types() MorphTo it reports:

phpstan: Method App\Models\Environment\CostType\ArticlePrice::types() 
return type with generic class Illuminate\Database\Eloquent\Relations\MorphTo 
does not specify its types: TRelatedModel, TChildModel

But if I change it into MorphTo<Model, Article|Category> which reflects the actually models it could return at this point it complains about:

phpstan: Method App\Models\Environment\CostType\ArticlePrice::types() should return
 Illuminate\Database\Eloquent\Relations\MorphTo<Illuminate\Database\Eloquent\Model,
 App\Models\Shop\Article|App\Models\Shop\Category> 
but returns Illuminate\Database\Eloquent\Relations\MorphTo<Illuminate\Database\Eloquent\Model,
 App\Models\Environment\CostType\ArticlePrice>.

which is weird to me, since we are in ArticlePrice, this seems nonsensical to me. It has the exact same complaint when I do <Model, Model> instead.

The other issue is that it does not understand the content of $types:

phpstan: Access to an undefined property Illuminate\Database\Eloquent\Model::$name.
phpstan: Access to an undefined property Illuminate\Database\Eloquent\Model::$vdm_code.

I hoped fixing the morphTo issue would fix this as well, since vdm_code and name are documented properties of their models. But I fear this would not work, so this might need a different way of fixing the issue.

In short, how can I get PHPStan to understand how my polymorphic relation works, and how do I get it to recognise properties of the polymorphic related models?


Solution

  • I fixed the problem with $types with a workaround by adding this to the class level docblock:

    * @property-read Article|Category|null $types
    

    This makes sure PHPStan understands the types $types can be, and the properties it can have.

    Only downside is now, I can't run ide-helper:models on this model, this would undo it.

    And this is the way I fixed the issue with the MorphTo, though its not a satisfactory solution to me. Since to me it feels like this gives the wrong information about the relation about the possible return types. It does get rid of the PHPStan issue, but I rather have be able to give the correct information.

    * @return MorphTo<Model, ArticlePrice>