Skip to content

Latest commit

 

History

History
3043 lines (2148 loc) · 44.2 KB

rules_overview.md

File metadata and controls

3043 lines (2148 loc) · 44.2 KB

82 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#';
}

👍


BoolishClassMethodPrefixRule

Method "%s()" returns bool type, so the name should start with is/has/was...

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


class SomeClass
{
    public function isOld(): bool
    {
        return $this->age > 100;
    }
}

👍


CheckAttributteArgumentClassExistsRule

Class was not found

#[SomeAttribute(firstName: 'MissingClass::class')]
class SomeClass
{
}


#[SomeAttribute(firstName: ExistingClass::class)]
class SomeClass
{
}

👍


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
{
}

👍


CheckNotTestsNamespaceOutsideTestsDirectoryRule

"*Test.php" file cannot be located outside "Tests" namespace

// file: "SomeTest.php
namespace App;

class SomeTest
{
}


// file: "SomeTest.php
namespace App\Tests;

class SomeTest
{
}

// file: "AnotherTest.php
namespace App\Tests\Features;

class AnotherTest
{
}

// file: "SomeOtherTest.php
namespace Tests\Features;

class SomeOtherTest
{
}

👍


CheckRequiredInterfaceInContractNamespaceRule

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

namespace App\Repository;

interface ProductRepositoryInterface
{
}


namespace App\Contract\Repository;

interface ProductRepositoryInterface
{
}

namespace App\Contracts\Repository;

interface ProductRepositoryInterface
{
}

👍


CheckSprintfMatchingTypesRule

sprintf() call mask type at index [%d] expects type "%s", but "%s" given

echo sprintf('My name is %s and I have %d children', 10, 'Tomas');


echo sprintf('My name is %s and I have %d children', 'Tomas', 10);

👍


CheckTypehintCallerTypeRule

Parameter %d should use "%s" type as the only type passed to this method

use PhpParser\Node;
use PhpParser\Node\Expr\MethodCall;

class SomeClass
{
    public function run(MethodCall $node)
    {
        $this->isCheck($node);
    }

    private function isCheck(Node $node)
    {
    }
}


use PhpParser\Node\Expr\MethodCall;

class SomeClass
{
    public function run(MethodCall $node)
    {
        $this->isCheck($node);
    }

    private function isCheck(MethodCall $node)
    {
    }
}

👍


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
{
}

👍


EmbeddedEnumClassConstSpotterRule

Constants "%s" should be extract to standalone enum class

🔧 configure it!

services:
    -
        class: Symplify\PHPStanRules\Rules\Enum\EmbeddedEnumClassConstSpotterRule
        tags: [phpstan.rules.rule]
        arguments:
            parentTypes:
                - AbstractObject

class SomeProduct extends AbstractObject
{
    public const STATUS_ENABLED = 1;

    public const STATUS_DISABLED = 0;
}


class SomeProduct extends AbstractObject
{
}

class SomeStatus
{
    public const ENABLED = 1;

    public const DISABLED = 0;
}

👍


ExclusiveNamespaceRule

Exclusive namespace can only contain classes of specific type, nothing else

🔧 configure it!

services:
    -
        class: Symplify\PHPStanRules\Rules\ExclusiveNamespaceRule
        tags: [phpstan.rules.rule]
        arguments:
            namespaceParts:
                - Presenter

namespace App\Presenter;

class SomeRepository
{
}


namespace App\Presenter;

class SomePresenter
{
}

👍


ForbiddenAlwaysSetterCallRule

The "%s" class always calls "%s()" setters, better move it to constructor

$firstPerson = new Person();
$firstPerson->setName('John');

$secondPerson = new Person();
$secondPerson->setName('Van');


$firstPerson = new Person('John');

$secondPerson = new Person('Van');

👍


ForbiddenAnonymousClassRule

Anonymous class is not allowed.

new class {};


class SomeClass
{

}

new SomeClass;

👍


ForbiddenArrayDestructRule

Array destruct is not allowed. Use value object to pass data instead

final class SomeClass
{
    public function run(): void
    {
        [$firstValue, $secondValue] = $this->getRandomData();
    }
}


final class SomeClass
{
    public function run(): void
    {
        $valueObject = $this->getValueObject();
        $firstValue = $valueObject->getFirstValue();
        $secondValue = $valueObject->getSecondValue();
    }
}

👍


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);
};

👍


ForbiddenArrayWithStringKeysRule

Array with keys is not allowed. Use value object to pass data instead

final class SomeClass
{
    public function run()
    {
        return [
            'name' => 'John',
            'surname' => 'Dope',
        ];
    }
}


