如何创建一个表单类型扩展
表单类型扩展(Form Type Extension)不可思议地强大:它们允许你在整个系统之内去 调整 任何一个既存的表单字段类型。
有两个主要使用场景:
你想添加一个 特定功能到某个表单类型 (诸如添加一个“下载”功能到
FileType
字段类型中);你想添加一个 通用功能到多个类型中(如,在所有“input text/文本输入”的类型中添加一个“help/帮助”信息)。
假设你有一个 Media
entity,每个媒体关联一个文件(file)。这个 Media
使用的是一个file字段类型,但是在编辑这个entity时,你希望看到它的图片自动显示在文件输入框的旁边。
表单类型扩展: Form Type Extension。
表单字段类型: Form Field Type。
Tip
老版文档提示1:自定义表单字段类型并控制其输出,可以实现同样目的。但使用表单类型扩展更清爽(它限制了模板中的业务逻辑)、更灵活(可以给某个表单类型添加多个扩展)。
Tip
老版文档提示2:表单类型扩展可以实现大多数表单字段类型所能达到的事,可是,字段类型只是对自己进行自定义,而扩展则可以打入到所有已知类型之中。
Tip
老版文档提示3:无论如何你可以通过自定义“该字段如何渲染到模板”来实现(译注:即自定义表单字段类型的方式)。但是表单类型扩展允许你使用一种更加DRY(省事)的方式来完成。
定义表单类型扩展 ¶
首先,创建表单类型扩展类:
Note
根据标准,表单扩展通常应保存在你bundle的 Form\Extension
目录中.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
// src/AppBundle/Form/Extension/ImageTypeExtension.PHP
namespace AppBundle\Form\Extension;
use Symfony\Component\Form\AbstractTypeExtension;
use Symfony\Component\Form\Extension\Core\Type\FileType;
class ImageTypeExtension extends AbstractTypeExtension
{
/**
* Returns the name of the type being extended.
* 返回待扩展的类型之名称
* @return string The name of the type being extended
*/
public function getExtendedType()
{
// use FormType::class to modify (nearly) every field in the system
// 若使用 FormType::class 即可修改系统中(几乎)所有的字段(类型)
return FileType::class;
}
} |
唯一的一个你必须要实现的方法是 getExtendedType
。它用于配置你要修改的是 哪个 表单字段或字段类型。
Tip
getExtendedType
方法返回的值对应你想要扩展的表单类型之完整名称。
除了 getExtendedType
函数,你可能还希望覆写以下方法:
buildForm()
buildView()
configureOptions()
finishView()
关于这些方法的更多信息,参考 如何创建一个自定义的表单字段类型。
把你的表单类型注册成一个服务 ¶
下一步是让Symfony了知你的扩展。通过用 form.type_extension
标签来注册你的服务即可实现:
1 2 3 4 5 6 |
services:
# ...
AppBundle\Form\Extension\ImageTypeExtension:
tags:
- { name: form.type_extension, extended_type: Symfony\Component\Form\Extension\Core\Type\FileType } |
1 2 3 4 5 6 7 8 9 10 11 12 |
<?xml version="1.0" encoding="UTF-8" ?>
<container xmlns="Http://symfony.com/schema/dic/services"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://symfony.com/schema/dic/services
http://symfony.com/schema/dic/services/services-1.0.xsd">
<services>
<service id="AppBundle\Form\Extension\ImageTypeExtension">
<tag name="form.type_extension" extended-type="Symfony\Component\Form\Extension\Core\Type\FileType" />
</service>
</services>
</container> |
1 2 3 4 5 6 7 8 |
use AppBundle\Form\Extension\ImageTypeExtension;
use Symfony\Component\Form\Extension\Core\Type\FileType;
$container->autowire(ImageTypeExtension::class)
->addTag('form.type_extension', array(
'extended_type' => FileType::class
))
; |
打标签时的 extended_type
必须要匹配你从 extended_type
方法中返回的类。—旦 你做完此事,你所覆写的任何方法(如 buildForm()
),都会在给定的类型(FileType
)所对应的 任何一个 字段在构建时被调用。我们可通过下例查看。
Symfony 3.3新增: Symfony 3.3之前,你需要把类型扩展的服务给定义成public。从Symfony 3.3开始,你也可以将其定义成private。
Tip
tag中有一个可选的属性叫做 priority
,默认值是0,控制的是表单类型扩展被加载时的顺序(愈高的优先级,该扩展愈先被加载)。这在你需要确保某个扩展要提前加载,或是在其他扩展之后加载时,十分有用。
Symfony 3.2新增:
priority
属性自Symfony 3.2起被引入。
为扩展添加业务逻辑 ¶
你的扩展的目标,就是在文件的输入框附近展示一张漂亮的图片(当底层的模型包含图片时)。为此,假设你的实现方式类似于 如何使用 Doctrine 处理文件上传 中所描述的:你有一个Media模型,内含一个path属性,用于将数图片路径保存在数据库中:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
// src/AppBundle/Entity/Media.php
namespace AppBundle\Entity;
use Symfony\Component\Validator\Constraints as Assert;
class Media
{
// ...
/**
* @var string The path - typically stored in the database / 一般存于数据库中
*/
private $path;
// ...
public function getWebPath()
{
// ... $webPath being the full image URL, to be used in templates
// ... $webPath 应是完整的图片链接,将用于模板中
return $webPath;
}
} |
为了扩展 FileType::class
表单类型,你的表单类型扩展类,需要做两件事:
覆写
configureOptions
方法,以便任何一个(使用了)FileType
的字段,都能拥有一个image_path
选项;重写
buildForm
和buildView
方法,来将图片的URL地址传递到view(视图层)。
Tip
老版文档提示4: 此处的逻辑如下:当添加一个 FileType::class
类型的表单字段时,你是能够设置一个新的选项的:image_path
。这个配置将告诉file字段如何去获取图片的真实URL并把它显示在视图层中。
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 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 |
// src/AppBundle/Form/Extension/ImageTypeExtension.php
namespace AppBundle\Form\Extension;
use Symfony\Component\Form\AbstractTypeExtension;
use Symfony\Component\Form\FormView;
use Symfony\Component\Form\FormInterface;
use Symfony\Component\PropertyAccess\PropertyAccess;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\Form\Extension\Core\Type\FileType;
class ImageTypeExtension extends AbstractTypeExtension
{
public function getExtendedType()
{
return FileType::class;
}
public function configureOptions(OptionsResolver $resolver)
{
// makes it legal for FileType fields to have an image_property option
// 应确保对于FileType字段来说,拥有一个image_property选项是合法的
$resolver->setDefined(array('image_property'));
}
public function buildView(FormView $view, FormInterface $form, array $options)
{
if (isset($options['image_property'])) {
// this will be whatever class/entity is bound to your form (e.g. Media)
// 这即是你的表单所绑定的任何一个entity或class(此处是Media)
$parentData = $form->getParent()->getData();
$imageUrl = null;
if (null !== $parentData) {
$accessor = PropertyAccess::createPropertyAccessor();
$imageUrl = $accessor->getValue($parentData, $options['image_property']);
}
// set an "image_url" variable that will be available when rendering this field
// 设置一个能当字段被输出时,够使用在模板中的 "image_url" 变量
$view->vars['image_url'] = $imageUrl;
}
}
} |
覆写文件控件(File Widget)的模板码段 ¶
每个字段类型,都是通过一个模板码段来呈现的。这些模板码段是可以被覆写的,为的是对表单的输出进行自定义。更多信息请参考 什么是表单主题?。
在你的扩展类中,你已经添加了一个变量(image_url
),但是你仍然需要在模板中来利用这个新变量。特别是,你需要重写 file_widget
区块:
1 2 3 4 5 6 7 8 9 10 11 12 |
{% extends 'form_div_layout.HTML.twig' %}
{% block file_widget %}
{% spaceless %}
{{ block('form_widget') }}
{% if image_url is not null %}
<img src="{{ asset(image_url) }}"/>
{% endif %}
{% endspaceless %}
{% endblock %} |
1 2 3 4 5 |
<!-- app/Resources/file_widget.html.php -->
<?php echo $view['form']->widget($form) ?>
<?php if (null !== $image_url): ?>
<img src="<?php echo $view['assets']->getUrl($image_url) ?>"/>
<?php endif ?> |
Tip
老版文档提示5: 你需要改变配置文件,或显式地指定你想要如何给表单增加主题,这都是为了让 Symfony 使用你覆写了的模板代码区块(block)。参考 什么是表单主题? 以了解更多。
确保 配置了此一表单主题的模板 以便系统能够发现它。
使用表单类型扩展 ¶
从现在开始,当你在表单中添加一个 FileType::class
类型的字段时,你可以指定一个 image_path
选项,这个选项将用来在file字段旁边展示图片。例如:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
// src/AppBundle/Form/Type/MediaType.php
namespace AppBundle\Form\Type;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\Extension\Core\Type\FileType;
class MediaType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('name', TextType::class)
->add('file', FileType::class, array('image_property' => 'webPath'));
}
} |
当显示表单时,如果底层模型(underlying model)已经关联到一张图片,你就会看到它显示在文件输入框的旁边。
通用型表单类型扩展 ¶
你可以一次性地修改若干表单类型,通过指定它们的通用父类型来实现(见Form Types参考)。例如,某些表单类型是从 TextType
表单类型继承过来的(如,EmailType
, SearchType
, UrlType
等)。一个应用到 TextType
的表单类型扩展(即,getExtendedType
方法返回的是 TextType::class
)将会同时应用到所有这些(继承者们的)表单类型中去。
利用这一方式,由于 大多数 Symfony原生表单类型都是继承自 FormType
这个表单类型,所以,一个应用到 FormType
的表单类型扩展,将同时应用到所有这些(继承者们的)表单类型中去(但有个重大例外是 ButtonType
这个表单类型)。另外注意,如果你创建或使用了一个自定义的表单类型,它是可以不去继承 FormType
的,因此你的表单类型扩展也可能作用不到它的身上。
Tip
老版文档提示6: 你自定义的表单类型属于 basetype
。ButtonType
请参考 ButtonType Field。