Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 9 additions & 4 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,14 +17,19 @@ visibility, and Symfony/Doctrine/PHPUnit proven practices). It is a PHPStan exte
every rule, e.g. `symplify.noTestMocks`.
- `config/*.neon` — one set per topic. Sets registered globally via the
`composer.json` `extra.phpstan.includes` are auto-loaded by
`phpstan/extension-installer`; the rest are opt-in via `includes:` in the user's
`phpstan.neon`.
`phpstan/extension-installer` and toggled via `symplify.*` parameters; only
`rector-rules.neon` stays opt-in via `includes:` in the user's `phpstan.neon`.
- `tests/Rules/<RuleName>/` — one dir per rule, with `Fixture/` PHP files and a
`config/configured_rule.neon`.

## Opt-in parameters
## Toggle parameters

Two rule groups are disabled by default and toggled by a single parameter:
Every auto-loaded set is toggled by a `symplify.*` parameter defined in
`config/phpstan-extensions.neon`, registered via `conditionalTags`. `naming`,
`complexity`, `configurable`, `static`, `symfony`, `symfonyConfig`, `doctrine` and
`phpunit` default to `true`.

Two rule groups are disabled by default:

- `mocks: true` — enables the PHPUnit mock rules in `config/mock-rules.neon`
(registered via `conditionalTags` keyed on `%mocks%`).
Expand Down
48 changes: 17 additions & 31 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,41 +30,27 @@ Configuration should be added to your `phpstan.neon` file.

<br>

Once you have most rules applied, it's best practice to include whole sets:

```yaml
includes:
- vendor/symplify/phpstan-rules/config/code-complexity-rules.neon
- vendor/symplify/phpstan-rules/config/configurable-rules.neon
- vendor/symplify/phpstan-rules/config/static-rules.neon

# project specific
- vendor/symplify/phpstan-rules/config/doctrine-rules.neon
- vendor/symplify/phpstan-rules/config/symfony-rules.neon

# special set for PHP configs
- vendor/symplify/phpstan-rules/config/symfony-config-rules.neon
```

<br>

The naming rules (class/constant naming and namespace rules, e.g. interface must live in a "Contract" namespace) load automatically and are **enabled by default**. Turn them off with the `naming` parameter:
All rule sets load automatically via `phpstan/extension-installer` - no `includes:` needed. Most are **enabled by default**, toggle them in `phpstan.neon`:

```yaml
parameters:
symplify:
naming: false
```

The `code-complexity-rules.neon`, `configurable-rules.neon` and `static-rules.neon` sets are **enabled by default** once included, and can be toggled the same way:

```yaml
parameters:
symplify:
complexity: false
configurable: false
static: false
```
# enabled by default
naming: true
complexity: true
configurable: true
static: true
symfony: true
symfonyConfig: true
doctrine: true
phpunit: true

