[QUERY] "INSTANCE OF" now behaves correctly with subclasses
There was a bug in the "INSTANCE OF" operator as described in https://groups.google.com/forum/#!topic/doctrine-user/B8raq8CNMgg "INSTANCE OF" was not taking into account subclasses. It was merely translating the class to its discriminator. This is not correct since the class can have subtypes and those are, indeed, still instance of the superclass. Also, classes may not have a discriminator (e.g. abstract classes). This commit also provides useful tests to avoid regression.
This commit is contained in:
parent
2c5e76c961
commit
e798bfe34a
@ -2038,8 +2038,10 @@ class SqlWalker implements TreeWalker
|
||||
|
||||
$sql .= $class->discriminatorColumn['name'] . ($instanceOfExpr->not ? ' NOT IN ' : ' IN ');
|
||||
|
||||
$sqlParameterList = [];
|
||||
$knownSubclasses = array_flip($discrClass->subClasses);
|
||||
|
||||
$sqlParameterList = [];
|
||||
$discriminators = [];
|
||||
foreach ($instanceOfExpr->value as $parameter) {
|
||||
if ($parameter instanceof AST\InputParameter) {
|
||||
$this->rsm->addMetadataParameterMapping($parameter->name, 'discriminatorValue');
|
||||
@ -2049,21 +2051,31 @@ class SqlWalker implements TreeWalker
|
||||
continue;
|
||||
}
|
||||
|
||||
// Get name from ClassMetadata to resolve aliases.
|
||||
$entityClassName = $this->em->getClassMetadata($parameter)->name;
|
||||
$discriminatorValue = $class->discriminatorValue;
|
||||
// Trim first backslash
|
||||
$parameter = ltrim($parameter, '\\');
|
||||
|
||||
if ($entityClassName !== $class->name) {
|
||||
$discrMap = array_flip($class->discriminatorMap);
|
||||
|
||||
if ( ! isset($discrMap[$entityClassName])) {
|
||||
throw QueryException::instanceOfUnrelatedClass($entityClassName, $class->rootEntityName);
|
||||
}
|
||||
|
||||
$discriminatorValue = $discrMap[$entityClassName];
|
||||
// Check parameter is really in the hierarchy
|
||||
if ($parameter !== $discrClass->name && ! array_key_exists($parameter, $knownSubclasses)) {
|
||||
throw QueryException::instanceOfUnrelatedClass($parameter, $discrClass->name);
|
||||
}
|
||||
|
||||
$sqlParameterList[] = $this->conn->quote($discriminatorValue);
|
||||
// Include discriminators for parameter class and its subclass
|
||||
$metadata = $this->em->getClassMetadata($parameter);
|
||||
$hierarchyClasses = $metadata->subClasses;
|
||||
$hierarchyClasses[] = $metadata->name;
|
||||
|
||||
foreach ($hierarchyClasses as $class) {
|
||||
$currentMetadata = $this->em->getClassMetadata($class);
|
||||
$currentDiscriminator = $currentMetadata->discriminatorValue;
|
||||
|
||||
if (is_string($currentDiscriminator) && ! array_key_exists($currentDiscriminator, $discriminators)) {
|
||||
$discriminators[$currentDiscriminator] = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
foreach (array_keys($discriminators) as $dis) {
|
||||
$sqlParameterList[] = $this->conn->quote($dis);
|
||||
}
|
||||
|
||||
$sql .= '(' . implode(', ', $sqlParameterList) . ')';
|
||||
|
113
tests/Doctrine/Tests/ORM/Functional/InstanceOfAbstractTest.php
Normal file
113
tests/Doctrine/Tests/ORM/Functional/InstanceOfAbstractTest.php
Normal file
@ -0,0 +1,113 @@
|
||||
<?php
|
||||
|
||||
namespace Doctrine\Tests\ORM\Functional {
|
||||
|
||||
use Doctrine\Tests\OrmFunctionalTestCase;
|
||||
|
||||
class InstanceOfAbstractTest extends OrmFunctionalTestCase
|
||||
{
|
||||
protected function setUp()
|
||||
{
|
||||
parent::setUp();
|
||||
|
||||
$this->_schemaTool->createSchema(array(
|
||||
$this->_em->getClassMetadata(__NAMESPACE__ . '\InstanceOfAbstractTest\Person'),
|
||||
$this->_em->getClassMetadata(__NAMESPACE__ . '\InstanceOfAbstractTest\Employee'),
|
||||
));
|
||||
}
|
||||
|
||||
public function testInstanceOf()
|
||||
{
|
||||
$this->loadData();
|
||||
|
||||
$dql = 'SELECT p FROM Doctrine\Tests\ORM\Functional\InstanceOfAbstractTest\Person p
|
||||
WHERE p INSTANCE OF Doctrine\Tests\ORM\Functional\InstanceOfAbstractTest\Person';
|
||||
$query = $this->_em->createQuery($dql);
|
||||
$result = $query->getResult();
|
||||
|
||||
$this->assertCount(1, $result);
|
||||
|
||||
foreach ($result as $r) {
|
||||
$this->assertInstanceOf('Doctrine\Tests\ORM\Functional\InstanceOfAbstractTest\Person', $r);
|
||||
$this->assertInstanceOf('Doctrine\Tests\ORM\Functional\InstanceOfAbstractTest\Employee', $r);
|
||||
$this->assertEquals('bar', $r->getName());
|
||||
}
|
||||
}
|
||||
|
||||
private function loadData()
|
||||
{
|
||||
$employee = new InstanceOfAbstractTest\Employee();
|
||||
$employee->setName('bar');
|
||||
$employee->setDepartement('qux');
|
||||
|
||||
$this->_em->persist($employee);
|
||||
|
||||
$this->_em->flush($employee);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
namespace Doctrine\Tests\ORM\Functional\InstanceOfAbstractTest {
|
||||
|
||||
/**
|
||||
* @Entity()
|
||||
* @Table(name="instance_of_abstract_test_person")
|
||||
* @InheritanceType(value="JOINED")
|
||||
* @DiscriminatorColumn(name="kind", type="string")
|
||||
* @DiscriminatorMap(value={
|
||||
* "employee": "Doctrine\Tests\ORM\Functional\InstanceOfAbstractTest\Employee"
|
||||
* })
|
||||
*/
|
||||
abstract class Person
|
||||
{
|
||||
/**
|
||||
* @Id()
|
||||
* @GeneratedValue()
|
||||
* @Column(type="integer")
|
||||
*/
|
||||
private $id;
|
||||
|
||||
/**
|
||||
* @Column(type="string")
|
||||
*/
|
||||
private $name;
|
||||
|
||||
public function getId()
|
||||
{
|
||||
return $this->id;
|
||||
}
|
||||
|
||||
public function getName()
|
||||
{
|
||||
return $this->name;
|
||||
}
|
||||
|
||||
public function setName($name)
|
||||
{
|
||||
$this->name = $name;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @Entity()
|
||||
* @Table(name="instance_of_abstract_test_employee")
|
||||
*/
|
||||
class Employee extends Person
|
||||
{
|
||||
/**
|
||||
* @Column(type="string")
|
||||
*/
|
||||
private $departement;
|
||||
|
||||
public function getDepartement()
|
||||
{
|
||||
return $this->departement;
|
||||
}
|
||||
|
||||
public function setDepartement($departement)
|
||||
{
|
||||
$this->departement = $departement;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
153
tests/Doctrine/Tests/ORM/Functional/InstanceOfMultiLevelTest.php
Normal file
153
tests/Doctrine/Tests/ORM/Functional/InstanceOfMultiLevelTest.php
Normal file
@ -0,0 +1,153 @@
|
||||
<?php
|
||||
|
||||
namespace Doctrine\Tests\ORM\Functional {
|
||||
|
||||
use Doctrine\Tests\OrmFunctionalTestCase;
|
||||
|
||||
class InstanceOfMultiLevelTest extends OrmFunctionalTestCase
|
||||
{
|
||||
protected function setUp()
|
||||
{
|
||||
parent::setUp();
|
||||
|
||||
$this->_schemaTool->createSchema(array(
|
||||
$this->_em->getClassMetadata(__NAMESPACE__ . '\InstanceOfMultiLevelTest\Person'),
|
||||
$this->_em->getClassMetadata(__NAMESPACE__ . '\InstanceOfMultiLevelTest\Employee'),
|
||||
$this->_em->getClassMetadata(__NAMESPACE__ . '\InstanceOfMultiLevelTest\Engineer'),
|
||||
));
|
||||
}
|
||||
|
||||
public function testInstanceOf()
|
||||
{
|
||||
$this->loadData();
|
||||
|
||||
$dql = 'SELECT p FROM Doctrine\Tests\ORM\Functional\InstanceOfMultiLevelTest\Person p
|
||||
WHERE p INSTANCE OF Doctrine\Tests\ORM\Functional\InstanceOfMultiLevelTest\Person';
|
||||
$query = $this->_em->createQuery($dql);
|
||||
$result = $query->getResult();
|
||||
|
||||
$this->assertCount(3, $result);
|
||||
|
||||
foreach ($result as $r) {
|
||||
$this->assertInstanceOf('Doctrine\Tests\ORM\Functional\InstanceOfMultiLevelTest\Person', $r);
|
||||
if ($r instanceof InstanceOfMultiLevelTest\Engineer) {
|
||||
$this->assertEquals('foobar', $r->getName());
|
||||
$this->assertEquals('doctrine', $r->getSpecialization());
|
||||
} elseif ($r instanceof InstanceOfMultiLevelTest\Employee) {
|
||||
$this->assertEquals('bar', $r->getName());
|
||||
$this->assertEquals('qux', $r->getDepartement());
|
||||
} else {
|
||||
$this->assertEquals('foo', $r->getName());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function loadData()
|
||||
{
|
||||
$person = new InstanceOfMultiLevelTest\Person();
|
||||
$person->setName('foo');
|
||||
|
||||
$employee = new InstanceOfMultiLevelTest\Employee();
|
||||
$employee->setName('bar');
|
||||
$employee->setDepartement('qux');
|
||||
|
||||
$engineer = new InstanceOfMultiLevelTest\Engineer();
|
||||
$engineer->setName('foobar');
|
||||
$engineer->setDepartement('dep');
|
||||
$engineer->setSpecialization('doctrine');
|
||||
|
||||
$this->_em->persist($person);
|
||||
$this->_em->persist($employee);
|
||||
$this->_em->persist($engineer);
|
||||
|
||||
$this->_em->flush(array($person, $employee, $engineer));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
namespace Doctrine\Tests\ORM\Functional\InstanceOfMultiLevelTest {
|
||||
/**
|
||||
* @Entity()
|
||||
* @Table(name="instance_of_multi_level_test_person")
|
||||
* @InheritanceType(value="JOINED")
|
||||
* @DiscriminatorColumn(name="kind", type="string")
|
||||
* @DiscriminatorMap(value={
|
||||
* "person": "Doctrine\Tests\ORM\Functional\InstanceOfMultiLevelTest\Person",
|
||||
* "employee": "Doctrine\Tests\ORM\Functional\InstanceOfMultiLevelTest\Employee",
|
||||
* "engineer": "Doctrine\Tests\ORM\Functional\InstanceOfMultiLevelTest\Engineer",
|
||||
* })
|
||||
*/
|
||||
class Person
|
||||
{
|
||||
/**
|
||||
* @Id()
|
||||
* @GeneratedValue()
|
||||
* @Column(type="integer")
|
||||
*/
|
||||
private $id;
|
||||
|
||||
/**
|
||||
* @Column(type="string")
|
||||
*/
|
||||
private $name;
|
||||
|
||||
public function getId()
|
||||
{
|
||||
return $this->id;
|
||||
}
|
||||
|
||||
public function getName()
|
||||
{
|
||||
return $this->name;
|
||||
}
|
||||
|
||||
public function setName($name)
|
||||
{
|
||||
$this->name = $name;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @Entity()
|
||||
* @Table(name="instance_of_multi_level_employee")
|
||||
*/
|
||||
class Employee extends Person
|
||||
{
|
||||
/**
|
||||
* @Column(type="string")
|
||||
*/
|
||||
private $departement;
|
||||
|
||||
public function getDepartement()
|
||||
{
|
||||
return $this->departement;
|
||||
}
|
||||
|
||||
public function setDepartement($departement)
|
||||
{
|
||||
$this->departement = $departement;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @Entity()
|
||||
* @Table(name="instance_of_multi_level_engineer")
|
||||
*/
|
||||
class Engineer extends Employee
|
||||
{
|
||||
/**
|
||||
* @Column(type="string")
|
||||
*/
|
||||
private $specialization;
|
||||
|
||||
public function getSpecialization()
|
||||
{
|
||||
return $this->specialization;
|
||||
}
|
||||
|
||||
public function setSpecialization($specialization)
|
||||
{
|
||||
$this->specialization = $specialization;
|
||||
}
|
||||
}
|
||||
}
|
119
tests/Doctrine/Tests/ORM/Functional/InstanceOfTest.php
Normal file
119
tests/Doctrine/Tests/ORM/Functional/InstanceOfTest.php
Normal file
@ -0,0 +1,119 @@
|
||||
<?php
|
||||
|
||||
namespace Doctrine\Tests\ORM\Functional {
|
||||
|
||||
use Doctrine\Tests\OrmFunctionalTestCase;
|
||||
|
||||
class InstanceOfTest extends OrmFunctionalTestCase
|
||||
{
|
||||
protected function setUp()
|
||||
{
|
||||
parent::setUp();
|
||||
|
||||
$this->_schemaTool->createSchema(array(
|
||||
$this->_em->getClassMetadata(__NAMESPACE__ . '\InstanceOfTest\Person'),
|
||||
$this->_em->getClassMetadata(__NAMESPACE__ . '\InstanceOfTest\Employee'),
|
||||
));
|
||||
}
|
||||
|
||||
public function testInstanceOf()
|
||||
{
|
||||
$this->loadData();
|
||||
|
||||
$dql = 'SELECT p FROM Doctrine\Tests\ORM\Functional\InstanceOfTest\Person p
|
||||
WHERE p INSTANCE OF Doctrine\Tests\ORM\Functional\InstanceOfTest\Person';
|
||||
$query = $this->_em->createQuery($dql);
|
||||
$result = $query->getResult();
|
||||
|
||||
$this->assertCount(2, $result);
|
||||
|
||||
foreach ($result as $r) {
|
||||
$this->assertInstanceOf('Doctrine\Tests\ORM\Functional\InstanceOfTest\Person', $r);
|
||||
if ($r instanceof InstanceOfTest\Employee) {
|
||||
$this->assertEquals('bar', $r->getName());
|
||||
} else {
|
||||
$this->assertEquals('foo', $r->getName());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function loadData()
|
||||
{
|
||||
$person = new InstanceOfTest\Person();
|
||||
$person->setName('foo');
|
||||
|
||||
$employee = new InstanceOfTest\Employee();
|
||||
$employee->setName('bar');
|
||||
$employee->setDepartement('qux');
|
||||
|
||||
$this->_em->persist($person);
|
||||
$this->_em->persist($employee);
|
||||
|
||||
$this->_em->flush(array($person, $employee));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
namespace Doctrine\Tests\ORM\Functional\InstanceOfTest {
|
||||
/**
|
||||
* @Entity()
|
||||
* @Table(name="instance_of_test_person")
|
||||
* @InheritanceType(value="JOINED")
|
||||
* @DiscriminatorColumn(name="kind", type="string")
|
||||
* @DiscriminatorMap(value={
|
||||
* "person": "Doctrine\Tests\ORM\Functional\InstanceOfTest\Person",
|
||||
* "employee": "Doctrine\Tests\ORM\Functional\InstanceOfTest\Employee"
|
||||
* })
|
||||
*/
|
||||
class Person
|
||||
{
|
||||
/**
|
||||
* @Id()
|
||||
* @GeneratedValue()
|
||||
* @Column(type="integer")
|
||||
*/
|
||||
private $id;
|
||||
|
||||
/**
|
||||
* @Column(type="string")
|
||||
*/
|
||||
private $name;
|
||||
|
||||
public function getId()
|
||||
{
|
||||
return $this->id;
|
||||
}
|
||||
|
||||
public function getName()
|
||||
{
|
||||
return $this->name;
|
||||
}
|
||||
|
||||
public function setName($name)
|
||||
{
|
||||
$this->name = $name;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @Entity()
|
||||
* @Table(name="instance_of_test_employee")
|
||||
*/
|
||||
class Employee extends Person
|
||||
{
|
||||
/**
|
||||
* @Column(type="string")
|
||||
*/
|
||||
private $departement;
|
||||
|
||||
public function getDepartement()
|
||||
{
|
||||
return $this->departement;
|
||||
}
|
||||
|
||||
public function setDepartement($departement)
|
||||
{
|
||||
$this->departement = $departement;
|
||||
}
|
||||
}
|
||||
}
|
@ -502,7 +502,7 @@ class SelectSqlGenerationTest extends OrmTestCase
|
||||
{
|
||||
$this->assertSqlGeneration(
|
||||
"SELECT u FROM Doctrine\Tests\Models\Company\CompanyPerson u WHERE u INSTANCE OF Doctrine\Tests\Models\Company\CompanyEmployee",
|
||||
"SELECT c0_.id AS id_0, c0_.name AS name_1, c0_.discr AS discr_2 FROM company_persons c0_ WHERE c0_.discr IN ('employee')"
|
||||
"SELECT c0_.id AS id_0, c0_.name AS name_1, c0_.discr AS discr_2 FROM company_persons c0_ WHERE c0_.discr IN ('manager', 'employee')"
|
||||
);
|
||||
}
|
||||
|
||||
@ -511,7 +511,7 @@ class SelectSqlGenerationTest extends OrmTestCase
|
||||
// This also uses FQCNs starting with or without a backslash in the INSTANCE OF parameter
|
||||
$this->assertSqlGeneration(
|
||||
"SELECT u FROM Doctrine\Tests\Models\Company\CompanyPerson u WHERE u INSTANCE OF (Doctrine\Tests\Models\Company\CompanyEmployee, \Doctrine\Tests\Models\Company\CompanyManager)",
|
||||
"SELECT c0_.id AS id_0, c0_.name AS name_1, c0_.discr AS discr_2 FROM company_persons c0_ WHERE c0_.discr IN ('employee', 'manager')"
|
||||
"SELECT c0_.id AS id_0, c0_.name AS name_1, c0_.discr AS discr_2 FROM company_persons c0_ WHERE c0_.discr IN ('manager', 'employee')"
|
||||
);
|
||||
}
|
||||
|
||||
@ -522,7 +522,7 @@ class SelectSqlGenerationTest extends OrmTestCase
|
||||
{
|
||||
$this->assertSqlGeneration(
|
||||
"SELECT u FROM Doctrine\Tests\Models\Company\CompanyPerson u WHERE u INSTANCE OF \Doctrine\Tests\Models\Company\CompanyEmployee",
|
||||
"SELECT c0_.id AS id_0, c0_.name AS name_1, c0_.discr AS discr_2 FROM company_persons c0_ WHERE c0_.discr IN ('employee')"
|
||||
"SELECT c0_.id AS id_0, c0_.name AS name_1, c0_.discr AS discr_2 FROM company_persons c0_ WHERE c0_.discr IN ('manager', 'employee')"
|
||||
);
|
||||
}
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user