Chadwick Meyer
Chadwick Meyer

Reputation: 7311

Adding New OneToMany Associated Entity in Symfony 2.5 preUpdate Event Listener

Using Symfony 2.5, the Doctrine documentation says that the preUpdate event listener is the most restrictive. You can't just make a change to the entity, instead you have to use the special function:

$entity->setNewValue($fieldName, $value)

However, I have an entity $view which has an one to many associated entity $routing, and I have a special setter in my $view object that adds multiple routes:

class View { 

    protected $routing;

    public function getRouting() {
        return $this->routing;
    }

    public function addRouting(\Gutensite\CmsBundle\Entity\Routing\Routing $routing)
    {
        // Maintain Relationship
        $routing->setView($this);
        $this->routing[] = $routing;
        return $this;
    }
}

I do not have a setRouting() function in that entity, because instead I have an addRouting() because it is a multiple oneToMany association.

So... in my preUpdate event listener, how am I supposed to create a new "default" route, and add it to view, since the $entity->setNewValue() function won't exactly work on a collection, and evidently I can't just call $view->addRouting($route) (I tried that but nothing gets persisted to the database).

Note: since 2.4 we should use the LifeCycleEventArgs instead of PreUpdateEventArgs (evidently).

public function preUpdate(LifeCycleEventArgs $eventArgs) {

    $entity = $eventArgs->getEntity();
    $em = $eventArgs->getEntityManager();

    if($entity instanceof View) {
        if(!$entity->getRouting() || !$entity->getRouting()[0]) {
            $routing = new Routing();
            // simplified code to Set Friendly URL
            $routing->setFurl('Unknown-Friendly-URL-'.$entity->getId());
            // Set Default Values for Route
            $routing->setFlagPrimary(1);
            $routing->setTime(time());
            $routing->setTimeMod(time());
            $routing->setStatus(1);
            $routing->setView($entity);

            // Add the route to the View Entity
            $entity->addRouting($routing);

            // Recalculate Entity Changes (not working)
            $uow = $em->getUnitOfWork();
            // This doesn't work whether I compute the changesets for $entity (which is $view) 
            // or if I calculate changes for $entity->getRouting() (which is the new route I'm adding).
            $classMetadata = $em->getClassMetadata(get_class($entity));
            // Recomputer Entire Change Set Because it's a New Record (I'm not sure this is necessary or not)
            $uow->computeChangeSet($classMetadata, $entity);
            // This doesn't work either
            $uow->recomputeSingleEntityChangeSet($classMetadata, $entity);

        }
    }
}

Upvotes: 1

Views: 979

Answers (2)

Chadwick Meyer
Chadwick Meyer

Reputation: 7311

I ended up just implementing this in an onFlush event. The preUpdate is too restrictive for my use case. So that begs the question: why use preUpdate when onFlush is more flexible?

Benefits of preUpdate

Insert a simple value to an entity, e.g.

$eventArgs->setNewValue('myField', $value);

You also have access to the entities old and new values (which is super useful for easily comparing WHICH fields changed, something I have needed before).

This event has a powerful feature however, it is executed with a PreUpdateEventArgs instance, which contains a reference to the computed change-set of this entity. This means you have access to all the fields that have changed for this entity with their old and new value. --reference

NOTE: If you do use preUpdate, remember that you must use the setNewValue() syntax, not the normal setter for your entity. There is no need to recalculate the change set, and you also should NOT call persist:

Any calls to EntityManager#persist() or EntityManager#remove(), even in combination with the UnitOfWork API are strongly discouraged and don’t work as expected outside the flush operation.

Problems with preUpdate

Changes to associations of the updated entity are never allowed in this event, since Doctrine cannot guarantee to correctly handle referential integrity at this point of the flush operation. --reference

So if you need to update a child or the parent value (e.g. update the modification time of the parent), you can't do that within preUpdate because the setNewValue method is called on the eventArgs for the current entity, not on a specific entity.

Or if you need to add a collection, as in my question, you can't call custom setters like addRouting (for the reasons I mentioned). I suspect that means@RomaKliuchko's suggestion would not work.

Please add other advantages and disadvantages.

Upvotes: 4

Roman  Kliuchko
Roman Kliuchko

Reputation: 499

Have you tried to implement setRouting(ArrayCollection $routing) method, which will override existing collection and do like this:

$routings = $entity->getRouting();
// add entities to routing collection if needed
$eventArgs->setNewValue('routing', $routings);

Upvotes: 0

Related Questions