diff --git a/docs/rector_rules_overview.md b/docs/rector_rules_overview.md index ff41dfcf..68688743 100644 --- a/docs/rector_rules_overview.md +++ b/docs/rector_rules_overview.md @@ -1,4 +1,4 @@ -# 56 Rules Overview +# 57 Rules Overview ## AddArgumentDefaultValueRector @@ -1015,6 +1015,23 @@ Replace `$this->faker` with the `fake()` helper function in Factories
+## ReplaceServiceContainerCallArgRector + +Changes the string or class const used for a service container make call + +:wrench: **configure it!** + +- class: [`RectorLaravel\Rector\MethodCall\ReplaceServiceContainerCallArgRector`](../src/Rector/MethodCall/ReplaceServiceContainerCallArgRector.php) + +```diff +-app('encrypter')->encrypt('...'); +-\Illuminate\Support\Facades\Application::make('encrypter')->encrypt('...'); ++app(Illuminate\Contracts\Encryption\Encrypter::class)->encrypt('...'); ++\Illuminate\Support\Facades\Application::make(Illuminate\Contracts\Encryption\Encrypter::class)->encrypt('...'); +``` + +
+ ## ReplaceWithoutJobsEventsAndNotificationsWithFacadeFakeRector Replace `withoutJobs`, `withoutEvents` and `withoutNotifications` with Facade `fake` diff --git a/src/Rector/MethodCall/ReplaceServiceContainerCallArgRector.php b/src/Rector/MethodCall/ReplaceServiceContainerCallArgRector.php new file mode 100644 index 00000000..fce7ee14 --- /dev/null +++ b/src/Rector/MethodCall/ReplaceServiceContainerCallArgRector.php @@ -0,0 +1,156 @@ +encrypt('...'); +\Illuminate\Support\Facades\Application::make('encrypter')->encrypt('...'); +CODE_SAMPLE, + <<<'CODE_SAMPLE' +app(Illuminate\Contracts\Encryption\Encrypter::class)->encrypt('...'); +\Illuminate\Support\Facades\Application::make(Illuminate\Contracts\Encryption\Encrypter::class)->encrypt('...'); +CODE_SAMPLE, + [ + new ReplaceServiceContainerCallArg( + 'encrypter', + new ClassConstFetch( + new Name('Illuminate\Contracts\Encryption\Encrypter'), + 'class' + ), + ), + ] + )] + ); + } + + public function getNodeTypes(): array + { + return [MethodCall::class, StaticCall::class, FuncCall::class]; + } + + /** + * @param MethodCall|StaticCall|FuncCall $node + */ + public function refactor(Node $node): MethodCall|StaticCall|FuncCall|null + { + if (! $this->validMethodCall($node) && + ! $this->validFuncCall($node)) { + return null; + } + + if ($node->args === [] || ! $node->args[0] instanceof Arg) { + return null; + } + + $hasChanged = false; + + foreach ($this->replaceServiceContainerCallArgs as $replaceServiceContainerCallArg) { + if ($this->isMatchForChangeServiceContainerCallArgValue($node->args[0], $replaceServiceContainerCallArg->getOldService())) { + $this->replaceCallArgValue($node->args[0], $replaceServiceContainerCallArg->getNewService()); + $hasChanged = true; + } + } + + return $hasChanged ? $node : null; + } + + public function configure(array $configuration): void + { + Assert::allIsInstanceOf($configuration, ReplaceServiceContainerCallArg::class); + + $this->replaceServiceContainerCallArgs = $configuration; + } + + private function isMatchForChangeServiceContainerCallArgValue(Arg $arg, ClassConstFetch|string $oldService): bool + { + if ($arg->value instanceof ClassConstFetch && $oldService instanceof ClassConstFetch) { + if ($arg->value->class instanceof Expr || $oldService->class instanceof Expr) { + return false; + } + + return $arg->value->class->toString() === $oldService->class->toString(); + } elseif ($arg->value instanceof String_) { + return $arg->value->value === $oldService; + } + + return false; + } + + private function replaceCallArgValue(Arg $arg, ClassConstFetch|string $newService): void + { + if ($newService instanceof ClassConstFetch) { + $arg->value = $newService; + + return; + } + + $arg->value = new String_($newService); + } + + private function validMethodCall(StaticCall|MethodCall|FuncCall $node): bool + { + if (! $node instanceof MethodCall && ! $node instanceof StaticCall) { + return false; + } + + if (! $node->name instanceof Identifier) { + return false; + } + + if (! $this->isNames($node->name, ['make', 'get'])) { + return false; + } + + [$callObject, $class] = match (true) { + $node instanceof MethodCall => [$node->var, 'Illuminate\Contracts\Container\Container'], + $node instanceof StaticCall => [$node->class, 'Illuminate\Support\Facades\Application'], + }; + + return $this->isObjectType($callObject, new ObjectType($class)); + } + + private function validFuncCall(StaticCall|MethodCall|FuncCall $node): bool + { + if (! $node instanceof FuncCall) { + return false; + } + + if (! $node->name instanceof Name) { + return false; + } + + return $this->isName($node->name, 'app'); + } +} diff --git a/src/ValueObject/ReplaceServiceContainerCallArg.php b/src/ValueObject/ReplaceServiceContainerCallArg.php new file mode 100644 index 00000000..106e3ba5 --- /dev/null +++ b/src/ValueObject/ReplaceServiceContainerCallArg.php @@ -0,0 +1,24 @@ +oldService; + } + + public function getNewService(): string|ClassConstFetch + { + return $this->newService; + } +} diff --git a/tests/Rector/MethodCall/ReplaceServiceContainerCallArgRector/Fixture/fixture.php.inc b/tests/Rector/MethodCall/ReplaceServiceContainerCallArgRector/Fixture/fixture.php.inc new file mode 100644 index 00000000..71530570 --- /dev/null +++ b/tests/Rector/MethodCall/ReplaceServiceContainerCallArgRector/Fixture/fixture.php.inc @@ -0,0 +1,43 @@ +make('encrypter')->encrypt('hello world'); +} + +\Illuminate\Support\Facades\Application::make('encrypter')->encrypt('hello world'); + +app('encrypter')->encrypt('hello world'); + +function foo(\Illuminate\Contracts\Container\Container $app) { + $app->make(\Illuminate\Contracts\Session\Session::class)->get('hello world'); +} + +\Illuminate\Support\Facades\Application::make(\Illuminate\Contracts\Session\Session::class)->get('hello world'); + +app(\Illuminate\Contracts\Session\Session::class)->get('hello world'); + +?> +----- +make(\Illuminate\Contracts\Encryption\Encrypter::class)->encrypt('hello world'); +} + +\Illuminate\Support\Facades\Application::make(\Illuminate\Contracts\Encryption\Encrypter::class)->encrypt('hello world'); + +app(\Illuminate\Contracts\Encryption\Encrypter::class)->encrypt('hello world'); + +function foo(\Illuminate\Contracts\Container\Container $app) { + $app->make('session')->get('hello world'); +} + +\Illuminate\Support\Facades\Application::make('session')->get('hello world'); + +app('session')->get('hello world'); + +?> diff --git a/tests/Rector/MethodCall/ReplaceServiceContainerCallArgRector/Fixture/skip_non_configured_services.php.inc b/tests/Rector/MethodCall/ReplaceServiceContainerCallArgRector/Fixture/skip_non_configured_services.php.inc new file mode 100644 index 00000000..4b8beb5b --- /dev/null +++ b/tests/Rector/MethodCall/ReplaceServiceContainerCallArgRector/Fixture/skip_non_configured_services.php.inc @@ -0,0 +1,13 @@ +make('foobar')->encrypt('hello world'); +} + +\Illuminate\Support\Facades\Application::make('foobar')->encrypt('hello world'); + +app('foobar')->encrypt('foobar'); + +?> diff --git a/tests/Rector/MethodCall/ReplaceServiceContainerCallArgRector/Fixture/skip_non_matching_classes.php.inc b/tests/Rector/MethodCall/ReplaceServiceContainerCallArgRector/Fixture/skip_non_matching_classes.php.inc new file mode 100644 index 00000000..2cebc8ae --- /dev/null +++ b/tests/Rector/MethodCall/ReplaceServiceContainerCallArgRector/Fixture/skip_non_matching_classes.php.inc @@ -0,0 +1,11 @@ +make('encrypter')->encrypt('hello world'); +} + +Application::make('encrypter')->encrypt('hello world'); + +?> diff --git a/tests/Rector/MethodCall/ReplaceServiceContainerCallArgRector/Fixture/skip_non_matching_method_calls.php.inc b/tests/Rector/MethodCall/ReplaceServiceContainerCallArgRector/Fixture/skip_non_matching_method_calls.php.inc new file mode 100644 index 00000000..9145b599 --- /dev/null +++ b/tests/Rector/MethodCall/ReplaceServiceContainerCallArgRector/Fixture/skip_non_matching_method_calls.php.inc @@ -0,0 +1,11 @@ +build('encrypter')->encrypt('hello world'); +} + +\Illuminate\Support\Facades\Application::build('encrypter')->encrypt('hello world'); + +?> diff --git a/tests/Rector/MethodCall/ReplaceServiceContainerCallArgRector/ReplaceServiceContainerCallArgRectorTest.php b/tests/Rector/MethodCall/ReplaceServiceContainerCallArgRector/ReplaceServiceContainerCallArgRectorTest.php new file mode 100644 index 00000000..4edc7406 --- /dev/null +++ b/tests/Rector/MethodCall/ReplaceServiceContainerCallArgRector/ReplaceServiceContainerCallArgRectorTest.php @@ -0,0 +1,31 @@ +doTestFile($filePath); + } + + public function provideConfigFilePath(): string + { + return __DIR__ . '/config/configured_rule.php'; + } +} diff --git a/tests/Rector/MethodCall/ReplaceServiceContainerCallArgRector/config/configured_rule.php b/tests/Rector/MethodCall/ReplaceServiceContainerCallArgRector/config/configured_rule.php new file mode 100644 index 00000000..ce2de9ea --- /dev/null +++ b/tests/Rector/MethodCall/ReplaceServiceContainerCallArgRector/config/configured_rule.php @@ -0,0 +1,33 @@ +import(__DIR__ . '/../../../../../config/config.php'); + + $rectorConfig->ruleWithConfiguration( + ReplaceServiceContainerCallArgRector::class, + [ + new ReplaceServiceContainerCallArg( + 'encrypter', + new ClassConstFetch( + new FullyQualified('Illuminate\Contracts\Encryption\Encrypter'), + 'class' + ), + ), + new ReplaceServiceContainerCallArg( + new ClassConstFetch( + new FullyQualified('Illuminate\Contracts\Session\Session'), + 'class' + ), + 'session', + ), + ] + ); +};