Vichuploaderbundle: replacing file causes "entity is not managed" error

Created on 1 Feb 2017  路  11Comments  路  Source: dustin10/VichUploaderBundle

I can successfully upload a file but if I try to replace the file I get this error:

Entity HazardBundle\Entity\SDS@000000006c3e5ddb000000004c9a326a is not managed. An entity is managed if its fetched from the database or registered as new through EntityManager#persist

Can anyone explain what is happening and how to fix it?

My controller action looks like this:

    /**
     * @Route("/", name="ci_test")
     * @Template
     */
    public function indexAction(Request $request)
    {
        $supply = $this->getDoctrine()->getRepository(Supply::class)->find(2838);
        $sds = ($supply->getSds()) ? $supply->getSds() : new SDS($supply);

        $form = $this->createForm(SDSType::class, $sds)->add('save', FT\SubmitType::class, array('label' => 'submit'));
        $form->handleRequest($request);

        if ($form->isSubmitted() && $form->isValid()) {
            $em = $this->getDoctrine()->getManager();
            $em->persist($sds);
            $em->flush();
        }

            return array('output' => ['form' => $form->createView()]);
    }

My form type looks like this:

class SDSType extends AbstractType
{
    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $builder
          ->add('sdsFile', VichFileType::class, array('allow_extra_fields' => true, 'required' => false)) //extra fields so 'delete' will be allowed
          ->add('issueDate', FT\DateType::class)
          ->add('version', FT\TextType::class)
          ->add('notes', FT\TextareaType::class, array('required' => false));
    }

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

    /**
     * @return string
     */
    public function getBlockPrefix()
    {
        return 'ciHaz_sds';
    }

    public function getName()
    {
        return $this->getBlockPrefix();
    }
}

The Uploadable entity, SDS has a One-to-One relation to another entity, Supply. Supply is the owning side:

/**
 * SDS
 *
 * @ORM\Table(name="cir_sds")
 * @ORM\Entity(repositoryClass="HazardBundle\Repository\SDSRepository")
 * @ORM\InheritanceType("SINGLE_TABLE")
 * @ORM\DiscriminatorColumn(name="discr", type="string")
 * @ORM\DiscriminatorMap({"sds" = "SDS", "msds" = "MSDS", "notice" = "Notice"})
 * @Vich\Uploadable
 */
class SDS
{
    /**
     * @var int
     *
     * @ORM\Column(name="id", type="integer")
     * @ORM\Id
     * @ORM\GeneratedValue(strategy="AUTO")
     */
    private $id;

    /**
     * @ORM\OneToOne(targetEntity="ChemicalBundle\Entity\Supply", mappedBy="sds", cascade={"persist"})
     * @Assert\NotNull()
     * @var Supply
     */
    private $supply;

    /**
     * @var string
     *
     * @ORM\Column(name="fileName", type="string", length=255, nullable=false, unique=true)
     */
    private $fileName;

    /**
     * NOTE: This is not a mapped field of entity metadata, just a simple property.
     * @Vich\UploadableField(mapping="sds", fileNameProperty="fileName")
     * @var File
     */
    private $sdsFile;

//etc.
/**
 * Supply
 *
 * @ORM\Table(name="cir_supply")
 * @ORM\Entity(repositoryClass="ChemicalBundle\Repository\SupplyRepository")
 *
 */
class Supply implements HasPictogramsInterface, DescribeableInterface
{
    use BlameableEntity;
    use TimestampableEntity;
    use PictogramsTrait;

  /**
   * @var int
   * @ORM\Column(name="id", type="integer")
   * @ORM\Id
   * @ORM\GeneratedValue(strategy="AUTO")
   */
  protected $id;

