Skip to content

Commit e98d9d0

Browse files
committed
Register conditionally enabled rules with attributes
Adds the optional enabledBy parameter to RegisteredRule and RegisteredCollector. It accepts a boolean parameter reference and only adds the rule or collector tag when that parameter is enabled. This moves PHPStan's 18 conditionally registered rules out of conditionalTags and service definitions and onto the rule classes. The five conditional restricted-usage extension tags remain in NEON because they are not rules or collectors.
1 parent 8b7d263 commit e98d9d0

31 files changed

Lines changed: 193 additions & 118 deletions

conf/config.level0.neon

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -12,13 +12,8 @@ conditionalTags:
1212
phpstan.restrictedClassNameUsageExtension: %featureToggles.internalTag%
1313
PHPStan\Rules\InternalTag\RestrictedInternalFunctionUsageExtension:
1414
phpstan.restrictedFunctionUsageExtension: %featureToggles.internalTag%
15-
PHPStan\Rules\Classes\NewStaticInAbstractClassStaticMethodRule:
16-
phpstan.rules.rule: %featureToggles.newStaticInAbstractClassStaticMethod%
1715

1816
services:
19-
-
20-
class: PHPStan\Rules\Classes\NewStaticInAbstractClassStaticMethodRule
21-
2217
-
2318
class: PHPStan\Rules\InternalTag\RestrictedInternalClassConstantUsageExtension
2419

conf/config.level1.neon

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,3 @@ parameters:
1010
autowiredAttributeServices:
1111
# registers rules with #[RegisteredRule] attribute
1212
level: 1
13-
14-
conditionalTags:
15-
PHPStan\Rules\Variables\AssignToByRefExprFromForeachRule:
16-
phpstan.rules.rule: %featureToggles.assignToByRefForeachExpr%
17-
18-
services:
19-
-
20-
class: PHPStan\Rules\Variables\AssignToByRefExprFromForeachRule

conf/config.level2.neon

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -15,20 +15,10 @@ conditionalTags:
1515
phpstan.restrictedPropertyUsageExtension: %featureToggles.internalTag%
1616
PHPStan\Rules\InternalTag\RestrictedInternalMethodUsageExtension:
1717
phpstan.restrictedMethodUsageExtension: %featureToggles.internalTag%
18-
PHPStan\Rules\Constants\ValueAssignedToDefineRule:
19-
phpstan.rules.rule: %featureToggles.checkDynamicConstantNameValues%
20-
PHPStan\Rules\Constants\ValueAssignedToGlobalConstantRule:
21-
phpstan.rules.rule: %featureToggles.checkDynamicConstantNameValues%
2218

2319
services:
2420
-
2521
class: PHPStan\Rules\InternalTag\RestrictedInternalPropertyUsageExtension
2622

2723
-
2824
class: PHPStan\Rules\InternalTag\RestrictedInternalMethodUsageExtension
29-
30-
-
31-
class: PHPStan\Rules\Constants\ValueAssignedToDefineRule
32-
33-
-
34-
class: PHPStan\Rules\Constants\ValueAssignedToGlobalConstantRule

conf/config.level4.neon

Lines changed: 0 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -5,47 +5,5 @@ autowiredAttributeServices:
55
# registers rules with #[RegisteredRule] and #[RegisteredCollector] attributes
66
level: 4
77

8-
conditionalTags:
9-
PHPStan\Rules\Exceptions\TooWideFunctionThrowTypeRule:
10-
phpstan.rules.rule: %exceptions.check.tooWideThrowType%
11-
PHPStan\Rules\Exceptions\TooWideMethodThrowTypeRule:
12-
phpstan.rules.rule: %exceptions.check.tooWideThrowType%
13-
PHPStan\Rules\Exceptions\TooWidePropertyHookThrowTypeRule:
14-
phpstan.rules.rule: %exceptions.check.tooWideThrowType%
15-
PHPStan\Rules\Keywords\UnusedLabelRule:
16-
phpstan.rules.rule: %featureToggles.unusedLabel%
17-
PHPStan\Rules\Comparison\ImpossibleInArrayHaystackFiniteTypesRule:
18-
phpstan.rules.rule: %featureToggles.finiteTypesInHaystack%
19-
PHPStan\Rules\Comparison\SwitchConditionRule:
20-
phpstan.rules.rule: %featureToggles.switchConditionAlwaysFalse%
21-
228
parameters:
239
checkAdvancedIsset: true
24-
25-
services:
26-
-
27-
class: PHPStan\Rules\Exceptions\TooWideFunctionThrowTypeRule
28-
29-
-
30-
class: PHPStan\Rules\Exceptions\TooWideMethodThrowTypeRule
31-
arguments:
32-
checkProtectedAndPublicMethods: %checkTooWideThrowTypesInProtectedAndPublicMethods%
33-
tooWideImplicitThrows: %exceptions.check.tooWideImplicitThrowType%
34-
35-
-
36-
class: PHPStan\Rules\Exceptions\TooWidePropertyHookThrowTypeRule
37-
arguments:
38-
checkProtectedAndPublicMethods: %checkTooWideThrowTypesInProtectedAndPublicMethods%
39-
40-
-
41-
class: PHPStan\Rules\Keywords\UnusedLabelRule
42-
43-
-
44-
class: PHPStan\Rules\Comparison\ImpossibleInArrayHaystackFiniteTypesRule
45-
arguments:
46-
treatPhpDocTypesAsCertain: %treatPhpDocTypesAsCertain%
47-
48-
-
49-
class: PHPStan\Rules\Comparison\SwitchConditionRule
50-
arguments:
51-
treatPhpDocTypesAsCertain: %treatPhpDocTypesAsCertain%

