Skip to content

Commit 3cefafa

Browse files
committed
working
1 parent 885ba02 commit 3cefafa

File tree

7 files changed

+210
-2
lines changed

7 files changed

+210
-2
lines changed

composer.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,8 @@
9898
"tests/debug_functions.php",
9999
"rules-tests/Transform/Rector/FuncCall/FuncCallToMethodCallRector/Source/some_view_function.php",
100100
"rules-tests/TypeDeclaration/Rector/ClassMethod/ParamTypeByMethodCallTypeRector/Source/FunctionTyped.php",
101-
"rules-tests/Php70/Rector/ClassMethod/Php4ConstructorRector/Source/ParentClass.php"
101+
"rules-tests/Php70/Rector/ClassMethod/Php4ConstructorRector/Source/ParentClass.php",
102+
"rules-tests/CodingStyle/Rector/Closure/StaticClosureRector/Source/functions.php"
102103
]
103104
},
104105
"scripts": {
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
<?php
2+
3+
namespace Rector\Tests\CodingStyle\Rector\Closure\StaticClosureRector\Fixture;
4+
5+
use Closure;
6+
use Rector\Tests\CodingStyle\Rector\Closure\StaticClosureRector\Source\BindObject;
7+
8+
class FixtureWithNamedThisBinding
9+
{
10+
public function first()
11+
{
12+
return BindObject::call(closure: function () {
13+
return 'bar';
14+
}, closureTwo: function () {
15+
return 'foo';
16+
});
17+
}
18+
}
19+
20+
?>
21+
-----
22+
<?php
23+
24+
namespace Rector\Tests\CodingStyle\Rector\Closure\StaticClosureRector\Fixture;
25+
26+
use Closure;
27+
use Rector\Tests\CodingStyle\Rector\Closure\StaticClosureRector\Source\BindObject;
28+
29+
class FixtureWithNamedThisBinding
30+
{
31+
public function first()
32+
{
33+
return BindObject::call(closure: function () {
34+
return 'bar';
35+
}, closureTwo: static function () {
36+
return 'foo';
37+
});
38+
}
39+
}
40+
41+
?>
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\CodingStyle\Rector\Closure\StaticClosureRector\Fixture;
4+
5+
use Rector\Tests\CodingStyle\Rector\Closure\StaticClosureRector\Source\BindObject;
6+
7+
BindObject::call(closure: function () {
8+
echo 'static call';
9+
});
10+
11+
(new BindObject())->callOnObject(function () {
12+
echo 'method call';
13+
});
14+
15+
bind_on_object(function () {
16+
echo 'closure func call 0';
17+
}, null, function () {
18+
echo 'closure func call 0';
19+
});
20+
21+
?>
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
<?php
2+
3+
namespace Rector\Tests\CodingStyle\Rector\Closure\StaticClosureRector\Source;
4+
5+
class BindObject
6+
{
7+
/**
8+
* @param-closure-this \Rector\Tests\CodingStyle\Rector\Closure\StaticClosureRector\Source\BindObject $closure
9+
*/
10+
public static function call(\Closure $closure, \Closure|null $closureTwo = null)
11+
{
12+
13+
}
14+
15+
/**
16+
* @param-closure-this \Rector\Tests\CodingStyle\Rector\Closure\StaticClosureRector\Source\BindObject $closure
17+
*/
18+
public function callOnObject(\Closure $closure)
19+
{
20+
21+
}
22+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
<?php
2+
3+
/**
4+
* @param-closure-this \Rector\Tests\CodingStyle\Rector\Closure\StaticClosureRector\Source\BindObject $closure
5+
* @param-closure-this \Rector\Tests\CodingStyle\Rector\Closure\StaticClosureRector\Source\BindObject $closureFinally
6+
*/
7+
function bind_on_object(Closure|null $closure = null, Closure|null $anotherClosure = null, Closure|null $closureFinally = null)
8+
{
9+
10+
}

rules/CodingStyle/Rector/Closure/StaticClosureRector.php

Lines changed: 46 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,10 @@
55
namespace Rector\CodingStyle\Rector\Closure;
66

77
use PhpParser\Node;
8+
use PhpParser\Node\Expr\CallLike;
89
use PhpParser\Node\Expr\Closure;
910
use Rector\CodingStyle\Guard\StaticGuard;
11+
use Rector\NodeAnalyzer\CallLikeExpectsThisBindedClosureArgsAnalyzer;
1012
use Rector\Rector\AbstractRector;
1113
use Symplify\RuleDocGenerator\ValueObject\CodeSample\CodeSample;
1214
use Symplify\RuleDocGenerator\ValueObject\RuleDefinition;
@@ -16,8 +18,11 @@
1618
*/
1719
final class StaticClosureRector extends AbstractRector
1820
{
21+
private const CLOSURE_USES_THIS = 'has_this_closure';
22+
1923
public function __construct(
20-
private readonly StaticGuard $staticGuard
24+
private readonly StaticGuard $staticGuard,
25+
private readonly CallLikeExpectsThisBindedClosureArgsAnalyzer $callLikeExpectsThisBindedClosureArgsAnalyzer
2126
) {
2227
}
2328

@@ -59,11 +64,51 @@ public function getNodeTypes(): array
5964
return [Closure::class];
6065
}
6166

67+
#[Override]
68+
public function beforeTraverse(array $nodes): array
69+
{
70+
parent::beforeTraverse($nodes);
71+
72+
$this->traverseNodesWithCallable($nodes, function (Node $node): ?CallLike {
73+
if (
74+
! $node instanceof Node\Expr\MethodCall
75+
&& ! $node instanceof Node\Expr\StaticCall
76+
&& ! $node instanceof Node\Expr\FuncCall
77+
) {
78+
return null;
79+
}
80+
81+
if ($node->isFirstClassCallable()) {
82+
return null;
83+
}
84+
85+
$args = $this->callLikeExpectsThisBindedClosureArgsAnalyzer->getArgsUsingThisBindedClosure($node);
86+
87+
if ($args === []) {
88+
return null;
89+
}
90+
91+
foreach ($args as $arg) {
92+
if ($arg->value instanceof Closure && ! $arg->hasAttribute(self::CLOSURE_USES_THIS)) {
93+
$arg->value->setAttribute(self::CLOSURE_USES_THIS, true);
94+
}
95+
}
96+
97+
return $node;
98+
});
99+
100+
return $nodes;
101+
}
102+
62103
/**
63104
* @param Closure $node
64105
*/
65106
public function refactor(Node $node): ?Node
66107
{
108+
if ($node->hasAttribute(self::CLOSURE_USES_THIS)) {
109+
return null;
110+
}
111+
67112
if (! $this->staticGuard->isLegal($node)) {
68113
return null;
69114
}
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
<?php
2+
3+
namespace Rector\NodeAnalyzer;
4+
5+
use PhpParser\Node\Arg;
6+
use PhpParser\Node\Expr\CallLike;
7+
use PhpParser\Node\Expr\Closure;
8+
use Rector\NodeTypeResolver\PHPStan\ParametersAcceptorSelectorVariantsWrapper;
9+
use Rector\PHPStan\ScopeFetcher;
10+
use Rector\Reflection\ReflectionResolver;
11+
use PHPStan\Reflection\ParameterReflectionWithPhpDocs;
12+
13+
class CallLikeExpectsThisBindedClosureArgsAnalyzer
14+
{
15+
public function __construct(private ReflectionResolver $reflectionResolver)
16+
{
17+
}
18+
19+
public function getArgsUsingThisBindedClosure(CallLike $callLike): array
20+
{
21+
/** @var Arg[] $args */
22+
$args = [];
23+
$reflection = $this->reflectionResolver->resolveFunctionLikeReflectionFromCall($callLike);
24+
25+
if ($reflection === null) {
26+
return [];
27+
}
28+
29+
$scope = ScopeFetcher::fetch($callLike);
30+
31+
$parametersAcceptor = ParametersAcceptorSelectorVariantsWrapper::select($reflection, $callLike, $scope);
32+
$parameters = $parametersAcceptor->getParameters();
33+
34+
foreach ($callLike->getArgs() as $index => $arg) {
35+
36+
if (! $arg->value instanceof Closure) {
37+
continue;
38+
}
39+
40+
if ($arg->name?->name !== null) {
41+
/** @var ParameterReflectionWithPhpDocs $parameter */
42+
foreach ($parameters as $parameter) {
43+
$hasObjectBinding = (bool) $parameter->getClosureThisType();
44+
if ($hasObjectBinding && $arg->name->name === $parameter->getName()) {
45+
$args[] = $arg;
46+
}
47+
}
48+
49+
continue;
50+
}
51+
52+
if ($arg->name?->name === null) {
53+
/** @var ParameterReflectionWithPhpDocs $parameter */
54+
$parameter = $parameters[$index] ?? null;
55+
56+
if ($parameter === null) {
57+
continue;
58+
}
59+
60+
if ($parameter->getClosureThisType() !== null) {
61+
$args[] = $arg;
62+
}
63+
}
64+
}
65+
66+
return $args;
67+
}
68+
}

0 commit comments

Comments
 (0)