Lewati ke konten
Rama's logo Qisthi Ramadhani
Go back

PHPStan Level 9 Strict Typing Guide for Laravel: How to Fix

TL;DR

PHPStan level 9 enforces the strictest static analysis rules in PHP, requiring full type coverage for all parameters, return types, properties, and generics. Laravel’s heavy use of magic methods (__get, __set, __call), dynamic facades, and loose-typed collections makes reaching level 9 compliance challenging. This guide walks you through identifying the symptoms, understanding the root cause, and implementing a production-tested fix with real code examples.


Symptoms

If you’re experiencing this issue, you’ll likely notice one or more of these signs in your application:

If any of these symptoms look familiar, you’re dealing with phpstan level 9 strict typing guide for laravel. Read on to understand why it happens and how to fix it properly.


Root Cause

PHPStan level 9 enforces the strictest static analysis rules in PHP, requiring full type coverage for all parameters, return types, properties, and generics. Laravel’s heavy use of magic methods (__get, __set, __call), dynamic facades, and loose-typed collections makes reaching level 9 compliance challenging. Key pain points include: Eloquent model properties not being type-safe by default, Collection generic types, facade return types, and service container resolution. The larastan (phpstan/phpstan for Laravel) extension resolves many of these by providing type stubs for Laravel’s magic.

Understanding the root cause is critical before applying a fix — treating only the symptoms often leads to the problem resurfacing in a different form, especially under production load.


How to Fix

Follow these steps in order. Each step builds on the previous one, and skipping steps may result in an incomplete fix.

Step 1: Install Larastan: composer require larastan/larastan —dev

Install Larastan: composer require larastan/larastan —dev

Step 2: Start at level 0 and incrementally increase

Start at level 0 and incrementally increase — don’t jump straight to 9

Step 3: Generate model type stubs: php artisan ide-helper:models —write to add @property PHPDoc to models

Generate model type stubs: php artisan ide-helper:models —write to add @property PHPDoc to models

Step 4: Use PHPStan baseline to ignore existing errors: phpstan analyse —generate-baseline

Use PHPStan baseline to ignore existing errors: phpstan analyse —generate-baseline

Step 5: Fix errors level by level, running your test suite after each batch of fixes

Fix errors level by level, running your test suite after each batch of fixes

Step 6: Add return types to all controller methods, service classes, and repository patterns

Add return types to all controller methods, service classes, and repository patterns

Step 7: Use @template and @extends annotations for custom Collection subclasses

Use @template and @extends annotations for custom Collection subclasses

Step 8: Configure phpstan.neon with strictRules, Laravel-specific rules, and custom paths to ignore

Configure phpstan.neon with strictRules, Laravel-specific rules, and custom paths to ignore


Code Example

The following before/after comparison shows the core pattern you need to change. Pay attention to the comments explaining why each change matters.

Before (Broken)

// PHPStan level 9 errors everywhere
class UserService
{
    public function findActive() // missing return type
    {
        return User::where('active', true)->get(); // Collection<mixed>
    }

    public function getFullName($user) // missing parameter type
    {
        return $user->first_name . ' ' . $user->last_name;
    }
}

After (Fixed)

// PHPStan level 9 compliant
use App\Models\User;
use Illuminate\Database\Eloquent\Collection;

class UserService
{
    /** @return Collection<int, User> */
    public function findActive(): Collection
    {
        return User::where('active', true)->get();
    }

    public function getFullName(User $user): string
    {
        return "{$user->first_name} {$user->last_name}";
    }
}

// phpstan.neon
includes:
    - vendor/larastan/larastan/extension.neon
parameters:
    level: 9
    paths:
        - app/
    ignoreErrors: []
    checkGenericClassInNonGenericObjectType: false

Key Takeaways


Difficulty Level

Intermediate

This guide assumes familiarity with the framework and its core tooling. You should understand basic database concepts, configuration patterns, and be comfortable reading framework source code when needed. Prior experience with similar issues will help but is not required.


Frequently Asked Questions

Is PHPStan level 9 worth it for Laravel projects?

Yes, for medium-to-large codebases with multiple developers. Level 9 catches entire categories of bugs at analysis time (null access, type mismatches, incorrect method calls). For solo developers or small projects, level 6-7 provides most of the value with less effort. The key is using Larastan and IDE Helper to handle Laravel’s magic methods.

How do I handle PHPStan errors in Eloquent relationships?

Install larastan and run php artisan ide-helper:models --write to generate @property PHPDoc annotations on your models. This tells PHPStan the types of all model attributes and relationships. For custom relationship types, add @return annotations to your relationship methods: /** @return HasMany<Post> */.


Share this post on:
LLM-friendly version:
Open in ChatGPT Open in Claude

Related Posts


Previous Post
PHP 8.4 Property Hooks Migration for Laravel: How to Fix
Next Post
Laravel Pulse: Production Monitoring Done Right