conf/config.level5.neon

Lines changed: 0 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -5,24 +5,6 @@ parameters:
55
checkFunctionArgumentTypes: true
66
checkArgumentsPassedByReference: true
77

8-
conditionalTags:
9-
PHPStan\Rules\Functions\ParameterCastableToNumberRule:
10-
phpstan.rules.rule: %featureToggles.checkParameterCastableToNumberFunctions%
11-
PHPStan\Rules\Functions\PrintfParameterTypeRule:
12-
phpstan.rules.rule: %featureToggles.checkPrintfParameterTypes%
13-
PHPStan\Rules\DateIntervalInstantiationRule:
14-
phpstan.rules.rule: %featureToggles.checkDateIntervalConstructor%
15-
168
autowiredAttributeServices:
179
# registers rules with #[RegisteredRule] attribute
1810
level: 5
19-
20-
services:
21-
-
22-
class: PHPStan\Rules\Functions\ParameterCastableToNumberRule
23-
-
24-
class: PHPStan\Rules\Functions\PrintfParameterTypeRule
25-
arguments:
26-
checkStrictPrintfPlaceholderTypes: %checkStrictPrintfPlaceholderTypes%
27-
-
28-
class: PHPStan\Rules\DateIntervalInstantiationRule

conf/config.neon

Lines changed: 0 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -260,36 +260,7 @@ parameters:
260260
autowiredAttributeServices:
261261
level: null
262262

263-
conditionalTags:
264-
PHPStan\Rules\Exceptions\MissingCheckedExceptionInFunctionThrowsRule:
265-
phpstan.rules.rule: %exceptions.check.missingCheckedExceptionInThrows%
266-
PHPStan\Rules\Exceptions\MissingCheckedExceptionInMethodThrowsRule:
267-
phpstan.rules.rule: %exceptions.check.missingCheckedExceptionInThrows%
268-
PHPStan\Rules\Exceptions\MissingCheckedExceptionInPropertyHookThrowsRule:
269-
phpstan.rules.rule: %exceptions.check.missingCheckedExceptionInThrows%
270-
PHPStan\Rules\Properties\UninitializedPropertyRule:
271-
phpstan.rules.rule: %checkUninitializedProperties%
272-
PHPStan\Rules\Exceptions\MethodThrowTypeCovarianceRule:
273-
phpstan.rules.rule: %exceptions.check.throwTypeCovariance%
274-
275263
services:
276-
-
277-
class: PHPStan\Rules\Exceptions\MissingCheckedExceptionInFunctionThrowsRule
278-
279-
-
280-
class: PHPStan\Rules\Exceptions\MissingCheckedExceptionInMethodThrowsRule
281-
282-
-
283-
class: PHPStan\Rules\Exceptions\MissingCheckedExceptionInPropertyHookThrowsRule
284-
285-
-
286-
class: PHPStan\Rules\Properties\UninitializedPropertyRule
287-
288-
-
289-
class: PHPStan\Rules\Exceptions\MethodThrowTypeCovarianceRule
290-
arguments:
291-
implicitThrows: %exceptions.implicitThrows%
292-
293264
# autowired services are now registered with the help of attributes
294265
# like #[PHPStan\DependencyInjection\AutowiredService] or #[PHPStan\DependencyInjection\GenerateFactory]
295266

src/DependencyInjection/AutowiredAttributeServicesExtension.php

Lines changed: 55 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,8 @@
2828
use function explode;
2929
use function implode;
3030
use function is_array;
31+
use function is_bool;
32+
use function preg_match;
3133
use function preg_split;
3234
use function sprintf;
3335
use function strcasecmp;
@@ -39,6 +41,9 @@
3941
final class AutowiredAttributeServicesExtension extends CompilerExtension
4042
{
4143

44+
/** @var list<array{ServiceDefinition, string, string}> */
45+
private array $conditionalTags = [];
46+
4247
#[Override]
4348
public function getConfigSchema(): Schema
4449
{
@@ -140,9 +145,9 @@ public function loadConfiguration(): void
140145

141146
$definition = $builder->addDefinition(null)
142147
->setFactory($class->name)
143-
->setAutowired($class->name)
144-
->addTag(LazyRegistry::RULE_TAG);
148+
->setAutowired($class->name);
145149

150+
$this->tag($definition, LazyRegistry::RULE_TAG, $attribute->enabledBy);
146151
self::processConstructorParameters($builder, $class->name, $definition, $constructorParameters);
147152
}
148153

@@ -154,13 +159,59 @@ public function loadConfiguration(): void
154159

155160
$definition = $builder->addDefinition(null)
156161
->setFactory($class->name)
157-
->setAutowired($class->name)
158-
->addTag(RegistryFactory::COLLECTOR_TAG);
162+
->setAutowired($class->name);
159163

164+
$this->tag($definition, RegistryFactory::COLLECTOR_TAG, $attribute->enabledBy);
160165
self::processConstructorParameters($builder, $class->name, $definition, $constructorParameters);
161166
}
162167
}
163168

