Create Weird Fun PHPStan Rules like Nobody's Watching

There are 2 ways to use PHPStan. You can use native levels, and official extensions and raise the level from 0 to 8. This is a good start, but it often requires enormous work and brings must-have value.

There is also a 2nd way: I wanted PHPStan to be more fun and more tailored to the unique projects I work with. That's why I made symplify/phpstan-rules, a package that just crossed 6 200 000 downloads. It is one of the most used PHPStan extensions apart from official ones.

I put all the fun and practical rules there, and often they prove to be useful to others too.

But today I want you to move from end-user to creator.

"You teach me, I forget. You show me, I remember.
You involve me, I understand."

Today we write a custom PHPStan rule together. Not for everyone, but only for you and your local project. We will not write tests, we will not make it 100 % reliable, and we will not cover all edge cases. We will just make it bring value, and make it fun and practical.

That's the real beauty of my own local PHPStan rules - they can be KISS – Simple & Stupid. I don't have to feel ashamed on socials if they seem too vague or for everyone.


I work on various codebases, raising type coverage one 1 % at a time. It's a lot of manual work or Copilot exchanges I need to verify. In short: repetitive thinking hurts my brain.


Last week, I noticed a simple pattern in one of the codebases:

public function get($userId): User
{ /* ... */ }

public function run($userId): void
{ /* ... */ }

public function request($userId, array $params): void
{ /* ... */ }

There is a type missing for $userId... what if we know it's always int or string?

I've checked other calls in codebase + database and made an astounding discovery: the user id is always an int!


I wondered: what if we make a PHPStan rule that:

10-min Experiment

I often have no idea if the PHPStan rule will work or not, so I put 10 10-minute experiment limit on it. If it doesn't work, I just throw it away. If it does, we keep the rule and improve it.


Let's dive in!


1. Create PHPStan rule class

First, make a directory:

/utils/phpstan/src

There, we create an empty ParamTypeByNameRule.php class:

/utils/phpstan/src/ParamTypeByNameRule.php

2. Autoload in composer.json

{
    "autoload-dev": {
        "psr-4": {
            "Utils\\PHPStan\\": "utils/phpstan/src",
        }
    }
}

Refresh PSR-4 paths:

composer dump-autoload

3. The fun part: write the rule

The boring setup is done, let's write the fun part! What should our PHPStan rule do?


Easy-peasy! I think it will be the shortest custom rule ever written.

<?php

namespace Utils\PHPStan;

use PHPStan\Rules\Rule;
use PhpParser\Node\Param;

class ParamTypeByNameRule implements Rule
{
    public function getNodeType(): string
    {
        return Param::class;
    }

    /**
     * @param Param $node
     */
    public function processNode(Node $node, Scope $scope): array
    {
        // we know the type - let's skip it
        if ($node->type !== null) {
            return [];
        }

        // what is the parameter name?
        $parameterName = $node->var->name->toString();
        if ($parameterName !== 'userId') {
            return [];
        }

        return [
            RuleErrorBuilder::message('The $userId param is missing `int` type')
                ->identifier('custom.paramTypeByName')
                ->build();
        ];
    }
}

That's all folks!


4. Register rule in phpstan.neon

rules:
    - Utils\PHPStan\ParamTypeByNameRule

Protip: try running only this rule alone without any levels:

parameters:
    customRulesetUsed: true

    # comment out level
    # level: 8

5. RunPHPStan

vendor/bin/phpstan

That's it!


6. Improve...

Does it bring value? If yes, improve it. If not, throw it away.

The next step would be to add more param-name‐ pairs - like $articleId, $groupName etc.


This rule was so much fun to write and use, that I've turned it into a generic one. Raising param type coverage is one the most complex type coverages, and this rule turned it into a fun game that saves time and brain power:


7. Your turn!

Now it's your turn to make however weird, stupid, simple, non-sense PHPStan rule you want. Don't forget - nobody's watching and you can be creative beyond reason.

Can you think it? Write it!

It's your project, your rules, your fun, and if it brings any value, stick with it.


Writing custom PHPStan rules is one of the greatest assets when it comes to raising codebase value in time. It stays in the project after you leave and helps others to keep the codebase clean and safe.


Happy coding!