What Static Analysis Catches

A static analyzer never runs your code. It parses each file into a syntax tree (nikic's PHP-Parser 17,470 (https://github.com/nikic/PHP-Parser 17,470 )), infers every expression's type from declarations and docblocks, and checks every path, including error branches no test reached: missing methods, wrong argument types, null reaching ->, dead code. It cannot catch wrong business logic, which is what tests are for. Invoice passes php -l yet hides a typo ($costumer), a string where number_format() wants an int, an untyped $qty, an array of unknown contents, and a find() that returns null for an unknown SKU.

src/Invoice.php, a class with five kinds of bugPHP
<?php
declare(strict_types=1);
namespace Acme\Shop;
final class Invoice {
  private array $lines = [];
  public function __construct(private readonly Catalog $catalog) {}
  public function addLine(string $sku, $qty): void {
    $this->lines[] = ['sku' => $sku, 'qty' => $qty];
  }
  public function total(): int {
    $sum = 0;
    foreach ($this->lines as $line) {
      $sum += $this->catalog->find($line['sku'])->priceCents * $line['qty'];
    }
    return $sum / 100;
  }
  public function label(): string { return 'Invoice for ' . $this->costumer; }
  public function print(): void { echo number_format($this->total(), '2'); }
}