Skip to content

Commit 5927f01

Browse files
committed
bug #181 fixed EntityRegenerator when using MappedSuperclass or Traits (andrewtch, weaverryan)
This PR was squashed before being merged into the 1.0-dev branch (closes #181). Discussion ---------- fixed EntityRegenerator when using MappedSuperclass or Traits Should fix #167 **What has been fixed** - generation for mapped inherited fields - generation for mapped fields that come from direct traits - skipping ```parent::__construct()``` if Entity has parent class and it has constructor **How it is done** - fields (fields, and associations, but not embedded) are checked before generation, generation happens only for class' fields - ClassSourceManipulator is updated to include parent call (if parent class exists and the class has already been generated) - EntityRegeneratorTest skips copying of Traits as it causes autoloader to fail (again, strangely, on Travis only, works fine on Mac) Verified on live project, seems to generate OK with a lot of different entities (MappedSuperclasses, DiscriminatorMaps etc). - [x] fix errors generating embedded fields - [x] fix strange Kernel class errors Commits ------- b869a5c Merge branch 'master' into fix-entity-regeneration-traits-ms e6c640b fixed EntityRegenerator when using MappedSuperclass or Traits
2 parents ce7b4af + b869a5c commit 5927f01

File tree

14 files changed

+620
-2
lines changed

14 files changed

+620
-2
lines changed

src/Doctrine/EntityRegenerator.php

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

67+
$mappedFields = $this->getMappedFieldsInEntity($classMetadata);
68+
6769
if ($classMetadata->customRepositoryClassName) {
6870
$this->generateRepository($classMetadata);
6971
}
@@ -93,6 +95,10 @@ public function regenerateEntities(string $classOrNamespace)
9395
continue;
9496
}
9597

98+
if (!in_array($fieldName, $mappedFields)) {
99+
continue;
100+
}
101+
96102
$manipulator->addEntityField($fieldName, $mapping);
97103
}
98104

@@ -106,6 +112,10 @@ public function regenerateEntities(string $classOrNamespace)
106112
};
107113

108114
foreach ($classMetadata->associationMappings as $fieldName => $mapping) {
115+
if (!in_array($fieldName, $mappedFields)) {
116+
continue;
117+
}
118+
109119
switch ($mapping['type']) {
110120
case ClassMetadata::MANY_TO_ONE:
111121
$relation = (new RelationManyToOne())
@@ -225,4 +235,36 @@ private function generateRepository(ClassMetadata $metadata)
225235

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

src/Util/ClassSourceManipulator.php

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -533,6 +533,19 @@ private function addStatementToConstructor(Node\Stmt $stmt)
533533
{
534534
if (!$this->getConstructorNode()) {
535535
$constructorNode = (new Builder\Method('__construct'))->makePublic()->getNode();
536+
537+
// add call to parent::__construct() if there is a need to
538+
try {
539+
$ref = new \ReflectionClass($this->getThisFullClassName());
540+
541+
if ($ref->getParentClass() && $ref->getParentClass()->getConstructor()) {
542+
$constructorNode->stmts[] = new Node\Stmt\Expression(
543+
new Node\Expr\StaticCall(new Node\Name('parent'), new Node\Identifier('__construct'))
544+
);
545+
}
546+
} catch (\ReflectionException $e) {
547+
}
548+
536549
$this->addNodeAfterProperties($constructorNode);
537550
}
538551

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)