larastan / larastan

⚗️ Adds code analysis to Laravel improving developer productivity and code quality.

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Wrong model instance is returned from builder collection

vunguyen-it opened this issue · comments

  • Larastan Version: v2.6.4
  • --level used: 5
  • Pull request with failing test:

Description

When retrieving models using the morphToMany relation, it doesn't return the correct model instance.
It seems not to work correctly with the morphToMany relationship, other kinds of relation work well.

Laravel code where the issue was found

Relation definition:

/**
     * A model may have multiple roles.
     */
    public function roles(): BelongsToMany
    {
        return $this->morphToMany(
            config('permission.models.role'),
            'model',
            config('permission.table_names.model_has_roles'),
            config('permission.column_names.model_morph_key'),
            PermissionRegistrar::$pivotRole
        );
    }

Function having issue:

     /**
     * Get the role associated with the User
     */
    public function getRoleAttribute(): Role | null
    {
        if ($this->relationLoaded('roles')) {
            return $this->roles->first();
        }

        return $this->roles()->first();
    }

Error message:
Method App\Models\User::getRoleAttribute() should return Spatie\Permission\Models\Role|null but returns Illuminate\Database\Eloquent\Model|null.

Please add PHP generics to roles method.

Like @szepeviktor said, it is not possible to statically determine the return type of the roles method. You can help yourself by adding genetic return type.

In my case, the roles method belongs to the 3rd party lib, so I can't modify it. Instead, adding a variable type suggestion like ** @var Type to the getRoleAttribute method can help. Here is the code for anyone that interested:

public function getRoleAttribute(): Role | null
    {
        if ($this->relationLoaded('roles')) {
            /** @var Role|null */
            return $this->roles->first();
        }

        /** @var Role|null */
        return $this->roles()->first();
    }

In my case, the roles method belongs to the 3rd party lib, so I can't modify it.

This is what stubs are for!!