Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

IdentityMap #10868

Open
tasselchof opened this issue Aug 2, 2023 · 14 comments
Open

IdentityMap #10868

tasselchof opened this issue Aug 2, 2023 · 14 comments

Comments

@tasselchof
Copy link

tasselchof commented Aug 2, 2023

Bug Report

Q A
Version 2.16.0

Summary

I am getting an exception about entity in identity map

Current behavior

While adding an entity of class Proxy was already present for the same ID. This exception\nis a safeguard against an internal inconsistency - IDs should uniquely map to\nentity object instances. This problem may occur if:\n\n- you use application-provided IDs and reuse ID values;\n- database-provided IDs are reassigned after truncating the database without \n clearing the EntityManager;\n- you might have been using EntityManager#getReference() to create a reference \n for a nonexistent ID that was subsequently (by the RDBMS) assigned to another \n entity. \n\nOtherwise, it might be an ORM-internal inconsistency, please report it.

How to reproduce

I am not sure how to reproduce it, but I am getting this in that case:

I am getting some values from database with native query (I am using composite keys) and generating reference from it (I can fetch from database this value - no matter, everything is fine):

$productOffer = $this->getObjectManager()->getReference(
    Offer::class,
    [
        'shop' => $locationLog['sclr_0'],
        'id'   => $locationLog['sclr_1'],
    ]
);

I am using this reference in further request and if I generate spl_object_id it's always same object. But later I am fetching one another entity from database:

$acceptanceItem = $this->getObjectManager()->getRepository(
    Acceptance\Item::class
)->find($acceptanceItem);

This entity contains property productOffer with is linked with the same ids (so technically it contains same object).

And if I am checking spl_object_id($acceptanceItem->getProductOffer()) it's different object.

I am using the value of this object in preFlush to populate search index:

