EasyAdminBundle
EasyAdminBundle copied to clipboard
Doc: Add password integration example
Short description of what this feature will allow to do: Add to the doc a example for password genaration
Example of how to use this feature UserCrudController.php
/**
* @var UserPasswordEncoderInterface
*/
private $passwordEncoder;
/**
* @var Security
*/
private $security;
/**
* UserCrudController constructor.
* @param UserPasswordEncoderInterface $passwordEncoder
* @param Security $security
*/
public function __construct(
UserPasswordEncoderInterface $passwordEncoder,
Security $security
) {
$this->passwordEncoder = $passwordEncoder;
$this->security = $security;
// get the user id from the logged in user
if (null !== $this->security->getUser()) {
$this->password = $this->security->getUser()->getPassword();
}
}
/**
* @param string $pageName
* @return iterable
*/
public function configureFields(string $pageName): iterable
{
$password = TextField::new('password')
->setFormType(PasswordType::class)
->setFormTypeOption('empty_data', '')
->setRequired(false)
->setHelp('If the right is not given, leave the field blank.');
switch ($pageName) {
case Crud::PAGE_INDEX:
return [
$password,
];
break;
case Crud::PAGE_DETAIL:
return [
$password,
];
break;
case Crud::PAGE_NEW:
return [
$password,
];
break;
case Crud::PAGE_EDIT:
return [
$password,
];
break;
}
}
/**
*
* @param EntityManagerInterface $entityManager
* @param $entityInstance
*/
public function updateEntity(EntityManagerInterface $entityManager, $entityInstance): void
{
// set new password with encoder interface
if (method_exists($entityInstance, 'setPassword')) {
$clearPassword = trim($this->get('request_stack')->getCurrentRequest()->request->all('User')['password']);
// if user password not change save the old one
if (isset($clearPassword) === true && $clearPassword === '') {
$entityInstance->setPassword($this->password);
} else {
$encodedPassword = $this->passwordEncoder->encodePassword($this->getUser(), $clearPassword);
$entityInstance->setPassword($encodedPassword);
}
}
parent::updateEntity($entityManager, $entityInstance);
}
I tried implementing your code but i found an issue.
If you submit the form with an empty password for the currently logged in User $this->getUser()->getPassword()
will return an empty string. Therefore in your database the user will no longer have a password.
Edit: to make it work i had to override the entire edit action to save the $currentPassword
in a variable. If the $entityInstance->getPassword()
call returns null
, i reset the password to $currentPassword
In my EA its work? Please show full example off youre code Thanks
I don't have the non working code anymore. I had more or less the same thing as yourself but if i edited the current logged in user i would lose the password.
I Fixit found the bug. I Edit my first coment
Hi, I just needed to solve a similar problem today: the password reset management in the admin backend
Actually I also found some problems in the implementation but the way to solve it is correct. So i decided to remake the @rogergerecke 's solution and post it here.
- Insert in the User entity a not-mapped field (with getter/setter methods): it is used to handle the clear password.
namespace App\Entity;
use App\Repository\UserRepository;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Security\Core\User\UserInterface;
/**
* @ORM\Entity(repositoryClass=UserRepository::class)
*/
class User implements UserInterface
{
//...
/**
* @var string clear password for backend
*/
private $clearpassword;
/**
* @return string
*/
public function getClearpassword(): string
{
if( $this->clearpassword == null ) return "";
return $this->clearpassword;
}
/**
* @param string $clearpassword
*/
public function setClearpassword(string $clearpassword): void
{
$this->clearpassword = $clearpassword;
}
//...
- Inject in the User CRUD controller the passwordEncoder and override the updateEntity method to set the encoded password in the entity
MyLog is a utitlity class for debugging, I leave it to you for readability ...
namespace App\Controller\Admin;
use App\Entity\User;
use App\Utils\MyLog;
use Doctrine\ORM\EntityManagerInterface;
use EasyCorp\Bundle\EasyAdminBundle\Config\Crud;
use EasyCorp\Bundle\EasyAdminBundle\Controller\AbstractCrudController;
use EasyCorp\Bundle\EasyAdminBundle\Field\ArrayField;
use EasyCorp\Bundle\EasyAdminBundle\Field\TextField;
use Symfony\Component\Form\Extension\Core\Type\PasswordType;
use Symfony\Component\Security\Core\Encoder\UserPasswordEncoderInterface;
class UserCrudController extends AbstractCrudController
{
/**
* @var UserPasswordEncoderInterface
*/
private $passwordEncoder;
/**
* UserCrudController constructor.
* @param UserPasswordEncoderInterface $passwordEncoder
*/
public function __construct(
UserPasswordEncoderInterface $passwordEncoder
) {
$this->passwordEncoder = $passwordEncoder;
}
public static function getEntityFqcn(): string
{
return User::class;
}
public function configureFields(string $pageName): iterable
{
$password = TextField::new('clearpassword')
->setLabel("New Password")
->setFormType(PasswordType::class)
->setFormTypeOption('empty_data', '')
->setRequired(false)
->setHelp('If the right is not given, leave the field blank.')
->hideOnIndex();
return [
// ...
$password,
// ...
];
}
public function updateEntity(EntityManagerInterface $entityManager, $entityInstance): void
{
// set new password with encoder interface
if (method_exists($entityInstance, 'setPassword')) {
$clearPassword = trim($this->get('request_stack')->getCurrentRequest()->request->all()['User']['clearpassword']);
///MyLog::info("clearPass:" . $clearPassword);
// save password only if is set a new clearpass
if ( !empty($clearPassword) ) {
////MyLog::info("clearPass not empty! encoding password...");
$encodedPassword = $this->passwordEncoder->encodePassword($this->getUser(), $clearPassword);
$entityInstance->setPassword($encodedPassword);
}
}
parent::updateEntity($entityManager, $entityInstance);
}
}
@labgua and @rogergerecke your solution works great but will throw an error when there are some AJAX requests in Index, like BooleanField.
A possible solution to avoid this could be to check if it is a Xml request or not.
Update the function updateEntity to:
public function updateEntity(EntityManagerInterface $entityManager, $entityInstance): void
{
// set new password with encoder interface
if (method_exists($entityInstance, 'setPassword') && !$this->get('request_stack')->getCurrentRequest()->isXmlHttpRequest()) {
$clearPassword = trim($this->get('request_stack')->getCurrentRequest()->request->all()['User']['clearpassword']);
///MyLog::info("clearPass:" . $clearPassword);
// save password only if is set a new clearpass
if ( !empty($clearPassword) ) {
////MyLog::info("clearPass not empty! encoding password...");
$encodedPassword = $this->passwordEncoder->encodePassword($this->getUser(), $clearPassword);
$entityInstance->setPassword($encodedPassword);
}
}
parent::updateEntity($entityManager, $entityInstance);
}
Here is my solution with EasyAdmin v3 and form events. It works even if the password field is mandatory.
<?php
namespace App\Controller\Admin;
use App\Entity\User;
use EasyCorp\Bundle\EasyAdminBundle\Config\KeyValueStore;
use EasyCorp\Bundle\EasyAdminBundle\Context\AdminContext;
use EasyCorp\Bundle\EasyAdminBundle\Dto\EntityDto;
use EasyCorp\Bundle\EasyAdminBundle\Field\Field;
use EasyCorp\Bundle\EasyAdminBundle\Field\FormField;
use Symfony\Component\Form\Extension\Core\Type\PasswordType;
use Symfony\Component\Form\Extension\Core\Type\RepeatedType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\Form\FormEvent;
use Symfony\Component\Form\FormEvents;
use Symfony\Component\Security\Core\Encoder\UserPasswordEncoderInterface;
class UserCrudController extends AbstractCrudController
{
/** @var UserPasswordEncoderInterface */
private $passwordEncoder;
public static function getEntityFqcn(): string
{
return User::class;
}
public function configureFields(string $pageName): iterable
{
return [
FormField::addPanel('Change password')->setIcon('fa fa-key'),
Field::new('plainPassword', 'New password')->onlyOnForms()
->setFormType(RepeatedType::class)
->setFormTypeOptions([
'type' => PasswordType::class,
'first_options' => ['label' => 'New password'],
'second_options' => ['label' => 'Repeat password'],
]),
];
}
public function createEditFormBuilder(EntityDto $entityDto, KeyValueStore $formOptions, AdminContext $context): FormBuilderInterface
{
$formBuilder = parent::createEditFormBuilder($entityDto, $formOptions, $context);
$this->addEncodePasswordEventListener($formBuilder);
return $formBuilder;
}
public function createNewFormBuilder(EntityDto $entityDto, KeyValueStore $formOptions, AdminContext $context): FormBuilderInterface
{
$formBuilder = parent::createNewFormBuilder($entityDto, $formOptions, $context);
$this->addEncodePasswordEventListener($formBuilder);
return $formBuilder;
}
/**
* @required
*/
public function setEncoder(UserPasswordEncoderInterface $passwordEncoder): void
{
$this->passwordEncoder = $passwordEncoder;
}
protected function addEncodePasswordEventListener(FormBuilderInterface $formBuilder)
{
$formBuilder->addEventListener(FormEvents::SUBMIT, function (FormEvent $event) {
/** @var User $user */
$user = $event->getData();
if ($user->getPlainPassword()) {
$user->setPassword($this->passwordEncoder->encodePassword($user, $user->getPlainPassword()));
}
});
}
}
Hello,
I have an issue on this getter. When I submit a form with empty passwords, I had an error "Expected argument of type "string", "null" given at property path "plainPassword".
Do you encourter the same issue ?
/**
* @return string
*/
public function getPlainPassword()
{
if( $this->plainPassword == null ) return "";
return $this->plainPassword;
}
Hi.
It seems to me that your error is coming from the setter and not the getter. If an argument is expected it would be at the setter level.
Can you share your setter's code please ?
Thanks for your answer.
Here is my setter, It is configured as described above 👍
/**
* @param string $plainPassword
*/
public function setPlainPassword(string $plainPassword): void
{
$this->plainPassword = $plainPassword;
}
Do you think I should modify something in this setter ?
No problem !
Yes, your function's argument is typed as a non nullable string
.
In order to be able to send null to the setPlainPassword
method you need to type your argument ?string
. The question mark specifies that your argument can be null instead of a string.
Marvelous, it works ! Thanks for your help.
So, for everyone who read this thead, here is my setter
/**
* @param string $plainPassword
*/
public function setPlainPassword(?string $plainPassword): void
{
$this->plainPassword = $plainPassword;
}
The solution at https://github.com/EasyCorp/EasyAdminBundle/issues/3349#issuecomment-695214741 works nicely, however, it doesn't allow EasyAdmin to expose any other fields on the User entity.
I don't see a way to do that by changing how the configureFields()
function works to return a Symfony form, rather than a list of FieldInterface
s.
For instance, the auto-upgrade from EasyAdmin 2 to 3 gives me this:
public function configureFields(string $pageName): iterable
{
$email = TextField::new('email');
$password = TextField::new('password');
$firstName = TextField::new('firstName');
$lastName = TextField::new('lastName');
$created = DateTimeField::new('created');
$updated = DateTimeField::new('updated');
if (Crud::PAGE_INDEX === $pageName) {
return [$id, $email, $firstName, $lastName, $created, $updated];
} elseif (Crud::PAGE_DETAIL === $pageName) {
return [$id, $email, $roles, $firstName, $lastName, $created, $updated];
} elseif (Crud::PAGE_NEW === $pageName) {
return [$email, $password, $firstName, $lastName, $created, $updated];
} elseif (Crud::PAGE_EDIT === $pageName) {
return [$email, $password, $firstName, $lastName, $created, $updated];
}
}
Is there a way to use the code from https://github.com/EasyCorp/EasyAdminBundle/issues/3349#issuecomment-695214741 here?
Would it not rather make more sense to create a PasswordField
which extends FieldInterface
?
it doesn't allow EasyAdmin to expose any other fields on the User entity
I am not sure to understand what you mean. I gave the minimal working example.
You can of course add as many fields as you need by adding them to the returned array.
I achieved this by using EA events. Like this
<?php
namespace App\Controller\Admin;
use App\Entity\User;
use EasyCorp\Bundle\EasyAdminBundle\Config\Crud;
use EasyCorp\Bundle\EasyAdminBundle\Controller\AbstractCrudController;
use EasyCorp\Bundle\EasyAdminBundle\Event\BeforeEntityPersistedEvent;
use EasyCorp\Bundle\EasyAdminBundle\Event\BeforeEntityUpdatedEvent;
use EasyCorp\Bundle\EasyAdminBundle\Field\TextField;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\Form\Extension\Core\Type\PasswordType;
use Symfony\Component\Security\Core\Encoder\UserPasswordEncoderInterface;
class UserCrudController extends AbstractCrudController implements EventSubscriberInterface
{
/** @var UserPasswordEncoderInterface */
private $passwordEncoder;
public function __construct(UserPasswordEncoderInterface $passwordEncoder)
{
$this->passwordEncoder = $passwordEncoder;
}
public static function getEntityFqcn(): string
{
return User::class;
}
public function configureFields(string $pageName): iterable
{
return array_map(function ($f) use ($pageName) {
if ($f->getAsDto()->getProperty() === 'password') {
$field = TextField::new('plain_password', Crud::PAGE_NEW === $pageName ? 'Password' : 'Change password')
->setFormType(PasswordType::class);
if (Crud::PAGE_NEW === $pageName) {
$field->setRequired(true);
}
return $field;
}
return $f;
}, parent::configureFields($pageName));
}
public static function getSubscribedEvents()
{
return [
BeforeEntityPersistedEvent::class => 'encodePassword',
BeforeEntityUpdatedEvent::class => 'encodePassword',
];
}
/** @internal */
public function encodePassword($event)
{
$user = $event->getEntityInstance();
if ($user instanceof User && $user->getPlainPassword()) {
$user->setPassword($this->passwordEncoder->encodePassword($user, $user->getPlainPassword()));
}
}
}
I achieved this by using EA events. Like this
<?php namespace App\Controller\Admin; use App\Entity\User; use EasyCorp\Bundle\EasyAdminBundle\Config\Crud; use EasyCorp\Bundle\EasyAdminBundle\Controller\AbstractCrudController; use EasyCorp\Bundle\EasyAdminBundle\Event\BeforeEntityPersistedEvent; use EasyCorp\Bundle\EasyAdminBundle\Event\BeforeEntityUpdatedEvent; use EasyCorp\Bundle\EasyAdminBundle\Field\TextField; use Symfony\Component\EventDispatcher\EventSubscriberInterface; use Symfony\Component\Form\Extension\Core\Type\PasswordType; use Symfony\Component\Security\Core\Encoder\UserPasswordEncoderInterface; class UserCrudController extends AbstractCrudController implements EventSubscriberInterface { /** @var UserPasswordEncoderInterface */ private $passwordEncoder; public function __construct(UserPasswordEncoderInterface $passwordEncoder) { $this->passwordEncoder = $passwordEncoder; } public static function getEntityFqcn(): string { return User::class; } public function configureFields(string $pageName): iterable { return array_map(function ($f) use ($pageName) { if ($f->getAsDto()->getProperty() === 'password') { $field = TextField::new('plain_password', Crud::PAGE_NEW === $pageName ? 'Password' : 'Change password') ->setFormType(PasswordType::class); if (Crud::PAGE_NEW === $pageName) { $field->setRequired(true); } return $field; } return $f; }, parent::configureFields($pageName)); } public static function getSubscribedEvents() { return [ BeforeEntityPersistedEvent::class => 'encodePassword', BeforeEntityUpdatedEvent::class => 'encodePassword', ]; } /** @internal */ public function encodePassword($event) { $user = $event->getEntityInstance(); if ($user->getPlainPassword()) { $user->setPassword($this->passwordEncoder->encodePassword($user, $user->getPlainPassword())); } } }
don't forget to put
if (!($user instanceof User)) { return; }
Yep. Or
if ($user instanceof User && $user->getPlainPassword()) {
Comment above updated
Is there a special reason to put the subsriber logic in the controller? I normally do this in a seperate subscriber class
Is there a special reason to put the subsriber logic in the controller?
No, it is just for example. Feel free to do it as you wish :wink: You can also subscribe to doctrine events for store encoded password from any place of your app.
This really should be in the docs.
@milosa make a PR then
Here is my solution with EasyAdmin v3 and form events. It works even if the password field is mandatory.
<?php namespace App\Controller\Admin; use App\Entity\User; use EasyCorp\Bundle\EasyAdminBundle\Config\KeyValueStore; use EasyCorp\Bundle\EasyAdminBundle\Context\AdminContext; use EasyCorp\Bundle\EasyAdminBundle\Dto\EntityDto; use EasyCorp\Bundle\EasyAdminBundle\Field\Field; use EasyCorp\Bundle\EasyAdminBundle\Field\FormField; use Symfony\Component\Form\Extension\Core\Type\PasswordType; use Symfony\Component\Form\Extension\Core\Type\RepeatedType; use Symfony\Component\Form\FormBuilderInterface; use Symfony\Component\Form\FormEvent; use Symfony\Component\Form\FormEvents; use Symfony\Component\Security\Core\Encoder\UserPasswordEncoderInterface; class UserCrudController extends AbstractCrudController { /** @var UserPasswordEncoderInterface */ private $passwordEncoder; public static function getEntityFqcn(): string { return User::class; } public function configureFields(string $pageName): iterable { return [ FormField::addPanel('Change password')->setIcon('fa fa-key'), Field::new('plainPassword', 'New password')->onlyOnForms() ->setFormType(RepeatedType::class) ->setFormTypeOptions([ 'type' => PasswordType::class, 'first_options' => ['label' => 'New password'], 'second_options' => ['label' => 'Repeat password'], ]), ]; } public function createEditFormBuilder(EntityDto $entityDto, KeyValueStore $formOptions, AdminContext $context): FormBuilderInterface { $formBuilder = parent::createEditFormBuilder($entityDto, $formOptions, $context); $this->addEncodePasswordEventListener($formBuilder); return $formBuilder; } public function createNewFormBuilder(EntityDto $entityDto, KeyValueStore $formOptions, AdminContext $context): FormBuilderInterface { $formBuilder = parent::createNewFormBuilder($entityDto, $formOptions, $context); $this->addEncodePasswordEventListener($formBuilder); return $formBuilder; } /** * @required */ public function setEncoder(UserPasswordEncoderInterface $passwordEncoder): void { $this->passwordEncoder = $passwordEncoder; } protected function addEncodePasswordEventListener(FormBuilderInterface $formBuilder) { $formBuilder->addEventListener(FormEvents::SUBMIT, function (FormEvent $event) { /** @var User $user */ $user = $event->getData(); if ($user->getPlainPassword()) { $user->setPassword($this->passwordEncoder->encodePassword($user, $user->getPlainPassword())); } }); } }
@Seb33300 I have tried to do that but I get this mistake "Call to a member function encodePassword() on null". How can I solve that?
@CristinaEsteban97 autowiring
needs to be enabled in order to be able to use @required
annotation.
See https://symfony.com/doc/current/service_container/autowiring.html#autowiring-other-methods-e-g-setters-and-public-typed-properties
@ CristinaEsteban97
autowiring
debe estar habilitado para poder usar la@required
anotación. Consulte https://symfony.com/doc/current/service_container/autowiring.html#autowiring-other-methods-eg-setters-and-public-typed-properties
@Seb33300 Solved! But I don't undenstand why we need the @required anotation to the method setEncoder() and @var UserPasswordEncoderInterface to the var passwordEncoder. Can you explain me that please?
It looks like a constructor replacement to me. Why not setting it in the constructor?
@CristinaEsteban97 awesome! Your example works perfectly for v4. Thank you.
I achieved this by using EA events. Like this
<?php namespace App\Controller\Admin; use App\Entity\User; use EasyCorp\Bundle\EasyAdminBundle\Config\Crud; use EasyCorp\Bundle\EasyAdminBundle\Controller\AbstractCrudController; use EasyCorp\Bundle\EasyAdminBundle\Event\BeforeEntityPersistedEvent; use EasyCorp\Bundle\EasyAdminBundle\Event\BeforeEntityUpdatedEvent; use EasyCorp\Bundle\EasyAdminBundle\Field\TextField; use Symfony\Component\EventDispatcher\EventSubscriberInterface; use Symfony\Component\Form\Extension\Core\Type\PasswordType; use Symfony\Component\Security\Core\Encoder\UserPasswordEncoderInterface; class UserCrudController extends AbstractCrudController implements EventSubscriberInterface { /** @var UserPasswordEncoderInterface */ private $passwordEncoder; public function __construct(UserPasswordEncoderInterface $passwordEncoder) { $this->passwordEncoder = $passwordEncoder; } public static function getEntityFqcn(): string { return User::class; } public function configureFields(string $pageName): iterable { return array_map(function ($f) use ($pageName) { if ($f->getAsDto()->getProperty() === 'password') { $field = TextField::new('plain_password', Crud::PAGE_NEW === $pageName ? 'Password' : 'Change password') ->setFormType(PasswordType::class); if (Crud::PAGE_NEW === $pageName) { $field->setRequired(true); } return $field; } return $f; }, parent::configureFields($pageName)); } public static function getSubscribedEvents() { return [ BeforeEntityPersistedEvent::class => 'encodePassword', BeforeEntityUpdatedEvent::class => 'encodePassword', ]; } /** @internal */ public function encodePassword($event) { $user = $event->getEntityInstance(); if ($user instanceof User && $user->getPlainPassword()) { $user->setPassword($this->passwordEncoder->encodePassword($user, $user->getPlainPassword())); } } }
Hi! Excuse me, where can I put the rest of the field configuration? (I'm new to EA, sorry)
"Attempted to call an undefined method named "getAsDto" of class "Generator".
"
Reading all the previous comments (thanks a lot guys for your examples) I've got it working en EasyAdmin 4 + Symfony 5.4 (php 8.1.1). Works for me on edit user and new user actions. In the New User action password field is required. In the Edit User action it is not. You can pass a blank password and the current one won't be changed.
<?php
#Controller/Admin/UserCrudController.php
namespace App\Controller\Admin;
use App\Entity\User;
use EasyCorp\Bundle\EasyAdminBundle\Config\KeyValueStore;
use EasyCorp\Bundle\EasyAdminBundle\Context\AdminContext;
use EasyCorp\Bundle\EasyAdminBundle\Controller\AbstractCrudController;
use EasyCorp\Bundle\EasyAdminBundle\Dto\EntityDto;
use EasyCorp\Bundle\EasyAdminBundle\Field\AssociationField;
use EasyCorp\Bundle\EasyAdminBundle\Field\ChoiceField;
use EasyCorp\Bundle\EasyAdminBundle\Field\EmailField;
use EasyCorp\Bundle\EasyAdminBundle\Field\Field;
use EasyCorp\Bundle\EasyAdminBundle\Field\FormField;
use EasyCorp\Bundle\EasyAdminBundle\Field\TextField;
use Symfony\Component\Form\Extension\Core\Type\PasswordType;
use Symfony\Component\Form\Extension\Core\Type\RepeatedType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\Form\FormEvent;
use Symfony\Component\Form\FormEvents;
use Symfony\Component\PasswordHasher\Hasher\UserPasswordHasherInterface;
class UserCrudController extends AbstractCrudController {
private UserPasswordHasherInterface $passwordEncoder;
public function __construct( UserPasswordHasherInterface $passwordEncoder ) {
$this->passwordEncoder = $passwordEncoder;
}
public static function getEntityFqcn(): string {
return User::class;
}
public function configureFields( string $pageName ): iterable {
yield FormField::addPanel( 'User data' )->setIcon( 'fa fa-user' );
yield EmailField::new( 'email' )->onlyWhenUpdating()->setDisabled();
yield EmailField::new( 'email' )->onlyWhenCreating();
yield TextField::new( 'email' )->onlyOnIndex();
$roles = [ 'ROLE_SUPER_ADMIN', 'ROLE_ADMIN', 'ROLE_USER' ];
yield ChoiceField::new( 'roles' )
->setChoices( array_combine( $roles, $roles ) )
->allowMultipleChoices()
->renderAsBadges();
yield FormField::addPanel( 'Change password' )->setIcon( 'fa fa-key' );
yield Field::new( 'password', 'New password' )->onlyWhenCreating()->setRequired( true )
->setFormType( RepeatedType::class )
->setFormTypeOptions( [
'type' => PasswordType::class,
'first_options' => [ 'label' => 'New password' ],
'second_options' => [ 'label' => 'Repeat password' ],
'error_bubbling' => true,
'invalid_message' => 'The password fields do not match.',
] );
yield Field::new( 'password', 'New password' )->onlyWhenUpdating()->setRequired( false )
->setFormType( RepeatedType::class )
->setFormTypeOptions( [
'type' => PasswordType::class,
'first_options' => [ 'label' => 'New password' ],
'second_options' => [ 'label' => 'Repeat password' ],
'error_bubbling' => true,
'invalid_message' => 'The password fields do not match.',
] );
}
public function createEditFormBuilder( EntityDto $entityDto, KeyValueStore $formOptions, AdminContext $context ): FormBuilderInterface {
$plainPassword = $entityDto->getInstance()?->getPassword();
$formBuilder = parent::createEditFormBuilder( $entityDto, $formOptions, $context );
$this->addEncodePasswordEventListener( $formBuilder, $plainPassword );
return $formBuilder;
}
public function createNewFormBuilder( EntityDto $entityDto, KeyValueStore $formOptions, AdminContext $context ): FormBuilderInterface {
$formBuilder = parent::createNewFormBuilder( $entityDto, $formOptions, $context );
$this->addEncodePasswordEventListener( $formBuilder );
return $formBuilder;
}
protected function addEncodePasswordEventListener( FormBuilderInterface $formBuilder, $plainPassword = null ): void {
$formBuilder->addEventListener( FormEvents::SUBMIT, function ( FormEvent $event ) use ( $plainPassword ) {
/** @var User $user */
$user = $event->getData();
if ( $user->getPassword() !== $plainPassword ) {
$user->setPassword( $this->passwordEncoder->hashPassword( $user, $user->getPassword() ) );
}
} );
}
}
In the User class I've set password as nullable:
<?php
#Entity/User.php
namespace App\Entity;
use App\Repository\UserRepository;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface;
use Symfony\Component\Security\Core\User\UserInterface;
#[ORM\Entity( repositoryClass: UserRepository::class )]
class User implements UserInterface, PasswordAuthenticatedUserInterface {
#[ORM\Column( type: 'string', nullable: true )]
private ?string $password = null;
public function getPassword(): ?string {
return $this->password;
}
public function setPassword( ?string $password ): self {
if (!is_null($password)) {
$this->password = $password;
}
return $this;
}
And to avoid deprecation messages I've also set this:
#config/packages/framework.yaml
framework:
form:
legacy_error_messages: false
Hello! I'm trying @luismisanchez solution on Symfony 6.1.5, with EA 4.3 and I'm getting an error. I don't really know what is happening.
The error is as follow:
App\Controller\Admin\UserCrudController::addEncodePasswordEventListener(): Argument #1 ($formBuilder) must be of type Symfony\Component\Form\Test\FormBuilderInterface, Symfony\Component\Form\FormBuilder given, called in /opt/homebrew/var/www/cesida/src/Controller/Admin/UserCrudController.php on line 77
The implementation of the UserCrudController
and the User
entity is the same as @luismisanchez solution.
Im new in PHP and Symfony and I don't understand why if FormBuilder
implements the FormBuilderInterface
interface, I'm getting this error.
Any help would be much appreciated :D
You probably have the wrong import in your UserCrudController?
Should be use Symfony\Component\Form\FormBuilderInterface;