Skip to content

Commit bb92ef7

Browse files
committed
make registration attributes
1 parent 64318ec commit bb92ef7

File tree

5 files changed

+155
-47
lines changed

5 files changed

+155
-47
lines changed

src/Maker/MakeRegistrationForm.php

Lines changed: 26 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
use Doctrine\Bundle\DoctrineBundle\DoctrineBundle;
1515
use Doctrine\Common\Annotations\Annotation;
1616
use Doctrine\ORM\EntityManagerInterface;
17+
use Doctrine\ORM\Mapping\Column;
1718
use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity;
1819
use Symfony\Bridge\Twig\Mime\TemplatedEmail;
1920
use Symfony\Bundle\MakerBundle\ConsoleStyle;
@@ -370,24 +371,40 @@ public function generate(InputInterface $input, ConsoleStyle $io, Generator $gen
370371
);
371372
$userManipulator->setIo($io);
372373

373-
$userManipulator->addAnnotationToClass(
374-
UniqueEntity::class,
375-
[
376-
'fields' => [$usernameField],
377-
'message' => sprintf('There is already an account with this %s', $usernameField),
378-
]
379-
);
374+
if ($this->doctrineHelper->isDoctrineSupportingAttributes()) {
375+
$userManipulator->addAttributeToClass(
376+
UniqueEntity::class,
377+
['fields' => [$usernameField], 'message' => sprintf('There is already an account with this %s', $usernameField)]
378+
);
379+
} else {
380+
$userManipulator->addAnnotationToClass(
381+
UniqueEntity::class,
382+
[
383+
'fields' => [$usernameField],
384+
'message' => sprintf('There is already an account with this %s', $usernameField),
385+
]
386+
);
387+
}
380388
$this->fileManager->dumpFile($classDetails->getPath(), $userManipulator->getSourceCode());
381389
}
382390

383391
if ($this->willVerifyEmail) {
384392
$classDetails = new ClassDetails($this->userClass);
385393
$userManipulator = new ClassSourceManipulator(
386-
file_get_contents($classDetails->getPath())
394+
file_get_contents($classDetails->getPath()),
395+
false,
396+
$this->doctrineHelper->isClassAnnotated($this->userClass),
397+
true,
398+
$this->doctrineHelper->doesClassUsesAttributes($this->userClass)
387399
);
388400
$userManipulator->setIo($io);
389401

390-
$userManipulator->addProperty('isVerified', ['@ORM\Column(type="boolean")'], false);
402+
$userManipulator->addProperty(
403+
'isVerified',
404+
['@ORM\Column(type="boolean")'],
405+
false,
406+
[$userManipulator->buildAttributeNode(Column::class, ['type' => 'boolean'], 'ORM')]
407+
);
391408
$userManipulator->addAccessorMethod('isVerified', 'isVerified', 'bool', false);
392409
$userManipulator->addSetter('isVerified', 'bool', false);
393410

src/Util/ClassSourceManipulator.php

Lines changed: 61 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,13 @@
1313

1414
use Doctrine\Common\Collections\ArrayCollection;
1515
use Doctrine\Common\Collections\Collection;
16+
use Doctrine\ORM\Mapping\Column;
17+
use Doctrine\ORM\Mapping\Embedded;
18+
use Doctrine\ORM\Mapping\JoinColumn;
19+
use Doctrine\ORM\Mapping\ManyToMany;
20+
use Doctrine\ORM\Mapping\ManyToOne;
21+
use Doctrine\ORM\Mapping\OneToMany;
22+
use Doctrine\ORM\Mapping\OneToOne;
1623
use PhpParser\Builder;
1724
use PhpParser\BuilderHelpers;
1825
use PhpParser\Comment\Doc;
@@ -93,7 +100,7 @@ public function addEntityField(string $propertyName, array $columnOptions, array
93100
$attributes = [];
94101

95102
if ($this->useAttributesForDoctrineMapping) {
96-
$attributes[] = $this->buildAttributeNode('ORM\Column', $columnOptions);
103+
$attributes[] = $this->buildAttributeNode(Column::class, $columnOptions, 'ORM');
97104
} else {
98105
$comments[] = $this->buildAnnotationLine('@ORM\Column', $columnOptions);
99106
}
@@ -138,10 +145,9 @@ public function addEmbeddedEntity(string $propertyName, string $className): void
138145
} else {
139146
$attributes = [
140147
$this->buildAttributeNode(
141-
'ORM\\Embedded',
142-
[
143-
'class' => new ClassNameValue($className, $typeHint),
144-
]
148+
Embedded::class,
149+
['class' => new ClassNameValue($className, $typeHint)],
150+
'ORM'
145151
),
146152
];
147153
}
@@ -333,6 +339,9 @@ public function createMethodLevelBlankLine()
333339
return $this->createBlankLineNode(self::CONTEXT_CLASS_METHOD);
334340
}
335341

