Поле динамического выбора формы Symfony2 с возвращаемыми значениями EAV

Я создаю пакет электронной коммерции с Symfony2 и Doctrine2. Я применяю подход EAV для функций продукта и значений продукта для неограниченных возможностей. Для этого у меня есть три основных объекта: Product, FeatureKind и FeatureValues.

  • FeatureKind связан с FeatureValues ​​с однонаправленным OneToMany отношения.
  • Продукт подключен к FeatureKind с отношением ManyToMany.

Проблема в том, что мне нужен FeatureType в качестве меток, а в виде продукта - различные поля в качестве поля выбора. Мне удалось получить свойство и связанные значения в форме продукта, но я не знаю, как превратить их в поля выбора.

Ниже перечислены все три сущности, контроллер и код формы и результат моего кода.

Примечание. Я удалил лишние вещи из кода, чтобы он был коротким.

product.php

namespace Webmuch\ProductBundle\Entity;

/**
 * @ORM\Table()
 * @ORM\Entity
 */
class Product
{
    /**
     * @ORM\Column(name="id", type="integer")
     * @ORM\Id
     * @ORM\GeneratedValue(strategy="AUTO")
     */
    private $id;

    /**
     * @ORM\Column(name="title", type="string", length=255)
     */
    private $title;

    /**
     * @ORM\ManyToMany(targetEntity="FeatureKind", inversedBy="product", cascade={"persist"})
     * @ORM\JoinTable(name="product_featurekind")
     **/
    private $featurekind;
}

FeatureKind.php

namespace Webmuch\ProductBundle\Entity;

/**
 * @ORM\Table(name="feature_kind")
 * @ORM\Entity
 */
class FeatureKind
{
    /**
     * @ORM\Id
     * @ORM\Column(name="id", type="integer")
     * @ORM\GeneratedValue(strategy="AUTO")
     */
    protected $id;

    /**
     * @ORM\Column(name="name", type="string", length=50)
     */
    protected $name;

    /**
     * @ORM\ManyToMany(targetEntity="FeatureValue")
     * @ORM\JoinTable(name="feature_kind_value",
     *      joinColumns={@ORM\JoinColumn(name="kind_id", referencedColumnName="id")},
     *      inverseJoinColumns={@ORM\JoinColumn(name="value_id", referencedColumnName="id", unique=true)}
     *      )
     **/
    protected $values;   
}

FeatureValue.php

namespace Webmuch\ProductBundle\Entity;

/**
 * @ORM\Table()
 * @ORM\Entity
 */
class FeatureValue
{
    /**
     * @ORM\Column(name="id", type="integer")
     * @ORM\Id
     * @ORM\GeneratedValue(strategy="AUTO")
     */
    protected $id;

    /**
     * @ORM\Column(name="value", type="string", length=100)
     */
    protected $value;
}

ProductController.php

public function newAction(Request $request)
{
    $entity = new Product();
    $em = $this->getDoctrine()->getEntityManager();
    $features = $em->getRepository('ProductBundle:FeatureKind')->findAll();

    foreach($features as $feature)
    {
        $featurekind = new FeatureKind();
        $featurekind->setTitle($feature->getTitle());
        foreach($feature->getValue() as $value ){
            $featurekind->getValue()->add($value);
        }
        $entity->getFeaturekind()->add($featurekind);   
    }

    $form = $this->createForm(new ProductType(), $entity);

     if ('POST' === $request->getMethod()) {
        $form->bindRequest($request);
        if ($form->isValid()) {
            $em->persist($entity);
            $em->flush();

            return $this->redirect($this->generateUrl('product_show', array(
                'id' => $entity->getId()
            )));
        }
    }
    return $this->render('ProductBundle:Product:new.html.twig', array(
       'form'   => $form->createView()
    ));
}

ProductType.php

namespace Webmuch\ProductBundle\Form;

use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilder;

class ProductType extends AbstractType
{
    public function buildForm(FormBuilder $builder, array $options)
    {
        $builder
            ->add('featurekind', 'collection', array('type' => new FeatureKindType()))
            ->getForm();
        }

    public function getDefaultOptions(array $options)
    {
        return array(
            'data_class' => 'Webmuch\ProductBundle\Entity\Product',
            'required' => true
        );
    }

    public function getName()
    {
        return 'product';
    }
}

FeatureKindType.php

namespace Webmuch\ProductBundle\Form;

class FeatureKindType extends AbstractType
{
    public function buildForm(FormBuilder $builder, array $options)
    {
        $builder
            ->add('title')
            ->add('value','collection', array(
                                               'type' => new FeatureValueType(),
                                               'allow_add'=>true))
            ->getForm();
    }

    public function getDefaultOptions(array $options)
    {
        return array(
            'data_class' => 'Webmuch\ProductBundle\Entity\FeatureKind',
        );
    }

