Skip to content

Commit eeb9027

Browse files
committed
bug #164 Doctrine embedded entity 'make:entity --regenerate' bugfix (sadikoff)
This PR was squashed before being merged into the 1.0-dev branch (closes #164). Discussion ---------- Doctrine embedded entity 'make:entity --regenerate' bugfix Closes #160 Commits ------- 0e36480 Doctrine embedded entity 'make:entity --regenerate' bugfix
2 parents 0de5134 + 0e36480 commit eeb9027

File tree

6 files changed

+179
-1
lines changed

6 files changed

+179
-1
lines changed

src/Doctrine/EntityRegenerator.php

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
namespace Symfony\Bundle\MakerBundle\Doctrine;
1313

1414
use Doctrine\ORM\Mapping\ClassMetadata;
15+
use Doctrine\ORM\Mapping\MappingException;
1516
use Symfony\Bundle\MakerBundle\Exception\RuntimeCommandException;
1617
use Symfony\Bundle\MakerBundle\FileManager;
1718
use Symfony\Bundle\MakerBundle\Generator;
@@ -40,7 +41,11 @@ public function __construct(DoctrineHelper $doctrineHelper, FileManager $fileMan
4041

4142
public function regenerateEntities(string $classOrNamespace)
4243
{
43-
$metadata = $this->doctrineHelper->getMetadata($classOrNamespace, true);
44+
try {
45+
$metadata = $this->doctrineHelper->getMetadata($classOrNamespace);
46+
} catch (MappingException $mappingException) {
47+
$metadata = $this->doctrineHelper->getMetadata($classOrNamespace, true);
48+
}
4449

4550
if ($metadata instanceof ClassMetadata) {
4651
$metadata = [$metadata];
@@ -67,7 +72,28 @@ public function regenerateEntities(string $classOrNamespace)
6772
$manipulator = $this->createClassManipulator($classPath);
6873
$operations[$classPath] = $manipulator;
6974

75+
$embeddedClasses = [];
76+
77+
foreach ($classMetadata->embeddedClasses as $fieldName => $mapping) {
78+
$className = $mapping['class'];
79+
80+
$embeddedClasses[$fieldName] = $this->getPathOfClass($className);
81+
82+
$operations[$embeddedClasses[$fieldName]] = $this->createClassManipulator($embeddedClasses[$fieldName]);
83+
84+
$manipulator->addEmbeddedEntity($fieldName, $className);
85+
}
86+
7087
foreach ($classMetadata->fieldMappings as $fieldName => $mapping) {
88+
// skip embedded fields
89+
if (false !== strpos($fieldName, '.')) {
90+
list($fieldName, $embeddedFiledName) = explode('.', $fieldName);
91+
92+
$operations[$embeddedClasses[$fieldName]]->addEntityField($embeddedFiledName, $mapping);
93+
94+
continue;
95+
}
96+
7197
$manipulator->addEntityField($fieldName, $mapping);
7298
}
7399

src/Util/ClassSourceManipulator.php

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,46 @@ public function addEntityField(string $propertyName, array $columnOptions)
106106
}
107107
}
108108

109+
public function addEmbeddedEntity(string $propertyName, string $className)
110+
{
111+
$typeHint = self::addUseStatementIfNecessary($className);
112+
113+
$annotations = [
114+
$this->buildAnnotationLine(
115+
'@ORM\\Embedded',
116+
[
117+
'class' => $className,
118+
]
119+
),
120+
];
121+
122+
$this->addProperty($propertyName, $annotations);
123+
124+
// logic to avoid re-adding the same ArrayCollection line
125+
$addEmbedded = true;
126+
if ($this->getConstructorNode()) {
127+
// We print the constructor to a string, then
128+
// look for "$this->propertyName = "
129+
130+
$constructorString = $this->printer->prettyPrint([$this->getConstructorNode()]);
131+
if (false !== strpos($constructorString, sprintf('$this->%s = ', $propertyName))) {
132+
$addEmbedded = false;
133+
}
134+
}
135+
136+
if ($addEmbedded) {
137+
$this->addStatementToConstructor(
138+
new Node\Stmt\Expression(new Node\Expr\Assign(
139+
new Node\Expr\PropertyFetch(new Node\Expr\Variable('this'), $propertyName),
140+
new Node\Expr\New_(new Node\Name($typeHint))
141+
))
142+
);
143+
}
144+
145+
$this->addGetter($propertyName, $typeHint, false);
146+
$this->addSetter($propertyName, $typeHint, false);
147+
}
148+
109149
public function addManyToOneRelation(RelationManyToOne $manyToOne)
110150
{
111151
$this->addSingularRelation($manyToOne);

tests/Maker/FunctionalTest.php

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -689,6 +689,18 @@ public function getCommandEntityTests()
689689
->setRequiredPhpVersion(70100)
690690
];
691691

692+
yield 'entity_regenerate_embeddable' => [MakerTestDetails::createTest(
693+
$this->getMakerInstance(MakeEntity::class),
694+
[
695+
// namespace: use default App\Entity
696+
'',
697+
])
698+
->setArgumentsString('--regenerate --overwrite')
699+
->setFixtureFilesPath(__DIR__ . '/../fixtures/MakeEntityRegenerateEmbedable')
700+
->configureDatabase()
701+
->setRequiredPhpVersion(70100)
702+
];
703+
692704
yield 'entity_regenerate_overwrite' => [MakerTestDetails::createTest(
693705
$this->getMakerInstance(MakeEntity::class),
694706
[
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
<?php
2+
3+
namespace App\Entity;
4+
5+
use Doctrine\ORM\Mapping as ORM;
6+
7+
/**
8+
* @ORM\Entity()
9+
*/
10+
class Food
11+
{
12+
/**
13+
* @ORM\Column(name="id", type="integer")
14+
* @ORM\Id
15+
* @ORM\GeneratedValue(strategy="AUTO")
16+
*/
17+
private $id;
18+
19+
/**
20+
* @ORM\Column(name="title", type="string", length=255)
21+
*/
22+
private $title;
23+
24+
/**
25+
* @ORM\Embedded(class="App\Entity\Recipe")
26+
*/
27+
private $recipe;
28+
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
<?php
2+
3+
namespace App\Entity;
4+
5+
use Doctrine\ORM\Mapping as ORM;
6+
7+
/**
8+
* @ORM\Embeddable()
9+
*/
10+
class Recipe
11+
{
12+
/**
13+
* @ORM\Column(type="string", length=255)
14+
*/
15+
private $ingredients;
16+
17+
/**
18+
* @ORM\Column(type="string", length=255)
19+
*/
20+
private $steps;
21+
}
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
<?php
2+
3+
namespace App\Tests;
4+
5+
use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;
6+
use Doctrine\ORM\EntityManager;
7+
use App\Entity\Food;
8+
use App\Entity\Recipe;
9+
10+
class GeneratedEntityTest extends KernelTestCase
11+
{
12+
public function testGeneratedEntity()
13+
{
14+
self::bootKernel();
15+
/** @var EntityManager $em */
16+
$em = self::$kernel->getContainer()
17+
->get('doctrine')
18+
->getManager();
19+
20+
$em->createQuery('DELETE FROM App\\Entity\\Food f')->execute();
21+
22+
$food = new Food();
23+
// check that the constructor was instantiated properly
24+
$this->assertInstanceOf(Recipe::class, $food->getRecipe());
25+
// fields should now have setters
26+
$food->setTitle('Borscht');
27+
28+
$recipe = new Recipe();
29+
$recipe->setIngredients('ingridients');
30+
$recipe->setSteps('steps');
31+
$food->setRecipe($recipe);
32+
33+
$em->persist($food);
34+
35+
$em->flush();
36+
$em->refresh($food);
37+
38+
/** @var Food[] $actualFood */
39+
$actualFood = $em->getRepository(Food::class)
40+
->findAll();
41+
42+
$this->assertcount(1, $actualFood);
43+
44+
/** @var Recipe $actualRecipe */
45+
$actualRecipe = $actualFood[0]->getRecipe();
46+
47+
$this->assertInstanceOf(Recipe::class, $actualRecipe);
48+
$this->assertEquals('ingridients', $actualRecipe->getIngredients());
49+
$this->assertEquals('steps', $actualRecipe->getSteps());
50+
}
51+
}

0 commit comments

Comments
 (0)