Skip to content

Commit e6c640b

Browse files
committed
fixed EntityRegenerator when using MappedSuperclass or Traits
1 parent 951dbff commit e6c640b

File tree

14 files changed

+621
-3
lines changed

14 files changed

+621
-3
lines changed

src/Doctrine/EntityRegenerator.php

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,8 @@ public function regenerateEntities(string $classOrNamespace)
6666
$classPath = $this->getPathOfClass($classMetadata->name);
6767
}
6868

69+
$mappedFields = $this->getMappedFieldsInEntity($classMetadata);
70+
6971
if ($classMetadata->customRepositoryClassName) {
7072
$this->generateRepository($classMetadata);
7173
}
@@ -95,6 +97,10 @@ public function regenerateEntities(string $classOrNamespace)
9597
continue;
9698
}
9799

100+
if (!in_array($fieldName, $mappedFields)) {
101+
continue;
102+
}
103+
98104
$manipulator->addEntityField($fieldName, $mapping);
99105
}
100106

@@ -108,6 +114,10 @@ public function regenerateEntities(string $classOrNamespace)
108114
};
109115

110116
foreach ($classMetadata->associationMappings as $fieldName => $mapping) {
117+
if (!in_array($fieldName, $mappedFields)) {
118+
continue;
119+
}
120+
111121
switch ($mapping['type']) {
112122
case ClassMetadata::MANY_TO_ONE:
113123
$relation = (new RelationManyToOne())
@@ -227,4 +237,36 @@ private function generateRepository(ClassMetadata $metadata)
227237

228238
$this->generator->writeChanges();
229239
}
240+
241+
private function getMappedFieldsInEntity(ClassMetadata $classMetadata)
242+
{
243+
/* @var $classReflection \ReflectionClass */
244+
$classReflection = $classMetadata->reflClass;
245+
246+
$targetFields = array_merge(
247+
array_keys($classMetadata->fieldMappings),
248+
array_keys($classMetadata->associationMappings)
249+
);
250+
251+
if ($classReflection) {
252+
// exclude traits
253+
$traitProperties = [];
254+
255+
foreach ($classReflection->getTraits() as $trait) {
256+
foreach ($trait->getProperties() as $property) {
257+
$traitProperties[] = $property->getName();
258+
}
259+
}
260+
261+
$targetFields = array_diff($targetFields, $traitProperties);
262+
263+
// exclude inherited properties
264+
$targetFields = array_filter($targetFields, function ($field) use ($classReflection) {
265+
return $classReflection->hasProperty($field) &&
266+
$classReflection->getProperty($field)->getDeclaringClass()->getName() == $classReflection->getName();
267+
});
268+
}
269+
270+
return $targetFields;
271+
}
230272
}

src/Util/ClassSourceManipulator.php

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -535,7 +535,20 @@ private function addCollectionRelation(BaseCollectionRelation $relation)
535535
private function addStatementToConstructor(Node\Stmt $stmt)
536536
{
537537
if (!$this->getConstructorNode()) {
538-
$constructorNode = $getterNodeBuilder = (new Builder\Method('__construct'))->makePublic()->getNode();
538+
$constructorNode = (new Builder\Method('__construct'))->makePublic()->getNode();
539+
540+
// add call to parent::__construct() if there is a need to
541+
try {
542+
$ref = new \ReflectionClass($this->getThisFullClassName());
543+
544+
if ($ref->getParentClass() && $ref->getParentClass()->getConstructor()) {
545+
$constructorNode->stmts[] = new Node\Stmt\Expression(
546+
new Node\Expr\StaticCall(new Node\Name('parent'), new Node\Identifier('__construct'))
547+
);
548+
}
549+
} catch (\ReflectionException $e) {
550+
}
551+
539552
$this->addNodeAfterProperties($constructorNode);
540553
}
541554

tests/Doctrine/EntityRegeneratorTest.php

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,9 @@ private function doTestRegeneration(string $sourceDir, Kernel $kernel, string $n
6969
$fs = new Filesystem();
7070
$tmpDir = __DIR__.'/../tmp/current_project';
7171
$fs->remove($tmpDir);
72-
$fs->mirror($sourceDir, $tmpDir);
72+
73+
// if traits (Timestampable, Teamable) gets copied into new project, tests will fail because of double exclusion
74+
$fs->mirror($sourceDir, $tmpDir, $this->createAllButTraitsIterator($sourceDir));
7375

7476
$kernel->boot();
7577
$container = $kernel->getContainer();
@@ -114,6 +116,13 @@ private function doTestRegeneration(string $sourceDir, Kernel $kernel, string $n
114116
$this->assertEquals($expectedContents, $actualContents, sprintf('File "%s" does not match: %s', $file->getFilename(), $actualContents));
115117
}
116118
}
119+
120+
private function createAllButTraitsIterator(string $sourceDir): \Iterator
121+
{
122+
$directoryIterator = new \RecursiveDirectoryIterator($sourceDir, \FilesystemIterator::SKIP_DOTS);
123+
$filter = new AllButTraitsIterator($directoryIterator);
124+
return new \RecursiveIteratorIterator($filter, \RecursiveIteratorIterator::SELF_FIRST);
125+
}
117126
}
118127

