Skip to main content

Command Palette

Search for a command to run...

Fixing PHPStan map() Type Errors with Laravel Eloquent Collections

Updated
3 min read
Fixing PHPStan map() Type Errors with Laravel Eloquent Collections

TL;DR

Refactor property declaration for collection as

/** 
 * @property Collection<int, Comment> $comment 
 ...

Overview

When using PHPStan (or Larastan) with Laravel, you may encounter confusing type errors when calling collection methods like map() on Eloquent relationships. A very common one looks like this:

Parameter #1 $callback of method Collection::map() expects callable(Model, int), Closure(Comment) given

This blog explains why this happens, what PHPStan is actually telling us, and the correct, long-term fix that works cleanly across your codebase.


The Problem Scenario

Consider a Laravel model relationship:

$user->comments->map(
    fn (Comment $comment) => [ 
        'comment' => $comment->text, 
        'created_date' => $answer->created_at?->toArray(),
    ]);

At runtime, this works perfectly. However, PHPStan reports an error similar to:

Parameter #1 $callback of method Illuminate\Support\Collection::map() expects callable(Illuminate\Database\Eloquent\Model, int) Closure(Comment) given

Why This Happens

1. Eloquent Collections Are Generic

Laravel Collections are generic:

Collection<TKey, TValue>

If PHPStan does not know the exact model type inside the collection, it falls back to:

Collection<int, Model>

So PHPStan assumes your callback must accept any Model, not a specific one like Comment.

2. Property Annotations Are Often Incomplete

Many projects annotate Eloquent properties like this:

@property Illuminate\Database\Eloquent\Collection<Comment> $comments

Unfortunately, this is incomplete for PHPStan.

PHPStan requires both key and value types.


❌ What PHPStan Sees (Incorrect)

Collection<int, Model>

✅ What We Want PHPStan to See

Collection<int, Comment>

The Correct Fix

✅ Option 1: Fix the Property Annotation

Update the PHPDoc on your model by using PHPStan generics:

/** 
* @property \Illuminate\Database\Eloquent\Collection<int, Comment> $answers 
*/ 
class Comment extends Model { // ... }

This tells PHPStan exactly what the collection contains.


If answers is an Eloquent relationship, this is the best and cleanest solution.

use Illuminate\Database\Eloquent\Relations\HasMany; 

/** * @return HasMany<Comment, $this> */ 
public function comments(): HasMany { 
    return $this->hasMany(Comment::class); 
}

Benefits:

  • No need for @property annotations

  • PHPStan auto-infers the collection type

  • Works everywhere the relationship is used


Result: Clean map() Usage

After applying either fix, this works without errors:

$user->comments->map(
    fn (Comment $comment) => [ 
        ... 
    ]);

✅ PHPStan passes
✅ Strong typing preserved


Common Anti-Patterns to Avoid

❌ Removing the type hint:

fn ($comment) => [...]

❌ Widening the type unnecessarily:

fn (Model $comment) => [...]

These silence PHPStan but lose static safety.


Takeaways

  • PHPStan errors here are not false positives

  • The issue is almost always missing or incomplete generics

  • Always prefer typing relationship return values

  • This fix also applies to filter, each, flatMap, reduce, etc.


Final Recommendation for Teams

Always annotate Eloquent relationship methods with generics.

This single practice eliminates an entire class of PHPStan errors and makes your Laravel codebase safer and more maintainable.


For more details on generics in PHP, please read here

Happy static analyzing! 🚀