PHPStan - Creating a rule to disallow assignments in a conditional expression

Daniel Opitz
Daniel Opitz
12 Jan 2023

PHPStan is a great tool for catching common mistakes and bugs in your PHP code, but sometimes you may need to create your own custom rules to catch specific issues. One common issue that can be hard to catch is accidental assignments in conditionals. In this post, I will show you how to create a custom rule in PHPStan to catch this issue.

First, let’s take a look at an example of an accidental assignment in a conditional:

if ($x = getValue()) {
    // do something
}

In this example, the developer probably meant to check if getValue() returns a boolean value, but instead they accidentally assigned the return value of getValue() to $x. This can cause unexpected behavior in the code and be hard to track down.

To catch this issue, we can create a custom rule in PHPStan that checks for assignments in conditional statements and raises an error if one is found. Here’s an example implementation of the rule:

File: src/Rules/AssignmentInConditionRule.php

<?php

namespace App\Rules;

use PhpParser\Node;
use PhpParser\Node\Expr\Assign;
use PhpParser\NodeFinder;
use PHPStan\Analyser\Scope;
use PHPStan\Rules\Rule;

/**
 * @implements Rule<Node\Stmt\If_>
 */
class AssignmentInConditionRule implements Rule
{
    private NodeFinder $nodeFinder;

    public function __construct()
    {
        $this->nodeFinder = new NodeFinder();
    }

    public function getNodeType(): string
    {
        return Node\Stmt\If_::class;
    }

    public function processNode(Node $node, Scope $scope): array
    {
        $assignNode = $this->nodeFinder->findFirstInstanceOf($node->cond, Assign::class);
        if (!$assignNode instanceof Assign) {
            return [];
        }

        return ['Assignment in conditional expression is not allowed.'];
    }
}

This class implements the Rule interface, and the method getNodeType() returns the type of node that the rule should be applied to, in this case it’s Node\Stmt\If_ which is a node that represents an if statement.

The processNode() method gets called by PHPStan for each Node\Stmt\If_ it encounters.

It finds the first instance of Assign node within the If condition node, if it’s not found it returns an empty array, indicating that there’s no issue found.

If this condition does not meet, it means that there’s a conditional statement containing an assignment, so it raises an error with a message indicating that the assignment is not allowed.

The AssignmentInConditionRule class contains a private property $nodeFinder which is an instance of PhpParser\NodeFinder class, this class is used to find specific nodes in a PHP syntax tree generated by the PhpParser library.

To use this PHPStan rule, you need to include the class in your PHPStan configuration file phpstan.neon:

services:
  - class: App\Rules\AssignmentInConditionRule
    tags: [ phpstan.rules.rule ]

Once enabled, PHPStan will raise an error if it finds an assignment in a conditional statement, indicating that it may be an accidental assignment.

Please keep in mind that this is a basic example, you may need to adjust the implementation of your rule based on your specific needs.

In conclusion, creating custom rules in PHPStan can be a great way to catch specific issues in your code that might be hard to track down otherwise. With a bit of PHP knowledge and the right tools, you can create your own rules to catch common mistakes and bugs in your code, and make your development process more efficient and effective.

Update: You can find the source code on GitHub: odan/phpstan-rules.