Introduction
We've all been there: a deploy goes live, and within minutes you're looking at a Slack message about a property that doesn't exist or a method call that was renamed three months ago. Static analysis with PHPStan and Larastan for production environments catches these bugs without running code, giving your team confidence before deployment. If you're serious about reliability, adding static analysis to your CI pipeline isn't optional—it's foundational.
In this article, we'll walk through setting up PHPStan and Larastan, configuring them at a level that matches your team's readiness, and integrating them into your workflow so they work for you rather than against you.
What PHPStan does and why it matters
PHPStan is a static analyzer that examines your PHP code without executing it. It catches type errors, undefined variables, incorrect array accesses, and countless other issues that would otherwise only surface when someone actually runs that code path in production.
Think of it like a linter on steroids. Where PHP's own type declarations help, PHPStan goes further—it infers types from patterns, follows data flows across function boundaries, and understands nullable types and union types. A simple example:
$user = User::find($id);
echo $user->email;PHPStan knows that User::find() may return null, so accessing email without a null check is an error. It flags this immediately, before any request hits that code.
The real value emerges on larger codebases. When a teammate refactors a method signature, when a package updates and a class moves, when someone forgets to check an array key—PHPStan catches it. For teams running e-commerce platforms, trading systems, or SaaS applications where downtime or data corruption is expensive, this kind of safety net is invaluable.
Start at a low level and raise it gradually
The biggest mistake teams make is setting PHPStan to its strictest level and expecting to pass immediately. You'll have hundreds of errors, your team will be overwhelmed, and static analysis gets a bad reputation. Instead, start low and climb.
PHPStan uses nine levels, from 0 (basic checks) to 8 (strictest). Begin at level 0 or 1 on an existing codebase:
// phpstan.neon
parameters:
level: 1
paths:
- app
- srcRun it once. You'll likely see a manageable number of errors. Fix the obvious ones—undefined variables, missing returns, obvious type mismatches. Then move to level 2, fix what emerges, and repeat.
On a mature Laravel project, expect this progression to take weeks or months. That's normal. You're not just fixing code; you're building a culture where type safety and code clarity matter. A senior engineer will recognize that jumping straight to level 8 on legacy code is a sunk cost trap.
Here's a realistic configuration for a team midway through adoption:
// phpstan.neon
parameters:
level: 5
paths:
- app
- src
excludePaths:
- tests
treatMissingPropertyAsNull: true
ignoreErrors:
# Temporary: Larastan can't always infer collection types
- '#Call to an undefined method.*\|null#'Note the ignoreErrors section. You'll use this to suppress false positives while you work upward. Later, clean them out.
Larastan understands Laravel magic
Vanilla PHPStan doesn't understand Laravel's container, facades, or dynamic method calls. Larastan is a PHPStan extension specifically built for Laravel codebases. It understands how DB::table() works, what Auth::user() returns, and how model relationships resolve.
Install it via Composer:
composer require --dev larastan/larastanThen update your phpstan.neon:
includes:
- vendor/larastan/larastan/extension.neon
parameters:
level: 5
paths:
- app
- src
bootstrapFiles:
- bootstrap/app.php
ignoreErrors: []Now PHPStan will understand this:
// Larastan knows $user is a User model, not generic Model
$user = User::find($id);
$email = $user->email; // Safe if email is a cast or attribute
// Larastan knows this returns a Collection of Post
$posts = $user->posts()->get();
foreach ($posts as $post) {
echo $post->title;
}Without Larastan, PHPStan would complain that you're accessing undefined properties. Larastan reads your models, casts, attributes, and relationships to provide accurate type information.
For complex codebases—those with custom packages, overridden container bindings, or service provider logic—you may need to add some configuration hints:
parameters:
larastan:
modelMixins:
- App\Models\Traits\HasTimestamps
checkModelProperties: trueRun it in your CI pipeline
Static analysis is only useful if it runs consistently, on every push. Add it to your CI workflow—GitHub Actions, GitLab CI, or Jenkins—right alongside your tests.
A typical GitHub Actions example:
name: Code Quality
on: [push, pull_request]
jobs:
phpstan:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: shivammathur/setup-php@v2
with:
php-version: '8.2'
tools: composer
- run: composer install
- run: ./vendor/bin/phpstan analyse --no-progressThe --no-progress flag keeps output clean in logs. If you want to fail the build on errors (recommended), PHPStan returns a non-zero exit code automatically.
On a CRM or SaaS codebase with continuous deployment, you'll want this check to happen before any merge to main. It becomes a code review tool: "PHPStan flagged a potential null pointer; let's look at the logic."
Running locally is also crucial. Add this to your Makefile or team docs:
make lint:
./vendor/bin/phpstan analyse --no-progressDevelopers who run make lint before pushing catch errors immediately, without waiting for CI. Faster feedback loop means faster iteration.
Baseline existing errors to adopt gradually
On a brownfield project with thousands of lines of legacy code, you can't fix everything at once. PHPStan provides a baseline feature: a list of known errors that you acknowledge and ignore until you're ready to fix them.
Generate a baseline:
./vendor/bin/phpstan analyse --generate-baseline phpstan-baseline.neonThis creates a file listing every current error. Then update your config:
includes:
- vendor/larastan/larastan/extension.neon
- phpstan-baseline.neon
parameters:
level: 5
paths:
- appNow PHPStan only complains about new errors. Your team can work through the baseline incrementally—perhaps dedicating one sprint per quarter to tackling a section. As you fix errors, remove them from the baseline.
This approach is pragmatic. A trading platform can't halt development to achieve perfect static analysis coverage. But it can commit to not introducing new problems while working through old ones. Over time, the baseline shrinks, and your codebase becomes safer.
Track the baseline in version control so every team member uses the same rules. When someone fixes a batch of errors, they update the baseline and commit it. The team sees progress.
Common mistakes
- Setting the level too high too fast. You'll drown in errors and abandon the tool. Start at 0 or 1, spend weeks there, then climb. This isn't a sprint.
- Not configuring Larastan properly. If you install Larastan but don't include its extension.neon, you lose 80% of its value. Laravel-specific errors will still fire.
- Suppressing errors without understanding them. It's tempting to add errors to
ignoreErrorsand move on. Instead, understand why the error exists. If it's a false positive, document it. If it's real, fix the code. - Running only in CI, never locally. Developers who only see errors in CI feedback feel separated from the tool. Running locally makes it personal and immediate.
- Not updating dependencies. PHPStan and Larastan improve frequently. Set a quarterly cadence to update them. New versions often catch bugs that older versions missed.
Key takeaways
- Static analysis with PHPStan and Larastan catches type errors and logic bugs before deployment, reducing production incidents.
- Start at a low PHPStan level (0 or 1) on existing codebases and climb gradually—rushing to level 8 creates technical debt frustration.
- Larastan extends PHPStan with Laravel-specific knowledge, understanding models, facades, and relationships without false positives.
- Integrate analysis into your CI pipeline so every merge is checked, but also run locally during development for faster feedback.
- Use baselines to acknowledge existing errors while preventing new ones, allowing teams to improve code quality incrementally without blocking delivery.






Comments
Loading comments…