Symfony头像上传报错:password参数应为string却传入null
问题解决方案
核心问题分析
你遇到两个关键问题:
- 密码字段提交时抛出
Expected argument of type "string", "null" given错误 - 头像文件未存储到服务器,文件名也未写入数据库
问题1:密码字段错误原因
UserType中重复定义了两次password字段,第二次的RepeatedType设置了required=true- 头像上传页面隐藏了密码输入框,提交时密码字段返回
null,但hashPassword方法要求传入字符串类型参数 - 编辑场景下密码不应强制必填,仅注册时需要
问题2:文件上传失败原因
- 表单未设置
enctype="multipart/form-data",导致文件数据无法正确传递到后端 - 密码错误导致表单验证不通过,文件上传逻辑未被执行
分步修复方案
1. 清理并优化表单密码字段
修改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') // 移除重复的PasswordType字段,保留RepeatedType ->add('password', RepeatedType::class, [ 'type' => PasswordType::class, 'invalid_message' => 'Les deux mots de passe doivent être identiques.', 'options' => ['attr' => ['class' => 'password-field']], // 根据动态选项控制是否必填 'required' => $options['require_password'], 'first_options' => ['label' => 'Password'], 'second_options' => ['label' => 'Repeat Password'], 'empty_data' => '' ]) ->add('photo', FileType::class, [ 'mapped' => false, 'required' => false, // 头像上传非必填 ]) ->add('save', SubmitType::class, [ 'attr' => ['class' => 'save'], ]); } public function configureOptions(OptionsResolver $resolver): void { $resolver->setDefaults([ 'data_class' => User::class, 'translation_domain' => 'forms', // 默认注册场景必填密码 'require_password' => true ]); $resolver->setAllowedTypes('require_password', 'bool'); } }
2. 修改头像上传控制器
在UserController.php的addPhoto方法中,创建表单时设置密码非必填:
#[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, [ 'require_password' => false ]); $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, ]); }
3. 优化用户编辑控制器
修改updateUser方法,仅当密码字段不为空时才更新密码:
#[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, [ 'require_password' => false ]); $userform->handleRequest($request); if ($userform->isSubmitted() && $userform->isValid()) { $plainPassword = $userform->get('password')->getData(); // 仅当输入密码时才更新 if (!empty($plainPassword)) { $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, ]); }
4. 修复表单文件上传编码
修改login.html.twig,添加multipart/form-data编码类型:
{% block body %} {% if app.user %} {{ form_start(form, { 'attr' : {'class' : 'position-absolute top-50 start-50 translate-middle'}, 'enctype': 'multipart/form-data' } ) }} {{ 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 %}
内容的提问来源于stack exchange,提问作者Emilie Tossan
相关产品推荐
相关产品推荐

