Skip to content

Commit

Permalink
Fix DDC-2084
Browse files Browse the repository at this point in the history
  • Loading branch information
FabioBatSilva committed Nov 6, 2012
1 parent ff80e99 commit a09a5b9
Show file tree
Hide file tree
Showing 5 changed files with 189 additions and 34 deletions.
46 changes: 13 additions & 33 deletions lib/Doctrine/ORM/AbstractQuery.php
Expand Up @@ -26,6 +26,7 @@
use Doctrine\DBAL\Cache\QueryCacheProfile;

use Doctrine\ORM\Query\QueryException;
use Doctrine\ORM\ORMInvalidArgumentException;

/**
* Base contract for ORM queries. Base class for Query and NativeQuery.
Expand Down Expand Up @@ -247,44 +248,23 @@ function ($parameter) use ($key)
*/
public function processParameterValue($value)
{
switch (true) {
case is_array($value):
foreach ($value as $key => $paramValue) {
$paramValue = $this->processParameterValue($paramValue);
$value[$key] = is_array($paramValue) ? $paramValue[key($paramValue)] : $paramValue;
}

return $value;

case is_object($value) && $this->_em->getMetadataFactory()->hasMetadataFor(ClassUtils::getClass($value)):
return $this->convertObjectParameterToScalarValue($value);

default:
return $value;
}
}

