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.
<?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'); }
}