phpstan-rules
phpstan-rules copied to clipboard
Advanced rules for PHPStan
PHPStan Rules
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!