How to re-save the entity as another row in Doctrine 2 - php

Let's say I have entity $e. Is there any generic way to store it as another row, which would have the same entity data but another primary key?
Why I need this: I'm implementing some sort of Temporal Database schema and instead of updating the row I just need to create another one.

Try cloning and add the following method to your entity
public function __clone() {
$this->id = null;
}
You may need to detach the entity before persisting it. I don't have my dev machine handy to test this right now.
$f = clone $e;
$em->detach($f);
$em->persist($f);
$em->flush();
Update
Just tried using a simple SQLite demo. You shouldn't need to do anything. The following worked for me without adding a __clone() method or doing anything else out of the ordinary
$new = clone $old;
$em->persist($new);
$em->flush();
Once flushed, the $new entity had a new ID and was saved as a new row in the DB.
I would still null the ID property via the __clone() method as it makes sense from a pure model view.
Update 2
Digging into the Doctrine code, this is because the generated proxy classes implement __clone() with this important line
unset($this->_entityPersister, $this->_identifier);

I just do:
/**
* __clone
*
* #return void
*/
public function __clone()
{
$this->id = null;
}
More details here https://www.doctrine-project.org/projects/doctrine-orm/en/2.7/cookbook/implementing-wakeup-or-clone.html

Copying the data in a new Object of the same class and persisting it will do. Keep it simple!

Related

Overload the __clone method of an object to set its ID to null

I want to clone objects and save a duplicate in the database, but the problem is that the ID needs to be reset to null so that it can get the automatic sequence number in the database.
I am making use of Doctrine and Symfony2. I understand that I need to modify the __clone method of my entity, but I don't know how what to insert in it.
I have a basic clone Action:
public function cloneAction()
{
$object = $this->admin->getSubject();
if (!$object) {
throw new NotFoundHttpException(sprintf('unable to find the object with id : %s', $id));
}
// Be careful, you may need to overload the __clone method of your object
// to set its id to null !
$clonedObject = clone $object;
$this->admin->create($clonedObject);
$this->addFlash('sonata_flash_success', 'Cloned successfully');
return new RedirectResponse($this->admin->generateUrl('list', $this->admin->getFilterParameters()));
}
The documentation of Sonata says I have to do that, but I've never worked with __clone before, and I'm relative new with PHP. Any help will be nice please.
There was no problem. It was not necessary to overload the __clone method whatsoever. All I had to do was fixing my entity mappings and it worked.

Doctrine 2 - Using a PersistentCollection

I'm currently in the process of writing tests for some legacy code. One of the entities I'm working on contains an ArrayCollection of other entities in a One-to-Many relationship. When I load the data in my fixtures, I can see that the entities on the One side of the relationship correctly contain the entities on the Many side of the relationship using PhpStorm's debugger.
Apparently, an ArrayCollection becomes a PersistentCollection when it's persisted to the database.
The method I'm currently testing is attempting to iterate through this newly formed PersistentCollection with a foreach, but, well, nothing is happening. The collection, according to the debugger, contains relationship metadata, but doesn't retrieve the values I entered into the original ArrayCollection.
Example pseudo-code (due to a NDA):
class Entity1
{
private $items;
public function __construct() {
$this->items = new ArrayCollection();
}
public function addItem(Entity2 $item) {
$this->items[] = $item;
}
public function getItems() {
return $this->items;
}
}
class Entity2 {}
$child1 = new Entity2();
$child2 = new Entity2();
$child3 = new Entity2();
$ent = new Entity1();
$ent->addItem($child1);
$ent->addItem($child2);
$ent->addItem($child3);
// persist them all with the entity manager
// end data fixtures
// inside a different file, in the method I'm trying to test:
$items = $ent->getItems();
foreach ($items as $item) {
// nothing happens as $items contains metadata/relationship data, but doesn't (lazy) load
// the Entity2 instances
}
And here's an actual screenshot of what's in the PersistentCollection:
Note how the coll collection, which I assume is what should be filled with my Entity2 instances, is empty.
So, I'm at a bit of a loss. I need to be able to access the Entity2 instances, but it's not happening. Any ideas?
Add a cascade parameter to you OneToMany configuration. If using annotations:
/**
* #ORM\OneToMany(targetEntity="Entity2", mappedBy="entity1", cascade={"persist"})
*/
Adjust the mappedBy parameter to your actual implementation.
Figured it out.
Attempting to relate entities by adding Entity2 instances to Entity1's ArrayCollection wasn't working. None of the Entity2 instances had an id of an Entity1 as a foreign key. Doing it the opposite way - manually setting the Entity1 for every Entity2 - worked, and I can now iterate through the collection.
I'm not sure if that behavior - not being able to create/enforce/whatever a relation through an ArrayCollection is defined behavior or a bug, but I figure I should post my solution here in any event.

