How to clone an entity whit relations? - php

I need to clone an entity called Projects with 2 entity relations calls Zone and Sector.
I'd tried to use something like that in the controller:
$new_project = clone $project;
$em = $this->getDoctrine()->getManager();
$em->persist($new_project);
$em->flush();
It works for copy the entity Projects, but it doesn't copy the other 2 entities and its relations...
Any suggestion?
EDIT: I found a fast and easy solution in this question. Thanks!

What I did when I've run this issue is write a function which reads the object's metadata dynamically, iterates through them, and copy each field manually.
The metadata will have a property called "fieldNames" with the non-relational fields, and "associationMappings" with the relational ones. An example:
$em = $this->getDoctrine()->getManager();
$objectToClone = $em->getRepository('Xxx')->find(xx);
$class = get_class($objectToClone);
$metadata = $em->getMetadataFactory()->getMetadataFor($class);
// Symfony\Component\PropertyAccess\PropertyAccessor;
$accessor = new PropertyAccessor();
$newObject = new Xxx();
foreach ($metadata->getFieldNames() as $value) {
if (property_exists($objectToClone, $value)) {
$accessor->setValue($newObject, $value, $accessor->getValue($objectToClone, $value));
}
}
foreach ($metadata->getAssociationMappings() as $key => $value) {
if (property_exists($objectToClone, $key)) {
$accessor->setValue($newObject, $key, $accessor->getValue($objectToClone, $key));
}
}
Hope this helps.

Try this function:
public static function cloneObject( $source ) {
if ( $source === null ) {
return null;
}
return unserialize( serialize( $source ) );
}
I use it to clone entities in a zf2 project, and it works fine. It allows me to iterate through One to Many related entities from the main entity.

Related

How to see what gets updated on flush in doctrine? [duplicate]

