Hello I'm trying to model an Entity that uses several different collections. I tried out the https://github.com/ninsuo/symfony-collection project and it offers a wide range of useful options. The closest example I've seen is the one with Collection of Collections where one Entity has several collections of the SAME child EntityType. I'm trying to achieve the same behaviour with several collections of DIFFERENT child EntityTypes.
The problem I face with my Entity is that when I put only one collection in it, the code works ok, but when I add a second collection of a different child Entity and send my form, my controller code ends up deleting the other collection's elements. I narrowed it down to the view, hence why I'm asking about that specific project.
I'm currently using Symfony 3.x, and have been able to follow the listed examples up to the point of working well with one collection only, I'm able to ADD, DELETE and UPDATE.
My controller code:
namespace SigavFileBundle\Form;
use Ivory\CKEditorBundle\Form\Type\CKEditorType;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use SigavFileBundle\Entity\BookingAccommodation;
use SigavFileBundle\Entity\BookingIncludes;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\DateType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\Form\Extension\Core\Type\CollectionType;
use Symfony\Component\Form\Extension\Core\Type\MoneyType;
use Symfony\Component\Form\Extension\Core\Type\IntegerType;
use Doctrine\ORM\EntityRepository;
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
class BookingType extends AbstractType
{
/**
* {@inheritdoc}
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('flights', CollectionType::class,
array(
'entry_type' => FlightDataType::class,
'allow_add' => true,
'allow_delete' => true,
'prototype' => true,
'required' => false,
'by_reference' => true,
'delete_empty' => true,
'prototype_name' => '__flights-collection__',
'attr' => [
'class' => "flights-collection",
],
)
)
->add('accommodations', CollectionType::class,
array(
'entry_type' => BookingAccommodationType::class,
'allow_add' => true,
'allow_delete' => true,
'prototype' => true,
'required' => false,
'by_reference' => true,
'delete_empty' => true,
'prototype_name' => '__accomm-collection__',
'attr' => [
'class' => "accomm-collection",
],
)
)
->add('cars', CollectionType::class,
array(
'entry_type' => BookingCarType::class,
'allow_add' => true,
'allow_delete' => true,
'prototype' => true,
'required' => false,
'by_reference' => true,
'delete_empty' => true,
'prototype_name' => '__cars-collection__',
'attr' => [
'class' => "cars-collection",
],
)
)
->add('transfers', CollectionType::class,
array(
'entry_type' => BookingTransferType::class,
'allow_add' => true,
'allow_delete' => true,
'prototype' => true,
'required' => false,
'by_reference' => true,
'delete_empty' => true,
'prototype_name' => '__transfers-collection__',
'attr' => [
'class' => "transfers-collection",
],
)
)
->add('excursions', CollectionType::class,
array(
'entry_type' => BookingExcursionType::class,
'allow_add' => true,
'allow_delete' => true,
'prototype' => true,
'required' => false,
'by_reference' => true,
'delete_empty' => true,
'prototype_name' => '__exc-collection__',
'attr' => [
'class' => "exc-collection",
],
)
)
->add('includes', CollectionType::class,
array(
'entry_type' => BookingIncludesType::class,
'allow_add' => true,
'allow_delete' => true,
'prototype' => true,
'required' => false,
'by_reference' => true,
'delete_empty' => true,
'prototype_name' => '__inc-collection__',
'attr' => [
'class' => "inc-collection",
],
)
)
->add('customActivities', CollectionType::class,
array(
'entry_type' => BookingCustomActivityType::class,
'allow_add' => true,
'allow_delete' => true,
'prototype' => true,
'required' => false,
'by_reference' => true,
'delete_empty' => true,
'prototype_name' => '__act-collection__',
'attr' => [
'class' => "act-collection",
],
)
)
->add('guides', CollectionType::class,
array(
'entry_type' => BookingGuideType::class,
'allow_add' => true,
'allow_delete' => true,
'prototype' => true,
'required' => false,
'by_reference' => true,
'delete_empty' => true,
'prototype_name' => '__guides-collection__',
'attr' => [
'class' => "guides-collection",
],
)
)
->add('commentaries', CollectionType::class,
array(
'entry_type' => BookingCommentariesType::class,
'allow_add' => true,
'allow_delete' => true,
'prototype' => true,
'required' => false,
'by_reference' => true,
'delete_empty' => true,
'prototype_name' => '__comm-collection__',
'attr' => [
'class' => "comm-collection",
],
)
)
;
}
/**
* {@inheritdoc}
*/
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'SigavFileBundle\Entity\Booking'
));
}
/**
* {@inheritdoc}
*/
public function getBlockPrefix()
{
return 'sigavfilebundle_booking';
}
}
As you can see, multiple collections of different types. Up next, this is the code for two of those FormTypes only, BookingAccommodationType and BookingCarType:
BookingAccommodationType:
namespace SigavFileBundle\Form;
use Symfony\Component\Form\Extension\Core\Type\IntegerType;
use SigavGeneralBundle\Controller\MealPlanController;
use SigavGeneralBundle\Entity\Hotel;
use SigavGeneralBundle\Entity\RoomType;
use SigavGeneralBundle\SigavGeneralBundle;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\Form\Extension\Core\Type\DateType;
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
use Doctrine\ORM\EntityRepository;
class BookingAccommodationType extends AbstractType
{
/**
* {@inheritdoc}
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
...
// Attributes go here
...
}
/**
* {@inheritdoc}
*/
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'SigavFileBundle\Entity\BookingAccommodation'
));
}
/**
* {@inheritdoc}
*/
public function getBlockPrefix()
{
return 'sigavfilebundle_bookingaccommodation';
}
}
BookingCarType:
namespace SigavFileBundle\Form;
use Doctrine\ORM\EntityRepository;
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\Form\Extension\Core\Type\DateType;
use Symfony\Component\Form\Extension\Core\Type\TimeType;
class BookingCarType extends AbstractType
{
/**
* {@inheritdoc}
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
...
// Attributes go here
...
}
/**
* {@inheritdoc}
*/
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'SigavFileBundle\Entity\BookingCar'
));
}
/**
* {@inheritdoc}
*/
public function getBlockPrefix()
{
return 'sigavfilebundle_bookingcar';
}
}
All view related code follows.
main_view.html.twig:
<!-- A lot of HTML code before //-->
{%
form_theme form.accommodations
'jquery.collection.html.twig'
'booking/bookingAccommodations.html.twig'
%}
{{ form( form.accommodations) }}
<!-- A lot of HTML code between //-->
{%
form_theme form.cars
'jquery.collection.html.twig'
'booking/bookingCars.html.twig'
%}
{{ form( form.cars) }}
<!-- A lot of HTML code after //-->
<script>
function initCollectionHolders( $id, $form_id )
{
$($id).collection({
name_prefix: $form_id,
add_at_the_end: true,
allow_add: 1,
allow_duplicate: 1,
allow_remove: 1,
duplicate: '<a href="#"><span class="pe-7s-copy"></span></a>',
add: '<a href="#"><span class="pe-7s-plus"></span></a>',
remove: '<a href="#"><span class="pe-7s-close-circle"></span></a>'
});
}
initCollectionHolders('.accomm-collection', '{{ form.accommodations.vars.full_name }}');
initCollectionHolders('.cars-collection', '{{ form.cars.vars.full_name }}');
<script>
bookingCars.html.twig:
{% block sigavfilebundle_bookingcar_label %}{% endblock %}
{% block sigavfilebundle_bookingcar_widget %}
{# HERE GOES THE ENTIRE FORM LAYOUT #}
{% endblock %}
bookingAccommodations.html.twig:
{% block sigavfilebundle_bookingaccommodation_label %}{% endblock %}
{% block sigavfilebundle_bookingaccommodation_widget %}
{# HERE GOES THE ENTIRE FORM LAYOUT #}
{% endblock %}
jquery.collection.html.twig:
{% block collection_widget %}
{% spaceless %}
{% if prototype is defined %}
{% set attr = attr|merge({'data-prototype': form_row(prototype)}) %}
{% set attr = attr|merge({'data-prototype-name': prototype.vars.name}) %}
{% endif %}
{% set attr = attr|merge({'data-allow-add': allow_add ? 1 : 0}) %}
{% set attr = attr|merge({'data-allow-remove': allow_delete ? 1 : 0 }) %}
{% set attr = attr|merge({'data-name-prefix': full_name}) %}
{{ block('form_widget') }}
{% endspaceless %}
{% endblock collection_widget %}
I wanted to know first if the symfony-collection library can be used in an environment with one Entity and multiple different child type collections
Thanks in advance ...