guyaloni
guyaloni

Reputation: 5872

Symfony 2 - flush in postUpdate fire preUpdate event

I detected this problem "thanks" to an exception I got:

Catchable Fatal Error: Argument 3 passed to
Doctrine\ORM\Event\PreUpdateEventArgs::__construct() 
must be an array, null given, called in 
/.../vendor/doctrine/lib/Doctrine/ORM/UnitOfWork.php on line 804 
and defined in
/.../vendor/doctrine/lib/Doctrine/ORM/Event/PreUpdateEventArgs.php line 28

I am working on a project that requieres a specific logic:
When the order field in entity book is modified, I need to update field books_order_modified_at in the parent entity bookstore (this field allows me to know whether the order of books in a bookstore was changed).

I decided to do it in an event listener since there are many places in the code that might change the order of books.

I didn't find any way to update a related entity from preUpdate event, so I have a private field in the listener class which I use to tell the postUpdate event to update the relevant bookstore entity.

My problem is that when I do so the preUpdate event of the book entity is fired.
When I check the change-set it contains only the modified_at field, but it has the same value before and after.

If someone has another idea how to solve the problem - great.

If not - any idea how can I prevent the preUpdate event from being fired when the flush is called in teh postUpdate event??

Upvotes: 11

Views: 7827

Answers (4)

Calin Bolea
Calin Bolea

Reputation: 87

Actually, this is a problem from doctrine Doctrine Issue DDC-2726. Solved it by adding a clear call on the entity manager after the flush in the listener so the 3-rd argument to that constructor, which is actually the entityChangeSets, will be re-written.

Upvotes: 6

RonnyKnoxville
RonnyKnoxville

Reputation: 6394

I had a similar problem to this. Trying to use preupdate to modify child elements caused the same error. In the end, my solution to simply update the children belonging to the parent. No explicit call to flush required.

/**
 * Update expiry dates for all runners belonging to a campaign
 *
 * @param $runners
 * @param $expiryDate
 */
private function updateCampaignRunners($runners, $expiryDate){
    foreach($runners as $runner){
        $runner->setExpiresAt($expiryDate);
        $this->getModelManager()->update($runner);
    }
}

/**
 * Post update and persist lifecycle callback
 *
 * @param Campaign $campaign
 */
private function postAction(Campaign $campaign)
{
    $runnerExpire = $this->getForm()->get("runnerExpire")->getData();
    if($runnerExpiryDate && $campaign->getRunners()){
        $this->updateCampaignRunners($campaign->getRunners(), $runnersExpiryDate);
    }
}

Upvotes: 0

Johnny
Johnny

Reputation: 439

I can suggest you to use Timestampable extension for Doctrine from DoctrineExtensionsBundle.

By using it you don't need to set created_at or modified_at values. This extension does it automatically. Even it can set modified_at only when specific fields were modified. See example.

I think you are writing something like this extension. So, you don't need to do that because this is already done :)

Upvotes: 0

Sgoettschkes
Sgoettschkes

Reputation: 13189

What about updating the modified_at within your entities and let doctrine handle it? You would change your setOrder method in your book to update the BookOrder entity like this:

class Book {
    public function setOrder($order) {
        // modify book
        $this->bookOrder->updateModifiedAt();
    }
}

Of course your BookOrder would have to implement modifiedAt:

class BookOrder {
    public function updateModifiedAt() {
        $this->modifiedAt = new \DateTime();
    }
}

If you use other classes for your Datetime, you of course have to change this code!

Doctrine should recognize that BookOrder has changed and should update it without any need to use a event listener.

Upvotes: 1

Related Questions