Doctrine 2 - Using a PersistentCollection - php

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.

Related

Returning a relationships results through static method

I'm trying to provide an extra static 'find' method on my eloquent model, shown here:
public static function findBySku($sku)
{
// Using new self; provides the same empty collection results
$instance = new static;
// Using $instance->sku()->newQuery()->get() also returns the same empty collection
$results = $instance->sku()->get();
/*
* This returns an empty collection, however there are records inside the
* relationship database table?
*/
dd($results);
}
So I can use: Inventory::findBySku($sku);
Here's the relationship:
public function sku()
{
return $this->hasOne('Stevebauman\Maintenance\Models\InventorySku', 'inventory_id', 'id');
}
I know the relationship itself isn't the issue because this returns the results from the database table fine:
Inventory::find(1)->sku()->get();
Anyone have any ideas why this doesn't work?
I know it could be because I'm calling a non-static method from a static instance, but why would it return a resulting collection without throwing an error?
Thanks!
Hang on, figured it out, apologies!
Eloquent relationships have a method getRelated() to access the related model instance. I can then call the methods I need off of it, for example:
public static function findBySku($sku)
{
$instance = new static;
// Using the getRelated() method allows me to run queries on the related model
$results = $instance->sku()->getRelated()->get();
dd($results);
}
Just sort of an odd workaround as you'd think accessing the relationship itself would give you the proper query.
I hope this helps out someone in the future!

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!)

Doctrine2 throwing 'No Entity in Identity Map'

I'm pulling a base entity out of the database, cloning it inside a loop to create new entities, changing some details and then persisting the entity.
For some reason this is throwing No Entity in Identity Map in addToIdentityMap in UnitOfWork when calling EntityManager->flush().
I've also tried creating an entity and manually setting all properties in one to the other, no difference.
I'm not particularly familiar with doctrine internals, whats going wrong here?
Try adding a __clone method to the entity class to null out the identifier so Doctrine recognizes it as a new entity:
public function __clone()
{
if ($this->id) {
$this->id = null;
}
}
Note the method is modeled after Doctrine's docs for safely implementing __clone

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.

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

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!

Categories