Skip to content

Commit 7ef1d4b

Browse files
authored
[config] Add FromServicePublicToDefaultsPublicRector (#857)
1 parent fd27daf commit 7ef1d4b

File tree

11 files changed

+254
-52
lines changed

11 files changed

+254
-52
lines changed

composer.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
"rector/type-perfect": "^2.1",
1919
"symfony/config": "^6.4",
2020
"symfony/dependency-injection": "^6.4",
21-
"symfony/http-kernel": "^7.0",
21+
"symfony/http-kernel": "^7.3",
2222
"symfony/routing": "^6.4",
2323
"symfony/security-core": "^6.4",
2424
"symfony/security-http": "^6.4",

config/sets/symfony/configs.php

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
declare(strict_types=1);
44

55
use Rector\Config\RectorConfig;
6+
use Rector\Symfony\Configs\Rector\Closure\FromServicePublicToDefaultsPublicRector;
67
use Rector\Symfony\Configs\Rector\Closure\MergeServiceNameTypeRector;
78
use Rector\Symfony\Configs\Rector\Closure\RemoveConstructorAutowireServiceRector;
89
use Rector\Symfony\Configs\Rector\Closure\ServiceArgsToServiceNamedArgRector;
@@ -18,5 +19,7 @@
1819
ServiceSettersToSettersAutodiscoveryRector::class,
1920
ServiceTagsToDefaultsAutoconfigureRector::class,
2021
RemoveConstructorAutowireServiceRector::class,
22+
23+
FromServicePublicToDefaultsPublicRector::class,
2124
]);
2225
};

phpstan.neon

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -51,11 +51,11 @@ parameters:
5151
- identifier: argument.type
5252
- identifier: assign.propertyType
5353

