Symfony, коллекция, множественный объект, получил arrayCollection, нужен объект - PullRequest
0 голосов
/ 12 марта 2019

В старом проекте с Symfony 2.8,

у меня есть 3 типа значений (computedValue, manualData, probeData) У меня есть имя объекта «dataSource», которое содержит 3 источника, но можно установить только один (2 другие установлены в null)

У меня есть другая сущность, которая содержит 3 DataSource и 3 ArrayCollection of DataSource.

Я создаю свою форму следующим образом:

        ...
        ->add('dsRef1', DataSourceType::class, [
            'site' => $site,
            'multiple' => false,
            'label_format' => 'form.%name%Ref1',
        ])


        ->add('dsList1', DataSourceType::class, [
            'site' => $site,
            'multiple' => true,
            'label_format' => 'form.%name%List1',
        ])
        ... ( 3 time , dsRef1 - 3 , dlList1 - 3 )

Мой DataSourceType:

        $builder
        // This value is added for force symfony to think the form is submit when the form need to be empty
        ->add('hiddenCrapValue', HiddenType::class, [
            'required' => false,
            'mapped' => false,
            'attr' => [
                'front-attr' => [
                    'render' => 'hidden',
                ],
            ],
        ])
        ->add(DataSourceInterface::DATA_SOURCE, ChoiceType::class, [
            'choices' => $site->getDataSources(),
            'choices_as_values' => true,
            'multiple' => $multiple,
            'mapped' => false,
            'required' => false,
            'label_format' => $labelFormat,
        ])
        ->add(DataSourceInterface::PROBE_DATA, EntityType::class, [
            'class' => ProbeData::class,
            'required' => false,
            'multiple' => $multiple,
            'attr' => [
                'front-attr' => [
                    'render' => 'hidden',
                ],
            ],
        ])
        ->add(DataSourceInterface::MANUAL_DATA, EntityType::class, [
            'class' => ManualData::class,
            'required' => false,
            'multiple' => $multiple,
            'attr' => [
                'front-attr' => [
                    'render' => 'hidden',
                ],
            ],
        ])
        ->add(DataSourceInterface::COMPUTED_VALUE, EntityType::class, [
            'class' => ComputedValue::class,
            'required' => false,
            'multiple' => $multiple,
            'attr' => [
                'front-attr' => [
                    'render' => 'hidden',
                ],
            ],
        ])
    ;

Это работает для моего ref (dsRef1, dsRef2, dsRef3) ... Но когда я добавляю dsList1, у меня появляется ошибка, которая говорит:

"The form's view data is expected to be an instance of class EnergySolution\\ApiBundle\\Entity\\DataSource, but is an instance of class Doctrine\\Common\\Collections\\ArrayCollection. You can avoid this error by setting the \"data_class\" option to null or by adding a view transformer that transforms an instance of class Doctrine\\Common\\Collections\\ArrayCollection to an instance of EnergySolution\\ApiBundle\\Entity\\DataSource.""

Почему мой вариант с несколькими вариантами не работает?

Редактировать добавить сопоставление:

    $table = $builder->getClassMetadata()->getTableName();
    $builder
        ->setTable('chart_energy_goal')
        ->createField('name', Type::STRING)
            ->nullable()
        ->build()
        ->createField('title1', Type::STRING)
            ->nullable()
        ->build()
        ->createManyToOne('dsRef1', DataSource::class)
            ->cascadeAll()
        ->build()
        ->createManyToMany('dsList1', DataSource::class)
            ->setJoinTable("{$table}_dsList1")
            ->addJoinColumn("{$table}_id", 'id')
        ->build()
        ->createField('goal1', Type::FLOAT)
        ->build()
        ->createField('title2', Type::STRING)
            ->nullable()
        ->build()
        ->createManyToOne('dsRef2', DataSource::class)
            ->cascadeAll()
        ->build()
        ->createManyToMany('dsList2', DataSource::class)
            ->setJoinTable("{$table}_dsList2")
            ->addJoinColumn("{$table}_id", 'id')
        ->build()
        ->createField('goal2', Type::FLOAT)
        ->build()
        ->createField('title3', Type::STRING)
            ->nullable()
        ->build()
        ->createManyToOne('dsRef3', DataSource::class)
            ->cascadeAll()
        ->build()
        ->createManyToMany('dsList3', DataSource::class)
            ->setJoinTable("{$table}_dsList3")
            ->addJoinColumn("{$table}_id", 'id')
        ->build()
        ->createField('goal3', Type::FLOAT)
        ->build()
    ;

DataSource :: class

    $builder = new ClassMetadataBuilder($metadata);
    $builder
        ->setTable('data_sources')
        ->setCustomRepositoryClass(EntityRepository::class)
        ->createField('id', Type::INTEGER)
        ->columnName('id')
        ->makePrimaryKey()
        ->generatedValue()
        ->build()
        ->addManyToOne('computedValue', ComputedValue::class)
        ->addManyToOne('probeData', ProbeData::class)
        ->addManyToOne('probeData', ManualData::class)
    ;

Редактировать решение дерьма:

После нескольких попыток я нашел свой возврат из formBuilderчто-то вроде

['computedValue' => [], 'probeData' => [], 'probeData' => []]

вместо

[dataSource ...] 

