Skip to content

Commit 10b6882

Browse files
imliamGeniJaho
andauthored
Refactor config() calls to use type-specific methods when the expected type is known (#399)
* Refactor `config()` calls to use type-specific methods when the expected type is known * Code style fixes --------- Co-authored-by: Geni Jaho <[email protected]>
1 parent 44cde57 commit 10b6882

File tree

7 files changed

+273
-0
lines changed

7 files changed

+273
-0
lines changed

docs/rector_rules_overview.md

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -555,6 +555,27 @@ Refactor `whereDate()` queries to include both date and time comparisons with Ca
555555

556556
<br>
557557

558+
## ConfigToTypedConfigMethodCallRector
559+
560+
Refactor `config()` calls to use type-specific methods when the expected type is known
561+
562+
- class: [`RectorLaravel\Rector\FuncCall\ConfigToTypedConfigMethodCallRector`](../src/Rector/FuncCall/ConfigToTypedConfigMethodCallRector.php)
563+
564+
```diff
565+
-$name = (string) config('app.name');
566+
-$lifetime = (int) config('session.lifetime');
567+
-$debug = (bool) config('app.debug');
568+
-$version = (float) config('app.version');
569+
-$connections = (array) config('database.connections');
570+
+$name = config()->string('app.name');
571+
+$lifetime = config()->integer('session.lifetime');
572+
+$debug = config()->boolean('app.debug');
573+
+$version = config()->float('app.version');
574+
+$connections = config()->array('database.connections');
575+
```
576+
577+
<br>
578+
558579
## ContainerBindConcreteWithClosureOnlyRector
559580

560581
Drop the specified abstract class from the bind method and replace it with a closure that returns the abstract class.
Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace RectorLaravel\Rector\FuncCall;
6+
7+
use PhpParser\Node;
8+
use PhpParser\Node\Expr\Cast;
9+
use PhpParser\Node\Expr\Cast\Array_;
10+
use PhpParser\Node\Expr\Cast\Bool_;
11+
use PhpParser\Node\Expr\Cast\Double;
12+
use PhpParser\Node\Expr\Cast\Int_;
13+
use PhpParser\Node\Expr\Cast\String_;
14+
use PhpParser\Node\Expr\FuncCall;
15+
use PhpParser\Node\Expr\MethodCall;
16+
use PhpParser\Node\Identifier;
17+
use PhpParser\Node\Name;
18+
use RectorLaravel\AbstractRector;
19+
use Symplify\RuleDocGenerator\ValueObject\CodeSample\CodeSample;
20+
use Symplify\RuleDocGenerator\ValueObject\RuleDefinition;
21+
22+
/**
23+
* @see \RectorLaravel\Tests\Rector\FuncCall\ConfigToTypedConfigMethodCallRector\ConfigToTypedConfigMethodCallRectorTest
24+
*/
25+
final class ConfigToTypedConfigMethodCallRector extends AbstractRector
26+
{
27+
public function getRuleDefinition(): RuleDefinition
28+
{
29+
return new RuleDefinition(
30+
'Refactor config() calls to use type-specific methods when the expected type is known',
31+
[
32+
new CodeSample(
33+
<<<'CODE_SAMPLE'
34+
$name = (string) config('app.name');
35+
$lifetime = (int) config('session.lifetime');
36+
$debug = (bool) config('app.debug');
37+
$version = (float) config('app.version');
38+
$connections = (array) config('database.connections');
39+
CODE_SAMPLE,
40+
<<<'CODE_SAMPLE'
41+
$name = config()->string('app.name');
42+
$lifetime = config()->integer('session.lifetime');
43+
$debug = config()->boolean('app.debug');
44+
$version = config()->float('app.version');
45+
$connections = config()->array('database.connections');
46+
CODE_SAMPLE
47+
),
48+
]
49+
);
50+
}
51+
52+
/**
53+
* @return array<class-string<Node>>
54+
*/
55+
public function getNodeTypes(): array
56+
{
57+
return [Cast::class];
58+
}
59+
60+
/**
61+
* @param Cast $node
62+
*/
63+
public function refactor(Node $node): ?Node
64+
{
65+
if (! $node->expr instanceof FuncCall) {
66+
return null;
67+
}
68+
69+
$funcCall = $node->expr;
70+
71+
if (! $this->isName($funcCall->name, 'config')) {
72+
return null;
73+
}
74+
75+
if (count($funcCall->args) !== 1) {
76+
return null;
77+
}
78+
79+
$methodName = $this->getMethodNameForCast($node);
80+
if ($methodName === null) {
81+
return null;
82+
}
83+
84+
$configCall = new FuncCall(new Name('config'));
85+
86+
return new MethodCall($configCall, new Identifier($methodName), $funcCall->args);
87+
}
88+
89+
private function getMethodNameForCast(Cast $cast): ?string
90+
{
91+
return match (true) {
92+
$cast instanceof String_ => 'string',
93+
$cast instanceof Int_ => 'integer',
94+
$cast instanceof Bool_ => 'boolean',
95+
$cast instanceof Double => 'float',
96+
$cast instanceof Array_ => 'array',
97+
default => null,
98+
};
99+
}
100+
}
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\FuncCall\ConfigToTypedConfigMethodCallRector;
6+
7+
use Iterator;
8+
use PHPUnit\Framework\Attributes\DataProvider;
9+
use Rector\Testing\PHPUnit\AbstractRectorTestCase;
10+
11+
final class ConfigToTypedConfigMethodCallRectorTest 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: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
<?php
2+
3+
namespace RectorLaravel\Tests\Rector\FuncCall\ConfigToTypedConfigMethodCallRector\Fixture;
4+
5+
class ComplexConfigCasts
6+
{
7+
public function run()
8+
{
9+
// Multiple casts in one method
10+
$appName = (string) config('app.name');
11+
$appDebug = (bool) config('app.debug');
12+
13+
// Nested in expressions
14+
$url = 'https://' . (string) config('app.domain');
15+
16+
// In conditionals
17+
if ((bool) config('feature.enabled')) {
18+
return true;
19+
}
20+
21+
// In return statements
22+
return (int) config('cache.ttl');
23+
}
24+
}
25+
26+
?>
27+
-----
28+
<?php
29+
30+
namespace RectorLaravel\Tests\Rector\FuncCall\ConfigToTypedConfigMethodCallRector\Fixture;
31+
32+
class ComplexConfigCasts
33+
{
34+
public function run()
35+
{
36+
// Multiple casts in one method
37+
$appName = config()->string('app.name');
38+
$appDebug = config()->boolean('app.debug');
39+
40+
// Nested in expressions
41+
$url = 'https://' . config()->string('app.domain');
42+
43+
// In conditionals
44+
if (config()->boolean('feature.enabled')) {
45+
return true;
46+
}
47+
48+
// In return statements
49+
return config()->integer('cache.ttl');
50+
}
51+
}
52+
53+
?>
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
<?php
2+
3+
namespace RectorLaravel\Tests\Rector\FuncCall\ConfigToTypedConfigMethodCallRector\Fixture;
4+
5+
class ConfigCasts
6+
{
7+
public function run()
8+
{
9+
$name = (string) config('app.name');
10+
$lifetime = (int) config('session.lifetime');
11+
$debug = (bool) config('app.debug');
12+
$version = (float) config('app.version');
13+
$connections = (array) config('database.connections');
14+
}
15+
}
16+
17+
?>
18+
-----
19+
<?php
20+
21+
namespace RectorLaravel\Tests\Rector\FuncCall\ConfigToTypedConfigMethodCallRector\Fixture;
22+
23+
class ConfigCasts
24+
{
25+
public function run()
26+
{
27+
$name = config()->string('app.name');
28+
$lifetime = config()->integer('session.lifetime');
29+
$debug = config()->boolean('app.debug');
30+
$version = config()->float('app.version');
31+
$connections = config()->array('database.connections');
32+
}
33+
}
34+
35+
?>
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
<?php
2+
3+
namespace RectorLaravel\Tests\Rector\FuncCall\ConfigToTypedConfigMethodCallRector\Fixture;
4+
5+
class SkipNonConfigCalls
6+
{
7+
public function run()
8+
{
9+
// Should skip: config() without arguments
10+
$config = config();
11+
12+
// Should skip: config() with array argument (setting values)
13+
config(['app.name' => 'My App']);
14+
15+
// Should skip: config() without cast
16+
$value = config('app.name');
17+
18+
// Should skip: other function casts
19+
$other = (string) someOtherFunc('key');
20+
}
21+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
use Rector\Config\RectorConfig;
6+
use RectorLaravel\Rector\FuncCall\ConfigToTypedConfigMethodCallRector;
7+
8+
return static function (RectorConfig $rectorConfig): void {
9+
$rectorConfig->import(__DIR__ . '/../../../../../config/config.php');
10+
11+
$rectorConfig->rule(ConfigToTypedConfigMethodCallRector::class);
12+
};

0 commit comments

Comments
 (0)