119128
class TestEntityRegeneratorKernel extends Kernel
@@ -203,4 +212,11 @@ public function getRootDir()
203212
{
204213
return __DIR__.'/../tmp/current_project';
205214
}
206-
}
215+
}
216+
217+
class AllButTraitsIterator extends \RecursiveFilterIterator
218+
{
219+
public function accept() {
220+
return !in_array($this->current()->getFilename(), ['TeamTrait.php', 'TimestampableTrait.php']);
221+
}
222+
}
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
<?php
2+
3+
namespace Symfony\Bundle\MakerBundle\Tests\Doctrine\fixtures\source_project\src\Entity;
4+
5+
use Doctrine\ORM\Mapping as ORM;
6+
7+
/**
8+
* @ORM\MappedSuperclass()
9+
*/
10+
class BaseClient
11+
{
12+
use TeamTrait;
13+
14+
/**
15+
* @ORM\Id
16+
* @ORM\GeneratedValue
17+
* @ORM\Column(type="integer")
18+
*/
19+
private $id;
20+
21+
/**
22+
* @ORM\Column(type="string")
23+
*/
24+
private $name;
25+
26+
/**
27+
* @ORM\ManyToOne(targetEntity="User")
28+
*/
29+
private $creator;
30+
31+
/**
32+
* @ORM\Column(type="integer")
33+
*/
34+
private $magic;
35+
36+
public function __construct()
37+
{
38+
$this->magic = 42;
39+
}
40+
41+
public function getId()
42+
{
43+
return $this->id;
44+
}
45+
46+
public function getName(): ?string
47+
{
48+
return $this->name;
49+
}
50+
51+
public function setName(string $name): self
52+
{
53+
$this->name = $name;
54+
55+
return $this;
56+
}
57+
58+
public function getMagic(): ?int
59+
{
60+
return $this->magic;
61+
}
62+
63+
public function setMagic(int $magic): self
64+
{
65+
$this->magic = $magic;
66+
67+
return $this;
68+
}
69+
70+
public function getCreator(): ?User
71+
{
72+
return $this->creator;
73+
}
74+
75+
public function setCreator(?User $creator): self
76+
{
77+
$this->creator = $creator;
78+
79+
return $this;
80+
}
81+
}
Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
<?php
2+
3+
namespace Symfony\Bundle\MakerBundle\Tests\Doctrine\fixtures\source_project\src\Entity;
4+
5+
use Doctrine\Common\Collections\ArrayCollection;
6+
use Doctrine\Common\Collections\Collection;
7+
use Doctrine\ORM\Mapping as ORM;
8+
9+
/**
10+
* @ORM\Entity()
11+
*/
12+
class Client extends BaseClient
13+
{
14+
use TimestampableTrait;
15+
16+
/**
17+
* @ORM\Column(type="string")
18+
* @var string
19+
*/
20+
private $apiKey;
21+
22+
/**
23+
* @ORM\ManyToMany(targetEntity="Tag")
24+
*/
25+
private $tags;
26+
27+
/**
28+
* @ORM\Embedded(class="Embed")
29+
*/
30+
private $embed;
31+
32+
public function __construct()
33+
{
34+
parent::__construct();
35+
$this->embed = new Embed();
36+
$this->tags = new ArrayCollection();
37+
}
38+
39+
public function getEmbed(): Embed
40+
{
41+
return $this->embed;
42+
}
43+
44+
public function setEmbed(Embed $embed): self
45+
{
46+
$this->embed = $embed;
47+
48+
return $this;
49+
}
50+
51+
public function getApiKey(): ?string
52+
{
53+
return $this->apiKey;
54+
}
55+
56+
public function setApiKey(string $apiKey): self
57+
{
58+
$this->apiKey = $apiKey;
59+
60+
return $this;
61+
}
62+
63+
/**
64+
* @return Collection|Tag[]
65+
*/
66+
public function getTags(): Collection
67+
{
68+
return $this->tags;
69+
}
70+
71+
public function addTag(Tag $tag): self
72+
{
73+
if (!$this->tags->contains($tag)) {
74+
$this->tags[] = $tag;
75+
}
76+
77+
return $this;
78+
}
79+
80+
public function removeTag(Tag $tag): self
81+
{
82+
if ($this->tags->contains($tag)) {
83+
$this->tags->removeElement($tag);
84+
}
85+
86+
return $this;
87+
}
88+
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
<?php
2+
3+
namespace Symfony\Bundle\MakerBundle\Tests\Doctrine\fixtures\source_project\src\Entity;
4+
5+
use Doctrine\ORM\Mapping as ORM;
6+
7+
/**
8+
* @ORM\Embeddable()
9+
*/
10+
class Embed
11+
{
12+
/**
13+
* @ORM\Column(type="integer")
14+
*/
15+
private $val;
16+
17+
public function getVal(): ?int
18+
{
19+
return $this->val;
20+
}
21+
22+
public function setVal(int $val): self
23+
{
24+
$this->val = $val;
25+
26+
return $this;
27+
}
28+
}

0 commit comments

Comments
 (0)