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 example
<?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 example
<?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 example
<?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 example
<?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 price as integer pennies in the input.
  • Return a formatted price label.
  • Include the expected output as comments.
Show solution
PHP example
<?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.