Quick Description:
When using merge() with lifecycle callbacks on @PrePersist and @PreUpdate, PrePersist is called on a blank entity and NOT the entity i'm trying to merge, so the entity i'm trying to merge never hits my @PrePersist callback!
@PreUpdate just never fires.
I can get both callbacks to fire in pathological cases, so it's not event registration that's the culprit.
Longer Description:
I have an entity that has some validation rules that I want to run anytime anytime an instance of that class is persisted. If the validation fails, it will throw a "ValidationException" which my stack knows how to handle and feed back to my client UI.
I run my validation by using symfony's built in validator (in particular, to use the UniqueEntity constraint). LIke so
$errorList = $validator->validate($newEntity);
if(count($errorList) > 0) {
throw new ValidationException($errorList);
}
If i put this code in a Controller, this works exactly as I would expect and my tests pass.
Now i want to do this any time the entity is persisted so a developer does not have to explicitly call validate (forced validation). I tried registering a @PrePersist callback and injecting the validator:
protected $validator;
public function prePersist($args) {
$entity = $args->getEntity();
$errorList = $this->validator->validate($newEntity);
if(count($errorList) > 0) {
throw new ValidationException($errorList);
}
}
This works in simple cases.
$obj = new Entity();
$obj->setAField('foo');
$em->persist($obj);
$em->flush();
Unfortunately, my actual constructor uses merge. This is because my 'saveEntity' route receives a JSON package from the client and deals with saving new entities AND saving edits to existing entities. It looks something like this:
$json = $request->request->get('objJSON');
$detachedEntity = $this->serializer->deserialize($json, 'MyEntity');
//do any clean up here. In some cases you need to manually retrieve and set
//associated classes...
$mergedEntity = $em->merge($detachedEntity);
//do any post-merge actions
$em->persist($mergedEntity);
$em->flush();
This workflow works in general. However my validation fails! After a long venture through the heart of the symfony code i found the reason:
merge() creates an empty entity and persists it. This empty entity has none of the properties filled in, so the validation ignores it (ignoring nulls). After it persists it, it merges the fields from the passed entity in and returns to the controller.
//From UnitOfWork.php:doMerge() - line 1788
// If there is no ID, it is actually NEW.
if ( ! $id) {
$managedCopy = $this->newInstance($class);
$this->persistNew($class, $managedCopy);
This triggers @PrePersist on a blank entity (useful!). It then merges the data from the detachedEntity i passed in into this managed entity.
I tried tracing the code on the persist() after the merge, but I had trouble finding where it actually commits the data to the database...
When the final persist is called, it's not inserting the entity, so @PrePersist is not triggered. OK. This is kind of frusterating, but I figured i could just treat it as an UPDATE instead (i want this checks run anytime an entity changes anyways), so i registered a @PreUpdate callback with identical code. It never gets called.
So.. is there something horribly wrong with my workflow, or is there a different callback I could call?
I find it really frusterating that the @PrePersist is called with the blank entity made by merge so I would prefer if there was another way to do that.
UPDATE This might be a bug... http://www.doctrine-project.org/jira/browse/DDC-2406?page=com.atlassian.jira.plugin.system.issuetabpanels:changehistory-tabpanel