Doctrine2: How to handle missing relation documents - php

within a huge dataset I sometimes get inconsistencies when one document is deleted. Symfony2 App with Doctrine ODM and FosREST
$a = new Element();
$b = new Element();
$c = new List();
$c->addElement($a);
$c->addElement($b);
$em->persist($c);
saving at this point works flawlessly
in 99% of the cases $a and $b are still valid Documents when $c is loaded later.
BUT sometimes either $a or $b is deleted without updating the reference in $c.
-> at this moment the next loading of $c will fail with a \Doctrine\ODM\MongoDB\DocumentNotFoundException
(message is something like: The "MongoDBODMProxies__CG__\App\Model\Element" document with identifier "541417702798711d2900607c" could not be found.)
What is the best approach now to handle this case?
I was thinking about either
catching the Exception and to check if the reference it tried to load was on the Element Model
custom exception Handler in fosRest to check for
custom repository function in the mapping and to check there if everything is still valid (+ to store somehow that there is a missing Element) -> but this then forces me to check on every occasion if the "error" is set
UPDATE: The Mapping between the Documents is a bit more complex than I described here
for one the element is basically a collection separated by a discriminator, where only one type of fields references another document (I call it "Tree" now)
a tree can be used in thousands of ElementTree's (that specific type that contains a Tree)
sometimes Tree's can be deleted (this is already a slow running process since a lot of data needs to be updated then)
I would now need to find out what Lists need to change and basically reject the api calls to those lists with the information that a specific element is no longer available.

A few things to check especially for MongoDB:
Make sure that there are no circular references (for example if you have the property $elements on the class List and references set to true on it, make sure List is not referenced on the Elements class as well) and your mappings are consistent.
In the addElement function IF the reference is held on the Element class make sure you also call $element->setList($this) inside the function. (and the same for removeElement, unset the reference if neccessary)
Make sure you cascade all the necessary operations. (For example cascade : ["persist", "delete", "refresh" or "all" ]
You can check your mappings with
$ app/console doctrine:mongodb:mapping:info
Finally if you expect that document to be deleted but you get an error from the proxy object you can clear the metadata cache
$ app/console doctrine:mongodb:cache:clear-metadata

Inperfect Solution that works for now
I now chose to throw a new Exception (it is important not let doctrine throw one because it will reject then any persist attempts in the same request).
In the PostLoad LifecycleEvent I check now the following (simplified):
if ($document instanceof List) {
foreach ($document->getElements() as $element) {
// at this moment $element->getId() is already defined but not yet loaded from mongo
$result = $this->elementRepository->findBy(array(‘_id’ => $element->getId()));
if (sizeof($result)==0) {
throw new InvalidElementInList($element->getId());
}
}
}
in the RestController this enables me now to catch this specific exception and to remove the invalid element from the list + to return a custom view to the user indicating that the element was removed.

Related

How to get the created object in controller when implementing CQRS

I am evaluating the CQRS pattern and wonder what would be the best way to obtain an Entity created by a command in the same action so I can render it in the view.
The two options I can think of are.
1) Create an id in the controller and send it with the command to fetch the entity by finding it by id.
2) Create an instance of the entity and send it with the command so I have a reference to it after it's populated
Example code
public function createEntityAction(array $data) {
$eventDispatcher = $this->get('event_dispatcher');
$eventDispatcher->dispatch(
CreateEntityHandler::name, // Handler
new Entity($data) // Command
);
// Placeholder //
$entity = get-the-created-entity
// //
return $this->view($entity, Response::HTTP_OK);
}
Second option is not really an option. "Entity creation", which is in fact is a business operation, is a command handling.
Generally speaking, the one who sends a command, whose handler creates an entity, should send the entity id with it. In what way the identity is generated is just an implementation concern.
Usually, command handlers either do what they suppose to do and return nothing (or ACK) or throw (or NAK).

Scheduled entity in onFlush is different instance

