Skip to content

Commit cc9cb2f

Browse files
calebdwondrejmirtes
authored andcommitted
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 22 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. Amp-Thread-ID: https://ampcode.com/threads/T-01a19219-8e6c-730d-9b2f-3ac8a4a98fcc
1 parent 091c5a7 commit cc9cb2f

31 files changed

Lines changed: 125 additions & 140 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 & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -5,62 +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-
PHPStan\Rules\DeadCode\UnusedVariableRule:
22-
phpstan.rules.rule: %featureToggles.unusedVariable%
23-
PHPStan\Rules\Functions\UnusedFunctionParametersRule:
24-
phpstan.rules.rule: %featureToggles.unusedParameters%
25-
PHPStan\Rules\Methods\UnusedMethodParametersRule:
26-
phpstan.rules.rule: %featureToggles.unusedParameters%
27-
288
parameters:
299
checkAdvancedIsset: true
30-
31-
services:
32-
-
33-
class: PHPStan\Rules\Exceptions\TooWideFunctionThrowTypeRule
34-
35-
-
36-
class: PHPStan\Rules\Exceptions\TooWideMethodThrowTypeRule
37-
arguments:
38-
checkProtectedAndPublicMethods: %checkTooWideThrowTypesInProtectedAndPublicMethods%
39-
tooWideImplicitThrows: %exceptions.check.tooWideImplicitThrowType%
40-
41-
-
42-
class: PHPStan\Rules\Exceptions\TooWidePropertyHookThrowTypeRule
43-
arguments:
44-
checkProtectedAndPublicMethods: %checkTooWideThrowTypesInProtectedAndPublicMethods%
45-
46-
-
47-
class: PHPStan\Rules\Keywords\UnusedLabelRule
48-
49-
-
50-
class: PHPStan\Rules\Comparison\ImpossibleInArrayHaystackFiniteTypesRule
51-
arguments:
52-
treatPhpDocTypesAsCertain: %treatPhpDocTypesAsCertain%
53-
54-
-
55-
class: PHPStan\Rules\Comparison\SwitchConditionRule
56-
arguments:
57-
treatPhpDocTypesAsCertain: %treatPhpDocTypesAsCertain%
58-
59-
-
60-
class: PHPStan\Rules\DeadCode\UnusedVariableRule
61-
62-
-
63-
class: PHPStan\Rules\Functions\UnusedFunctionParametersRule
64-
65-
-
66-
class: PHPStan\Rules\Methods\UnusedMethodParametersRule

‎conf/config.level5.neon‎

Lines changed: 0 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -5,31 +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-
PHPStan\Rules\Functions\SortWithoutEffectRule:
16-
phpstan.rules.rule: %featureToggles.sortWithoutEffect%
17-
188
autowiredAttributeServices:
199
# registers rules with #[RegisteredRule] attribute
2010
level: 5
21-
22-
services:
23-
-
24-
class: PHPStan\Rules\Functions\ParameterCastableToNumberRule
25-
-
26-
class: PHPStan\Rules\Functions\PrintfParameterTypeRule
27-
arguments:
28-
checkStrictPrintfPlaceholderTypes: %checkStrictPrintfPlaceholderTypes%
29-
-
30-
class: PHPStan\Rules\DateIntervalInstantiationRule
31-
-
32-
class: PHPStan\Rules\Functions\SortWithoutEffectRule
33-
arguments:
34-
treatPhpDocTypesAsCertain: %treatPhpDocTypesAsCertain%
35-
treatPhpDocTypesAsCertainTip: %tips.treatPhpDocTypesAsCertain%

‎conf/config.neon‎

Lines changed: 0 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -271,36 +271,7 @@ parameters:
271271
autowiredAttributeServices:
272272
level: null
273273

274-
conditionalTags:
275-
PHPStan\Rules\Exceptions\MissingCheckedExceptionInFunctionThrowsRule:
276-
phpstan.rules.rule: %exceptions.check.missingCheckedExceptionInThrows%
277-
PHPStan\Rules\Exceptions\MissingCheckedExceptionInMethodThrowsRule:
278-
phpstan.rules.rule: %exceptions.check.missingCheckedExceptionInThrows%
279-
PHPStan\Rules\Exceptions\MissingCheckedExceptionInPropertyHookThrowsRule:
280-
phpstan.rules.rule: %exceptions.check.missingCheckedExceptionInThrows%
281-
PHPStan\Rules\Properties\UninitializedPropertyRule:
282-
phpstan.rules.rule: %checkUninitializedProperties%
283-
PHPStan\Rules\Exceptions\MethodThrowTypeCovarianceRule:
284-
phpstan.rules.rule: %exceptions.check.throwTypeCovariance%
285-
286274
services:
287-
-
288-
class: PHPStan\Rules\Exceptions\MissingCheckedExceptionInFunctionThrowsRule
289-
290-
-
291-
class: PHPStan\Rules\Exceptions\MissingCheckedExceptionInMethodThrowsRule
292-
293-
-
294-
class: PHPStan\Rules\Exceptions\MissingCheckedExceptionInPropertyHookThrowsRule
295-
296-
-
297-
class: PHPStan\Rules\Properties\UninitializedPropertyRule
298-
299-
-
300-
class: PHPStan\Rules\Exceptions\MethodThrowTypeCovarianceRule
301-
arguments:
302-
implicitThrows: %exceptions.implicitThrows%
303-
304275
# autowired services are now registered with the help of attributes
305276
# like #[PHPStan\DependencyInjection\AutowiredService] or #[PHPStan\DependencyInjection\GenerateFactory]
306277