# disabled by default
mocks: false
ctor: false
```

*Note: If you included the sets manually before, remove those `includes:` lines - PHPStan reports duplicated includes.*

<br>

Expand Down
7 changes: 7 additions & 0 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,13 @@
"includes": [
"config/services/services.neon",
"config/naming-rules.neon",
"config/code-complexity-rules.neon",
"config/configurable-rules.neon",
"config/static-rules.neon",
"config/symfony-rules.neon",
"config/symfony-config-rules.neon",
"config/doctrine-rules.neon",
"config/phpunit-rules.neon",
"config/ctor-rules.neon",
"config/mock-rules.neon",
"config/phpstan-extensions.neon"
Expand Down
30 changes: 24 additions & 6 deletions config/doctrine-rules.neon
Original file line number Diff line number Diff line change
@@ -1,14 +1,32 @@
rules:
conditionalTags:
Symplify\PHPStanRules\Rules\Doctrine\NoGetRepositoryOutsideServiceRule:
phpstan.rules.rule: %symplify.doctrine%
Symplify\PHPStanRules\Rules\Doctrine\NoParentRepositoryRule:
phpstan.rules.rule: %symplify.doctrine%
Symplify\PHPStanRules\Rules\Doctrine\NoRepositoryCallInDataFixtureRule:
phpstan.rules.rule: %symplify.doctrine%
Symplify\PHPStanRules\Rules\Doctrine\NoGetRepositoryOnServiceRepositoryEntityRule:
phpstan.rules.rule: %symplify.doctrine%

Symplify\PHPStanRules\Rules\Doctrine\NoDoctrineListenerWithoutContractRule:
phpstan.rules.rule: %symplify.doctrine%

# test fixtures
Symplify\PHPStanRules\Rules\Doctrine\RequireQueryBuilderOnRepositoryRule:
phpstan.rules.rule: %symplify.doctrine%

# entity mapping
Symplify\PHPStanRules\Rules\Doctrine\NoStringTargetEntityRule:
phpstan.rules.rule: %symplify.doctrine%
Symplify\PHPStanRules\Rules\Doctrine\NoReadonlyEntityClassRule:
phpstan.rules.rule: %symplify.doctrine%

services:
- Symplify\PHPStanRules\Rules\Doctrine\NoGetRepositoryOutsideServiceRule
- Symplify\PHPStanRules\Rules\Doctrine\NoParentRepositoryRule
- Symplify\PHPStanRules\Rules\Doctrine\NoRepositoryCallInDataFixtureRule
- Symplify\PHPStanRules\Rules\Doctrine\NoGetRepositoryOnServiceRepositoryEntityRule

- Symplify\PHPStanRules\Rules\Doctrine\NoDoctrineListenerWithoutContractRule

# test fixtures
- Symplify\PHPStanRules\Rules\Doctrine\RequireQueryBuilderOnRepositoryRule

# entity mapping
- Symplify\PHPStanRules\Rules\Doctrine\NoStringTargetEntityRule
- Symplify\PHPStanRules\Rules\Doctrine\NoReadonlyEntityClassRule
8 changes: 8 additions & 0 deletions config/phpstan-extensions.neon
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@ parameters:
complexity: true
configurable: true
static: true
symfony: true
symfonyConfig: true
doctrine: true
phpunit: true
mocks: false
ctor: false
symfonyReturnType: false
Expand All @@ -21,6 +25,10 @@ parametersSchema:
complexity: bool()
configurable: bool()
static: bool()
symfony: bool()
symfonyConfig: bool()
doctrine: bool()
phpunit: bool()
mocks: bool()
ctor: bool()
symfonyReturnType: bool()
Expand Down
8 changes: 7 additions & 1 deletion config/phpunit-rules.neon
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
rules:
conditionalTags:
Symplify\PHPStanRules\Rules\PHPUnit\PublicStaticDataProviderRule:
phpstan.rules.rule: %symplify.phpunit%
Symplify\PHPStanRules\Rules\PHPUnit\NoAssertFuncCallInTestsRule:
phpstan.rules.rule: %symplify.phpunit%

services:
- Symplify\PHPStanRules\Rules\PHPUnit\PublicStaticDataProviderRule
- Symplify\PHPStanRules\Rules\PHPUnit\NoAssertFuncCallInTestsRule
68 changes: 48 additions & 20 deletions config/symfony-config-rules.neon
Original file line number Diff line number Diff line change
@@ -1,38 +1,66 @@
rules:
- Symplify\PHPStanRules\Rules\Symfony\ConfigClosure\ServicesExcludedDirectoryMustExistRule
- Symplify\PHPStanRules\Rules\Symfony\ConfigClosure\NoBundleResourceConfigRule
- Symplify\PHPStanRules\Rules\Symfony\ConfigClosure\AlreadyRegisteredAutodiscoveryServiceRule
- Symplify\PHPStanRules\Rules\Symfony\ConfigClosure\TaggedIteratorOverRepeatedServiceCallRule
conditionalTags:
Symplify\PHPStanRules\Rules\Symfony\ConfigClosure\ServicesExcludedDirectoryMustExistRule:
phpstan.rules.rule: %symplify.symfonyConfig%
Symplify\PHPStanRules\Rules\Symfony\ConfigClosure\NoBundleResourceConfigRule:
phpstan.rules.rule: %symplify.symfonyConfig%
Symplify\PHPStanRules\Rules\Symfony\ConfigClosure\AlreadyRegisteredAutodiscoveryServiceRule:
phpstan.rules.rule: %symplify.symfonyConfig%
Symplify\PHPStanRules\Rules\Symfony\ConfigClosure\TaggedIteratorOverRepeatedServiceCallRule:
phpstan.rules.rule: %symplify.symfonyConfig%

# sync file name and extension name
- Symplify\PHPStanRules\Rules\Symfony\ConfigClosure\FileNameMatchesExtensionRule
Symplify\PHPStanRules\Rules\Symfony\ConfigClosure\FileNameMatchesExtensionRule:
phpstan.rules.rule: %symplify.symfonyConfig%

# no autowire duplicate
- Symplify\PHPStanRules\Rules\Symfony\NoServiceAutowireDuplicateRule
Symplify\PHPStanRules\Rules\Symfony\NoServiceAutowireDuplicateRule:
phpstan.rules.rule: %symplify.symfonyConfig%

# args() and arg() call
- Symplify\PHPStanRules\Rules\Symfony\ConfigClosure\NoDuplicateArgsAutowireByTypeRule
- Symplify\PHPStanRules\Rules\Symfony\ConfigClosure\NoDuplicateArgAutowireByTypeRule
Symplify\PHPStanRules\Rules\Symfony\ConfigClosure\NoDuplicateArgsAutowireByTypeRule:
phpstan.rules.rule: %symplify.symfonyConfig%
Symplify\PHPStanRules\Rules\Symfony\ConfigClosure\NoDuplicateArgAutowireByTypeRule:
phpstan.rules.rule: %symplify.symfonyConfig%

# $services->set('X', 'X')
- Symplify\PHPStanRules\Rules\Symfony\ConfigClosure\NoServiceSameNameSetClassRule
Symplify\PHPStanRules\Rules\Symfony\ConfigClosure\NoServiceSameNameSetClassRule:
phpstan.rules.rule: %symplify.symfonyConfig%

# $services->set('X')->class('X')
- Symplify\PHPStanRules\Rules\Symfony\ConfigClosure\NoSetClassServiceDuplicationRule
Symplify\PHPStanRules\Rules\Symfony\ConfigClosure\NoSetClassServiceDuplicationRule:
phpstan.rules.rule: %symplify.symfonyConfig%

# service('id') where an alias points 'id' at a class
- Symplify\PHPStanRules\Rules\Symfony\ConfigClosure\PreferClassServiceReferenceRule
Symplify\PHPStanRules\Rules\Symfony\ConfigClosure\PreferClassServiceReferenceRule:
phpstan.rules.rule: %symplify.symfonyConfig%

# redundant ->tag() that autoconfigure() already adds
- Symplify\PHPStanRules\Rules\Symfony\ConfigClosure\NoAutoconfiguredServiceTagRule
Symplify\PHPStanRules\Rules\Symfony\ConfigClosure\NoAutoconfiguredServiceTagRule:
phpstan.rules.rule: %symplify.symfonyConfig%

# ->call('setX', [service()]) that should be a #[Required] setter
- Symplify\PHPStanRules\Rules\Symfony\ConfigClosure\NoServiceSetterCallRule
Symplify\PHPStanRules\Rules\Symfony\ConfigClosure\NoServiceSetterCallRule:
phpstan.rules.rule: %symplify.symfonyConfig%









services:
-
class: Symplify\PHPStanRules\Collector\ClassTargetServiceAliasCollector
tags: [phpstan.collector]
-
class: Symplify\PHPStanRules\Collector\ServiceStringReferenceCollector
tags: [phpstan.collector]
- Symplify\PHPStanRules\Rules\Symfony\ConfigClosure\ServicesExcludedDirectoryMustExistRule
- Symplify\PHPStanRules\Rules\Symfony\ConfigClosure\NoBundleResourceConfigRule
- Symplify\PHPStanRules\Rules\Symfony\ConfigClosure\AlreadyRegisteredAutodiscoveryServiceRule
- Symplify\PHPStanRules\Rules\Symfony\ConfigClosure\TaggedIteratorOverRepeatedServiceCallRule
- Symplify\PHPStanRules\Rules\Symfony\ConfigClosure\FileNameMatchesExtensionRule
- Symplify\PHPStanRules\Rules\Symfony\NoServiceAutowireDuplicateRule
- Symplify\PHPStanRules\Rules\Symfony\ConfigClosure\NoDuplicateArgsAutowireByTypeRule
- Symplify\PHPStanRules\Rules\Symfony\ConfigClosure\NoDuplicateArgAutowireByTypeRule
- Symplify\PHPStanRules\Rules\Symfony\ConfigClosure\NoServiceSameNameSetClassRule
- Symplify\PHPStanRules\Rules\Symfony\ConfigClosure\NoSetClassServiceDuplicationRule
- Symplify\PHPStanRules\Rules\Symfony\ConfigClosure\PreferClassServiceReferenceRule
- Symplify\PHPStanRules\Rules\Symfony\ConfigClosure\NoAutoconfiguredServiceTagRule
- Symplify\PHPStanRules\Rules\Symfony\ConfigClosure\NoServiceSetterCallRule
68 changes: 57 additions & 11 deletions config/symfony-rules.neon
Original file line number Diff line number Diff line change
@@ -1,33 +1,79 @@
rules:
conditionalTags:
Symplify\PHPStanRules\Rules\Symfony\SingleArgEventDispatchRule:
phpstan.rules.rule: %symplify.symfony%
Symplify\PHPStanRules\Rules\Symfony\NoListenerWithoutContractRule:
phpstan.rules.rule: %symplify.symfony%
Symplify\PHPStanRules\Rules\Symfony\NoStringInGetSubscribedEventsRule:
phpstan.rules.rule: %symplify.symfony%
Symplify\PHPStanRules\Rules\Symfony\FormTypeClassNameRule:
phpstan.rules.rule: %symplify.symfony%

# routing
Symplify\PHPStanRules\Rules\Symfony\NoRoutingPrefixRule:
phpstan.rules.rule: %symplify.symfony%
Symplify\PHPStanRules\Rules\Symfony\NoClassLevelRouteRule:
phpstan.rules.rule: %symplify.symfony%
Symplify\PHPStanRules\Rules\Symfony\NoRouteTrailingSlashPathRule:
phpstan.rules.rule: %symplify.symfony%
Symplify\PHPStanRules\Rules\Symfony\RequireRouteNameToGenerateControllerRouteRule:
phpstan.rules.rule: %symplify.symfony%

# dependency injection
Symplify\PHPStanRules\Rules\Symfony\NoServiceJugglingRule:
phpstan.rules.rule: %symplify.symfony%
Symplify\PHPStanRules\Rules\Symfony\PreferClassInDefinitionFetchRule:
phpstan.rules.rule: %symplify.symfony%
Symplify\PHPStanRules\Rules\Symfony\NoGetInControllerRule:
phpstan.rules.rule: %symplify.symfony%
Symplify\PHPStanRules\Rules\Symfony\NoGetInCommandRule:
phpstan.rules.rule: %symplify.symfony%
Symplify\PHPStanRules\Rules\Symfony\NoGetDoctrineInControllerRule:
phpstan.rules.rule: %symplify.symfony%
Symplify\PHPStanRules\Rules\Symfony\NoFindTaggedServiceIdsCallRule:
phpstan.rules.rule: %symplify.symfony%

# magic required inject
Symplify\PHPStanRules\Rules\Symfony\SingleRequiredMethodRule:
phpstan.rules.rule: %symplify.symfony%
Symplify\PHPStanRules\Rules\Symfony\NoConstructorAndRequiredTogetherRule:
phpstan.rules.rule: %symplify.symfony%

# attributes
Symplify\PHPStanRules\Rules\Symfony\RequireIsGrantedEnumRule:
phpstan.rules.rule: %symplify.symfony%
Symplify\PHPStanRules\Rules\Symfony\NoBareAndSecurityIsGrantedContentsRule:
phpstan.rules.rule: %symplify.symfony%
Symplify\PHPStanRules\Rules\Symfony\CommandMustHaveAsCommandAttributeRule:
phpstan.rules.rule: %symplify.symfony%
Symplify\PHPStanRules\Rules\Symfony\ConstraintMustHaveAttributeRule:
phpstan.rules.rule: %symplify.symfony%

# constructor injection
Symplify\PHPStanRules\Rules\Symfony\PreferInterfaceInConstructorRule:
phpstan.rules.rule: %symplify.symfony%
Symplify\PHPStanRules\Rules\Symfony\NoNullableServiceInConstructorRule:
phpstan.rules.rule: %symplify.symfony%

services:
- Symplify\PHPStanRules\Rules\Symfony\SingleArgEventDispatchRule
- Symplify\PHPStanRules\Rules\Symfony\NoListenerWithoutContractRule
- Symplify\PHPStanRules\Rules\Symfony\NoStringInGetSubscribedEventsRule
- Symplify\PHPStanRules\Rules\Symfony\FormTypeClassNameRule

# routing
- Symplify\PHPStanRules\Rules\Symfony\NoRoutingPrefixRule
- Symplify\PHPStanRules\Rules\Symfony\NoClassLevelRouteRule
- Symplify\PHPStanRules\Rules\Symfony\NoRouteTrailingSlashPathRule
- Symplify\PHPStanRules\Rules\Symfony\RequireRouteNameToGenerateControllerRouteRule

# dependency injection
- Symplify\PHPStanRules\Rules\Symfony\NoServiceJugglingRule
- Symplify\PHPStanRules\Rules\Symfony\PreferClassInDefinitionFetchRule
- Symplify\PHPStanRules\Rules\Symfony\NoGetInControllerRule
- Symplify\PHPStanRules\Rules\Symfony\NoGetInCommandRule
- Symplify\PHPStanRules\Rules\Symfony\NoGetDoctrineInControllerRule
- Symplify\PHPStanRules\Rules\Symfony\NoFindTaggedServiceIdsCallRule

# magic required inject
- Symplify\PHPStanRules\Rules\Symfony\SingleRequiredMethodRule
- Symplify\PHPStanRules\Rules\Symfony\NoConstructorAndRequiredTogetherRule

# attributes
- Symplify\PHPStanRules\Rules\Symfony\RequireIsGrantedEnumRule
- Symplify\PHPStanRules\Rules\Symfony\NoBareAndSecurityIsGrantedContentsRule
- Symplify\PHPStanRules\Rules\Symfony\CommandMustHaveAsCommandAttributeRule
- Symplify\PHPStanRules\Rules\Symfony\ConstraintMustHaveAttributeRule

# constructor injection
- Symplify\PHPStanRules\Rules\Symfony\PreferInterfaceInConstructorRule
- Symplify\PHPStanRules\Rules\Symfony\NoNullableServiceInConstructorRule
Loading