Skip to content

Commit bb3f543

Browse files
committed
Rule for checking a dynamic call on static methods
1 parent 15be909 commit bb3f543

File tree

5 files changed

+178
-0
lines changed

5 files changed

+178
-0
lines changed

Diff for: README.md

+1
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
* Functions `in_array` and `array_search` must be called with third parameter `$strict` set to `true` to search values with matching types only.
1111
* Variables assigned in `while` loop condition and `for` loop initial assignment cannot be used after the loop.
1212
* Types in `switch` condition and `case` value must match. PHP compares them loosely by default and that can lead to unexpected results.
13+
* Statically declared methods are called statically.
1314

1415
Additional rules are coming in subsequent releases!
1516

Diff for: rules.neon

+5
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,11 @@ services:
3232
tags:
3333
- phpstan.rules.rule
3434

35+
-
36+
class: PHPStan\Rules\StrictCalls\DynamicCallOnStaticMethodsRule
37+
tags:
38+
- phpstan.rules.rule
39+
3540
-
3641
class: PHPStan\Rules\StrictCalls\StrictFunctionCallsRule
3742
tags:
+67
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace PHPStan\Rules\StrictCalls;
4+
5+
use PhpParser\Node;
6+
use PhpParser\Node\Expr\MethodCall;
7+
use PHPStan\Analyser\Scope;
8+
use PHPStan\Rules\RuleLevelHelper;
9+
use PHPStan\Type\ErrorType;
10+
11+
class DynamicCallOnStaticMethodsRule implements \PHPStan\Rules\Rule
12+
{
13+
14+
/**
15+
* @var \PHPStan\Rules\RuleLevelHelper
16+
*/
17+
private $ruleLevelHelper;
18+
19+
public function __construct(RuleLevelHelper $ruleLevelHelper)
20+
{
21+
$this->ruleLevelHelper = $ruleLevelHelper;
22+
}
23+
24+
public function getNodeType(): string
25+
{
26+
return MethodCall::class;
27+
}
28+
29+
/**
30+
* @param \PhpParser\Node\Expr\MethodCall $node
31+
* @param \PHPStan\Analyser\Scope $scope
32+
* @return string[]
33+
*/
34+
public function processNode(Node $node, Scope $scope): array
35+
{
36+
if (!is_string($node->name)) {
37+
return [];
38+
}
39+
40+
$name = $node->name;
41+
$type = $this->ruleLevelHelper->findTypeToCheck(
42+
$scope,
43+
$node->var,
44+
''
45+
)->getType();
46+
47+
if ($type instanceof ErrorType || !$type->canCallMethods() || !$type->hasMethod($name)) {
48+
return [];
49+
}
50+
51+
$methodReflection = $type->getMethod($name, $scope);
52+
if (!$scope->canCallMethod($methodReflection)) {
53+
return [];
54+
}
55+
56+
if ($methodReflection->isStatic()) {
57+
return [sprintf(
58+
'Dynamic call to static method %s::%s().',
59+
$methodReflection->getDeclaringClass()->getDisplayName(),
60+
$methodReflection->getName()
61+
)];
62+
}
63+
64+
return [];
65+
}
66+
67+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace PHPStan\Rules\StrictCalls;
4+
5+
use PHPStan\Rules\Rule;
6+
use PHPStan\Rules\RuleLevelHelper;
7+
8+
class DynamicCallOnStaticMethodsRuleTest extends \PHPStan\Testing\RuleTestCase
9+
{
10+
11+
/**
12+
* @var bool
13+
*/
14+
private $checkThisOnly;
15+
16+
protected function getRule(): Rule
17+
{
18+
$broker = $this->createBroker();
19+
$ruleLevelHelper = new RuleLevelHelper($broker, true, $this->checkThisOnly, true);
20+
return new DynamicCallOnStaticMethodsRule($ruleLevelHelper);
21+
}
22+
23+
public function testRule()
24+
{
25+
$this->checkThisOnly = false;
26+
$this->analyse([__DIR__ . '/data/dynamic-calls-on-static-methods.php'], [
27+
[
28+
'Dynamic call to static method StrictCalls\ClassWithStaticMethod::foo().',
29+
14,
30+
],
31+
[
32+
'Dynamic call to static method StrictCalls\ClassWithStaticMethod::foo().',
33+
20,
34+
],
35+
[
36+
'Dynamic call to static method StrictCalls\ClassUsingTrait::foo().',
37+
32,
38+
],
39+
[
40+
'Dynamic call to static method StrictCalls\ClassUsingTrait::foo().',
41+
43,
42+
],
43+
]);
44+
}
45+
46+
public function testRuleOnThisOnly()
47+
{
48+
$this->checkThisOnly = true;
49+
$this->analyse([__DIR__ . '/data/dynamic-calls-on-static-methods.php'], [
50+
[
51+
'Dynamic call to static method StrictCalls\ClassWithStaticMethod::foo().',
52+
14,
53+
],
54+
[
55+
'Dynamic call to static method StrictCalls\ClassUsingTrait::foo().',
56+
32,
57+
],
58+
]);
59+
}
60+
61+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
<?php
2+
3+
namespace StrictCalls;
4+
5+
class ClassWithStaticMethod
6+
{
7+
public static function foo()
8+
{
9+
10+
}
11+
12+
public function bar()
13+
{
14+
$this->foo();
15+
}
16+
}
17+
18+
function () {
19+
$classWithStaticMethod = new ClassWithStaticMethod();
20+
$classWithStaticMethod->foo();
21+
};
22+
23+
trait TraitWithStaticMethod
24+
{
25+
public static function foo()
26+
{
27+
28+
}
29+
30+
public function bar()
31+
{
32+
$this->foo();
33+
}
34+
}
35+
36+
class ClassUsingTrait
37+
{
38+
use TraitWithStaticMethod;
39+
}
40+
41+
function () {
42+
$classUsingTrait = new ClassUsingTrait();
43+
$classUsingTrait->foo();
44+
};

0 commit comments

Comments
 (0)