symplify / phpstan-rules
Set of Symplify rules for PHPStan
Fund package maintenance!
tomasvotruba
www.paypal.me/rectorphp
Installs: 6 413 454
Dependents: 172
Suggesters: 2
Security: 0
Stars: 212
Watchers: 4
Forks: 29
Open Issues: 0
Type:phpstan-extension
Requires
- php: ^7.4|^8.0
- nette/utils: ^3.2|^4.0
- phpstan/phpstan: ^2.1.8
- webmozart/assert: ^1.11
- dev-main
- 14.4.9
- 14.4.8
- 14.4.7
- 14.4.6
- 14.4.5
- 14.4.4
- 14.4.3
- 14.4.2
- 14.4.1
- 14.4.0
- 14.3.5
- 14.3.4
- 14.3.3
- 14.3.2
- 14.3.1
- 14.3.0
- 14.2.8
- 14.2.7
- 14.2.6
- 14.2.5
- 14.2.4
- 14.2.3
- 14.2.2
- 14.2.1
- 14.2.0
- 14.1.5
- 14.1.4
- 14.1.3
- 14.1.2
- 14.1.1
- 14.1.0
- 14.0.4
- 14.0.3
- 14.0.2
- 14.0.1
- 14.0.0
- 13.0.1
- 13.0.0
- 12.7.0
- 12.6.4
- 12.6.3
- 12.6.2
- 12.6.1
- 12.6.0
- 12.5.2
- 12.5.1
- 12.5.0
- 12.4.9
- 12.4.8
- 12.4.7
- 12.4.6
- 12.4.5
- 12.4.4
- 12.4.3
- 12.4.2
- 12.4.1
- 12.4.0
- 12.3.1
- 12.3.0.72
- 12.3.0
- 12.2.7.72
- 12.2.7
- 12.2.6.72
- 12.2.6
- 12.2.5.72
- 12.2.5
- 12.2.4.72
- 12.2.4
- 12.2.3.72
- 12.2.3
- 12.2.2.72
- 12.2.2
- 12.2.1.72
- 12.2.1
- 12.2.0.72
- 12.2.0
- 12.1.4.72
- 12.1.4
- 12.1.3.72
- 12.1.3
- 12.1.2.72
- 12.1.2
- 12.1.1.72
- 12.1.1
- 12.1.0.72
- 12.1.0
- 12.0.4.72
- 12.0.4
- 12.0.3.72
- 12.0.3
- 12.0.2.72
- 12.0.2
- 12.0.1.72
- 12.0.1
- 12.0.0.72
- 12.0.0
- 11.4.1.72
- 11.4.1
- 11.4.0.72
- 11.4.0
- 11.3.5.72
- 11.3.5
- 11.3.4.72
- 11.3.4
- 11.3.3.72
- 11.3.3
- 11.3.2.72
- 11.3.2
- 11.3.1.72
- 11.3.1
- 11.3.0.72
- 11.3.0
- 11.2.5.72
- 11.2.5
- 11.2.4.72
- 11.2.4
- 11.2.3.72
- 11.2.3
- 11.2.2.72
- 11.2.2
- 11.2.1
- 11.2.0.72
- 11.2.0
- 11.1.28.72
- 11.1.28
- 11.1.27.72
- 11.1.27
- 11.1.26.72
- 11.1.26
- 11.1.25.72
- 11.1.25
- 11.1.24
- 11.1.23
- 11.1.22
- 11.1.21
- 11.1.20
- 11.1.19
- 11.1.18
- 11.1.17
- 11.1.16
- 11.1.15
- 11.1.14
- 11.1.13
- 11.1.12
- 11.1.11
- 11.1.10
- 11.1.9
- 11.1.8
- 11.1.7
- 11.1.6
- 11.1.5
- 11.1.4
- 11.1.1
- 11.1.0
- 11.0.9
- 11.0.8
- 11.0.7
- 11.0.6
- 11.0.5
- 11.0.4
- 11.0.3
- 11.0.2
- 11.0.1
- 11.0.0
- 10.3.3
- 10.3.2
- 10.3.1
- 10.3.0
- 10.2.11
- 10.2.10
- 10.2.9
- 10.2.8
- 10.2.7
- 10.2.6
- 10.2.5
- 10.2.4
- 10.2.3
- 10.2.2
- 10.2.1
- 10.2.0
- 10.1.4
- 10.1.3
- 10.1.2
- 10.1.1
- 10.1.0
- 10.0.25
- 10.0.24
- 10.0.23
- 10.0.22
- 10.0.21
- 10.0.20
- 10.0.19
- 10.0.18
- 10.0.17
- 10.0.16
- 10.0.15
- 10.0.14
- 10.0.13
- 10.0.12
- 10.0.11
- 10.0.10
- 10.0.9
- 10.0.8
- 10.0.7
- 10.0.6
- 10.0.5
- 10.0.4
- 10.0.3
- 10.0.2
- 10.0.1
- 10.0.0
- 10.0.0-beta17
- 10.0.0-beta16
- 10.0.0-beta15
- 10.0.0-beta14
- 10.0.0-beta13
- 10.0.0-beta12
- 10.0.0-beta11
- 10.0.0-beta10
- 10.0.0-beta9
- 10.0.0-beta8
- 10.0.0-beta7
- 10.0.0-beta6
- 10.0.0-beta5
- 10.0.0-beta4
- 10.0.0-beta3
- 10.0.0-beta2
- 10.0.0-beta1
- 9.4.70
- 9.4.69
- 9.4.68
- 9.4.67
- 9.4.66
- 9.4.65
- 9.4.64
- 9.4.63
- 9.4.62
- 9.4.61
- 9.4.60
- 9.4.59
- 9.4.58
- 9.4.57
- 9.4.56
- 9.4.55
- 9.4.54
- 9.4.53
- 9.4.52
- 9.4.51
- 9.4.50
- 9.4.49
- 9.4.48
- 9.4.47
- 9.4.46
- 9.4.45
- 9.4.44
- 9.4.43
- 9.4.42
- 9.4.41
- 9.4.40
- 9.4.39
- 9.4.38
- 9.4.37
- 9.4.36
- 9.4.35
- 9.4.34
- 9.4.33
- 9.4.32
- 9.4.31
- 9.4.30
- 9.4.29
- 9.4.28
- 9.4.27
- 9.4.26
- 9.4.25
- 9.4.24
- 9.4.23
- 9.4.22
- 9.4.21
- 9.4.20
- 9.4.19
- 9.4.18
- 9.4.17
- 9.4.16
- 9.4.15
- 9.4.14
- 9.4.13
- 9.4.12
- 9.4.11
- 9.4.10
- 9.4.9
- 9.4.8
- 9.4.7
- 9.4.6
- 9.4.5
- 9.4.4
- 9.4.3
- 9.4.2
- v9.4.1
- v9.4.0
- v9.3.27
- v9.3.26
- v9.3.25
- v9.3.24
- v9.3.23
- v9.3.22
- v9.3.21
- v9.3.20
- v9.3.19
- v9.3.18
- v9.3.17
- v9.3.16
- v9.3.15
- v9.3.14
- v9.3.13
- v9.3.12
- v9.3.11
- v9.3.10
- v9.3.8
- v9.3.6
- v9.3.5
- v9.3.4
- v9.3.3
- v9.3.1
- v9.3.0
- v9.2.24
- v9.2.23
- v9.2.22
- v9.2.21
- v9.2.20
- v9.2.19
- v9.2.18
- v9.2.17
- v9.2.16
- v9.2.15
- v9.2.14
- v9.2.13
- v9.2.12
- v9.2.11
- v9.2.10
- v9.2.9
- v9.2.8
- v9.2.7
- v9.2.6
- v9.2.5
- v9.2.4
- v9.2.3
- v9.2.2
- 9.2.1
- 9.2.0
- 9.1.9
- 9.1.8
- 9.1.7
- 9.1.6
- 9.1.5
- 9.1.4
- 9.1.3
- 9.1.1
- 9.1.0
- 9.0.50
- 9.0.49
- 9.0.48
- 9.0.47
- 9.0.46
- 9.0.45
- 9.0.44
- 9.0.43
- 9.0.42
- 9.0.41
- 9.0.40
- 9.0.39
- 9.0.38
- 9.0.37
- 9.0.36
- 9.0.35
- 9.0.34
- 9.0.33
- 9.0.32
- 9.0.31
- 9.0.30
- 9.0.29
- 9.0.28
- 9.0.27
- 9.0.26
- 9.0.25
- 9.0.24
- 9.0.23
- 9.0.22
- 9.0.21
- 9.0.20
- 9.0.19
- 9.0.18
- 9.0.17
- 9.0.16
- 9.0.15
- 9.0.14
- 9.0.13
- 9.0.12
- 9.0.11
- 9.0.10
- 9.0.9
- 9.0.8
- 9.0.7
- 9.0.6
- 9.0.5
- 9.0.4
- 9.0.3
- 9.0.2
- 9.0.1
- 9.0.0
- 9.0.0-rc1
- 9.0.0-BETA9
- 9.0.0-BETA8
- 9.0.0-BETA7
- 9.0.0-BETA6
- 9.0.0-BETA5
- 9.0.0-BETA4
- 9.0.0-BETA3
- 9.0.0-BETA2
- 9.0.0-BETA1
- 8.3.48
- dev-tv-doctine-listener
- dev-tv-skip-validator-test-case
- dev-tv-extend-sets
- dev-tv-symfony-set
- dev-tv-load-functions
This package is auto-updated.
Last update: 2025-03-25 16:10:08 UTC
README
Set of 65+ PHPStan fun and practical rules that check:
- clean architecture, logical errors,
- naming, class namespace locations
- accidental visibility override,
- and Symfony, Doctrine or PHPUnit
bestproven practices.
Useful for any type of PHP project, from legacy to modern stack.
Install
composer require symplify/phpstan-rules --dev
Note: Make sure you use phpstan/extension-installer
to load necessary service configs.
Usage
Later, once you have most rules applied, it's best practice to include whole sets:
includes: - vendor/symplify/phpstan-rules/config/code-complexity-rules.neon - vendor/symplify/phpstan-rules/config/configurable-rules.neon - vendor/symplify/phpstan-rules/config/naming-rules.neon - vendor/symplify/phpstan-rules/config/static-rules.neon # project specific - vendor/symplify/phpstan-rules/config/rector-rules.neon - vendor/symplify/phpstan-rules/config/doctrine-rules.neon - vendor/symplify/phpstan-rules/config/symfony-rules.neon
But at start, make baby steps with one rule at a time:
Jump to: Symfony-specific rules, Doctrine-specific rules or PHPUnit-specific rules.
Special rules
Tired of ever growing ignored error count in your phpstan.neon
? Set hard limit to keep them low:
parameters: maximumIgnoredErrorCount: 50
ParamNameToTypeConventionRule
By convention, we can define parameter type by its name. If we know the "userId" is always an int
, PHPStan can warn us about it and let us know to fill the type.
services: - class: Symplify\PHPStanRules\Rules\Convention\ParamNameToTypeConventionRule tags: [phpstan.rules.rule] arguments: paramNamesToTypes: userId: int
function run($userId) { }
❌
function run(int $userId) { }
👍
CheckRequiredInterfaceInContractNamespaceRule
Interface must be located in "Contract" or "Contracts" namespace
rules: - Symplify\PHPStanRules\Rules\CheckRequiredInterfaceInContractNamespaceRule
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 { }
👍
StringFileAbsolutePathExistsRule
Absolute file path must exist. Checked suffixes are "yaml", "yml", "sql", "php" and "json".
rules: - Symplify\PHPStanRules\Rules\StringFileAbsolutePathExistsRule
// missing file path return __DIR__ . '/some_file.yml';
❌
// correct file path return __DIR__ . '/../fixtures/some_file.yml';
👍
NoConstructorOverrideRule
Possible __construct() override, this can cause missing dependencies or setup
rules: - Symplify\PHPStanRules\Rules\NoConstructorOverrideRule
class ParentClass { public function __construct(private string $dependency) { } } class SomeClass extends ParentClass { public function __construct() { } }
❌
final class SomeClass extends ParentClass { public function __construct(private string $dependency) { } }
👍
ExplicitClassPrefixSuffixRule
Interface have suffix of "Interface", trait have "Trait" suffix exclusively
rules: - Symplify\PHPStanRules\Rules\Explicit\ExplicitClassPrefixSuffixRule
<?php interface NotSuffixed { } trait NotSuffixed { } abstract class NotPrefixedClass { }
❌
<?php interface SuffixedInterface { } trait SuffixedTrait { } abstract class AbstractClass { }
👍
NoProtectedClassStmtRule
Avoid protected class stmts as they yield unexpected behavior. Use clear interface contract instead
rules: - Symplify\PHPStanRules\Rules\Explicit\NoProtectedClassStmtRule
ForbiddenArrayMethodCallRule
Array method calls [$this, "method"] are not allowed. Use explicit method instead to help PhpStorm, PHPStan and Rector understand your code
rules: - Symplify\PHPStanRules\Rules\Complexity\ForbiddenArrayMethodCallRule
usort($items, [$this, "method"]);
❌
usort($items, function (array $apples) { return $this->method($apples); };
👍
NoJustPropertyAssignRule
Instead of assigning service property to a variable, use the property directly
rules: - Symplify\PHPStanRules\Rules\Complexity\NoJustPropertyAssignRule
class SomeClass { // ... public function run() { $someService = $this->someService; $someService->run(); } }
❌
class SomeClass { // ... public function run() { $this->someService->run(); } }
👍
ForbiddenExtendOfNonAbstractClassRule
Only abstract classes can be extended
rules: - Symplify\PHPStanRules\Rules\ForbiddenExtendOfNonAbstractClassRule
final class SomeClass extends ParentClass { } class ParentClass { }
❌
abstract class ParentClass { }
👍
ForbiddenNewArgumentRule
Type "%s" is forbidden to be created manually with new X()
. Use service and constructor injection instead
services: - class: Symplify\PHPStanRules\Rules\ForbiddenNewArgumentRule tag: [phpstan.rules.rule] arguments: forbiddenTypes: - RepositoryService
↓
class SomeService { public function run() { $repositoryService = new RepositoryService(); $item = $repositoryService->get(1); } }
❌
class SomeService { public function __construct(private RepositoryService $repositoryService) { } public function run() { $item = $this->repositoryService->get(1); } }
👍
ForbiddenFuncCallRule
Function "%s()"
cannot be used/left in the code
🔧 configure it!
services: - class: Symplify\PHPStanRules\Rules\ForbiddenFuncCallRule tags: [phpstan.rules.rule] arguments: forbiddenFunctions: - dump # or with custom error message dump: 'seems you missed some debugging function'
↓
dump('...');
❌
echo '...';
👍
ForbiddenMultipleClassLikeInOneFileRule
Multiple class/interface/trait is not allowed in single file
rules: - Symplify\PHPStanRules\Rules\ForbiddenMultipleClassLikeInOneFileRule
// 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
rules: - Symplify\PHPStanRules\Rules\ForbiddenStaticClassConstFetchRule
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
rules: - Symplify\PHPStanRules\Rules\NoDynamicNameRule
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
rules: - Symplify\PHPStanRules\Rules\NoEntityOutsideEntityNamespaceRule
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
rules: - Symplify\PHPStanRules\Rules\NoGlobalConstRule
const SOME_GLOBAL_CONST = 'value';
❌
class SomeClass { public function run() { return self::SOME_CONST; } }
👍
NoReferenceRule
Use explicit return value over magic &reference
rules: - Symplify\PHPStanRules\Rules\NoReferenceRule
class SomeClass { public function run(&$value) { } }
❌
class SomeClass { public function run($value) { return $value; } }
👍
NoReturnSetterMethodRule
Setter method cannot return anything, only set value
rules: - Symplify\PHPStanRules\Rules\NoReturnSetterMethodRule
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; } }
👍
NoTestMocksRule
Mocking "%s" class is forbidden. Use direct/anonymous class instead for better static analysis
rules: - Symplify\PHPStanRules\Rules\PHPUnit\NoTestMocksRule
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.
rules: - Symplify\PHPStanRules\Rules\PreventParentMethodVisibilityOverrideRule
class SomeParentClass { public function run() { } } class SomeClass extends SomeParentClass { protected function run() { } }
❌
class SomeParentClass { public function run() { } } class SomeClass extends SomeParentClass { public function run() { } }
👍
RequiredOnlyInAbstractRule
@required
annotation should be used only in abstract classes, to child classes can use clean __construct()
service injection.
rules: - Symplify\PHPStanRules\Rules\Symfony\RequiredOnlyInAbstractRule
RequireRouteNameToGenerateControllerRouteRule
To pass a controller class to generate() method, the controller must have "#[Route(name: self::class)]" above the __invoke() method
rules: - Symplify\PHPStanRules\Rules\Symfony\RequireRouteNameToGenerateControllerRouteRule
SingleRequiredMethodRule
There must be maximum 1 @required method in the class. Merge it to one to avoid possible injection collision or duplicated injects.
rules: - Symplify\PHPStanRules\Rules\Symfony\SingleRequiredMethodRule
RequireAttributeNameRule
Attribute must have all names explicitly defined
rules: - Symplify\PHPStanRules\Rules\RequireAttributeNameRule
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() { } }
👍
NoRouteTrailingSlashPathRule
Avoid trailing slash in route path, to prevent redirects and SEO issues
rules: - Symplify\PHPStanRules\Rules\Symfony\NoRouteTrailingSlashPathRule
RequireAttributeNamespaceRule
Attribute must be located in "Attribute" namespace
rules: - Symplify\PHPStanRules\Rules\Domain\RequireAttributeNamespaceRule
// 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
rules: - Symplify\PHPStanRules\Rules\Domain\RequireExceptionNamespaceRule
// app/Controller/SomeException.php namespace App\Controller; final class SomeException extends Exception { }
❌
// app/Exception/SomeException.php namespace App\Exception; final class SomeException extends Exception { }
👍
RequireUniqueEnumConstantRule
Enum constants "%s" are duplicated. Make them unique instead
rules: - Symplify\PHPStanRules\Rules\Enum\RequireUniqueEnumConstantRule
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
rules: - Symplify\PHPStanRules\Rules\UppercaseConstantRule
final class SomeClass { public const some = 'value'; }
❌
final class SomeClass { public const SOME = 'value'; }
👍
2. Doctrine-specific Rules
RequireQueryBuilderOnRepositoryRule
Prevents using $entityManager->createQueryBuilder('...')
, use $repository->createQueryBuilder()
as safer.
rules: - Symplify\PHPStanRules\Rules\Doctrine\RequireQueryBuilderOnRepositoryRule
NoGetRepositoryOutsideServiceRule
Instead of getting repository from EntityManager, use constructor injection and service pattern to keep code clean
rules: - Symplify\PHPStanRules\Rules\Doctrine\NoGetRepositoryOutsideServiceRule
class SomeClass { public function run(EntityManagerInterface $entityManager) { return $entityManager->getRepository(SomeEntity::class); } }
❌
class SomeClass { public function __construct(SomeEntityRepository $someEntityRepository) { } }
👍
NoParentRepositoryRule
Repository should not extend parent repository, as it can lead to tight coupling
rules: - Symplify\PHPStanRules\Rules\Doctrine\NoParentRepositoryRule
use Doctrine\ORM\EntityRepository; final class SomeRepository extends EntityRepository { }
❌
final class SomeRepository { public function __construct(EntityManagerInterface $entityManager) { $this->repository = $entityManager->getRepository(SomeEntity::class); } }
👍
NoGetRepositoryOnServiceRepositoryEntityRule
Instead of calling "->getRepository(...::class)" service locator, inject service repository via constructor and use it directly
rules: - Symplify\PHPStanRules\Rules\Doctrine\NoGetRepositoryOnServiceRepositoryEntityRule
use Doctrine\ORM\Mapping as ORM; /** * @ORM\Entity(repositoryClass=SomeRepository::class) */ class SomeEntity { }
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository; final class SomeEntityRepository extends ServiceEntityRepository { }
use Doctrine\ORM\EntityManagerInterface; final class SomeService { public function run(EntityManagerInterface $entityManager) { return $this->entityManager->getRepository(SomeEntity::class); } }
❌
use Doctrine\ORM\EntityManagerInterface; final class SomeService { public function __construct(private SomeEntityRepository $someEntityRepository) { } }
👍
NoRepositoryCallInDataFixtureRule
Repository should not be called in data fixtures, as it can lead to tight coupling
rules: - Symplify\PHPStanRules\Rules\Doctrine\NoRepositoryCallInDataFixtureRule
use Doctrine\Common\DataFixtures\AbstractFixture; final class SomeFixture extends AbstractFixture { public function load(ObjectManager $objectManager) { $someRepository = $objectManager->getRepository(SomeEntity::class); $someEntity = $someRepository->get(1); } }
❌
use Doctrine\Common\DataFixtures\AbstractFixture; final class SomeFixture extends AbstractFixture { public function load(ObjectManager $objectManager) { $someEntity = $this->getReference('some-entity-1'); } }
👍
3. Symfony-specific Rules
FormTypeClassNameRule
rules: - Symplify\PHPStanRules\Rules\Symfony\FormTypeClassNameRule
Classes that extend AbstractType
should have *FormType
suffix, to make it clear it's a form class.
NoConstructorAndRequiredTogetherRule
Constructor injection and #[Required]
method should not be used together in single class. Pick one, to keep architecture clean.
rules: - Symplify\PHPStanRules\Rules\Symfony\NoConstructorAndRequiredTogetherRule
NoGetDoctrineInControllerRule
Prevents using $this->getDoctrine()
in controllers, to promote dependency injection.
rules: - Symplify\PHPStanRules\Rules\Symfony\NoGetDoctrineInControllerRule
NoGetInControllerRule
Prevents using $this->get(...)
in controllers, to promote dependency injection.
rules: - Symplify\PHPStanRules\Rules\Symfony\NoGetInControllerRule
NoGetInCommandRule
Prevents using $this->get(...)
in commands, to promote dependency injection.
rules: - Symplify\PHPStanRules\Rules\Symfony\NoGetInCommandRule
NoAbstractControllerConstructorRule
Abstract controller should not have constructor, as it can lead to tight coupling. Use @required annotation instead
rules: - Symplify\PHPStanRules\Rules\Symfony\NoAbstractControllerConstructorRule
abstract class AbstractController extends Controller { public function __construct( private SomeService $someService ) { } }
❌
abstract class AbstractController extends Controller { private $someService; #[Required] public function autowireAbstractController(SomeService $someService) { $this->someService = $someService; } }
👍
NoRoutingPrefixRule
Avoid global route prefixing. Use single place for paths in @Route/#[Route] and improve static analysis instead.
rules: - Symplify\PHPStanRules\Rules\Symfony\NoRoutingPrefixRule
use Symfony\Component\Routing\Loader\Configurator\RoutingConfigurator; return static function (RoutingConfigurator $routingConfigurator): void { $routingConfigurator->import(__DIR__ . '/some-path') ->prefix('/some-prefix'); };
❌
use Symfony\Component\Routing\Loader\Configurator\RoutingConfigurator; return static function (RoutingConfigurator $routingConfigurator): void { $routingConfigurator->import(__DIR__ . '/some-path'); };
👍
NoClassLevelRouteRule
Avoid class-level route prefixing. Use method route to keep single source of truth and focus
rules: - Symplify\PHPStanRules\Rules\Symfony\NoClassLevelRouteRule
use Symfony\Component\Routing\Attribute\Route; #[Route('/some-prefix')] class SomeController { #[Route('/some-action')] public function someAction() { } }
❌
use Symfony\Component\Routing\Attribute\Route; class SomeController { #[Route('/some-prefix/some-action')] public function someAction() { } }
👍
NoFindTaggedServiceIdsCallRule
Instead of "$this->findTaggedServiceIds()" use more reliable registerForAutoconfiguration() and tagged iterator attribute. Those work outside any configuration and avoid missed tag errors
rules: - Symplify\PHPStanRules\Rules\Symfony\NoFindTaggedServiceIdsCallRule
NoRequiredOutsideClassRule
Symfony #[Require]/@required should be used only in classes to avoid misuse
rules: - Symplify\PHPStanRules\Rules\Symfony\NoRequiredOutsideClassRule
use Symfony\Component\DependencyInjection\Attribute\Required; trait SomeTrait { #[Required] public function autowireSomeTrait(SomeService $someService) { // ... } }
❌
abstract class SomeClass { #[Required] public function autowireSomeClass(SomeService $someService) { // ... } }
👍
SingleArgEventDispatchRule
The event dispatch() method can have only 1 arg - the event object
rules: - Symplify\PHPStanRules\Rules\Symfony\SingleArgEventDispatchRule
use Symfony\Component\EventDispatcher\EventDispatcherInterface; final class SomeClass { public function __construct( private EventDispatcherInterface $eventDispatcher ) { } public function run() { $this->eventDispatcher->dispatch('event', 'another-arg'); } }
❌
use Symfony\Component\EventDispatcher\EventDispatcherInterface; final class SomeClass { public function __construct( private EventDispatcherInterface $eventDispatcher ) { } public function run() { $this->eventDispatcher->dispatch(new EventObject()); } }
👍
NoListenerWithoutContractRule
There should be no listeners modified in config. Use EventSubscriberInterface contract or #[AsEventListener] attribute and PHP instead
rules: - Symplify\PHPStanRules\Rules\Symfony\NoListenerWithoutContractRule
class SomeListener { public function onEvent() { } }
❌
use Symfony\Component\EventDispatcher\EventSubscriberInterface; class SomeListener implements EventSubscriberInterface { public static function getSubscribedEvents(): array { return [ 'event' => 'onEvent', ]; } public function onEvent() { } }
👍
use Symfony\Component\EventDispatcher\Attribute\AsEventListener; #[AsEventListener] class SomeListener { public function __invoke() { } }
👍
NoDoctrineListenerWithoutContractRule
There should be no Doctrine listeners modified in config. Implement "Document\Event\EventSubscriber" to provide events in the class itself
rules: - Symplify\PHPStanRules\Rules\Doctrine\NoDoctrineListenerWithoutContractRule
class SomeListener { public function onFlush() { } }
❌
use Doctrine\Common\EventSubscriber; use Doctrine\ODM\MongoDB\Events; class SomeListener implements EventSubscriber { public function onFlush() { } public static function getSubscribedEvents(): array { return [ Events::onFlush ]; } }
👍
NoStringInGetSubscribedEventsRule
Symfony getSubscribedEvents() method must contain only event class references, no strings
rules: - Symplify\PHPStanRules\Rules\Symfony\NoStringInGetSubscribedEventsRule
use Symfony\Component\EventDispatcher\EventSubscriberInterface; class SomeListener implements EventSubscriberInterface { public static function getSubscribedEvents(): array { return [ 'event' => 'onEvent', ]; } public function onEvent() { } }
❌
use Symfony\Component\EventDispatcher\EventSubscriberInterface; class SomeListener implements EventSubscriberInterface { public static function getSubscribedEvents(): array { return [ Event::class => 'onEvent', ]; } public function onEvent() { } }
👍
RequireInvokableControllerRule
Use invokable controller with __invoke() method instead of named action method
rules: - Symplify\PHPStanRules\Rules\Symfony\RequireInvokableControllerRule
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; final class SomeController extends AbstractController { #[Route()] public function __invoke() { } }
👍
4. PHPUnit-specific Rules
NoMockObjectAndRealObjectPropertyRule
Avoid using one property for both real object and mock object. Use separate properties or single type instead
rules: - Symplify\PHPStanRules\Rules\PHPUnit\NoMockObjectAndRealObjectPropertyRule
NoEntityMockingRule, NoDocumentMockingRule
Instead of entity or document mocking, create object directly to get better type support
rules: - Symplify\PHPStanRules\Rules\PHPUnit\NoEntityMockingRule - Symplify\PHPStanRules\Rules\PHPUnit\NoDocumentMockingRule
use PHPUnit\Framework\TestCase; final class SomeTest extends TestCase { public function test() { $someEntityMock = $this->createMock(SomeEntity::class); } }
❌
use PHPUnit\Framework\TestCase; final class SomeTest extends TestCase { public function test() { $someEntityMock = new SomeEntity(); } }
👍
NoAssertFuncCallInTestsRule
Avoid using assert*() functions in tests, as they can lead to false positives
rules: - Symplify\PHPStanRules\Rules\PHPUnit\NoAssertFuncCallInTestsRule
NoMockOnlyTestRule
Test should have at least one non-mocked property, to test something
rules: - Symplify\PHPStanRules\Rules\PHPUnit\NoMockOnlyTestRule
use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; class SomeTest extends TestCase { private MockObject $firstMock; private MockObject $secondMock; public function setUp() { $this->firstMock = $this->createMock(SomeService::class); $this->secondMock = $this->createMock(AnotherService::class); } }
❌
use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; class SomeTest extends TestCase { private SomeService $someService; private FirstMock $firstMock; public function setUp() { $this->someService = new SomeService(); $this->firstMock = $this->createMock(AnotherService::class); } }
👍
PublicStaticDataProviderRule
PHPUnit data provider method "%s" must be public
rules: - Symplify\PHPStanRules\Rules\PHPUnit\PublicStaticDataProviderRule
use PHPUnit\Framework\TestCase; final class SomeTest extends TestCase { /** * @dataProvider dataProvider */ public function test(): array { return []; } protected function dataProvider(): array { return []; } }
❌
use PHPUnit\Framework\TestCase; final class SomeTest extends TestCase { /** * @dataProvider dataProvider */ public function test(): array { return []; } public static function dataProvider(): array { return []; } }
👍
Happy coding!