code quality and tooling
PHPStan
PHPStan is a static analysis tool for PHP. It checks code without running the application and reports type problems, unreachable code, missing methods, incorrect PHPDoc, and many other issues that can be found from source code.
In job work, PHPStan is commonly used as a local quality gate and a CI check. A junior developer should know how to run it, read the first error, and fix the code rather than silencing the tool immediately.
Running PHPStan
In a Composer project, PHPStan is usually installed as a development dependency and run from vendor/bin.
vendor/bin/phpstan analyse src
Many teams wrap that in a Composer script:
{
"scripts": {
"analyse": "phpstan analyse"
}
}
Then the command becomes:
composer analyse
Use the command configured by the project you are working in.
Rule levels
PHPStan has rule levels from 0 to 10. Lower levels are easier to adopt. Higher levels are stricter and require better type information.
vendor/bin/phpstan analyse --level 6 src
The best level is not always "maximum immediately". Existing projects often increase levels gradually. The practical goal is to stop new code from making the codebase worse.
Configuration
PHPStan uses NEON configuration, commonly in phpstan.neon or phpstan.neon.dist.
parameters:
level: 6
paths:
- src
- tests
This tells PHPStan which level to use and which paths to analyse.
If a project already has a config file, use it. Do not invent a separate command that analyses different paths unless you know why.
Reading an error
PHPStan errors usually tell you the file, line, and problem.
------ ----------------------------------------------------------------
Line ProductReport.php
------ ----------------------------------------------------------------
12 Function productPriceLabel() should return string but returns int.
------ ----------------------------------------------------------------
Read the message literally. It says the function's declared contract and the implementation disagree.
<?php
declare(strict_types=1);
/**
* @param array{name: string, price: int} $product
*/
function productPriceLabel(array $product): string
{
return $product['price'];
}
The fix is to return a string.
<?php
declare(strict_types=1);
/**
* @param array{name: string, price: int} $product
*/
function productPriceLabel(array $product): string
{
return 'GBP ' . number_format($product['price'] / 100, 2);
}
PHPDoc matters
PHPStan becomes much stronger when array shapes and lists are documented accurately.
<?php
declare(strict_types=1);
/**
* @param list<array{name: string, price: int}> $products
*/
function totalPrice(array $products): int
{
$total = 0;
foreach ($products as $product) {
$total += $product['price'];
}
return $total;
}
Without the docblock, PHPStan sees only array. With the docblock, it knows what each product should contain.
Baselines
A baseline records existing PHPStan errors so a legacy project can start using the tool without fixing everything at once.
vendor/bin/phpstan analyse --generate-baseline
A baseline is a temporary compromise, not a badge of quality. New code should avoid adding new baseline entries, and old entries should be removed as the code improves.
Ignoring errors
Sometimes PHPStan needs help. Maybe a framework provides a dynamic method, or a tool cannot infer a value that the code guarantees.
Do not start by ignoring the error. Try first to:
- add a native type
- improve PHPDoc
- validate the value
- refactor unclear code
- install the relevant framework extension
Use ignores only when the code is correct and the tool cannot reasonably understand it.
PHPStan in CI
In a professional workflow, PHPStan usually runs in CI before code is merged.
That means a local pass matters. Run the same command CI runs before opening or updating a pull request. If CI fails on PHPStan, read the first error and fix the root cause.
What to remember
PHPStan is an automated reviewer for type and structure problems. Start at the project's configured level, read messages carefully, improve types and PHPDoc, and treat baselines and ignores as tools for managing adoption rather than shortcuts.
Before moving on, make sure you can run PHPStan, identify the file and line from an error, and fix a return-type mismatch.
Practice
Task: Fix A PHPStan Return Type Error
Fix the code based on this PHPStan-style error.
Line 12: Function productPriceLabel() should return string but returns int.
<?php
declare(strict_types=1);
/**
* @param array{name: string, price: int} $product
*/
function productPriceLabel(array $product): string
{
return $product['price'];
}
echo productPriceLabel(['name' => 'Notebook', 'price' => 499]);
Requirements
- Keep the function return type as
string. - Keep
priceas integer pennies in the input. - Return a formatted price label.
- Include the expected output as comments.
Show solution
<?php
declare(strict_types=1);
/**
* @param array{name: string, price: int} $product
*/
function productPriceLabel(array $product): string
{
return 'GBP ' . number_format($product['price'] / 100, 2);
}
echo productPriceLabel(['name' => 'Notebook', 'price' => 499]);
// Prints:
// GBP 4.99
PHPStan was right: the original function returned an integer even though the signature promised a string. Formatting the integer pennies creates the intended string output.