Kodokon kodokon.com

Quality: PHPUnit, PHPStan and the PSRs

Tool up your code like a professional with PHPUnit tests, PHPStan static analysis at level 9, and the PSR standards.

10 min · 3 questions

Open this lesson in Kodokon

Expert-level code is recognized less by its clever tricks than by its safety net. Three complementary pillars: tests (PHPUnit) that verify behavior by running the code, static analysis (PHPStan) that proves properties without running anything, and shared standards (PSR) that make the ecosystem interoperable. Everything installs with Composer, as development dependencies.

BASH
composer require --dev phpunit/phpunit
composer require --dev phpstan/phpstan
./vendor/bin/phpunit --testdox tests
./vendor/bin/phpstan analyse src --level=9
The --testdox option displays the tests as a readable specification.

Since PHPUnit 10, metadata is written as PHP attributes: #[DataProvider] replaces the docblock @dataProvider annotation. A data provider must be public and static, because PHPUnit reads it even before instantiating the test class. Each data set carries a descriptive key, displayed as-is when the case fails - an accents failure is located instantly.

PHP
<?php
declare(strict_types=1);

use PHPUnit\Framework\Attributes\DataProvider;
use PHPUnit\Framework\TestCase;

final class SlugifierTest extends TestCase
{
    #[DataProvider('provideTitles')]
    public function testSlugify(
        string $input,
        string $expected
    ): void {
        $this->assertSame($expected, slugify($input));
    }

    public static function provideTitles(): array
    {
        return [
            'simple' => [
                'Hello World', 'hello-world'
            ],
            'accents' => [
                'Déjà vu', 'deja-vu'
            ],
        ];
    }
}
Tests a slugify function defined elsewhere in your project.

PHPStan grades its strictness by levels, from 0 to 9 (a level 10 exists in recent versions): at level 9, any mixed value becomes unusable without first being narrowed down by explicit checks. Its real power comes from the docblock types that the PHP engine ignores but the analyzer verifies: array shapes array{email: string}, lists list<int>, generics. They document and prove at the same time.

PHP
<?php
declare(strict_types=1);

function findEmail(?array $user): string
{
    return $user['email'];
}

/**
 * @param array{email: string}|null $user
 */
function findEmailSafe(?array $user): string
{
    return $user === null
        ? 'n/a'
        : $user['email'];
}
Level 9: the first function is rejected (possible access on null, unknown key and type), the second one passes.

The PSRs (PHP Standards Recommendations) are the PHP-FIG conventions. PSR-1 and PSR-12 standardize style: indentation, braces, import order - applied automatically by php-cs-fixer or phpcs. PSR-4 defines autoloading: a namespace prefix maps to a directory, which lets Composer locate any class without a manual require. Finally, the interface PSRs - PSR-3 for loggers, PSR-7 for HTTP messages - let you swap one library for another without touching the code that consumes it.

Knowledge check

Make sure you remember the key points of this lesson.

  1. Since PHPUnit 10, what constraint applies to a data provider method referenced by #[DataProvider]?
    • It must be public and static, because PHPUnit reads it before instantiating the test class
    • It must return a generator, never an array
    • It must have the same name as the test, suffixed with Provider
  2. What does PHPStan level 9 impose?
    • Banning docblocks in favor of native types only
    • Strict handling of mixed: you cannot use such a value without narrowing it first
    • 100% test coverage on the analyzed code
  3. What does the PSR-4 standard define?
    • The format of application log messages
    • The mapping between a namespace prefix and a directory, used by Composer's autoloader
    • The indentation style and the placement of braces