final class SomeClass
{
    public function run()
    {
        return new Person('John', 'Dope');
    }
}

👍


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
{
}

👍


ForbiddenNamedArgumentsRule

Named arguments do not add any value here. Use normal arguments in the same order

return strlen(string: 'name');


return strlen('name');

👍


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('...');

👍


ForbiddenParamTypeRemovalRule

Removing parent param type is forbidden

interface RectorInterface
{
    public function refactor(Node $node);
}

final class SomeRector implements RectorInterface
{
    public function refactor($node)
    {
    }
}


interface RectorInterface
{
    public function refactor(Node $node);
}

final class SomeRector implements RectorInterface
{
    public function refactor(Node $node)
    {
    }
}

👍


ForbiddenSameNamedNewInstanceRule

New objects with "%s" name are overridden. This can lead to unwanted bugs, please pick a different name to avoid it.

$product = new Product();
$product = new Product();

$this->productRepository->save($product);


$firstProduct = new Product();
$secondProduct = new Product();

$this->productRepository->save($firstProduct);

👍


ForbiddenSpreadOperatorRule

Spread operator is not allowed.

$args = [$firstValue, $secondValue];
$message = sprintf('%s', ...$args);


$message = sprintf('%s', $firstValue, $secondValue);

👍


ForbiddenThisArgumentRule

$this as argument is not allowed. Refactor method to service composition

$this->someService->process($this, ...);


$this->someService->process($value, ...);

👍


IfElseToMatchSpotterRule

If/else construction can be replace with more robust match()

class SomeClass
{
    public function spot($value)
    {
        if ($value === 100) {
            $items = ['yes'];
        } else {
            $items = ['no'];
        }

        return $items;
    }
}


class SomeClass
{
    public function spot($value)
    {
        return match($value) {
            100 => ['yes'],
            default => ['no'],
        };
    }
}

👍


NarrowPublicClassMethodParamTypeByCallerTypeRule

Parameters should use "%s" types as the only types passed to this method

use PhpParser\Node\Expr\MethodCall;

final class SomeClass
{
    public function run(SomeService $someService, MethodCall $methodCall)
    {
        $someService->isCheck($node);
    }
}

final class SomeService
{
    public function isCheck($methodCall)
    {
    }
}


use PhpParser\Node\Expr\MethodCall;

final class SomeClass
{
    public function run(SomeService $someService, MethodCall $methodCall)
    {
        $someService->isCheck($node);
    }
}

final class SomeService
{
    public function isCheck(MethodCall $methodCall)
    {
    }
}

👍


NoAbstractMethodRule

Use explicit interface contract or a service over unclear abstract methods

abstract class SomeClass
{
    abstract public function run();
}


abstract class SomeClass implements RunnableInterface
{
}

interface RunnableInterface
{
    public function run();
}

👍


NoAbstractRule

Instead of abstract class, use specific service with composition

final class NormalHelper extends AbstractHelper
{
}

abstract class AbstractHelper
{
}


final class NormalHelper
{
    public function __construct(
        private SpecificHelper $specificHelper
    ) {
    }
}

final class SpecificHelper
{
}

👍


NoArrayAccessOnObjectRule

Use explicit methods over array access on object

class SomeClass
{
    public function run(MagicArrayObject $magicArrayObject)
    {
        return $magicArrayObject['more_magic'];
    }
}


class SomeClass
{
    public function run(MagicArrayObject $magicArrayObject)
    {
        return $magicArrayObject->getExplicitValue();
    }
}

👍


NoConstructorInTestRule

Do not use constructor in tests. Move to setUp() method

final class SomeTest
{
    public function __construct()
    {
        // ...
    }
}


final class SomeTest
{
    public function setUp()
    {
        // ...
    }
}

👍


NoConstructorSymfonyFormObjectRule

This object is used in a Symfony form, that uses magic setters/getters, so it cannot have required constructor

final class Ticket
{
    public function __construct(private int $price)
    {
    }
}

---

use Symfony\Component\Form\AbstractType;
use Symfony\Component\OptionsResolver\OptionsResolver;
use App\Entity\Ticket;

final class TicketFormType extends AbstractType
{
    public function configureOptions(OptionsResolver $resolver): void
    {
        $resolver->setDefaults([
            'data_class' => Ticket::class,
        ]);
    }
}


final class Ticket
{
    private ?int $price = null;

    public function setPrice(int $price): void
    {
        $this->price = $price;
    }

    public function getPrice(): ?int
    {
        return $this->price;
    }
}

---

use Symfony\Component\Form\AbstractType;
use Symfony\Component\OptionsResolver\OptionsResolver;
use App\Entity\Ticket;

