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
@@ -0,0 +1,46 @@
<?php declare(strict_types = 1);

namespace PHPStan\Rules\Methods;

use PHPStan\Rules\Rule;
use PHPStan\Testing\RuleTestCase;

/**
* @extends RuleTestCase<CallMethodsRule>
*/
class CallMethodsRuleClosureThisSubclassTest extends RuleTestCase
{

public static function getAdditionalConfigFiles(): array
{
return [__DIR__ . '/closure-this-subclass.neon'];
}

protected function getRule(): Rule
{
return self::getContainer()->getByType(CallMethodsRule::class);
}

public function testClosureThisSubclassScope(): void
{
$this->analyse([__DIR__ . '/data/closure-this-subclass.php'], [
[
'Call to private method privateHelper() of class ClosureThisSubclass\\BaseTestCase.',
31,
],
[
'Call to private method privateHelper() of class ClosureThisSubclass\\BaseTestCase.',
37,
],
[
'Call to protected method protectedHelper() of class ClosureThisSubclass\\BaseTestCase.',
49,
],
[
'Call to private method privateHelper() of class ClosureThisSubclass\\BaseTestCase.',
50,
],
]);
}

}
45 changes: 45 additions & 0 deletions tests/PHPStan/Rules/Methods/ClosureThisSubclassExtension.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
<?php declare(strict_types = 1);

namespace PHPStan\Rules\Methods;

use PhpParser\Node\Expr\FuncCall;
use PhpParser\Node\Name\FullyQualified;
use PhpParser\Node\Stmt\Class_;
use PHPStan\Analyser\DependencyTracker;
use PHPStan\Analyser\Scope;
use PHPStan\Reflection\FunctionReflection;
use PHPStan\Reflection\ParameterReflection;
use PHPStan\Reflection\ReflectionProvider;
use PHPStan\Type\FunctionParameterClosureThisExtension;
use PHPStan\Type\ObjectType;
use PHPStan\Type\Type;
use function in_array;

class ClosureThisSubclassExtension implements FunctionParameterClosureThisExtension
{

public function __construct(private ReflectionProvider $reflectionProvider)
{
}

public function isFunctionSupported(FunctionReflection $functionReflection, ParameterReflection $parameter): bool
{
return in_array($functionReflection->getName(), ['ClosureThisSubclass\\runInSubclass', 'ClosureThisSubclass\\runInBase'], true)
&& $parameter->getName() === 'callback';
}

public function getClosureThisTypeFromFunctionCall(FunctionReflection $functionReflection, FuncCall $functionCall, ParameterReflection $parameter, Scope&DependencyTracker $scope): ?Type
{
if ($functionReflection->getName() === 'ClosureThisSubclass\\runInBase') {
return new ObjectType('ClosureThisSubclass\\BaseTestCase');
}

$classNode = new Class_(null, [
'extends' => new FullyQualified('ClosureThisSubclass\\BaseTestCase'),
], $functionCall->getAttributes());
$classReflection = $this->reflectionProvider->getAnonymousClassReflection($classNode, $scope);

return new ObjectType($classReflection->getName(), classReflection: $classReflection);
}

}
5 changes: 5 additions & 0 deletions tests/PHPStan/Rules/Methods/closure-this-subclass.neon
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
services:
-
class: PHPStan\Rules\Methods\ClosureThisSubclassExtension
tags:
- phpstan.functionParameterClosureThisExtension
51 changes: 51 additions & 0 deletions tests/PHPStan/Rules/Methods/data/closure-this-subclass.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
<?php

namespace ClosureThisSubclass;

class BaseTestCase
{

public function publicHelper(): string
{
return 'public';
}

protected function protectedHelper(): string
{
return 'protected';
}

private function privateHelper(): string
{
return 'private';
}

}

function runInSubclass(\Closure $callback): void {}
function runInBase(\Closure $callback): void {}

runInSubclass(function (): void {
$this->publicHelper();
$this->protectedHelper();
$this->privateHelper();
});

runInSubclass(fn (): array => [
$this->publicHelper(),
$this->protectedHelper(),
$this->privateHelper(),
]);

runInBase(function (): void {
$this->publicHelper();
$this->protectedHelper();
$this->privateHelper();
});

function outsideClosure(BaseTestCase $testCase): void
{
$testCase->publicHelper();
$testCase->protectedHelper();
$testCase->privateHelper();
}
Loading