SonataAdminBundle: Unable to delete from sonata_type_collection (worked in 2.0 but not after upgrade)

I know that this used to be a common problem and the answer was always to put 'by_reference' => false. Here’s my code:

protected function configureFormFields(FormMapper $form)
{
    $form
        ->add('title')
        ->add('collectionHasStories', 'sonata_type_collection', array('by_reference' => false, 'label' => 'Stories'), array(
            'edit' => 'inline',
            'inline' => 'table',
            'sortable'  => 'position'
        ))
    ;
}

I don’t think that this is something I’m doing wrong as I’ve noticed that since upgrading I’m not even able to delete media items from a gallery’s galleryHasMedia collection. This is a very big problem for me!

Has anything changed since that would cause this to break? Or is it something to do with the particular versions I’m using? Here is everything related to sonata in my composer.json:

    "sonata-project/user-bundle": "*",
    "sonata-project/cache-bundle": "dev-master",
    "sonata-project/doctrine-orm-admin-bundle": "*",
    "sonata-project/media-bundle": "*",

(I found I didn’t need to add the admin-bundle as the user-bundle depends on the dev-master version of it)

About this issue

  • Original URL
  • State: closed
  • Created 12 years ago
  • Reactions: 4
  • Comments: 19 (7 by maintainers)

Commits related to this issue

Most upvoted comments

Hello, I got the same problem on symfony 2.1 + sonata + Admin with many-to-many field; I set the doctrine’s orphanRemoval property on my one-to-many field and then makes deletion working on type Collection, something likes :

/**
 * @var \Doctrine\Common\Collections\ArrayCollection
 * 
 * @ORM\OneToMany(targetEntity="CollectionHasStories", mappedBy="collection", cascade={"all"}, orphanRemoval=true)
 */
protected $collectionHasStories;

My Entity (simplified)

class News {
    /**
     * @ORM\OneToMany(targetEntity="NewsAttachment", mappedBy="owner", cascade={"all"}, orphanRemoval=true)
     * @ORM\OrderBy({"position" = "ASC"})
     *
     * @var NewsAttachment[]
     */
    private $attachments;

    public function setAttachments(iterable $attachments): void {
        $this->attachments = $attachments;
    }

    public function addAttachment(NewsAttachment $attachment): void {
        $attachment->setOwner($this);
        $this->attachments->add($attachment);
    }
}

Note that orphanRemoval=true is added to the OneToMany annotation. Also I left by_reference to be false in the form mapper.

The problem

Similar to above, I am seeing a problem that all items got deleted upon save.

The cause

This is due to the code in setAttachments, which replaces the existing PersistentCollection of the entity by a new array/ArrayCollection. This is WRONG. (See https://github.com/doctrine/doctrine2/issues/6344, quote: “Replacing collection instances is not really fully supported”)

The solution

So I modified the setAttachments method like this:

    public function setAttachments(iterable $attachments): void
    {
        $itemsToAdd = [];
        $existingIds = [];

        /** @var NewsAttachment $attachment */
        foreach ($attachments as $attachment) {
            // New item's ID is null
            if (null === ($id = $attachment->getId())) {
                $itemsToAdd[] = $attachment;
            } else {
                $existingIds[$id] = true;
            }
        }

        /** @var AnnouncementFile $attachment */
        foreach ($this->attachments as $idx => $attachment) {
            if (!$attachment->getId()) {
                continue;
            }

            // Remove item from the collection if it isn't in the supplied
            // $attachments.
            if (!isset($existingIds[$attachment->getId()])) {
                $attachment->setOwner(null);
                unset($this->attachments[$idx]);
            }
        }

        // Add new items
        foreach ($itemsToAdd as $attachment) {
            $this->attachments[] = $attachment;
        }
    }

The local $attachments contains the new and existing items but not the deleted ones (they are removed by the ResizeFormListener). So the setAttachments method compares the entity’s $attachments against the local one, adds new items to it and removes deleted items from it.

Now delete function should work correctly.

Alternative method

If I add the removeAttachment method to the News class it will get called by the PropertyAccessor before setAttachments():

    public function removeAttachment(NewsAttachment $attachment)
    {
        $this->attachment->removeElement($attachment);
    }

In this case, it’s no longer neccessary to track the removed items in setAttachments(). Also, it turns out to be fine adding the same object to the PersistentCollection. This means that it’s also not neccessary to track whether an item has already been added to the collection, we can just add everything to it and let Doctrine handles the rest. The setAttachments() method can then be simplified to:

    public function setAttachments(iterable $attachments): void
    {
        foreach ($attachments as $attachment) {
            $this->attachments[] = $attachment;
        }
    }

This works, but this would mean that the attachments array could be in an invalid state (Entity’s $attachments field may contain duplicated item). Also setAttachments() now assumes that items not found in the $attachments argument be removed via removeFile in advance.

Also fixed this issue by adding orphanRemoval=true to my OneToMany associations in my OneToMany <-> ManyToMany <-> OneToMany relationship.

It is an issue with reference and the Form component. Please review: https://github.com/sonata-project/SonataMediaBundle/blob/master/Admin/GalleryAdmin.php#L125