如何去创建一个自定义的表单字段类型
Symfony提供了一组核心字段类型,用于构建表单。然而某些情况下,你可能想要创建一个自定义的表单字段类型去实现一个特定的目标。本文假设你需要一个字段来定义快递选项,基于已有的choice字段。下面将解释如何定义字段,你将如何自定义其布局,以及,最后如何将它注册到程序中来使用。
定义字段类型 ¶
为了创建自定义的字段类型,首先要创建代表这个字段的类。在这种情况下,持有字段类型的类将被称为 ShippingType
,并且类文件将被存储在表单字段专享的默认位置,即 <BundleName>\Form\Type
。请确保字段继承了 AbstractType
:
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 |
// src/AppBundle/Form/Type/ShippingType.php
namespace AppBundle\Form\Type;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
class ShippingType extends AbstractType
{
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(array(
'choices' => array(
'Standard Shipping' => 'standard',
'Expedited Shipping' => 'expedited',
'Priority Shipping' => 'priority',
)
));
}
public function getParent()
{
return ChoiceType::class;
}
} |
Tip
此文件的位置并不重要,Form\Type
目录仅仅是一个约定(惯例)。
在这里,getParent()
函数的返回值指明了你正在扩展 ChoiceType
字段。这意味着,默认情况下,你继承了这个字段类型的全部逻辑和模板输出。你可以查看 ChoiceType 类来看到这些逻辑。它有三个极为重要的方法:
buildForm()
- 每个字段类型都有一个
buildForm()
方法,这里是你配置和构建任意(多个)字段的地方。注意这是和你用来设置 你的 表单的同一个方法,此处的用法(和设置表单)也是相同的。 buildView()
- 这个方法可用于设置任何额外的变量,你在模板中输出你的字段时需要用到。例如,在 ChoiceType 中,设置了一个
multiple
变量,它在模板中用于设置 (或不去设置)select
字段的multiple
属性。参考 为字段创建一个模板 以了解更多。 configureOptions()
- 这个方法定义了你的表单字段的选项,它们可以用在
buildForm()
和buildView()
中。有大量选项常被用于所有的字段中 (参考 FormType Field),但你也可以在这里创建所需的任何其他的选项。
Tip
如果你创建一个包含有很多字段的字段,记得将你的 “父”类型 设置成 form
或者是继承了 form
的类型。同时,如果你需要从父类型中修改任何一个子类型的“view”,可使用 finishView()
方法。
字段的目的是继承choice类型来开启shipping type(“快递”表单类型)的选项。这可以通过把 choices
调整为一个可用的“快递选项”清单来实现。
为字段创建一个模板 ¶
每个字段都是通过一个模板码段来输出的,这个“码段”是由你的类型之名称的某些部分来决定的。参考 什么是表单主题? 以了解更多。
Note
前缀中的第一个部分 (如shipping
) 来自类名 (ShippingType -> shipping) 。这可以通过覆写 ShippingType 中的 getBlockPrefix()
方法来控制 。
Caution
当你的表单类的名字匹配到任何一个内置的字段类型时,你的表单可能不会正确输出。一个命名为 AppBundle\Form\PasswordType
的表单字段类型,将和内置的 PasswordType
拥有相同的码段区块名称,且将不会被正确地渲染输出。覆写 getBlockPrefix()
方法以返回一个唯一的区块前缀(如 app_password)来避免冲突。
本例中,由于父字段是 ChoiceType
,你 毋须 做任何工作,因为这个自定义的字段会自动地作为 ChoiceType
被输出。出于演示的目的,假设你的字段是“expanded”(即是 radio 或是 checkboxes 之类,而非select下拉字段),你希望始终在 ul
元素中来渲染它。在你的表单主题模板中(详见上面的链接),创建一个 shipping_widget
区块来处理它:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
{# app/Resources/views/form/fields.HTML.twig #}
{% block shipping_widget %}
{% spaceless %}
{% if expanded %}
<ul {{ block('widget_container_attributes') }}>
{% for child in form %}
<li>
{{ form_widget(child) }}
{{ form_label(child) }}
</li>
{% endfor %}
</ul>
{% else %}
{# just let the choice widget render the select tag #}
{# 只需让 choice widget 来渲染 select 标签 #}
{{ block('choice_widget') }}
{% endif %}
{% endspaceless %}
{% endblock %} |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
<!-- app/Resources/views/form/shipping_widget.html.php -->
<?php if ($expanded) : ?>
<ul <?php $view['form']->block($form, 'widget_container_attributes') ?>>
<?php foreach ($form as $child) : ?>
<li>
<?php echo $view['form']->widget($child) ?>
<?php echo $view['form']->label($child) ?>
</li>
<?php endforeach ?>
</ul>
<?php else : ?>
<!-- just let the choice widget render the select tag -->
<?php echo $view['form']->renderBlock('choice_widget') ?>
<?php endif ?> |
Note
要确保正确的控件(widget)前缀被使用。本例中的名字应该是 shipping_widget
(参考 什么是表单主题?)。进一步的,在主力配置文件指向这个自定义的表单模板,以便在渲染所有表单时能够使用它。
使用Twig时的配置如下:
1 2 3 4 |
# app/config/config.yml
twig:
form_themes:
- 'form/fields.html.twig' |
1 2 3 4 |
<!-- app/config/config.xml -->
<twig:config>
<twig:form-theme>form/fields.html.twig</twig:form-theme>
</twig:config> |
1 2 3 4 5 6 |
// app/config/config.php
$container->loadFromExtension('twig', array(
'form_themes' => array(
'form/fields.html.twig',
),
)); |
对于php模板引擎,你的配置信息应该是这样的:
1 2 3 4 5 6 |
# app/config/config.yml
framework:
templating:
form:
resources:
- ':form:fields.html.php' |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
<!-- app/config/config.xml -->
<?xml version="1.0" encoding="UTF-8" ?>
<container xmlns="Http://symfony.com/schema/dic/services"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:framework="http://symfony.com/schema/dic/symfony"
xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd
http://symfony.com/schema/dic/symfony http://symfony.com/schema/dic/symfony/symfony-1.0.xsd">
<framework:config>
<framework:templating>
<framework:form>
<framework:resource>:form:fields.html.php</twig:resource>
</framework:form>
</framework:templating>
</framework:config>
</container> |
1 2 3 4 5 6 7 8 9 10 |
// app/config/config.php
$container->loadFromExtension('framework', array(
'templating' => array(
'form' => array(
'resources' => array(
':form:fields.html.php',
),
),
),
)); |
使用字段类型 ¶
现在,你可以立即使用你的自定义字段类型了,只需在你的表单中的某个字段里为该类型创建一个新实例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
/ src/AppBundle/Form/Type/AuthorType.php
namespace AppBundle\Form\Type;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use AppBundle\Form\Type\ShippingType;
class OrderType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('shipping_code', ShippingType::class, array(
'placeholder' => 'Choose a delivery option',
));
}
} |
但这只是勉强能用而已,因为 ShippingType
太过简单。如果快递部分的相关代码存在了配置文件中或数据库中又该如何?下一节将介绍更为复杂的字段类型以解决此问题。
访问服务及配置 ¶
如果你需要在表单类中访问 服务,请按通常的方式来添加一个 __construct()
方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
// src/AppBundle/Form/Type/ShippingType.php
namespace AppBundle\Form\Type;
// ...
use Doctrine\ORM\EntityManagerInterface;
class ShippingType extends AbstractType
{
private $em;
public function __construct(EntityManagerInterface $em)
{
$this->em = $em;
}
// use $this->em down anywhere you wAnt ...
// 可以在下面的代码中随需使用 $this->em ...
} |
如果你使用了默认的 services.yml
配置信息 (即 Form/
下的文件会被当作服务来加载,而且开启了 autoconfigure
选项),这将始终能够运行!参考 在容器中创建/配置服务 以了解更多。
Tip
如果你未使用 自动配置,则应确保对你的服务 打上 form.type
标签。
玩得开心!