Skip to content

Commit bad2607

Browse files
committed
Bleeding edge - private method called through static::
1 parent 716fe52 commit bad2607

File tree

8 files changed

+154
-1
lines changed

8 files changed

+154
-1
lines changed

conf/bleedingEdge.neon

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,5 +25,6 @@ parameters:
2525
crossCheckInterfaces: true
2626
finalByPhpDocTag: true
2727
classConstants: true
28+
privateStaticCall: true
2829
stubFiles:
2930
- ../stubs/arrayFunctions.stub

conf/config.level2.neon

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ parameters:
99
conditionalTags:
1010
PHPStan\Rules\PhpDoc\IncompatibleClassConstantPhpDocTypeRule:
1111
phpstan.rules.rule: %featureToggles.classConstants%
12+
PHPStan\Rules\Methods\CallPrivateMethodThroughStaticRule:
13+
phpstan.rules.rule: %featureToggles.privateStaticCall%
1214

1315
rules:
1416
- PHPStan\Rules\Cast\EchoRule
@@ -54,6 +56,8 @@ services:
5456
crossCheckInterfaces: %featureToggles.crossCheckInterfaces%
5557
tags:
5658
- phpstan.rules.rule
59+
-
60+
class: PHPStan\Rules\Methods\CallPrivateMethodThroughStaticRule
5761
-
5862
class: PHPStan\Rules\PhpDoc\IncompatibleClassConstantPhpDocTypeRule
5963
-

conf/config.neon

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ parameters:
4949
crossCheckInterfaces: false
5050
finalByPhpDocTag: false
5151
classConstants: false
52+
privateStaticCall: false
5253
fileExtensions:
5354
- php
5455
checkAlwaysTrueCheckTypeFunctionCall: false
@@ -227,7 +228,8 @@ parametersSchema:
227228
validateOverridingMethodsInStubs: bool(),
228229
crossCheckInterfaces: bool(),
229230
finalByPhpDocTag: bool(),
230-
classConstants: bool()
231+
classConstants: bool(),
232+
privateStaticCall: bool()
231233
])
232234
fileExtensions: listOf(string())
233235
checkAlwaysTrueCheckTypeFunctionCall: bool()
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace PHPStan\Rules\Methods;
4+
5+
use PhpParser\Node;
6+
use PhpParser\Node\Expr\StaticCall;
7+
use PhpParser\Node\Name;
8+
use PHPStan\Analyser\Scope;
9+
use PHPStan\Rules\Rule;
10+
use PHPStan\Rules\RuleErrorBuilder;
11+
12+
/**
13+
* @implements Rule<StaticCall>
14+
*/
15+
class CallPrivateMethodThroughStaticRule implements Rule
16+
{
17+
18+
public function getNodeType(): string
19+
{
20+
return StaticCall::class;
21+
}
22+
23+
public function processNode(Node $node, Scope $scope): array
24+
{
25+
if (!$node->name instanceof Node\Identifier) {
26+
return [];
27+
}
28+
if (!$node->class instanceof Name) {
29+
return [];
30+
}
31+
32+
$methodName = $node->name->name;
33+
$className = $node->class;
34+
if ($className->toLowerString() !== 'static') {
35+
return [];
36+
}
37+
38+
$classType = $scope->resolveTypeByName($className);
39+
if (!$classType->hasMethod($methodName)->yes()) {
40+
return [];
41+
}
42+
43+
$method = $classType->getMethod($methodName, $scope);
44+
if (!$method->isPrivate()) {
45+
return [];
46+
}
47+
48+
if ($scope->isInClass() && $scope->getClassReflection()->isFinal()) {
49+
return [];
50+
}
51+
52+
return [
53+
RuleErrorBuilder::message(sprintf(
54+
'Unsafe call to private method %s::%s() through static::.',
55+
$method->getDeclaringClass()->getDisplayName(),
56+
$method->getName()
57+
))->build(),
58+
];
59+
}
60+
61+
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace PHPStan\Rules\Methods;
4+
5+
use PHPStan\Testing\RuleTestCase;
6+
7+
/**
8+
* @extends RuleTestCase<CallPrivateMethodThroughStaticRule>
9+
*/
10+
class CallPrivateMethodThroughStaticRuleTest extends RuleTestCase
11+
{
12+
13+
protected function getRule(): \PHPStan\Rules\Rule
14+
{
15+
return new CallPrivateMethodThroughStaticRule();
16+
}
17+
18+
public function testRule(): void
19+
{
20+
$this->analyse([__DIR__ . '/data/call-private-method-static.php'], [
21+
[
22+
'Unsafe call to private method CallPrivateMethodThroughStatic\Foo::doBar() through static::.',
23+
12,
24+
],
25+
]);
26+
}
27+
28+
}

tests/PHPStan/Rules/Methods/CallStaticMethodsRuleTest.php

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -225,6 +225,10 @@ public function testCallStaticMethods(): void
225225
328,
226226
'Learn more at https://phpstan.org/user-guide/discovering-symbols',
227227
],
228+
[
229+
'Call to an undefined static method static(CallStaticMethods\CallWithStatic)::nonexistent().',
230+
344,
231+
],
228232
]);
229233
}
230234

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
<?php
2+
3+
namespace CallPrivateMethodThroughStatic;
4+
5+
class Foo
6+
{
7+
8+
public function doFoo()
9+
{
10+
static::doFoo();
11+
static::nonexistent();
12+
static::doBar();
13+
}
14+
15+
private function doBar()
16+
{
17+
18+
}
19+
20+
}
21+
22+
final class Bar
23+
{
24+
25+
public function doFoo()
26+
{
27+
static::doFoo();
28+
static::nonexistent();
29+
static::doBar();
30+
}
31+
32+
private function doBar()
33+
{
34+
35+
}
36+
37+
}

tests/PHPStan/Rules/Methods/data/call-static-methods.php

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -329,3 +329,19 @@ public function doBar(TraitWithStaticMethod $a): void
329329
}
330330

331331
}
332+
333+
class CallWithStatic
334+
{
335+
336+
private function doFoo()
337+
{
338+
339+
}
340+
341+
public function doBar()
342+
{
343+
static::doFoo(); // reported by different rule
344+
static::nonexistent();
345+
}
346+
347+
}

0 commit comments

Comments
 (0)