Static World

Dynamic World

Static Analysis

get properties of program without executing it

What properties?

  • Is this code use proper code style?
  • Does this code works?
  • What complexity this code have?
  • How often this method is changed?
  • Who is using this method?
  • ...

Code Review Goals

  • Knowledge sharing
  • Opportunities for improvements
  • Double check for stupid mistakes
  • Preserve coding standards of project
class UserResource{

/**
  * Transformer for user resource
 * @return array<id: int, name: string, age: int, score: float>
*/
public function default(User $user, Scores $scores) {
   return [
     'id' => $user->id(),     'name' => $user->name(),
     'age' => $user->age(), 'score' => $scores->forUser($user),
   ];
}
}

It's too boring to maintain consistent code style

class UserResource
{
    /**
     * Transformer for user resource.
     *
     * @return array<id: int, name: string, age: int, score: float>
     */
    public function default(User $user, Scores $scores)
    {
        return [
            'id'    => $user->id(),
            'name'  => $user->name(),
            'age'   => $user->age(),
            'score' => $scores->forUser($user),
        ];
    }
}

So let's automate it.

php-cs-fixer, php code sniffer, prettier-php

Coding standards

is not only about code style

Acyclic dependencies principle

Type Checking

  • PhpStorm
  • Phan
  • PhpStan
  • Psalm

from Rasmus Lerdorf

Statically typed languages just too verbose

Don't turn PHP into Java!
I don't need type checking, I have tests!
ERROR: UndefinedMethod - 11:1 
Method Bar::foo does not exist

Type inference

function tokenize(string $expression)
{
    $groups = ['number', 'operation'];
    $regex = '/(?<number>\d+)|(?<operation>[\+\-\*\/]?)|\s+$/';
    preg_match_all($regex, $expression, $matches, PREG_SET_ORDER);
    $tokens = [];
    foreach ($matches as $match) {
        /** @var string[] $lexem */
        $lexem = array_intersect_key($match, array_flip($groups));
        foreach ($groups as $group) {
            if (
                !isset($lexem[$group])
                || '' === $lexem[$group]
            ) {
                continue;
            }

            $tokens[] = [
                'type'  => $group,
                'value' => $lexem[$group],
            ];
        }
    }

    return $tokens;
}

More complicated example...

/**
 * @return string[][]
 *
 * @psalm-return array<int, array{type:string, value:string}>
 */
function tokenize(string $expression): array
{
// ...

Psalter

May fix some typings

/**
 * @return string[][]
 *
 * @psalm-return array<int, array{type:string, value:string}>
 */
function tokenize(string $expression): array
{
// ...

No standard way to express types

<?php
declare(strict_types=1);

function sum(int $a, int $b): int {
    return $a + $b;
}

$tokens = tokenize('2 + 3 * 2');

sum(
    $tokens[0]['value'], 
    $tokens[0]['value']
);

A bit simpler example

It's not perfect...

But PhpStan could catch it!

If all types defined...

function processFile(string $content): array 
{
    // do something with it
}


processFile(
    file_get_contents($filePath)
);

No errors found!

Fatal error: Uncaught TypeError: Argument 1 passed to processFile() must be of the type string, boolean given

Type checking is imprecise

dynamic testing is even worse

Add type checking to existing project

  • Configure levels of errors
  • Fix errors if you touching file with errors
  • Fail only if new error occurred

Use metrics to find interesting places

Opportunities for improvements

PhpMetrics

Your own analyzers

  • Api Documentation
  • Business Process Visualization
  • Decomposition helper using force-directed graph
  • many more

nikic/PHP-Parser, Roave/BetterReflection

Documentation from source code

And let's static analysis save some of your time!

That's All!

Static Analysis

By Sergey Protko

Static Analysis

  • 841