‎src/DependencyInjection/AutowiredAttributeServicesExtension.php‎

Lines changed: 55 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,8 @@
2727
use function explode;
2828
use function implode;
2929
use function is_array;
30+
use function is_bool;
31+
use function preg_match;
3032
use function preg_split;
3133
use function sprintf;
3234
use function strcasecmp;
@@ -38,6 +40,9 @@
3840
final class AutowiredAttributeServicesExtension extends CompilerExtension
3941
{
4042

43+
/** @var list<array{ServiceDefinition, string, string}> */
44+
private array $conditionalTags = [];
45+
4146
#[Override]
4247
public function getConfigSchema(): Schema
4348
{
@@ -133,9 +138,9 @@ public function loadConfiguration(): void
133138

134139
$definition = $builder->addDefinition(null)
135140
->setFactory($class->name)
136-
->setAutowired($class->name)
137-
->addTag(LazyRegistry::RULE_TAG);
141+
->setAutowired($class->name);
138142

143+
$this->tag($definition, LazyRegistry::RULE_TAG, $attribute->enabledBy);
139144
self::processConstructorParameters($builder, $class->name, $definition, $constructorParameters);
140145
}
141146

@@ -147,13 +152,59 @@ public function loadConfiguration(): void
147152

148153
$definition = $builder->addDefinition(null)
149154
->setFactory($class->name)
150-
->setAutowired($class->name)
151-
->addTag(RegistryFactory::COLLECTOR_TAG);
155+
->setAutowired($class->name);
152156

157+
$this->tag($definition, RegistryFactory::COLLECTOR_TAG, $attribute->enabledBy);
153158
self::processConstructorParameters($builder, $class->name, $definition, $constructorParameters);
154159
}
155160
}
156161

162+
private function tag(ServiceDefinition $definition, string $tag, ?string $enabledBy): void
163+
{
164+
if ($enabledBy === null) {
165+
$definition->addTag($tag);
166+
167+
return;
168+
}
169+
170+
$this->conditionalTags[] = [$definition, $tag, $enabledBy];
171+
}
172+
173+
#[Override]
174+
public function beforeCompile(): void
175+
{
176+
$builder = $this->getContainerBuilder();
177+
foreach ($this->conditionalTags as [$definition, $tag, $enabledBy]) {
178+
if (!self::resolveCondition($builder, $enabledBy)) {
179+
continue;
180+
}
181+
182+
$definition->addTag($tag);
183+
}
184+
}
185+
186+
private static function resolveCondition(ContainerBuilder $builder, string $enabledBy): bool
187+
{
188+
if (preg_match('#^%([\w.-]+)%$#D', $enabledBy, $matches) !== 1) {
189+
throw new ShouldNotHappenException(sprintf('enabledBy must be a parameter reference, %s given.', $enabledBy));
190+
}
191+
192+
$parameter = $builder->parameters;
193+
foreach (explode('.', $matches[1]) as $key) {
194+
if (!is_array($parameter) || !array_key_exists($key, $parameter)) {
195+
throw new ShouldNotHappenException(sprintf("Missing parameter '%s'.", $matches[1]));
196+
}
197+
198+
$parameter = $parameter[$key];
199+
}
200+
201+
if (!is_bool($parameter)) {
202+
throw new ShouldNotHappenException(sprintf('Parameter %s referenced by enabledBy must be bool.', $enabledBy));
203+
}
204+
205+
return $parameter;
206+
}
207+
157208
/**
158209
* @param class-string $className
159210
* @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,14 +7,18 @@
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
*/
1317
#[Attribute(flags: Attribute::TARGET_CLASS)]
1418
final class RegisteredCollector
1519
{
1620

17-
public function __construct(public int $level)
21+
public function __construct(public int $level, public ?string $enabledBy = null)
1822
{
1923
}
2024

‎src/DependencyInjection/RegisteredRule.php‎

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,14 +7,18 @@
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
*/
1317
#[Attribute(flags: Attribute::TARGET_CLASS)]
1418
final class RegisteredRule
1519
{
1620

17-
public function __construct(public int $level)
21+
public function __construct(public int $level, public ?string $enabledBy = null)
1822
{
1923
}
2024

‎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)