What is the expected return of the clone() method in PHP?

I am building a small framework, and I'm trying to implement a clone() method in some of my objects. The goal of this method is to create a fresh copy of a given record, making it easier to the user to create a new record based in another one.
It seemed to me a perfect scenario to use the clone() and __clone() PHP methods. But what exactly should be returned by calling clone()? When I create a clone, semms like the cloned object is the same as the original, even though I have changed some properties inside the __clone magic method.
Here is what I have done unitl now:
My application uses ascincronous communication, so there is a Service class, called by the API; This Service class creates a new instance of my Record class, and creates a clone. Inside this Record class, there is a implementation of the __clone magic method, to make some changes in the data, and save the new record.
the cloneRecord method inside Service Class:
public function cloneRecord($original_id) {
$originalObject = new Record($original_id);
$originalObject->load(); //access the database and retrieve the property values for this record
$cloned = clone $originalObject;
return $cloned->id; // here is the problem! See explanation below
}
the __clone method inside Record Class:
public function __clone() {
$cloned = new Record();
//id and code will be generated automatically in the save() method:
$cloned->id = NULL;
$cloned->code = NULL;
//these other properties will be cloned:
$cloned->name = $this->name;
$cloned->startDate = $this->startDate;
$cloned->dueDate = $this->dueDate;
$cloned->save();
}
Until this point, everything seems to work properly. The new Record is saved into the database, a new id and a new code is generated for this new Record.
How I call the method:
$service = new Service();
$newRecordId = $service->cloneRecord(200);
Here something strange happens!
What I expect to get from the line above is the id of the new Record (perhaps, 201). Instead, I receive the same original id (200).
Is this is the expected behaviour?
Check the manual page of __clone(). It says:
Once the cloning is complete, if a __clone() method is defined, then the newly created object's __clone() method will be called, to allow any necessary properties that need to be changed.
This means __clone() will run in the scope of the newly created object.
Your __clone() method should look like this:
public function __clone() {
//id and code will be generated automatically in the save() method:
$this->id = NULL;
$this->code = NULL;
// I would not put it here. It should happen explicitly (imo)
// But this design decision is up to you.
$this->save();
}

How to clear or empty the Array Collection in doctrine symfony