final class TicketFormType extends AbstractType
{
    public function configureOptions(OptionsResolver $resolver): void
    {
        $resolver->setDefaults([
            'data_class' => Ticket::class,
        ]);
    }
}

👍


NoDefaultExceptionRule

Use custom exceptions instead of native "%s"

throw new RuntimeException('...');


use App\Exception\FileNotFoundException;

throw new FileNotFoundException('...');

👍


NoDuplicatedRegexRule

The "%s" constant contains duplicated regex "%s". Instead of duplicated regexes, extract domain regexes together to save maintenance

class SomeClass
{
    private const CLASS_NAME_REGEX = '#[\w\\]+#';
}

class AnotherClass
{
    private const DIFFERENT_NAME_REGEX = '#[\w\\]+#';
}


class ClassRegexRecipies
{
    private const NAME_REGEX = '#[\w\\]+#';
}

👍


NoDuplicatedShortClassNameRule

Class with base "%s" name is already used in "%s". Use unique name to make classes easy to recognize

🔧 configure it!

services:
    -
        class: Symplify\PHPStanRules\Rules\NoDuplicatedShortClassNameRule
        tags: [phpstan.rules.rule]
        arguments:
            toleratedNestingLevel: 1

namespace App;

class SomeClass
{
}

namespace App\Nested;

class SomeClass
{
}


namespace App;

class SomeClass
{
}

namespace App\Nested;

class AnotherClass
{
}

👍


NoDuplicatedTraitMethodNameRule

Method name "%s()" is used in multiple traits. Make it unique to avoid conflicts

trait FirstTrait
{
    public function run()
    {
    }
}

trait SecondTrait
{
    public function run()
    {
    }
}


trait FirstTrait
{
    public function run()
    {
    }
}

trait SecondTrait
{
    public function fly()
    {
    }
}

👍


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();
    }
}

👍


NoEmptyClassRule

There should be no empty class

class SomeClass
{
}


class SomeClass
{
    public function getSome()
    {
    }
}

👍


NoGetterAndPropertyRule

There are 2 way to get "%s" value: public property and getter now - pick one to avoid variant behavior.

final class SomeProduct
{
    public $name;

    public function getName(): string
    {
        return $this->name;
    }
}


final class SomeProduct
{
    private $name;

    public function getName(): string
    {
        return $this->name;
    }
}

👍


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);
    }
}

👍


NoIssetOnObjectRule

Use default null value and nullable compare instead of isset on object

class SomeClass
{
    public function run()
    {
        if (random_int(0, 1)) {
            $object = new SomeClass();
        }

        if (isset($object)) {
            return $object;
        }
    }
}


class SomeClass
{
    public function run()
    {
        $object = null;
        if (random_int(0, 1)) {
            $object = new SomeClass();
        }

        if ($object !== null) {
            return $object;
        }
    }
}

👍


NoMissingDirPathRule

The path "%s" was not found

$filePath = __DIR__ . '/missing_location.txt';


$filePath = __DIR__ . '/existing_location.txt';

👍


NoMixedCallableRule

Make callable type explicit. Here is how: https://phpstan.org/writing-php-code/phpdoc-types#callables

function run(callable $callable)
{
    return $callable(100);
}


/**
 * @param callable(): int $callable
 */
function run(callable $callable): int
{
    return $callable(100);
}

👍


NoMixedMethodCallerRule

Anonymous variable in a %s->...() method call can lead to false dead methods. Make sure the variable type is known

function run($unknownType)
{
    return $unknownType->call();
}


function run(KnownType $knownType)
{
    return $knownType->call();
}

👍


NoMixedPropertyFetcherRule

Anonymous variables in a "%s->..." property fetch can lead to false dead property. Make sure the variable type is known

function run($unknownType)
{
    return $unknownType->name;
}


function run(KnownType $knownType)
{
    return $knownType->name;
}

👍


NoNestedFuncCallRule

Use separate function calls with readable variable names

$filteredValues = array_filter(array_map($callback, $items));


$mappedItems = array_map($callback, $items);
$filteredValues = array_filter($mappedItems);

👍


NoNullableArrayPropertyRule

Use required typed property over of nullable array property

final class SomeClass
{
    private ?array $property = null;
}


final class SomeClass
{
    private array $property = [];
}

👍


NoParentMethodCallOnNoOverrideProcessRule

Do not call parent method if no override process

class SomeClass extends Printer
{
    public function print($nodes)
    {
        return parent::print($nodes);
    }
}


class SomeClass extends Printer
{
}

👍