public function preFlush(PreFlushEventArgs $args)
    {
        $extendedValues = [];
        if (! empty($this->getProductOffer())) {
            $productOffer = $this->getProductOffer();

            $extendedValues[] = $productOffer->getName();
            $extendedValues[] = $productOffer->getSku();
            $extendedValues[] = $productOffer->getArticle();

And exactly here I am getting this exception:

#0  Doctrine\ORM\UnitOfWork->addToIdentityMap() called at [/home/dev/domains/alpha.eu/vendor/doctrine/orm/lib/Doctrine/ORM/UnitOfWork.php:3399]
#1  Doctrine\ORM\UnitOfWork->registerManaged() called at [/home/dev/domains/alpha.eu/vendor/doctrine/orm/lib/Doctrine/ORM/Internal/Hydration/AbstractHydrator.php:692]
#2  Doctrine\ORM\Internal\Hydration\AbstractHydrator->registerManaged() called at [/home/dev/domains/alpha.eu/vendor/doctrine/orm/lib/Doctrine/ORM/Internal/Hydration/SimpleObjectHydrator.php:169]
#3  Doctrine\ORM\Internal\Hydration\SimpleObjectHydrator->hydrateRowData() called at [/home/dev/domains/alpha.eu/vendor/doctrine/orm/lib/Doctrine/ORM/Internal/Hydration/SimpleObjectHydrator.php:65]
#4  Doctrine\ORM\Internal\Hydration\SimpleObjectHydrator->hydrateAllData() called at [/home/dev/domains/alpha.eu/vendor/doctrine/orm/lib/Doctrine/ORM/Internal/Hydration/AbstractHydrator.php:270]
#5  Doctrine\ORM\Internal\Hydration\AbstractHydrator->hydrateAll() called at [/home/dev/domains/alpha.eu/vendor/doctrine/orm/lib/Doctrine/ORM/Persisters/Entity/BasicEntityPersister.php:779]
#6  Doctrine\ORM\Persisters\Entity\BasicEntityPersister->load() called at [/home/dev/domains/alpha.eu/vendor/doctrine/orm/lib/Doctrine/ORM/Persisters/Entity/BasicEntityPersister.php:789]
#7  Doctrine\ORM\Persisters\Entity\BasicEntityPersister->loadById() called at [/home/dev/domains/alpha.eu/vendor/doctrine/orm/lib/Doctrine/ORM/Proxy/ProxyFactory.php:215]
#8  Doctrine\ORM\Proxy\ProxyFactory->Doctrine\ORM\Proxy\{closure}()
#9  Closure->__invoke() called at [/home/dev/domains/alpha.eu/data/DoctrineORMModule/Proxy/__CG__OrderadminProductsEntityProductOffer.php:318]

Expected behavior

I suppose should be the same object as it is already loaded before flush.

@greg0ire
Copy link
Member

greg0ire commented Aug 3, 2023

Please learn how to format Github messages.

@derrabus
Copy link
Member

derrabus commented Aug 3, 2023

Please learn how to format Github messages.

I've fixed that for you. 😉

@derrabus
Copy link
Member

derrabus commented Aug 3, 2023

Looks like two proxies have been created for the same entity and one of the proxies is being resolved during the preFlush event in your case. This looks like an interesting edge case of your identity map corruption detection, @mpdude. Can you take a look?

@tasselchof
Copy link
Author

Please learn how to format Github messages.

I m sorry, I was very limited in time, but I thought it’s important to create an issue because it might impact not only our software.

This is not in our code but this is something, on the first look, that os affecting doctrine itself.

I think if I will do simple test case: pull Offer from database and after this try to pull same one from proxy it will be the same problem.

mpdude added a commit to mpdude/doctrine2 that referenced this issue Aug 3, 2023
@mpdude
Copy link
Contributor

mpdude commented Aug 3, 2023

I don't fully understand how to reproduce the issue.

I am getting some values from database with native query (I am using composite keys) and generating reference from it.
[...] I am using this reference in further request and if I generate spl_object_id it's always same object. But later I am fetching one another entity from database. [...] This entity contains property productOffer with is linked with the same ids (so technically it contains same object).
And if I am checking spl_object_id($acceptanceItem->getProductOffer()) it's different object.

I have tried to put that into #10873, but that's OK so far.

So, what is the missing piece?

@tasselchof
Copy link
Author

@mpdude I tried to play a bit with reproducing an issue and rebuild annotations to be closer to live code.

The problem is that this part of the code is set to refactoring (it's very old and bad done), but it's causing this issue and I want to try to figure out why. There is 3 blocks in one function. First block writing location logs, second block updating barcodes of the offer and third is updating quantity in item. If I comment block 1 or 2 everything is fine. They just can't work together.

Adding this reference in any part of the code causing this exception as well.

@tasselchof
Copy link
Author

tasselchof commented Aug 4, 2023

There is something here... I found another case in the code. Worked like that: there is a part of logic that do some modifications and flushes the result. After flush getting reference causing this exception. And I broke down this to one case: I have order that has collection of orderProducts, each of those has a reference to Offer. So If I do like that:

echo $order->getOrderProducts()->count();

$productOffer = $this->getObjectManager()->getReference(
                    Offer::class,
                    [
                        'shop' => $shop,
                        'id'   => $result['productoffer_id']
                    ]
                );

                if ($productOffer->getType() != Offer::TYPE_BUNDLE) {
                    continue;
                }

$productOffer->getType() causing and exception ($productOffer is a proxy).

tasselchof added a commit to tasselchof/doctrine2-orm that referenced this issue Aug 4, 2023
@tasselchof
Copy link
Author

tasselchof commented Aug 4, 2023

@mpdude can you take a look? productOffer that I got from the collection is not Proxy, but reference is proxy, test is here: tests/Doctrine/Tests/ORM/Functional/Ticket/GH10868Test_2.php. Maybe it's something?

@mpdude
Copy link
Contributor

mpdude commented Aug 5, 2023

@tasselchof #10873 contains the code as you suggest.

$reference is a reference to an instance of GH10868Offer, whereas $order->orderProducts->first() is an instance of GH10868OrderProduct.

To me, this seems logically wrong, so no surprise the test is failing.

mpdude added a commit to mpdude/doctrine2 that referenced this issue Aug 5, 2023
@tasselchof
Copy link
Author

@mpdude no, there is nothing wrong in logic, check the assertion:

self::assertSame($reference, $orderProductFromOrder->productOffer);

I am taking first order product and comparing reference and productOffer of first orderProduct. They should be the same. But they are not.

@mpdude
Copy link
Contributor

mpdude commented Aug 5, 2023

@tasselchof Let's continue the discussion in #10873, so we're closer to the code and can make sure we're on the same page.

tasselchof added a commit to tasselchof/doctrine2-orm that referenced this issue Aug 5, 2023
tasselchof added a commit to tasselchof/doctrine2-orm that referenced this issue Aug 5, 2023
@tasselchof
Copy link
Author

tasselchof commented Aug 5, 2023

@mpdude I've found how this is happening (spoiler: I've updated test in it passing well). I have in Shop entity __toString magic method where I am returning "name" of the shop (if it is set). So when UnitOfWork here:

final public static function getIdHashByIdentifier(array $identifier): string
{
return implode(
' ',
array_map(
static function ($value) {
if ($value instanceof BackedEnum) {
return $value->value;
}
return $value;
},
$identifier
)
);
}
trying to get key (in my case composite key) it is getting something like "123 Store" instead of "123 1" like it should. Such a weird id is missing in the identity map and this is leading to attempt to register it once more.

And removing magic method causing this:

Error: Object of class DoctrineORMModule\Proxy\__CG__\O\Entity\Shop could not be converted to string in file /home/dev/domains/alpha/vendor/doctrine/orm/lib/Doctrine/ORM/UnitOfWork.php on line 1697
Stack trace:
  1. Error->() /home/dev/domains/alpha/vendor/doctrine/orm/lib/Doctrine/ORM/UnitOfWork.php:1697

So this is a specific issue for an entity, that has a composite key and one of the composite key components is an object.

@conradfr
Copy link

conradfr commented Aug 8, 2023

FWIW I get this error when I try to create an entity in a submit form in Symfony where I create the object before and set the id (manual id).

The code has not changed since "composer upgrade" and was working before.

Downgrading to doctrine/orm 1.15.5 did the trick.

Something like

        $greatEntity = new GreatEntity();
        $greatEntity->setId($id);

        $form = $this->createForm(GreatEntityType::class, $greatEntity);
        $form->handleRequest($request);

        if ($form->isSubmitted() && $form->isValid()) {
            $em->persist($form->getData());
            $em->flush();
        }

@mpdude
Copy link
Contributor

mpdude commented Aug 9, 2023

@conradfr the error message may be the same, but since you’re using application-provided IDs it seems to me this is a slightly different case.

Please open a dedicated issue (or better, PR) and provide a test case to reproduce. Have a look at the GHxxxTest.php functional test cases in this repo, that should help you get going. I don’t think (at this time) Symfony has anything to do with it. Leave those parts (form handling etc) out.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

5 participants