342+
/**
343+
* @param array<Node\Attribute|Node\AttributeGroup> $attributes
344+
*/
336345
public function addProperty(string $name, array $annotationLines = [], $defaultValue = null, array $attributes = []): void
337346
{
338347
if ($this->propertyExists($name)) {
@@ -342,14 +351,14 @@ public function addProperty(string $name, array $annotationLines = [], $defaultV
342351

343352
$newPropertyBuilder = (new Builder\Property($name))->makePrivate();
344353

345-
if ($annotationLines && $this->useAnnotations) {
354+
if ($this->useAttributesForDoctrineMapping) {
355+
foreach ($attributes as $attribute) {
356+
$newPropertyBuilder->addAttribute($attribute);
357+
}
358+
} elseif ($annotationLines && $this->useAnnotations) {
346359
$newPropertyBuilder->setDocComment($this->createDocBlock($annotationLines));
347360
}
348361

349-
foreach ($attributes as $attribute) {
350-
$newPropertyBuilder->addAttribute($attribute);
351-
}
352-
353362
if (null !== $defaultValue) {
354363
$newPropertyBuilder->setDefault($defaultValue);
355364
}
@@ -358,6 +367,17 @@ public function addProperty(string $name, array $annotationLines = [], $defaultV
358367
$this->addNodeAfterProperties($newPropertyNode);
359368
}
360369

370+
public function addAttributeToClass(string $attributeClass, array $options): void
371+
{
372+
$this->addUseStatementIfNecessary($attributeClass);
373+
374+
$classNode = $this->getClassNode();
375+
376+
$classNode->attrGroups[] = new Node\AttributeGroup([$this->buildAttributeNode($attributeClass, $options)]);
377+
378+
$this->updateSourceCodeFromNewStmts();
379+
}
380+
361381
public function addAnnotationToClass(string $annotationClass, array $options): void
362382
{
363383
$annotationClassAlias = $this->addUseStatementIfNecessary($annotationClass);
@@ -532,8 +552,9 @@ private function addSingularRelation(BaseRelation $relation): void
532552
} else {
533553
$attributes = [
534554
$this->buildAttributeNode(
535-
$relation instanceof RelationManyToOne ? 'ORM\\ManyToOne' : 'ORM\\OneToOne',
536-
$annotationOptions
555+
$relation instanceof RelationManyToOne ? ManyToOne::class : OneToOne::class,
556+
$annotationOptions,
557+
'ORM'
537558
),
538559
];
539560
}
@@ -544,9 +565,7 @@ private function addSingularRelation(BaseRelation $relation): void
544565
'nullable' => false,
545566
]);
546567
} else {
547-
$attributes[] = $this->buildAttributeNode('ORM\\JoinColumn', [
548-
'nullable' => false,
549-
]);
568+
$attributes[] = $this->buildAttributeNode(JoinColumn::class, ['nullable' => false], 'ORM');
550569
}
551570
}
552571

@@ -628,8 +647,9 @@ private function addCollectionRelation(BaseCollectionRelation $relation): void
628647
} else {
629648
$attributes = [
630649
$this->buildAttributeNode(
631-
$relation instanceof RelationManyToMany ? 'ORM\\ManyToMany' : 'ORM\\OneToMany',
632-
$annotationOptions
650+
$relation instanceof RelationManyToMany ? ManyToMany::class : OneToMany::class,
651+
$annotationOptions,
652+
'ORM'
633653
),
634654
];
635655
}
@@ -900,6 +920,30 @@ public function addUseStatementIfNecessary(string $class): string
900920
return $shortClassName;
901921
}
902922

