troccoli's avatar

Larastan and Eloquent relations

I'm starting a new project so I installed Larastan from the start, set at level 8.

I have two models DataPortal and Programme and a belongsTo relationship between the two

class DataPortal extends Model
{
    public function programme(): BelongsTo
    {
        return $this->belongsTo(Programme::class);
    }
}

Running Larastan returns the following error:

Method App\Models\DataPortal::programme() return type with generic class Illuminate\Database\Eloquent\Relations\BelongsTo does not specify its types: TRelatedModel, TDeclaringModel

So I added a docblock

    /** @return BelongsTo<Programme> */
    public function programme(): BelongsTo
    {
        return $this->belongsTo(Programme::class);
    }

But Larastan still complains, twice:

Generic type Illuminate\Database\Eloquent\Relations\BelongsTo<App\Models\Programme> in PHPDoc tag @return does not specify all template types of class Illuminate\Database\Eloquent\Relations\BelongsTo: TRelatedModel, TDeclaringModel

Method App\Models\DataPortal::programme() should return Illuminate\Database\Eloquent\Relations\BelongsTo<App\Models\Programme> but returns Illuminate\Database\Eloquent\Relations\BelongsTo<App\Models\Programme, $this(App\Models\DataPortal)>.

So I change the docblock to:

    /** @return BelongsTo<Programme, DataPortal> */
    public function programme(): BelongsTo
    {
        return $this->belongsTo(Programme::class);
    }

which got rid of the first error but not the second.

I wouldn't think I had to specify the declaring model, i.e. DataPortal but I can live with that if it had worked.

So, my question is, what is the right way to write the docblock for Eloquent relations?

P.S. I know I could disable the rule, but it should be possible to write a docblock that passes the rule.

0 likes
10 replies
LaryAI's avatar
Level 58

To resolve the issue with Larastan and the Eloquent relationship, you need to ensure that the docblock specifies both the related model and the declaring model for the BelongsTo relationship. The error you're encountering is because Larastan expects both template types to be specified.

Here's how you can correctly write the docblock for your programme method:

use Illuminate\Database\Eloquent\Relations\BelongsTo;
use App\Models\Programme;

/**
 * @return BelongsTo<Programme, DataPortal>
 */
public function programme(): BelongsTo
{
    return $this->belongsTo(Programme::class);
}

This docblock specifies both the related model (Programme) and the declaring model (DataPortal), which should satisfy Larastan's requirements. Make sure that your use statements at the top of your file are correct, as shown in the example, to ensure that the classes are properly referenced.

If you continue to experience issues, double-check that your Larastan configuration is up-to-date and that there are no other conflicting settings or rules that might be causing the error.

troccoli's avatar

No @laryai , that does not work. I still get

Method App\Models\DataPortal::programme() should return Illuminate\Database\Eloquent\Relations\BelongsTo<App\Models\Programme, App\Models\DataPortal> but returns                   
 Illuminate\Database\Eloquent\Relations\BelongsTo<App\Models\Programme, $this(App\Models\DataPortal)>.

But I just notice this too

Template type TDeclaringModel on class Illuminate\Database\Eloquent\Relations\BelongsTo is not covariant.
troccoli's avatar
troccoli
OP
Best Answer
Level 6

Answering my own question, in case other's are looking for this.

    /** @return BelongsTo<Programme, $this> */
    public function programme(): BelongsTo
    {
        return $this->belongsTo(Programme::class);
    }

It works with Larastan 3.0.0

16 likes
__jean__'s avatar

Hi every one I encountered the same issue and manage to fix it with the proposed solution but ...

As soon as you are using a pivot model the fix is not working anymore

    /**
     * @phpstan-return BelongsToMany<Lot, $this>
     * @return BelongsToMany<Lot, $this>
     */
    public function lots(): BelongsToMany
    {
        return $this->belongsToMany(Lot::class)
            ->using(ClientLot::class);
    }

throws this phpstan error

Method App\Models\Client::lots() should return Illuminate\Database\Eloquent\Relations\BelongsToMany<App\Models\Lot,
         $this(App\Models\Client)> but returns Illuminate\Database\Eloquent\Relations\BelongsToMany<App\Models\Lot,
         $this(App\Models\Client), Illuminate\Database\Eloquent\Relations\Pivot>.
         🪪  return.type

I haven't yet found an solution on my own. Has anyone ever experienced this issue ?

mattroylloyd's avatar

@__jean__ if your ClientLot extends Illuminate\Database\Eloquent\Relations\Pivot, you should be able to use

    /**
     * @return BelongsToMany<Lot, $this, ClientLot>
     */
    public function lots(): BelongsToMany
    {
        return $this->belongsToMany(Lot::class)
            ->using(ClientLot::class);
    }

__jean__'s avatar

@mattroylloyd Hi,

I confirm that the ClientLot model extends Illuminate\Database\Eloquent\Relations\Pivot.

But I cannot add a third parameter in the phpdoc. When I do this phpstan throws another exception saying that BelongsToMany only support 2 parameters.

troccoli's avatar

@__jean__

I have just upgraded to Laravel from 12.2 to 12.3 and I got the same issue now.

To clarify, I have many-to-many relationship

class Team extends Model
{
    /** @return BelongsToMany<Division, $this> */
    public function divisions(): BelongsToMany
    {
        return $this->belongsToMany(Division::class);
    }
}

class Division extends Model
{
    /** @return BelongsToMany<Team, $this> */
    public function teams(): BelongsToMany
    {
        return $this->belongsToMany(Team::class);
    }
}

Larastan, level 8, threw the error that the teams() method returns BelongsToMany<Team, $this(Division), Pivot> and similarly for the divisions() method (I have simplified the message).

But adding Pivot

/** @return BelongsToMany<Division, $this, Pivot> */
public function divisions(): BelongsToMany
{
    return $this->belongsToMany(Division::class);
}

resulted in a different error, as @__jean__ reported too

Generic type Illuminate\Database\Eloquent\Relations\BelongsToMany<App\Models\Division, $this(App\Models\Team), Illuminate\Database\Eloquent\Relations\Pivot> in PHPDoc tag @return specifies 3 template types, but class Illuminate\Database\Eloquent\Relations\BelongsToMany supports only 2: TRelatedModel, TDeclaringModel  

Please or to participate in this conversation.