How to save file without clicking the submit button ? Symfony 6.0.2

Viewed 35

Expected argument of type "string", "null" given at property path "password".

I don't understand why I have this error when I submit my form. I wanted to be sure to not have problem any more by adding 'empty_data' => '' to my password inputs, but it didn't solve the problem.

Note : I gave "d-none" class to password inputs and save button because thanks to onChange attribute, I wish to send directly the file when it's been uploaded.

edit.html.twig

{{ form_start(form)  }}
    {{ form_widget(form.photo, {'attr' : {'onChange' : 'this.form.submit();'}} ) }}
    {{ 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) }}

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\SubmitType;
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('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'
        ]);
    }
}

SecurityController.php

<?php

namespace App\Controller;

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\Security\Http\Authentication\AuthenticationUtils;

class SecurityController extends AbstractController
{
    #[Route(path: '/login', name: 'login')]
    public function login(AuthenticationUtils $authenticationUtils): Response
    {
        // if ($this->getUser()) {
        //     return $this->redirectToRoute('target_path');
        // }

        // get the login error if there is one
        $error = $authenticationUtils->getLastAuthenticationError();
        // last username entered by the user
        $lastUsername = $authenticationUtils->getLastUsername();
            
        return $this->render('security/login.html.twig', ['last_username' => $lastUsername, 'error' => $error]);
    }

    #[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(path: '/logout', name: 'logout')]
    public function logout(): void
    {
        throw new \LogicException('This method can be blank - it will be intercepted by the logout key on your firewall.');
    }
}
1 Answers

I think the "problem" is in User::class and not in SecurityController.php or somewhere else. To be more exact, in setPassword() method of your User-entity.

I assume your setter expecting only string

/**
 * @param string $password
 */
public function setPassword(string $password)
{
    $this->password = $password;
}

Since you do not show password-field, and it stays empty it becomes NULL, but your setPassword() expects only string.

So you could change your setter to accept both types string or null


/**
 * @param string|null $password
 */
public function setPassword(?string $password = null)
{
    $this->password = $password;
}

As an alternative, you can make your password-fields also disabled. You'll need to use "setRendered()" on both fields to get rid of form-labels too.

{{ form_start(form)  }}
    {{ form_widget(form.photo, {'attr' : {'onChange' : 'this.form.submit();'}} ) }}
    {{ form_widget(form.password.first, {disabled: true, attr: { class: 'd-none' }} ) }}
    {% do form.password.first.setRendered %}
    {{ form_widget(form.password.second, {disabled: true, attr: { class: 'd-none' }} ) }}
    {% do form.password.second.setRendered %}
    {{ form_widget(form.save, {'attr' : {'class' : 'd-none'}}) }}
{{ form_end(form) }}

But it's not an ideal solution! I would reconsider UserType::class (maybe two different forms. One with password fields and another without.) Otherwise, you have to set your password-field as optional.


//somewhere in your UserType::class

$builder->add('password', RepeatedType::class, [ 
 // your other options
 'required' => false,
]);

 
Related