Skip to content

Latest commit

Β 

History

History
1195 lines (830 loc) Β· 17.3 KB

README.md

File metadata and controls

1195 lines (830 loc) Β· 17.3 KB

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!