vimeo/psalm
Psalm is a PHP static analysis tool that finds type errors, dead code, and risky patterns before runtime. Add it to your CI to improve code quality, enforce stricter typing, and catch bugs early in applications and libraries.
Installation Add Psalm to your Laravel project via Composer:
composer require vimeo/psalm --dev
Initialize Psalm with Laravel-specific configuration:
vendor/bin/psalm --init
This generates a psalm.xml file. Update it to include Laravel’s bootstrap/app.php and config/ directory:
<projectFiles>
<directory name="app" />
<directory name="config" />
<directory name="routes" />
<file name="bootstrap/app.php" />
</projectFiles>
First Run Run a basic analysis to identify type-related issues:
vendor/bin/psalm --no-cache
Fix any critical errors (e.g., missing return types, undefined variables) before proceeding.
Integrate with CI Add a Psalm step to your GitHub Actions (or equivalent) workflow:
- name: Run Psalm
run: vendor/bin/psalm --no-cache --output-format=github
Pre-Commit Hooks
Use psalm with --shepherd to auto-fix simple issues (e.g., missing @var annotations):
composer require --dev dealerdirect/phpcodesniffer-composer-installer
vendor/bin/psalm --shepherd --alter
Add to .git/hooks/pre-commit:
#!/bin/sh
vendor/bin/psalm --no-cache --shepherd --alter
IDE Integration (PHPStorm) Configure Psalm as a PHPStorm inspection tool:
PHPSTORM=1 in your environment to enable seamless IDE feedback.Laravel-Specific Patterns
$this->app->bind(MyService::class, fn() => new MyService(config('my_service.settings')));
Use @psalm-param-type in AppServiceProvider:
/** @psalm-param array{settings: array<string, mixed>} $config */
public function register(): void { ... }
@mixin for dynamic properties:
/** @mixin \Illuminate\Database\Eloquent\Model */
class User extends Model { ... }
Custom Stubs
Generate stubs for Laravel’s dynamic classes (e.g., Request, Response):
vendor/bin/psalm --generate-stubs
Add stubs to psalm.xml:
<stubFiles>
<file name="stubs/laravel-stubs.php" />
</stubFiles>
Security Analysis Run focused security checks (e.g., SQL injection, XSS):
vendor/bin/psalm --issues=Security,UnsafeDatabaseQuery
False Positives in Dynamic Code
$$var or call_user_func() as untyped.@psalm-suppress or @var annotations:
/** @var callable(): mixed */
$callback = $this->getCallback();
Laravel’s Dynamic Properties
Request->input() or Model->attributes.@property or @mixin annotations:
/** @property string $name */
class User extends Model { ... }
Configuration Overrides
psalm.xml settings.psalm.xml is in the project root and not cached:
vendor/bin/psalm --init # Re-generate config
vendor/bin/psalm --no-cache
Performance with Large Codebases
psalm.xml:
<param name="jit" type="bool">false</param>
Or use --force-jit sparingly for debugging.Plugin Conflicts
psalm-plugin-laravel) cause crashes.<plugins>
<pluginClass value="PsalmPlugin\Laravel\Plugin" />
</plugins>
Incremental Analysis
Use --no-cache for full scans, but cache results for daily workflows:
vendor/bin/psalm --cache-globals # Cache global state
Custom Error Levels Suppress warnings but keep errors:
<errorLevel value="Errors" />
Or per-issue:
vendor/bin/psalm --issues=Errors,MissingReturnType
Automated Annotations
Run Psalm with --alter to auto-add @var and mutability annotations:
vendor/bin/psalm --alter --issues=MissingPureAnnotation,MissingImmutableAnnotation
Debugging Complex Types
Use --show-snippet to inspect type inference:
vendor/bin/psalm --show-snippet --issues=TypeError
Laravel-Specific Plugins Install the official Laravel plugin for deeper integration:
composer require --dev bobweber/psalm-plugin-laravel
Update psalm.xml:
<plugins>
<pluginClass value="BobWeber\PsalmPlugin\Laravel\Plugin" />
</plugins>
CI-Specific Outputs
Use --output-format=github for PR-friendly reports or --stats for coverage metrics:
vendor/bin/psalm --stats --output-format=github
vendor/bin/psalm app/Services/MyService.php
vendor/bin/psalm -vvv
rm -rf .psalm-cache
How can I help you explore Laravel packages today?