code quality and tooling
Static Analysis
Static analysis checks PHP code without running the application. It reads files, type declarations, PHPDoc, and configuration to find likely bugs before they reach production.
The two common PHP tools are PHPStan and Psalm. Later lessons cover them directly. This lesson explains the idea behind both.
What static analysis catches
Static analysis is good at finding mismatches that can be understood from the code.
<?php
declare(strict_types=1);
function productPriceLabel(array $product): string
{
return $product['price'];
}
This function promises to return a string, but it returns whatever is stored in $product['price']. If the product price is an integer, a static analyser can report the mismatch.
Types make analysis stronger
The more clearly the code describes values, the more useful static analysis becomes.
<?php
declare(strict_types=1);
function formatMoney(int $pennies): string
{
return 'GBP ' . number_format($pennies / 100, 2);
}
echo formatMoney(499);
// Prints:
// GBP 4.99
The function signature gives the tool a clear contract: pass an integer in, get a string out.
PHPDoc fills gaps
Native PHP can say array, but it cannot describe every key in an array shape. PHPDoc makes static analysis much more precise.
<?php
declare(strict_types=1);
/**
* @param array{name: string, price: int} $product
*/
function reportLine(array $product): string
{
return $product['name'] . ': GBP ' . number_format($product['price'] / 100, 2);
}
With the docblock, a static analyser can warn when code passes an array without name or price.
Static analysis is not the same as tests
Static analysis checks what can be inferred from code. Tests run code and check behaviour.
Static analysis can catch:
- passing the wrong type
- returning the wrong type
- reading a missing array key when the shape is known
- calling an undefined method
- using a class that does not exist
- unreachable branches
- PHPDoc that conflicts with code
Tests are still needed for:
- business rules
- real database behaviour
- framework integration
- user journeys
- calculations with many examples
- regressions that depend on runtime state
Use both. They catch different classes of problems.
Levels and baselines
Static analysis tools usually let teams choose strictness. A new project can start strict. An old project may start at a lower level or use a baseline file to record existing issues.
A baseline is not a goal. It is a way to start using the tool without fixing every old issue at once. New code should avoid adding new baseline entries.
Do not blindly silence warnings
When a static analyser reports something, treat it like a reviewer asking a question. It may be wrong, but it is often pointing at unclear code.
Bad response:
<?php
declare(strict_types=1);
/** @phpstan-ignore-next-line */
echo $product['price'];
Better response: make the data shape clear, validate the input, or change the code so the tool can understand it.
A practical workflow
A professional static-analysis workflow usually looks like this:
- run the tool locally
- read the first error carefully
- fix the code or improve the type information
- rerun the tool
- keep the change small enough to review
- add the command to CI so errors are caught before merge
The command depends on the tool and project, but it often appears in composer.json scripts.
{
"scripts": {
"analyse": "phpstan analyse"
}
}
Then the team can run:
composer analyse
What to remember
Static analysis is an automated review of your code's type and structure. It works best when the code uses clear native types and accurate PHPDoc.
Before moving on, make sure you can explain one bug static analysis can catch before runtime and one bug that still needs a test.
Practice
Task: Fix A Static Analysis Type Mismatch
Read the code and identify what a static analyser could complain about.
<?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
- Identify the type mismatch.
- Fix the function without changing the input data.
- Keep the output useful for a human reader.
- Include the expected output as comments.
Show solution
The function promises to return string, but $product['price'] is documented as int. The fix is to format the integer as a string before returning it.
<?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
The PHPDoc says price is an integer, and the return type says the function returns a string. The implementation now satisfies both.