Home > database >  Expected argument of type "string", "null" given at property path "password
Expected argument of type "string", "null" given at property path "password

Time:09-14

I created a form to allow user to register. Then, I used the same form to allow user to edit his informations. I created his profile page on login.html.twig and I wish to allow him to access to one button so that when he clicks on it, I can upload his picture, and without clicking on submit button his picture would be sent to data.

The file isn't stored and its name is not sent to my database, and it ended up with this error Expected argument of type "string", "null" given at property path "password" which I don't understand because the form is also display on the page where user can edit all other informations, and there is no problem to keep password inputs empty.

How can make it work ?

I'll be so grateful if you help me find the solution.

login.html.twig

{% block body %}

{% if app.user %}

{{ form_start(form, {'attr' : {'class' : 'position-absolute top-50 start-50 translate-middle'}} )  }}
    {{ form_widget(form.photo, {'attr' : {'onChange' : 'this.form.submit();'}} ) }}
    {{ form_widget(form.gender, {'attr' : {'class' : 'd-none'}}) }}
    {{ form_widget(form.lastname, {'attr' : {'class' : 'd-none'}}) }}
    {{ form_widget(form.firstname, {'attr' : {'class' : 'd-none'}}) }}
    {{ form_widget(form.birthdate, {'attr' : {'class' : 'd-none'}}) }}
    {{ form_widget(form.occupation, {'attr' : {'class' : 'd-none'}}) }}
    {{ form_widget(form.nationality, {'attr' : {'class' : 'd-none'}}) }}
    {{ form_widget(form.nativelanguage, {'attr' : {'class' : 'd-none'}}) }}
    {{ form_widget(form.email, {'attr' : {'class' : 'd-none'}}) }}
    {{ form_widget(form.password.first, {'attr' : {'class' : 'd-none'}}) }}
    {{ form_widget(form.password.second, {'attr' : {'class' : 'd-none'}}) }}
    {{ form_widget(form.save, {'attr' : {'class' : 'd-none'}}) }}
{{ form_end(form) }}

{% endif %}

{% endblock %}

UserController.php

<?php

namespace App\Controller;

use App\Entity\User;
use App\Form\UserType;
use App\Repository\UserRepository;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Component\String\Slugger\SluggerInterface;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\PasswordHasher\Hasher\UserPasswordHasherInterface;

class UserController extends AbstractController
{
    #[Route('/', name: 'app_user_index', methods: ['GET'])]
    public function index(UserRepository $userRepository): Response
    {
        return $this->render('user/index.html.twig', [
            'users' => $userRepository->findAll(),
        ]);
    }

    #[Route('/register', name: 'register', methods: ['GET', 'POST'])]
    public function createUser(
        Request $request,
        EntityManagerInterface $entityManagerInterface,
        UserPasswordHasherInterface $userPasswordHasherInterface
    ){
        $user = new User();
        $userform = $this->createForm(UserType::class, $user);
        $userform->handleRequest($request);

        if ($userform->isSubmitted() && $userform->isValid()) {
            $user->setRoles(["ROLE_USER"]);

            $plainPassword = $userform->get('password')->getData();
            $hashedPassword = $userPasswordHasherInterface->hashPassword($user, $plainPassword);
            $user->setPassword($hashedPassword);

            $entityManagerInterface->persist($user);
            $entityManagerInterface->flush();

            return $this->redirectToRoute('home');
        }

        return $this->render('front/register.html.twig', [
            'userform' => $userform->createView()
        ]);
    }

    #[Route('/user/{id}', name: 'app_user_show', methods: ['GET'])]
    public function show(User $user): Response
    {
        return $this->render('user/show.html.twig', [
            'user' => $user,
        ]);
    }
    
