Skip to content

Commit b771178

Browse files
bocharsky-bwweaverryan
authored andcommitted
[make:reset-password] Translate exception reasons provided by ResetPasswordBundle
1 parent 085358c commit b771178

15 files changed

+269
-75
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/Maker/MakeResetPassword.php

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,8 +44,10 @@
4444
use Symfony\Component\PasswordHasher\Hasher\UserPasswordHasherInterface;
4545
use Symfony\Component\Routing\Annotation\Route;
4646
use Symfony\Component\Security\Core\Encoder\UserPasswordEncoderInterface;
47+
use Symfony\Component\Translation\Translator;
4748
use Symfony\Component\Validator\Validation;
4849
use Symfony\Component\Yaml\Yaml;
50+
use Symfony\Contracts\Translation\TranslatorInterface;
4951
use SymfonyCasts\Bundle\ResetPassword\Controller\ResetPasswordControllerTrait;
5052
use SymfonyCasts\Bundle\ResetPassword\Exception\ResetPasswordExceptionInterface;
5153
use SymfonyCasts\Bundle\ResetPassword\Model\ResetPasswordRequestInterface;
@@ -236,6 +238,18 @@ public function generate(InputInterface $input, ConsoleStyle $io, Generator $gen
236238
EntityManagerInterface::class,
237239
];
238240

241+
// Namespace for ResetPasswordExceptionInterface was imported above
242+
$problemValidateMessageOrConstant = \defined('SymfonyCasts\Bundle\ResetPassword\Exception\ResetPasswordExceptionInterface::MESSAGE_PROBLEM_VALIDATE')
243+
? 'ResetPasswordExceptionInterface::MESSAGE_PROBLEM_VALIDATE'
244+
: "'There was a problem validating your password reset request'";
245+
$problemHandleMessageOrConstant = \defined('SymfonyCasts\Bundle\ResetPassword\Exception\ResetPasswordExceptionInterface::MESSAGE_PROBLEM_HANDLE')
246+
? 'ResetPasswordExceptionInterface::MESSAGE_PROBLEM_HANDLE'
247+
: "'There was a problem handling your password reset request'";
248+
249+
if ($isTranslatorAvailable = class_exists(Translator::class)) {
250+
$useStatements[] = TranslatorInterface::class;
251+
}
252+
239253
$generator->generateController(
240254
$controllerClassNameDetails->getFullName(),
241255
'resetPassword/ResetPasswordController.tpl.php',
@@ -253,6 +267,9 @@ public function generate(InputInterface $input, ConsoleStyle $io, Generator $gen
253267
'password_hasher_class_details' => ($passwordClassDetails = $generator->createClassNameDetails($passwordHasher, '\\')),
254268
'password_hasher_variable_name' => str_replace('Interface', '', sprintf('$%s', lcfirst($passwordClassDetails->getShortName()))), // @legacy see passwordHasher conditional above
255269
'use_password_hasher' => UserPasswordHasherInterface::class === $passwordHasher, // @legacy see passwordHasher conditional above
270+
'problem_validate_message_or_constant' => $problemValidateMessageOrConstant,
271+
'problem_handle_message_or_constant' => $problemHandleMessageOrConstant,
272+
'translator_available' => $isTranslatorAvailable,
256273
]
257274
);
258275

src/Resources/skeleton/authenticator/Security52EmptyAuthenticator.tpl.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
88
use Symfony\Component\Security\Core\Exception\AuthenticationException;
99
use Symfony\Component\Security\Http\Authenticator\AbstractAuthenticator;
10-
<?= $use_legacy_passport_interface ? 'use Symfony\\Component\\Security\\Http\\Authenticator\\Passport\\PassportInterface;'."\n" : '' ?>
10+
use <?= $use_legacy_passport_interface ? 'Symfony\\Component\\Security\\Http\\Authenticator\\Passport\\PassportInterface' : 'Symfony\\Component\\Security\\Http\\Authenticator\\Passport\\Passport' ?>;
1111