    /**
     * @ORM\OneToOne(targetEntity="HazardBundle\Entity\SDS", cascade={"persist","remove"}, orphanRemoval=true, inversedBy="supply")
     * @ORM\JoinColumn(name="sds_id", referencedColumnName="id", nullable=true, unique=true, onDelete="set null")
     * Supply is the OWNING side
     */
    protected $sds;

//etc.
Support

Most helpful comment

@dnagirl , a more simple solution :
Update your "FileName" property in your entity : "nullable=true"
And add an EntityListener on "PostUpdate" event to delete your entity if FileName is null. (In this way you can easily check if it's an update or remove for VichUploader)
Here an example with my "recipeImage" Entity Listener

recipeImageEventListener:
    class: AppBundle\EventListener\Entity\RecipeImageEventListener
    tags:
        -  { name: doctrine.orm.entity_listener, entity: AppBundle\Entity\RecipeImage, event: postUpdate }
<?php

namespace AppBundle\EventListener\Entity;

use AppBundle\Entity\RecipeImage;
use Doctrine\ORM\Event\LifecycleEventArgs;

class RecipeImageEventListener
{
    /**
     * @param RecipeImage $recipeImage
     * @param LifecycleEventArgs $event
     */
    public function postUpdate(RecipeImage $recipeImage, LifecycleEventArgs $event) {
        $entity = $event->getEntity();

        if(is_null($entity->getImageName())){
            $manager = $event->getEntityManager();
            $manager->remove($entity);
            $manager->flush();
        }
    }

}

All 11 comments

I don't know.
Try to remove your file field from form and see if it's working. If so, it doesn't depend on VichUploaderBundle

I figured it out. I'd forgotten that I'd added a listener for the onPostRemoveevent as described here. That results in a detached entity when replacing a file.

Is there a way I can conditionally turn off or skip my listener when the file is being replaced rather than deleted?

The event should be triggered only on file remove, not on replace

I read through the VichUploader code. When onPreUpdate is dispatched, the CleanListener calls UploadHandler ::clean() , which calls UploadHandler::remove() which dispatches the onPostRemove event.

That is meant to delete an old file when a new one is uploaded. It's not about deleting an entity

I know. But my listener does delete the entity. And my listener listens to onPostRemove. So my listener gets called during a replace-file operation.

Is there any way of knowing, from inside an onPostRemove listener, whether the original operation is a file-replace or a file-delete?

Try to do manually the delete of your entity inside your listener

I ended up having to check the form's 'delete' field value in the Controller and then, manually remove the file entity. It's not optimal, but nothing else I've tried works.

    public function editAction(Request $request, Supply $supply)
    {

        $form = $this->createForm(SupplyType::class, $supply)->add('save', FT\SubmitType::class, array('label' => 'btn.update'));
        $form->handleRequest($request);

        if ($form->isSubmitted() && $form->isValid()) {
            $em = $this->getDoctrine()->getManager();
            if($safetyfile = $supply->getSafetyFile()){
                $delete = $form->get('safetyfile')->has('delete') && $form->get('safetyfile')->get('delete')->getData();
                if($delete) {
                    $supply->setSafetyFile(null);
                }
            }
            $em->persist($supply);
            $em->flush();

            $this->addFlash('success', 'msg.changes.saved');
        }

            return array(
                'form' => $form->createView(),
                'supply' => $supply
            );
    }

Hopefully this will be helpful to someone else.

@dnagirl , a more simple solution :
Update your "FileName" property in your entity : "nullable=true"
And add an EntityListener on "PostUpdate" event to delete your entity if FileName is null. (In this way you can easily check if it's an update or remove for VichUploader)
Here an example with my "recipeImage" Entity Listener

recipeImageEventListener:
    class: AppBundle\EventListener\Entity\RecipeImageEventListener
    tags:
        -  { name: doctrine.orm.entity_listener, entity: AppBundle\Entity\RecipeImage, event: postUpdate }
<?php

namespace AppBundle\EventListener\Entity;

use AppBundle\Entity\RecipeImage;
use Doctrine\ORM\Event\LifecycleEventArgs;

class RecipeImageEventListener
{
    /**
     * @param RecipeImage $recipeImage
     * @param LifecycleEventArgs $event
     */
    public function postUpdate(RecipeImage $recipeImage, LifecycleEventArgs $event) {
        $entity = $event->getEntity();

        if(is_null($entity->getImageName())){
            $manager = $event->getEntityManager();
            $manager->remove($entity);
            $manager->flush();
        }
    }

}

Can I somehow reopen this issue? I know, I'm a little bit late, but I get the exact same error message as @dnagirl got, only, I don't have an EventListener that could cause the problem. So I don't really know where to look for a solution?
So, I have an image and I'd like to upload a new image as a replacement of the old one, but when trying so, I get the error message.

this is my Controller function:

/**
  * @Route("/uploadImage/{id}", name="appBundle_upload_image", requirements={"id" = "\d+"})
  * @Security("is_granted('ROLE_ATTACHEMENT_ADMIN')")
  */
  public function uploadAction(Request $request, $id){
    $repository = $this->getDoctrine()
      ->getRepository('AppBundle:Image');
    $em = $this->getDoctrine ()->getManager ();
    $image = $repository->findOneById($id);
    $form = $this->createForm(UploadImageType::class, $image);
    $form->handleRequest($request);
    if ($form->isSubmitted() && $form->isValid()) {
      $em->persist($image);
      $em->flush();
    return $this->render('AppBundle:Image:upload.html.twig', array(
      'form' => $form->createView(),
    ));
    }
    return $this->render('AppBundle:Image:upload.html.twig', array(
      'form' => $form->createView(),
      'image' => $image,
    ));
  }

my Entity file is 1:1 the same as described in the VichUploaderBundle documentation.

anybody that can help?

Was this page helpful?
0 / 5 - 0 ratings

Related issues

benIT picture benIT  路  3Comments

NicolaPez picture NicolaPez  路  6Comments

andrea-daru picture andrea-daru  路  3Comments

Propscode picture Propscode  路  4Comments

seddighi78 picture seddighi78  路  4Comments