    public function getName()
    {
        return 'featurekind';
    }
}

This is my form result.

EDIT:

Через несколько дней работы я застрял с простым набором функций и их несколькими значениями:

Array
(
    [Color] => Array
        (
            [Red] => Red
            [Green] => Green
        )

    [Size] => Array
        (
            [Large] => Large
            [Medium] => Medium
            [Small] => Small
        )

    [Sleeve Style] => Array
        (
            [Half Sleeved] => Half Sleeved
            [Full Sleeved] => Full Sleeved
            [Cut Sleeves] => Cut Sleeves
        )

)

Я попытался создать форму следующим образом: $this- > choice содержит массив.

$builder
    ->add('name')
    ->add('slug')
    ->add('active')
;

foreach ($this->choices as $choice) {
    $builder->add('featurekind', 'choice', array(
        'required' => 'false',
        'choices' => $choice,
        'empty_value' => 'Choose an option',
        'empty_data'  => null
    ));
}

$builder->getForm();

Вышеописанное не работает над свойством $featurekind. Я получаю сообщение об ошибке:

Notice: Object of class Doctrine\Common\Collections\ArrayCollection could not be converted to int in /vagrant/project/vendor/symfony/symfony/src/Symfony/Component/Form/Extension/Core/ChoiceList/ChoiceList.php line 457

Хотя если поле формы привязано к любому несвязавшемуся свойству, например: $name, оно все равно создает только одно поле формы для последней итерации цикла.

У меня нет вариантов.

Ответы

Ответ 1

То, что вы хотите сделать, не может быть выполнено с вашей текущей структурой. Позвольте мне попытаться объяснить: FeatureKind имеет отношение один к многим с FeatureValue. Это означает, что вы можете иметь "цветной" вид, который может иметь значения "красный", "розовый" и т.д. Это нормально. Но ваш объект продукта имеет коллекцию объектов FeatureKind, поэтому у него может быть список "Цвет", "Размер" и т.д. НО (это самая важная часть), он не имеет никакого отношения к определенному значению для любого этих видов: нет свойства, которое имеет конкретное значение для каждого вида. Надеюсь, вы могли бы разобраться в этом, его немного сложно объяснить.

Что вам нужно сделать:

Определите классы FeatureValue и FeatureKind так, как они есть.

Определите NEW объект, который обрабатывает связь между видом и значением для продукта:

namespace Webmuch\ProductBundle\Entity;

/**
 * @ORM\Table()
 * @ORM\Entity
 */
class FeatureKindValue
{
    /**
     * @ORM\Id
     * @ORM\Column(name="id", type="integer")
     * @ORM\GeneratedValue(strategy="AUTO")
     */
    protected $id;

    /**
     * @ManyToOne(targetEntity="Product", inversedBy="features")
     **/
    private $product;

    /**
     * @ORM\ManyToOne(targetEntity="FeatureKind")
     **/
    protected $kind;   

    /**
     * @ORM\ManyToOne(targetEntity="FeatureValue")
     **/
    protected $value;   
}

Этот объект обрабатывает пары вида: значение, например цвет: красный

Наконец, ваш объект продукта имеет свойство этого нового типа:

namespace Webmuch\ProductBundle\Entity;

/**
 * @ORM\Table()
 * @ORM\Entity
 */
class Product
{
    /**
     * @ORM\Column(name="id", type="integer")
     * @ORM\Id
     * @ORM\GeneratedValue(strategy="AUTO")
     */
    private $id;

    /**
     * @ORM\Column(name="title", type="string", length=255)
     */
    private $title;

    /**
     * @ORM\OneToMany(targetEntity="FeatureKindValue", mappedBy="product")
     **/
    private $features;
}

Затем, чтобы представить форму по вашему желанию, сделайте что-то похожее на инструкции, приведенные в ответе на этот stackoverflow question

Ответ 2

Этот тип материала может быть сложным, но вы можете использовать этот подход в своем классе FeatureKindType:

public function buildForm(FormBuilderInterface $builder, array $options)
{
    $builder->addEventListener(
        FormEvents::PRE_SET_DATA,
        function (DataEvent $event) use ($builder) {
            /* @var FormBuilderInterface $builder */
            $form = $event->getForm();
            /* @var FeatureKind $data */
            $data = $event->getData();
            if ($data !== null) {
                $form->add(
                    $builder->getFormFactory()->createNamed(
                        'values',
                        'entity',
                        null,
                        array(
                            'label' => $data->getName(),
                            'class' => 'WebmuchProductBundle:FeatureValue',
                        )
                    )
                );
            }
        }
    );
}

Помните, я не пробовал публиковать форму и сохранять объекты в вашем случае, но форма теперь дублирует имя метки FeatureKind и выпадающего списка, содержащего соответствующий FeatureKindValues.

Я использую этот подход в моем проекте, и он работает для меня.