これが私がやったことです。多くの試行錯誤を繰り返し、EntityType クラス階層を掘り下げて、フォーム型が実際にどのように機能するかを学びました。最も難しい部分は、ソース コードを調べて、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
));