Static Analysis in Laravel 13 with Larastan and PHPStan
Larastan, the PHPStan extension for Laravel, catches errors in facades, Eloquent, and collections without running a single line: install it with one composer require and hook it into CI so every push reviews your code. Static analysis in Laravel 13 is the next step toward fewer bugs in production.
What Static Analysis Is and Why Laravel Needs It
Static analysis examines code without executing it: it walks through your classes, methods, and calls, flagging type mismatches, wrong arguments, or accesses to properties that do not exist. It is a reviewer that works in milliseconds and never skips anything, exactly what a human misses during an afternoon review.
PHPStan: The Analyzer That Understands PHP Without Running It
PHPStan is the most popular static analysis engine for PHP. It does not run your application: it reads it and builds a model of which types flow through every variable, parameter, and return value, warning you when something does not add up. The stricter the configured level, the more rules it applies and the finer the filter.
What Larastan Adds: Facades, Eloquent, and Collections
The problem is that Laravel is full of "magic": facades resolve classes at runtime, Eloquent creates dynamic properties from database columns, and collections rely on generics. PHPStan on its own cannot understand that magic, so it either ignored the code or flooded it with false positives. Larastan teaches PHPStan the rules of Laravel: it understands facades, types Eloquent properties, and validates collection generics. The result is that analysis focuses on real errors in your code.
Installing Larastan in Laravel 13
Installation is a single command and the initial configuration fits in three lines. You do not need to touch your application, just add a development dependency.
composer require --dev larastan/larastan:^3.0
Larastan v3 is the current version, compatible with PHPStan 2.x and Laravel 11.15 or newer, so Laravel 13 (which runs on PHP 8.3+) is more than covered. Install it as a development dependency:
composer require --dev "larastan/larastan:^3.0"Because it is --dev, it does not bloat the production autoloader or get installed on deploys that run composer install --no-dev.
phpstan.neon: includes, paths, and level
Then create phpstan.neon at the project root with the minimal configuration:
includes:
- vendor/larastan/larastan/extension.neon
parameters:
paths:
- app
level: 5The includes section loads Larastan's rules, paths tells it which folders to analyze, and level sets how strict the analysis is. With that in place you can already run the analysis:
vendor/bin/phpstan analyse --no-progressStarting at the Right Level
PHPStan defines ten levels, from 0 to 9, and each one adds stricter rules on top of the previous. There is no universal "correct" level: there is a correct level for each project and moment.
PHPStan Levels: From 0 to 9
Level 0 runs basic type checks and calls to known functions. Moving up means demanding more: checking types on properties and return values, handling nullable values, array types, and at the highest levels, correct generics in collections and calls to methods on types that could be null. Level 9 is the ceiling and the strictest of all.
Level 5 vs Level 9: What You Gain with Each
Level 5 is an excellent starting point: it catches most real bugs without drowning you in typing details. Level 9, on the other hand, demands total rigor: in Laravel the most common pain when moving up is collections, because you have to type their generics explicitly. If your team is small or the project is legacy, start at 5 and raise it as the code can handle it; if the project is new, set 9 as a medium-term goal.
Errors Larastan Catches (with Examples)
Seeing the analysis work on concrete errors is the best way to understand its value. These three are the classics Larastan catches before they reach production.
Dynamic Eloquent Properties
An Eloquent model exposes its columns as properties, but PHP does not know them at the static level. Larastan uses model docblocks to know which columns exist:
/** @property string $name */
class User extends Model {}With that, accessing $user->nmae stops being a silent runtime error and becomes an analysis error on the spot. The typo that used to blow up in production now shows up in your terminal.
Facades and Magic Methods
Facades delegate to underlying classes through magic methods, something PHPStan cannot track on its own. Larastan knows the real facade-to-class mapping, so a call to a method that does not exist on Cache:: or DB:: is caught during static analysis, not when a user triggers it.
Collections and Generics: The Level 9 Pain Point
Collections are where most projects get stuck at the higher levels. collect([1, 2, 3]) returns a collection of integers, and PHPStan wants to know it:
/** @var Collection<int, User> $users */
$users = User::all();With the type declared, methods like first() or map() return known types and the analysis validates whole chains. Without the declaration, level 9 is nothing but generic complaints.
The Baseline: Static Analysis in Legacy Projects
Adopting static analysis in a legacy codebase is scary because the first run usually returns hundreds of errors. The baseline exists exactly for that: freeze the current state and move forward without blocking delivery.
vendor/bin/phpstan analyse --generate-baseline
The command generates a file with all current errors:
vendor/bin/phpstan analyse --generate-baselineIt creates phpstan-baseline.neon listing the existing errors. Once included in phpstan.neon, PHPStan ignores those frozen errors but still fails on any new one. That is how you start analyzing today without having to fix a year of technical debt tomorrow.
Raising the Level Gradually Without Blocking Delivery
With the baseline active, the team can burn errors down little by little: each PR fixes some, the baseline file gets regenerated, and the level can rise once the frozen error count drops. It is a realistic path from level 0 to level 9 in a project that has been in production for years, without blocking development at any point.
Larastan in CI with GitHub Actions
Static analysis only does its job if it runs on every change, and that is where CI comes in. A GitHub Actions job that runs PHPStan on every push turns review into something automatic and mandatory.
The Analysis Job on Every Push
A minimal job looks like this:
name: Static Analysis
on: [push]
jobs:
phpstan:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: shivammathur/setup-php@v2
with:
php-version: '8.3'
- run: composer install --no-interaction --prefer-dist
- run: vendor/bin/phpstan analyse --no-progressIf the analysis finds errors, the job fails and the PR cannot be merged. From there, the flow is the same on GitLab CI or Travis: one more step in the pipeline, and quality is guaranteed by the tool, not by the team's memory.
New Projects: No Baseline from Day One
If you are starting a new project, you do not need a baseline: configure a high level from the first commit and analysis demands clean code from the beginning. It is much cheaper to maintain level 9 on a one-month-old project than to reach it on a three-year-old one.
Larastan + Laravel Pint: Complete Code Quality
It is important to understand what each tool does: Larastan finds type and call errors, it does not fix style. For formatting and code conventions there is Laravel Pint, Laravel's official formatter based on PHP-CS-Fixer. The combination works as a permanent automatic code reviewer: Pint keeps the style uniform and Larastan watches over type correctness, both without human intervention and running on every push.
Conclusion
Larastan brings static analysis to Laravel 13 and turns PHPStan into a reviewer that understands facades, Eloquent, and collections: install it with one command, configure it in three lines, and integrate it into CI so every push reviews your code. Start at a low level, use the baseline if you have legacy code, and raise the level calmly. If you already automate your deploys with GitHub Actions or just migrated to Laravel 13, this is the missing piece before bugs reach production.