2018-10-03 13:14:08 +00:00
|
|
|
<?php declare(strict_types=1);
|
|
|
|
|
|
|
|
namespace Rector\Php\Rector\Each;
|
|
|
|
|
|
|
|
use PhpParser\Node;
|
|
|
|
use PhpParser\Node\Expr\Assign;
|
|
|
|
use PhpParser\Node\Expr\FuncCall;
|
|
|
|
use PhpParser\Node\Expr\List_;
|
|
|
|
use PhpParser\Node\Stmt\Do_;
|
|
|
|
use PhpParser\Node\Stmt\Expression;
|
|
|
|
use Rector\NodeTypeResolver\Node\Attribute;
|
|
|
|
use Rector\Rector\AbstractRector;
|
|
|
|
use Rector\RectorDefinition\CodeSample;
|
|
|
|
use Rector\RectorDefinition\RectorDefinition;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @source https://wiki.php.net/rfc/deprecations_php_7_2#each
|
|
|
|
*/
|
|
|
|
final class ListEachRector extends AbstractRector
|
|
|
|
{
|
|
|
|
public function getDefinition(): RectorDefinition
|
|
|
|
{
|
|
|
|
return new RectorDefinition(
|
|
|
|
'each() function is deprecated, use foreach() instead.',
|
|
|
|
[
|
|
|
|
new CodeSample(
|
|
|
|
<<<'CODE_SAMPLE'
|
|
|
|
list($key, $callback) = each($callbacks);
|
|
|
|
CODE_SAMPLE
|
|
|
|
,
|
|
|
|
<<<'CODE_SAMPLE'
|
|
|
|
$key = key($opt->option);
|
|
|
|
$val = current($opt->option);
|
|
|
|
CODE_SAMPLE
|
|
|
|
),
|
|
|
|
]
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @return string[]
|
|
|
|
*/
|
|
|
|
public function getNodeTypes(): array
|
|
|
|
{
|
|
|
|
return [Assign::class];
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2018-10-15 04:36:58 +00:00
|
|
|
* @param Assign $node
|
2018-10-03 13:14:08 +00:00
|
|
|
*/
|
2018-10-15 04:36:58 +00:00
|
|
|
public function refactor(Node $node): ?Node
|
2018-10-03 13:14:08 +00:00
|
|
|
{
|
2018-10-15 04:36:58 +00:00
|
|
|
if ($this->shouldSkip($node)) {
|
2018-10-21 10:19:14 +00:00
|
|
|
return null;
|
2018-10-03 13:14:08 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/** @var List_ $listNode */
|
2018-10-15 04:36:58 +00:00
|
|
|
$listNode = $node->var;
|
2018-10-03 13:14:08 +00:00
|
|
|
|
|
|
|
/** @var FuncCall $eachFuncCall */
|
2018-10-15 04:36:58 +00:00
|
|
|
$eachFuncCall = $node->expr;
|
2018-10-03 13:14:08 +00:00
|
|
|
|
|
|
|
// only key: list($key, ) = each($values);
|
|
|
|
if ($listNode->items[0] && $listNode->items[1] === null) {
|
2018-11-03 09:35:05 +00:00
|
|
|
$keyFuncCall = $this->createFunction('key', $eachFuncCall->args);
|
2018-10-03 13:14:08 +00:00
|
|
|
return new Assign($listNode->items[0]->value, $keyFuncCall);
|
|
|
|
}
|
|
|
|
|
|
|
|
// only value: list(, $value) = each($values);
|
|
|
|
if ($listNode->items[1] && $listNode->items[0] === null) {
|
2018-11-03 09:35:05 +00:00
|
|
|
$nextFuncCall = $this->createFunction('next', $eachFuncCall->args);
|
2018-10-15 04:36:58 +00:00
|
|
|
$this->addNodeAfterNode($nextFuncCall, $node);
|
2018-10-03 13:14:08 +00:00
|
|
|
|
2018-11-03 09:35:05 +00:00
|
|
|
$currentFuncCall = $this->createFunction('current', $eachFuncCall->args);
|
2018-10-03 13:14:08 +00:00
|
|
|
return new Assign($listNode->items[1]->value, $currentFuncCall);
|
|
|
|
}
|
|
|
|
|
|
|
|
// both: list($key, $value) = each($values);
|
|
|
|
// ↓
|
|
|
|
// $key = key($values);
|
|
|
|
// $value = current($values);
|
|
|
|
// next($values); - only inside a loop
|
2018-11-03 09:35:05 +00:00
|
|
|
$currentFuncCall = $this->createFunction('current', $eachFuncCall->args);
|
2018-10-07 09:53:59 +00:00
|
|
|
$assignCurrentNode = new Assign($listNode->items[1]->value, $currentFuncCall);
|
2018-10-15 04:36:58 +00:00
|
|
|
$this->addNodeAfterNode($assignCurrentNode, $node);
|
2018-10-03 13:14:08 +00:00
|
|
|
|
2018-10-15 04:36:58 +00:00
|
|
|
if ($this->isInsideDoWhile($node)) {
|
2018-11-03 09:35:05 +00:00
|
|
|
$nextFuncCall = $this->createFunction('next', $eachFuncCall->args);
|
2018-10-15 04:36:58 +00:00
|
|
|
$this->addNodeAfterNode($nextFuncCall, $node);
|
2018-10-03 13:14:08 +00:00
|
|
|
}
|
|
|
|
|
2018-11-03 09:35:05 +00:00
|
|
|
$keyFuncCall = $this->createFunction('key', $eachFuncCall->args);
|
2018-10-07 09:53:59 +00:00
|
|
|
return new Assign($listNode->items[0]->value, $keyFuncCall);
|
2018-10-03 13:14:08 +00:00
|
|
|
}
|
|
|
|
|
2018-10-31 15:34:37 +00:00
|
|
|
private function shouldSkip(Assign $assignNode): bool
|
2018-10-03 13:14:08 +00:00
|
|
|
{
|
2018-10-31 15:34:37 +00:00
|
|
|
if (! $this->isListToEachAssign($assignNode)) {
|
|
|
|
return true;
|
2018-10-03 13:14:08 +00:00
|
|
|
}
|
|
|
|
|
2018-10-31 15:34:37 +00:00
|
|
|
// assign should be top level, e.g. not in a while loop
|
|
|
|
if (! $assignNode->getAttribute(Attribute::PARENT_NODE) instanceof Expression) {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
/** @var List_ $listNode */
|
|
|
|
$listNode = $assignNode->var;
|
|
|
|
|
|
|
|
if (count($listNode->items) !== 2) {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
// empty list → cannot handle
|
|
|
|
return $listNode->items[0] === null && $listNode->items[1] === null;
|
2018-10-03 13:14:08 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Is inside the "do {} while ();" loop → need to add "next()"
|
|
|
|
*/
|
|
|
|
private function isInsideDoWhile(Node $assignNode): bool
|
|
|
|
{
|
|
|
|
$parentNode = $assignNode->getAttribute(Attribute::PARENT_NODE);
|
|
|
|
if (! $parentNode instanceof Expression) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
$parentParentNode = $parentNode->getAttribute(Attribute::PARENT_NODE);
|
|
|
|
|
|
|
|
return $parentParentNode instanceof Do_;
|
|
|
|
}
|
2018-10-07 09:53:59 +00:00
|
|
|
|
2018-10-31 15:34:37 +00:00
|
|
|
private function isListToEachAssign(Assign $assignNode): bool
|
2018-10-07 09:53:59 +00:00
|
|
|
{
|
2018-10-31 15:34:37 +00:00
|
|
|
if (! $assignNode->var instanceof List_) {
|
|
|
|
return false;
|
2018-10-07 09:53:59 +00:00
|
|
|
}
|
|
|
|
|
2018-10-31 15:34:37 +00:00
|
|
|
return $assignNode->expr instanceof FuncCall && $this->isName($assignNode->expr, 'each');
|
2018-10-07 09:53:59 +00:00
|
|
|
}
|
2018-10-03 13:14:08 +00:00
|
|
|
}
|