Можно ли динамически генерировать выбор в коллекциях форм Symfony 4? - PullRequest
1 голос
/ 17 мая 2019

Можно ли динамически генерировать варианты в коллекциях форм?

Ситуация:

  • ItemEntity

  • PropertyEntity

Эти сущности можно добавлять и редактировать с помощью Форма элемента .

Когда вы добавляете тип к объекту Item , статус объектов свойств будет загружен через запрос ajax.Каждый тип элемента имеет различные варианты статуса, предоставляемые службой.

Этот сервис внедряется в PropertyFormType для предоставления доступных вариантов выбора.

Все работает нормально, кроме отправки формы, по-прежнему возвращаются ошибки.Похоже, что варианты не загружены.

На объекте Свойство предоставленные опции status пусты.Я знаю, что служба предоставляет правильные данные (массив со всеми вариантами статуса для типа элемента).

Отладка сообщает, что во время события POST_SUBMIT данные не установлены.dump($event->getForm()->getData()); показывает мне, что свойство type Item Entity по-прежнему равно нулю, даже если оно задано в форме.

Можно ли прочитать отправленные данные из родительской формыобъект для определения, какие варианты были загружены через ajax для исправления ошибок ConstraintViolation?

Документы Symfony:

Ошибки формы:

Caused by:
ConstraintViolation {#2314 ▶}
TransformationFailedException {#1587 ▼
  #message: "Unable to reverse value for property path "status": The choice "test" does not exist or is not unique"
  #code: 0
  #file: "/home/vagrant/shop4raad2/vendor/symfony/form/Form.php"
  #line: 1150
  trace: {...}
   …1
}
TransformationFailedException {#1598 ▼
  #message: "The choice "test" does not exist or is not unique"
  #code: 0
  #file: "/home/vagrant/shop4raad2/vendor/symfony/form/Extension/Core/DataTransformer/ChoiceToValueTransformer.php"
  #line: 48
  trace: {...}
}

Форма изделия:

namespace App\Form;

use App\Entity\ItemEntity;
use App\Form\Type\PropertyFormType;
use App\Service\Provider\ItemTypeProvider;
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
use Symfony\Component\Form\Extension\Core\Type\CollectionType;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;

/**
 * Item Form
 */
class ItemForm extends AbstractType
{
    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $item = $builder->getData();

        $builder->add("type", ChoiceType::class, [
            "choices"            => ItemEntity::getTypeChoices(),
            "disabled"           => null !== $dataImportMapping->getId(),
            "required"           => null === $dataImportMapping->getId(),
            /* ... */
        ]);

        $builder->add("properties", CollectionType::class, [
            "entry_type"         => PropertyFormType::class,
            "entry_options"      => [PropertyFormType::OPTION_ITEM => $item],
            "prototype"          => true,
            "allow_add"          => true,
            "allow_delete"       => true,
            /* ... */
        ]);
    }

    public function configureOptions(OptionsResolver $resolver)
    {
        $resolver->setDefaults([
            'data_class' => ItemEntity::class,
        ]);
    }
}

Тип формы собственности:

namespace App\Form\Type;

use App\Entity\PropertyEntity;
use App\Service\Provider\PropertyStatusProvider;
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\Form\FormEvent;
use Symfony\Component\Form\FormEvents;
use Symfony\Component\Form\FormInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;

/**
 * Property Form Type
 */
class PropertyFormType extends AbstractType
{
    const OPTION_ITEM = "data_item";

    private $statusProvider;

    public function __construct(PropertyStatusProvider $statusProvider)
    {
        $this->statusProvider = $statusProvider;
    }

    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $item = $options[self::OPTION_ITEM];

        $formModifier = function(FormInterface $form, ItemEntity $item) {

            // load choices from service - this service returns an array of available choices by 
            $statusChoices = $this->statusProvider->getAvailableChoices($item->getType());

            $form->add("status", ChoiceType::class, [
                "choices"  => $statusChoices,
                "required" => true,
                /* ... */
            ]);
        };

        $builder->addEventListener(
            FormEvents::PRE_SET_DATA,
            function (FormEvent $event) use ($formModifier) {
                /* @var PropertyItem $property */
                $property = $event->getData();

                $formModifier($event->getForm(), $property->getItem());
            }
        );

        $builder->addEventListener(
            FormEvents::POST_SUBMIT,
            function (FormEvent $event) use ($formModifier) {

                // It's important here to fetch $event->getForm()->getData(), as
                // $event->getData() will get you the client data (that is, the ID)
                /* @var PropertyItem $property */
                $property = $event->getForm()->getData();

                // since we've added the listener to the child, we'll have to pass on
                // the parent to the callback functions
                $formModifier($event->getForm()->getParent(), $property->getItem());
            }
        );
    }

    public function configureOptions(OptionsResolver $resolver)
    {
        $resolver->setDefaults([
            'data_class' => PropertyEntity::class,
        ]);
    }
}


Выборки загружаются динамически в свойства, выбранные через ajax, с использованием библиотеки select2

item-form.js:

    $("select[id^='item_form_properties_'][id$='_status']").select2({
        ajax: {
            type: "GET",
            url: Routing.generate("async-item-properties"),
            dataType: "json",
            data: function(params) {
                var query = {
                    term: params.term,
                };

                var mapping_type = $("select#item_form_type").val();

                if (null !== mapping_type && "" !== mapping_type) {
                    query['type'] = mapping_type;
                }

                return query;
            },
            processResults: function(data) {

                var properties = [];

                $.each(data, function(key, item) {
                    properties.push({
                        id: item.id,
                        text: item.value,
                    });
                });

                return {
                    results: properties
                };
            },
        },
        /* ... */
    });

Объект Entity:

/**
 * @ORM\Entity(/* ... */)
 * @ORM\Tabele(/* ... */)
 */
class ItemEntity
{
    /**
     * @ORM\Id
     * @ORM\Column(name = "item_id", type = "integer")
     */
    private $id;

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

    /**
     * @ORM\OneToMany(targetEntity = "PropertyEntity", mappedBy = "item", cascade={"persist", "remove"})
     */
    private $properties;

    /* ... */

    /**
     * @param PropertyEntity $property
     *
     * @return self
     */
    public function addProperty(PropertyEntity $property)
    {
        $property->setItem($this);

        $this->properties[] = $property;

        return $this;
    }

    /**
     * @param PropertyEntity $property
     */
    public function removeProperty(PropertyEntity $property)
    {
        $this->properties->removeElement($property);
    }
}

Объект Entity:

/**
 * @ORM\Entity(/* ... */)
 * @ORM\Tabele(/* ... */)
 */
class PropertyEntity
{
    /**
     * @ORM\Id
     * @ORM\Column(name = "property_id", type = "integer")
     */
    private $id;

    /**
     * @ORM\ManyToOne(targetEntity = "ItemEntity", inversedBy = "properties")
     * @ORM\JoinColumn(name = "item_id", referencedColumnName = "item_id")
     */
    private $item;

    /**
     * @ORM\Column(type = "string", length = 64)
     */
    private $status;

    /* ... */
}

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

...