Extending PHPStan

Extending PHPStan for Your Own Code

Extensions are Composer 5,243 packages: phpstan/phpstan-strict-rules (2.0.12) and phpstan/phpstan-deprecation-rules (2.0.5) add checks, larastan/larastan (3.12.2) understands Laravel 2,157 (Laravel), and phpstan/extension-installer registers them. A project rule is one class: getNodeType() names the syntax node to visit, and processNode() returns errors for it.

tools/phpstan/NoDebugCallsRule.php, autoloaded through autoload-devPHP
<?php
namespace Acme\Shop\PHPStan;
use PhpParser\Node;
use PHPStan\Analyser\Scope;
use PHPStan\Rules\{Rule, RuleErrorBuilder};
/** @implements Rule<Node\Expr\FuncCall> */
final class NoDebugCallsRule implements Rule {
  public function getNodeType(): string { return Node\Expr\FuncCall::class; }
  public function processNode(Node $node, Scope $scope): array {
    $name = $node->name instanceof Node\Name ? strtolower($node->name->toString()) : '';
    if (!in_array($name, ['var_dump', 'dump', 'dd', 'xdebug_break'], true)) return [];
    return [RuleErrorBuilder::message("Debug call $name() must not be committed.")
      ->identifier('shop.debugCall')->build()];
  }
}

List it under rules: in phpstan.neon, and Report.php, which still calls dump($total), fails the next run: Debug call dump() must not be committed. on line 8, identifier shop.debugCall.

$scope->getType($expr) returns the inferred type at the node, so rules can enforce type policies too, such as never passing a float to a money method.