这就是我最终要做的。它需要大量的试验和错误,并深入研究 EntityType 类层次结构并了解 Form 类型是如何真正工作的。最难的部分是查看源代码并弄清楚如何从 PHP 类到 Twig 模板(哪些变量可用)。
这就是我所做的。这不是一个完美的解决方案(感觉有点老套),但它适用于我的目的。这个想法是将底层实体暴露给我的视图,以便我可以获得它的属性。
最大的问题是file
保存文件路径的属性在视图中是硬编码的。无论如何,我发布了整个解决方案,因为它可能对其他人有帮助。如果有人能找到更好的解决方案,我也愿意批评。
(省略命名空间)
扩展实体类型
<?php
class ExtendedEntityType extends EntityType
{
public function getParent()
{
return 'extended_choice';
}
public function getName()
{
return 'extended_entity';
}
}
扩展选择类型(只需更改 addSubForms 但它是私有的)
<?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>
form_layout.html.twig
(这是基于 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
));