formssymfonypersistsymfony6

Symfony 6 form/entity editing not persisting


I'm trying to create a controller method to edit an existing entity UserGroup, but to modify only a ManyToMany relation field to entity User, this is the only field in the FormType.

The problem is, nothing is getting saved, although POST data is being sent and inspecting the entity after handleRequest() shows that relation field has been correctly captured.

Note: Using Symfony generated CRUD controller doesn't save this relation field either, it does save other fields but not the relation field so maybe there's something wrong in relation definition.

This is UserGroup entity:

#[ORM\Entity(repositoryClass: UserGroupRepository::class)]
class UserGroup extends ItemCollection implements IMessageInternalRecipient
{
    const USER_GROUP_MARKETING = 'marketing';

    #[ORM\ManyToMany(targetEntity: User::class, mappedBy: 'groups', cascade: ['persist', 'remove'])]
    private Collection $users;

    public function __construct()
    {
        $this->users = new ArrayCollection();
    } 

    /*
    insert into user_group(view_order, shortname, name) values(1, 'marketing', 'Equipo de marketing');
    */

    /**
     * @return Collection<int, User>
     */
    public function getUsers(): Collection
    {
        return $this->users;
    }

    public function addUser(User $user): static
    {
        if (!$this->users->contains($user)) {
            $this->users->add($user);
            $user->addGroup($this);
        }

        return $this;
    }

    public function removeUser(User $user): static
    {
        if ($this->users->removeElement($user)) {
            $user->removeGroup($this);
        }

        return $this;
    }
}

This is ItemCollection (which previous entity extends from):

#[ORM\MappedSuperclass]
class ItemCollection
{
    #[ORM\Id]
    #[ORM\GeneratedValue]
    #[ORM\Column(type: 'integer')]
    protected $id;

    #[ORM\Column(type: 'string', length: 100)]
    protected $shortname;

    //... and other fields
}

This is relation in User entity:

#[ORM\ManyToMany(targetEntity: UserGroup::class, inversedBy: 'users')]
private Collection $groups;

UserGroupType:

class UserGroupType extends AbstractType
{
    public function buildForm(FormBuilderInterface $builder, array $options): void
    {
        $builder
            ->add('users', EntityType::class, [
                                                'label'        => 'Marketing users',
                                                'class'        => User::class,
                                                'choice_label' => 'email',
                                                'multiple'     => true,
                                                'expanded'     => true
                                              ])
        ;
    }

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

And this is controller method. Notes: I don't pass a $userGroup instance but a shortname, and perform a find() on that shortname:

#[Route('/group_users/{shortname}', name: 'app_user_group_users', methods: ['GET', 'POST'])]
public function groupUsers(Request $request,
                           EntityManagerInterface $entityManager,
                           UserGroupRepository $userGroupRepository,
                           LoggerInterface $logger,
                           string $shortname): Response
{
    $userGroup = $userGroupRepository->findOneBy(['shortname' => $shortname]);

    if ($userGroup == null)
        return $this->redirectToRoute('app_user_index', [], Response::HTTP_SEE_OTHER);
        
    $form = $this->createForm(UserGroupType::class, $userGroup, 
                             ['action' => $this->generateUrl('app_user_group_users', ['shortname' => $shortname])]);
    $form->handleRequest($request);

    if ($form->isSubmitted() && $form->isValid())
    {
        // $entityManager->persist($userGroup); //This was tried but didn't work either 
        $entityManager->flush();

        return $this->redirectToRoute('app_user_index', [], Response::HTTP_SEE_OTHER);
    }

    return $this->render('user/group_users.html.twig', [
                            'user_group' => $userGroup,
                            'form'       => $form,
                        ]);
}

Solution

  • You should add the parameter 'by_reference' => false in the UserGroupType for the users field

    public function buildForm(FormBuilderInterface $builder, array $options): void
    {
        $builder
            ->add('users', EntityType::class, [
                'label'         => 'Marketing users',
                'class'         => User::class,
                'choice_label'  => 'email',
                'multiple'      => true,
                'expanded'      => true,
                'by_reference'  => false,
            ])
        ;
    }