# Fixing PHPStan map() Type Errors with Laravel Eloquent Collections

> TL;DR
> 
> *Refactor property declaration for collection as*
> 
> ```php
> /** 
>  * @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:

```php
$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:

```plaintext
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**:

```php
Collection<TKey, TValue>
```

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

```php
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:

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

Unfortunately, this is **incomplete for PHPStan**.

PHPStan requires **both key and value types**.

---

## ❌ What PHPStan Sees (Incorrect)

```php
Collection<int, Model>
```

## ✅ What We Want PHPStan to See

```php
Collection<int, Comment>
```

---

## The Correct Fix

### ✅ Option 1: Fix the Property Annotation

Update the PHPDoc on your model by using PHPStan generics:

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

This tells PHPStan **exactly** what the collection contains.

---

### ⭐ Option 2 (Recommended): Type the Relationship Method

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

```php
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:

```php
$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](https://phpstan.org/blog/generics-by-examples)

*Happy static analyzing!* 🚀
