Skip to content

Commit afb6d38

Browse files
committed
feat: enhance ArrayToArrGetRector to add default values
1 parent 6cbdbf7 commit afb6d38

File tree

4 files changed

+173
-15
lines changed

4 files changed

+173
-15
lines changed

docs/rector_rules_overview.md

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -296,15 +296,20 @@ Move help facade-like function calls to constructor injection
296296

297297
## ArrayToArrGetRector
298298

299-
Convert array access to Arr::get() method call
299+
Convert array access to `Arr::get()` method call, skips null coalesce with throw expressions
300300

301301
- class: [`RectorLaravel\Rector\ArrayDimFetch\ArrayToArrGetRector`](../src/Rector/ArrayDimFetch/ArrayToArrGetRector.php)
302302

303303
```diff
304304
-$array['key'];
305305
-$array['nested']['key'];
306+
-$array['key'] ?? 'default';
307+
-$array['nested']['key'] ?? 'default';
306308
+\Illuminate\Support\Arr::get($array, 'key');
307309
+\Illuminate\Support\Arr::get($array, 'nested.key');
310+
+\Illuminate\Support\Arr::get($array, 'key', 'default');
311+
+\Illuminate\Support\Arr::get($array, 'nested.key', 'default');
312+
$array['key'] ?? throw new Exception('Required');
308313
```
309314

310315
<br>

src/Rector/ArrayDimFetch/ArrayToArrGetRector.php

Lines changed: 91 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,14 @@
44

55
namespace RectorLaravel\Rector\ArrayDimFetch;
66

