Phan Orientation
Phan is a static analyser for PHP. It checks source code without running the application and reports problems such as incompatible types, invalid calls, missing symbols, suspicious array access, and unreachable or unused code.
PHPStan and Psalm are more common choices in many modern projects, but Phan remains an established tool. When a repository already uses it, the practical skill is to understand its configuration, run the same command as CI, and fix issues without replacing the tool merely because another analyser is more familiar.
Install And Initialise Phan
Install Phan as a development dependency in a Composer project.
composer require --dev phan/phan
vendor/bin/phan --init
Initialisation creates .phan/config.php. Commit the project configuration so local development and CI analyse the same files with the same settings.
Phan 6 runs on PHP 8.1 or newer and can analyse PHP 8.1 through PHP 8.5 syntax. It normally uses the php-ast extension. Where that extension is unavailable, Phan also provides a tolerant parser through --allow-polyfill-parser, with some parsing differences documented by the project.
Configure Project Boundaries
A small .phan/config.php might look like this:
<?php
return [
'target_php_version' => '8.5',
'directory_list' => [
'src',
'vendor',
],
'exclude_analysis_directory_list' => [
'vendor',
],
];
directory_list tells Phan which code it must parse to understand classes and functions. exclude_analysis_directory_list prevents third-party code from being reported as application issues while still allowing its symbols to be understood.
Use the repository's existing configuration. Changing paths or target versions can alter hundreds of results and should be reviewed as a tooling change of its own.
Run The Project Command
The direct command is:
vendor/bin/phan
A project may wrap it in Composer:
{
"scripts": {
"phan": "phan"
}
}
Then developers and CI can run:
composer phan
Prefer the repository command because it may include a config path, parser option, plugins, or output format.
Read Issues Before Suppressing Them
Phan issue names describe the category of problem. For example, a return-type issue means the declared contract and inferred value disagree.
<?php
declare(strict_types=1);
/**
* @param array{name?: string} $product
*/
function productName(array $product): string
{
return $product['name'] ?? 0;
}
The fallback is an integer, so this function does not always return string. The useful fix is to make the fallback match the contract or reject invalid input.
<?php
declare(strict_types=1);
/**
* @param array{name?: string} $product
*/
function productName(array $product): string
{
return $product['name'] ?? 'Unknown product';
}
echo productName([]);
// Prints:
// Unknown product
Phan supports targeted suppressions such as @suppress, but suppression should follow investigation. First improve native types, PHPDoc, validation, or code structure.
Adopt It Gradually
A legacy project may produce too many findings to fix at once. Phan provides configuration and issue controls that allow teams to begin with a useful subset, fix code incrementally, and strengthen analysis later.
Keep the adoption rule simple:
- do not add new issues in changed code
- fix nearby findings when the risk is understood
- tighten settings in focused changes
- remove obsolete suppressions
- keep local and CI commands identical
Phan also offers optional checks for dead code, unused variables, and redundant conditions. Enable broader checks deliberately because they can change both runtime and review cost.
Plugins And Editor Integration
Phan includes plugins for additional checks such as unreachable code, duplicate array keys, regular-expression validation, and printf() argument checking. Add plugins because they address a project risk, not merely because they exist.
Phan also supports editor integration through a language server. Editor feedback is convenient, but the committed CLI configuration and CI result remain the shared source of truth.
Phan, PHPStan, Or Psalm
All three tools aim to catch defects before runtime, but their configurations, issue names, inference, extensions, and suppression mechanisms differ. A project rarely benefits from replacing a working analyser without a concrete reason.
When joining a repository, look for .phan/config.php, Composer scripts, CI jobs, and suppressions. Run the configured tool first and understand the existing quality gate before proposing a migration.
What To Remember
Phan is a mature PHP static analyser configured through .phan/config.php. Parse the dependencies Phan needs, analyse only owned code, read issue types carefully, strengthen checks gradually, and use the project command in both local development and CI.
Before moving on, make sure you can explain why vendor may be parsed but excluded from analysis, when the polyfill parser is relevant, and why suppressing an issue is not the first response.
Practice
Task: Fix A Phan Return-Type Issue
Phan reports that this function can return an integer even though it declares string.
<?php
declare(strict_types=1);
/**
* @param array{name?: string} $product
*/
function productName(array $product): string
{
return $product['name'] ?? 0;
}
Requirements
- Keep the native return type as
string. - Keep
nameoptional in the input shape. - Return
Unknown productwhen the key is absent. - Call the function with and without a name.
- Print both results.
- Include expected output comments in the same PHP block.
- Explain why changing or suppressing the declared return type would be a weaker fix.
Show solution
<?php
declare(strict_types=1);
/**
* @param array{name?: string} $product
*/
function productName(array $product): string
{
return $product['name'] ?? 'Unknown product';
}
echo productName(['name' => 'Notebook']) . PHP_EOL;
echo productName([]) . PHP_EOL;
// Prints:
// Notebook
// Unknown product
The fallback now matches the declared string contract for every path. Weakening the return type or suppressing the issue would force callers to handle an unnecessary integer possibility while leaving the underlying mismatch in place.