Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP

Loading…

[DDC-2166] Refactor identity hash generation #522

Closed
wants to merge 1 commit into from

6 participants

@beberlei
Owner

This work prepares for the merge of GH-232, allowing more complex and robust identifier hash generation.

@doctrinebot
Collaborator

Hello,

thank you for positing this Pull Request. I have automatically opened an issue on our Jira Bug Tracker for you with the details of this Pull-Request. See the Link:

http://doctrine-project.org/jira/browse/DDC-2167

@Ocramius Ocramius commented on the diff
lib/Doctrine/ORM/UnitOfWork.php
((7 lines not shown))
+ *
+ * @param ClassMetadata $class
+ * @param mixed $id
+ * @return array
+ */
+ public function normalizeIdentifier($class, $id)
+ {
+ if (is_object($id) && $this->em->getMetadataFactory()->hasMetadataFor(ClassUtils::getClass($id))) {
+ $id = $this->getSingleIdentifierValue($id);
+
+ if ($id === null) {
+ throw ORMInvalidArgumentException::invalidIdentifierBindingEntity();
+ }
+ }
+
+ if ( ! is_array($id)) {
@Ocramius Owner

Identifiers are always arrays as of class metadata. We shouldn't introduce more inconsistencies on that one (already a mess in the various ODMs, where that interface is currently ignored).

@beberlei Owner

$id is a scalar when people call $entityManager->find("Entity", 1); Thats why the method is about normalizing.

@Ocramius Owner

@beberlei yeah, but then we're adding this call everywhere (instead of just where the API states @param mixed $id). Nvm, guess it's an acceptable overhead.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
@Ocramius Ocramius commented on the diff
lib/Doctrine/ORM/UnitOfWork.php
@@ -3069,4 +3043,65 @@ public function isReadOnly($object)
return isset($this->readOnlyObjects[spl_object_hash($object)]);
}
+
+ /**
+ * Normalize identifier into a sorted array of identifier values.
+ *
+ * @param ClassMetadata $class
+ * @param mixed $id
+ * @return array
+ */
+ public function normalizeIdentifier($class, $id)
+ {
+ if (is_object($id) && $this->em->getMetadataFactory()->hasMetadataFor(ClassUtils::getClass($id))) {
@Ocramius Owner

Shouldn't this be moved at line 3074?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
@Ocramius Ocramius commented on the diff
lib/Doctrine/ORM/UnitOfWork.php
((24 lines not shown))
+ }
+
+ $sortedId = array();
+
+ foreach ($class->identifier as $identifier) {
+ if ( ! isset($id[$identifier])) {
+ throw ORMException::missingIdentifierField($class->name, $identifier);
+ }
+
+ $sortedId[$identifier] = $id[$identifier];
+ }
+
+ return $sortedId;
+ }
+
+ public function getHashForEntityIdentifier($class, $id)
@Ocramius Owner

Don't forget type hints on those :)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
@FabioBatSilva FabioBatSilva commented on the diff
...e/ORM/Internal/IdentityMap/DerivedKeyHashStrategy.php
((27 lines not shown))
+ * @var \Doctrine\ORM\Mapping\ClassMetadata
+ */
+ private $class;
+
+ /**
+ * @var \Doctrine\ORM\EntityManager
+ */
+ private $entityManager;
+
+ public function __construct(ClassMetadata $class, EntityManager $entityManager)
+ {
+ $this->class = $class;
+ $this->entityManager = $entityManager;
+ }
+
+ public function getHash(array $identifier)
@FabioBatSilva Owner

Could you assign the callback to a variable and align the =

Something like :

<?php
/**
 * {@inheritdoc}
 */
public function getHash(array $identifier)
{
    $class      = $this->class;
    $em         = $this->entityManager;
    $callback   = function ($fieldName) use ($identifier, $class, $em)
    {
        if (is_object($identifier[$fieldName]) && ! $em->getMetadataFactory()->isTransient($identifier[$fieldName])) {
            $class  = $em->getClassMetadata(get_class($identifier[$fieldName]));
            $id     = $em->getUnitOfWork()->getEntityIdentifier($identifier[$fieldName]);

            return $em->getUnitOfWork()->getHashForEntityIdentifier($class, $id);
        }

        return $identifier[$fieldName];
    };

    return implode(' ', array_map($callback, $class->identifier));
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
@FabioBatSilva FabioBatSilva commented on the diff
lib/Doctrine/ORM/UnitOfWork.php
@@ -2773,7 +2746,8 @@ public function getSingleIdentifierValue($entity)
*/
public function tryGetById($id, $rootClassName)
{
- $idHash = implode(' ', (array) $id);
+ $classMetadata = $this->em->getClassMetadata($rootClassName);
@FabioBatSilva Owner

Please align = signs

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
@TomasVotruba

This is almost 2 years old. Still relevant @beberlei ?

If so, it would be great to merge the change. As the time goes by, potential merge conflict will grow.

@Ocramius
Owner

@guilhermeblanco can you allocate some of @FabioBatSilva's time to check this and #1113 PR?

@Ocramius Ocramius self-assigned this
@Ocramius
Owner

This has been partially handled in the IdentifierFlattener class now. Closing as incomplete.

@Ocramius Ocramius closed this
@Ocramius Ocramius deleted the DDC-2166 branch
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Commits on Nov 25, 2012
  1. @beberlei
This page is out of date. Refresh to see the latest.
View
59 lib/Doctrine/ORM/EntityManager.php
@@ -29,7 +29,6 @@
use Doctrine\ORM\Query\ResultSetMapping;
use Doctrine\ORM\Proxy\ProxyFactory;
use Doctrine\ORM\Query\FilterCollection;
-use Doctrine\Common\Util\ClassUtils;
/**
* The EntityManager is the central access point to ORM functionality.
@@ -368,31 +367,9 @@ public function flush($entity = null)
*/
public function find($entityName, $id, $lockMode = LockMode::NONE, $lockVersion = null)
{
- $class = $this->metadataFactory->getMetadataFor(ltrim($entityName, '\\'));
-
- if (is_object($id) && $this->metadataFactory->hasMetadataFor(ClassUtils::getClass($id))) {
- $id = $this->unitOfWork->getSingleIdentifierValue($id);
-
- if ($id === null) {
- throw ORMInvalidArgumentException::invalidIdentifierBindingEntity();
- }
- }
-
- if ( ! is_array($id)) {
- $id = array($class->identifier[0] => $id);
- }
-
- $sortedId = array();
-
- foreach ($class->identifier as $identifier) {
- if ( ! isset($id[$identifier])) {
- throw ORMException::missingIdentifierField($class->name, $identifier);
- }
-
- $sortedId[$identifier] = $id[$identifier];
- }
-
$unitOfWork = $this->getUnitOfWork();
+ $class = $this->metadataFactory->getMetadataFor(ltrim($entityName, '\\'));
+ $sortedId = $unitOfWork->normalizeIdentifier($class, $id);
// Check identity map first
if (($entity = $unitOfWork->tryGetById($sortedId, $class->rootEntityName)) !== false) {
@@ -451,21 +428,8 @@ public function find($entityName, $id, $lockMode = LockMode::NONE, $lockVersion
*/
public function getReference($entityName, $id)
{
- $class = $this->metadataFactory->getMetadataFor(ltrim($entityName, '\\'));
-
- if ( ! is_array($id)) {
- $id = array($class->identifier[0] => $id);
- }
-
- $sortedId = array();
-
- foreach ($class->identifier as $identifier) {
- if ( ! isset($id[$identifier])) {
- throw ORMException::missingIdentifierField($class->name, $identifier);
- }
-
- $sortedId[$identifier] = $id[$identifier];
- }
+ $class = $this->metadataFactory->getMetadataFor(ltrim($entityName, '\\'));
+ $sortedId = $this->unitOfWork->normalizeIdentifier($class, $id);
// Check identity map first, if its already in there just return it.
if (($entity = $this->unitOfWork->tryGetById($sortedId, $class->rootEntityName)) !== false) {
@@ -476,10 +440,6 @@ public function getReference($entityName, $id)
return $this->find($entityName, $sortedId);
}
- if ( ! is_array($sortedId)) {
- $sortedId = array($class->identifier[0] => $sortedId);
- }
-
$entity = $this->proxyFactory->getProxy($class->name, $sortedId);
$this->unitOfWork->registerManaged($entity, $sortedId, array());
@@ -509,21 +469,18 @@ public function getReference($entityName, $id)
public function getPartialReference($entityName, $identifier)
{
$class = $this->metadataFactory->getMetadataFor(ltrim($entityName, '\\'));
+ $sortedId = $this->unitOfWork->normalizeIdentifier($class, $identifier);
// Check identity map first, if its already in there just return it.
- if (($entity = $this->unitOfWork->tryGetById($identifier, $class->rootEntityName)) !== false) {
+ if (($entity = $this->unitOfWork->tryGetById($sortedId, $class->rootEntityName)) !== false) {
return ($entity instanceof $class->name) ? $entity : null;
}
- if ( ! is_array($identifier)) {
- $identifier = array($class->identifier[0] => $identifier);
- }
-
$entity = $class->newInstance();
- $class->setIdentifierValues($entity, $identifier);
+ $class->setIdentifierValues($entity, $sortedId);
- $this->unitOfWork->registerManaged($entity, $identifier, array());
+ $this->unitOfWork->registerManaged($entity, $sortedId, array());
$this->unitOfWork->markReadOnly($entity);
return $entity;
View
55 lib/Doctrine/ORM/Internal/IdentityMap/CompositeKeyHashStrategy.php
@@ -0,0 +1,55 @@
+<?php
+/*
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * This software consists of voluntary contributions made by many individuals
+ * and is licensed under the MIT license. For more information, see
+ * <http://www.doctrine-project.org>.
+ */
+
+namespace Doctrine\ORM\Internal\IdentityMap;
+
+use Doctrine\ORM\Mapping\ClassMetadata;
+
+/**
+ * The composite key hash strategy works on composite, non-derived keys.
+ *
+ * Applying this strategy guarantees that the order of the identifiers passed
+ * alaways leads to the same hash, independent of the order of the fields in the array.
+ *
+ * Important: Hashes are seperated by a space, meaning that identifier values
+ * that contain spaces themselves may lead to id hash duplicates.
+ */
+class CompositeKeyHashStrategy implements IdentifierHashStrategy
+{
+ /**
+ * @var \Doctrine\ORM\Mapping\ClassMetadata
+ */
+ private $class;
+
+ public function __construct(ClassMetadata $class)
+ {
+ $this->class = $class;
+ }
+
+ public function getHash(array $identifier)
+ {
+ return implode(
+ ' ',
+ array_map(function ($fieldName) use ($identifier) {
+ return $identifier[$fieldName];
+ }, $this->class->identifier)
+ );
+ }
+}
+
View
62 lib/Doctrine/ORM/Internal/IdentityMap/DerivedKeyHashStrategy.php
@@ -0,0 +1,62 @@
+<?php
+/*
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * This software consists of voluntary contributions made by many individuals
+ * and is licensed under the MIT license. For more information, see
+ * <http://www.doctrine-project.org>.
+ */
+namespace Doctrine\ORM\Internal\IdentityMap;
+
+use Doctrine\ORM\Mapping\ClassMetadata;
+use Doctrine\ORM\EntityManager;
+
+class DerivedKeyHashStrategy implements IdentifierHashStrategy
+{
+ /**
+ * @var \Doctrine\ORM\Mapping\ClassMetadata
+ */
+ private $class;
+
+ /**
+ * @var \Doctrine\ORM\EntityManager
+ */
+ private $entityManager;
+
+ public function __construct(ClassMetadata $class, EntityManager $entityManager)
+ {
+ $this->class = $class;
+ $this->entityManager = $entityManager;
+ }
+
+ public function getHash(array $identifier)
@FabioBatSilva Owner

Could you assign the callback to a variable and align the =

Something like :

<?php
/**
 * {@inheritdoc}
 */
public function getHash(array $identifier)
{
    $class      = $this->class;
    $em         = $this->entityManager;
    $callback   = function ($fieldName) use ($identifier, $class, $em)
    {
        if (is_object($identifier[$fieldName]) && ! $em->getMetadataFactory()->isTransient($identifier[$fieldName])) {
            $class  = $em->getClassMetadata(get_class($identifier[$fieldName]));
            $id     = $em->getUnitOfWork()->getEntityIdentifier($identifier[$fieldName]);

            return $em->getUnitOfWork()->getHashForEntityIdentifier($class, $id);
        }

        return $identifier[$fieldName];
    };

    return implode(' ', array_map($callback, $class->identifier));
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
+ {
+ $class = $this->class;
+ $em = $this->entityManager;
+ return implode(
+ ' ',
+ array_map(function ($fieldName) use ($identifier, $class, $em) {
+
+ if (is_object($identifier[$fieldName]) && ! $em->getMetadataFactory()->isTransient($identifier[$fieldName])) {
+ $class = $em->getClassMetadata(get_class($identifier[$fieldName]));
+ $id = $em->getUnitOfWork()->getEntityIdentifier($identifier[$fieldName]);
+ return $em->getUnitOfWork()->getHashForEntityIdentifier($class, $id);
+ }
+
+ return $identifier[$fieldName];
+
+ }, $class->identifier)
+ );
+ }
+}
+
View
40 lib/Doctrine/ORM/Internal/IdentityMap/IdentifierHashStrategy.php
@@ -0,0 +1,40 @@
+<?php
+/*
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * This software consists of voluntary contributions made by many individuals
+ * and is licensed under the MIT license. For more information, see
+ * <http://www.doctrine-project.org>.
+ */
+
+namespace Doctrine\ORM\Internal\IdentityMap;
+
+/**
+ * Strategy to hash identifiers based on entity identifier type.
+ *
+ * Depending on the type of Entity the algorithm for retrieving data
+ * from the identity map can actually be quite complex and performance sensitive.
+ * Delegating this work out to an access strategy interface allows us
+ * to keep the performance optimal for the default surrogate use-case.
+ */
+interface IdentifierHashStrategy
+{
+ /**
+ * Return the identifier hash of the entity data.
+ *
+ * @param array $data
+ * @return string
+ */
+ public function getHash(array $data);
+}
+
View
36 lib/Doctrine/ORM/Internal/IdentityMap/SurrogateKeyHashStrategy.php
@@ -0,0 +1,36 @@
+<?php
+/*
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * This software consists of voluntary contributions made by many individuals
+ * and is licensed under the MIT license. For more information, see
+ * <http://www.doctrine-project.org>.
+ */
+
+namespace Doctrine\ORM\Internal\IdentityMap;
+
+class SurrogateKeyHashStrategy implements IdentifierHashStrategy
+{
+ private $identifier;
+
+ public function __construct($identifier)
+ {
+ $this->identifier = $identifier;
+ }
+
+ public function getHash(array $identifier)
+ {
+ return (string)$identifier[$this->identifier];
+ }
+}
+
View
2  lib/Doctrine/ORM/Persisters/BasicEntityPersister.php
@@ -689,7 +689,7 @@ public function load(array $criteria, $entity = null, $assoc = null, array $hint
*/
public function loadOneToOneEntity(array $assoc, $sourceEntity, array $identifier = array())
{
- if (($foundEntity = $this->_em->getUnitOfWork()->tryGetById($identifier, $assoc['targetEntity'])) != false) {
+ if ($identifier && ($foundEntity = $this->_em->getUnitOfWork()->tryGetById($identifier, $assoc['targetEntity'])) != false) {
return $foundEntity;
}
View
115 lib/Doctrine/ORM/UnitOfWork.php
@@ -27,9 +27,11 @@
use Doctrine\Common\NotifyPropertyChanged;
use Doctrine\Common\PropertyChangedListener;
use Doctrine\Common\Persistence\ObjectManagerAware;
+use Doctrine\Common\Util\ClassUtils;
use Doctrine\ORM\Event\LifecycleEventArgs;
use Doctrine\ORM\Mapping\ClassMetadata;
use Doctrine\ORM\Proxy\Proxy;
+use Doctrine\ORM\Internal\IdentityMap;
/**
* The UnitOfWork is responsible for tracking changes to objects during an
@@ -234,6 +236,11 @@ class UnitOfWork implements PropertyChangedListener
private $eagerLoadingEntities = array();
/**
+ * @var array<string,IdentifierHashStrategy>
+ */
+ private $entityHashStrategy = array();
+
+ /**
* Initializes a new UnitOfWork instance, bound to the given EntityManager.
*
* @param \Doctrine\ORM\EntityManager $em
@@ -1327,7 +1334,7 @@ public function isEntityScheduled($entity)
public function addToIdentityMap($entity)
{
$classMetadata = $this->em->getClassMetadata(get_class($entity));
- $idHash = implode(' ', $this->entityIdentifiers[spl_object_hash($entity)]);
+ $idHash = $this->getHashForEntityIdentifier($classMetadata, $this->entityIdentifiers[spl_object_hash($entity)]);
if ($idHash === '') {
throw ORMInvalidArgumentException::entityWithoutIdentity($classMetadata->name, $entity);
@@ -1437,7 +1444,7 @@ public function removeFromIdentityMap($entity)
{
$oid = spl_object_hash($entity);
$classMetadata = $this->em->getClassMetadata(get_class($entity));
- $idHash = implode(' ', $this->entityIdentifiers[$oid]);
+ $idHash = $this->getHashForEntityIdentifier($classMetadata, $this->entityIdentifiers[$oid]);
if ($idHash === '') {
throw ORMInvalidArgumentException::entityHasNoIdentity($entity, "remove from identity map");
@@ -1459,21 +1466,6 @@ public function removeFromIdentityMap($entity)
/**
* INTERNAL:
- * Gets an entity in the identity map by its identifier hash.
- *
- * @ignore
- * @param string $idHash
- * @param string $rootClassName
- *
- * @return object
- */
- public function getByIdHash($idHash, $rootClassName)
- {
- return $this->identityMap[$rootClassName][$idHash];
- }
-
- /**
- * INTERNAL:
* Tries to get an entity by its identifier hash. If no entity is found for
* the given hash, FALSE is returned.
*
@@ -1508,7 +1500,7 @@ public function isInIdentityMap($entity)
}
$classMetadata = $this->em->getClassMetadata(get_class($entity));
- $idHash = implode(' ', $this->entityIdentifiers[$oid]);
+ $idHash = $this->getHashForEntityIdentifier($classMetadata, $this->entityIdentifiers[$oid]);
if ($idHash === '') {
return false;
@@ -1518,21 +1510,6 @@ public function isInIdentityMap($entity)
}
/**
- * INTERNAL:
- * Checks whether an identifier hash exists in the identity map.
- *
- * @ignore
- * @param string $idHash
- * @param string $rootClassName
- *
- * @return boolean
- */
- public function containsIdHash($idHash, $rootClassName)
- {
- return isset($this->identityMap[$rootClassName][$idHash]);
- }
-
- /**
* Persists an entity as part of the current unit of work.
*
* @param object $entity The entity to persist.
@@ -2517,11 +2494,7 @@ public function createEntity($className, array $data, &$hints = array())
$hints['fetchMode'][$class->name][$field] = $assoc['fetch'];
}
- // Foreign key is set
- // Check identity map first
- // FIXME: Can break easily with composite keys if join column values are in
- // wrong order. The correct order is the one in ClassMetadata#identifier.
- $relatedIdHash = implode(' ', $associatedId);
+ $relatedIdHash = $this->getHashForEntityIdentifier($targetClass, $associatedId);
switch (true) {
case (isset($this->identityMap[$targetClass->rootEntityName][$relatedIdHash])):
@@ -2760,7 +2733,7 @@ public function getSingleIdentifierValue($entity)
return isset($values[$class->identifier[0]]) ? $values[$class->identifier[0]] : null;
}
-
+
/**
* Tries to find an entity with the given identifier in the identity map of
* this UnitOfWork.
@@ -2773,7 +2746,8 @@ public function getSingleIdentifierValue($entity)
*/
public function tryGetById($id, $rootClassName)
{
- $idHash = implode(' ', (array) $id);
+ $classMetadata = $this->em->getClassMetadata($rootClassName);
@FabioBatSilva Owner

Please align = signs

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
+ $idHash = $this->getHashForEntityIdentifier($classMetadata, (array) $id);
if (isset($this->identityMap[$rootClassName][$idHash])) {
return $this->identityMap[$rootClassName][$idHash];
@@ -3069,4 +3043,65 @@ public function isReadOnly($object)
return isset($this->readOnlyObjects[spl_object_hash($object)]);
}
+
+ /**
+ * Normalize identifier into a sorted array of identifier values.
+ *
+ * @param ClassMetadata $class
+ * @param mixed $id
+ * @return array
+ */
+ public function normalizeIdentifier($class, $id)
+ {
+ if (is_object($id) && $this->em->getMetadataFactory()->hasMetadataFor(ClassUtils::getClass($id))) {
@Ocramius Owner

Shouldn't this be moved at line 3074?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
+ $id = $this->getSingleIdentifierValue($id);
+
+ if ($id === null) {
+ throw ORMInvalidArgumentException::invalidIdentifierBindingEntity();
+ }
+ }
+
+ if ( ! is_array($id)) {
@Ocramius Owner

Identifiers are always arrays as of class metadata. We shouldn't introduce more inconsistencies on that one (already a mess in the various ODMs, where that interface is currently ignored).

@beberlei Owner

$id is a scalar when people call $entityManager->find("Entity", 1); Thats why the method is about normalizing.

@Ocramius Owner

@beberlei yeah, but then we're adding this call everywhere (instead of just where the API states @param mixed $id). Nvm, guess it's an acceptable overhead.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
+ $id = array($class->identifier[0] => $id);
+ }
+
+ $sortedId = array();
+
+ foreach ($class->identifier as $identifier) {
+ if ( ! isset($id[$identifier])) {
+ throw ORMException::missingIdentifierField($class->name, $identifier);
+ }
+
+ $sortedId[$identifier] = $id[$identifier];
+ }
+
+ return $sortedId;
+ }
+
+ public function getHashForEntityIdentifier($class, $id)
@Ocramius Owner

Don't forget type hints on those :)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
+ {
+ if ( ! $id) {
+ return '';
+ }
+
+ if ( ! isset($this->entityHashStrategy[$class->rootEntityName])) {
+ $this->entityHashStrategy[$class->rootEntityName] = $this->createHashStrategy($class);
+ }
+
+ return $this->entityHashStrategy[$class->rootEntityName]->getHash($id);
+ }
+
+ private function createHashStrategy($class)
+ {
+ if ($class->containsForeignIdentifier) {
+ return new IdentityMap\DerivedKeyHashStrategy($class, $this->em);
+ }
+
+ if ($class->isIdentifierComposite) {
+ return new IdentityMap\CompositeKeyHashStrategy($class);
+ }
+
+ return new IdentityMap\SurrogateKeyHashStrategy($class->identifier[0]);
+ }
}
+
View
2  tests/Doctrine/Tests/ORM/Functional/UnitOfWorkLifecycleTest.php
@@ -68,4 +68,4 @@ public function testMarkReadOnlyNonManaged()
$this->setExpectedException("Doctrine\ORM\ORMInvalidArgumentException", "Only managed entities can be marked or checked as read only. But Doctrine\Tests\Models\CMS\CmsUser@");
$this->_em->getUnitOfWork()->markReadOnly($user);
}
-}
+}
Something went wrong with that request. Please try again.