Symfony 6.2注册表单提交数据为空及CSRF令牌异常求助
Symfony 6.2注册表单提交无数据+CSRF令牌问题排查
问题背景
在Symfony 6.2中沿用Symfony 2/3/4的方式构建注册表单,提交后无法获取数据,还曾出现CSRF令牌错误(已临时禁用)。
相关代码
表单类型(SignupType)
class SignupType extends AbstractType { public function buildForm(FormBuilderInterface $builder, array $options): void { $builder->add('firstName', TextType::class, [ 'required' => true, ]); $builder->add('lastName', TextType::class, [ 'required' => true, ]); $builder->add('email', TextType::class, [ 'required' => true, ]); $builder->add('password', PasswordType::class, [ 'required' => true, ]); $builder->add('confirmPassword', PasswordType::class, [ 'required' => true, ]); } public function configureOptions(OptionsResolver $resolver) { $resolver->setDefaults([ 'data_class' => SignupModel::class ]); } }
数据模型(SignupModel)
class SignupModel { /** * @Assert\NotNull(message="First name is required.") * @var string */ private string $firstName; /** * @Assert\NotNull(message="Last name is required.") * @var string */ private string $lastName; /** * @Assert\NotNull(message="Please enter a valid email address.") * @var string */ private string $email; /** * @Assert\NotNull(message="Password is required") * @var string */ private string $password; /** * @Assert\NotNull(message="Confirm password is required") * @var string */ private string $confirmPassword; public function getFirstName(): string { return $this->firstName; } public function setFirstName(string $firstName): void { $this->firstName = $firstName; } public function getLastName(): string { return $this->lastName; } public function setLastName(string $lastName): void { $this->lastName = $lastName; } public function getEmail(): string { return $this->email; } public function setEmail(string $email): void { $this->email = $email; } public function getPassword(): string { return $this->password; } public function setPassword(string $password): void { $this->password = $password; } public function getConfirmPassword(): string { return $this->confirmPassword; } public function setConfirmPassword(string $confirmPassword): void { $this->confirmPassword = $confirmPassword; } }
控制器代码
$form = $this->createForm(SignupType::class, new SignupModel()); if ($request->getMethod() !== Request::METHOD_POST) { return $this->render("signup.html.twig", [ 'signupForm' => $form->createView() ]); } $form->handleRequest($request); if (!$form->isValid() && $form->getErrors()->count()) { $this->addFlash('danger', (string)$form->getErrors()); return $this->redirectToRoute("signup"); } dump($form->getData()); die;
Twig模板代码
<form class="js-validate needs-validation" action="{{ path('signup') }}" method="post"> <div class="mb-5"> <h3 class="card-title">Signup</h3> </div> {# <div class="row"> <div class="col-sm-6"> <div class="mb-4"> {{ form_row(signupForm.firstName) }} </div> </div> <div class="col-sm-6"> <div class="mb-4"> {{ form_row(signupForm.lastName) }} </div> </div> </div> <div class="mb-4"> {{ form_row(signupForm.email) }} </div> <div class="mb-4"> <div class="input-group-merge"> {{ form_row(signupForm.password) }} </div> </div> <div class="mb-4"> {{ form_row(signupForm.confirmPassword) }} </div> #} {#<input type="hidden" id="signup__token" name="signup[_token]" value="{{ csrf_token('authenticate') }}">#} {{ form_rest(signupForm) }} {#{% include "shared/_captcha.html.twig" %}#} {#<input type="hidden" name="_token" value="{{ csrf_token('authenticate') }}" />#} <div class="form-check mb-4"> <label class="form-check-label" for="termsCheckbox">I accept the <a href="#">Terms and Conditions</a></label> <input class="form-check-input" type="checkbox" value="" id="termsCheckbox" required> </div> {% if error is defined %} <div class="alert alert-danger"> {{ error.messageKey|trans(error.messageData, 'security') }} </div> {% endif %} <div class="d-grid gap-4"> <button name="signup" type="submit" class="btn btn-primary btn-lg">Signup</button> <div class="mb-4"> <p class="card-text text-muted"> Already have an account? <a class="link" href="{{ path('login') }}">Login</a> </p> </div> </div> {#{{ form_end(signupForm) }}#} </form>
问题解决步骤
1. 修复表单字段渲染问题
模板中所有form_row调用都被注释,仅保留form_rest——但form_rest只会渲染未显式输出的字段,注释所有字段后,页面上没有任何输入框,提交自然无数据。
取消字段渲染代码的注释,确保每个表单字段都被正确渲染:
<div class="row"> <div class="col-sm-6"> <div class="mb-4"> {{ form_row(signupForm.firstName) }} </div> </div> <div class="col-sm-6"> <div class="mb-4"> {{ form_row(signupForm.lastName) }} </div> </div> </div> <div class="mb-4"> {{ form_row(signupForm.email) }} </div> <div class="mb-4"> <div class="input-group-merge"> {{ form_row(signupForm.password) }} </div> </div> <div class="mb-4"> {{ form_row(signupForm.confirmPassword) }} </div>
2. 修复CSRF令牌问题
不要手动注释或添加CSRF令牌代码,Symfony表单组件会自动处理。使用form_end(signupForm)代替手动闭合表单,它会自动渲染CSRF令牌隐藏字段:
- 删除所有手动添加CSRF令牌的注释代码
- 取消
{{ form_end(signupForm) }}的注释,替换掉手动的</form>标签
修改后的表单结尾:
<div class="d-grid gap-4"> <button name="signup" type="submit" class="btn btn-primary btn-lg">Signup</button> <div class="mb-4"> <p class="card-text text-muted"> Already have an account? <a class="link" href="{{ path('login') }}">Login</a> </p> </div> </div> {{ form_end(signupForm) }}
3. 优化控制器逻辑
控制器中!$form->isValid() && $form->getErrors()->count()的判断逻辑冗余,isValid()已经包含错误检查,改为:
$form->handleRequest($request); if (!$form->isValid()) { $errors = []; foreach ($form->getErrors(true) as $error) { $errors[] = $error->getMessage(); } $this->addFlash('danger', implode(', ', $errors)); return $this->redirectToRoute("signup"); }
4. 验证字段映射
确认SignupModel的属性名与表单字段名完全一致,且所有属性都有对应的getter和setter(当前代码已满足)。
内容的提问来源于stack exchange,提问作者rashidkhan
相关产品推荐
相关产品推荐