    #[Route('/profile', name: 'profile')]
    public function addPhoto(
        Request $request,
        UserRepository $userRepository,
        SluggerInterface $sluggerInterface,
        EntityManagerInterface $entityManagerInterface
    ){
        $connected = $this->getUser();
        $useremail = $connected->getUserIdentifier();
        $user = $userRepository->findOneBy(['email' => $useremail]);

        $userform = $this->createForm(UserType::class, $user);
        $userform->handleRequest($request);

        if ($userform->isSubmitted() && $userform->isValid()) {
            $imagefile = $userform->get('photo')->getData();

            if ($imagefile){
                $originalFileName = pathinfo($imagefile->getClientOriginalName(), PATHINFO_FILENAME);
                $safeFileName = $sluggerInterface->slug($originalFileName);
                $newFileName = $safeFileName . '-' . uniqid() . '.' . $imagefile->guessExtension();
    
                $imagefile->move(
                    $this->getParameter('images_directory'),
                    $newFileName
                );
    
                $user->setPhoto($newFileName);
            }

            $entityManagerInterface->persist($user);
            $entityManagerInterface->flush();
            return $this->redirectToRoute('login');
        }

        return $this->renderForm('security/login.html.twig', [
            'user' => $user,
            'form' => $userform,
        ]);
    }

    #[Route('/update', name: 'update')]
    public function updateUser(
        Request $request,
        UserRepository $userRepository,
        EntityManagerInterface $entityManagerInterface,
        UserPasswordHasherInterface $userPasswordHasherInterface
    ){
        $connected = $this->getUser();
        $useremail = $connected->getUserIdentifier();
        $user = $userRepository->findOneBy(['email' => $useremail]);

        $userform = $this->createForm(UserType::class, $user);
        $userform->handleRequest($request);

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

            $plainPassword = $userform->get('password')->getData();
            $hashedPassword = $userPasswordHasherInterface->hashPassword($user, $plainPassword);
            $user->setPassword($hashedPassword);

            $entityManagerInterface->persist($user);
            $entityManagerInterface->flush();
            return $this->redirectToRoute('home');
        }

        return $this->renderForm('front/edituser.html.twig', [
            'user' => $user,
            'form' => $userform,
        ]);
    }

    #[Route('/delete', name: 'delete')]
    public function deleteUser(
        UserRepository $userRepository,
        EntityManagerInterface $entityManagerInterface
    ){
        $connected = $this->getUser();
        $useremail = $connected->getUserIdentifier();
        $user = $userRepository->findOneBy(['email' => $useremail]);
        
        $entityManagerInterface->remove($user);
        $entityManagerInterface->flush();

        return $this->redirectToRoute('home');
    }
}

UserType.php

<?php

namespace App\Form;

use App\Entity\User;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\Form\Extension\Core\Type\FileType;
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
use Symfony\Component\Form\Extension\Core\Type\SubmitType;
use Symfony\Component\Form\Extension\Core\Type\CountryType;
use Symfony\Component\Form\Extension\Core\Type\BirthdayType;
use Symfony\Component\Form\Extension\Core\Type\LanguageType;
use Symfony\Component\Form\Extension\Core\Type\PasswordType;
use Symfony\Component\Form\Extension\Core\Type\RepeatedType;

class UserType extends AbstractType
{
    public function buildForm(FormBuilderInterface $builder, array $options): void
    {
        $builder
            ->add('gender', ChoiceType::class, [
                'choices' => [
                    'Je suis ...' => '',
                    'un homme' => 'male',
                    'une femme' =>'female',
                    'non-binaire' => 'non-binary'
                ]
            ])
            ->add('lastname')
            ->add('firstname')
            ->add('birthdate', BirthdayType::class, [
                'placeholder' => [
                    'year' => 'Année', 'month' => 'Mois', 'day' => 'Jour',
                ],
                'choice_translation_domain' => true
            ])
            ->add('occupation')
            ->add('nationality', CountryType::class, [
                'placeholder' => 'Je choisis un pays',
            ])
            ->add('nativelanguage', LanguageType::class, [
                'placeholder' => 'Je choisis ta langue maternelle',
            ])
            ->add('email')
            ->add('password', PasswordType::class, [
                'mapped' => false,
                'empty_data' => ''
            ])
            ->add('password', RepeatedType::class, [
                'type' => PasswordType::class,
                'invalid_message' => 'Les deux mots de passe doivent être identiques.',
                'options' => ['attr' => ['class' => 'password-field']],
                'required' => true,
                'first_options'  => ['label' => 'Password'],
                'second_options' => ['label' => 'Repeat Password'],
                'empty_data' => ''
            ])
            ->add('photo', FileType::class, [
                'mapped' => false,
            ])
            ->add('save', SubmitType::class, [
                'attr' => ['class' => 'save'],
            ])
        ;
    }