1212
class <?php echo $class_name ?> extends AbstractAuthenticator
1313
{

src/Resources/skeleton/resetPassword/ResetPasswordController.tpl.php

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -34,15 +34,16 @@ public function __construct(ResetPasswordHelperInterface $resetPasswordHelper, E
3434
* @Route("", name="app_forgot_password_request")
3535
*/
3636
<?php } ?>
37-
public function request(Request $request, MailerInterface $mailer): Response
37+
public function request(Request $request, MailerInterface $mailer<?php if ($translator_available): ?>, TranslatorInterface $translator<?php endif ?>): Response
3838
{
3939
$form = $this->createForm(<?= $request_form_type_class_name ?>::class);
4040
$form->handleRequest($request);
4141

4242
if ($form->isSubmitted() && $form->isValid()) {
4343
return $this->processSendingPasswordResetEmail(
4444
$form->get('<?= $email_field ?>')->getData(),
45-
$mailer
45+
$mailer<?php if ($translator_available): ?>,
46+
$translator<?php endif ?><?= "\n" ?>
4647
);
4748
}
4849

@@ -84,7 +85,7 @@ public function checkEmail(): Response
8485
* @Route("/reset/{token}", name="app_reset_password")
8586
*/
8687
<?php } ?>
87-
public function reset(Request $request, <?= $password_hasher_class_details->getShortName() ?> <?= $password_hasher_variable_name ?>, string $token = null): Response
88+
public function reset(Request $request, <?= $password_hasher_class_details->getShortName() ?> <?= $password_hasher_variable_name ?><?php if ($translator_available): ?>, TranslatorInterface $translator<?php endif ?>, string $token = null): Response
8889
{
8990
if ($token) {
9091
// We store the token in session and remove it from the URL, to avoid the URL being
@@ -103,8 +104,9 @@ public function reset(Request $request, <?= $password_hasher_class_details->getS
103104
$user = $this->resetPasswordHelper->validateTokenAndFetchUser($token);
104105
} catch (ResetPasswordExceptionInterface $e) {
105106
$this->addFlash('reset_password_error', sprintf(
106-
'There was a problem validating your reset request - %s',
107-
$e->getReason()
107+
'%s - %s',
108+
<?php if ($translator_available): ?>$translator->trans(<?= $problem_validate_message_or_constant ?>, [], 'ResetPasswordBundle')<?php else: ?><?= $problem_validate_message_or_constant ?><?php endif ?>,
109+
<?php if ($translator_available): ?>$translator->trans($e->getReason(), [], 'ResetPasswordBundle')<?php else: ?>$e->getReason()<?php endif ?><?= "\n" ?>
108110
));
109111

110112
return $this->redirectToRoute('app_forgot_password_request');
@@ -138,7 +140,7 @@ public function reset(Request $request, <?= $password_hasher_class_details->getS
138140
]);
139141
}
140142

141-
private function processSendingPasswordResetEmail(string $emailFormData, MailerInterface $mailer): RedirectResponse
143+
private function processSendingPasswordResetEmail(string $emailFormData, MailerInterface $mailer<?php if ($translator_available): ?>, TranslatorInterface $translator<?php endif ?>): RedirectResponse
142144
{
143145
$user = $this->entityManager->getRepository(<?= $user_class_name ?>::class)->findOneBy([
144146
'<?= $email_field ?>' => $emailFormData,
@@ -157,8 +159,9 @@ private function processSendingPasswordResetEmail(string $emailFormData, MailerI
157159
// Caution: This may reveal if a user is registered or not.
158160
//
159161
// $this->addFlash('reset_password_error', sprintf(
160-
// 'There was a problem handling your password reset request - %s',
161-
// $e->getReason()
162+
// '%s - %s',
163+
// <?php if ($translator_available): ?>$translator->trans(<?= $problem_handle_message_or_constant ?>, [], 'ResetPasswordBundle')<?php else: ?><?= $problem_handle_message_or_constant ?><?php endif ?>,
164+
// <?php if ($translator_available): ?>$translator->trans($e->getReason(), [], 'ResetPasswordBundle')<?php else: ?>$e->getReason()<?php endif ?><?= "\n" ?>
162165
// ));
163166

164167
return $this->redirectToRoute('app_check_email');

src/Test/MakerTestRunner.php

Lines changed: 29 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -170,18 +170,38 @@ public function removeFromFile(string $filename, string $find, bool $allowNotFou
170170

171171
public function configureDatabase(bool $createSchema = true): void
172172
{
173-
$this->replaceInFile(
174-
'.env',
175-
'postgresql://symfony:ChangeMe@127.0.0.1:5432/app?serverVersion=13&charset=utf8',
176-
getenv('TEST_DATABASE_DSN')
177-
);
173+
// @legacy Drop conditional when Symfony 4.4 is no longer supported.
174+
if (50000 > $this->environment->getSymfonyVersionInApp()) {
175+
$this->replaceInFile(
176+
'.env',
177+
'postgresql://db_user:db_password@127.0.0.1:5432/db_name?serverVersion=13&charset=utf8',
178+
getenv('TEST_DATABASE_DSN')
179+
);
180+
} else {
181+
$this->replaceInFile(
182+
'.env',
183+
'postgresql://symfony:ChangeMe@127.0.0.1:5432/app?serverVersion=13&charset=utf8',
184+
getenv('TEST_DATABASE_DSN')
185+
);
186+
}
178187

179188
// Flex includes a recipe to suffix the dbname w/ "_test" - lets keep
180189
// things simple for these tests and not do that.
181-
$this->removeFromFile(
182-
'config/packages/test/doctrine.yaml',
183-
"dbname_suffix: '_test%env(default::TEST_TOKEN)%'"
184-
);
190+
$this->modifyYamlFile('config/packages/doctrine.yaml', function (array $config) {
191+
if (isset($config['when@test']['doctrine']['dbal']['dbname_suffix'])) {
192+
unset($config['when@test']['doctrine']['dbal']['dbname_suffix']);
193+
}
194+
195+
return $config;
196+
});
197+
198+
// @legacy DoctrineBundle 2.4 recipe uses when@test instead of a test/doctrine.yaml config
199+
if ($this->filesystem->exists('config/packages/test/doctrine.yaml')) {
200+
$this->removeFromFile(
201+
'config/packages/test/doctrine.yaml',
202+
"dbname_suffix: '_test%env(default::TEST_TOKEN)%'"
203+
);
204+
}
185205

186206
// this looks silly, but it's the only way to drop the database *for sure*,
187207
// as doctrine:database:drop will error if there is no database

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.

0 commit comments

Comments
 (0)