44
55namespace RectorLaravel \Rector \ArrayDimFetch ;
66
7- use PhpParser \Node \Name \FullyQualified ;
87use PhpParser \Node ;
98use PhpParser \Node \Arg ;
109use PhpParser \Node \Expr ;
1110use PhpParser \Node \Expr \ArrayDimFetch ;
11+ use PhpParser \Node \Expr \BinaryOp \Coalesce ;
1212use PhpParser \Node \Expr \StaticCall ;
13- use PhpParser \Node \Name ;
13+ use PhpParser \Node \Expr \Throw_ ;
14+ use PhpParser \Node \Name \FullyQualified ;
1415use PhpParser \Node \Scalar ;
1516use PhpParser \Node \Scalar \String_ ;
1617use RectorLaravel \AbstractRector ;
2223 */
2324final 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');
3342CODE_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');
3749CODE_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}
0 commit comments