Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,10 @@
namespace Rector\Tests\Transform\Rector\ArrayDimFetch\ArrayDimFetchToMethodCallRector\Fixture;

/** @var \SomeClass $object */
$object['key']->get()
$object['key'];
$object['key'] = 42;
isset($object['key']);
unset($object['key']);

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

/** @var \SomeClass $object */
$object->make('key')->get()
$object->get('key');
$object->set('key', 42);
$object->has('key');
$object->unset('key');

?>
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
<?php

namespace Rector\Tests\Transform\Rector\ArrayDimFetch\ArrayDimFetchToMethodCallRector\Fixture;

/** @var \Container $object */
$object['key'];
$object['key'] = 'value';
isset($object['key']);
unset($object['key']);

?>
-----
<?php

namespace Rector\Tests\Transform\Rector\ArrayDimFetch\ArrayDimFetchToMethodCallRector\Fixture;

/** @var \Container $object */
$object->get('key');
$object['key'] = 'value';
isset($object['key']);
unset($object['key']);

?>
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
<?php

namespace Rector\Tests\Transform\Rector\ArrayDimFetch\ArrayDimFetchToMethodCallRector\Fixture;

/** @var \SomeClass $object */
/** @var \SomeOtherClass $other */
if (isset($object['key1'], $other['key'], $object['key2'])) {
echo 'Keys are set';
}

?>
-----
<?php

namespace Rector\Tests\Transform\Rector\ArrayDimFetch\ArrayDimFetchToMethodCallRector\Fixture;

/** @var \SomeClass $object */
/** @var \SomeOtherClass $other */
if (isset($other['key']) && $object->has('key1') && $object->has('key2')) {
echo 'Keys are set';
}

?>
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<?php

namespace Rector\Tests\Transform\Rector\ArrayDimFetch\ArrayDimFetchToMethodCallRector\Fixture;

/** @var \SomeClass $object */
/** @var \SomeOtherClass $other */
unset($object['key1'], $other['key'], $object['key2']);

?>
-----
<?php

namespace Rector\Tests\Transform\Rector\ArrayDimFetch\ArrayDimFetchToMethodCallRector\Fixture;

/** @var \SomeClass $object */
/** @var \SomeOtherClass $other */
unset($other['key']);
$object->unset('key1');
$object->unset('key2');

?>
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@ class FooBar extends SomeExtendedClass
public function someMethod()
{
$this->something['key'];
$this->something['key'] = 42;
isset($this->something['key']);
unset($this->something['key']);
}
}

