支付宝扫一扫付款
微信扫一扫付款
(微信为保护隐私,不显示你的昵称)
Symfony提供了很多种来渲染表单的方法。在本章中,你将学习如何自定义你的表单的每一个可能的部分,使用尽可能少的步骤,无论你在你的模板引擎中使用的是 Twig 还是 PHP。
回忆一下,你可以很容易的使用form_row
的Twig函数和php助手函数row
方法渲染表单字段的label,错误提示以及 HTML控件。
1 | {{ form_row(form.age) }} |
1 | <?php echo $view['form']->row($form['age']); ?> |
你也可以分别渲染字段的三个部分:
1 2 3 4 5 | <div>
{{ form_label(form.age) }}
{{ form_errors(form.age) }}
{{ form_widget(form.age) }}
</div> |
1 2 3 4 5 | <div>
<?php echo $view['form']->label($form['age']); ?>
<?php echo $view['form']->errors($form['age']); ?>
<?php echo $view['form']->widget($form['age']); ?>
</div> |
这两个例子,通过symofny标记来渲染表单的label,错误提示和HTML控件。上面的模板会呈现如下效果:
1 2 3 4 5 6 7 | <div>
<label for="form_age">Age</label>
<ul>
<li>This field is required</li>
</ul>
<input type="number" id="form_age" name="form[age]" />
</div> |
还有更快方式来呈现原型或者一个测试表单,只需要要一样代码就可以渲染整个表单:
1 2 3 4 5 | {# renders all fields #}
{{ form_widget(form) }}
{# renders all fields *and* the form start and end tags #}
{{ form(form) }} |
1 2 3 4 5 | <!-- renders all fields -->
<?php echo $view['form']->widget($form) ?>
<!-- renders all fields *and* the form start and end tags -->
<?php echo $view['form']->form($form) ?> |
剩下的部分将解释表单的每一个标记的不同修改程度。关于更多表单渲染的知识,请阅读:如何去控制表单渲染
Symfony使用表单片段 - 一个小块模板渲染表单的一部分 - 每一个小模板渲染每一个表单部分 - 字段label,字段错误信息,inpput
文本字段,select
标签,等。
这些片段在 Twig 中被定义为区域,同时在 PHP 中被定义为模板文件。
一个主题只不过就是渲染表单时的一组片段的集合。换句话说,如果你想自定义一部分表单渲染,你需要导入一个主题,这个主题包含了个性化的表单片段。
Symfony带有一些内置的表单主题,这个主题定义了需要渲染表单的每一个部分:
<div>
元素内的每个表单字段<table>
元素中,并且把每个表单字段包含在<tr>
元素中<div>
元素中的每个表单字段。label
和input
在一行)<div>
元素中的每个表单字段。Caution
当你使用Bootstrap表单主题并手动渲染字段时,调用checkbox/radio
字段的form_label()
没有显示任何东西。这是由于Bootstrap内部,label已经通过form_widget()
显示。
下一章你将学习如何自定义主题,通过重写(一些或者全部的)片段。
例如,当integer
字段类型的控件被渲染时,一个input
,number
字段就会生成:
1 | {{ form_widget(form.age) }} |
1 | <?php echo $view['form']->widget($form['age']) ?> |
渲染:
1 | <input type="number" id="form_age" name="form[age]" required="required" value="33" /> |
在内部,symfony使用integer_widget
片段去渲染字段。这是因为这个字段的类型是integer
,并且你也渲染了他的控件(有label和errors)。
这个就是从twig中的默认 form_div_layout.html.twig 模板的integer_widget
块中来。
在 PHP 中将会是位于 FrameworkBundle/Resources/views/Form
文件夹的 integer_widget.html.php
文件。
这个integer_widget
片段的默认实现如下:
1 2 3 4 5 | {# form_div_layout.html.twig #}
{% block integer_widget %}
{% set type = type|default('number') %}
{{ block('form_widget_simple') }}
{% endblock integer_widget %} |
正如你看到的,这些片段自己也渲染其他的片段 - form_widget_simple
:
1 2 3 4 5 | {# form_div_layout.html.twig #}
{% block form_widget_simple %}
{% set type = type|default('text') %}
<input type="{{ type }}" {{ block('widget_attributes') }} {% if value is not empty %}value="{{ value }}" {% endif %}/>
{% endblock form_widget_simple %} |
1 2 3 4 5 6 |
关键是,这个片段指示表单的每个部分的html输出。为了定义表单的输出,你仅仅需要去识别并重写当前的片段。一组表单片段的集合就是大家所熟知的表单“主题”。当表单渲染时,你可以选择你想要使用的表单主题。
在Twig中一个主题是一个单独的模板文件并且这些片段定义在文件的block。
在php中一个主题是一个文件夹并且这些片段就是文件夹中的独立模板文件。
看看表单主题化的威力,假设你想要在每个input number
字段外包裹一个div
标签。要做到这一点的关键是自定义integer_widget
片段。
当在twig中自定义表单字段块时,你有两个选择:
方法 | 优点 | 缺点 |
在同一模板内部自定义表单 | 快速简单 | 不能重用到其他模板 |
独立的模板内自定义表单 | 可以在很多模板中重用 | 需要创建额外的模板 |
这两种方法有相同的效果,但是在不同的情况下会有不同的效果。
自定义integer_widget
块的最简单的方式是直接在要渲染的表单模板上定义:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | {% extends 'base.html.twig' %}
{% form_theme form _self %}
{% block integer_widget %}
<div class="integer_widget">
{% set type = type|default('number') %}
{{ block('form_widget_simple') }}
</div>
{% endblock %}
{% block content %}
{# ... render the form #}
{{ form_row(form.age) }}
{% endblock %} |
通过使用特殊的{% form_theme form _self %}
标签,Twig在同一模板内部寻找复写的表单块。假设form.age
字段是一个integer
类型的字段,当他的部件被渲染时,这个自定义的integer_widget
块将被使用。
这个方法的缺点是当在其他模板渲染表单时,自定义的表单块不能重用。换句话说,在你应用程序中的一个表单中使用自定义表单时,他是非常有用的。如果你想在多个表单中自定义一个能够重用的自定义表单,那么请你继续阅读。
你可以选择把自定义的integer_widget
表单块放到一个单独的模板中。代码以及最终的结果都是一样的,但是你现在可以在多个模板中使用表单自定义了:
1 2 3 4 5 6 7 | {# app/Resources/views/form/fields.html.twig #}
{% block integer_widget %}
<div class="integer_widget">
{% set type = type|default('number') %}
{{ block('form_widget_simple') }}
</div>
{% endblock %} |
现在您已经创建了自定义表单,你需要告诉Symfony使用它。在你实际渲染的表单模板内,通过form_theme
标签告诉symfony使用这个模板。
1 2 3 | {% form_theme form 'form/fields.html.twig' %}
{{ form_widget(form.age) }} |
当form.age
被渲染时,symfony将使用新模板的integer_widget
块并且这个input
标签将包裹在自定义块指定的div
元素中。
表单也可以通过多个模板来定制。要做到这一点,需要使用with
关键字将所有模板名称作为一个数组传递:
1 2 3 | {% form_theme form with ['common.html.twig', 'form/fields.html.twig'] %}
{# ... #} |
这些模板可以位于不同的bundle中,使用函数名来引用这些模板,例如 AcmeFormExtraBundle:form:fields.html.twig
。
你也可以应用表单主题来指定你的子表单:
1 | {% form_theme form.child 'form/fields.html.twig' %} |
当你想为一个主表单的子表单嵌入不同的自定义主题,这是非常有用的。只要区分你的主题就好了:
1 2 3 | {% form_theme form 'form/fields.html.twig' %}
{% form_theme form.child 'form/fields_child.html.twig' %} |
当使用php作为一个模板引擎时,唯一的方法就是去定义一个片段,然后去创建一个新的模板文件 - 这个和使用twig的第二种方式很相似。
模板文件必须以片段名命名。你必须创建一个integer_widget.html.php
文件以便去定义integer_widget
片段。
现在你已经创建了自定义的表单模板,你需要去告诉symofny来使用它。在你实际渲染表单的地方插入模板,通过 setTheme
帮助方法告诉 Symfony 来使用它:
1 2 3 | <?php $view['form']->setTheme($form, array(':form')); ?>
<?php $view['form']->widget($form['age']) ?> |
当form.age
被渲染时,Symfony将使用自定义的integer_widget.html.php
模板并将input标签将包裹在div
元素内。
如果你想把主题应用到特定的子表单上,那就传递它到setTheme
方法:
1 | <?php $view['form']->setTheme($form['child'], ':form'); ?> |
Note
:form
语法是基于模板的函数名:Bundle:Directory
。这个表单的目录就在app/Resources/views
,因为Bundle
中找不到,所以结果在:form
。
到目前为止,重写一个特定的表单块,最好的方式是从form_div_layout.html.twig中复制默认的块,粘贴到不同的模板中,并自定义它。更多情况下,当你自定义这些时,你通过使用基础的模板,使用引用表单块的方式来避免重复的粘贴。
这个很容易做到,但你的表单块在同一模板或独立模板中,他们可能会略有不同。
在你要渲染的表单模板中输入use
标签来引入块:
1 | {% use 'form_div_layout.html.twig' with integer_widget as base_integer_widget %} |
现在,当这个块从 form_div_layout.html.twig 引入后,这个integer_widget
块会调用base_integer_widget
。这意味着,当你重定义integer_widget
块时,你能够引入你刚刚标记的,默认模板中的,原始块 base_integer_widget
。
1 2 3 4 5 | {% block integer_widget %}
<div class="integer_widget">
{{ block('base_integer_widget') }}
</div>
{% endblock %} |
如果你的自定义表单位于外部模板,你可以通过使用 Twig 的 parent()
功能来引用基本块:
1 2 3 4 5 6 7 8 | {# app/Resources/views/Form/fields.html.twig #}
{% extends 'form_div_layout.html.twig' %}
{% block integer_widget %}
<div class="integer_widget">
{{ parent() }}
</div>
{% endblock %} |
Note
当使用 PHP 作为模板引擎的时候将不可能引用基本块。你必须手动复制基本块的内容到你的新模板文件中。
如果你想要你的定制表单全局到你的应用程序,你能够通过在外部模板定制表单来实现它,然后将他导入到你的应用程序配置中:
通过使用以下配置,当一个模板被渲染时,任何在form/fields.html.twig
模板中的定义表单块都可以被全局使用。
1 2 3 4 5 | # app/config/config.yml
twig:
form_themes:
- 'form/fields.html.twig'
# ... |
1 2 3 4 5 | <!-- app/config/config.xml -->
<twig:config>
<twig:form-theme>form/fields.html.twig</twig:form-theme>
<!-- ... -->
</twig:config> |
默认情况下,当渲染表单时 Twig 使用 div
布局。然而,有些人可能更喜欢使用 table
渲染表单。使用 form_table_layout.html.twig
资源来调用这样的布局:
1 2 3 4 5 | # app/config/config.yml
twig:
form_themes:
- 'form_table_layout.html.twig'
# ... |
1 2 3 4 5 | <!-- app/config/config.xml -->
<twig:config>
<twig:form-theme>form_table_layout.html.twig</twig:form-theme>
<!-- ... -->
</twig:config> |
如果你只是想要在一个模板中修改,在你的模板文件中添加这样一行代码就可以了,而不用添加模板作为一个资源:
1 | {% form_theme form 'form_table_layout.html.twig' %} |
注意上面的form
变量是模板视图变量,他被传入到你的模板中。
通过使用以下配置,当一个模板被渲染时,任何在app/Resources/views/Form
文件夹中自定义表单块都可以被全局使用。
1 2 3 4 5 6 7 | # app/config/config.yml
framework:
templating:
form:
resources:
- 'AppBundle:Form'
# ... |
1 2 3 4 5 6 7 8 9 | <!-- app/config/config.xml -->
<framework:config>
<framework:templating>
<framework:form>
<resource>AppBundle:Form</resource>
</framework:form>
</framework:templating>
<!-- ... -->
</framework:config> |
默认情况下,当渲染表单时,php引擎使用div布局。有些人,可能喜欢用table
渲染表单。那么你要调用FrameworkBundle:FormTable
资源来做布局:
1 2 3 4 5 6 | # app/config/config.yml
framework:
templating:
form:
resources:
- 'FrameworkBundle:FormTable' |
1 2 3 4 5 6 7 8 9 | <!-- app/config/config.xml -->
<framework:config>
<framework:templating>
<framework:form>
<resource>FrameworkBundle:FormTable</resource>
</framework:form>
</framework:templating>
<!-- ... -->
</framework:config> |
如果你只是想要在一个模板中有更改,在你的模板中添加下列这一行代码而不是将模板添加成资源:
1 | <?php $view['form']->setTheme($form, array('FrameworkBundle:FormTable')); ?> |
注意上面的$form
变量是模板视图变量,他被传入到你的模板中。
迄今为止,你已经看到了你能够使用不同的方法去定义所有text
字段类型的组件输出。你也可以定义一个单独的字段。例如,假设在你的product
表单中有两个text
字段 - name
和description
- 但是你只想定义其中的一个字段。他的片段名称是由字段的id
属性组成,这部分的字段就可以被定义。例如,只定义一个name
字段:
1 2 3 4 5 6 7 8 9 | {% form_theme form _self %}
{% block _product_name_widget %}
<div class="text_widget">
{{ block('form_widget_simple') }}
</div>
{% endblock %}
{{ form_widget(form.name) }} |
1 2 3 4 5 6 7 8 9 | <!-- Main template -->
<?php echo $view['form']->setTheme($form, array(':form')); ?>
<?php echo $view['form']->widget($form['name']); ?>
<!-- app/Resources/views/Form/_product_name_widget.html.php -->
<div class="text_widget">
<?php echo $view['form']->block('form_widget_simple') ?>
</div> |
在这里,_product_name_widget
片段自定义模板,使用id
为product_name
的字段名(名字是product[name])。
Tip
这个product
是表单的名称,他可以手动设置也可以根据你表单的类型名称自动生成(例如,ProductType
相当于product
)。如果你不确定你表单的名字,那就查看生成表单的源。
如果你想要去改变这个product
或者_product_name_widget
块的名字,你应该在你的表单类型中设置block_name
配置选项:
然后这个块的名称将是_product_custom_name_widget
。
你也可以使用相同的方法来重写整个字段行:
1 2 3 4 5 6 7 8 9 10 11 | {% form_theme form _self %}
{% block _product_name_row %}
<div class="name_row">
{{ form_label(form) }}
{{ form_errors(form) }}
{{ form_widget(form) }}
</div>
{% endblock %}
{{ form_row(form.name) }} |
1 2 3 4 5 6 7 8 9 10 11 | <!-- Main template -->
<?php echo $view['form']->setTheme($form, array(':form')); ?>
<?php echo $view['form']->row($form['name']); ?>
<!-- app/Resources/views/Form/_product_name_row.html.php -->
<div class="name_row">
<?php echo $view['form']->label($form) ?>
<?php echo $view['form']->errors($form) ?>
<?php echo $view['form']->widget($form) ?>
</div> |
当使用一个表单集合,prototype会被重写块的一个自定义的prototype完全覆盖。例如,如果你的表单字段名为tasks,你能够改变下每一个task的控件:
不仅可以重写渲染的控件,你也可以改变完整的表单行或者字段标签。上面给出了tasks字段,下面给出这个块的名称:
该表单的一部分 | 块名称 |
label | _tasks_entry_label |
widget | _tasks_entry_widget |
row | _tasks_entry_row |
目前为止,本指导已经介绍过一些如何渲染表单的不同的自定义方法。关键就是要自定义特定的片段,这个片段和你想要控制的表单的属性相关。(请查看表单块的命名)。
接下来的章节中,你会看到几种常见的表单是如何定义的。为了这些表单的自定义,需要使用到表单主题化章节中的方法。
Note
表单组件只处理错误的呈现,而不是实际处理验证的错误信息。这个错误信息本身取决于你对象的验证约束。更多信息,请查看 验证。
当一个表单提交后出现错误信息,这个里有很多不同的方法可以渲染表单的错误信息。当你使用form_errors
助手时,字段的错误信息就会被渲染:
1 | {{ form_errors(form.age) }} |
1 | <?php echo $view['form']->errors($form['age']); ?> |
默认情况下,这个错误被渲染在一个无序列表中:
1 2 3 | <ul>
<li>This field is required</li>
</ul> |
如何重写所有渲染字段的错误信息,简单就是复制,粘贴并定义form_errors
片段:
1 2 3 4 5 6 7 8 9 10 11 12 | {# form_errors.html.twig #}
{% block form_errors %}
{% spaceless %}
{% if errors|length > 0 %}
<ul>
{% for error in errors %}
<li>{{ error.message }}</li>
{% endfor %}
</ul>
{% endif %}
{% endspaceless %}
{% endblock form_errors %} |
1 2 3 4 5 6 7 8 | <!-- form_errors.html.php -->
<?php if ($errors): ?>
<ul>
<?php foreach ($errors as $error): ?>
<li><?php echo $error->getMessage() ?></li>
<?php endforeach ?>
</ul>
<?php endif ?> |
Tip
如何应用这些自定义参见表单主题化。
你也可以自定义这些错误输出仅为一个特定的字段类型。去定义仅有的标记用于这些错误,遵循和上面相同的方式但是把内容放到相关的 _errors 块(或者php模板文件中)。例如,text_errors(或者text_errors.html.php)。
Tip
查看 表单片段命名 去找到你需要定义的特定块和文件。
在你的表单可以渲染全局错误(不分别在字段渲染错误)而是在你表单的顶部,一起把错误输出:
1 | {{ form_errors(form) }} |
1 | <?php echo $view['form']->render($form); ?> |
为了仅仅自定义这些错误所使用的标记,遵循和上面相同的指示,但是现在要检查compound
变量是否设置为true
。如果为true,这意味着目前渲染的是一个字段的集合(如一个整体的表单),而不仅仅是一个单独的字段:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | {# form_errors.html.twig #}
{% block form_errors %}
{% spaceless %}
{% if errors|length > 0 %}
{% if compound %}
<ul>
{% for error in errors %}
<li>{{ error.message }}</li>
{% endfor %}
</ul>
{% else %}
{# ... display the errors for a single field #}
{% endif %}
{% endif %}
{% endspaceless %}
{% endblock form_errors %} |
1 2 3 4 5 6 7 8 9 10 11 12 | <!-- form_errors.html.php -->
<?php if ($errors): ?>
<?php if ($compound): ?>
<ul>
<?php foreach ($errors as $error): ?>
<li><?php echo $error->getMessage() ?></li>
<?php endforeach ?>
</ul>
<?php else: ?>
<!-- ... render the errors for a single field -->
<?php endif ?>
<?php endif ?> |
当你能够管理它的时候,最简单的方式去渲染一个表单字段就是使用form_row
功能,它能渲染字段的label,错误,和HTML控件。为了自定义渲染所有表单字段行所使用的标志,要重写form_row
片段。例如,你想要在每一行的周围添加一个div
的class属性:
1 2 3 4 5 6 7 8 | {# form_row.html.twig #}
{% block form_row %}
<div class="form_row">
{{ form_label(form) }}
{{ form_errors(form) }}
{{ form_widget(form) }}
</div>
{% endblock form_row %} |
1 2 3 4 5 6 | <!-- form_row.html.php -->
<div class="form_row">
<?php echo $view['form']->label($form) ?>
<?php echo $view['form']->errors($form) ?>
<?php echo $view['form']->widget($form) ?>
</div> |
Tip
如何应用这些定义请查看 表单主题化 。
如果你想要将你的所有的必填字段加上必填星号标注(*
),你可以通过个性化 form_label
片段来完成。
在 Twig 中,如果你正在你的表单的相同的模板中自定义表单,修改 use
标签并且添加下列代码:
1 2 3 4 5 6 7 8 9 | {% use 'form_div_layout.html.twig' with form_label as base_form_label %}
{% block form_label %}
{{ block('base_form_label') }}
{% if required %}
<span class="required" title="This field is required">*</span>
{% endif %}
{% endblock %} |
在 Twig 中,如果你正在表单的不同的模板中自定义表单,使用下列代码:
1 2 3 4 5 6 7 8 9 | {% extends 'form_div_layout.html.twig' %}
{% block form_label %}
{{ parent() }}
{% if required %}
<span class="required" title="This field is required">*</span>
{% endif %}
{% endblock %} |
当使用 PHP 作为模板引擎时你必须从原始模板中复制内容:
1 2 3 4 5 6 7 8 9 10 11 12 | <!-- form_label.html.php -->
<!-- original content -->
<?php if ($required) { $label_attr['class'] = trim((isset($label_attr['class']) ? $label_attr['class'] : '').' required'); } ?>
<?php if (!$compound) { $label_attr['for'] = $id; } ?>
<?php if (!$label) { $label = $view['form']->humanize($name); } ?>
<label <?php foreach ($label_attr as $k => $v) { printf('%s="%s" ', $view->escape($k), $view->escape($v)); } ?>><?php echo $view->escape($view['translator']->trans($label, array(), $translation_domain)) ?></label>
<!-- customization -->
<?php if ($required) : ?>
<span class="required" title="This field is required">*</span>
<?php endif ?> |
Tip
如何应用这些定义请查看 表单主题化 。
你也可以让你的自定义、表单控件拥有一个“帮助”信息的配置选项。
在 Twig 中,如果你正在表单的相同的模板中自定义表单,修改 use
标签并且添加下列代码:
1 2 3 4 5 6 7 8 9 | {% use 'form_div_layout.html.twig' with form_widget_simple as base_form_widget_simple %}
{% block form_widget_simple %}
{{ block('base_form_widget_simple') }}
{% if help is defined %}
<span class="help">{{ help }}</span>
{% endif %}
{% endblock %} |
在 Twig 中,如果你正在表单的不同的模板中自定义表单,使用下列代码:
1 2 3 4 5 6 7 8 9 | {% extends 'form_div_layout.html.twig' %}
{% block form_widget_simple %}
{{ parent() }}
{% if help is defined %}
<span class="help">{{ help }}</span>
{% endif %}
{% endblock %} |
当使用 PHP 作为模板引擎时你必须从原始模板中复制内容:
1 2 3 4 5 6 7 8 9 10 11 12 13 | <!-- form_widget_simple.html.php -->
<!-- Original content -->
<input
type="<?php echo isset($type) ? $view->escape($type) : 'text' ?>"
<?php if (!empty($value)): ?>value="<?php echo $view->escape($value) ?>"<?php endif ?>
<?php echo $view['form']->block($form, 'widget_attributes') ?>
/>
<!-- Customization -->
<?php if (isset($help)) : ?>
<span class="help"><?php echo $view->escape($help) ?></span>
<?php endif ?> |
为了渲染一个字段的帮助信息,需要传入一个help
变量:
1 | {{ form_widget(form.title, {'help': 'foobar'}) }} |
1 | <?php echo $view['form']->widget($form['title'], array('help' => 'foobar')) ?> |
Tip
如何应用这些定义请查看 表单主题化 。
大部分的函数可用于渲染表单的不同部分(例如表单控件,表单标签,表单错误等等)都可以允许你直接做特定的自定义。看下面这个例子:
这个包含表单“变量”的数组作为第二个参数传递。更多细节请查看,Reference分类下的Form Variables .
本文,包括例程代码在内,采用的是 Creative Commons BY-SA 3.0 创作共用授权。