skills/phpstan-fixer/SKILL.md
Fix PHPStan static analysis errors by adding type annotations and PHPDocs. Use when encountering PHPStan errors, type mismatches, missing type hints, or static analysis failures. Never ignores errors without user approval.
npx skillsauth add marcelorodrigo/agent-skills phpstan-fixerInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Fix PHPStan static analysis errors through proper type annotations, PHPDocs, and code improvements. This skill teaches agents how to resolve errors without suppressing them, respecting the project's configured strictness level.
phpstan.neon settings
(level, paths, parameters)ignoreErrors to config without explicit
user approvalvendor/. Use stub
files instead to override wrong typesBefore fixing errors, identify the project type:
# Check for Laravel
grep laravel/framework composer.json
# Check for Symfony
grep symfony/symfony composer.json
# Check for PHPStan extensions
grep phpstan composer.json
# Read PHPStan config
cat phpstan.neon
# Check project guidelines
cat AGENTS.md
Key information to extract:
PHPStan errors have this structure:
------ ----------------------------------------------
Line /path/to/File.php
------ ----------------------------------------------
42 Parameter $user of method foo() has invalid
type App\User.
💡 Identifier: parameter.type
------ ----------------------------------------------
Extract:
parameter.type, missingType.return)Use the error identifier to determine the fix strategy:
After applying fixes, run PHPStan again to confirm:
vendor/bin/phpstan analyse
Important:
missingType.parameter — Missing parameter typeError:
Parameter $name has no type specified.
Fix — Add native type:
// Before
function greet($name) {
return "Hello, $name";
}
// After
function greet(string $name): string {
return "Hello, $name";
}
Fix — Use PHPDoc for complex types:
// Before
function processUsers($users) { ... }
// After
/**
* @param array<int, User> $users
*/
function processUsers(array $users): void { ... }
missingType.return — Missing return typeError:
Method foo() has no return type specified.
Fix — Add native return type:
// Before
public function getUser() {
return $this->user;
}
// After
public function getUser(): User {
return $this->user;
}
Fix — Use PHPDoc for union/intersection types:
// Before
public function findUser($id) { ... }
// After
/**
* @return User|null
*/
public function findUser(int $id): ?User { ... }
argument.type — Wrong argument typeError:
Parameter #1 $id of method find() expects int, string given.
Fix — Cast the argument:
// Before
$user = $repository->find($request->input('id'));
// After
$user = $repository->find((int) $request->input('id'));
Fix — Narrow the type earlier:
// Better approach
$id = $request->integer('id'); // Laravel helper
$user = $repository->find($id);
return.type — Wrong return typeError:
Method foo() should return User but returns User|null.
Fix — Adjust return type:
// Before
public function getUser(): User {
return $this->user ?? null;
}
// After
public function getUser(): ?User {
return $this->user ?? null;
}
Fix — Ensure non-null with assertion:
public function getUser(): User {
assert($this->user !== null);
return $this->user;
}
property.notFound — Undefined property accessError:
Access to an undefined property User::$name.
Fix — Add property declaration:
class User {
private string $name;
public function __construct(string $name) {
$this->name = $name;
}
}
Fix — Document magic property:
/**
* @property string $name
*/
class User {
public function __get($key) { ... }
}
Fix (Laravel) — Use IDE Helper:
# Generate PHPDocs for Eloquent models
php artisan ide-helper:models
property.onlyWritten — Property written but never readError:
Property User::$name is never read, only written.
Fix — Remove unused property or add getter:
// If truly unused, remove it
// If needed, add usage:
public function getName(): string {
return $this->name;
}
method.notFound — Undefined method callError:
Call to an undefined method App\User::getFullName().
Fix — Add method:
class User {
public function getFullName(): string {
return $this->first_name . ' ' . $this->last_name;
}
}
Fix — Document magic method:
/**
* @method string getFullName()
*/
class User {
public function __call($method, $args) { ... }
}
Fix (Laravel) — Add to @mixin for query builders:
/**
* @mixin \Illuminate\Database\Eloquent\Builder
*/
class User extends Model { ... }
offsetAccess.notFound — Undefined array offsetError:
Offset 'email' does not exist on array.
Fix — Use array shape PHPDoc:
/**
* @param array{email: string, name: string} $data
*/
function createUser(array $data): void {
echo $data['email']; // PHPStan knows this exists
}
Fix — Add existence check:
if (isset($data['email'])) {
echo $data['email'];
}
Fix — Use null coalescing:
$email = $data['email'] ?? '[email protected]';
missingType.generics — Missing generic typeError:
Class Collection has @template T but does not specify it.
Fix — Specify generic type in PHPDoc:
// Before
/** @var Collection $users */
$users = User::all();
// After
/** @var Collection<int, User> $users */
$users = User::all();
Fix (Laravel) — Use IDE Helper stubs for collections.
deadCode.unreachable — Unreachable codeError:
Unreachable statement - code above always terminates.
Fix — Remove dead code:
// Before
function foo() {
return true;
echo "This never runs"; // Error
}
// After
function foo() {
return true;
}
identical.alwaysTrue / identical.alwaysFalse — Condition is always true/falseError:
Strict comparison using === between int and string will always evaluate to false.
Fix — Remove useless condition or fix type:
// Before
if ($id === '123') { ... } // $id is int
// After
if ($id === 123) { ... }
Install Larastan for Laravel-aware analysis:
composer require --dev larastan/larastan
Check phpstan.neon includes Larastan (ask user to add if missing):
includes:
- vendor/larastan/larastan/extension.neon
Common Laravel fixes:
// Eloquent relationships - use @property PHPDoc
/**
* @property-read \Illuminate\Database\Eloquent\Collection<int, Post> $posts
*/
class User extends Model {
public function posts() {
return $this->hasMany(Post::class);
}
}
// Collections - specify generic types
/** @var \Illuminate\Support\Collection<int, User> $users */
$users = User::all();
// Request input - use typed helpers
$id = $request->integer('id'); // Not $request->input('id')
$email = $request->string('email')->toString();
Install Symfony PHPStan extension:
composer require --dev phpstan/phpstan-symfony
Check phpstan.neon includes Symfony extension (ask user to add if missing):
includes:
- vendor/phpstan/phpstan-symfony/extension.neon
parameters:
symfony:
containerXmlPath: var/cache/dev/App_KernelDevDebugContainer.xml
Common Symfony fixes:
// Service container - use proper type hints
public function __construct(
private UserRepository $userRepository, // Not mixed
) {}
// Forms - type the data
/** @var array{email: string, password: string} $data */
$data = $form->getData();
Sometimes a legitimate ignore is needed. Always ask the user first using the Question tool:
Step 1: Explain the situation
I found a PHPStan error that cannot be easily fixed:
Error: [describe error]
Location: [file:line]
Reason: [explain why it can't be fixed]
Step 2: Use Question tool to get user choice
Use the Question tool with these options:
- Header: "PHPStan Error Resolution"
- Question: "How would you like to handle this error?"
- Options:
1. "Use @phpstan-ignore with comment" - description: "Add inline ignore with explanation (recommended for third-party type issues)"
2. "Add to baseline" - description: "Generate baseline file (recommended for legacy code migration)"
3. "Refactor code" - description: "Modify code to satisfy PHPStan (most robust but may require significant changes)"
4. "Skip for now" - description: "Leave unfixed and continue with other errors"
Example Question tool usage:
{
"questions": [{
"header": "PHPStan Error Resolution",
"question": "File src/Service.php:42 has argument type mismatch with third-party API. How should I handle this?",
"options": [
{
"label": "Use @phpstan-ignore (Recommended)",
"description": "Add inline ignore with explanation"
},
{
"label": "Add to baseline",
"description": "Include in baseline file for tracking"
},
{
"label": "Refactor code",
"description": "Modify to satisfy PHPStan"
},
{
"label": "Skip for now",
"description": "Continue with other errors"
}
]
}]
}
Valid reasons for ignoring:
How to ignore (if approved):
// Inline ignore with explanation
/** @phpstan-ignore argument.type (API returns string|int, we handle both) */
$result = $api->getValue();
// Baseline for legacy code
vendor/bin/phpstan analyse --generate-baseline
Never do this without approval:
# Don't add this to phpstan.neon without user consent
parameters:
ignoreErrors:
- '#.*#' # NEVER
Use \PHPStan\dumpType() to see what PHPStan thinks:
$user = User::find($id);
\PHPStan\dumpType($user); // Reports: App\User|null
// Remove before committing!
Check:
composer dump-autoload)paths in phpstan.neon)Check:
@var too much? (Fix at source instead)Install and run:
composer require --dev barryvdh/laravel-ide-helper
php artisan ide-helper:generate
php artisan ide-helper:models --write
php artisan ide-helper:meta
Full list: https://phpstan.org/error-identifiers
Most common categories:
argument.* — Function/method argument issuesreturn.* — Return type mismatchesmissingType.* — Missing type declarationsproperty.* — Property access/declaration issuesmethod.* — Method call issuesoffsetAccess.* — Array/ArrayAccess issuesclass.* — Class inheritance/usage issuesdeadCode.* — Unreachable codeidentical.* / equal.* — Comparison issuesdocumentation
Write technical content for senior engineering audiences using narrative arcs, hooks, and structured conventions.
tools
Create Pull Requests following best conventions. Use when opening PRs, writing PR descriptions, or preparing changes for review.
testing
Expert Spring Boot 4 testing specialist that selects the best Spring Boot testing techniques for your situation with Junit 6 and AssertJ.
development
Build Laravel admin panels with Filament v5. Use for creating resources, forms, tables, widgets, and testing admin interfaces with Livewire v4.