Expand All @@ -24,7 +27,10 @@ class FooBar extends SomeExtendedClass
{
public function someMethod()
{
$this->something->make('key');
$this->something->get('key');
$this->something->set('key', 42);
$this->something->has('key');
$this->something->unset('key');
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
return static function (RectorConfig $rectorConfig): void {
$rectorConfig
->ruleWithConfiguration(ArrayDimFetchToMethodCallRector::class, [
new ArrayDimFetchToMethodCall(new ObjectType('SomeClass'), 'make'),
new ArrayDimFetchToMethodCall(new ObjectType('SomeClass'), 'get', 'set', 'has', 'unset'),
new ArrayDimFetchToMethodCall(new ObjectType('Container'), 'get'),
]);
};
151 changes: 138 additions & 13 deletions rules/Transform/Rector/ArrayDimFetch/ArrayDimFetchToMethodCallRector.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,18 @@
namespace Rector\Transform\Rector\ArrayDimFetch;

use PhpParser\Node;
use PhpParser\NodeVisitor;
use PhpParser\Node\Arg;
use PhpParser\Node\Expr;
use PhpParser\Node\Expr\ArrayDimFetch;
use PhpParser\Node\Expr\Assign;
use PhpParser\Node\Expr\BinaryOp\BooleanAnd;
use PhpParser\Node\Expr\Isset_;
use PhpParser\Node\Expr\MethodCall;
use PHPStan\Type\ObjectType;
use PhpParser\Node\Stmt;
use PhpParser\Node\Stmt\Expression;
use PhpParser\Node\Stmt\Unset_;
use Rector\Contract\Rector\ConfigurableRectorInterface;
use Rector\Rector\AbstractRector;
use Rector\Transform\ValueObject\ArrayDimFetchToMethodCall;
Expand All @@ -31,41 +39,54 @@ public function getRuleDefinition(): RuleDefinition
return new RuleDefinition('Change array dim fetch to method call', [
new ConfiguredCodeSample(
<<<'CODE_SAMPLE'
$app['someService'];
$object['key'];
$object['key'] = 'value';
isset($object['key']);
unset($object['key']);
CODE_SAMPLE
,
<<<'CODE_SAMPLE'
$app->make('someService');
$object->get('key');
$object->set('key', 'value');
$object->has('key');
$object->unset('key');
CODE_SAMPLE
,
[new ArrayDimFetchToMethodCall(new ObjectType('SomeClass'), 'make')]
[new ArrayDimFetchToMethodCall(new ObjectType('SomeClass'), 'get', 'set', 'has', 'unset')],
),
]);
}

public function getNodeTypes(): array
{
return [ArrayDimFetch::class];
return [Assign::class, Isset_::class, Unset_::class, ArrayDimFetch::class];
}

/**
* @param ArrayDimFetch $node
* @template TNode of ArrayDimFetch|Assign|Isset_|Unset_
* @param TNode $node
* @return ($node is Unset_ ? Stmt[]|int : ($node is Isset_ ? Expr|int : MethodCall|int|null))
*/
public function refactor(Node $node): ?MethodCall
public function refactor(Node $node): array|Expr|null|int
{
if (! $node->dim instanceof Node) {
return null;
if ($node instanceof Unset_) {
return $this->handleUnset($node);
}

foreach ($this->arrayDimFetchToMethodCalls as $arrayDimFetchToMethodCall) {
if (! $this->isObjectType($node->var, $arrayDimFetchToMethodCall->getObjectType())) {
continue;
if ($node instanceof Isset_) {
return $this->handleIsset($node);
}

if ($node instanceof Assign) {
if (!$node->var instanceof ArrayDimFetch) {
return null;
}

return new MethodCall($node->var, $arrayDimFetchToMethodCall->getMethod(), [new Arg($node->dim)]);
return $this->getMethodCall($node->var, 'set', $node->expr)
?? NodeVisitor::DONT_TRAVERSE_CHILDREN;
}

return null;
return $this->getMethodCall($node, 'get');
}

public function configure(array $configuration): void
Expand All @@ -74,4 +95,108 @@ public function configure(array $configuration): void

$this->arrayDimFetchToMethodCalls = $configuration;
}

private function handleIsset(Isset_ $node): Expr|int|null
{
$issets = [];
$exprs = [];

foreach ($node->vars as $var) {
if ($var instanceof ArrayDimFetch) {
$methodCall = $this->getMethodCall($var, 'exists');

if ($methodCall !== null) {
$exprs[] = $methodCall;
continue;
}
}

$issets[] = $var;
}

if ($exprs === []) {
return NodeVisitor::DONT_TRAVERSE_CHILDREN;
}

if ($issets !== []) {
$node->vars = $issets;
array_unshift($exprs, $node);
}

return array_reduce(
$exprs,
fn (?Expr $carry, Expr $expr) => $carry === null ? $expr : new BooleanAnd($carry, $expr),
null,
);
}

/**
* @return Stmt[]|int
*/
private function handleUnset(Unset_ $node): array|int
{
$unsets = [];
$stmts = [];

foreach ($node->vars as $var) {
if ($var instanceof ArrayDimFetch) {
$methodCall = $this->getMethodCall($var, 'unset');

if ($methodCall !== null) {
$stmts[] = new Expression($methodCall);
continue;
}
}

$unsets[] = $var;
}

if ($stmts === []) {
return NodeVisitor::DONT_TRAVERSE_CHILDREN;
}

if ($unsets !== []) {
$node->vars = $unsets;
array_unshift($stmts, $node);
}

return $stmts;
}

/**
* @param 'get'|'set'|'exists'|'unset' $action
*/
private function getMethodCall(ArrayDimFetch $fetch, string $action, ?Expr $value = null): ?MethodCall
{
if (!$fetch->dim instanceof Node) {
return null;
}

foreach ($this->arrayDimFetchToMethodCalls as $arrayDimFetchToMethodCall) {
if (!$this->isObjectType($fetch->var, $arrayDimFetchToMethodCall->getObjectType())) {
continue;
}

$method = match ($action) {
'get' => $arrayDimFetchToMethodCall->getMethod(),
'set' => $arrayDimFetchToMethodCall->getSetMethod(),
'exists' => $arrayDimFetchToMethodCall->getExistsMethod(),
'unset' => $arrayDimFetchToMethodCall->getUnsetMethod(),
};

if ($method === null) {
continue;
}

$args = [new Arg($fetch->dim)];

if ($value instanceof Expr) {
$args[] = new Arg($value);
}

return new MethodCall($fetch->var, $method, $args);
}

return null;
}
}
22 changes: 21 additions & 1 deletion rules/Transform/ValueObject/ArrayDimFetchToMethodCall.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,12 @@ class ArrayDimFetchToMethodCall
{
public function __construct(
private readonly ObjectType $objectType,
private readonly string $method
private readonly string $method,
// Optional methods for set, exists, and unset operations
// if null, then these operations will not be transformed
private readonly ?string $setMethod = null,
private readonly ?string $existsMethod = null,
private readonly ?string $unsetMethod = null,
) {
}

Expand All @@ -23,4 +28,19 @@ public function getMethod(): string
{
return $this->method;
}

public function getSetMethod(): ?string
{
return $this->setMethod;
}

public function getExistsMethod(): ?string
{
return $this->existsMethod;
}

public function getUnsetMethod(): ?string
{
return $this->unsetMethod;
}
}