Skip to content

Commit 2091311

Browse files
lookymanondrejmirtes
authored andcommitted
Rule for checking a dynamic call on static methods
1 parent e4a2045 commit 2091311

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:
+63
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
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 ($methodReflection->isStatic()) {
53+
return [sprintf(
54+
'Dynamic call to static method %s::%s().',
55+
$methodReflection->getDeclaringClass()->getDisplayName(),
56+
$methodReflection->getName()
57+
)];
58+
}
59+
60+
return [];
61+
}
62+
63+
}
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(): void
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+
21,
34+
],
35+
[
36+
'Dynamic call to static method StrictCalls\ClassUsingTrait::foo().',
37+
34,
38+
],
39+
[
40+
'Dynamic call to static method StrictCalls\ClassUsingTrait::foo().',
41+
46,
42+
],
43+
]);
44+
}
45+
46+
public function testRuleOnThisOnly(): void
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+
34,
57+
],
58+
]);
59+
}
60+
61+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
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+
$this->bar();
16+
}
17+
}
18+
19+
function () {
20+
$classWithStaticMethod = new ClassWithStaticMethod();
21+
$classWithStaticMethod->foo();
22+
$classWithStaticMethod->bar();
23+
};
24+
25+
trait TraitWithStaticMethod
26+
{
27+
public static function foo()
28+
{
29+
30+
}
31+
32+
public function bar()
33+
{
34+
$this->foo();
35+
$this->bar();
36+
}
37+
}
38+
39+
class ClassUsingTrait
40+
{
41+
use TraitWithStaticMethod;
42+
}
43+
44+
function () {
45+
$classUsingTrait = new ClassUsingTrait();
46+
$classUsingTrait->foo();
47+
$classUsingTrait->bar();
48+
};

0 commit comments

Comments
 (0)