Hey Arthur-E,
Thank you for your interest in SymfonyCasts tutorials!
We didn't cover Doctrine's low-level features like UnitOfWork in this course on purpose because it's out of scope here. We're mostly talking ...
Validating how Values Change
... with validation... but with the help of a special
class from Doctrine called the UnitOfWork.
Alrighty, let's whip up a test to shine a light on this pesky bug. Inside
tests/Functional/, open UserResourceTest. Copy the ...
|
// ... lines 1 - 10
|
|
class TreasuresAllowedOwnerChangeValidator extends ConstraintValidator |
|
{ |
|
// ... lines 13 - 16
|
|
public function validate($value, Constraint $constraint) |
|
{ |
|
// ... lines 19 - 27
|
|
$unitOfWork = $this->entityManager->getUnitOfWork(); |
|
foreach ($value as $dragonTreasure) { |
|
assert($dragonTreasure instanceof DragonTreasure); |
|
|
|
$originalData = $unitOfWork->getOriginalEntityData($dragonTreasure); |
|
dd($dragonTreasure, $originalData); |
|
} |
|
// ... lines 35 - 39
|
|
} |
|
} |
See Code Block in Script
|
// ... lines 1 - 10
|
|
class TreasuresAllowedOwnerChangeValidator extends ConstraintValidator |
|
{ |
|
// ... lines 13 - 16
|
|
public function validate($value, Constraint $constraint): void |
|
{ |
|
// ... lines 19 - 27
|
|
$unitOfWork = $this->entityManager->getUnitOfWork(); |
|
foreach ($value as $dragonTreasure) { |
|
// ... lines 30 - 31
|
|
$originalData = $unitOfWork->getOriginalEntityData($dragonTreasure); |
|
$originalOwnerId = $originalData['owner_id']; |
|
$newOwnerId = $dragonTreasure->getOwner()->getId(); |
|
|
|
if (!$originalOwnerId || $originalOwnerId === $newOwnerId) { |
|
return; |
|
} |
|
|
|
|
|
$this->context->buildViolation($constraint->message) |
|
->addViolation(); |
|
} |
|
} |
|
} |
See Code Block in Script
|
// ... lines 1 - 10
|
|
class TreasuresAllowedOwnerChangeValidator extends ConstraintValidator |
|
{ |
|
// ... lines 13 - 16
|
|
public function validate($value, Constraint $constraint) |
|
{ |
|
// ... lines 19 - 27
|
|
$unitOfWork = $this->entityManager->getUnitOfWork(); |
|
foreach ($value as $dragonTreasure) { |
|
assert($dragonTreasure instanceof DragonTreasure); |
|
|
|
$originalData = $unitOfWork->getOriginalEntityData($dragonTreasure); |
|
$originalOwnerId = $originalData['owner_id']; |
|
$newOwnerId = $dragonTreasure->getOwner()->getId(); |
|
|
|
if (!$originalOwnerId || $originalOwnerId === $newOwnerId) { |
|
return; |
|
} |
|
|
|
|
|
$this->context->buildViolation($constraint->message) |
|
->addViolation(); |
|
} |
|
} |
|
} |
See Code Block in Script
Wall Time Exclusive Time Other Wonders
... not very useful: the top item is where our script starts executing,
the second is the next function call, and so on. Go back to exclusive.
So apparently, the biggest problem, according to exclusive time, is this
UnitOfWork ...
Simpler Validator for Checking State Change
... #[TreasuresAllowedOwnerChange].
In the last tutorial, we put this above that same $dragonTreasures property,
but inside the User entity. The validator would loop over each DragonTreasure,
use Doctrine's UnitOfWork to get the $originalOwnerId ...
In that case, you can get the UnitOfWork out of the `EntityManager` and add the object manually to the identityMap
`$entityManager->getUnitOfWork()->addToIdentityMap($object);`
Cheers! ...
|
// ... lines 1 - 10
|
|
class TreasuresAllowedOwnerChangeValidator extends ConstraintValidator |
|
{ |
|
// ... lines 13 - 16
|
|
public function validate($value, Constraint $constraint) |
|
{ |
|
// ... lines 19 - 24
|
|
|
|
assert($value instanceof Collection); |
|
|
|
$unitOfWork = $this->entityManager->getUnitOfWork(); |
|
// ... lines 29 - 39
|
|
} |
|
} |
See Code Block in Script
The Patterns Behind Doctrine
... And that's no, no
that's intentional because basically the EntityManager will pass its called to
the UnitOfWork and the UnitOfWork will store all those things.
So when we pass in the Order, the UnitOfWork will figure out ...
Running Code On Publish
...
of DragonTreasure objects, loop over them, then use Doctrine's UnitOfWork to see
what each DragonTreasure looked like when it was originally loaded
from the database.
Should we use that same trick here to see what the ...
Finding Issues via the Call Graph
There are two different ways to optimize any function: either optimize the
code inside that function or you can try to call the function less times. In
our case, we found that the most problematic function is UnitOfWork ...
... UnitOfWork. Unless you have some customization, the class that should be handling that is this one: https://github.com/api-platform/core/blob/master/src/Bridge/Doctrine/Orm/ItemDataProvider.php#L68 - I would put some dump ...
... ": "Doctrine\\ORM",
"short_class": "UnitOfWork",
"class": "Doctrine\\ORM\\UnitOfWork",
"type": "->",
"function": "doRemove",
"file": "/Users/me/Documents/Projects/api-platform/vendor/doctrine/orm ...
... //www.doctrine-project.org/projects/doctrine-orm/en/3.0/reference/unitofwork-associations.html#:~:text=The%20owning%20side%20has%20to,side%20of%20a%20bidirectional%20association.)
> Own which side of the ownership can an ...
... Queries"? I mean like we do have $unitOfWork where we work with "entityInsertions" | "entityUpdates" | "entityDeletions" or $metadataFactory in Doctrine and there are loads of other complex obstacles when you are working ...
... believe, NOT have been modified yet: only the input class will be modified at this point. And so, believe the logic actually becomes a bit simpler. To get the "original value", instead of using using Doctrine's UnitOfWork ...
... "forgets" that it knows about it. Specifically, the UnitOfWork keeps track of every entity that it has queried for or saved during a request. The error we're seeing (I believe) is that you've queried for this `CartProduct ...
... Doctrine\ORM\UnitOfWork->assertThatThereAreNoUnintentionallyNonPersistedAssociations()
(vendor/doctrine/orm/lib/Doctrine/ORM/UnitOfWork.php:364)
at Doctrine\ORM\UnitOfWork->commit(null)
(vendor/doctrine/orm ...
22
UnitOfWork
Filter Results