symfony-collection: Deleting an item from the top does not remove the corresponding Entity but the bottom one

I have a list of objects that refer to other objects

[ CourseParticipant: id1 ]->[Student A, id11]
[ CourseParticipant: id2 ]->[Student B, id22]
[ CourseParticipant: id3 ]->[Student C, id33]

after attempting to delete [ CourseParticipant: id1 ] instead of this:

[ CourseParticipant: id2 ]->[Student B, id200]
[ CourseParticipant: id3 ]->[Student C, id300]

I get :

[ CourseParticipant: id1 ]->[Student B, id200]
[ CourseParticipant: id2 ]->[Student C, id300]

The thing is that i have other entity ParticipantResults that associate to CourseParticipant and should cascade delete on deletion of CourseParticipant.

[ParticipantResults: id111 ]->[ CourseParticipant: id1 ]
[ParticipantResults: id222 ]->[ CourseParticipant: id2 ]
[ParticipantResults: id333 ]->[ CourseParticipant: id3 ]

And what happens after (attempted) deletion of [ CourseParticipant: id1 ]:

[ParticipantResults: id111 ]->[ CourseParticipant: id1 ]
[ParticipantResults: id222 ]->[ CourseParticipant: id2 ]

Now if i want to know results of Student B i get those of removed Student A. And the results of Student C are gone

About this issue

  • Original URL
  • State: open
  • Created 7 years ago
  • Comments: 17 (9 by maintainers)

Commits related to this issue

Most upvoted comments

@DaviUchoa I have tried the ‘positionField’ approach and it did not help me.

I decided to post a distillation of my code: Students can follow learning tracks:

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

   /**
     * One Track has Many UserTracks.
     * @ORM\OneToMany(targetEntity="AppUserTrack", mappedBy="track", cascade={"persist"})
     */
    private $trackUsers;
..

Many AppUsers can be associated with a Track via OnToMany trackUsers property that is a collection of AppUserTrack entities:

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

    /**
     * Many UserTracks have One AppUser.
     * @ORM\ManyToOne(targetEntity="AppUser", inversedBy="userTracks")
     * @ORM\JoinColumn(onDelete="CASCADE", nullable=false)
     */
    private $user;

    /**
     * Many UserTracks have One Track.
     * @ORM\ManyToOne(targetEntity="Track", inversedBy="trackUsers", fetch="EAGER")
     */
    private $track;

    /** @ORM\Column(type="string") */
    private $role;
}

In order to manage track users there is a controller:

    public function manageUsersAction(Track $track, Request $request)
    {
        $form = $this->createForm(TrackManageUsersType::class, $track);
        $form->handleRequest($request);

        if ($form->isSubmitted()) {
            if ($form->isValid()) {
                $this->getDoctrine()->getManager()->flush();
            } 

            return new JsonResponse([]);
        }

        return $this->render('track-manage-users.html.twig', array(
            'track' => $track,
            'form' => $form->createView(),
        ));
    }

where TrackManageUsersType looks like this

class TrackManageUsersType extends AbstractType
{
    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $track = $builder->getData();
        $builder->add('trackUsers', CollectionType::class, [
                'entry_type' => TrackUserType::class,
                'by_reference' => true,
                'delete_empty' => true,
                'required' => false,
                'allow_add' => true,
                'allow_delete' => true,
                'prototype' => true,
                'prototype_name' => '__track_user__',
            ]);
    }

    public function configureOptions(OptionsResolver $resolver)
    {
        $resolver->setDefaults(array(
            'data_class' => Track::class
        ));
    }

    public function getBlockPrefix()
    {
        return 'track_type';
    }
}

I defined a TrackUserType as this:

class TrackUserType extends AbstractType
{
    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $builder
// this was my attempt of using position to preserve the entities from swapping properties
//            ->add('position', HiddenType::class, [
//                'attr' => [
//                    'class' => 'my-position', // selector is the one used on the js side
//                ]
//            ])
            ->add('User', EntityType::class, [
                'choice_label' => 'name',
                'placeholder' => 'Choose User',
                'class' => AppUser::class,
                'query_builder' => function (EntityRepository $er) use ($track) {
                    ...
                }
            ])
            ->add('role', ChoiceType::class, [
                'label_attr' => ['class' => 'hidden'],
                'placeholder' => 'Choose Role',
                'choices' => [
                    'Student' => AppUserTrack::ROLE_STUDENT,
                    'Manager' => AppUserTrack::ROLE_MANAGER
                ],
                'expanded' => false,
                'multiple' => false,
            ]);
    }
   ...
}

All of this is initialized in js:

...
$trackUsers.collection({
//            attempted to use position to preserve the entities from swapping properties
//            position_field_selector: '.my-position',
//            allow_duplicate: true,
            add_at_the_end: true,
            hide_useless_buttons: true,
            allow_up: false,
            allow_down: false,
            add: '<button type="button" class="btn btn-sm btn-default"><span class="fa fa-fw fa-plus"></span></button>',
            remove: '<button type="button" class="btn btn-sm btn-default"><span class="fa fa-fw fa-trash"></span></button>'
        });

I hope you can do something with this!!!