54-
# avoid notice on run on php 8.3
55-
- identifier: typeCoverage.constantTypeCoverage
56-
5754
# false postitive
5855
-
5956
identifier: typePerfect.narrowPublicClassMethodParamType
60-
path: src/NodeAnalyzer/SymfonyPhpClosureDetector.php
57+
paths:
58+
- src/NodeAnalyzer/SymfonyPhpClosureDetector.php
59+
- rules/Configs/NodeDecorator/ServiceDefaultsCallClosureDecorator.php
60+
6161
- '#Parameter 1 should use "PHPStan\\BetterReflection\\Reflection\\Adapter\\ReflectionMethod" type as the only type passed to this method#'
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
<?php
2+
3+
namespace Rector\Symfony\Tests\Configs\Rector\Closure\ServiceTagsToDefaultsAutoconfigureRector\Fixture;
4+
5+
use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator;
6+
7+
return static function (ContainerConfigurator $containerConfigurator): void {
8+
$services = $containerConfigurator->services();
9+
10+
$services->set(SomeCommand::class)
11+
->public();
12+
13+
$services->set(AnotherCommand::class)
14+
->public();
15+
};
16+
17+
?>
18+
-----
19+
<?php
20+
21+
namespace Rector\Symfony\Tests\Configs\Rector\Closure\ServiceTagsToDefaultsAutoconfigureRector\Fixture;
22+
23+
use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator;
24+
25+
return static function (ContainerConfigurator $containerConfigurator): void {
26+
$services = $containerConfigurator->services();
27+
$services->defaults()->public();
28+
29+
$services->set(SomeCommand::class);
30+
31+
$services->set(AnotherCommand::class);
32+
};
33+
34+
?>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Rector\Symfony\Tests\Configs\Rector\Closure\FromServicePublicToDefaultsPublicRector;
6+
7+
use Iterator;
8+
use PHPUnit\Framework\Attributes\DataProvider;
9+
use Rector\Testing\PHPUnit\AbstractRectorTestCase;
10+
11+
final class FromServicePublicToDefaultsPublicRectorTest extends AbstractRectorTestCase
12+
{
13+
#[DataProvider('provideData')]
14+
public function test(string $filePath): void
15+
{
16+
$this->doTestFile($filePath);
17+
}
18+
19+
public static function provideData(): Iterator
20+
{
21+
return self::yieldFilesFromDirectory(__DIR__ . '/Fixture');
22+
}
23+
24+
public function provideConfigFilePath(): string
25+
{
26+
return __DIR__ . '/config/configured_rule.php';
27+
}
28+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
use Rector\Config\RectorConfig;
6+
use Rector\Symfony\Configs\Rector\Closure\FromServicePublicToDefaultsPublicRector;
7+
8+
return static function (RectorConfig $rectorConfig): void {
9+
$rectorConfig->rule(FromServicePublicToDefaultsPublicRector::class);
10+
};
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Rector\Symfony\Configs\NodeDecorator;
6+
7+
use PhpParser\Node\Expr\Assign;
8+
use PhpParser\Node\Expr\Closure;
9+
use PhpParser\Node\Expr\MethodCall;
10+
use PhpParser\Node\Expr\Variable;
11+
use PhpParser\Node\Stmt\Expression;
12+
13+
final class ServiceDefaultsCallClosureDecorator
14+
{
15+
public function decorate(Closure $closure, string $methodName): void
16+
{
17+
foreach ($closure->stmts as $key => $nodeStmt) {
18+
if (! $nodeStmt instanceof Expression) {
19+
continue;
20+
}
21+
22+
if (! $nodeStmt->expr instanceof Assign) {
23+
continue;
24+
}
25+
26+
$assign = $nodeStmt->expr;
27+
if (! $assign->var instanceof Variable) {
28+
continue;
29+
}
30+
31+
if ($assign->var->name !== 'services') {
32+
continue;
33+
}
34+
35+
$servicesVariable = $assign->var;
36+
37+
// add defaults here, right after assign :)
38+
$autoconfigureExpression = $this->createDefaultsAutoconfigureExpression($methodName);
39+
array_splice($closure->stmts, $key + 1, 0, [$autoconfigureExpression]);
40+
break;
41+
}
42+
}
43+
44+
public function createDefaultsAutoconfigureExpression(string $methodName): Expression
45+
{
46+
$defaultsMethodCall = new MethodCall(new Variable('services'), 'defaults');
47+
$autoconfigureMethodCall = new MethodCall($defaultsMethodCall, $methodName);
48+
49+
return new Expression($autoconfigureMethodCall);
50+
}
51+
}
Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Rector\Symfony\Configs\Rector\Closure;
6+
7+
use PhpParser\Node;
8+
use PhpParser\Node\Expr;
9+
use PhpParser\Node\Expr\Closure;
10+
use PhpParser\Node\Expr\MethodCall;
11+
use Rector\Rector\AbstractRector;
12+
use Rector\Symfony\Configs\NodeDecorator\ServiceDefaultsCallClosureDecorator;
13+
use Rector\Symfony\NodeAnalyzer\SymfonyPhpClosureDetector;
14+
use Symplify\RuleDocGenerator\ValueObject\CodeSample\CodeSample;
15+
use Symplify\RuleDocGenerator\ValueObject\RuleDefinition;
16+
17+
/**
18+
* @see \Rector\Symfony\Tests\Configs\Rector\Closure\FromServicePublicToDefaultsPublicRector\FromServicePublicToDefaultsPublicRectorTest
19+
*/
20+
final class FromServicePublicToDefaultsPublicRector extends AbstractRector
21+
{
22+
public function __construct(
23+
private readonly SymfonyPhpClosureDetector $symfonyPhpClosureDetector,
24+
private readonly ServiceDefaultsCallClosureDecorator $serviceDefaultsCallClosureDecorator
25+
) {
26+
}
27+
28+
public function getRuleDefinition(): RuleDefinition
29+
{
30+
return new RuleDefinition(
31+
'Instead of per service public() call, use it once in defaults()',
32+
[
33+
new CodeSample(
34+
<<<'CODE_SAMPLE'
35+
use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator;
36+
37+
return static function (ContainerConfigurator $containerConfigurator): void {
38+
$services = $containerConfigurator->services();
39+
40+
$services->set(SomeCommand::class)
41+
->public();
42+
43+
$services->set(AnotherCommand::class)
44+
->public();
45+
46+
$services->set(NextCommand::class)
47+
->public();
48+
};
49+
CODE_SAMPLE
50+
51+
,
52+
<<<'CODE_SAMPLE'
53+
use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator;
54+
55+
return static function (ContainerConfigurator $containerConfigurator): void {
56+
$services = $containerConfigurator->services();
57+
58+
$services->defaults()->public();
59+
60+
$services->set(SomeCommand::class);
61+
$services->set(AnotherCommand::class);
62+
$services->set(NextCommand::class);
63+
};
64+
CODE_SAMPLE
65+
),
66+
67+
]
68+
);
69+
}
70+
71+
/**
72+
* @return array<class-string<Node>>
73+
*/
74+
public function getNodeTypes(): array
75+
{
76+
return [Closure::class];
77+
}
78+
79+
/**
80+
* @param Closure $node
81+
*/
82+
public function refactor(Node $node): ?Node
83+
{
84+
if (! $this->symfonyPhpClosureDetector->detect($node)) {
85+
return null;
86+
}
87+
88+
$hasDefaultsPublic = $this->symfonyPhpClosureDetector->hasDefaultsConfigured($node, 'public');
89+
90+
$hasChanged = false;
91+
92+
$this->traverseNodesWithCallable($node->stmts, function (Node $node) use (&$hasChanged): ?Expr {
93+
if (! $node instanceof MethodCall) {
94+
return null;
95+
}
96+
97+
if (! $this->isName($node->name, 'public') && $node->getArgs() === []) {
98+
return null;
99+
}
100+
101+
$hasChanged = true;
102+
103+
return $node->var;
104+
});
105+
106+
if ($hasChanged === false) {
107+
return null;
108+
}
109+
110+
if ($hasDefaultsPublic === false) {
111+
$this->serviceDefaultsCallClosureDecorator->decorate($node, 'public');
112+
}
113+
114+
return $node;
115+
}
116+
}

rules/Configs/Rector/Closure/MergeServiceNameTypeRector.php

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
use PHPStan\Type\ObjectType;
1111
use Rector\PhpParser\Node\Value\ValueResolver;
1212
use Rector\Rector\AbstractRector;
13+
use Rector\Symfony\Enum\SymfonyClass;
1314
use Rector\Symfony\NodeAnalyzer\SymfonyPhpClosureDetector;
1415
use Symplify\RuleDocGenerator\ValueObject\CodeSample\CodeSample;
1516
use Symplify\RuleDocGenerator\ValueObject\RuleDefinition;
@@ -131,9 +132,6 @@ private function isSetServices(MethodCall $methodCall): bool
131132
return false;
132133
}
133134

