1
0
mirror of synced 2025-01-20 15:31:40 +03:00

[DDC-250] Add tests and fix some glitches and finalized index-by patch.

This commit is contained in:
Benjamin Eberlei 2011-02-05 11:42:10 +01:00
parent a9fe9f43e9
commit 9768d08458
11 changed files with 289 additions and 11 deletions

View File

@ -257,6 +257,7 @@
<xs:attribute name="target-entity" type="xs:string" use="required" /> <xs:attribute name="target-entity" type="xs:string" use="required" />
<xs:attribute name="field" type="xs:NMTOKEN" use="required" /> <xs:attribute name="field" type="xs:NMTOKEN" use="required" />
<xs:attribute name="mapped-by" type="xs:NMTOKEN" /> <xs:attribute name="mapped-by" type="xs:NMTOKEN" />
<xs:attribute name="index-by" type="xs:MNTOKEN" />
<xs:attribute name="inversed-by" type="xs:NMTOKEN" /> <xs:attribute name="inversed-by" type="xs:NMTOKEN" />
<xs:attribute name="fetch" type="orm:fetch-type" default="LAZY" /> <xs:attribute name="fetch" type="orm:fetch-type" default="LAZY" />
</xs:complexType> </xs:complexType>
@ -269,6 +270,7 @@
<xs:attribute name="target-entity" type="xs:string" use="required" /> <xs:attribute name="target-entity" type="xs:string" use="required" />
<xs:attribute name="mapped-by" type="xs:NMTOKEN" use="required" /> <xs:attribute name="mapped-by" type="xs:NMTOKEN" use="required" />
<xs:attribute name="field" type="xs:NMTOKEN" use="required" /> <xs:attribute name="field" type="xs:NMTOKEN" use="required" />
<xs:attribute name="index-by" type="xs:MNTOKEN" />
<xs:attribute name="orphan-removal" type="xs:boolean" default="false" /> <xs:attribute name="orphan-removal" type="xs:boolean" default="false" />
<xs:attribute name="fetch" type="orm:fetch-type" default="LAZY" /> <xs:attribute name="fetch" type="orm:fetch-type" default="LAZY" />
</xs:complexType> </xs:complexType>

View File

@ -723,7 +723,7 @@ class ClassMetadataInfo
$mapping['isOwningSide'] = true; // assume owning side until we hit mappedBy $mapping['isOwningSide'] = true; // assume owning side until we hit mappedBy
// unset optional indexBy attribute if its empty // unset optional indexBy attribute if its empty
if (isset($mapping['indexBy']) && !$mapping['indexBy']) { if (!isset($mapping['indexBy']) || !$mapping['indexBy']) {
unset($mapping['indexBy']); unset($mapping['indexBy']);
} }

View File

