src/App/Form/Type/EncodedPasswordType.php line 23

Open in your IDE?
  1. <?php declare(strict_types=1);
  2. namespace App\Form\Type;
  3. use App\Entity\BackendUser;
  4. use App\Entity\User;
  5. use Symfony\Component\EventDispatcher\EventDispatcherInterface;
  6. use Symfony\Component\Form\AbstractType;
  7. use Symfony\Component\Form\Extension\Core\Type\PasswordType;
  8. use Symfony\Component\Form\FormBuilderInterface;
  9. use Symfony\Component\Form\FormEvent;
  10. use Symfony\Component\Form\FormEvents;
  11. use Symfony\Component\OptionsResolver\Options;
  12. use Symfony\Component\OptionsResolver\OptionsResolver;
  13. use Symfony\Component\PasswordHasher\Hasher\PasswordHasherFactoryInterface;
  14. use Symfony\Component\Validator\Constraints\Callback;
  15. use Symfony\Component\Validator\Constraints\Length;
  16. use Symfony\Component\Validator\Constraints\NotBlank;
  17. use Symfony\Component\Validator\Constraints\Regex;
  18. use Symfony\Component\Validator\Context\ExecutionContextInterface;
  19. use Symfony\Component\Validator\Exception\UnexpectedTypeException;
  20. class EncodedPasswordType extends AbstractType
  21. {
  22.     private PasswordHasherFactoryInterface $passwordHasherFactory;
  23.     public function __construct(PasswordHasherFactoryInterface $passwordHasherFactory)
  24.     {
  25.         $this->passwordHasherFactory $passwordHasherFactory;
  26.     }
  27.     /**
  28.      * @param mixed[] $options
  29.      */
  30.     public function buildForm(FormBuilderInterface $builder, array $options): void
  31.     {
  32.         $eventDispatcher $options['event_dispatcher'];
  33.         if (!$eventDispatcher instanceof EventDispatcherInterface) {
  34.             return;
  35.         }
  36.         $eventDispatcher->addListener(FormEvents::POST_SUBMIT, function (FormEvent $event): void {
  37.             $form $event->getForm();
  38.             $passwordField $form->get('password');
  39.             if ($passwordField->isDisabled() ||
  40.                 !$passwordField->isSubmitted() ||
  41.                 !$passwordField->isValid()
  42.             ) {
  43.                 return;
  44.             }
  45.             $plainPassword $passwordField->getData();
  46.             if (!is_string($plainPassword) || '' === $plainPassword) {
  47.                 return;
  48.             }
  49.             /** @var User|BackendUser $user */
  50.             $user $form->getData();
  51.             if (is_callable([$user'setPlainPassword'])) {
  52.                 $user->setPlainPassword($plainPassword);
  53.             }
  54.             $passwordEncoder $this->passwordHasherFactory->getPasswordHasher($user);
  55.             $user->setPassword($passwordEncoder->hash($plainPassword));
  56.         }, -200); // after validation
  57.     }
  58.     public function configureOptions(OptionsResolver $resolver): void
  59.     {
  60.         $resolver->setDefaults([
  61.             'mapped' => false// so encoder will  have a chance to set on parent user object and not be overridden
  62.             'password_validation_groups' => [],
  63.             'create' => false,
  64.             'constraints' => static function (Options $options): array {
  65.                 return [];
  66.             },
  67.         ]);
  68.         // $resolver->setDefault('password_validation_groups', ['Default', 'Registration']);
  69.         $resolver->setAllowedTypes('password_validation_groups', ['array']);
  70.         $resolver->setRequired('event_dispatcher');
  71.         $resolver->setAllowedTypes('event_dispatcher', [EventDispatcherInterface::class, 'null']);
  72.         $resolver->setAllowedTypes('create''bool');
  73.         $resolver->setNormalizer('constraints', static function (Options $options$constraints): array {
  74.             if (is_object($constraints)) {
  75.                 $constraints = [$constraints];
  76.             }
  77.             $groups $options['password_validation_groups'];
  78.             if (!is_array($groups)) {
  79.                 throw new UnexpectedTypeException($groups'array');
  80.             }
  81.             if (count($groups) > 0) {
  82.                 $constraints array_merge([
  83.                     new NotBlank([
  84.                         'message' => 'password.empty',
  85.                         'groups' => $groups,
  86.                     ]),
  87.                     new Length([
  88.                         'min' => 6,
  89.                         'minMessage' => 'password.length',
  90.                         'groups' => $groups,
  91.                     ]),
  92.                     new Regex(
  93.                         [
  94.                             'pattern' => '/(?=.*\p{Lu})(?=.*[0-9])/',
  95.                             'message' => 'password.pattern',
  96.                             'groups' => $groups,
  97.                         ]
  98.                     ),
  99.                 ], $constraints);
  100.             } else {
  101.                 if (!is_bool($options['create'])) {
  102.                     throw new UnexpectedTypeException($options['create'], 'bool');
  103.                 }
  104.                 $isCreate $options['create'];
  105.                 $constraints array_merge($constraints, [new Callback([
  106.                     'callback' => static function ($valueExecutionContextInterface $context) use ($isCreate): void {
  107.                         if (!$isCreate || $value) {
  108.                             return;
  109.                         }
  110.                         $context->buildViolation('password.empty')
  111.                             ->addViolation();
  112.                     },
  113.                 ])]);
  114.             }
  115.             return $constraints;
  116.         });
  117.     }
  118.     public function getParent(): ?string
  119.     {
  120.         return PasswordType::class;
  121.     }
  122. }