PHP AST to measuring your code complexity

Most developers associate PHP with Laravel, WordPress, or writing controllers and views. But deep inside, PHP offers something powerful yet underused: Abstract Syntax Trees (AST).

In this post, we’ll explore what AST is, why it’s important, and how to leverage PHP’s AST for static analysis, code transformation, and custom tooling — something that can truly elevate you as a PHP backend engineer.

What Is an Abstract Syntax Tree (AST)?

An AST is a tree representation of the abstract syntactic structure of code. Every node in the tree denotes a construct in the source code.

Example:

$sum = $a + $b;

In AST, this is interpreted as:

  • Assignment (=)
    • Variable $sum
    • Expression: Binary Operation (+)
      • Left: $a
      • Right: $b

This structure helps us understand code semantically, not just as raw text. It’s the same idea used in compilers, linters, and static analyzers.

Why Should a PHP Developer Care?

PHP added AST support starting from PHP 7 via the php-ast extension. You can now analyze PHP code in a structured way without needing to reimplement a parser yourself.

Use cases include:

  • Static analysis (like PHPStan or Psalm)
  • Automatic code refactoring
  • Code cleanup / transformation
  • Custom linters for project-specific rules
  • Test coverage mapping

Getting Started with php-ast

1. Install the extension
pecl install ast
2. Parse PHP code

Use nikic/php-parser, a library that converts PHP code into AST nodes you can walk through.

use PhpParser\ParserFactory;

$parser = (new ParserFactory)->create(ParserFactory::PREFER_PHP7);
$ast = $parser->parse('<?php $sum = $a + $b;');

Now you can recursively loop through $ast and inspect node types, like Expr_Assign, Expr_BinaryOp_Plus, etc.

Now lets dive deeper with this Extension

Most PHP developers know about tools like PHP_CodeSniffer or PHPStan, but few realize what’s happening under the hood — these tools rely on something powerful and underutilized: the Abstract Syntax Tree (AST).

In this article, we’ll use AST to build a simple complexity analyzer that counts how “complex” a PHP function is — not by line count, but by its control structures: if, for, while, switch, etc.

What is Code Complexity?

Code complexity, particularly cyclomatic complexity, measures the number of independent execution paths through a function. More branches = more test cases = harder to maintain.

For example:

function check($x) {
if ($x > 0) {
echo "Positive";
} else {
echo "Not positive";
}
}

This function has 2 paths = complexity 3 (1 base + 2 if).

Using nikic/php-parser, we’ll walk through PHP code and count:

  • if, elseif, else
  • for, foreach, while
  • case, default in switch
  • &&, || in conditions (optional)

Step-by-Step: Count Complexity with PHP AST

1. Install the parser:
composer require nikic/php-parser
Create an analyzer script:
use PhpParser\ParserFactory;
use PhpParser\NodeTraverser;
use PhpParser\NodeVisitorAbstract;
use PhpParser\Node;

require 'vendor/autoload.php';

$code = <<<'CODE'
<?php
function example($x) {
    if ($x > 0) {
        for ($i = 0; $i < $x; $i++) {
            echo $i;
        }
    } else if ($x < 0) {
        echo "Negative";
    } else {
        echo "Zero";
    }
}
CODE;

$parser = (new ParserFactory)->create(ParserFactory::PREFER_PHP7);
$ast = $parser->parse($code);

$traverser = new NodeTraverser();
$visitor = new class extends NodeVisitorAbstract {
    public $complexity = 1; // base complexity

    public function enterNode(Node $node) {
        if ($node instanceof Node\Stmt\If_
            || $node instanceof Node\Stmt\ElseIf_
            || $node instanceof Node\Stmt\For_
            || $node instanceof Node\Stmt\Foreach_
            || $node instanceof Node\Stmt\While_
            || $node instanceof Node\Stmt\Do_
            || $node instanceof Node\Stmt\Case_
            || $node instanceof Node\Expr\BinaryOp\BooleanAnd
            || $node instanceof Node\Expr\BinaryOp\BooleanOr) {
            $this->complexity++;
        }
    }
};

$traverser->addVisitor($visitor);
$traverser->traverse($ast);

echo "Complexity: " . $visitor->complexity . PHP_EOL;
Output:
Complexity: 5

Perfect! This shows your function has 5 paths — and might be due for refactoring if it gets higher.

Leave a Reply

Your email address will not be published. Required fields are marked *