-
Notifications
You must be signed in to change notification settings - Fork 98
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add rule to prevent final constructors in Doctrine entities
- Loading branch information
Showing
6 changed files
with
194 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,63 @@ | ||
<?php declare(strict_types = 1); | ||
|
||
namespace PHPStan\Rules\Doctrine\ORM; | ||
|
||
use PhpParser\Node; | ||
use PhpParser\Node\Stmt\ClassMethod; | ||
use PHPStan\Analyser\Scope; | ||
use PHPStan\Rules\Rule; | ||
use PHPStan\Rules\RuleErrorBuilder; | ||
use PHPStan\ShouldNotHappenException; | ||
use PHPStan\Type\Doctrine\ObjectMetadataResolver; | ||
use function sprintf; | ||
|
||
/** | ||
* @implements Rule<ClassMethod> | ||
*/ | ||
class EntityConstructorNotFinalRule implements Rule | ||
{ | ||
|
||
/** @var ObjectMetadataResolver */ | ||
private $objectMetadataResolver; | ||
|
||
public function __construct(ObjectMetadataResolver $objectMetadataResolver) | ||
{ | ||
$this->objectMetadataResolver = $objectMetadataResolver; | ||
} | ||
|
||
public function getNodeType(): string | ||
{ | ||
return ClassMethod::class; | ||
} | ||
|
||
public function processNode(Node $node, Scope $scope): array | ||
{ | ||
if ($node->name->name !== '__construct') { | ||
return []; | ||
} | ||
|
||
if (!$node->isFinal()) { | ||
return []; | ||
} | ||
|
||
$classReflection = $scope->getClassReflection(); | ||
if ($classReflection === null) { | ||
throw new ShouldNotHappenException(); | ||
} | ||
|
||
if ($this->objectMetadataResolver->isTransient($classReflection->getName())) { | ||
return []; | ||
} | ||
|
||
$metadata = $this->objectMetadataResolver->getClassMetadata($classReflection->getName()); | ||
if ($metadata !== null && $metadata->isEmbeddedClass === true) { | ||
return []; | ||
} | ||
|
||
return [RuleErrorBuilder::message(sprintf( | ||
'Constructor of class %s is final which can cause problems with proxies.', | ||
$classReflection->getDisplayName() | ||
))->build()]; | ||
} | ||
|
||
} |
87 changes: 87 additions & 0 deletions
87
tests/Rules/Doctrine/ORM/EntityConstructorNotFinalRuleTest.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,87 @@ | ||
<?php declare(strict_types = 1); | ||
|
||
namespace PHPStan\Rules\Doctrine\ORM; | ||
|
||
use Iterator; | ||
use PHPStan\Rules\Rule; | ||
use PHPStan\Testing\RuleTestCase; | ||
use PHPStan\Type\Doctrine\ObjectMetadataResolver; | ||
|
||
/** | ||
* @extends RuleTestCase<EntityConstructorNotFinalRule> | ||
*/ | ||
class EntityConstructorNotFinalRuleTest extends RuleTestCase | ||
{ | ||
|
||
/** @var string|null */ | ||
private $objectManagerLoader; | ||
|
||
protected function getRule(): Rule | ||
{ | ||
return new EntityConstructorNotFinalRule( | ||
new ObjectMetadataResolver($this->objectManagerLoader) | ||
); | ||
} | ||
|
||
/** | ||
* @dataProvider ruleProvider | ||
* @param list<array{0: string, 1: int, 2?: string}> $expectedErrors | ||
*/ | ||
public function testRule(string $file, array $expectedErrors): void | ||
{ | ||
$this->objectManagerLoader = __DIR__ . '/entity-manager.php'; | ||
$this->analyse([$file], $expectedErrors); | ||
} | ||
|
||
/** | ||
* @dataProvider ruleProvider | ||
* @param list<array{0: string, 1: int, 2?: string}> $expectedErrors | ||
*/ | ||
public function testRuleWithoutObjectManagerLoader(string $file, array $expectedErrors): void | ||
{ | ||
$this->objectManagerLoader = null; | ||
$this->analyse([$file], $expectedErrors); | ||
} | ||
|
||
/** | ||
* @return Iterator<mixed[]> | ||
*/ | ||
public function ruleProvider(): Iterator | ||
{ | ||
yield 'entity final constructor' => [ | ||
__DIR__ . '/data/EntityFinalConstructor.php', | ||
[ | ||
[ | ||
'Constructor of class PHPStan\Rules\Doctrine\ORM\EntityFinalConstructor is final which can cause problems with proxies.', | ||
12, | ||
], | ||
], | ||
]; | ||
|
||
yield 'entity non-final constructor' => [ | ||
__DIR__ . '/data/EntityNonFinalConstructor.php', | ||
[], | ||
]; | ||
|
||
yield 'correct entity' => [ | ||
__DIR__ . '/data/MyEntity.php', | ||
[], | ||
]; | ||
|
||
yield 'non-entity final constructor' => [ | ||
__DIR__ . '/data/NonEntityFinalConstructor.php', | ||
[], | ||
]; | ||
|
||
yield 'final embeddable' => [ | ||
__DIR__ . '/data/FinalEmbeddable.php', | ||
[], | ||
]; | ||
|
||
yield 'non final embeddable' => [ | ||
__DIR__ . '/data/MyEmbeddable.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,14 @@ | ||
<?php declare(strict_types = 1); | ||
|
||
namespace PHPStan\Rules\Doctrine\ORM; | ||
|
||
use Doctrine\ORM\Mapping as ORM; | ||
|
||
/** | ||
* @ORM\Entity() | ||
*/ | ||
class EntityFinalConstructor | ||
{ | ||
final public function __construct(string $x) | ||
{} | ||
} |
17 changes: 17 additions & 0 deletions
17
tests/Rules/Doctrine/ORM/data/EntityNonFinalConstructor.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,17 @@ | ||
<?php declare(strict_types = 1); | ||
|
||
namespace PHPStan\Rules\Doctrine\ORM; | ||
|
||
use Doctrine\ORM\Mapping as ORM; | ||
|
||
/** | ||
* @ORM\Entity() | ||
*/ | ||
class EntityNonFinalConstructor | ||
{ | ||
public function __construct() | ||
{} | ||
|
||
final public function foo() | ||
{} | ||
} |
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,9 @@ | ||
<?php declare(strict_types = 1); | ||
|
||
namespace PHPStan\Rules\Doctrine\ORM; | ||
|
||
class NonEntityFinalConstructor | ||
{ | ||
final public function __construct(string $x) | ||
{} | ||
} |