Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions rules.neon
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ rules:
- PHPStan\Rules\Doctrine\ORM\DqlRule
- PHPStan\Rules\Doctrine\ORM\RepositoryMethodCallRule
- PHPStan\Rules\Doctrine\ORM\EntityRelationRule
- PHPStan\Rules\Doctrine\ORM\EntityMustNotBeFinalRule
- PHPStan\Rules\Doctrine\ORM\EntityMustNotContainFinalMethodsRule

services:
-
Expand Down
58 changes: 58 additions & 0 deletions src/Rules/Doctrine/ORM/EntityMustNotBeFinalRule.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
<?php declare(strict_types = 1);

namespace PHPStan\Rules\Doctrine\ORM;

use PhpParser\Node;
use PHPStan\Analyser\Scope;
use PHPStan\Rules\Rule;
use PHPStan\Type\Doctrine\ObjectMetadataResolver;
use function sprintf;

class EntityMustNotBeFinalRule implements Rule
{

/** @var \PHPStan\Type\Doctrine\ObjectMetadataResolver */
private $objectMetadataResolver;

public function __construct(ObjectMetadataResolver $objectMetadataResolver)
{
$this->objectMetadataResolver = $objectMetadataResolver;
}

public function getNodeType(): string
{
return Node\Stmt\Class_::class;
}

/**
* @param \PhpParser\Node\Stmt\Class_ $node
* @param \PHPStan\Analyser\Scope $scope
* @return string[]
*/
public function processNode(Node $node, Scope $scope): array
{
$objectManager = $this->objectMetadataResolver->getObjectManager();
if ($objectManager === null) {
return [];
}

$className = (string) $node->namespacedName;

// not an entity
if ($objectManager->getMetadataFactory()->isTransient($className)) {
return [];
}

if ($node->isFinal()) {
return [
sprintf(
'Class %s is a Doctrine ORM entity, so it should not be marked as final',
$className
),
];
}

return [];
}

}
62 changes: 62 additions & 0 deletions src/Rules/Doctrine/ORM/EntityMustNotContainFinalMethodsRule.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
<?php declare(strict_types = 1);

namespace PHPStan\Rules\Doctrine\ORM;

use PhpParser\Node;
use PHPStan\Analyser\Scope;
use PHPStan\Rules\Rule;
use PHPStan\Type\Doctrine\ObjectMetadataResolver;
use function sprintf;

class EntityMustNotContainFinalMethodsRule implements Rule
{

/** @var \PHPStan\Type\Doctrine\ObjectMetadataResolver */
private $objectMetadataResolver;

public function __construct(ObjectMetadataResolver $objectMetadataResolver)
{
$this->objectMetadataResolver = $objectMetadataResolver;
}

public function getNodeType(): string
{
return Node\Stmt\ClassMethod::class;
}

/**
* @param \PhpParser\Node\Stmt\ClassMethod $node
* @param \PHPStan\Analyser\Scope $scope
* @return string[]
*/
public function processNode(Node $node, Scope $scope): array
{
$class = $scope->getClassReflection();
if ($class === null) {
return [];
}

$objectManager = $this->objectMetadataResolver->getObjectManager();
if ($objectManager === null) {
return [];
}

$className = $class->getName();
if ($objectManager->getMetadataFactory()->isTransient($className)) {
return [];
}

if ($node->isFinal()) {
return [
sprintf(
'Class %s is a Doctrine ORM entity, so its method %s() must not be marked as final.',
$className,
(string) $node->name
),
];
}

return [];
}

}
45 changes: 45 additions & 0 deletions tests/Rules/Doctrine/ORM/EntityMustNotBeFinalRuleTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
<?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;

class EntityMustNotBeFinalRuleTest extends RuleTestCase
{

protected function getRule(): Rule
{
return new EntityMustNotBeFinalRule(
new ObjectMetadataResolver(__DIR__ . '/entity-manager.php', null)
);
}

/**
* @dataProvider ruleProvider
*/
public function testRule(string $file, array $expectedErrors): void
{
$this->analyse([$file], $expectedErrors);
}

public function ruleProvider(): Iterator
{
yield 'regular class' => [__DIR__ . '/data/FinalClassNotAnEntity.php', []];

yield 'nice entity' => [__DIR__ . '/data/EntityWithRelations.php', []];

yield 'final entity class' => [
__DIR__ . '/data/FinalEntity.php',
[
[
'Class PHPStan\Rules\Doctrine\ORM\FinalEntity is a Doctrine ORM entity, so it should not be marked as final',
10,
],
],
];
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
<?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;

class EntityMustNotContainFinalMethodsRuleTest extends RuleTestCase
{

protected function getRule(): Rule
{
return new EntityMustNotContainFinalMethodsRule(
new ObjectMetadataResolver(__DIR__ . '/entity-manager.php', null)
);
}

/**
* @dataProvider ruleProvider
*/
public function testRule(string $file, array $expectedErrors): void
{
$this->analyse([$file], $expectedErrors);
}

public function ruleProvider(): Iterator
{
yield 'regular class' => [__DIR__ . '/data/FinalClassNotAnEntity.php', []];

yield 'nice entity' => [__DIR__ . '/data/EntityWithRelations.php', []];

yield 'entity with final method' => [
__DIR__ . '/data/EntityWithFinalMethod.php',
[
[
'Class PHPStan\Rules\Doctrine\ORM\EntityWithFinalMethod is a Doctrine ORM entity, so its method getData() must not be marked as final.',
31,
],
],
];
}

}
36 changes: 36 additions & 0 deletions tests/Rules/Doctrine/ORM/data/EntityWithFinalMethod.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
<?php declare(strict_types = 1);

namespace PHPStan\Rules\Doctrine\ORM;

use Doctrine\ORM\Mapping as ORM;

/**
* @ORM\Entity()
*/
class EntityWithFinalMethod
{

/**
* @ORM\Id()
* @ORM\GeneratedValue()
* @ORM\Column(type="integer")
*
* @var int
*/
private $id;

/**
* @ORM\Column(type="string")
* @var string
*/
private $name;

/**
* @return mixed[]
*/
public final function getData(): array
{
return [];
}

}
16 changes: 16 additions & 0 deletions tests/Rules/Doctrine/ORM/data/FinalClassNotAnEntity.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<?php declare(strict_types = 1);

namespace PHPStan\Rules\Doctrine\ORM;

final class FinalClassNotAnEntity
{

/**
* @return mixed[]
*/
final public function getData(): array
{
return [];
}

}
28 changes: 28 additions & 0 deletions tests/Rules/Doctrine/ORM/data/FinalEntity.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
<?php declare(strict_types = 1);

namespace PHPStan\Rules\Doctrine\ORM;

use Doctrine\ORM\Mapping as ORM;

/**
* @ORM\Entity()
*/
final class FinalEntity
{

/**
* @ORM\Id()
* @ORM\GeneratedValue()
* @ORM\Column(type="integer")
*
* @var int
*/
private $id;

/**
* @var string
* @ORM\Column(type="string")
*/
private $name;

}