NoProtectedClassElementRule

Instead of protected element, use private element or contract method

final class SomeClass
{
    protected function run()
    {
    }
}


final class SomeClass
{
    private function run()
    {
    }
}

👍


NoPublicPropertyByTypeRule

Class cannot have public properties. Use getter/setters instead

🔧 configure it!

services:
    -
        class: Symplify\PHPStanRules\Rules\Privatization\NoPublicPropertyByTypeRule
        tags: [phpstan.rules.rule]
        arguments:
            classTypes:
                - Entity

final class Person extends Entity
{
    public $name;
}


final class Person extends Entity
{
    private $name;

    public function getName()
    {
        return $this->name;
    }
}

👍


NoReferenceRule

Use explicit return value over magic &reference

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


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

👍


NoRelativeFilePathRule

Relative file path "%s" is not allowed, use absolute one with DIR

$filePath = 'some_file.txt';


$filePath = __DIR__ . '/some_file.txt';

👍


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);
    }
}

👍


NoReturnFalseInNonBoolClassMethodRule

Returning false in non return bool class method. Use null instead

class SomeClass
{
    /**
     * @var Item[]
     */
    private $items = [];

    public function getItem($key)
    {
        if (isset($this->items[$key])) {
            return $this->items[$key];
        }

        return false;
    }
}


class SomeClass
{
    /**
     * @var Item[]
     */
    private $items = [];

    public function getItem($key): ?Item
    {
        if (isset($this->items[$key])) {
            return $this->items[$key];
        }

        return null;
    }
}

👍


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;
    }
}

👍


NoRightPHPUnitAssertScalarRule

The compare assert arguments are switched. Move the expected value to the 1st left

use PHPUnit\Framework\TestCase;

final class SomeFlippedAssert extends TestCase
{
    public function test()
    {
        $value = 1000;
        $this->assertSame($value, 10);
    }
}


use PHPUnit\Framework\TestCase;

final class SomeFlippedAssert extends TestCase
{
    public function test()
    {
        $value = 1000;
        $this->assertSame(10, $value);
    }
}

👍


NoShortNameRule

Do not name "%s", shorter than %d chars

🔧 configure it!

services:
    -
        class: Symplify\PHPStanRules\ObjectCalisthenics\Rules\NoShortNameRule
        tags: [phpstan.rules.rule]
        arguments:
            minNameLength: 3

function is()
{
}


function isClass()
{
}

👍


NoStaticPropertyRule

Do not use static property

final class SomeClass
{
    private static $customFileNames = [];
}


final class SomeClass
{
    private $customFileNames = [];
}

👍


NoVoidGetterMethodRule

Getter method must return something, not void

final class SomeClass
{
    public function getData(): void
    {
        // ...
    }
}


final class SomeClass
{
    public function getData(): array
    {
        // ...
    }
}

👍


PreferredAttributeOverAnnotationRule

Use attribute instead of "%s" annotation

🔧 configure it!

services:
    -
        class: Symplify\PHPStanRules\Rules\PreferredAttributeOverAnnotationRule
        tags: [phpstan.rules.rule]
        arguments:
            annotations:
                - Symfony\Component\Routing\Annotation\Route

use Symfony\Component\Routing\Annotation\Route;

class SomeController
{
    /**
     * @Route()
     */
    public function action()
    {
    }
}


use Symfony\Component\Routing\Annotation\Route;

class SomeController
{
    #[Route]
    public function action()
    {
    }
}

👍


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('...');
    }
}

👍


PrefixAbstractClassRule

Abstract class name "%s" must be prefixed with "Abstract"

abstract class SomeClass
{
}


abstract class AbstractSomeClass
{
}

👍


PreventDuplicateClassMethodRule

Content of method "%s()" is duplicated. Use unique content or service instead

🔧 configure it!

services:
    -
        class: Symplify\PHPStanRules\Rules\PreventDuplicateClassMethodRule
        tags: [phpstan.rules.rule]
        arguments:
            minimumLineCount: 3

class SomeClass
{
    public function someMethod()
    {
        echo 'statement';
        $value = new SmartFinder();
    }
}

class AnotherClass
{
    public function someMethod()
    {
        echo 'statement';
        $differentValue = new SmartFinder();
    }
}


class SomeClass
{
    public function someMethod()
    {
        echo 'statement';
        $value = new SmartFinder();
    }
}
}

👍


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
{
}

👍


RequireCascadeValidateRule

Property "$%s" is missing @Valid annotation

use Symfony\Component\Form\AbstractType;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\Validator\Constraints\Valid;

