roave/infection-static-analysis-plugin
Adds a wrapper around infection/infection that runs Psalm on generated mutants. Mutations that would cause type errors are marked killed, improving mutation score. Run vendor/bin/roave-infection-static-analysis-plugin; supports Infection args plus --psalm-config.
Installation:
composer require --dev roave/infection-static-analysis-plugin
This replaces infection/infection as a dev dependency.
First Run:
Replace your existing infection command with:
vendor/bin/roave-infection-static-analysis-plugin
This accepts all standard infection flags (e.g., --threads, --min-msi).
Quick Win: Run against a single test file to verify type safety:
vendor/bin/roave-infection-static-analysis-plugin --threads=4 --min-msi=90 tests/Unit/ExampleTest.php
psalm.xml or phpstan.neon for strictness levels (e.g., level: 5).infection.config.php has minimalMutantScore set to a realistic threshold (e.g., 90).CI Pipeline:
Add to your CI (e.g., GitHub Actions) after phpunit:
- name: Run Infection with Static Analysis
run: vendor/bin/roave-infection-static-analysis-plugin --threads=2 --min-msi=95
Pre-Commit Hook:
Use with php-cs-fixer and psalm in a pre-commit script:
# .github/hooks/pre-commit
vendor/bin/roave-infection-static-analysis-plugin --only-git-diff --min-msi=80
Targeted Testing: Focus on critical paths by excluding trivial mutations:
vendor/bin/roave-infection-static-analysis-plugin \
--exclude-mutations="RemoveConditionals" \
--psalm-config=config/psalm.xml
Service Container Mutations: Test type safety in constructors/injectors:
// Example: Mutate a resolved binding to ensure type checks pass
$this->app->bind(MyService::class, fn() => new MyService($this->app->make(OtherService::class)));
Run with:
vendor/bin/roave-infection-static-analysis-plugin --only-git-diff --min-msi=90 app/Providers/
Eloquent Model Mutations: Validate type safety in query builders:
vendor/bin/roave-infection-static-analysis-plugin --min-msi=85 app/Models/
API Resource Mutations:
Ensure toArray()/toJson() return correct types:
vendor/bin/roave-infection-static-analysis-plugin --min-msi=90 app/Http/Resources/
psalm to cache results (add to psalm.xml):
<fileList>
<directory name="app" suffix=".php"/>
<directory name="tests" suffix=".php"/>
</fileList>
<cacheDirectory>storage/psalm-cache</cacheDirectory>
--threads=max to leverage multi-core systems.Version Locking:
infection/infection to specific versions. Never update infection/infection manually—let the plugin manage its dependency.composer require infection/infection:^0.32.0 only if the plugin explicitly supports it (check releases).False Positives:
return null; in methods returning void may trigger static analysis errors. Suppress these in psalm.xml:
<ignoreErrors>
<error>TypeError</error>
<pattern>app/Exceptions/Handler.php</pattern>
</ignoreErrors>
Slowdowns:
--only-git-diff.min-msi temporarily for CI (e.g., 80 instead of 95).Psalm vs. PHPStan:
Verbose Output: Enable debug logs to inspect failed mutations:
vendor/bin/roave-infection-static-analysis-plugin --verbose
Look for lines like:
[ERROR] Mutant killed by static analysis: Return type mismatch (expected: list<T>, actual: array<int|string, T>)
Isolate Mutations: Test a single file to debug:
vendor/bin/roave-infection-static-analysis-plugin --only-tests=TestClass::testMethod app/Services/MyService.php
Static Analysis Errors: If a mutant passes tests but fails static analysis, fix the type signature in your code. Example:
// Before (fails static analysis)
public function getItems(): array { return $this->items; } // $items is array<int, string>
// After (passes)
public function getItems(): array<int, string> { return $this->items; }
Custom Mutators: Extend the plugin by forking and adding support for PHPStan (see #46).
Post-Analysis Hooks:
Use infection's --post-run to trigger additional checks:
vendor/bin/roave-infection-static-analysis-plugin --post-run="php vendor/bin/psalm --init"
Laravel Artisan Integration: Create a custom Artisan command to wrap the plugin:
// app/Console/Commands/RunInfectionStatic.php
public function handle()
{
$command = base_path('vendor/bin/roave-infection-static-analysis-plugin');
$this->callSilently('infection:run', [
'command' => $command,
'--min-msi' => $this->option('min-msi'),
]);
}
Combine with Pest:
Use Pest’s --minimal flag to reduce mutation noise:
vendor/bin/roave-infection-static-analysis-plugin --test-framework=pest --min-msi=90
GitHub Actions Matrix: Test across PHP versions with static analysis:
strategy:
matrix:
php: [8.1, 8.2, 8.3]
jobs:
infection:
runs-on: ubuntu-latest
steps:
- run: vendor/bin/roave-infection-static-analysis-plugin --psalm-config=config/psalm.xml
Exclude Trivial Mutations: Improve speed by excluding obvious mutations:
vendor/bin/roave-infection-static-analysis-plugin \
--exclude-mutations="RemoveIncrements,RemoveDecrements" \
--min-msi=85
How can I help you explore Laravel packages today?