Skip to content

Commit 4dfb209

Browse files
authored
feat: add support for set, isset, and unset operations in ArrayDimFetchToMethodCallRector (#7095)
1 parent 86456bd commit 4dfb209

8 files changed

Lines changed: 243 additions & 18 deletions

File tree

rules-tests/Transform/Rector/ArrayDimFetch/ArrayDimFetchToMethodCallRector/Fixture/fixture.php.inc

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,10 @@
33
namespace Rector\Tests\Transform\Rector\ArrayDimFetch\ArrayDimFetchToMethodCallRector\Fixture;
44

55
/** @var \SomeClass $object */
6-
$object['key']->get()
6+
$object['key'];
7+
$object['key'] = 42;
8+
isset($object['key']);
9+
unset($object['key']);
710

811
?>
912
-----
@@ -12,6 +15,9 @@ $object['key']->get()
1215
namespace Rector\Tests\Transform\Rector\ArrayDimFetch\ArrayDimFetchToMethodCallRector\Fixture;
1316

1417
/** @var \SomeClass $object */
15-
$object->make('key')->get()
18+
$object->get('key');
19+
$object->set('key', 42);
20+
$object->has('key');
21+
$object->unset('key');
1622

1723
?>
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
<?php
2+
3+
namespace Rector\Tests\Transform\Rector\ArrayDimFetch\ArrayDimFetchToMethodCallRector\Fixture;
4+
5+
/** @var \Container $object */
6+
$object['key'];
7+
$object['key'] = 'value';
8+
isset($object['key']);
9+
unset($object['key']);
10+
11+
?>
12+
-----
13+
<?php
14+
15+
namespace Rector\Tests\Transform\Rector\ArrayDimFetch\ArrayDimFetchToMethodCallRector\Fixture;
16+
17+
/** @var \Container $object */
18+
$object->get('key');
19+
$object['key'] = 'value';
20+
isset($object['key']);
21+
unset($object['key']);
22+
23+
?>
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
<?php
2+
3+
namespace Rector\Tests\Transform\Rector\ArrayDimFetch\ArrayDimFetchToMethodCallRector\Fixture;
4+
5+
/** @var \SomeClass $object */
6+
/** @var \SomeOtherClass $other */
7+
if (isset($object['key1'], $other['key'], $object['key2'])) {
8+
echo 'Keys are set';
9+
}
10+
11+
?>
12+
-----
13+
<?php
14+
15+
namespace Rector\Tests\Transform\Rector\ArrayDimFetch\ArrayDimFetchToMethodCallRector\Fixture;
16+
17+
/** @var \SomeClass $object */
18+
/** @var \SomeOtherClass $other */
19+
if (isset($other['key']) && $object->has('key1') && $object->has('key2')) {
20+
echo 'Keys are set';
21+
}
22+
23+
?>
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
<?php
2+
3+
namespace Rector\Tests\Transform\Rector\ArrayDimFetch\ArrayDimFetchToMethodCallRector\Fixture;
4+
5+
/** @var \SomeClass $object */
6+
/** @var \SomeOtherClass $other */
7+
unset($object['key1'], $other['key'], $object['key2']);
8+
9+
?>
10+
-----
11+
<?php
12+
13+
namespace Rector\Tests\Transform\Rector\ArrayDimFetch\ArrayDimFetchToMethodCallRector\Fixture;
14+
15+
/** @var \SomeClass $object */
16+
/** @var \SomeOtherClass $other */
17+
unset($other['key']);
18+
$object->unset('key1');
19+
$object->unset('key2');
20+
21+
?>

rules-tests/Transform/Rector/ArrayDimFetch/ArrayDimFetchToMethodCallRector/Fixture/transforms_property_fetch.php.inc

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,9 @@ class FooBar extends SomeExtendedClass
99
public function someMethod()
1010
{
1111
$this->something['key'];
12+
$this->something['key'] = 42;
13+
isset($this->something['key']);
14+
unset($this->something['key']);
1215
}
1316
}
1417

@@ -24,7 +27,10 @@ class FooBar extends SomeExtendedClass
2427
{
2528
public function someMethod()
2629
{
27-
$this->something->make('key');
30+
$this->something->get('key');
31+
$this->something->set('key', 42);
32+
$this->something->has('key');
33+
$this->something->unset('key');
2834
}
2935
}
3036

rules-tests/Transform/Rector/ArrayDimFetch/ArrayDimFetchToMethodCallRector/config/configured_rule.php

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
return static function (RectorConfig $rectorConfig): void {
1111
$rectorConfig
1212
->ruleWithConfiguration(ArrayDimFetchToMethodCallRector::class, [
13-
new ArrayDimFetchToMethodCall(new ObjectType('SomeClass'), 'make'),
13+
new ArrayDimFetchToMethodCall(new ObjectType('SomeClass'), 'get', 'set', 'has', 'unset'),
14+
new ArrayDimFetchToMethodCall(new ObjectType('Container'), 'get'),
1415
]);
1516
};

rules/Transform/Rector/ArrayDimFetch/ArrayDimFetchToMethodCallRector.php

Lines changed: 138 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,18 @@
55
namespace Rector\Transform\Rector\ArrayDimFetch;
66

77
use PhpParser\Node;
8+
use PhpParser\NodeVisitor;
89
use PhpParser\Node\Arg;
10+
use PhpParser\Node\Expr;
911
use PhpParser\Node\Expr\ArrayDimFetch;
12+
use PhpParser\Node\Expr\Assign;
13+
use PhpParser\Node\Expr\BinaryOp\BooleanAnd;
14+
use PhpParser\Node\Expr\Isset_;
1015
use PhpParser\Node\Expr\MethodCall;
1116
use PHPStan\Type\ObjectType;
17+
use PhpParser\Node\Stmt;
18+
use PhpParser\Node\Stmt\Expression;
19+
use PhpParser\Node\Stmt\Unset_;
1220
use Rector\Contract\Rector\ConfigurableRectorInterface;
1321
use Rector\Rector\AbstractRector;
1422
use 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']);
3546
CODE_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');
3953
CODE_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
}

rules/Transform/ValueObject/ArrayDimFetchToMethodCall.php

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,12 @@ class ArrayDimFetchToMethodCall
1010
{
1111
public function __construct(
1212
private readonly ObjectType $objectType,
13-
private readonly string $method
13+
private readonly string $method,
14+
// Optional methods for set, exists, and unset operations
15+
// if null, then these operations will not be transformed
16+
private readonly ?string $setMethod = null,
17+
private readonly ?string $existsMethod = null,
18+
private readonly ?string $unsetMethod = null,
1419
) {
1520
}
1621

@@ -23,4 +28,19 @@ public function getMethod(): string
2328
{
2429
return $this->method;
2530
}
31+
32+
public function getSetMethod(): ?string
33+
{
34+
return $this->setMethod;
35+
}
36+
37+
public function getExistsMethod(): ?string
38+
{
39+
return $this->existsMethod;
40+
}
41+
42+
public function getUnsetMethod(): ?string
43+
{
44+
return $this->unsetMethod;
45+
}
2646
}

0 commit comments

Comments
 (0)