我有一个同时具有名称(字符串)和文件(也是表示文件名的字符串)的实体。这是“Icon”实体。 我有另一个名为“Category”的实体,它有一个名字(字符串)和一个Icon(OneToMany)的关系。我希望表单允许用户选择类别的图标。
所以我可以在表格中显示它:
$builder->add('icon', 'entity', array(
'class' => 'CroltsMainBundle:Icon',
'expanded' => true,
'multiple' => false
));
但我真正想要的是在每个单选按钮的枝条中显示这样的东西:
<div>
<label for="something"><img src="/icons/{{icon.file }}" />{{icon.name}}</label>
<input type="radio" name="something" value="{{ icon.id }}" />
</div>
有没有一种很好的方法可以用Symfony形式制作这种类型的无线电形式?就像我想要的定制类型一样?我真的没有用自定义类型做太多,知道这有多大可能。
答案 0 :(得分:8)
不确定这是最好的方法,但这是我如何管理这种情况:
创建一个新的表单类型,代表entityType
,IconCheckType
例如:
(http://symfony.com/doc/master/cookbook/form/create_custom_field_type.html)
namespace .....\Form\Type;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilder;
use Symfony\Component\Form\FormView;
use Symfony\Component\Form\FormInterface;
class IconCheckType extends AbstractType
{
/**
* {@inheritdoc}
*/
public function buildForm(FormBuilder $builder, array $options) {
$builder -> setAttribute('dataType', $options['dataType']);
}
/**
* {@inheritdoc}
*/
public function buildView(FormView $view, FormInterface $form) {
$view -> set('dataType', $form -> getAttribute('dataType'));
}
/**
* {@inheritdoc}
*/
public function getDefaultOptions(array $options) {
return array('required' => false,'dataType'=>'entity');
}
/**
* Returns the allowed option values for each option (if any).
*
* @param array $options
*
* @return array The allowed option values
*/
public function getAllowedOptionValues(array $options)
{
return array('required' => array(false));
}
/**
* {@inheritdoc}
*/
public function getParent(array $options) {
return 'entity';
}
/**
* {@inheritdoc}
*/
public function getName() {
return 'iconcheck';
}
}
在您的表单中
...
->add('icon', 'iconcheck', array(
'class' => 'CroltsMainBundle:Icon',
'property'=>'formField',
'multiple'=>false,
'expanded'=>true
))
...
注意property=>'formField'
,这意味着它不会返回__toString
作为标签,而是从实体类中返回函数getFormField所需的任何内容
因此,在您的实体类中:
class Icon {
....
public function getFormField() {
return $this; /* or an array with only the needed attributes */
}
....
}
然后您可以渲染自定义字段
{% block iconcheck_widget %}
{% for child in form %}
{% set obj=child.vars.label %}
<div>
<label for="something"><img src="/icons/{{obj.file }}" />{{obj.name}}</label>
{{ form_widget(child) }} {# the radio/checkbox #}
</div>
{{ form_widget(child) }}#}
{% endfor %}
{% endblock %}
答案 1 :(得分:1)
您是否可以制作__toString()
方法:
<?php
// Icon entity
public function __toString()
{
return '<img src="/icons/'. $this->file .'" />' . $this->name';
}
如果没有,则必须创建自定义类型。然而,它真的很容易
<?php
namespace Your\NameSpace;
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\Form\FormViewInterface;
use Symfony\Component\Form\FormInterface;
use Symfony\Component\OptionsResolver\Options;
use Symfony\Component\OptionsResolver\OptionsResolverInterface;
class MyCustomType extends AbstractType
{
public function getParent()
{
// By calling get parent here your custom type will
// automatically inherit all the properties/functionality
// of the type you extend
return 'radio';
}
}
然后,您可以为您的类型制作自定义小部件。如果我是你,我会阅读cookbook entry,因为它很好地解释了这个过程。您可以查看表单的默认Twig小部件,以了解如何编写自己的小部件。
答案 2 :(得分:1)
我必须在选择文件按钮前面添加一个缩略图,以便今天上传图像。我这样做了。抱歉,我没有时间为您的案例创建完整的示例。
/src/AcmeBundle/Form/Type/AcmeFormType.php
<?php
namespace Acme\AcmeBundle\Form\Type;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
class AcmeFormType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
parent::buildForm($builder, $options);
$builder
->add('icon', 'vich_uploadable')
...
config.yml
twig:
form:
resources:
- 'AcmeBundle:Form:fields.html.twig'
services:
acme.type.vich_uploadable:
class: Acme\AcmeBundle\Form\Type\VichUploadableFieldType
arguments: ["@doctrine.orm.entity_manager"]
tags:
- { name: form.type, alias: vich_uploadable }
/src/Acme/AcmeBundle/Form/fields.html.twig
{% block vich_uploadable_widget %}
{% spaceless %}
{% if attribute(form.parent.vars.value, form.name) is not empty %}
<img src="{{ vich_uploader_asset(form.parent.vars.value, form.name) | imagine_filter('thumb_square') }}" />
{% endif %}
{{ form_widget(form) }} {# If you're extending the radio button, it would show here #}
{% endspaceless %}
{% endblock %}
答案 3 :(得分:0)
这是我最终做的事情。它花费了大量的试验和错误,并深入了解EntityType类层次结构并学习Form类型的真正工作方式。最难的部分是查看源代码并弄清楚如何从PHP类到Twig模板(可用的变量)。
这就是我所做的。它不是一个完美的解决方案(感觉有点hacky)但它适用于我的目的。我的想法是将基础实体暴露给我的视图,以便我可以获得它的属性。
最大的问题是保存文件路径的file
属性在视图中是硬编码的。无论如何,我发布了整个解决方案,因为它可能对其他人有所帮助。如果有人能找到更好的解决方案,我也会批评。
(省略名称空间)
<?php
class ExtendedEntityType extends EntityType
{
public function getParent()
{
return 'extended_choice';
}
public function getName()
{
return 'extended_entity';
}
}
<?php
class ExtendedChoiceType extends ChoiceType
{
/**
* {@inheritdoc}
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
if (!$options['choice_list'] && !is_array($options['choices']) && !$options['choices'] instanceof \Traversable) {
throw new FormException('Either the option "choices" or "choice_list" must be set.');
}
if ($options['expanded']) {
$this->addSubForms($builder, $options['choice_list']->getPreferredViews(), $options);
$this->addSubForms($builder, $options['choice_list']->getRemainingViews(), $options);
if ($options['multiple']) {
$builder
->addViewTransformer(new ChoicesToBooleanArrayTransformer($options['choice_list']))
->addEventSubscriber(new FixCheckboxInputListener($options['choice_list']), 10)
;
} else {
$builder
->addViewTransformer(new ChoiceToBooleanArrayTransformer($options['choice_list']))
->addEventSubscriber(new FixRadioInputListener($options['choice_list']), 10)
;
}
} else {
if ($options['multiple']) {
$builder->addViewTransformer(new ChoicesToValuesTransformer($options['choice_list']));
} else {
$builder->addViewTransformer(new ChoiceToValueTransformer($options['choice_list']));
}
}
if ($options['multiple'] && $options['by_reference']) {
// Make sure the collection created during the client->norm
// transformation is merged back into the original collection
$builder->addEventSubscriber(new MergeCollectionListener(true, true));
}
}
/**
* {@inheritdoc}
*/
public function getParent()
{
return 'choice';
}
/**
* {@inheritdoc}
*/
public function getName()
{
return 'extended_choice';
}
/**
* Adds the sub fields for an expanded choice field.
*
* @param FormBuilderInterface $builder The form builder.
* @param array $choiceViews The choice view objects.
* @param array $options The build options.
*/
private function addSubForms(FormBuilderInterface $builder, array $choiceViews, array $options)
{
foreach ($choiceViews as $i => $choiceView) {
if (is_array($choiceView)) {
// Flatten groups
$this->addSubForms($builder, $choiceView, $options);
} else {
$choiceOpts = array(
'value' => $choiceView->value,
// Expose more data
'label' => array(
'data' => $choiceView->data,
'label' => $choiceView->label,
),
'translation_domain' => $options['translation_domain'],
);
if ($options['multiple']) {
$choiceType = 'checkbox';
// The user can check 0 or more checkboxes. If required
// is true, he is required to check all of them.
$choiceOpts['required'] = false;
} else {
$choiceType = 'radio';
}
$builder->add((string) $i, $choiceType, $choiceOpts);
}
}
}
}
<service id="crolts_main.type.extended_choice" class="My\MainBundle\Form\Type\ExtendedChoiceType">
<tag name="form.type" alias="extended_choice" />
</service>
<service id="crolts_main.type.extended_entity" class="My\MainBundle\Form\Type\ExtendedEntityType">
<tag name="form.type" alias="extended_entity" />
<argument type="service" id="doctrine" />
</service>
(这是基于MopaBootStrapBundle,但想法是一样的。区别在于MopaBootstrap将<label>
包裹在<radio>
附近
{% block extended_choice_widget %}
{% spaceless %}
{% if expanded %}
{{ block('extended_choice_widget_expanded') }}
{% else %}
{# not being used, just default #}
{{ block('choice_widget_collapsed') }}
{% endif %}
{% endspaceless %}
{% endblock extended_choice_widget %}
{% block extended_choice_widget_expanded %}
{% spaceless %}
<div {{ block('widget_container_attributes') }}>
{% for child in form %}
<label class="{{ (multiple ? 'checkbox' : 'radio') ~ (widget_type ? ' ' ~ widget_type : '') ~ (inline is defined and inline ? ' inline' : '') }}">
{{ form_widget(child, {'attr': {'class': attr.widget_class|default('')}}) }}
{% if child.vars.label.data.file is defined %}
<img src="{{ vich_uploader_asset(child.vars.label.data, 'file')}}" alt="">
{% endif %}
{{ child.vars.label.label|trans({}, translation_domain) }}
</label>
{% endfor %}
</div>
{% endspaceless %}
{% endblock extended_choice_widget_expanded %}
<?php
$builder->add('icon', 'extended_entity', array(
'class' => 'MyMainBundle:MenuIcon',
'property' => 'name', // this is still used in label.label
'expanded' => true,
'multiple' => false
));