表单

表单是Symfony中最易被误用的组件之一,只因它博大精深且功能无限。本章提供了一些最佳实践,令你巧用表单而快速上手。

构建表单 

Best Practice

把表单定义在PHP类中

表单组件允许你在控制器中快速构建表单。当你不需要在别处复用表单时这是很好用的。但是为了组织代码和可复用,我们推荐你把每一个表单定义在它们自己的PHP类中:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
namespace AppBundle\Form;
 
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\Form\Extension\Core\Type\TextareaType;
use Symfony\Component\Form\Extension\Core\Type\EmailType;
use Symfony\Component\Form\Extension\Core\Type\DateTimeType;
 
class PostType extends AbstractType
{
    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $builder
            ->add('title')
            ->add('summary', TextareaType::class)
            ->add('content', TextareaType::class)
            ->add('authorEmail', EmailType::class)
            ->add('publishedAt', DateTimeType::class)
        ;
    }
 
    public function configureOptions(OptionsResolver $resolver)
    {
        $resolver->setDefaults(array(
            'data_class' => 'AppBundle\Entity\Post'
        ));
    }
}

Best Practice

把form type(表单类型)类放到AppBundle\Form命名空间下,除非你使用了其他的表单定制类,比如data transformers。

要使用这个类,使用createForm()即可,然后把FQCN完整类名传入:

1
2
3
4
5
6
7
8
9
10
11
// ...
use AppBundle\Form\PostType;
 
// ...
public function newAction(Request $request)
{
    $post = new Post();
    $form = $this->createForm(PostType::class, $post);
 
    // ...
}

注册表单为服务 

你也可以将表单注册为服务。这仅在你的表单类型需要利用容器将一些依赖注入进来时才有意义,否则,这会造成不必要的过载,因此,对于所有form type类,皆不推荐这样做(即将form type注册为服务)。

表单BUTTON配置 

表单类应该不被知悉“要被用在哪里”。这样它们才能在未来更容易被复用。

Best Practice

在模板中去添加表单按钮,而不是在form type类或控制器中。

Symfony表单组件允许你在表单中把buttons作为字段来添加。这是个很好的方式,可以简化模板渲染表单。但如果你在表单类中直接添加按钮,将会实质上影响那个表单的使用范围:

1
2
3
4
5
6
7
8
9
10
11
12
class PostType extends AbstractType
{
    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $builder
            // ...
            ->add('save', SubmitType::class, array('label' => 'Create Post'))
        ;
    }
 
    // ...
}

这个表单也许 被设计为创建贴子用,但如果你希望复用它来编辑贴子,则按钮的标签就是错的。取而代之,一些开发者在控制器中配置按钮:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
namespace AppBundle\Controller\Admin;
 
use Symfony\Component\HttpFoundation\Request;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\Form\Extension\Core\Type\SubmitType;
use AppBundle\Entity\Post;
use AppBundle\Form\PostType;
 
class PostController extends Controller
{
    // ...
 
    public function newAction(Request $request)
    {
        $post = new Post();
        $form = $this->createForm(PostType::class, $post);
        $form->add('submit', SubmitType::class, array(
            'label' => 'Create',
            'attr'  => array('class' => 'btn btn-default pull-right')
        ));
 
        // ...
    }
}

这同样犯了重大错误,因为你在纯PHP代码中混合了表现层的标记(labels、css classes,等等)。关联分离(seperation of concern)永远是值得遵循的最佳实践,所以应该把这些与视图相关的东西,移动到view层:

1
2
3
4
5
6
{{ form_start(form) }}
    {{ form_widget(form) }}
 
    <input type="submit" value="Create"
           class="btn btn-default pull-right" />
{{ form_end(form) }}

渲染表单 

有很多方式可以渲染你的表单,比如用一行代码渲出整个表单,或是独立渲染每一个表单字段。哪种更合适取决于你所需要的表单自定义程度。

一个最简单的方式——在开发阶段格外有用——就是通过form标签和form_widget()模板函数来渲染出所有字段:

1
2
3
{{ form_start(form, {'attr': {'class': 'my-form-class'} }) }}
    {{ form_widget(form) }}
{{ form_end(form) }}

如果你需要精细控制字段渲染,那么你应去除form_widget(form)函数并手动逐个渲染字段。参考如何自定义表单渲染来了解具体办法,以及如何能够使用全局主题来控制表单渲染。

控制表单提交 

在处理表单的提交时,通常遵循着相似的模式:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public function newAction(Request $request)
{
    // build the form ... 构建表单
 
    $form->handleRequest($request);
 
    if ($form->isSubmitted() && $form->isValid()) {
        $em = $this->getDoctrine()->getManager();
        $em->persist($post);
        $em->flush();
 
        return $this->redirect($this->generateUrl(
            'admin_post_show',
            array('id' => $post->getId())
        ));
    }
 
    // render the template 渲染模板
}

这里有两处地方值得注意。首先,我们推荐你使用单一action来完成渲染表单和处理表单提交两件事。例如,你有一个newAction()仅用来渲染表单,而另一个createAction()用于处理表单提交。这两个action几乎是完全相同的。所以更省力的办法是让createAction()处理所有事。

再来,我们推荐在if声明中使用$form->Submitted()以利明晰。这并非技术上的要求,因为isValid()最先调用的就是isSubmitted()。但是如果不这样写,程序读起来就不太好,因为看上去表单似乎总是 被执行(甚至在GET请求时)。

本文,包括例程代码在内,采用的是 Creative Commons BY-SA 3.0 创作共用授权。

登录symfonychina 发表评论或留下问题(我们会尽量回复)