phpstan-rules icon indicating copy to clipboard operation
phpstan-rules copied to clipboard

Advanced rules for PHPStan

PHPStan Rules

Downloads

Set of rules for PHPStan used by Symplify projects

  • See Rules Overview

Install

composer require symplify/phpstan-rules --dev

Note: Make sure you use phpstan/extension-installer to load necessary service configs.


1. Add Prepared Sets

Sets are bunch of rules grouped by a common area, e.g. improve naming. You can pick from 5 sets:

includes:
    - vendor/symplify/phpstan-rules/config/code-complexity-rules.neon
    - vendor/symplify/phpstan-rules/config/naming-rules.neon
    - vendor/symplify/phpstan-rules/config/regex-rules.neon
    - vendor/symplify/phpstan-rules/config/static-rules.neon

Add sets one by one, fix what you find useful and ignore the rest.


Do you write custom Rector rules? Add rules for them too:

includes:
    - vendor/symplify/phpstan-rules/config/rector-rules.neon

2. Cherry-pick Configurable Rules

There is one set with pre-configured configurable rules. Include it and see what is errors are found:

# phpstan.neon
includes:
    - vendor/symplify/phpstan-rules/config/configurable-rules.neon

Would you like to tailor it to fit your taste? Pick one PHPStan rule and configure it manually ↓

services:
    -
        class: Symplify\PHPStanRules\Rules\ForbiddenNodeRule
        tags: [phpstan.rules.rule]
        arguments:
            forbiddenNodes:
                - PhpParser\Node\Expr\Empty_
                - PhpParser\Node\Stmt\Switch_

3. Register Particular Rules

[ruledoc-start]

AnnotateRegexClassConstWithRegexLinkRule

Add regex101.com link to that shows the regex in practise, so it will be easier to maintain in case of bug/extension in the future

  • class: Symplify\PHPStanRules\Rules\AnnotateRegexClassConstWithRegexLinkRule
class SomeClass
{
    private const COMPLICATED_REGEX = '#some_complicated_stu|ff#';
}

:x:


class SomeClass
{
    /**
     * @see https://regex101.com/r/SZr0X5/12
     */
    private const COMPLICATED_REGEX = '#some_complicated_stu|ff#';
}

:+1:


CheckClassNamespaceFollowPsr4Rule

Class like namespace "%s" does not follow PSR-4 configuration in composer.json

  • class: Symplify\PHPStanRules\Rules\CheckClassNamespaceFollowPsr4Rule
// defined "Foo\Bar" namespace in composer.json > autoload > psr-4
namespace Foo;

class Baz
{
}

:x:


// defined "Foo\Bar" namespace in composer.json > autoload > psr-4
namespace Foo\Bar;

class Baz
{
}

:+1:


CheckRequiredInterfaceInContractNamespaceRule

Interface must be located in "Contract" or "Contracts" namespace

  • class: Symplify\PHPStanRules\Rules\CheckRequiredInterfaceInContractNamespaceRule
namespace App\Repository;

interface ProductRepositoryInterface
{
}

:x:


namespace App\Contract\Repository;

interface ProductRepositoryInterface
{
}

:+1:


ClassNameRespectsParentSuffixRule

Class should have suffix "%s" to respect parent type

:wrench: configure it!

  • class: Symplify\PHPStanRules\Rules\ClassNameRespectsParentSuffixRule
services:
    -
        class: Symplify\PHPStanRules\Rules\ClassNameRespectsParentSuffixRule
        tags: [phpstan.rules.rule]
        arguments:
            parentClasses:
                - Symfony\Component\Console\Command\Command

class Some extends Command
{
}

:x:


class SomeCommand extends Command
{
}

:+1:


ExplicitClassPrefixSuffixRule

Interface have suffix of "Interface", trait have "Trait" suffix exclusively

  • class: Symplify\PHPStanRules\Rules\Explicit\ExplicitClassPrefixSuffixRule
<?php

interface NotSuffixed
{
}

trait NotSuffixed
{
}