final class NullablePropertyFormType extends AbstractType
{
    public function configureOptions(OptionsResolver $optionsResolver): void
    {
        $optionsResolver->setDefaults([
            'data_class' => SomeEntity::class,
            'constraints' => new Valid(),
        ]);
    }
}

class SomeEntity
{
    /**
     * @var NestedEntity
     */
    private $nestedEntity;
}


use Symfony\Component\Validator\Constraints as Assert;

class SomeEntity
{
    /**
     * @Assert\Valid
     * @var NestedEntity
     */
    private $nestedEntity;
}

👍


RequireConstantInMethodCallPositionRule

Parameter argument on position %d must use constant

🔧 configure it!

services:
    -
        class: Symplify\PHPStanRules\Rules\Enum\RequireConstantInMethodCallPositionRule
        tags: [phpstan.rules.rule]
        arguments:
            requiredLocalConstantInMethodCall:
                SomeType:
                    someMethod:
                        - 0

class SomeClass
{
    public function someMethod(SomeType $someType)
    {
        $someType->someMethod('hey');
    }
}


class SomeClass
{
    private const HEY = 'hey'

    public function someMethod(SomeType $someType)
    {
        $someType->someMethod(self::HEY);
    }
}

👍


RequireEnumDocBlockOnConstantListPassRule

On passing a constant, the method should have an enum type. See https://phpstan.org/writing-php-code/phpdoc-types#literals-and-constants

final class Direction
{
    public const LEFT = 'left';

    public const RIGHT = 'right';
}

final class Driver
{
    public function goToWork()
    {
        $this->turn(Direction::LEFT);
    }

    private function turn(string $direction)
    {
        // ...
    }
}


final class Direction
{
    public const LEFT = 'left';

    public const RIGHT = 'right';
}

final class Driver
{
    public function goToWork()
    {
        $this->turn(Direction::LEFT);
    }

    /**
     * @param Direction::*
     */
    private function turn(string $direction)
    {
        // ...
    }
}

👍


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()
    {
    }
}

👍


RequireNativeArraySymfonyRenderCallRule

Second argument of $this->render("template.twig", [...]) method should be explicit array, to avoid accidental variable override, see https://tomasvotruba.com/blog/2021/02/15/how-dangerous-is-your-nette-template-assign/

use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;

class SomeController extends AbstractController
{
    public function default()
    {
        $parameters['name'] = 'John';
        $parameters['name'] = 'Doe';
        return $this->render('...', $parameters);
    }
}


use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;

class SomeController extends AbstractController
{
    public function default()
    {
        return $this->render('...', [
            'name' => 'John'
        ]);
    }
}

👍


RequireSpecificReturnTypeOverAbstractRule

Provide more specific return type "%s" over abstract one

final class IssueControlFactory
{
    public function create(): Control
    {
        return new IssueControl();
    }
}

final class IssueControl extends Control
{
}


final class IssueControlFactory
{
    public function create(): IssueControl
    {
        return new IssueControl();
    }
}

final class IssueControl extends Control
{
}

👍


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';
}

👍


RequiredAbstractClassKeywordRule

Class name starting with "Abstract" must have an abstract keyword

class AbstractClass
{
}


abstract class AbstractClass
{
}

👍


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
{
}

👍


SuffixInterfaceRule

Interface must be suffixed with "Interface" exclusively

interface SomeClass
{
}


interface SomeInterface
{
}

👍


SuffixTraitRule

Trait must be suffixed by "Trait" exclusively

trait SomeClass
{
}


trait SomeTrait
{
}

👍


SwitchToMatchSpotterRule

Switch construction can be replace with more robust match()

switch ($key) {
    case 1:
        return 100;
    case 2:
        return 200;
    default:
        return 300;
};


return match($key) {
    1 => 100,
    2 => 200,
    default => 300,
};

👍


TwigPublicCallableExistsRule

The callable method [$this, "%s"] was not found

use Twig\Extension\AbstractExtension;
use Twig_SimpleFunction;

final class TwigExtensionWithMissingCallable extends AbstractExtension
{
    public function getFunctions()
    {
        return [
            new Twig_SimpleFunction('someFunctionName', [$this, 'someMethod']),
        ];
    }
}


use Twig\Extension\AbstractExtension;
use Twig_SimpleFunction;

final class TwigExtensionWithMissingCallable extends AbstractExtension
{
    public function getFunctions()
    {
        return [
            new Twig_SimpleFunction('someFunctionName', [$this, 'someMethod']),
        ];
    }

    public function someMethod()
    {
        // ...
    }
}

👍


UppercaseConstantRule

Constant "%s" must be uppercase

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


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

👍