55namespace Rector \Transform \Rector \ArrayDimFetch ;
66
77use PhpParser \Node ;
8+ use PhpParser \NodeVisitor ;
89use PhpParser \Node \Arg ;
10+ use PhpParser \Node \Expr ;
911use PhpParser \Node \Expr \ArrayDimFetch ;
12+ use PhpParser \Node \Expr \Assign ;
13+ use PhpParser \Node \Expr \BinaryOp \BooleanAnd ;
14+ use PhpParser \Node \Expr \Isset_ ;
1015use PhpParser \Node \Expr \MethodCall ;
1116use PHPStan \Type \ObjectType ;
17+ use PhpParser \Node \Stmt ;
18+ use PhpParser \Node \Stmt \Expression ;
19+ use PhpParser \Node \Stmt \Unset_ ;
1220use Rector \Contract \Rector \ConfigurableRectorInterface ;
1321use Rector \Rector \AbstractRector ;
1422use Rector \Transform \ValueObject \ArrayDimFetchToMethodCall ;
@@ -31,41 +39,54 @@ public function getRuleDefinition(): RuleDefinition
3139 return new RuleDefinition ('Change array dim fetch to method call ' , [
3240 new ConfiguredCodeSample (
3341 <<<'CODE_SAMPLE'
34- $app['someService'];
42+ $object['key'];
43+ $object['key'] = 'value';
44+ isset($object['key']);
45+ unset($object['key']);
3546CODE_SAMPLE
3647 ,
3748 <<<'CODE_SAMPLE'
38- $app->make('someService');
49+ $object->get('key');
50+ $object->set('key', 'value');
51+ $object->has('key');
52+ $object->unset('key');
3953CODE_SAMPLE
4054 ,
41- [new ArrayDimFetchToMethodCall (new ObjectType ('SomeClass ' ), 'make ' )]
55+ [new ArrayDimFetchToMethodCall (new ObjectType ('SomeClass ' ), 'get ' , ' set ' , ' has ' , ' unset ' )],
4256 ),
4357 ]);
4458 }
4559
4660 public function getNodeTypes (): array
4761 {
48- return [ArrayDimFetch::class];
62+ return [Assign::class, Isset_::class, Unset_::class, ArrayDimFetch::class];
4963 }
5064
5165 /**
52- * @param ArrayDimFetch $node
66+ * @template TNode of ArrayDimFetch|Assign|Isset_|Unset_
67+ * @param TNode $node
68+ * @return ($node is Unset_ ? Stmt[]|int : ($node is Isset_ ? Expr|int : MethodCall|int|null))
5369 */
54- public function refactor (Node $ node ): ? MethodCall
70+ public function refactor (Node $ node ): array | Expr | null | int
5571 {
56- if (! $ node-> dim instanceof Node ) {
57- return null ;
72+ if ($ node instanceof Unset_ ) {
73+ return $ this -> handleUnset ( $ node ) ;
5874 }
5975
60- foreach ($ this ->arrayDimFetchToMethodCalls as $ arrayDimFetchToMethodCall ) {
61- if (! $ this ->isObjectType ($ node ->var , $ arrayDimFetchToMethodCall ->getObjectType ())) {
62- continue ;
76+ if ($ node instanceof Isset_) {
77+ return $ this ->handleIsset ($ node );
78+ }
79+
80+ if ($ node instanceof Assign) {
81+ if (!$ node ->var instanceof ArrayDimFetch) {
82+ return null ;
6383 }
6484
65- return new MethodCall ($ node ->var , $ arrayDimFetchToMethodCall ->getMethod (), [new Arg ($ node ->dim )]);
85+ return $ this ->getMethodCall ($ node ->var , 'set ' , $ node ->expr )
86+ ?? NodeVisitor::DONT_TRAVERSE_CHILDREN ;
6687 }
6788
68- return null ;
89+ return $ this -> getMethodCall ( $ node , ' get ' ) ;
6990 }
7091
7192 public function configure (array $ configuration ): void
@@ -74,4 +95,108 @@ public function configure(array $configuration): void
7495
7596 $ this ->arrayDimFetchToMethodCalls = $ configuration ;
7697 }
98+
99+ private function handleIsset (Isset_ $ node ): Expr |int |null
100+ {
101+ $ issets = [];
102+ $ exprs = [];
103+
104+ foreach ($ node ->vars as $ var ) {
105+ if ($ var instanceof ArrayDimFetch) {
106+ $ methodCall = $ this ->getMethodCall ($ var , 'exists ' );
107+
108+ if ($ methodCall !== null ) {
109+ $ exprs [] = $ methodCall ;
110+ continue ;
111+ }
112+ }
113+
114+ $ issets [] = $ var ;
115+ }
116+
117+ if ($ exprs === []) {
118+ return NodeVisitor::DONT_TRAVERSE_CHILDREN ;
119+ }
120+
121+ if ($ issets !== []) {
122+ $ node ->vars = $ issets ;
123+ array_unshift ($ exprs , $ node );
124+ }
125+
126+ return array_reduce (
127+ $ exprs ,
128+ fn (?Expr $ carry , Expr $ expr ) => $ carry === null ? $ expr : new BooleanAnd ($ carry , $ expr ),
129+ null ,
130+ );
131+ }
132+
133+ /**
134+ * @return Stmt[]|int
135+ */
136+ private function handleUnset (Unset_ $ node ): array |int
137+ {
138+ $ unsets = [];
139+ $ stmts = [];
140+
141+ foreach ($ node ->vars as $ var ) {
142+ if ($ var instanceof ArrayDimFetch) {
143+ $ methodCall = $ this ->getMethodCall ($ var , 'unset ' );
144+
145+ if ($ methodCall !== null ) {
146+ $ stmts [] = new Expression ($ methodCall );
147+ continue ;
148+ }
149+ }
150+
151+ $ unsets [] = $ var ;
152+ }
153+
154+ if ($ stmts === []) {
155+ return NodeVisitor::DONT_TRAVERSE_CHILDREN ;
156+ }
157+
158+ if ($ unsets !== []) {
159+ $ node ->vars = $ unsets ;
160+ array_unshift ($ stmts , $ node );
161+ }
162+
163+ return $ stmts ;
164+ }
165+
166+ /**
167+ * @param 'get'|'set'|'exists'|'unset' $action
168+ */
169+ private function getMethodCall (ArrayDimFetch $ fetch , string $ action , ?Expr $ value = null ): ?MethodCall
170+ {
171+ if (!$ fetch ->dim instanceof Node) {
172+ return null ;
173+ }
174+
175+ foreach ($ this ->arrayDimFetchToMethodCalls as $ arrayDimFetchToMethodCall ) {
176+ if (!$ this ->isObjectType ($ fetch ->var , $ arrayDimFetchToMethodCall ->getObjectType ())) {
177+ continue ;
178+ }
179+
180+ $ method = match ($ action ) {
181+ 'get ' => $ arrayDimFetchToMethodCall ->getMethod (),
182+ 'set ' => $ arrayDimFetchToMethodCall ->getSetMethod (),
183+ 'exists ' => $ arrayDimFetchToMethodCall ->getExistsMethod (),
184+ 'unset ' => $ arrayDimFetchToMethodCall ->getUnsetMethod (),
185+ };
186+
187+ if ($ method === null ) {
188+ continue ;
189+ }
190+
191+ $ args = [new Arg ($ fetch ->dim )];
192+
193+ if ($ value instanceof Expr) {
194+ $ args [] = new Arg ($ value );
195+ }
196+
197+ return new MethodCall ($ fetch ->var , $ method , $ args );
198+ }
199+
200+ return null ;
201+ }
77202}
0 commit comments