7-
use PhpParser\Node\Name\FullyQualified;
87
use PhpParser\Node;
98
use PhpParser\Node\Arg;
109
use PhpParser\Node\Expr;
1110
use PhpParser\Node\Expr\ArrayDimFetch;
11+
use PhpParser\Node\Expr\BinaryOp\Coalesce;
1212
use PhpParser\Node\Expr\StaticCall;
13-
use PhpParser\Node\Name;
13+
use PhpParser\Node\Expr\Throw_;
14+
use PhpParser\Node\Name\FullyQualified;
1415
use PhpParser\Node\Scalar;
1516
use PhpParser\Node\Scalar\String_;
1617
use RectorLaravel\AbstractRector;
@@ -22,18 +23,29 @@
2223
*/
2324
final class ArrayToArrGetRector extends AbstractRector
2425
{
26+
/**
27+
* @var ArrayDimFetch[]
28+
*/
29+
private array $processedArrayDimFetches = [];
30+
2531
public function getRuleDefinition(): RuleDefinition
2632
{
2733
return new RuleDefinition(
28-
'Convert array access to Arr::get() method call',
34+
'Convert array access to Arr::get() method call, skips null coalesce with throw expressions',
2935
[new CodeSample(
3036
<<<'CODE_SAMPLE'
3137
$array['key'];
3238
$array['nested']['key'];
39+
$array['key'] ?? 'default';
40+
$array['nested']['key'] ?? 'default';
41+
$array['key'] ?? throw new Exception('Required');
3342
CODE_SAMPLE,
3443
<<<'CODE_SAMPLE'
3544
\Illuminate\Support\Arr::get($array, 'key');
3645
\Illuminate\Support\Arr::get($array, 'nested.key');
46+
\Illuminate\Support\Arr::get($array, 'key', 'default');
47+
\Illuminate\Support\Arr::get($array, 'nested.key', 'default');
48+
$array['key'] ?? throw new Exception('Required');
3749
CODE_SAMPLE
3850
)]
3951
);
@@ -44,28 +56,73 @@ public function getRuleDefinition(): RuleDefinition
4456
*/
4557
public function getNodeTypes(): array
4658
{
47-
return [ArrayDimFetch::class];
59+
return [ArrayDimFetch::class, Coalesce::class];
4860
}
4961

5062
/**
51-
* @param ArrayDimFetch $node
63+
* @param ArrayDimFetch|Coalesce $node
5264
*/
5365
public function refactor(Node $node): ?StaticCall
5466
{
55-
if ($node->dim === null) {
67+
if ($node instanceof Coalesce) {
68+
$result = $this->refactorCoalesce($node);
69+
if ($result instanceof StaticCall && $node->left instanceof ArrayDimFetch) {
70+
$this->processedArrayDimFetches[] = $node->left;
71+
}
72+
73+
return $result;
74+
}
75+
76+
if ($node instanceof ArrayDimFetch) {
77+
if (in_array($node, $this->processedArrayDimFetches, true)) {
78+
return null;
79+
}
80+
81+
return $this->refactorArrayDimFetch($node);
82+
}
83+
84+
return null;
85+
}
86+
87+
private function refactorCoalesce(Coalesce $coalesce): ?StaticCall
88+
{
89+
if (! $coalesce->left instanceof ArrayDimFetch) {
5690
return null;
5791
}
5892

59-
if (! $node->dim instanceof Scalar) {
93+
if ($coalesce->right instanceof Throw_) {
94+
$this->markArrayDimFetchAsProcessed($coalesce->left);
95+
6096
return null;
6197
}
6298

63-
$keyPath = $this->buildKeyPath($node);
99+
$staticCall = $this->createArrGetCall($coalesce->left);
100+
if (! $staticCall instanceof StaticCall) {
101+
return null;
102+
}
103+
104+
$staticCall->args[] = new Arg($coalesce->right);
105+
106+
return $staticCall;
107+
}
108+
109+
private function refactorArrayDimFetch(ArrayDimFetch $arrayDimFetch): ?StaticCall
110+
{
111+
return $this->createArrGetCall($arrayDimFetch);
112+
}
113+
114+
private function createArrGetCall(ArrayDimFetch $arrayDimFetch): ?StaticCall
115+
{
116+
if (! $this->isValidArrayDimFetch($arrayDimFetch)) {
117+
return null;
118+
}
119+
120+
$keyPath = $this->buildKeyPath($arrayDimFetch);
64121
if (! $keyPath instanceof Expr) {
65122
return null;
66123
}
67124

68-
$expr = $this->getRootVariable($node);
125+
$expr = $this->getRootVariable($arrayDimFetch);
69126

70127
return new StaticCall(
71128
new FullyQualified('Illuminate\Support\Arr'),
@@ -77,17 +134,24 @@ public function refactor(Node $node): ?StaticCall
77134
);
78135
}
79136

137+
private function isValidArrayDimFetch(ArrayDimFetch $arrayDimFetch): bool
138+
{
139+
return $arrayDimFetch->dim instanceof Scalar;
140+
}
141+
80142
private function buildKeyPath(ArrayDimFetch $arrayDimFetch): ?Expr
81143
{
82144
$keys = [];
83145
$current = $arrayDimFetch;
84146

85147
while ($current instanceof ArrayDimFetch) {
86-
if (! $current->dim instanceof Expr || ! $current->dim instanceof Scalar) {
148+
if (! $this->isValidArrayDimFetch($current)) {
87149
return null;
88150
}
89151

90-
array_unshift($keys, $current->dim);
152+
/** @var scalar $dim */
153+
$dim = $current->dim;
154+
array_unshift($keys, $dim);
91155
$current = $current->var;
92156
}
93157

@@ -103,19 +167,21 @@ private function buildKeyPath(ArrayDimFetch $arrayDimFetch): ?Expr
103167
}
104168

105169
/**
106-
* @param array<Scalar> $keys
170+
* @param array<scalar> $keys
107171
*/
108172
private function createDotNotationString(array $keys): ?String_
109173
{
110174
$stringParts = [];
111175

112176
foreach ($keys as $key) {
113-
$value = $this->getType($key)->getConstantScalarValues()[0] ?? null;
177+
$constantValues = $this->getType($key)->getConstantScalarValues();
114178

115-
if ($value === null) {
179+
if ($constantValues === []) {
116180
return null;
117181
}
118182

183+
$value = $constantValues[0];
184+
119185
if (! is_string($value) && ! is_int($value)) {
120186
return null;
121187
}
@@ -136,4 +202,15 @@ private function getRootVariable(ArrayDimFetch $arrayDimFetch): Expr
136202

137203
return $current;
138204
}
205+
206+
private function markArrayDimFetchAsProcessed(ArrayDimFetch $arrayDimFetch): void
207+
{
208+
$this->processedArrayDimFetches[] = $arrayDimFetch;
209+
210+
$current = $arrayDimFetch;
211+
while ($current instanceof ArrayDimFetch) {
212+
$this->processedArrayDimFetches[] = $current;
213+
$current = $current->var;
214+
}
215+
}
139216
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
<?php
2+
3+
namespace RectorLaravel\Tests\Rector\ArrayDimFetch\ArrayToArrGetRector\Fixture;
4+
5+
class SkipThrowExpressions
6+
{
7+
public function run()
8+
{
9+
$array = ['key' => 'value'];
10+
11+
// Should skip throw expressions - no transformation
12+
$value = $array['key'] ?? throw new \Exception('Key not found');
13+
14+
// Should skip variable keys with throw - no transformation
15+
$dynamicKey = 'key';
16+
$value2 = $array[$dynamicKey] ?? throw new \RuntimeException('Missing');
17+
18+
// Should skip nested throw expressions - no transformation
19+
$value3 = $array['nested']['inner'] ?? throw new \InvalidArgumentException('Nested missing');
20+
}
21+
}
22+
23+
?>
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\ArrayDimFetch\ArrayToArrGetRector\Fixture;
4+
5+
class WithDefaultValues
6+
{
7+
public function run()
8+
{
9+
$array = ['key' => 'value', 'nested' => ['inner' => 'data']];
10+
11+
// Simple array access with defaults
12+
$value = $array['key'] ?? 'default';
13+
14+
// Nested array access with defaults
15+
$nested = $array['nested']['inner'] ?? 'fallback';
16+
17+
// Multiple levels with defaults
18+
$data = $array['level1']['level2']['level3'] ?? null;
19+
20+
// Integer keys with defaults
21+
$indexed = $array[0] ?? 'empty';
22+
$multiIndexed = $array[0][1] ?? 42;
23+
}
24+
}
25+
26+
?>
27+
-----
28+
<?php
29+
30+
namespace RectorLaravel\Tests\Rector\ArrayDimFetch\ArrayToArrGetRector\Fixture;
31+
32+
class WithDefaultValues
33+
{
34+
public function run()
35+
{
36+
$array = ['key' => 'value', 'nested' => ['inner' => 'data']];
37+
38+
// Simple array access with defaults
39+
$value = \Illuminate\Support\Arr::get($array, 'key', 'default');
40+
41+
// Nested array access with defaults
42+
$nested = \Illuminate\Support\Arr::get($array, 'nested.inner', 'fallback');
43+
44+
// Multiple levels with defaults
45+
$data = \Illuminate\Support\Arr::get($array, 'level1.level2.level3', null);
46+
47+
// Integer keys with defaults
48+
$indexed = \Illuminate\Support\Arr::get($array, 0, 'empty');
49+
$multiIndexed = \Illuminate\Support\Arr::get($array, '0.1', 42);
50+
}
51+
}
52+
53+
?>

0 commit comments

Comments
 (0)