169+
private function tag(ServiceDefinition $definition, string $tag, ?string $enabledBy): void
170+
{
171+
if ($enabledBy === null) {
172+
$definition->addTag($tag);
173+
174+
return;
175+
}
176+
177+
$this->conditionalTags[] = [$definition, $tag, $enabledBy];
178+
}
179+
180+
#[Override]
181+
public function beforeCompile(): void
182+
{
183+
$builder = $this->getContainerBuilder();
184+
foreach ($this->conditionalTags as [$definition, $tag, $enabledBy]) {
185+
if (!self::resolveCondition($builder, $enabledBy)) {
186+
continue;
187+
}
188+
189+
$definition->addTag($tag);
190+
}
191+
}
192+
193+
private static function resolveCondition(ContainerBuilder $builder, string $enabledBy): bool
194+
{
195+
if (preg_match('#^%([\w.-]+)%$#D', $enabledBy, $matches) !== 1) {
196+
throw new ShouldNotHappenException(sprintf('enabledBy must be a parameter reference, %s given.', $enabledBy));
197+
}
198+
199+
$parameter = $builder->parameters;
200+
foreach (explode('.', $matches[1]) as $key) {
201+
if (!is_array($parameter) || !array_key_exists($key, $parameter)) {
202+
throw new ShouldNotHappenException(sprintf("Missing parameter '%s'.", $matches[1]));
203+
}
204+
205+
$parameter = $parameter[$key];
206+
}
207+
208+
if (!is_bool($parameter)) {
209+
throw new ShouldNotHappenException(sprintf('Parameter %s referenced by enabledBy must be bool.', $enabledBy));
210+
}
211+
212+
return $parameter;
213+
}
214+
164215
/**
165216
* @param class-string $className
166217
* @param array<lowercase-string, non-empty-list<TargetMethodParameter<AutowiredParameter>>> $constructorParameters

src/DependencyInjection/RegisteredCollector.php

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,10 @@
77
/**
88
* Registers a collector in the DI container on the set rule level.
99
*
10+
* Pass enabledBy a `%parameter%` reference to make the registration depend on
11+
* configuration, the way a `conditionalTags` entry would. The collector is only
12+
* tagged when the parameter is truthy.
13+
*
1014
* Works thanks to https://github.com/ondrejmirtes/composer-attribute-collector
1115
* and AutowiredAttributeServicesExtension.
1216
*
@@ -19,7 +23,7 @@
1923
final class RegisteredCollector
2024
{
2125

22-
public function __construct(public int $level)
26+
public function __construct(public int $level, public ?string $enabledBy = null)
2327
{
2428
}
2529

src/DependencyInjection/RegisteredRule.php

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,10 @@
77
/**
88
* Registers a rule in the DI container on the set rule level.
99
*
10+
* Pass enabledBy a `%parameter%` reference to make the registration depend on
11+
* configuration, the way a `conditionalTags` entry would. The rule is only
12+
* tagged when the parameter is truthy.
13+
*
1014
* Works thanks to https://github.com/ondrejmirtes/composer-attribute-collector
1115
* and AutowiredAttributeServicesExtension.
1216
*
@@ -19,7 +23,7 @@
1923
final class RegisteredRule
2024
{
2125

22-
public function __construct(public int $level)
26+
public function __construct(public int $level, public ?string $enabledBy = null)
2327
{
2428
}
2529

src/Rules/Classes/NewStaticInAbstractClassStaticMethodRule.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
use PhpParser\Node;
66
use PHPStan\Analyser\Scope;
7+
use PHPStan\DependencyInjection\RegisteredRule;
78
use PHPStan\Reflection\Php\PhpMethodFromParserNodeReflection;
89
use PHPStan\Rules\Rule;
910
use PHPStan\Rules\RuleErrorBuilder;
@@ -13,6 +14,7 @@
1314
/**
1415
* @implements Rule<Node\Expr\New_>
1516
*/
17+
#[RegisteredRule(level: 0, enabledBy: '%featureToggles.newStaticInAbstractClassStaticMethod%')]
1618
final class NewStaticInAbstractClassStaticMethodRule implements Rule
1719
{
1820

0 commit comments

Comments
 (0)