I have a strange problem with \Doctrine\ORM\UnitOfWork::getScheduledEntityDeletions used inside onFlush event
foreach ($unitOfWork->getScheduledEntityDeletions() as $entity) {
if ($entity instanceof PollVote) {
$arr = $entity->getAnswer()->getVotes()->toArray();
dump($arr);
dump($entity);
dump(in_array($entity, $arr, true));
dump(in_array($entity, $arr));
}
}
And here is the result:
So we see that the object is pointing to a different instance than the original, therefore in_array no longer yields expected results when used with stick comparison (AKA ===). Furthermore, the \DateTime object is pointing to a different instance.
The only possible explanation I found is the following (source):
Whenever you fetch an object from the database Doctrine will keep a copy of all the properties and associations inside the UnitOfWork. Because variables in the PHP language are subject to “copy-on-write” the memory usage of a PHP request that only reads objects from the database is the same as if Doctrine did not keep this variable copy. Only if you start changing variables PHP will create new variables internally that consume new memory.
However, I did not change anything (even the created field is kept as it is). The only operations that were preformed on entity are:
\Doctrine\ORM\EntityRepository::findBy (fetching from DB)
\Doctrine\Common\Persistence\ObjectManager::remove (scheduling for removal)
$em->flush(); (triggering synchronization with DB)
Which leads me to think (I might be wrong) that the Doctrine's change tracking method has nothing to do with the issue that I'm experiencing. Which leads me to following questions:
What causes this?
How to reliably check if an entity scheduled for deletion is inside a collection (\Doctrine\Common\Collections\Collection::contains uses in_array with strict comparison) or which items in a collection are scheduled for deletion?
The problem is that when you tell doctrine to remove entity, it is removed from identity map (here):
<?php
public function scheduleForDelete($entity)
{
$oid = spl_object_hash($entity);
// ....
$this->removeFromIdentityMap($entity);
// ...
if ( ! isset($this->entityDeletions[$oid])) {
$this->entityDeletions[$oid] = $entity;
$this->entityStates[$oid] = self::STATE_REMOVED;
}
}
And when you do $entity->getAnswer()->getVotes(), it does the following:
Load all votes from database
For every vote, checks if it is in identity map, use old one
If it is not in identity map, create new object
Try to call $entity->getAnswer()->getVotes() before you delete entity. If the problem disappears, then I am right. Of cause, I would not suggest this hack as a solution, just to make sure we understand what is going on under the hood.
UPD instead of $entity->getAnswer()->getVotes() you should probably do foreach for all votes, because of lazy loading. If you just call $entity->getAnswer()->getVotes(), Doctrine probably wouldn't do anytning, and will load them only when you start to iterate through them.
From the doc:
If you call the EntityManager and ask for an entity with a specific ID twice, it will return the same instance
So calling twice findOneBy(['id' => 12]) should result in two exact same instances.
So it all depends on how both instances are retrieved by Doctrine.
In my opinion, the one you get in $arr is from a One-to-Many association on $votes in the Answer entity, which results in a separate query (maybe a id IN (12)) by the ORM.
Something you could try is to declare this association as EAGER (fetch="EAGER"), it may force the ORM to make a specific query and keep it in cache so that the second time you want to get it, the same instance is returned ?
Could you have a look at the logs and post them here ? It may indicates something interesting or at least relevant to investigate further.

Doctrine2 - Best way for retrieving two differents objects of a same entity

