Symfony 登录页面的表单生成器

Form builder for Symfony login page

如何创建使用表单生成器和表单辅助函数来呈现视图的自定义登录页面?我终于想通了!我的解决方案如下。

为什么需要它?

好吧,我想使用表单生成器来创建 Symfony 登录页面,因为我想使用表单辅助函数来呈现视图。这样我的字段将始终使用正确的模板(手动编写表单 - 根据 Symfony 书籍示例 - 意味着如果主模板已更新,则必须手动完成对登录表单的更改 - 并且有可能忘记!)

我使用了没有 class 的 createFormBuilder(参见 Symfony - Using a Form without a Class),因此我可以只呈现登录所需的字段:

/**
 * @Route("/login", name="login")
 */
public function loginAction(Request $request)
{
    if ($this->get('security.authorization_checker')->isGranted('IS_AUTHENTICATED_FULLY')) {
        $this->addFlash('warning', 'You are already fully logged in.');
        return $this->redirectToRoute('my_homepage');
    } else {
        $authenticationUtils = $this->get('security.authentication_utils');
        $defaultData = array('username' => $authenticationUtils->getLastUsername());
        $form = $this->createFormBuilder($defaultData)
            ->add('username', \Symfony\Component\Form\Extension\Core\Type\TextType::class)
            ->add('password', \Symfony\Component\Form\Extension\Core\Type\PasswordType::class)
            ->add('logIn', \Symfony\Component\Form\Extension\Core\Type\SubmitType::class)
            ->getForm();
        if (!is_null($authenticationUtils->getLastAuthenticationError(false))) {
            $form->addError(new \Symfony\Component\Form\FormError(
                $authenticationUtils->getLastAuthenticationError()->getMessageKey()
            ));
        }
        $form->handleRequest($request);
        return $this->render('login.html.twig', array(
                'form' => $form->createView(),
                )
        );
    }
}

然后我以通常的方式呈现表单,除了我使用 full_name 表单 twig 变量(参见 Symfony - Form Variables Reference)来设置 _username 和 [= 的正确字段名称16=]:

{% extends 'base.html.twig' %}

{% block title %}Log in to {{ app.request.host }}{% endblock %}

{% block body %}
  <h1>Account Log In</h1>

  {{ form_start(form) }}
  {{ form_row(form.username, {'full_name': '_username'}) }}
  {{ form_row(form.password, {'full_name': '_password'}) }}
  {{ form_errors(form) }}
  {{ form_end(form) }}
{% endblock %}

我不确定这仅限于哪个版本 - 我使用的是当前的 3.0。

编辑

如果您想知道.. 是的,您可以使用 FormBuilderInterface 创建表单,以防您想在其他地方使用相同的表单(使用不同的模板)。这是相当标准的。只需创建您的 LoginType 文件(或您选择的任何名称):

namespace AppBundle\Form;

use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\Extension\Core\Type\PasswordType;
use Symfony\Component\Form\Extension\Core\Type\SubmitType;

class LoginType extends AbstractType
{

    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $builder
                ->add('username', TextType::class)
                ->add('password', PasswordType::class)
                ->add('logIn', SubmitType::class);
    }

}

然后使用 createForm() 获取您的表单:

$form = $this->createForm(\AppBundle\Form\LoginType::class, $defaultData);

你可以做得更简单更好(内置错误处理),解决方案就在 Symfony 中 :) ...看看这个 class 作为例子 UserLoginType

<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Bundle\SecurityBundle\Tests\Functional\Bundle\CsrfFormLoginBundle\Form;

use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\Form\FormError;
use Symfony\Component\Form\FormEvents;
use Symfony\Component\Form\FormEvent;
use Symfony\Component\HttpFoundation\RequestStack;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\Security\Core\Security;

/**
 * Form type for use with the Security component's form-based authentication
 * listener.
 *
 * @author Henrik Bjornskov <henrik@bjrnskov.dk>
 * @author Jeremy Mikola <jmikola@gmail.com>
 */
