Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Rules for checking direct calls to __construct()
- Loading branch information
Showing
8 changed files
with
249 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
<?php declare(strict_types = 1); | ||
|
||
namespace PHPStan\Rules\Methods; | ||
|
||
use PhpParser\Node; | ||
use PHPStan\Analyser\Scope; | ||
use PHPStan\Rules\Rule; | ||
use PHPStan\Rules\RuleErrorBuilder; | ||
|
||
/** | ||
* @implements Rule<Node\Expr\MethodCall> | ||
*/ | ||
class IllegalConstructorMethodCallRule implements Rule | ||
{ | ||
|
||
public function getNodeType(): string | ||
{ | ||
return Node\Expr\MethodCall::class; | ||
} | ||
|
||
public function processNode(Node $node, Scope $scope): array | ||
{ | ||
if (!$node->name instanceof Node\Identifier || $node->name->toLowerString() !== '__construct') { | ||
return []; | ||
} | ||
|
||
return [ | ||
RuleErrorBuilder::message('Call to __construct() on an existing object is not allowed.') | ||
->build(), | ||
]; | ||
} | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,58 @@ | ||
<?php declare(strict_types = 1); | ||
|
||
namespace PHPStan\Rules\Methods; | ||
|
||
use PhpParser\Node; | ||
use PHPStan\Analyser\Scope; | ||
use PHPStan\Rules\Rule; | ||
use PHPStan\Rules\RuleErrorBuilder; | ||
|
||
/** | ||
* @implements Rule<Node\Expr\StaticCall> | ||
*/ | ||
class IllegalConstructorStaticCallRule implements Rule | ||
{ | ||
|
||
public function getNodeType(): string | ||
{ | ||
return Node\Expr\StaticCall::class; | ||
} | ||
|
||
public function processNode(Node $node, Scope $scope): array | ||
{ | ||
if (!$node->name instanceof Node\Identifier || $node->name->toLowerString() !== '__construct') { | ||
return []; | ||
} | ||
|
||
if ($this->isCollectCallingConstructor($node, $scope)) { | ||
return []; | ||
} | ||
|
||
return [ | ||
RuleErrorBuilder::message('Static call to __construct() is only allowed on a parent class in the constructor.') | ||
->build(), | ||
]; | ||
} | ||
|
||
private function isCollectCallingConstructor(Node $node, Scope $scope): bool | ||
{ | ||
if (!$node instanceof Node\Expr\StaticCall) { | ||
return true; | ||
} | ||
// __construct should be called from inside constructor | ||
if ($scope->getFunction() !== null && $scope->getFunction()->getName() !== '__construct') { | ||
return false; | ||
} | ||
|
||
if (!$scope->isInClass()) { | ||
return false; | ||
} | ||
|
||
if (!$node->class instanceof Node\Name) { | ||
return false; | ||
} | ||
|
||
return $node->class->toLowerString() === 'parent'; | ||
} | ||
|
||
} |
37 changes: 37 additions & 0 deletions
37
tests/PHPStan/Rules/Methods/IllegalConstructorMethodCallRuleTest.php
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
<?php declare(strict_types = 1); | ||
|
||
namespace PHPStan\Rules\Methods; | ||
|
||
use PHPStan\Rules\Rule; | ||
use PHPStan\Testing\RuleTestCase; | ||
|
||
/** | ||
* @extends RuleTestCase<IllegalConstructorMethodCallRule> | ||
*/ | ||
class IllegalConstructorMethodCallRuleTest extends RuleTestCase | ||
{ | ||
|
||
protected function getRule(): Rule | ||
{ | ||
return new IllegalConstructorMethodCallRule(); | ||
} | ||
|
||
public function testMethods(): void | ||
{ | ||
$this->analyse([__DIR__ . '/data/illegal-constructor-call-rule-test.php'], [ | ||
[ | ||
'Call to __construct() on an existing object is not allowed.', | ||
13, | ||
], | ||
[ | ||
'Call to __construct() on an existing object is not allowed.', | ||
18, | ||
], | ||
[ | ||
'Call to __construct() on an existing object is not allowed.', | ||
60, | ||
], | ||
]); | ||
} | ||
|
||
} |
45 changes: 45 additions & 0 deletions
45
tests/PHPStan/Rules/Methods/IllegalConstructorStaticCallRuleTest.php
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,45 @@ | ||
<?php declare(strict_types = 1); | ||
|
||
namespace PHPStan\Rules\Methods; | ||
|
||
use PHPStan\Rules\Rule; | ||
use PHPStan\Testing\RuleTestCase; | ||
|
||
/** | ||
* @extends RuleTestCase<IllegalConstructorStaticCallRule> | ||
*/ | ||
class IllegalConstructorStaticCallRuleTest extends RuleTestCase | ||
{ | ||
|
||
protected function getRule(): Rule | ||
{ | ||
return new IllegalConstructorStaticCallRule(); | ||
} | ||
|
||
public function testMethods(): void | ||
{ | ||
$this->analyse([__DIR__ . '/data/illegal-constructor-call-rule-test.php'], [ | ||
[ | ||
'Static call to __construct() is only allowed on a parent class in the constructor.', | ||
31, | ||
], | ||
[ | ||
'Static call to __construct() is only allowed on a parent class in the constructor.', | ||
43, | ||
], | ||
[ | ||
'Static call to __construct() is only allowed on a parent class in the constructor.', | ||
44, | ||
], | ||
[ | ||
'Static call to __construct() is only allowed on a parent class in the constructor.', | ||
49, | ||
], | ||
[ | ||
'Static call to __construct() is only allowed on a parent class in the constructor.', | ||
50, | ||
], | ||
]); | ||
} | ||
|
||
} |
63 changes: 63 additions & 0 deletions
63
tests/PHPStan/Rules/Methods/data/illegal-constructor-call-rule-test.php
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,63 @@ | ||
<?php | ||
|
||
namespace IllegalConstructorMethodCall; | ||
|
||
class ExtendedDateTimeWithMethodCall extends \DateTimeImmutable | ||
{ | ||
public function __construct(string $datetime = "now", ?\DateTimeZone $timezone = null) | ||
{ | ||
// Avoid infinite loop | ||
if (count(debug_backtrace()) > 1) { | ||
return; | ||
} | ||
$this->__construct($datetime, $timezone); | ||
} | ||
|
||
public function mutate(string $datetime = "now", ?\DateTimeZone $timezone = null): void | ||
{ | ||
$this->__construct($datetime, $timezone); | ||
} | ||
} | ||
|
||
class ExtendedDateTimeWithParentCall extends \DateTimeImmutable | ||
{ | ||
public function __construct(string $datetime = "now", ?\DateTimeZone $timezone = null) | ||
{ | ||
parent::__construct($datetime, $timezone); | ||
} | ||
|
||
public function mutate(string $datetime = "now", ?\DateTimeZone $timezone = null): void | ||
{ | ||
parent::__construct($datetime, $timezone); | ||
} | ||
} | ||
|
||
class ExtendedDateTimeWithSelfCall extends \DateTimeImmutable | ||
{ | ||
public function __construct(string $datetime = "now", ?\DateTimeZone $timezone = null) | ||
{ | ||
// Avoid infinite loop | ||
if (count(debug_backtrace()) > 1) { | ||
return; | ||
} | ||
self::__construct($datetime, $timezone); | ||
ExtendedDateTimeWithSelfCall::__construct($datetime, $timezone); | ||
} | ||
|
||
public function mutate(string $datetime = "now", ?\DateTimeZone $timezone = null): void | ||
{ | ||
self::__construct($datetime, $timezone); | ||
ExtendedDateTimeWithSelfCall::__construct($datetime, $timezone); | ||
} | ||
} | ||
|
||
class Foo | ||
{ | ||
|
||
public function doFoo() | ||
{ | ||
$extendedDateTime = new ExtendedDateTimeWithMethodCall('2022/04/12'); | ||
$extendedDateTime->__construct('2022/04/13'); | ||
} | ||
|
||
} |