【问题标题】:Symfony2 Entity type form with name and image带有名称和图像的 Symfony2 实体类型表单
【发布时间】:2012-06-26 18:26:35
【问题描述】:

我有一个具有名称(字符串)和文件(也是表示文件名的字符串)的实体。这是“图标”实体。 我有另一个名为“类别”的实体,它有一个名称(字符串)和一个与图标(OneToMany)的关系。我希望表单允许用户为类别选择图标。

所以我可以将其显示为:

$builder->add('icon', 'entity', array(
    'class' => 'CroltsMainBundle:Icon',
    'expanded' => true,
    'multiple' => false
));

但我真正想要的是在 twig 中为每个单选按钮显示类似的内容:

<div>
<label for="something"><img src="/icons/{{icon.file }}" />{{icon.name}}</label>
<input type="radio" name="something" value="{{ icon.id }}" />
</div>

有没有用 Symfony 表单制作那种类型的广播表单的好方法?就像我想要的自定义类型一样吗?我真的没有对自定义类型做太多事情,不知道这有多少是可能的。

【问题讨论】:

  • 下载genemu表单包以学习和重用有关自定义表单字段的内容,因为这听起来就是您所需要的
  • 我在看很多自定义表单的东西,我只是对如何从我的新“EntityType”访问实体的其他属性感到困惑

标签: symfony


【解决方案1】:

不确定这是最好的方法,但这是我处理这种情况的方法:

  1. 创建一个代表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';
      }
    
    }
    
  2. 在您的表单中

    ...
    ->add('icon', 'iconcheck', array(
            'class' => 'CroltsMainBundle:Icon',
            'property'=>'formField',
            'multiple'=>false,
            'expanded'=>true
          ))
    ...
    

    注意property=&gt;'formField',这意味着它不会返回__toString 作为标签,而是会从实体类的函数getFormField 中返回您想要的任何内容

  3. 所以,在你的实体类中:

    class Icon {
    
    ....
        public function getFormField() {  
           return $this;   /* or an array with only the needed attributes */ 
        }
    
    ....
    }
    
  4. 然后你可以渲染你的自定义字段

    {% 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 %}
    

【讨论】:

  • 我的方法(见下文)似乎有效,但它没有将正确的收音机选择标记为“已检查”。您的方法适用于 Sf2.1 的一些修复(尽管当我第一次提出问题时我没有使用 2.1)。无论如何,感谢第一个似乎有效的答案!
【解决方案2】:

你能不能让你的__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 小部件,以了解如何编写自己的表单。

【讨论】:

  • 第一个建议不起作用。字符串被编码,所以它不能是 HTML(输出是&amp;lt;img...)。第二个答案是我正在尝试的东西(我知道如何制作一种新类型)。我不知道如何让它与实体的两个属性相关联。
  • 好的,我能弄清楚这一点。虽然这不是我意识到的首选解决方案,因为现在文件的位置被硬编码为/icons/,并且在 ENTITY 中设置。我可以通过将标签输出更改为{{ label|raw }} 来让它工作。
  • 嗨,马特,很抱歉我没能就那个问题回复你,很高兴你想出了某种解决方案。
【解决方案3】:

我今天必须在选择文件按钮前添加一个缩略图来上传图片。我最终这样做了。抱歉,我没有时间为您的案例创建一个完整的示例。

  • 我只是访问父对象以获取要传递给 vich_uploadable_asset() 帮助器的实体。

/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 %}

【讨论】:

  • 这似乎没有帮助。我可以像你做的那样添加一个图像。我需要显示一个实体列表,其中包含基于一个属性的图像和基于另一个属性的文本。
  • 我现在看到了 vich_uploader_asset 是如何对我正在做的事情有用的 Twig 函数。我部分使用此解决方案。我将分享我如何将它们组合在一起。
【解决方案4】:

这就是我最终要做的事情。它需要大量的试验和错误,并深入研究 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 将 &lt;label&gt; 包裹在 &lt;radio&gt; 周围)

{% 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
    ));

【讨论】:

    猜你喜欢
    • 2013-07-09
    • 2016-03-08
    • 1970-01-01
    • 1970-01-01
    • 2013-03-02
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-02-10
    相关资源
    最近更新 更多