Let's suppose I retrieve an entity $e and modify its state with setters:
$e->setFoo('a');
$e->setBar('b');
Is there any possibility to retrieve an array of fields that have been changed?
In case of my example I'd like to retrieve foo => a, bar => b as a result
PS: yes, I know I can modify all the accessors and implement this feature manually, but I'm looking for some handy way of doing this
You can use
Doctrine\ORM\EntityManager#getUnitOfWork to get a Doctrine\ORM\UnitOfWork.
Then just trigger changeset computation (works only on managed entities) via Doctrine\ORM\UnitOfWork#computeChangeSets().
You can use also similar methods like Doctrine\ORM\UnitOfWork#recomputeSingleEntityChangeSet(Doctrine\ORM\ClassMetadata $meta, $entity) if you know exactly what you want to check without iterating over the entire object graph.
After that you can use Doctrine\ORM\UnitOfWork#getEntityChangeSet($entity) to retrieve all changes to your object.
Putting it together:
$entity = $em->find('My\Entity', 1);
$entity->setTitle('Changed Title!');
$uow = $em->getUnitOfWork();
$uow->computeChangeSets(); // do not compute changes if inside a listener
$changeset = $uow->getEntityChangeSet($entity);
Note. If trying to get the updated fields inside a preUpdate listener, don't recompute change set, as it has already been done. Simply call the getEntityChangeSet to get all of the changes made to the entity.
Warning: As explained in the comments, this solution should not be used outside of Doctrine event listeners. This will break Doctrine's behavior.
Check this public (and not internal) function:
$this->em->getUnitOfWork()->getOriginalEntityData($entity);
From doctrine repo:
/**
* Gets the original data of an entity. The original data is the data that was
* present at the time the entity was reconstituted from the database.
*
* #param object $entity
*
* #return array
*/
public function getOriginalEntityData($entity)
All you have to do is implement a toArray or serialize function in your entity and make a diff. Something like this :
$originalData = $em->getUnitOfWork()->getOriginalEntityData($entity);
$toArrayEntity = $entity->toArray();
$changes = array_diff_assoc($toArrayEntity, $originalData);
Big beware sign for those that want to check for the changes on the entity using the method described above.
$uow = $em->getUnitOfWork();
$uow->computeChangeSets();
The $uow->computeChangeSets() method is used internally by the persisting routine in a way that renders the above solution unusable. That's also what's written in the comments to the method: #internal Don't call from the outside.
After checking on the changes to the entities with $uow->computeChangeSets(), the following piece of code is executed at the end of the method (per each managed entity):
if ($changeSet) {
$this->entityChangeSets[$oid] = $changeSet;
$this->originalEntityData[$oid] = $actualData;
$this->entityUpdates[$oid] = $entity;
}
The $actualData array holds the current changes to the entity's properties. As soon as these are written into $this->originalEntityData[$oid], these not yet persisted changes are considered the original properties of the entity.
Later, when the $em->persist($entity) is called to save the changes to the entity, it also involves the method $uow->computeChangeSets(), but now it won't be able to find the changes to the entity, as these not yet persisted changes are considered the original properties of the entity.
It will return changes
$entityManager->getUnitOfWork()->getEntityChangeSet($entity)
You can track the changes with Notify policies.
First, implements the NotifyPropertyChanged interface:
/**
* #Entity
* #ChangeTrackingPolicy("NOTIFY")
*/
class MyEntity implements NotifyPropertyChanged
{
// ...
private $_listeners = array();
public function addPropertyChangedListener(PropertyChangedListener $listener)
{
$this->_listeners[] = $listener;
}
}
Then, just call the _onPropertyChanged on every method that changes data throw your entity as below:
class MyEntity implements NotifyPropertyChanged
{
// ...
protected function _onPropertyChanged($propName, $oldValue, $newValue)
{
if ($this->_listeners) {
foreach ($this->_listeners as $listener) {
$listener->propertyChanged($this, $propName, $oldValue, $newValue);
}
}
}
public function setData($data)
{
if ($data != $this->data) {
$this->_onPropertyChanged('data', $this->data, $data);
$this->data = $data;
}
}
}
So... what to do when we want to find a changeset outside of the Doctrine lifecycle? As mentioned in my comment on #Ocramius' post above, perhaps it is possible to create a "readonly" method that doesn't mess with the actual Doctrine persistence but gives the user a view of what has changed.
Here's an example of what I'm thinking of...
/**
* Try to get an Entity changeSet without changing the UnitOfWork
*
* #param EntityManager $em
* #param $entity
* #return null|array
*/
public static function diffDoctrineObject(EntityManager $em, $entity) {
$uow = $em->getUnitOfWork();
/*****************************************/
/* Equivalent of $uow->computeChangeSet($this->em->getClassMetadata(get_class($entity)), $entity);
/*****************************************/
$class = $em->getClassMetadata(get_class($entity));
$oid = spl_object_hash($entity);
$entityChangeSets = array();
if ($uow->isReadOnly($entity)) {
return null;
}
if ( ! $class->isInheritanceTypeNone()) {
$class = $em->getClassMetadata(get_class($entity));
}
// These parts are not needed for the changeSet?
// $invoke = $uow->listenersInvoker->getSubscribedSystems($class, Events::preFlush) & ~ListenersInvoker::INVOKE_MANAGER;
//
// if ($invoke !== ListenersInvoker::INVOKE_NONE) {
// $uow->listenersInvoker->invoke($class, Events::preFlush, $entity, new PreFlushEventArgs($em), $invoke);
// }
$actualData = array();
foreach ($class->reflFields as $name => $refProp) {
$value = $refProp->getValue($entity);
if ($class->isCollectionValuedAssociation($name) && $value !== null) {
if ($value instanceof PersistentCollection) {
if ($value->getOwner() === $entity) {
continue;
}
$value = new ArrayCollection($value->getValues());
}
// If $value is not a Collection then use an ArrayCollection.
if ( ! $value instanceof Collection) {
$value = new ArrayCollection($value);
}
$assoc = $class->associationMappings[$name];
// Inject PersistentCollection
$value = new PersistentCollection(
$em, $em->getClassMetadata($assoc['targetEntity']), $value
);
$value->setOwner($entity, $assoc);
$value->setDirty( ! $value->isEmpty());
$class->reflFields[$name]->setValue($entity, $value);
$actualData[$name] = $value;
continue;
}
if (( ! $class->isIdentifier($name) || ! $class->isIdGeneratorIdentity()) && ($name !== $class->versionField)) {
$actualData[$name] = $value;
}
}
$originalEntityData = $uow->getOriginalEntityData($entity);
if (empty($originalEntityData)) {
// Entity is either NEW or MANAGED but not yet fully persisted (only has an id).
// These result in an INSERT.
$originalEntityData = $actualData;
$changeSet = array();
foreach ($actualData as $propName => $actualValue) {
if ( ! isset($class->associationMappings[$propName])) {
$changeSet[$propName] = array(null, $actualValue);
continue;
}
$assoc = $class->associationMappings[$propName];
if ($assoc['isOwningSide'] && $assoc['type'] & ClassMetadata::TO_ONE) {
$changeSet[$propName] = array(null, $actualValue);
}
}
$entityChangeSets[$oid] = $changeSet; // #todo - remove this?
} else {
// Entity is "fully" MANAGED: it was already fully persisted before
// and we have a copy of the original data
$originalData = $originalEntityData;
$isChangeTrackingNotify = $class->isChangeTrackingNotify();
$changeSet = $isChangeTrackingNotify ? $uow->getEntityChangeSet($entity) : array();
foreach ($actualData as $propName => $actualValue) {
// skip field, its a partially omitted one!
if ( ! (isset($originalData[$propName]) || array_key_exists($propName, $originalData))) {
continue;
}
$orgValue = $originalData[$propName];
// skip if value haven't changed
if ($orgValue === $actualValue) {
continue;
}
// if regular field
if ( ! isset($class->associationMappings[$propName])) {
if ($isChangeTrackingNotify) {
continue;
}
$changeSet[$propName] = array($orgValue, $actualValue);
continue;
}
$assoc = $class->associationMappings[$propName];
// Persistent collection was exchanged with the "originally"
// created one. This can only mean it was cloned and replaced
// on another entity.
if ($actualValue instanceof PersistentCollection) {
$owner = $actualValue->getOwner();
if ($owner === null) { // cloned
$actualValue->setOwner($entity, $assoc);
} else if ($owner !== $entity) { // no clone, we have to fix
// #todo - what does this do... can it be removed?
if (!$actualValue->isInitialized()) {
$actualValue->initialize(); // we have to do this otherwise the cols share state
}
$newValue = clone $actualValue;
$newValue->setOwner($entity, $assoc);
$class->reflFields[$propName]->setValue($entity, $newValue);
}
}
if ($orgValue instanceof PersistentCollection) {
// A PersistentCollection was de-referenced, so delete it.
// These parts are not needed for the changeSet?
// $coid = spl_object_hash($orgValue);
//
// if (isset($uow->collectionDeletions[$coid])) {
// continue;
// }
//
// $uow->collectionDeletions[$coid] = $orgValue;
$changeSet[$propName] = $orgValue; // Signal changeset, to-many assocs will be ignored.
continue;
}
if ($assoc['type'] & ClassMetadata::TO_ONE) {
if ($assoc['isOwningSide']) {
$changeSet[$propName] = array($orgValue, $actualValue);
}
// These parts are not needed for the changeSet?
// if ($orgValue !== null && $assoc['orphanRemoval']) {
// $uow->scheduleOrphanRemoval($orgValue);
// }
}
}
if ($changeSet) {
$entityChangeSets[$oid] = $changeSet;
// These parts are not needed for the changeSet?
// $originalEntityData = $actualData;
// $uow->entityUpdates[$oid] = $entity;
}
}
// These parts are not needed for the changeSet?
//// Look for changes in associations of the entity
//foreach ($class->associationMappings as $field => $assoc) {
// if (($val = $class->reflFields[$field]->getValue($entity)) !== null) {
// $uow->computeAssociationChanges($assoc, $val);
// if (!isset($entityChangeSets[$oid]) &&
// $assoc['isOwningSide'] &&
// $assoc['type'] == ClassMetadata::MANY_TO_MANY &&
// $val instanceof PersistentCollection &&
// $val->isDirty()) {
// $entityChangeSets[$oid] = array();
// $originalEntityData = $actualData;
// $uow->entityUpdates[$oid] = $entity;
// }
// }
//}
/*********************/
return $entityChangeSets[$oid];
}
It's phrased here as a static method but could become a method inside UnitOfWork...?
I'm not up to speed on all the internals of Doctrine, so might have missed something that has a side effect or misunderstood part of what this method does, but a (very) quick test of it seems to give me the results I expect to see.
I hope this helps somebody!
In case someone is still interested in a different way than the accepted answer (it was not working for me and I found it messier than this way in my personal opinion).
I installed the JMS Serializer Bundle and on each entity and on each property that I consider a change I added a #Group({"changed_entity_group"}). This way, I can then make a serialization between the old entity, and the updated entity and after that it's just a matter of saying $oldJson == $updatedJson. If the properties that you are interested in or that you would like to consider changes the JSON won't be the same and if you even want to register WHAT specifically changed then you can turn it into an array and search for the differences.
I used this method since I was interested mainly in a few properties of a bunch of entities and not in the entity entirely. An example where this would be useful is if you have a #PrePersist #PreUpdate and you have a last_update date, that will always be updated therefore you will always get that the entity was updated using unit of work and stuff like that.
Hope this method is helpful to anyone.
in my case i want to get old value of relation in the entity, so i use the Doctrine\ORM\PersistentCollection::getSnapshot base on this
Working with UnitOfWork and computeChangeSets within an Doctrine Event Listeners is probably the preferred method.
However: If you want to persist and flush a new entity within this listener you might confront a lot of hassle. As it seems, the only proper listener would be onFlush with its own set of problems.
So I suggest a simple but lightweight comparison, which can be used within Controllers and even Services by simply injecting the EntityManagerInterface (inspired by #Mohamed Ramrami in the post above):
$uow = $entityManager->getUnitOfWork();
$originalEntityData = $uow->getOriginalEntityData($blog);
// for nested entities, as suggested in the docs
$defaultContext = [
AbstractNormalizer::CIRCULAR_REFERENCE_HANDLER => function ($object, $format, $context) {
return $object->getId();
},
];
$normalizer = new Serializer([new DateTimeNormalizer(), new ObjectNormalizer(null, null, null, null, null, null, $defaultContext)]);
$yourEntityNormalized = $normalizer->normalize();
$originalNormalized = $normalizer->normalize($originalEntityData);
$changed = [];
foreach ($originalNormalized as $item=>$value) {
if(array_key_exists($item, $yourEntityNormalized)) {
if($value !== $yourEntityNormalized[$item]) {
$changed[] = $item;
}
}
}
Note: it does compare strings, datetimes, bools, integers and floats correctly however fails on objects (due to the Circular reference problems). One could compare these objects more in depth, but for e.g. text change detection this is enough and much more simple than handling Event Listeners.
More Info:
Serializers and Normalizers
Handling Circular References
In my case, for sync data from a remote WS to a local DB I used this way to compare two entities (check il old entity has diffs from the edited entity).
I symply clone the persisted entity to have two objects not persisted:
<?php
$entity = $repository->find($id);// original entity exists
if (null === $entity) {
$entity = new $className();// local entity not exists, create new one
}
$oldEntity = clone $entity;// make a detached "backup" of the entity before it's changed
// make some changes to the entity...
$entity->setX('Y');
// now compare entities properties/values
$entityCloned = clone $entity;// clone entity for detached (not persisted) entity comparaison
if ( ! $em->contains( $entity ) || $entityCloned != $oldEntity) {// do not compare strictly!
$em->persist( $entity );
$em->flush();
}
unset($entityCloned, $oldEntity, $entity);
Another possibility rather than compare objects directly:
<?php
// here again we need to clone the entity ($entityCloned)
$entity_diff = array_keys(
array_diff_key(
get_object_vars( $entityCloned ),
get_object_vars( $oldEntity )
)
);
if(count($entity_diff) > 0){
// persist & flush
}
It works for me
1. import EntityManager
2. Now you can use this anywhere into the class.
use Doctrine\ORM\EntityManager;
$preData = $this->em->getUnitOfWork()->getOriginalEntityData($entity);
// $preData['active'] for old data and $entity->getActive() for new data
if($preData['active'] != $entity->getActive()){
echo 'Send email';
}

Update association with doctrine and symfony 2.7

I want to know if it exists a better way to update association in doctrine from the inversed side entity, in this case it's a many to many type, but it may be another one.
This is the method I used :
$entity = $em->getRepository('MyBundle:MyEntity')->find($id);
foreach ($entity->getAssociation() as $value) {
$entity->removeAssociation($value);
}
$editForm = $this->createEditForm($entity);
$editForm->handleRequest($request);
if ($editForm->isValid()) {
foreach ($entity->getAssociation() as $value) {
$entity->addAssociation($value);
}
$em->flush();
}
I think that it's not a good approach, beacause I want to update (so remove and add) only the association that i select or unselect in my form, not every element of the array. So I made a service with an update function :
public function updateCollection(&$newEntity, $newCollection, $oldCollection, $contains, $add, $remove) {
foreach ($oldCollection as $value) {
$item = call_user_func(array($newCollection, $contains), $value);
if(!$item){
call_user_func( array($newEntity, $remove), $value );
}
}
foreach ($newCollection as $value) {
$item = call_user_func(array($oldCollection, $contains), $value );
if(!$item){
call_user_func( array($newEntity, $add), $value );
}
}
}
And I call this method with this :
$oldAssociation = clone $entity->getAssociation();
$editForm = $this->createEditForm($entity);
$editForm->handleRequest($request);
if ($editForm->isValid()) {
$this->get('service_update')->updateCollection(
$entity,
$entity->getAssociation(),
$oldAssociation,
'contains',
'addAssociation',
'removeAssociation'
);
$em->flush();
}
But I think that this method is more time expensive than the first one, but it only remove and add elements from the array for wich the association has been changed. I don't know what is the best way to do this, maybe none of the two. Can you bring me on the right way please?
Thank you and sorry for my bad english
I was doing it in the wrong way.
I found the best solution ever to update the inversed side of a relation in this post:
https://knpuniversity.com/screencast/collections/saving-inverse-side-collection
I don't know why but I didn't found that before, I advise people to use that solution for a multiple choices of entity type elements.

Deep cloning an object : Clone vs Serialize

I have this function duplicateCourseAction whose goal is to ... duplicate a Course object
public function duplicateCourseAction(Request $request) {
if ($this->getRequest()->isXmlHttpRequest() == false) {
return new Response("Bad request", 405);
}
$em = $this->getDoctrine()->getManager();
$parameters = $request->request->all();
$course = $em->getRepository('EntTimeBundle:Course')->findOneById($parameters['id']);
$duplicate = clone $course;
$duplicate->setDate(new \DateTime($parameters['date']));
$em->persist($duplicate);
$em->flush();
return new Response("200");
}
According to documentations, "clone" keyword make a surface copy (ie. a reference copy).
This is clearly not what I want because my Course entity contains many relations to others entities, I would rather want a values copy.
I discovered the unserialize(serialize(object)) trick :
public function duplicateCourseAction(Request $request) {
if ($this->getRequest()->isXmlHttpRequest() == false) {
return new Response("Bad request", 405);
}
$em = $this->getDoctrine()->getManager();
$parameters = $request->request->all();
$course = $em->getRepository('EntTimeBundle:Course')->findOneById($parameters['id']);
$duplicate = unserialize(serialize($course));
$duplicate->setDate(new \DateTime($parameters['date']));
$em->persist($duplicate);
$em->flush();
return new Response("200");
}
But I have this error with Doctrine :
Notice: Undefined index: 000000003ed2e9ea00000000ee270fde in /home/mart_q/Diderot/ent/vendor/doctrine/orm/lib/Doctrine/ORM/UnitOfWork.php line 2776
You can control what exactly gets cloned by overriding the __clone() method in your Course entity. You can set id to null and clone referenced objects if you need a deep copy.
The serialization/unserialization feels like a hack, so I recommend against using it.
Trick here is that you must unset duplicate entity id. Otherwise breaks the logic of the doctrine. Doctrine has some known limitations. Also check this question, its very similar.

Is there a built-in way to get all of the changed/updated fields in a Doctrine 2 entity

Let's suppose I retrieve an entity $e and modify its state with setters:
$e->setFoo('a');
$e->setBar('b');
Is there any possibility to retrieve an array of fields that have been changed?
In case of my example I'd like to retrieve foo => a, bar => b as a result
PS: yes, I know I can modify all the accessors and implement this feature manually, but I'm looking for some handy way of doing this
You can use
Doctrine\ORM\EntityManager#getUnitOfWork to get a Doctrine\ORM\UnitOfWork.
Then just trigger changeset computation (works only on managed entities) via Doctrine\ORM\UnitOfWork#computeChangeSets().
You can use also similar methods like Doctrine\ORM\UnitOfWork#recomputeSingleEntityChangeSet(Doctrine\ORM\ClassMetadata $meta, $entity) if you know exactly what you want to check without iterating over the entire object graph.
After that you can use Doctrine\ORM\UnitOfWork#getEntityChangeSet($entity) to retrieve all changes to your object.
Putting it together:
$entity = $em->find('My\Entity', 1);
$entity->setTitle('Changed Title!');
$uow = $em->getUnitOfWork();
$uow->computeChangeSets(); // do not compute changes if inside a listener
$changeset = $uow->getEntityChangeSet($entity);
Note. If trying to get the updated fields inside a preUpdate listener, don't recompute change set, as it has already been done. Simply call the getEntityChangeSet to get all of the changes made to the entity.
Warning: As explained in the comments, this solution should not be used outside of Doctrine event listeners. This will break Doctrine's behavior.
Check this public (and not internal) function:
$this->em->getUnitOfWork()->getOriginalEntityData($entity);
From doctrine repo:
/**
* Gets the original data of an entity. The original data is the data that was
* present at the time the entity was reconstituted from the database.
*
* #param object $entity
*
* #return array
*/
public function getOriginalEntityData($entity)
All you have to do is implement a toArray or serialize function in your entity and make a diff. Something like this :
$originalData = $em->getUnitOfWork()->getOriginalEntityData($entity);
$toArrayEntity = $entity->toArray();
$changes = array_diff_assoc($toArrayEntity, $originalData);
Big beware sign for those that want to check for the changes on the entity using the method described above.
$uow = $em->getUnitOfWork();
$uow->computeChangeSets();
The $uow->computeChangeSets() method is used internally by the persisting routine in a way that renders the above solution unusable. That's also what's written in the comments to the method: #internal Don't call from the outside.
After checking on the changes to the entities with $uow->computeChangeSets(), the following piece of code is executed at the end of the method (per each managed entity):
if ($changeSet) {
$this->entityChangeSets[$oid] = $changeSet;
$this->originalEntityData[$oid] = $actualData;
$this->entityUpdates[$oid] = $entity;
}
The $actualData array holds the current changes to the entity's properties. As soon as these are written into $this->originalEntityData[$oid], these not yet persisted changes are considered the original properties of the entity.
Later, when the $em->persist($entity) is called to save the changes to the entity, it also involves the method $uow->computeChangeSets(), but now it won't be able to find the changes to the entity, as these not yet persisted changes are considered the original properties of the entity.
It will return changes
$entityManager->getUnitOfWork()->getEntityChangeSet($entity)
You can track the changes with Notify policies.
First, implements the NotifyPropertyChanged interface:
/**
* #Entity
* #ChangeTrackingPolicy("NOTIFY")
*/
class MyEntity implements NotifyPropertyChanged
{
// ...
private $_listeners = array();
public function addPropertyChangedListener(PropertyChangedListener $listener)
{
$this->_listeners[] = $listener;
}
}
Then, just call the _onPropertyChanged on every method that changes data throw your entity as below:
class MyEntity implements NotifyPropertyChanged
{
// ...
protected function _onPropertyChanged($propName, $oldValue, $newValue)
{
if ($this->_listeners) {
foreach ($this->_listeners as $listener) {
$listener->propertyChanged($this, $propName, $oldValue, $newValue);
}
}
}
public function setData($data)
{
if ($data != $this->data) {
$this->_onPropertyChanged('data', $this->data, $data);
$this->data = $data;
}
}
}
So... what to do when we want to find a changeset outside of the Doctrine lifecycle? As mentioned in my comment on #Ocramius' post above, perhaps it is possible to create a "readonly" method that doesn't mess with the actual Doctrine persistence but gives the user a view of what has changed.
Here's an example of what I'm thinking of...
/**
* Try to get an Entity changeSet without changing the UnitOfWork
*
* #param EntityManager $em
* #param $entity
* #return null|array
*/
public static function diffDoctrineObject(EntityManager $em, $entity) {
$uow = $em->getUnitOfWork();
/*****************************************/
/* Equivalent of $uow->computeChangeSet($this->em->getClassMetadata(get_class($entity)), $entity);
/*****************************************/
$class = $em->getClassMetadata(get_class($entity));
$oid = spl_object_hash($entity);
$entityChangeSets = array();
if ($uow->isReadOnly($entity)) {
return null;
}
if ( ! $class->isInheritanceTypeNone()) {
$class = $em->getClassMetadata(get_class($entity));
}
// These parts are not needed for the changeSet?
// $invoke = $uow->listenersInvoker->getSubscribedSystems($class, Events::preFlush) & ~ListenersInvoker::INVOKE_MANAGER;
//
// if ($invoke !== ListenersInvoker::INVOKE_NONE) {
// $uow->listenersInvoker->invoke($class, Events::preFlush, $entity, new PreFlushEventArgs($em), $invoke);
// }
$actualData = array();
foreach ($class->reflFields as $name => $refProp) {
$value = $refProp->getValue($entity);
if ($class->isCollectionValuedAssociation($name) && $value !== null) {
if ($value instanceof PersistentCollection) {
if ($value->getOwner() === $entity) {
continue;
}
$value = new ArrayCollection($value->getValues());
}
// If $value is not a Collection then use an ArrayCollection.
if ( ! $value instanceof Collection) {
$value = new ArrayCollection($value);
}
$assoc = $class->associationMappings[$name];
// Inject PersistentCollection
$value = new PersistentCollection(
$em, $em->getClassMetadata($assoc['targetEntity']), $value
);
$value->setOwner($entity, $assoc);
$value->setDirty( ! $value->isEmpty());
$class->reflFields[$name]->setValue($entity, $value);
$actualData[$name] = $value;
continue;
}
if (( ! $class->isIdentifier($name) || ! $class->isIdGeneratorIdentity()) && ($name !== $class->versionField)) {
$actualData[$name] = $value;
}
}
$originalEntityData = $uow->getOriginalEntityData($entity);
if (empty($originalEntityData)) {
// Entity is either NEW or MANAGED but not yet fully persisted (only has an id).
// These result in an INSERT.
$originalEntityData = $actualData;
$changeSet = array();
foreach ($actualData as $propName => $actualValue) {
if ( ! isset($class->associationMappings[$propName])) {
$changeSet[$propName] = array(null, $actualValue);
continue;
}
$assoc = $class->associationMappings[$propName];
if ($assoc['isOwningSide'] && $assoc['type'] & ClassMetadata::TO_ONE) {
$changeSet[$propName] = array(null, $actualValue);
}
}
$entityChangeSets[$oid] = $changeSet; // #todo - remove this?
} else {
// Entity is "fully" MANAGED: it was already fully persisted before
// and we have a copy of the original data
$originalData = $originalEntityData;
$isChangeTrackingNotify = $class->isChangeTrackingNotify();
$changeSet = $isChangeTrackingNotify ? $uow->getEntityChangeSet($entity) : array();
foreach ($actualData as $propName => $actualValue) {
// skip field, its a partially omitted one!
if ( ! (isset($originalData[$propName]) || array_key_exists($propName, $originalData))) {
continue;
}
$orgValue = $originalData[$propName];
// skip if value haven't changed
if ($orgValue === $actualValue) {
continue;
}
// if regular field
if ( ! isset($class->associationMappings[$propName])) {
if ($isChangeTrackingNotify) {
continue;
}
$changeSet[$propName] = array($orgValue, $actualValue);
continue;
}
$assoc = $class->associationMappings[$propName];
// Persistent collection was exchanged with the "originally"
// created one. This can only mean it was cloned and replaced
// on another entity.
if ($actualValue instanceof PersistentCollection) {
$owner = $actualValue->getOwner();
if ($owner === null) { // cloned
$actualValue->setOwner($entity, $assoc);
} else if ($owner !== $entity) { // no clone, we have to fix
// #todo - what does this do... can it be removed?
if (!$actualValue->isInitialized()) {
$actualValue->initialize(); // we have to do this otherwise the cols share state
}
$newValue = clone $actualValue;
$newValue->setOwner($entity, $assoc);
$class->reflFields[$propName]->setValue($entity, $newValue);
}
}
if ($orgValue instanceof PersistentCollection) {
// A PersistentCollection was de-referenced, so delete it.
// These parts are not needed for the changeSet?
// $coid = spl_object_hash($orgValue);
//
// if (isset($uow->collectionDeletions[$coid])) {
// continue;
// }
//
// $uow->collectionDeletions[$coid] = $orgValue;
$changeSet[$propName] = $orgValue; // Signal changeset, to-many assocs will be ignored.
continue;
}
if ($assoc['type'] & ClassMetadata::TO_ONE) {
if ($assoc['isOwningSide']) {
$changeSet[$propName] = array($orgValue, $actualValue);
}
// These parts are not needed for the changeSet?
// if ($orgValue !== null && $assoc['orphanRemoval']) {
// $uow->scheduleOrphanRemoval($orgValue);
// }
}
}
if ($changeSet) {
$entityChangeSets[$oid] = $changeSet;
// These parts are not needed for the changeSet?
// $originalEntityData = $actualData;
// $uow->entityUpdates[$oid] = $entity;
}
}
// These parts are not needed for the changeSet?
//// Look for changes in associations of the entity
//foreach ($class->associationMappings as $field => $assoc) {
// if (($val = $class->reflFields[$field]->getValue($entity)) !== null) {
// $uow->computeAssociationChanges($assoc, $val);
// if (!isset($entityChangeSets[$oid]) &&
// $assoc['isOwningSide'] &&
// $assoc['type'] == ClassMetadata::MANY_TO_MANY &&
// $val instanceof PersistentCollection &&
// $val->isDirty()) {
// $entityChangeSets[$oid] = array();
// $originalEntityData = $actualData;
// $uow->entityUpdates[$oid] = $entity;
// }
// }
//}
/*********************/
return $entityChangeSets[$oid];
}
It's phrased here as a static method but could become a method inside UnitOfWork...?
I'm not up to speed on all the internals of Doctrine, so might have missed something that has a side effect or misunderstood part of what this method does, but a (very) quick test of it seems to give me the results I expect to see.
I hope this helps somebody!
In case someone is still interested in a different way than the accepted answer (it was not working for me and I found it messier than this way in my personal opinion).
I installed the JMS Serializer Bundle and on each entity and on each property that I consider a change I added a #Group({"changed_entity_group"}). This way, I can then make a serialization between the old entity, and the updated entity and after that it's just a matter of saying $oldJson == $updatedJson. If the properties that you are interested in or that you would like to consider changes the JSON won't be the same and if you even want to register WHAT specifically changed then you can turn it into an array and search for the differences.
I used this method since I was interested mainly in a few properties of a bunch of entities and not in the entity entirely. An example where this would be useful is if you have a #PrePersist #PreUpdate and you have a last_update date, that will always be updated therefore you will always get that the entity was updated using unit of work and stuff like that.
Hope this method is helpful to anyone.
in my case i want to get old value of relation in the entity, so i use the Doctrine\ORM\PersistentCollection::getSnapshot base on this
Working with UnitOfWork and computeChangeSets within an Doctrine Event Listeners is probably the preferred method.
However: If you want to persist and flush a new entity within this listener you might confront a lot of hassle. As it seems, the only proper listener would be onFlush with its own set of problems.
So I suggest a simple but lightweight comparison, which can be used within Controllers and even Services by simply injecting the EntityManagerInterface (inspired by #Mohamed Ramrami in the post above):
$uow = $entityManager->getUnitOfWork();
$originalEntityData = $uow->getOriginalEntityData($blog);
// for nested entities, as suggested in the docs
$defaultContext = [
AbstractNormalizer::CIRCULAR_REFERENCE_HANDLER => function ($object, $format, $context) {
return $object->getId();
},
];
$normalizer = new Serializer([new DateTimeNormalizer(), new ObjectNormalizer(null, null, null, null, null, null, $defaultContext)]);
$yourEntityNormalized = $normalizer->normalize();
$originalNormalized = $normalizer->normalize($originalEntityData);
$changed = [];
foreach ($originalNormalized as $item=>$value) {
if(array_key_exists($item, $yourEntityNormalized)) {
if($value !== $yourEntityNormalized[$item]) {
$changed[] = $item;
}
}
}
Note: it does compare strings, datetimes, bools, integers and floats correctly however fails on objects (due to the Circular reference problems). One could compare these objects more in depth, but for e.g. text change detection this is enough and much more simple than handling Event Listeners.
More Info:
Serializers and Normalizers
Handling Circular References
In my case, for sync data from a remote WS to a local DB I used this way to compare two entities (check il old entity has diffs from the edited entity).
I symply clone the persisted entity to have two objects not persisted:
<?php
$entity = $repository->find($id);// original entity exists
if (null === $entity) {
$entity = new $className();// local entity not exists, create new one
}
$oldEntity = clone $entity;// make a detached "backup" of the entity before it's changed
// make some changes to the entity...
$entity->setX('Y');
// now compare entities properties/values
$entityCloned = clone $entity;// clone entity for detached (not persisted) entity comparaison
if ( ! $em->contains( $entity ) || $entityCloned != $oldEntity) {// do not compare strictly!
$em->persist( $entity );
$em->flush();
}
unset($entityCloned, $oldEntity, $entity);
Another possibility rather than compare objects directly:
<?php
// here again we need to clone the entity ($entityCloned)
$entity_diff = array_keys(
array_diff_key(
get_object_vars( $entityCloned ),
get_object_vars( $oldEntity )
)
);
if(count($entity_diff) > 0){
// persist & flush
}
It works for me
1. import EntityManager
2. Now you can use this anywhere into the class.
use Doctrine\ORM\EntityManager;
$preData = $this->em->getUnitOfWork()->getOriginalEntityData($entity);
// $preData['active'] for old data and $entity->getActive() for new data
if($preData['active'] != $entity->getActive()){
echo 'Send email';
}

Copy a Doctrine object with all relations

I want to copy a record with all his relations.
I'm trying with:
$o = Doctrine::getTable('Table')->Find(x);
$copy = $object->copy();
$relations = $o->getRelations();
foreach ($relations as $name => $relation) {
$copy->$relation = $object->$relation->copy();
}
$copy->save();
This code doesn't works, but I think it's on the way.
I never could get the deep copy function to operate correctly.
I manually coded a deep copy function for one of my models like this
public function copyAndSave ()
{
$filters = array('id', 'created');
$survey = $this->copy();
$survey->Survey_Entries = new Doctrine_Collection("Survey_Model_Entry");
$survey->Assignment_Assignments = new Doctrine_Collection("Assignment_Model_Assignment");
$survey->Survey_Questions = new Doctrine_Collection("Survey_Model_Question");
$survey->save();
foreach ($this->Survey_Questions as $question)
{
$answers = $question->Survey_Answers;
$newQuestion = $question->copy();
$newQuestion->survey_surveys_id = $survey->id;
$newQuestion->save();
$newAnswers = new Doctrine_Collection("Survey_Model_Answer");
foreach($answers as $answer)
{
$answer = $answer->copy();
$answer->save();
$answer->survey_questions_id = $newQuestion->id;
$newAnswers->add($answer);
}
$newQuestion->Survey_Answers = $newAnswers;
$survey->Survey_Questions->add($newQuestion);
}
return $survey->save();
}
You can read about copy() here. It takes an optional parameter $deep:
$deep
whether to duplicates the objects targeted by the relations
So
$copy = $object->copy(true);
should do it.
Sorry if I'm resurrecting this thread...
I found myself in search of a solution recently where I needed to copy a record and retain the references of the original. A deep copy $record->copy(true) copies the references, which was no good for me. This was my solution:
$record = Doctrine_Core::getTable('Foo')->find(1);
$copy = $record->copy();
foreach($record->getTable()->getRelations() as $relation) {
if ($relation instanceof Doctrine_Relation_Association) {
$ids = array();
foreach ($relation->fetchRelatedFor($record) as $r) {
$ids[] = $r->getId();
}
$copy->link($relation->getAlias(), $ids);
}
}
if ($copy->isValid()) {
$copy->save();
}
Hope this helps :)
This is how i done, but some fix is needed.
$table = $entidade->getTable();
$relations = $table->getRelations();
foreach($relations as $relation => $data) {
try {
$entity->loadReference($relation);
} catch(Exception $e) {
die($e->getMessage());
}
}
I am using Symfony1.4.1 and that uses Doctrine 1.2.1 (I think).
I have been trying to make a function that did all the above myself, when I found one that already exists.
Try this in any function and look at the results:
$tmp=$this->toArray(TRUE);
var_dump($tmp);
$this->refreshRelated();
$tmp=$this->toArray();
var_dump($tmp);
$tmp=$this->toArray(TRUE);
var_dump($tmp);
exit();
I am going to try two different things:
A/ put $this->refreshRelated() into the constructor of all my model objects.
B/ write a function that takes an array depicting the object graph that I want populated. Calling the function refereshRelatedGraph($objectGraphArray). With the right structure of the array (having all the appropriate relation names at each level), I could control which relations get populated and which don't. One use for this is to populate only children, not parent relations. The other is for when a ERD/Schema/ObjectGraph has an element that is 'owned' by more than one object (many to many, other special circumstances that I have), I could control which side of the relationships get pre(non lazy) loaded.

Categories