SDK Form demonstrator

This commit is contained in:
Eric Espie
2025-04-14 15:38:24 +02:00
parent c4a9e980da
commit 6acd687e05
418 changed files with 46669 additions and 91 deletions

View File

@@ -0,0 +1,116 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Form\Extension\PasswordHasher\EventListener;
use Symfony\Component\Form\Exception\InvalidConfigurationException;
use Symfony\Component\Form\Extension\Core\Type\RepeatedType;
use Symfony\Component\Form\FormEvent;
use Symfony\Component\Form\FormInterface;
use Symfony\Component\PasswordHasher\Hasher\UserPasswordHasherInterface;
use Symfony\Component\PropertyAccess\PropertyAccess;
use Symfony\Component\PropertyAccess\PropertyAccessorInterface;
use Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface;
/**
* @author Sébastien Alfaiate <s.alfaiate@webarea.fr>
* @author Gábor Egyed <gabor.egyed@gmail.com>
*/
class PasswordHasherListener
{
private array $passwords = [];
public function __construct(
private UserPasswordHasherInterface $passwordHasher,
private ?PropertyAccessorInterface $propertyAccessor = null,
) {
$this->propertyAccessor ??= PropertyAccess::createPropertyAccessor();
}
/**
* @return void
*/
public function registerPassword(FormEvent $event)
{
if (null === $event->getData() || '' === $event->getData()) {
return;
}
$this->assertNotMapped($event->getForm());
$this->passwords[] = [
'form' => $event->getForm(),
'property_path' => $event->getForm()->getConfig()->getOption('hash_property_path'),
'password' => $event->getData(),
];
}
/**
* @return void
*/
public function hashPasswords(FormEvent $event)
{
$form = $event->getForm();
if (!$form->isRoot()) {
return;
}
if ($form->isValid()) {
foreach ($this->passwords as $password) {
$user = $this->getUser($password['form']);
$this->propertyAccessor->setValue(
$user,
$password['property_path'],
$this->passwordHasher->hashPassword($user, $password['password'])
);
}
}
$this->passwords = [];
}
private function getTargetForm(FormInterface $form): FormInterface
{
if (!$parentForm = $form->getParent()) {
return $form;
}
$parentType = $parentForm->getConfig()->getType();
do {
if ($parentType->getInnerType() instanceof RepeatedType) {
return $parentForm;
}
} while ($parentType = $parentType->getParent());
return $form;
}
private function getUser(FormInterface $form): PasswordAuthenticatedUserInterface
{
$parent = $this->getTargetForm($form)->getParent();
if (!($user = $parent?->getData()) || !$user instanceof PasswordAuthenticatedUserInterface) {
throw new InvalidConfigurationException(sprintf('The "hash_property_path" option only supports "%s" objects, "%s" given.', PasswordAuthenticatedUserInterface::class, get_debug_type($user)));
}
return $user;
}
private function assertNotMapped(FormInterface $form): void
{
if ($this->getTargetForm($form)->getConfig()->getMapped()) {
throw new InvalidConfigurationException('The "hash_property_path" option cannot be used on mapped field.');
}
}
}

View File

@@ -0,0 +1,36 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Form\Extension\PasswordHasher;
use Symfony\Component\Form\AbstractExtension;
use Symfony\Component\Form\Extension\PasswordHasher\EventListener\PasswordHasherListener;
/**
* Integrates the PasswordHasher component with the Form library.
*
* @author Sébastien Alfaiate <s.alfaiate@webarea.fr>
*/
class PasswordHasherExtension extends AbstractExtension
{
public function __construct(
private PasswordHasherListener $passwordHasherListener,
) {
}
protected function loadTypeExtensions(): array
{
return [
new Type\FormTypePasswordHasherExtension($this->passwordHasherListener),
new Type\PasswordTypePasswordHasherExtension($this->passwordHasherListener),
];
}
}

View File

@@ -0,0 +1,42 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Form\Extension\PasswordHasher\Type;
use Symfony\Component\Form\AbstractTypeExtension;
use Symfony\Component\Form\Extension\Core\Type\FormType;
use Symfony\Component\Form\Extension\PasswordHasher\EventListener\PasswordHasherListener;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\Form\FormEvents;
/**
* @author Sébastien Alfaiate <s.alfaiate@webarea.fr>
*/
class FormTypePasswordHasherExtension extends AbstractTypeExtension
{
public function __construct(
private PasswordHasherListener $passwordHasherListener,
) {
}
/**
* @return void
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->addEventListener(FormEvents::POST_SUBMIT, [$this->passwordHasherListener, 'hashPasswords']);
}
public static function getExtendedTypes(): iterable
{
return [FormType::class];
}
}

View File

@@ -0,0 +1,60 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Form\Extension\PasswordHasher\Type;
use Symfony\Component\Form\AbstractTypeExtension;
use Symfony\Component\Form\Extension\Core\Type\PasswordType;
use Symfony\Component\Form\Extension\PasswordHasher\EventListener\PasswordHasherListener;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\Form\FormEvents;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\PropertyAccess\PropertyPath;
/**
* @author Sébastien Alfaiate <s.alfaiate@webarea.fr>
*/
class PasswordTypePasswordHasherExtension extends AbstractTypeExtension
{
public function __construct(
private PasswordHasherListener $passwordHasherListener,
) {
}
/**
* @return void
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
if ($options['hash_property_path']) {
$builder->addEventListener(FormEvents::POST_SUBMIT, [$this->passwordHasherListener, 'registerPassword']);
}
}
/**
* @return void
*/
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
'hash_property_path' => null,
]);
$resolver->setAllowedTypes('hash_property_path', ['null', 'string', PropertyPath::class]);
$resolver->setInfo('hash_property_path', 'A valid PropertyAccess syntax where the hashed password will be set.');
}
public static function getExtendedTypes(): iterable
{
return [PasswordType::class];
}
}