Reputation: 382
On old project with symfony 2.8,
i have 3 type of value (computedValue, manualData, probeData) I have a entity name 'dataSource' that contains the 3 source, but only one can be set ( the 2 other is set to null )
I have other entity that contains 3 DataSource, and 3 ArrayCollection of DataSource.
I make my form like this:
...
->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 )
My DataSourceType is:
$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',
],
],
])
;
That work for my ref ( dsRef1, dsRef2, dsRef3 ) ... But when i add the dsList1 i have a error that say :
"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.""
Why my multiple option seem not working ?
Edit add mapping:
$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)
;
Edit crap solution :
After multiple try i found my formBuilder return something like
['computedValue' => [], 'probeData' => [], 'probeData' => []]
instead of
[dataSource ...]
So i Transforme the data like this.
$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;
}
))
;
Upvotes: 0
Views: 1350
Reputation: 8374
okay, as far as i can tell DataSourceType
can handle only one DataSource
(not an array of them).
As I assume from your data transformer, that dsList1
is a many-to-many field, thus an array or ArrayCollection
(or similar), probably coming from doctrine. It's probably an ArrayCollection
of DataSource
s?
Now, the error message given implies exactly that that's the problem, you provide an ArrayCollection
where a DataSource
is expected.
In my opinion you have two options:
If your dsList1 is really only one entity all the time (although the question is, why it's a many-to-many, but let's say legacy reasons), you could access the first datasource in the collection by adapting your data transformer accordingly:
$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;
}
))
;
however, your second function in the callback transformer gets ONE DataSource
and thus must turn a single DataSource
into an ArrayCollection
of DataSources
, which is
$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]);
}
))
;
turn your ("parent") forms dsList1
field into a collection, since it only is supposed to have one entry:
// 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',
],
])
and you should be golden. On the logical form stuff. However, the templates will render this weirdly, probably... but you can update your form rendering, probably, by overriding the form_widget code with some custom block_prefix or something. (I guess you can figure this out from the symfony docs)
also, check out the CollectionType options, where you can (and possibly should) deny adding/removing (I believe the default is, that you can't anyway) and make a constraint, that there's always one (or none?).
as a comment: I would advise to not return an ArrayCollection
from the entity on get{CollectionField}()
, and in the set{CollectionField}
expect an array and create the ArrayCollection
on the spot there (or better: modify the existing one, so unnecessary updates are avoided ...)
and obligatory notice: since symfony 2.8 is not maintained anymore ... you should probably think about upgrading ... but I suppose this is not going to happen. ;o)
now, as far as I can tell, you wanted to turn your DataSourceType
into being able to handle multiple DataSource
s, by providing the multiple
option. To do this, you would have to do some evil logic to somehow manage this, multiplexing an ArrayCollection
of DataSource
s into what the form expects to be a single DataSource
(logic TBD) as a data transformer and do the same in reverse (logic TBD as well, and it's shaky af).
I would advise against it, you remove almost all advantages using the data_class of a DataSource
by breaking into some incestuous Pseudo-DataSource
or maybe even an array that resembles a DataSource
. just to be able to use the same form. it's not worth it really. Keep the object. If you're certain there's always just one data source in your dsList[123], then just use one of the other options. if there might be another data source, option 2 is probably the preferred one. I would prefer it, TBH.
Upvotes: 1