| name | solving-phpstan-errors |
| description | Use when encountering PHPStan static analysis errors that need systematic resolution, especially when dealing with multiple errors (>30) or complex type inference issues |
Solving PHPStan Errors
Overview
Core principle: Fix PHPStan errors one at a time with verification between each fix. Prefer proper types over inline annotations.
When to Use
Use when:
- Running static analysis shows PHPStan errors
- Dealing with template type resolution issues
- Fixing nullable object access warnings
- Working with Laravel collections and type inference
Quick Reference
| Error Type | First Choice | Last Resort |
|---|
| Template type resolution | Extract to typed variable | @var inline |
| Nullable access | Null check or ?-> | @phpstan-ignore |
| Collection types | Type before collect() | Generic collection type |
| Complex arrays | Data object/DTO | Array shape annotation |
Implementation
Step 1: Analyze Scope
Decision point: >20 errors = create written plan with TodoWrite
Step 2: Fix One Error at a Time
CRITICAL: Never fix multiple errors in parallel. One fix may resolve others.
digraph fix_process {
"Identify first error" [shape=box];
"Choose fix approach" [shape=diamond];
"Proper type/DTO" [shape=box];
"Inline annotation" [shape=box];
"Ask for review" [shape=box];
"Apply fix" [shape=box];
"Run analysis" [shape=box];
"Error still exists?" [shape=diamond];
"Error count changed?" [shape=diamond];
"More errors?" [shape=diamond];
"Done" [shape=box];
"Identify first error" -> "Choose fix approach";
"Choose fix approach" -> "Proper type/DTO" [label="can create type"];
"Choose fix approach" -> "Inline annotation" [label="needs annotation"];
"Choose fix approach" -> "Ask for review" [label="needs ignore"];
"Proper type/DTO" -> "Apply fix";
"Inline annotation" -> "Apply fix";
"Ask for review" -> "Apply fix" [label="if approved"];
"Apply fix" -> "Run analysis";
"Run analysis" -> "Error still exists?" [label="check target error"];
"Error still exists?" -> "Choose fix approach" [label="yes"];
"Error still exists?" -> "Error count changed?" [label="no - fixed"];
"Error count changed?" -> "More errors?" [label="verify"];
"More errors?" -> "Identify first error" [label="yes"];
"More errors?" -> "Done" [label="no"];
}
Step 3: Choose Fix Strategy
Preference order (best to worst):
-
Create proper type (Data Object/DTO)
final readonly class UserData
{
public function __construct(
public string $name,
public string $email,
) {}
}
return new UserData($name, $email);
-
Extract and type variable
$users = $response['users'];
$collection = collect($users);
-
Inline type annotation
$users = collect($data)->filter(...);
-
PHPStan ignore comments
return $collection[$index];
Common Error Patterns
Template Type Resolution in collect()
Problem: PHPStan can't infer TKey/TValue from array
$collection = collect($array)->map(...);
Solution: Type the array first
$items = $array;
$collection = collect($items)->map(...);
Nullable Collection Access
Problem: Accessing collection item that might not exist
$answers = $answersByQuiz[$index]->map(...);
Solution: Null check with explicit error
$answers = $answersByQuiz->get($index);
if ($answers === null) {
throw new RuntimeException("Missing answers at index {$index}");
}
$mapped = $answers->map(...);
Unnecessary Null Coalescing
Problem: Using ?? when type guarantees non-null
$correct = collect($answers)->filter(fn($a) => $a['is_correct'] ?? false);
Solution: Remove ?? since type guarantees existence
$correct = collect($answers)->filter(fn($a) => $a['is_correct']);
Planning for Large Error Sets
When errors >30, create plan with TodoWrite:
1. Group errors by type (template resolution, nullable access, etc.)
2. Estimate: ~2-5 minutes per error
3. Create todos for each group
4. Fix group by group, not file by file
Red Flags - Ask for Review First
Stop and ask before using:
@phpstan-ignore-next-line
@phpstan-ignore-line
- Disabling specific rules in phpstan.neon
- Suppressing errors with
@ operator
These usually indicate architectural issues worth discussing.
Verification Protocol
After EACH fix:
- Run the phpstan command to check for errors (it might be specific to the project which command to run)
- Check if target error disappeared
- Check if error count changed (one fix might resolve multiple)
- Document unexpected changes
Never:
- Fix multiple files without running analysis
- Assume similar errors need similar fixes
- Skip verification "because it's obvious"
Common Mistakes
| Mistake | Why Bad | Fix |
|---|
| Fix all errors before testing | One fix might resolve many | Test after each fix |
Use @phpstan-ignore first | Hides real issues | Try proper types first |
Generic @var mixed annotations | Defeats type safety | Be specific with array shapes |
| Skip error count verification | Miss cascading fixes | Always check total count |
| Inline all type hints | Hard to read, maintain | Create DTOs for complex types |
Real-World Impact
Before: 36 PHPStan errors blocking deployment
After: Systematic fix-and-verify process:
- Fixed 36 → 12 errors in first pass (nullable access fixes resolved related errors)
- Fixed 12 → 0 errors in second pass (template type annotations)
- Total time: ~30 minutes
- Zero false solutions, zero regressions