-
Notifications
You must be signed in to change notification settings - Fork 2.6k
Closed
Description
Bug Report
| Q | A |
|---|---|
| Version | 3.3.1 |
| Previous Version if the bug is a regression | 3.3.0 |
Summary
A regression was introduced by this commit:
https://github.com/doctrine/orm/commit/439b4dacf415b743b0d40d65c490a4123759c520/#diff-ecc4306d602db732bf06eda6ac0fc41b23fe8a25f4a2548196f813f30dd2ac35R1277
- Have an entity with a one-to-many relation
- Define index-by in the mapping
- Within the same UnitOfWork perform (Edit: this probably happens every time an entity is fetched for a second time as part of a collection which has index-by defined in the mapping after enabling or disabling a filter, which causes the persister's filter hash to change):
$childEntity->getParent()so that it goes throughvendor/doctrine/orm/src/Proxy/ProxyFactory.php:279
$entityPersister = $this->uow->getEntityPersister($className);- enable a doctrine filter, which changes the
$this->filterHashof the BasicEntityPersister $parentEntity->getChildCollection()so that it goes throughvendor/doctrine/orm/src/UnitOfWork.php:2702
$persister = $this->getEntityPersister($assoc->targetEntity);
- This will cause the same instance of the BasicEntityPersister to populate its
->currentPersisterContext->rsminstance of ResultSetMapping by calling the->addFieldResulttwice for each ChildEntity column. So theResultSetMapping->fieldMappingsarray in the end will hold each entity column twice, but with different alias (index). The\Doctrine\ORM\Persisters\Entity\BasicEntityPersister::getSelectColumnsSQL()mechanism for detecting whether the current instance has already calculated the select columns from a previous query
vendor/doctrine/orm/src/Persisters/Entity/BasicEntityPersister.php:1234
if ($this->currentPersisterContext->selectColumnListSql !== null && $this->filterHash === $this->em->getFilters()->getHash()) {
is bypassed due to the new filter hash, but the->currentPersisterContext->sqlAliasCounteris not reset and
vendor/doctrine/orm/src/Persisters/Entity/BasicEntityPersister.php:1243
$columnList[] = $this->getSelectColumnSQL($field, $this->class);
will result in
\Doctrine\ORM\Persisters\Entity\BasicEntityPersister::getSQLColumnAlias()
to keep incrementing the
$this->currentPersisterContext->sqlAliasCounter++
from the position it was left at from the previous query, thus the second column set with the greater alias indexes will eventually be fetched in the result row. Due to the entity persister's ResultSetMapping instance's->indexByMappointing to the alias with the lesser index the following happens:
vendor/doctrine/orm/src/Internal/Hydration/ObjectHydrator.php:498
$resultKey = $row[$this->resultSetMapping()->indexByMap[$dqlAlias]];
Here$resultKeybecomes null and
$this->hints['collection']->hydrateSet($resultKey, $element);
in turn calls
vendor/doctrine/orm/src/PersistentCollection.php:162
$this->unwrap()->set($key, $element);
resulting in the exception from the title of the issue.
Current behavior
An exception is thrown.
Expected behavior
Collection indexing used to work in this case in version 3.3.0 of doctrine/orm
How to reproduce
Providing partial stack traces of the two fetches within the same UnitOfWork - with the breakpoint set at the problematic double addition of the columns in the ResultSetMapping->fieldMappings.
ResultSetMapping.php:327, Doctrine\ORM\Query\ResultSetMapping->addFieldResult()
BasicEntityPersister.php:1505, Doctrine\ORM\Persisters\Entity\BasicEntityPersister->getSelectColumnSQL()
BasicEntityPersister.php:1243, Doctrine\ORM\Persisters\Entity\BasicEntityPersister->getSelectColumnsSQL()
BasicEntityPersister.php:1119, Doctrine\ORM\Persisters\Entity\BasicEntityPersister->getSelectSQL()
BasicEntityPersister.php:734, Doctrine\ORM\Persisters\Entity\BasicEntityPersister->load()
BasicEntityPersister.php:754, Doctrine\ORM\Persisters\Entity\BasicEntityPersister->loadById()
ProxyFactory.php:220, Doctrine\ORM\Proxy\ProxyFactory::Doctrine\ORM\Proxy\{closure:/path/to/project/vendor/doctrine/orm/src/Proxy/ProxyFactory.php:219-242}()
ProxyFactory.php:286, Doctrine\Entities\__CG__\Devision\Auth\Context\UserClient\Domain\UserClient::Doctrine\ORM\Proxy\{closure:/path/to/project/vendor/doctrine/orm/src/Proxy/ProxyFactory.php:285-287}()
LazyObjectState.php:100, Symfony\Component\VarExporter\Internal\LazyObjectState->initialize()
LazyGhostTrait.php:178, Doctrine\Entities\__CG__\Devision\Auth\Context\UserClient\Domain\UserClient->__get()
UserClient.php:51, Devision\Auth\Context\UserClient\Domain\UserClient->getUser()
ResultSetMapping.php:327, Doctrine\ORM\Query\ResultSetMapping->addFieldResult()
BasicEntityPersister.php:1505, Doctrine\ORM\Persisters\Entity\BasicEntityPersister->getSelectColumnSQL()
BasicEntityPersister.php:1243, Doctrine\ORM\Persisters\Entity\BasicEntityPersister->getSelectColumnsSQL()
BasicEntityPersister.php:1119, Doctrine\ORM\Persisters\Entity\BasicEntityPersister->getSelectSQL()
BasicEntityPersister.php:1831, Doctrine\ORM\Persisters\Entity\BasicEntityPersister->getOneToManyStatement()
BasicEntityPersister.php:1779, Doctrine\ORM\Persisters\Entity\BasicEntityPersister->loadOneToManyCollection()
UnitOfWork.php:2706, Doctrine\ORM\UnitOfWork->loadCollection()
PersistentCollection.php:636, Doctrine\ORM\PersistentCollection->doInitialize()
PersistentCollection.php:186, Doctrine\ORM\PersistentCollection->initialize()
AbstractLazyCollection.php:138, Doctrine\Common\Collections\AbstractLazyCollection->getValues()
User.php:287, Devision\Auth\Context\User\Domain\User->getUserClientCollection()
And a JSON formatted partial stack trace of the Exception:
"message": "Doctrine\\Common\\Collections\\ArrayCollection::set(): Argument #1 ($key) must be of type string|int, null given, called in /path/to/project/vendor/doctrine/orm/src/PersistentCollection.php on line 162",
"code": 0,
"type": "TypeError",
"file:line": "/path/to/project/vendor/doctrine/collections/src/ArrayCollection.php:310",
"previous": null,
"trace": [
{
"file:line": "/path/to/project/vendor/doctrine/orm/src/PersistentCollection.php:162",
"callee": "Doctrine\\Common\\Collections\\ArrayCollection->set",
"args": [
"NULL",
"{instanceof Devision\\Auth\\Context\\UserClient\\Domain\\UserClient}"
]
},
{
"file:line": "/path/to/project/vendor/doctrine/orm/src/Internal/Hydration/ObjectHydrator.php:501",
"callee": "Doctrine\\ORM\\PersistentCollection->hydrateSet",
"args": [
"NULL",
"{instanceof Devision\\Auth\\Context\\UserClient\\Domain\\UserClient}"
]
},
{
"file:line": "/path/to/project/vendor/doctrine/orm/src/Internal/Hydration/ObjectHydrator.php:143",
"callee": "Doctrine\\ORM\\Internal\\Hydration\\ObjectHydrator->hydrateRowData",
"args": [
"array ( 'uuid_9' => '\\'<REDACTED>\\'', 'created_10' => '\\'<REDACTED>\\'', 'modified_11' => '\\'<REDACTED>\\'', 'not_archived_12' => '<REDACTED>', 'archived_13' => 'NULL', 'id_14' => '<REDACTED>', 'auth_user_id_15' => '<REDACTED>', 'auth_client_id_16' => '<REDACTED>',)",
"array ()"
]
},
{
"file:line": "/path/to/project/vendor/doctrine/orm/src/Internal/Hydration/AbstractHydrator.php:168",
"callee": "Doctrine\\ORM\\Internal\\Hydration\\ObjectHydrator->hydrateAllData",
"args": []
},
{
"file:line": "/path/to/project/vendor/doctrine/orm/src/Persisters/Entity/BasicEntityPersister.php:1001",
"callee": "Doctrine\\ORM\\Internal\\Hydration\\AbstractHydrator->hydrateAll",
"args": [
"{instanceof Doctrine\\DBAL\\Result}",
"{instanceof Doctrine\\ORM\\Query\\ResultSetMapping}",
"array ( 'deferEagerLoad' => 'true', 'collection' => '{instanceof Doctrine\\\\ORM\\\\PersistentCollection}',)"
]
},
{
"file:line": "/path/to/project/vendor/doctrine/orm/src/Persisters/Entity/BasicEntityPersister.php:1781",
"callee": "Doctrine\\ORM\\Persisters\\Entity\\BasicEntityPersister->loadCollectionFromStatement",
"args": [
"{instanceof Doctrine\\ORM\\Mapping\\OneToManyAssociationMapping}",
"{instanceof Doctrine\\DBAL\\Result}",
"{instanceof Doctrine\\ORM\\PersistentCollection}"
]
},
{
"file:line": "/path/to/project/vendor/doctrine/orm/src/UnitOfWork.php:2706",
"callee": "Doctrine\\ORM\\Persisters\\Entity\\BasicEntityPersister->loadOneToManyCollection",
"args": [
"{instanceof Doctrine\\ORM\\Mapping\\OneToManyAssociationMapping}",
"{instanceof Devision\\Auth\\Context\\User\\Domain\\User}",
"{instanceof Doctrine\\ORM\\PersistentCollection}"
]
},
{
"file:line": "/path/to/project/vendor/doctrine/orm/src/PersistentCollection.php:636",
"callee": "Doctrine\\ORM\\UnitOfWork->loadCollection",
"args": [
"{instanceof Doctrine\\ORM\\PersistentCollection}"
]
},
{
"file:line": "/path/to/project/vendor/doctrine/orm/src/PersistentCollection.php:186",
"callee": "Doctrine\\ORM\\PersistentCollection->doInitialize",
"args": []
},
{
"file:line": "/path/to/project/vendor/doctrine/collections/src/AbstractLazyCollection.php:138",
"callee": "Doctrine\\ORM\\PersistentCollection->initialize",
"args": []
},
{
"file:line": "/path/to/project/vendor/devision/devision-auth/src/Context/User/Domain/User.php:287",
"callee": "Doctrine\\Common\\Collections\\AbstractLazyCollection->getValues",
"args": []
},
{
"file:line": "/path/to/project/vendor/devision/adex-sales/src/Context/Auth/Context/User/Application/Service/UserService.php:292",
"callee": "Devision\\Auth\\Context\\User\\Domain\\User->getUserClientCollection",
"args": []
},
jsunier
Metadata
Metadata
Assignees
Labels
No labels