Skip to content

Commit

Permalink
Rule for checking a dynamic call on static methods
Browse files Browse the repository at this point in the history
  • Loading branch information
lookyman authored and ondrejmirtes committed Dec 28, 2017
1 parent e4a2045 commit 2091311
Show file tree
Hide file tree
Showing 5 changed files with 178 additions and 0 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
* Functions `in_array` and `array_search` must be called with third parameter `$strict` set to `true` to search values with matching types only.
* Variables assigned in `while` loop condition and `for` loop initial assignment cannot be used after the loop.
* Types in `switch` condition and `case` value must match. PHP compares them loosely by default and that can lead to unexpected results.
* Statically declared methods are called statically.

Additional rules are coming in subsequent releases!

Expand Down
5 changes: 5 additions & 0 deletions rules.neon
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,11 @@ services:
tags:
- phpstan.rules.rule

-
class: PHPStan\Rules\StrictCalls\DynamicCallOnStaticMethodsRule
tags:
- phpstan.rules.rule

-
class: PHPStan\Rules\StrictCalls\StrictFunctionCallsRule
tags:
Expand Down
63 changes: 63 additions & 0 deletions src/Rules/StrictCalls/DynamicCallOnStaticMethodsRule.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
<?php declare(strict_types = 1);

namespace PHPStan\Rules\StrictCalls;

use PhpParser\Node;
use PhpParser\Node\Expr\MethodCall;
use PHPStan\Analyser\Scope;
use PHPStan\Rules\RuleLevelHelper;
use PHPStan\Type\ErrorType;

class DynamicCallOnStaticMethodsRule implements \PHPStan\Rules\Rule
{

/**
* @var \PHPStan\Rules\RuleLevelHelper
*/
private $ruleLevelHelper;

public function __construct(RuleLevelHelper $ruleLevelHelper)
{
$this->ruleLevelHelper = $ruleLevelHelper;
}

public function getNodeType(): string
{
return MethodCall::class;
}

/**
* @param \PhpParser\Node\Expr\MethodCall $node
* @param \PHPStan\Analyser\Scope $scope
* @return string[]
*/
public function processNode(Node $node, Scope $scope): array
{
if (!is_string($node->name)) {
return [];
}

$name = $node->name;
$type = $this->ruleLevelHelper->findTypeToCheck(
$scope,
$node->var,
''
)->getType();

if ($type instanceof ErrorType || !$type->canCallMethods() || !$type->hasMethod($name)) {
return [];
}

$methodReflection = $type->getMethod($name, $scope);
if ($methodReflection->isStatic()) {
return [sprintf(
'Dynamic call to static method %s::%s().',
$methodReflection->getDeclaringClass()->getDisplayName(),
$methodReflection->getName()
)];
}

return [];
}

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

namespace PHPStan\Rules\StrictCalls;

use PHPStan\Rules\Rule;
use PHPStan\Rules\RuleLevelHelper;

class DynamicCallOnStaticMethodsRuleTest extends \PHPStan\Testing\RuleTestCase
{

/**
* @var bool
*/
private $checkThisOnly;

protected function getRule(): Rule
{
$broker = $this->createBroker();
$ruleLevelHelper = new RuleLevelHelper($broker, true, $this->checkThisOnly, true);
return new DynamicCallOnStaticMethodsRule($ruleLevelHelper);
}

public function testRule(): void
{
$this->checkThisOnly = false;
$this->analyse([__DIR__ . '/data/dynamic-calls-on-static-methods.php'], [
[
'Dynamic call to static method StrictCalls\ClassWithStaticMethod::foo().',
14,
],
[
'Dynamic call to static method StrictCalls\ClassWithStaticMethod::foo().',
21,
],
[
'Dynamic call to static method StrictCalls\ClassUsingTrait::foo().',
34,
],
[
'Dynamic call to static method StrictCalls\ClassUsingTrait::foo().',
46,
],
]);
}

public function testRuleOnThisOnly(): void
{
$this->checkThisOnly = true;
$this->analyse([__DIR__ . '/data/dynamic-calls-on-static-methods.php'], [
[
'Dynamic call to static method StrictCalls\ClassWithStaticMethod::foo().',
14,
],
[
'Dynamic call to static method StrictCalls\ClassUsingTrait::foo().',
34,
],
]);
}

}
48 changes: 48 additions & 0 deletions tests/Rules/StrictCalls/data/dynamic-calls-on-static-methods.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
<?php

namespace StrictCalls;

class ClassWithStaticMethod
{
public static function foo()
{

}

public function bar()
{
$this->foo();
$this->bar();
}
}

function () {
$classWithStaticMethod = new ClassWithStaticMethod();
$classWithStaticMethod->foo();
$classWithStaticMethod->bar();
};

trait TraitWithStaticMethod
{
public static function foo()
{

}

public function bar()
{
$this->foo();
$this->bar();
}
}

class ClassUsingTrait
{
use TraitWithStaticMethod;
}

function () {
$classUsingTrait = new ClassUsingTrait();
$classUsingTrait->foo();
$classUsingTrait->bar();
};

5 comments on commit 2091311

@o5
Copy link

@o5 o5 commented on 2091311 Jun 27, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@lookyman @ondrejmirtes:

What is the best way with PHPUnit and well-known Dynamic call to static method PHPUnit\Framework\Assert::assert*()?

Should I manually add item to ignoreErrors in every project with phpstan/phpstan-strict-rules?

Thank you.

@ondrejmirtes
Copy link
Member

@ondrejmirtes ondrejmirtes commented on 2091311 Jun 27, 2018 via email

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@o5
Copy link

@o5 o5 commented on 2091311 Jun 27, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And what about suppress these errors in phpstan/phpstan-phpunit?

@Majkl578
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not a good idea. I.e. I use phpstan-phpunit and I don't want dynamic calls to static methods. :)

@ondrejmirtes
Copy link
Member

@ondrejmirtes ondrejmirtes commented on 2091311 Jun 27, 2018 via email

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sign in to comment.