mirror of
https://github.com/rectorphp/rector.git
synced 2025-01-17 13:28:18 +01:00
[DoctrineCodeQuality] Initialize collectoins in constructor
This commit is contained in:
parent
8e442f9fb6
commit
6fbdf5461a
@ -55,6 +55,7 @@
|
||||
"Rector\\ConsoleDiffer\\": "packages/ConsoleDiffer/src",
|
||||
"Rector\\DeadCode\\": "packages/DeadCode/src",
|
||||
"Rector\\Doctrine\\": "packages/Doctrine/src",
|
||||
"Rector\\DoctrineCodeQuality\\": "packages/DoctrineCodeQuality/src",
|
||||
"Rector\\ElasticSearchDSL\\": "packages/ElasticSearchDSL/src",
|
||||
"Rector\\FileSystemRector\\": "packages/FileSystemRector/src",
|
||||
"Rector\\Guzzle\\": "packages/Guzzle/src",
|
||||
@ -111,6 +112,7 @@
|
||||
"Rector\\CodingStyle\\Tests\\": "packages/CodingStyle/tests",
|
||||
"Rector\\DeadCode\\Tests\\": "packages/DeadCode/tests",
|
||||
"Rector\\Doctrine\\Tests\\": "packages/Doctrine/tests",
|
||||
"Rector\\DoctrineCodeQuality\\Tests\\": "packages/DoctrineCodeQuality/tests",
|
||||
"Rector\\ElasticSearchDSL\\Tests\\": "packages/ElasticSearchDSL/tests",
|
||||
"Rector\\Guzzle\\Tests\\": "packages/Guzzle/tests",
|
||||
"Rector\\Laravel\\Tests\\": "packages/Laravel/tests",
|
||||
|
@ -1,2 +1,3 @@
|
||||
services:
|
||||
Rector\Doctrine\Rector\Class_\ManagerRegistryGetManagerToEntityManagerRector: ~
|
||||
Rector\DoctrineCodeQuality\Rector\Class_\InitializeDefaultEntityCollectionRector: ~
|
||||
|
@ -3808,7 +3808,7 @@ Remove 0 from break and continue
|
||||
|
||||
- class: `Rector\Php55\Rector\FuncCall\PregReplaceEModifierRector`
|
||||
|
||||
The /e modifier is no longer supported, use preg_replace_callback instead
|
||||
The /e modifier is no longer supported, use preg_replace_callback instead
|
||||
|
||||
```diff
|
||||
class SomeClass
|
||||
|
@ -917,7 +917,7 @@ if (true) {
|
||||
|
||||
```php
|
||||
?>
|
||||
<strong>feel</strong><?php
|
||||
<strong>feel</strong><?php
|
||||
```
|
||||
<br>
|
||||
|
||||
|
@ -0,0 +1,190 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace Rector\DoctrineCodeQuality\Rector\Class_;
|
||||
|
||||
use PhpParser\Node;
|
||||
use PhpParser\Node\Expr\Assign;
|
||||
use PhpParser\Node\Expr\New_;
|
||||
use PhpParser\Node\Name\FullyQualified;
|
||||
use PhpParser\Node\Stmt\Class_;
|
||||
use PhpParser\Node\Stmt\Expression;
|
||||
use Rector\BetterPhpDocParser\Contract\Doctrine\ToManyTagNodeInterface;
|
||||
use Rector\BetterPhpDocParser\PhpDocNode\Doctrine\Class_\EntityTagValueNode;
|
||||
use Rector\Doctrine\ValueObject\DoctrineClass;
|
||||
use Rector\Rector\AbstractRector;
|
||||
use Rector\RectorDefinition\CodeSample;
|
||||
use Rector\RectorDefinition\RectorDefinition;
|
||||
|
||||
/**
|
||||
* @see https://www.doctrine-project.org/projects/doctrine-orm/en/2.6/reference/best-practices.html#initialize-collections-in-the-constructor
|
||||
*
|
||||
* @see \Rector\DoctrineCodeQuality\Tests\Rector\Class_\InitializeDefaultEntityCollectionRector\InitializeDefaultEntityCollectionRectorTest
|
||||
*/
|
||||
final class InitializeDefaultEntityCollectionRector extends AbstractRector
|
||||
{
|
||||
public function getDefinition(): RectorDefinition
|
||||
{
|
||||
return new RectorDefinition('Initialize collection property in Entity constructor', [
|
||||
new CodeSample(
|
||||
<<<'PHP'
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
|
||||
/**
|
||||
* @ORM\Entity
|
||||
*/
|
||||
class SomeClass
|
||||
{
|
||||
/**
|
||||
* @ORM\OneToMany(targetEntity="MarketingEvent")
|
||||
*/
|
||||
private $marketingEvents = [];
|
||||
}
|
||||
PHP
|
||||
,
|
||||
<<<'PHP'
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
|
||||
/**
|
||||
* @ORM\Entity
|
||||
*/
|
||||
class SomeClass
|
||||
{
|
||||
/**
|
||||
* @ORM\OneToMany(targetEntity="MarketingEvent")
|
||||
*/
|
||||
private $marketingEvents = [];
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->marketingEvents = new ArrayCollection();
|
||||
}
|
||||
}
|
||||
PHP
|
||||
),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string[]
|
||||
*/
|
||||
public function getNodeTypes(): array
|
||||
{
|
||||
return [Class_::class];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Class_ $node
|
||||
*/
|
||||
public function refactor(Node $node): ?Node
|
||||
{
|
||||
$classPhpDocInfo = $this->getPhpDocInfo($node);
|
||||
if ($classPhpDocInfo === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (! $classPhpDocInfo->getByType(EntityTagValueNode::class)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$toManyPropertyNames = $this->resolveToManyPropertyNames($node);
|
||||
if ($toManyPropertyNames === []) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$constructClassMethod = $node->getMethod('__construct');
|
||||
$assigns = $this->createAssignsOfArrayCollectionsForPropertyNames($toManyPropertyNames);
|
||||
|
||||
if ($constructClassMethod === null) {
|
||||
$constructClassMethod = $this->nodeFactory->createPublicMethod('__construct');
|
||||
$constructClassMethod->stmts = $assigns;
|
||||
|
||||
$node->stmts = array_merge((array) $node->stmts, [$constructClassMethod]);
|
||||
} else {
|
||||
$assigns = $this->filterOutExistingAssigns($constructClassMethod, $assigns);
|
||||
|
||||
// all properties are initialized → skip
|
||||
if ($assigns === []) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$constructClassMethod->stmts = array_merge($assigns, (array) $constructClassMethod->stmts);
|
||||
}
|
||||
|
||||
return $node;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string[]
|
||||
*/
|
||||
private function resolveToManyPropertyNames(Class_ $class): array
|
||||
{
|
||||
$collectionPropertyNames = [];
|
||||
|
||||
foreach ($class->getProperties() as $property) {
|
||||
if (count($property->props) !== 1) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$propertyPhpDocInfo = $this->getPhpDocInfo($property);
|
||||
if ($propertyPhpDocInfo === null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (! $propertyPhpDocInfo->getByType(ToManyTagNodeInterface::class)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
/** @var string $propertyName */
|
||||
$propertyName = $this->getName($property);
|
||||
|
||||
$collectionPropertyNames[] = $propertyName;
|
||||
}
|
||||
|
||||
return $collectionPropertyNames;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string[] $propertyNames
|
||||
* @return Expression[]
|
||||
*/
|
||||
private function createAssignsOfArrayCollectionsForPropertyNames(array $propertyNames): array
|
||||
{
|
||||
$assigns = [];
|
||||
foreach ($propertyNames as $propertyName) {
|
||||
$assigns[] = $this->createPropertyArrayCollectionAssign($propertyName);
|
||||
}
|
||||
|
||||
return $assigns;
|
||||
}
|
||||
|
||||
private function createPropertyArrayCollectionAssign(string $toManyPropertyName): Expression
|
||||
{
|
||||
$propertyFetch = $this->createPropertyFetch('this', $toManyPropertyName);
|
||||
$newCollection = new New_(new FullyQualified(DoctrineClass::ARRAY_COLLECTION));
|
||||
|
||||
$assign = new Assign($propertyFetch, $newCollection);
|
||||
|
||||
return new Expression($assign);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Expression[] $assigns
|
||||
* @return Expression[]
|
||||
*/
|
||||
private function filterOutExistingAssigns(Node\Stmt\ClassMethod $constructClassMethod, array $assigns): array
|
||||
{
|
||||
$this->traverseNodesWithCallable((array) $constructClassMethod->stmts, function (Node $node) use (&$assigns) {
|
||||
foreach ($assigns as $key => $assign) {
|
||||
if (! $this->areNodesEqual($node, $assign)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
unset($assigns[$key]);
|
||||
}
|
||||
|
||||
return null;
|
||||
});
|
||||
|
||||
return $assigns;
|
||||
}
|
||||
}
|
@ -0,0 +1,48 @@
|
||||
<?php
|
||||
|
||||
namespace Rector\DoctrineCodeQuality\Tests\Rector\Class_\InitializeDefaultEntityCollectionRector\Fixture;
|
||||
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
|
||||
/**
|
||||
* @ORM\Entity
|
||||
*/
|
||||
class ExistingConstructor
|
||||
{
|
||||
/**
|
||||
* @ORM\OneToMany(targetEntity="MarketingEvent")
|
||||
*/
|
||||
private $marketingEvents = [];
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$value = 5;
|
||||
}
|
||||
}
|
||||
|
||||
?>
|
||||
-----
|
||||
<?php
|
||||
|
||||
namespace Rector\DoctrineCodeQuality\Tests\Rector\Class_\InitializeDefaultEntityCollectionRector\Fixture;
|
||||
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
|
||||
/**
|
||||
* @ORM\Entity
|
||||
*/
|
||||
class ExistingConstructor
|
||||
{
|
||||
/**
|
||||
* @ORM\OneToMany(targetEntity="MarketingEvent")
|
||||
*/
|
||||
private $marketingEvents = [];
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->marketingEvents = new \Doctrine\Common\Collections\ArrayCollection();
|
||||
$value = 5;
|
||||
}
|
||||
}
|
||||
|
||||
?>
|
@ -0,0 +1,41 @@
|
||||
<?php
|
||||
|
||||
namespace Rector\DoctrineCodeQuality\Tests\Rector\Class_\InitializeDefaultEntityCollectionRector\Fixture;
|
||||
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
|
||||
/**
|
||||
* @ORM\Entity
|
||||
*/
|
||||
class SomeClass
|
||||
{
|
||||
/**
|
||||
* @ORM\OneToMany(targetEntity="MarketingEvent")
|
||||
*/
|
||||
private $marketingEvents = [];
|
||||
}
|
||||
|
||||
?>
|
||||
-----
|
||||
<?php
|
||||
|
||||
namespace Rector\DoctrineCodeQuality\Tests\Rector\Class_\InitializeDefaultEntityCollectionRector\Fixture;
|
||||
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
|
||||
/**
|
||||
* @ORM\Entity
|
||||
*/
|
||||
class SomeClass
|
||||
{
|
||||
/**
|
||||
* @ORM\OneToMany(targetEntity="MarketingEvent")
|
||||
*/
|
||||
private $marketingEvents = [];
|
||||
public function __construct()
|
||||
{
|
||||
$this->marketingEvents = new \Doctrine\Common\Collections\ArrayCollection();
|
||||
}
|
||||
}
|
||||
|
||||
?>
|
@ -0,0 +1,23 @@
|
||||
<?php
|
||||
|
||||
namespace Rector\DoctrineCodeQuality\Tests\Rector\Class_\InitializeDefaultEntityCollectionRector\Fixture;
|
||||
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
use Doctrine\Common\Collections\ArrayCollection;
|
||||
|
||||
/**
|
||||
* @ORM\Entity
|
||||
*/
|
||||
class SkipAdded
|
||||
{
|
||||
/**
|
||||
* @ORM\OneToMany(targetEntity="MarketingEvent")
|
||||
*/
|
||||
private $marketingEvents = [];
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->marketingEvents = new ArrayCollection();
|
||||
$value = 5;
|
||||
}
|
||||
}
|
@ -0,0 +1,30 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace Rector\DoctrineCodeQuality\Tests\Rector\Class_\InitializeDefaultEntityCollectionRector;
|
||||
|
||||
use Iterator;
|
||||
use Rector\DoctrineCodeQuality\Rector\Class_\InitializeDefaultEntityCollectionRector;
|
||||
use Rector\Testing\PHPUnit\AbstractRectorTestCase;
|
||||
|
||||
final class InitializeDefaultEntityCollectionRectorTest extends AbstractRectorTestCase
|
||||
{
|
||||
/**
|
||||
* @dataProvider provideDataForTest()
|
||||
*/
|
||||
public function test(string $file): void
|
||||
{
|
||||
$this->doTestFile($file);
|
||||
}
|
||||
|
||||
public function provideDataForTest(): Iterator
|
||||
{
|
||||
yield [__DIR__ . '/Fixture/fixture.php.inc'];
|
||||
yield [__DIR__ . '/Fixture/existing_constructor.php.inc'];
|
||||
yield [__DIR__ . '/Fixture/skip_added.php.inc'];
|
||||
}
|
||||
|
||||
protected function getRectorClass(): string
|
||||
{
|
||||
return InitializeDefaultEntityCollectionRector::class;
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user