@ -763,7 +763,7 @@ class BasicEntityPersister
private function loadArrayFromStatement($assoc, $stmt) private function loadArrayFromStatement($assoc, $stmt)
{ {
$entities = array(); $entities = array();
if ($assoc['indexBy']) { if (isset($assoc['indexBy'])) {
while ($result = $stmt->fetch(PDO::FETCH_ASSOC)) { while ($result = $stmt->fetch(PDO::FETCH_ASSOC)) {
$entity = $this->_createEntity($result); $entity = $this->_createEntity($result);
$entities[$this->_class->reflFields[$assoc['indexBy']]->getValue($entity)] = $entity; $entities[$this->_class->reflFields[$assoc['indexBy']]->getValue($entity)] = $entity;
@ -786,7 +786,7 @@ class BasicEntityPersister
*/ */
private function loadCollectionFromStatement($assoc, $stmt, $coll) private function loadCollectionFromStatement($assoc, $stmt, $coll)
{ {
if ($assoc['indexBy']) { if (isset($assoc['indexBy'])) {
while ($result = $stmt->fetch(PDO::FETCH_ASSOC)) { while ($result = $stmt->fetch(PDO::FETCH_ASSOC)) {
$entity = $this->_createEntity($result); $entity = $this->_createEntity($result);
$coll->hydrateSet($this->_class->reflFields[$assoc['indexBy']]->getValue($entity), $entity); $coll->hydrateSet($this->_class->reflFields[$assoc['indexBy']]->getValue($entity), $entity);

View File

@ -923,6 +923,10 @@ class Parser
$token = $this->_lexer->lookahead; $token = $this->_lexer->lookahead;
$identVariable = $this->IdentificationVariable(); $identVariable = $this->IdentificationVariable();
if (!isset($this->_queryComponents[$identVariable])) {
$this->semanticalError('Identification Variable ' . $identVariable .' used in join path expression but was not defined before.');
}
$this->match(Lexer::T_DOT); $this->match(Lexer::T_DOT);
$this->match(Lexer::T_IDENTIFIER); $this->match(Lexer::T_IDENTIFIER);

View File

@ -718,14 +718,6 @@ class SqlWalker implements TreeWalker
$join = $joinVarDecl->join; $join = $joinVarDecl->join;
$joinType = $join->joinType; $joinType = $join->joinType;
if ($joinVarDecl->indexBy) {
// For Many-To-One or One-To-One associations this obviously makes no sense, but is ignored silently.
$this->_rsm->addIndexBy(
$joinVarDecl->indexBy->simpleStateFieldPathExpression->identificationVariable,
$joinVarDecl->indexBy->simpleStateFieldPathExpression->field
);
}
if ($joinType == AST\Join::JOIN_TYPE_LEFT || $joinType == AST\Join::JOIN_TYPE_LEFTOUTER) { if ($joinType == AST\Join::JOIN_TYPE_LEFT || $joinType == AST\Join::JOIN_TYPE_LEFTOUTER) {
$sql = ' LEFT JOIN '; $sql = ' LEFT JOIN ';
} else { } else {
@ -750,6 +742,16 @@ class SqlWalker implements TreeWalker
} }
} }
if ($joinVarDecl->indexBy) {
// For Many-To-One or One-To-One associations this obviously makes no sense, but is ignored silently.
$this->_rsm->addIndexBy(
$joinVarDecl->indexBy->simpleStateFieldPathExpression->identificationVariable,
$joinVarDecl->indexBy->simpleStateFieldPathExpression->field
);
} else if (isset($relation['indexBy'])) {
$this->_rsm->addIndexBy($joinedDqlAlias, $relation['indexBy']);
}
// This condition is not checking ClassMetadata::MANY_TO_ONE, because by definition it cannot // This condition is not checking ClassMetadata::MANY_TO_ONE, because by definition it cannot
// be the owning side and previously we ensured that $assoc is always the owning side of the associations. // be the owning side and previously we ensured that $assoc is always the owning side of the associations.
// The owning side is necessary at this point because only it contains the JoinColumn information. // The owning side is necessary at this point because only it contains the JoinColumn information.

View File

@ -0,0 +1,48 @@
<?php
namespace Doctrine\Tests\Models\StockExchange;
use Doctrine\Common\Collections\ArrayCollection;
/**
* Bonds have many stocks. This uses a many to many assocation and fails to model how many of a
* particular stock a bond has. But i Need a many-to-many assocation, so please bear with my modelling skills ;)
*
* @Entity
* @Table(name="exchange_bonds")
*/
class Bond
{
/**
* @Id @GeneratedValue @column(type="integer")
* @var int
*/
private $id;
/**
* @column(type="string")
* @var string
*/
private $name;
/**
* @ManyToMany(targetEntity="Stock", indexBy="symbol")
* @JoinTable(name="exchange_bonds_stocks")
* @var Stock[]
*/
public $stocks;
public function __construct($name)
{
$this->name = $name;
}
public function getId()
{
return $this->id;
}
public function addStock(Stock $stock)
{
$this->stocks[$stock->getSymbol()] = $stock;
}
}

View File

@ -0,0 +1,56 @@
<?php
namespace Doctrine\Tests\Models\StockExchange;
use Doctrine\Common\Collections\ArrayCollection;
/**
* @Entity
* @Table(name="exchange_markets")
*/
class Market
{
/**
* @Id @Column(type="integer") @GeneratedValue
* @var int
*/
private $id;
/**
* @Column(type="string")
* @var string
*/
private $name;
/**
* @OneToMany(targetEntity="Stock", mappedBy="market", indexBy="symbol")
* @var Stock[]
*/
public $stocks;
public function __construct($name)
{
$this->name = $name;
$this->stocks = new ArrayCollection();
}
public function getId()
{
return $this->id;
}
public function getName()
{
return $this->name;
}
public function addStock(Stock $stock)
{
$this->stocks[$stock->getSymbol()] = $stock;
}
public function getStock($symbol)
{
return $this->stocks[$symbol];
}
}

View File

@ -0,0 +1,49 @@
<?php
namespace Doctrine\Tests\Models\StockExchange;
use Doctrine\Common\Collections\ArrayCollection;
/**
* @Entity
* @Table(name="exchange_stocks")
*/
class Stock
{
/**
* @Id @GeneratedValue @Column(type="integer")
* @var int
*/
private $id;
/**
* For real this column would have to be unique=true. But I want to test behavior of non-unique overrides.
*
* @Column(type="string")
*/
private $symbol;
/**
* @Column(type="decimal")
*/
private $price;
/**
* @ManyToOne(targetEntity="Market", inversedBy="stocks")
* @var Market
*/
private $market;
public function __construct($symbol, $initialOfferingPrice, Market $market)
{
$this->symbol = $symbol;
$this->price = $initialOfferingPrice;
$this->market = $market;
$market->addStock($this);
}
public function getSymbol()
{
return $this->symbol;
}
}

View File

@ -45,6 +45,7 @@ class AllTests
$suite->addTestSuite('Doctrine\Tests\ORM\Functional\ManyToManySelfReferentialAssociationTest'); $suite->addTestSuite('Doctrine\Tests\ORM\Functional\ManyToManySelfReferentialAssociationTest');
$suite->addTestSuite('Doctrine\Tests\ORM\Functional\OrderedCollectionTest'); $suite->addTestSuite('Doctrine\Tests\ORM\Functional\OrderedCollectionTest');
$suite->addTestSuite('Doctrine\Tests\ORM\Functional\OrderedJoinedTableInheritanceCollectionTest'); $suite->addTestSuite('Doctrine\Tests\ORM\Functional\OrderedJoinedTableInheritanceCollectionTest');
$suite->addTestSuite('Doctrine\Tests\ORM\Functional\IndexByAssociationTest');
$suite->addTestSuite('Doctrine\Tests\ORM\Functional\CompositePrimaryKeyTest'); $suite->addTestSuite('Doctrine\Tests\ORM\Functional\CompositePrimaryKeyTest');
$suite->addTestSuite('Doctrine\Tests\ORM\Functional\ReferenceProxyTest'); $suite->addTestSuite('Doctrine\Tests\ORM\Functional\ReferenceProxyTest');
$suite->addTestSuite('Doctrine\Tests\ORM\Functional\LifecycleCallbackTest'); $suite->addTestSuite('Doctrine\Tests\ORM\Functional\LifecycleCallbackTest');

View File

@ -0,0 +1,105 @@
<?php
namespace Doctrine\Tests\ORM\Functional;
use Doctrine\Tests\Models\StockExchange\Stock;
use Doctrine\Tests\Models\StockExchange\Market;
use Doctrine\Tests\Models\StockExchange\Bond;
require_once __DIR__ . '/../../TestInit.php';
/**
* @group DDC-250
*/
class IndexByAssociationTest extends \Doctrine\Tests\OrmFunctionalTestCase
{
/**
* @var Doctrine\Tests\Models\StockExchange\Market
*/
private $market;
private $bond;
public function setUp()
{
$this->useModelSet('stockexchange');
parent::setUp();
$this->loadFixture();
}
public function loadFixture()
{
$this->market = new Market("Some Exchange");
$stock1 = new Stock("AAPL", 10, $this->market);
$stock2 = new Stock("GOOG", 20, $this->market);
$this->bond = new Bond("MyBond");
$this->bond->addStock($stock1);
$this->bond->addStock($stock2);
$this->_em->persist($this->market);
$this->_em->persist($stock1);
$this->_em->persist($stock2);
$this->_em->persist($this->bond);
$this->_em->flush();
$this->_em->clear();
}
public function testManyToOneFinder()
{
/* @var $market Doctrine\Tests\Models\StockExchange\Market */
$market = $this->_em->find('Doctrine\Tests\Models\StockExchange\Market', $this->market->getId());
$this->assertEquals(2, count($market->stocks));
$this->assertTrue(isset($market->stocks['AAPL']), "AAPL symbol has to be key in indexed assocation.");
$this->assertTrue(isset($market->stocks['GOOG']), "GOOG symbol has to be key in indexed assocation.");
$this->assertEquals("AAPL", $market->stocks['AAPL']->getSymbol());
$this->assertEquals("GOOG", $market->stocks['GOOG']->getSymbol());
}
public function testManyToOneDQL()
{
$dql = "SELECT m, s FROM Doctrine\Tests\Models\StockExchange\Market m JOIN m.stocks s WHERE m.id = ?1";
$market = $this->_em->createQuery($dql)->setParameter(1, $this->market->getId())->getSingleResult();
$this->assertEquals(2, count($market->stocks));
$this->assertTrue(isset($market->stocks['AAPL']), "AAPL symbol has to be key in indexed assocation.");
$this->assertTrue(isset($market->stocks['GOOG']), "GOOG symbol has to be key in indexed assocation.");
$this->assertEquals("AAPL", $market->stocks['AAPL']->getSymbol());
$this->assertEquals("GOOG", $market->stocks['GOOG']->getSymbol());
}
public function testManyToMany()
{
$bond = $this->_em->find('Doctrine\Tests\Models\StockExchange\Bond', $this->bond->getId());
$this->assertEquals(2, count($bond->stocks));
$this->assertTrue(isset($bond->stocks['AAPL']), "AAPL symbol has to be key in indexed assocation.");
$this->assertTrue(isset($bond->stocks['GOOG']), "GOOG symbol has to be key in indexed assocation.");
$this->assertEquals("AAPL", $bond->stocks['AAPL']->getSymbol());
$this->assertEquals("GOOG", $bond->stocks['GOOG']->getSymbol());
}
public function testManytoManyDQL()
{
$dql = "SELECT b, s FROM Doctrine\Tests\Models\StockExchange\Bond b JOIN b.stocks s WHERE b.id = ?1";
$bond = $this->_em->createQuery($dql)->setParameter(1, $this->bond->getId())->getSingleResult();
$this->assertEquals(2, count($bond->stocks));
$this->assertTrue(isset($bond->stocks['AAPL']), "AAPL symbol has to be key in indexed assocation.");
$this->assertTrue(isset($bond->stocks['GOOG']), "GOOG symbol has to be key in indexed assocation.");
$this->assertEquals("AAPL", $bond->stocks['AAPL']->getSymbol());
$this->assertEquals("GOOG", $bond->stocks['GOOG']->getSymbol());
}
public function testDqlOverrideIndexBy()
{
$dql = "SELECT b, s FROM Doctrine\Tests\Models\StockExchange\Bond b JOIN b.stocks s INDEX BY s.id WHERE b.id = ?1";
$bond = $this->_em->createQuery($dql)->setParameter(1, $this->bond->getId())->getSingleResult();
$this->assertEquals(2, count($bond->stocks));
$this->assertFalse(isset($bond->stocks['AAPL']), "AAPL symbol not exists in re-indexed assocation.");
$this->assertFalse(isset($bond->stocks['GOOG']), "GOOG symbol not exists in re-indexed assocation.");
}
}

View File

@ -99,6 +99,11 @@ abstract class OrmFunctionalTestCase extends OrmTestCase
'Doctrine\Tests\Models\DDC117\DDC117ApproveChanges', 'Doctrine\Tests\Models\DDC117\DDC117ApproveChanges',
'Doctrine\Tests\Models\DDC117\DDC117Editor', 'Doctrine\Tests\Models\DDC117\DDC117Editor',
), ),
'stockexchange' => array(
'Doctrine\Tests\Models\StockExchange\Bond',
'Doctrine\Tests\Models\StockExchange\Stock',
'Doctrine\Tests\Models\StockExchange\Market',
),
); );
protected function useModelSet($setName) protected function useModelSet($setName)
@ -191,6 +196,12 @@ abstract class OrmFunctionalTestCase extends OrmTestCase
$conn->executeUpdate('DELETE FROM DDC117Translation'); $conn->executeUpdate('DELETE FROM DDC117Translation');
$conn->executeUpdate('DELETE FROM DDC117Article'); $conn->executeUpdate('DELETE FROM DDC117Article');
} }
if (isset($this->_usedModelSets['stockexchange'])) {
$conn->executeUpdate('DELETE FROM exchange_bonds_stocks');
$conn->executeUpdate('DELETE FROM exchange_bonds');
$conn->executeUpdate('DELETE FROM exchange_stocks');
$conn->executeUpdate('DELETE FROM exchange_markets');
}
$this->_em->clear(); $this->_em->clear();
} }