當(dāng)你需要特定目的的表單類(lèi)型時(shí)自定義表單字段類(lèi)型就很好,例如性別選擇或者增值稅發(fā)票號(hào)碼輸入。
但是有時(shí)候,你不需要添加新的字段類(lèi)型——你只是想要在已經(jīng)存在的上面添加一些特征。這時(shí)候表單類(lèi)型擴(kuò)展就出現(xiàn)了。
表單類(lèi)型擴(kuò)展主要有兩種用途:
在這兩種情況下,通過(guò)定制的表單渲染或者定制的表單字段類(lèi)型可能會(huì)實(shí)現(xiàn)你的目標(biāo)。但是使用表單類(lèi)型擴(kuò)展可以更加清楚(通過(guò)限制模板中的業(yè)務(wù)邏輯)并且更靈活(你可以向一個(gè)表單類(lèi)型中添加幾個(gè)類(lèi)型擴(kuò)展)。
表單類(lèi)型擴(kuò)展能夠完成大多是定制的字段類(lèi)型能做的事情,但是代替自己成為字段類(lèi)型,它們插入到已經(jīng)存在的類(lèi)型中。
假設(shè)你管理一個(gè)媒體實(shí)體,并且每一個(gè)媒體都和一個(gè)文件相關(guān)聯(lián)。你的媒體表單使用了文件類(lèi)型,但是當(dāng)編輯這個(gè)實(shí)體的時(shí)候,你就會(huì)看到它的臨近文件輸入的圖像自動(dòng)渲染。
你當(dāng)然可以通過(guò)在模板中配置字段如何被渲染。但是字段類(lèi)型擴(kuò)展允許你以一種更加流行的方式。
你的第一個(gè)任務(wù)就是創(chuàng)建一個(gè)表單類(lèi)型擴(kuò)展類(lèi)(在本文中叫做 ImageTypeExtension)。標(biāo)準(zhǔn)情況下表單類(lèi)型擴(kuò)展通常位于你的某一個(gè) bundle 的 Form\Extension 目錄之下。
當(dāng)創(chuàng)建表單類(lèi)型擴(kuò)展的時(shí)候,你可以啟用 FormTypeExtensionInterface 界面或者擴(kuò)展 AbstractTypeExtension 類(lèi)。大多數(shù)情況下擴(kuò)展 abstract 類(lèi)更容易一些:
// src/Acme/DemoBundle/Form/Extension/ImageTypeExtension.php
namespace Acme\DemoBundle\Form\Extension;
use Symfony\Component\Form\AbstractTypeExtension;
class ImageTypeExtension extends AbstractTypeExtension
{
/**
* Returns the name of the type being extended.
*
* @return string The name of the type being extended
*/
public function getExtendedType()
{
return 'file';
}
}
你必須使用的一個(gè)方法就是 getExtendedType 功能。它是用來(lái)被你的擴(kuò)展所擴(kuò)展的表單類(lèi)型的名稱(chēng)的。
getExtendedType 方法返回的值和你希望擴(kuò)展的表單類(lèi)型類(lèi)的 getName 方法所返回的值相一致。
處理 getExtendedType 方法,你可能還想重寫(xiě)下列的一個(gè)方法:
有關(guān)于這些方法的用途的更多信息,你可以參考創(chuàng)建自定義表單類(lèi)型這篇指導(dǎo)文章。
接下來(lái)這一步就是使得 Symfony 知道你的擴(kuò)展。你所需要做的就是使用 form.type_extension 標(biāo)簽將它聲明為一個(gè)服務(wù):
YAML:
services:
acme_demo_bundle.image_type_extension:
class: Acme\DemoBundle\Form\Extension\ImageTypeExtension
tags:
- { name: form.type_extension, alias: file }
XML:
<service id="acme_demo_bundle.image_type_extension"
class="Acme\DemoBundle\Form\Extension\ImageTypeExtension"
>
<tag name="form.type_extension" alias="file" />
</service>
PHP:
$container
->register(
'acme_demo_bundle.image_type_extension',
'Acme\DemoBundle\Form\Extension\ImageTypeExtension'
)
->addTag('form.type_extension', array('alias' => 'file'));
標(biāo)簽的別名值就是擴(kuò)展將要應(yīng)用到的字段的類(lèi)型。在你應(yīng)用的時(shí)候,如果你想要擴(kuò)展文件字段類(lèi)型,你就可以用文件作為別名。
你的擴(kuò)展的目標(biāo)就是展示完美的圖片在文件輸入旁邊(當(dāng)基本類(lèi)型包括圖片的時(shí)候)。為了達(dá)到這個(gè)目的,假設(shè)你是用的方法和如何使用 Doctrine 處理文件上傳中所描述的一樣:你有一個(gè)具有文件屬性的媒體模型(和表單中的文件字段相對(duì)應(yīng))以及一個(gè)路徑屬性(和數(shù)據(jù)庫(kù)中的圖片路徑相對(duì)應(yīng)):
// src/Acme/DemoBundle/Entity/Media.php
namespace Acme\DemoBundle\Entity;
use Symfony\Component\Validator\Constraints as Assert;
class Media
{
// ...
/**
* @var string The path - typically stored in the database
*/
private $path;
/**
* @var \Symfony\Component\HttpFoundation\File\UploadedFile
* @Assert\File(maxSize="2M")
*/
public $file;
// ...
/**
* Get the image URL
*
* @return null|string
*/
public function getWebPath()
{
// ... $webPath being the full image URL, to be used in templates
return $webPath;
}
}
你的表單類(lèi)型擴(kuò)展類(lèi)將需要做兩件事來(lái)擴(kuò)展文件表單類(lèi)型:
邏輯如下:當(dāng)添加文件類(lèi)型的表單字段,你就能制定一個(gè)新的選項(xiàng):image_path。這個(gè)選項(xiàng)將會(huì)告訴文件字段如何獲得實(shí)際的圖片地址并且在視圖中展示它:
// src/Acme/DemoBundle/Form/Extension/ImageTypeExtension.php
namespace Acme\DemoBundle\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;
class ImageTypeExtension extends AbstractTypeExtension
{
/**
* Returns the name of the type being extended.
*
* @return string The name of the type being extended
*/
public function getExtendedType()
{
return 'file';
}
/**
* Add the image_path option
*
* @param OptionsResolver $resolver
*/
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefined(array('image_path'));
}
/**
* Pass the image URL to the view
*
* @param FormView $view
* @param FormInterface $form
* @param array $options
*/
public function buildView(FormView $view, FormInterface $form, array $options)
{
if (array_key_exists('image_path', $options)) {
$parentData = $form->getParent()->getData();
if (null !== $parentData) {
$accessor = PropertyAccess::createPropertyAccessor();
$imageUrl = $accessor->getValue($parentData, $options['image_path']);
} else {
$imageUrl = null;
}
// set an "image_url" variable that will be available when rendering this field
$view->vars['image_url'] = $imageUrl;
}
}
}
每一個(gè)字段類(lèi)型都是由模板碎片所渲染的。那些模板碎片可以被重寫(xiě)從而來(lái)自定義表單渲染。獲取更多信息,你可以閱讀什么是表單主題?這篇文章。
在你的擴(kuò)展類(lèi)之中,你已經(jīng)添加了一個(gè)新的變量(image_url),但是你依舊需要使用你的模板中的新的變量。特別的,你需要重寫(xiě) file_widget 區(qū)域:
Twig:
{# src/Acme/DemoBundle/Resources/views/Form/fields.html.twig #}
{% 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 %}
PHP:
<!-- src/Acme/DemoBundle/Resources/views/Form/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 ?>
你需要改變你的配置文件或者明確指定你想要如何給你的表單加主題為了使 Symfony 使用你所重寫(xiě)的區(qū)域。更多信息詳見(jiàn)什么是表單主題?這篇文章。
從現(xiàn)在起,當(dāng)在你的表單中添加文件類(lèi)型的字段時(shí),你就可以指定 image_path 選項(xiàng),這個(gè)選項(xiàng)將用來(lái)展示文件字段旁的圖片。舉例來(lái)說(shuō):
// src/Acme/DemoBundle/Form/Type/MediaType.php
namespace Acme\DemoBundle\Form\Type;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
class MediaType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('name', 'text')
->add('file', 'file', array('image_path' => 'webPath'));
}
public function getName()
{
return 'media';
}
}
當(dāng)展示表單的時(shí)候,如果基本的模型已經(jīng)和圖片關(guān)聯(lián),你就會(huì)看到它在文件輸入旁邊顯示。