Skip to content

Commit d6da062

Browse files
authored
Adds ReplaceServiceContainerCallArgRector (#189)
* Adds ReplaceServiceContainerCallArgRector * CS Fix
1 parent bf0cd91 commit d6da062

File tree

9 files changed

+340
-1
lines changed

9 files changed

+340
-1
lines changed

docs/rector_rules_overview.md

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# 56 Rules Overview
1+
# 57 Rules Overview
22

33
## AddArgumentDefaultValueRector
44

@@ -1015,6 +1015,23 @@ Replace `$this->faker` with the `fake()` helper function in Factories
10151015

10161016
<br>
10171017

1018+
## ReplaceServiceContainerCallArgRector
1019+
1020+
Changes the string or class const used for a service container make call
1021+
1022+
:wrench: **configure it!**
1023+
1024+
- class: [`RectorLaravel\Rector\MethodCall\ReplaceServiceContainerCallArgRector`](../src/Rector/MethodCall/ReplaceServiceContainerCallArgRector.php)
1025+
1026+
```diff
1027+
-app('encrypter')->encrypt('...');
1028+
-\Illuminate\Support\Facades\Application::make('encrypter')->encrypt('...');
1029+
+app(Illuminate\Contracts\Encryption\Encrypter::class)->encrypt('...');
1030+
+\Illuminate\Support\Facades\Application::make(Illuminate\Contracts\Encryption\Encrypter::class)->encrypt('...');
1031+
```
1032+
1033+
<br>
1034+
10181035
## ReplaceWithoutJobsEventsAndNotificationsWithFacadeFakeRector
10191036

10201037
Replace `withoutJobs`, `withoutEvents` and `withoutNotifications` with Facade `fake`
Lines changed: 156 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,156 @@
1+
<?php
2+
3+
namespace RectorLaravel\Rector\MethodCall;
4+
5+
use PhpParser\Node;
6+
use PhpParser\Node\Arg;
7+
use PhpParser\Node\Expr;
8+
use PhpParser\Node\Expr\ClassConstFetch;
9+
use PhpParser\Node\Expr\FuncCall;
10+
use PhpParser\Node\Expr\MethodCall;
11+
use PhpParser\Node\Expr\StaticCall;
12+
use PhpParser\Node\Identifier;
13+
use PhpParser\Node\Name;
14+
use PhpParser\Node\Scalar\String_;
15+
use PHPStan\Type\ObjectType;
16+
use Rector\Contract\Rector\ConfigurableRectorInterface;
17+
use Rector\Rector\AbstractRector;
18+
use RectorLaravel\ValueObject\ReplaceServiceContainerCallArg;
19+
use Symplify\RuleDocGenerator\ValueObject\CodeSample\ConfiguredCodeSample;
20+
use Symplify\RuleDocGenerator\ValueObject\RuleDefinition;
21+
use Webmozart\Assert\Assert;
22+
23+
/**
24+
* @see \RectorLaravel\Tests\Rector\MethodCall\ReplaceServiceContainerCallArgRector\ReplaceServiceContainerCallArgRectorTest
25+
*/
26+
class ReplaceServiceContainerCallArgRector extends AbstractRector implements ConfigurableRectorInterface
27+
{
28+
/**
29+
* @var ReplaceServiceContainerCallArg[]
30+
*/
31+
private array $replaceServiceContainerCallArgs = [];
32+
33+
public function getRuleDefinition(): RuleDefinition
34+
{
35+
return new RuleDefinition('Changes the string or class const used for a service container make call',
36+
[new ConfiguredCodeSample(
37+
<<<'CODE_SAMPLE'
38+
app('encrypter')->encrypt('...');
39+
\Illuminate\Support\Facades\Application::make('encrypter')->encrypt('...');
40+
CODE_SAMPLE,
41+
<<<'CODE_SAMPLE'
42+
app(Illuminate\Contracts\Encryption\Encrypter::class)->encrypt('...');
43+
\Illuminate\Support\Facades\Application::make(Illuminate\Contracts\Encryption\Encrypter::class)->encrypt('...');
44+
CODE_SAMPLE,
45+
[
46+
new ReplaceServiceContainerCallArg(
47+
'encrypter',
48+
new ClassConstFetch(
49+
new Name('Illuminate\Contracts\Encryption\Encrypter'),
50+
'class'
51+
),
52+
),
53+
]
54+
)]
55+
);
56+
}
57+
58+
public function getNodeTypes(): array
59+
{
60+
return [MethodCall::class, StaticCall::class, FuncCall::class];
61+
}
62+
63+
/**
64+
* @param MethodCall|StaticCall|FuncCall $node
65+
*/
66+
public function refactor(Node $node): MethodCall|StaticCall|FuncCall|null
67+
{
68+
if (! $this->validMethodCall($node) &&
69+
! $this->validFuncCall($node)) {
70+
return null;
71+
}
72+
73+
if ($node->args === [] || ! $node->args[0] instanceof Arg) {
74+
return null;
75+
}
76+
77+
$hasChanged = false;
78+
79+
foreach ($this->replaceServiceContainerCallArgs as $replaceServiceContainerCallArg) {
80+
if ($this->isMatchForChangeServiceContainerCallArgValue($node->args[0], $replaceServiceContainerCallArg->getOldService())) {
81+
$this->replaceCallArgValue($node->args[0], $replaceServiceContainerCallArg->getNewService());
82+
$hasChanged = true;
83+
}
84+
}
85+
86+
return $hasChanged ? $node : null;
87+
}
88+
89+
public function configure(array $configuration): void
90+
{
91+
Assert::allIsInstanceOf($configuration, ReplaceServiceContainerCallArg::class);
92+
93+
$this->replaceServiceContainerCallArgs = $configuration;
94+
}
95+
96+
private function isMatchForChangeServiceContainerCallArgValue(Arg $arg, ClassConstFetch|string $oldService): bool
97+
{
98+
if ($arg->value instanceof ClassConstFetch && $oldService instanceof ClassConstFetch) {
99+
if ($arg->value->class instanceof Expr || $oldService->class instanceof Expr) {
100+
return false;
101+
}
102+
103+
return $arg->value->class->toString() === $oldService->class->toString();
104+
} elseif ($arg->value instanceof String_) {
105+
return $arg->value->value === $oldService;
106+
}
107+
108+
return false;
109+
}
110+
111+
private function replaceCallArgValue(Arg $arg, ClassConstFetch|string $newService): void
112+
{
113+
if ($newService instanceof ClassConstFetch) {
114+
$arg->value = $newService;
115+
116+
return;
117+
}
118+
119+
$arg->value = new String_($newService);
120+
}
121+
122+
private function validMethodCall(StaticCall|MethodCall|FuncCall $node): bool
123+
{
124+
if (! $node instanceof MethodCall && ! $node instanceof StaticCall) {
125+
return false;
126+
}
127+
128+
if (! $node->name instanceof Identifier) {
129+
return false;
130+
}
131+
132+
if (! $this->isNames($node->name, ['make', 'get'])) {
133+
return false;
134+
}
135+
136+
[$callObject, $class] = match (true) {
137+
$node instanceof MethodCall => [$node->var, 'Illuminate\Contracts\Container\Container'],
138+
$node instanceof StaticCall => [$node->class, 'Illuminate\Support\Facades\Application'],
139+
};
140+
141+
return $this->isObjectType($callObject, new ObjectType($class));
142+
}
143+
144+
private function validFuncCall(StaticCall|MethodCall|FuncCall $node): bool
145+
{
146+
if (! $node instanceof FuncCall) {
147+
return false;
148+
}
149+
150+
if (! $node->name instanceof Name) {
151+
return false;
152+
}
153+
154+
return $this->isName($node->name, 'app');
155+
}
156+
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
<?php
2+
3+
namespace RectorLaravel\ValueObject;
4+
5+
use PhpParser\Node\Expr\ClassConstFetch;
6+
7+
final readonly class ReplaceServiceContainerCallArg
8+
{
9+
public function __construct(
10+
private string|ClassConstFetch $oldService,
11+
private string|ClassConstFetch $newService
12+
) {
13+
}
14+
15+
public function getOldService(): string|ClassConstFetch
16+
{
17+
return $this->oldService;
18+
}
19+
20+
public function getNewService(): string|ClassConstFetch
21+
{
22+
return $this->newService;
23+
}
24+
}
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
<?php
2+
3+
namespace RectorLaravel\Tests\Rector\MethodCall\ReplaceServiceContainerCallArgRector\Fixture;
4+
5+
function foo(\Illuminate\Contracts\Container\Container $app) {
6+
$app->make('encrypter')->encrypt('hello world');
7+
}
8+
9+
\Illuminate\Support\Facades\Application::make('encrypter')->encrypt('hello world');
10+
11+
app('encrypter')->encrypt('hello world');
12+
13+
function foo(\Illuminate\Contracts\Container\Container $app) {
14+
$app->make(\Illuminate\Contracts\Session\Session::class)->get('hello world');
15+
}
16+
17+
\Illuminate\Support\Facades\Application::make(\Illuminate\Contracts\Session\Session::class)->get('hello world');
18+
19+
app(\Illuminate\Contracts\Session\Session::class)->get('hello world');
20+
21+
?>
22+
-----
23+
<?php
24+
25+
namespace RectorLaravel\Tests\Rector\MethodCall\ReplaceServiceContainerCallArgRector\Fixture;
26+
27+
function foo(\Illuminate\Contracts\Container\Container $app) {
28+
$app->make(\Illuminate\Contracts\Encryption\Encrypter::class)->encrypt('hello world');
29+
}
30+
31+
\Illuminate\Support\Facades\Application::make(\Illuminate\Contracts\Encryption\Encrypter::class)->encrypt('hello world');
32+
33+
app(\Illuminate\Contracts\Encryption\Encrypter::class)->encrypt('hello world');
34+
35+
function foo(\Illuminate\Contracts\Container\Container $app) {
36+
$app->make('session')->get('hello world');
37+
}
38+
39+
\Illuminate\Support\Facades\Application::make('session')->get('hello world');
40+
41+
app('session')->get('hello world');
42+
43+
?>
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
<?php
2+
3+
namespace RectorLaravel\Tests\Rector\MethodCall\ReplaceServiceContainerCallArgRector\Fixture;
4+
5+
function foo(\Illuminate\Contracts\Container\Container $app) {
6+
$app->make('foobar')->encrypt('hello world');
7+
}
8+
9+
\Illuminate\Support\Facades\Application::make('foobar')->encrypt('hello world');
10+
11+
app('foobar')->encrypt('foobar');
12+
13+
?>
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
<?php
2+
3+
namespace RectorLaravel\Tests\Rector\MethodCall\ReplaceServiceContainerCallArgRector\Fixture;
4+
5+
function foo ($app) {
6+
$app->make('encrypter')->encrypt('hello world');
7+
}
8+
9+
Application::make('encrypter')->encrypt('hello world');
10+
11+
?>
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
<?php
2+
3+
namespace RectorLaravel\Tests\Rector\MethodCall\ReplaceServiceContainerCallArgRector\Fixture;
4+
5+
function foo (\Illuminate\Contracts\Container\Container $app) {
6+
$app->build('encrypter')->encrypt('hello world');
7+
}
8+
9+
\Illuminate\Support\Facades\Application::build('encrypter')->encrypt('hello world');
10+
11+
?>
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace RectorLaravel\Tests\Rector\MethodCall\ReplaceServiceContainerCallArgRector;
6+
7+
use Iterator;
8+
use PHPUnit\Framework\Attributes\DataProvider;
9+
use Rector\Testing\PHPUnit\AbstractRectorTestCase;
10+
11+
final class ReplaceServiceContainerCallArgRectorTest extends AbstractRectorTestCase
12+
{
13+
public static function provideData(): Iterator
14+
{
15+
return self::yieldFilesFromDirectory(__DIR__ . '/Fixture');
16+
}
17+
18+
/**
19+
* @test
20+
*/
21+
#[DataProvider('provideData')]
22+
public function test(string $filePath): void
23+
{
24+
$this->doTestFile($filePath);
25+
}
26+
27+
public function provideConfigFilePath(): string
28+
{
29+
return __DIR__ . '/config/configured_rule.php';
30+
}
31+
}
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
use PhpParser\Node\Expr\ClassConstFetch;
6+
use PhpParser\Node\Name\FullyQualified;
7+
use Rector\Config\RectorConfig;
8+
use RectorLaravel\Rector\MethodCall\ReplaceServiceContainerCallArgRector;
9+
use RectorLaravel\ValueObject\ReplaceServiceContainerCallArg;
10+
11+
return static function (RectorConfig $rectorConfig): void {
12+
$rectorConfig->import(__DIR__ . '/../../../../../config/config.php');
13+
14+
$rectorConfig->ruleWithConfiguration(
15+
ReplaceServiceContainerCallArgRector::class,
16+
[
17+
new ReplaceServiceContainerCallArg(
18+
'encrypter',
19+
new ClassConstFetch(
20+
new FullyQualified('Illuminate\Contracts\Encryption\Encrypter'),
21+
'class'
22+
),
23+
),
24+
new ReplaceServiceContainerCallArg(
25+
new ClassConstFetch(
26+
new FullyQualified('Illuminate\Contracts\Session\Session'),
27+
'class'
28+
),
29+
'session',
30+
),
31+
]
32+
);
33+
};

0 commit comments

Comments
 (0)