发布于 2015-08-27 16:47:36 | 181 次阅读 | 评论: 0 | 来源: 网络整理
Symfony comes with a bunch of core field types available for building forms. However there are situations where you may want to create a custom form field type for a specific purpose. This recipe assumes you need a field definition that holds a person’s gender, based on the existing choice field. This section explains how the field is defined, how you can customize its layout and finally, how you can register it for use in your application.
In order to create the custom field type, first you have to create the class
representing the field. In this situation the class holding the field type
will be called GenderType
and the file will be stored in the default location
for form fields, which is <BundleName>FormType
. Make sure the field extends
AbstractType
:
// src/AppBundle/Form/Type/GenderType.php
namespace AppBundleFormType;
use SymfonyComponentFormAbstractType;
use SymfonyComponentOptionsResolverOptionsResolver;
class GenderType extends AbstractType
{
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(array(
'choices' => array(
'm' => 'Male',
'f' => 'Female',
)
));
}
public function getParent()
{
return 'choice';
}
public function getName()
{
return 'gender';
}
}
小技巧
The location of this file is not important - the FormType
directory
is just a convention.
Here, the return value of the getParent
function indicates that you’re
extending the choice
field type. This means that, by default, you inherit
all of the logic and rendering of that field type. To see some of the logic,
check out the ChoiceType class. There are three methods that are particularly
important:
buildForm()
buildForm
method, which is where
you configure and build any field(s). Notice that this is the same method
you use to setup your forms, and it works the same here.buildView()
multiple
variable is set and used in the template to set (or not
set) the multiple
attribute on the select
field. See Creating a Template for the Field
for more details.2.7 新版功能: The configureOptions()
method was introduced in Symfony 2.7. Previously,
the method was called setDefaultOptions()
.
configureOptions()
buildForm()
and buildView()
. There are a lot of
options common to all fields (see form Field Type),
but you can create any others that you need here.小技巧
If you’re creating a field that consists of many fields, then be sure
to set your “parent” type as form
or something that extends form
.
Also, if you need to modify the “view” of any of your child types from
your parent type, use the finishView()
method.
The getName()
method returns an identifier which should be unique in
your application. This is used in various places, such as when customizing
how your form type will be rendered.
The goal of this field was to extend the choice type to enable selection of
a gender. This is achieved by fixing the choices
to a list of possible
genders.
Each field type is rendered by a template fragment, which is determined in
part by the value of your getName()
method. For more information, see
What are Form Themes?.
In this case, since the parent field is choice
, you don’t need to do
any work as the custom field type will automatically be rendered like a choice
type. But for the sake of this example, suppose that when your field is “expanded”
(i.e. radio buttons or checkboxes, instead of a select field), you want to
always render it in a ul
element. In your form theme template (see above
link for details), create a gender_widget
block to handle this:
{# src/AppBundle/Resources/views/Form/fields.html.twig #}
{% block gender_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 #}
{{ block('choice_widget') }}
{% endif %}
{% endspaceless %}
{% endblock %}
<!-- src/AppBundle/Resources/views/Form/gender_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 ?>
注解
Make sure the correct widget prefix is used. In this example the name should
be gender_widget
, according to the value returned by getName
.
Further, the main config file should point to the custom form template
so that it’s used when rendering all forms.
When using Twig this is:
# 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',
),
));
For the PHP templating engine, your configuration should look like this:
# app/config/config.yml
framework:
templating:
form:
resources:
- 'AppBundle:Form'
<!-- 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>AppBundle:Form</twig:resource>
</framework:form>
</framework:templating>
</framework:config>
</container>
// app/config/config.php
$container->loadFromExtension('framework', array(
'templating' => array(
'form' => array(
'resources' => array(
'AppBundle:Form',
),
),
),
));
You can now use your custom field type immediately, simply by creating a new instance of the type in one of your forms:
// src/AppBundle/Form/Type/AuthorType.php
namespace AppBundleFormType;
use SymfonyComponentFormAbstractType;
use SymfonyComponentFormFormBuilderInterface;
class AuthorType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('gender_code', new GenderType(), array(
'placeholder' => 'Choose a gender',
));
}
}
But this only works because the GenderType()
is very simple. What if
the gender codes were stored in configuration or in a database? The next
section explains how more complex field types solve this problem.
2.6 新版功能: The placeholder
option was introduced in Symfony 2.6 in favor of
empty_value
, which is available prior to 2.6.
So far, this entry has assumed that you have a very simple custom field type. But if you need access to configuration, a database connection, or some other service, then you’ll want to register your custom type as a service. For example, suppose that you’re storing the gender parameters in configuration:
# app/config/config.yml
parameters:
genders:
m: Male
f: Female
<!-- app/config/config.xml -->
<parameters>
<parameter key="genders" type="collection">
<parameter key="m">Male</parameter>
<parameter key="f">Female</parameter>
</parameter>
</parameters>
// app/config/config.php
$container->setParameter('genders.m', 'Male');
$container->setParameter('genders.f', 'Female');
To use the parameter, define your custom field type as a service, injecting
the genders
parameter value as the first argument to its to-be-created
__construct
function:
# src/AppBundle/Resources/config/services.yml
services:
app.form.type.gender:
class: AppBundleFormTypeGenderType
arguments:
- "%genders%"
tags:
- { name: form.type, alias: gender }
<!-- src/AppBundle/Resources/config/services.xml -->
<service id="app.form.type.gender" class="AppBundleFormTypeGenderType">
<argument>%genders%</argument>
<tag name="form.type" alias="gender" />
</service>
// src/AppBundle/Resources/config/services.php
use SymfonyComponentDependencyInjectionDefinition;
$container
->setDefinition('app.form.type.gender', new Definition(
'AppBundleFormTypeGenderType',
array('%genders%')
))
->addTag('form.type', array(
'alias' => 'gender',
))
;
小技巧
Make sure the services file is being imported. See 使用 imports 导入 for details.
Be sure that the alias
attribute of the tag corresponds with the value
returned by the getName
method defined earlier. You’ll see the importance
of this in a moment when you use the custom field type. But first, add a __construct
method to GenderType
, which receives the gender configuration:
// src/AppBundle/Form/Type/GenderType.php
namespace AppBundleFormType;
use SymfonyComponentOptionsResolverOptionsResolver;
// ...
// ...
class GenderType extends AbstractType
{
private $genderChoices;
public function __construct(array $genderChoices)
{
$this->genderChoices = $genderChoices;
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(array(
'choices' => $this->genderChoices,
));
}
// ...
}
Great! The GenderType
is now fueled by the configuration parameters and
registered as a service. Additionally, because you used the form.type
alias in its
configuration, using the field is now much easier:
// src/AppBundle/Form/Type/AuthorType.php
namespace AppBundleFormType;
use SymfonyComponentFormFormBuilderInterface;
// ...
class AuthorType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('gender_code', 'gender', array(
'placeholder' => 'Choose a gender',
));
}
}
Notice that instead of instantiating a new instance, you can just refer to
it by the alias used in your service configuration, gender
. Have fun!