923+
/**
924+
* Builds a PHPParser attribute node.
925+
*
926+
* @param string $attributeClass The attribute class which should be used for the attribute E.g. #[Column()]
927+
* @param array $options The named arguments for the attribute ($key = argument name, $value = argument value)
928+
* @param ?string $attributePrefix If a prefix is provided, the node is built using the prefix. E.g. #[ORM\Column()]
929+
*/
930+
public function buildAttributeNode(string $attributeClass, array $options, ?string $attributePrefix = null): Node\Attribute
931+
{
932+
$options = $this->sortOptionsByClassConstructorParameters($options, $attributeClass);
933+
934+
$context = $this;
935+
$nodeArguments = array_map(static function ($option, $value) use ($context) {
936+
return new Node\Arg($context->buildNodeExprByValue($value), false, false, [], new Node\Identifier($option));
937+
}, array_keys($options), array_values($options));
938+
939+
$class = $attributePrefix ? sprintf('%s\\%s', $attributePrefix, Str::getShortClassName($attributeClass)) : Str::getShortClassName($attributeClass);
940+
941+
return new Node\Attribute(
942+
new Node\Name($class),
943+
$nodeArguments
944+
);
945+
}
946+
903947
private function updateSourceCodeFromNewStmts(): void
904948
{
905949
$newCode = $this->printer->printFormatPreserving(
@@ -1421,27 +1465,6 @@ private function buildNodeExprByValue($value): Node\Expr
14211465
return $nodeValue;
14221466
}
14231467

1424-
/**
1425-
* builds an PHPParser attribute node.
1426-
*
1427-
* @param string $attributeClass the attribute class which should be used for the attribute
1428-
* @param array $options the named arguments for the attribute ($key = argument name, $value = argument value)
1429-
*/
1430-
private function buildAttributeNode(string $attributeClass, array $options): Node\Attribute
1431-
{
1432-
$options = $this->sortOptionsByClassConstructorParameters($options, $attributeClass);
1433-
1434-
$context = $this;
1435-
$nodeArguments = array_map(static function ($option, $value) use ($context) {
1436-
return new Node\Arg($context->buildNodeExprByValue($value), false, false, [], new Node\Identifier($option));
1437-
}, array_keys($options), array_values($options));
1438-
1439-
return new Node\Attribute(
1440-
new Node\Name($attributeClass),
1441-
$nodeArguments
1442-
);
1443-
}
1444-
14451468
/**
14461469
* sort the given options based on the constructor parameters for the given $classString
14471470
* this prevents code inspections warnings for IDEs like intellij/phpstorm.

tests/Util/ClassSourceManipulatorTest.php

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@
1111

1212
namespace Symfony\Bundle\MakerBundle\Tests\Util;
1313

14+
use Doctrine\ORM\Mapping\Column;
15+
use Doctrine\ORM\Mapping\Entity;
1416
use PhpParser\Builder\Param;
1517
use PHPUnit\Framework\TestCase;
1618
use Symfony\Bundle\MakerBundle\Doctrine\RelationManyToMany;
@@ -167,6 +169,41 @@ public function getAddSetterTests()
167169
];
168170
}
169171

172+
/**
173+
* @dataProvider getAttributeClassTests
174+
*/
175+
public function testAddAttributeToClass(string $sourceFilename, string $expectedSourceFilename, string $attributeClass, array $attributeOptions, string $attributePrefix = null): void
176+
{
177+
// @legacy Remove conditional when PHP < 8.0 support is dropped.
178+
if ((\PHP_VERSION_ID < 80000)) {
179+
$this->markTestSkipped('Requires PHP >= PHP 8.0');
180+
}
181+
182+
$source = file_get_contents(__DIR__.'/fixtures/source/'.$sourceFilename);
183+
$expectedSource = file_get_contents(__DIR__.'/fixtures/add_class_attribute/'.$expectedSourceFilename);
184+
$manipulator = new ClassSourceManipulator($source);
185+
$manipulator->addAttributeToClass($attributeClass, $attributeOptions, $attributePrefix);
186+
187+
self::assertSame($expectedSource, $manipulator->getSourceCode());
188+
}
189+
190+
public function getAttributeClassTests(): \Generator
191+
{
192+
yield 'Empty class' => [
193+
'User_empty.php',
194+
'User_empty.php',
195+
Entity::class,
196+
[],
197+
];
198+
199+
yield 'Class already has attributes' => [
200+
'User_simple.php',
201+
'User_simple.php',
202+
Column::class,
203+
['message' => 'We use this attribute for class level tests so we dont have to add additional test dependencies.'],
204+
];
205+
}
206+
170207
/**
171208
* @dataProvider getAnnotationTests
172209
*/
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
<?php
2+
3+
namespace App\Entity;
4+
5+
use Doctrine\ORM\Mapping\Entity;
6+
7+
#[Entity]
8+
class User
9+
{
10+
}
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+
use Doctrine\ORM\Mapping\Column;
7+
8+
#[ORM\Entity]
9+
#[Column(message: 'We use this attribute for class level tests so we dont have to add additional test dependencies.')]
10+
class User
11+
{
12+
#[ORM\Id]
13+
#[ORM\GeneratedValue]
14+
#[ORM\Column(type: 'integer')]
15+
private $id;
16+
17+
public function getId(): ?int
18+
{
19+
return $this->id;
20+
}
21+
}

0 commit comments

Comments
 (0)