发布于 2015-08-27 16:48:57 | 149 次阅读 | 评论: 0 | 来源: 网络整理
Symfony gives you a wide variety of ways to customize how a form is rendered. In this guide, you’ll learn how to customize every possible part of your form with as little effort as possible whether you use Twig or PHP as your templating engine.
Recall that the label, error and HTML widget of a form field can easily
be rendered by using the form_row
Twig function or the row
PHP helper
method:
{{ form_row(form.age) }}
<?php echo $view['form']->row($form['age']); ?>
You can also render each of the three parts of the field individually:
<div>
{{ form_label(form.age) }}
{{ form_errors(form.age) }}
{{ form_widget(form.age) }}
</div>
<div>
<?php echo $view['form']->label($form['age']); ?>
<?php echo $view['form']->errors($form['age']); ?>
<?php echo $view['form']->widget($form['age']); ?>
</div>
In both cases, the form label, errors and HTML widget are rendered by using a set of markup that ships standard with Symfony. For example, both of the above templates would render:
<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>
To quickly prototype and test a form, you can render the entire form with just one line:
{# renders all fields #}
{{ form_widget(form) }}
{# renders all fields *and* the form start and end tags #}
{{ form(form) }}
<!-- 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) ?>
The remainder of this recipe will explain how every part of the form’s markup can be modified at several different levels. For more information about form rendering in general, see 在模板里生成表单.
Symfony uses form fragments - a small piece of a template that renders just
one part of a form - to render each part of a form - field labels, errors,
input
text fields, select
tags, etc.
The fragments are defined as blocks in Twig and as template files in PHP.
A theme is nothing more than a set of fragments that you want to use when rendering a form. In other words, if you want to customize one portion of how a form is rendered, you’ll import a theme which contains a customization of the appropriate form fragments.
Symfony comes with four built-in form themes that define each and every fragment needed to render every part of a form:
<div>
element.<table>
element and each form field inside a <tr>
element.<div>
element
with the appropriate CSS classes to apply the default Bootstrap 3 CSS framework
styles.In the next section you will learn how to customize a theme by overriding some or all of its fragments.
For example, when the widget of an integer
type field is rendered, an input
number
field is generated
{{ form_widget(form.age) }}
<?php echo $view['form']->widget($form['age']) ?>
renders:
<input type="number" id="form_age" name="form[age]" required="required" value="33" />
Internally, Symfony uses the integer_widget
fragment to render the field.
This is because the field type is integer
and you’re rendering its widget
(as opposed to its label
or errors
).
In Twig that would default to the block integer_widget
from the form_div_layout.html.twig
template.
In PHP it would rather be the integer_widget.html.php
file located in
the FrameworkBundle/Resources/views/Form
folder.
The default implementation of the integer_widget
fragment looks like this:
{# form_div_layout.html.twig #}
{% block integer_widget %}
{% set type = type|default('number') %}
{{ block('form_widget_simple') }}
{% endblock integer_widget %}
<!-- integer_widget.html.php -->
<?php echo $view['form']->block($form, 'form_widget_simple', array('type' => isset($type) ? $type : "number")) ?>
As you can see, this fragment itself renders another fragment - form_widget_simple
:
{# 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 %}
<!-- FrameworkBundle/Resources/views/Form/form_widget_simple.html.php -->
<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') ?>
/>
The point is, the fragments dictate the HTML output of each part of a form. To customize the form output, you just need to identify and override the correct fragment. A set of these form fragment customizations is known as a form “theme”. When rendering a form, you can choose which form theme(s) you want to apply.
In Twig a theme is a single template file and the fragments are the blocks defined in this file.
In PHP a theme is a folder and the fragments are individual template files in this folder.
To see the power of form theming, suppose you want to wrap every input number
field with a div
tag. The key to doing this is to customize the
integer_widget
fragment.
When customizing the form field block in Twig, you have two options on where the customized form block can live:
Method | Pros | Cons |
---|---|---|
Inside the same template as the form | Quick and easy | Can’t be reused in other templates |
Inside a separate template | Can be reused by many templates | Requires an extra template to be created |
Both methods have the same effect but are better in different situations.
The easiest way to customize the integer_widget
block is to customize it
directly in the template that’s actually rendering the form.
{% 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 %}
By using the special {% form_theme form _self %}
tag, Twig looks inside
the same template for any overridden form blocks. Assuming the form.age
field is an integer
type field, when its widget is rendered, the customized
integer_widget
block will be used.
The disadvantage of this method is that the customized form block can’t be reused when rendering other forms in other templates. In other words, this method is most useful when making form customizations that are specific to a single form in your application. If you want to reuse a form customization across several (or all) forms in your application, read on to the next section.
You can also choose to put the customized integer_widget
form block in a
separate template entirely. The code and end-result are the same, but you
can now re-use the form customization across many templates:
{# src/AppBundle/Resources/views/Form/fields.html.twig #}
{% block integer_widget %}
<div class="integer_widget">
{% set type = type|default('number') %}
{{ block('form_widget_simple') }}
</div>
{% endblock %}
Now that you’ve created the customized form block, you need to tell Symfony
to use it. Inside the template where you’re actually rendering your form,
tell Symfony to use the template via the form_theme
tag:
{% form_theme form 'AppBundle:Form:fields.html.twig' %}
{{ form_widget(form.age) }}
When the form.age
widget is rendered, Symfony will use the integer_widget
block from the new template and the input
tag will be wrapped in the
div
element specified in the customized block.
A form can also be customized by applying several templates. To do this, pass the
name of all the templates as an array using the with
keyword:
{% form_theme form with ['::common.html.twig', ':Form:fields.html.twig',
'AppBundle:Form:fields.html.twig'] %}
{# ... #}
The templates can be located at different bundles and they can even be stored
at the global app/Resources/views/
directory.
You can also apply a form theme to a specific child of your form:
{% form_theme form.child 'AppBundle:Form:fields.html.twig' %}
This is useful when you want to have a custom theme for a nested form that’s different than the one of your main form. Just specify both your themes:
{% form_theme form 'AppBundle:Form:fields.html.twig' %}
{% form_theme form.child 'AppBundle:Form:fields_child.html.twig' %}
When using PHP as a templating engine, the only method to customize a fragment is to create a new template file - this is similar to the second method used by Twig.
The template file must be named after the fragment. You must create a integer_widget.html.php
file in order to customize the integer_widget
fragment.
<!-- src/AppBundle/Resources/views/Form/integer_widget.html.php -->
<div class="integer_widget">
<?php echo $view['form']->block($form, 'form_widget_simple', array('type' => isset($type) ? $type : "number")) ?>
</div>
Now that you’ve created the customized form template, you need to tell Symfony
to use it. Inside the template where you’re actually rendering your form,
tell Symfony to use the theme via the setTheme
helper method:
<?php $view['form']->setTheme($form, array('AppBundle:Form')); ?>
<?php $view['form']->widget($form['age']) ?>
When the form.age
widget is rendered, Symfony will use the customized
integer_widget.html.php
template and the input
tag will be wrapped in
the div
element.
If you want to apply a theme to a specific child form, pass it to the setTheme
method:
<?php $view['form']->setTheme($form['child'], 'AppBundle:Form/Child'); ?>
So far, to override a particular form block, the best method is to copy the default block from form_div_layout.html.twig, paste it into a different template, and then customize it. In many cases, you can avoid doing this by referencing the base block when customizing it.
This is easy to do, but varies slightly depending on if your form block customizations are in the same template as the form or a separate template.
Import the blocks by adding a use
tag in the template where you’re rendering
the form:
{% use 'form_div_layout.html.twig' with integer_widget as base_integer_widget %}
Now, when the blocks from form_div_layout.html.twig are imported, the
integer_widget
block is called base_integer_widget
. This means that when
you redefine the integer_widget
block, you can reference the default markup
via base_integer_widget
:
{% block integer_widget %}
<div class="integer_widget">
{{ block('base_integer_widget') }}
</div>
{% endblock %}
If your form customizations live inside an external template, you can reference
the base block by using the parent()
Twig function:
{# src/AppBundle/Resources/views/Form/fields.html.twig #}
{% extends 'form_div_layout.html.twig' %}
{% block integer_widget %}
<div class="integer_widget">
{{ parent() }}
</div>
{% endblock %}
注解
It is not possible to reference the base block when using PHP as the templating engine. You have to manually copy the content from the base block to your new template file.
If you’d like a certain form customization to be global to your application, you can accomplish this by making the form customizations in an external template and then importing it inside your application configuration.
By using the following configuration, any customized form blocks inside the
AppBundle:Form:fields.html.twig
template will be used globally when a
form is rendered.
# app/config/config.yml
twig:
form_themes:
- 'AppBundle:Form:fields.html.twig'
# ...
<!-- app/config/config.xml -->
<twig:config>
<twig:form-theme>AppBundle:Form:fields.html.twig</twig:form-theme>
<!-- ... -->
</twig:config>
// app/config/config.php
$container->loadFromExtension('twig', array(
'form_themes' => array(
'AppBundle:Form:fields.html.twig',
),
// ...
));
By default, Twig uses a div layout when rendering forms. Some people, however,
may prefer to render forms in a table layout. Use the form_table_layout.html.twig
resource to use such a layout:
# app/config/config.yml
twig:
form_themes:
- 'form_table_layout.html.twig'
# ...
<!-- app/config/config.xml -->
<twig:config>
<twig:form-theme>form_table_layout.html.twig</twig:form-theme>
<!-- ... -->
</twig:config>
// app/config/config.php
$container->loadFromExtension('twig', array(
'form_themes' => array(
'form_table_layout.html.twig',
),
// ...
));
If you only want to make the change in one template, add the following line to your template file rather than adding the template as a resource:
{% form_theme form 'form_table_layout.html.twig' %}
Note that the form
variable in the above code is the form view variable
that you passed to your template.
By using the following configuration, any customized form fragments inside the
src/AppBundle/Resources/views/Form
folder will be used globally when a
form is rendered.
# app/config/config.yml
framework:
templating:
form:
resources:
- 'AppBundle:Form'
# ...
<!-- app/config/config.xml -->
<framework:config>
<framework:templating>
<framework:form>
<resource>AppBundle:Form</resource>
</framework:form>
</framework:templating>
<!-- ... -->
</framework:config>
// app/config/config.php
// PHP
$container->loadFromExtension('framework', array(
'templating' => array(
'form' => array(
'resources' => array(
'AppBundle:Form',
),
),
),
// ...
));
By default, the PHP engine uses a div layout when rendering forms. Some people,
however, may prefer to render forms in a table layout. Use the FrameworkBundle:FormTable
resource to use such a layout:
# app/config/config.yml
framework:
templating:
form:
resources:
- 'FrameworkBundle:FormTable'
<!-- app/config/config.xml -->
<framework:config>
<framework:templating>
<framework:form>
<resource>FrameworkBundle:FormTable</resource>
</framework:form>
</framework:templating>
<!-- ... -->
</framework:config>
// app/config/config.php
$container->loadFromExtension('framework', array(
'templating' => array(
'form' => array(
'resources' => array(
'FrameworkBundle:FormTable',
),
),
),
// ...
));
If you only want to make the change in one template, add the following line to your template file rather than adding the template as a resource:
<?php $view['form']->setTheme($form, array('FrameworkBundle:FormTable')); ?>
Note that the $form
variable in the above code is the form view variable
that you passed to your template.
So far, you’ve seen the different ways you can customize the widget output
of all text field types. You can also customize individual fields. For example,
suppose you have two text
fields in a product
form - name
and
description
- but you only want to customize one of the fields. This can be
accomplished by customizing a fragment whose name is a combination of the field’s
id
attribute and which part of the field is being customized. For example, to
customize the name
field only:
{% form_theme form _self %}
{% block _product_name_widget %}
<div class="text_widget">
{{ block('form_widget_simple') }}
</div>
{% endblock %}
{{ form_widget(form.name) }}
<!-- Main template -->
<?php echo $view['form']->setTheme($form, array('AppBundle:Form')); ?>
<?php echo $view['form']->widget($form['name']); ?>
<!-- src/AppBundle/Resources/views/Form/_product_name_widget.html.php -->
<div class="text_widget">
echo $view['form']->block('form_widget_simple') ?>
</div>
Here, the _product_name_widget
fragment defines the template to use for the
field whose id is product_name
(and name is product[name]
).
小技巧
The product
portion of the field is the form name, which may be set
manually or generated automatically based on your form type name (e.g.
ProductType
equates to product
). If you’re not sure what your
form name is, just view the source of your generated form.
If you want to change the product
or name
portion of the block
name _product_name_widget
you can set the block_name
option in your
form type:
use SymfonyComponentFormFormBuilderInterface;
public function buildForm(FormBuilderInterface $builder, array $options)
{
// ...
$builder->add('name', 'text', array(
'block_name' => 'custom_name',
));
}
Then the block name will be _product_custom_name_widget
.
You can also override the markup for an entire field row using the same method:
{% 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) }}
<!-- Main template -->
<?php echo $view['form']->setTheme($form, array('AppBundle:Form')); ?>
<?php echo $view['form']->row($form['name']); ?>
<!-- src/AppBundle/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>
So far, this recipe has shown you several different ways to customize a single piece of how a form is rendered. The key is to customize a specific fragment that corresponds to the portion of the form you want to control (see naming form blocks).
In the next sections, you’ll see how you can make several common form customizations. To apply these customizations, use one of the methods described in the Form Theming section.
注解
The Form component only handles how the validation errors are rendered, and not the actual validation error messages. The error messages themselves are determined by the validation constraints you apply to your objects. For more information, see the chapter on validation.
There are many different ways to customize how errors are rendered when a
form is submitted with errors. The error messages for a field are rendered
when you use the form_errors
helper:
{{ form_errors(form.age) }}
<?php echo $view['form']->errors($form['age']); ?>
By default, the errors are rendered inside an unordered list:
<ul>
<li>This field is required</li>
</ul>
To override how errors are rendered for all fields, simply copy, paste
and customize the form_errors
fragment.
{# 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 %}
<!-- form_errors.html.php -->
<?php if ($errors): ?>
<ul>
<?php foreach ($errors as $error): ?>
<li><?php echo $error->getMessage() ?></li>
<?php endforeach ?>
</ul>
<?php endif ?>
小技巧
See Form Theming for how to apply this customization.
You can also customize the error output for just one specific field type.
To customize only the markup used for these errors, follow the same directions
as above but put the contents in a relative _errors
block (or file in case
of PHP templates). For example: text_errors
(or text_errors.html.php
).
小技巧
See 表单函数命名规则 to find out which specific block or file you have to customize.
Certain errors that are more global to your form (i.e. not specific to just one field) are rendered separately, usually at the top of your form:
{{ form_errors(form) }}
<?php echo $view['form']->render($form); ?>
To customize only the markup used for these errors, follow the same directions
as above, but now check if the compound
variable is set to true
. If it
is true
, it means that what’s being currently rendered is a collection of
fields (e.g. a whole form), and not just an individual field.
{# 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 %}
<!-- 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 ?>
When you can manage it, the easiest way to render a form field is via the
form_row
function, which renders the label, errors and HTML widget of
a field. To customize the markup used for rendering all form field rows,
override the form_row
fragment. For example, suppose you want to add a
class to the div
element around each row:
{# form_row.html.twig #}
{% block form_row %}
<div class="form_row">
{{ form_label(form) }}
{{ form_errors(form) }}
{{ form_widget(form) }}
</div>
{% endblock form_row %}
<!-- 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>
小技巧
See Form Theming for how to apply this customization.
If you want to denote all of your required fields with a required asterisk (*
),
you can do this by customizing the form_label
fragment.
In Twig, if you’re making the form customization inside the same template as your
form, modify the use
tag and add the following:
{% 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 %}
In Twig, if you’re making the form customization inside a separate template, use the following:
{% extends 'form_div_layout.html.twig' %}
{% block form_label %}
{{ parent() }}
{% if required %}
<span class="required" title="This field is required">*</span>
{% endif %}
{% endblock %}
When using PHP as a templating engine you have to copy the content from the original template:
<!-- 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 ?>
小技巧
See Form Theming for how to apply this customization.
You can also customize your form widgets to have an optional “help” message.
In Twig, if you’re making the form customization inside the same template as your
form, modify the use
tag and add the following:
{% 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 %}
In Twig, if you’re making the form customization inside a separate template, use the following:
{% extends 'form_div_layout.html.twig' %}
{% block form_widget_simple %}
{{ parent() }}
{% if help is defined %}
<span class="help">{{ help }}</span>
{% endif %}
{% endblock %}
When using PHP as a templating engine you have to copy the content from the original template:
<!-- 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 ?>
To render a help message below a field, pass in a help
variable:
{{ form_widget(form.title, {'help': 'foobar'}) }}
<?php echo $view['form']->widget($form['title'], array('help' => 'foobar')) ?>
小技巧
See Form Theming for how to apply this customization.
Most of the functions available for rendering different parts of a form (e.g. the form widget, form label, form errors, etc.) also allow you to make certain customizations directly. Look at the following example:
{# render a widget, but add a "foo" class to it #}
{{ form_widget(form.name, { 'attr': {'class': 'foo'} }) }}
<!-- render a widget, but add a "foo" class to it -->
<?php echo $view['form']->widget($form['name'], array(
'attr' => array(
'class' => 'foo',
),
)) ?>
The array passed as the second argument contains form “variables”. For more details about this concept in Twig, see More about Form Variables.