    public function configureOptions(OptionsResolver $resolver): void
    {
        $resolver->setDefaults([
            'data_class' => User::class,
            'translation_domain' => 'forms'
        ]);
    }
}

CodePudding user response:

Instead of {{form_end(form)}}, I used {{ form_end(form, {'render_rest': false}) }}, as explained in the Symfony Twig documentation.

It worked well. :)

CodePudding user response:

After many years of using symfony - I would also (as @Cerad already did) suggest you to create multiple form-classes for different scenarios. There's nothing wrong to have dozens *Type.php in your src/Form directory.

  • Create an UserRegisterationType for registration only.
  • Create an UserDataEditType to update user's data (e.g. from some kind of /admin where all fields are editable. Or maybe some additional fields, like a checkbox to resend an activation email to this user.)
  • Create an UserProfileEditType to let user update only some data if it's profile.

I think you get the Idea.

About user's profile page. (I know it's kinda off - but just to prove my point)

My personal rule of thumb - to create many "small" forms for such "special cases" like "change password" or/and "change email" or/and "delete account/profile". With such approach, you can react to specific action more precise. It's not convenient (and more error-prone) to have one big form, where user could change password and change email and click at only one submit button so save it all.

in UserProfileController.php


/**
 * PSEUDO_CODE. UNTESTED.
 *
 *
 * @param Request $reuqest
 * @return Response
 */
public function editProfile(Request $reuqest): Response
{
    // some mandatory stuff for user-profile page...
    
    $user = $this->getUser();
    $this->denyAccessUnlessGranted('ROLE_USER');


    $changePwdForm = $this->createForm(UserProfileChangePasswordType::class, $user);
    $changeEmailForm = $this->createForm(UserProfileChangeEmailType::class, $user);
    $deleteAccountForm = $this->createForm(UserProfileChangeEmailType::class, $user);


    if ($reuqest->isMethod(Request::METHOD_POST)) {
        $changePwdForm->handleRequest($reuqest);

        if ($changePwdForm->isSubmitted() && $changePwdForm->isValid()) {
            // apply your user-changed-pwd logic, save, add flash-message and redirect (e.g. to profile-edit page)
            // maybe you want to force user to re-login with new pwd after this... or log out user on all _other_ devices...
        }


        if ($changeEmailForm->isSubmitted() && $changeEmailForm->isValid()) {
            // same here.. apply your logic, save, maybe send confirmation-email to new address and so on...
            // redirect.
        }


        if ($deleteAccountForm->isSubmitted() && $deleteAccountForm->isValid()) {
            // double-check if user sure about this and, maybe, perform some additional checks...
            // save, logout and redirect to homepage..
        }

    }

    return $this->render('user_profile/edit_profile.html.twig', [
        'changePasswordForm' => $changePwdForm->createView(),
        'changeEmailForm'    => $changeEmailForm->createView(),
        'deleteAccountForm'  => $deleteAccountForm->createView(),
    ]);
}

somewhere in your user_profile/edit_profile.html.twig template

{% if changePasswordForm is defined and changePasswordForm|default %}
    {{ form_start(changePasswordForm) }}
    <fieldset>
        <legend>
            Change Password ...
        </legend>

        {{ form_rest(changePasswordForm) }}
    </fieldset>
    {{ form_end(changePasswordForm) }}
{% endif %}


{% if changeEmailForm is defined and changeEmailForm|default %}
    {{ form_start(changeEmailForm) }}
    <fieldset>
        <legend>
            Change E-mail ...
        </legend>

        {{ form_rest(changeEmailForm) }}
    </fieldset>
    {{ form_end(changeEmailForm) }}
{% endif %}


{% if deleteAccountForm is defined and deleteAccountForm|default %}
    {{ form_start(deleteAccountForm) }}
    <fieldset>
        <legend>
            Delete your account ...
        </legend>
        {{ form_rest(deleteAccountForm) }}
    </fieldset>
    {{ form_end(deleteAccountForm) }}
{% endif %}
  • Related