Asked  2 Months ago    Answers:  5   Viewed   41 times

I executed the make:crud command for generate some files for the entity user.

All works like a charm, but I have one problem when I edit a user. When I edit a user, I can :

  • change the password and save

or

  • don't modify the password and save

From the generated user 'edit' controller:

/**
 * @Route("/{id}/edit", name="user_edit", methods={"GET","POST"})
 */
public function edit(Request $request, User $user): Response
{
    $form = $this->createForm(UserType::class, $user);
    $form->handleRequest($request);

    if ($form->isSubmitted() && $form->isValid()) {

        // HERE : How can I check if the password was changed ? 

        $this->getDoctrine()->getManager()->flush();

        return $this->redirectToRoute('user_index', [
            'id' => $user->getId(),
        ]);
    }

    return $this->render('user/edit.html.twig', [
        'user' => $user,
        'form' => $form->createView(),
        'title' => 'edit userr'
    ]);
}

How can I check if the password was changed ? The user variable contains the new password value...

If the password is new, I must use the encoder service. If not, I just must update the user with new data

 Answers

45

For this it helps to have a class variable in your User that is not written to the database. In your form you can then use this variable to temporarily store the updated password in, that you then encode and remove. This is what the eraseCredentials()-method in the UserInterface is for.

For example in your User you could have

class User implements UserInterface
{
    private $plainPassword;

    // ...

    public function setPlainPassword(string $plainPassword)
    {
        $this->plainPassword = $plainPassword;
    }

    public function getPlainPassword()
    {
        return $this->plainPassword;
    }

    public function eraseCredentials()
    {
        $this->plainPassword = null;
    }
}

Notice how private $plainPassword does not have any ORM-annotations, that means it will not be stored in the database. You can however use validation constraints, e.g. if you want to make sure that passwords have a minimum length or a certain complexity. You will still require the original password field that stores the encrypted password.

You then add this field to your user update-form instead of the actual password field. In your controller you can then only check if the new plainPassword-field was filled out and then read the value, encode it and replace the actual password field.

if ($form->isSubmitted() && $form->isValid()) {
    $user = $form->getData();
    if ($user->getPlainPassword() !== null) {
        $user->setPassword($this->userPasswordEncoder->encode(
            $user->getPlainPassword(),
            $user
        );
    }

    // ...

Another way of doing this, without adding this "helper"-property to the user is using an unmapped form field:

# UserForm
public function buildForm(FormBuilderInterface $builder, array $options)
{
    $builder
        // ...
        ->add('plainPassword', PasswordType::class, ['mapped' => false])
    ;
}

The controller will look similar, only you fetch the data from the unmapped field instead of from the user:

 $form->get('plainPassword')->getData();
Wednesday, September 1, 2021
 
Hunex
answered 2 Months ago
13

This is due to the deprecation of the templating component, see https://symfony.com/blog/new-in-symfony-4-3-deprecated-the-templating-component-integration

Solution:

  1. Remove "symfony/templating" from composer.json
  2. Remove this from framework.yaml:
    templating:
        engines:
            - twig
    
  3. Run composer update

This should remove all the deprecation warnings.

If you're getting this error

Cannot autowire service "...": argument "$templating" of method "__construct()" references interface "SymfonyBundleFrameworkBundleTemplatingEngineInterface" but no such service exists. Did you create a class that implements this interface?

... you're still using the old templating in some service.
Solution: Change the dependency from SymfonyBundleFrameworkBundleTemplatingEngineInterface to TwigEnvironment:

use TwigEnvironment;

private $twig;

public function __construct(Environment $twig)
{
    $this->twig = $twig;
}

See also https://github.com/symfony/symfony/issues/31645

Wednesday, March 31, 2021
 
brombeer
answered 7 Months ago
49

As Lawrence Cherone stated in the comments looks like it doesn't work with the built-in php server but it does work with the composer require server included in Symfony Recipes.

php bin/console server:run
Wednesday, March 31, 2021
 
nhunston
answered 7 Months ago
74

You can add a new error to ModelStateDictionary if the user exist in the db.

Also, Looks like your view is only sending FullName and LastName. In that case, why not keep your view model to have only those properties so that your view specific-view model will not have any tight coupling with your entity models.

public class CreateUserVM
{
  [Required]
  public string FullName { set;get;}
  public string LastName { set;get;}
}

And in your GET action make sure you are sending an object of this

public ActionResult Create()
{
  return View(new CreateUserVM());
}

and your view will be strongly typed to our flat view model

@model CreateUserVM
@using(Html.BeginForm())
{
  @Html.ValidationSummary(false)

  <label>Full Name</label>
  @Html.TextBoxFor(s=>s.FullName)

  <label>Last Name</label>
  @Html.TextBoxFor(s=>s.LastName)

  <input type="submit" />
}

and your HttpPost action method will be

public ActionResult Create(CreateUserVM model)
{
    if (ModelState.IsValid)
    {
       var exist= db.Users.Any(x => x.FullName == model.FullName)
       if(exist)
       {
          ModelState.AddModelError(string.Empty, "Username exists");
          return View(vmModel);
       }

        var user = new Users()
        {
            FullName = model.FullName,
            LastName = model.LastName
        };
        //Inserting in Parent table to get the Id that we will used in Child table.
        db.Users.Add(user);
        db.SaveChanges();
        return ReidrectToAction("Index"); //PRG pattern
    }
    return View(vmModel);
}
Saturday, August 7, 2021
 
superhero
answered 3 Months ago
88

You have to put the DATABASE_URL into your phpunit.xml file, e.g. like this:

<php>
        <ini name="error_reporting" value="-1" />
        <env name="KERNEL_CLASS" value="AppKernel" />
        <env name="APP_ENV" value="test" />

        <!-- ###+ doctrine/doctrine-bundle ### -->
        <!-- Format described at http://docs.doctrine-project.org/projects/doctrine-dbal/en/latest/reference/configuration.html#connecting-using-a-url -->
        <!-- For an SQLite database, use: "sqlite:///%kernel.project_dir%/var/data.db" -->
        <!-- Configure your db driver and server_version in config/packages/doctrine.yaml -->
        <env name="DATABASE_URL" value="mysql://root@127.0.0.1:3306/symfony4-database"/>
        <!-- ###- doctrine/doctrine-bundle ### -->
    </php>
Monday, August 23, 2021
 
danjah
answered 2 Months ago
Only authorized users can answer the question. Please sign in first, or register a free account.
Not the answer you're looking for? Browse other questions tagged :
 
Share