I'm working on a tool for concurrency with Doctrine 2.
I'm facing a "best practice" issue to retrieve a new instance of an entity without cache (the idea after that is to be able to compare some properties from 2 differents objects of the same entity and return the differences)
Some code might help (+ the doc: (http://doctrine-orm.readthedocs.org/projects/doctrine-orm/en/latest/reference/transactions-and-concurrency.html)):
// This is my current implementation.
$entity = $em->find(1);
$entity->setName('TEST');
// This entity as a "version" field equal to 2 in DB for example
try {
$em->lock($entity, LockMode::OPTIMISTIC, 1); // Will throw an OptimisticLockException
} catch(OptimisticLockException $e) {
$em->detach($entity);
$dbEntity = $this->find($entity->getId());
$em->detach($dbEntity);
$entity = $em->merge($entity);
var_dump($entity->getName()); // TEST
var_dump($dbEntity->getName()); // The old value
... do more stuff, like comparing the two objects ...
}
Is using the detach + merge methods a good practice for this behavior ? Any better idea to improve this code ?
--
Edit 1:
Actually, after adding some tests, the "merge" method is not what I expected: the object is not "re-attach" to the unit of work.
This behaviour is not what I want because the developer can't performs changes + flush on his entity after using my tool.
--
Edit 2:
After digging in the documentation and the source code, the "merge" method is actually what I wanted: a new instance of the entity is attached, not the one I provided ($entity in my example).
Since this code (In my tool) is in a method which purpose is to returns the $dbEntity object of my $entity object, passing the $entity reference (&$entity) solve my "Edit 1" issue.

Doctrine2: Dependency graph for a set of entities

Using Symfony and Doctrine, I’m writing a component that populates the database with entries of arbitrary entities during the deployment of an application.
I want to make this component generic, so I’m trying to resolve dependencies between entities automatically and insert them in the correct order.
I am currently get the dependencies of each single entity through the entity metadata, recursively:
public function getEntityDeps($eName)
{
$deps = [];
foreach ($this->entityManager->getClassMetadata($eName)->getAssociationMappings() as $mapping)
{
$deps[] = $mapping['targetEntity'];
$deps = array_merge($deps, $this->getEntityDeps($mapping['targetEntity']));
}
return $deps;
}
The result is obviously a list of the following kind:
// NOTE: The real list of course contains class names instead of entity aliases.
[
"FooBundle:EntityA" => [],
"FooBundle:EntityB" => ["FooBundle:EntityA", "FooBundle:EntityC"],
"FooBundle:EntityC" => ["FooBundle:EntityA"],
"BarBundle:EntityA" => ["BarBundle:EntityB"],
"BarBundle:EntityB" => []
]
The next step would be apply some type of topological sorting to the list, I guess.
However, I’m not sure if I can use a generic algorithm here or if I’m forgetting something. Especially as entities are not necessarily related (so that, in fact, we may have more than one dependency graph).
Also, I’d hope that there’s some internal Doctrine functionality which can do the sorting for me.
So what would be the most reliable way to sort an arbitrary set of Doctrine entities? Is there some reusable Doctrine feature?
So, after digging through the Doctrine source code for a while, I found that they have indeed an implementation of topological sorting (DFS for that matter). It's implemented in Doctrine\ORM\Internal\CommitOrderCalculator.
And where is this CommitOrderCalculator used? In UnitOfWork, of course.
So, instead of manually calculating the correct commit order, I just needed … to move the call to $em->flush() outside the foreach loop that went through the entities and let UOW do the work itself.
Duh.

Save object with original data after form bind

I want to make a single revision option for saving certain objects in Sonata Admin.
I though to do this in the following way:
user edits entry
form is validated
the new information is saved as a separate entry (i'll call it revision)
the original object is not modified, except for a relation to the revision
So the code looks something like this (source Sonata\AdminBundle\Controller\CRUDController::editAction()):
$object = $this->admin->getObject($id);
$this->admin->setSubject($object);
$form = $this->admin->getForm();
$form->setData($object);
$form->bind($this->get('request')); // does this persist the object ?
// and here is what I basically want to do:
$object->setId(null);
$orig = $em->getRepository("MedtravelClinicBundle:Clinic")->find($id);
$orig->setRevision($object);
$this->admin->update($orig);
The problem is that $orig loads the already modified, so var_dump($orig === $object) is true.
I also tried $em->getUnitOfWork()->getOriginalEntityData($object); - which grabs the correct data, but as an array, not as an object (this will probably be the last resort).
So, how can I get (and save) the original object after the form bind took place ?
I think you should use the clone keyword to get a independent instance of the object you want to store. It should works by following these steps:
Load the original entity ($object)
Clone the original entity to get a new temporary entity ($newObject)
Alter the $newObject to make it a new entry: $newObject->setId(null);
Bind $newObject to the form
Save (persist) $newObject as a revision
Add the revision to ($object) and persist it too
I hope that if the form is invalid you won't lose all the data sent by the user.
Just in case, I used this answer to find the differences between the original entity and the one modified by the form.

Categories