class UserLoginType extends AbstractType
{
    private $requestStack;

    public function __construct(RequestStack $requestStack)
    {
        $this->requestStack = $requestStack;
    }

    /**
     * {@inheritdoc}
     */
    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $builder
            ->add('username', 'Symfony\Component\Form\Extension\Core\Type\TextType')
            ->add('password', 'Symfony\Component\Form\Extension\Core\Type\PasswordType')
            ->add('_target_path', 'Symfony\Component\Form\Extension\Core\Type\HiddenType')
        ;

        $request = $this->requestStack->getCurrentRequest();

        /* Note: since the Security component's form login listener intercepts
         * the POST request, this form will never really be bound to the
         * request; however, we can match the expected behavior by checking the
         * session for an authentication error and last username.
         */
        $builder->addEventListener(FormEvents::PRE_SET_DATA, function (FormEvent $event) use ($request) {
            if ($request->attributes->has(Security::AUTHENTICATION_ERROR)) {
                $error = $request->attributes->get(Security::AUTHENTICATION_ERROR);
            } else {
                $error = $request->getSession()->get(Security::AUTHENTICATION_ERROR);
            }

            if ($error) {
                $event->getForm()->addError(new FormError($error->getMessage()));
            }

            $event->setData(array_replace((array) $event->getData(), array(
                'username' => $request->getSession()->get(Security::LAST_USERNAME),
            )));
        });
    }

    /**
     * {@inheritdoc}
     */
    public function configureOptions(OptionsResolver $resolver)
    {
        /* Note: the form's csrf_token_id must correspond to that for the form login
         * listener in order for the CSRF token to validate successfully.
         */

        $resolver->setDefaults(array(
            'csrf_token_id' => 'authenticate',
        ));
    }
}

如果你想做干净的事情,我建议使用专门的工厂来构建你的表单:

控制器:

public function signInAction(SignInFormFactory $formFactory): Response
{
    $form = $formFactory->createForm();

    return $this->render('Security/SignIn.html.twig', array(
        'form' => $form->createView(),
    ));
}

表单工厂:

<?php

namespace App\Form\Factory;

use App\Form\SignInForm;
use Symfony\Component\Form\FormError;
use Symfony\Component\Form\FormFactoryInterface;
use Symfony\Component\Form\FormInterface;
use Symfony\Component\Security\Http\Authentication\AuthenticationUtils;

class SignInFormFactory
{
    protected $formFactory;
    protected $authenticationUtils;

    public function __construct(FormFactoryInterface $formFactory, AuthenticationUtils $authenticationUtils)
    {
        $this->formFactory = $formFactory;
        $this->authenticationUtils = $authenticationUtils;
    }

    public function createForm(): FormInterface
    {
        $form = $this->formFactory->create(SignInForm::class);
        $form->get('_username')->setData($this->authenticationUtils->getLastUsername());

        if ($error = $this->authenticationUtils->getLastAuthenticationError()) {
            $form->addError(new FormError($error->getMessage()));
        }

        return $form;
    }
}

表单类型:

<?php

namespace App\Form;

use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\CheckboxType;
use Symfony\Component\Form\Extension\Core\Type\PasswordType;
use Symfony\Component\Form\Extension\Core\Type\SubmitType;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;

class SignInForm extends AbstractType
{
    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $builder
            ->add('_username', TextType::class)
            ->add('_password', PasswordType::class)
            ->add('_remember_me', CheckboxType::class, array(
                'required' => false,
                'data' => true,
            ))
            ->add('submit', SubmitType::class)
        ;
    }

    public function configureOptions(OptionsResolver $resolver)
    {
        $resolver->setDefaults(array(
            'csrf_field_name' => '_csrf_token',
            'csrf_token_id' => 'authenticate',
        ));
    }

    public function getBlockPrefix()
    {
        return '';
    }
}

此示例在 security.yaml.

中的登录表单的默认配置下运行良好