Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why do I need to flush() after remove() and before persist() in doctrine

I have the following code:

//Delete old existing file(s)
$files = $record->getFiles();

foreach ($files as $file) {
   $em->remove($file);
}

$em->flush();

$link = $record->getLink() ? $record->getLink() : new Link();
$link->setRecord($record);
$link->setUrl($metaData['location']);

$em->persist($link);

$em->flush();

I need to call the first flush() or else the $file entities are not deleted..why won't they be deleted by just using the second flush()?

For reference, here is the relationship definition of Record:

/**
 * @var \AppBundle\Entity\Link
 *
 * @ORM\OneToOne(targetEntity="AppBundle\Entity\Link", cascade={"persist"}, mappedBy="record")
 */
private $link;

/**
 * @var \AppBundle\Entity\File
 *
 * @ORM\OneToMany(targetEntity="AppBundle\Entity\File", cascade={"persist"}, mappedBy="record")
 */
private $files;

Also, this code using a single flush() works fine (it's deleting a OneOnOne Entity instead of a OneToMany):

 //Delete old existing link
 $link = $record->getLink();
 if ($link) {
      $em->remove($link);
 }

 $file = $record->getFile() ? $record->getFile() : new File();
 $file->setRecord($record);

 $em->persist($file);

 $em->flush();
like image 366
Oli Avatar asked Oct 15 '25 20:10

Oli


2 Answers

I have found two ways of making my code work, which take two different approaches:

First scenario: do not set any cascading logic in the Entity and process all operations manually.

Record Entity

/**
 * @var \AppBundle\Entity\Link
 *
 * @ORM\OneToOne(targetEntity="AppBundle\Entity\Link", mappedBy="record")
 */
private $link;

/**
 * @var \AppBundle\Entity\File
 *
 * ORM\OneToMany(targetEntity="AppBundle\Entity\File", mappedBy="record")
 */
private $files;

Controller

//Delete old existing file(s)
$files = $record->getFiles();

foreach ($files as $file) {
    $fileService->deleteFile($file);
    //Remove the *owning* entity of the relationship
    $em->remove($file);
}

$em->flush();
$em->clear();

//We need to call clear() to remove all existing references of files 
//from the $record entity. Get the record again after this.
$record = $this->getRecordRepository()->findActive($id);

$link = $record->getLink() ? $record->getLink() : new Link();
$link->setRecord($record);
$link->setUrl($metaData['location']);

$em->persist($link);
$record->setType(Record::TYPE_LINK);

$em->flush();

Second scenario: approach all DB operations from the $record perspective, and let the cascade and orphanRemoval do the rest

Record Entity

/**
 * @var \AppBundle\Entity\Link
 *
 * @ORM\OneToOne(targetEntity="AppBundle\Entity\Link", cascade={"persist", "remove"}, mappedBy="record", orphanRemoval=true)
 */
private $link;

/**
 * @var \AppBundle\Entity\File
 *
 * @ORM\OneToMany(targetEntity="AppBundle\Entity\File", cascade={"persist", "remove"}, mappedBy="record", orphanRemoval=true)
 */
private $files;

Controller

//Delete old existing file(s)
$files = $record->getFiles();

foreach ($files as $file) {
    $fileService->deleteFile($file);
    $record->removeFile($file);
}

$link = $record->getLink() ? $record->getLink() : new Link();
$link->setRecord($record);
$link->setUrl($metaData['location']);

$em->persist($link);
$record->setType(Record::TYPE_LINK);

$em->flush();

I personally favor the second approach, which needs less PHP code is more readable.

I'll leave this answer open for any comments/tips and close it this week.

like image 50
Oli Avatar answered Oct 17 '25 09:10

Oli


The cascade parameter has a options: 'remove', 'persist', 'refresh', 'merge', and 'detach'.

Treat them as flags so you can write:

cascade={"persist","remove"}

or even:

cascade={"all"}

Maybe this helps ...

like image 28
pauluZ Avatar answered Oct 17 '25 10:10

pauluZ



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!