abstract class NotPrefixedClass
{
}

:x:


<?php

interface SuffixedInterface
{
}

trait SuffixedTrait
{
}

abstract class AbstractClass
{
}

:+1:


ForbiddenArrayMethodCallRule

Array method calls [$this, "method"] are not allowed. Use explicit method instead to help PhpStorm, PHPStan and Rector understand your code

  • class: Symplify\PHPStanRules\Rules\Complexity\ForbiddenArrayMethodCallRule
usort($items, [$this, "method"]);

:x:


usort($items, function (array $apples) {
    return $this->method($apples);
};

:+1:


ForbiddenExtendOfNonAbstractClassRule

Only abstract classes can be extended

  • class: Symplify\PHPStanRules\Rules\ForbiddenExtendOfNonAbstractClassRule
final class SomeClass extends ParentClass
{
}

class ParentClass
{
}

:x:


final class SomeClass extends ParentClass
{
}

abstract class ParentClass
{
}

:+1:


ForbiddenFuncCallRule

Function "%s()" cannot be used/left in the code

:wrench: configure it!

  • class: Symplify\PHPStanRules\Rules\ForbiddenFuncCallRule
services:
    -
        class: Symplify\PHPStanRules\Rules\ForbiddenFuncCallRule
        tags: [phpstan.rules.rule]
        arguments:
            forbiddenFunctions:
                - eval

echo eval('...');

:x:


echo '...';

:+1:


services:
    -
        class: Symplify\PHPStanRules\Rules\ForbiddenFuncCallRule
        tags: [phpstan.rules.rule]
        arguments:
            forbiddenFunctions:
                dump: 'seems you missed some debugging function'

dump($value);
echo $value;

:x:


echo $value;

:+1:


ForbiddenMultipleClassLikeInOneFileRule

Multiple class/interface/trait is not allowed in single file

  • class: Symplify\PHPStanRules\Rules\ForbiddenMultipleClassLikeInOneFileRule
// src/SomeClass.php
class SomeClass
{
}

interface SomeInterface
{
}

:x:


// src/SomeClass.php
class SomeClass
{
}

// src/SomeInterface.php
interface SomeInterface
{
}

:+1:


ForbiddenNodeRule

"%s" is forbidden to use

:wrench: configure it!

  • class: Symplify\PHPStanRules\Rules\ForbiddenNodeRule
services:
    -
        class: Symplify\PHPStanRules\Rules\ForbiddenNodeRule
        tags: [phpstan.rules.rule]
        arguments:
            forbiddenNodes:
                - PhpParser\Node\Expr\ErrorSuppress

return @strlen('...');

:x:


return strlen('...');

:+1:


ForbiddenStaticClassConstFetchRule

Avoid static access of constants, as they can change value. Use interface and contract method instead

  • class: Symplify\PHPStanRules\Rules\ForbiddenStaticClassConstFetchRule
class SomeClass
{
    public function run()
    {
        return static::SOME_CONST;
    }
}

:x:


class SomeClass
{
    public function run()
    {
        return self::SOME_CONST;
    }
}

:+1:


NoDynamicNameRule

Use explicit names over dynamic ones

  • class: Symplify\PHPStanRules\Rules\NoDynamicNameRule
class SomeClass
{
    public function old(): bool
    {
        return $this->${variable};
    }
}

:x:


class SomeClass
{
    public function old(): bool
    {
        return $this->specificMethodName();
    }
}

:+1:


NoEntityOutsideEntityNamespaceRule

Class with #[Entity] attribute must be located in "Entity" namespace to be loaded by Doctrine

  • class: Symplify\PHPStanRules\Rules\NoEntityOutsideEntityNamespaceRule
namespace App\ValueObject;

use Doctrine\ORM\Mapping as ORM;

#[ORM\Entity]
class Product
{
}

:x:


namespace App\Entity;

use Doctrine\ORM\Mapping as ORM;

#[ORM\Entity]
class Product
{
}

:+1:


NoGlobalConstRule

Global constants are forbidden. Use enum-like class list instead

  • class: Symplify\PHPStanRules\Rules\NoGlobalConstRule
const SOME_GLOBAL_CONST = 'value';

:x:


class SomeClass
{
    public function run()
    {
        return self::SOME_CONST;
    }
}

:+1:


NoInlineStringRegexRule

Use local named constant instead of inline string for regex to explain meaning by constant name

  • class: Symplify\PHPStanRules\Rules\NoInlineStringRegexRule
class SomeClass
{
    public function run($value)
    {
        return preg_match('#some_stu|ff#', $value);
    }
}

:x:


class SomeClass
{
    /**
     * @var string
     */
    public const SOME_STUFF_REGEX = '#some_stu|ff#';

    public function run($value)
    {
        return preg_match(self::SOME_STUFF_REGEX, $value);
    }
}

:+1:


NoReferenceRule

Use explicit return value over magic &reference

  • class: Symplify\PHPStanRules\Rules\NoReferenceRule
class SomeClass
{
    public function run(&$value)
    {
    }
}

:x:


class SomeClass
{
    public function run($value)
    {
        return $value;
    }
}

:+1:


NoReturnArrayVariableListRule

Use value object over return of values

  • class: Symplify\PHPStanRules\Rules\NoReturnArrayVariableListRule
class ReturnVariables
{
    public function run($value, $value2): array
    {
        return [$value, $value2];
    }
}

:x:


final class ReturnVariables
{
    public function run($value, $value2): ValueObject
    {
        return new ValueObject($value, $value2);
    }
}

:+1:


NoReturnSetterMethodRule

Setter method cannot return anything, only set value

  • class: Symplify\PHPStanRules\Rules\NoReturnSetterMethodRule
final class SomeClass
{
    private $name;

    public function setName(string $name): int
    {
        return 1000;
    }
}

:x:


final class SomeClass
{
    private $name;

    public function setName(string $name): void
    {
        $this->name = $name;
    }
}

:+1:


NoSingleInterfaceImplementerRule

Interface "%s" has only single implementer. Consider using the class directly as there is no point in using the interface.

  • class: Symplify\PHPStanRules\Rules\NoSingleInterfaceImplementerRule
class SomeClass implements SomeInterface
{
}

interface SomeInterface
{
}

:x:


class SomeClass implements SomeInterface
{
}

class AnotherClass implements SomeInterface
{
}

interface SomeInterface
{
}

:+1:


NoTestMocksRule

Mocking "%s" class is forbidden. Use direct/anonymous class instead for better static analysis

  • class: Symplify\PHPStanRules\Rules\PHPUnit\NoTestMocksRule
use PHPUnit\Framework\TestCase;

final class SkipApiMock extends TestCase
{
    public function test()
    {
        $someTypeMock = $this->createMock(SomeType::class);
    }
}

:x:


use PHPUnit\Framework\TestCase;

final class SkipApiMock extends TestCase
{
    public function test()
    {
        $someTypeMock = new class() implements SomeType {};
    }
}

:+1:


PreferredClassRule

Instead of "%s" class/interface use "%s"

:wrench: configure it!

  • class: Symplify\PHPStanRules\Rules\PreferredClassRule
services:
    -
        class: Symplify\PHPStanRules\Rules\PreferredClassRule
        tags: [phpstan.rules.rule]
        arguments:
            oldToPreferredClasses:
                SplFileInfo: CustomFileInfo

class SomeClass
{
    public function run()
    {
        return new SplFileInfo('...');
    }
}

:x:


class SomeClass
{
    public function run()
    {
        return new CustomFileInfo('...');
    }
}

:+1:


PreventParentMethodVisibilityOverrideRule

Change "%s()" method visibility to "%s" to respect parent method visibility.

  • class: Symplify\PHPStanRules\Rules\PreventParentMethodVisibilityOverrideRule
class SomeParentClass
{
    public function run()
    {
    }
}

class SomeClass extends SomeParentClass
{
    protected function run()
    {
    }
}

:x:


class SomeParentClass
{
    public function run()
    {
    }
}

class SomeClass extends SomeParentClass
{
    public function run()
    {
    }
}

:+1:


RegexSuffixInRegexConstantRule

Name your constant with "_REGEX" suffix, instead of "%s"

  • class: Symplify\PHPStanRules\Rules\RegexSuffixInRegexConstantRule
class SomeClass
{
    public const SOME_NAME = '#some\s+name#';

    public function run($value)
    {
        $somePath = preg_match(self::SOME_NAME, $value);
    }
}

:x:


class SomeClass
{
    public const SOME_NAME_REGEX = '#some\s+name#';

    public function run($value)
    {
        $somePath = preg_match(self::SOME_NAME_REGEX, $value);
    }
}

:+1:


RequireAttributeNameRule

Attribute must have all names explicitly defined

  • class: Symplify\PHPStanRules\Rules\RequireAttributeNameRule
use Symfony\Component\Routing\Annotation\Route;

class SomeController
{
    #[Route("/path")]
    public function someAction()
    {
    }
}

:x:


use Symfony\Component\Routing\Annotation\Route;

class SomeController
{
    #[Route(path: "/path")]
    public function someAction()
    {
    }
}

:+1:


RequireAttributeNamespaceRule

Attribute must be located in "Attribute" namespace

  • class: Symplify\PHPStanRules\Rules\Domain\RequireAttributeNamespaceRule
// app/Entity/SomeAttribute.php
namespace App\Controller;

#[\Attribute]
final class SomeAttribute
{
}

:x:


// app/Attribute/SomeAttribute.php
namespace App\Attribute;

#[\Attribute]
final class SomeAttribute
{
}

:+1:


RequireExceptionNamespaceRule

Exception must be located in "Exception" namespace

  • class: Symplify\PHPStanRules\Rules\Domain\RequireExceptionNamespaceRule
// app/Controller/SomeException.php
namespace App\Controller;

final class SomeException extends Exception
{

}

:x:


// app/Exception/SomeException.php
namespace App\Exception;

final class SomeException extends Exception
{
}

:+1:


RequireInvokableControllerRule

Use invokable controller with __invoke() method instead of named action method

  • class: Symplify\PHPStanRules\Symfony\Rules\RequireInvokableControllerRule
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\Routing\Annotation\Route;

final class SomeController extends AbstractController
{
    #[Route()]
    public function someMethod()
    {
    }
}

:x:


use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\Routing\Annotation\Route;

final class SomeController extends AbstractController
{
    #[Route()]
    public function __invoke()
    {
    }
}

:+1:


RequireUniqueEnumConstantRule

Enum constants "%s" are duplicated. Make them unique instead

  • class: Symplify\PHPStanRules\Rules\Enum\RequireUniqueEnumConstantRule
use MyCLabs\Enum\Enum;

class SomeClass extends Enum
{
    private const YES = 'yes';

    private const NO = 'yes';
}

:x:


use MyCLabs\Enum\Enum;

class SomeClass extends Enum
{
    private const YES = 'yes';

    private const NO = 'no';
}

:+1:


SeeAnnotationToTestRule

Class "%s" is missing @see annotation with test case class reference

:wrench: configure it!

  • class: Symplify\PHPStanRules\Rules\SeeAnnotationToTestRule
services:
    -
        class: Symplify\PHPStanRules\Rules\SeeAnnotationToTestRule
        tags: [phpstan.rules.rule]
        arguments:
            requiredSeeTypes:
                - Rule

class SomeClass extends Rule
{
}

:x:


/**
 * @see SomeClassTest
 */
class SomeClass extends Rule
{
}

:+1:


UppercaseConstantRule

Constant "%s" must be uppercase

  • class: Symplify\PHPStanRules\Rules\UppercaseConstantRule
final class SomeClass
{
    public const some = 'value';
}

:x:


final class SomeClass
{
    public const SOME = 'value';
}

:+1:


[ruledoc-end]

Happy coding!