Skip to content
3 changes: 2 additions & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,8 @@
"tests/debug_functions.php",
"rules-tests/Transform/Rector/FuncCall/FuncCallToMethodCallRector/Source/some_view_function.php",
"rules-tests/TypeDeclaration/Rector/ClassMethod/ParamTypeByMethodCallTypeRector/Source/FunctionTyped.php",
"rules-tests/Php70/Rector/ClassMethod/Php4ConstructorRector/Source/ParentClass.php"
"rules-tests/Php70/Rector/ClassMethod/Php4ConstructorRector/Source/ParentClass.php",
"rules-tests/CodingStyle/Rector/Closure/StaticClosureRector/Source/functions.php"
]
},
"scripts": {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
<?php

namespace Rector\Tests\CodingStyle\Rector\Closure\StaticClosureRector\Fixture;

use Closure;
use Rector\Tests\CodingStyle\Rector\Closure\StaticClosureRector\Source\BindObject;

class FixtureWithNamedThisBinding
{
public function first()
{
return BindObject::call(closure: function () {
return 'bar';
}, closureTwo: function () {
return 'foo';
});
}
}

?>
-----
<?php

namespace Rector\Tests\CodingStyle\Rector\Closure\StaticClosureRector\Fixture;

use Closure;
use Rector\Tests\CodingStyle\Rector\Closure\StaticClosureRector\Source\BindObject;

class FixtureWithNamedThisBinding
{
public function first()
{
return BindObject::call(closure: function () {
return 'bar';
}, closureTwo: static function () {
return 'foo';
});
}
}

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

namespace Rector\Tests\CodingStyle\Rector\Closure\StaticClosureRector\Fixture;

use Rector\Tests\CodingStyle\Rector\Closure\StaticClosureRector\Source\BindObject;

BindObject::call(closure: function () {
echo 'static call';
});

(new BindObject())->callOnObject(function () {
echo 'method call';
});

bind_on_object(function () {
echo 'closure func call 0';
}, null, function () {
echo 'closure func call 0';
});

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

namespace Rector\Tests\CodingStyle\Rector\Closure\StaticClosureRector\Source;

class BindObject
{
/**
* @param-closure-this \Rector\Tests\CodingStyle\Rector\Closure\StaticClosureRector\Source\BindObject $closure
*/
public static function call(\Closure $closure, \Closure|null $closureTwo = null)
{

}

/**
* @param-closure-this \Rector\Tests\CodingStyle\Rector\Closure\StaticClosureRector\Source\BindObject $closure
*/
public function callOnObject(\Closure $closure)
{

}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<?php

/**
* @param-closure-this \Rector\Tests\CodingStyle\Rector\Closure\StaticClosureRector\Source\BindObject $closure
* @param-closure-this \Rector\Tests\CodingStyle\Rector\Closure\StaticClosureRector\Source\BindObject $closureFinally
*/
function bind_on_object(Closure|null $closure = null, Closure|null $anotherClosure = null, Closure|null $closureFinally = null)
Comment thread
peterfox marked this conversation as resolved.
{

}
7 changes: 6 additions & 1 deletion rules/CodingStyle/Rector/Closure/StaticClosureRector.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
use PhpParser\Node;
use PhpParser\Node\Expr\Closure;
use Rector\CodingStyle\Guard\StaticGuard;
use Rector\NodeTypeResolver\Node\AttributeKey;
use Rector\Rector\AbstractRector;
use Symplify\RuleDocGenerator\ValueObject\CodeSample\CodeSample;
use Symplify\RuleDocGenerator\ValueObject\RuleDefinition;
Expand All @@ -17,7 +18,7 @@
final class StaticClosureRector extends AbstractRector
{
public function __construct(
private readonly StaticGuard $staticGuard
private readonly StaticGuard $staticGuard,
) {
}

Expand Down Expand Up @@ -64,6 +65,10 @@ public function getNodeTypes(): array
*/
public function refactor(Node $node): ?Node
{
if ($node->hasAttribute(AttributeKey::IS_CLOSURE_USES_THIS)) {
return null;
}

if (! $this->staticGuard->isLegal($node)) {
return null;
}
Expand Down
2 changes: 2 additions & 0 deletions src/DependencyInjection/LazyContainerFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,7 @@
use Rector\NodeTypeResolver\PHPStan\Scope\NodeVisitor\AssignedToNodeVisitor;
use Rector\NodeTypeResolver\PHPStan\Scope\NodeVisitor\ByRefReturnNodeVisitor;
use Rector\NodeTypeResolver\PHPStan\Scope\NodeVisitor\ByRefVariableNodeVisitor;
use Rector\NodeTypeResolver\PHPStan\Scope\NodeVisitor\CallLikeThisBoundClosureArgsNodeVisitor;
use Rector\NodeTypeResolver\PHPStan\Scope\NodeVisitor\ClassConstFetchNodeVisitor;
use Rector\NodeTypeResolver\PHPStan\Scope\NodeVisitor\ContextNodeVisitor;
use Rector\NodeTypeResolver\PHPStan\Scope\NodeVisitor\GlobalVariableNodeVisitor;
Expand Down Expand Up @@ -252,6 +253,7 @@ final class LazyContainerFactory
StaticVariableNodeVisitor::class,
PropertyOrClassConstDefaultNodeVisitor::class,
ClassConstFetchNodeVisitor::class,
CallLikeThisBoundClosureArgsNodeVisitor::class,
];

/**
Expand Down
78 changes: 78 additions & 0 deletions src/NodeAnalyzer/CallLikeExpectsThisBindedClosureArgsAnalyzer.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
<?php

declare(strict_types=1);

namespace Rector\NodeAnalyzer;

use PhpParser\Node\Arg;
use PhpParser\Node\Expr\CallLike;
use PhpParser\Node\Expr\Closure;
use PHPStan\Reflection\ExtendedParameterReflection;
use Rector\NodeTypeResolver\PHPStan\ParametersAcceptorSelectorVariantsWrapper;
use Rector\PHPStan\ScopeFetcher;
use Rector\Reflection\ReflectionResolver;

final readonly class CallLikeExpectsThisBindedClosureArgsAnalyzer
{
public function __construct(
private ReflectionResolver $reflectionResolver
) {
}

/**
* @return Arg[]
*/
public function getArgsUsingThisBindedClosure(CallLike $callLike): array
{
$args = [];
$reflection = $this->reflectionResolver->resolveFunctionLikeReflectionFromCall($callLike);

if ($callLike->isFirstClassCallable()) {
return [];
}

if ($reflection === null) {
return [];
}

$scope = ScopeFetcher::fetch($callLike);
Comment thread
samsonasik marked this conversation as resolved.
Outdated
$parametersAcceptor = ParametersAcceptorSelectorVariantsWrapper::select($reflection, $callLike, $scope);
$parameters = $parametersAcceptor->getParameters();

foreach ($callLike->getArgs() as $index => $arg) {
if (! $arg->value instanceof Closure) {
continue;
}

if ($arg->name?->name !== null) {
foreach ($parameters as $parameter) {
if (! $parameter instanceof ExtendedParameterReflection) {
continue;
}

$hasObjectBinding = (bool) $parameter->getClosureThisType();
if ($hasObjectBinding && $arg->name->name === $parameter->getName()) {
$args[] = $arg;
}
}

continue;
}

if (! is_string($arg->name?->name)) {
$parameter = $parameters[$index] ?? null;

if (! $parameter instanceof ExtendedParameterReflection) {
continue;
}

$hasObjectBinding = (bool) $parameter->getClosureThisType();
if ($hasObjectBinding) {
$args[] = $arg;
}
}
}

return $args;
}
}
2 changes: 2 additions & 0 deletions src/NodeTypeResolver/Node/AttributeKey.php
Original file line number Diff line number Diff line change
Expand Up @@ -288,4 +288,6 @@ final class AttributeKey
public const CLASS_CONST_FETCH_NAME = 'class_const_fetch_name';

public const PHP_VERSION_CONDITIONED = 'php_version_conditioned';

public const IS_CLOSURE_USES_THIS = 'has_this_closure';
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
<?php

declare(strict_types=1);

namespace Rector\NodeTypeResolver\PHPStan\Scope\NodeVisitor;

use PhpParser\Node;
use PhpParser\Node\Expr\Closure;
use PhpParser\Node\Expr\FuncCall;
use PhpParser\Node\Expr\MethodCall;
use PhpParser\Node\Expr\StaticCall;
use PhpParser\NodeVisitorAbstract;
use Rector\Contract\PhpParser\DecoratingNodeVisitorInterface;
use Rector\NodeAnalyzer\CallLikeExpectsThisBindedClosureArgsAnalyzer;
use Rector\NodeTypeResolver\Node\AttributeKey;

final class CallLikeThisBoundClosureArgsNodeVisitor extends NodeVisitorAbstract implements DecoratingNodeVisitorInterface
{
public function __construct(
private readonly CallLikeExpectsThisBindedClosureArgsAnalyzer $callLikeExpectsThisBindedClosureArgsAnalyzer
) {
}

public function enterNode(Node $node): ?Node
{
if (
! $node instanceof MethodCall
&& ! $node instanceof StaticCall
&& ! $node instanceof FuncCall
) {
return null;
}

if ($node->isFirstClassCallable()) {
return null;
}

$args = $this->callLikeExpectsThisBindedClosureArgsAnalyzer->getArgsUsingThisBindedClosure($node);

if ($args === []) {
return null;
}

foreach ($args as $arg) {
if ($arg->value instanceof Closure && ! $arg->hasAttribute(AttributeKey::IS_CLOSURE_USES_THIS)) {
$arg->value->setAttribute(AttributeKey::IS_CLOSURE_USES_THIS, true);
}
}

return $node;
}
}
Loading