private function convertObjectParameterToScalarValue($value)
{
$class = $this->_em->getClassMetadata(get_class($value));
if (is_array($value)) {
foreach ($value as $key => $paramValue) {
$paramValue = $this->processParameterValue($paramValue);
$value[$key] = is_array($paramValue) ? reset($paramValue) : $paramValue;
}

if ($class->isIdentifierComposite) {
throw new \InvalidArgumentException(
"Binding an entity with a composite primary key to a query is not supported. " .
"You should split the parameter into the explicit fields and bind them seperately."
);
return $value;
}

$values = ($this->_em->getUnitOfWork()->getEntityState($value) === UnitOfWork::STATE_MANAGED)
? $this->_em->getUnitOfWork()->getEntityIdentifier($value)
: $class->getIdentifierValues($value);
if (is_object($value) && $this->_em->getMetadataFactory()->hasMetadataFor(ClassUtils::getClass($value))) {
$value = $this->_em->getUnitOfWork()->getSingleIdentifierValue($value);

$value = $values[$class->getSingleIdentifierFieldName()];
if ($value === null) {
throw ORMInvalidArgumentException::invalidIdentifierBindingEntity();
}

if (null === $value) {
throw new \InvalidArgumentException(
"Binding entities to query parameters only allowed for entities that have an identifier."
);
return $value;
}

return $value;
Expand Down
11 changes: 10 additions & 1 deletion lib/Doctrine/ORM/EntityManager.php
Expand Up @@ -29,6 +29,7 @@
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.
Expand Down Expand Up @@ -354,7 +355,7 @@ public function flush($entity = null)

$this->unitOfWork->commit($entity);
}

/**
* Finds an Entity by its identifier.
*
Expand All @@ -369,6 +370,14 @@ public function find($entityName, $id, $lockMode = LockMode::NONE, $lockVersion
{
$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);
}
Expand Down
11 changes: 11 additions & 0 deletions lib/Doctrine/ORM/ORMInvalidArgumentException.php
Expand Up @@ -102,6 +102,17 @@ public static function invalidObject($context, $given, $parameterIndex = 1)
' to be an entity object, '. gettype($given) . ' given.');
}

public static function invalidCompositeIdentifier()
{
return new self("Binding an entity with a composite primary key to a query is not supported. " .
"You should split the parameter into the explicit fields and bind them seperately.");
}

public static function invalidIdentifierBindingEntity()
{
return new self("Binding entities to query parameters only allowed for entities that have an identifier.");
}

/**
* Helper method to show an object as string.
*
Expand Down
24 changes: 24 additions & 0 deletions lib/Doctrine/ORM/UnitOfWork.php
Expand Up @@ -2737,6 +2737,30 @@ public function getEntityIdentifier($entity)
return $this->entityIdentifiers[spl_object_hash($entity)];
}

/**
* Process an entity instance to extract their identifier values.
*
* @param object $entity The entity instance.
*
* @return scalar
*
* @throws \Doctrine\ORM\ORMInvalidArgumentException
*/
public function getSingleIdentifierValue($entity)
{
$class = $this->em->getClassMetadata(get_class($entity));

if ($class->isIdentifierComposite) {
throw ORMInvalidArgumentException::invalidCompositeIdentifier();
}

$values = ($this->getEntityState($entity) === UnitOfWork::STATE_MANAGED)
? $this->getEntityIdentifier($entity)
: $class->getIdentifierValues($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.
Expand Down
131 changes: 131 additions & 0 deletions tests/Doctrine/Tests/ORM/Functional/Ticket/DDC2084Test.php
@@ -0,0 +1,131 @@
<?php

namespace Doctrine\Tests\ORM\Functional\Ticket;


require_once __DIR__ . '/../../../TestInit.php';

/**
* @group DDC-2084
*/
class DDC2084Test extends \Doctrine\Tests\OrmFunctionalTestCase
{
public function setUp()
{
parent::setUp();

try {
$this->_schemaTool->createSchema(array(
$this->_em->getClassMetadata(__NAMESPACE__ . '\DDC2084\MyEntity1'),
$this->_em->getClassMetadata(__NAMESPACE__ . '\DDC2084\MyEntity2'),
));
} catch (\Exception $exc) {
}
}

public function loadFixture()
{
$e2 = new DDC2084\MyEntity2('Foo');
$e1 = new DDC2084\MyEntity1($e2);

$this->_em->persist($e2);
$this->_em->flush();

$this->_em->persist($e1);
$this->_em->flush();

$this->_em->clear();

return $e1;
}

public function testIssue()
{
$e1 = $this->loadFixture();
$e2 = $e1->getMyEntity2();
$e = $this->_em->find(__NAMESPACE__ . '\DDC2084\MyEntity1', $e2);

$this->assertInstanceOf(__NAMESPACE__ . '\DDC2084\MyEntity1', $e);
$this->assertInstanceOf(__NAMESPACE__ . '\DDC2084\MyEntity2', $e->getMyEntity2());
$this->assertEquals('Foo', $e->getMyEntity2()->getValue());
}

/**
* @expectedException \Doctrine\ORM\ORMInvalidArgumentException
* @expectedExceptionMessage Binding entities to query parameters only allowed for entities that have an identifier.
*/
public function testinvalidIdentifierBindingEntityException()
{
$this->_em->find(__NAMESPACE__ . '\DDC2084\MyEntity1', new DDC2084\MyEntity2('Foo'));
}
}

namespace Doctrine\Tests\ORM\Functional\Ticket\DDC2084;

/**
* @Entity
* @Table(name="DDC2084_ENTITY1")
*/
class MyEntity1
{
/**
* @Id
* @OneToOne(targetEntity="MyEntity2")
* @JoinColumn(name="entity2_id", referencedColumnName="id", nullable=false)
*/
private $entity2;

public function __construct(MyEntity2 $myEntity2)
{
$this->entity2 = $myEntity2;
}

public function setMyEntity2(MyEntity2 $myEntity2)
{
$this->entity2 = $myEntity2;
}

public function getMyEntity2()
{
return $this->entity2;
}
}

/**
* @Entity
* @Table(name="DDC2084_ENTITY2")
*/
class MyEntity2
{
/**
* @Id
* @Column(type="integer")
* @GeneratedValue(strategy="AUTO")
*/
private $id;

/**
* @Column
*/
private $value;

public function __construct($value)
{
$this->value = $value;
}

public function getId()
{
return $this->id;
}

public function getValue()
{
return $this->value;
}

public function setValue($value)
{
$this->value = $value;
}
}

0 comments on commit a09a5b9

Please sign in to comment.