Так что я преобразую данные следующим образом.

    $builder->get('dsList1')
        ->addModelTransformer(new CallbackTransformer(
            function ($dsListAsArray) {
                // never edit the form
                return $dsListAsArray;
            },
            function ($ArrayOfDataSources) {
                // transform list of 'dataSource' to liste of dataSource obj.
                $collection = new ArrayCollection();
                $propertyAccessor = PropertyAccess::createPropertyAccessorBuilder()
                    ->enableExceptionOnInvalidIndex()
                    ->getPropertyAccessor()
                ;
                foreach ($ArrayOfDataSources as $dataSourceType => $dataSourcesArray) {
                    foreach ($dataSourcesArray as $dataSourceArray) {
                        $dataSource = new DataSource();
                        $propertyAccessor->setValue($dataSource, $dataSourceType, $dataSourceArray);
                        $collection->add($dataSource);
                    }
                }
                return $collection;
            }
        ))
    ;

1 Ответ

1 голос
/ 12 марта 2019

хорошо, насколько я могу судить, DataSourceType может обрабатывать только один DataSource (не массив из них).

Как я предполагаю из вашего преобразователя данных, dsList1 - это поле «многие ко многим», то есть массив или ArrayCollection (или аналог), вероятно, из доктрины. Вероятно, это ArrayCollection из DataSource с?

Теперь, данное сообщение об ошибке означает, что проблема именно в этом, вы указываете ArrayCollection, где ожидается DataSource.

На мой взгляд, у вас есть два варианта:

вариант 1. действительно просто визуализировать первый источник данных

Если ваш dsList1 на самом деле всего лишь одна сущность все время (хотя вопрос в том, почему это многие-ко-многим, но, скажем, унаследованные причины), вы могли бы сначала получить доступ к источник данных в коллекции путем соответствующей адаптации преобразователя данных:

$builder->get('dsList1')
    ->addModelTransformer(new CallbackTransformer(
        function ($dsListAsArray) {
            // ### here you get a list, but want only the first entry? ###
            return reset($dsListAsArray); // returns first element
        },
        function ($ArrayOfDataSources) {
            $collection = new ArrayCollection();
            // stuff you already wrote, BUT, see text below
            return $collection;
        }
    ))
;

однако ваша вторая функция в преобразователе обратного вызова получает ОДИН DataSource и, следовательно, должна превратить один DataSource в ArrayCollection из DataSources, что составляет

$builder->get('dsList1')
    ->addModelTransformer(new CallbackTransformer(
        function ($dsListAsArray) {
            // ### here you get a list, but want only the first entry? ###
            return reset($dsListAsArray); // returns first element
        },
        function ($dataSource) {
            return new ArrayCollection([$dataSource]); 
        }
    ))
;

вариант 2. просто отобразить его как коллекцию

превратит ваше ("родительское") поле форм dsList1 в коллекцию, так как оно должно иметь только одну запись:

    // in the file header part add (if not already there):  
    // use Symfony\Component\Form\Extension\Core\Type\CollectionType;

    ->add('dsList1', CollectionType::class, [
        'entry_type' => DataSourceType::class, 
        'entry_options' => [
            'site' => $site,
            'multiple' => true, // <-- can probably scrap this one?!
            'label_format' => 'form.%name%List1',
        ],
    ])

и ты должен быть золотым. По логической форме мелочи. Тем не менее, шаблоны будут отображать это странно, вероятно ... но вы можете обновить визуализацию формы, возможно, переопределив код form_widget с помощью некоторого пользовательского block_prefix или чего-то еще. (Я думаю, вы можете понять это из документации Symfony)

также, проверьте параметры CollectionType, где вы можете (и, возможно, должны) отрицать добавление / удаление (я полагаю, по умолчанию, что вы не можете в любом случае) и сделать ограничение, что всегда есть один (или нет? ).

в качестве комментария: я бы посоветовал не возвращать ArrayCollection от сущности на get{CollectionField}(), а в set{CollectionField} ожидать массив и создавать там ArrayCollection на месте (или лучше: изменить существующий, поэтому ненужные обновления исключаются ...)

и обязательное уведомление: поскольку Symfony 2.8 больше не поддерживается ... вам, вероятно, следует подумать об обновлении ... но я полагаю, этого не произойдет. ; О)

вариант 3. мультиплексировать как сумасшедший - не делай этого!

Теперь, насколько я могу судить, вы хотели превратить ваш DataSourceType в возможность обработки нескольких DataSource с, предоставив опцию multiple. Чтобы сделать это, вам нужно было бы сделать какую-то злую логику, чтобы как-то управлять этим, мультиплексируя ArrayCollection из DataSource s в то, что форма ожидает, чтобы быть одним DataSource (логическим TBD) в качестве преобразователя данных и делать то же самое в обратном порядке (логика TBD также и шаткая аф).

Я бы посоветовал против этого: вы удалите почти все преимущества, используя data_class DataSource, взломав некоторый кровосмесительный псевдо-DataSource или, возможно, даже массив, который напоминает DataSource. просто чтобы иметь возможность использовать ту же форму. это того не стоит. Держите объект. Если вы уверены, что в вашем dsList [123] всегда есть только один источник данных, просто используйте один из других вариантов. если может быть другой источник данных, вариант 2, вероятно, является предпочтительным. Я бы предпочел это, ТБХ.

...