Static Code Analysis in PHP

PHP Usergroup Dresden

About Me

Benjamin Cremer

Benjamin Cremer
@benjamincremer

PHP Usergroup Münster

Once a month. Speakers wanted!
meetup.com/phpugms
@phpugms

Questions?

Feel free to ask at any time

Error Types

Logik Errors

Logik Errors


public function addNumbers(float $a, $float $b): float
{
    return $a - $b;
}
            

Compile Time
vs.
Runtime Errors

Syntax Errors

A special kind of compile time error


function foo() {
    bar()
    $baz
}
    

# Run PHP Syntax check (lint)
$ php -l example.php
PHP Parse error:  syntax error, unexpected '$baz' (T_VARIABLE) in example.php
on line 4
Errors parsing syntax-error.php
    

# "compile" code
# Syntax errors are also compile-time errors
$ php example.php
PHP Parse error:  syntax error, unexpected '$baz' (T_VARIABLE) in example.php on line 4
    

Run Syntax Checks with PHP Lint

  • First line of defence
  • Easy to implement
  • Fast

Syntax Check with PHP Parallel Lint

Wrapper around php -l with several options for your convenience. checks your files in parallel making use of your multiple-core machines.

Screenshot of PHP-Parallel-Lint

Compile Time Errors


interface Car
{
    public function drive(): int;
}

class Tesla implements Car
{
    public function drive(): string
    {
        return 'fasel';
    }
}
            

$ php -l example.php
No syntax errors detected in example.php
            

$ php example.php
PHP Fatal error:  Declaration of Tesla::drive(): string must
be compatible with Car::drive(): int in example.php on line 8
            

Runtime Errors


class Car
{
    public function drive(): void {}
}

function testdrive(Car $car) {
    $car->fly();
}
            

$ php -l example.php
No syntax errors detected in example.php
                    

$ php example.php
(Exit code: 0)
               

class Car
{
    public function drive(): void {}
}

function testdrive(Car $car) {
    $car->fly();
}

testdrive(new Car()); // execute above code
            

$ php example.php
PHP Fatal error:  Uncaught Error: Call to undefined method Car::fly()
in example.php:9
               

class Car
{
    public function drive(): void {}
}

function testdrive(Car $car, bool $fly) {
    if ($fly) {
        $car->fly();
    } else {
        $car->drive();
    }
}
            

testdrive(new Car(), false); // works just fine
           

testdrive(new Car(), true); // kaboom
           

Write Tests?

Write a lot of Tests?

Types vs. Tests

Ideology

A talk by Gary Bernhardt from Strange Loop 2015

destroyallsoftware.com/talks/ideology

Static Analysis

What is Static Analysis?

Predict the behavior of software without running it.

Promote runtime errors to compile time error.

Find Bugs In Your Code Without Writing Tests!

The analyser needs your help

Type Inference

Inferring types when types are not given.


function addStrings($part1, $part2)
{
    // concat operation always yields a string
    return $part1 . $part2;
}

// $foo can be inferred as string
$foo = addStrings($unknownA, $unknownB);
            

/**
 * @param DateTimeInterface $dateTime
 * @param int $days
 * @return \DateTimeInterface
 */
function addDays($dateTime, $days) {
    [...]
}
            

function addDays(
    \DateTimeInterface $dateTime,
    int $days
): \DateTimeInterface {
    [...]
}
            

/**
 * @return string[]
 */
function getDaysOfWeek(): array {
    return [
        'monday',
        'tuesday',
        [...]
    ];
}
            

/**
 * @return array<int, string>
 */
function getDaysOfWeek(): array {
    return [
        'monday',
        'tuesday',
        [...]
    ];
}
            

            /**
             * @method Booking|null findOneByUuid(string $uuid)
             * @method Booking[] findByUuid(string $uuid)
             */
            class BookingRepository extends EntityRepository
            {
            }
            

Add types for third party code


/** @var \DateTimeInterfa
    ce $date */
$date = Thirdparty::createDate();
            

$date = Thirdparty::createDate();
assert($date instanceof \DateTimeInterface);
            
via: https://github.com/doctrine/coding-standard/pull/47

PHP-CS-Fixer and PHP_CodeSniffer can assist you with that

PHP_CodeSniffer Demo


                php composer require --dev doctrine/coding-standard
            

Abstract syntax tree

PHP AST Parsers

nikic/php-parser by Nikita Popov
A PHP parser written in PHP

nikic/php-ast by Nikita Popov
Extension that exposes the abstract syntax tree generated by PHP 7

nikic/php-parser Demo


php composer require nikic/php-parser
./vendor/bin/php-parse src/ast_example.php
            

Gimme the tools

PHPStorm + Php Inspections
(EA Extended)

Not very CI friendly

yes, there is a inspection.sh, but no

vimeo/psalm phan/phan phpstan/phpstan
Author Vimeo Rasmus Lerdorf Ondřej Mirtes
PHP Version >= 5.6 >= 7.0 >= 7.1
AST PHP-Parser ext-ast PHP-Parser

And so much more: https://github.com/exakat/php-static-analysis-tools

PHPStan

  • fast
  • extensible
  • in active development
  • used in Build-Pipelines at CHECK24

Usage


                composer require --dev phpstan/phpstan
                ./vendor/bin/phpstan analyse -l3 src/
            

                docker run --rm -v $PWD:/app phpstan/phpstan analyse -l3 /app/src/
            

namespace Example;

class Testdriver
{
    public static function testdrive(Car $car, bool $fly)
    {
        if ($fly) {
            $car->fly();
        } else {
            $car->drive();
        }
    }
}
            

$ ./vendor/bin/phpstan analyse -l3 src/Testdriver.php
 1/1 [▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓] 100%

 ------ -------------------------------------------------
  Line   Testdriver.php
 ------ -------------------------------------------------
  9      Call to an undefined method Example\Car::fly().
 ------ -------------------------------------------------


 [ERROR] Found 1 error
            

PHPStan Tips

  • Use "ignoreErrors"-Regex
  • Level up fast up to Level 4-5
  • Low level in CI, higher level in Dev
  • Adjust level per Module
  • PHPStan is a tool to help you, not to hinder you

Thank you!

Please leave feedback

@benjamincremer
talks.benjamin-cremer.de/phpugdd_sca

PHP Usergroup Münster

meetup.com/phpugms
@phpugms