/phpstan-rules

Advanced rules for PHPStan

Primary LanguagePHPMIT LicenseMIT

PHPStan Rules

Downloads

Set of rules for PHPStan used by Symplify projects


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


30 Rules Overview

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 SomeClass
{
    private const COMPLICATED_REGEX = '#some_complicated_stu|ff#';
}


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

👍


CheckClassNamespaceFollowPsr4Rule

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

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

class Baz
{
}


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

class Baz
{
}

👍


CheckRequiredInterfaceInContractNamespaceRule

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

namespace App\Repository;

interface ProductRepositoryInterface
{
}


namespace App\Contract\Repository;

interface ProductRepositoryInterface
{
}

👍


ClassNameRespectsParentSuffixRule

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

🔧 configure it!

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

class Some extends Command
{
}


class SomeCommand extends Command
{
}

👍


ExplicitClassPrefixSuffixRule

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

<?php

interface NotSuffixed
{
}

trait NotSuffixed
{
}

abstract class NotPrefixedClass
{
}


<?php

interface SuffixedInterface
{
}

trait SuffixedTrait
{
}

abstract class AbstractClass
{
}

👍


ForbiddenArrayMethodCallRule

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

usort($items, [$this, "method"]);


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

👍


ForbiddenExtendOfNonAbstractClassRule

Only abstract classes can be extended

final class SomeClass extends ParentClass
{
}

class ParentClass
{
}


final class SomeClass extends ParentClass
{
}

abstract class ParentClass
{
}

👍


ForbiddenFuncCallRule

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

🔧 configure it!

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

echo eval('...');


echo '...';

👍


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

dump($value);
echo $value;


echo $value;

👍


ForbiddenMultipleClassLikeInOneFileRule

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

// src/SomeClass.php
class SomeClass
{
}

interface SomeInterface
{
}


// src/SomeClass.php
class SomeClass
{
}

// src/SomeInterface.php
interface SomeInterface
{
}

👍


ForbiddenNodeRule

"%s" is forbidden to use

🔧 configure it!

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

return @strlen('...');


return strlen('...');

👍


ForbiddenStaticClassConstFetchRule

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

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


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

👍


NoDynamicNameRule

Use explicit names over dynamic ones

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


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

👍


NoEntityOutsideEntityNamespaceRule

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

namespace App\ValueObject;

use Doctrine\ORM\Mapping as ORM;

#[ORM\Entity]
class Product
{
}


namespace App\Entity;

use Doctrine\ORM\Mapping as ORM;

#[ORM\Entity]
class Product
{
}

👍


NoGlobalConstRule

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

const SOME_GLOBAL_CONST = 'value';


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

👍


NoInlineStringRegexRule

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

class SomeClass
{
    public function run($value)
    {
        return preg_match('#some_stu|ff#', $value);
    }
}


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

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

👍


NoReferenceRule

Use explicit return value over magic &reference

class SomeClass
{
    public function run(&$value)
    {
    }
}


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

👍


NoReturnArrayVariableListRule

Use value object over return of values

class ReturnVariables
{
    public function run($value, $value2): array
    {
        return [$value, $value2];
    }
}


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

👍


NoReturnSetterMethodRule

Setter method cannot return anything, only set value

final class SomeClass
{
    private $name;

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


final class SomeClass
{
    private $name;

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

👍


NoSingleInterfaceImplementerRule

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

class SomeClass implements SomeInterface
{
}

interface SomeInterface
{
}


class SomeClass implements SomeInterface
{
}

class AnotherClass implements SomeInterface
{
}

interface SomeInterface
{
}

👍


NoTestMocksRule

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

use PHPUnit\Framework\TestCase;

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


use PHPUnit\Framework\TestCase;

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

👍


PreferredClassRule

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

🔧 configure it!

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

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


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

👍


PreventParentMethodVisibilityOverrideRule

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

class SomeParentClass
{
    public function run()
    {
    }
}

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


class SomeParentClass
{
    public function run()
    {
    }
}

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

👍


RegexSuffixInRegexConstantRule

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

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

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


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

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

👍


RequireAttributeNameRule

Attribute must have all names explicitly defined

use Symfony\Component\Routing\Annotation\Route;

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


use Symfony\Component\Routing\Annotation\Route;

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

👍


RequireAttributeNamespaceRule

Attribute must be located in "Attribute" namespace

// app/Entity/SomeAttribute.php
namespace App\Controller;

#[\Attribute]
final class SomeAttribute
{
}


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

#[\Attribute]
final class SomeAttribute
{
}

👍


RequireExceptionNamespaceRule

Exception must be located in "Exception" namespace

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

final class SomeException extends Exception
{

}


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

final class SomeException extends Exception
{
}

👍


RequireInvokableControllerRule

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

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

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


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

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

👍


RequireUniqueEnumConstantRule

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

use MyCLabs\Enum\Enum;

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

    private const NO = 'yes';
}


use MyCLabs\Enum\Enum;

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

    private const NO = 'no';
}

👍


SeeAnnotationToTestRule

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

🔧 configure it!

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

class SomeClass extends Rule
{
}


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

👍


UppercaseConstantRule

Constant "%s" must be uppercase

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


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

👍



Happy coding!