2019-10-13 05:59:52 +00:00
|
|
|
<?php
|
|
|
|
|
|
|
|
declare(strict_types=1);
|
2019-04-04 12:08:55 +00:00
|
|
|
|
|
|
|
namespace Rector\CodeQuality\Rector\Array_;
|
|
|
|
|
|
|
|
use PhpParser\Node;
|
2019-04-17 19:17:29 +00:00
|
|
|
use PhpParser\Node\Arg;
|
|
|
|
use PhpParser\Node\Expr\Array_;
|
2020-12-21 02:12:42 +00:00
|
|
|
use PhpParser\Node\Expr\ArrayItem;
|
2019-12-30 14:04:45 +00:00
|
|
|
use PhpParser\Node\Expr\FuncCall;
|
2019-05-07 14:52:00 +00:00
|
|
|
use PhpParser\Node\Expr\PropertyFetch;
|
2019-04-17 19:17:29 +00:00
|
|
|
use PhpParser\Node\Expr\Variable;
|
2019-05-07 14:52:00 +00:00
|
|
|
use PhpParser\Node\Scalar\String_;
|
2021-02-28 07:47:48 +00:00
|
|
|
use PHPStan\Reflection\Php\PhpMethodReflection;
|
2021-01-16 21:45:18 +00:00
|
|
|
use Rector\CodeQuality\NodeAnalyzer\CallableClassMethodMatcher;
|
|
|
|
use Rector\CodeQuality\NodeFactory\AnonymousFunctionFactory;
|
2020-02-06 21:48:18 +00:00
|
|
|
use Rector\Core\Rector\AbstractRector;
|
2019-11-07 20:37:39 +00:00
|
|
|
use Rector\NodeTypeResolver\Node\AttributeKey;
|
2020-11-16 17:50:38 +00:00
|
|
|
use Symplify\RuleDocGenerator\ValueObject\CodeSample\CodeSample;
|
|
|
|
use Symplify\RuleDocGenerator\ValueObject\RuleDefinition;
|
2019-04-04 12:08:55 +00:00
|
|
|
|
|
|
|
/**
|
2021-04-10 18:47:17 +00:00
|
|
|
* @changelog https://www.php.net/manual/en/language.types.callable.php#117260
|
2019-04-04 12:08:55 +00:00
|
|
|
* @see https://3v4l.org/MsMbQ
|
2019-04-06 07:23:53 +00:00
|
|
|
* @see https://3v4l.org/KM1Ji
|
2019-09-04 12:10:29 +00:00
|
|
|
*
|
2021-03-12 22:20:25 +00:00
|
|
|
* @see \Rector\Tests\CodeQuality\Rector\Array_\CallableThisArrayToAnonymousFunctionRector\CallableThisArrayToAnonymousFunctionRectorTest
|
2019-04-04 12:08:55 +00:00
|
|
|
*/
|
|
|
|
final class CallableThisArrayToAnonymousFunctionRector extends AbstractRector
|
|
|
|
{
|
2021-01-16 21:45:18 +00:00
|
|
|
/**
|
|
|
|
* @var CallableClassMethodMatcher
|
|
|
|
*/
|
|
|
|
private $callableClassMethodMatcher;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @var AnonymousFunctionFactory
|
|
|
|
*/
|
|
|
|
private $anonymousFunctionFactory;
|
|
|
|
|
2021-01-19 23:29:52 +00:00
|
|
|
public function __construct(
|
|
|
|
CallableClassMethodMatcher $callableClassMethodMatcher,
|
|
|
|
AnonymousFunctionFactory $anonymousFunctionFactory
|
|
|
|
) {
|
2021-01-16 21:45:18 +00:00
|
|
|
$this->callableClassMethodMatcher = $callableClassMethodMatcher;
|
|
|
|
$this->anonymousFunctionFactory = $anonymousFunctionFactory;
|
|
|
|
}
|
|
|
|
|
2020-11-16 17:50:38 +00:00
|
|
|
public function getRuleDefinition(): RuleDefinition
|
2019-04-04 12:08:55 +00:00
|
|
|
{
|
2020-11-16 17:50:38 +00:00
|
|
|
return new RuleDefinition(
|
|
|
|
'Convert [$this, "method"] to proper anonymous function',
|
|
|
|
[
|
|
|
|
new CodeSample(
|
|
|
|
<<<'CODE_SAMPLE'
|
2019-04-04 12:08:55 +00:00
|
|
|
class SomeClass
|
|
|
|
{
|
|
|
|
public function run()
|
|
|
|
{
|
|
|
|
$values = [1, 5, 3];
|
|
|
|
usort($values, [$this, 'compareSize']);
|
|
|
|
|
|
|
|
return $values;
|
|
|
|
}
|
|
|
|
|
|
|
|
private function compareSize($first, $second)
|
|
|
|
{
|
|
|
|
return $first <=> $second;
|
|
|
|
}
|
|
|
|
}
|
2020-09-15 08:23:13 +00:00
|
|
|
CODE_SAMPLE
|
2020-11-16 17:50:38 +00:00
|
|
|
,
|
|
|
|
<<<'CODE_SAMPLE'
|
2019-04-04 12:08:55 +00:00
|
|
|
class SomeClass
|
|
|
|
{
|
|
|
|
public function run()
|
|
|
|
{
|
|
|
|
$values = [1, 5, 3];
|
|
|
|
usort($values, function ($first, $second) {
|
|
|
|
return $this->compareSize($first, $second);
|
|
|
|
});
|
|
|
|
|
|
|
|
return $values;
|
|
|
|
}
|
|
|
|
|
|
|
|
private function compareSize($first, $second)
|
|
|
|
{
|
|
|
|
return $first <=> $second;
|
|
|
|
}
|
|
|
|
}
|
2020-09-15 08:23:13 +00:00
|
|
|
CODE_SAMPLE
|
2021-05-06 18:51:25 +00:00
|
|
|
),
|
|
|
|
]
|
|
|
|
);
|
2019-04-04 12:08:55 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2021-02-27 00:06:15 +00:00
|
|
|
* @return array<class-string<Node>>
|
2019-04-04 12:08:55 +00:00
|
|
|
*/
|
|
|
|
public function getNodeTypes(): array
|
|
|
|
{
|
2019-04-17 19:17:29 +00:00
|
|
|
return [Array_::class];
|
2019-04-04 12:08:55 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2019-04-30 22:49:59 +00:00
|
|
|
* @param Array_ $node
|
2019-04-04 12:08:55 +00:00
|
|
|
*/
|
|
|
|
public function refactor(Node $node): ?Node
|
|
|
|
{
|
2019-05-07 13:49:21 +00:00
|
|
|
if ($this->shouldSkipArray($node)) {
|
2019-04-04 12:08:55 +00:00
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
2020-12-21 02:12:42 +00:00
|
|
|
$firstArrayItem = $node->items[0];
|
|
|
|
if (! $firstArrayItem instanceof ArrayItem) {
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
|
|
|
$objectVariable = $firstArrayItem->value;
|
2019-05-07 13:49:21 +00:00
|
|
|
if (! $objectVariable instanceof Variable && ! $objectVariable instanceof PropertyFetch) {
|
2019-05-07 13:37:50 +00:00
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
2020-12-21 02:12:42 +00:00
|
|
|
$secondArrayItem = $node->items[1];
|
|
|
|
if (! $secondArrayItem instanceof ArrayItem) {
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
|
|
|
$methodName = $secondArrayItem->value;
|
2019-05-07 13:49:21 +00:00
|
|
|
if (! $methodName instanceof String_) {
|
|
|
|
return null;
|
|
|
|
}
|
2019-04-04 12:08:55 +00:00
|
|
|
|
2021-02-28 07:47:48 +00:00
|
|
|
$phpMethodReflection = $this->callableClassMethodMatcher->match($objectVariable, $methodName);
|
|
|
|
if (! $phpMethodReflection instanceof PhpMethodReflection) {
|
2019-04-04 12:08:55 +00:00
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
2021-02-28 07:47:48 +00:00
|
|
|
return $this->anonymousFunctionFactory->create($phpMethodReflection, $objectVariable);
|
2019-04-04 12:08:55 +00:00
|
|
|
}
|
|
|
|
|
2019-10-30 09:49:07 +00:00
|
|
|
private function shouldSkipArray(Array_ $array): bool
|
2019-04-04 12:08:55 +00:00
|
|
|
{
|
2019-10-30 09:49:07 +00:00
|
|
|
// callback is exactly "[$two, 'items']"
|
2020-12-25 00:22:45 +00:00
|
|
|
if (count($array->items) !== 2) {
|
2019-10-30 09:49:07 +00:00
|
|
|
return true;
|
2019-04-04 12:08:55 +00:00
|
|
|
}
|
|
|
|
|
2019-10-30 09:49:07 +00:00
|
|
|
// can be totally empty in case of "[, $value]"
|
2019-12-30 14:04:45 +00:00
|
|
|
if ($array->items[0] === null) {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
if ($array->items[1] === null) {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2021-03-31 08:04:42 +00:00
|
|
|
return $this->isCallbackAtFunctionNames($array, ['register_shutdown_function', 'forward_static_call']);
|
2019-04-04 12:08:55 +00:00
|
|
|
}
|
2019-04-06 07:23:53 +00:00
|
|
|
|
2021-03-31 08:04:42 +00:00
|
|
|
/**
|
|
|
|
* @param string[] $functionNames
|
|
|
|
*/
|
|
|
|
private function isCallbackAtFunctionNames(Array_ $array, array $functionNames): bool
|
2020-01-05 18:33:35 +00:00
|
|
|
{
|
2020-02-01 16:04:38 +00:00
|
|
|
$parentNode = $array->getAttribute(AttributeKey::PARENT_NODE);
|
|
|
|
if (! $parentNode instanceof Arg) {
|
|
|
|
return false;
|
2020-01-05 18:33:35 +00:00
|
|
|
}
|
|
|
|
|
2020-02-01 16:04:38 +00:00
|
|
|
$parentParentNode = $parentNode->getAttribute(AttributeKey::PARENT_NODE);
|
|
|
|
if (! $parentParentNode instanceof FuncCall) {
|
|
|
|
return false;
|
2019-10-30 09:49:07 +00:00
|
|
|
}
|
|
|
|
|
2021-03-31 08:04:42 +00:00
|
|
|
return $this->isNames($parentParentNode, $functionNames);
|
2019-10-30 09:49:07 +00:00
|
|
|
}
|
2019-04-04 12:08:55 +00:00
|
|
|
}
|