I have the Array collection of objects like this
Class User
{
private $tasks;
}
How can i empty or clear the collection once user gets loaded from database.
When i query for user then Doctrine will lazy load the tasks in user object but i want to first clear those tasks
something like
$user->getTasks().empty()
First of all, I imagine your User entity's constructor looks something like this:
class User
{
public function __construct()
{
...
$this->tasks = new \Doctrine\Common\Collections\ArrayCollection();
...
}
}
If that's not correct so far, then stop reading, and correct me in the comments :)
Note that the ArrayCollection class was created by Doctrine. Symfony and most of its components are pretty good about documenting the classes. When you look up that class, you'll find:
https://www.doctrine-project.org/api/collections/latest/Doctrine/Common/Collections/ArrayCollection.html
(of course, make sure you're on the same version; otherwise, try to find the documentation for your version)
The documentation lists all the methods available to the ArrayCollection object. Among them: clear().
That said, adding a new method to the User class should work:
class User
{
public function clearTasks()
{
$this->getTasks()->clear();
}
}
Then, on the User object, just call:
$user->clearTasks();
(and don't forget to persist to the database!)

How to check if entity changed in Doctrine 2?

I need to check if a persisted entity has changed and needs to be updated on the database.
What I made (and did not work) was the following:
$product = $entityManager->getRepository('Product')->find(3);
$product->setName('A different name');
var_export($entityManager->getUnitOfWork()->isScheduledForUpdate($product));
That code prints always false, I also tried to flush before check the unit of work, but did not work.
Anyone has a suggestion?
The first thing I'd check it that your setName function is actually doing something ($this-> name = $name...) If it's already working, then you could define an event listener on your services.yml that is triggered when you call the flush.
entity.listener:
class: YourName\YourBundle\EventListener\EntityListener
calls:
- [setContainer, ["#service_container"]]
tags:
- { name: doctrine.event_listener, event: onFlush }
Then you define the EntityListener
namespace YourName\YourBundle\EventListener;
use Doctrine\ORM\Event;
use Symfony\Component\DependencyInjection\ContainerAware;
class EntityListener extends ContainerAware
{
/**
* Gets all the entities to flush
*
* #param Event\OnFlushEventArgs $eventArgs Event args
*/
public function onFlush(Event\OnFlushEventArgs $eventArgs)
{
$em = $eventArgs->getEntityManager();
$uow = $em->getUnitOfWork();
//Insertions
foreach ($uow->getScheduledEntityInsertions() as $entity) {
# your code here for the inserted entities
}
//Updates
foreach ($uow->getScheduledEntityUpdates() as $entity) {
# your code here for the updated entities
}
//Deletions
foreach ($uow->getScheduledEntityDeletions() as $entity) {
# your code here for the deleted entities
}
}
}
If you need to know which entities are being changed, but do something with them after they've been saved to the database, just store the entities changed in a private array, an then define a onFlush event that gets the entities from the array.
BTW, to trigger this kind of events you need to add the #ORM\HasLifecycleCallbacks on the entity.
I didn't need/want to create Listeners for my case so I ended up with
$product->setName('A different name');
$uow = $entityManager->getUnitOfWork();
$uow->computeChangeSets();
if ($uow->isEntityScheduled($product)) {
// My entity has changed
}
Doctrine2 Docs. 17. Change Tracking Policies
If you use third form (17.3. Notify) as i do, you can test if your entity is changed doing:
$uow = $entityManager->getUnitOfWork();
$uow->computeChangeSets();
$aChangeSet = $uow->getEntityChangeSet($oEntity);
If nothing changed it will return blank array.
You may also want to look at the PreUpdate event, if you need access to entity fields with their old and new values.
A bit of an example mostly taken from the link provided:
<?php
class NeverAliceOnlyBobListener
{
public function preUpdate(PreUpdateEventArgs $eventArgs)
{
if ($eventArgs->getEntity() instanceof User) {
if ($eventArgs->hasChangedField('name') && $eventArgs->getNewValue('name') == 'Alice') {
$oldValue = $eventArgs->getOldValue('name');
$eventArgs->setNewValue('name', 'Bob');
}
}
}
}
If you only need to compare old and new state of object then probably this would be simpler:
$originalEntityData = $entityManager->getUnitOfWork()->getOriginalEntityData($entityObject);
The issue is quite old but there may be still some group of people that might face this problem from a different point of view.
The UnitOfWork works great but it only returns the array of changes. It can be a pain in butt when someone doesn't actually knows which fields may have changed and just wants to get the whole entity as an object to compare $oldEntity and $newEntity. Even though the event's name is preUpdate if someone will try to fetch the data from the database as follows:
$er->find($id);
the returned entity will contain all changes.
The workaround is quite simple but it has some hooks:
public function preUpdate(Entity $entity, PreUpdateEventArgs $args)
{
$entity = clone $entity; //as Doctrine under the hood
//uses reference to manage entities you might want
//to work on the entity copy. Otherwise,
//the below refresh($entity) will affect both
//old and new entity.
$em = $args->getEntityManager();
$currentEntity = $em->getRepository('AppBundle:Entity')->find($entity->getId());
$em->refresh($currentEntity);
}
For those who are using another event, like preFlush, I've quickly checked it and the workaround didn't work well because probably the refresh() method discards any flush changes so what needs to be done is to call the flush once again in listener and create some static $alreadyFlushed toggle to avoid circular reference.
Based on my needs, answers here and the docs, I came up with the following solution for a modifiedAt timestamp in an Entity.
/**
* #Doctrine\ORM\Mapping\PreUpdate()
*
* #param \Doctrine\ORM\Event\PreUpdateEventArgs $args
* #return $this
*/
public function preUpdateModifiedAt(\Doctrine\ORM\Event\PreUpdateEventArgs $args)
{
$this->setModifiedAt(new \DateTime('now'));
return $this;
}
This is based on what the docs say about this Event as opposed to the other available ones, such as PostPersist and PreFlush:
PreUpdate is the most restrictive to use event, since it is called
right before an update statement is called for an entity inside the
EntityManager#flush() method. Note that this event is not triggered
when the computed changeset is empty.
Using PreUpdate as opposed to the others lets you leave all the computations and calculation intensive functions to the process already defined by Doctrine. Manually triggering computation of changesets, such as in these answers above are server CPU intensive. The onFlush Event, such as used in the accepted answer is an option (in the way demonstrated), but not if you rely on detecting a change to the Entity, as you can with the function above (preUpdateModifiedAt(PreUpdateEventArgs $args)).
I agree with #Andrew Atkinson when he said:
You may also want to look at the PreUpdate event, if you need
access to entity fields with their old and new values.
But I disagree with the example he proposed, from my experience, there is a better way to check if something changed or not.
<?php
class Spock
{
public function preUpdate(PreUpdateEventArgs $eventArgs)
{
if (!empty($eventArgs->getEntityChangeSet())) {
// fill this how you see fit
}
}
}
This way the if will only be triggered if there is really some field that changed or not.
As to how to do it if this or that field was changed, then yeah, I recommend his solution.
I am curious about Doctrine and everyone documenting postFlush, as in some case, you have an ongoing transaction.
I'd like to point out there's also postTransactionCommit, which could be safer depending on what you're trying to achieve in the postFlush event.

Categories