134-
return $this->isObjectType(
135-
$methodCall->var,
136-
new ObjectType('Symfony\Component\DependencyInjection\Loader\Configurator\ServicesConfigurator')
137-
);
135+
return $this->isObjectType($methodCall->var, new ObjectType(SymfonyClass::SERVICES_CONFIGURATOR));
138136
}
139137
}

rules/Configs/Rector/Closure/ServiceSettersToSettersAutodiscoveryRector.php

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
use Rector\Exception\ShouldNotHappenException;
2222
use Rector\PhpParser\Node\Value\ValueResolver;
2323
use Rector\Rector\AbstractRector;
24+
use Rector\Symfony\Enum\SymfonyClass;
2425
use Rector\Symfony\MinimalSharedStringSolver;
2526
use Rector\Symfony\NodeAnalyzer\SymfonyPhpClosureDetector;
2627
use Rector\Symfony\ValueObject\ClassNameAndFilePath;
@@ -130,10 +131,7 @@ public function isBareServicesSetMethodCall(MethodCall $methodCall): bool
130131
return false;
131132
}
132133

133-
if (! $this->isObjectType(
134-
$methodCall->var,
135-
new ObjectType('Symfony\Component\DependencyInjection\Loader\Configurator\ServicesConfigurator')
136-
)) {
134+
if (! $this->isObjectType($methodCall->var, new ObjectType(SymfonyClass::SERVICES_CONFIGURATOR))) {
137135
return false;
138136
}
139137

0 commit comments

Comments
 (0)