From 97a6caf059243643835e4ccd4df311ebbdea67a3 Mon Sep 17 00:00:00 2001 From: Benjamin Eberlei Date: Tue, 11 Oct 2011 13:22:26 +0200 Subject: [PATCH 001/135] Update Common to latest to have the current Persistence Interface --- lib/vendor/doctrine-common | 2 +- tests/Doctrine/Tests/ORM/Functional/QueryCacheTest.php | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/vendor/doctrine-common b/lib/vendor/doctrine-common index ef431a148..b2fd909b4 160000 --- a/lib/vendor/doctrine-common +++ b/lib/vendor/doctrine-common @@ -1 +1 @@ -Subproject commit ef431a14852d7e8f2d0ea789487509ab266e5ce2 +Subproject commit b2fd909b4b5476df01744c9d34c7a23723a687b6 diff --git a/tests/Doctrine/Tests/ORM/Functional/QueryCacheTest.php b/tests/Doctrine/Tests/ORM/Functional/QueryCacheTest.php index eb7d662d6..13a774b76 100644 --- a/tests/Doctrine/Tests/ORM/Functional/QueryCacheTest.php +++ b/tests/Doctrine/Tests/ORM/Functional/QueryCacheTest.php @@ -104,7 +104,7 @@ class QueryCacheTest extends \Doctrine\Tests\OrmFunctionalTestCase $query = $this->_em->createQuery('select ux from Doctrine\Tests\Models\CMS\CmsUser ux'); - $cache = $this->getMock('Doctrine\Common\Cache\ArrayCache', array('doFetch', 'doSave')); + $cache = $this->getMock('Doctrine\Common\Cache\ArrayCache', array('doFetch', 'doSave', 'doGetStats')); $cache->expects($this->at(0)) ->method('doFetch') ->with($this->isType('string')) @@ -135,7 +135,7 @@ class QueryCacheTest extends \Doctrine\Tests\OrmFunctionalTestCase ->will($this->returnValue($sqlExecMock)); $cache = $this->getMock('Doctrine\Common\Cache\CacheProvider', - array('doFetch', 'doContains', 'doSave', 'doDelete', 'doFlush')); + array('doFetch', 'doContains', 'doSave', 'doDelete', 'doFlush', 'doGetStats')); $cache->expects($this->once()) ->method('doFetch') ->with($this->isType('string')) From cb0e5dbff32711093e4f7055ab3d9daaf036d401 Mon Sep 17 00:00:00 2001 From: Benjamin Eberlei Date: Sun, 16 Oct 2011 12:46:17 +0200 Subject: [PATCH 002/135] DDC-1385 - Fixed Mixed result hydration using INDEX BY to work on the top-level indexes, not some weird result. This is a BC break to those that actually use this crazy logic, sorry for that :-) --- .../ORM/Internal/Hydration/ObjectHydrator.php | 22 +++--- .../ORM/Hydration/ObjectHydratorTest.php | 69 ++++++++++++++++--- 2 files changed, 70 insertions(+), 21 deletions(-) diff --git a/lib/Doctrine/ORM/Internal/Hydration/ObjectHydrator.php b/lib/Doctrine/ORM/Internal/Hydration/ObjectHydrator.php index 1287a138b..b07f1ae48 100644 --- a/lib/Doctrine/ORM/Internal/Hydration/ObjectHydrator.php +++ b/lib/Doctrine/ORM/Internal/Hydration/ObjectHydrator.php @@ -430,19 +430,16 @@ class ObjectHydrator extends AbstractHydrator $element = $this->_getEntity($rowData[$dqlAlias], $dqlAlias); if (isset($this->_rsm->indexByMap[$dqlAlias])) { $field = $this->_rsm->indexByMap[$dqlAlias]; - $key = $this->_ce[$entityName]->reflFields[$field]->getValue($element); + $resultKey = $this->_ce[$entityName]->reflFields[$field]->getValue($element); if ($this->_rsm->isMixed) { - $element = array($key => $element); - $result[] = $element; - $this->_identifierMap[$dqlAlias][$id[$dqlAlias]] = $this->_resultCounter; - ++$this->_resultCounter; - } else { - $result[$key] = $element; - $this->_identifierMap[$dqlAlias][$id[$dqlAlias]] = $key; + $element = array(0 => $element); } + $result[$resultKey] = $element; + $this->_identifierMap[$dqlAlias][$id[$dqlAlias]] = $resultKey; + if (isset($this->_hints['collection'])) { - $this->_hints['collection']->hydrateSet($key, $element); + $this->_hints['collection']->hydrateSet($resultKey, $element); } } else { if ($this->_rsm->isMixed) { @@ -464,6 +461,7 @@ class ObjectHydrator extends AbstractHydrator // Update result pointer $index = $this->_identifierMap[$dqlAlias][$id[$dqlAlias]]; $this->_resultPointers[$dqlAlias] = $result[$index]; + $resultKey = $index; /*if ($this->_rsm->isMixed) { $result[] = $result[$index]; ++$this->_resultCounter; @@ -474,8 +472,12 @@ class ObjectHydrator extends AbstractHydrator // Append scalar values to mixed result sets if (isset($scalars)) { + if ( ! isset($resultKey) ) { + $resultKey = $this->_resultCounter - 1; + } + foreach ($scalars as $name => $value) { - $result[$this->_resultCounter - 1][$name] = $value; + $result[$resultKey][$name] = $value; } } } diff --git a/tests/Doctrine/Tests/ORM/Hydration/ObjectHydratorTest.php b/tests/Doctrine/Tests/ORM/Hydration/ObjectHydratorTest.php index 581a3504a..68a76cc80 100644 --- a/tests/Doctrine/Tests/ORM/Hydration/ObjectHydratorTest.php +++ b/tests/Doctrine/Tests/ORM/Hydration/ObjectHydratorTest.php @@ -352,24 +352,24 @@ class ObjectHydratorTest extends HydrationTestCase $this->assertEquals(2, count($result)); $this->assertTrue(is_array($result)); - $this->assertTrue(is_array($result[0])); $this->assertTrue(is_array($result[1])); + $this->assertTrue(is_array($result[2])); // test the scalar values - $this->assertEquals('ROMANB', $result[0]['nameUpper']); - $this->assertEquals('JWAGE', $result[1]['nameUpper']); + $this->assertEquals('ROMANB', $result[1]['nameUpper']); + $this->assertEquals('JWAGE', $result[2]['nameUpper']); - $this->assertInstanceOf('Doctrine\Tests\Models\CMS\CmsUser', $result[0]['1']); - $this->assertInstanceOf('Doctrine\Tests\Models\CMS\CmsUser', $result[1]['2']); - $this->assertInstanceOf('Doctrine\ORM\PersistentCollection', $result[0]['1']->phonenumbers); + $this->assertInstanceOf('Doctrine\Tests\Models\CMS\CmsUser', $result[1][0]); + $this->assertInstanceOf('Doctrine\Tests\Models\CMS\CmsUser', $result[2][0]); + $this->assertInstanceOf('Doctrine\ORM\PersistentCollection', $result[1][0]->phonenumbers); // first user => 2 phonenumbers. notice the custom indexing by user id - $this->assertEquals(2, count($result[0]['1']->phonenumbers)); + $this->assertEquals(2, count($result[1][0]->phonenumbers)); // second user => 1 phonenumber. notice the custom indexing by user id - $this->assertEquals(1, count($result[1]['2']->phonenumbers)); + $this->assertEquals(1, count($result[2][0]->phonenumbers)); // test the custom indexing of the phonenumbers - $this->assertTrue(isset($result[0]['1']->phonenumbers['42'])); - $this->assertTrue(isset($result[0]['1']->phonenumbers['43'])); - $this->assertTrue(isset($result[1]['2']->phonenumbers['91'])); + $this->assertTrue(isset($result[1][0]->phonenumbers['42'])); + $this->assertTrue(isset($result[1][0]->phonenumbers['43'])); + $this->assertTrue(isset($result[2][0]->phonenumbers['91'])); } /** @@ -1169,4 +1169,51 @@ class ObjectHydratorTest extends HydrationTestCase $this->assertInstanceOf('Doctrine\Tests\Models\CMS\CmsAddress', $result[0][0]->address); $this->assertNull($result[1][0]->address); } + + /** + * @group DDC-1385 + */ + public function testIndexByAndMixedResult() + { + $rsm = new ResultSetMapping; + $rsm->addEntityResult('Doctrine\Tests\Models\CMS\CmsUser', 'u'); + $rsm->addFieldResult('u', 'u__id', 'id'); + $rsm->addFieldResult('u', 'u__status', 'status'); + $rsm->addScalarResult('sclr0', 'nameUpper'); + $rsm->addIndexBy('u', 'id'); + + // Faked result set + $resultSet = array( + //row1 + array( + 'u__id' => '1', + 'u__status' => 'developer', + 'sclr0' => 'ROMANB', + ), + array( + 'u__id' => '2', + 'u__status' => 'developer', + 'sclr0' => 'JWAGE', + ), + ); + + $stmt = new HydratorMockStatement($resultSet); + $hydrator = new \Doctrine\ORM\Internal\Hydration\ObjectHydrator($this->_em); + + $result = $hydrator->hydrateAll($stmt, $rsm, array(Query::HINT_FORCE_PARTIAL_LOAD => true)); + + $this->assertEquals(2, count($result)); + $this->assertTrue(isset($result[1])); + $this->assertEquals(1, $result[1][0]->id); + $this->assertTrue(isset($result[2])); + $this->assertEquals(2, $result[2][0]->id); + } + + /** + * @group DDC-1385 + */ + public function testIndexByAndScalarResult() + { + $rsm = new ResultSetMapping; + } } From 2730f64d90cb0ec97fe35cea950d530722bbafbc Mon Sep 17 00:00:00 2001 From: Benjamin Eberlei Date: Sun, 16 Oct 2011 16:13:59 +0200 Subject: [PATCH 003/135] DDC-1385 - Refactor ObjectHydrator --- .../ORM/Internal/Hydration/ObjectHydrator.php | 24 +++++++++---------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/lib/Doctrine/ORM/Internal/Hydration/ObjectHydrator.php b/lib/Doctrine/ORM/Internal/Hydration/ObjectHydrator.php index b07f1ae48..69b441480 100644 --- a/lib/Doctrine/ORM/Internal/Hydration/ObjectHydrator.php +++ b/lib/Doctrine/ORM/Internal/Hydration/ObjectHydrator.php @@ -428,32 +428,32 @@ class ObjectHydrator extends AbstractHydrator // check for existing result from the iterations before if ( ! isset($this->_identifierMap[$dqlAlias][$id[$dqlAlias]])) { $element = $this->_getEntity($rowData[$dqlAlias], $dqlAlias); + if ($this->_rsm->isMixed) { + $element = array(0 => $element); + } + if (isset($this->_rsm->indexByMap[$dqlAlias])) { $field = $this->_rsm->indexByMap[$dqlAlias]; - $resultKey = $this->_ce[$entityName]->reflFields[$field]->getValue($element); - if ($this->_rsm->isMixed) { - $element = array(0 => $element); - } - - $result[$resultKey] = $element; - $this->_identifierMap[$dqlAlias][$id[$dqlAlias]] = $resultKey; + $resultKey = $rowData[$dqlAlias][$field]; if (isset($this->_hints['collection'])) { $this->_hints['collection']->hydrateSet($resultKey, $element); } + + $result[$resultKey] = $element; } else { - if ($this->_rsm->isMixed) { - $element = array(0 => $element); - } - $result[] = $element; - $this->_identifierMap[$dqlAlias][$id[$dqlAlias]] = $this->_resultCounter; + $resultKey = $this->_resultCounter; ++$this->_resultCounter; if (isset($this->_hints['collection'])) { $this->_hints['collection']->hydrateAdd($element); } + + $result[] = $element; } + $this->_identifierMap[$dqlAlias][$id[$dqlAlias]] = $resultKey; + // Update result pointer $this->_resultPointers[$dqlAlias] = $element; From ee924ffaba4dc8b666dc2b6070ce1b4b5f7f1308 Mon Sep 17 00:00:00 2001 From: Benjamin Eberlei Date: Sun, 16 Oct 2011 16:27:50 +0200 Subject: [PATCH 004/135] DDC-1385 - Fix scalar handling for array hydrator --- .../ORM/Internal/Hydration/ArrayHydrator.php | 32 ++++++----- .../Tests/ORM/Hydration/ArrayHydratorTest.php | 55 ++++++++++++++++--- .../ORM/Hydration/ObjectHydratorTest.php | 8 --- 3 files changed, 64 insertions(+), 31 deletions(-) diff --git a/lib/Doctrine/ORM/Internal/Hydration/ArrayHydrator.php b/lib/Doctrine/ORM/Internal/Hydration/ArrayHydrator.php index 4b1c21c6f..cd30a37c0 100644 --- a/lib/Doctrine/ORM/Internal/Hydration/ArrayHydrator.php +++ b/lib/Doctrine/ORM/Internal/Hydration/ArrayHydrator.php @@ -174,26 +174,24 @@ class ArrayHydrator extends AbstractHydrator // Check for an existing element if ($this->_isSimpleQuery || ! isset($this->_identifierMap[$dqlAlias][$id[$dqlAlias]])) { $element = $rowData[$dqlAlias]; + if ($this->_rsm->isMixed) { + $element = array(0 => $element); + } + if (isset($this->_rsm->indexByMap[$dqlAlias])) { $field = $this->_rsm->indexByMap[$dqlAlias]; - if ($this->_rsm->isMixed) { - $result[] = array($element[$field] => $element); - ++$this->_resultCounter; - } else { - $result[$element[$field]] = $element; - } + $resultKey = $rowData[$dqlAlias][$field]; + $result[$resultKey] = $element; } else { - if ($this->_rsm->isMixed) { - $result[] = array($element); - ++$this->_resultCounter; - } else { - $result[] = $element; - } + $resultKey = $this->_resultCounter; + $result[] = $element; + ++$this->_resultCounter; } - end($result); - $this->_identifierMap[$dqlAlias][$id[$dqlAlias]] = key($result); + + $this->_identifierMap[$dqlAlias][$id[$dqlAlias]] = $resultKey; } else { $index = $this->_identifierMap[$dqlAlias][$id[$dqlAlias]]; + $resultKey = $index; /*if ($this->_rsm->isMixed) { $result[] =& $result[$index]; ++$this->_resultCounter; @@ -205,8 +203,12 @@ class ArrayHydrator extends AbstractHydrator // Append scalar values to mixed result sets if (isset($scalars)) { + if ( ! isset($resultKey) ) { + $resultKey = $this->_resultCounter - 1; + } + foreach ($scalars as $name => $value) { - $result[$this->_resultCounter - 1][$name] = $value; + $result[$resultKey][$name] = $value; } } } diff --git a/tests/Doctrine/Tests/ORM/Hydration/ArrayHydratorTest.php b/tests/Doctrine/Tests/ORM/Hydration/ArrayHydratorTest.php index a318f78af..616f19146 100644 --- a/tests/Doctrine/Tests/ORM/Hydration/ArrayHydratorTest.php +++ b/tests/Doctrine/Tests/ORM/Hydration/ArrayHydratorTest.php @@ -256,20 +256,20 @@ class ArrayHydratorTest extends HydrationTestCase $this->assertEquals(2, count($result)); $this->assertTrue(is_array($result)); - $this->assertTrue(is_array($result[0])); $this->assertTrue(is_array($result[1])); + $this->assertTrue(is_array($result[2])); // test the scalar values - $this->assertEquals('ROMANB', $result[0]['nameUpper']); - $this->assertEquals('JWAGE', $result[1]['nameUpper']); + $this->assertEquals('ROMANB', $result[1]['nameUpper']); + $this->assertEquals('JWAGE', $result[2]['nameUpper']); // first user => 2 phonenumbers. notice the custom indexing by user id - $this->assertEquals(2, count($result[0]['1']['phonenumbers'])); + $this->assertEquals(2, count($result[1][0]['phonenumbers'])); // second user => 1 phonenumber. notice the custom indexing by user id - $this->assertEquals(1, count($result[1]['2']['phonenumbers'])); + $this->assertEquals(1, count($result[2][0]['phonenumbers'])); // test the custom indexing of the phonenumbers - $this->assertTrue(isset($result[0]['1']['phonenumbers']['42'])); - $this->assertTrue(isset($result[0]['1']['phonenumbers']['43'])); - $this->assertTrue(isset($result[1]['2']['phonenumbers']['91'])); + $this->assertTrue(isset($result[1][0]['phonenumbers']['42'])); + $this->assertTrue(isset($result[1][0]['phonenumbers']['43'])); + $this->assertTrue(isset($result[2][0]['phonenumbers']['91'])); } /** @@ -817,4 +817,43 @@ class ArrayHydratorTest extends HydrationTestCase $this->assertEquals(array('id' => 2, 'status' => 'developer'), $result[2][0]); $this->assertNull($result[3][0]); } + + /** + * @group DDC-1385 + */ + public function testIndexByAndMixedResult() + { + $rsm = new ResultSetMapping; + $rsm->addEntityResult('Doctrine\Tests\Models\CMS\CmsUser', 'u'); + $rsm->addFieldResult('u', 'u__id', 'id'); + $rsm->addFieldResult('u', 'u__status', 'status'); + $rsm->addScalarResult('sclr0', 'nameUpper'); + $rsm->addIndexBy('u', 'id'); + + // Faked result set + $resultSet = array( + //row1 + array( + 'u__id' => '1', + 'u__status' => 'developer', + 'sclr0' => 'ROMANB', + ), + array( + 'u__id' => '2', + 'u__status' => 'developer', + 'sclr0' => 'JWAGE', + ), + ); + + $stmt = new HydratorMockStatement($resultSet); + $hydrator = new \Doctrine\ORM\Internal\Hydration\ArrayHydrator($this->_em); + + $result = $hydrator->hydrateAll($stmt, $rsm); + + $this->assertEquals(2, count($result)); + $this->assertTrue(isset($result[1])); + $this->assertEquals(1, $result[1][0]['id']); + $this->assertTrue(isset($result[2])); + $this->assertEquals(2, $result[2][0]['id']); + } } \ No newline at end of file diff --git a/tests/Doctrine/Tests/ORM/Hydration/ObjectHydratorTest.php b/tests/Doctrine/Tests/ORM/Hydration/ObjectHydratorTest.php index 68a76cc80..d2d6f24ce 100644 --- a/tests/Doctrine/Tests/ORM/Hydration/ObjectHydratorTest.php +++ b/tests/Doctrine/Tests/ORM/Hydration/ObjectHydratorTest.php @@ -1208,12 +1208,4 @@ class ObjectHydratorTest extends HydrationTestCase $this->assertTrue(isset($result[2])); $this->assertEquals(2, $result[2][0]->id); } - - /** - * @group DDC-1385 - */ - public function testIndexByAndScalarResult() - { - $rsm = new ResultSetMapping; - } } From 8466060797f0f9c85dd8f9d6b36906c4cbf2e8f7 Mon Sep 17 00:00:00 2001 From: Benjamin Eberlei Date: Sun, 16 Oct 2011 19:23:20 +0200 Subject: [PATCH 005/135] DDC-1385 - Add INDEX BY scalar variables on the top-level --- .../ORM/Internal/Hydration/ArrayHydrator.php | 18 ++++++---- .../ORM/Internal/Hydration/ObjectHydrator.php | 17 +++++---- lib/Doctrine/ORM/Query/ResultSetMapping.php | 36 ++++++++++++++++++- lib/Doctrine/ORM/Query/SqlWalker.php | 25 ++++++++++--- .../ORM/Hydration/ObjectHydratorTest.php | 28 +++++++++++++++ 5 files changed, 104 insertions(+), 20 deletions(-) diff --git a/lib/Doctrine/ORM/Internal/Hydration/ArrayHydrator.php b/lib/Doctrine/ORM/Internal/Hydration/ArrayHydrator.php index cd30a37c0..cbc77d2d2 100644 --- a/lib/Doctrine/ORM/Internal/Hydration/ArrayHydrator.php +++ b/lib/Doctrine/ORM/Internal/Hydration/ArrayHydrator.php @@ -66,12 +66,12 @@ class ArrayHydrator extends AbstractHydrator } /** @override */ - protected function _hydrateRow(array $data, array &$cache, array &$result) + protected function _hydrateRow(array $row, array &$cache, array &$result) { // 1) Initialize $id = $this->_idTemplate; // initialize the id-memory $nonemptyComponents = array(); - $rowData = $this->_gatherRowData($data, $cache, $id, $nonemptyComponents); + $rowData = $this->_gatherRowData($row, $cache, $id, $nonemptyComponents); // Extract scalar values. They're appended at the end. if (isset($rowData['scalars'])) { @@ -128,8 +128,7 @@ class ArrayHydrator extends AbstractHydrator if ( ! $indexExists || ! $indexIsValid) { $element = $data; if (isset($this->_rsm->indexByMap[$dqlAlias])) { - $field = $this->_rsm->indexByMap[$dqlAlias]; - $baseElement[$relationAlias][$element[$field]] = $element; + $baseElement[$relationAlias][$row[$this->_rsm->indexByMap[$dqlAlias]]] = $element; } else { $baseElement[$relationAlias][] = $element; } @@ -167,6 +166,7 @@ class ArrayHydrator extends AbstractHydrator } else { $result[] = null; } + $resultKey = $this->_resultCounter; ++$this->_resultCounter; continue; } @@ -179,8 +179,7 @@ class ArrayHydrator extends AbstractHydrator } if (isset($this->_rsm->indexByMap[$dqlAlias])) { - $field = $this->_rsm->indexByMap[$dqlAlias]; - $resultKey = $rowData[$dqlAlias][$field]; + $resultKey = $row[$this->_rsm->indexByMap[$dqlAlias]]; $result[$resultKey] = $element; } else { $resultKey = $this->_resultCounter; @@ -204,7 +203,12 @@ class ArrayHydrator extends AbstractHydrator // Append scalar values to mixed result sets if (isset($scalars)) { if ( ! isset($resultKey) ) { - $resultKey = $this->_resultCounter - 1; + // this only ever happens when no object is fetched (scalar result only) + if (isset($this->_rsm->indexByMap['scalars'])) { + $resultKey = $row[$this->_rsm->indexByMap['scalars']]; + } else { + $resultKey = $this->_resultCounter - 1; + } } foreach ($scalars as $name => $value) { diff --git a/lib/Doctrine/ORM/Internal/Hydration/ObjectHydrator.php b/lib/Doctrine/ORM/Internal/Hydration/ObjectHydrator.php index 69b441480..4cbba6bb7 100644 --- a/lib/Doctrine/ORM/Internal/Hydration/ObjectHydrator.php +++ b/lib/Doctrine/ORM/Internal/Hydration/ObjectHydrator.php @@ -273,13 +273,13 @@ class ObjectHydrator extends AbstractHydrator * @param array $cache The cache to use. * @param array $result The result array to fill. */ - protected function _hydrateRow(array $data, array &$cache, array &$result) + protected function _hydrateRow(array $row, array &$cache, array &$result) { // Initialize $id = $this->_idTemplate; // initialize the id-memory $nonemptyComponents = array(); // Split the row data into chunks of class data. - $rowData = $this->_gatherRowData($data, $cache, $id, $nonemptyComponents); + $rowData = $this->_gatherRowData($row, $cache, $id, $nonemptyComponents); // Extract scalar values. They're appended at the end. if (isset($rowData['scalars'])) { @@ -352,8 +352,7 @@ class ObjectHydrator extends AbstractHydrator $element = $this->_getEntity($data, $dqlAlias); if (isset($this->_rsm->indexByMap[$dqlAlias])) { - $field = $this->_rsm->indexByMap[$dqlAlias]; - $indexValue = $this->_ce[$entityName]->reflFields[$field]->getValue($element); + $indexValue = $row[$this->_rsm->indexByMap[$dqlAlias]]; $reflFieldValue->hydrateSet($indexValue, $element); $this->_identifierMap[$path][$id[$parentAlias]][$id[$dqlAlias]] = $indexValue; } else { @@ -421,6 +420,7 @@ class ObjectHydrator extends AbstractHydrator } else { $result[] = null; } + $resultKey = $this->_resultCounter; ++$this->_resultCounter; continue; } @@ -433,8 +433,7 @@ class ObjectHydrator extends AbstractHydrator } if (isset($this->_rsm->indexByMap[$dqlAlias])) { - $field = $this->_rsm->indexByMap[$dqlAlias]; - $resultKey = $rowData[$dqlAlias][$field]; + $resultKey = $row[$this->_rsm->indexByMap[$dqlAlias]]; if (isset($this->_hints['collection'])) { $this->_hints['collection']->hydrateSet($resultKey, $element); @@ -473,7 +472,11 @@ class ObjectHydrator extends AbstractHydrator // Append scalar values to mixed result sets if (isset($scalars)) { if ( ! isset($resultKey) ) { - $resultKey = $this->_resultCounter - 1; + if (isset($this->_rsm->indexByMap['scalars'])) { + $resultKey = $row[$this->_rsm->indexByMap['scalars']]; + } else { + $resultKey = $this->_resultCounter - 1; + } } foreach ($scalars as $name => $value) { diff --git a/lib/Doctrine/ORM/Query/ResultSetMapping.php b/lib/Doctrine/ORM/Query/ResultSetMapping.php index 3a086e2cd..fcfc9b834 100644 --- a/lib/Doctrine/ORM/Query/ResultSetMapping.php +++ b/lib/Doctrine/ORM/Query/ResultSetMapping.php @@ -157,7 +157,41 @@ class ResultSetMapping */ public function addIndexBy($alias, $fieldName) { - $this->indexByMap[$alias] = $fieldName; + $found = false; + foreach ($this->fieldMappings AS $columnName => $columnFieldName) { + if ($columnFieldName === $fieldName && $this->columnOwnerMap[$columnName] == $alias) { + $this->addIndexByColumn($alias, $columnName); + $found = true; + break; + } + } + if (!$found) { + throw new \LogicException("Cannot add index by for dql alias " . $alias . " and field " . + $fieldName . " without calling addFieldResult() for them before."); + } + } + + /** + * Set to index by a scalar result column name + * + * @param $resultColumnName + * @return void + */ + public function addIndexByScalar($resultColumnName) + { + $this->indexByMap['scalars'] = $resultColumnName; + } + + /** + * Sets a column to use for indexing an entity or joined entity result by the given alias name. + * + * @param $alias + * @param $resultColumnName + * @return void + */ + public function addIndexByColumn($alias, $resultColumnName) + { + $this->indexByMap[$alias] = $resultColumnName; } /** diff --git a/lib/Doctrine/ORM/Query/SqlWalker.php b/lib/Doctrine/ORM/Query/SqlWalker.php index 188350197..e98e0db31 100644 --- a/lib/Doctrine/ORM/Query/SqlWalker.php +++ b/lib/Doctrine/ORM/Query/SqlWalker.php @@ -71,6 +71,13 @@ class SqlWalker implements TreeWalker /** Map from result variable names to their SQL column alias names. */ private $_scalarResultAliasMap = array(); + /** + * Map from DQL-Alias + Field-Name to SQL Column Alias + * + * @var array + */ + private $_scalarFields = array(); + /** Map of all components/classes that appear in the DQL query. */ private $_queryComponents; @@ -381,7 +388,7 @@ class SqlWalker implements TreeWalker } else if ($lockMode == LockMode::OPTIMISTIC) { foreach ($this->_selectedClasses AS $class) { if ( ! $class->isVersioned) { - throw \Doctrine\ORM\OptimisticLockException::lockFailed(); + throw \Doctrine\ORM\OptimisticLockException::lockFailed($class->name); } } } @@ -619,10 +626,17 @@ class SqlWalker implements TreeWalker } if ($identificationVariableDecl->indexBy) { - $this->_rsm->addIndexBy( - $identificationVariableDecl->indexBy->simpleStateFieldPathExpression->identificationVariable, - $identificationVariableDecl->indexBy->simpleStateFieldPathExpression->field - ); + $alias = $identificationVariableDecl->indexBy->simpleStateFieldPathExpression->identificationVariable; + $field = $identificationVariableDecl->indexBy->simpleStateFieldPathExpression->field; + + if (isset($this->_scalarFields[$alias][$field])) { + $this->_rsm->addIndexByScalar($this->_scalarFields[$alias][$field]); + } else { + $this->_rsm->addIndexBy( + $identificationVariableDecl->indexBy->simpleStateFieldPathExpression->identificationVariable, + $identificationVariableDecl->indexBy->simpleStateFieldPathExpression->field + ); + } } $sqlParts[] = $this->_platform->appendLockHint($sql, $this->_query->getHint(Query::HINT_LOCK_MODE)); @@ -1010,6 +1024,7 @@ class SqlWalker implements TreeWalker if ( ! $hidden) { $this->_rsm->addScalarResult($columnAlias, $resultAlias); + $this->_scalarFields[$dqlAlias][$fieldName] = $columnAlias; } } else if ($expr instanceof AST\AggregateExpression) { if ( ! $selectExpression->fieldIdentificationVariable) { diff --git a/tests/Doctrine/Tests/ORM/Hydration/ObjectHydratorTest.php b/tests/Doctrine/Tests/ORM/Hydration/ObjectHydratorTest.php index d2d6f24ce..65cd57c95 100644 --- a/tests/Doctrine/Tests/ORM/Hydration/ObjectHydratorTest.php +++ b/tests/Doctrine/Tests/ORM/Hydration/ObjectHydratorTest.php @@ -1208,4 +1208,32 @@ class ObjectHydratorTest extends HydrationTestCase $this->assertTrue(isset($result[2])); $this->assertEquals(2, $result[2][0]->id); } + + /** + * @group DDC-1385 + */ + public function testIndexByScalarsOnly() + { + $rsm = new ResultSetMapping; + $rsm->addScalarResult('sclr0', 'nameUpper'); + $rsm->addIndexByScalar('sclr0'); + + // Faked result set + $resultSet = array( + //row1 + array( + 'sclr0' => 'ROMANB', + ), + array( + 'sclr0' => 'JWAGE', + ), + ); + + $stmt = new HydratorMockStatement($resultSet); + $hydrator = new \Doctrine\ORM\Internal\Hydration\ObjectHydrator($this->_em); + + $result = $hydrator->hydrateAll($stmt, $rsm, array(Query::HINT_FORCE_PARTIAL_LOAD => true)); + + $this->assertEquals(array('ROMANB' => array('nameUpper' => 'ROMANB'), 'JWAGE' => array('nameUpper' => 'JWAGE')), $result); + } } From 97321a1ff2cf0949ba5317522a748558d22b3792 Mon Sep 17 00:00:00 2001 From: Asmir Mustafic Date: Tue, 18 Oct 2011 16:18:25 +0200 Subject: [PATCH 006/135] Collapsed cascade elements, if cascade-all. (better readability for generated xml) --- lib/Doctrine/ORM/Tools/Export/Driver/XmlExporter.php | 3 +++ 1 file changed, 3 insertions(+) diff --git a/lib/Doctrine/ORM/Tools/Export/Driver/XmlExporter.php b/lib/Doctrine/ORM/Tools/Export/Driver/XmlExporter.php index f9668c78f..9390f144f 100644 --- a/lib/Doctrine/ORM/Tools/Export/Driver/XmlExporter.php +++ b/lib/Doctrine/ORM/Tools/Export/Driver/XmlExporter.php @@ -276,6 +276,9 @@ class XmlExporter extends AbstractExporter if ($associationMapping['isCascadeDetach']) { $cascade[] = 'cascade-detach'; } + if(count($cascade)==5){ + $cascade = array('cascade-all'); + } if ($cascade) { $cascadeXml = $associationMappingXml->addChild('cascade'); foreach ($cascade as $type) { From e19fd756cbc5f57e696a91b8b3626747c70c0192 Mon Sep 17 00:00:00 2001 From: Asmir Mustafic Date: Wed, 19 Oct 2011 09:07:18 +0200 Subject: [PATCH 007/135] Better indentation for generated class --- lib/Doctrine/ORM/Tools/EntityGenerator.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/Doctrine/ORM/Tools/EntityGenerator.php b/lib/Doctrine/ORM/Tools/EntityGenerator.php index 62efbac85..0792ceb91 100644 --- a/lib/Doctrine/ORM/Tools/EntityGenerator.php +++ b/lib/Doctrine/ORM/Tools/EntityGenerator.php @@ -406,7 +406,7 @@ public function () } if ($collections) { - return $this->_prefixCodeWithSpaces(str_replace("", implode("\n", $collections), self::$_constructorMethodTemplate)); + return $this->_prefixCodeWithSpaces(str_replace("", implode("\n".$this->_spaces, $collections), self::$_constructorMethodTemplate)); } return ''; From 9c1202a76667d8e778a29d008a54c4ad3bda9c2e Mon Sep 17 00:00:00 2001 From: Asmir Mustafic Date: Wed, 19 Oct 2011 09:15:41 +0200 Subject: [PATCH 008/135] Added on generated class. This allow to unset many-to-one and one-to-one relations Example: $user->setGroup(null); --- lib/Doctrine/ORM/Tools/EntityGenerator.php | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/lib/Doctrine/ORM/Tools/EntityGenerator.php b/lib/Doctrine/ORM/Tools/EntityGenerator.php index 0792ceb91..e8ae73aee 100644 --- a/lib/Doctrine/ORM/Tools/EntityGenerator.php +++ b/lib/Doctrine/ORM/Tools/EntityGenerator.php @@ -117,7 +117,7 @@ public function () * @param $ * @return */ -public function ($) +public function ($) { $this-> = $; return $this; @@ -634,7 +634,7 @@ public function () foreach ($metadata->associationMappings as $associationMapping) { if ($associationMapping['type'] & ClassMetadataInfo::TO_ONE) { - if ($code = $this->_generateEntityStubMethod($metadata, 'set', $associationMapping['fieldName'], $associationMapping['targetEntity'])) { + if ($code = $this->_generateEntityStubMethod($metadata, 'set', $associationMapping['fieldName'], $associationMapping['targetEntity'], 'null')) { $methods[] = $code; } if ($code = $this->_generateEntityStubMethod($metadata, 'get', $associationMapping['fieldName'], $associationMapping['targetEntity'])) { @@ -707,7 +707,7 @@ public function () return implode("\n", $lines); } - private function _generateEntityStubMethod(ClassMetadataInfo $metadata, $type, $fieldName, $typeHint = null) + private function _generateEntityStubMethod(ClassMetadataInfo $metadata, $type, $fieldName, $typeHint = null, $defaultValue = null) { if ($type == "add") { $addMethod = explode("\\", $typeHint); @@ -737,6 +737,7 @@ public function () '' => Inflector::camelize($fieldName), '' => $methodName, '' => $fieldName, + '' => ($defaultValue!==null?('='.$defaultValue):''), '' => $this->_getClassName($metadata) ); From 8f092812c42dfae3f0c80f504c1766812bf59443 Mon Sep 17 00:00:00 2001 From: Asmir Mustafic Date: Wed, 19 Oct 2011 09:17:30 +0200 Subject: [PATCH 009/135] Spaces --- lib/Doctrine/ORM/Tools/EntityGenerator.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/Doctrine/ORM/Tools/EntityGenerator.php b/lib/Doctrine/ORM/Tools/EntityGenerator.php index e8ae73aee..f130908e7 100644 --- a/lib/Doctrine/ORM/Tools/EntityGenerator.php +++ b/lib/Doctrine/ORM/Tools/EntityGenerator.php @@ -737,7 +737,7 @@ public function () '' => Inflector::camelize($fieldName), '' => $methodName, '' => $fieldName, - '' => ($defaultValue!==null?('='.$defaultValue):''), + '' => ($defaultValue!==null?('='.$defaultValue):''), '' => $this->_getClassName($metadata) ); From be3adfb35ebb0c4139f8ee4cae7b88ff8c9cd5c9 Mon Sep 17 00:00:00 2001 From: Asmir Mustafic Date: Wed, 19 Oct 2011 09:25:40 +0200 Subject: [PATCH 010/135] With TO_MANY relations, class filed is instanceof ArrayCollection, instead of targetEntity class type. --- lib/Doctrine/ORM/Tools/EntityGenerator.php | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/lib/Doctrine/ORM/Tools/EntityGenerator.php b/lib/Doctrine/ORM/Tools/EntityGenerator.php index f130908e7..a3a27892c 100644 --- a/lib/Doctrine/ORM/Tools/EntityGenerator.php +++ b/lib/Doctrine/ORM/Tools/EntityGenerator.php @@ -806,7 +806,12 @@ public function () { $lines = array(); $lines[] = $this->_spaces . '/**'; - $lines[] = $this->_spaces . ' * @var ' . $associationMapping['targetEntity']; + + if ($associationMapping['type'] & ClassMetadataInfo::TO_MANY) { + $lines[] = $this->_spaces . ' * @var \Doctrine\Common\Collections\ArrayCollection'; + }else{ + $lines[] = $this->_spaces . ' * @var ' . $associationMapping['targetEntity']; + } if ($this->_generateAnnotations) { $lines[] = $this->_spaces . ' *'; From 596ba3d5b1e32f685f77e116524c83621573edb8 Mon Sep 17 00:00:00 2001 From: Asmir Mustafic Date: Wed, 19 Oct 2011 15:04:16 +0200 Subject: [PATCH 011/135] Collection inteface instead of ArrayCollection --- lib/Doctrine/ORM/Tools/EntityGenerator.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/Doctrine/ORM/Tools/EntityGenerator.php b/lib/Doctrine/ORM/Tools/EntityGenerator.php index a3a27892c..7de6bdaea 100644 --- a/lib/Doctrine/ORM/Tools/EntityGenerator.php +++ b/lib/Doctrine/ORM/Tools/EntityGenerator.php @@ -808,7 +808,7 @@ public function () $lines[] = $this->_spaces . '/**'; if ($associationMapping['type'] & ClassMetadataInfo::TO_MANY) { - $lines[] = $this->_spaces . ' * @var \Doctrine\Common\Collections\ArrayCollection'; + $lines[] = $this->_spaces . ' * @var \Doctrine\Common\Collections\Collection'; }else{ $lines[] = $this->_spaces . ' * @var ' . $associationMapping['targetEntity']; } From fe84a61d0b24ecc9bb5ba323e56a2fb00eaf3263 Mon Sep 17 00:00:00 2001 From: Asmir Mustafic Date: Fri, 21 Oct 2011 09:38:37 +0200 Subject: [PATCH 012/135] Better code generation when association is nullable --- lib/Doctrine/ORM/Tools/EntityGenerator.php | 23 ++++++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/lib/Doctrine/ORM/Tools/EntityGenerator.php b/lib/Doctrine/ORM/Tools/EntityGenerator.php index 7de6bdaea..bbde24346 100644 --- a/lib/Doctrine/ORM/Tools/EntityGenerator.php +++ b/lib/Doctrine/ORM/Tools/EntityGenerator.php @@ -634,7 +634,8 @@ public function () foreach ($metadata->associationMappings as $associationMapping) { if ($associationMapping['type'] & ClassMetadataInfo::TO_ONE) { - if ($code = $this->_generateEntityStubMethod($metadata, 'set', $associationMapping['fieldName'], $associationMapping['targetEntity'], 'null')) { + $nullable = $this->_associationIsNullable($associationMapping); + if ($code = $this->_generateEntityStubMethod($metadata, 'set', $associationMapping['fieldName'], $associationMapping['targetEntity'], ($nullable?'null':null))) { $methods[] = $code; } if ($code = $this->_generateEntityStubMethod($metadata, 'get', $associationMapping['fieldName'], $associationMapping['targetEntity'])) { @@ -653,6 +654,24 @@ public function () return implode("\n\n", $methods); } + private function _associationIsNullable($associationMapping) + { + if(isset($associationMapping['joinColumns'])){ + $joinColumns = $associationMapping['joinColumns']; + }else{ + //@todo thereis no way to retreive targetEntity metadata + //$targetMetadata = $this->getClassMetadata($associationMapping['targetEntity']); + //$joinColumns = $targetMetadata->associationMappings[$associationMapping["mappedBy"]]['joinColumns']; + $joinColumns = array(); + } + foreach ($joinColumns as $joinColumn) { + if(!isset($joinColumn['nullable']) || !$joinColumn['nullable']){ + return false; + } + } + return true; + } + private function _generateEntityLifecycleCallbackMethods(ClassMetadataInfo $metadata) { if (isset($metadata->lifecycleCallbacks) && $metadata->lifecycleCallbacks) { @@ -808,7 +827,7 @@ public function () $lines[] = $this->_spaces . '/**'; if ($associationMapping['type'] & ClassMetadataInfo::TO_MANY) { - $lines[] = $this->_spaces . ' * @var \Doctrine\Common\Collections\Collection'; + $lines[] = $this->_spaces . ' * @var \Doctrine\Common\Collections\ArrayCollection'; }else{ $lines[] = $this->_spaces . ' * @var ' . $associationMapping['targetEntity']; } From 4a50eb4fa749cfbb59552dfa0abc56323c6930a9 Mon Sep 17 00:00:00 2001 From: armetiz Date: Fri, 21 Oct 2011 11:55:54 +0300 Subject: [PATCH 013/135] Update lib/Doctrine/ORM/Query/Expr.php --- lib/Doctrine/ORM/Query/Expr.php | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/lib/Doctrine/ORM/Query/Expr.php b/lib/Doctrine/ORM/Query/Expr.php index 246f92717..6ba6a5d73 100644 --- a/lib/Doctrine/ORM/Query/Expr.php +++ b/lib/Doctrine/ORM/Query/Expr.php @@ -40,10 +40,12 @@ class Expr * * [php] * // (u.type = ?1) AND (u.role = ?2) - * $expr->andX('u.type = ?1', 'u.role = ?2')); + * $expr->andX($expr->eq('u.type', ':1'), $expr->eq('u.role', ':2')); * - * @param mixed $x Optional clause. Defaults = null, but requires - * at least one defined when converting to string. + * @param Doctrine\ORM\Query\Expr\Comparison | + * Doctrine\ORM\Query\Expr\Func | + * Doctrine\ORM\Query\Expr\Orx + * $x Optional clause. Defaults = null, but requires at least one defined when converting to string. * @return Expr\Andx */ public function andX($x = null) From baef4f735f403b10a194603380b267cee59a761b Mon Sep 17 00:00:00 2001 From: armetiz Date: Fri, 21 Oct 2011 16:30:06 +0300 Subject: [PATCH 014/135] Update lib/Doctrine/ORM/Query/Expr/Base.php --- lib/Doctrine/ORM/Query/Expr/Base.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/Doctrine/ORM/Query/Expr/Base.php b/lib/Doctrine/ORM/Query/Expr/Base.php index abc2c210e..beac2bb9c 100644 --- a/lib/Doctrine/ORM/Query/Expr/Base.php +++ b/lib/Doctrine/ORM/Query/Expr/Base.php @@ -57,7 +57,7 @@ abstract class Base public function add($arg) { - if ( $arg !== null || ($arg instanceof self && $arg->count() > 0)) { + if ( $arg !== null ) { // If we decide to keep Expr\Base instances, we can use this check if ( ! is_string($arg)) { $class = get_class($arg); From d4059b88ca8048d655015682262c3b9b9976b5a1 Mon Sep 17 00:00:00 2001 From: Asmir Mustafic Date: Fri, 21 Oct 2011 15:30:21 +0200 Subject: [PATCH 015/135] Nullable relations, fixing join condition --- lib/Doctrine/ORM/Tools/EntityGenerator.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/Doctrine/ORM/Tools/EntityGenerator.php b/lib/Doctrine/ORM/Tools/EntityGenerator.php index bbde24346..c135ecac4 100644 --- a/lib/Doctrine/ORM/Tools/EntityGenerator.php +++ b/lib/Doctrine/ORM/Tools/EntityGenerator.php @@ -634,7 +634,7 @@ public function () foreach ($metadata->associationMappings as $associationMapping) { if ($associationMapping['type'] & ClassMetadataInfo::TO_ONE) { - $nullable = $this->_associationIsNullable($associationMapping); + $nullable = $this->_isAssociationIsNullable($associationMapping); if ($code = $this->_generateEntityStubMethod($metadata, 'set', $associationMapping['fieldName'], $associationMapping['targetEntity'], ($nullable?'null':null))) { $methods[] = $code; } @@ -654,7 +654,7 @@ public function () return implode("\n\n", $methods); } - private function _associationIsNullable($associationMapping) + private function _isAssociationIsNullable($associationMapping) { if(isset($associationMapping['joinColumns'])){ $joinColumns = $associationMapping['joinColumns']; @@ -665,7 +665,7 @@ public function () $joinColumns = array(); } foreach ($joinColumns as $joinColumn) { - if(!isset($joinColumn['nullable']) || !$joinColumn['nullable']){ + if(isset($joinColumn['nullable']) && !$joinColumn['nullable']){ return false; } } From f569a2a389264adefbfc9aca03e67a83c131a087 Mon Sep 17 00:00:00 2001 From: Benjamin Eberlei Date: Sat, 22 Oct 2011 13:44:33 +0200 Subject: [PATCH 016/135] DDC-720 - Add support to flush only one entity (within cascade rules) through EntityManager#flush() --- lib/Doctrine/ORM/EntityManager.php | 4 +- lib/Doctrine/ORM/UnitOfWork.php | 53 ++++++- .../ORM/Functional/BasicFunctionalTest.php | 140 ++++++++++++++++++ 3 files changed, 193 insertions(+), 4 deletions(-) diff --git a/lib/Doctrine/ORM/EntityManager.php b/lib/Doctrine/ORM/EntityManager.php index b951e355e..3dd9047da 100644 --- a/lib/Doctrine/ORM/EntityManager.php +++ b/lib/Doctrine/ORM/EntityManager.php @@ -328,10 +328,10 @@ class EntityManager implements ObjectManager * @throws Doctrine\ORM\OptimisticLockException If a version check on an entity that * makes use of optimistic locking fails. */ - public function flush() + public function flush($entity = null) { $this->errorIfClosed(); - $this->unitOfWork->commit(); + $this->unitOfWork->commit($entity); } /** diff --git a/lib/Doctrine/ORM/UnitOfWork.php b/lib/Doctrine/ORM/UnitOfWork.php index 8f5977e5d..ab5bf25bb 100644 --- a/lib/Doctrine/ORM/UnitOfWork.php +++ b/lib/Doctrine/ORM/UnitOfWork.php @@ -255,10 +255,14 @@ class UnitOfWork implements PropertyChangedListener * 5) All entity deletions * */ - public function commit() + public function commit($entity = null) { // Compute changes done since last commit. - $this->computeChangeSets(); + if ($entity === null) { + $this->computeChangeSets(); + } else { + $this->computeSingleEntityChangeSet($entity); + } if ( ! ($this->entityInsertions || $this->entityDeletions || @@ -346,6 +350,51 @@ class UnitOfWork implements PropertyChangedListener $this->scheduledForDirtyCheck = $this->orphanRemovals = array(); } + + /** + * Only flush the given entity according to a rulset that keeps the UoW consistent. + * + * 1. All entities scheduled for insertion, (orphan) removals and changes in collections are processed as well! + * 2. Read Only entities are skipped. + * 3. Proxies are skipped. + * 4. Only if entity is properly managed. + * + * @param Proxy $entity + * @return type + */ + private function computeSingleEntityChangeSet($entity) + { + if ( ! $this->isInIdentityMap($entity) ) { + throw new \InvalidArgumentException("Entity has to be managed for single computation " . self::objToStr($entity)); + } + + $class = $this->em->getClassMetadata(get_class($entity)); + + if ($class->isChangeTrackingDeferredImplicit()) { + $this->persist($entity); + } + + // Compute changes for INSERTed entities first. This must always happen even in this case. + foreach ($this->entityInsertions as $entity) { + $class = $this->em->getClassMetadata(get_class($entity)); + $this->computeChangeSet($class, $entity); + } + + if ( $class->isReadOnly ) { + return; + } + + // Ignore uninitialized proxy objects + if ($entity instanceof Proxy && ! $entity->__isInitialized__) { + return; + } + + // Only MANAGED entities that are NOT SCHEDULED FOR INSERTION are processed here. + $oid = spl_object_hash($entity); + if ( ! isset($this->entityInsertions[$oid]) && isset($this->entityStates[$oid])) { + $this->computeChangeSet($class, $entity); + } + } /** * Executes any extra updates that have been scheduled. diff --git a/tests/Doctrine/Tests/ORM/Functional/BasicFunctionalTest.php b/tests/Doctrine/Tests/ORM/Functional/BasicFunctionalTest.php index a2e2d05dd..9eaa088a6 100644 --- a/tests/Doctrine/Tests/ORM/Functional/BasicFunctionalTest.php +++ b/tests/Doctrine/Tests/ORM/Functional/BasicFunctionalTest.php @@ -1030,4 +1030,144 @@ class BasicFunctionalTest extends \Doctrine\Tests\OrmFunctionalTestCase $this->assertEquals(\Doctrine\ORM\UnitOfWork::STATE_DETACHED, $unitOfWork->getEntityState($address)); } + + /** + * @group DDC-720 + */ + public function testFlushSingleManagedEntity() + { + $user = new CmsUser; + $user->name = 'Dominik'; + $user->username = 'domnikl'; + $user->status = 'developer'; + + $this->_em->persist($user); + $this->_em->flush(); + + $user->status = 'administrator'; + $this->_em->flush($user); + $this->_em->clear(); + + $user = $this->_em->find(get_class($user), $user->id); + $this->assertEquals('administrator', $user->status); + } + + /** + * @group DDC-720 + */ + public function testFlushSingleUnmanagedEntity() + { + $user = new CmsUser; + $user->name = 'Dominik'; + $user->username = 'domnikl'; + $user->status = 'developer'; + + $this->setExpectedException('InvalidArgumentException', 'Entity has to be managed for single computation'); + $this->_em->flush($user); + } + + /** + * @group DDC-720 + */ + public function testFlushSingleAndNewEntity() + { + $user = new CmsUser; + $user->name = 'Dominik'; + $user->username = 'domnikl'; + $user->status = 'developer'; + + $this->_em->persist($user); + $this->_em->flush(); + + $otherUser = new CmsUser; + $otherUser->name = 'Dominik2'; + $otherUser->username = 'domnikl2'; + $otherUser->status = 'developer'; + + $user->status = 'administrator'; + + $this->_em->persist($otherUser); + $this->_em->flush($user); + + $this->assertTrue($this->_em->contains($otherUser), "Other user is contained in EntityManager"); + $this->assertTrue($otherUser->id > 0, "other user has an id"); + } + + /** + * @group DDC-720 + */ + public function testFlushAndCascadePersist() + { + $user = new CmsUser; + $user->name = 'Dominik'; + $user->username = 'domnikl'; + $user->status = 'developer'; + + $this->_em->persist($user); + $this->_em->flush(); + + $address = new CmsAddress(); + $address->city = "Springfield"; + $address->zip = "12354"; + $address->country = "Germany"; + $address->street = "Foo Street"; + $address->user = $user; + $user->address = $address; + + $this->_em->flush($user); + + $this->assertTrue($this->_em->contains($address), "Other user is contained in EntityManager"); + $this->assertTrue($address->id > 0, "other user has an id"); + } + + /** + * @group DDC-720 + */ + public function testFlushSingleAndNoCascade() + { + $user = new CmsUser; + $user->name = 'Dominik'; + $user->username = 'domnikl'; + $user->status = 'developer'; + + $this->_em->persist($user); + $this->_em->flush(); + + $article1 = new CmsArticle(); + $article1->topic = 'Foo'; + $article1->text = 'Foo Text'; + $article1->author = $user; + $user->articles[] = $article1; + + $this->setExpectedException('InvalidArgumentException', "A new entity was found through the relationship 'Doctrine\Tests\Models\CMS\CmsUser#articles'"); + $this->_em->flush($user); + } + + /** + * @group DDC-720 + */ + public function testProxyIsIgnored() + { + $user = new CmsUser; + $user->name = 'Dominik'; + $user->username = 'domnikl'; + $user->status = 'developer'; + + $this->_em->persist($user); + $this->_em->flush(); + $this->_em->clear(); + + $user = $this->_em->getReference(get_class($user), $user->id); + + $otherUser = new CmsUser; + $otherUser->name = 'Dominik2'; + $otherUser->username = 'domnikl2'; + $otherUser->status = 'developer'; + + $this->_em->persist($otherUser); + $this->_em->flush($user); + + $this->assertTrue($this->_em->contains($otherUser), "Other user is contained in EntityManager"); + $this->assertTrue($otherUser->id > 0, "other user has an id"); + } } From b910a487c54b4c3dbe8603fc0857184d5083b13f Mon Sep 17 00:00:00 2001 From: Benjamin Eberlei Date: Sat, 22 Oct 2011 14:31:23 +0200 Subject: [PATCH 017/135] DDC-720 - Wait, we should really test it only changes the passed entity. --- .../ORM/Functional/BasicFunctionalTest.php | 29 +++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/tests/Doctrine/Tests/ORM/Functional/BasicFunctionalTest.php b/tests/Doctrine/Tests/ORM/Functional/BasicFunctionalTest.php index 9eaa088a6..feef41c76 100644 --- a/tests/Doctrine/Tests/ORM/Functional/BasicFunctionalTest.php +++ b/tests/Doctrine/Tests/ORM/Functional/BasicFunctionalTest.php @@ -1170,4 +1170,33 @@ class BasicFunctionalTest extends \Doctrine\Tests\OrmFunctionalTestCase $this->assertTrue($this->_em->contains($otherUser), "Other user is contained in EntityManager"); $this->assertTrue($otherUser->id > 0, "other user has an id"); } + + /** + * @group DDC-720 + */ + public function testFlushSingleSaveOnlySingle() + { + $user = new CmsUser; + $user->name = 'Dominik'; + $user->username = 'domnikl'; + $user->status = 'developer'; + $this->_em->persist($user); + + $user2 = new CmsUser; + $user2->name = 'Dominik'; + $user2->username = 'domnikl2'; + $user2->status = 'developer'; + $this->_em->persist($user2); + + $this->_em->flush(); + + $user->status = 'admin'; + $user2->status = 'admin'; + + $this->_em->flush($user); + $this->_em->clear(); + + $user2 = $this->_em->find(get_class($user2), $user2->id); + $this->assertEquals('developer', $user2->status); + } } From 079e2b130286f52b797edbe547a1ce31815f8a80 Mon Sep 17 00:00:00 2001 From: Alexander Date: Sat, 22 Oct 2011 16:27:56 +0200 Subject: [PATCH 018/135] [DDC-1384] Fix for generating sql with aliases not longer than MaxIdentifierLength --- .../AbstractEntityInheritancePersister.php | 9 ++-- .../ORM/Persisters/BasicEntityPersister.php | 23 ++++++-- lib/Doctrine/ORM/Query/SqlWalker.php | 53 +++++++------------ .../ORM/Query/SelectSqlGenerationTest.php | 29 ++++++++-- 4 files changed, 67 insertions(+), 47 deletions(-) diff --git a/lib/Doctrine/ORM/Persisters/AbstractEntityInheritancePersister.php b/lib/Doctrine/ORM/Persisters/AbstractEntityInheritancePersister.php index 670cf11e7..84540a337 100644 --- a/lib/Doctrine/ORM/Persisters/AbstractEntityInheritancePersister.php +++ b/lib/Doctrine/ORM/Persisters/AbstractEntityInheritancePersister.php @@ -62,7 +62,7 @@ abstract class AbstractEntityInheritancePersister extends BasicEntityPersister { $columnName = $class->columnNames[$field]; $sql = $this->_getSQLTableAlias($class->name, $alias == 'r' ? '' : $alias) . '.' . $class->getQuotedColumnName($field, $this->_platform); - $columnAlias = $this->_platform->getSQLResultCasing($columnName . $this->_sqlAliasCounter++); + $columnAlias = $this->getSQLColumnAlias($columnName); $this->_rsm->addFieldResult($alias, $columnAlias, $field, $class->name); return $sql . ' AS ' . $columnAlias; @@ -70,10 +70,9 @@ abstract class AbstractEntityInheritancePersister extends BasicEntityPersister protected function getSelectJoinColumnSQL($tableAlias, $joinColumnName, $className) { - $columnAlias = $joinColumnName . $this->_sqlAliasCounter++; - $resultColumnName = $this->_platform->getSQLResultCasing($columnAlias); - $this->_rsm->addMetaResult('r', $resultColumnName, $joinColumnName); + $columnAlias = $this->getSQLColumnAlias($joinColumnName); + $this->_rsm->addMetaResult('r', $columnAlias, $joinColumnName); return $tableAlias . '.' . $joinColumnName . ' AS ' . $columnAlias; } -} \ No newline at end of file +} diff --git a/lib/Doctrine/ORM/Persisters/BasicEntityPersister.php b/lib/Doctrine/ORM/Persisters/BasicEntityPersister.php index 0379ccf56..9fa2e3dff 100644 --- a/lib/Doctrine/ORM/Persisters/BasicEntityPersister.php +++ b/lib/Doctrine/ORM/Persisters/BasicEntityPersister.php @@ -1060,8 +1060,7 @@ class BasicEntityPersister foreach ($assoc['targetToSourceKeyColumns'] as $srcColumn) { if ($columnList) $columnList .= ', '; - $columnAlias = $srcColumn . $this->_sqlAliasCounter++; - $resultColumnName = $this->_platform->getSQLResultCasing($columnAlias); + $resultColumnName = $this->getSQLColumnAlias($srcColumn); $columnList .= $this->_getSQLTableAlias($class->name, ($alias == 'r' ? '' : $alias) ) . '.' . $srcColumn . ' AS ' . $resultColumnName; $this->_rsm->addMetaResult($alias, $resultColumnName, $srcColumn, isset($assoc['id']) && $assoc['id'] === true); @@ -1180,11 +1179,10 @@ class BasicEntityPersister */ protected function _getSelectColumnSQL($field, ClassMetadata $class, $alias = 'r') { - $columnName = $class->columnNames[$field]; $sql = $this->_getSQLTableAlias($class->name, $alias == 'r' ? '' : $alias) . '.' . $class->getQuotedColumnName($field, $this->_platform); - $columnAlias = $this->_platform->getSQLResultCasing($columnName . $this->_sqlAliasCounter++); - + $columnAlias = $this->getSQLColumnAlias($class->columnNames[$field]); + $this->_rsm->addFieldResult($alias, $columnAlias, $field); return $sql . ' AS ' . $columnAlias; @@ -1500,4 +1498,19 @@ class BasicEntityPersister return (bool) $this->_conn->fetchColumn($sql, $params); } + + /** + * Gets an SQL column alias for a column name. + * + * @param string $columnName + * @return string + */ + public function getSQLColumnAlias($columnName) + { + // Trim the column alias to the maximum identifier length of the platform. + // If the alias is to long, characters are cut off from the beginning. + return $this->_platform->getSQLResultCasing( + substr($columnName . $this->_sqlAliasCounter++, -$this->_platform->getMaxIdentifierLength()) + ); + } } diff --git a/lib/Doctrine/ORM/Query/SqlWalker.php b/lib/Doctrine/ORM/Query/SqlWalker.php index 188350197..6dc662493 100644 --- a/lib/Doctrine/ORM/Query/SqlWalker.php +++ b/lib/Doctrine/ORM/Query/SqlWalker.php @@ -229,7 +229,11 @@ class SqlWalker implements TreeWalker */ public function getSQLColumnAlias($columnName) { - return $columnName . $this->_aliasCounter++; + // Trim the column alias to the maximum identifier length of the platform. + // If the alias is to long, characters are cut off from the beginning. + return $this->_platform->getSQLResultCasing( + substr($columnName . $this->_aliasCounter++, -$this->_platform->getMaxIdentifierLength()) + ); } /** @@ -535,7 +539,6 @@ class SqlWalker implements TreeWalker $sqlSelectExpressions[] = $tblAlias . '.' . $discrColumn['name'] . ' AS ' . $columnAlias; - $columnAlias = $this->_platform->getSQLResultCasing($columnAlias); $this->_rsm->setDiscriminatorColumn($dqlAlias, $columnAlias); $this->_rsm->addMetaResult($dqlAlias, $columnAlias, $discrColumn['fieldName']); @@ -556,8 +559,7 @@ class SqlWalker implements TreeWalker $sqlSelectExpressions[] = $sqlTableAlias . '.' . $srcColumn . ' AS ' . $columnAlias; - $columnAlias = $this->_platform->getSQLResultCasing($columnAlias); - $this->_rsm->addMetaResult($dqlAlias, $this->_platform->getSQLResultCasing($columnAlias), $srcColumn, (isset($assoc['id']) && $assoc['id'] === true)); + $this->_rsm->addMetaResult($dqlAlias, $columnAlias, $srcColumn, (isset($assoc['id']) && $assoc['id'] === true)); } } } @@ -574,8 +576,7 @@ class SqlWalker implements TreeWalker $sqlSelectExpressions[] = $sqlTableAlias . '.' . $srcColumn . ' AS ' . $columnAlias; - $columnAlias = $this->_platform->getSQLResultCasing($columnAlias); - $this->_rsm->addMetaResult($dqlAlias, $this->_platform->getSQLResultCasing($columnAlias), $srcColumn, (isset($assoc['id']) && $assoc['id'] === true)); + $this->_rsm->addMetaResult($dqlAlias, $columnAlias, $srcColumn, (isset($assoc['id']) && $assoc['id'] === true)); } } } @@ -1006,7 +1007,6 @@ class SqlWalker implements TreeWalker $columnAlias = $this->getSQLColumnAlias($columnName); $sql .= $sqlTableAlias . '.' . $columnName . ' AS ' . $columnAlias; - $columnAlias = $this->_platform->getSQLResultCasing($columnAlias); if ( ! $hidden) { $this->_rsm->addScalarResult($columnAlias, $resultAlias); @@ -1018,12 +1018,10 @@ class SqlWalker implements TreeWalker $resultAlias = $selectExpression->fieldIdentificationVariable; } - $columnAlias = 'sclr' . $this->_aliasCounter++; + $columnAlias = $this->getSQLColumnAlias('sclr'); $sql .= $this->walkAggregateExpression($expr) . ' AS ' . $columnAlias; $this->_scalarResultAliasMap[$resultAlias] = $columnAlias; - $columnAlias = $this->_platform->getSQLResultCasing($columnAlias); - if ( ! $hidden) { $this->_rsm->addScalarResult($columnAlias, $resultAlias); } @@ -1034,12 +1032,10 @@ class SqlWalker implements TreeWalker $resultAlias = $selectExpression->fieldIdentificationVariable; } - $columnAlias = 'sclr' . $this->_aliasCounter++; + $columnAlias = $this->getSQLColumnAlias('sclr'); $sql .= '(' . $this->walkSubselect($expr) . ') AS '.$columnAlias; $this->_scalarResultAliasMap[$resultAlias] = $columnAlias; - $columnAlias = $this->_platform->getSQLResultCasing($columnAlias); - if ( ! $hidden) { $this->_rsm->addScalarResult($columnAlias, $resultAlias); } @@ -1050,12 +1046,10 @@ class SqlWalker implements TreeWalker $resultAlias = $selectExpression->fieldIdentificationVariable; } - $columnAlias = 'sclr' . $this->_aliasCounter++; + $columnAlias = $this->getSQLColumnAlias('sclr'); $sql .= $this->walkFunction($expr) . ' AS ' . $columnAlias; $this->_scalarResultAliasMap[$resultAlias] = $columnAlias; - $columnAlias = $this->_platform->getSQLResultCasing($columnAlias); - if ( ! $hidden) { $this->_rsm->addScalarResult($columnAlias, $resultAlias); } @@ -1072,18 +1066,15 @@ class SqlWalker implements TreeWalker $resultAlias = $selectExpression->fieldIdentificationVariable; } - $columnAlias = 'sclr' . $this->_aliasCounter++; - + $columnAlias = $this->getSQLColumnAlias('sclr'); if ($expr instanceof AST\Literal) { $sql .= $this->walkLiteral($expr) . ' AS ' .$columnAlias; } else { $sql .= $this->walkSimpleArithmeticExpression($expr) . ' AS ' . $columnAlias; } - + $this->_scalarResultAliasMap[$resultAlias] = $columnAlias; - $columnAlias = $this->_platform->getSQLResultCasing($columnAlias); - if ( ! $hidden) { $this->_rsm->addScalarResult($columnAlias, $resultAlias); } @@ -1099,14 +1090,11 @@ class SqlWalker implements TreeWalker $resultAlias = $selectExpression->fieldIdentificationVariable; } - $columnAlias = 'sclr' . $this->_aliasCounter++; - + $columnAlias = $this->getSQLColumnAlias('sclr'); $sql .= $this->walkCaseExpression($expr) . ' AS ' . $columnAlias; - + $this->_scalarResultAliasMap[$resultAlias] = $columnAlias; - $columnAlias = $this->_platform->getSQLResultCasing($columnAlias); - if ( ! $hidden) { $this->_rsm->addScalarResult($columnAlias, $resultAlias); } @@ -1146,8 +1134,6 @@ class SqlWalker implements TreeWalker $sql .= $sqlTableAlias . '.' . $class->getQuotedColumnName($fieldName, $this->_platform) . ' AS ' . $columnAlias; - $columnAlias = $this->_platform->getSQLResultCasing($columnAlias); - $this->_rsm->addFieldResult($dqlAlias, $columnAlias, $fieldName, $class->name); } @@ -1171,7 +1157,6 @@ class SqlWalker implements TreeWalker $sql .= $sqlTableAlias . '.' . $subClass->getQuotedColumnName($fieldName, $this->_platform) . ' AS ' . $columnAlias; - $columnAlias = $this->_platform->getSQLResultCasing($columnAlias); $this->_rsm->addFieldResult($dqlAlias, $columnAlias, $fieldName, $subClassName); } @@ -1185,7 +1170,7 @@ class SqlWalker implements TreeWalker $columnAlias = $this->getSQLColumnAlias($srcColumn); $sql .= $sqlTableAlias . '.' . $srcColumn . ' AS ' . $columnAlias; - $this->_rsm->addMetaResult($dqlAlias, $this->_platform->getSQLResultCasing($columnAlias), $srcColumn); + $this->_rsm->addMetaResult($dqlAlias, $columnAlias, $srcColumn); } } } @@ -1312,7 +1297,7 @@ class SqlWalker implements TreeWalker $alias = $simpleSelectExpression->fieldIdentificationVariable; } - $columnAlias = 'sclr' . $this->_aliasCounter++; + $columnAlias = $this->getSQLColumnAlias('sclr'); $sql .= '(' . $this->walkSubselect($expr) . ') AS ' . $columnAlias; $this->_scalarResultAliasMap[$alias] = $columnAlias; } else if ($expr instanceof AST\Functions\FunctionNode) { @@ -1322,7 +1307,7 @@ class SqlWalker implements TreeWalker $alias = $simpleSelectExpression->fieldIdentificationVariable; } - $columnAlias = 'sclr' . $this->_aliasCounter++; + $columnAlias = $this->getSQLColumnAlias('sclr'); $sql .= $this->walkFunction($expr) . ' AS ' . $columnAlias; $this->_scalarResultAliasMap[$alias] = $columnAlias; } else if ( @@ -1337,7 +1322,7 @@ class SqlWalker implements TreeWalker $alias = $simpleSelectExpression->fieldIdentificationVariable; } - $columnAlias = 'sclr' . $this->_aliasCounter++; + $columnAlias = $this->getSQLColumnAlias('sclr'); $sql .= $this->walkSimpleArithmeticExpression($expr) . ' AS ' . $columnAlias; $this->_scalarResultAliasMap[$alias] = $columnAlias; } else if ( @@ -1352,7 +1337,7 @@ class SqlWalker implements TreeWalker $alias = $simpleSelectExpression->fieldIdentificationVariable; } - $columnAlias = 'sclr' . $this->_aliasCounter++; + $columnAlias = $this->getSQLColumnAlias('sclr'); $sql .= $this->walkCaseExpression($expr) . ' AS ' . $columnAlias; $this->_scalarResultAliasMap[$alias] = $columnAlias; diff --git a/tests/Doctrine/Tests/ORM/Query/SelectSqlGenerationTest.php b/tests/Doctrine/Tests/ORM/Query/SelectSqlGenerationTest.php index 53002a8fc..e3ea9c004 100644 --- a/tests/Doctrine/Tests/ORM/Query/SelectSqlGenerationTest.php +++ b/tests/Doctrine/Tests/ORM/Query/SelectSqlGenerationTest.php @@ -729,12 +729,12 @@ class SelectSqlGenerationTest extends \Doctrine\Tests\OrmTestCase $this->assertSqlGeneration( "SELECT b FROM Doctrine\Tests\Models\Generic\BooleanModel b WHERE b.booleanField = true", - "SELECT b0_.id AS id0, b0_.booleanField AS booleanField1 FROM boolean_model b0_ WHERE b0_.booleanField = true" + "SELECT b0_.id AS id0, b0_.booleanField AS booleanfield1 FROM boolean_model b0_ WHERE b0_.booleanField = true" ); $this->assertSqlGeneration( "SELECT b FROM Doctrine\Tests\Models\Generic\BooleanModel b WHERE b.booleanField = false", - "SELECT b0_.id AS id0, b0_.booleanField AS booleanField1 FROM boolean_model b0_ WHERE b0_.booleanField = false" + "SELECT b0_.id AS id0, b0_.booleanField AS booleanfield1 FROM boolean_model b0_ WHERE b0_.booleanField = false" ); $this->_em->getConnection()->setDatabasePlatform($oldPlat); @@ -877,7 +877,7 @@ class SelectSqlGenerationTest extends \Doctrine\Tests\OrmTestCase $this->assertSqlGeneration( "SELECT u FROM Doctrine\Tests\Models\CMS\CmsUser u WHERE u.username = 'gblanco'", - "SELECT c0_.id AS id0, c0_.status AS status1, c0_.username AS username2, c0_.name AS name3 ". + "SELECT c0_.id AS ID0, c0_.status AS STATUS1, c0_.username AS USERNAME2, c0_.name AS NAME3 ". "FROM cms_users c0_ WHERE c0_.username = 'gblanco' FOR UPDATE", array(Query::HINT_LOCK_MODE => \Doctrine\DBAL\LockMode::PESSIMISTIC_READ) ); @@ -1251,6 +1251,17 @@ class SelectSqlGenerationTest extends \Doctrine\Tests\OrmTestCase array(Query::HINT_FORCE_PARTIAL_LOAD => false) ); } + + /** + * @group DDC-1384 + */ + function testAliasDoesNotExceedPlatformDefinedLength() + { + $this->assertSqlGeneration( + 'SELECT m FROM ' . __NAMESPACE__ . '\\DDC1384Model m', + "SELECT d0_.aVeryLongIdentifierThatShouldBeShortenedByTheSQLWalker_fooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo AS fooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo0 FROM DDC1384Model d0_" + ); + } } @@ -1281,3 +1292,15 @@ class MyAbsFunction extends \Doctrine\ORM\Query\AST\Functions\FunctionNode $parser->match(\Doctrine\ORM\Query\Lexer::T_CLOSE_PARENTHESIS); } } +/** + * @Entity + */ +class DDC1384Model +{ + /** + * @Id + * @Column(type="integer") + * @GeneratedValue + */ + protected $aVeryLongIdentifierThatShouldBeShortenedByTheSQLWalker_fooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo; +} From 46a3fecb4fb3be0098a3d8efeee7dc080b6dc353 Mon Sep 17 00:00:00 2001 From: Daniel Freudenberger Date: Sat, 22 Oct 2011 18:38:51 +0200 Subject: [PATCH 019/135] added the postFlush event --- lib/Doctrine/ORM/Event/PostFlushEventArgs.php | 57 +++++++++++ lib/Doctrine/ORM/Events.php | 11 +++ lib/Doctrine/ORM/UnitOfWork.php | 5 + .../ORM/Functional/PostFlushEventTest.php | 95 +++++++++++++++++++ 4 files changed, 168 insertions(+) create mode 100644 lib/Doctrine/ORM/Event/PostFlushEventArgs.php create mode 100644 tests/Doctrine/Tests/ORM/Functional/PostFlushEventTest.php diff --git a/lib/Doctrine/ORM/Event/PostFlushEventArgs.php b/lib/Doctrine/ORM/Event/PostFlushEventArgs.php new file mode 100644 index 000000000..92e88ae21 --- /dev/null +++ b/lib/Doctrine/ORM/Event/PostFlushEventArgs.php @@ -0,0 +1,57 @@ +. +*/ + +namespace Doctrine\ORM\Event; +use Doctrine\ORM\EntityManager; +use Doctrine\Common\EventArgs; + +/** + * Provides event arguments for the postFlush event. + * + * @license http://www.opensource.org/licenses/lgpl-license.php LGPL + * @link www.doctrine-project.com + * @since 2.0 + * @version $Revision$ + * @author Daniel Freudenberger + */ +class PostFlushEventArgs extends EventArgs +{ + /** + * @var EntityManager + */ + private $em; + + /** + * @param EntityManager $em + */ + public function __construct(EntityManager $em) + { + $this->em = $em; + } + + /** + * @return EntityManager + */ + public function getEntityManager() + { + return $this->em; + } +} \ No newline at end of file diff --git a/lib/Doctrine/ORM/Events.php b/lib/Doctrine/ORM/Events.php index 8344b07c1..4204f899c 100644 --- a/lib/Doctrine/ORM/Events.php +++ b/lib/Doctrine/ORM/Events.php @@ -120,6 +120,17 @@ final class Events */ const onFlush = 'onFlush'; + /** + * The postFlush event occurs when the EntityManager#flush() operation is invoked and + * after all actual database operations are executed successfully. The event is only raised if there is + * actually something to do for the underlying UnitOfWork. If nothing needs to be done, + * the onFlush event is not raised. The event won't be raised if an error occurs during the + * flush operation. + * + * @var string + */ + const postFlush = 'postFlush'; + /** * The onClear event occurs when the EntityManager#clear() operation is invoked, * after all references to entities have been removed from the unit of work. diff --git a/lib/Doctrine/ORM/UnitOfWork.php b/lib/Doctrine/ORM/UnitOfWork.php index 8f5977e5d..e9cddd035 100644 --- a/lib/Doctrine/ORM/UnitOfWork.php +++ b/lib/Doctrine/ORM/UnitOfWork.php @@ -329,6 +329,11 @@ class UnitOfWork implements PropertyChangedListener throw $e; } + // Raise postFlush + if ($this->evm->hasListeners(Events::postFlush)) { + $this->evm->dispatchEvent(Events::postFlush, new Event\PostFlushEventArgs($this->em)); + } + // Take new snapshots from visited collections foreach ($this->visitedCollections as $coll) { $coll->takeSnapshot(); diff --git a/tests/Doctrine/Tests/ORM/Functional/PostFlushEventTest.php b/tests/Doctrine/Tests/ORM/Functional/PostFlushEventTest.php new file mode 100644 index 000000000..6981d4784 --- /dev/null +++ b/tests/Doctrine/Tests/ORM/Functional/PostFlushEventTest.php @@ -0,0 +1,95 @@ + + */ +class PostFlushEventTest extends \Doctrine\Tests\OrmFunctionalTestCase +{ + /** + * @var PostFlushListener + */ + private $listener; + + protected function setUp() + { + parent::setUp(); + $this->useModelSet('cms'); + $this->listener = new PostFlushListener(); + $evm = $this->_em->getEventManager(); + $evm->addEventListener(Events::postFlush, $this->listener); + } + + public function testListenerShouldBeNotified() + { + $this->_em->persist($this->createNewValidUser()); + $this->_em->flush(); + $this->assertTrue($this->listener->wasNotified); + } + + public function testListenerShouldNotBeNotifiedWhenFlushThrowsException() + { + $user = new CmsUser(); + $user->username = 'dfreudenberger'; + $this->_em->persist($user); + $exceptionRaised = false; + + try { + $this->_em->flush(); + } catch (\Exception $ex) { + $exceptionRaised = true; + } + + $this->assertTrue($exceptionRaised); + $this->assertFalse($this->listener->wasNotified); + } + + public function testListenerShouldReceiveEntityManagerThroughArgs() + { + $this->_em->persist($this->createNewValidUser()); + $this->_em->flush(); + $receivedEm = $this->listener->receivedArgs->getEntityManager(); + $this->assertSame($this->_em, $receivedEm); + } + + /** + * @return CmsUser + */ + private function createNewValidUser() + { + $user = new CmsUser(); + $user->username = 'dfreudenberger'; + $user->name = 'Daniel Freudenberger'; + return $user; + } +} + +class PostFlushListener +{ + /** + * @var bool + */ + public $wasNotified = false; + + /** + * @var PostFlushEventArgs + */ + public $receivedArgs; + + /** + * @param PostFlushEventArgs $args + */ + public function postFlush(PostFlushEventArgs $args) + { + $this->wasNotified = true; + $this->receivedArgs = $args; + } +} + + From 5e28273548023283290a81dd0502cdd4a384e943 Mon Sep 17 00:00:00 2001 From: Daniel Freudenberger Date: Sat, 22 Oct 2011 18:57:48 +0200 Subject: [PATCH 020/135] fixed typo in docblock --- lib/Doctrine/ORM/Events.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/Doctrine/ORM/Events.php b/lib/Doctrine/ORM/Events.php index 4204f899c..e8c350aa6 100644 --- a/lib/Doctrine/ORM/Events.php +++ b/lib/Doctrine/ORM/Events.php @@ -124,7 +124,7 @@ final class Events * The postFlush event occurs when the EntityManager#flush() operation is invoked and * after all actual database operations are executed successfully. The event is only raised if there is * actually something to do for the underlying UnitOfWork. If nothing needs to be done, - * the onFlush event is not raised. The event won't be raised if an error occurs during the + * the postFlush event is not raised. The event won't be raised if an error occurs during the * flush operation. * * @var string From fa8000fa5c1d8a6d689e0df8b9ab4b8953a048e6 Mon Sep 17 00:00:00 2001 From: Daniel Freudenberger Date: Sun, 23 Oct 2011 01:27:09 +0200 Subject: [PATCH 021/135] dispatch the event after the snapshot was taken --- lib/Doctrine/ORM/UnitOfWork.php | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/lib/Doctrine/ORM/UnitOfWork.php b/lib/Doctrine/ORM/UnitOfWork.php index e9cddd035..7faaf581e 100644 --- a/lib/Doctrine/ORM/UnitOfWork.php +++ b/lib/Doctrine/ORM/UnitOfWork.php @@ -328,17 +328,17 @@ class UnitOfWork implements PropertyChangedListener $conn->rollback(); throw $e; } - - // Raise postFlush - if ($this->evm->hasListeners(Events::postFlush)) { - $this->evm->dispatchEvent(Events::postFlush, new Event\PostFlushEventArgs($this->em)); - } // Take new snapshots from visited collections foreach ($this->visitedCollections as $coll) { $coll->takeSnapshot(); } + // Raise postFlush + if ($this->evm->hasListeners(Events::postFlush)) { + $this->evm->dispatchEvent(Events::postFlush, new Event\PostFlushEventArgs($this->em)); + } + // Clear up $this->entityInsertions = $this->entityUpdates = From 5d3298e706b1457ca8be02469b00ef219afe84e6 Mon Sep 17 00:00:00 2001 From: Benjamin Eberlei Date: Sun, 23 Oct 2011 10:05:46 +0200 Subject: [PATCH 022/135] DDC-720 - Correct mentioned issues by @asm89 --- lib/Doctrine/ORM/EntityManager.php | 4 ++++ lib/Doctrine/ORM/UnitOfWork.php | 33 +++++++++++++++++++----------- 2 files changed, 25 insertions(+), 12 deletions(-) diff --git a/lib/Doctrine/ORM/EntityManager.php b/lib/Doctrine/ORM/EntityManager.php index 3dd9047da..b3583de8e 100644 --- a/lib/Doctrine/ORM/EntityManager.php +++ b/lib/Doctrine/ORM/EntityManager.php @@ -325,6 +325,10 @@ class EntityManager implements ObjectManager * This effectively synchronizes the in-memory state of managed objects with the * database. * + * If an entity is explicitly passed to this method only this entity and + * the cascade-persist semantics + scheduled inserts/removals are synchronized. + * + * @param object $entity * @throws Doctrine\ORM\OptimisticLockException If a version check on an entity that * makes use of optimistic locking fails. */ diff --git a/lib/Doctrine/ORM/UnitOfWork.php b/lib/Doctrine/ORM/UnitOfWork.php index ab5bf25bb..8c7273856 100644 --- a/lib/Doctrine/ORM/UnitOfWork.php +++ b/lib/Doctrine/ORM/UnitOfWork.php @@ -253,7 +253,9 @@ class UnitOfWork implements PropertyChangedListener * 3) All collection deletions * 4) All collection updates * 5) All entity deletions - * + * + * @param object $entity + * @return void */ public function commit($entity = null) { @@ -352,15 +354,28 @@ class UnitOfWork implements PropertyChangedListener } /** - * Only flush the given entity according to a rulset that keeps the UoW consistent. + * Compute the changesets of all entities scheduled for insertion + * + * @return void + */ + private function computeScheduleInsertsChangeSets() + { + foreach ($this->entityInsertions as $entity) { + $class = $this->em->getClassMetadata(get_class($entity)); + $this->computeChangeSet($class, $entity); + } + } + + /** + * Only flush the given entity according to a ruleset that keeps the UoW consistent. * * 1. All entities scheduled for insertion, (orphan) removals and changes in collections are processed as well! * 2. Read Only entities are skipped. * 3. Proxies are skipped. * 4. Only if entity is properly managed. * - * @param Proxy $entity - * @return type + * @param object $entity + * @return void */ private function computeSingleEntityChangeSet($entity) { @@ -375,10 +390,7 @@ class UnitOfWork implements PropertyChangedListener } // Compute changes for INSERTed entities first. This must always happen even in this case. - foreach ($this->entityInsertions as $entity) { - $class = $this->em->getClassMetadata(get_class($entity)); - $this->computeChangeSet($class, $entity); - } + $this->computeScheduleInsertsChangeSets(); if ( $class->isReadOnly ) { return; @@ -575,10 +587,7 @@ class UnitOfWork implements PropertyChangedListener public function computeChangeSets() { // Compute changes for INSERTed entities first. This must always happen. - foreach ($this->entityInsertions as $entity) { - $class = $this->em->getClassMetadata(get_class($entity)); - $this->computeChangeSet($class, $entity); - } + $this->computeScheduleInsertsChangeSets(); // Compute changes for other MANAGED entities. Change tracking policies take effect here. foreach ($this->identityMap as $className => $entities) { From 66e2a9260e1306d7ffc6bc952a0a890f30916ae4 Mon Sep 17 00:00:00 2001 From: everzet Date: Sun, 23 Oct 2011 18:39:16 +0300 Subject: [PATCH 023/135] added PreFlush lifetime event and lifecycle callback --- lib/Doctrine/ORM/Event/PreFlushEventArgs.php | 56 +++++++++++++++++++ lib/Doctrine/ORM/Events.php | 7 +++ .../ORM/Mapping/Driver/AnnotationDriver.php | 4 ++ .../Mapping/Driver/DoctrineAnnotations.php | 6 ++ lib/Doctrine/ORM/UnitOfWork.php | 35 ++++++++++++ 5 files changed, 108 insertions(+) create mode 100644 lib/Doctrine/ORM/Event/PreFlushEventArgs.php diff --git a/lib/Doctrine/ORM/Event/PreFlushEventArgs.php b/lib/Doctrine/ORM/Event/PreFlushEventArgs.php new file mode 100644 index 000000000..8ed39b6cb --- /dev/null +++ b/lib/Doctrine/ORM/Event/PreFlushEventArgs.php @@ -0,0 +1,56 @@ +. +*/ + +namespace Doctrine\ORM\Event; + +/** + * Provides event arguments for the preFlush event. + * + * @license http://www.opensource.org/licenses/lgpl-license.php LGPL + * @link www.doctrine-project.com + * @since 2.0 + * @version $Revision$ + * @author Roman Borschel + * @author Benjamin Eberlei + */ +class PreFlushEventArgs extends \Doctrine\Common\EventArgs +{ + /** + * @var EntityManager + */ + private $_em; + + //private $_entitiesToPersist = array(); + //private $_entitiesToRemove = array(); + + public function __construct($em) + { + $this->_em = $em; + } + + /** + * @return EntityManager + */ + public function getEntityManager() + { + return $this->_em; + } +} diff --git a/lib/Doctrine/ORM/Events.php b/lib/Doctrine/ORM/Events.php index e8c350aa6..8af7a9b61 100644 --- a/lib/Doctrine/ORM/Events.php +++ b/lib/Doctrine/ORM/Events.php @@ -109,6 +109,13 @@ final class Events */ const loadClassMetadata = 'loadClassMetadata'; + /** + * The preFlush event occurs when the EntityManager#flush() operation is invoked, + * but before any changes to managed entites have been calculated. This event is + * always raised right after EntityManager#flush() call. + */ + const preFlush = 'preFlush'; + /** * The onFlush event occurs when the EntityManager#flush() operation is invoked, * after any changes to managed entities have been determined but before any diff --git a/lib/Doctrine/ORM/Mapping/Driver/AnnotationDriver.php b/lib/Doctrine/ORM/Mapping/Driver/AnnotationDriver.php index 36e3dcb01..b09cabc95 100644 --- a/lib/Doctrine/ORM/Mapping/Driver/AnnotationDriver.php +++ b/lib/Doctrine/ORM/Mapping/Driver/AnnotationDriver.php @@ -446,6 +446,10 @@ class AnnotationDriver implements Driver if (isset($annotations['Doctrine\ORM\Mapping\PostLoad'])) { $metadata->addLifecycleCallback($method->getName(), \Doctrine\ORM\Events::postLoad); } + + if (isset($annotations['Doctrine\ORM\Mapping\PreFlush'])) { + $metadata->addLifecycleCallback($method->getName(), \Doctrine\ORM\Events::preFlush); + } } } } diff --git a/lib/Doctrine/ORM/Mapping/Driver/DoctrineAnnotations.php b/lib/Doctrine/ORM/Mapping/Driver/DoctrineAnnotations.php index e471a0d71..7f25ecbb1 100644 --- a/lib/Doctrine/ORM/Mapping/Driver/DoctrineAnnotations.php +++ b/lib/Doctrine/ORM/Mapping/Driver/DoctrineAnnotations.php @@ -387,3 +387,9 @@ final class PostRemove implements Annotation {} * @Target("METHOD") */ final class PostLoad implements Annotation {} + +/** + * @Annotation + * @Target("METHOD") + */ +final class PreFlush implements Annotation {} diff --git a/lib/Doctrine/ORM/UnitOfWork.php b/lib/Doctrine/ORM/UnitOfWork.php index 4366250dd..d8b640c47 100644 --- a/lib/Doctrine/ORM/UnitOfWork.php +++ b/lib/Doctrine/ORM/UnitOfWork.php @@ -259,6 +259,41 @@ class UnitOfWork implements PropertyChangedListener */ public function commit($entity = null) { + // Run preFlush lifecycle callback for new entities + foreach ($this->entityInsertions as $classEntity) { + $class = $this->em->getClassMetadata(get_class($classEntity)); + + // Skip class if instances are read-only + if ($class->isReadOnly) { + continue; + } + + if (isset($class->lifecycleCallbacks[Events::preFlush])) { + $class->invokeLifecycleCallbacks(Events::preFlush, $classEntity); + } + } + + // Run preFlush lifecycle callback for persisted entities + foreach ($this->identityMap as $className => $classEntities) { + $class = $this->em->getClassMetadata($className); + + // Skip class if instances are read-only + if ($class->isReadOnly) { + continue; + } + + if (isset($class->lifecycleCallbacks[Events::preFlush])) { + foreach ($classEntities as $classEntity) { + $class->invokeLifecycleCallbacks(Events::preFlush, $classEntity); + } + } + } + + // Raise preFlush + if ($this->evm->hasListeners(Events::preFlush)) { + $this->evm->dispatchEvent(Events::preFlush, new Event\PreFlushEventArgs($this->em)); + } + // Compute changes done since last commit. if ($entity === null) { $this->computeChangeSets(); From 20ed8869e43cd7f583a75339bca470212a6f199d Mon Sep 17 00:00:00 2001 From: everzet Date: Sun, 23 Oct 2011 18:39:53 +0300 Subject: [PATCH 024/135] added test for PreFlush lifetime event --- .../ORM/Functional/LifecycleCallbackTest.php | 30 +++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/tests/Doctrine/Tests/ORM/Functional/LifecycleCallbackTest.php b/tests/Doctrine/Tests/ORM/Functional/LifecycleCallbackTest.php index 2d71541d2..8015f341f 100644 --- a/tests/Doctrine/Tests/ORM/Functional/LifecycleCallbackTest.php +++ b/tests/Doctrine/Tests/ORM/Functional/LifecycleCallbackTest.php @@ -43,6 +43,29 @@ class LifecycleCallbackTest extends \Doctrine\Tests\OrmFunctionalTestCase $this->assertEquals('changed from preUpdate callback!', $result[0]->value); } + public function testPreFlushCallbacksAreInvoked() + { + $entity = new LifecycleCallbackTestEntity; + $entity->value = 'hello'; + $this->_em->persist($entity); + + $this->_em->flush(); + + $this->assertTrue($entity->prePersistCallbackInvoked); + $this->assertTrue($entity->preFlushCallbackInvoked); + + $entity->preFlushCallbackInvoked = false; + $this->_em->flush(); + + $this->assertTrue($entity->preFlushCallbackInvoked); + + $entity->value = 'bye'; + $entity->preFlushCallbackInvoked = false; + $this->_em->flush(); + + $this->assertTrue($entity->preFlushCallbackInvoked); + } + public function testChangesDontGetLost() { $user = new LifecycleCallbackTestUser; @@ -190,6 +213,8 @@ class LifecycleCallbackTestEntity public $postPersistCallbackInvoked = false; public $postLoadCallbackInvoked = false; + public $preFlushCallbackInvoked = false; + /** * @Id @Column(type="integer") * @GeneratedValue(strategy="AUTO") @@ -233,6 +258,11 @@ class LifecycleCallbackTestEntity public function doStuffOnPreUpdate() { $this->value = 'changed from preUpdate callback!'; } + + /** @PreFlush */ + public function doStuffOnPreFlush() { + $this->preFlushCallbackInvoked = true; + } } /** From 91d8829c431b332d357ee8ce95e2f374d45b028e Mon Sep 17 00:00:00 2001 From: everzet Date: Sun, 23 Oct 2011 18:50:24 +0300 Subject: [PATCH 025/135] removed non-used code --- lib/Doctrine/ORM/Event/PreFlushEventArgs.php | 3 --- 1 file changed, 3 deletions(-) diff --git a/lib/Doctrine/ORM/Event/PreFlushEventArgs.php b/lib/Doctrine/ORM/Event/PreFlushEventArgs.php index 8ed39b6cb..b86967a72 100644 --- a/lib/Doctrine/ORM/Event/PreFlushEventArgs.php +++ b/lib/Doctrine/ORM/Event/PreFlushEventArgs.php @@ -38,9 +38,6 @@ class PreFlushEventArgs extends \Doctrine\Common\EventArgs */ private $_em; - //private $_entitiesToPersist = array(); - //private $_entitiesToRemove = array(); - public function __construct($em) { $this->_em = $em; From f1df4ffca482f0269673b4a535781752ac0adcc8 Mon Sep 17 00:00:00 2001 From: Alexander Date: Sun, 23 Oct 2011 21:36:29 +0200 Subject: [PATCH 026/135] Fix testsuite --- tests/Doctrine/Tests/ORM/Functional/PostFlushEventTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/Doctrine/Tests/ORM/Functional/PostFlushEventTest.php b/tests/Doctrine/Tests/ORM/Functional/PostFlushEventTest.php index 6981d4784..819887196 100644 --- a/tests/Doctrine/Tests/ORM/Functional/PostFlushEventTest.php +++ b/tests/Doctrine/Tests/ORM/Functional/PostFlushEventTest.php @@ -19,8 +19,8 @@ class PostFlushEventTest extends \Doctrine\Tests\OrmFunctionalTestCase protected function setUp() { - parent::setUp(); $this->useModelSet('cms'); + parent::setUp(); $this->listener = new PostFlushListener(); $evm = $this->_em->getEventManager(); $evm->addEventListener(Events::postFlush, $this->listener); From 80c96909268b3a28bc8e227ffdcf43acaa3dadef Mon Sep 17 00:00:00 2001 From: Alexander Date: Sun, 23 Oct 2011 22:06:03 +0200 Subject: [PATCH 027/135] Fix mysql testsuite --- .../Tests/ORM/Functional/DatabaseDriverTest.php | 2 +- .../SchemaTool/MySqlSchemaToolTest.php | 17 +++++++++-------- 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/tests/Doctrine/Tests/ORM/Functional/DatabaseDriverTest.php b/tests/Doctrine/Tests/ORM/Functional/DatabaseDriverTest.php index 37ce0b6ea..999d52db8 100644 --- a/tests/Doctrine/Tests/ORM/Functional/DatabaseDriverTest.php +++ b/tests/Doctrine/Tests/ORM/Functional/DatabaseDriverTest.php @@ -98,7 +98,7 @@ class DatabaseDriverTest extends \Doctrine\Tests\OrmFunctionalTestCase $this->assertArrayHasKey('CmsUsers', $metadatas, 'CmsUsers entity was not detected.'); $this->assertArrayHasKey('CmsGroups', $metadatas, 'CmsGroups entity was not detected.'); - $this->assertEquals(1, count($metadatas['CmsUsers']->associationMappings)); + $this->assertEquals(2, count($metadatas['CmsUsers']->associationMappings)); $this->assertArrayHasKey('group', $metadatas['CmsUsers']->associationMappings); $this->assertEquals(1, count($metadatas['CmsGroups']->associationMappings)); $this->assertArrayHasKey('user', $metadatas['CmsGroups']->associationMappings); diff --git a/tests/Doctrine/Tests/ORM/Functional/SchemaTool/MySqlSchemaToolTest.php b/tests/Doctrine/Tests/ORM/Functional/SchemaTool/MySqlSchemaToolTest.php index 50e8a95c9..be671bf3d 100644 --- a/tests/Doctrine/Tests/ORM/Functional/SchemaTool/MySqlSchemaToolTest.php +++ b/tests/Doctrine/Tests/ORM/Functional/SchemaTool/MySqlSchemaToolTest.php @@ -27,15 +27,16 @@ class MySqlSchemaToolTest extends \Doctrine\Tests\OrmFunctionalTestCase $tool = new SchemaTool($this->_em); $sql = $tool->getCreateSchemaSql($classes); $this->assertEquals("CREATE TABLE cms_addresses (id INT AUTO_INCREMENT NOT NULL, user_id INT DEFAULT NULL, country VARCHAR(50) NOT NULL, zip VARCHAR(50) NOT NULL, city VARCHAR(50) NOT NULL, UNIQUE INDEX UNIQ_ACAC157BA76ED395 (user_id), PRIMARY KEY(id)) ENGINE = InnoDB", $sql[0]); - $this->assertEquals("CREATE TABLE cms_users (id INT AUTO_INCREMENT NOT NULL, status VARCHAR(50) NOT NULL, username VARCHAR(255) NOT NULL, name VARCHAR(255) NOT NULL, UNIQUE INDEX UNIQ_3AF03EC5F85E0677 (username), PRIMARY KEY(id)) ENGINE = InnoDB", $sql[1]); + $this->assertEquals("CREATE TABLE cms_users (id INT AUTO_INCREMENT NOT NULL, email_id INT DEFAULT NULL, status VARCHAR(50) DEFAULT NULL, username VARCHAR(255) NOT NULL, name VARCHAR(255) NOT NULL, UNIQUE INDEX UNIQ_3AF03EC5F85E0677 (username), UNIQUE INDEX UNIQ_3AF03EC5A832C1C9 (email_id), PRIMARY KEY(id)) ENGINE = InnoDB", $sql[1]); $this->assertEquals("CREATE TABLE cms_users_groups (user_id INT NOT NULL, group_id INT NOT NULL, INDEX IDX_7EA9409AA76ED395 (user_id), INDEX IDX_7EA9409AFE54D947 (group_id), PRIMARY KEY(user_id, group_id)) ENGINE = InnoDB", $sql[2]); $this->assertEquals("CREATE TABLE cms_phonenumbers (phonenumber VARCHAR(50) NOT NULL, user_id INT DEFAULT NULL, INDEX IDX_F21F790FA76ED395 (user_id), PRIMARY KEY(phonenumber)) ENGINE = InnoDB", $sql[3]); - $this->assertEquals("ALTER TABLE cms_addresses ADD FOREIGN KEY (user_id) REFERENCES cms_users(id)", $sql[4]); - $this->assertEquals("ALTER TABLE cms_users_groups ADD FOREIGN KEY (user_id) REFERENCES cms_users(id)", $sql[5]); - $this->assertEquals("ALTER TABLE cms_users_groups ADD FOREIGN KEY (group_id) REFERENCES cms_groups(id)", $sql[6]); - $this->assertEquals("ALTER TABLE cms_phonenumbers ADD FOREIGN KEY (user_id) REFERENCES cms_users(id)", $sql[7]); - - $this->assertEquals(8, count($sql)); + $this->assertEquals("ALTER TABLE cms_addresses ADD CONSTRAINT FK_ACAC157BA76ED395 FOREIGN KEY (user_id) REFERENCES cms_users (id)", $sql[4]); + $this->assertEquals("ALTER TABLE cms_users ADD CONSTRAINT FK_3AF03EC5A832C1C9 FOREIGN KEY (email_id) REFERENCES cms_emails (id)", $sql[5]); + $this->assertEquals("ALTER TABLE cms_users_groups ADD CONSTRAINT FK_7EA9409AA76ED395 FOREIGN KEY (user_id) REFERENCES cms_users (id)", $sql[6]); + $this->assertEquals("ALTER TABLE cms_users_groups ADD CONSTRAINT FK_7EA9409AFE54D947 FOREIGN KEY (group_id) REFERENCES cms_groups (id)", $sql[7]); + $this->assertEquals("ALTER TABLE cms_phonenumbers ADD CONSTRAINT FK_F21F790FA76ED395 FOREIGN KEY (user_id) REFERENCES cms_users (id)", $sql[8]); + + $this->assertEquals(9, count($sql)); } public function testGetCreateSchemaSql2() @@ -63,4 +64,4 @@ class MySqlSchemaToolTest extends \Doctrine\Tests\OrmFunctionalTestCase $this->assertEquals(1, count($sql)); $this->assertEquals("CREATE TABLE boolean_model (id INT AUTO_INCREMENT NOT NULL, booleanField TINYINT(1) NOT NULL, PRIMARY KEY(id)) ENGINE = InnoDB", $sql[0]); } -} \ No newline at end of file +} From adc4840ccecad7e35f97e43c740dd30602ce2241 Mon Sep 17 00:00:00 2001 From: Benjamin Eberlei Date: Sun, 23 Oct 2011 23:28:23 +0200 Subject: [PATCH 028/135] DDC-217 - Add support for QueryCacheProfiles and remove the old result caching code from ORM. Deprecate a bunch of methods in favor of using the AbstractQuery#getQueryCacheProfile method. --- lib/Doctrine/ORM/AbstractQuery.php | 145 ++++++------------ lib/Doctrine/ORM/Configuration.php | 21 --- lib/Doctrine/ORM/NativeQuery.php | 16 +- lib/Doctrine/ORM/Query.php | 3 + .../ORM/Query/Exec/AbstractSqlExecutor.php | 19 ++- .../Query/Exec/MultiTableDeleteExecutor.php | 9 +- .../Query/Exec/MultiTableUpdateExecutor.php | 6 +- .../ORM/Query/Exec/SingleSelectExecutor.php | 8 +- .../Exec/SingleTableDeleteUpdateExecutor.php | 8 +- lib/vendor/doctrine-dbal | 2 +- .../Tests/ORM/Functional/ResultCacheTest.php | 14 +- 11 files changed, 95 insertions(+), 156 deletions(-) diff --git a/lib/Doctrine/ORM/AbstractQuery.php b/lib/Doctrine/ORM/AbstractQuery.php index 5d71cf0aa..0f28e71db 100644 --- a/lib/Doctrine/ORM/AbstractQuery.php +++ b/lib/Doctrine/ORM/AbstractQuery.php @@ -20,7 +20,8 @@ namespace Doctrine\ORM; use Doctrine\DBAL\Types\Type, - Doctrine\ORM\Query\QueryException; + Doctrine\ORM\Query\QueryException, + Doctrine\DBAL\Cache\QueryCacheProfile; /** * Base contract for ORM queries. Base class for Query and NativeQuery. @@ -91,34 +92,15 @@ abstract class AbstractQuery protected $_hydrationMode = self::HYDRATE_OBJECT; /** - * The locally set cache driver used for caching result sets of this query. - * - * @var CacheDriver + * @param \Doctrine\DBAL\Cache\QueryCacheProfile */ - protected $_resultCacheDriver; - - /** - * Boolean flag for whether or not to cache the results of this query. - * - * @var boolean - */ - protected $_useResultCache; - - /** - * @var string The id to store the result cache entry under. - */ - protected $_resultCacheId; + protected $_queryCacheProfile; /** * @var boolean Boolean value that indicates whether or not expire the result cache. */ protected $_expireResultCache = false; - /** - * @var int Result Cache lifetime. - */ - protected $_resultCacheTTL; - /** * Initializes a new instance of a class derived from AbstractQuery. * @@ -260,7 +242,7 @@ abstract class AbstractQuery } /** - * Defines a cache driver to be used for caching result sets. + * Defines a cache driver to be used for caching result sets and implictly enables caching. * * @param Doctrine\Common\Cache\Cache $driver Cache driver * @return Doctrine\ORM\AbstractQuery @@ -270,9 +252,10 @@ abstract class AbstractQuery if ($resultCacheDriver !== null && ! ($resultCacheDriver instanceof \Doctrine\Common\Cache\Cache)) { throw ORMException::invalidResultCacheDriver(); } - $this->_resultCacheDriver = $resultCacheDriver; - if ($resultCacheDriver) { - $this->_useResultCache = true; + if ($this->_queryCacheProfile) { + $this->_queryCacheProfile = $this->_queryCacheProfile->setResultCacheDriver($resultCacheDriver); + } else { + $this->_queryCacheProfile = new QueryCacheProfile(0, null, $resultCacheDriver); } return $this; } @@ -280,12 +263,13 @@ abstract class AbstractQuery /** * Returns the cache driver used for caching result sets. * + * @deprecated * @return Doctrine\Common\Cache\Cache Cache driver */ public function getResultCacheDriver() { - if ($this->_resultCacheDriver) { - return $this->_resultCacheDriver; + if ($this->_queryCacheProfile && $this->_queryCacheProfile->getResultCacheDriver()) { + return $this->_queryCacheProfile->getResultCacheDriver(); } else { return $this->_em->getConfiguration()->getResultCacheImpl(); } @@ -296,18 +280,17 @@ abstract class AbstractQuery * how long and which ID to use for the cache entry. * * @param boolean $bool - * @param integer $timeToLive + * @param integer $lifetime * @param string $resultCacheId * @return Doctrine\ORM\AbstractQuery This query instance. */ - public function useResultCache($bool, $timeToLive = null, $resultCacheId = null) + public function useResultCache($bool, $lifetime = null, $resultCacheId = null) { - $this->_useResultCache = $bool; - if ($timeToLive) { - $this->setResultCacheLifetime($timeToLive); - } - if ($resultCacheId) { - $this->_resultCacheId = $resultCacheId; + if ($bool) { + $this->setResultCacheLifetime($lifetime); + $this->setResultCacheId($resultCacheId); + } else { + $this->_queryCacheProfile = null; } return $this; } @@ -315,27 +298,33 @@ abstract class AbstractQuery /** * Defines how long the result cache will be active before expire. * - * @param integer $timeToLive How long the cache entry is valid. + * @param integer $lifetime How long the cache entry is valid. * @return Doctrine\ORM\AbstractQuery This query instance. */ - public function setResultCacheLifetime($timeToLive) + public function setResultCacheLifetime($lifetime) { - if ($timeToLive !== null) { - $timeToLive = (int) $timeToLive; + if ($lifetime === null) { + $lifetime = 0; + } else { + $lifetime = (int)$lifetime; + } + if ($this->_queryCacheProfile) { + $this->_queryCacheProfile = $this->_queryCacheProfile->setLifetime($lifetime); + } else { + $this->_queryCacheProfile = new QueryCacheProfile($lifetime); } - - $this->_resultCacheTTL = $timeToLive; return $this; } /** * Retrieves the lifetime of resultset cache. * + * @deprecated * @return integer */ public function getResultCacheLifetime() { - return $this->_resultCacheTTL; + return $this->_queryCacheProfile ? $this->_queryCacheProfile->getLifetime() : 0; } /** @@ -360,6 +349,14 @@ abstract class AbstractQuery return $this->_expireResultCache; } + /** + * @return QueryCacheProfile + */ + public function getQueryCacheProfile() + { + return $this->_queryCacheProfile; + } + /** * Change the default fetch mode of an association for this query. * @@ -584,28 +581,6 @@ abstract class AbstractQuery $this->setParameters($params); } - // Check result cache - if ($this->_useResultCache && $cacheDriver = $this->getResultCacheDriver()) { - list($key, $hash) = $this->getResultCacheId(); - $cached = $this->_expireResultCache ? false : $cacheDriver->fetch($hash); - - if ($cached === false || !isset($cached[$key])) { - // Cache miss. - $stmt = $this->_doExecute(); - - $result = $this->_em->getHydrator($this->_hydrationMode)->hydrateAll( - $stmt, $this->_resultSetMapping, $this->_hints - ); - - $cacheDriver->save($hash, array($key => $result), $this->_resultCacheTTL); - - return $result; - } else { - // Cache hit. - return $cached[$key]; - } - } - $stmt = $this->_doExecute(); if (is_numeric($stmt)) { @@ -627,43 +602,23 @@ abstract class AbstractQuery */ public function setResultCacheId($id) { - $this->_resultCacheId = $id; + if ($this->_queryCacheProfile) { + $this->_queryCacheProfile = $this->_queryCacheProfile->setCacheKey($id); + } else { + $this->_queryCacheProfile = new QueryCacheProfile(0, $id); + } return $this; } /** - * Get the result cache id to use to store the result set cache entry. - * Will return the configured id if it exists otherwise a hash will be - * automatically generated for you. + * Get the result cache id to use to store the result set cache entry if set. * - * @return array ($key, $hash) + * @deprecated + * @return string */ - protected function getResultCacheId() + public function getResultCacheId() { - if ($this->_resultCacheId) { - return array($this->_resultCacheId, $this->_resultCacheId); - } else { - $params = $this->_params; - foreach ($params AS $key => $value) { - if (is_object($value) && $this->_em->getMetadataFactory()->hasMetadataFor(get_class($value))) { - if ($this->_em->getUnitOfWork()->getEntityState($value) == UnitOfWork::STATE_MANAGED) { - $idValues = $this->_em->getUnitOfWork()->getEntityIdentifier($value); - } else { - $class = $this->_em->getClassMetadata(get_class($value)); - $idValues = $class->getIdentifierValues($value); - } - $params[$key] = $idValues; - } else { - $params[$key] = $value; - } - } - - $sql = $this->getSql(); - ksort($this->_hints); - $key = implode(";", (array)$sql) . var_export($params, true) . - var_export($this->_hints, true)."&hydrationMode=".$this->_hydrationMode; - return array($key, md5($key)); - } + return $this->_queryCacheProfile ? $this->_queryCacheProfile->getCacheKey() : null; } /** diff --git a/lib/Doctrine/ORM/Configuration.php b/lib/Doctrine/ORM/Configuration.php index ee6496087..eaa4df562 100644 --- a/lib/Doctrine/ORM/Configuration.php +++ b/lib/Doctrine/ORM/Configuration.php @@ -209,27 +209,6 @@ class Configuration extends \Doctrine\DBAL\Configuration $this->_attributes['metadataDriverImpl'] : null; } - /** - * Gets the cache driver implementation that is used for query result caching. - * - * @return \Doctrine\Common\Cache\Cache - */ - public function getResultCacheImpl() - { - return isset($this->_attributes['resultCacheImpl']) ? - $this->_attributes['resultCacheImpl'] : null; - } - - /** - * Sets the cache driver implementation that is used for query result caching. - * - * @param \Doctrine\Common\Cache\Cache $cacheImpl - */ - public function setResultCacheImpl(Cache $cacheImpl) - { - $this->_attributes['resultCacheImpl'] = $cacheImpl; - } - /** * Gets the cache driver implementation that is used for the query cache (SQL cache). * diff --git a/lib/Doctrine/ORM/NativeQuery.php b/lib/Doctrine/ORM/NativeQuery.php index 2c0a5ab28..dea223fa3 100644 --- a/lib/Doctrine/ORM/NativeQuery.php +++ b/lib/Doctrine/ORM/NativeQuery.php @@ -57,17 +57,17 @@ final class NativeQuery extends AbstractQuery */ protected function _doExecute() { - $stmt = $this->_em->getConnection()->prepare($this->_sql); $params = $this->_params; - foreach ($params as $key => $value) { - if (isset($this->_paramTypes[$key])) { - $stmt->bindValue($key, $value, $this->_paramTypes[$key]); - } else { - $stmt->bindValue($key, $value); + $types = $this->_paramTypes; + if ($params) { + if (is_int(key($params))) { + ksort($params); + ksort($types); + $params = array_values($params); + $types = array_values($types); } } - $stmt->execute(); - return $stmt; + return $this->_em->getConnection()->executeQuery($this->_sql, $params, $types, $this->_queryCacheProfile); } } \ No newline at end of file diff --git a/lib/Doctrine/ORM/Query.php b/lib/Doctrine/ORM/Query.php index e0bbcec5a..293c64391 100644 --- a/lib/Doctrine/ORM/Query.php +++ b/lib/Doctrine/ORM/Query.php @@ -232,6 +232,9 @@ final class Query extends AbstractQuery protected function _doExecute() { $executor = $this->_parse()->getSqlExecutor(); + if ($this->_queryCacheProfile) { + $executor->setQueryCacheProfile($this->_queryCacheProfile); + } // Prepare parameters $paramMappings = $this->_parserResult->getParameterMappings(); diff --git a/lib/Doctrine/ORM/Query/Exec/AbstractSqlExecutor.php b/lib/Doctrine/ORM/Query/Exec/AbstractSqlExecutor.php index 7879b0ff2..f44e383b9 100644 --- a/lib/Doctrine/ORM/Query/Exec/AbstractSqlExecutor.php +++ b/lib/Doctrine/ORM/Query/Exec/AbstractSqlExecutor.php @@ -1,7 +1,5 @@ _sqlStatements; } + public function setQueryCacheProfile(QueryCacheProfile $qcp) + { + $this->queryCacheProfile = $qcp; + } + /** * Executes all sql statements. * * @param Doctrine\DBAL\Connection $conn The database connection that is used to execute the queries. * @param array $params The parameters. + * @param array $types The parameter types. * @return Doctrine\DBAL\Driver\Statement */ - abstract public function execute(Connection $conn, array $params, array $types); + abstract public function execute(Connection $conn, array $params, array $types); } \ No newline at end of file diff --git a/lib/Doctrine/ORM/Query/Exec/MultiTableDeleteExecutor.php b/lib/Doctrine/ORM/Query/Exec/MultiTableDeleteExecutor.php index a6c22cecd..5b07d4d02 100644 --- a/lib/Doctrine/ORM/Query/Exec/MultiTableDeleteExecutor.php +++ b/lib/Doctrine/ORM/Query/Exec/MultiTableDeleteExecutor.php @@ -1,7 +1,5 @@ - * @version $Revision$ * @link www.doctrine-project.org * @since 2.0 */ @@ -41,8 +38,11 @@ class SingleSelectExecutor extends AbstractSqlExecutor $this->_sqlStatements = $sqlWalker->walkSelectStatement($AST); } + /** + * {@inheritDoc} + */ public function execute(Connection $conn, array $params, array $types) { - return $conn->executeQuery($this->_sqlStatements, $params, $types); + return $conn->executeQuery($this->_sqlStatements, $params, $types, $this->queryCacheProfile); } } diff --git a/lib/Doctrine/ORM/Query/Exec/SingleTableDeleteUpdateExecutor.php b/lib/Doctrine/ORM/Query/Exec/SingleTableDeleteUpdateExecutor.php index 94db13b05..facccb715 100644 --- a/lib/Doctrine/ORM/Query/Exec/SingleTableDeleteUpdateExecutor.php +++ b/lib/Doctrine/ORM/Query/Exec/SingleTableDeleteUpdateExecutor.php @@ -1,7 +1,5 @@ - * @version $Revision$ * @link www.doctrine-project.org * @since 2.0 * @todo This is exactly the same as SingleSelectExecutor. Unify in SingleStatementExecutor. @@ -45,7 +42,10 @@ class SingleTableDeleteUpdateExecutor extends AbstractSqlExecutor $this->_sqlStatements = $sqlWalker->walkDeleteStatement($AST); } } - + + /** + * {@inheritDoc} + */ public function execute(Connection $conn, array $params, array $types) { return $conn->executeUpdate($this->_sqlStatements, $params, $types); diff --git a/lib/vendor/doctrine-dbal b/lib/vendor/doctrine-dbal index f91395b6f..dea79e7bf 160000 --- a/lib/vendor/doctrine-dbal +++ b/lib/vendor/doctrine-dbal @@ -1 +1 @@ -Subproject commit f91395b6f469b5076f52fefd64574c443b076485 +Subproject commit dea79e7bfbabf973e807539e258ccf3b9ee98f45 diff --git a/tests/Doctrine/Tests/ORM/Functional/ResultCacheTest.php b/tests/Doctrine/Tests/ORM/Functional/ResultCacheTest.php index 235b4c91c..3dcae4fab 100644 --- a/tests/Doctrine/Tests/ORM/Functional/ResultCacheTest.php +++ b/tests/Doctrine/Tests/ORM/Functional/ResultCacheTest.php @@ -90,10 +90,10 @@ class ResultCacheTest extends \Doctrine\Tests\OrmFunctionalTestCase public function testUseResultCache() { $cache = new \Doctrine\Common\Cache\ArrayCache(); - $this->_em->getConfiguration()->setResultCacheImpl($cache); $query = $this->_em->createQuery('select ux from Doctrine\Tests\Models\CMS\CmsUser ux'); $query->useResultCache(true); + $query->setResultCacheDriver($cache); $query->setResultCacheId('testing_result_cache_id'); $users = $query->getResult(); @@ -108,11 +108,11 @@ class ResultCacheTest extends \Doctrine\Tests\OrmFunctionalTestCase public function testUseResultCacheParams() { $cache = new \Doctrine\Common\Cache\ArrayCache(); - $this->_em->getConfiguration()->setResultCacheImpl($cache); $sqlCount = count($this->_sqlLoggerStack->queries); $query = $this->_em->createQuery('select ux from Doctrine\Tests\Models\CMS\CmsUser ux WHERE ux.id = ?1'); $query->setParameter(1, 1); + $query->setResultCacheDriver($cache); $query->useResultCache(true); $query->getResult(); @@ -149,10 +149,10 @@ class ResultCacheTest extends \Doctrine\Tests\OrmFunctionalTestCase } /** - * @param $query + * @param string $query * @depends testNativeQueryResultCaching */ - public function testResultCacheDependsOnQueryHints($query) + public function testResultCacheNotDependsOnQueryHints($query) { $cache = $query->getResultCacheDriver(); $cacheCount = $this->getCacheSize($cache); @@ -160,7 +160,7 @@ class ResultCacheTest extends \Doctrine\Tests\OrmFunctionalTestCase $query->setHint('foo', 'bar'); $query->getResult(); - $this->assertEquals($cacheCount + 1, $this->getCacheSize($cache)); + $this->assertEquals($cacheCount, $this->getCacheSize($cache)); } /** @@ -182,7 +182,7 @@ class ResultCacheTest extends \Doctrine\Tests\OrmFunctionalTestCase * @param $query * @depends testNativeQueryResultCaching */ - public function testResultCacheDependsOnHydrationMode($query) + public function testResultCacheNotDependsOnHydrationMode($query) { $cache = $query->getResultCacheDriver(); $cacheCount = $this->getCacheSize($cache); @@ -190,7 +190,7 @@ class ResultCacheTest extends \Doctrine\Tests\OrmFunctionalTestCase $this->assertNotEquals(\Doctrine\ORM\Query::HYDRATE_ARRAY, $query->getHydrationMode()); $query->getArrayResult(); - $this->assertEquals($cacheCount + 1, $this->getCacheSize($cache)); + $this->assertEquals($cacheCount, $this->getCacheSize($cache)); } /** From a8052dec3775835852344951c4f5e9aa053b6004 Mon Sep 17 00:00:00 2001 From: Benjamin Eberlei Date: Sun, 23 Oct 2011 23:35:04 +0200 Subject: [PATCH 029/135] Added note about result cache changes --- UPGRADE_TO_2_2 | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/UPGRADE_TO_2_2 b/UPGRADE_TO_2_2 index 220fb39f9..757413029 100644 --- a/UPGRADE_TO_2_2 +++ b/UPGRADE_TO_2_2 @@ -1,3 +1,17 @@ +# ResultCache implementation rewritten + +The result cache is completly rewritten and now works on the database result level, not inside the ORM AbstractQuery +anymore. This means that for result cached queries the hydration will now always be performed again, regardless of +the hydration mode. Affected areas are: + +1. Fixes the problem that entities coming from the result cache were not registered in the UnitOfWork + leading to problems during EntityManager#flush. Calls to EntityManager#merge are not necessary anymore. +2. Affects the array hydrator which now includes the overhead of hydration compared to caching the final result. + +The API is backwards compatible however most of the getter methods on the `AbstractQuery` object are now +deprecated in favor of calling AbstractQuery#getQueryCacheProfile(). This method returns a `Doctrine\DBAL\Cache\QueryCacheProfile` +instance with access to result cache driver, lifetime and cache key. + # EntityManager#getPartialReference() creates read-only entity Entities returned from EntityManager#getPartialReference() are now marked as read-only if they From 1b83fcc46d980f2baa2c98ebf9d14b37778d9b86 Mon Sep 17 00:00:00 2001 From: Asmir Mustafic Date: Mon, 24 Oct 2011 09:20:24 +0200 Subject: [PATCH 030/135] Coding standards --- lib/Doctrine/ORM/Tools/Export/Driver/XmlExporter.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/Doctrine/ORM/Tools/Export/Driver/XmlExporter.php b/lib/Doctrine/ORM/Tools/Export/Driver/XmlExporter.php index 9390f144f..5c8d97327 100644 --- a/lib/Doctrine/ORM/Tools/Export/Driver/XmlExporter.php +++ b/lib/Doctrine/ORM/Tools/Export/Driver/XmlExporter.php @@ -276,7 +276,7 @@ class XmlExporter extends AbstractExporter if ($associationMapping['isCascadeDetach']) { $cascade[] = 'cascade-detach'; } - if(count($cascade)==5){ + if(count($cascade) === 5){ $cascade = array('cascade-all'); } if ($cascade) { From cb76222e63db2eb2b3a58f666c12367cb5307c76 Mon Sep 17 00:00:00 2001 From: Asmir Mustafic Date: Mon, 24 Oct 2011 09:54:31 +0200 Subject: [PATCH 031/135] Collapse cascade persist, remove, refresh, detach, merge into cascade-all (implemented currently only for XML annotation) --- .../AbstractClassMetadataExporterTest.php | 18 +++++++++++++++++- ...octrine.Tests.ORM.Tools.Export.User.dcm.xml | 10 ++++++++++ 2 files changed, 27 insertions(+), 1 deletion(-) diff --git a/tests/Doctrine/Tests/ORM/Tools/Export/AbstractClassMetadataExporterTest.php b/tests/Doctrine/Tests/ORM/Tools/Export/AbstractClassMetadataExporterTest.php index 2571a1b98..a5f92d870 100644 --- a/tests/Doctrine/Tests/ORM/Tools/Export/AbstractClassMetadataExporterTest.php +++ b/tests/Doctrine/Tests/ORM/Tools/Export/AbstractClassMetadataExporterTest.php @@ -324,7 +324,23 @@ abstract class AbstractClassMetadataExporterTest extends \Doctrine\Tests\OrmTest { $this->assertEquals('user', $class->associationMappings['address']['inversedBy']); } - + /** + * @depends testOneToManyAssociationsAreExported + * @param ClassMetadataInfo $class + */ + public function testCascadeIsDetected($class) + { + if(!isset($class->associationMappings['interests'])){ + $this->markTestSkipped('The "interests" association is not aviable.'); + }else{ + $this->assertTrue($class->associationMappings['interests']['isCascadePersist']); + $this->assertTrue($class->associationMappings['interests']['isCascadeMerge']); + $this->assertTrue($class->associationMappings['interests']['isCascadeRemove']); + $this->assertTrue($class->associationMappings['interests']['isCascadeRefresh']); + $this->assertTrue($class->associationMappings['interests']['isCascadeDetach']); + } + return $class; + } public function __destruct() { # $this->_deleteDirectory(__DIR__ . '/export/'.$this->_getType()); diff --git a/tests/Doctrine/Tests/ORM/Tools/Export/xml/Doctrine.Tests.ORM.Tools.Export.User.dcm.xml b/tests/Doctrine/Tests/ORM/Tools/Export/xml/Doctrine.Tests.ORM.Tools.Export.User.dcm.xml index c562003c6..843882278 100644 --- a/tests/Doctrine/Tests/ORM/Tools/Export/xml/Doctrine.Tests.ORM.Tools.Export.User.dcm.xml +++ b/tests/Doctrine/Tests/ORM/Tools/Export/xml/Doctrine.Tests.ORM.Tools.Export.User.dcm.xml @@ -35,6 +35,16 @@ + + + + + + + + + + From 5f80b575541d53489f651f9d50aa44db5bdb49df Mon Sep 17 00:00:00 2001 From: Asmir Mustafic Date: Mon, 24 Oct 2011 10:19:01 +0200 Subject: [PATCH 032/135] Improoved coding standards --- lib/Doctrine/ORM/Tools/EntityGenerator.php | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/lib/Doctrine/ORM/Tools/EntityGenerator.php b/lib/Doctrine/ORM/Tools/EntityGenerator.php index c135ecac4..1542f6cf0 100644 --- a/lib/Doctrine/ORM/Tools/EntityGenerator.php +++ b/lib/Doctrine/ORM/Tools/EntityGenerator.php @@ -635,7 +635,7 @@ public function () foreach ($metadata->associationMappings as $associationMapping) { if ($associationMapping['type'] & ClassMetadataInfo::TO_ONE) { $nullable = $this->_isAssociationIsNullable($associationMapping); - if ($code = $this->_generateEntityStubMethod($metadata, 'set', $associationMapping['fieldName'], $associationMapping['targetEntity'], ($nullable?'null':null))) { + if ($code = $this->_generateEntityStubMethod($metadata, 'set', $associationMapping['fieldName'], $associationMapping['targetEntity'], ($nullable ? 'null' : null))) { $methods[] = $code; } if ($code = $this->_generateEntityStubMethod($metadata, 'get', $associationMapping['fieldName'], $associationMapping['targetEntity'])) { @@ -656,16 +656,14 @@ public function () private function _isAssociationIsNullable($associationMapping) { - if(isset($associationMapping['joinColumns'])){ + if (isset($associationMapping['joinColumns'])) { $joinColumns = $associationMapping['joinColumns']; - }else{ + } else { //@todo thereis no way to retreive targetEntity metadata - //$targetMetadata = $this->getClassMetadata($associationMapping['targetEntity']); - //$joinColumns = $targetMetadata->associationMappings[$associationMapping["mappedBy"]]['joinColumns']; $joinColumns = array(); } foreach ($joinColumns as $joinColumn) { - if(isset($joinColumn['nullable']) && !$joinColumn['nullable']){ + if(isset($joinColumn['nullable']) && !$joinColumn['nullable']) { return false; } } @@ -756,7 +754,7 @@ public function () '' => Inflector::camelize($fieldName), '' => $methodName, '' => $fieldName, - '' => ($defaultValue!==null?('='.$defaultValue):''), + '' => (($defaultValue !== null ) ? ('='.$defaultValue) : ''), '' => $this->_getClassName($metadata) ); @@ -825,10 +823,10 @@ public function () { $lines = array(); $lines[] = $this->_spaces . '/**'; - + if ($associationMapping['type'] & ClassMetadataInfo::TO_MANY) { $lines[] = $this->_spaces . ' * @var \Doctrine\Common\Collections\ArrayCollection'; - }else{ + } else { $lines[] = $this->_spaces . ' * @var ' . $associationMapping['targetEntity']; } From f5330741ac1bff9e015172e2ec9c2a3f019cd84b Mon Sep 17 00:00:00 2001 From: Asmir Mustafic Date: Mon, 24 Oct 2011 10:59:50 +0200 Subject: [PATCH 033/135] Coding standards --- lib/Doctrine/ORM/Tools/EntityGenerator.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/Doctrine/ORM/Tools/EntityGenerator.php b/lib/Doctrine/ORM/Tools/EntityGenerator.php index 1542f6cf0..b7522380f 100644 --- a/lib/Doctrine/ORM/Tools/EntityGenerator.php +++ b/lib/Doctrine/ORM/Tools/EntityGenerator.php @@ -634,8 +634,8 @@ public function () foreach ($metadata->associationMappings as $associationMapping) { if ($associationMapping['type'] & ClassMetadataInfo::TO_ONE) { - $nullable = $this->_isAssociationIsNullable($associationMapping); - if ($code = $this->_generateEntityStubMethod($metadata, 'set', $associationMapping['fieldName'], $associationMapping['targetEntity'], ($nullable ? 'null' : null))) { + $nullable = $this->_isAssociationIsNullable($associationMapping) ? 'null' : null; + if ($code = $this->_generateEntityStubMethod($metadata, 'set', $associationMapping['fieldName'], $associationMapping['targetEntity'], $nullable)) { $methods[] = $code; } if ($code = $this->_generateEntityStubMethod($metadata, 'get', $associationMapping['fieldName'], $associationMapping['targetEntity'])) { From cdb452b27b5a341a372dff833ae2d40a4e03d513 Mon Sep 17 00:00:00 2001 From: Jaik Dean Date: Mon, 24 Oct 2011 10:01:27 +0100 Subject: [PATCH 034/135] Fixed typos of 'discriminator' --- lib/Doctrine/ORM/Mapping/ClassMetadataInfo.php | 2 +- lib/Doctrine/ORM/Mapping/MappingException.php | 2 +- .../Doctrine/Tests/ORM/Mapping/BasicInheritanceMappingTest.php | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/Doctrine/ORM/Mapping/ClassMetadataInfo.php b/lib/Doctrine/ORM/Mapping/ClassMetadataInfo.php index 17255cb23..25a3350d2 100644 --- a/lib/Doctrine/ORM/Mapping/ClassMetadataInfo.php +++ b/lib/Doctrine/ORM/Mapping/ClassMetadataInfo.php @@ -318,7 +318,7 @@ class ClassMetadataInfo implements ClassMetadata public $discriminatorMap = array(); /** - * READ-ONLY: The definition of the descriminator column used in JOINED and SINGLE_TABLE + * READ-ONLY: The definition of the discriminator column used in JOINED and SINGLE_TABLE * inheritance mappings. * * @var array diff --git a/lib/Doctrine/ORM/Mapping/MappingException.php b/lib/Doctrine/ORM/Mapping/MappingException.php index 97fbf94f6..fcdd5c575 100644 --- a/lib/Doctrine/ORM/Mapping/MappingException.php +++ b/lib/Doctrine/ORM/Mapping/MappingException.php @@ -288,7 +288,7 @@ class MappingException extends \Doctrine\ORM\ORMException public static function mappedClassNotPartOfDiscriminatorMap($className, $rootClassName) { return new self( - "Entity '" . $className . "' has to be part of the descriminator map of '" . $rootClassName . "' " . + "Entity '" . $className . "' has to be part of the discriminator map of '" . $rootClassName . "' " . "to be properly mapped in the inheritance hierachy. Alternatively you can make '".$className."' an abstract class " . "to avoid this exception from occuring." ); diff --git a/tests/Doctrine/Tests/ORM/Mapping/BasicInheritanceMappingTest.php b/tests/Doctrine/Tests/ORM/Mapping/BasicInheritanceMappingTest.php index 93504ec53..1d8d6c118 100644 --- a/tests/Doctrine/Tests/ORM/Mapping/BasicInheritanceMappingTest.php +++ b/tests/Doctrine/Tests/ORM/Mapping/BasicInheritanceMappingTest.php @@ -114,7 +114,7 @@ class BasicInheritanceMappingTest extends \Doctrine\Tests\OrmTestCase */ public function testUnmappedEntityInHierachy() { - $this->setExpectedException('Doctrine\ORM\Mapping\MappingException', "Entity 'Doctrine\Tests\ORM\Mapping\HierachyBEntity' has to be part of the descriminator map of 'Doctrine\Tests\ORM\Mapping\HierachyBase' to be properly mapped in the inheritance hierachy. Alternatively you can make 'Doctrine\Tests\ORM\Mapping\HierachyBEntity' an abstract class to avoid this exception from occuring."); + $this->setExpectedException('Doctrine\ORM\Mapping\MappingException', "Entity 'Doctrine\Tests\ORM\Mapping\HierachyBEntity' has to be part of the discriminator map of 'Doctrine\Tests\ORM\Mapping\HierachyBase' to be properly mapped in the inheritance hierachy. Alternatively you can make 'Doctrine\Tests\ORM\Mapping\HierachyBEntity' an abstract class to avoid this exception from occuring."); $class = $this->_factory->getMetadataFor(__NAMESPACE__ . '\\HierachyE'); } From 7efd615b8c6d6658b05740c923170ef8a24dbba7 Mon Sep 17 00:00:00 2001 From: Asmir Mustafic Date: Mon, 24 Oct 2011 12:00:11 +0200 Subject: [PATCH 035/135] Coding standards --- lib/Doctrine/ORM/Tools/EntityGenerator.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/Doctrine/ORM/Tools/EntityGenerator.php b/lib/Doctrine/ORM/Tools/EntityGenerator.php index 1542f6cf0..b1ba714d8 100644 --- a/lib/Doctrine/ORM/Tools/EntityGenerator.php +++ b/lib/Doctrine/ORM/Tools/EntityGenerator.php @@ -634,8 +634,8 @@ public function () foreach ($metadata->associationMappings as $associationMapping) { if ($associationMapping['type'] & ClassMetadataInfo::TO_ONE) { - $nullable = $this->_isAssociationIsNullable($associationMapping); - if ($code = $this->_generateEntityStubMethod($metadata, 'set', $associationMapping['fieldName'], $associationMapping['targetEntity'], ($nullable ? 'null' : null))) { + $nullable = $this->_isAssociationIsNullable($associationMapping) ? 'null' : null; + if ($code = $this->_generateEntityStubMethod($metadata, 'set', $associationMapping['fieldName'], $associationMapping['targetEntity'], $nullable)) { $methods[] = $code; } if ($code = $this->_generateEntityStubMethod($metadata, 'get', $associationMapping['fieldName'], $associationMapping['targetEntity'])) { @@ -754,7 +754,7 @@ public function () '' => Inflector::camelize($fieldName), '' => $methodName, '' => $fieldName, - '' => (($defaultValue !== null ) ? ('='.$defaultValue) : ''), + '' => ($defaultValue !== null ) ? ('='.$defaultValue) : '', '' => $this->_getClassName($metadata) ); From 3b9312e291b0f730f863c276c131cf2d2d01a68d Mon Sep 17 00:00:00 2001 From: Benjamin Eberlei Date: Tue, 25 Oct 2011 22:54:20 +0200 Subject: [PATCH 036/135] Bump DBAL version and make TestUtil more lenient --- lib/vendor/doctrine-dbal | 2 +- tests/Doctrine/Tests/TestUtil.php | 14 ++++++++++---- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/lib/vendor/doctrine-dbal b/lib/vendor/doctrine-dbal index 82cc92144..537de7ea6 160000 --- a/lib/vendor/doctrine-dbal +++ b/lib/vendor/doctrine-dbal @@ -1 +1 @@ -Subproject commit 82cc921447fde697bf3d9f5285d0f0b8587303d8 +Subproject commit 537de7ea6a34edbcc40bc6ca92e0a3f816b59330 diff --git a/tests/Doctrine/Tests/TestUtil.php b/tests/Doctrine/Tests/TestUtil.php index a77812f7f..b78d06e4e 100644 --- a/tests/Doctrine/Tests/TestUtil.php +++ b/tests/Doctrine/Tests/TestUtil.php @@ -70,10 +70,16 @@ class TestUtil } else { $sm = $realConn->getSchemaManager(); - $tableNames = $sm->listTableNames(); - - foreach ($tableNames AS $tableName) { - $sm->dropTable($tableName); + /* @var $schema Schema */ + $schema = $sm->createSchema(); + $stmts = $schema->toDropSql($realConn->getDatabasePlatform()); + + foreach ($stmts AS $stmt) { + try { + $realConn->exec($stmt); + } catch(\Exception $e) { + // TODO: Now is this a real good idea? + } } } From 0a5a23628f74f3203d2e659939b4aee39fa4c462 Mon Sep 17 00:00:00 2001 From: Lukas Kahwe Smith Date: Tue, 25 Oct 2011 23:21:39 +0200 Subject: [PATCH 037/135] added EntityRepository::getClassName() to fullfill the ObjectRepository interface see https://github.com/doctrine/common/pull/70 --- lib/Doctrine/ORM/EntityRepository.php | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/lib/Doctrine/ORM/EntityRepository.php b/lib/Doctrine/ORM/EntityRepository.php index 9760a1c42..a4c239001 100644 --- a/lib/Doctrine/ORM/EntityRepository.php +++ b/lib/Doctrine/ORM/EntityRepository.php @@ -225,6 +225,14 @@ class EntityRepository implements ObjectRepository return $this->_entityName; } + /** + * @return string + */ + public function getClassName() + { + return $this->getEntityName(); + } + /** * @return EntityManager */ @@ -240,4 +248,4 @@ class EntityRepository implements ObjectRepository { return $this->_class; } -} \ No newline at end of file +} From 035ca8e500f687b9d62bb555bb40aac69b6195f7 Mon Sep 17 00:00:00 2001 From: Asmir Mustafic Date: Wed, 26 Oct 2011 10:14:59 +0200 Subject: [PATCH 038/135] Collapse cascade all test --- .../AbstractClassMetadataExporterTest.php | 26 ++++++++++--------- 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/tests/Doctrine/Tests/ORM/Tools/Export/AbstractClassMetadataExporterTest.php b/tests/Doctrine/Tests/ORM/Tools/Export/AbstractClassMetadataExporterTest.php index a5f92d870..925e51550 100644 --- a/tests/Doctrine/Tests/ORM/Tools/Export/AbstractClassMetadataExporterTest.php +++ b/tests/Doctrine/Tests/ORM/Tools/Export/AbstractClassMetadataExporterTest.php @@ -325,21 +325,23 @@ abstract class AbstractClassMetadataExporterTest extends \Doctrine\Tests\OrmTest $this->assertEquals('user', $class->associationMappings['address']['inversedBy']); } /** - * @depends testOneToManyAssociationsAreExported - * @param ClassMetadataInfo $class + * @depends testExportDirectoryAndFilesAreCreated */ - public function testCascadeIsDetected($class) + public function testCascadeAllCollapsed() { - if(!isset($class->associationMappings['interests'])){ - $this->markTestSkipped('The "interests" association is not aviable.'); - }else{ - $this->assertTrue($class->associationMappings['interests']['isCascadePersist']); - $this->assertTrue($class->associationMappings['interests']['isCascadeMerge']); - $this->assertTrue($class->associationMappings['interests']['isCascadeRemove']); - $this->assertTrue($class->associationMappings['interests']['isCascadeRefresh']); - $this->assertTrue($class->associationMappings['interests']['isCascadeDetach']); + $type = $this->_getType(); + if ($type == 'xml') { + $xml = simplexml_load_file(__DIR__ . '/export/'.$type.'/Doctrine.Tests.ORM.Tools.Export.ExportedUser.dcm.xml'); + + $xml->registerXPathNamespace("d", "http://doctrine-project.org/schemas/orm/doctrine-mapping"); + $nodes = $xml->xpath("/d:doctrine-mapping/d:entity/d:one-to-many[@field='interests']/d:cascade/d:*"); + $this->assertEquals(1, count($nodes)); + + $this->assertEquals('cascade-all', $nodes[0]->getName()); + + } else { + $this->markTestSkipped('Test aviable only for XML dirver'); } - return $class; } public function __destruct() { From d09285e9d33128f4059b3166b7340b37d836a002 Mon Sep 17 00:00:00 2001 From: Asmir Mustafic Date: Wed, 26 Oct 2011 10:59:15 +0200 Subject: [PATCH 039/135] Collapse cascade all test (YAML too) --- lib/Doctrine/ORM/Tools/Export/Driver/XmlExporter.php | 2 +- .../ORM/Tools/Export/Driver/YamlExporter.php | 3 +++ .../Export/AbstractClassMetadataExporterTest.php | 12 ++++++++++-- .../Doctrine.Tests.ORM.Tools.Export.User.dcm.yml | 5 +++++ 4 files changed, 19 insertions(+), 3 deletions(-) diff --git a/lib/Doctrine/ORM/Tools/Export/Driver/XmlExporter.php b/lib/Doctrine/ORM/Tools/Export/Driver/XmlExporter.php index 5c8d97327..f4f708e4e 100644 --- a/lib/Doctrine/ORM/Tools/Export/Driver/XmlExporter.php +++ b/lib/Doctrine/ORM/Tools/Export/Driver/XmlExporter.php @@ -276,7 +276,7 @@ class XmlExporter extends AbstractExporter if ($associationMapping['isCascadeDetach']) { $cascade[] = 'cascade-detach'; } - if(count($cascade) === 5){ + if (count($cascade) === 5) { $cascade = array('cascade-all'); } if ($cascade) { diff --git a/lib/Doctrine/ORM/Tools/Export/Driver/YamlExporter.php b/lib/Doctrine/ORM/Tools/Export/Driver/YamlExporter.php index e8410db2d..de76c7bdd 100644 --- a/lib/Doctrine/ORM/Tools/Export/Driver/YamlExporter.php +++ b/lib/Doctrine/ORM/Tools/Export/Driver/YamlExporter.php @@ -147,6 +147,9 @@ class YamlExporter extends AbstractExporter if ($associationMapping['isCascadeDetach']) { $cascade[] = 'detach'; } + if (count($cascade) === 5) { + $cascade = array('all'); + } $associationMappingArray = array( 'targetEntity' => $associationMapping['targetEntity'], 'cascade' => $cascade, diff --git a/tests/Doctrine/Tests/ORM/Tools/Export/AbstractClassMetadataExporterTest.php b/tests/Doctrine/Tests/ORM/Tools/Export/AbstractClassMetadataExporterTest.php index 925e51550..6ff3d1808 100644 --- a/tests/Doctrine/Tests/ORM/Tools/Export/AbstractClassMetadataExporterTest.php +++ b/tests/Doctrine/Tests/ORM/Tools/Export/AbstractClassMetadataExporterTest.php @@ -338,9 +338,17 @@ abstract class AbstractClassMetadataExporterTest extends \Doctrine\Tests\OrmTest $this->assertEquals(1, count($nodes)); $this->assertEquals('cascade-all', $nodes[0]->getName()); - + } elseif ($type == 'yaml') { + + $yaml = new \Symfony\Component\Yaml\Parser(); + $value = $yaml->parse(file_get_contents(__DIR__ . '/export/'.$type.'/Doctrine.Tests.ORM.Tools.Export.ExportedUser.dcm.yml')); + + $this->assertTrue(isset($value['Doctrine\Tests\ORM\Tools\Export\ExportedUser']['oneToMany']['interests']['cascade'])); + $this->assertEquals(1, count($value['Doctrine\Tests\ORM\Tools\Export\ExportedUser']['oneToMany']['interests']['cascade'])); + $this->assertEquals('all', $value['Doctrine\Tests\ORM\Tools\Export\ExportedUser']['oneToMany']['interests']['cascade'][0]); + } else { - $this->markTestSkipped('Test aviable only for XML dirver'); + $this->markTestSkipped('Test aviable only for '.$type.' dirver'); } } public function __destruct() diff --git a/tests/Doctrine/Tests/ORM/Tools/Export/yaml/Doctrine.Tests.ORM.Tools.Export.User.dcm.yml b/tests/Doctrine/Tests/ORM/Tools/Export/yaml/Doctrine.Tests.ORM.Tools.Export.User.dcm.yml index 9231bb189..ee48d8511 100644 --- a/tests/Doctrine/Tests/ORM/Tools/Export/yaml/Doctrine.Tests.ORM.Tools.Export.User.dcm.yml +++ b/tests/Doctrine/Tests/ORM/Tools/Export/yaml/Doctrine.Tests.ORM.Tools.Export.User.dcm.yml @@ -34,6 +34,11 @@ Doctrine\Tests\ORM\Tools\Export\User: number: ASC cascade: [ persist, merge ] orphanRemoval: true + interests: + targetEntity: Doctrine\Tests\ORM\Tools\Export\Interests + mappedBy: user + cascade: [ persist, merge, remove, refresh, detach ] + orphanRemoval: true manyToMany: groups: targetEntity: Doctrine\Tests\ORM\Tools\Export\Group From c5ef21864fa524b9239f698d3918ad06b3265b35 Mon Sep 17 00:00:00 2001 From: Guilherme Blanco Date: Wed, 26 Oct 2011 15:04:49 -0200 Subject: [PATCH 040/135] Fixed bug with fetch=EAGER associations that have already been hydrated during querying. --- lib/Doctrine/ORM/UnitOfWork.php | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/lib/Doctrine/ORM/UnitOfWork.php b/lib/Doctrine/ORM/UnitOfWork.php index 4366250dd..e499d3c5f 100644 --- a/lib/Doctrine/ORM/UnitOfWork.php +++ b/lib/Doctrine/ORM/UnitOfWork.php @@ -2054,6 +2054,11 @@ class UnitOfWork implements PropertyChangedListener // Loading the entity right here, if its in the eager loading map get rid of it there. unset($this->eagerLoadingEntities[$class->rootEntityName][$idHash]); + if (isset($this->eagerLoadingEntities[$class->rootEntityName]) && + ! $this->eagerLoadingEntities[$class->rootEntityName]) { + unset($this->eagerLoadingEntities[$class->rootEntityName]); + } + // Properly initialize any unfetched associations, if partial objects are not allowed. if ( ! isset($hints[Query::HINT_FORCE_PARTIAL_LOAD])) { foreach ($class->associationMappings as $field => $assoc) { From 4a0227e5f249a591429c4b5c6adb6f35ed33cae2 Mon Sep 17 00:00:00 2001 From: Benjamin Eberlei Date: Fri, 28 Oct 2011 00:24:41 +0200 Subject: [PATCH 041/135] Revert Expr\Base patch --- lib/Doctrine/ORM/Query/Expr/Base.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/Doctrine/ORM/Query/Expr/Base.php b/lib/Doctrine/ORM/Query/Expr/Base.php index beac2bb9c..975d450cd 100644 --- a/lib/Doctrine/ORM/Query/Expr/Base.php +++ b/lib/Doctrine/ORM/Query/Expr/Base.php @@ -57,7 +57,7 @@ abstract class Base public function add($arg) { - if ( $arg !== null ) { + if ( $arg !== null || ($arg instanceof self && $arg->count() > 0) ) { // If we decide to keep Expr\Base instances, we can use this check if ( ! is_string($arg)) { $class = get_class($arg); From f34eb83a7c962f6fead4ba6d72eedf2979a23fe5 Mon Sep 17 00:00:00 2001 From: Benjamin Eberlei Date: Fri, 28 Oct 2011 00:50:10 +0200 Subject: [PATCH 042/135] DDC-1454 - Fix exists() for Joined table inheritance --- .../ORM/Persisters/BasicEntityPersister.php | 296 +++++++++--------- .../Persisters/JoinedSubclassPersister.php | 78 ++--- .../ORM/Functional/Ticket/DDC1454Test.php | 69 ++++ 3 files changed, 256 insertions(+), 187 deletions(-) create mode 100644 tests/Doctrine/Tests/ORM/Functional/Ticket/DDC1454Test.php diff --git a/lib/Doctrine/ORM/Persisters/BasicEntityPersister.php b/lib/Doctrine/ORM/Persisters/BasicEntityPersister.php index 0379ccf56..fab80ff29 100644 --- a/lib/Doctrine/ORM/Persisters/BasicEntityPersister.php +++ b/lib/Doctrine/ORM/Persisters/BasicEntityPersister.php @@ -92,7 +92,7 @@ class BasicEntityPersister /** * The database platform. - * + * * @var Doctrine\DBAL\Platforms\AbstractPlatform */ protected $_platform; @@ -110,12 +110,12 @@ class BasicEntityPersister * @var array */ protected $_queuedInserts = array(); - + /** * ResultSetMapping that is used for all queries. Is generated lazily once per request. - * + * * TODO: Evaluate Caching in combination with the other cached SQL snippets. - * + * * @var Query\ResultSetMapping */ protected $_rsm; @@ -123,7 +123,7 @@ class BasicEntityPersister /** * The map of column names to DBAL mapping types of all prepared columns used * when INSERTing or UPDATEing an entity. - * + * * @var array * @see _prepareInsertData($entity) * @see _prepareUpdateData($entity) @@ -133,7 +133,7 @@ class BasicEntityPersister /** * The INSERT SQL statement used for entities handled by this persister. * This SQL is only generated once per request, if at all. - * + * * @var string */ private $_insertSql; @@ -141,29 +141,29 @@ class BasicEntityPersister /** * The SELECT column list SQL fragment used for querying entities by this persister. * This SQL fragment is only generated once per request, if at all. - * + * * @var string */ protected $_selectColumnListSql; - + /** * The JOIN SQL fragement used to eagerly load all many-to-one and one-to-one * associations configured as FETCH_EAGER, aswell as all inverse one-to-one associations. - * + * * @var string */ protected $_selectJoinSql; /** * Counter for creating unique SQL table and column aliases. - * + * * @var integer */ protected $_sqlAliasCounter = 0; /** * Map from class names (FQCN) to the corresponding generated SQL table aliases. - * + * * @var array */ protected $_sqlTableAliases = array(); @@ -171,7 +171,7 @@ class BasicEntityPersister /** * Initializes a new BasicEntityPersister that uses the given EntityManager * and persists instances of the class described by the given ClassMetadata descriptor. - * + * * @param Doctrine\ORM\EntityManager $em * @param Doctrine\ORM\Mapping\ClassMetadata $class */ @@ -205,7 +205,7 @@ class BasicEntityPersister /** * Executes all queued entity insertions and returns any generated post-insert * identifiers that were created as a result of the insertions. - * + * * If no inserts are queued, invoking this method is a NOOP. * * @return array An array of any generated post-insert IDs. This will be an empty array @@ -229,7 +229,7 @@ class BasicEntityPersister if (isset($insertData[$tableName])) { $paramIndex = 1; - + foreach ($insertData[$tableName] as $column => $value) { $stmt->bindValue($paramIndex++, $value, $this->_columnTypes[$column]); } @@ -257,7 +257,7 @@ class BasicEntityPersister /** * Retrieves the default version value which was created - * by the preceding INSERT statement and assigns it back in to the + * by the preceding INSERT statement and assigns it back in to the * entities version field. * * @param object $entity @@ -271,7 +271,7 @@ class BasicEntityPersister /** * Fetch the current version value of a versioned entity. - * + * * @param Doctrine\ORM\Mapping\ClassMetadata $versionedClass * @param mixed $id * @return mixed @@ -280,9 +280,9 @@ class BasicEntityPersister { $versionField = $versionedClass->versionField; $identifier = $versionedClass->getIdentifierColumnNames(); - + $versionFieldColumnName = $versionedClass->getQuotedColumnName($versionField, $this->_platform); - + //FIXME: Order with composite keys might not be correct $sql = 'SELECT ' . $versionFieldColumnName . ' FROM ' . $versionedClass->getQuotedTableName($this->_platform) @@ -299,7 +299,7 @@ class BasicEntityPersister * The data to update is retrieved through {@link _prepareUpdateData}. * Subclasses that override this method are supposed to obtain the update data * in the same way, through {@link _prepareUpdateData}. - * + * * Subclasses are also supposed to take care of versioning when overriding this method, * if necessary. The {@link _updateTable} method can be used to apply the data retrieved * from {@_prepareUpdateData} on the target tables, thereby optionally applying versioning. @@ -310,7 +310,7 @@ class BasicEntityPersister { $updateData = $this->_prepareUpdateData($entity); $tableName = $this->_class->getTableName(); - + if (isset($updateData[$tableName]) && $updateData[$tableName]) { $this->_updateTable( $entity, $this->_class->getQuotedTableName($this->_platform), @@ -338,17 +338,17 @@ class BasicEntityPersister $set = $params = $types = array(); foreach ($updateData as $columnName => $value) { - $set[] = (isset($this->_class->fieldNames[$columnName])) + $set[] = (isset($this->_class->fieldNames[$columnName])) ? $this->_class->getQuotedColumnName($this->_class->fieldNames[$columnName], $this->_platform) . ' = ?' : $columnName . ' = ?'; - + $params[] = $value; $types[] = $this->_columnTypes[$columnName]; } $where = array(); $id = $this->_em->getUnitOfWork()->getEntityIdentifier($entity); - + foreach ($this->_class->identifier as $idField) { if (isset($this->_class->associationMappings[$idField])) { $targetMapping = $this->_em->getClassMetadata($this->_class->associationMappings[$idField]['targetEntity']); @@ -366,13 +366,13 @@ class BasicEntityPersister $versionField = $this->_class->versionField; $versionFieldType = $this->_class->fieldMappings[$versionField]['type']; $versionColumn = $this->_class->getQuotedColumnName($versionField, $this->_platform); - + if ($versionFieldType == Type::INTEGER) { $set[] = $versionColumn . ' = ' . $versionColumn . ' + 1'; } else if ($versionFieldType == Type::DATETIME) { $set[] = $versionColumn . ' = CURRENT_TIMESTAMP'; } - + $where[] = $versionColumn; $params[] = $this->_class->reflFields[$versionField]->getValue($entity); $types[] = $this->_class->fieldMappings[$versionField]['type']; @@ -401,18 +401,18 @@ class BasicEntityPersister // @Todo this only covers scenarios with no inheritance or of the same level. Is there something // like self-referential relationship between different levels of an inheritance hierachy? I hope not! $selfReferential = ($mapping['targetEntity'] == $mapping['sourceEntity']); - + if ( ! $mapping['isOwningSide']) { $relatedClass = $this->_em->getClassMetadata($mapping['targetEntity']); $mapping = $relatedClass->associationMappings[$mapping['mappedBy']]; $keys = array_keys($mapping['relationToTargetKeyColumns']); - + if ($selfReferential) { $otherKeys = array_keys($mapping['relationToSourceKeyColumns']); } } else { $keys = array_keys($mapping['relationToSourceKeyColumns']); - + if ($selfReferential) { $otherKeys = array_keys($mapping['relationToTargetKeyColumns']); } @@ -420,13 +420,13 @@ class BasicEntityPersister if ( ! isset($mapping['isOnDeleteCascade'])) { $this->_conn->delete( - $this->_class->getQuotedJoinTableName($mapping, $this->_platform), + $this->_class->getQuotedJoinTableName($mapping, $this->_platform), array_combine($keys, $identifier) ); if ($selfReferential) { $this->_conn->delete( - $this->_class->getQuotedJoinTableName($mapping, $this->_platform), + $this->_class->getQuotedJoinTableName($mapping, $this->_platform), array_combine($otherKeys, $identifier) ); } @@ -458,7 +458,7 @@ class BasicEntityPersister * Prepares the changeset of an entity for database insertion (UPDATE). * * The changeset is obtained from the currently running UnitOfWork. - * + * * During this preparation the array that is passed as the second parameter is filled with * => pairs, grouped by table name. * @@ -493,7 +493,7 @@ class BasicEntityPersister if (isset($this->_class->associationMappings[$field])) { $assoc = $this->_class->associationMappings[$field]; - + // Only owning side of x-1 associations can have a FK column. if ( ! $assoc['isOwningSide'] || ! ($assoc['type'] & ClassMetadata::TO_ONE)) { continue; @@ -501,7 +501,7 @@ class BasicEntityPersister if ($newVal !== null) { $oid = spl_object_hash($newVal); - + if (isset($this->_queuedInserts[$oid]) || $uow->isScheduledForInsert($newVal)) { // The associated entity $newVal is not yet persisted, so we must // set $newVal = null, in order to insert a null value and schedule an @@ -528,7 +528,7 @@ class BasicEntityPersister } else { $result[$owningTable][$sourceColumn] = $newValId[$targetClass->fieldNames[$targetColumn]]; } - + $this->_columnTypes[$sourceColumn] = $targetClass->getTypeOfColumn($targetColumn); } } else { @@ -537,7 +537,7 @@ class BasicEntityPersister $result[$this->getOwningTable($field)][$columnName] = $newVal; } } - + return $result; } @@ -589,7 +589,7 @@ class BasicEntityPersister $sql = $this->_getSelectEntitiesSQL($criteria, $assoc, $lockMode, $limit); list($params, $types) = $this->expandParameters($criteria); $stmt = $this->_conn->executeQuery($sql, $params, $types); - + if ($entity !== null) { $hints[Query::HINT_REFRESH] = true; $hints[Query::HINT_REFRESH_ENTITY] = $entity; @@ -597,7 +597,7 @@ class BasicEntityPersister $hydrator = $this->_em->newHydrator($this->_selectJoinSql ? Query::HYDRATE_OBJECT : Query::HYDRATE_SIMPLEOBJECT); $entities = $hydrator->hydrateAll($stmt, $this->_rsm, $hints); - + return $entities ? $entities[0] : null; } @@ -626,17 +626,17 @@ class BasicEntityPersister // Mark inverse side as fetched in the hints, otherwise the UoW would // try to load it in a separate query (remember: to-one inverse sides can not be lazy). $hints = array(); - + if ($isInverseSingleValued) { $hints['fetched'][$targetClass->name][$assoc['inversedBy']] = true; - + if ($targetClass->subClasses) { foreach ($targetClass->subClasses as $targetSubclassName) { $hints['fetched'][$targetSubclassName][$assoc['inversedBy']] = true; } } } - + /* cascade read-only status if ($this->_em->getUnitOfWork()->isReadOnly($sourceEntity)) { $hints[Query::HINT_READ_ONLY] = true; @@ -652,7 +652,7 @@ class BasicEntityPersister } else { $sourceClass = $this->_em->getClassMetadata($assoc['sourceEntity']); $owningAssoc = $targetClass->getAssociationMapping($assoc['mappedBy']); - + // TRICKY: since the association is specular source and target are flipped foreach ($owningAssoc['targetToSourceKeyColumns'] as $sourceKeyColumn => $targetKeyColumn) { if ( ! isset($sourceClass->fieldNames[$sourceKeyColumn])) { @@ -660,12 +660,12 @@ class BasicEntityPersister $sourceClass->name, $sourceKeyColumn ); } - + // unset the old value and set the new sql aliased value here. By definition // unset($identifier[$targetKeyColumn] works here with how UnitOfWork::createEntity() calls this method. $identifier[$this->_getSQLTableAlias($targetClass->name) . "." . $targetKeyColumn] = $sourceClass->reflFields[$sourceClass->fieldNames[$sourceKeyColumn]]->getValue($sourceEntity); - + unset($identifier[$targetKeyColumn]); } @@ -681,7 +681,7 @@ class BasicEntityPersister /** * Refreshes a managed entity. - * + * * @param array $id The identifier of the entity as an associative array from * column or field names to values. * @param object $entity The entity to refresh. @@ -691,16 +691,16 @@ class BasicEntityPersister $sql = $this->_getSelectEntitiesSQL($id); list($params, $types) = $this->expandParameters($id); $stmt = $this->_conn->executeQuery($sql, $params, $types); - + $hydrator = $this->_em->newHydrator(Query::HYDRATE_OBJECT); $hydrator->hydrateAll($stmt, $this->_rsm, array(Query::HINT_REFRESH => true)); if (isset($this->_class->lifecycleCallbacks[Events::postLoad])) { $this->_class->invokeLifecycleCallbacks(Events::postLoad, $entity); } - + $evm = $this->_em->getEventManager(); - + if ($evm->hasListeners(Events::postLoad)) { $evm->dispatchEvent(Events::postLoad, new LifecycleEventArgs($entity, $this->_em)); } @@ -708,7 +708,7 @@ class BasicEntityPersister /** * Loads a list of entities by a list of field criteria. - * + * * @param array $criteria * @param array $orderBy * @param int $limit @@ -723,13 +723,13 @@ class BasicEntityPersister $stmt = $this->_conn->executeQuery($sql, $params, $types); $hydrator = $this->_em->newHydrator(($this->_selectJoinSql) ? Query::HYDRATE_OBJECT : Query::HYDRATE_SIMPLEOBJECT); - + return $hydrator->hydrateAll($stmt, $this->_rsm, array('deferEagerLoads' => true)); } /** * Get (sliced or full) elements of the given collection. - * + * * @param array $assoc * @param object $sourceEntity * @param int|null $offset @@ -739,16 +739,16 @@ class BasicEntityPersister public function getManyToManyCollection(array $assoc, $sourceEntity, $offset = null, $limit = null) { $stmt = $this->getManyToManyStatement($assoc, $sourceEntity, $offset, $limit); - + return $this->loadArrayFromStatement($assoc, $stmt); } /** * Load an array of entities from a given dbal statement. - * + * * @param array $assoc * @param Doctrine\DBAL\Statement $stmt - * + * * @return array */ private function loadArrayFromStatement($assoc, $stmt) @@ -763,21 +763,21 @@ class BasicEntityPersister } $hydrator = $this->_em->newHydrator(Query::HYDRATE_OBJECT); - + return $hydrator->hydrateAll($stmt, $rsm, $hints); } /** * Hydrate a collection from a given dbal statement. - * + * * @param array $assoc * @param Doctrine\DBAL\Statement $stmt * @param PersistentCollection $coll - * + * * @return array */ private function loadCollectionFromStatement($assoc, $stmt, $coll) - { + { $hints = array('deferEagerLoads' => true, 'collection' => $coll); if (isset($assoc['indexBy'])) { @@ -788,7 +788,7 @@ class BasicEntityPersister } $hydrator = $this->_em->newHydrator(Query::HYDRATE_OBJECT); - + return $hydrator->hydrateAll($stmt, $rsm, $hints); } @@ -805,7 +805,7 @@ class BasicEntityPersister public function loadManyToManyCollection(array $assoc, $sourceEntity, PersistentCollection $coll) { $stmt = $this->getManyToManyStatement($assoc, $sourceEntity); - + return $this->loadCollectionFromStatement($assoc, $stmt, $coll); } @@ -813,15 +813,15 @@ class BasicEntityPersister { $criteria = array(); $sourceClass = $this->_em->getClassMetadata($assoc['sourceEntity']); - + if ($assoc['isOwningSide']) { $quotedJoinTable = $sourceClass->getQuotedJoinTableName($assoc, $this->_platform); - + foreach ($assoc['relationToSourceKeyColumns'] as $relationKeyColumn => $sourceKeyColumn) { if ($sourceClass->containsForeignIdentifier) { $field = $sourceClass->getFieldForColumn($sourceKeyColumn); $value = $sourceClass->reflFields[$field]->getValue($sourceEntity); - + if (isset($sourceClass->associationMappings[$field])) { $value = $this->_em->getUnitOfWork()->getEntityIdentifier($value); $value = $value[$this->_em->getClassMetadata($sourceClass->associationMappings[$field]['targetEntity'])->identifier[0]]; @@ -839,18 +839,18 @@ class BasicEntityPersister } else { $owningAssoc = $this->_em->getClassMetadata($assoc['targetEntity'])->associationMappings[$assoc['mappedBy']]; $quotedJoinTable = $sourceClass->getQuotedJoinTableName($owningAssoc, $this->_platform); - + // TRICKY: since the association is inverted source and target are flipped foreach ($owningAssoc['relationToTargetKeyColumns'] as $relationKeyColumn => $sourceKeyColumn) { if ($sourceClass->containsForeignIdentifier) { $field = $sourceClass->getFieldForColumn($sourceKeyColumn); $value = $sourceClass->reflFields[$field]->getValue($sourceEntity); - + if (isset($sourceClass->associationMappings[$field])) { $value = $this->_em->getUnitOfWork()->getEntityIdentifier($value); $value = $value[$this->_em->getClassMetadata($sourceClass->associationMappings[$field]['targetEntity'])->identifier[0]]; } - + $criteria[$quotedJoinTable . "." . $relationKeyColumn] = $value; } else if (isset($sourceClass->fieldNames[$sourceKeyColumn])) { $criteria[$quotedJoinTable . "." . $relationKeyColumn] = $sourceClass->reflFields[$sourceClass->fieldNames[$sourceKeyColumn]]->getValue($sourceEntity); @@ -864,7 +864,7 @@ class BasicEntityPersister $sql = $this->_getSelectEntitiesSQL($criteria, $assoc, 0, $limit, $offset); list($params, $types) = $this->expandParameters($criteria); - + return $this->_conn->executeQuery($sql, $params, $types); } @@ -890,7 +890,7 @@ class BasicEntityPersister $orderBySql = $orderBy ? $this->_getOrderBySQL($orderBy, $this->_getSQLTableAlias($this->_class->name)) : ''; $lockSql = ''; - + if ($lockMode == LockMode::PESSIMISTIC_READ) { $lockSql = ' ' . $this->_platform->getReadLockSql(); } else if ($lockMode == LockMode::PESSIMISTIC_WRITE) { @@ -908,7 +908,7 @@ class BasicEntityPersister /** * Gets the ORDER BY SQL snippet for ordered collections. - * + * * @param array $orderBy * @param string $baseTableAlias * @return string @@ -917,7 +917,7 @@ class BasicEntityPersister protected final function _getOrderBySQL(array $orderBy, $baseTableAlias) { $orderBySql = ''; - + foreach ($orderBy as $fieldName => $orientation) { if ( ! isset($this->_class->fieldMappings[$fieldName])) { throw ORMException::unrecognizedField($fieldName); @@ -928,7 +928,7 @@ class BasicEntityPersister : $baseTableAlias; $columnName = $this->_class->getQuotedColumnName($fieldName, $this->_platform); - + $orderBySql .= $orderBySql ? ', ' : ' ORDER BY '; $orderBySql .= $tableAlias . '.' . $columnName . ' ' . $orientation; } @@ -944,7 +944,7 @@ class BasicEntityPersister * list SQL fragment. Note that in the implementation of BasicEntityPersister * the resulting SQL fragment is generated only once and cached in {@link _selectColumnListSql}. * Subclasses may or may not do the same. - * + * * @return string The SQL fragment. * @todo Rename: _getSelectColumnsSQL() */ @@ -961,75 +961,75 @@ class BasicEntityPersister // Add regular columns to select list foreach ($this->_class->fieldNames as $field) { if ($columnList) $columnList .= ', '; - + $columnList .= $this->_getSelectColumnSQL($field, $this->_class); } $this->_selectJoinSql = ''; $eagerAliasCounter = 0; - + foreach ($this->_class->associationMappings as $assocField => $assoc) { $assocColumnSQL = $this->_getSelectColumnAssociationSQL($assocField, $assoc, $this->_class); - + if ($assocColumnSQL) { if ($columnList) $columnList .= ', '; - + $columnList .= $assocColumnSQL; } - + if ($assoc['type'] & ClassMetadata::TO_ONE && ($assoc['fetch'] == ClassMetadata::FETCH_EAGER || !$assoc['isOwningSide'])) { $eagerEntity = $this->_em->getClassMetadata($assoc['targetEntity']); - + if ($eagerEntity->inheritanceType != ClassMetadata::INHERITANCE_TYPE_NONE) { continue; // now this is why you shouldn't use inheritance } - + $assocAlias = 'e' . ($eagerAliasCounter++); $this->_rsm->addJoinedEntityResult($assoc['targetEntity'], $assocAlias, 'r', $assocField); - + foreach ($eagerEntity->fieldNames AS $field) { if ($columnList) $columnList .= ', '; - + $columnList .= $this->_getSelectColumnSQL($field, $eagerEntity, $assocAlias); } - + foreach ($eagerEntity->associationMappings as $assoc2Field => $assoc2) { $assoc2ColumnSQL = $this->_getSelectColumnAssociationSQL($assoc2Field, $assoc2, $eagerEntity, $assocAlias); - + if ($assoc2ColumnSQL) { if ($columnList) $columnList .= ', '; $columnList .= $assoc2ColumnSQL; } } - + $this->_selectJoinSql .= ' LEFT JOIN'; // TODO: Inner join when all join columns are NOT nullable. $first = true; - + if ($assoc['isOwningSide']) { $this->_selectJoinSql .= ' ' . $eagerEntity->getQuotedTableName($this->_platform) . ' ' . $this->_getSQLTableAlias($eagerEntity->name, $assocAlias) .' ON '; - + foreach ($assoc['sourceToTargetKeyColumns'] AS $sourceCol => $targetCol) { if ( ! $first) { $this->_selectJoinSql .= ' AND '; } - - $this->_selectJoinSql .= $this->_getSQLTableAlias($assoc['sourceEntity']) . '.' . $sourceCol . ' = ' + + $this->_selectJoinSql .= $this->_getSQLTableAlias($assoc['sourceEntity']) . '.' . $sourceCol . ' = ' . $this->_getSQLTableAlias($assoc['targetEntity'], $assocAlias) . '.' . $targetCol . ' '; $first = false; } } else { $eagerEntity = $this->_em->getClassMetadata($assoc['targetEntity']); $owningAssoc = $eagerEntity->getAssociationMapping($assoc['mappedBy']); - - $this->_selectJoinSql .= ' ' . $eagerEntity->getQuotedTableName($this->_platform) . ' ' + + $this->_selectJoinSql .= ' ' . $eagerEntity->getQuotedTableName($this->_platform) . ' ' . $this->_getSQLTableAlias($eagerEntity->name, $assocAlias) . ' ON '; foreach ($owningAssoc['sourceToTargetKeyColumns'] AS $sourceCol => $targetCol) { if ( ! $first) { $this->_selectJoinSql .= ' AND '; } - - $this->_selectJoinSql .= $this->_getSQLTableAlias($owningAssoc['sourceEntity'], $assocAlias) . '.' . $sourceCol . ' = ' + + $this->_selectJoinSql .= $this->_getSQLTableAlias($owningAssoc['sourceEntity'], $assocAlias) . '.' . $sourceCol . ' = ' . $this->_getSQLTableAlias($owningAssoc['targetEntity']) . '.' . $targetCol . ' '; $first = false; } @@ -1041,33 +1041,33 @@ class BasicEntityPersister return $this->_selectColumnListSql; } - + /** * Gets the SQL join fragment used when selecting entities from an association. - * + * * @param string $field * @param array $assoc * @param ClassMetadata $class * @param string $alias - * - * @return string + * + * @return string */ protected function _getSelectColumnAssociationSQL($field, $assoc, ClassMetadata $class, $alias = 'r') { $columnList = ''; - + if ($assoc['isOwningSide'] && $assoc['type'] & ClassMetadata::TO_ONE) { foreach ($assoc['targetToSourceKeyColumns'] as $srcColumn) { if ($columnList) $columnList .= ', '; $columnAlias = $srcColumn . $this->_sqlAliasCounter++; $resultColumnName = $this->_platform->getSQLResultCasing($columnAlias); - $columnList .= $this->_getSQLTableAlias($class->name, ($alias == 'r' ? '' : $alias) ) + $columnList .= $this->_getSQLTableAlias($class->name, ($alias == 'r' ? '' : $alias) ) . '.' . $srcColumn . ' AS ' . $resultColumnName; $this->_rsm->addMetaResult($alias, $resultColumnName, $srcColumn, isset($assoc['id']) && $assoc['id'] === true); } } - + return $columnList; } @@ -1087,10 +1087,10 @@ class BasicEntityPersister $owningAssoc = $this->_em->getClassMetadata($manyToMany['targetEntity'])->associationMappings[$manyToMany['mappedBy']]; $joinClauses = $owningAssoc['relationToSourceKeyColumns']; } - + $joinTableName = $this->_class->getQuotedJoinTableName($owningAssoc, $this->_platform); $joinSql = ''; - + foreach ($joinClauses as $joinTableColumn => $sourceColumn) { if ($joinSql != '') $joinSql .= ' AND '; @@ -1109,7 +1109,7 @@ class BasicEntityPersister /** * Gets the INSERT SQL used by the persister to persist a new entity. - * + * * @return string */ protected function _getInsertSQL() @@ -1117,7 +1117,7 @@ class BasicEntityPersister if ($this->_insertSql === null) { $insertSql = ''; $columns = $this->_getInsertColumnList(); - + if (empty($columns)) { $insertSql = $this->_platform->getEmptyIdentityInsertSQL( $this->_class->getQuotedTableName($this->_platform), @@ -1130,10 +1130,10 @@ class BasicEntityPersister $insertSql = 'INSERT INTO ' . $this->_class->getQuotedTableName($this->_platform) . ' (' . implode(', ', $columns) . ') VALUES (' . implode(', ', $values) . ')'; } - + $this->_insertSql = $insertSql; } - + return $this->_insertSql; } @@ -1148,15 +1148,15 @@ class BasicEntityPersister protected function _getInsertColumnList() { $columns = array(); - + foreach ($this->_class->reflFields as $name => $field) { if ($this->_class->isVersioned && $this->_class->versionField == $name) { continue; } - + if (isset($this->_class->associationMappings[$name])) { $assoc = $this->_class->associationMappings[$name]; - + if ($assoc['isOwningSide'] && $assoc['type'] & ClassMetadata::TO_ONE) { foreach ($assoc['targetToSourceKeyColumns'] as $sourceCol) { $columns[] = $sourceCol; @@ -1181,10 +1181,10 @@ class BasicEntityPersister protected function _getSelectColumnSQL($field, ClassMetadata $class, $alias = 'r') { $columnName = $class->columnNames[$field]; - $sql = $this->_getSQLTableAlias($class->name, $alias == 'r' ? '' : $alias) + $sql = $this->_getSQLTableAlias($class->name, $alias == 'r' ? '' : $alias) . '.' . $class->getQuotedColumnName($field, $this->_platform); $columnAlias = $this->_platform->getSQLResultCasing($columnName . $this->_sqlAliasCounter++); - + $this->_rsm->addFieldResult($alias, $columnAlias, $field); return $sql . ' AS ' . $columnAlias; @@ -1192,7 +1192,7 @@ class BasicEntityPersister /** * Gets the SQL table alias for the given class name. - * + * * @param string $className * @return string The SQL table alias. * @todo Reconsider. Binding table aliases to class names is not such a good idea. @@ -1202,15 +1202,15 @@ class BasicEntityPersister if ($assocName) { $className .= '#' . $assocName; } - + if (isset($this->_sqlTableAliases[$className])) { return $this->_sqlTableAliases[$className]; } - + $tableAlias = 't' . $this->_sqlAliasCounter++; $this->_sqlTableAliases[$className] = $tableAlias; - + return $tableAlias; } @@ -1234,9 +1234,9 @@ class BasicEntityPersister $sql = 'SELECT 1 ' . $this->_platform->appendLockHint($this->getLockTablesSql(), $lockMode) . ($conditionSql ? ' WHERE ' . $conditionSql : '') . ' ' . $lockSql; - + list($params, $types) = $this->expandParameters($criteria); - + $stmt = $this->_conn->executeQuery($sql, $params, $types); } @@ -1265,25 +1265,25 @@ class BasicEntityPersister protected function _getSelectConditionSQL(array $criteria, $assoc = null) { $conditionSql = ''; - + foreach ($criteria as $field => $value) { $conditionSql .= $conditionSql ? ' AND ' : ''; if (isset($this->_class->columnNames[$field])) { $className = (isset($this->_class->fieldMappings[$field]['inherited'])) - ? $this->_class->fieldMappings[$field]['inherited'] + ? $this->_class->fieldMappings[$field]['inherited'] : $this->_class->name; - + $conditionSql .= $this->_getSQLTableAlias($className) . '.' . $this->_class->getQuotedColumnName($field, $this->_platform); } else if (isset($this->_class->associationMappings[$field])) { if ( ! $this->_class->associationMappings[$field]['isOwningSide']) { throw ORMException::invalidFindByInverseAssociation($this->_class->name, $field); } - + $className = (isset($this->_class->associationMappings[$field]['inherited'])) ? $this->_class->associationMappings[$field]['inherited'] : $this->_class->name; - + $conditionSql .= $this->_getSQLTableAlias($className) . '.' . $this->_class->associationMappings[$field]['joinColumns'][0]['name']; } else if ($assoc !== null && strpos($field, " ") === false && strpos($field, "(") === false) { // very careless developers could potentially open up this normally hidden api for userland attacks, @@ -1294,7 +1294,7 @@ class BasicEntityPersister } else { throw ORMException::unrecognizedField($field); } - + $conditionSql .= (is_array($value)) ? ' IN (?)' : (($value === null) ? ' IS NULL' : ' = ?'); } return $conditionSql; @@ -1312,7 +1312,7 @@ class BasicEntityPersister public function getOneToManyCollection(array $assoc, $sourceEntity, $offset = null, $limit = null) { $stmt = $this->getOneToManyStatement($assoc, $sourceEntity, $offset, $limit); - + return $this->loadArrayFromStatement($assoc, $stmt); } @@ -1328,7 +1328,7 @@ class BasicEntityPersister public function loadOneToManyCollection(array $assoc, $sourceEntity, PersistentCollection $coll) { $stmt = $this->getOneToManyStatement($assoc, $sourceEntity); - + return $this->loadCollectionFromStatement($assoc, $stmt, $coll); } @@ -1353,12 +1353,12 @@ class BasicEntityPersister if ($sourceClass->containsForeignIdentifier) { $field = $sourceClass->getFieldForColumn($sourceKeyColumn); $value = $sourceClass->reflFields[$field]->getValue($sourceEntity); - + if (isset($sourceClass->associationMappings[$field])) { $value = $this->_em->getUnitOfWork()->getEntityIdentifier($value); $value = $value[$this->_em->getClassMetadata($sourceClass->associationMappings[$field]['targetEntity'])->identifier[0]]; } - + $criteria[$tableAlias . "." . $targetKeyColumn] = $value; } else { $criteria[$tableAlias . "." . $targetKeyColumn] = $sourceClass->reflFields[$sourceClass->fieldNames[$sourceKeyColumn]]->getValue($sourceEntity); @@ -1389,13 +1389,13 @@ class BasicEntityPersister $types[] = $this->getType($field, $value); $params[] = $this->getValue($value); } - + return array($params, $types); } - + /** * Infer field type to be used by parameter type casting. - * + * * @param string $field * @param mixed $value * @return integer @@ -1409,11 +1409,11 @@ class BasicEntityPersister case (isset($this->_class->associationMappings[$field])): $assoc = $this->_class->associationMappings[$field]; - + if (count($assoc['sourceToTargetKeyColumns']) > 1) { throw Query\QueryException::associationPathCompositeKeyNotSupported(); } - + $targetClass = $this->_em->getClassMetadata($assoc['targetEntity']); $targetColumn = $assoc['joinColumns'][0]['referencedColumnName']; $type = null; @@ -1431,36 +1431,36 @@ class BasicEntityPersister if (is_array($value)) { $type += Connection::ARRAY_PARAM_OFFSET; } - + return $type; } - + /** * Retrieve parameter value - * + * * @param mixed $value - * @return mixed + * @return mixed */ private function getValue($value) { if (is_array($value)) { $newValue = array(); - + foreach ($value as $itemValue) { $newValue[] = $this->getIndividualValue($itemValue); } - + return $newValue; } - + return $this->getIndividualValue($value); } - + /** * Retrieve an invidiual parameter value - * + * * @param mixed $value - * @return mixed + * @return mixed */ private function getIndividualValue($value) { @@ -1471,11 +1471,11 @@ class BasicEntityPersister $class = $this->_em->getClassMetadata(get_class($value)); $idValues = $class->getIdentifierValues($value); } - + $value = $idValues[key($idValues)]; } - - return $value; + + return $value; } /** @@ -1487,17 +1487,17 @@ class BasicEntityPersister public function exists($entity, array $extraConditions = array()) { $criteria = $this->_class->getIdentifierValues($entity); - + if ($extraConditions) { $criteria = array_merge($criteria, $extraConditions); } - $sql = 'SELECT 1' - . ' FROM ' . $this->_class->getQuotedTableName($this->_platform) . ' ' . $this->_getSQLTableAlias($this->_class->name) + $sql = 'SELECT 1 ' + . $this->getLockTablesSql() . ' WHERE ' . $this->_getSelectConditionSQL($criteria); - + list($params, $types) = $this->expandParameters($criteria); - + return (bool) $this->_conn->fetchColumn($sql, $params); } } diff --git a/lib/Doctrine/ORM/Persisters/JoinedSubclassPersister.php b/lib/Doctrine/ORM/Persisters/JoinedSubclassPersister.php index c95fee755..fb60d5e32 100644 --- a/lib/Doctrine/ORM/Persisters/JoinedSubclassPersister.php +++ b/lib/Doctrine/ORM/Persisters/JoinedSubclassPersister.php @@ -46,7 +46,7 @@ class JoinedSubclassPersister extends AbstractEntityInheritancePersister /** * Map of table to quoted table names. - * + * * @var array */ private $_quotedTableMap = array(); @@ -59,7 +59,7 @@ class JoinedSubclassPersister extends AbstractEntityInheritancePersister $class = ($this->_class->name !== $this->_class->rootEntityName) ? $this->_em->getClassMetadata($this->_class->rootEntityName) : $this->_class; - + return $class->getTableName(); } @@ -73,10 +73,10 @@ class JoinedSubclassPersister extends AbstractEntityInheritancePersister { if (isset($this->_class->fieldMappings[$this->_class->versionField]['inherited'])) { $definingClassName = $this->_class->fieldMappings[$this->_class->versionField]['inherited']; - + return $this->_em->getClassMetadata($definingClassName); } - + return $this->_class; } @@ -92,7 +92,7 @@ class JoinedSubclassPersister extends AbstractEntityInheritancePersister if (isset($this->_owningTableMap[$fieldName])) { return $this->_owningTableMap[$fieldName]; } - + if (isset($this->_class->associationMappings[$fieldName]['inherited'])) { $cm = $this->_em->getClassMetadata($this->_class->associationMappings[$fieldName]['inherited']); } else if (isset($this->_class->fieldMappings[$fieldName]['inherited'])) { @@ -130,15 +130,15 @@ class JoinedSubclassPersister extends AbstractEntityInheritancePersister // Prepare statements for sub tables. $subTableStmts = array(); - + if ($rootClass !== $this->_class) { $subTableStmts[$this->_class->getTableName()] = $this->_conn->prepare($this->_getInsertSQL()); } - + foreach ($this->_class->parentClasses as $parentClassName) { $parentClass = $this->_em->getClassMetadata($parentClassName); $parentTableName = $parentClass->getTableName(); - + if ($parentClass !== $rootClass) { $parentPersister = $this->_em->getUnitOfWork()->getEntityPersister($parentClassName); $subTableStmts[$parentTableName] = $this->_conn->prepare($parentPersister->_getInsertSQL()); @@ -153,11 +153,11 @@ class JoinedSubclassPersister extends AbstractEntityInheritancePersister // Execute insert on root table $paramIndex = 1; - + foreach ($insertData[$rootTableName] as $columnName => $value) { $rootTableStmt->bindValue($paramIndex++, $value, $this->_columnTypes[$columnName]); } - + $rootTableStmt->execute(); if ($isPostInsertId) { @@ -172,23 +172,23 @@ class JoinedSubclassPersister extends AbstractEntityInheritancePersister foreach ($subTableStmts as $tableName => $stmt) { $data = isset($insertData[$tableName]) ? $insertData[$tableName] : array(); $paramIndex = 1; - + foreach ((array) $id as $idName => $idVal) { $type = isset($this->_columnTypes[$idName]) ? $this->_columnTypes[$idName] : Type::STRING; - + $stmt->bindValue($paramIndex++, $idVal, $type); } - + foreach ($data as $columnName => $value) { $stmt->bindValue($paramIndex++, $value, $this->_columnTypes[$columnName]); } - + $stmt->execute(); } } $rootTableStmt->closeCursor(); - + foreach ($subTableStmts as $stmt) { $stmt->closeCursor(); } @@ -220,7 +220,7 @@ class JoinedSubclassPersister extends AbstractEntityInheritancePersister $entity, $this->_quotedTableMap[$tableName], $data, $isVersioned && $versionedTable == $tableName ); } - + // Make sure the table with the version column is updated even if no columns on that // table were affected. if ($isVersioned && ! isset($updateData[$versionedTable])) { @@ -251,7 +251,7 @@ class JoinedSubclassPersister extends AbstractEntityInheritancePersister } else { // Delete from all tables individually, starting from this class' table up to the root table. $this->_conn->delete($this->_class->getQuotedTableName($this->_platform), $id); - + foreach ($this->_class->parentClasses as $parentClass) { $this->_conn->delete( $this->_em->getClassMetadata($parentClass)->getQuotedTableName($this->_platform), $id @@ -270,16 +270,16 @@ class JoinedSubclassPersister extends AbstractEntityInheritancePersister // Create the column list fragment only once if ($this->_selectColumnListSql === null) { - + $this->_rsm = new ResultSetMapping(); $this->_rsm->addEntityResult($this->_class->name, 'r'); - + // Add regular columns $columnList = ''; - + foreach ($this->_class->fieldMappings as $fieldName => $mapping) { if ($columnList != '') $columnList .= ', '; - + $columnList .= $this->_getSelectColumnSQL( $fieldName, isset($mapping['inherited']) ? $this->_em->getClassMetadata($mapping['inherited']) : $this->_class @@ -290,12 +290,12 @@ class JoinedSubclassPersister extends AbstractEntityInheritancePersister foreach ($this->_class->associationMappings as $assoc2) { if ($assoc2['isOwningSide'] && $assoc2['type'] & ClassMetadata::TO_ONE) { $tableAlias = isset($assoc2['inherited']) ? $this->_getSQLTableAlias($assoc2['inherited']) : $baseTableAlias; - + foreach ($assoc2['targetToSourceKeyColumns'] as $srcColumn) { if ($columnList != '') $columnList .= ', '; - + $columnList .= $this->getSelectJoinColumnSQL( - $tableAlias, + $tableAlias, $srcColumn, isset($assoc2['inherited']) ? $assoc2['inherited'] : $this->_class->name ); @@ -309,23 +309,23 @@ class JoinedSubclassPersister extends AbstractEntityInheritancePersister $columnList .= ', ' . $tableAlias . '.' . $discrColumn; $resultColumnName = $this->_platform->getSQLResultCasing($discrColumn); - + $this->_rsm->setDiscriminatorColumn('r', $resultColumnName); $this->_rsm->addMetaResult('r', $resultColumnName, $discrColumn); } // INNER JOIN parent tables $joinSql = ''; - + foreach ($this->_class->parentClasses as $parentClassName) { $parentClass = $this->_em->getClassMetadata($parentClassName); $tableAlias = $this->_getSQLTableAlias($parentClassName); $joinSql .= ' INNER JOIN ' . $parentClass->getQuotedTableName($this->_platform) . ' ' . $tableAlias . ' ON '; $first = true; - + foreach ($idColumns as $idColumn) { if ($first) $first = false; else $joinSql .= ' AND '; - + $joinSql .= $baseTableAlias . '.' . $idColumn . ' = ' . $tableAlias . '.' . $idColumn; } } @@ -339,7 +339,7 @@ class JoinedSubclassPersister extends AbstractEntityInheritancePersister // Add subclass columns foreach ($subClass->fieldMappings as $fieldName => $mapping) { if (isset($mapping['inherited'])) continue; - + $columnList .= ', ' . $this->_getSelectColumnSQL($fieldName, $subClass); } @@ -348,9 +348,9 @@ class JoinedSubclassPersister extends AbstractEntityInheritancePersister if ($assoc2['isOwningSide'] && $assoc2['type'] & ClassMetadata::TO_ONE && ! isset($assoc2['inherited'])) { foreach ($assoc2['targetToSourceKeyColumns'] as $srcColumn) { if ($columnList != '') $columnList .= ', '; - + $columnList .= $this->getSelectJoinColumnSQL( - $tableAlias, + $tableAlias, $srcColumn, isset($assoc2['inherited']) ? $assoc2['inherited'] : $subClass->name ); @@ -362,10 +362,10 @@ class JoinedSubclassPersister extends AbstractEntityInheritancePersister // Add LEFT JOIN $joinSql .= ' LEFT JOIN ' . $subClass->getQuotedTableName($this->_platform) . ' ' . $tableAlias . ' ON '; $first = true; - + foreach ($idColumns as $idColumn) { if ($first) $first = false; else $joinSql .= ' AND '; - + $joinSql .= $baseTableAlias . '.' . $idColumn . ' = ' . $tableAlias . '.' . $idColumn; } } @@ -382,7 +382,7 @@ class JoinedSubclassPersister extends AbstractEntityInheritancePersister } $lockSql = ''; - + if ($lockMode == LockMode::PESSIMISTIC_READ) { $lockSql = ' ' . $this->_platform->getReadLockSql(); } else if ($lockMode == LockMode::PESSIMISTIC_WRITE) { @@ -408,29 +408,29 @@ class JoinedSubclassPersister extends AbstractEntityInheritancePersister // INNER JOIN parent tables $joinSql = ''; - + foreach ($this->_class->parentClasses as $parentClassName) { $parentClass = $this->_em->getClassMetadata($parentClassName); $tableAlias = $this->_getSQLTableAlias($parentClassName); $joinSql .= ' INNER JOIN ' . $parentClass->getQuotedTableName($this->_platform) . ' ' . $tableAlias . ' ON '; $first = true; - + foreach ($idColumns as $idColumn) { if ($first) $first = false; else $joinSql .= ' AND '; - + $joinSql .= $baseTableAlias . '.' . $idColumn . ' = ' . $tableAlias . '.' . $idColumn; } } return 'FROM ' . $this->_class->getQuotedTableName($this->_platform) . ' ' . $baseTableAlias . $joinSql; } - + /* Ensure this method is never called. This persister overrides _getSelectEntitiesSQL directly. */ protected function _getSelectColumnListSQL() { throw new \BadMethodCallException("Illegal invocation of ".__METHOD__."."); } - + /** {@inheritdoc} */ protected function _getInsertColumnList() { diff --git a/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC1454Test.php b/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC1454Test.php new file mode 100644 index 000000000..eaf9dd3f9 --- /dev/null +++ b/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC1454Test.php @@ -0,0 +1,69 @@ +_schemaTool->createSchema(array( + $this->_em->getClassMetadata(__NAMESPACE__ . '\DDC1454File'), + $this->_em->getClassMetadata(__NAMESPACE__ . '\DDC1454Picture'), + )); + } catch (\Exception $ignored) { + + } + } + + public function testFailingCase() + { + $pic = new DDC1454Picture(); + $this->_em->getUnitOfWork()->getEntityState($pic); + } + +} + +/** + * @Entity + */ +class DDC1454Picture extends DDC1454File +{ + +} + +/** + * @Entity + * @InheritanceType("JOINED") + * @DiscriminatorColumn(name="discr", type="string") + * @DiscriminatorMap({"picture" = "DDC1454Picture"}) + */ +class DDC1454File +{ + /** + * @Column(name="file_id", type="integer") + * @Id + */ + public $fileId; + + public function __construct() { + $this->fileId = rand(); + } + + /** + * Get fileId + */ + public function getFileId() { + return $this->fileId; + } + +} \ No newline at end of file From 1579c43433788fc9ab0ff3d5a3f54a530e371a51 Mon Sep 17 00:00:00 2001 From: Guilherme Blanco Date: Fri, 28 Oct 2011 12:49:01 -0200 Subject: [PATCH 043/135] Code beautification and docblocks enhancements. --- .../ORM/Event/EntityEventDelegator.php | 38 ++++++--- lib/Doctrine/ORM/Event/LifecycleEventArgs.php | 39 ++++++--- .../ORM/Event/LoadClassMetadataEventArgs.php | 40 +++++++-- lib/Doctrine/ORM/Event/OnClearEventArgs.php | 20 +++-- lib/Doctrine/ORM/Event/OnFlushEventArgs.php | 28 +++--- lib/Doctrine/ORM/Event/PostFlushEventArgs.php | 16 ++-- lib/Doctrine/ORM/Event/PreUpdateEventArgs.php | 85 +++++++++++++------ 7 files changed, 183 insertions(+), 83 deletions(-) diff --git a/lib/Doctrine/ORM/Event/EntityEventDelegator.php b/lib/Doctrine/ORM/Event/EntityEventDelegator.php index d7c46e68e..09532bc4b 100644 --- a/lib/Doctrine/ORM/Event/EntityEventDelegator.php +++ b/lib/Doctrine/ORM/Event/EntityEventDelegator.php @@ -19,14 +19,16 @@ namespace Doctrine\ORM\Event; -use \Doctrine\Common\EventSubscriber; -use \LogicException; +use Doctrine\Common\EventSubscriber; +use LogicException; /** * Delegate events only for certain entities they are registered for. * - * @author Benjamin Eberlei - * @since 2.2 + * @link www.doctrine-project.org + * @author Benjamin Eberlei + * @author Guilherme Blanco + * @since 2.2 */ class EntityEventDelegator implements EventSubscriber { @@ -54,17 +56,23 @@ class EntityEventDelegator implements EventSubscriber public function addEventListener($events, $entities, $listener) { if ($this->frozen) { - throw new LogicException("Cannot add event listeners after EntityEventDelegator::getSubscribedEvents() " . - "is called once. This happens when you register the delegator with the event manager."); + throw new LogicException( + "Cannot add event listeners after EntityEventDelegator::getSubscribedEvents() " . + "is called once. This happens when you register the delegator with the event manager." + ); } // Picks the hash code related to that listener - $hash = spl_object_hash($listener); + $hash = spl_object_hash($listener); + $entities = array_flip((array) $entities); foreach ((array) $events as $event) { // Overrides listener if a previous one was associated already // Prevents duplicate listeners on same event (same instance only) - $this->listeners[$event][$hash] = array('listener' => $listener, 'entities' => array_flip((array)$entities)); + $this->listeners[$event][$hash] = array( + 'listener' => $listener, + 'entities' => $entities + ); } } @@ -73,6 +81,7 @@ class EntityEventDelegator implements EventSubscriber * interested in and added as a listener for these events. * * @param Doctrine\Common\EventSubscriber $subscriber The subscriber. + * @param array $entities */ public function addEventSubscriber(EventSubscriber $subscriber, $entities) { @@ -87,24 +96,27 @@ class EntityEventDelegator implements EventSubscriber public function getSubscribedEvents() { $this->frozen = true; + return array_keys($this->listeners); } /** * Delegate the event to an appropriate listener * - * @param $eventName - * @param $event + * @param string $eventName + * @param array $args * @return void */ public function __call($eventName, $args) { $event = $args[0]; + foreach ($this->listeners[$eventName] AS $listenerData) { $class = get_class($event->getEntity()); - if (isset($listenerData['entities'][$class])) { - $listenerData['listener']->$eventName($event); - } + + if ( ! isset($listenerData['entities'][$class])) continue; + + $listenerData['listener']->$eventName($event); } } } diff --git a/lib/Doctrine/ORM/Event/LifecycleEventArgs.php b/lib/Doctrine/ORM/Event/LifecycleEventArgs.php index a5dd39cfd..0c91d8475 100644 --- a/lib/Doctrine/ORM/Event/LifecycleEventArgs.php +++ b/lib/Doctrine/ORM/Event/LifecycleEventArgs.php @@ -19,42 +19,59 @@ namespace Doctrine\ORM\Event; +use Doctrine\Common\EventArgs; +use Doctrine\ORM\EntityManager; + /** * Lifecycle Events are triggered by the UnitOfWork during lifecycle transitions * of entities. * - * @since 2.0 + * @link www.doctrine-project.org + * @since 2.0 * @author Roman Borschel * @author Benjamin Eberlei */ -class LifecycleEventArgs extends \Doctrine\Common\EventArgs +class LifecycleEventArgs extends EventArgs { /** - * @var EntityManager + * @var Doctrine\ORM\EntityManager */ - private $_em; + private $em; /** * @var object */ - private $_entity; + private $entity; - public function __construct($entity, $em) + /** + * Constructor + * + * @param object $entity + * @param Doctrine\ORM\EntityManager $em + */ + public function __construct($entity, EntityManager $em) { - $this->_entity = $entity; - $this->_em = $em; + $this->entity = $entity; + $this->em = $em; } + /** + * Retireve associated Entity. + * + * @return object + */ public function getEntity() { - return $this->_entity; + return $this->entity; } /** - * @return EntityManager + * Retrieve associated EntityManager. + * + * @return Doctrine\ORM\EntityManager */ public function getEntityManager() { - return $this->_em; + return $this->em; } } \ No newline at end of file diff --git a/lib/Doctrine/ORM/Event/LoadClassMetadataEventArgs.php b/lib/Doctrine/ORM/Event/LoadClassMetadataEventArgs.php index f00520a20..a87f45cc3 100644 --- a/lib/Doctrine/ORM/Event/LoadClassMetadataEventArgs.php +++ b/lib/Doctrine/ORM/Event/LoadClassMetadataEventArgs.php @@ -1,9 +1,25 @@ . + */ namespace Doctrine\ORM\Event; use Doctrine\Common\EventArgs; - use Doctrine\ORM\Mapping\ClassMetadataInfo; use Doctrine\ORM\EntityManager; @@ -11,32 +27,36 @@ use Doctrine\ORM\EntityManager; * Class that holds event arguments for a loadMetadata event. * * @author Jonathan H. Wage - * @since 2.0 + * @since 2.0 */ class LoadClassMetadataEventArgs extends EventArgs { /** - * @var ClassMetadata + * @var Doctrine\ORM\Mapping\ClassMetadata */ private $classMetadata; /** - * @var EntityManager + * @var Doctrine\ORM\EntityManager */ private $em; /** - * @param ClassMetadataInfo $classMetadata - * @param EntityManager $em + * Constructor. + * + * @param Doctrine\ORM\Mapping\ClassMetadataInfo $classMetadata + * @param Doctrine\ORM\EntityManager $em */ public function __construct(ClassMetadataInfo $classMetadata, EntityManager $em) { $this->classMetadata = $classMetadata; - $this->em = $em; + $this->em = $em; } /** - * @return ClassMetadataInfo + * Retrieve associated ClassMetadata. + * + * @return Doctrine\ORM\Mapping\ClassMetadataInfo */ public function getClassMetadata() { @@ -44,7 +64,9 @@ class LoadClassMetadataEventArgs extends EventArgs } /** - * @return EntityManager + * Retrieve associated EntityManager. + * + * @return Doctrine\ORM\EntityManager */ public function getEntityManager() { diff --git a/lib/Doctrine/ORM/Event/OnClearEventArgs.php b/lib/Doctrine/ORM/Event/OnClearEventArgs.php index 60ce4b3eb..49b5e8695 100644 --- a/lib/Doctrine/ORM/Event/OnClearEventArgs.php +++ b/lib/Doctrine/ORM/Event/OnClearEventArgs.php @@ -15,7 +15,7 @@ * This software consists of voluntary contributions made by many individuals * and is licensed under the LGPL. For more information, see * . -*/ + */ namespace Doctrine\ORM\Event; @@ -23,16 +23,15 @@ namespace Doctrine\ORM\Event; * Provides event arguments for the onClear event. * * @license http://www.opensource.org/licenses/lgpl-license.php LGPL - * @link www.doctrine-project.com + * @link www.doctrine-project.org * @since 2.0 - * @version $Revision$ * @author Roman Borschel * @author Benjamin Eberlei */ class OnClearEventArgs extends \Doctrine\Common\EventArgs { /** - * @var \Doctrine\ORM\EntityManager + * @var Doctrine\ORM\EntityManager */ private $em; @@ -42,16 +41,21 @@ class OnClearEventArgs extends \Doctrine\Common\EventArgs private $entityClass; /** - * @param \Doctrine\ORM\EntityManager $em + * Constructor. + * + * @param Doctrine\ORM\EntityManager $em + * @param string $entityClass Optional entity class */ public function __construct($em, $entityClass = null) { - $this->em = $em; + $this->em = $em; $this->entityClass = $entityClass; } /** - * @return \Doctrine\ORM\EntityManager + * Retrieve associated EntityManager. + * + * @return Doctrine\ORM\EntityManager */ public function getEntityManager() { @@ -75,6 +79,6 @@ class OnClearEventArgs extends \Doctrine\Common\EventArgs */ public function clearsAllEntities() { - return $this->entityClass === null; + return ($this->entityClass === null); } } \ No newline at end of file diff --git a/lib/Doctrine/ORM/Event/OnFlushEventArgs.php b/lib/Doctrine/ORM/Event/OnFlushEventArgs.php index 1b4cb9ba8..5e6e839fe 100644 --- a/lib/Doctrine/ORM/Event/OnFlushEventArgs.php +++ b/lib/Doctrine/ORM/Event/OnFlushEventArgs.php @@ -21,37 +21,45 @@ namespace Doctrine\ORM\Event; +use Doctrine\ORM\EntityManager; + /** * Provides event arguments for the preFlush event. * * @license http://www.opensource.org/licenses/lgpl-license.php LGPL - * @link www.doctrine-project.com + * @link www.doctrine-project.org * @since 2.0 - * @version $Revision$ * @author Roman Borschel * @author Benjamin Eberlei */ class OnFlushEventArgs extends \Doctrine\Common\EventArgs { /** - * @var EntityManager + * @var Doctirne\ORM\EntityManager */ - private $_em; + private $em; - //private $_entitiesToPersist = array(); - //private $_entitiesToRemove = array(); + //private $entitiesToPersist = array(); + //private $entitiesToRemove = array(); - public function __construct($em) + /** + * Constructor. + * + * @param Doctrine\ORM\EntityManager $em + */ + public function __construct(EntityManager $em) { - $this->_em = $em; + $this->em = $em; } /** - * @return EntityManager + * Retrieve associated EntityManager. + * + * @return Doctrine\ORM\EntityManager */ public function getEntityManager() { - return $this->_em; + return $this->em; } /* diff --git a/lib/Doctrine/ORM/Event/PostFlushEventArgs.php b/lib/Doctrine/ORM/Event/PostFlushEventArgs.php index 92e88ae21..f500ad92f 100644 --- a/lib/Doctrine/ORM/Event/PostFlushEventArgs.php +++ b/lib/Doctrine/ORM/Event/PostFlushEventArgs.php @@ -17,9 +17,10 @@ * This software consists of voluntary contributions made by many individuals * and is licensed under the LGPL. For more information, see * . -*/ + */ namespace Doctrine\ORM\Event; + use Doctrine\ORM\EntityManager; use Doctrine\Common\EventArgs; @@ -27,20 +28,21 @@ use Doctrine\Common\EventArgs; * Provides event arguments for the postFlush event. * * @license http://www.opensource.org/licenses/lgpl-license.php LGPL - * @link www.doctrine-project.com + * @link www.doctrine-project.org * @since 2.0 - * @version $Revision$ * @author Daniel Freudenberger */ class PostFlushEventArgs extends EventArgs { /** - * @var EntityManager + * @var Doctrine\ORM\EntityManager */ private $em; /** - * @param EntityManager $em + * Constructor. + * + * @param Doctrine\ORM\EntityManager $em */ public function __construct(EntityManager $em) { @@ -48,7 +50,9 @@ class PostFlushEventArgs extends EventArgs } /** - * @return EntityManager + * Retrieve associated EntityManager. + * + * @return Doctrine\ORM\EntityManager */ public function getEntityManager() { diff --git a/lib/Doctrine/ORM/Event/PreUpdateEventArgs.php b/lib/Doctrine/ORM/Event/PreUpdateEventArgs.php index ab1cc15de..35539591a 100644 --- a/lib/Doctrine/ORM/Event/PreUpdateEventArgs.php +++ b/lib/Doctrine/ORM/Event/PreUpdateEventArgs.php @@ -1,4 +1,23 @@ . + */ namespace Doctrine\ORM\Event; @@ -8,42 +27,50 @@ use Doctrine\Common\EventArgs, /** * Class that holds event arguments for a preInsert/preUpdate event. * + * @author Guilherme Blanco * @author Roman Borschel * @author Benjamin Eberlei - * @since 2.0 + * @since 2.0 */ class PreUpdateEventArgs extends LifecycleEventArgs { /** * @var array */ - private $_entityChangeSet; + private $entityChangeSet; /** - * + * Constructor. + * * @param object $entity - * @param EntityManager $em + * @param Doctrine\ORM\EntityManager $em * @param array $changeSet */ - public function __construct($entity, $em, array &$changeSet) + public function __construct($entity, EntityManager $em, array &$changeSet) { parent::__construct($entity, $em); - $this->_entityChangeSet = &$changeSet; - } - - public function getEntityChangeSet() - { - return $this->_entityChangeSet; + + $this->entityChangeSet = &$changeSet; } /** - * Field has a changeset? + * Retrieve entity changeset. + * + * @return array + */ + public function getEntityChangeSet() + { + return $this->entityChangeSet; + } + + /** + * Check if field has a changeset. * - * @return bool + * @return boolean */ public function hasChangedField($field) { - return isset($this->_entityChangeSet[$field]); + return isset($this->entityChangeSet[$field]); } /** @@ -54,9 +81,9 @@ class PreUpdateEventArgs extends LifecycleEventArgs */ public function getOldValue($field) { - $this->_assertValidField($field); + $this->assertValidField($field); - return $this->_entityChangeSet[$field][0]; + return $this->entityChangeSet[$field][0]; } /** @@ -67,9 +94,9 @@ class PreUpdateEventArgs extends LifecycleEventArgs */ public function getNewValue($field) { - $this->_assertValidField($field); + $this->assertValidField($field); - return $this->_entityChangeSet[$field][1]; + return $this->entityChangeSet[$field][1]; } /** @@ -80,18 +107,24 @@ class PreUpdateEventArgs extends LifecycleEventArgs */ public function setNewValue($field, $value) { - $this->_assertValidField($field); + $this->assertValidField($field); - $this->_entityChangeSet[$field][1] = $value; + $this->entityChangeSet[$field][1] = $value; } - private function _assertValidField($field) + /** + * Assert the field exists in changeset. + * + * @param string $field + */ + private function assertValidField($field) { - if (!isset($this->_entityChangeSet[$field])) { - throw new \InvalidArgumentException( - "Field '".$field."' is not a valid field of the entity ". - "'".get_class($this->getEntity())."' in PreUpdateEventArgs." - ); + if ( ! isset($this->entityChangeSet[$field])) { + throw new \InvalidArgumentException(sprintf( + 'Field "%s" is not a valid field of the entity "%s" in PreUpdateEventArgs.', + $field, + get_class($this->getEntity()) + )); } } } From 1f06e9fca5c5f73ff63cf74ce3efea4fa77af315 Mon Sep 17 00:00:00 2001 From: Guilherme Blanco Date: Fri, 28 Oct 2011 12:56:14 -0200 Subject: [PATCH 044/135] Fixed issue with SimpleSelectExpression containing Literals. This issue is related to a previously fixed ticket DDC-1079. --- lib/Doctrine/ORM/Query/SqlWalker.php | 143 +++++++++--------- .../ORM/Query/SelectSqlGenerationTest.php | 19 ++- 2 files changed, 88 insertions(+), 74 deletions(-) diff --git a/lib/Doctrine/ORM/Query/SqlWalker.php b/lib/Doctrine/ORM/Query/SqlWalker.php index 188350197..9ada21b0f 100644 --- a/lib/Doctrine/ORM/Query/SqlWalker.php +++ b/lib/Doctrine/ORM/Query/SqlWalker.php @@ -1292,83 +1292,80 @@ class SqlWalker implements TreeWalker */ public function walkSimpleSelectExpression($simpleSelectExpression) { - $sql = ''; $expr = $simpleSelectExpression->expression; - - if ($expr instanceof AST\PathExpression) { - $sql .= $this->walkPathExpression($expr); - } else if ($expr instanceof AST\AggregateExpression) { - if ( ! $simpleSelectExpression->fieldIdentificationVariable) { - $alias = $this->_scalarResultCounter++; - } else { - $alias = $simpleSelectExpression->fieldIdentificationVariable; - } - - $sql .= $this->walkAggregateExpression($expr) . ' AS dctrn__' . $alias; - } else if ($expr instanceof AST\Subselect) { - if ( ! $simpleSelectExpression->fieldIdentificationVariable) { - $alias = $this->_scalarResultCounter++; - } else { - $alias = $simpleSelectExpression->fieldIdentificationVariable; - } - - $columnAlias = 'sclr' . $this->_aliasCounter++; - $sql .= '(' . $this->walkSubselect($expr) . ') AS ' . $columnAlias; - $this->_scalarResultAliasMap[$alias] = $columnAlias; - } else if ($expr instanceof AST\Functions\FunctionNode) { - if ( ! $simpleSelectExpression->fieldIdentificationVariable) { - $alias = $this->_scalarResultCounter++; - } else { - $alias = $simpleSelectExpression->fieldIdentificationVariable; - } - - $columnAlias = 'sclr' . $this->_aliasCounter++; - $sql .= $this->walkFunction($expr) . ' AS ' . $columnAlias; - $this->_scalarResultAliasMap[$alias] = $columnAlias; - } else if ( - $expr instanceof AST\SimpleArithmeticExpression || - $expr instanceof AST\ArithmeticTerm || - $expr instanceof AST\ArithmeticFactor || - $expr instanceof AST\ArithmeticPrimary - ) { - if ( ! $simpleSelectExpression->fieldIdentificationVariable) { - $alias = $this->_scalarResultCounter++; - } else { - $alias = $simpleSelectExpression->fieldIdentificationVariable; - } - - $columnAlias = 'sclr' . $this->_aliasCounter++; - $sql .= $this->walkSimpleArithmeticExpression($expr) . ' AS ' . $columnAlias; - $this->_scalarResultAliasMap[$alias] = $columnAlias; - } else if ( - $expr instanceof AST\NullIfExpression || - $expr instanceof AST\CoalesceExpression || - $expr instanceof AST\GeneralCaseExpression || - $expr instanceof AST\SimpleCaseExpression - ) { - if ( ! $simpleSelectExpression->fieldIdentificationVariable) { - $alias = $this->_scalarResultCounter++; - } else { - $alias = $simpleSelectExpression->fieldIdentificationVariable; - } - - $columnAlias = 'sclr' . $this->_aliasCounter++; - $sql .= $this->walkCaseExpression($expr) . ' AS ' . $columnAlias; + $sql = ' '; + + switch (true) { + case ($expr instanceof AST\PathExpression): + $sql .= $this->walkPathExpression($expr); + break; - $this->_scalarResultAliasMap[$alias] = $columnAlias; - } else { - // IdentificationVariable - $class = $this->_queryComponents[$expr]['metadata']; - $tableAlias = $this->getSQLTableAlias($class->getTableName(), $expr); - $first = true; + case ($expr instanceof AST\AggregateExpression): + $alias = $simpleSelectExpression->fieldIdentificationVariable ?: $this->_scalarResultCounter++; + + $sql .= $this->walkAggregateExpression($expr) . ' AS dctrn__' . $alias; + break; + + case ($expr instanceof AST\Subselect): + $alias = $simpleSelectExpression->fieldIdentificationVariable ?: $this->_scalarResultCounter++; + + $columnAlias = 'sclr' . $this->_aliasCounter++; + $this->_scalarResultAliasMap[$alias] = $columnAlias; + + $sql .= '(' . $this->walkSubselect($expr) . ') AS ' . $columnAlias; + break; + + case ($expr instanceof AST\Functions\FunctionNode): + $alias = $simpleSelectExpression->fieldIdentificationVariable ?: $this->_scalarResultCounter++; + + $columnAlias = 'sclr' . $this->_aliasCounter++; + $this->_scalarResultAliasMap[$alias] = $columnAlias; + + $sql .= $this->walkFunction($expr) . ' AS ' . $columnAlias; + break; + + case ($expr instanceof AST\Literal): + $sql .= $this->walkLiteral($expr); + break; + + case ($expr instanceof AST\SimpleArithmeticExpression): + case ($expr instanceof AST\ArithmeticTerm): + case ($expr instanceof AST\ArithmeticFactor): + case ($expr instanceof AST\ArithmeticPrimary): + $alias = $simpleSelectExpression->fieldIdentificationVariable ?: $this->_scalarResultCounter++; + + $columnAlias = 'sclr' . $this->_aliasCounter++; + $this->_scalarResultAliasMap[$alias] = $columnAlias; + + $sql .= $this->walkSimpleArithmeticExpression($expr) . ' AS ' . $columnAlias; + break; + + case ($expr instanceof AST\NullIfExpression): + case ($expr instanceof AST\CoalesceExpression): + case ($expr instanceof AST\GeneralCaseExpression): + case ($expr instanceof AST\SimpleCaseExpression): + $alias = $simpleSelectExpression->fieldIdentificationVariable ?: $this->_scalarResultCounter++; + + $columnAlias = 'sclr' . $this->_aliasCounter++; + $this->_scalarResultAliasMap[$alias] = $columnAlias; + + $sql .= $this->walkCaseExpression($expr) . ' AS ' . $columnAlias; + break; + + default: // IdentificationVariable + $class = $this->_queryComponents[$expr]['metadata']; + $tableAlias = $this->getSQLTableAlias($class->getTableName(), $expr); + $sqlParts = array(); - foreach ($class->identifier as $identifier) { - if ($first) $first = false; else $sql .= ', '; - $sql .= $tableAlias . '.' . $class->getQuotedColumnName($identifier, $this->_platform); - } + foreach ($class->identifier as $identifier) { + $sqlParts[] = $tableAlias . '.' . $class->getQuotedColumnName($identifier, $this->_platform); + } + + $sql .= implode(', ', $sqlParts); + break; } - - return ' ' . $sql; + + return $sql; } /** diff --git a/tests/Doctrine/Tests/ORM/Query/SelectSqlGenerationTest.php b/tests/Doctrine/Tests/ORM/Query/SelectSqlGenerationTest.php index 53002a8fc..6e3a52770 100644 --- a/tests/Doctrine/Tests/ORM/Query/SelectSqlGenerationTest.php +++ b/tests/Doctrine/Tests/ORM/Query/SelectSqlGenerationTest.php @@ -604,7 +604,24 @@ class SelectSqlGenerationTest extends \Doctrine\Tests\OrmTestCase . ' WHERE EXISTS (' . 'SELECT c1_.id FROM cms_employees c1_ WHERE c1_.id = c0_.spouse_id' . ')' - + ); + } + + public function testExistsExpressionWithSimpleSelectReturningScalar() + { + $this->assertSqlGeneration( + // DQL + // The result of this query consists of all employees whose spouses are also employees. + 'SELECT DISTINCT emp FROM Doctrine\Tests\Models\CMS\CmsEmployee emp + WHERE EXISTS ( + SELECT 1 + FROM Doctrine\Tests\Models\CMS\CmsEmployee spouseEmp + WHERE spouseEmp = emp.spouse)', + // SQL + 'SELECT DISTINCT c0_.id AS id0, c0_.name AS name1 FROM cms_employees c0_' + . ' WHERE EXISTS (' + . 'SELECT 1 FROM cms_employees c1_ WHERE c1_.id = c0_.spouse_id' + . ')' ); } From 3745e948c6f50c64d53fc3e8f09bef9de0be569e Mon Sep 17 00:00:00 2001 From: Guilherme Blanco Date: Fri, 28 Oct 2011 14:25:12 -0200 Subject: [PATCH 045/135] Made SimpleSelectExpression (Literal) be included as a scalar result. More general SQL Walker optimizations. --- lib/Doctrine/ORM/Query/SqlWalker.php | 238 +++++++++--------- .../ORM/Query/SelectSqlGenerationTest.php | 2 +- 2 files changed, 114 insertions(+), 126 deletions(-) diff --git a/lib/Doctrine/ORM/Query/SqlWalker.php b/lib/Doctrine/ORM/Query/SqlWalker.php index 9ada21b0f..6e6f80ce6 100644 --- a/lib/Doctrine/ORM/Query/SqlWalker.php +++ b/lib/Doctrine/ORM/Query/SqlWalker.php @@ -157,32 +157,24 @@ class SqlWalker implements TreeWalker */ public function getExecutor($AST) { - $isDeleteStatement = $AST instanceof AST\DeleteStatement; - $isUpdateStatement = $AST instanceof AST\UpdateStatement; - - if ($isDeleteStatement) { - $primaryClass = $this->_em->getClassMetadata( - $AST->deleteClause->abstractSchemaName - ); - - if ($primaryClass->isInheritanceTypeJoined()) { - return new Exec\MultiTableDeleteExecutor($AST, $this); - } else { - return new Exec\SingleTableDeleteUpdateExecutor($AST, $this); - } - } else if ($isUpdateStatement) { - $primaryClass = $this->_em->getClassMetadata( - $AST->updateClause->abstractSchemaName - ); - - if ($primaryClass->isInheritanceTypeJoined()) { - return new Exec\MultiTableUpdateExecutor($AST, $this); - } else { - return new Exec\SingleTableDeleteUpdateExecutor($AST, $this); - } + switch (true) { + case ($AST instanceof AST\DeleteStatement): + $primaryClass = $this->_em->getClassMetadata($AST->deleteClause->abstractSchemaName); + + return ($primaryClass->isInheritanceTypeJoined()) + ? new Exec\MultiTableDeleteExecutor($AST, $this) + : new Exec\SingleTableDeleteUpdateExecutor($AST, $this); + + case ($AST instanceof AST\UpdateStatement): + $primaryClass = $this->_em->getClassMetadata($AST->updateClause->abstractSchemaName); + + return ($primaryClass->isInheritanceTypeJoined()) + ? new Exec\MultiTableUpdateExecutor($AST, $this) + : new Exec\SingleTableDeleteUpdateExecutor($AST, $this); + + default: + return new Exec\SingleSelectExecutor($AST, $this); } - - return new Exec\SingleSelectExecutor($AST, $this); } /** @@ -1324,14 +1316,11 @@ class SqlWalker implements TreeWalker $sql .= $this->walkFunction($expr) . ' AS ' . $columnAlias; break; - case ($expr instanceof AST\Literal): - $sql .= $this->walkLiteral($expr); - break; - case ($expr instanceof AST\SimpleArithmeticExpression): case ($expr instanceof AST\ArithmeticTerm): case ($expr instanceof AST\ArithmeticFactor): case ($expr instanceof AST\ArithmeticPrimary): + case ($expr instanceof AST\Literal): $alias = $simpleSelectExpression->fieldIdentificationVariable ?: $this->_scalarResultCounter++; $columnAlias = 'sclr' . $this->_aliasCounter++; @@ -1388,25 +1377,24 @@ class SqlWalker implements TreeWalker */ public function walkGroupByClause($groupByClause) { - $sql = ''; + $sqlParts = array(); + foreach ($groupByClause->groupByItems AS $groupByItem) { - if (is_string($groupByItem)) { - foreach ($this->_queryComponents[$groupByItem]['metadata']->identifier AS $idField) { - if ($sql != '') { - $sql .= ', '; - } - $groupByItem = new AST\PathExpression(AST\PathExpression::TYPE_STATE_FIELD, $groupByItem, $idField); - $groupByItem->type = AST\PathExpression::TYPE_STATE_FIELD; - $sql .= $this->walkGroupByItem($groupByItem); - } - } else { - if ($sql != '') { - $sql .= ', '; - } - $sql .= $this->walkGroupByItem($groupByItem); + if ( ! is_string($groupByItem)) { + $sqlParts[] = $this->walkGroupByItem($groupByItem); + + continue; + } + + foreach ($this->_queryComponents[$groupByItem]['metadata']->identifier AS $idField) { + $groupByItem = new AST\PathExpression(AST\PathExpression::TYPE_STATE_FIELD, $groupByItem, $idField); + $groupByItem->type = AST\PathExpression::TYPE_STATE_FIELD; + + $sqlParts[] = $this->walkGroupByItem($groupByItem); } } - return ' GROUP BY ' . $sql; + + return ' GROUP BY ' . implode(', ', $sqlParts); } /** @@ -1428,12 +1416,11 @@ class SqlWalker implements TreeWalker */ public function walkDeleteClause(AST\DeleteClause $deleteClause) { - $sql = 'DELETE FROM '; - $class = $this->_em->getClassMetadata($deleteClause->abstractSchemaName); - $sql .= $class->getQuotedTableName($this->_platform); - - $this->setSQLTableAlias($class->getTableName(), $class->getTableName(), $deleteClause->aliasIdentificationVariable); - + $class = $this->_em->getClassMetadata($deleteClause->abstractSchemaName); + $tableName = $class->getTableName(); + $sql = 'DELETE FROM ' . $class->getQuotedTableName($this->_platform); + + $this->setSQLTableAlias($tableName, $tableName, $deleteClause->aliasIdentificationVariable); $this->_rootAliases[] = $deleteClause->aliasIdentificationVariable; return $sql; @@ -1447,17 +1434,14 @@ class SqlWalker implements TreeWalker */ public function walkUpdateClause($updateClause) { - $sql = 'UPDATE '; - $class = $this->_em->getClassMetadata($updateClause->abstractSchemaName); - $sql .= $class->getQuotedTableName($this->_platform); - - $this->setSQLTableAlias($class->getTableName(), $class->getTableName(), $updateClause->aliasIdentificationVariable); - + $class = $this->_em->getClassMetadata($updateClause->abstractSchemaName); + $tableName = $class->getTableName(); + $sql = 'UPDATE ' . $class->getQuotedTableName($this->_platform); + + $this->setSQLTableAlias($tableName, $tableName, $updateClause->aliasIdentificationVariable); $this->_rootAliases[] = $updateClause->aliasIdentificationVariable; - $sql .= ' SET ' . implode( - ', ', array_map(array($this, 'walkUpdateItem'), $updateClause->updateItems) - ); + $sql .= ' SET ' . implode(', ', array_map(array($this, 'walkUpdateItem'), $updateClause->updateItems)); return $sql; } @@ -1473,16 +1457,21 @@ class SqlWalker implements TreeWalker $useTableAliasesBefore = $this->_useSqlTableAliases; $this->_useSqlTableAliases = false; - $sql = $this->walkPathExpression($updateItem->pathExpression) . ' = '; - + $sql = $this->walkPathExpression($updateItem->pathExpression) . ' = '; $newValue = $updateItem->newValue; - if ($newValue === null) { - $sql .= 'NULL'; - } else if ($newValue instanceof AST\Node) { - $sql .= $newValue->dispatch($this); - } else { - $sql .= $this->_conn->quote($newValue); + switch (true) { + case ($newValue instanceof AST\Node): + $sql .= $newValue->dispatch($this); + break; + + case ($newValue === null): + $sql .= 'NULL'; + break; + + default: + $sql .= $this->_conn->quote($newValue); + break; } $this->_useSqlTableAliases = $useTableAliasesBefore; @@ -1499,12 +1488,14 @@ class SqlWalker implements TreeWalker */ public function walkWhereClause($whereClause) { - $condSql = null !== $whereClause ? $this->walkConditionalExpression($whereClause->conditionalExpression) : ''; + $condSql = null !== $whereClause ? $this->walkConditionalExpression($whereClause->conditionalExpression) : ''; $discrSql = $this->_generateDiscriminatorColumnConditionSql($this->_rootAliases); if ($condSql) { return ' WHERE ' . (( ! $discrSql) ? $condSql : '(' . $condSql . ') AND ' . $discrSql); - } else if ($discrSql) { + } + + if ($discrSql) { return ' WHERE ' . $discrSql; } @@ -1521,11 +1512,11 @@ class SqlWalker implements TreeWalker { // Phase 2 AST optimization: Skip processment of ConditionalExpression // if only one ConditionalTerm is defined - return ( ! ($condExpr instanceof AST\ConditionalExpression)) - ? $this->walkConditionalTerm($condExpr) - : implode( - ' OR ', array_map(array($this, 'walkConditionalTerm'), $condExpr->conditionalTerms) - ); + if ( ! ($condExpr instanceof AST\ConditionalExpression)) { + return $this->walkConditionalTerm($condExpr); + } + + return implode(' OR ', array_map(array($this, 'walkConditionalTerm'), $condExpr->conditionalTerms)); } /** @@ -1538,11 +1529,11 @@ class SqlWalker implements TreeWalker { // Phase 2 AST optimization: Skip processment of ConditionalTerm // if only one ConditionalFactor is defined - return ( ! ($condTerm instanceof AST\ConditionalTerm)) - ? $this->walkConditionalFactor($condTerm) - : implode( - ' AND ', array_map(array($this, 'walkConditionalFactor'), $condTerm->conditionalFactors) - ); + if ( ! ($condTerm instanceof AST\ConditionalTerm)) { + return $this->walkConditionalFactor($condTerm); + } + + return implode(' AND ', array_map(array($this, 'walkConditionalFactor'), $condTerm->conditionalFactors)); } /** @@ -1570,7 +1561,9 @@ class SqlWalker implements TreeWalker { if ($primary->isSimpleConditionalExpression()) { return $primary->simpleConditionalExpression->dispatch($this); - } else if ($primary->isConditionalExpression()) { + } + + if ($primary->isConditionalExpression()) { $condExpr = $primary->conditionalExpression; return '(' . $this->walkConditionalExpression($condExpr) . ')'; @@ -1749,14 +1742,12 @@ class SqlWalker implements TreeWalker */ public function walkInExpression($inExpr) { - $sql = $this->walkPathExpression($inExpr->pathExpression) + $sql = $this->walkPathExpression($inExpr->pathExpression) . ($inExpr->not ? ' NOT' : '') . ' IN ('; - if ($inExpr->subselect) { - $sql .= $this->walkSubselect($inExpr->subselect); - } else { - $sql .= implode(', ', array_map(array($this, 'walkInParameter'), $inExpr->literals)); - } + $sql .= ($inExpr->subselect) + ? $this->walkSubselect($inExpr->subselect) + : implode(', ', array_map(array($this, 'walkInParameter'), $inExpr->literals)); $sql .= ')'; @@ -1831,9 +1822,9 @@ class SqlWalker implements TreeWalker */ public function walkInParameter($inParam) { - return $inParam instanceof AST\InputParameter ? - $this->walkInputParameter($inParam) : - $this->walkLiteral($inParam); + return $inParam instanceof AST\InputParameter + ? $this->walkInputParameter($inParam) + : $this->walkLiteral($inParam); } /** @@ -1926,23 +1917,19 @@ class SqlWalker implements TreeWalker */ public function walkComparisonExpression($compExpr) { - $sql = ''; - $leftExpr = $compExpr->leftExpression; + $leftExpr = $compExpr->leftExpression; $rightExpr = $compExpr->rightExpression; - - if ($leftExpr instanceof AST\Node) { - $sql .= $leftExpr->dispatch($this); - } else { - $sql .= is_numeric($leftExpr) ? $leftExpr : $this->_conn->quote($leftExpr); - } + $sql = ''; + + $sql .= ($leftExpr instanceof AST\Node) + ? $leftExpr->dispatch($this) + : (is_numeric($leftExpr) ? $leftExpr : $this->_conn->quote($leftExpr)); $sql .= ' ' . $compExpr->operator . ' '; - if ($rightExpr instanceof AST\Node) { - $sql .= $rightExpr->dispatch($this); - } else { - $sql .= is_numeric($rightExpr) ? $rightExpr : $this->_conn->quote($rightExpr); - } + $sql .= ($rightExpr instanceof AST\Node) + ? $rightExpr->dispatch($this) + : (is_numeric($rightExpr) ? $rightExpr : $this->_conn->quote($rightExpr)); return $sql; } @@ -1981,11 +1968,11 @@ class SqlWalker implements TreeWalker */ public function walkSimpleArithmeticExpression($simpleArithmeticExpr) { - return ( ! ($simpleArithmeticExpr instanceof AST\SimpleArithmeticExpression)) - ? $this->walkArithmeticTerm($simpleArithmeticExpr) - : implode( - ' ', array_map(array($this, 'walkArithmeticTerm'), $simpleArithmeticExpr->arithmeticTerms) - ); + if ( ! ($simpleArithmeticExpr instanceof AST\SimpleArithmeticExpression)) { + return $this->walkArithmeticTerm($simpleArithmeticExpr); + } + + return implode(' ', array_map(array($this, 'walkArithmeticTerm'), $simpleArithmeticExpr->arithmeticTerms)); } /** @@ -1997,22 +1984,18 @@ class SqlWalker implements TreeWalker public function walkArithmeticTerm($term) { if (is_string($term)) { - if (isset($this->_queryComponents[$term])) { - $columnName = $this->_queryComponents[$term]['token']['value']; - - return $this->_scalarResultAliasMap[$columnName]; - } - - return $term; + return (isset($this->_queryComponents[$term])) + ? $this->_scalarResultAliasMap[$this->_queryComponents[$term]['token']['value']] + : $term; } // Phase 2 AST optimization: Skip processment of ArithmeticTerm // if only one ArithmeticFactor is defined - return ( ! ($term instanceof AST\ArithmeticTerm)) - ? $this->walkArithmeticFactor($term) - : implode( - ' ', array_map(array($this, 'walkArithmeticFactor'), $term->arithmeticFactors) - ); + if ( ! ($term instanceof AST\ArithmeticTerm)) { + return $this->walkArithmeticFactor($term); + } + + return implode(' ', array_map(array($this, 'walkArithmeticFactor'), $term->arithmeticFactors)); } /** @@ -2029,10 +2012,13 @@ class SqlWalker implements TreeWalker // Phase 2 AST optimization: Skip processment of ArithmeticFactor // if only one ArithmeticPrimary is defined - return ( ! ($factor instanceof AST\ArithmeticFactor)) - ? $this->walkArithmeticPrimary($factor) - : ($factor->isNegativeSigned() ? '-' : ($factor->isPositiveSigned() ? '+' : '')) - . $this->walkArithmeticPrimary($factor->arithmeticPrimary); + if ( ! ($factor instanceof AST\ArithmeticFactor)) { + return $this->walkArithmeticPrimary($factor); + } + + $sign = $factor->isNegativeSigned() ? '-' : ($factor->isPositiveSigned() ? '+' : ''); + + return $sign . $this->walkArithmeticPrimary($factor->arithmeticPrimary); } /** @@ -2045,7 +2031,9 @@ class SqlWalker implements TreeWalker { if ($primary instanceof AST\SimpleArithmeticExpression) { return '(' . $this->walkSimpleArithmeticExpression($primary) . ')'; - } else if ($primary instanceof AST\Node) { + } + + if ($primary instanceof AST\Node) { return $primary->dispatch($this); } diff --git a/tests/Doctrine/Tests/ORM/Query/SelectSqlGenerationTest.php b/tests/Doctrine/Tests/ORM/Query/SelectSqlGenerationTest.php index 6e3a52770..8b501a50e 100644 --- a/tests/Doctrine/Tests/ORM/Query/SelectSqlGenerationTest.php +++ b/tests/Doctrine/Tests/ORM/Query/SelectSqlGenerationTest.php @@ -620,7 +620,7 @@ class SelectSqlGenerationTest extends \Doctrine\Tests\OrmTestCase // SQL 'SELECT DISTINCT c0_.id AS id0, c0_.name AS name1 FROM cms_employees c0_' . ' WHERE EXISTS (' - . 'SELECT 1 FROM cms_employees c1_ WHERE c1_.id = c0_.spouse_id' + . 'SELECT 1 AS sclr2 FROM cms_employees c1_ WHERE c1_.id = c0_.spouse_id' . ')' ); } From 66d2b9e0fba22e6e6b524548ee8a9c2b6df76d08 Mon Sep 17 00:00:00 2001 From: Thiago Festa Date: Fri, 28 Oct 2011 17:54:15 -0200 Subject: [PATCH 046/135] The ProxyFactory was redeclaring methods serialize and unserialize on the cache file on some OSs. --- lib/Doctrine/ORM/Proxy/ProxyFactory.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/Doctrine/ORM/Proxy/ProxyFactory.php b/lib/Doctrine/ORM/Proxy/ProxyFactory.php index 490c3a119..f9ed3640a 100644 --- a/lib/Doctrine/ORM/Proxy/ProxyFactory.php +++ b/lib/Doctrine/ORM/Proxy/ProxyFactory.php @@ -167,7 +167,7 @@ class ProxyFactory foreach ($class->reflClass->getMethods() as $method) { /* @var $method ReflectionMethod */ - if ($method->isConstructor() || in_array(strtolower($method->getName()), array("__sleep", "__clone"))) { + if ($method->isConstructor() || in_array(strtolower($method->getName()), array("__sleep", "__clone")) || $class->reflClass->getName() != $method->class) { continue; } From 7be98f475e4a8280ea4477f4a956546841c78428 Mon Sep 17 00:00:00 2001 From: Benjamin Eberlei Date: Fri, 28 Oct 2011 23:45:03 +0200 Subject: [PATCH 047/135] DDC-1410 - Remove code-inlining that caused problems --- .../ORM/Internal/Hydration/ObjectHydrator.php | 27 +++++++++---------- 1 file changed, 12 insertions(+), 15 deletions(-) diff --git a/lib/Doctrine/ORM/Internal/Hydration/ObjectHydrator.php b/lib/Doctrine/ORM/Internal/Hydration/ObjectHydrator.php index 1287a138b..5e0c9c0be 100644 --- a/lib/Doctrine/ORM/Internal/Hydration/ObjectHydrator.php +++ b/lib/Doctrine/ORM/Internal/Hydration/ObjectHydrator.php @@ -62,7 +62,7 @@ class ObjectHydrator extends AbstractHydrator if (!isset($this->_hints['deferEagerLoad'])) { $this->_hints['deferEagerLoad'] = true; } - + foreach ($this->_rsm->aliasMap as $dqlAlias => $className) { $this->_identifierMap[$dqlAlias] = array(); $this->_idTemplate[$dqlAlias] = ''; @@ -116,13 +116,13 @@ class ObjectHydrator extends AbstractHydrator protected function _cleanup() { $eagerLoad = (isset($this->_hints['deferEagerLoad'])) && $this->_hints['deferEagerLoad'] == true; - + parent::_cleanup(); $this->_identifierMap = $this->_initializedCollections = $this->_existingCollections = $this->_resultPointers = array(); - + if ($eagerLoad) { $this->_em->getUnitOfWork()->triggerEagerLoads(); } @@ -192,7 +192,7 @@ class ObjectHydrator extends AbstractHydrator /** * Gets an entity instance. - * + * * @param $data The instance data. * @param $dqlAlias The DQL alias of the entity's class. * @return object The entity. @@ -205,12 +205,12 @@ class ObjectHydrator extends AbstractHydrator $className = $this->_ce[$className]->discriminatorMap[$data[$discrColumn]]; unset($data[$discrColumn]); } - + if (isset($this->_hints[Query::HINT_REFRESH_ENTITY]) && isset($this->_rootAliases[$dqlAlias])) { $class = $this->_ce[$className]; $this->registerManaged($class, $this->_hints[Query::HINT_REFRESH_ENTITY], $data); } - + return $this->_uow->createEntity($className, $data, $this->_hints); } @@ -240,7 +240,7 @@ class ObjectHydrator extends AbstractHydrator * Gets a ClassMetadata instance from the local cache. * If the instance is not yet in the local cache, it is loaded into the * local cache. - * + * * @param string $className The name of the class. * @return ClassMetadata */ @@ -254,21 +254,21 @@ class ObjectHydrator extends AbstractHydrator /** * Hydrates a single row in an SQL result set. - * + * * @internal * First, the data of the row is split into chunks where each chunk contains data * that belongs to a particular component/class. Afterwards, all these chunks * are processed, one after the other. For each chunk of class data only one of the * following code paths is executed: - * + * * Path A: The data chunk belongs to a joined/associated object and the association * is collection-valued. * Path B: The data chunk belongs to a joined/associated object and the association * is single-valued. * Path C: The data chunk belongs to a root result element/object that appears in the topmost * level of the hydrated result. A typical example are the objects of the type - * specified by the FROM clause in a DQL query. - * + * specified by the FROM clause in a DQL query. + * * @param array $data The data of the row to process. * @param array $cache The cache to use. * @param array $result The result array to fill. @@ -369,10 +369,7 @@ class ObjectHydrator extends AbstractHydrator $this->_resultPointers[$dqlAlias] = $reflFieldValue[$index]; } } else if ( ! $reflField->getValue($parentObject)) { - $coll = new PersistentCollection($this->_em, $this->_ce[$entityName], new ArrayCollection); - $coll->setOwner($parentObject, $relation); - $reflField->setValue($parentObject, $coll); - $this->_uow->setOriginalEntityProperty($oid, $relationField, $coll); + $reflFieldValue = $this->_initRelatedCollection($parentObject, $parentClass, $relationField); } } else { // PATH B: Single-valued association From 7d921a8220cd5ba835a40c08bbc36ac63f8fd6f2 Mon Sep 17 00:00:00 2001 From: Benjamin Eberlei Date: Sat, 29 Oct 2011 00:22:45 +0200 Subject: [PATCH 048/135] DDC-1452 - Attach working testcase --- .../ORM/Functional/Ticket/DDC1452Test.php | 77 +++++++++++++++++++ 1 file changed, 77 insertions(+) create mode 100644 tests/Doctrine/Tests/ORM/Functional/Ticket/DDC1452Test.php diff --git a/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC1452Test.php b/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC1452Test.php new file mode 100644 index 000000000..33241fad2 --- /dev/null +++ b/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC1452Test.php @@ -0,0 +1,77 @@ +_schemaTool->createSchema(array( + $this->_em->getClassMetadata(__NAMESPACE__ . '\DDC1452EntityA'), + $this->_em->getClassMetadata(__NAMESPACE__ . '\DDC1452EntityB'), + )); + } catch (\Exception $ignored) { + } + } + + public function testIssue() + { + $a = new DDC1452EntityA(); + $a->title = "foo"; + + $b = new DDC1452EntityB(); + $b->entityAFrom = $a; + $b->entityATo = $a; + + $this->_em->persist($a); + $this->_em->persist($b); + $this->_em->flush(); + $this->_em->clear(); + + $dql = "SELECT a, b, ba FROM " . __NAMESPACE__ . "\DDC1452EntityA AS a LEFT JOIN a.entitiesB AS b LEFT JOIN b.entityATo AS ba"; + $results = $this->_em->createQuery($dql)->getResult(); + + $this->assertSame($results[0], $results[0]->entitiesB[0]->entityAFrom); + $this->assertSame($results[0], $results[0]->entitiesB[0]->entityATo); + } +} + +/** + * @Entity + */ +class DDC1452EntityA +{ + /** @Id @Column(type="integer") @GeneratedValue */ + public $id; + /** @Column */ + public $title; + /** @ManyToMany(targetEntity="DDC1452EntityB", mappedBy="entityAFrom") */ + public $entitiesB; +} + +/** + * @Entity + */ +class DDC1452EntityB +{ + /** @Id @Column(type="integer") @GeneratedValue */ + public $id; + + /** + * @ManyToOne(targetEntity="DDC1452EntityA", inversedBy="entitiesB") + */ + public $entityAFrom; + /** + * @ManyToOne(targetEntity="DDC1452EntityA") + */ + public $entityATo; +} \ No newline at end of file From 0ec2cc557f51d6240396689e36101f62d84d2a38 Mon Sep 17 00:00:00 2001 From: Guilherme Blanco Date: Sat, 29 Oct 2011 02:00:35 -0200 Subject: [PATCH 049/135] Implemented support to entities with association marked as @Id support in many situations. Fixed DDC-1435. --- .../ORM/Mapping/ClassMetadataInfo.php | 72 ++++++++++++++----- lib/Doctrine/ORM/Query/SqlWalker.php | 20 +++--- .../Tests/Models/DDC117/DDC117Article.php | 1 + .../ORM/Query/SelectSqlGenerationTest.php | 11 +++ 4 files changed, 75 insertions(+), 29 deletions(-) diff --git a/lib/Doctrine/ORM/Mapping/ClassMetadataInfo.php b/lib/Doctrine/ORM/Mapping/ClassMetadataInfo.php index 25a3350d2..10f7bc4b6 100644 --- a/lib/Doctrine/ORM/Mapping/ClassMetadataInfo.php +++ b/lib/Doctrine/ORM/Mapping/ClassMetadataInfo.php @@ -1111,25 +1111,25 @@ class ClassMetadataInfo implements ClassMetadata */ public function getIdentifierColumnNames() { - if ($this->isIdentifierComposite) { - $columnNames = array(); - foreach ($this->identifier as $idField) { - if (isset($this->associationMappings[$idField])) { - // no composite pk as fk entity assumption: - $columnNames[] = $this->associationMappings[$idField]['joinColumns'][0]['name']; - } else { - $columnNames[] = $this->fieldMappings[$idField]['columnName']; - } + $columnNames = array(); + + foreach ($this->identifier as $idProperty) { + if (isset($this->fieldMappings[$idProperty])) { + $columnNames[] = $this->fieldMappings[$idProperty]['columnName']; + + continue; } - return $columnNames; - } else if(isset($this->fieldMappings[$this->identifier[0]])) { - return array($this->fieldMappings[$this->identifier[0]]['columnName']); - } else { - // no composite pk as fk entity assumption: - return array($this->associationMappings[$this->identifier[0]]['joinColumns'][0]['name']); + + // Association defined as Id field + $joinColumns = $this->associationMappings[$idProperty]['joinColumns']; + $assocColumnNames = array_map(function ($joinColumn) { return $joinColumn['name']; }, $joinColumns); + + $columnNames = array_merge($columnNames, $assocColumnNames); } + + return $columnNames; } - + /** * Sets the type of Id generator to use for the mapped class. */ @@ -1904,6 +1904,42 @@ class ClassMetadataInfo implements ClassMetadata return $this->name; } + /** + * Gets the (possibly quoted) identifier column names for safe use in an SQL statement. + * + * @param AbstractPlatform $platform + * @return array + */ + public function getQuotedIdentifierColumnNames($platform) + { + $quotedColumnNames = array(); + + foreach ($this->identifier as $idProperty) { + if (isset($this->fieldMappings[$idProperty])) { + $quotedColumnNames[] = isset($this->fieldMappings[$idProperty]['quoted']) + ? $platform->quoteIdentifier($this->fieldMappings[$idProperty]['columnName']) + : $this->fieldMappings[$idProperty]['columnName']; + + continue; + } + + // Association defined as Id field + $joinColumns = $this->associationMappings[$idProperty]['joinColumns']; + $assocQuotedColumnNames = array_map( + function ($joinColumn) { + return isset($joinColumn['quoted']) + ? $platform->quoteIdentifier($joinColumn['name']) + : $joinColumn['name']; + }, + $joinColumns + ); + + $quotedColumnNames = array_merge($quotedColumnNames, $assocQuotedColumnNames); + } + + return $quotedColumnNames; + } + /** * Gets the (possibly quoted) column name of a mapped field for safe use * in an SQL statement. @@ -1914,7 +1950,9 @@ class ClassMetadataInfo implements ClassMetadata */ public function getQuotedColumnName($field, $platform) { - return isset($this->fieldMappings[$field]['quoted']) ? $platform->quoteIdentifier($this->fieldMappings[$field]['columnName']) : $this->fieldMappings[$field]['columnName']; + return isset($this->fieldMappings[$field]['quoted']) + ? $platform->quoteIdentifier($this->fieldMappings[$field]['columnName']) + : $this->fieldMappings[$field]['columnName']; } /** diff --git a/lib/Doctrine/ORM/Query/SqlWalker.php b/lib/Doctrine/ORM/Query/SqlWalker.php index 6e6f80ce6..8939d198e 100644 --- a/lib/Doctrine/ORM/Query/SqlWalker.php +++ b/lib/Doctrine/ORM/Query/SqlWalker.php @@ -248,10 +248,9 @@ class SqlWalker implements TreeWalker $sql .= 'JOIN ' . $parentClass->getQuotedTableName($this->_platform) . ' ' . $tableAlias . ' ON '; $first = true; - foreach ($class->identifier as $idField) { + foreach ($class->getQuotedIdentifierColumnNames($this->_platform) as $columnName) { if ($first) $first = false; else $sql .= ' AND '; - $columnName = $class->getQuotedColumnName($idField, $this->_platform); $sql .= $baseTableAlias . '.' . $columnName . ' = ' . $tableAlias . '.' . $columnName; } } @@ -264,10 +263,9 @@ class SqlWalker implements TreeWalker $sql .= ' LEFT JOIN ' . $subClass->getQuotedTableName($this->_platform) . ' ' . $tableAlias . ' ON '; $first = true; - foreach ($class->identifier as $idField) { + foreach ($class->getQuotedIdentifierColumnNames($this->_platform) as $columnName) { if ($first) $first = false; else $sql .= ' AND '; - $columnName = $class->getQuotedColumnName($idField, $this->_platform); $sql .= $baseTableAlias . '.' . $columnName . ' = ' . $tableAlias . '.' . $columnName; } } @@ -1346,8 +1344,8 @@ class SqlWalker implements TreeWalker $tableAlias = $this->getSQLTableAlias($class->getTableName(), $expr); $sqlParts = array(); - foreach ($class->identifier as $identifier) { - $sqlParts[] = $tableAlias . '.' . $class->getQuotedColumnName($identifier, $this->_platform); + foreach ($class->getQuotedIdentifierColumnNames($this->_platform) as $columnName) { + $sqlParts[] = $tableAlias . '.' . $columnName; } $sql .= implode(', ', $sqlParts); @@ -1634,12 +1632,11 @@ class SqlWalker implements TreeWalker $sql .= ' AND '; $first = true; - foreach ($targetClass->identifier as $idField) { + foreach ($targetClass->getQuotedIdentifierColumnNames($this->_platform) as $targetColumnName) { if ($first) $first = false; else $sql .= ' AND '; $this->_parserResult->addParameterMapping($dqlParamKey, $this->_sqlParamIndex++); - $sql .= $targetTableAlias . '.' - . $targetClass->getQuotedColumnName($idField, $this->_platform) . ' = ?'; + $sql .= $targetTableAlias . '.' . $targetColumnName . ' = ?'; } } else { // many-to-many $targetClass = $this->_em->getClassMetadata($assoc['targetEntity']); @@ -1684,12 +1681,11 @@ class SqlWalker implements TreeWalker $sql .= ' AND '; $first = true; - foreach ($targetClass->identifier as $idField) { + foreach ($targetClass->getQuotedIdentifierColumnNames($this->_platform) as $targetColumnName) { if ($first) $first = false; else $sql .= ' AND '; $this->_parserResult->addParameterMapping($dqlParamKey, $this->_sqlParamIndex++); - $sql .= $targetTableAlias . '.' - . $targetClass->getQuotedColumnName($idField, $this->_platform) . ' = ?'; + $sql .= $targetTableAlias . '.' . $targetColumnName . ' = ?'; } } diff --git a/tests/Doctrine/Tests/Models/DDC117/DDC117Article.php b/tests/Doctrine/Tests/Models/DDC117/DDC117Article.php index 51ea2278d..70b1c6353 100644 --- a/tests/Doctrine/Tests/Models/DDC117/DDC117Article.php +++ b/tests/Doctrine/Tests/Models/DDC117/DDC117Article.php @@ -9,6 +9,7 @@ class DDC117Article { /** @Id @Column(type="integer", name="article_id") @GeneratedValue */ private $id; + /** @Column */ private $title; diff --git a/tests/Doctrine/Tests/ORM/Query/SelectSqlGenerationTest.php b/tests/Doctrine/Tests/ORM/Query/SelectSqlGenerationTest.php index 8b501a50e..6589890fc 100644 --- a/tests/Doctrine/Tests/ORM/Query/SelectSqlGenerationTest.php +++ b/tests/Doctrine/Tests/ORM/Query/SelectSqlGenerationTest.php @@ -1268,6 +1268,17 @@ class SelectSqlGenerationTest extends \Doctrine\Tests\OrmTestCase array(Query::HINT_FORCE_PARTIAL_LOAD => false) ); } + + /** + * @group DDC-1435 + */ + public function testForeignKeyAsPrimaryKeySubselect() + { + $this->assertSqlGeneration( + "SELECT s FROM Doctrine\Tests\Models\DDC117\DDC117Article s WHERE EXISTS (SELECT r FROM Doctrine\Tests\Models\DDC117\DDC117Reference r WHERE r.source = s)", + "SELECT d0_.article_id AS article_id0, d0_.title AS title1 FROM DDC117Article d0_ WHERE EXISTS (SELECT d1_.source_id, d1_.target_id FROM DDC117Reference d1_ WHERE d1_.source_id = d0_.article_id)" + ); + } } From e39bfced4a584ca967fce079385df3d57e176d54 Mon Sep 17 00:00:00 2001 From: Adrien BRAULT Date: Sat, 29 Oct 2011 13:40:01 +0200 Subject: [PATCH 050/135] Fix iterate method doc return type --- lib/Doctrine/ORM/AbstractQuery.php | 2 +- lib/Doctrine/ORM/Query.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/Doctrine/ORM/AbstractQuery.php b/lib/Doctrine/ORM/AbstractQuery.php index 0f28e71db..72eab194b 100644 --- a/lib/Doctrine/ORM/AbstractQuery.php +++ b/lib/Doctrine/ORM/AbstractQuery.php @@ -545,7 +545,7 @@ abstract class AbstractQuery * * @param array $params The query parameters. * @param integer $hydrationMode The hydration mode to use. - * @return IterableResult + * @return \Doctrine\ORM\Internal\Hydration\IterableResult */ public function iterate(array $params = array(), $hydrationMode = null) { diff --git a/lib/Doctrine/ORM/Query.php b/lib/Doctrine/ORM/Query.php index 293c64391..ac1eb75d7 100644 --- a/lib/Doctrine/ORM/Query.php +++ b/lib/Doctrine/ORM/Query.php @@ -526,7 +526,7 @@ final class Query extends AbstractQuery * * @param array $params The query parameters. * @param integer $hydrationMode The hydration mode to use. - * @return IterableResult + * @return \Doctrine\ORM\Internal\Hydration\IterableResult */ public function iterate(array $params = array(), $hydrationMode = self::HYDRATE_OBJECT) { From fb55e8094b408d1943f113e04f9eb4c0ac135390 Mon Sep 17 00:00:00 2001 From: Benjamin Eberlei Date: Sat, 29 Oct 2011 18:48:34 +0200 Subject: [PATCH 051/135] DDC-1384 - Made tests case-insenstive to work with latest change regarding column sizes --- tests/Doctrine/Tests/ORM/Functional/Ticket/DDC448Test.php | 5 ++++- tests/Doctrine/Tests/ORM/Functional/Ticket/DDC493Test.php | 5 ++++- tests/Doctrine/Tests/ORM/Functional/Ticket/DDC513Test.php | 5 ++++- tests/Doctrine/Tests/ORM/Functional/Ticket/DDC698Test.php | 5 ++++- 4 files changed, 16 insertions(+), 4 deletions(-) diff --git a/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC448Test.php b/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC448Test.php index f0867f352..4ea830863 100644 --- a/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC448Test.php +++ b/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC448Test.php @@ -18,7 +18,10 @@ class DDC448Test extends \Doctrine\Tests\OrmFunctionalTestCase public function testIssue() { $q = $this->_em->createQuery("select b from ".__NAMESPACE__."\\DDC448SubTable b where b.connectedClassId = ?1"); - $this->assertEquals('SELECT d0_.id AS id0, d0_.discr AS discr1, d0_.connectedClassId AS connectedClassId2 FROM SubTable s1_ INNER JOIN DDC448MainTable d0_ ON s1_.id = d0_.id WHERE d0_.connectedClassId = ?', $q->getSQL()); + $this->assertEquals( + strtolower('SELECT d0_.id AS id0, d0_.discr AS discr1, d0_.connectedClassId AS connectedClassId2 FROM SubTable s1_ INNER JOIN DDC448MainTable d0_ ON s1_.id = d0_.id WHERE d0_.connectedClassId = ?'), + strtolower($q->getSQL()) + ); } } diff --git a/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC493Test.php b/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC493Test.php index 25f378255..9e8d58be9 100644 --- a/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC493Test.php +++ b/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC493Test.php @@ -18,7 +18,10 @@ class DDC493Test extends \Doctrine\Tests\OrmFunctionalTestCase public function testIssue() { $q = $this->_em->createQuery("select u, c.data from ".__NAMESPACE__."\\DDC493Distributor u JOIN u.contact c"); - $this->assertEquals('SELECT d0_.id AS id0, d1_.data AS data1, d0_.discr AS discr2, d0_.contact AS contact3 FROM DDC493Distributor d2_ INNER JOIN DDC493Customer d0_ ON d2_.id = d0_.id INNER JOIN DDC493Contact d1_ ON d0_.contact = d1_.id', $q->getSQL()); + $this->assertEquals( + strtolower('SELECT d0_.id AS id0, d1_.data AS data1, d0_.discr AS discr2, d0_.contact AS contact3 FROM DDC493Distributor d2_ INNER JOIN DDC493Customer d0_ ON d2_.id = d0_.id INNER JOIN DDC493Contact d1_ ON d0_.contact = d1_.id'), + strtolower($q->getSQL()) + ); } } diff --git a/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC513Test.php b/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC513Test.php index 125f297db..b71d674cc 100644 --- a/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC513Test.php +++ b/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC513Test.php @@ -18,7 +18,10 @@ class DDC513Test extends \Doctrine\Tests\OrmFunctionalTestCase public function testIssue() { $q = $this->_em->createQuery("select u from ".__NAMESPACE__."\\DDC513OfferItem u left join u.price p"); - $this->assertEquals('SELECT d0_.id AS id0, d0_.discr AS discr1, d0_.price AS price2 FROM DDC513OfferItem d1_ INNER JOIN DDC513Item d0_ ON d1_.id = d0_.id LEFT JOIN DDC513Price d2_ ON d0_.price = d2_.id', $q->getSQL()); + $this->assertEquals( + strtolower('SELECT d0_.id AS id0, d0_.discr AS discr1, d0_.price AS price2 FROM DDC513OfferItem d1_ INNER JOIN DDC513Item d0_ ON d1_.id = d0_.id LEFT JOIN DDC513Price d2_ ON d0_.price = d2_.id'), + strtolower($q->getSQL()) + ); } } diff --git a/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC698Test.php b/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC698Test.php index 9fbfd01ab..4786cc176 100644 --- a/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC698Test.php +++ b/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC698Test.php @@ -30,7 +30,10 @@ class DDC698Test extends \Doctrine\Tests\OrmFunctionalTestCase $sql = $qb->getQuery()->getSQL(); - $this->assertEquals('SELECT p0_.privilegeID AS privilegeID0, p0_.name AS name1, r1_.roleID AS roleID2, r1_.name AS name3, r1_.shortName AS shortName4 FROM Privileges p0_ LEFT JOIN RolePrivileges r2_ ON p0_.privilegeID = r2_.privilegeID LEFT JOIN Roles r1_ ON r1_.roleID = r2_.roleID', $sql); + $this->assertEquals( + strtolower('SELECT p0_.privilegeID AS privilegeID0, p0_.name AS name1, r1_.roleID AS roleID2, r1_.name AS name3, r1_.shortName AS shortName4 FROM Privileges p0_ LEFT JOIN RolePrivileges r2_ ON p0_.privilegeID = r2_.privilegeID LEFT JOIN Roles r1_ ON r1_.roleID = r2_.roleID'), + strtolower($sql) + ); } } From 50e028212d1e30cc4d32b95eeee4480d2446644c Mon Sep 17 00:00:00 2001 From: Benjamin Eberlei Date: Sat, 29 Oct 2011 20:42:44 +0200 Subject: [PATCH 052/135] DDC-1384 - Fix a bunch of Oracle test failures --- .../ORM/Query/ResultSetMappingBuilder.php | 6 +- .../ORM/Functional/CustomTreeWalkersTest.php | 26 ++-- .../Tests/ORM/Functional/NativeQueryTest.php | 40 +++--- .../ORM/Functional/Ticket/DDC1225Test.php | 10 +- .../ORM/Functional/Ticket/DDC331Test.php | 17 +-- .../ORM/Functional/Ticket/DDC719Test.php | 11 +- .../ORM/Query/SelectSqlGenerationTest.php | 130 ++++++++++-------- 7 files changed, 127 insertions(+), 113 deletions(-) diff --git a/lib/Doctrine/ORM/Query/ResultSetMappingBuilder.php b/lib/Doctrine/ORM/Query/ResultSetMappingBuilder.php index d1690b72c..f84686ad2 100644 --- a/lib/Doctrine/ORM/Query/ResultSetMappingBuilder.php +++ b/lib/Doctrine/ORM/Query/ResultSetMappingBuilder.php @@ -86,20 +86,22 @@ class ResultSetMappingBuilder extends ResultSetMapping if (isset($renamedColumns[$columnName])) { $columnName = $renamedColumns[$columnName]; } + $columnName = $platform->getSQLResultCasing($columnName); if (isset($this->fieldMappings[$columnName])) { throw new \InvalidArgumentException("The column '$columnName' conflicts with another column in the mapper."); } - $this->addFieldResult($alias, $platform->getSQLResultCasing($columnName), $propertyName); + $this->addFieldResult($alias, $columnName, $propertyName); } foreach ($classMetadata->associationMappings AS $associationMapping) { if ($associationMapping['isOwningSide'] && $associationMapping['type'] & ClassMetadataInfo::TO_ONE) { foreach ($associationMapping['joinColumns'] AS $joinColumn) { $columnName = $joinColumn['name']; $renamedColumnName = isset($renamedColumns[$columnName]) ? $renamedColumns[$columnName] : $columnName; + $renamedColumnName = $platform->getSQLResultCasing($renamedColumnName); if (isset($this->metaMappings[$renamedColumnName])) { throw new \InvalidArgumentException("The column '$renamedColumnName' conflicts with another column in the mapper."); } - $this->addMetaResult($alias, $platform->getSQLResultCasing($renamedColumnName), $platform->getSQLResultCasing($columnName)); + $this->addMetaResult($alias, $renamedColumnName, $columnName); } } } diff --git a/tests/Doctrine/Tests/ORM/Functional/CustomTreeWalkersTest.php b/tests/Doctrine/Tests/ORM/Functional/CustomTreeWalkersTest.php index ada41d00b..72fc6d587 100644 --- a/tests/Doctrine/Tests/ORM/Functional/CustomTreeWalkersTest.php +++ b/tests/Doctrine/Tests/ORM/Functional/CustomTreeWalkersTest.php @@ -1,7 +1,5 @@ useModelSet('cms'); - parent::setUp(); + private $_em; + + protected function setUp() + { + $this->_em = $this->_getTestEntityManager(); } public function assertSqlGeneration($dqlToBeTested, $sqlToBeConfirmed) @@ -70,7 +70,7 @@ class CustomTreeWalkersTest extends \Doctrine\Tests\OrmFunctionalTestCase "SELECT c0_.id AS id0, c0_.status AS status1, c0_.username AS username2, c0_.name AS name3, c0_.email_id AS email_id4 FROM cms_users c0_ WHERE (c0_.name = ? OR c0_.name = ?) AND c0_.id = 1" ); } - + public function testSupportsQueriesWithSimpleConditionalExpression() { $this->assertSqlGeneration( @@ -94,7 +94,7 @@ class CustomTreeWalker extends Query\TreeWalkerAdapter $dqlAliases[] = $dqlAlias; } } - + // Create our conditions for all involved classes $factors = array(); foreach ($dqlAliases as $alias) { @@ -108,7 +108,7 @@ class CustomTreeWalker extends Query\TreeWalkerAdapter $factor = new Query\AST\ConditionalFactor($condPrimary); $factors[] = $factor; } - + if (($whereClause = $selectStatement->whereClause) !== null) { // There is already a WHERE clause, so append the conditions $condExpr = $whereClause->conditionalExpression; @@ -119,18 +119,18 @@ class CustomTreeWalker extends Query\TreeWalkerAdapter $whereClause->conditionalExpression = $condExpr; } - + $existingTerms = $whereClause->conditionalExpression->conditionalTerms; - + if (count($existingTerms) > 1) { // More than one term, so we need to wrap all these terms in a single root term // i.e: "WHERE u.name = :foo or u.other = :bar" => "WHERE (u.name = :foo or u.other = :bar) AND " - + $primary = new Query\AST\ConditionalPrimary; $primary->conditionalExpression = new Query\AST\ConditionalExpression($existingTerms); $existingFactor = new Query\AST\ConditionalFactor($primary); $term = new Query\AST\ConditionalTerm(array_merge(array($existingFactor), $factors)); - + $selectStatement->whereClause->conditionalExpression->conditionalTerms = array($term); } else { // Just one term so we can simply append our factors to that term diff --git a/tests/Doctrine/Tests/ORM/Functional/NativeQueryTest.php b/tests/Doctrine/Tests/ORM/Functional/NativeQueryTest.php index 3c41e0201..eb46329f1 100644 --- a/tests/Doctrine/Tests/ORM/Functional/NativeQueryTest.php +++ b/tests/Doctrine/Tests/ORM/Functional/NativeQueryTest.php @@ -35,7 +35,7 @@ class NativeQueryTest extends \Doctrine\Tests\OrmFunctionalTestCase $user->status = 'dev'; $this->_em->persist($user); $this->_em->flush(); - + $this->_em->clear(); $rsm = new ResultSetMapping; @@ -94,24 +94,24 @@ class NativeQueryTest extends \Doctrine\Tests\OrmFunctionalTestCase $this->assertEquals($addr->street, $addresses[0]->street); $this->assertTrue($addresses[0]->user instanceof CmsUser); } - + public function testJoinedOneToManyNativeQuery() { $user = new CmsUser; $user->name = 'Roman'; $user->username = 'romanb'; $user->status = 'dev'; - + $phone = new CmsPhonenumber; $phone->phonenumber = 424242; - + $user->addPhonenumber($phone); - + $this->_em->persist($user); $this->_em->flush(); - + $this->_em->clear(); - + $rsm = new ResultSetMapping; $rsm->addEntityResult('Doctrine\Tests\Models\CMS\CmsUser', 'u'); $rsm->addFieldResult('u', $this->platform->getSQLResultCasing('id'), 'id'); @@ -119,7 +119,7 @@ class NativeQueryTest extends \Doctrine\Tests\OrmFunctionalTestCase $rsm->addFieldResult('u', $this->platform->getSQLResultCasing('status'), 'status'); $rsm->addJoinedEntityResult('Doctrine\Tests\Models\CMS\CmsPhonenumber', 'p', 'u', 'phonenumbers'); $rsm->addFieldResult('p', $this->platform->getSQLResultCasing('phonenumber'), 'phonenumber'); - + $query = $this->_em->createNativeQuery('SELECT id, name, status, phonenumber FROM cms_users INNER JOIN cms_phonenumbers ON id = user_id WHERE username = ?', $rsm); $query->setParameter(1, 'romanb'); @@ -133,30 +133,30 @@ class NativeQueryTest extends \Doctrine\Tests\OrmFunctionalTestCase $phones = $users[0]->getPhonenumbers(); $this->assertEquals(424242, $phones[0]->phonenumber); $this->assertTrue($phones[0]->getUser() === $users[0]); - + } - + public function testJoinedOneToOneNativeQuery() { $user = new CmsUser; $user->name = 'Roman'; $user->username = 'romanb'; $user->status = 'dev'; - + $addr = new CmsAddress; $addr->country = 'germany'; $addr->zip = 10827; $addr->city = 'Berlin'; - - + + $user->setAddress($addr); - + $this->_em->persist($user); $this->_em->flush(); - + $this->_em->clear(); - - + + $rsm = new ResultSetMapping; $rsm->addEntityResult('Doctrine\Tests\Models\CMS\CmsUser', 'u'); $rsm->addFieldResult('u', $this->platform->getSQLResultCasing('id'), 'id'); @@ -167,12 +167,12 @@ class NativeQueryTest extends \Doctrine\Tests\OrmFunctionalTestCase $rsm->addFieldResult('a', $this->platform->getSQLResultCasing('country'), 'country'); $rsm->addFieldResult('a', $this->platform->getSQLResultCasing('zip'), 'zip'); $rsm->addFieldResult('a', $this->platform->getSQLResultCasing('city'), 'city'); - + $query = $this->_em->createNativeQuery('SELECT u.id, u.name, u.status, a.id AS a_id, a.country, a.zip, a.city FROM cms_users u INNER JOIN cms_addresses a ON u.id = a.user_id WHERE u.username = ?', $rsm); $query->setParameter(1, 'romanb'); - + $users = $query->getResult(); - + $this->assertEquals(1, count($users)); $this->assertInstanceOf('Doctrine\Tests\Models\CMS\CmsUser', $users[0]); $this->assertEquals('Roman', $users[0]->name); diff --git a/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC1225Test.php b/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC1225Test.php index cec258f37..95dcc2b65 100644 --- a/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC1225Test.php +++ b/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC1225Test.php @@ -21,10 +21,10 @@ class DDC1225Test extends \Doctrine\Tests\OrmFunctionalTestCase $this->_em->getClassMetadata(__NAMESPACE__ . '\\DDC1225_TestEntity2'), )); } catch(\PDOException $e) { - + } } - + public function testIssue() { $qb = $this->_em->createQueryBuilder(); @@ -32,10 +32,10 @@ class DDC1225Test extends \Doctrine\Tests\OrmFunctionalTestCase ->select('te1') ->where('te1.testEntity2 = ?1') ->setParameter(1, 0); - + $this->assertEquals( - 'SELECT t0_.test_entity2_id AS test_entity2_id0 FROM te1 t0_ WHERE t0_.test_entity2_id = ?', - $qb->getQuery()->getSQL() + strtolower('SELECT t0_.test_entity2_id AS test_entity2_id0 FROM te1 t0_ WHERE t0_.test_entity2_id = ?'), + strtolower($qb->getQuery()->getSQL()) ); } } diff --git a/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC331Test.php b/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC331Test.php index 512b1c9ea..2db32b9b8 100644 --- a/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC331Test.php +++ b/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC331Test.php @@ -25,18 +25,15 @@ class DDC331Test extends \Doctrine\Tests\OrmFunctionalTestCase parent::setUp(); } + /** + * @group DDC-331 + */ public function testSelectFieldOnRootEntity() { - $employee = new CompanyEmployee; - $employee->setName('Roman S. Borschel'); - $employee->setSalary(100000); - $employee->setDepartment('IT'); - - $this->_em->persist($employee); - $this->_em->flush(); - $this->_em->clear(); - $q = $this->_em->createQuery('SELECT e.name FROM Doctrine\Tests\Models\Company\CompanyEmployee e'); - $this->assertEquals('SELECT c0_.name AS name0 FROM company_employees c1_ INNER JOIN company_persons c0_ ON c1_.id = c0_.id LEFT JOIN company_managers c2_ ON c1_.id = c2_.id', $q->getSql()); + $this->assertEquals( + strtolower('SELECT c0_.name AS name0 FROM company_employees c1_ INNER JOIN company_persons c0_ ON c1_.id = c0_.id LEFT JOIN company_managers c2_ ON c1_.id = c2_.id'), + strtolower($q->getSql()) + ); } } \ No newline at end of file diff --git a/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC719Test.php b/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC719Test.php index 82cb67223..6bd18ef98 100644 --- a/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC719Test.php +++ b/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC719Test.php @@ -19,7 +19,10 @@ class DDC719Test extends \Doctrine\Tests\OrmFunctionalTestCase { $q = $this->_em->createQuery('SELECT g, c FROM Doctrine\Tests\ORM\Functional\Ticket\DDC719Group g LEFT JOIN g.children c WHERE g.parents IS EMPTY'); - $this->assertEquals('SELECT g0_.name AS name0, g0_.description AS description1, g0_.id AS id2, g1_.name AS name3, g1_.description AS description4, g1_.id AS id5 FROM groups g0_ LEFT JOIN groups_groups g2_ ON g0_.id = g2_.parent_id LEFT JOIN groups g1_ ON g1_.id = g2_.child_id WHERE (SELECT COUNT(*) FROM groups_groups g3_ WHERE g3_.child_id = g0_.id) = 0', $q->getSQL()); + $this->assertEquals( + strtolower('SELECT g0_.name AS name0, g0_.description AS description1, g0_.id AS id2, g1_.name AS name3, g1_.description AS description4, g1_.id AS id5 FROM groups g0_ LEFT JOIN groups_groups g2_ ON g0_.id = g2_.parent_id LEFT JOIN groups g1_ ON g1_.id = g2_.child_id WHERE (SELECT COUNT(*) FROM groups_groups g3_ WHERE g3_.child_id = g0_.id) = 0'), + strtolower($q->getSQL()) + ); } } @@ -28,12 +31,12 @@ class DDC719Test extends \Doctrine\Tests\OrmFunctionalTestCase */ class Entity { - /** + /** * @Id @GeneratedValue - * @Column(type="integer") + * @Column(type="integer") */ protected $id; - + public function getId() { return $this->id; } } diff --git a/tests/Doctrine/Tests/ORM/Query/SelectSqlGenerationTest.php b/tests/Doctrine/Tests/ORM/Query/SelectSqlGenerationTest.php index e3ea9c004..c5a41e7c7 100644 --- a/tests/Doctrine/Tests/ORM/Query/SelectSqlGenerationTest.php +++ b/tests/Doctrine/Tests/ORM/Query/SelectSqlGenerationTest.php @@ -17,11 +17,11 @@ class SelectSqlGenerationTest extends \Doctrine\Tests\OrmTestCase /** * Assert a valid SQL generation. - * + * * @param string $dqlToBeTested * @param string $sqlToBeConfirmed * @param array $queryHints - * @param array $queryParams + * @param array $queryParams */ public function assertSqlGeneration($dqlToBeTested, $sqlToBeConfirmed, array $queryHints = array(), array $queryParams = array()) { @@ -34,11 +34,11 @@ class SelectSqlGenerationTest extends \Doctrine\Tests\OrmTestCase $query->setHint(Query::HINT_FORCE_PARTIAL_LOAD, true) ->useQueryCache(false); - + foreach ($queryHints AS $name => $value) { $query->setHint($name, $value); } - + parent::assertEquals($sqlToBeConfirmed, $query->getSQL()); $query->free(); } catch (\Exception $e) { @@ -383,7 +383,7 @@ class SelectSqlGenerationTest extends \Doctrine\Tests\OrmTestCase "SELECT c0_.id AS id0, c0_.name AS name1, c0_.discr AS discr2 FROM company_persons c0_ WHERE c0_.discr IN ('employee')" ); } - + public function testSupportsInstanceOfExpressionInWherePartWithMultipleValues() { $this->assertSqlGeneration( @@ -391,7 +391,7 @@ class SelectSqlGenerationTest extends \Doctrine\Tests\OrmTestCase "SELECT c0_.id AS id0, c0_.name AS name1, c0_.discr AS discr2 FROM company_persons c0_ WHERE c0_.discr IN ('employee', 'manager')" ); } - + /** * @group DDC-1194 */ @@ -402,7 +402,7 @@ class SelectSqlGenerationTest extends \Doctrine\Tests\OrmTestCase "SELECT c0_.id AS id0, c0_.name AS name1, c0_.discr AS discr2 FROM company_persons c0_ WHERE c0_.discr IN ('employee')" ); } - + /** * @group DDC-1194 */ @@ -686,7 +686,7 @@ class SelectSqlGenerationTest extends \Doctrine\Tests\OrmTestCase "SELECT c0_.id AS id0, c0_.status AS status1, c0_.username AS username2, c0_.name AS name3, (SELECT COUNT(*) FROM cms_articles c1_ WHERE c1_.user_id = c0_.id) AS sclr4 FROM cms_users c0_ ORDER BY sclr4 ASC" ); } - + public function testOrderBySupportsSingleValuedPathExpressionOwningSide() { $this->assertSqlGeneration( @@ -694,7 +694,7 @@ class SelectSqlGenerationTest extends \Doctrine\Tests\OrmTestCase "SELECT c0_.id AS id0, c0_.topic AS topic1, c0_.text AS text2, c0_.version AS version3 FROM cms_articles c0_ ORDER BY c0_.user_id ASC" ); } - + /** * @expectedException Doctrine\ORM\Query\QueryException */ @@ -931,7 +931,7 @@ class SelectSqlGenerationTest extends \Doctrine\Tests\OrmTestCase 'SELECT c0_.id AS id0, c0_.name AS name1, count(c1_.id) AS sclr2 FROM cms_groups c0_ INNER JOIN cms_users_groups c2_ ON c0_.id = c2_.group_id INNER JOIN cms_users c1_ ON c1_.id = c2_.user_id GROUP BY c0_.id' ); } - + public function testCaseContainingNullIf() { $this->assertSqlGeneration( @@ -939,7 +939,7 @@ class SelectSqlGenerationTest extends \Doctrine\Tests\OrmTestCase 'SELECT NULLIF(c0_.id, c0_.name) AS sclr0 FROM cms_groups c0_' ); } - + public function testCaseContainingCoalesce() { $this->assertSqlGeneration( @@ -1009,244 +1009,244 @@ class SelectSqlGenerationTest extends \Doctrine\Tests\OrmTestCase "SELECT d0_.article_id AS article_id0, d0_.title AS title1, d1_.article_id AS article_id2, d1_.title AS title3 FROM DDC117Link d2_ INNER JOIN DDC117Article d0_ ON d2_.target_id = d0_.article_id INNER JOIN DDC117Article d1_ ON d2_.source_id = d1_.article_id" ); } - + public function testGeneralCaseWithSingleWhenClause() { $this->assertSqlGeneration( - "SELECT g.id, CASE WHEN ((g.id / 2) > 18) THEN 1 ELSE 0 END AS test FROM Doctrine\Tests\Models\CMS\CmsGroup g", + "SELECT g.id, CASE WHEN ((g.id / 2) > 18) THEN 1 ELSE 0 END AS test FROM Doctrine\Tests\Models\CMS\CmsGroup g", "SELECT c0_.id AS id0, CASE WHEN (c0_.id / 2 > 18) THEN 1 ELSE 0 END AS sclr1 FROM cms_groups c0_" ); } - + public function testGeneralCaseWithMultipleWhenClause() { $this->assertSqlGeneration( - "SELECT g.id, CASE WHEN (g.id / 2 < 10) THEN 2 WHEN ((g.id / 2) > 20) THEN 1 ELSE 0 END AS test FROM Doctrine\Tests\Models\CMS\CmsGroup g", + "SELECT g.id, CASE WHEN (g.id / 2 < 10) THEN 2 WHEN ((g.id / 2) > 20) THEN 1 ELSE 0 END AS test FROM Doctrine\Tests\Models\CMS\CmsGroup g", "SELECT c0_.id AS id0, CASE WHEN (c0_.id / 2 < 10) THEN 2 WHEN (c0_.id / 2 > 20) THEN 1 ELSE 0 END AS sclr1 FROM cms_groups c0_" ); } - + public function testSimpleCaseWithSingleWhenClause() { $this->assertSqlGeneration( - "SELECT g FROM Doctrine\Tests\Models\CMS\CmsGroup g WHERE g.id = CASE g.name WHEN 'admin' THEN 1 ELSE 2 END", + "SELECT g FROM Doctrine\Tests\Models\CMS\CmsGroup g WHERE g.id = CASE g.name WHEN 'admin' THEN 1 ELSE 2 END", "SELECT c0_.id AS id0, c0_.name AS name1 FROM cms_groups c0_ WHERE c0_.id = CASE c0_.name WHEN admin THEN 1 ELSE 2 END" ); } - + public function testSimpleCaseWithMultipleWhenClause() { $this->assertSqlGeneration( - "SELECT g FROM Doctrine\Tests\Models\CMS\CmsGroup g WHERE g.id = (CASE g.name WHEN 'admin' THEN 1 WHEN 'moderator' THEN 2 ELSE 3 END)", + "SELECT g FROM Doctrine\Tests\Models\CMS\CmsGroup g WHERE g.id = (CASE g.name WHEN 'admin' THEN 1 WHEN 'moderator' THEN 2 ELSE 3 END)", "SELECT c0_.id AS id0, c0_.name AS name1 FROM cms_groups c0_ WHERE c0_.id = CASE c0_.name WHEN admin THEN 1 WHEN moderator THEN 2 ELSE 3 END" ); } - + public function testGeneralCaseWithSingleWhenClauseInSubselect() { $this->assertSqlGeneration( - "SELECT g FROM Doctrine\Tests\Models\CMS\CmsGroup g WHERE g.id IN (SELECT CASE WHEN ((g2.id / 2) > 18) THEN 2 ELSE 1 END FROM Doctrine\Tests\Models\CMS\CmsGroup g2)", + "SELECT g FROM Doctrine\Tests\Models\CMS\CmsGroup g WHERE g.id IN (SELECT CASE WHEN ((g2.id / 2) > 18) THEN 2 ELSE 1 END FROM Doctrine\Tests\Models\CMS\CmsGroup g2)", "SELECT c0_.id AS id0, c0_.name AS name1 FROM cms_groups c0_ WHERE c0_.id IN (SELECT CASE WHEN (c1_.id / 2 > 18) THEN 2 ELSE 1 END AS sclr2 FROM cms_groups c1_)" ); } - + public function testGeneralCaseWithMultipleWhenClauseInSubselect() { $this->assertSqlGeneration( - "SELECT g FROM Doctrine\Tests\Models\CMS\CmsGroup g WHERE g.id IN (SELECT CASE WHEN (g.id / 2 < 10) THEN 3 WHEN ((g.id / 2) > 20) THEN 2 ELSE 1 END FROM Doctrine\Tests\Models\CMS\CmsGroup g2)", + "SELECT g FROM Doctrine\Tests\Models\CMS\CmsGroup g WHERE g.id IN (SELECT CASE WHEN (g.id / 2 < 10) THEN 3 WHEN ((g.id / 2) > 20) THEN 2 ELSE 1 END FROM Doctrine\Tests\Models\CMS\CmsGroup g2)", "SELECT c0_.id AS id0, c0_.name AS name1 FROM cms_groups c0_ WHERE c0_.id IN (SELECT CASE WHEN (c0_.id / 2 < 10) THEN 3 WHEN (c0_.id / 2 > 20) THEN 2 ELSE 1 END AS sclr2 FROM cms_groups c1_)" ); } - + public function testSimpleCaseWithSingleWhenClauseInSubselect() { $this->assertSqlGeneration( - "SELECT g FROM Doctrine\Tests\Models\CMS\CmsGroup g WHERE g.id IN (SELECT CASE g2.name WHEN 'admin' THEN 1 ELSE 2 END FROM Doctrine\Tests\Models\CMS\CmsGroup g2)", + "SELECT g FROM Doctrine\Tests\Models\CMS\CmsGroup g WHERE g.id IN (SELECT CASE g2.name WHEN 'admin' THEN 1 ELSE 2 END FROM Doctrine\Tests\Models\CMS\CmsGroup g2)", "SELECT c0_.id AS id0, c0_.name AS name1 FROM cms_groups c0_ WHERE c0_.id IN (SELECT CASE c1_.name WHEN admin THEN 1 ELSE 2 END AS sclr2 FROM cms_groups c1_)" ); } - + public function testSimpleCaseWithMultipleWhenClauseInSubselect() { $this->assertSqlGeneration( - "SELECT g FROM Doctrine\Tests\Models\CMS\CmsGroup g WHERE g.id IN (SELECT CASE g2.name WHEN 'admin' THEN 1 WHEN 'moderator' THEN 2 ELSE 3 END FROM Doctrine\Tests\Models\CMS\CmsGroup g2)", + "SELECT g FROM Doctrine\Tests\Models\CMS\CmsGroup g WHERE g.id IN (SELECT CASE g2.name WHEN 'admin' THEN 1 WHEN 'moderator' THEN 2 ELSE 3 END FROM Doctrine\Tests\Models\CMS\CmsGroup g2)", "SELECT c0_.id AS id0, c0_.name AS name1 FROM cms_groups c0_ WHERE c0_.id IN (SELECT CASE c1_.name WHEN admin THEN 1 WHEN moderator THEN 2 ELSE 3 END AS sclr2 FROM cms_groups c1_)" ); } - + /** * @group DDC-1339 */ public function testIdentityFunctionInSelectClause() { $this->assertSqlGeneration( - "SELECT IDENTITY(u.email) as email_id FROM Doctrine\Tests\Models\CMS\CmsUser u", + "SELECT IDENTITY(u.email) as email_id FROM Doctrine\Tests\Models\CMS\CmsUser u", "SELECT c0_.email_id AS sclr0 FROM cms_users c0_" ); } - + /** * @group DDC-1339 */ public function testIdentityFunctionDoesNotAcceptStateField() { $this->assertInvalidSqlGeneration( - "SELECT IDENTITY(u.name) as name FROM Doctrine\Tests\Models\CMS\CmsUser u", + "SELECT IDENTITY(u.name) as name FROM Doctrine\Tests\Models\CMS\CmsUser u", "Doctrine\ORM\Query\QueryException" ); } - + /** * @group DDC-1389 */ public function testInheritanceTypeJoinInRootClassWithDisabledForcePartialLoad() { $this->assertSqlGeneration( - 'SELECT p FROM Doctrine\Tests\Models\Company\CompanyPerson p', + 'SELECT p FROM Doctrine\Tests\Models\Company\CompanyPerson p', 'SELECT c0_.id AS id0, c0_.name AS name1, c1_.title AS title2, c1_.car_id AS car_id3, c2_.salary AS salary4, c2_.department AS department5, c2_.startDate AS startDate6, c0_.discr AS discr7, c0_.spouse_id AS spouse_id8 FROM company_persons c0_ LEFT JOIN company_managers c1_ ON c0_.id = c1_.id LEFT JOIN company_employees c2_ ON c0_.id = c2_.id', array(Query::HINT_FORCE_PARTIAL_LOAD => false) ); } - + /** * @group DDC-1389 */ public function testInheritanceTypeJoinInRootClassWithEnabledForcePartialLoad() { $this->assertSqlGeneration( - 'SELECT p FROM Doctrine\Tests\Models\Company\CompanyPerson p', + 'SELECT p FROM Doctrine\Tests\Models\Company\CompanyPerson p', 'SELECT c0_.id AS id0, c0_.name AS name1, c0_.discr AS discr2 FROM company_persons c0_', array(Query::HINT_FORCE_PARTIAL_LOAD => true) ); } - + /** * @group DDC-1389 */ public function testInheritanceTypeJoinInChildClassWithDisabledForcePartialLoad() { $this->assertSqlGeneration( - 'SELECT e FROM Doctrine\Tests\Models\Company\CompanyEmployee e', + 'SELECT e FROM Doctrine\Tests\Models\Company\CompanyEmployee e', 'SELECT c0_.id AS id0, c0_.name AS name1, c1_.salary AS salary2, c1_.department AS department3, c1_.startDate AS startDate4, c2_.title AS title5, c2_.car_id AS car_id6, c0_.discr AS discr7, c0_.spouse_id AS spouse_id8 FROM company_employees c1_ INNER JOIN company_persons c0_ ON c1_.id = c0_.id LEFT JOIN company_managers c2_ ON c1_.id = c2_.id', array(Query::HINT_FORCE_PARTIAL_LOAD => false) ); } - + /** * @group DDC-1389 */ public function testInheritanceTypeJoinInChildClassWithEnabledForcePartialLoad() { $this->assertSqlGeneration( - 'SELECT e FROM Doctrine\Tests\Models\Company\CompanyEmployee e', + 'SELECT e FROM Doctrine\Tests\Models\Company\CompanyEmployee e', 'SELECT c0_.id AS id0, c0_.name AS name1, c1_.salary AS salary2, c1_.department AS department3, c1_.startDate AS startDate4, c0_.discr AS discr5 FROM company_employees c1_ INNER JOIN company_persons c0_ ON c1_.id = c0_.id', array(Query::HINT_FORCE_PARTIAL_LOAD => true) ); } - + /** * @group DDC-1389 */ public function testInheritanceTypeJoinInLeafClassWithDisabledForcePartialLoad() { $this->assertSqlGeneration( - 'SELECT m FROM Doctrine\Tests\Models\Company\CompanyManager m', + 'SELECT m FROM Doctrine\Tests\Models\Company\CompanyManager m', 'SELECT c0_.id AS id0, c0_.name AS name1, c1_.salary AS salary2, c1_.department AS department3, c1_.startDate AS startDate4, c2_.title AS title5, c0_.discr AS discr6, c0_.spouse_id AS spouse_id7, c2_.car_id AS car_id8 FROM company_managers c2_ INNER JOIN company_employees c1_ ON c2_.id = c1_.id INNER JOIN company_persons c0_ ON c2_.id = c0_.id', array(Query::HINT_FORCE_PARTIAL_LOAD => false) ); } - + /** * @group DDC-1389 */ public function testInheritanceTypeJoinInLeafClassWithEnabledForcePartialLoad() { $this->assertSqlGeneration( - 'SELECT m FROM Doctrine\Tests\Models\Company\CompanyManager m', + 'SELECT m FROM Doctrine\Tests\Models\Company\CompanyManager m', 'SELECT c0_.id AS id0, c0_.name AS name1, c1_.salary AS salary2, c1_.department AS department3, c1_.startDate AS startDate4, c2_.title AS title5, c0_.discr AS discr6 FROM company_managers c2_ INNER JOIN company_employees c1_ ON c2_.id = c1_.id INNER JOIN company_persons c0_ ON c2_.id = c0_.id', array(Query::HINT_FORCE_PARTIAL_LOAD => true) ); } - + /** * @group DDC-1389 */ public function testInheritanceTypeSingleTableInRootClassWithDisabledForcePartialLoad() { $this->assertSqlGeneration( - 'SELECT c FROM Doctrine\Tests\Models\Company\CompanyContract c', + 'SELECT c FROM Doctrine\Tests\Models\Company\CompanyContract c', "SELECT c0_.id AS id0, c0_.completed AS completed1, c0_.fixPrice AS fixPrice2, c0_.hoursWorked AS hoursWorked3, c0_.pricePerHour AS pricePerHour4, c0_.maxPrice AS maxPrice5, c0_.discr AS discr6, c0_.salesPerson_id AS salesPerson_id7 FROM company_contracts c0_ WHERE c0_.discr IN ('fix', 'flexible', 'flexultra')", array(Query::HINT_FORCE_PARTIAL_LOAD => false) ); } - + /** * @group DDC-1389 */ public function testInheritanceTypeSingleTableInRootClassWithEnabledForcePartialLoad() { $this->assertSqlGeneration( - 'SELECT c FROM Doctrine\Tests\Models\Company\CompanyContract c', + 'SELECT c FROM Doctrine\Tests\Models\Company\CompanyContract c', "SELECT c0_.id AS id0, c0_.completed AS completed1, c0_.fixPrice AS fixPrice2, c0_.hoursWorked AS hoursWorked3, c0_.pricePerHour AS pricePerHour4, c0_.maxPrice AS maxPrice5, c0_.discr AS discr6 FROM company_contracts c0_ WHERE c0_.discr IN ('fix', 'flexible', 'flexultra')", array(Query::HINT_FORCE_PARTIAL_LOAD => true) ); } - + /** * @group DDC-1389 */ public function testInheritanceTypeSingleTableInChildClassWithDisabledForcePartialLoad() { $this->assertSqlGeneration( - 'SELECT fc FROM Doctrine\Tests\Models\Company\CompanyFlexContract fc', + 'SELECT fc FROM Doctrine\Tests\Models\Company\CompanyFlexContract fc', "SELECT c0_.id AS id0, c0_.completed AS completed1, c0_.hoursWorked AS hoursWorked2, c0_.pricePerHour AS pricePerHour3, c0_.maxPrice AS maxPrice4, c0_.discr AS discr5, c0_.salesPerson_id AS salesPerson_id6 FROM company_contracts c0_ WHERE c0_.discr IN ('flexible', 'flexultra')", array(Query::HINT_FORCE_PARTIAL_LOAD => false) ); } - + /** * @group DDC-1389 */ public function testInheritanceTypeSingleTableInChildClassWithEnabledForcePartialLoad() { $this->assertSqlGeneration( - 'SELECT fc FROM Doctrine\Tests\Models\Company\CompanyFlexContract fc', + 'SELECT fc FROM Doctrine\Tests\Models\Company\CompanyFlexContract fc', "SELECT c0_.id AS id0, c0_.completed AS completed1, c0_.hoursWorked AS hoursWorked2, c0_.pricePerHour AS pricePerHour3, c0_.maxPrice AS maxPrice4, c0_.discr AS discr5 FROM company_contracts c0_ WHERE c0_.discr IN ('flexible', 'flexultra')", array(Query::HINT_FORCE_PARTIAL_LOAD => true) ); } - + /** * @group DDC-1389 */ public function testInheritanceTypeSingleTableInLeafClassWithDisabledForcePartialLoad() { $this->assertSqlGeneration( - 'SELECT fuc FROM Doctrine\Tests\Models\Company\CompanyFlexUltraContract fuc', + 'SELECT fuc FROM Doctrine\Tests\Models\Company\CompanyFlexUltraContract fuc', "SELECT c0_.id AS id0, c0_.completed AS completed1, c0_.hoursWorked AS hoursWorked2, c0_.pricePerHour AS pricePerHour3, c0_.maxPrice AS maxPrice4, c0_.discr AS discr5, c0_.salesPerson_id AS salesPerson_id6 FROM company_contracts c0_ WHERE c0_.discr IN ('flexultra')", array(Query::HINT_FORCE_PARTIAL_LOAD => false) ); } - + /** * @group DDC-1389 */ public function testInheritanceTypeSingleTableInLeafClassWithEnabledForcePartialLoad() { $this->assertSqlGeneration( - 'SELECT fuc FROM Doctrine\Tests\Models\Company\CompanyFlexUltraContract fuc', + 'SELECT fuc FROM Doctrine\Tests\Models\Company\CompanyFlexUltraContract fuc', "SELECT c0_.id AS id0, c0_.completed AS completed1, c0_.hoursWorked AS hoursWorked2, c0_.pricePerHour AS pricePerHour3, c0_.maxPrice AS maxPrice4, c0_.discr AS discr5 FROM company_contracts c0_ WHERE c0_.discr IN ('flexultra')", array(Query::HINT_FORCE_PARTIAL_LOAD => true) ); } - + /** * @group DDC-1161 */ public function testSelfReferenceWithOneToOneDoesNotDuplicateAlias() { $this->assertSqlGeneration( - 'SELECT p, pp FROM Doctrine\Tests\Models\Company\CompanyPerson p JOIN p.spouse pp', + 'SELECT p, pp FROM Doctrine\Tests\Models\Company\CompanyPerson p JOIN p.spouse pp', "SELECT c0_.id AS id0, c0_.name AS name1, c1_.title AS title2, c1_.car_id AS car_id3, c2_.salary AS salary4, c2_.department AS department5, c2_.startDate AS startDate6, c3_.id AS id7, c3_.name AS name8, c4_.title AS title9, c4_.car_id AS car_id10, c5_.salary AS salary11, c5_.department AS department12, c5_.startDate AS startDate13, c0_.discr AS discr14, c0_.spouse_id AS spouse_id15, c3_.discr AS discr16, c3_.spouse_id AS spouse_id17 FROM company_persons c0_ LEFT JOIN company_managers c1_ ON c0_.id = c1_.id LEFT JOIN company_employees c2_ ON c0_.id = c2_.id INNER JOIN company_persons c3_ ON c0_.spouse_id = c3_.id LEFT JOIN company_managers c4_ ON c3_.id = c4_.id LEFT JOIN company_employees c5_ ON c3_.id = c5_.id", array(Query::HINT_FORCE_PARTIAL_LOAD => false) ); @@ -1255,13 +1255,25 @@ class SelectSqlGenerationTest extends \Doctrine\Tests\OrmTestCase /** * @group DDC-1384 */ - function testAliasDoesNotExceedPlatformDefinedLength() + public function testAliasDoesNotExceedPlatformDefinedLength() { $this->assertSqlGeneration( 'SELECT m FROM ' . __NAMESPACE__ . '\\DDC1384Model m', "SELECT d0_.aVeryLongIdentifierThatShouldBeShortenedByTheSQLWalker_fooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo AS fooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo0 FROM DDC1384Model d0_" ); } + + /** + * @group DDC-331 + * @group DDC-1384 + */ + public function testIssue331() + { + $this->assertSqlGeneration( + 'SELECT e.name FROM Doctrine\Tests\Models\Company\CompanyEmployee e', + 'SELECT c0_.name AS name0 FROM company_employees c1_ INNER JOIN company_persons c0_ ON c1_.id = c0_.id' + ); + } } @@ -1288,7 +1300,7 @@ class MyAbsFunction extends \Doctrine\ORM\Query\AST\Functions\FunctionNode $parser->match(\Doctrine\ORM\Query\Lexer::T_OPEN_PARENTHESIS); $this->simpleArithmeticExpression = $parser->SimpleArithmeticExpression(); - + $parser->match(\Doctrine\ORM\Query\Lexer::T_CLOSE_PARENTHESIS); } } From 30731e0727d05248e9da9dc3ef7956647637b3b2 Mon Sep 17 00:00:00 2001 From: Benjamin Eberlei Date: Sat, 29 Oct 2011 23:58:09 +0200 Subject: [PATCH 053/135] DDC-1384 - Fix all tests on Oracle --- lib/Doctrine/ORM/Tools/SchemaTool.php | 18 ++-- lib/Doctrine/ORM/Tools/ToolsException.php | 27 +++++ .../Tests/Models/Legacy/LegacyUser.php | 2 +- .../Models/Legacy/LegacyUserReference.php | 4 +- .../Tests/ORM/Functional/ReadOnlyTest.php | 8 +- .../ORM/Functional/Ticket/DDC1040Test.php | 8 +- .../ORM/Functional/Ticket/DDC1209Test.php | 2 +- .../ORM/Functional/Ticket/DDC1228Test.php | 44 ++++---- .../ORM/Functional/Ticket/DDC1238Test.php | 32 +++--- .../ORM/Functional/Ticket/DDC1335Test.php | 102 +++++++++--------- 10 files changed, 139 insertions(+), 108 deletions(-) diff --git a/lib/Doctrine/ORM/Tools/SchemaTool.php b/lib/Doctrine/ORM/Tools/SchemaTool.php index 77106ec7d..568a960a4 100644 --- a/lib/Doctrine/ORM/Tools/SchemaTool.php +++ b/lib/Doctrine/ORM/Tools/SchemaTool.php @@ -67,7 +67,9 @@ class SchemaTool /** * Creates the database schema for the given array of ClassMetadata instances. * + * @throws ToolsException * @param array $classes + * @return void */ public function createSchema(array $classes) { @@ -75,7 +77,11 @@ class SchemaTool $conn = $this->_em->getConnection(); foreach ($createSchemaSql as $sql) { - $conn->executeQuery($sql); + try { + $conn->executeQuery($sql); + } catch(\Exception $e) { + throw ToolsException::schemaToolFailure($sql, $e); + } } } @@ -94,7 +100,7 @@ class SchemaTool /** * Some instances of ClassMetadata don't need to be processed in the SchemaTool context. This method detects them. - * + * * @param ClassMetadata $class * @param array $processedClasses * @return bool @@ -551,7 +557,7 @@ class SchemaTool try { $conn->executeQuery($sql); } catch(\Exception $e) { - + } } } @@ -589,7 +595,7 @@ class SchemaTool /** * Get SQL to drop the tables defined by the passed classes. - * + * * @param array $classes * @return array */ @@ -615,7 +621,7 @@ class SchemaTool } } } - + if ($this->_platform->supportsSequences()) { foreach ($schema->getSequences() AS $sequence) { $visitor->acceptSequence($sequence); @@ -659,7 +665,7 @@ class SchemaTool /** * Gets the sequence of SQL statements that need to be performed in order * to bring the given class mappings in-synch with the relational schema. - * If $saveMode is set to true the command is executed in the Database, + * If $saveMode is set to true the command is executed in the Database, * else SQL is returned. * * @param array $classes The classes to consider. diff --git a/lib/Doctrine/ORM/Tools/ToolsException.php b/lib/Doctrine/ORM/Tools/ToolsException.php index f7ed87105..4551d87da 100644 --- a/lib/Doctrine/ORM/Tools/ToolsException.php +++ b/lib/Doctrine/ORM/Tools/ToolsException.php @@ -1,11 +1,38 @@ . + */ namespace Doctrine\ORM\Tools; use Doctrine\ORM\ORMException; +/** + * Tools related Exceptions + * + * @author Benjamin Eberlei + */ class ToolsException extends ORMException { + public static function schemaToolFailure($sql, \Exception $e) + { + return new self("Schema-Tool failed with Error '" . $e->getMessage() . "' while executing DDL: " . $sql, "0", $e); + } + public static function couldNotMapDoctrine1Type($type) { return new self("Could not map doctrine 1 type '$type'!"); diff --git a/tests/Doctrine/Tests/Models/Legacy/LegacyUser.php b/tests/Doctrine/Tests/Models/Legacy/LegacyUser.php index f4f5e1fcb..3e3deedb1 100644 --- a/tests/Doctrine/Tests/Models/Legacy/LegacyUser.php +++ b/tests/Doctrine/Tests/Models/Legacy/LegacyUser.php @@ -21,7 +21,7 @@ class LegacyUser */ public $_username; /** - * @Column(type="string", length=255) + * @Column(type="string", length=255, name="name") */ public $_name; /** diff --git a/tests/Doctrine/Tests/Models/Legacy/LegacyUserReference.php b/tests/Doctrine/Tests/Models/Legacy/LegacyUserReference.php index c6cd891a1..e666ae196 100644 --- a/tests/Doctrine/Tests/Models/Legacy/LegacyUserReference.php +++ b/tests/Doctrine/Tests/Models/Legacy/LegacyUserReference.php @@ -23,12 +23,12 @@ class LegacyUserReference private $_target; /** - * @column(type="string") + * @column(type="string", name="description") */ private $_description; /** - * @column(type="datetime") + * @column(type="datetime", name="created") */ private $_created; diff --git a/tests/Doctrine/Tests/ORM/Functional/ReadOnlyTest.php b/tests/Doctrine/Tests/ORM/Functional/ReadOnlyTest.php index 8a5819956..44832fc76 100644 --- a/tests/Doctrine/Tests/ORM/Functional/ReadOnlyTest.php +++ b/tests/Doctrine/Tests/ORM/Functional/ReadOnlyTest.php @@ -27,14 +27,14 @@ class ReadOnlyTest extends \Doctrine\Tests\OrmFunctionalTestCase $this->_em->flush(); $readOnly->name = "Test2"; - $readOnly->number = 4321; + $readOnly->numericValue = 4321; $this->_em->flush(); $this->_em->clear(); $dbReadOnly = $this->_em->find('Doctrine\Tests\ORM\Functional\ReadOnlyEntity', $readOnly->id); $this->assertEquals("Test1", $dbReadOnly->name); - $this->assertEquals(1234, $dbReadOnly->number); + $this->assertEquals(1234, $dbReadOnly->numericValue); } } @@ -51,11 +51,11 @@ class ReadOnlyEntity /** @column(type="string") */ public $name; /** @Column(type="integer") */ - public $number; + public $numericValue; public function __construct($name, $number) { $this->name = $name; - $this->number = $number; + $this->numericValue = $number; } } \ No newline at end of file diff --git a/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC1040Test.php b/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC1040Test.php index 21a042187..93dc0eea6 100644 --- a/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC1040Test.php +++ b/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC1040Test.php @@ -24,7 +24,7 @@ class DDC1040Test extends \Doctrine\Tests\OrmFunctionalTestCase $user->name = "John Galt"; $user->username = "jgalt"; $user->status = "inactive"; - + $article = new CmsArticle(); $article->topic = "This is John Galt speaking!"; $article->text = "Yadda Yadda!"; @@ -44,11 +44,10 @@ class DDC1040Test extends \Doctrine\Tests\OrmFunctionalTestCase ->setParameter('author', $user) ->getResult(); - $dql = "SELECT a FROM Doctrine\Tests\Models\CMS\CmsArticle a WHERE a.topic = :topic AND a.user = :author AND a.user = :author AND a.text = :text"; + $dql = "SELECT a FROM Doctrine\Tests\Models\CMS\CmsArticle a WHERE a.topic = :topic AND a.user = :author AND a.user = :author"; $farticle = $this->_em->createQuery($dql) ->setParameter('author', $user) ->setParameter('topic', 'This is John Galt speaking!') - ->setParameter('text', 'Yadda Yadda!') ->getSingleResult(); $this->assertSame($article, $farticle); @@ -70,12 +69,11 @@ class DDC1040Test extends \Doctrine\Tests\OrmFunctionalTestCase $this->_em->persist($article); $this->_em->flush(); - $dql = "SELECT a FROM Doctrine\Tests\Models\CMS\CmsArticle a WHERE a.topic = ?1 AND a.user = ?2 AND a.user = ?3 AND a.text = ?4"; + $dql = "SELECT a FROM Doctrine\Tests\Models\CMS\CmsArticle a WHERE a.topic = ?1 AND a.user = ?2 AND a.user = ?3"; $farticle = $this->_em->createQuery($dql) ->setParameter(1, 'This is John Galt speaking!') ->setParameter(2, $user) ->setParameter(3, $user) - ->setParameter(4, 'Yadda Yadda!') ->getSingleResult(); $this->assertSame($article, $farticle); diff --git a/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC1209Test.php b/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC1209Test.php index 472978bc2..bddbbdf44 100644 --- a/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC1209Test.php +++ b/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC1209Test.php @@ -106,7 +106,7 @@ class DDC1209_3 { /** * @Id - * @Column(type="datetime") + * @Column(type="datetime", name="somedate") */ private $date; diff --git a/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC1228Test.php b/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC1228Test.php index 34aef78cf..6e14e2111 100644 --- a/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC1228Test.php +++ b/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC1228Test.php @@ -21,58 +21,58 @@ class DDC1228Test extends \Doctrine\Tests\OrmFunctionalTestCase $this->_em->getClassMetadata(__NAMESPACE__ . '\\DDC1228User'), $this->_em->getClassMetadata(__NAMESPACE__ . '\\DDC1228Profile'), )); - } catch(\PDOException $e) { - + } catch(\Exception $e) { + } } - + public function testOneToOnePersist() { $user = new DDC1228User; $profile = new DDC1228Profile(); $profile->name = "Foo"; $user->profile = $profile; - + $this->_em->persist($user); $this->_em->persist($profile); $this->_em->flush(); $this->_em->clear(); - + $user = $this->_em->find(__NAMESPACE__ . '\\DDC1228User', $user->id); - + $this->assertFalse($user->getProfile()->__isInitialized__, "Proxy is not initialized"); $user->getProfile()->setName("Bar"); $this->assertTrue($user->getProfile()->__isInitialized__, "Proxy is not initialized"); - + $this->assertEquals("Bar", $user->getProfile()->getName()); $this->assertEquals(array("id" => 1, "name" => "Foo"), $this->_em->getUnitOfWork()->getOriginalEntityData($user->getProfile())); - + $this->_em->flush(); $this->_em->clear(); - + $user = $this->_em->find(__NAMESPACE__ . '\\DDC1228User', $user->id); $this->assertEquals("Bar", $user->getProfile()->getName()); } - + public function testRefresh() { $user = new DDC1228User; $profile = new DDC1228Profile(); $profile->name = "Foo"; $user->profile = $profile; - + $this->_em->persist($user); $this->_em->persist($profile); $this->_em->flush(); $this->_em->clear(); - + $user = $this->_em->getReference(__NAMESPACE__ . '\\DDC1228User', $user->id); - + $this->_em->refresh($user); $user->name = "Baz"; $this->_em->flush(); $this->_em->clear(); - + $user = $this->_em->find(__NAMESPACE__ . '\\DDC1228User', $user->id); $this->assertEquals("Baz", $user->name); } @@ -88,20 +88,20 @@ class DDC1228User * @var int */ public $id; - + /** - * @column(type="string") + * @Column(type="string") * @var string */ - public $name = ''; - + public $name = 'Bar'; + /** * @OneToOne(targetEntity="DDC1228Profile") * @var Profile */ public $profile; - - public function getProfile() + + public function getProfile() { return $this->profile; } @@ -117,13 +117,13 @@ class DDC1228Profile * @var int */ public $id; - + /** * @column(type="string") * @var string */ public $name; - + public function getName() { return $this->name; diff --git a/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC1238Test.php b/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC1238Test.php index 467577a43..6783928ef 100644 --- a/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC1238Test.php +++ b/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC1238Test.php @@ -19,49 +19,49 @@ class DDC1238Test extends \Doctrine\Tests\OrmFunctionalTestCase $this->_schemaTool->createSchema(array( $this->_em->getClassMetadata(__NAMESPACE__ . '\\DDC1238User'), )); - } catch(\PDOException $e) { - + } catch(\Exception $e) { + } } - + public function testIssue() { $user = new DDC1238User; $user->setName("test"); - + $this->_em->persist($user); $this->_em->flush(); $this->_em->clear(); - + $userId = $user->getId(); $this->_em->clear(); - + $user = $this->_em->getReference(__NAMESPACE__ . '\\DDC1238User', $userId); $this->_em->clear(); - + $userId2 = $user->getId(); $this->assertEquals($userId, $userId2, "This proxy can still be initialized."); } - + public function testIssueProxyClear() { $user = new DDC1238User; $user->setName("test"); - + $this->_em->persist($user); $this->_em->flush(); $this->_em->clear(); - + // force proxy load, getId() doesn't work anymore $user->getName(); $userId = $user->getId(); $this->_em->clear(); - + $user = $this->_em->getReference(__NAMESPACE__ . '\\DDC1238User', $userId); $this->_em->clear(); - + $user2 = $this->_em->getReference(__NAMESPACE__ . '\\DDC1238User', $userId); - + // force proxy load, getId() doesn't work anymore $user->getName(); $this->assertNull($user->getId(), "Now this is null, we already have a user instance of that type"); @@ -75,18 +75,18 @@ class DDC1238User { /** @Id @GeneratedValue @Column(type="integer") */ private $id; - + /** * @Column * @var string */ private $name; - + public function getId() { return $this->id; } - + public function getName() { return $this->name; diff --git a/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC1335Test.php b/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC1335Test.php index e12ee9ab7..e283b9f1c 100644 --- a/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC1335Test.php +++ b/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC1335Test.php @@ -7,56 +7,56 @@ use DateTime; require_once __DIR__ . '/../../../TestInit.php'; /** - * @group DDC-1135 + * @group DDC-1335 */ -class DDC1135Test extends \Doctrine\Tests\OrmFunctionalTestCase +class DDC1335Test extends \Doctrine\Tests\OrmFunctionalTestCase { protected function setUp() { parent::setUp(); try { $this->_schemaTool->createSchema(array( - $this->_em->getClassMetadata(__NAMESPACE__ . '\DDC1135User'), - $this->_em->getClassMetadata(__NAMESPACE__ . '\DDC1135Phone'), + $this->_em->getClassMetadata(__NAMESPACE__ . '\DDC1335User'), + $this->_em->getClassMetadata(__NAMESPACE__ . '\DDC1335Phone'), )); $this->loadFixture(); } catch(\Exception $e) { } } - - + + public function testDql() { - $dql = 'SELECT u FROM ' . __NAMESPACE__ . '\DDC1135User u INDEX BY u.id'; + $dql = 'SELECT u FROM ' . __NAMESPACE__ . '\DDC1335User u INDEX BY u.id'; $query = $this->_em->createQuery($dql); $result = $query->getResult(); - + $this->assertEquals(sizeof($result), 3); $this->assertArrayHasKey(1, $result); $this->assertArrayHasKey(2, $result); $this->assertArrayHasKey(3, $result); - - $dql = 'SELECT u, p FROM '.__NAMESPACE__ . '\DDC1135User u INDEX BY u.email INNER JOIN u.phones p INDEX BY p.id'; + + $dql = 'SELECT u, p FROM '.__NAMESPACE__ . '\DDC1335User u INDEX BY u.email INNER JOIN u.phones p INDEX BY p.id'; $query = $this->_em->createQuery($dql); $result = $query->getResult(); - + $this->assertEquals(sizeof($result), 3); $this->assertArrayHasKey('foo@foo.com', $result); $this->assertArrayHasKey('bar@bar.com', $result); $this->assertArrayHasKey('foobar@foobar.com', $result); - + $this->assertEquals(sizeof($result['foo@foo.com']->phones), 3); $this->assertEquals(sizeof($result['bar@bar.com']->phones), 3); $this->assertEquals(sizeof($result['foobar@foobar.com']->phones), 3); - + $this->assertArrayHasKey(1, $result['foo@foo.com']->phones->toArray()); $this->assertArrayHasKey(2, $result['foo@foo.com']->phones->toArray()); $this->assertArrayHasKey(3, $result['foo@foo.com']->phones->toArray()); - + $this->assertArrayHasKey(4, $result['bar@bar.com']->phones->toArray()); $this->assertArrayHasKey(5, $result['bar@bar.com']->phones->toArray()); $this->assertArrayHasKey(6, $result['bar@bar.com']->phones->toArray()); - + $this->assertArrayHasKey(7, $result['foobar@foobar.com']->phones->toArray()); $this->assertArrayHasKey(8, $result['foobar@foobar.com']->phones->toArray()); $this->assertArrayHasKey(9, $result['foobar@foobar.com']->phones->toArray()); @@ -65,77 +65,77 @@ class DDC1135Test extends \Doctrine\Tests\OrmFunctionalTestCase public function testTicket() { $builder = $this->_em->createQueryBuilder(); - $builder->select('u')->from(__NAMESPACE__ . '\DDC1135User', 'u', 'u.id'); + $builder->select('u')->from(__NAMESPACE__ . '\DDC1335User', 'u', 'u.id'); $dql = $builder->getQuery()->getDQL(); $result = $builder->getQuery()->getResult(); - + $this->assertEquals(sizeof($result), 3); $this->assertArrayHasKey(1, $result); $this->assertArrayHasKey(2, $result); $this->assertArrayHasKey(3, $result); - $this->assertEquals('SELECT u FROM ' . __NAMESPACE__ . '\DDC1135User u INDEX BY u.id', $dql); + $this->assertEquals('SELECT u FROM ' . __NAMESPACE__ . '\DDC1335User u INDEX BY u.id', $dql); } - + public function testIndexByUnique() { $builder = $this->_em->createQueryBuilder(); - $builder->select('u')->from(__NAMESPACE__ . '\DDC1135User', 'u', 'u.email'); + $builder->select('u')->from(__NAMESPACE__ . '\DDC1335User', 'u', 'u.email'); $dql = $builder->getQuery()->getDQL(); $result = $builder->getQuery()->getResult(); - + $this->assertEquals(sizeof($result), 3); $this->assertArrayHasKey('foo@foo.com', $result); $this->assertArrayHasKey('bar@bar.com', $result); $this->assertArrayHasKey('foobar@foobar.com', $result); - $this->assertEquals('SELECT u FROM ' . __NAMESPACE__ . '\DDC1135User u INDEX BY u.email', $dql); + $this->assertEquals('SELECT u FROM ' . __NAMESPACE__ . '\DDC1335User u INDEX BY u.email', $dql); } - + public function testIndexWithJoin() { $builder = $this->_em->createQueryBuilder(); $builder->select('u','p') - ->from(__NAMESPACE__ . '\DDC1135User', 'u', 'u.email') + ->from(__NAMESPACE__ . '\DDC1335User', 'u', 'u.email') ->join('u.phones', 'p', null, null, 'p.id'); - + $dql = $builder->getQuery()->getDQL(); $result = $builder->getQuery()->getResult(); - + $this->assertEquals(sizeof($result), 3); $this->assertArrayHasKey('foo@foo.com', $result); $this->assertArrayHasKey('bar@bar.com', $result); $this->assertArrayHasKey('foobar@foobar.com', $result); - + $this->assertEquals(sizeof($result['foo@foo.com']->phones), 3); $this->assertEquals(sizeof($result['bar@bar.com']->phones), 3); $this->assertEquals(sizeof($result['foobar@foobar.com']->phones), 3); - + $this->assertArrayHasKey(1, $result['foo@foo.com']->phones->toArray()); $this->assertArrayHasKey(2, $result['foo@foo.com']->phones->toArray()); $this->assertArrayHasKey(3, $result['foo@foo.com']->phones->toArray()); - + $this->assertArrayHasKey(4, $result['bar@bar.com']->phones->toArray()); $this->assertArrayHasKey(5, $result['bar@bar.com']->phones->toArray()); $this->assertArrayHasKey(6, $result['bar@bar.com']->phones->toArray()); - + $this->assertArrayHasKey(7, $result['foobar@foobar.com']->phones->toArray()); $this->assertArrayHasKey(8, $result['foobar@foobar.com']->phones->toArray()); $this->assertArrayHasKey(9, $result['foobar@foobar.com']->phones->toArray()); - - $this->assertEquals('SELECT u, p FROM '.__NAMESPACE__ . '\DDC1135User u INDEX BY u.email INNER JOIN u.phones p INDEX BY p.id', $dql); + + $this->assertEquals('SELECT u, p FROM '.__NAMESPACE__ . '\DDC1335User u INDEX BY u.email INNER JOIN u.phones p INDEX BY p.id', $dql); } - + private function loadFixture() { $p1 = array('11 xxxx-xxxx','11 yyyy-yyyy','11 zzzz-zzzz'); $p2 = array('22 xxxx-xxxx','22 yyyy-yyyy','22 zzzz-zzzz'); $p3 = array('33 xxxx-xxxx','33 yyyy-yyyy','33 zzzz-zzzz'); - - $u1 = new DDC1135User("foo@foo.com", "Foo",$p1); - $u2 = new DDC1135User("bar@bar.com", "Bar",$p2); - $u3 = new DDC1135User("foobar@foobar.com", "Foo Bar",$p3); - + + $u1 = new DDC1335User("foo@foo.com", "Foo",$p1); + $u2 = new DDC1335User("bar@bar.com", "Bar",$p2); + $u3 = new DDC1335User("foobar@foobar.com", "Foo Bar",$p3); + $this->_em->persist($u1); $this->_em->persist($u2); $this->_em->persist($u3); @@ -148,7 +148,7 @@ class DDC1135Test extends \Doctrine\Tests\OrmFunctionalTestCase /** * @Entity */ -class DDC1135User +class DDC1335User { /** * @Id @Column(type="integer") @@ -160,25 +160,25 @@ class DDC1135User * @Column(type="string", unique=true) */ public $email; - + /** * @Column(type="string") */ public $name; - + /** - * @OneToMany(targetEntity="DDC1135Phone", mappedBy="user", cascade={"persist", "remove"}) + * @OneToMany(targetEntity="DDC1335Phone", mappedBy="user", cascade={"persist", "remove"}) */ public $phones; - + public function __construct($email, $name, array $numbers = array()) { $this->name = $name; $this->email = $email; $this->phones = new \Doctrine\Common\Collections\ArrayCollection(); - + foreach ($numbers as $number) { - $this->phones->add(new DDC1135Phone($this,$number)); + $this->phones->add(new DDC1335Phone($this,$number)); } } } @@ -186,22 +186,22 @@ class DDC1135User /** * @Entity */ -class DDC1135Phone +class DDC1335Phone { /** * @Id * @Column(name="id", type="integer") - * @GeneratedValue(strategy="AUTO") + * @GeneratedValue */ public $id; /** - * @Column(name="number", type="string", nullable = false) + * @Column(name="numericalValue", type="string", nullable = false) */ - public $number; + public $numericalValue; /** - * @ManyToOne(targetEntity="DDC1135User", inversedBy="phones") + * @ManyToOne(targetEntity="DDC1335User", inversedBy="phones") * @JoinColumn(name="user_id", referencedColumnName="id", nullable = false) */ public $user; @@ -209,6 +209,6 @@ class DDC1135Phone public function __construct($user, $number) { $this->user = $user; - $this->number = $number; + $this->numericalValue = $number; } } \ No newline at end of file From 1bbec8dd33881aaf54a58d67ac3ae739a6f39f19 Mon Sep 17 00:00:00 2001 From: Benjamin Eberlei Date: Sun, 30 Oct 2011 00:25:49 +0200 Subject: [PATCH 054/135] Clarify error message --- tests/Doctrine/Tests/ORM/Functional/Ticket/DDC949Test.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC949Test.php b/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC949Test.php index 473a8679f..a3734b75a 100644 --- a/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC949Test.php +++ b/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC949Test.php @@ -34,10 +34,10 @@ class DDC949Test extends \Doctrine\Tests\OrmFunctionalTestCase $true = $this->_em->getRepository('Doctrine\Tests\Models\Generic\BooleanModel')->findOneBy(array('booleanField' => true)); $false = $this->_em->getRepository('Doctrine\Tests\Models\Generic\BooleanModel')->findOneBy(array('booleanField' => false)); - $this->assertInstanceOf('Doctrine\Tests\Models\Generic\BooleanModel', $true); + $this->assertInstanceOf('Doctrine\Tests\Models\Generic\BooleanModel', $true, "True model not found"); $this->assertTrue($true->booleanField, "True Boolean Model should be true."); - $this->assertInstanceOf('Doctrine\Tests\Models\Generic\BooleanModel', $false); + $this->assertInstanceOf('Doctrine\Tests\Models\Generic\BooleanModel', $false, "False model not found"); $this->assertFalse($false->booleanField, "False Boolean Model should be false."); } } \ No newline at end of file From 2c036b3185c4da38fb447b9d576e4219fe6af835 Mon Sep 17 00:00:00 2001 From: Benjamin Eberlei Date: Sun, 30 Oct 2011 08:25:19 +0100 Subject: [PATCH 055/135] Fix glitch in testsuites tearDown() --- .../ORM/Functional/EntityRepositoryTest.php | 58 ++++++++++--------- 1 file changed, 30 insertions(+), 28 deletions(-) diff --git a/tests/Doctrine/Tests/ORM/Functional/EntityRepositoryTest.php b/tests/Doctrine/Tests/ORM/Functional/EntityRepositoryTest.php index 4f8e11420..719a9f993 100644 --- a/tests/Doctrine/Tests/ORM/Functional/EntityRepositoryTest.php +++ b/tests/Doctrine/Tests/ORM/Functional/EntityRepositoryTest.php @@ -20,7 +20,9 @@ class EntityRepositoryTest extends \Doctrine\Tests\OrmFunctionalTestCase public function tearDown() { - $this->_em->getConfiguration()->setEntityNamespaces(array()); + if ($this->_em) { + $this->_em->getConfiguration()->setEntityNamespaces(array()); + } parent::tearDown(); } @@ -78,7 +80,7 @@ class EntityRepositoryTest extends \Doctrine\Tests\OrmFunctionalTestCase return array($user->id, $address->id); } - + public function buildUser($name, $username, $status, $address) { $user = new CmsUser(); @@ -89,10 +91,10 @@ class EntityRepositoryTest extends \Doctrine\Tests\OrmFunctionalTestCase $this->_em->persist($user); $this->_em->flush(); - + return $user; } - + public function buildAddress($country, $city, $street, $zip) { $address = new CmsAddress(); @@ -103,7 +105,7 @@ class EntityRepositoryTest extends \Doctrine\Tests\OrmFunctionalTestCase $this->_em->persist($address); $this->_em->flush(); - + return $address; } @@ -134,22 +136,22 @@ class EntityRepositoryTest extends \Doctrine\Tests\OrmFunctionalTestCase { $address1 = $this->buildAddress('Germany', 'Berlim', 'Foo st.', '123456'); $user1 = $this->buildUser('Benjamin', 'beberlei', 'dev', $address1); - + $address2 = $this->buildAddress('Brazil', 'São Paulo', 'Bar st.', '654321'); $user2 = $this->buildUser('Guilherme', 'guilhermeblanco', 'freak', $address2); - + $address3 = $this->buildAddress('USA', 'Nashville', 'Woo st.', '321654'); $user3 = $this->buildUser('Jonathan', 'jwage', 'dev', $address3); - + unset($address1); unset($address2); unset($address3); - + $this->_em->clear(); - + $repository = $this->_em->getRepository('Doctrine\Tests\Models\CMS\CmsAddress'); $addresses = $repository->findBy(array('user' => array($user1->getId(), $user2->getId()))); - + $this->assertEquals(2, count($addresses)); $this->assertInstanceOf('Doctrine\Tests\Models\CMS\CmsAddress',$addresses[0]); } @@ -158,22 +160,22 @@ class EntityRepositoryTest extends \Doctrine\Tests\OrmFunctionalTestCase { $address1 = $this->buildAddress('Germany', 'Berlim', 'Foo st.', '123456'); $user1 = $this->buildUser('Benjamin', 'beberlei', 'dev', $address1); - + $address2 = $this->buildAddress('Brazil', 'São Paulo', 'Bar st.', '654321'); $user2 = $this->buildUser('Guilherme', 'guilhermeblanco', 'freak', $address2); - + $address3 = $this->buildAddress('USA', 'Nashville', 'Woo st.', '321654'); $user3 = $this->buildUser('Jonathan', 'jwage', 'dev', $address3); - + unset($address1); unset($address2); unset($address3); - + $this->_em->clear(); - + $repository = $this->_em->getRepository('Doctrine\Tests\Models\CMS\CmsAddress'); $addresses = $repository->findBy(array('user' => array($user1, $user2))); - + $this->assertEquals(2, count($addresses)); $this->assertInstanceOf('Doctrine\Tests\Models\CMS\CmsAddress',$addresses[0]); } @@ -189,7 +191,7 @@ class EntityRepositoryTest extends \Doctrine\Tests\OrmFunctionalTestCase $this->assertEquals('Guilherme', $users[0]->name); $this->assertEquals('dev', $users[0]->status); } - + public function testFindAll() { $user1Id = $this->loadFixture(); @@ -280,7 +282,7 @@ class EntityRepositoryTest extends \Doctrine\Tests\OrmFunctionalTestCase $userId = $user->id; $this->_em->find('Doctrine\Tests\Models\CMS\CmsUser', $userId); - + $this->setExpectedException('Doctrine\ORM\OptimisticLockException'); $this->_em->find('Doctrine\Tests\Models\CMS\CmsUser', $userId, \Doctrine\DBAL\LockMode::OPTIMISTIC); } @@ -423,7 +425,7 @@ class EntityRepositoryTest extends \Doctrine\Tests\OrmFunctionalTestCase public function testFindByLimitOffset() { $this->loadFixture(); - + $repos = $this->_em->getRepository('Doctrine\Tests\Models\CMS\CmsUser'); $users1 = $repos->findBy(array(), null, 1, 0); @@ -451,8 +453,8 @@ class EntityRepositoryTest extends \Doctrine\Tests\OrmFunctionalTestCase $this->assertSame($usersAsc[0], $usersDesc[2]); $this->assertSame($usersAsc[2], $usersDesc[0]); } - - + + /** * @group DDC-753 */ @@ -465,19 +467,19 @@ class EntityRepositoryTest extends \Doctrine\Tests\OrmFunctionalTestCase $repos = $this->_em->getRepository('Doctrine\Tests\Models\DDC753\DDC753EntityWithDefaultCustomRepository'); $this->assertInstanceOf("Doctrine\Tests\Models\DDC753\DDC753DefaultRepository", $repos); $this->assertTrue($repos->isDefaultRepository()); - - + + $repos = $this->_em->getRepository('Doctrine\Tests\Models\DDC753\DDC753EntityWithCustomRepository'); $this->assertInstanceOf("Doctrine\Tests\Models\DDC753\DDC753CustomRepository", $repos); $this->assertTrue($repos->isCustomRepository()); - + $this->assertEquals($this->_em->getConfiguration()->getDefaultRepositoryClassName(), "Doctrine\Tests\Models\DDC753\DDC753DefaultRepository"); $this->_em->getConfiguration()->setDefaultRepositoryClassName("Doctrine\ORM\EntityRepository"); $this->assertEquals($this->_em->getConfiguration()->getDefaultRepositoryClassName(), "Doctrine\ORM\EntityRepository"); } - - + + /** * @group DDC-753 * @expectedException Doctrine\ORM\ORMException @@ -488,6 +490,6 @@ class EntityRepositoryTest extends \Doctrine\Tests\OrmFunctionalTestCase $this->assertEquals($this->_em->getConfiguration()->getDefaultRepositoryClassName(), "Doctrine\ORM\EntityRepository"); $this->_em->getConfiguration()->setDefaultRepositoryClassName("Doctrine\Tests\Models\DDC753\DDC753InvalidRepository"); } - + } From 3cfa479c01e927364edd8d5f96b86f602341d6b2 Mon Sep 17 00:00:00 2001 From: Guilherme Blanco Date: Sun, 30 Oct 2011 15:46:07 -0200 Subject: [PATCH 056/135] Micro optimization in computeChangeSet when using readOnly entities. --- lib/Doctrine/ORM/UnitOfWork.php | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/lib/Doctrine/ORM/UnitOfWork.php b/lib/Doctrine/ORM/UnitOfWork.php index e499d3c5f..6add3bd6b 100644 --- a/lib/Doctrine/ORM/UnitOfWork.php +++ b/lib/Doctrine/ORM/UnitOfWork.php @@ -471,19 +471,21 @@ class UnitOfWork implements PropertyChangedListener */ public function computeChangeSet(ClassMetadata $class, $entity) { - if ( ! $class->isInheritanceTypeNone()) { - $class = $this->em->getClassMetadata(get_class($entity)); - } - $oid = spl_object_hash($entity); if (isset($this->readOnlyObjects[$oid])) { return; } + if ( ! $class->isInheritanceTypeNone()) { + $class = $this->em->getClassMetadata(get_class($entity)); + } + $actualData = array(); + foreach ($class->reflFields as $name => $refProp) { $value = $refProp->getValue($entity); + if (isset($class->associationMappings[$name]) && ($class->associationMappings[$name]['type'] & ClassMetadata::TO_MANY) && $value !== null From c246c6b28b0569bed6ffd259df2a87a2f8ecf08f Mon Sep 17 00:00:00 2001 From: Guilherme Blanco Date: Sun, 30 Oct 2011 16:07:43 -0200 Subject: [PATCH 057/135] AssignedGenerator optimization. --- lib/Doctrine/ORM/Id/AssignedGenerator.php | 54 ++++++++--------------- 1 file changed, 19 insertions(+), 35 deletions(-) diff --git a/lib/Doctrine/ORM/Id/AssignedGenerator.php b/lib/Doctrine/ORM/Id/AssignedGenerator.php index 90a35fa12..2e2d4f2f6 100644 --- a/lib/Doctrine/ORM/Id/AssignedGenerator.php +++ b/lib/Doctrine/ORM/Id/AssignedGenerator.php @@ -20,6 +20,7 @@ namespace Doctrine\ORM\Id; use Doctrine\ORM\EntityManager; +use Doctrine\ORM\Mapping\ClassMetadata; use Doctrine\ORM\ORMException; /** @@ -42,46 +43,29 @@ class AssignedGenerator extends AbstractIdGenerator */ public function generate(EntityManager $em, $entity) { - $class = $em->getClassMetadata(get_class($entity)); + $class = $em->getClassMetadata(get_class($entity)); + $idFields = $class->getIdentifierFieldNames(); $identifier = array(); - if ($class->isIdentifierComposite) { - $idFields = $class->getIdentifierFieldNames(); - foreach ($idFields as $idField) { - $value = $class->reflFields[$idField]->getValue($entity); - if (isset($value)) { - if (isset($class->associationMappings[$idField])) { - if (!$em->getUnitOfWork()->isInIdentityMap($value)) { - throw ORMException::entityMissingForeignAssignedId($entity, $value); - } - - // NOTE: Single Columns as associated identifiers only allowed - this constraint it is enforced. - $identifier[$idField] = current($em->getUnitOfWork()->getEntityIdentifier($value)); - } else { - $identifier[$idField] = $value; - } - } else { - throw ORMException::entityMissingAssignedIdForField($entity, $idField); - } - } - } else { - $idField = $class->identifier[0]; + + foreach ($idFields as $idField) { $value = $class->reflFields[$idField]->getValue($entity); - if (isset($value)) { - if (isset($class->associationMappings[$idField])) { - if (!$em->getUnitOfWork()->isInIdentityMap($value)) { - throw ORMException::entityMissingForeignAssignedId($entity, $value); - } - - // NOTE: Single Columns as associated identifiers only allowed - this constraint it is enforced. - $identifier[$idField] = current($em->getUnitOfWork()->getEntityIdentifier($value)); - } else { - $identifier[$idField] = $value; - } - } else { + + if ( ! isset($value)) { throw ORMException::entityMissingAssignedIdForField($entity, $idField); } - } + + if (isset($class->associationMappings[$idField])) { + if ( ! $em->getUnitOfWork()->isInIdentityMap($value)) { + throw ORMException::entityMissingForeignAssignedId($entity, $value); + } + // NOTE: Single Columns as associated identifiers only allowed - this constraint it is enforced. + $value = current($em->getUnitOfWork()->getEntityIdentifier($value)); + } + + $identifier[$idField] = $value; + } + return $identifier; } } From d444f0e06b6d588d62bdf3493a282cf61bfcd956 Mon Sep 17 00:00:00 2001 From: Guilherme Blanco Date: Sun, 30 Oct 2011 16:22:45 -0200 Subject: [PATCH 058/135] Micro optimization in SqlWalker. --- lib/Doctrine/ORM/Query/SqlWalker.php | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/lib/Doctrine/ORM/Query/SqlWalker.php b/lib/Doctrine/ORM/Query/SqlWalker.php index 45837bf1f..d90dc28f2 100644 --- a/lib/Doctrine/ORM/Query/SqlWalker.php +++ b/lib/Doctrine/ORM/Query/SqlWalker.php @@ -980,17 +980,13 @@ class SqlWalker implements TreeWalker $qComp = $this->_queryComponents[$dqlAlias]; $class = $qComp['metadata']; - if ( ! $selectExpression->fieldIdentificationVariable) { - $resultAlias = $fieldName; - } else { - $resultAlias = $selectExpression->fieldIdentificationVariable; - } + $resultAlias = ( ! $selectExpression->fieldIdentificationVariable) + ? $fieldName + : $selectExpression->fieldIdentificationVariable; - if ($class->isInheritanceTypeJoined()) { - $tableName = $this->_em->getUnitOfWork()->getEntityPersister($class->name)->getOwningTable($fieldName); - } else { - $tableName = $class->getTableName(); - } + $tableName = ($class->isInheritanceTypeJoined()) + ? $this->_em->getUnitOfWork()->getEntityPersister($class->name)->getOwningTable($fieldName) + : $class->getTableName(); $sqlTableAlias = $this->getSQLTableAlias($tableName, $dqlAlias); $columnName = $class->getQuotedColumnName($fieldName, $this->_platform); From 11f82bd41f021560bcbb4d611c51d2c4b71715fb Mon Sep 17 00:00:00 2001 From: Benjamin Eberlei Date: Mon, 31 Oct 2011 20:49:28 +0100 Subject: [PATCH 059/135] DDC-1439 - Fix validate mapping some more --- lib/Doctrine/ORM/Tools/SchemaValidator.php | 306 +++++++++--------- .../Tests/ORM/Tools/SchemaValidatorTest.php | 84 +++++ 2 files changed, 245 insertions(+), 145 deletions(-) diff --git a/lib/Doctrine/ORM/Tools/SchemaValidator.php b/lib/Doctrine/ORM/Tools/SchemaValidator.php index a7f8e3a1c..2ddd0bf66 100644 --- a/lib/Doctrine/ORM/Tools/SchemaValidator.php +++ b/lib/Doctrine/ORM/Tools/SchemaValidator.php @@ -50,7 +50,7 @@ class SchemaValidator } /** - * Checks the internal consistency of mapping files. + * Checks the internal consistency of all mapping files. * * There are several checks that can't be done at runtime or are too expensive, which can be verified * with this command. For example: @@ -69,150 +69,7 @@ class SchemaValidator $classes = $cmf->getAllMetadata(); foreach ($classes AS $class) { - $ce = array(); - /* @var $class ClassMetadata */ - foreach ($class->associationMappings AS $fieldName => $assoc) { - if (!$cmf->hasMetadataFor($assoc['targetEntity'])) { - $ce[] = "The target entity '" . $assoc['targetEntity'] . "' specified on " . $class->name . '#' . $fieldName . ' is unknown.'; - } - - if ($assoc['mappedBy'] && $assoc['inversedBy']) { - $ce[] = "The association " . $class . "#" . $fieldName . " cannot be defined as both inverse and owning."; - } - - $targetMetadata = $cmf->getMetadataFor($assoc['targetEntity']); - - /* @var $assoc AssociationMapping */ - if ($assoc['mappedBy']) { - if ($targetMetadata->hasField($assoc['mappedBy'])) { - $ce[] = "The association " . $class->name . "#" . $fieldName . " refers to the owning side ". - "field " . $assoc['targetEntity'] . "#" . $assoc['mappedBy'] . " which is not defined as association."; - } - if (!$targetMetadata->hasAssociation($assoc['mappedBy'])) { - $ce[] = "The association " . $class->name . "#" . $fieldName . " refers to the owning side ". - "field " . $assoc['targetEntity'] . "#" . $assoc['mappedBy'] . " which does not exist."; - } else if ($targetMetadata->associationMappings[$assoc['mappedBy']]['inversedBy'] == null) { - $ce[] = "The field " . $class->name . "#" . $fieldName . " is on the inverse side of a ". - "bi-directional relationship, but the specified mappedBy association on the target-entity ". - $assoc['targetEntity'] . "#" . $assoc['mappedBy'] . " does not contain the required ". - "'inversedBy' attribute."; - } else if ($targetMetadata->associationMappings[$assoc['mappedBy']]['inversedBy'] != $fieldName) { - $ce[] = "The mappings " . $class->name . "#" . $fieldName . " and " . - $assoc['targetEntity'] . "#" . $assoc['mappedBy'] . " are ". - "incosistent with each other."; - } - } - - if ($assoc['inversedBy']) { - if ($targetMetadata->hasField($assoc['inversedBy'])) { - $ce[] = "The association " . $class->name . "#" . $fieldName . " refers to the inverse side ". - "field " . $assoc['targetEntity'] . "#" . $assoc['inversedBy'] . " which is not defined as association."; - } - if (!$targetMetadata->hasAssociation($assoc['inversedBy'])) { - $ce[] = "The association " . $class->name . "#" . $fieldName . " refers to the inverse side ". - "field " . $assoc['targetEntity'] . "#" . $assoc['inversedBy'] . " which does not exist."; - } else if ($targetMetadata->associationMappings[$assoc['inversedBy']]['mappedBy'] == null) { - $ce[] = "The field " . $class->name . "#" . $fieldName . " is on the owning side of a ". - "bi-directional relationship, but the specified mappedBy association on the target-entity ". - $assoc['targetEntity'] . "#" . $assoc['mappedBy'] . " does not contain the required ". - "'inversedBy' attribute."; - } else if ($targetMetadata->associationMappings[$assoc['inversedBy']]['mappedBy'] != $fieldName) { - $ce[] = "The mappings " . $class->name . "#" . $fieldName . " and " . - $assoc['targetEntity'] . "#" . $assoc['inversedBy'] . " are ". - "incosistent with each other."; - } - } - - if ($assoc['isOwningSide']) { - if ($assoc['type'] == ClassMetadataInfo::MANY_TO_MANY) { - foreach ($assoc['joinTable']['joinColumns'] AS $joinColumn) { - if (!isset($class->fieldNames[$joinColumn['referencedColumnName']])) { - $ce[] = "The referenced column name '" . $joinColumn['referencedColumnName'] . "' does not " . - "have a corresponding field with this column name on the class '" . $class->name . "'."; - break; - } - - $fieldName = $class->fieldNames[$joinColumn['referencedColumnName']]; - if (!in_array($fieldName, $class->identifier)) { - $ce[] = "The referenced column name '" . $joinColumn['referencedColumnName'] . "' " . - "has to be a primary key column."; - } - } - foreach ($assoc['joinTable']['inverseJoinColumns'] AS $inverseJoinColumn) { - $targetClass = $cmf->getMetadataFor($assoc['targetEntity']); - if (!isset($targetClass->fieldNames[$inverseJoinColumn['referencedColumnName']])) { - $ce[] = "The inverse referenced column name '" . $inverseJoinColumn['referencedColumnName'] . "' does not " . - "have a corresponding field with this column name on the class '" . $targetClass->name . "'."; - break; - } - - $fieldName = $targetClass->fieldNames[$inverseJoinColumn['referencedColumnName']]; - if (!in_array($fieldName, $targetClass->identifier)) { - $ce[] = "The referenced column name '" . $inverseJoinColumn['referencedColumnName'] . "' " . - "has to be a primary key column."; - } - } - - if (count($targetClass->identifier) != count($assoc['joinTable']['inverseJoinColumns'])) { - $ce[] = "The inverse join columns of the many-to-many table '" . $assoc['joinTable']['name'] . "' " . - "have to match to ALL identifier columns of the target entity '". $targetClass->name . "'"; - } - - if (count($class->identifier) != count($assoc['joinTable']['joinColumns'])) { - $ce[] = "The join columns of the many-to-many table '" . $assoc['joinTable']['name'] . "' " . - "have to match to ALL identifier columns of the source entity '". $class->name . "'"; - } - - } else if ($assoc['type'] & ClassMetadataInfo::TO_ONE) { - foreach ($assoc['joinColumns'] AS $joinColumn) { - $targetClass = $cmf->getMetadataFor($assoc['targetEntity']); - if (!isset($targetClass->fieldNames[$joinColumn['referencedColumnName']])) { - $ce[] = "The referenced column name '" . $joinColumn['referencedColumnName'] . "' does not " . - "have a corresponding field with this column name on the class '" . $targetClass->name . "'."; - break; - } - - $fieldName = $targetClass->fieldNames[$joinColumn['referencedColumnName']]; - if (!in_array($fieldName, $targetClass->identifier)) { - $ce[] = "The referenced column name '" . $joinColumn['referencedColumnName'] . "' " . - "has to be a primary key column."; - } - } - - if (count($class->identifier) != count($assoc['joinColumns'])) { - $ce[] = "The join columns of the association '" . $assoc['fieldName'] . "' " . - "have to match to ALL identifier columns of the source entity '". $class->name . "'"; - } - } - } - - if (isset($assoc['orderBy']) && $assoc['orderBy'] !== null) { - $targetClass = $cmf->getMetadataFor($assoc['targetEntity']); - foreach ($assoc['orderBy'] AS $orderField => $orientation) { - if (!$targetClass->hasField($orderField)) { - $ce[] = "The association " . $class->name."#".$fieldName." is ordered by a foreign field " . - $orderField . " that is not a field on the target entity " . $targetClass->name; - } - } - } - } - - foreach ($class->reflClass->getProperties(\ReflectionProperty::IS_PUBLIC) as $publicAttr) { - if ($publicAttr->isStatic()) { - continue; - } - $ce[] = "Field '".$publicAttr->getName()."' in class '".$class->name."' must be private ". - "or protected. Public fields may break lazy-loading."; - } - - foreach ($class->subClasses AS $subClass) { - if (!in_array($class->name, class_parents($subClass))) { - $ce[] = "According to the discriminator map class '" . $subClass . "' has to be a child ". - "of '" . $class->name . "' but these entities are not related through inheritance."; - } - } - - if ($ce) { + if ($ce = $this->validateClass($class)) { $errors[$class->name] = $ce; } } @@ -220,6 +77,165 @@ class SchemaValidator return $errors; } + /** + * Validate a single class of the current + * + * @param ClassMetadataInfo $class + * @return array + */ + public function validateClass(ClassMetadataInfo $class) + { + $ce = array(); + $cmf = $this->em->getMetadataFactory(); + + foreach ($class->associationMappings AS $fieldName => $assoc) { + if (!$cmf->hasMetadataFor($assoc['targetEntity'])) { + $ce[] = "The target entity '" . $assoc['targetEntity'] . "' specified on " . $class->name . '#' . $fieldName . ' is unknown.'; + return $ce; + } + + if ($assoc['mappedBy'] && $assoc['inversedBy']) { + $ce[] = "The association " . $class . "#" . $fieldName . " cannot be defined as both inverse and owning."; + } + + $targetMetadata = $cmf->getMetadataFor($assoc['targetEntity']); + + /* @var $assoc AssociationMapping */ + if ($assoc['mappedBy']) { + if ($targetMetadata->hasField($assoc['mappedBy'])) { + $ce[] = "The association " . $class->name . "#" . $fieldName . " refers to the owning side ". + "field " . $assoc['targetEntity'] . "#" . $assoc['mappedBy'] . " which is not defined as association."; + } + if (!$targetMetadata->hasAssociation($assoc['mappedBy'])) { + $ce[] = "The association " . $class->name . "#" . $fieldName . " refers to the owning side ". + "field " . $assoc['targetEntity'] . "#" . $assoc['mappedBy'] . " which does not exist."; + } else if ($targetMetadata->associationMappings[$assoc['mappedBy']]['inversedBy'] == null) { + $ce[] = "The field " . $class->name . "#" . $fieldName . " is on the inverse side of a ". + "bi-directional relationship, but the specified mappedBy association on the target-entity ". + $assoc['targetEntity'] . "#" . $assoc['mappedBy'] . " does not contain the required ". + "'inversedBy' attribute."; + } else if ($targetMetadata->associationMappings[$assoc['mappedBy']]['inversedBy'] != $fieldName) { + $ce[] = "The mappings " . $class->name . "#" . $fieldName . " and " . + $assoc['targetEntity'] . "#" . $assoc['mappedBy'] . " are ". + "incosistent with each other."; + } + } + + if ($assoc['inversedBy']) { + if ($targetMetadata->hasField($assoc['inversedBy'])) { + $ce[] = "The association " . $class->name . "#" . $fieldName . " refers to the inverse side ". + "field " . $assoc['targetEntity'] . "#" . $assoc['inversedBy'] . " which is not defined as association."; + } + if (!$targetMetadata->hasAssociation($assoc['inversedBy'])) { + $ce[] = "The association " . $class->name . "#" . $fieldName . " refers to the inverse side ". + "field " . $assoc['targetEntity'] . "#" . $assoc['inversedBy'] . " which does not exist."; + } else if ($targetMetadata->associationMappings[$assoc['inversedBy']]['mappedBy'] == null) { + $ce[] = "The field " . $class->name . "#" . $fieldName . " is on the owning side of a ". + "bi-directional relationship, but the specified mappedBy association on the target-entity ". + $assoc['targetEntity'] . "#" . $assoc['mappedBy'] . " does not contain the required ". + "'inversedBy' attribute."; + } else if ($targetMetadata->associationMappings[$assoc['inversedBy']]['mappedBy'] != $fieldName) { + $ce[] = "The mappings " . $class->name . "#" . $fieldName . " and " . + $assoc['targetEntity'] . "#" . $assoc['inversedBy'] . " are ". + "incosistent with each other."; + } + } + + if ($assoc['isOwningSide']) { + if ($assoc['type'] == ClassMetadataInfo::MANY_TO_MANY) { + foreach ($assoc['joinTable']['joinColumns'] AS $joinColumn) { + if (!isset($class->fieldNames[$joinColumn['referencedColumnName']])) { + $ce[] = "The referenced column name '" . $joinColumn['referencedColumnName'] . "' does not " . + "have a corresponding field with this column name on the class '" . $class->name . "'."; + break; + } + + $fieldName = $class->fieldNames[$joinColumn['referencedColumnName']]; + if (!in_array($fieldName, $class->identifier)) { + $ce[] = "The referenced column name '" . $joinColumn['referencedColumnName'] . "' " . + "has to be a primary key column."; + } + } + foreach ($assoc['joinTable']['inverseJoinColumns'] AS $inverseJoinColumn) { + if (!isset($targetMetadata->fieldNames[$inverseJoinColumn['referencedColumnName']])) { + $ce[] = "The inverse referenced column name '" . $inverseJoinColumn['referencedColumnName'] . "' does not " . + "have a corresponding field with this column name on the class '" . $targetMetadata->name . "'."; + break; + } + + $fieldName = $targetMetadata->fieldNames[$inverseJoinColumn['referencedColumnName']]; + if (!in_array($fieldName, $targetMetadata->identifier)) { + $ce[] = "The referenced column name '" . $inverseJoinColumn['referencedColumnName'] . "' " . + "has to be a primary key column."; + } + } + + if (count($targetMetadata->getIdentifierColumnNames()) != count($assoc['joinTable']['inverseJoinColumns'])) { + $ce[] = "The inverse join columns of the many-to-many table '" . $assoc['joinTable']['name'] . "' " . + "have to contain to ALL identifier columns of the target entity '". $targetMetadata->name . "', " . + "however '" . implode(", ", array_diff($targetMetadata->getIdentifierColumnNames(), $assoc['relationToTargetKeyColumns'])) . + "' are missing."; + } + + if (count($class->getIdentifierColumnNames()) != count($assoc['joinTable']['joinColumns'])) { + $ce[] = "The join columns of the many-to-many table '" . $assoc['joinTable']['name'] . "' " . + "have to contain to ALL identifier columns of the source entity '". $class->name . "', " . + "however '" . implode(", ", array_diff($class->getIdentifierColumnNames(), $assoc['relationToSourceKeyColumns'])) . + "' are missing."; + } + + } else if ($assoc['type'] & ClassMetadataInfo::TO_ONE) { + foreach ($assoc['joinColumns'] AS $joinColumn) { + if (!isset($targetMetadata->fieldNames[$joinColumn['referencedColumnName']])) { + $ce[] = "The referenced column name '" . $joinColumn['referencedColumnName'] . "' does not " . + "have a corresponding field with this column name on the class '" . $targetMetadata->name . "'."; + break; + } + + $fieldName = $targetMetadata->fieldNames[$joinColumn['referencedColumnName']]; + if (!in_array($fieldName, $targetMetadata->identifier)) { + $ce[] = "The referenced column name '" . $joinColumn['referencedColumnName'] . "' " . + "has to be a primary key column."; + } + } + + if (count($class->getIdentifierColumnNames()) != count($assoc['joinColumns'])) { + $ce[] = "The join columns of the association '" . $assoc['fieldName'] . "' " . + "have to match to ALL identifier columns of the source entity '". $class->name . "', " . + "however '" . implode(", ", array_diff($class->getIdentifierColumnNames(), $assoc['joinColumns'])) . + "' are missing."; + } + } + } + + if (isset($assoc['orderBy']) && $assoc['orderBy'] !== null) { + foreach ($assoc['orderBy'] AS $orderField => $orientation) { + if (!$targetMetadata->hasField($orderField)) { + $ce[] = "The association " . $class->name."#".$fieldName." is ordered by a foreign field " . + $orderField . " that is not a field on the target entity " . $targetMetadata->name; + } + } + } + } + + foreach ($class->reflClass->getProperties(\ReflectionProperty::IS_PUBLIC) as $publicAttr) { + if ($publicAttr->isStatic()) { + continue; + } + $ce[] = "Field '".$publicAttr->getName()."' in class '".$class->name."' must be private ". + "or protected. Public fields may break lazy-loading."; + } + + foreach ($class->subClasses AS $subClass) { + if (!in_array($class->name, class_parents($subClass))) { + $ce[] = "According to the discriminator map class '" . $subClass . "' has to be a child ". + "of '" . $class->name . "' but these entities are not related through inheritance."; + } + } + + return $ce; + } + /** * Check if the Database Schema is in sync with the current metadata state. * diff --git a/tests/Doctrine/Tests/ORM/Tools/SchemaValidatorTest.php b/tests/Doctrine/Tests/ORM/Tools/SchemaValidatorTest.php index 64bb03f36..0fc3bb636 100644 --- a/tests/Doctrine/Tests/ORM/Tools/SchemaValidatorTest.php +++ b/tests/Doctrine/Tests/ORM/Tools/SchemaValidatorTest.php @@ -71,4 +71,88 @@ class SchemaValidatorTest extends \Doctrine\Tests\OrmTestCase )); $this->validator->validateMapping(); } + + /** + * @group DDC-1439 + */ + public function testInvalidManyToManyJoinColumnSchema() + { + $class1 = $this->em->getClassMetadata(__NAMESPACE__ . '\InvalidEntity1'); + $class2 = $this->em->getClassMetadata(__NAMESPACE__ . '\InvalidEntity2'); + + $ce = $this->validator->validateClass($class1); + + $this->assertEquals( + array( + "The inverse join columns of the many-to-many table 'Entity1Entity2' have to contain to ALL identifier columns of the target entity 'Doctrine\Tests\ORM\Tools\InvalidEntity2', however 'key4' are missing.", + "The join columns of the many-to-many table 'Entity1Entity2' have to contain to ALL identifier columns of the source entity 'Doctrine\Tests\ORM\Tools\InvalidEntity1', however 'key2' are missing." + ), + $ce + ); + } + + /** + * @group DDC-1439 + */ + public function testInvalidToOneJoinColumnSchema() + { + $class1 = $this->em->getClassMetadata(__NAMESPACE__ . '\InvalidEntity1'); + $class2 = $this->em->getClassMetadata(__NAMESPACE__ . '\InvalidEntity2'); + + $ce = $this->validator->validateClass($class2); + + $this->assertEquals( + array( + "The referenced column name 'id' does not have a corresponding field with this column name on the class 'Doctrine\Tests\ORM\Tools\InvalidEntity1'.", + "The join columns of the association 'assoc' have to match to ALL identifier columns of the source entity 'Doctrine\Tests\ORM\Tools\InvalidEntity2', however 'key3, key4' are missing." + ), + $ce + ); + } +} + +/** + * @Entity + */ +class InvalidEntity1 +{ + /** + * @Id @Column + */ + protected $key1; + /** + * @Id @Column + */ + protected $key2; + /** + * @ManyToMany (targetEntity="InvalidEntity2") + * @JoinTable (name="Entity1Entity2", + * joinColumns={@JoinColumn(name="key1", referencedColumnName="key1")}, + * inverseJoinColumns={@JoinColumn(name="key3", referencedColumnName="key3")} + * ) + */ + protected $entity2; +} + +/** + * @Entity + */ +class InvalidEntity2 +{ + /** + * @Id @Column + * @GeneratedValue(strategy="AUTO") + */ + protected $key3; + + /** + * @Id @Column + * @GeneratedValue(strategy="AUTO") + */ + protected $key4; + + /** + * @ManyToOne(targetEntity="InvalidEntity1") + */ + protected $assoc; } \ No newline at end of file From 95193ab5f84a8f2a82c969ae784a7bd049fcefee Mon Sep 17 00:00:00 2001 From: Benjamin Eberlei Date: Mon, 31 Oct 2011 21:17:01 +0100 Subject: [PATCH 060/135] DDC-1399 - Fix extra lazy collections when inner collection contains values but persistent collection is marked not dirty because of flush() --- lib/Doctrine/ORM/PersistentCollection.php | 46 +++++++++---------- .../Functional/ExtraLazyCollectionTest.php | 32 +++++++++++-- 2 files changed, 50 insertions(+), 28 deletions(-) diff --git a/lib/Doctrine/ORM/PersistentCollection.php b/lib/Doctrine/ORM/PersistentCollection.php index 82d616686..9840bead2 100644 --- a/lib/Doctrine/ORM/PersistentCollection.php +++ b/lib/Doctrine/ORM/PersistentCollection.php @@ -93,21 +93,21 @@ final class PersistentCollection implements Collection /** * Whether the collection has already been initialized. - * + * * @var boolean */ private $initialized = true; - + /** * The wrapped Collection instance. - * + * * @var Collection */ private $coll; /** * Creates a new persistent collection. - * + * * @param EntityManager $em The EntityManager the collection will be associated with. * @param ClassMetadata $class The class descriptor of the entity type of this collection. * @param array The collection elements. @@ -144,7 +144,7 @@ final class PersistentCollection implements Collection { return $this->owner; } - + public function getTypeClass() { return $this->typeClass; @@ -154,7 +154,7 @@ final class PersistentCollection implements Collection * INTERNAL: * Adds an element to a collection during hydration. This will automatically * complete bidirectional associations in the case of a one-to-many association. - * + * * @param mixed $element The element to add. */ public function hydrateAdd($element) @@ -172,7 +172,7 @@ final class PersistentCollection implements Collection $this->owner); } } - + /** * INTERNAL: * Sets a keyed element in the collection during hydration. @@ -271,7 +271,7 @@ final class PersistentCollection implements Collection { return $this->association; } - + /** * Marks this collection as changed/dirty. */ @@ -306,17 +306,17 @@ final class PersistentCollection implements Collection { $this->isDirty = $dirty; } - + /** * Sets the initialized flag of the collection, forcing it into that state. - * + * * @param boolean $bool */ public function setInitialized($bool) { $this->initialized = $bool; } - + /** * Checks whether this collection has been initialized. * @@ -377,7 +377,7 @@ final class PersistentCollection implements Collection $this->em->getUnitOfWork()->getCollectionPersister($this->association) ->deleteRows($this, $element); }*/ - + $this->initialize(); $removed = $this->coll->removeElement($element); if ($removed) { @@ -410,7 +410,7 @@ final class PersistentCollection implements Collection ->getCollectionPersister($this->association) ->contains($this, $element); } - + $this->initialize(); return $this->coll->contains($element); } @@ -468,7 +468,7 @@ final class PersistentCollection implements Collection if (!$this->initialized && $this->association['fetch'] == Mapping\ClassMetadataInfo::FETCH_EXTRA_LAZY) { return $this->em->getUnitOfWork() ->getCollectionPersister($this->association) - ->count($this) + $this->coll->count(); + ->count($this) + ($this->isDirty ? $this->coll->count() : 0); } $this->initialize(); @@ -503,7 +503,7 @@ final class PersistentCollection implements Collection $this->initialize(); return $this->coll->isEmpty(); } - + /** * {@inheritdoc} */ @@ -530,7 +530,7 @@ final class PersistentCollection implements Collection $this->initialize(); return $this->coll->filter($p); } - + /** * {@inheritdoc} */ @@ -548,7 +548,7 @@ final class PersistentCollection implements Collection $this->initialize(); return $this->coll->partition($p); } - + /** * {@inheritdoc} */ @@ -579,7 +579,7 @@ final class PersistentCollection implements Collection $this->takeSnapshot(); } } - + /** * Called by PHP when this collection is serialized. Ensures that only the * elements are properly serialized. @@ -591,7 +591,7 @@ final class PersistentCollection implements Collection { return array('coll', 'initialized'); } - + /* ArrayAccess implementation */ /** @@ -629,12 +629,12 @@ final class PersistentCollection implements Collection { return $this->remove($offset); } - + public function key() { return $this->coll->key(); } - + /** * Gets the element of the collection at the current iterator position. */ @@ -642,7 +642,7 @@ final class PersistentCollection implements Collection { return $this->coll->current(); } - + /** * Moves the internal iterator position to the next element. */ @@ -650,7 +650,7 @@ final class PersistentCollection implements Collection { return $this->coll->next(); } - + /** * Retrieves the wrapped Collection instance. */ diff --git a/tests/Doctrine/Tests/ORM/Functional/ExtraLazyCollectionTest.php b/tests/Doctrine/Tests/ORM/Functional/ExtraLazyCollectionTest.php index 4bf010602..43b45650f 100644 --- a/tests/Doctrine/Tests/ORM/Functional/ExtraLazyCollectionTest.php +++ b/tests/Doctrine/Tests/ORM/Functional/ExtraLazyCollectionTest.php @@ -29,7 +29,7 @@ class ExtraLazyCollectionTest extends \Doctrine\Tests\OrmFunctionalTestCase $class = $this->_em->getClassMetadata('Doctrine\Tests\Models\CMS\CmsGroup'); $class->associationMappings['users']['fetch'] = ClassMetadataInfo::FETCH_EXTRA_LAZY; - + $this->loadFixture(); } @@ -137,9 +137,9 @@ class ExtraLazyCollectionTest extends \Doctrine\Tests\OrmFunctionalTestCase { $user = $this->_em->find('Doctrine\Tests\Models\CMS\CmsUser', $this->userId); $this->assertFalse($user->groups->isInitialized(), "Pre-Condition: Collection is not initialized."); - + $queryCount = $this->getCurrentQueryCount(); - + $someGroups = $user->groups->slice(0, 2); $this->assertContainsOnly('Doctrine\Tests\Models\CMS\CmsGroup', $someGroups); @@ -225,7 +225,7 @@ class ExtraLazyCollectionTest extends \Doctrine\Tests\OrmFunctionalTestCase $this->assertFalse($user->articles->isInitialized(), "Pre-Condition: Collection is not initialized."); $article = $this->_em->find('Doctrine\Tests\Models\CMS\CmsArticle', $this->articleId); - + $queryCount = $this->getCurrentQueryCount(); $this->assertTrue($user->articles->contains($article)); $this->assertFalse($user->articles->isInitialized(), "Post-Condition: Collection is not initialized."); @@ -304,6 +304,28 @@ class ExtraLazyCollectionTest extends \Doctrine\Tests\OrmFunctionalTestCase $this->assertFalse($user->groups->isInitialized(), "Post-Condition: Collection is not initialized."); } + /** + * @group DDC-1399 + */ + public function testCountAfterAddThenFlush() + { + $user = $this->_em->find('Doctrine\Tests\Models\CMS\CmsUser', $this->userId); + + $newGroup = new \Doctrine\Tests\Models\CMS\CmsGroup(); + $newGroup->name = "Test4"; + + $user->addGroup($newGroup); + $this->_em->persist($newGroup); + + $this->assertFalse($user->groups->isInitialized()); + $this->assertEquals(4, count($user->groups)); + $this->assertFalse($user->groups->isInitialized()); + + $this->_em->flush(); + + $this->assertEquals(4, count($user->groups)); + } + private function loadFixture() { $user1 = new \Doctrine\Tests\Models\CMS\CmsUser(); @@ -364,7 +386,7 @@ class ExtraLazyCollectionTest extends \Doctrine\Tests\OrmFunctionalTestCase $this->_em->persist($article1); $this->_em->persist($article2); - + $this->_em->flush(); $this->_em->clear(); From 1dc5b7fba4496274287ca87b95c2b6b3ef27fbd8 Mon Sep 17 00:00:00 2001 From: Benjamin Eberlei Date: Mon, 31 Oct 2011 21:34:22 +0100 Subject: [PATCH 061/135] DDC-1462 - Fix bug in slice when calling on a dirty collection that is marked extra lazy --- lib/Doctrine/ORM/PersistentCollection.php | 5 ++++- .../Functional/ExtraLazyCollectionTest.php | 21 +++++++++++++++++++ 2 files changed, 25 insertions(+), 1 deletion(-) diff --git a/lib/Doctrine/ORM/PersistentCollection.php b/lib/Doctrine/ORM/PersistentCollection.php index 9840bead2..b64cacfdc 100644 --- a/lib/Doctrine/ORM/PersistentCollection.php +++ b/lib/Doctrine/ORM/PersistentCollection.php @@ -672,7 +672,10 @@ final class PersistentCollection implements Collection */ public function slice($offset, $length = null) { - if (!$this->initialized && $this->association['fetch'] == Mapping\ClassMetadataInfo::FETCH_EXTRA_LAZY) { + if ( ! $this->initialized && + ! $this->isDirty && + $this->association['fetch'] == Mapping\ClassMetadataInfo::FETCH_EXTRA_LAZY) { + return $this->em->getUnitOfWork() ->getCollectionPersister($this->association) ->slice($this, $offset, $length); diff --git a/tests/Doctrine/Tests/ORM/Functional/ExtraLazyCollectionTest.php b/tests/Doctrine/Tests/ORM/Functional/ExtraLazyCollectionTest.php index 43b45650f..351c1e25b 100644 --- a/tests/Doctrine/Tests/ORM/Functional/ExtraLazyCollectionTest.php +++ b/tests/Doctrine/Tests/ORM/Functional/ExtraLazyCollectionTest.php @@ -326,6 +326,27 @@ class ExtraLazyCollectionTest extends \Doctrine\Tests\OrmFunctionalTestCase $this->assertEquals(4, count($user->groups)); } + /** + * @group DDC-1462 + */ + public function testSliceOnDirtyCollection() + { + $user = $this->_em->find('Doctrine\Tests\Models\CMS\CmsUser', $this->userId); + /* @var $user CmsUser */ + + $newGroup = new \Doctrine\Tests\Models\CMS\CmsGroup(); + $newGroup->name = "Test4"; + + $user->addGroup($newGroup); + $this->_em->persist($newGroup); + + $qc = $this->getCurrentQueryCount(); + $groups = $user->groups->slice(0, 10); + + $this->assertEquals(4, count($groups)); + $this->assertEquals($qc + 1, $this->getCurrentQueryCount()); + } + private function loadFixture() { $user1 = new \Doctrine\Tests\Models\CMS\CmsUser(); From 3994b80aa4c31e7ca4253f1fef1c2d9a72789c3f Mon Sep 17 00:00:00 2001 From: Alexander Date: Mon, 31 Oct 2011 21:36:55 +0100 Subject: [PATCH 062/135] Fix TODO: Inner join when all join columns are NOT nullable. --- .../ORM/Persisters/BasicEntityPersister.php | 23 +++++++++++++++++-- .../Functional/OneToOneEagerLoadingTest.php | 3 ++- 2 files changed, 23 insertions(+), 3 deletions(-) diff --git a/lib/Doctrine/ORM/Persisters/BasicEntityPersister.php b/lib/Doctrine/ORM/Persisters/BasicEntityPersister.php index 0379ccf56..78c79c283 100644 --- a/lib/Doctrine/ORM/Persisters/BasicEntityPersister.php +++ b/lib/Doctrine/ORM/Persisters/BasicEntityPersister.php @@ -1002,10 +1002,10 @@ class BasicEntityPersister } } - $this->_selectJoinSql .= ' LEFT JOIN'; // TODO: Inner join when all join columns are NOT nullable. $first = true; if ($assoc['isOwningSide']) { + $this->_selectJoinSql .= $this->getJoinSQLForJoinColumns($assoc['joinColumns']); $this->_selectJoinSql .= ' ' . $eagerEntity->getQuotedTableName($this->_platform) . ' ' . $this->_getSQLTableAlias($eagerEntity->name, $assocAlias) .' ON '; foreach ($assoc['sourceToTargetKeyColumns'] AS $sourceCol => $targetCol) { @@ -1020,7 +1020,8 @@ class BasicEntityPersister } else { $eagerEntity = $this->_em->getClassMetadata($assoc['targetEntity']); $owningAssoc = $eagerEntity->getAssociationMapping($assoc['mappedBy']); - + + $this->_selectJoinSql .= $this->getJoinSQLForJoinColumns($owningAssoc['joinColumns']); $this->_selectJoinSql .= ' ' . $eagerEntity->getQuotedTableName($this->_platform) . ' ' . $this->_getSQLTableAlias($eagerEntity->name, $assocAlias) . ' ON '; @@ -1500,4 +1501,22 @@ class BasicEntityPersister return (bool) $this->_conn->fetchColumn($sql, $params); } + + /** + * Generates the appropriate join SQL for the given join column. + * + * @param array $joinColumns The join columns definition of an association. + * @return string LEFT JOIN if one of the columns is nullable, INNER JOIN otherwise. + */ + protected function getJoinSQLForJoinColumns($joinColumns) + { + // if one of the join columns is nullable, return left join + foreach($joinColumns as $joinColumn) { + if(isset($joinColumn['nullable']) && $joinColumn['nullable']){ + return ' LEFT JOIN '; + } + } + + return ' INNER JOIN '; + } } diff --git a/tests/Doctrine/Tests/ORM/Functional/OneToOneEagerLoadingTest.php b/tests/Doctrine/Tests/ORM/Functional/OneToOneEagerLoadingTest.php index 044a17381..7b87046f9 100644 --- a/tests/Doctrine/Tests/ORM/Functional/OneToOneEagerLoadingTest.php +++ b/tests/Doctrine/Tests/ORM/Functional/OneToOneEagerLoadingTest.php @@ -130,6 +130,7 @@ class Train /** * Owning side * @OneToOne(targetEntity="TrainDriver", inversedBy="train", fetch="EAGER", cascade={"persist"}) + * @JoinColumn(nullable=true) */ public $driver; /** @@ -195,4 +196,4 @@ class Waggon { $this->train = $train; } -} \ No newline at end of file +} From 0f938b8c1d37402a80c94c0b16c8d5020f8f314d Mon Sep 17 00:00:00 2001 From: Alexander Date: Mon, 31 Oct 2011 21:53:46 +0100 Subject: [PATCH 063/135] Added tests for inner join generation with eager loading --- .../Functional/OneToOneEagerLoadingTest.php | 28 +++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/tests/Doctrine/Tests/ORM/Functional/OneToOneEagerLoadingTest.php b/tests/Doctrine/Tests/ORM/Functional/OneToOneEagerLoadingTest.php index 7b87046f9..f8760c6f6 100644 --- a/tests/Doctrine/Tests/ORM/Functional/OneToOneEagerLoadingTest.php +++ b/tests/Doctrine/Tests/ORM/Functional/OneToOneEagerLoadingTest.php @@ -115,6 +115,34 @@ class OneToOneEagerLoadingTest extends \Doctrine\Tests\OrmFunctionalTestCase $this->assertNotInstanceOf('Doctrine\ORM\Proxy\Proxy', $waggon->train); $this->assertNotNull($waggon->train); } + + public function testEagerLoadWithNullableColumnsGeneratesLeftJoin() + { + $train = new Train(); + $this->_em->persist($train); + $this->_em->flush(); + $this->_em->clear(); + + $train = $this->_em->find(get_class($train), $train->id); + $this->assertEquals( + $this->_sqlLoggerStack->queries[$this->_sqlLoggerStack->currentQuery]['sql'], + "SELECT t0.id AS id1, t0.driver_id AS driver_id2, t3.id AS id4, t3.name AS name5 FROM Train t0 LEFT JOIN TrainDriver t3 ON t0.driver_id = t3.id WHERE t0.id = ?" + ); + } + + public function testEagerLoadWithNonNullableColumnsGeneratesInnerJoin() + { + $waggon = new Waggon(); + $this->_em->persist($waggon); + $this->_em->flush(); + $this->_em->clear(); + + $waggon = $this->_em->find(get_class($waggon), $waggon->id); + $this->assertEquals( + $this->_sqlLoggerStack->queries[$this->_sqlLoggerStack->currentQuery]['sql'], + "SELECT t0.id AS id1, t0.train_id AS train_id2, t3.id AS id4, t3.driver_id AS driver_id5 FROM Waggon t0 INNER JOIN Train t3 ON t0.train_id = t3.id WHERE t0.id = ?" + ); + } } /** From 22b3b46b61fa0cf647129fe2b52dd06549475dab Mon Sep 17 00:00:00 2001 From: Alexander Date: Mon, 31 Oct 2011 22:08:40 +0100 Subject: [PATCH 064/135] Removed unnecessary spaces in generated SQL --- lib/Doctrine/ORM/Persisters/BasicEntityPersister.php | 12 ++++++------ .../ORM/Functional/OneToOneEagerLoadingTest.php | 8 ++++---- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/lib/Doctrine/ORM/Persisters/BasicEntityPersister.php b/lib/Doctrine/ORM/Persisters/BasicEntityPersister.php index 78c79c283..b747155b3 100644 --- a/lib/Doctrine/ORM/Persisters/BasicEntityPersister.php +++ b/lib/Doctrine/ORM/Persisters/BasicEntityPersister.php @@ -1005,7 +1005,7 @@ class BasicEntityPersister $first = true; if ($assoc['isOwningSide']) { - $this->_selectJoinSql .= $this->getJoinSQLForJoinColumns($assoc['joinColumns']); + $this->_selectJoinSql .= ' ' . $this->getJoinSQLForJoinColumns($assoc['joinColumns']); $this->_selectJoinSql .= ' ' . $eagerEntity->getQuotedTableName($this->_platform) . ' ' . $this->_getSQLTableAlias($eagerEntity->name, $assocAlias) .' ON '; foreach ($assoc['sourceToTargetKeyColumns'] AS $sourceCol => $targetCol) { @@ -1014,14 +1014,14 @@ class BasicEntityPersister } $this->_selectJoinSql .= $this->_getSQLTableAlias($assoc['sourceEntity']) . '.' . $sourceCol . ' = ' - . $this->_getSQLTableAlias($assoc['targetEntity'], $assocAlias) . '.' . $targetCol . ' '; + . $this->_getSQLTableAlias($assoc['targetEntity'], $assocAlias) . '.' . $targetCol; $first = false; } } else { $eagerEntity = $this->_em->getClassMetadata($assoc['targetEntity']); $owningAssoc = $eagerEntity->getAssociationMapping($assoc['mappedBy']); - $this->_selectJoinSql .= $this->getJoinSQLForJoinColumns($owningAssoc['joinColumns']); + $this->_selectJoinSql .= ' ' . $this->getJoinSQLForJoinColumns($owningAssoc['joinColumns']); $this->_selectJoinSql .= ' ' . $eagerEntity->getQuotedTableName($this->_platform) . ' ' . $this->_getSQLTableAlias($eagerEntity->name, $assocAlias) . ' ON '; @@ -1031,7 +1031,7 @@ class BasicEntityPersister } $this->_selectJoinSql .= $this->_getSQLTableAlias($owningAssoc['sourceEntity'], $assocAlias) . '.' . $sourceCol . ' = ' - . $this->_getSQLTableAlias($owningAssoc['targetEntity']) . '.' . $targetCol . ' '; + . $this->_getSQLTableAlias($owningAssoc['targetEntity']) . '.' . $targetCol; $first = false; } } @@ -1513,10 +1513,10 @@ class BasicEntityPersister // if one of the join columns is nullable, return left join foreach($joinColumns as $joinColumn) { if(isset($joinColumn['nullable']) && $joinColumn['nullable']){ - return ' LEFT JOIN '; + return 'LEFT JOIN'; } } - return ' INNER JOIN '; + return 'INNER JOIN'; } } diff --git a/tests/Doctrine/Tests/ORM/Functional/OneToOneEagerLoadingTest.php b/tests/Doctrine/Tests/ORM/Functional/OneToOneEagerLoadingTest.php index f8760c6f6..5361aad45 100644 --- a/tests/Doctrine/Tests/ORM/Functional/OneToOneEagerLoadingTest.php +++ b/tests/Doctrine/Tests/ORM/Functional/OneToOneEagerLoadingTest.php @@ -125,8 +125,8 @@ class OneToOneEagerLoadingTest extends \Doctrine\Tests\OrmFunctionalTestCase $train = $this->_em->find(get_class($train), $train->id); $this->assertEquals( - $this->_sqlLoggerStack->queries[$this->_sqlLoggerStack->currentQuery]['sql'], - "SELECT t0.id AS id1, t0.driver_id AS driver_id2, t3.id AS id4, t3.name AS name5 FROM Train t0 LEFT JOIN TrainDriver t3 ON t0.driver_id = t3.id WHERE t0.id = ?" + "SELECT t0.id AS id1, t0.driver_id AS driver_id2, t3.id AS id4, t3.name AS name5 FROM Train t0 LEFT JOIN TrainDriver t3 ON t0.driver_id = t3.id WHERE t0.id = ?", + $this->_sqlLoggerStack->queries[$this->_sqlLoggerStack->currentQuery]['sql'] ); } @@ -139,8 +139,8 @@ class OneToOneEagerLoadingTest extends \Doctrine\Tests\OrmFunctionalTestCase $waggon = $this->_em->find(get_class($waggon), $waggon->id); $this->assertEquals( - $this->_sqlLoggerStack->queries[$this->_sqlLoggerStack->currentQuery]['sql'], - "SELECT t0.id AS id1, t0.train_id AS train_id2, t3.id AS id4, t3.driver_id AS driver_id5 FROM Waggon t0 INNER JOIN Train t3 ON t0.train_id = t3.id WHERE t0.id = ?" + "SELECT t0.id AS id1, t0.train_id AS train_id2, t3.id AS id4, t3.driver_id AS driver_id5 FROM Waggon t0 INNER JOIN Train t3 ON t0.train_id = t3.id WHERE t0.id = ?", + $this->_sqlLoggerStack->queries[$this->_sqlLoggerStack->currentQuery]['sql'] ); } } From 231d84b625f4374ed196eb53d46b50fc6f7a12d3 Mon Sep 17 00:00:00 2001 From: Benjamin Eberlei Date: Mon, 31 Oct 2011 22:14:46 +0100 Subject: [PATCH 065/135] Fix xml fix again --- lib/Doctrine/ORM/Mapping/Driver/XmlDriver.php | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/lib/Doctrine/ORM/Mapping/Driver/XmlDriver.php b/lib/Doctrine/ORM/Mapping/Driver/XmlDriver.php index b676ca8da..fd6bf081a 100644 --- a/lib/Doctrine/ORM/Mapping/Driver/XmlDriver.php +++ b/lib/Doctrine/ORM/Mapping/Driver/XmlDriver.php @@ -89,7 +89,7 @@ class XmlDriver extends AbstractFileDriver if (isset($xmlRoot['schema'])) { $metadata->table['schema'] = (string)$xmlRoot['schema']; }*/ - + if (isset($xmlRoot['inheritance-type'])) { $inheritanceType = (string)$xmlRoot['inheritance-type']; $metadata->setInheritanceType(constant('Doctrine\ORM\Mapping\ClassMetadata::INHERITANCE_TYPE_' . $inheritanceType)); @@ -327,6 +327,8 @@ class XmlDriver extends AbstractFileDriver if (isset($oneToManyElement['index-by'])) { $mapping['indexBy'] = (string)$oneToManyElement['index-by']; + } else if (isset($oneToManyElement->{'index-by'})) { + throw new \InvalidArgumentException(" is not a valid tag"); } $metadata->mapOneToMany($mapping); @@ -432,8 +434,10 @@ class XmlDriver extends AbstractFileDriver $mapping['orderBy'] = $orderBy; } - if (isset($manyToManyElement->{'index-by'})) { - $mapping['indexBy'] = (string)$manyToManyElement->{'index-by'}; + if (isset($manyToManyElement['index-by'])) { + $mapping['indexBy'] = (string)$manyToManyElement['index-by']; + } else if (isset($manyToManyElement->{'index-by'})) { + throw new \InvalidArgumentException(" is not a valid tag"); } $metadata->mapManyToMany($mapping); From e31e164896dc391b7e5ec03cdfbc97351e5cea93 Mon Sep 17 00:00:00 2001 From: Benjamin Eberlei Date: Mon, 31 Oct 2011 23:12:52 +0100 Subject: [PATCH 066/135] DBAL-1420 - Use safe mode for schema validation. Dropping stuff isnt necessary here. --- lib/Doctrine/ORM/Tools/SchemaValidator.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/Doctrine/ORM/Tools/SchemaValidator.php b/lib/Doctrine/ORM/Tools/SchemaValidator.php index 2ddd0bf66..2c0bd94ff 100644 --- a/lib/Doctrine/ORM/Tools/SchemaValidator.php +++ b/lib/Doctrine/ORM/Tools/SchemaValidator.php @@ -246,6 +246,6 @@ class SchemaValidator $schemaTool = new SchemaTool($this->em); $allMetadata = $this->em->getMetadataFactory()->getAllMetadata(); - return (count($schemaTool->getUpdateSchemaSql($allMetadata, false)) == 0); + return (count($schemaTool->getUpdateSchemaSql($allMetadata, true)) == 0); } } From c965d231b1d32aa9ee380e915a1bf8cf5ebf48ab Mon Sep 17 00:00:00 2001 From: Benjamin Eberlei Date: Mon, 31 Oct 2011 23:21:11 +0100 Subject: [PATCH 067/135] Rename method and refactor code a bit --- lib/Doctrine/ORM/Tools/SchemaTool.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/Doctrine/ORM/Tools/SchemaTool.php b/lib/Doctrine/ORM/Tools/SchemaTool.php index 568a960a4..e052c69d7 100644 --- a/lib/Doctrine/ORM/Tools/SchemaTool.php +++ b/lib/Doctrine/ORM/Tools/SchemaTool.php @@ -145,7 +145,7 @@ class SchemaTool $this->_gatherRelationsSql($class, $table, $schema); // Add the discriminator column - $discrColumnDef = $this->_getDiscriminatorColumnDefinition($class, $table); + $this->addDiscriminatorColumnDefinition($class, $table); // Aggregate all the information from all classes in the hierarchy foreach ($class->parentClasses as $parentClassName) { @@ -177,7 +177,7 @@ class SchemaTool // Add the discriminator column only to the root table if ($class->name == $class->rootEntityName) { - $discrColumnDef = $this->_getDiscriminatorColumnDefinition($class, $table); + $this->addDiscriminatorColumnDefinition($class, $table); } else { // Add an ID FK column to child tables /* @var Doctrine\ORM\Mapping\ClassMetadata $class */ @@ -267,7 +267,7 @@ class SchemaTool * @return array The portable column definition of the discriminator column as required by * the DBAL. */ - private function _getDiscriminatorColumnDefinition($class, $table) + private function addDiscriminatorColumnDefinition($class, $table) { $discrColumn = $class->discriminatorColumn; From 4b316ec54fac9e95324ba622364427cc51a3b5fb Mon Sep 17 00:00:00 2001 From: Benjamin Eberlei Date: Mon, 31 Oct 2011 23:35:41 +0100 Subject: [PATCH 068/135] DDC-1389 - Add validation for empty discriminator map values --- .../ORM/Internal/Hydration/HydrationException.php | 11 ++++++++++- .../ORM/Internal/Hydration/ObjectHydrator.php | 5 +++++ .../ORM/Internal/Hydration/SimpleObjectHydrator.php | 5 +++++ 3 files changed, 20 insertions(+), 1 deletion(-) diff --git a/lib/Doctrine/ORM/Internal/Hydration/HydrationException.php b/lib/Doctrine/ORM/Internal/Hydration/HydrationException.php index 886b42dec..147f6acae 100644 --- a/lib/Doctrine/ORM/Internal/Hydration/HydrationException.php +++ b/lib/Doctrine/ORM/Internal/Hydration/HydrationException.php @@ -8,10 +8,19 @@ class HydrationException extends \Doctrine\ORM\ORMException { return new self("The result returned by the query was not unique."); } - + public static function parentObjectOfRelationNotFound($alias, $parentAlias) { return new self("The parent object of entity result with alias '$alias' was not found." . " The parent alias is '$parentAlias'."); } + + public static function emptyDiscriminatorValue($dqlAlias) + { + return new self("The DQL alias '" . $dqlAlias . "' contains an entity ". + "of an inheritance hierachy with an empty discriminator value. This means " . + "that the database contains inconsistent data with an empty " . + "discriminator value in a table row." + ); + } } \ No newline at end of file diff --git a/lib/Doctrine/ORM/Internal/Hydration/ObjectHydrator.php b/lib/Doctrine/ORM/Internal/Hydration/ObjectHydrator.php index 1bd16164b..3f787694b 100644 --- a/lib/Doctrine/ORM/Internal/Hydration/ObjectHydrator.php +++ b/lib/Doctrine/ORM/Internal/Hydration/ObjectHydrator.php @@ -202,6 +202,11 @@ class ObjectHydrator extends AbstractHydrator $className = $this->_rsm->aliasMap[$dqlAlias]; if (isset($this->_rsm->discriminatorColumns[$dqlAlias])) { $discrColumn = $this->_rsm->metaMappings[$this->_rsm->discriminatorColumns[$dqlAlias]]; + + if ($data[$discrColumn] === "") { + throw HydrationException::emptyDiscriminatorValue($dqlAlias); + } + $className = $this->_ce[$className]->discriminatorMap[$data[$discrColumn]]; unset($data[$discrColumn]); } diff --git a/lib/Doctrine/ORM/Internal/Hydration/SimpleObjectHydrator.php b/lib/Doctrine/ORM/Internal/Hydration/SimpleObjectHydrator.php index 34d8e31b1..e9f6395f9 100644 --- a/lib/Doctrine/ORM/Internal/Hydration/SimpleObjectHydrator.php +++ b/lib/Doctrine/ORM/Internal/Hydration/SimpleObjectHydrator.php @@ -89,8 +89,13 @@ class SimpleObjectHydrator extends AbstractHydrator $entityName = $this->class->name; } else { $discrColumnName = $this->_platform->getSQLResultCasing($this->class->discriminatorColumn['name']); + if ($sqlResult[$discrColumnName] === "") { + throw HydrationException::emptyDiscriminatorValue(key($this->_rsm->aliasMap)); + } + $entityName = $this->class->discriminatorMap[$sqlResult[$discrColumnName]]; unset($sqlResult[$discrColumnName]); + foreach ($sqlResult as $column => $value) { if (!isset($cache[$column])) { if (isset($this->_rsm->fieldMappings[$column])) { From 98e3e0447720121caa65ca77d6c218f9c41370fa Mon Sep 17 00:00:00 2001 From: Benjamin Eberlei Date: Tue, 1 Nov 2011 15:05:38 +0100 Subject: [PATCH 069/135] Add autoload section to composer.json --- composer.json | 3 +++ 1 file changed, 3 insertions(+) diff --git a/composer.json b/composer.json index 080885107..8f570e200 100644 --- a/composer.json +++ b/composer.json @@ -16,5 +16,8 @@ "ext-pdo": "*", "doctrine/common": "master-dev", "doctrine/dbal": "master-dev" + }, + "autoload": { + "psr-0": { "Doctrine\\ORM": "lib/" } } } From afb8d63fcb262d1e23560425cd2066d785f98791 Mon Sep 17 00:00:00 2001 From: Benjamin Eberlei Date: Tue, 1 Nov 2011 16:42:03 +0100 Subject: [PATCH 070/135] Fixed some AbstractHydrator docblocks --- .../ORM/Internal/Hydration/AbstractHydrator.php | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/lib/Doctrine/ORM/Internal/Hydration/AbstractHydrator.php b/lib/Doctrine/ORM/Internal/Hydration/AbstractHydrator.php index 5899a69ca..61a0f48fa 100644 --- a/lib/Doctrine/ORM/Internal/Hydration/AbstractHydrator.php +++ b/lib/Doctrine/ORM/Internal/Hydration/AbstractHydrator.php @@ -158,11 +158,12 @@ abstract class AbstractHydrator /** * Processes a row of the result set. + * * Used for identity-based hydration (HYDRATE_OBJECT and HYDRATE_ARRAY). - * Puts the elements of a result row into a new array, grouped by the class + * Puts the elements of a result row into a new array, grouped by the dql alias * they belong to. The column names in the result set are mapped to their * field names during this procedure as well as any necessary conversions on - * the values applied. + * the values applied. Scalar values are kept in a specfic key 'scalars'. * * @param array $data SQL Result Row * @param array &$cache Cache for column to field result information @@ -203,7 +204,7 @@ abstract class AbstractHydrator $cache[$key]['isIdentifier'] = isset($this->_rsm->isIdentifierColumn[$cache[$key]['dqlAlias']][$key]); } } - + if (isset($cache[$key]['isScalar'])) { $rowData['scalars'][$cache[$key]['fieldName']] = $value; continue; @@ -221,7 +222,7 @@ abstract class AbstractHydrator } continue; } - + // in an inheritance hierachy the same field could be defined several times. // We overwrite this value so long we dont have a non-null value, that value we keep. // Per definition it cannot be that a field is defined several times and has several values. @@ -241,6 +242,7 @@ abstract class AbstractHydrator /** * Processes a row of the result set. + * * Used for HYDRATE_SCALAR. This is a variant of _gatherRowData() that * simply converts column names to field names and properly converts the * values according to their types. The resulting row has the same number @@ -277,7 +279,7 @@ abstract class AbstractHydrator $cache[$key]['dqlAlias'] = $this->_rsm->columnOwnerMap[$key]; } } - + $fieldName = $cache[$key]['fieldName']; if (isset($cache[$key]['isScalar'])) { @@ -292,7 +294,7 @@ abstract class AbstractHydrator return $rowData; } - + protected function registerManaged($class, $entity, $data) { if ($class->isIdentifierComposite) { From d532de9da3e1a413302540a407d6e73da8cdf39e Mon Sep 17 00:00:00 2001 From: Benjamin Eberlei Date: Tue, 1 Nov 2011 17:43:23 +0100 Subject: [PATCH 071/135] Fix docblocks ObjectHydratorTest --- .../ORM/Hydration/ObjectHydratorTest.php | 58 ++++++------------- 1 file changed, 19 insertions(+), 39 deletions(-) diff --git a/tests/Doctrine/Tests/ORM/Hydration/ObjectHydratorTest.php b/tests/Doctrine/Tests/ORM/Hydration/ObjectHydratorTest.php index 65cd57c95..57c297cf0 100644 --- a/tests/Doctrine/Tests/ORM/Hydration/ObjectHydratorTest.php +++ b/tests/Doctrine/Tests/ORM/Hydration/ObjectHydratorTest.php @@ -16,7 +16,7 @@ require_once __DIR__ . '/../../TestInit.php'; class ObjectHydratorTest extends HydrationTestCase { /** - * Select u.id, u.name from \Doctrine\Tests\Models\CMS\CmsUser u + * SELECT PARTIAL u.{id,name} FROM \Doctrine\Tests\Models\CMS\CmsUser u */ public function testSimpleEntityQuery() { @@ -54,6 +54,7 @@ class ObjectHydratorTest extends HydrationTestCase /** * @group DDC-644 + * SELECT PARTIAL u.{id,name} FROM \Doctrine\Tests\Models\CMS\CmsUser u */ public function testSkipUnknownColumns() { @@ -80,7 +81,8 @@ class ObjectHydratorTest extends HydrationTestCase } /** - * Select u.id, u.name from \Doctrine\Tests\Models\CMS\CmsUser u + * SELECT PARTIAL u.{id,name}, PARTIAL a.{id,topic} + * FROM \Doctrine\Tests\Models\CMS\CmsUser u, \Doctrine\Tests\Models\CMS\CmsArticle a */ public function testSimpleMultipleRootEntityQuery() { @@ -115,7 +117,7 @@ class ObjectHydratorTest extends HydrationTestCase $result = $hydrator->hydrateAll($stmt, $rsm, array(Query::HINT_FORCE_PARTIAL_LOAD => true)); $this->assertEquals(4, count($result)); - + $this->assertInstanceOf('Doctrine\Tests\Models\CMS\CmsUser', $result[0]); $this->assertInstanceOf('Doctrine\Tests\Models\CMS\CmsArticle', $result[1]); $this->assertInstanceOf('Doctrine\Tests\Models\CMS\CmsUser', $result[2]); @@ -132,7 +134,7 @@ class ObjectHydratorTest extends HydrationTestCase } /** - * Select p from \Doctrine\Tests\Models\ECommerce\ECommerceProduct p + * SELECT p FROM \Doctrine\Tests\Models\ECommerce\ECommerceProduct p */ public function testCreatesProxyForLazyLoadingWithForeignKeys() { @@ -177,11 +179,7 @@ class ObjectHydratorTest extends HydrationTestCase } /** - * select u.id, u.status, p.phonenumber, upper(u.name) nameUpper from User u - * join u.phonenumbers p - * = - * select u.id, u.status, p.phonenumber, upper(u.name) as u__0 from USERS u - * INNER JOIN PHONENUMBERS p ON u.id = p.user_id + * select u, p, upper(u.name) nameUpper from User u JOIN u.phonenumbers p */ public function testMixedQueryFetchJoin() { @@ -251,11 +249,8 @@ class ObjectHydratorTest extends HydrationTestCase } /** - * select u.id, u.status, count(p.phonenumber) numPhones from User u - * join u.phonenumbers p group by u.status, u.id - * = - * select u.id, u.status, count(p.phonenumber) as p__0 from USERS u - * INNER JOIN PHONENUMBERS p ON u.id = p.user_id group by u.id, u.status + * select u, count(p.phonenumber) numPhones from User u + * join u.phonenumbers p group by u.id */ public function testMixedQueryNormalJoin() { @@ -298,11 +293,8 @@ class ObjectHydratorTest extends HydrationTestCase } /** - * select u.id, u.status, upper(u.name) nameUpper from User u index by u.id + * select u, p, upper(u.name) nameUpper from User u index by u.id * join u.phonenumbers p indexby p.phonenumber - * = - * select u.id, u.status, upper(u.name) as p__0 from USERS u - * INNER JOIN PHONENUMBERS p ON u.id = p.user_id */ public function testMixedQueryFetchJoinCustomIndex() { @@ -373,15 +365,10 @@ class ObjectHydratorTest extends HydrationTestCase } /** - * select u.id, u.status, p.phonenumber, upper(u.name) nameUpper, a.id, a.topic + * select u, p, upper(u.name) nameUpper, a * from User u * join u.phonenumbers p * join u.articles a - * = - * select u.id, u.status, p.phonenumber, upper(u.name) as u__0, a.id, a.topic - * from USERS u - * inner join PHONENUMBERS p ON u.id = p.user_id - * inner join ARTICLES a ON u.id = a.user_id */ public function testMixedQueryMultipleFetchJoin() { @@ -484,19 +471,12 @@ class ObjectHydratorTest extends HydrationTestCase } /** - * select u.id, u.status, p.phonenumber, upper(u.name) nameUpper, a.id, a.topic, + * select u, p, upper(u.name) nameUpper, a, c * c.id, c.topic * from User u * join u.phonenumbers p * join u.articles a * left join a.comments c - * = - * select u.id, u.status, p.phonenumber, upper(u.name) as u__0, a.id, a.topic, - * c.id, c.topic - * from USERS u - * inner join PHONENUMBERS p ON u.id = p.user_id - * inner join ARTICLES a ON u.id = a.user_id - * left outer join COMMENTS c ON a.id = c.article_id */ public function testMixedQueryMultipleDeepMixedFetchJoin() { @@ -717,7 +697,7 @@ class ObjectHydratorTest extends HydrationTestCase $this->assertEquals(1, count($result[1]->boards)); } - + public function testChainedJoinWithEmptyCollections() { $rsm = new ResultSetMapping; @@ -773,10 +753,10 @@ class ObjectHydratorTest extends HydrationTestCase $this->assertEquals(0, $result[0]->articles->count()); $this->assertEquals(0, $result[1]->articles->count()); } - + /** * DQL: select partial u.{id,status}, a.id, a.topic, c.id as cid, c.topic as ctopic from CmsUser u left join u.articles a left join a.comments c - * + * * @group bubu */ /*public function testChainedJoinWithScalars() @@ -825,25 +805,25 @@ class ObjectHydratorTest extends HydrationTestCase $result = $hydrator->hydrateAll($stmt, $rsm, array(Query::HINT_FORCE_PARTIAL_LOAD => true)); $this->assertEquals(3, count($result)); - + $this->assertInstanceOf('Doctrine\Tests\Models\CMS\CmsUser', $result[0][0]); // User object $this->assertEquals(1, $result[0]['id']); $this->assertEquals('The First', $result[0]['topic']); $this->assertEquals(1, $result[0]['cid']); $this->assertEquals('First Comment', $result[0]['ctopic']); - + $this->assertInstanceOf('Doctrine\Tests\Models\CMS\CmsUser', $result[1][0]); // Same User object $this->assertEquals(1, $result[1]['id']); // duplicated $this->assertEquals('The First', $result[1]['topic']); // duplicated $this->assertEquals(2, $result[1]['cid']); $this->assertEquals('Second Comment', $result[1]['ctopic']); - + $this->assertInstanceOf('Doctrine\Tests\Models\CMS\CmsUser', $result[2][0]); // Same User object $this->assertEquals(42, $result[2]['id']); $this->assertEquals('The Answer', $result[2]['topic']); $this->assertNull($result[2]['cid']); $this->assertNull($result[2]['ctopic']); - + $this->assertTrue($result[0][0] === $result[1][0]); $this->assertTrue($result[1][0] === $result[2][0]); $this->assertTrue($result[0][0] === $result[2][0]); From d1bfd57fd9df6d26d3d8f0525aa86aa4a8a73499 Mon Sep 17 00:00:00 2001 From: Guilherme Blanco Date: Wed, 2 Nov 2011 22:08:24 -0200 Subject: [PATCH 072/135] Initial code optimization in Hydrators. --- .../Internal/Hydration/AbstractHydrator.php | 78 +++++--- .../ORM/Internal/Hydration/ArrayHydrator.php | 81 ++++++--- .../ORM/Internal/Hydration/ObjectHydrator.php | 19 +- .../ORM/Internal/Hydration/ScalarHydrator.php | 23 ++- .../Hydration/SimpleObjectHydrator.php | 168 +++++++++++------- .../Hydration/SingleScalarHydrator.php | 33 ++-- 6 files changed, 257 insertions(+), 145 deletions(-) diff --git a/lib/Doctrine/ORM/Internal/Hydration/AbstractHydrator.php b/lib/Doctrine/ORM/Internal/Hydration/AbstractHydrator.php index 61a0f48fa..493b1461c 100644 --- a/lib/Doctrine/ORM/Internal/Hydration/AbstractHydrator.php +++ b/lib/Doctrine/ORM/Internal/Hydration/AbstractHydrator.php @@ -22,15 +22,17 @@ namespace Doctrine\ORM\Internal\Hydration; use PDO, Doctrine\DBAL\Connection, Doctrine\DBAL\Types\Type, - Doctrine\ORM\EntityManager; + Doctrine\ORM\EntityManager, + Doctrine\ORM\Mapping\ClassMetadata; /** * Base class for all hydrators. A hydrator is a class that provides some form * of transformation of an SQL result set into another structure. * - * @since 2.0 - * @author Konsta Vesterinen - * @author Roman Borschel + * @since 2.0 + * @author Konsta Vesterinen + * @author Roman Borschel + * @author Guilherme Blanco */ abstract class AbstractHydrator { @@ -62,9 +64,9 @@ abstract class AbstractHydrator */ public function __construct(EntityManager $em) { - $this->_em = $em; + $this->_em = $em; $this->_platform = $em->getConnection()->getDatabasePlatform(); - $this->_uow = $em->getUnitOfWork(); + $this->_uow = $em->getUnitOfWork(); } /** @@ -72,14 +74,17 @@ abstract class AbstractHydrator * * @param object $stmt * @param object $resultSetMapping + * * @return IterableResult */ public function iterate($stmt, $resultSetMapping, array $hints = array()) { - $this->_stmt = $stmt; - $this->_rsm = $resultSetMapping; + $this->_stmt = $stmt; + $this->_rsm = $resultSetMapping; $this->_hints = $hints; - $this->_prepare(); + + $this->prepare(); + return new IterableResult($this); } @@ -92,12 +97,16 @@ abstract class AbstractHydrator */ public function hydrateAll($stmt, $resultSetMapping, array $hints = array()) { - $this->_stmt = $stmt; - $this->_rsm = $resultSetMapping; + $this->_stmt = $stmt; + $this->_rsm = $resultSetMapping; $this->_hints = $hints; - $this->_prepare(); - $result = $this->_hydrateAll(); - $this->_cleanup(); + + $this->prepare(); + + $result = $this->hydrateAllData(); + + $this->cleanup(); + return $result; } @@ -110,12 +119,17 @@ abstract class AbstractHydrator public function hydrateRow() { $row = $this->_stmt->fetch(PDO::FETCH_ASSOC); + if ( ! $row) { - $this->_cleanup(); + $this->cleanup(); + return false; } + $result = array(); - $this->_hydrateRow($row, $this->_cache, $result); + + $this->hydrateRowData($row, $this->_cache, $result); + return $result; } @@ -123,16 +137,17 @@ abstract class AbstractHydrator * Excutes one-time preparation tasks, once each time hydration is started * through {@link hydrateAll} or {@link iterate()}. */ - protected function _prepare() + protected function prepare() {} /** * Excutes one-time cleanup tasks at the end of a hydration that was initiated * through {@link hydrateAll} or {@link iterate()}. */ - protected function _cleanup() + protected function cleanup() { $this->_rsm = null; + $this->_stmt->closeCursor(); $this->_stmt = null; } @@ -146,15 +161,15 @@ abstract class AbstractHydrator * @param array $cache The cache to use. * @param mixed $result The result to fill. */ - protected function _hydrateRow(array $data, array &$cache, array &$result) + protected function hydrateRowData(array $data, array &$cache, array &$result) { - throw new HydrationException("_hydrateRow() not implemented by this hydrator."); + throw new HydrationException("hydrateRowData() not implemented by this hydrator."); } /** * Hydrates all rows from the current statement instance at once. */ - abstract protected function _hydrateAll(); + abstract protected function hydrateAllData(); /** * Processes a row of the result set. @@ -173,7 +188,7 @@ abstract class AbstractHydrator * @return array An array with all the fields (name => value) of the data row, * grouped by their component alias. */ - protected function _gatherRowData(array $data, array &$cache, array &$id, array &$nonemptyComponents) + protected function gatherRowData(array $data, array &$cache, array &$id, array &$nonemptyComponents) { $rowData = array(); @@ -207,6 +222,7 @@ abstract class AbstractHydrator if (isset($cache[$key]['isScalar'])) { $rowData['scalars'][$cache[$key]['fieldName']] = $value; + continue; } @@ -220,10 +236,11 @@ abstract class AbstractHydrator if (!isset($rowData[$dqlAlias][$cache[$key]['fieldName']]) || $value !== null) { $rowData[$dqlAlias][$cache[$key]['fieldName']] = $value; } + continue; } - // in an inheritance hierachy the same field could be defined several times. + // in an inheritance hierarchy the same field could be defined several times. // We overwrite this value so long we dont have a non-null value, that value we keep. // Per definition it cannot be that a field is defined several times and has several values. if (isset($rowData[$dqlAlias][$cache[$key]['fieldName']]) && $value === null) { @@ -250,9 +267,10 @@ abstract class AbstractHydrator * * @param array $data * @param array $cache + * * @return array The processed row. */ - protected function _gatherScalarRowData(&$data, &$cache) + protected function gatherScalarRowData(&$data, &$cache) { $rowData = array(); @@ -294,8 +312,15 @@ abstract class AbstractHydrator return $rowData; } - - protected function registerManaged($class, $entity, $data) + + /** + * Register entity as managed in UnitOfWork. + * + * @param Doctrine\ORM\Mapping\ClassMetadata $class + * @param object $entity + * @param array $data + */ + protected function registerManaged(ClassMetadata $class, $entity, array $data) { if ($class->isIdentifierComposite) { $id = array(); @@ -313,6 +338,7 @@ abstract class AbstractHydrator $id = array($class->identifier[0] => $data[$class->identifier[0]]); } } + $this->_em->getUnitOfWork()->registerManaged($entity, $id, $data); } } diff --git a/lib/Doctrine/ORM/Internal/Hydration/ArrayHydrator.php b/lib/Doctrine/ORM/Internal/Hydration/ArrayHydrator.php index cbc77d2d2..e25df2fea 100644 --- a/lib/Doctrine/ORM/Internal/Hydration/ArrayHydrator.php +++ b/lib/Doctrine/ORM/Internal/Hydration/ArrayHydrator.php @@ -25,8 +25,9 @@ use PDO, Doctrine\DBAL\Connection, Doctrine\ORM\Mapping\ClassMetadata; * The ArrayHydrator produces a nested array "graph" that is often (not always) * interchangeable with the corresponding object graph for read-only access. * + * @since 2.0 * @author Roman Borschel - * @since 1.0 + * @author Guilherme Blanco */ class ArrayHydrator extends AbstractHydrator { @@ -38,45 +39,54 @@ class ArrayHydrator extends AbstractHydrator private $_idTemplate = array(); private $_resultCounter = 0; - /** @override */ - protected function _prepare() + /** + * {@inheritdoc} + */ + protected function prepare() { - $this->_isSimpleQuery = count($this->_rsm->aliasMap) <= 1; - $this->_identifierMap = array(); + $this->_isSimpleQuery = count($this->_rsm->aliasMap) <= 1; + $this->_identifierMap = array(); $this->_resultPointers = array(); - $this->_idTemplate = array(); - $this->_resultCounter = 0; + $this->_idTemplate = array(); + $this->_resultCounter = 0; + foreach ($this->_rsm->aliasMap as $dqlAlias => $className) { - $this->_identifierMap[$dqlAlias] = array(); + $this->_identifierMap[$dqlAlias] = array(); $this->_resultPointers[$dqlAlias] = array(); - $this->_idTemplate[$dqlAlias] = ''; + $this->_idTemplate[$dqlAlias] = ''; } } - /** @override */ - protected function _hydrateAll() + /** + * {@inheritdoc} + */ + protected function hydrateAllData() { $result = array(); - $cache = array(); + $cache = array(); + while ($data = $this->_stmt->fetch(PDO::FETCH_ASSOC)) { - $this->_hydrateRow($data, $cache, $result); + $this->hydrateRowData($data, $cache, $result); } return $result; } - /** @override */ - protected function _hydrateRow(array $row, array &$cache, array &$result) + /** + * {@inheritdoc} + */ + protected function hydrateRowData(array $row, array &$cache, array &$result) { // 1) Initialize $id = $this->_idTemplate; // initialize the id-memory $nonemptyComponents = array(); - $rowData = $this->_gatherRowData($row, $cache, $id, $nonemptyComponents); + $rowData = $this->gatherRowData($row, $cache, $id, $nonemptyComponents); // Extract scalar values. They're appended at the end. if (isset($rowData['scalars'])) { $scalars = $rowData['scalars']; unset($rowData['scalars']); + if (empty($rowData)) { ++$this->_resultCounter; } @@ -111,7 +121,7 @@ class ArrayHydrator extends AbstractHydrator } $relationAlias = $this->_rsm->relationMap[$dqlAlias]; - $relation = $this->_getClassMetadata($this->_rsm->aliasMap[$parent])->associationMappings[$relationAlias]; + $relation = $this->getClassMetadata($this->_rsm->aliasMap[$parent])->associationMappings[$relationAlias]; // Check the type of the relation (many or single-valued) if ( ! ($relation['type'] & ClassMetadata::TO_ONE)) { @@ -230,28 +240,45 @@ class ArrayHydrator extends AbstractHydrator { if ($coll === null) { unset($this->_resultPointers[$dqlAlias]); // Ticket #1228 + return; } + if ($index !== false) { $this->_resultPointers[$dqlAlias] =& $coll[$index]; + + return; + } + + if ( ! $coll) { return; - } else { - if ($coll) { - if ($oneToOne) { - $this->_resultPointers[$dqlAlias] =& $coll; - } else { - end($coll); - $this->_resultPointers[$dqlAlias] =& $coll[key($coll)]; - } - } } + + if ($oneToOne) { + $this->_resultPointers[$dqlAlias] =& $coll; + + return; + } + + end($coll); + $this->_resultPointers[$dqlAlias] =& $coll[key($coll)]; + + return; } - private function _getClassMetadata($className) + /** + * Retrieve ClassMetadata associated to entity class name. + * + * @param string $className + * + * @return Doctrine\ORM\Mapping\ClassMetadata + */ + private function getClassMetadata($className) { if ( ! isset($this->_ce[$className])) { $this->_ce[$className] = $this->_em->getClassMetadata($className); } + return $this->_ce[$className]; } } \ No newline at end of file diff --git a/lib/Doctrine/ORM/Internal/Hydration/ObjectHydrator.php b/lib/Doctrine/ORM/Internal/Hydration/ObjectHydrator.php index 3f787694b..d7d0281c4 100644 --- a/lib/Doctrine/ORM/Internal/Hydration/ObjectHydrator.php +++ b/lib/Doctrine/ORM/Internal/Hydration/ObjectHydrator.php @@ -29,8 +29,10 @@ use PDO, /** * The ObjectHydrator constructs an object graph out of an SQL result set. * + * @since 2.0 * @author Roman Borschel - * @since 2.0 + * @author Guilherme Blanco + * * @internal Highly performance-sensitive code. */ class ObjectHydrator extends AbstractHydrator @@ -53,7 +55,7 @@ class ObjectHydrator extends AbstractHydrator /** @override */ - protected function _prepare() + protected function prepare() { $this->_identifierMap = $this->_resultPointers = @@ -113,11 +115,12 @@ class ObjectHydrator extends AbstractHydrator /** * {@inheritdoc} */ - protected function _cleanup() + protected function cleanup() { $eagerLoad = (isset($this->_hints['deferEagerLoad'])) && $this->_hints['deferEagerLoad'] == true; - parent::_cleanup(); + parent::cleanup(); + $this->_identifierMap = $this->_initializedCollections = $this->_existingCollections = @@ -131,13 +134,13 @@ class ObjectHydrator extends AbstractHydrator /** * {@inheritdoc} */ - protected function _hydrateAll() + protected function hydrateAllData() { $result = array(); $cache = array(); while ($row = $this->_stmt->fetch(PDO::FETCH_ASSOC)) { - $this->_hydrateRow($row, $cache, $result); + $this->hydrateRowData($row, $cache, $result); } // Take snapshots from all newly initialized collections @@ -278,13 +281,13 @@ class ObjectHydrator extends AbstractHydrator * @param array $cache The cache to use. * @param array $result The result array to fill. */ - protected function _hydrateRow(array $row, array &$cache, array &$result) + protected function hydrateRowData(array $row, array &$cache, array &$result) { // Initialize $id = $this->_idTemplate; // initialize the id-memory $nonemptyComponents = array(); // Split the row data into chunks of class data. - $rowData = $this->_gatherRowData($row, $cache, $id, $nonemptyComponents); + $rowData = $this->gatherRowData($row, $cache, $id, $nonemptyComponents); // Extract scalar values. They're appended at the end. if (isset($rowData['scalars'])) { diff --git a/lib/Doctrine/ORM/Internal/Hydration/ScalarHydrator.php b/lib/Doctrine/ORM/Internal/Hydration/ScalarHydrator.php index f15307310..29c0d12e5 100644 --- a/lib/Doctrine/ORM/Internal/Hydration/ScalarHydrator.php +++ b/lib/Doctrine/ORM/Internal/Hydration/ScalarHydrator.php @@ -26,25 +26,32 @@ use Doctrine\DBAL\Connection; * The created result is almost the same as a regular SQL result set, except * that column names are mapped to field names and data type conversions take place. * + * @since 2.0 * @author Roman Borschel - * @since 2.0 + * @author Guilherme Blanco */ class ScalarHydrator extends AbstractHydrator { - /** @override */ - protected function _hydrateAll() + /** + * {@inheritdoc} + */ + protected function hydrateAllData() { $result = array(); - $cache = array(); + $cache = array(); + while ($data = $this->_stmt->fetch(\PDO::FETCH_ASSOC)) { - $result[] = $this->_gatherScalarRowData($data, $cache); + $this->hydrateRowData($data, $cache, $result); } + return $result; } - /** @override */ - protected function _hydrateRow(array $data, array &$cache, array &$result) + /** + * {@inheritdoc} + */ + protected function hydrateRowData(array $data, array &$cache, array &$result) { - $result[] = $this->_gatherScalarRowData($data, $cache); + $result[] = $this->gatherScalarRowData($data, $cache); } } \ No newline at end of file diff --git a/lib/Doctrine/ORM/Internal/Hydration/SimpleObjectHydrator.php b/lib/Doctrine/ORM/Internal/Hydration/SimpleObjectHydrator.php index e9f6395f9..c045c1edf 100644 --- a/lib/Doctrine/ORM/Internal/Hydration/SimpleObjectHydrator.php +++ b/lib/Doctrine/ORM/Internal/Hydration/SimpleObjectHydrator.php @@ -17,7 +17,6 @@ * . */ - namespace Doctrine\ORM\Internal\Hydration; use \PDO; @@ -32,15 +31,21 @@ class SimpleObjectHydrator extends AbstractHydrator */ private $class; + /** + * @var array + */ private $declaringClasses = array(); - protected function _hydrateAll() + /** + * {@inheritdoc} + */ + protected function hydrateAllData() { $result = array(); $cache = array(); while ($row = $this->_stmt->fetch(PDO::FETCH_ASSOC)) { - $this->_hydrateRow($row, $cache, $result); + $this->hydrateRowData($row, $cache, $result); } $this->_em->getUnitOfWork()->triggerEagerLoads(); @@ -48,82 +53,71 @@ class SimpleObjectHydrator extends AbstractHydrator return $result; } - protected function _prepare() + /** + * {@inheritdoc} + */ + protected function prepare() { - if (count($this->_rsm->aliasMap) == 1) { - $this->class = $this->_em->getClassMetadata(reset($this->_rsm->aliasMap)); - if ($this->class->inheritanceType !== ClassMetadata::INHERITANCE_TYPE_NONE) { - foreach ($this->_rsm->declaringClasses AS $column => $class) { - $this->declaringClasses[$column] = $this->_em->getClassMetadata($class); - } - } - } else { - throw new \RuntimeException("Cannot use SimpleObjectHydrator with a ResultSetMapping not containing exactly one object result."); + if (count($this->_rsm->aliasMap) !== 1) { + throw new \RuntimeException("Cannot use SimpleObjectHydrator with a ResultSetMapping that contains more than one object result."); } + if ($this->_rsm->scalarMappings) { throw new \RuntimeException("Cannot use SimpleObjectHydrator with a ResultSetMapping that contains scalar mappings."); } + + $this->class = $this->_em->getClassMetadata(reset($this->_rsm->aliasMap)); + + // We only need to add declaring classes if we have inheritance. + if ($this->class->inheritanceType === ClassMetadata::INHERITANCE_TYPE_NONE) { + return; + } + + foreach ($this->_rsm->declaringClasses AS $column => $class) { + $this->declaringClasses[$column] = $this->_em->getClassMetadata($class); + } } - protected function _hydrateRow(array $sqlResult, array &$cache, array &$result) + /** + * {@inheritdoc} + */ + protected function hydrateRowData(array $sqlResult, array &$cache, array &$result) { - $data = array(); - if ($this->class->inheritanceType == ClassMetadata::INHERITANCE_TYPE_NONE) { - foreach ($sqlResult as $column => $value) { - - if (!isset($cache[$column])) { - if (isset($this->_rsm->fieldMappings[$column])) { - $cache[$column]['name'] = $this->_rsm->fieldMappings[$column]; - $cache[$column]['field'] = true; - } else { - $cache[$column]['name'] = $this->_rsm->metaMappings[$column]; - } - } - - if (isset($cache[$column]['field'])) { - $value = Type::getType($this->class->fieldMappings[$cache[$column]['name']]['type']) - ->convertToPHPValue($value, $this->_platform); - } - $data[$cache[$column]['name']] = $value; - } - $entityName = $this->class->name; - } else { + $entityName = $this->class->name; + $data = array(); + + // We need to find the correct entity class name if we have inheritance in resultset + if ($this->class->inheritanceType !== ClassMetadata::INHERITANCE_TYPE_NONE) { $discrColumnName = $this->_platform->getSQLResultCasing($this->class->discriminatorColumn['name']); - if ($sqlResult[$discrColumnName] === "") { + + if ($sqlResult[$discrColumnName] === '') { throw HydrationException::emptyDiscriminatorValue(key($this->_rsm->aliasMap)); } $entityName = $this->class->discriminatorMap[$sqlResult[$discrColumnName]]; + unset($sqlResult[$discrColumnName]); - - foreach ($sqlResult as $column => $value) { - if (!isset($cache[$column])) { - if (isset($this->_rsm->fieldMappings[$column])) { - $field = $this->_rsm->fieldMappings[$column]; - $class = $this->declaringClasses[$column]; - if ($class->name == $entityName || is_subclass_of($entityName, $class->name)) { - $cache[$column]['name'] = $field; - $cache[$column]['class'] = $class; - } - } else if (isset($this->_rsm->relationMap[$column])) { - if ($this->_rsm->relationMap[$column] == $entityName || is_subclass_of($entityName, $this->_rsm->relationMap[$column])) { - $cache[$column]['name'] = $field; - } - } else { - $cache[$column]['name'] = $this->_rsm->metaMappings[$column]; - } + } + + foreach ($sqlResult as $column => $value) { + // Hydrate column information if not yet present + if ( ! isset($cache[$column])) { + if (($info = $this->hydrateColumnInfo($entityName, $column)) === null) { + continue; } + + $cache[$column] = $info; + } - if (isset($cache[$column]['class'])) { - $value = Type::getType($cache[$column]['class']->fieldMappings[$cache[$column]['name']]['type']) - ->convertToPHPValue($value, $this->_platform); - } - - // the second and part is to prevent overwrites in case of multiple - // inheritance classes using the same property name (See AbstractHydrator) - if (isset($cache[$column]) && (!isset($data[$cache[$column]['name']]) || $value !== null)) { - $data[$cache[$column]['name']] = $value; - } + // Convert field to a valid PHP value + if (isset($cache[$column]['field'])) { + $type = Type::getType($cache[$column]['class']->fieldMappings[$cache[$column]['name']]['type']); + $value = $type->convertToPHPValue($value, $this->_platform); + } + + // Prevent overwrite in case of inherit classes using same property name (See AbstractHydrator) + if (isset($cache[$column]) && ( ! isset($data[$cache[$column]['name']]) || $value !== null)) { + $data[$cache[$column]['name']] = $value; } } @@ -133,4 +127,52 @@ class SimpleObjectHydrator extends AbstractHydrator $result[] = $this->_em->getUnitOfWork()->createEntity($entityName, $data, $this->_hints); } + + /** + * Retrieve column information form ResultSetMapping. + * + * @param string $entityName + * @param string $column + * + * @return array + */ + protected function hydrateColumnInfo($entityName, $column) + { + switch (true) { + case (isset($this->_rsm->fieldMappings[$column])): + $class = isset($this->declaringClasses[$column]) + ? $this->declaringClasses[$column] + : $this->class; + + // If class is not part of the inheritance, ignore + if ( ! ($class->name === $entityName || is_subclass_of($entityName, $class->name))) { + return null; + } + + return array( + 'class' => $class, + 'name' => $this->_rsm->fieldMappings[$column], + 'field' => true, + ); + + case (isset($this->_rsm->relationMap[$column])): + $class = isset($this->_rsm->relationMap[$column]) + ? $this->_rsm->relationMap[$column] + : $this->class; + + // If class is not self referencing, ignore + if ( ! ($class === $entityName || is_subclass_of($entityName, $class))) { + return null; + } + + // TODO: Decide what to do with associations. It seems original code is incomplete. + // One solution is to load the association, but it might require extra efforts. + return array('name' => $column); + + default: + return array( + 'name' => $this->_rsm->metaMappings[$column] + ); + } + } } \ No newline at end of file diff --git a/lib/Doctrine/ORM/Internal/Hydration/SingleScalarHydrator.php b/lib/Doctrine/ORM/Internal/Hydration/SingleScalarHydrator.php index 466815521..98a45960e 100644 --- a/lib/Doctrine/ORM/Internal/Hydration/SingleScalarHydrator.php +++ b/lib/Doctrine/ORM/Internal/Hydration/SingleScalarHydrator.php @@ -19,30 +19,37 @@ namespace Doctrine\ORM\Internal\Hydration; -use Doctrine\DBAL\Connection; +use Doctrine\DBAL\Connection, + Doctrine\ORM\NoResultException, + Doctrine\ORM\NonUniqueResultException; /** * Hydrator that hydrates a single scalar value from the result set. * + * @since 2.0 * @author Roman Borschel - * @since 2.0 + * @author Guilherme Blanco */ class SingleScalarHydrator extends AbstractHydrator { - /** @override */ - protected function _hydrateAll() + /** + * {@inheritdoc} + */ + protected function hydrateAllData() { - $cache = array(); - $result = $this->_stmt->fetchAll(\PDO::FETCH_ASSOC); - $num = count($result); - - if ($num == 0) { - throw new \Doctrine\ORM\NoResultException; - } else if ($num > 1 || count($result[key($result)]) > 1) { - throw new \Doctrine\ORM\NonUniqueResultException; + $data = $this->_stmt->fetchAll(\PDO::FETCH_ASSOC); + $numRows = count($data); + + if ($numRows === 0) { + throw new NoResultException(); } - $result = $this->_gatherScalarRowData($result[key($result)], $cache); + if ($numRows > 1 || count($data[key($data)]) > 1) { + throw new NonUniqueResultException(); + } + + $cache = array(); + $result = $this->gatherScalarRowData($data[key($data)], $cache); return array_shift($result); } From 058242fa27b7f41aa0effc02072a94ff25496d38 Mon Sep 17 00:00:00 2001 From: Guilherme Blanco Date: Thu, 3 Nov 2011 02:37:54 -0200 Subject: [PATCH 073/135] Fixed missing changes. --- .../ORM/Hydration/CustomHydratorTest.php | 2 +- .../ORM/Hydration/ObjectHydratorTest.php | 237 +++++++++++------- 2 files changed, 149 insertions(+), 90 deletions(-) diff --git a/tests/Doctrine/Tests/ORM/Hydration/CustomHydratorTest.php b/tests/Doctrine/Tests/ORM/Hydration/CustomHydratorTest.php index 8daf961b5..09be6bcd3 100644 --- a/tests/Doctrine/Tests/ORM/Hydration/CustomHydratorTest.php +++ b/tests/Doctrine/Tests/ORM/Hydration/CustomHydratorTest.php @@ -22,7 +22,7 @@ class CustomHydratorTest extends HydrationTestCase class CustomHydrator extends AbstractHydrator { - protected function _hydrateAll() + protected function hydrateAllData() { return $this->_stmt->fetchAll(PDO::FETCH_ASSOC); } diff --git a/tests/Doctrine/Tests/ORM/Hydration/ObjectHydratorTest.php b/tests/Doctrine/Tests/ORM/Hydration/ObjectHydratorTest.php index 57c297cf0..268118d72 100644 --- a/tests/Doctrine/Tests/ORM/Hydration/ObjectHydratorTest.php +++ b/tests/Doctrine/Tests/ORM/Hydration/ObjectHydratorTest.php @@ -16,9 +16,10 @@ require_once __DIR__ . '/../../TestInit.php'; class ObjectHydratorTest extends HydrationTestCase { /** - * SELECT PARTIAL u.{id,name} FROM \Doctrine\Tests\Models\CMS\CmsUser u + * SELECT PARTIAL u.{id,name} + * FROM Doctrine\Tests\Models\CMS\CmsUser u */ - public function testSimpleEntityQuery() + public function testSimpleEntityScalarFieldsQuery() { $rsm = new ResultSetMapping; $rsm->addEntityResult('Doctrine\Tests\Models\CMS\CmsUser', 'u'); @@ -30,22 +31,22 @@ class ObjectHydratorTest extends HydrationTestCase array( 'u__id' => '1', 'u__name' => 'romanb' - ), + ), array( 'u__id' => '2', 'u__name' => 'jwage' - ) - ); + ) + ); - - $stmt = new HydratorMockStatement($resultSet); + $stmt = new HydratorMockStatement($resultSet); $hydrator = new \Doctrine\ORM\Internal\Hydration\ObjectHydrator($this->_em); - - $result = $hydrator->hydrateAll($stmt, $rsm, array(Query::HINT_FORCE_PARTIAL_LOAD => true)); + $result = $hydrator->hydrateAll($stmt, $rsm, array(Query::HINT_FORCE_PARTIAL_LOAD => true)); $this->assertEquals(2, count($result)); + $this->assertInstanceOf('Doctrine\Tests\Models\CMS\CmsUser', $result[0]); $this->assertInstanceOf('Doctrine\Tests\Models\CMS\CmsUser', $result[1]); + $this->assertEquals(1, $result[0]->id); $this->assertEquals('romanb', $result[0]->name); $this->assertEquals(2, $result[1]->id); @@ -53,8 +54,10 @@ class ObjectHydratorTest extends HydrationTestCase } /** + * SELECT PARTIAL u.{id,name} + * FROM Doctrine\Tests\Models\CMS\CmsUser u + * * @group DDC-644 - * SELECT PARTIAL u.{id,name} FROM \Doctrine\Tests\Models\CMS\CmsUser u */ public function testSkipUnknownColumns() { @@ -69,20 +72,60 @@ class ObjectHydratorTest extends HydrationTestCase 'u__id' => '1', 'u__name' => 'romanb', 'foo' => 'bar', // unknown! - ), + ), ); - $stmt = new HydratorMockStatement($resultSet); + $stmt = new HydratorMockStatement($resultSet); $hydrator = new \Doctrine\ORM\Internal\Hydration\ObjectHydrator($this->_em); - - $result = $hydrator->hydrateAll($stmt, $rsm, array(Query::HINT_FORCE_PARTIAL_LOAD => true)); + $result = $hydrator->hydrateAll($stmt, $rsm, array(Query::HINT_FORCE_PARTIAL_LOAD => true)); $this->assertEquals(1, count($result)); } /** - * SELECT PARTIAL u.{id,name}, PARTIAL a.{id,topic} - * FROM \Doctrine\Tests\Models\CMS\CmsUser u, \Doctrine\Tests\Models\CMS\CmsArticle a + * SELECT u.id, + * u.name + * FROM Doctrine\Tests\Models\CMS\CmsUser u + */ + public function testScalarQueryWithoutResultVariables() + { + $rsm = new ResultSetMapping; + $rsm->addEntityResult('Doctrine\Tests\Models\CMS\CmsUser', 'u'); + $rsm->addScalarResult('sclr0', 'id'); + $rsm->addScalarResult('sclr1', 'name'); + + // Faked result set + $resultSet = array( + array( + 'sclr0' => '1', + 'sclr1' => 'romanb' + ), + array( + 'sclr0' => '2', + 'sclr1' => 'jwage' + ) + ); + + $stmt = new HydratorMockStatement($resultSet); + $hydrator = new \Doctrine\ORM\Internal\Hydration\ObjectHydrator($this->_em); + $result = $hydrator->hydrateAll($stmt, $rsm, array(Query::HINT_FORCE_PARTIAL_LOAD => true)); + + $this->assertEquals(2, count($result)); + + $this->assertInternalType('array', $result[0]); + $this->assertInternalType('array', $result[1]); + + $this->assertEquals(1, $result[0]['id']); + $this->assertEquals('romanb', $result[0]['name']); + $this->assertEquals(2, $result[1]['id']); + $this->assertEquals('jwage', $result[1]['name']); + } + + /** + * SELECT PARTIAL u.{id, name} + * PARTIAL a.{id, topic} + * FROM Doctrine\Tests\Models\CMS\CmsUser u, + * Doctrine\Tests\Models\CMS\CmsArticle a */ public function testSimpleMultipleRootEntityQuery() { @@ -101,20 +144,18 @@ class ObjectHydratorTest extends HydrationTestCase 'u__name' => 'romanb', 'a__id' => '1', 'a__topic' => 'Cool things.' - ), + ), array( 'u__id' => '2', 'u__name' => 'jwage', 'a__id' => '2', 'a__topic' => 'Cool things II.' - ) - ); + ) + ); - - $stmt = new HydratorMockStatement($resultSet); + $stmt = new HydratorMockStatement($resultSet); $hydrator = new \Doctrine\ORM\Internal\Hydration\ObjectHydrator($this->_em); - - $result = $hydrator->hydrateAll($stmt, $rsm, array(Query::HINT_FORCE_PARTIAL_LOAD => true)); + $result = $hydrator->hydrateAll($stmt, $rsm, array(Query::HINT_FORCE_PARTIAL_LOAD => true)); $this->assertEquals(4, count($result)); @@ -134,7 +175,8 @@ class ObjectHydratorTest extends HydrationTestCase } /** - * SELECT p FROM \Doctrine\Tests\Models\ECommerce\ECommerceProduct p + * SELECT p + * FROM Doctrine\Tests\Models\ECommerce\ECommerceProduct p */ public function testCreatesProxyForLazyLoadingWithForeignKeys() { @@ -150,8 +192,8 @@ class ObjectHydratorTest extends HydrationTestCase 'p__id' => '1', 'p__name' => 'Doctrine Book', 'p__shipping_id' => 42 - ) - ); + ) + ); $proxyInstance = new \Doctrine\Tests\Models\ECommerce\ECommerceShipping(); @@ -159,8 +201,7 @@ class ObjectHydratorTest extends HydrationTestCase $proxyFactory = $this->getMock('Doctrine\ORM\Proxy\ProxyFactory', array('getProxy'), array(), '', false, false, false); $proxyFactory->expects($this->once()) ->method('getProxy') - ->with($this->equalTo('Doctrine\Tests\Models\ECommerce\ECommerceShipping'), - array('id' => 42)) + ->with($this->equalTo('Doctrine\Tests\Models\ECommerce\ECommerceShipping'), array('id' => 42)) ->will($this->returnValue($proxyInstance)); $this->_em->setProxyFactory($proxyFactory); @@ -169,32 +210,36 @@ class ObjectHydratorTest extends HydrationTestCase $metadata = $this->_em->getClassMetadata('Doctrine\Tests\Models\ECommerce\ECommerceProduct'); $metadata->associationMappings['shipping']['fetch'] = ClassMetadata::FETCH_LAZY; - $stmt = new HydratorMockStatement($resultSet); + $stmt = new HydratorMockStatement($resultSet); $hydrator = new \Doctrine\ORM\Internal\Hydration\ObjectHydrator($this->_em); - - $result = $hydrator->hydrateAll($stmt, $rsm); + $result = $hydrator->hydrateAll($stmt, $rsm); $this->assertEquals(1, count($result)); + $this->assertInstanceOf('Doctrine\Tests\Models\ECommerce\ECommerceProduct', $result[0]); } /** - * select u, p, upper(u.name) nameUpper from User u JOIN u.phonenumbers p + * SELECT PARTIAL u.{id, status}, + * PARTIAL p.{phonenumber}, + * UPPER(u.name) nameUpper + * FROM User u + * JOIN u.phonenumbers p */ public function testMixedQueryFetchJoin() { $rsm = new ResultSetMapping; $rsm->addEntityResult('Doctrine\Tests\Models\CMS\CmsUser', 'u'); $rsm->addJoinedEntityResult( - 'Doctrine\Tests\Models\CMS\CmsPhonenumber', - 'p', - 'u', - 'phonenumbers' + 'Doctrine\Tests\Models\CMS\CmsPhonenumber', + 'p', + 'u', + 'phonenumbers' ); $rsm->addFieldResult('u', 'u__id', 'id'); $rsm->addFieldResult('u', 'u__status', 'status'); - $rsm->addScalarResult('sclr0', 'nameUpper'); $rsm->addFieldResult('p', 'p__phonenumber', 'phonenumber'); + $rsm->addScalarResult('sclr0', 'nameUpper'); // Faked result set $resultSet = array( @@ -202,32 +247,32 @@ class ObjectHydratorTest extends HydrationTestCase array( 'u__id' => '1', 'u__status' => 'developer', - 'sclr0' => 'ROMANB', 'p__phonenumber' => '42', - ), + 'sclr0' => 'ROMANB', + ), array( 'u__id' => '1', 'u__status' => 'developer', - 'sclr0' => 'ROMANB', 'p__phonenumber' => '43', - ), + 'sclr0' => 'ROMANB', + ), array( 'u__id' => '2', 'u__status' => 'developer', + 'p__phonenumber' => '91', 'sclr0' => 'JWAGE', - 'p__phonenumber' => '91' - ) - ); + ) + ); - $stmt = new HydratorMockStatement($resultSet); + $stmt = new HydratorMockStatement($resultSet); $hydrator = new \Doctrine\ORM\Internal\Hydration\ObjectHydrator($this->_em); - - $result = $hydrator->hydrateAll($stmt, $rsm, array(Query::HINT_FORCE_PARTIAL_LOAD => true)); + $result = $hydrator->hydrateAll($stmt, $rsm, array(Query::HINT_FORCE_PARTIAL_LOAD => true)); $this->assertEquals(2, count($result)); - $this->assertTrue(is_array($result)); - $this->assertTrue(is_array($result[0])); - $this->assertTrue(is_array($result[1])); + + $this->assertInternalType('array', $result); + $this->assertInternalType('array', $result[0]); + $this->assertInternalType('array', $result[1]); $this->assertInstanceOf('Doctrine\Tests\Models\CMS\CmsUser', $result[0][0]); $this->assertInstanceOf('Doctrine\ORM\PersistentCollection', $result[0][0]->phonenumbers); @@ -239,6 +284,7 @@ class ObjectHydratorTest extends HydrationTestCase // first user => 2 phonenumbers $this->assertEquals(2, count($result[0][0]->phonenumbers)); $this->assertEquals('ROMANB', $result[0]['nameUpper']); + // second user => 1 phonenumber $this->assertEquals(1, count($result[1][0]->phonenumbers)); $this->assertEquals('JWAGE', $result[1]['nameUpper']); @@ -249,8 +295,11 @@ class ObjectHydratorTest extends HydrationTestCase } /** - * select u, count(p.phonenumber) numPhones from User u - * join u.phonenumbers p group by u.id + * SELECT PARTIAL u.{id, status}, + * COUNT(p.phonenumber) numPhones + * FROM User u + * JOIN u.phonenumbers p + * GROUP BY u.id */ public function testMixedQueryNormalJoin() { @@ -267,25 +316,27 @@ class ObjectHydratorTest extends HydrationTestCase 'u__id' => '1', 'u__status' => 'developer', 'sclr0' => '2', - ), + ), array( 'u__id' => '2', 'u__status' => 'developer', 'sclr0' => '1', - ) - ); + ) + ); - $stmt = new HydratorMockStatement($resultSet); + $stmt = new HydratorMockStatement($resultSet); $hydrator = new \Doctrine\ORM\Internal\Hydration\ObjectHydrator($this->_em); - - $result = $hydrator->hydrateAll($stmt, $rsm, array(Query::HINT_FORCE_PARTIAL_LOAD => true)); + $result = $hydrator->hydrateAll($stmt, $rsm, array(Query::HINT_FORCE_PARTIAL_LOAD => true)); $this->assertEquals(2, count($result)); - $this->assertTrue(is_array($result)); - $this->assertTrue(is_array($result[0])); - $this->assertTrue(is_array($result[1])); + + $this->assertInternalType('array', $result); + $this->assertInternalType('array', $result[0]); + $this->assertInternalType('array', $result[1]); + // first user => 2 phonenumbers $this->assertEquals(2, $result[0]['numPhones']); + // second user => 1 phonenumber $this->assertEquals(1, $result[1]['numPhones']); $this->assertInstanceOf('Doctrine\Tests\Models\CMS\CmsUser', $result[0][0]); @@ -293,18 +344,23 @@ class ObjectHydratorTest extends HydrationTestCase } /** - * select u, p, upper(u.name) nameUpper from User u index by u.id - * join u.phonenumbers p indexby p.phonenumber + * SELECT u, + * p, + * UPPER(u.name) nameUpper + * FROM User u + * INDEX BY u.id + * JOIN u.phonenumbers p + * INDEX BY p.phonenumber */ public function testMixedQueryFetchJoinCustomIndex() { $rsm = new ResultSetMapping; $rsm->addEntityResult('Doctrine\Tests\Models\CMS\CmsUser', 'u'); $rsm->addJoinedEntityResult( - 'Doctrine\Tests\Models\CMS\CmsPhonenumber', - 'p', - 'u', - 'phonenumbers' + 'Doctrine\Tests\Models\CMS\CmsPhonenumber', + 'p', + 'u', + 'phonenumbers' ); $rsm->addFieldResult('u', 'u__id', 'id'); $rsm->addFieldResult('u', 'u__status', 'status'); @@ -321,31 +377,31 @@ class ObjectHydratorTest extends HydrationTestCase 'u__status' => 'developer', 'sclr0' => 'ROMANB', 'p__phonenumber' => '42', - ), + ), array( 'u__id' => '1', 'u__status' => 'developer', 'sclr0' => 'ROMANB', 'p__phonenumber' => '43', - ), + ), array( 'u__id' => '2', 'u__status' => 'developer', 'sclr0' => 'JWAGE', 'p__phonenumber' => '91' - ) - ); + ) + ); - $stmt = new HydratorMockStatement($resultSet); + $stmt = new HydratorMockStatement($resultSet); $hydrator = new \Doctrine\ORM\Internal\Hydration\ObjectHydrator($this->_em); - - $result = $hydrator->hydrateAll($stmt, $rsm, array(Query::HINT_FORCE_PARTIAL_LOAD => true)); + $result = $hydrator->hydrateAll($stmt, $rsm, array(Query::HINT_FORCE_PARTIAL_LOAD => true)); $this->assertEquals(2, count($result)); - $this->assertTrue(is_array($result)); - $this->assertTrue(is_array($result[1])); - $this->assertTrue(is_array($result[2])); + + $this->assertInternalType('array', $result); + $this->assertInternalType('array', $result[1]); + $this->assertInternalType('array', $result[2]); // test the scalar values $this->assertEquals('ROMANB', $result[1]['nameUpper']); @@ -354,10 +410,13 @@ class ObjectHydratorTest extends HydrationTestCase $this->assertInstanceOf('Doctrine\Tests\Models\CMS\CmsUser', $result[1][0]); $this->assertInstanceOf('Doctrine\Tests\Models\CMS\CmsUser', $result[2][0]); $this->assertInstanceOf('Doctrine\ORM\PersistentCollection', $result[1][0]->phonenumbers); + // first user => 2 phonenumbers. notice the custom indexing by user id $this->assertEquals(2, count($result[1][0]->phonenumbers)); + // second user => 1 phonenumber. notice the custom indexing by user id $this->assertEquals(1, count($result[2][0]->phonenumbers)); + // test the custom indexing of the phonenumbers $this->assertTrue(isset($result[1][0]->phonenumbers['42'])); $this->assertTrue(isset($result[1][0]->phonenumbers['43'])); @@ -1130,15 +1189,15 @@ class ObjectHydratorTest extends HydrationTestCase 'sclr0' => 'ROMANB', 'a__id' => 1, 'a__city' => 'Berlin', - ), + ), array( 'u__id' => '2', 'u__status' => 'developer', 'sclr0' => 'BENJAMIN', 'a__id' => null, 'a__city' => null, - ), - ); + ), + ); $stmt = new HydratorMockStatement($resultSet); $hydrator = new \Doctrine\ORM\Internal\Hydration\ObjectHydrator($this->_em); @@ -1169,13 +1228,13 @@ class ObjectHydratorTest extends HydrationTestCase 'u__id' => '1', 'u__status' => 'developer', 'sclr0' => 'ROMANB', - ), + ), array( 'u__id' => '2', 'u__status' => 'developer', 'sclr0' => 'JWAGE', - ), - ); + ), + ); $stmt = new HydratorMockStatement($resultSet); $hydrator = new \Doctrine\ORM\Internal\Hydration\ObjectHydrator($this->_em); @@ -1198,21 +1257,21 @@ class ObjectHydratorTest extends HydrationTestCase $rsm->addScalarResult('sclr0', 'nameUpper'); $rsm->addIndexByScalar('sclr0'); - // Faked result set + // Faked result set $resultSet = array( //row1 array( 'sclr0' => 'ROMANB', - ), + ), array( 'sclr0' => 'JWAGE', - ), - ); + ), + ); - $stmt = new HydratorMockStatement($resultSet); + $stmt = new HydratorMockStatement($resultSet); $hydrator = new \Doctrine\ORM\Internal\Hydration\ObjectHydrator($this->_em); - $result = $hydrator->hydrateAll($stmt, $rsm, array(Query::HINT_FORCE_PARTIAL_LOAD => true)); + $result = $hydrator->hydrateAll($stmt, $rsm, array(Query::HINT_FORCE_PARTIAL_LOAD => true)); $this->assertEquals(array('ROMANB' => array('nameUpper' => 'ROMANB'), 'JWAGE' => array('nameUpper' => 'JWAGE')), $result); } From 3c31d888104107f5759bcef36a8db774324f3daf Mon Sep 17 00:00:00 2001 From: Guilherme Blanco Date: Thu, 3 Nov 2011 02:44:50 -0200 Subject: [PATCH 074/135] Major optimizations in SqlWalker code, reducing overhead, reducing lookahead checks. --- lib/Doctrine/ORM/Query/QueryException.php | 5 + lib/Doctrine/ORM/Query/SqlWalker.php | 550 +++++++----------- .../ORM/Query/SelectSqlGenerationTest.php | 8 +- 3 files changed, 235 insertions(+), 328 deletions(-) diff --git a/lib/Doctrine/ORM/Query/QueryException.php b/lib/Doctrine/ORM/Query/QueryException.php index 39dc42505..b9ab2bb07 100644 --- a/lib/Doctrine/ORM/Query/QueryException.php +++ b/lib/Doctrine/ORM/Query/QueryException.php @@ -47,6 +47,11 @@ class QueryException extends \Doctrine\ORM\ORMException return new self('[Semantical Error] ' . $message); } + public static function invalidLockMode() + { + return new self('Invalid lock mode hint provided.'); + } + public static function invalidParameterType($expected, $received) { return new self('Invalid parameter type, ' . $received . ' given, but ' . $expected . ' expected.'); diff --git a/lib/Doctrine/ORM/Query/SqlWalker.php b/lib/Doctrine/ORM/Query/SqlWalker.php index 13021a4eb..b0f919549 100644 --- a/lib/Doctrine/ORM/Query/SqlWalker.php +++ b/lib/Doctrine/ORM/Query/SqlWalker.php @@ -252,34 +252,40 @@ class SqlWalker implements TreeWalker // INNER JOIN parent class tables foreach ($class->parentClasses as $parentClassName) { $parentClass = $this->_em->getClassMetadata($parentClassName); - $tableAlias = $this->getSQLTableAlias($parentClass->getTableName(), $dqlAlias); + $tableAlias = $this->getSQLTableAlias($parentClass->getTableName(), $dqlAlias); // If this is a joined association we must use left joins to preserve the correct result. $sql .= isset($this->_queryComponents[$dqlAlias]['relation']) ? ' LEFT ' : ' INNER '; $sql .= 'JOIN ' . $parentClass->getQuotedTableName($this->_platform) . ' ' . $tableAlias . ' ON '; - $first = true; + + $sqlParts = array(); foreach ($class->getQuotedIdentifierColumnNames($this->_platform) as $columnName) { - if ($first) $first = false; else $sql .= ' AND '; - - $sql .= $baseTableAlias . '.' . $columnName . ' = ' . $tableAlias . '.' . $columnName; + $sqlParts[] = $baseTableAlias . '.' . $columnName . ' = ' . $tableAlias . '.' . $columnName; } + + $sql .= implode(' AND ', $sqlParts); } - // LEFT JOIN subclass tables, if partial objects disallowed. - if ( ! $this->_query->getHint(Query::HINT_FORCE_PARTIAL_LOAD)) { - foreach ($class->subClasses as $subClassName) { - $subClass = $this->_em->getClassMetadata($subClassName); - $tableAlias = $this->getSQLTableAlias($subClass->getTableName(), $dqlAlias); - $sql .= ' LEFT JOIN ' . $subClass->getQuotedTableName($this->_platform) . ' ' . $tableAlias . ' ON '; - $first = true; + // Ignore subclassing inclusion if partial objects is disallowed + if ($this->_query->getHint(Query::HINT_FORCE_PARTIAL_LOAD)) { + return $sql; + } + + // LEFT JOIN child class tables + foreach ($class->subClasses as $subClassName) { + $subClass = $this->_em->getClassMetadata($subClassName); + $tableAlias = $this->getSQLTableAlias($subClass->getTableName(), $dqlAlias); - foreach ($class->getQuotedIdentifierColumnNames($this->_platform) as $columnName) { - if ($first) $first = false; else $sql .= ' AND '; + $sql .= ' LEFT JOIN ' . $subClass->getQuotedTableName($this->_platform) . ' ' . $tableAlias . ' ON '; - $sql .= $baseTableAlias . '.' . $columnName . ' = ' . $tableAlias . '.' . $columnName; - } + $sqlParts = array(); + + foreach ($subClass->getQuotedIdentifierColumnNames($this->_platform) as $columnName) { + $sqlParts[] = $baseTableAlias . '.' . $columnName . ' = ' . $tableAlias . '.' . $columnName; } + + $sql .= implode(' AND ', $sqlParts); } return $sql; @@ -287,28 +293,24 @@ class SqlWalker implements TreeWalker private function _generateOrderedCollectionOrderByItems() { - $sql = ''; - + $sqlParts = array(); + foreach ($this->_selectedClasses AS $dqlAlias => $class) { $qComp = $this->_queryComponents[$dqlAlias]; - if (isset($qComp['relation']['orderBy'])) { - foreach ($qComp['relation']['orderBy'] AS $fieldName => $orientation) { - $tableName = ($qComp['metadata']->isInheritanceTypeJoined()) - ? $this->_em->getUnitOfWork()->getEntityPersister($class->name)->getOwningTable($fieldName) - : $qComp['metadata']->getTableName(); - - if ($sql != '') { - $sql .= ', '; - } - - $sql .= $this->getSQLTableAlias($tableName, $dqlAlias) . '.' - . $qComp['metadata']->getQuotedColumnName($fieldName, $this->_platform) . ' ' . $orientation; - } + if ( ! isset($qComp['relation']['orderBy'])) continue; + + foreach ($qComp['relation']['orderBy'] AS $fieldName => $orientation) { + $columnName = $qComp['metadata']->getQuotedColumnName($fieldName, $this->_platform); + $tableName = ($qComp['metadata']->isInheritanceTypeJoined()) + ? $this->_em->getUnitOfWork()->getEntityPersister($class->name)->getOwningTable($fieldName) + : $qComp['metadata']->getTableName(); + + $sqlParts[] = $this->getSQLTableAlias($tableName, $dqlAlias) . '.' . $columnName . ' ' . $orientation; } } - return $sql; + return implode(', ', $sqlParts); } /** @@ -319,36 +321,31 @@ class SqlWalker implements TreeWalker */ private function _generateDiscriminatorColumnConditionSQL(array $dqlAliases) { - $encapsulate = false; - $sql = ''; + $sqlParts = array(); foreach ($dqlAliases as $dqlAlias) { $class = $this->_queryComponents[$dqlAlias]['metadata']; - if ($class->isInheritanceTypeSingleTable()) { - $conn = $this->_em->getConnection(); - $values = array(); + if ( ! $class->isInheritanceTypeSingleTable()) continue; + + $conn = $this->_em->getConnection(); + $values = array(); - if ($class->discriminatorValue !== null) { // discrimnators can be 0 - $values[] = $conn->quote($class->discriminatorValue); - } - - foreach ($class->subClasses as $subclassName) { - $values[] = $conn->quote($this->_em->getClassMetadata($subclassName)->discriminatorValue); - } - - if ($sql != '') { - $sql .= ' AND '; - $encapsulate = true; - } - - $sql .= ($sql != '' ? ' AND ' : '') - . (($this->_useSqlTableAliases) ? $this->getSQLTableAlias($class->getTableName(), $dqlAlias) . '.' : '') - . $class->discriminatorColumn['name'] . ' IN (' . implode(', ', $values) . ')'; + if ($class->discriminatorValue !== null) { // discrimnators can be 0 + $values[] = $conn->quote($class->discriminatorValue); } + + foreach ($class->subClasses as $subclassName) { + $values[] = $conn->quote($this->_em->getClassMetadata($subclassName)->discriminatorValue); + } + + $sqlParts[] = (($this->_useSqlTableAliases) ? $this->getSQLTableAlias($class->getTableName(), $dqlAlias) . '.' : '') + . $class->discriminatorColumn['name'] . ' IN (' . implode(', ', $values) . ')'; } - return ($encapsulate) ? '(' . $sql . ')' : $sql; + $sql = implode(' AND ', $sqlParts); + + return (count($sqlParts) > 1) ? '(' . $sql . ')' : $sql; } /** @@ -375,16 +372,25 @@ class SqlWalker implements TreeWalker ); if (($lockMode = $this->_query->getHint(Query::HINT_LOCK_MODE)) !== false) { - if ($lockMode == LockMode::PESSIMISTIC_READ) { - $sql .= ' ' . $this->_platform->getReadLockSQL(); - } else if ($lockMode == LockMode::PESSIMISTIC_WRITE) { - $sql .= ' ' . $this->_platform->getWriteLockSQL(); - } else if ($lockMode == LockMode::OPTIMISTIC) { - foreach ($this->_selectedClasses AS $class) { - if ( ! $class->isVersioned) { - throw \Doctrine\ORM\OptimisticLockException::lockFailed($class->name); + switch ($lockMode) { + case LockMode::PESSIMISTIC_READ: + $sql .= ' ' . $this->_platform->getReadLockSQL(); + break; + + case LockMode::PESSIMISTIC_WRITE: + $sql .= ' ' . $this->_platform->getWriteLockSQL(); + break; + + case LockMode::PESSIMISTIC_OPTIMISTIC: + foreach ($this->_selectedClasses AS $class) { + if ( ! $class->isVersioned) { + throw \Doctrine\ORM\OptimisticLockException::lockFailed($class->name); + } } - } + break; + + default: + throw \Doctrine\ORM\Query\QueryException::invalidLockMode(); } } @@ -529,8 +535,8 @@ class SqlWalker implements TreeWalker if ($class->isInheritanceTypeSingleTable() || $class->isInheritanceTypeJoined()) { // Add discriminator columns to SQL - $rootClass = $this->_em->getClassMetadata($class->rootEntityName); - $tblAlias = $this->getSQLTableAlias($rootClass->getTableName(), $dqlAlias); + $rootClass = $this->_em->getClassMetadata($class->rootEntityName); + $tblAlias = $this->getSQLTableAlias($rootClass->getTableName(), $dqlAlias); $discrColumn = $rootClass->discriminatorColumn; $columnAlias = $this->getSQLColumnAlias($discrColumn['name']); @@ -538,44 +544,44 @@ class SqlWalker implements TreeWalker $this->_rsm->setDiscriminatorColumn($dqlAlias, $columnAlias); $this->_rsm->addMetaResult($dqlAlias, $columnAlias, $discrColumn['fieldName']); + } + + // Add foreign key columns to SQL, if necessary + if ( ! $addMetaColumns) continue; + + // Add foreign key columns of class and also parent classes + foreach ($class->associationMappings as $assoc) { + if ( ! ($assoc['isOwningSide'] && $assoc['type'] & ClassMetadata::TO_ONE)) continue; - // Add foreign key columns to SQL, if necessary - if ($addMetaColumns) { - //FIXME: Include foreign key columns of child classes also!!?? - foreach ($class->associationMappings as $assoc) { - if ($assoc['isOwningSide'] && $assoc['type'] & ClassMetadata::TO_ONE) { - if (isset($assoc['inherited'])) { - $owningClass = $this->_em->getClassMetadata($assoc['inherited']); - $sqlTableAlias = $this->getSQLTableAlias($owningClass->getTableName(), $dqlAlias); - } else { - $sqlTableAlias = $this->getSQLTableAlias($class->getTableName(), $dqlAlias); - } + $owningClass = (isset($assoc['inherited'])) ? $this->_em->getClassMetadata($assoc['inherited']) : $class; + $sqlTableAlias = $this->getSQLTableAlias($owningClass->getTableName(), $dqlAlias); - foreach ($assoc['targetToSourceKeyColumns'] as $srcColumn) { - $columnAlias = $this->getSQLColumnAlias($srcColumn); + foreach ($assoc['targetToSourceKeyColumns'] as $srcColumn) { + $columnAlias = $this->getSQLColumnAlias($srcColumn); - $sqlSelectExpressions[] = $sqlTableAlias . '.' . $srcColumn . ' AS ' . $columnAlias; + $sqlSelectExpressions[] = $sqlTableAlias . '.' . $srcColumn . ' AS ' . $columnAlias; - $this->_rsm->addMetaResult($dqlAlias, $columnAlias, $srcColumn, (isset($assoc['id']) && $assoc['id'] === true)); - } - } - } + $this->_rsm->addMetaResult($dqlAlias, $columnAlias, $srcColumn, (isset($assoc['id']) && $assoc['id'] === true)); } - } else { - // Add foreign key columns to SQL, if necessary - if ($addMetaColumns) { - $sqlTableAlias = $this->getSQLTableAlias($class->getTableName(), $dqlAlias); + } + + // Add foreign key columns of subclasses + foreach ($class->subClasses as $subClassName) { + $subClass = $this->_em->getClassMetadata($subClassName); + $sqlTableAlias = $this->getSQLTableAlias($subClass->getTableName(), $dqlAlias); - foreach ($class->associationMappings as $assoc) { - if ($assoc['isOwningSide'] && $assoc['type'] & ClassMetadata::TO_ONE) { - foreach ($assoc['targetToSourceKeyColumns'] as $srcColumn) { - $columnAlias = $this->getSQLColumnAlias($srcColumn); + foreach ($subClass->associationMappings as $assoc) { + // Skip if association is inherited + if (isset($assoc['inherited'])) continue; + + if ( ! ($assoc['isOwningSide'] && $assoc['type'] & ClassMetadata::TO_ONE)) continue; + + foreach ($assoc['targetToSourceKeyColumns'] as $srcColumn) { + $columnAlias = $this->getSQLColumnAlias($srcColumn); + + $sqlSelectExpressions[] = $sqlTableAlias . '.' . $srcColumn . ' AS ' . $columnAlias; - $sqlSelectExpressions[] = $sqlTableAlias . '.' . $srcColumn . ' AS ' . $columnAlias; - - $this->_rsm->addMetaResult($dqlAlias, $columnAlias, $srcColumn, (isset($assoc['id']) && $assoc['id'] === true)); - } - } + $this->_rsm->addMetaResult($dqlAlias, $columnAlias, $srcColumn); } } } @@ -654,15 +660,13 @@ class SqlWalker implements TreeWalker */ public function walkOrderByClause($orderByClause) { - $colSql = $this->_generateOrderedCollectionOrderByItems(); - if ($colSql != '') { - $colSql = ", ".$colSql; + $orderByItems = array_map(array($this, 'walkOrderByItem'), $orderByClause->orderByItems); + + if (($collectionOrderByItems = $this->_generateOrderedCollectionOrderByItems()) !== '') { + $orderByItems = array_merge($orderByItems, (array) $collectionOrderByItems); } - - // OrderByClause ::= "ORDER" "BY" OrderByItem {"," OrderByItem}* - return ' ORDER BY ' . implode( - ', ', array_map(array($this, 'walkOrderByItem'), $orderByClause->orderByItems) - ) . $colSql; + + return ' ORDER BY ' . implode(', ', $orderByItems); } /** @@ -673,16 +677,10 @@ class SqlWalker implements TreeWalker */ public function walkOrderByItem($orderByItem) { - $sql = ''; $expr = $orderByItem->expression; - - if ($expr instanceof AST\PathExpression) { - $sql = $this->walkPathExpression($expr); - } else { - $columnName = $this->_queryComponents[$expr]['token']['value']; - - $sql = $this->_scalarResultAliasMap[$columnName]; - } + $sql = ($expr instanceof AST\PathExpression) + ? $this->walkPathExpression($expr) + : $this->_scalarResultAliasMap[$this->_queryComponents[$expr]['token']['value']]; return $sql . ' ' . strtoupper($orderByItem->type); } @@ -706,22 +704,11 @@ class SqlWalker implements TreeWalker */ public function walkJoinVariableDeclaration($joinVarDecl) { - $join = $joinVarDecl->join; + $join = $joinVarDecl->join; $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) { - $sql = ' LEFT JOIN '; - } else { - $sql = ' INNER JOIN '; - } + $sql = ($joinType == AST\Join::JOIN_TYPE_LEFT || $joinType == AST\Join::JOIN_TYPE_LEFTOUTER) + ? ' LEFT JOIN ' + : ' INNER JOIN '; $joinAssocPathExpr = $join->joinAssociationPathExpression; $joinedDqlAlias = $join->aliasIdentificationVariable; @@ -737,10 +724,8 @@ class SqlWalker implements TreeWalker // Ensure we got the owning side, since it has all mapping info $assoc = ( ! $relation['isOwningSide']) ? $targetClass->associationMappings[$relation['mappedBy']] : $relation; - if ($this->_query->getHint(Query::HINT_INTERNAL_ITERATION) == true) { - if ($relation['type'] == ClassMetadata::ONE_TO_MANY || $relation['type'] == ClassMetadata::MANY_TO_MANY) { - throw QueryException::iterateWithFetchJoinNotAllowed($assoc); - } + if ($this->_query->getHint(Query::HINT_INTERNAL_ITERATION) == true && $relation['type'] & ClassMetadata::TO_MANY) { + throw QueryException::iterateWithFetchJoinNotAllowed($assoc); } if ($joinVarDecl->indexBy) { @@ -757,7 +742,6 @@ class SqlWalker implements TreeWalker // 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. if ($assoc['type'] & ClassMetadata::TO_ONE) { - $sql .= $targetTableName . ' ' . $targetTableAlias . ' ON '; $first = true; @@ -984,199 +968,133 @@ class SqlWalker implements TreeWalker $expr = $selectExpression->expression; $hidden = $selectExpression->hiddenAliasResultVariable; - if ($expr instanceof AST\PathExpression) { - if ($expr->type !== AST\PathExpression::TYPE_STATE_FIELD) { - throw QueryException::invalidPathExpression($expr->type); - } - - $fieldName = $expr->field; - $dqlAlias = $expr->identificationVariable; - $qComp = $this->_queryComponents[$dqlAlias]; - $class = $qComp['metadata']; - - $resultAlias = ( ! $selectExpression->fieldIdentificationVariable) - ? $fieldName - : $selectExpression->fieldIdentificationVariable; - - $tableName = ($class->isInheritanceTypeJoined()) - ? $this->_em->getUnitOfWork()->getEntityPersister($class->name)->getOwningTable($fieldName) - : $class->getTableName(); - - $sqlTableAlias = $this->getSQLTableAlias($tableName, $dqlAlias); - $columnName = $class->getQuotedColumnName($fieldName, $this->_platform); - - $columnAlias = $this->getSQLColumnAlias($columnName); - $sql .= $sqlTableAlias . '.' . $columnName . ' AS ' . $columnAlias; - - if ( ! $hidden) { - $this->_rsm->addScalarResult($columnAlias, $resultAlias); - $this->_scalarFields[$dqlAlias][$fieldName] = $columnAlias; - } - } else if ($expr instanceof AST\AggregateExpression) { - if ( ! $selectExpression->fieldIdentificationVariable) { - $resultAlias = $this->_scalarResultCounter++; - } else { - $resultAlias = $selectExpression->fieldIdentificationVariable; - } - - $columnAlias = $this->getSQLColumnAlias('sclr'); - $sql .= $this->walkAggregateExpression($expr) . ' AS ' . $columnAlias; - $this->_scalarResultAliasMap[$resultAlias] = $columnAlias; - - if ( ! $hidden) { - $this->_rsm->addScalarResult($columnAlias, $resultAlias); - } - } else if ($expr instanceof AST\Subselect) { - if ( ! $selectExpression->fieldIdentificationVariable) { - $resultAlias = $this->_scalarResultCounter++; - } else { - $resultAlias = $selectExpression->fieldIdentificationVariable; - } - - $columnAlias = $this->getSQLColumnAlias('sclr'); - $sql .= '(' . $this->walkSubselect($expr) . ') AS '.$columnAlias; - $this->_scalarResultAliasMap[$resultAlias] = $columnAlias; - - if ( ! $hidden) { - $this->_rsm->addScalarResult($columnAlias, $resultAlias); - } - } else if ($expr instanceof AST\Functions\FunctionNode) { - if ( ! $selectExpression->fieldIdentificationVariable) { - $resultAlias = $this->_scalarResultCounter++; - } else { - $resultAlias = $selectExpression->fieldIdentificationVariable; - } - - $columnAlias = $this->getSQLColumnAlias('sclr'); - $sql .= $this->walkFunction($expr) . ' AS ' . $columnAlias; - $this->_scalarResultAliasMap[$resultAlias] = $columnAlias; - - if ( ! $hidden) { - $this->_rsm->addScalarResult($columnAlias, $resultAlias); - } - } else if ( - $expr instanceof AST\SimpleArithmeticExpression || - $expr instanceof AST\ArithmeticTerm || - $expr instanceof AST\ArithmeticFactor || - $expr instanceof AST\ArithmeticPrimary || - $expr instanceof AST\Literal - ) { - if ( ! $selectExpression->fieldIdentificationVariable) { - $resultAlias = $this->_scalarResultCounter++; - } else { - $resultAlias = $selectExpression->fieldIdentificationVariable; - } - - $columnAlias = $this->getSQLColumnAlias('sclr'); - if ($expr instanceof AST\Literal) { - $sql .= $this->walkLiteral($expr) . ' AS ' .$columnAlias; - } else { - $sql .= $this->walkSimpleArithmeticExpression($expr) . ' AS ' . $columnAlias; - } - - $this->_scalarResultAliasMap[$resultAlias] = $columnAlias; - - if ( ! $hidden) { - $this->_rsm->addScalarResult($columnAlias, $resultAlias); - } - } else if ( - $expr instanceof AST\NullIfExpression || - $expr instanceof AST\CoalesceExpression || - $expr instanceof AST\GeneralCaseExpression || - $expr instanceof AST\SimpleCaseExpression - ) { - if ( ! $selectExpression->fieldIdentificationVariable) { - $resultAlias = $this->_scalarResultCounter++; - } else { - $resultAlias = $selectExpression->fieldIdentificationVariable; - } - - $columnAlias = $this->getSQLColumnAlias('sclr'); - $sql .= $this->walkCaseExpression($expr) . ' AS ' . $columnAlias; - - $this->_scalarResultAliasMap[$resultAlias] = $columnAlias; - - if ( ! $hidden) { - $this->_rsm->addScalarResult($columnAlias, $resultAlias); - } - } else { - // IdentificationVariable or PartialObjectExpression - if ($expr instanceof AST\PartialObjectExpression) { - $dqlAlias = $expr->identificationVariable; - $partialFieldSet = $expr->partialFieldSet; - } else { - $dqlAlias = $expr; - $partialFieldSet = array(); - } - - $queryComp = $this->_queryComponents[$dqlAlias]; - $class = $queryComp['metadata']; - - if ( ! isset($this->_selectedClasses[$dqlAlias])) { - $this->_selectedClasses[$dqlAlias] = $class; - } - - $beginning = true; - - // Select all fields from the queried class - foreach ($class->fieldMappings as $fieldName => $mapping) { - if ($partialFieldSet && !in_array($fieldName, $partialFieldSet)) { - continue; + switch (true) { + case ($expr instanceof AST\PathExpression): + if ($expr->type !== AST\PathExpression::TYPE_STATE_FIELD) { + throw QueryException::invalidPathExpression($expr->type); } - $tableName = (isset($mapping['inherited'])) - ? $this->_em->getClassMetadata($mapping['inherited'])->getTableName() + $fieldName = $expr->field; + $dqlAlias = $expr->identificationVariable; + $qComp = $this->_queryComponents[$dqlAlias]; + $class = $qComp['metadata']; + + $resultAlias = $selectExpression->fieldIdentificationVariable ?: $fieldName; + $tableName = ($class->isInheritanceTypeJoined()) + ? $this->_em->getUnitOfWork()->getEntityPersister($class->name)->getOwningTable($fieldName) : $class->getTableName(); - if ($beginning) $beginning = false; else $sql .= ', '; - $sqlTableAlias = $this->getSQLTableAlias($tableName, $dqlAlias); - $columnAlias = $this->getSQLColumnAlias($mapping['columnName']); - $sql .= $sqlTableAlias . '.' . $class->getQuotedColumnName($fieldName, $this->_platform) - . ' AS ' . $columnAlias; + $columnName = $class->getQuotedColumnName($fieldName, $this->_platform); + $columnAlias = $this->getSQLColumnAlias($columnName); + + $sql .= $sqlTableAlias . '.' . $columnName . ' AS ' . $columnAlias; - $this->_rsm->addFieldResult($dqlAlias, $columnAlias, $fieldName, $class->name); - } + if ( ! $hidden) { + $this->_rsm->addScalarResult($columnAlias, $resultAlias); + $this->_scalarFields[$dqlAlias][$fieldName] = $columnAlias; + } + break; + + case ($expr instanceof AST\AggregateExpression): + case ($expr instanceof AST\Functions\FunctionNode): + case ($expr instanceof AST\SimpleArithmeticExpression): + case ($expr instanceof AST\ArithmeticTerm): + case ($expr instanceof AST\ArithmeticFactor): + case ($expr instanceof AST\ArithmeticPrimary): + case ($expr instanceof AST\Literal): + case ($expr instanceof AST\NullIfExpression): + case ($expr instanceof AST\CoalesceExpression): + case ($expr instanceof AST\GeneralCaseExpression): + case ($expr instanceof AST\SimpleCaseExpression): + $columnAlias = $this->getSQLColumnAlias('sclr'); + $resultAlias = $selectExpression->fieldIdentificationVariable ?: $this->_scalarResultCounter++; + + $sql .= $expr->dispatch($this) . ' AS ' . $columnAlias; + + $this->_scalarResultAliasMap[$resultAlias] = $columnAlias; - // Add any additional fields of subclasses (excluding inherited fields) - // 1) on Single Table Inheritance: always, since its marginal overhead - // 2) on Class Table Inheritance only if partial objects are disallowed, - // since it requires outer joining subtables. - if ($class->isInheritanceTypeSingleTable() || ! $this->_query->getHint(Query::HINT_FORCE_PARTIAL_LOAD)) { - foreach ($class->subClasses as $subClassName) { - $subClass = $this->_em->getClassMetadata($subClassName); - $sqlTableAlias = $this->getSQLTableAlias($subClass->getTableName(), $dqlAlias); + if ( ! $hidden) { + $this->_rsm->addScalarResult($columnAlias, $resultAlias); + } + break; + + case ($expr instanceof AST\Subselect): + $columnAlias = $this->getSQLColumnAlias('sclr'); + $resultAlias = $selectExpression->fieldIdentificationVariable ?: $this->_scalarResultCounter++; + + $sql .= '(' . $this->walkSubselect($expr) . ') AS '.$columnAlias; + + $this->_scalarResultAliasMap[$resultAlias] = $columnAlias; - foreach ($subClass->fieldMappings as $fieldName => $mapping) { - if (isset($mapping['inherited']) || $partialFieldSet && !in_array($fieldName, $partialFieldSet)) { - continue; - } + if ( ! $hidden) { + $this->_rsm->addScalarResult($columnAlias, $resultAlias); + } + break; + + default: + // IdentificationVariable or PartialObjectExpression + if ($expr instanceof AST\PartialObjectExpression) { + $dqlAlias = $expr->identificationVariable; + $partialFieldSet = $expr->partialFieldSet; + } else { + $dqlAlias = $expr; + $partialFieldSet = array(); + } - if ($beginning) $beginning = false; else $sql .= ', '; + $queryComp = $this->_queryComponents[$dqlAlias]; + $class = $queryComp['metadata']; - $columnAlias = $this->getSQLColumnAlias($mapping['columnName']); - $sql .= $sqlTableAlias . '.' . $subClass->getQuotedColumnName($fieldName, $this->_platform) - . ' AS ' . $columnAlias; + if ( ! isset($this->_selectedClasses[$dqlAlias])) { + $this->_selectedClasses[$dqlAlias] = $class; + } - $this->_rsm->addFieldResult($dqlAlias, $columnAlias, $fieldName, $subClassName); + $beginning = true; + + // Select all fields from the queried class + foreach ($class->fieldMappings as $fieldName => $mapping) { + if ($partialFieldSet && !in_array($fieldName, $partialFieldSet)) { + continue; } - // Add join columns (foreign keys) of the subclass - //TODO: Probably better do this in walkSelectClause to honor the INCLUDE_META_COLUMNS hint - foreach ($subClass->associationMappings as $fieldName => $assoc) { - if ($assoc['isOwningSide'] && $assoc['type'] & ClassMetadata::TO_ONE && ! isset($assoc['inherited'])) { - foreach ($assoc['targetToSourceKeyColumns'] as $srcColumn) { - if ($beginning) $beginning = false; else $sql .= ', '; + $tableName = (isset($mapping['inherited'])) + ? $this->_em->getClassMetadata($mapping['inherited'])->getTableName() + : $class->getTableName(); - $columnAlias = $this->getSQLColumnAlias($srcColumn); - $sql .= $sqlTableAlias . '.' . $srcColumn . ' AS ' . $columnAlias; + if ($beginning) $beginning = false; else $sql .= ', '; - $this->_rsm->addMetaResult($dqlAlias, $columnAlias, $srcColumn); + $sqlTableAlias = $this->getSQLTableAlias($tableName, $dqlAlias); + $columnAlias = $this->getSQLColumnAlias($mapping['columnName']); + $sql .= $sqlTableAlias . '.' . $class->getQuotedColumnName($fieldName, $this->_platform) + . ' AS ' . $columnAlias; + + $this->_rsm->addFieldResult($dqlAlias, $columnAlias, $fieldName, $class->name); + } + + // Add any additional fields of subclasses (excluding inherited fields) + // 1) on Single Table Inheritance: always, since its marginal overhead + // 2) on Class Table Inheritance only if partial objects are disallowed, + // since it requires outer joining subtables. + if ($class->isInheritanceTypeSingleTable() || ! $this->_query->getHint(Query::HINT_FORCE_PARTIAL_LOAD)) { + foreach ($class->subClasses as $subClassName) { + $subClass = $this->_em->getClassMetadata($subClassName); + $sqlTableAlias = $this->getSQLTableAlias($subClass->getTableName(), $dqlAlias); + + foreach ($subClass->fieldMappings as $fieldName => $mapping) { + if (isset($mapping['inherited']) || $partialFieldSet && !in_array($fieldName, $partialFieldSet)) { + continue; } + + if ($beginning) $beginning = false; else $sql .= ', '; + + $columnAlias = $this->getSQLColumnAlias($mapping['columnName']); + $sql .= $sqlTableAlias . '.' . $subClass->getQuotedColumnName($fieldName, $this->_platform) + . ' AS ' . $columnAlias; + + $this->_rsm->addFieldResult($dqlAlias, $columnAlias, $fieldName, $subClassName); } } } - } } return $sql; @@ -1302,27 +1220,11 @@ class SqlWalker implements TreeWalker break; case ($expr instanceof AST\Functions\FunctionNode): - $alias = $simpleSelectExpression->fieldIdentificationVariable ?: $this->_scalarResultCounter++; - - $columnAlias = $this->getSQLColumnAlias('sclr'); - $this->_scalarResultAliasMap[$alias] = $columnAlias; - - $sql .= $this->walkFunction($expr) . ' AS ' . $columnAlias; - break; - case ($expr instanceof AST\SimpleArithmeticExpression): case ($expr instanceof AST\ArithmeticTerm): case ($expr instanceof AST\ArithmeticFactor): case ($expr instanceof AST\ArithmeticPrimary): case ($expr instanceof AST\Literal): - $alias = $simpleSelectExpression->fieldIdentificationVariable ?: $this->_scalarResultCounter++; - - $columnAlias = $this->getSQLColumnAlias('sclr'); - $this->_scalarResultAliasMap[$alias] = $columnAlias; - - $sql .= $this->walkSimpleArithmeticExpression($expr) . ' AS ' . $columnAlias; - break; - case ($expr instanceof AST\NullIfExpression): case ($expr instanceof AST\CoalesceExpression): case ($expr instanceof AST\GeneralCaseExpression): @@ -1332,7 +1234,7 @@ class SqlWalker implements TreeWalker $columnAlias = $this->getSQLColumnAlias('sclr'); $this->_scalarResultAliasMap[$alias] = $columnAlias; - $sql .= $this->walkCaseExpression($expr) . ' AS ' . $columnAlias; + $sql .= $expr->dispatch($this) . ' AS ' . $columnAlias; break; default: // IdentificationVariable diff --git a/tests/Doctrine/Tests/ORM/Query/SelectSqlGenerationTest.php b/tests/Doctrine/Tests/ORM/Query/SelectSqlGenerationTest.php index 499fb8c4c..8eaaf37bb 100644 --- a/tests/Doctrine/Tests/ORM/Query/SelectSqlGenerationTest.php +++ b/tests/Doctrine/Tests/ORM/Query/SelectSqlGenerationTest.php @@ -563,7 +563,7 @@ class SelectSqlGenerationTest extends \Doctrine\Tests\OrmTestCase $this->_em->getClassMetadata(get_class($person))->setIdentifierValues($person, array('id' => 101)); $q3->setParameter('param', $person); $this->assertEquals( - 'SELECT c0_.id AS id0, c0_.name AS name1, c1_.title AS title2, c1_.car_id AS car_id3, c2_.salary AS salary4, c2_.department AS department5, c2_.startDate AS startDate6, c0_.discr AS discr7, c0_.spouse_id AS spouse_id8 FROM company_persons c0_ LEFT JOIN company_managers c1_ ON c0_.id = c1_.id LEFT JOIN company_employees c2_ ON c0_.id = c2_.id WHERE EXISTS (SELECT 1 FROM company_persons_friends c3_ INNER JOIN company_persons c4_ ON c3_.friend_id = c4_.id WHERE c3_.person_id = c0_.id AND c4_.id = ?)', + 'SELECT c0_.id AS id0, c0_.name AS name1, c1_.title AS title2, c2_.salary AS salary3, c2_.department AS department4, c2_.startDate AS startDate5, c0_.discr AS discr6, c0_.spouse_id AS spouse_id7, c1_.car_id AS car_id8 FROM company_persons c0_ LEFT JOIN company_managers c1_ ON c0_.id = c1_.id LEFT JOIN company_employees c2_ ON c0_.id = c2_.id WHERE EXISTS (SELECT 1 FROM company_persons_friends c3_ INNER JOIN company_persons c4_ ON c3_.friend_id = c4_.id WHERE c3_.person_id = c0_.id AND c4_.id = ?)', $q3->getSql() ); } @@ -1120,7 +1120,7 @@ class SelectSqlGenerationTest extends \Doctrine\Tests\OrmTestCase { $this->assertSqlGeneration( 'SELECT p FROM Doctrine\Tests\Models\Company\CompanyPerson p', - 'SELECT c0_.id AS id0, c0_.name AS name1, c1_.title AS title2, c1_.car_id AS car_id3, c2_.salary AS salary4, c2_.department AS department5, c2_.startDate AS startDate6, c0_.discr AS discr7, c0_.spouse_id AS spouse_id8 FROM company_persons c0_ LEFT JOIN company_managers c1_ ON c0_.id = c1_.id LEFT JOIN company_employees c2_ ON c0_.id = c2_.id', + 'SELECT c0_.id AS id0, c0_.name AS name1, c1_.title AS title2, c2_.salary AS salary3, c2_.department AS department4, c2_.startDate AS startDate5, c0_.discr AS discr6, c0_.spouse_id AS spouse_id7, c1_.car_id AS car_id8 FROM company_persons c0_ LEFT JOIN company_managers c1_ ON c0_.id = c1_.id LEFT JOIN company_employees c2_ ON c0_.id = c2_.id', array(Query::HINT_FORCE_PARTIAL_LOAD => false) ); } @@ -1144,7 +1144,7 @@ class SelectSqlGenerationTest extends \Doctrine\Tests\OrmTestCase { $this->assertSqlGeneration( 'SELECT e FROM Doctrine\Tests\Models\Company\CompanyEmployee e', - 'SELECT c0_.id AS id0, c0_.name AS name1, c1_.salary AS salary2, c1_.department AS department3, c1_.startDate AS startDate4, c2_.title AS title5, c2_.car_id AS car_id6, c0_.discr AS discr7, c0_.spouse_id AS spouse_id8 FROM company_employees c1_ INNER JOIN company_persons c0_ ON c1_.id = c0_.id LEFT JOIN company_managers c2_ ON c1_.id = c2_.id', + 'SELECT c0_.id AS id0, c0_.name AS name1, c1_.salary AS salary2, c1_.department AS department3, c1_.startDate AS startDate4, c2_.title AS title5, c0_.discr AS discr6, c0_.spouse_id AS spouse_id7, c2_.car_id AS car_id8 FROM company_employees c1_ INNER JOIN company_persons c0_ ON c1_.id = c0_.id LEFT JOIN company_managers c2_ ON c1_.id = c2_.id', array(Query::HINT_FORCE_PARTIAL_LOAD => false) ); } @@ -1264,7 +1264,7 @@ class SelectSqlGenerationTest extends \Doctrine\Tests\OrmTestCase { $this->assertSqlGeneration( 'SELECT p, pp FROM Doctrine\Tests\Models\Company\CompanyPerson p JOIN p.spouse pp', - "SELECT c0_.id AS id0, c0_.name AS name1, c1_.title AS title2, c1_.car_id AS car_id3, c2_.salary AS salary4, c2_.department AS department5, c2_.startDate AS startDate6, c3_.id AS id7, c3_.name AS name8, c4_.title AS title9, c4_.car_id AS car_id10, c5_.salary AS salary11, c5_.department AS department12, c5_.startDate AS startDate13, c0_.discr AS discr14, c0_.spouse_id AS spouse_id15, c3_.discr AS discr16, c3_.spouse_id AS spouse_id17 FROM company_persons c0_ LEFT JOIN company_managers c1_ ON c0_.id = c1_.id LEFT JOIN company_employees c2_ ON c0_.id = c2_.id INNER JOIN company_persons c3_ ON c0_.spouse_id = c3_.id LEFT JOIN company_managers c4_ ON c3_.id = c4_.id LEFT JOIN company_employees c5_ ON c3_.id = c5_.id", + "SELECT c0_.id AS id0, c0_.name AS name1, c1_.title AS title2, c2_.salary AS salary3, c2_.department AS department4, c2_.startDate AS startDate5, c3_.id AS id6, c3_.name AS name7, c4_.title AS title8, c5_.salary AS salary9, c5_.department AS department10, c5_.startDate AS startDate11, c0_.discr AS discr12, c0_.spouse_id AS spouse_id13, c1_.car_id AS car_id14, c3_.discr AS discr15, c3_.spouse_id AS spouse_id16, c4_.car_id AS car_id17 FROM company_persons c0_ LEFT JOIN company_managers c1_ ON c0_.id = c1_.id LEFT JOIN company_employees c2_ ON c0_.id = c2_.id INNER JOIN company_persons c3_ ON c0_.spouse_id = c3_.id LEFT JOIN company_managers c4_ ON c3_.id = c4_.id LEFT JOIN company_employees c5_ ON c3_.id = c5_.id", array(Query::HINT_FORCE_PARTIAL_LOAD => false) ); } From 305da5b8ff0ddfc667c1a770404a22f97f3270b1 Mon Sep 17 00:00:00 2001 From: Guilherme Blanco Date: Thu, 3 Nov 2011 02:49:50 -0200 Subject: [PATCH 075/135] Added missing indexBy. --- lib/Doctrine/ORM/Query/SqlWalker.php | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/lib/Doctrine/ORM/Query/SqlWalker.php b/lib/Doctrine/ORM/Query/SqlWalker.php index b0f919549..03933d3d9 100644 --- a/lib/Doctrine/ORM/Query/SqlWalker.php +++ b/lib/Doctrine/ORM/Query/SqlWalker.php @@ -709,6 +709,14 @@ class SqlWalker implements TreeWalker $sql = ($joinType == AST\Join::JOIN_TYPE_LEFT || $joinType == AST\Join::JOIN_TYPE_LEFTOUTER) ? ' LEFT JOIN ' : ' INNER JOIN '; + + 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 + ); + } $joinAssocPathExpr = $join->joinAssociationPathExpression; $joinedDqlAlias = $join->aliasIdentificationVariable; From ca438fa1109142ef301bf15d72e8dd5af76d4d04 Mon Sep 17 00:00:00 2001 From: Alexander Date: Thu, 3 Nov 2011 09:20:41 +0100 Subject: [PATCH 076/135] Eagerly fetched entities should only be inner joined if they are loaded from the owning side. --- .../ORM/Persisters/BasicEntityPersister.php | 2 +- .../Functional/OneToOneEagerLoadingTest.php | 93 ++++++++++++++++--- 2 files changed, 79 insertions(+), 16 deletions(-) diff --git a/lib/Doctrine/ORM/Persisters/BasicEntityPersister.php b/lib/Doctrine/ORM/Persisters/BasicEntityPersister.php index 9d04cf5f1..8663f3014 100644 --- a/lib/Doctrine/ORM/Persisters/BasicEntityPersister.php +++ b/lib/Doctrine/ORM/Persisters/BasicEntityPersister.php @@ -1019,7 +1019,7 @@ class BasicEntityPersister $eagerEntity = $this->_em->getClassMetadata($assoc['targetEntity']); $owningAssoc = $eagerEntity->getAssociationMapping($assoc['mappedBy']); - $this->_selectJoinSql .= ' ' . $this->getJoinSQLForJoinColumns($owningAssoc['joinColumns']); + $this->_selectJoinSql .= ' LEFT JOIN'; $this->_selectJoinSql .= ' ' . $eagerEntity->getQuotedTableName($this->_platform) . ' ' . $this->_getSQLTableAlias($eagerEntity->name, $assocAlias) . ' ON '; diff --git a/tests/Doctrine/Tests/ORM/Functional/OneToOneEagerLoadingTest.php b/tests/Doctrine/Tests/ORM/Functional/OneToOneEagerLoadingTest.php index 5361aad45..5fa55036c 100644 --- a/tests/Doctrine/Tests/ORM/Functional/OneToOneEagerLoadingTest.php +++ b/tests/Doctrine/Tests/ORM/Functional/OneToOneEagerLoadingTest.php @@ -19,6 +19,7 @@ class OneToOneEagerLoadingTest extends \Doctrine\Tests\OrmFunctionalTestCase $schemaTool->createSchema(array( $this->_em->getClassMetadata('Doctrine\Tests\ORM\Functional\Train'), $this->_em->getClassMetadata('Doctrine\Tests\ORM\Functional\TrainDriver'), + $this->_em->getClassMetadata('Doctrine\Tests\ORM\Functional\TrainOwner'), $this->_em->getClassMetadata('Doctrine\Tests\ORM\Functional\Waggon'), )); } catch(\Exception $e) {} @@ -26,7 +27,7 @@ class OneToOneEagerLoadingTest extends \Doctrine\Tests\OrmFunctionalTestCase public function testEagerLoadOneToOneOwningSide() { - $train = new Train(); + $train = new Train(new TrainOwner("Alexander")); $driver = new TrainDriver("Benjamin"); $waggon = new Waggon(); @@ -48,7 +49,7 @@ class OneToOneEagerLoadingTest extends \Doctrine\Tests\OrmFunctionalTestCase public function testEagerLoadOneToOneNullOwningSide() { - $train = new Train(); + $train = new Train(new TrainOwner("Alexander")); $this->_em->persist($train); // cascades $this->_em->flush(); @@ -65,9 +66,8 @@ class OneToOneEagerLoadingTest extends \Doctrine\Tests\OrmFunctionalTestCase public function testEagerLoadOneToOneInverseSide() { - $train = new Train(); - $driver = new TrainDriver("Benjamin"); - $train->setDriver($driver); + $owner = new TrainOwner("Alexander"); + $train = new Train($owner); $this->_em->persist($train); // cascades $this->_em->flush(); @@ -75,9 +75,9 @@ class OneToOneEagerLoadingTest extends \Doctrine\Tests\OrmFunctionalTestCase $sqlCount = count($this->_sqlLoggerStack->queries); - $driver = $this->_em->find(get_class($driver), $driver->id); - $this->assertNotInstanceOf('Doctrine\ORM\Proxy\Proxy', $driver->train); - $this->assertNotNull($driver->train); + $driver = $this->_em->find(get_class($owner), $owner->id); + $this->assertNotInstanceOf('Doctrine\ORM\Proxy\Proxy', $owner->train); + $this->assertNotNull($owner->train); $this->assertEquals($sqlCount + 1, count($this->_sqlLoggerStack->queries)); } @@ -103,7 +103,7 @@ class OneToOneEagerLoadingTest extends \Doctrine\Tests\OrmFunctionalTestCase public function testEagerLoadManyToOne() { - $train = new Train(); + $train = new Train(new TrainOwner("Alexander")); $waggon = new Waggon(); $train->addWaggon($waggon); @@ -116,21 +116,31 @@ class OneToOneEagerLoadingTest extends \Doctrine\Tests\OrmFunctionalTestCase $this->assertNotNull($waggon->train); } - public function testEagerLoadWithNullableColumnsGeneratesLeftJoin() + public function testEagerLoadWithNullableColumnsGeneratesLeftJoinOnBothSides() { - $train = new Train(); + $train = new Train(new TrainOwner("Alexander")); + $driver = new TrainDriver("Benjamin"); + $train->setDriver($driver); + $this->_em->persist($train); $this->_em->flush(); $this->_em->clear(); $train = $this->_em->find(get_class($train), $train->id); $this->assertEquals( - "SELECT t0.id AS id1, t0.driver_id AS driver_id2, t3.id AS id4, t3.name AS name5 FROM Train t0 LEFT JOIN TrainDriver t3 ON t0.driver_id = t3.id WHERE t0.id = ?", + "SELECT t0.id AS id1, t0.driver_id AS driver_id2, t3.id AS id4, t3.name AS name5, t0.owner_id AS owner_id6, t7.id AS id8, t7.name AS name9 FROM Train t0 LEFT JOIN TrainDriver t3 ON t0.driver_id = t3.id INNER JOIN TrainOwner t7 ON t0.owner_id = t7.id WHERE t0.id = ?", + $this->_sqlLoggerStack->queries[$this->_sqlLoggerStack->currentQuery]['sql'] + ); + + $this->_em->clear(); + $driver = $this->_em->find(get_class($driver), $driver->id); + $this->assertEquals( + "SELECT t0.id AS id1, t0.name AS name2, t3.id AS id4, t3.driver_id AS driver_id5, t3.owner_id AS owner_id6 FROM TrainOwner t0 LEFT JOIN Train t3 ON t3.owner_id = t0.id WHERE t0.id IN (?)", $this->_sqlLoggerStack->queries[$this->_sqlLoggerStack->currentQuery]['sql'] ); } - public function testEagerLoadWithNonNullableColumnsGeneratesInnerJoin() + public function testEagerLoadWithNonNullableColumnsGeneratesInnerJoinOnOwningSide() { $waggon = new Waggon(); $this->_em->persist($waggon); @@ -139,7 +149,22 @@ class OneToOneEagerLoadingTest extends \Doctrine\Tests\OrmFunctionalTestCase $waggon = $this->_em->find(get_class($waggon), $waggon->id); $this->assertEquals( - "SELECT t0.id AS id1, t0.train_id AS train_id2, t3.id AS id4, t3.driver_id AS driver_id5 FROM Waggon t0 INNER JOIN Train t3 ON t0.train_id = t3.id WHERE t0.id = ?", + "SELECT t0.id AS id1, t0.train_id AS train_id2, t3.id AS id4, t3.driver_id AS driver_id5, t3.owner_id AS owner_id6 FROM Waggon t0 INNER JOIN Train t3 ON t0.train_id = t3.id WHERE t0.id = ?", + $this->_sqlLoggerStack->queries[$this->_sqlLoggerStack->currentQuery]['sql'] + ); + } + + public function testEagerLoadWithNonNullableColumnsGeneratesLeftJoinOnNonOwningSide() + { + $owner = new TrainOwner('Alexander'); + $train = new Train($owner); + $this->_em->persist($train); + $this->_em->flush(); + $this->_em->clear(); + + $waggon = $this->_em->find(get_class($owner), $owner->id); + $this->assertEquals( + "SELECT t0.id AS id1, t0.name AS name2, t3.id AS id4, t3.driver_id AS driver_id5, t3.owner_id AS owner_id6 FROM TrainOwner t0 LEFT JOIN Train t3 ON t3.owner_id = t0.id WHERE t0.id = ?", $this->_sqlLoggerStack->queries[$this->_sqlLoggerStack->currentQuery]['sql'] ); } @@ -161,14 +186,20 @@ class Train * @JoinColumn(nullable=true) */ public $driver; + /** + * Owning side + * @OneToOne(targetEntity="TrainOwner", inversedBy="train", fetch="EAGER", cascade={"persist"}) + */ + public $owner; /** * @oneToMany(targetEntity="Waggon", mappedBy="train", cascade={"persist"}) */ public $waggons; - public function __construct() + public function __construct(TrainOwner $owner) { $this->waggons = new \Doctrine\Common\Collections\ArrayCollection(); + $this->setOwner($owner); } public function setDriver(TrainDriver $driver) @@ -177,6 +208,12 @@ class Train $driver->setTrain($this); } + public function setOwner(TrainOwner $owner) + { + $this->owner = $owner; + $owner->setTrain($this); + } + public function addWaggon(Waggon $w) { $w->setTrain($this); @@ -210,6 +247,32 @@ class TrainDriver } } +/** + * @Entity + */ +class TrainOwner +{ + /** @Id @Column(type="integer") @GeneratedValue */ + public $id; + /** @column(type="string") */ + public $name; + /** + * Inverse side + * @OneToOne(targetEntity="Train", mappedBy="owner", fetch="EAGER") + */ + public $train; + + public function __construct($name) + { + $this->name = $name; + } + + public function setTrain(Train $t) + { + $this->train = $t; + } +} + /** * @Entity */ From 9c4c06c42219866b2bb3d99785b2abb2bb7f4952 Mon Sep 17 00:00:00 2001 From: everzet Date: Thu, 3 Nov 2011 16:24:47 +0200 Subject: [PATCH 077/135] optimized PreFlush (moved into computeChangeSet function) --- lib/Doctrine/ORM/UnitOfWork.php | 35 +++++---------------------------- 1 file changed, 5 insertions(+), 30 deletions(-) diff --git a/lib/Doctrine/ORM/UnitOfWork.php b/lib/Doctrine/ORM/UnitOfWork.php index d8b640c47..ea7138c98 100644 --- a/lib/Doctrine/ORM/UnitOfWork.php +++ b/lib/Doctrine/ORM/UnitOfWork.php @@ -259,36 +259,6 @@ class UnitOfWork implements PropertyChangedListener */ public function commit($entity = null) { - // Run preFlush lifecycle callback for new entities - foreach ($this->entityInsertions as $classEntity) { - $class = $this->em->getClassMetadata(get_class($classEntity)); - - // Skip class if instances are read-only - if ($class->isReadOnly) { - continue; - } - - if (isset($class->lifecycleCallbacks[Events::preFlush])) { - $class->invokeLifecycleCallbacks(Events::preFlush, $classEntity); - } - } - - // Run preFlush lifecycle callback for persisted entities - foreach ($this->identityMap as $className => $classEntities) { - $class = $this->em->getClassMetadata($className); - - // Skip class if instances are read-only - if ($class->isReadOnly) { - continue; - } - - if (isset($class->lifecycleCallbacks[Events::preFlush])) { - foreach ($classEntities as $classEntity) { - $class->invokeLifecycleCallbacks(Events::preFlush, $classEntity); - } - } - } - // Raise preFlush if ($this->evm->hasListeners(Events::preFlush)) { $this->evm->dispatchEvent(Events::preFlush, new Event\PreFlushEventArgs($this->em)); @@ -516,6 +486,11 @@ class UnitOfWork implements PropertyChangedListener return; } + // Fire PreFlush lifecycle callbacks + if (isset($class->lifecycleCallbacks[Events::preFlush])) { + $class->invokeLifecycleCallbacks(Events::preFlush, $entity); + } + $actualData = array(); foreach ($class->reflFields as $name => $refProp) { $value = $refProp->getValue($entity); From c6a3ff4da54e904311e87a2fcaa37906aed61582 Mon Sep 17 00:00:00 2001 From: Guilherme Blanco Date: Sat, 5 Nov 2011 03:09:14 -0200 Subject: [PATCH 078/135] Optimizations in UnitOfWork. --- .../ORM/Mapping/ClassMetadataInfo.php | 12 +- lib/Doctrine/ORM/UnitOfWork.php | 261 ++++++++++-------- 2 files changed, 154 insertions(+), 119 deletions(-) diff --git a/lib/Doctrine/ORM/Mapping/ClassMetadataInfo.php b/lib/Doctrine/ORM/Mapping/ClassMetadataInfo.php index 10f7bc4b6..592367ab7 100644 --- a/lib/Doctrine/ORM/Mapping/ClassMetadataInfo.php +++ b/lib/Doctrine/ORM/Mapping/ClassMetadataInfo.php @@ -136,10 +136,6 @@ class ClassMetadataInfo implements ClassMetadata * Identifies a many-to-one association. */ const MANY_TO_ONE = 2; - /** - * Combined bitmask for to-one (single-valued) associations. - */ - const TO_ONE = 3; /** * Identifies a one-to-many association. */ @@ -148,6 +144,10 @@ class ClassMetadataInfo implements ClassMetadata * Identifies a many-to-many association. */ const MANY_TO_MANY = 8; + /** + * Combined bitmask for to-one (single-valued) associations. + */ + const TO_ONE = 3; /** * Combined bitmask for to-many (collection-valued) associations. */ @@ -1504,14 +1504,16 @@ class ClassMetadataInfo implements ClassMetadata /** * Stores the association mapping. * - * @param AssociationMapping $assocMapping + * @param array $assocMapping */ protected function _storeAssociationMapping(array $assocMapping) { $sourceFieldName = $assocMapping['fieldName']; + if (isset($this->fieldMappings[$sourceFieldName]) || isset($this->associationMappings[$sourceFieldName])) { throw MappingException::duplicateFieldMapping($this->name, $sourceFieldName); } + $this->associationMappings[$sourceFieldName] = $assocMapping; } diff --git a/lib/Doctrine/ORM/UnitOfWork.php b/lib/Doctrine/ORM/UnitOfWork.php index 6add3bd6b..bfe18fc7f 100644 --- a/lib/Doctrine/ORM/UnitOfWork.php +++ b/lib/Doctrine/ORM/UnitOfWork.php @@ -290,8 +290,8 @@ class UnitOfWork implements PropertyChangedListener $commitOrder = $this->getCommitOrder(); $conn = $this->em->getConnection(); - $conn->beginTransaction(); + try { if ($this->entityInsertions) { foreach ($commitOrder as $class) { @@ -312,13 +312,11 @@ class UnitOfWork implements PropertyChangedListener // Collection deletions (deletions of complete collections) foreach ($this->collectionDeletions as $collectionToDelete) { - $this->getCollectionPersister($collectionToDelete->getMapping()) - ->delete($collectionToDelete); + $this->getCollectionPersister($collectionToDelete->getMapping())->delete($collectionToDelete); } // Collection updates (deleteRows, updateRows, insertRows) foreach ($this->collectionUpdates as $collectionToUpdate) { - $this->getCollectionPersister($collectionToUpdate->getMapping()) - ->update($collectionToUpdate); + $this->getCollectionPersister($collectionToUpdate->getMapping())->update($collectionToUpdate); } // Entity deletions come last and need to be in reverse commit order @@ -332,6 +330,7 @@ class UnitOfWork implements PropertyChangedListener } catch (Exception $e) { $this->em->close(); $conn->rollback(); + throw $e; } @@ -397,7 +396,7 @@ class UnitOfWork implements PropertyChangedListener // Compute changes for INSERTed entities first. This must always happen even in this case. $this->computeScheduleInsertsChangeSets(); - if ( $class->isReadOnly ) { + if ($class->isReadOnly) { return; } @@ -408,6 +407,7 @@ class UnitOfWork implements PropertyChangedListener // Only MANAGED entities that are NOT SCHEDULED FOR INSERTION are processed here. $oid = spl_object_hash($entity); + if ( ! isset($this->entityInsertions[$oid]) && isset($this->entityStates[$oid])) { $this->computeChangeSet($class, $entity); } @@ -420,6 +420,7 @@ class UnitOfWork implements PropertyChangedListener { foreach ($this->extraUpdates as $oid => $update) { list ($entity, $changeset) = $update; + $this->entityChangeSets[$oid] = $changeset; $this->getEntityPersister(get_class($entity))->update($entity); } @@ -433,10 +434,8 @@ class UnitOfWork implements PropertyChangedListener public function getEntityChangeSet($entity) { $oid = spl_object_hash($entity); - if (isset($this->entityChangeSets[$oid])) { - return $this->entityChangeSets[$oid]; - } - return array(); + + return isset($this->entityChangeSets[$oid]) ? $this->entityChangeSets[$oid] : array(); } /** @@ -486,11 +485,7 @@ class UnitOfWork implements PropertyChangedListener foreach ($class->reflFields as $name => $refProp) { $value = $refProp->getValue($entity); - if (isset($class->associationMappings[$name]) - && ($class->associationMappings[$name]['type'] & ClassMetadata::TO_MANY) - && $value !== null - && ! ($value instanceof PersistentCollection)) { - + if ($class->isCollectionValuedAssociation($name) && $value !== null && ! ($value instanceof PersistentCollection)) { // If $value is not a Collection then use an ArrayCollection. if ( ! $value instanceof Collection) { $value = new ArrayCollection($value); @@ -499,17 +494,20 @@ class UnitOfWork implements PropertyChangedListener $assoc = $class->associationMappings[$name]; // Inject PersistentCollection - $coll = new PersistentCollection( - $this->em, - $this->em->getClassMetadata($assoc['targetEntity']), - $value + $value = new PersistentCollection( + $this->em, $this->em->getClassMetadata($assoc['targetEntity']), $value ); + $value->setOwner($entity, $assoc); + $value->setDirty( ! $value->isEmpty()); - $coll->setOwner($entity, $assoc); - $coll->setDirty( ! $coll->isEmpty()); - $class->reflFields[$name]->setValue($entity, $coll); - $actualData[$name] = $coll; - } else if ( (! $class->isIdentifier($name) || ! $class->isIdGeneratorIdentity()) && ($name !== $class->versionField) ) { + $class->reflFields[$name]->setValue($entity, $value); + + $actualData[$name] = $value; + + continue; + } + + if (( ! $class->isIdentifier($name) || ! $class->isIdGeneratorIdentity()) && ($name !== $class->versionField)) { $actualData[$name] = $value; } } @@ -519,13 +517,17 @@ class UnitOfWork implements PropertyChangedListener // These result in an INSERT. $this->originalEntityData[$oid] = $actualData; $changeSet = array(); + foreach ($actualData as $propName => $actualValue) { - if (isset($class->associationMappings[$propName])) { - $assoc = $class->associationMappings[$propName]; - if ($assoc['isOwningSide'] && $assoc['type'] & ClassMetadata::TO_ONE) { - $changeSet[$propName] = array(null, $actualValue); - } - } else { + if ( ! isset($class->associationMappings[$propName])) { + $changeSet[$propName] = array(null, $actualValue); + + continue; + } + + $assoc = $class->associationMappings[$propName]; + + if ($assoc['isOwningSide'] && $assoc['type'] & ClassMetadata::TO_ONE) { $changeSet[$propName] = array(null, $actualValue); } } @@ -533,54 +535,65 @@ class UnitOfWork implements PropertyChangedListener } else { // Entity is "fully" MANAGED: it was already fully persisted before // and we have a copy of the original data - $originalData = $this->originalEntityData[$oid]; + $originalData = $this->originalEntityData[$oid]; $isChangeTrackingNotify = $class->isChangeTrackingNotify(); - $changeSet = ($isChangeTrackingNotify && isset($this->entityChangeSets[$oid])) ? $this->entityChangeSets[$oid] : array(); + $changeSet = ($isChangeTrackingNotify && isset($this->entityChangeSets[$oid])) + ? $this->entityChangeSets[$oid] + : array(); foreach ($actualData as $propName => $actualValue) { - if (isset($originalData[$propName])) { - $orgValue = $originalData[$propName]; - } else if (array_key_exists($propName, $originalData)) { - $orgValue = null; - } else { - // skip field, its a partially omitted one! + // skip field, its a partially omitted one! + if ( ! (isset($originalData[$propName]) || array_key_exists($propName, $originalData))) continue; + + $orgValue = $originalData[$propName]; + + // skip if value havent changed + if ($orgValue === $actualValue) continue; + + // if regular field + if ( ! isset($class->associationMappings[$propName])) { + if ($isChangeTrackingNotify) continue; + + $changeSet[$propName] = array($orgValue, $actualValue); + + continue; + } + + if ($orgValue instanceof PersistentCollection) { + // A PersistentCollection was de-referenced, so delete it. + $coid = spl_object_hash($orgValue); + + if (isset($this->collectionDeletions[$coid])) continue; + + $this->collectionDeletions[$coid] = $orgValue; + $changeSet[$propName] = $orgValue; // Signal changeset, to-many assocs will be ignored. + continue; } - if (isset($class->associationMappings[$propName])) { - $assoc = $class->associationMappings[$propName]; - if ($assoc['type'] & ClassMetadata::TO_ONE && $orgValue !== $actualValue) { - if ($assoc['isOwningSide']) { - $changeSet[$propName] = array($orgValue, $actualValue); - } - if ($orgValue !== null && $assoc['orphanRemoval']) { - $this->scheduleOrphanRemoval($orgValue); - } - } else if ($orgValue instanceof PersistentCollection && $orgValue !== $actualValue) { - // A PersistentCollection was de-referenced, so delete it. - $coid = spl_object_hash($orgValue); - if ( ! isset($this->collectionDeletions[$coid]) ) { - $this->collectionDeletions[$coid] = $orgValue; - $changeSet[$propName] = $orgValue; // Signal changeset, to-many assocs will be ignored. - } + $assoc = $class->associationMappings[$propName]; + + if ($assoc['type'] & ClassMetadata::TO_ONE) { + if ($assoc['isOwningSide']) { + $changeSet[$propName] = array($orgValue, $actualValue); + } + + if ($orgValue !== null && $assoc['orphanRemoval']) { + $this->scheduleOrphanRemoval($orgValue); } - } else if ($isChangeTrackingNotify) { - continue; - } else if ($orgValue !== $actualValue) { - $changeSet[$propName] = array($orgValue, $actualValue); } } + if ($changeSet) { - $this->entityChangeSets[$oid] = $changeSet; + $this->entityChangeSets[$oid] = $changeSet; $this->originalEntityData[$oid] = $actualData; - $this->entityUpdates[$oid] = $entity; + $this->entityUpdates[$oid] = $entity; } } // Look for changes in associations of the entity foreach ($class->associationMappings as $field => $assoc) { - $val = $class->reflFields[$field]->getValue($entity); - if ($val !== null) { + if (($val = $class->reflFields[$field]->getValue($entity)) !== null) { $this->computeAssociationChanges($assoc, $val); } } @@ -601,24 +614,21 @@ class UnitOfWork implements PropertyChangedListener $class = $this->em->getClassMetadata($className); // Skip class if instances are read-only - if ($class->isReadOnly) { - continue; - } + if ($class->isReadOnly) continue; // If change tracking is explicit or happens through notification, then only compute // changes on entities of that type that are explicitly marked for synchronization. - $entitiesToProcess = ! $class->isChangeTrackingDeferredImplicit() ? - (isset($this->scheduledForDirtyCheck[$className]) ? - $this->scheduledForDirtyCheck[$className] : array()) - : $entities; + $entitiesToProcess = ! $class->isChangeTrackingDeferredImplicit() + ? (isset($this->scheduledForDirtyCheck[$className]) ? $this->scheduledForDirtyCheck[$className] : array()) + : $entities; foreach ($entitiesToProcess as $entity) { // Ignore uninitialized proxy objects - if ($entity instanceof Proxy && ! $entity->__isInitialized__) { - continue; - } + if ($entity instanceof Proxy && ! $entity->__isInitialized__) continue; + // Only MANAGED entities that are NOT SCHEDULED FOR INSERTION are processed here. $oid = spl_object_hash($entity); + if ( ! isset($this->entityInsertions[$oid]) && isset($this->entityStates[$oid])) { $this->computeChangeSet($class, $entity); } @@ -634,75 +644,96 @@ class UnitOfWork implements PropertyChangedListener */ private function computeAssociationChanges($assoc, $value) { + if ($value instanceof Proxy && ! $value->__isInitialized__) { + return; + } + if ($value instanceof PersistentCollection && $value->isDirty()) { $coid = spl_object_hash($value); + if ($assoc['isOwningSide']) { $this->collectionUpdates[$coid] = $value; } + $this->visitedCollections[$coid] = $value; } - + // Look through the entities, and in any of their associations, for transient (new) - // enities, recursively. ("Persistence by reachability") - if ($assoc['type'] & ClassMetadata::TO_ONE) { - if ($value instanceof Proxy && ! $value->__isInitialized__) { - return; // Ignore uninitialized proxy objects - } - $value = array($value); - } else if ($value instanceof PersistentCollection) { - // Unwrap. Uninitialized collections will simply be empty. - $value = $value->unwrap(); - } - - $targetClass = $this->em->getClassMetadata($assoc['targetEntity']); - foreach ($value as $entry) { + // entities, recursively. ("Persistence by reachability") + // Unwrap. Uninitialized collections will simply be empty. + $unwrappedValue = ($assoc['type'] & ClassMetadata::TO_ONE) ? array($value) : $value->unwrap(); + $targetClass = $this->em->getClassMetadata($assoc['targetEntity']); + + foreach ($unwrappedValue as $key => $entry) { $state = $this->getEntityState($entry, self::STATE_NEW); - $oid = spl_object_hash($entry); - if ($state == self::STATE_NEW) { - if ( ! $assoc['isCascadePersist']) { - throw new InvalidArgumentException("A new entity was found through the relationship '" - . $assoc['sourceEntity'] . "#" . $assoc['fieldName'] . "' that was not" - . " configured to cascade persist operations for entity: " . self::objToStr($entry) . "." - . " Explicitly persist the new entity or configure cascading persist operations" - . " on the relationship. If you cannot find out which entity causes the problem" - . " implement '" . $assoc['targetEntity'] . "#__toString()' to get a clue."); - } - $this->persistNew($targetClass, $entry); - $this->computeChangeSet($targetClass, $entry); - } else if ($state == self::STATE_REMOVED) { - return new InvalidArgumentException("Removed entity detected during flush: " - . self::objToStr($entry).". Remove deleted entities from associations."); - } else if ($state == self::STATE_DETACHED) { - // Can actually not happen right now as we assume STATE_NEW, - // so the exception will be raised from the DBAL layer (constraint violation). - throw new InvalidArgumentException("A detached entity was found through a " - . "relationship during cascading a persist operation."); + $oid = spl_object_hash($entry); + + switch ($state) { + case self::STATE_NEW: + if ( ! $assoc['isCascadePersist']) { + $message = "A new entity was found through the relationship '%s#%s' that was not configured " . + ' to cascade persist operations for entity: %s. Explicitly persist the new entity or ' . + 'configure cascading persist operations on tbe relationship. If you cannot find out ' . + 'which entity causes the problem, implement %s#__toString() to get a clue.'; + + throw new InvalidArgumentException(sprintf( + $message, $assoc['sourceEntity'], $assoc['fieldName'], self::objToStr($entry), $assoc['targetEntity'] + )); + } + + $this->persistNew($targetClass, $entry); + $this->computeChangeSet($targetClass, $entry); + break; + + case self::STATE_REMOVED: + // Consume the $value as array (it's either an array or an ArrayAccess) + // and remove the element from Collection. + if ($assoc['type'] & ClassMetadata::TO_MANY) { + unset($value[$key]); + } + break; + + case self::STATE_DETACHED: + // Can actually not happen right now as we assume STATE_NEW, + // so the exception will be raised from the DBAL layer (constraint violation). + $message = 'A detached entity was found through a relationship during cascading a persist operation.'; + + throw new InvalidArgumentException($message); + break; + + default: + // MANAGED associated entities are already taken into account + // during changeset calculation anyway, since they are in the identity map. } - // MANAGED associated entities are already taken into account - // during changeset calculation anyway, since they are in the identity map. } } private function persistNew($class, $entity) { $oid = spl_object_hash($entity); + if (isset($class->lifecycleCallbacks[Events::prePersist])) { $class->invokeLifecycleCallbacks(Events::prePersist, $entity); } + if ($this->evm->hasListeners(Events::prePersist)) { $this->evm->dispatchEvent(Events::prePersist, new LifecycleEventArgs($entity, $this->em)); } $idGen = $class->idGenerator; + if ( ! $idGen->isPostInsertGenerator()) { $idValue = $idGen->generate($this->em, $entity); + if ( ! $idGen instanceof \Doctrine\ORM\Id\AssignedGenerator) { - $this->entityIdentifiers[$oid] = array($class->identifier[0] => $idValue); - $class->setIdentifierValues($entity, $this->entityIdentifiers[$oid]); - } else { - $this->entityIdentifiers[$oid] = $idValue; + $idValue = array($class->identifier[0] => $idValue); + + $class->setIdentifierValues($entity, $idValue); } + + $this->entityIdentifiers[$oid] = $idValue; } + $this->entityStates[$oid] = self::STATE_MANAGED; $this->scheduleForInsert($entity); @@ -730,16 +761,17 @@ class UnitOfWork implements PropertyChangedListener throw new InvalidArgumentException('Entity must be managed.'); } - /* TODO: Just return if changetracking policy is NOTIFY? + // skip if change tracking is "NOTIFY" if ($class->isChangeTrackingNotify()) { return; - }*/ + } if ( ! $class->isInheritanceTypeNone()) { $class = $this->em->getClassMetadata(get_class($entity)); } $actualData = array(); + foreach ($class->reflFields as $name => $refProp) { if ( ! $class->isIdentifier($name) || ! $class->isIdGeneratorIdentity()) { $actualData[$name] = $refProp->getValue($entity); @@ -751,6 +783,7 @@ class UnitOfWork implements PropertyChangedListener foreach ($actualData as $propName => $actualValue) { $orgValue = isset($originalData[$propName]) ? $originalData[$propName] : null; + if (is_object($orgValue) && $orgValue !== $actualValue) { $changeSet[$propName] = array($orgValue, $actualValue); } else if ($orgValue != $actualValue || ($orgValue === null ^ $actualValue === null)) { From ea69d9ca0cc20e27a14770414efc25414dc7ab0f Mon Sep 17 00:00:00 2001 From: Guilherme Blanco Date: Sat, 5 Nov 2011 19:21:35 -0200 Subject: [PATCH 079/135] Fixed wrong optimization. Optimized more pieces of code in UnitOfWork. --- lib/Doctrine/ORM/UnitOfWork.php | 133 ++++++++++++++++++-------------- 1 file changed, 77 insertions(+), 56 deletions(-) diff --git a/lib/Doctrine/ORM/UnitOfWork.php b/lib/Doctrine/ORM/UnitOfWork.php index bfe18fc7f..d73d625c6 100644 --- a/lib/Doctrine/ORM/UnitOfWork.php +++ b/lib/Doctrine/ORM/UnitOfWork.php @@ -435,7 +435,11 @@ class UnitOfWork implements PropertyChangedListener { $oid = spl_object_hash($entity); - return isset($this->entityChangeSets[$oid]) ? $this->entityChangeSets[$oid] : array(); + if (isset($this->entityChangeSets[$oid])) { + return $this->entityChangeSets[$oid]; + } + + return array(); } /** @@ -531,6 +535,7 @@ class UnitOfWork implements PropertyChangedListener $changeSet[$propName] = array(null, $actualValue); } } + $this->entityChangeSets[$oid] = $changeSet; } else { // Entity is "fully" MANAGED: it was already fully persisted before @@ -1195,61 +1200,67 @@ class UnitOfWork implements PropertyChangedListener public function getEntityState($entity, $assume = null) { $oid = spl_object_hash($entity); - if ( ! isset($this->entityStates[$oid])) { - // State can only be NEW or DETACHED, because MANAGED/REMOVED states are known. - // Note that you can not remember the NEW or DETACHED state in _entityStates since - // the UoW does not hold references to such objects and the object hash can be reused. - // More generally because the state may "change" between NEW/DETACHED without the UoW being aware of it. - if ($assume === null) { - $class = $this->em->getClassMetadata(get_class($entity)); - $id = $class->getIdentifierValues($entity); - if ( ! $id) { - return self::STATE_NEW; - } else if ($class->isIdentifierNatural()) { - // Check for a version field, if available, to avoid a db lookup. - if ($class->isVersioned) { - if ($class->getFieldValue($entity, $class->versionField)) { - return self::STATE_DETACHED; - } else { - return self::STATE_NEW; - } - } else { - // Last try before db lookup: check the identity map. - if ($this->tryGetById($id, $class->rootEntityName)) { - return self::STATE_DETACHED; - } else { - // db lookup - if ($this->getEntityPersister(get_class($entity))->exists($entity)) { - return self::STATE_DETACHED; - } else { - return self::STATE_NEW; - } - } - } - } else if (!$class->idGenerator->isPostInsertGenerator()) { - // if we have a pre insert generator we can't be sure that having an id - // really means that the entity exists. We have to verify this through - // the last resort: a db lookup - - // Last try before db lookup: check the identity map. - if ($this->tryGetById($id, $class->rootEntityName)) { - return self::STATE_DETACHED; - } else { - // db lookup - if ($this->getEntityPersister(get_class($entity))->exists($entity)) { - return self::STATE_DETACHED; - } else { - return self::STATE_NEW; - } - } - } else { + + if (isset($this->entityStates[$oid])) { + return $this->entityStates[$oid]; + } + + if ($assume !== null) { + return $assume; + } + + // State can only be NEW or DETACHED, because MANAGED/REMOVED states are known. + // Note that you can not remember the NEW or DETACHED state in _entityStates since + // the UoW does not hold references to such objects and the object hash can be reused. + // More generally because the state may "change" between NEW/DETACHED without the UoW being aware of it. + $class = $this->em->getClassMetadata(get_class($entity)); + $id = $class->getIdentifierValues($entity); + + if ( ! $id) { + return self::STATE_NEW; + } + + switch (true) { + case ($class->isIdentifierNatural()); + // Check for a version field, if available, to avoid a db lookup. + if ($class->isVersioned) { + return ($class->getFieldValue($entity, $class->versionField)) + ? self::STATE_DETACHED + : self::STATE_NEW; + } + + // Last try before db lookup: check the identity map. + if ($this->tryGetById($id, $class->rootEntityName)) { return self::STATE_DETACHED; } - } else { - return $assume; - } + + // db lookup + if ($this->getEntityPersister(get_class($entity))->exists($entity)) { + return self::STATE_DETACHED; + } + + return self::STATE_NEW; + + case ( ! $class->idGenerator->isPostInsertGenerator()): + // if we have a pre insert generator we can't be sure that having an id + // really means that the entity exists. We have to verify this through + // the last resort: a db lookup + + // Last try before db lookup: check the identity map. + if ($this->tryGetById($id, $class->rootEntityName)) { + return self::STATE_DETACHED; + } + + // db lookup + if ($this->getEntityPersister(get_class($entity))->exists($entity)) { + return self::STATE_DETACHED; + } + + return self::STATE_NEW; + + default: + return self::STATE_DETACHED; } - return $this->entityStates[$oid]; } /** @@ -1266,13 +1277,17 @@ class UnitOfWork implements PropertyChangedListener $oid = spl_object_hash($entity); $classMetadata = $this->em->getClassMetadata(get_class($entity)); $idHash = implode(' ', $this->entityIdentifiers[$oid]); + if ($idHash === '') { throw new InvalidArgumentException("The given entity has no identity."); } + $className = $classMetadata->rootEntityName; + if (isset($this->identityMap[$className][$idHash])) { unset($this->identityMap[$className][$idHash]); //$this->entityStates[$oid] = self::STATE_DETACHED; + return true; } @@ -1305,8 +1320,11 @@ class UnitOfWork implements PropertyChangedListener */ public function tryGetByIdHash($idHash, $rootClassName) { - return isset($this->identityMap[$rootClassName][$idHash]) ? - $this->identityMap[$rootClassName][$idHash] : false; + if (isset($this->identityMap[$rootClassName][$idHash])) { + return $this->identityMap[$rootClassName][$idHash]; + } + + return false; } /** @@ -1318,11 +1336,14 @@ class UnitOfWork implements PropertyChangedListener public function isInIdentityMap($entity) { $oid = spl_object_hash($entity); + if ( ! isset($this->entityIdentifiers[$oid])) { return false; } + $classMetadata = $this->em->getClassMetadata(get_class($entity)); - $idHash = implode(' ', $this->entityIdentifiers[$oid]); + $idHash = implode(' ', $this->entityIdentifiers[$oid]); + if ($idHash === '') { return false; } From 96aa25fb3e5e3066ff89d569e75a84b7750774d1 Mon Sep 17 00:00:00 2001 From: Guilherme Blanco Date: Sun, 6 Nov 2011 02:03:34 -0200 Subject: [PATCH 080/135] Optimized more pieces of code in UnitOfWork. --- lib/Doctrine/ORM/UnitOfWork.php | 446 ++++++++++++++++++++------------ 1 file changed, 283 insertions(+), 163 deletions(-) diff --git a/lib/Doctrine/ORM/UnitOfWork.php b/lib/Doctrine/ORM/UnitOfWork.php index d73d625c6..01473f875 100644 --- a/lib/Doctrine/ORM/UnitOfWork.php +++ b/lib/Doctrine/ORM/UnitOfWork.php @@ -623,9 +623,19 @@ class UnitOfWork implements PropertyChangedListener // If change tracking is explicit or happens through notification, then only compute // changes on entities of that type that are explicitly marked for synchronization. - $entitiesToProcess = ! $class->isChangeTrackingDeferredImplicit() - ? (isset($this->scheduledForDirtyCheck[$className]) ? $this->scheduledForDirtyCheck[$className] : array()) - : $entities; + switch (true) { + case ($class->isChangeTrackingDeferredImplicit()): + $entitiesToProcess = $entities; + break; + + case (isset($this->scheduledForDirtyCheck[$className])): + $entitiesToProcess = $this->scheduledForDirtyCheck[$className]; + break; + + default: + $entitiesToProcess = array(); + + } foreach ($entitiesToProcess as $entity) { // Ignore uninitialized proxy objects @@ -800,6 +810,7 @@ class UnitOfWork implements PropertyChangedListener if (isset($this->entityChangeSets[$oid])) { $this->entityChangeSets[$oid] = array_merge($this->entityChangeSets[$oid], $changeSet); } + $this->originalEntityData[$oid] = $actualData; } } @@ -813,20 +824,20 @@ class UnitOfWork implements PropertyChangedListener { $className = $class->name; $persister = $this->getEntityPersister($className); + $entities = array(); $hasLifecycleCallbacks = isset($class->lifecycleCallbacks[Events::postPersist]); - $hasListeners = $this->evm->hasListeners(Events::postPersist); - if ($hasLifecycleCallbacks || $hasListeners) { - $entities = array(); - } + $hasListeners = $this->evm->hasListeners(Events::postPersist); foreach ($this->entityInsertions as $oid => $entity) { - if (get_class($entity) === $className) { - $persister->addInsert($entity); - unset($this->entityInsertions[$oid]); - if ($hasLifecycleCallbacks || $hasListeners) { - $entities[] = $entity; - } + if (get_class($entity) !== $className) continue; + + $persister->addInsert($entity); + + unset($this->entityInsertions[$oid]); + + if ($hasLifecycleCallbacks || $hasListeners) { + $entities[] = $entity; } } @@ -835,24 +846,26 @@ class UnitOfWork implements PropertyChangedListener if ($postInsertIds) { // Persister returned post-insert IDs foreach ($postInsertIds as $id => $entity) { - $oid = spl_object_hash($entity); + $oid = spl_object_hash($entity); $idField = $class->identifier[0]; + $class->reflFields[$idField]->setValue($entity, $id); + $this->entityIdentifiers[$oid] = array($idField => $id); $this->entityStates[$oid] = self::STATE_MANAGED; $this->originalEntityData[$oid][$idField] = $id; + $this->addToIdentityMap($entity); } } - if ($hasLifecycleCallbacks || $hasListeners) { - foreach ($entities as $entity) { - if ($hasLifecycleCallbacks) { - $class->invokeLifecycleCallbacks(Events::postPersist, $entity); - } - if ($hasListeners) { - $this->evm->dispatchEvent(Events::postPersist, new LifecycleEventArgs($entity, $this->em)); - } + foreach ($entities as $entity) { + if ($hasLifecycleCallbacks) { + $class->invokeLifecycleCallbacks(Events::postPersist, $entity); + } + + if ($hasListeners) { + $this->evm->dispatchEvent(Events::postPersist, new LifecycleEventArgs($entity, $this->em)); } } } @@ -868,35 +881,41 @@ class UnitOfWork implements PropertyChangedListener $persister = $this->getEntityPersister($className); $hasPreUpdateLifecycleCallbacks = isset($class->lifecycleCallbacks[Events::preUpdate]); - $hasPreUpdateListeners = $this->evm->hasListeners(Events::preUpdate); + $hasPreUpdateListeners = $this->evm->hasListeners(Events::preUpdate); + $hasPostUpdateLifecycleCallbacks = isset($class->lifecycleCallbacks[Events::postUpdate]); - $hasPostUpdateListeners = $this->evm->hasListeners(Events::postUpdate); + $hasPostUpdateListeners = $this->evm->hasListeners(Events::postUpdate); foreach ($this->entityUpdates as $oid => $entity) { - if (get_class($entity) == $className || $entity instanceof Proxy && get_parent_class($entity) == $className) { + if ( ! (get_class($entity) === $className || $entity instanceof Proxy && get_parent_class($entity) === $className)) { + continue; + } - if ($hasPreUpdateLifecycleCallbacks) { - $class->invokeLifecycleCallbacks(Events::preUpdate, $entity); - $this->recomputeSingleEntityChangeSet($class, $entity); - } + if ($hasPreUpdateLifecycleCallbacks) { + $class->invokeLifecycleCallbacks(Events::preUpdate, $entity); - if ($hasPreUpdateListeners) { - $this->evm->dispatchEvent(Events::preUpdate, new Event\PreUpdateEventArgs( - $entity, $this->em, $this->entityChangeSets[$oid]) - ); - } + $this->recomputeSingleEntityChangeSet($class, $entity); + } - if ($this->entityChangeSets[$oid]) { - $persister->update($entity); - } - unset($this->entityUpdates[$oid]); - - if ($hasPostUpdateLifecycleCallbacks) { - $class->invokeLifecycleCallbacks(Events::postUpdate, $entity); - } - if ($hasPostUpdateListeners) { - $this->evm->dispatchEvent(Events::postUpdate, new LifecycleEventArgs($entity, $this->em)); - } + if ($hasPreUpdateListeners) { + $this->evm->dispatchEvent( + Events::preUpdate, + new Event\PreUpdateEventArgs($entity, $this->em, $this->entityChangeSets[$oid]) + ); + } + + if ($this->entityChangeSets[$oid]) { + $persister->update($entity); + } + + unset($this->entityUpdates[$oid]); + + if ($hasPostUpdateLifecycleCallbacks) { + $class->invokeLifecycleCallbacks(Events::postUpdate, $entity); + } + + if ($hasPostUpdateListeners) { + $this->evm->dispatchEvent(Events::postUpdate, new LifecycleEventArgs($entity, $this->em)); } } } @@ -915,27 +934,32 @@ class UnitOfWork implements PropertyChangedListener $hasListeners = $this->evm->hasListeners(Events::postRemove); foreach ($this->entityDeletions as $oid => $entity) { - if (get_class($entity) == $className || $entity instanceof Proxy && get_parent_class($entity) == $className) { - $persister->delete($entity); - unset( - $this->entityDeletions[$oid], - $this->entityIdentifiers[$oid], - $this->originalEntityData[$oid], - $this->entityStates[$oid] - ); - // Entity with this $oid after deletion treated as NEW, even if the $oid - // is obtained by a new entity because the old one went out of scope. - //$this->entityStates[$oid] = self::STATE_NEW; - if ( ! $class->isIdentifierNatural()) { - $class->reflFields[$class->identifier[0]]->setValue($entity, null); - } + if ( ! (get_class($entity) == $className || $entity instanceof Proxy && get_parent_class($entity) == $className)) { + continue; + } + + $persister->delete($entity); + + unset( + $this->entityDeletions[$oid], + $this->entityIdentifiers[$oid], + $this->originalEntityData[$oid], + $this->entityStates[$oid] + ); + + // Entity with this $oid after deletion treated as NEW, even if the $oid + // is obtained by a new entity because the old one went out of scope. + //$this->entityStates[$oid] = self::STATE_NEW; + if ( ! $class->isIdentifierNatural()) { + $class->reflFields[$class->identifier[0]]->setValue($entity, null); + } - if ($hasLifecycleCallbacks) { - $class->invokeLifecycleCallbacks(Events::postRemove, $entity); - } - if ($hasListeners) { - $this->evm->dispatchEvent(Events::postRemove, new LifecycleEventArgs($entity, $this->em)); - } + if ($hasLifecycleCallbacks) { + $class->invokeLifecycleCallbacks(Events::postRemove, $entity); + } + + if ($hasListeners) { + $this->evm->dispatchEvent(Events::postRemove, new LifecycleEventArgs($entity, $this->em)); } } } @@ -948,51 +972,57 @@ class UnitOfWork implements PropertyChangedListener private function getCommitOrder(array $entityChangeSet = null) { if ($entityChangeSet === null) { - $entityChangeSet = array_merge( - $this->entityInsertions, - $this->entityUpdates, - $this->entityDeletions - ); + $entityChangeSet = array_merge($this->entityInsertions, $this->entityUpdates, $this->entityDeletions); } $calc = $this->getCommitOrderCalculator(); // See if there are any new classes in the changeset, that are not in the // commit order graph yet (dont have a node). - - // TODO: Can we know the know the possible $newNodes based on something more efficient? IdentityMap? + // We have to inspect changeSet to be able to correctly build dependencies. + // It is not possible to use IdentityMap here because post inserted ids + // are not yet available. $newNodes = array(); + foreach ($entityChangeSet as $oid => $entity) { - $className = get_class($entity); - if ( ! $calc->hasClass($className)) { - $class = $this->em->getClassMetadata($className); - $calc->addClass($class); - $newNodes[] = $class; - } + $className = get_class($entity); + + if ($calc->hasClass($className)) continue; + + $class = $this->em->getClassMetadata($className); + $calc->addClass($class); + + $newNodes[] = $class; } - + // Calculate dependencies for new nodes while ($class = array_pop($newNodes)) { foreach ($class->associationMappings as $assoc) { - if ($assoc['isOwningSide'] && $assoc['type'] & ClassMetadata::TO_ONE) { - $targetClass = $this->em->getClassMetadata($assoc['targetEntity']); - if ( ! $calc->hasClass($targetClass->name)) { - $calc->addClass($targetClass); - $newNodes[] = $targetClass; - } - $calc->addDependency($targetClass, $class); - // If the target class has mapped subclasses, - // these share the same dependency. - if ($targetClass->subClasses) { - foreach ($targetClass->subClasses as $subClassName) { - $targetSubClass = $this->em->getClassMetadata($subClassName); - if ( ! $calc->hasClass($subClassName)) { - $calc->addClass($targetSubClass); - $newNodes[] = $targetSubClass; - } - $calc->addDependency($targetSubClass, $class); - } + if ( ! ($assoc['isOwningSide'] && $assoc['type'] & ClassMetadata::TO_ONE)) continue; + + $targetClass = $this->em->getClassMetadata($assoc['targetEntity']); + + if ( ! $calc->hasClass($targetClass->name)) { + $calc->addClass($targetClass); + + $newNodes[] = $targetClass; + } + + $calc->addDependency($targetClass, $class); + + // If the target class has mapped subclasses, these share the same dependency. + if ( ! $targetClass->subClasses) continue; + + foreach ($targetClass->subClasses as $subClassName) { + $targetSubClass = $this->em->getClassMetadata($subClassName); + + if ( ! $calc->hasClass($subClassName)) { + $calc->addClass($targetSubClass); + + $newNodes[] = $targetSubClass; } + + $calc->addDependency($targetSubClass, $class); } } } @@ -1013,9 +1043,11 @@ class UnitOfWork implements PropertyChangedListener if (isset($this->entityUpdates[$oid])) { throw new InvalidArgumentException("Dirty entity can not be scheduled for insertion."); } + if (isset($this->entityDeletions[$oid])) { throw new InvalidArgumentException("Removed entity can not be scheduled for insertion."); } + if (isset($this->entityInsertions[$oid])) { throw new InvalidArgumentException("Entity can not be scheduled for insertion twice."); } @@ -1046,9 +1078,11 @@ class UnitOfWork implements PropertyChangedListener public function scheduleForUpdate($entity) { $oid = spl_object_hash($entity); + if ( ! isset($this->entityIdentifiers[$oid])) { throw new InvalidArgumentException("Entity has no identity."); } + if (isset($this->entityDeletions[$oid])) { throw new InvalidArgumentException("Entity is removed."); } @@ -1071,13 +1105,16 @@ class UnitOfWork implements PropertyChangedListener */ public function scheduleExtraUpdate($entity, array $changeset) { - $oid = spl_object_hash($entity); + $oid = spl_object_hash($entity); + $extraUpdate = array($entity, $changeset); + if (isset($this->extraUpdates[$oid])) { list($ignored, $changeset2) = $this->extraUpdates[$oid]; - $this->extraUpdates[$oid] = array($entity, $changeset + $changeset2); - } else { - $this->extraUpdates[$oid] = array($entity, $changeset); + + $extraUpdate = array($entity, $changeset + $changeset2); } + + $this->extraUpdates[$oid] = $extraUpdate; } /** @@ -1093,9 +1130,17 @@ class UnitOfWork implements PropertyChangedListener return isset($this->entityUpdates[spl_object_hash($entity)]); } + + /** + * Checks whether an entity is registered to be checked in the unit of work. + * + * @param object $entity + * @return boolean + */ public function isScheduledForDirtyCheck($entity) { $rootEntityName = $this->em->getClassMetadata(get_class($entity))->rootEntityName; + return isset($this->scheduledForDirtyCheck[$rootEntityName][spl_object_hash($entity)]); } @@ -1113,22 +1158,25 @@ class UnitOfWork implements PropertyChangedListener if ($this->isInIdentityMap($entity)) { $this->removeFromIdentityMap($entity); } + unset($this->entityInsertions[$oid], $this->entityStates[$oid]); + return; // entity has not been persisted yet, so nothing more to do. } if ( ! $this->isInIdentityMap($entity)) { - return; // ignore + return; } - + $this->removeFromIdentityMap($entity); - + if (isset($this->entityUpdates[$oid])) { unset($this->entityUpdates[$oid]); } + if ( ! isset($this->entityDeletions[$oid])) { $this->entityDeletions[$oid] = $entity; - $this->entityStates[$oid] = self::STATE_REMOVED; + $this->entityStates[$oid] = self::STATE_REMOVED; } } @@ -1153,9 +1201,10 @@ class UnitOfWork implements PropertyChangedListener public function isEntityScheduled($entity) { $oid = spl_object_hash($entity); - return isset($this->entityInsertions[$oid]) || - isset($this->entityUpdates[$oid]) || - isset($this->entityDeletions[$oid]); + + return isset($this->entityInsertions[$oid]) + || isset($this->entityUpdates[$oid]) + || isset($this->entityDeletions[$oid]); } /** @@ -1172,18 +1221,24 @@ class UnitOfWork implements PropertyChangedListener public function addToIdentityMap($entity) { $classMetadata = $this->em->getClassMetadata(get_class($entity)); - $idHash = implode(' ', $this->entityIdentifiers[spl_object_hash($entity)]); + $idHash = implode(' ', $this->entityIdentifiers[spl_object_hash($entity)]); + if ($idHash === '') { - throw new InvalidArgumentException("The given entity has no identity."); + throw new InvalidArgumentException('The given entity has no identity.'); } + $className = $classMetadata->rootEntityName; + if (isset($this->identityMap[$className][$idHash])) { return false; } + $this->identityMap[$className][$idHash] = $entity; + if ($entity instanceof NotifyPropertyChanged) { $entity->addPropertyChangedListener($this); } + return true; } @@ -1274,18 +1329,19 @@ class UnitOfWork implements PropertyChangedListener */ public function removeFromIdentityMap($entity) { - $oid = spl_object_hash($entity); + $oid = spl_object_hash($entity); $classMetadata = $this->em->getClassMetadata(get_class($entity)); - $idHash = implode(' ', $this->entityIdentifiers[$oid]); + $idHash = implode(' ', $this->entityIdentifiers[$oid]); if ($idHash === '') { - throw new InvalidArgumentException("The given entity has no identity."); + throw new InvalidArgumentException('The given entity has no identity.'); } $className = $classMetadata->rootEntityName; if (isset($this->identityMap[$className][$idHash])) { unset($this->identityMap[$className][$idHash]); + //$this->entityStates[$oid] = self::STATE_DETACHED; return true; @@ -1373,6 +1429,7 @@ class UnitOfWork implements PropertyChangedListener public function persist($entity) { $visited = array(); + $this->doPersist($entity, $visited); } @@ -1388,6 +1445,7 @@ class UnitOfWork implements PropertyChangedListener private function doPersist($entity, array &$visited) { $oid = spl_object_hash($entity); + if (isset($visited[$oid])) { return; // Prevent infinite recursion } @@ -1409,19 +1467,24 @@ class UnitOfWork implements PropertyChangedListener $this->scheduleForDirtyCheck($entity); } break; + case self::STATE_NEW: $this->persistNew($class, $entity); break; + case self::STATE_REMOVED: // Entity becomes managed again unset($this->entityDeletions[$oid]); + $this->entityStates[$oid] = self::STATE_MANAGED; break; + case self::STATE_DETACHED: // Can actually not happen right now since we assume STATE_NEW. - throw new InvalidArgumentException("Detached entity passed to persist()."); + throw new InvalidArgumentException('Detached entity passed to persist().'); + default: - throw new UnexpectedValueException("Unexpected entity state: $entityState."); + throw new UnexpectedValueException(sprintf('Unexpected entity state: %s', $entityState)); } $this->cascadePersist($entity, $visited); @@ -1435,6 +1498,7 @@ class UnitOfWork implements PropertyChangedListener public function remove($entity) { $visited = array(); + $this->doRemove($entity, $visited); } @@ -1451,6 +1515,7 @@ class UnitOfWork implements PropertyChangedListener private function doRemove($entity, array &$visited) { $oid = spl_object_hash($entity); + if (isset($visited[$oid])) { return; // Prevent infinite recursion } @@ -1461,26 +1526,32 @@ class UnitOfWork implements PropertyChangedListener // can cause problems when a lazy proxy has to be initialized for the cascade operation. $this->cascadeRemove($entity, $visited); - $class = $this->em->getClassMetadata(get_class($entity)); + $class = $this->em->getClassMetadata(get_class($entity)); $entityState = $this->getEntityState($entity); + switch ($entityState) { case self::STATE_NEW: case self::STATE_REMOVED: // nothing to do break; + case self::STATE_MANAGED: if (isset($class->lifecycleCallbacks[Events::preRemove])) { $class->invokeLifecycleCallbacks(Events::preRemove, $entity); } + if ($this->evm->hasListeners(Events::preRemove)) { $this->evm->dispatchEvent(Events::preRemove, new LifecycleEventArgs($entity, $this->em)); } + $this->scheduleForDelete($entity); break; + case self::STATE_DETACHED: - throw new InvalidArgumentException("A detached entity can not be removed."); + throw new InvalidArgumentException('A detached entity can not be removed.'); + default: - throw new UnexpectedValueException("Unexpected entity state: $entityState."); + throw new UnexpectedValueException(sprintf('Unexpected entity state: %s', $entityState)); } } @@ -1498,6 +1569,7 @@ class UnitOfWork implements PropertyChangedListener public function merge($entity) { $visited = array(); + return $this->doMerge($entity, $visited); } @@ -1514,6 +1586,7 @@ class UnitOfWork implements PropertyChangedListener private function doMerge($entity, array &$visited, $prevManagedCopy = null, $assoc = null) { $oid = spl_object_hash($entity); + if (isset($visited[$oid])) { return; // Prevent infinite recursion } @@ -1526,9 +1599,9 @@ class UnitOfWork implements PropertyChangedListener // an extra db-roundtrip this way. If it is not MANAGED but has an identity, // we need to fetch it from the db anyway in order to merge. // MANAGED entities are ignored by the merge operation. - if ($this->getEntityState($entity, self::STATE_DETACHED) == self::STATE_MANAGED) { - $managedCopy = $entity; - } else { + $managedCopy = $entity; + + if ($this->getEntityState($entity, self::STATE_DETACHED) !== self::STATE_MANAGED) { if ($entity instanceof Proxy && ! $entity->__isInitialized__) { $entity->__load(); } @@ -1539,9 +1612,11 @@ class UnitOfWork implements PropertyChangedListener // If there is no ID, it is actually NEW. if ( ! $id) { $managedCopy = $class->newInstance(); + $this->persistNew($class, $managedCopy); } else { $managedCopy = $this->tryGetById($id, $class->rootEntityName); + if ($managedCopy) { // We have the entity in-memory already, just make sure its not removed. if ($this->getEntityState($managedCopy) == self::STATE_REMOVED) { @@ -1556,19 +1631,21 @@ class UnitOfWork implements PropertyChangedListener if ($managedCopy === null) { // If the identifier is ASSIGNED, it is NEW, otherwise an error // since the managed entity was not found. - if ($class->isIdentifierNatural()) { - $managedCopy = $class->newInstance(); - $class->setIdentifierValues($managedCopy, $id); - $this->persistNew($class, $managedCopy); - } else { + if ( ! $class->isIdentifierNatural()) { throw new EntityNotFoundException; } + + $managedCopy = $class->newInstance(); + $class->setIdentifierValues($managedCopy, $id); + + $this->persistNew($class, $managedCopy); } } if ($class->isVersioned) { $managedCopyVersion = $class->reflFields[$class->versionField]->getValue($managedCopy); $entityVersion = $class->reflFields[$class->versionField]->getValue($entity); + // Throw exception if versions dont match. if ($managedCopyVersion != $entityVersion) { throw OptimisticLockException::lockFailedVersionMissmatch($entity, $entityVersion, $managedCopyVersion); @@ -1631,6 +1708,7 @@ class UnitOfWork implements PropertyChangedListener if (!$managedCol->isEmpty() && $managedCol != $mergeCol) { $managedCol->unwrap()->clear(); $managedCol->setDirty(true); + if ($assoc2['isOwningSide'] && $assoc2['type'] == ClassMetadata::MANY_TO_MANY && $class->isChangeTrackingNotify()) { $this->scheduleForDirtyCheck($managedCopy); } @@ -1638,11 +1716,13 @@ class UnitOfWork implements PropertyChangedListener } } } + if ($class->isChangeTrackingNotify()) { // Just treat all properties as changed, there is no other choice. $this->propertyChanged($managedCopy, $name, null, $prop->getValue($managedCopy)); } } + if ($class->isChangeTrackingDeferredExplicit()) { $this->scheduleForDirtyCheck($entity); } @@ -1651,10 +1731,12 @@ class UnitOfWork implements PropertyChangedListener if ($prevManagedCopy !== null) { $assocField = $assoc['fieldName']; $prevClass = $this->em->getClassMetadata(get_class($prevManagedCopy)); + if ($assoc['type'] & ClassMetadata::TO_ONE) { $prevClass->reflFields[$assocField]->setValue($prevManagedCopy, $managedCopy); } else { $prevClass->reflFields[$assocField]->getValue($prevManagedCopy)->add($managedCopy); + if ($assoc['type'] == ClassMetadata::ONE_TO_MANY) { $class->reflFields[$assoc['mappedBy']]->setValue($managedCopy, $prevManagedCopy); } @@ -2031,7 +2113,7 @@ class UnitOfWork implements PropertyChangedListener public function isCollectionScheduledForDeletion(PersistentCollection $coll) { - return isset( $this->collectionsDeletions[spl_object_hash($coll)] ); + return isset($this->collectionsDeletions[spl_object_hash($coll)]); } /** @@ -2235,6 +2317,7 @@ class UnitOfWork implements PropertyChangedListener if (isset($class->lifecycleCallbacks[Events::postLoad])) { $class->invokeLifecycleCallbacks(Events::postLoad, $entity); } + if ($this->evm->hasListeners(Events::postLoad)) { $this->evm->dispatchEvent(Events::postLoad, new LifecycleEventArgs($entity, $this->em)); } @@ -2247,17 +2330,20 @@ class UnitOfWork implements PropertyChangedListener */ public function triggerEagerLoads() { - if (!$this->eagerLoadingEntities) { + if ( ! $this->eagerLoadingEntities) { return; } // avoid infinite recursion - $eagerLoadingEntities = $this->eagerLoadingEntities; + $eagerLoadingEntities = $this->eagerLoadingEntities; $this->eagerLoadingEntities = array(); foreach ($eagerLoadingEntities AS $entityName => $ids) { $class = $this->em->getClassMetadata($entityName); - $this->getEntityPersister($entityName)->loadAll(array_combine($class->identifier, array(array_values($ids)))); + + $this->getEntityPersister($entityName)->loadAll( + array_combine($class->identifier, array(array_values($ids))) + ); } } @@ -2269,15 +2355,16 @@ class UnitOfWork implements PropertyChangedListener */ public function loadCollection(PersistentCollection $collection) { - $assoc = $collection->getMapping(); + $assoc = $collection->getMapping(); + $persister = $this->getEntityPersister($assoc['targetEntity']); + switch ($assoc['type']) { case ClassMetadata::ONE_TO_MANY: - $this->getEntityPersister($assoc['targetEntity'])->loadOneToManyCollection( - $assoc, $collection->getOwner(), $collection); + $persister->loadOneToManyCollection($assoc, $collection->getOwner(), $collection); break; + case ClassMetadata::MANY_TO_MANY: - $this->getEntityPersister($assoc['targetEntity'])->loadManyToManyCollection( - $assoc, $collection->getOwner(), $collection); + $persister->loadManyToManyCollection($assoc, $collection->getOwner(), $collection); break; } } @@ -2302,9 +2389,11 @@ class UnitOfWork implements PropertyChangedListener public function getOriginalEntityData($entity) { $oid = spl_object_hash($entity); + if (isset($this->originalEntityData[$oid])) { return $this->originalEntityData[$oid]; } + return array(); } @@ -2373,6 +2462,7 @@ class UnitOfWork implements PropertyChangedListener public function scheduleForDirtyCheck($entity) { $rootClassName = $this->em->getClassMetadata(get_class($entity))->rootEntityName; + $this->scheduledForDirtyCheck[$rootClassName][spl_object_hash($entity)] = $entity; } @@ -2394,34 +2484,45 @@ class UnitOfWork implements PropertyChangedListener */ public function size() { - $count = 0; - foreach ($this->identityMap as $entitySet) { - $count += count($entitySet); - } - return $count; + $countArray = array_map(function ($item) { return count($item); }, $this->identityMap); + + return array_sum($countArray); } /** * Gets the EntityPersister for an Entity. * * @param string $entityName The name of the Entity. + * * @return Doctrine\ORM\Persisters\AbstractEntityPersister */ public function getEntityPersister($entityName) { - if ( ! isset($this->persisters[$entityName])) { - $class = $this->em->getClassMetadata($entityName); - if ($class->isInheritanceTypeNone()) { - $persister = new Persisters\BasicEntityPersister($this->em, $class); - } else if ($class->isInheritanceTypeSingleTable()) { - $persister = new Persisters\SingleTablePersister($this->em, $class); - } else if ($class->isInheritanceTypeJoined()) { - $persister = new Persisters\JoinedSubclassPersister($this->em, $class); - } else { - $persister = new Persisters\UnionSubclassPersister($this->em, $class); - } - $this->persisters[$entityName] = $persister; + if (isset($this->persisters[$entityName])) { + return $this->persisters[$entityName]; } + + $class = $this->em->getClassMetadata($entityName); + + switch (true) { + case ($class->isInheritanceTypeNone()): + $persister = new Persisters\BasicEntityPersister($this->em, $class); + break; + + case ($class->isInheritanceTypeSingleTable()): + $persister = new Persisters\SingleTablePersister($this->em, $class); + break; + + case ($class->isInheritanceTypeJoined()): + $persister = new Persisters\JoinedSubclassPersister($this->em, $class); + break; + + default: + $persister = new Persisters\UnionSubclassPersister($this->em, $class); + } + + $this->persisters[$entityName] = $persister; + return $this->persisters[$entityName]; } @@ -2429,19 +2530,29 @@ class UnitOfWork implements PropertyChangedListener * Gets a collection persister for a collection-valued association. * * @param AssociationMapping $association + * * @return AbstractCollectionPersister */ public function getCollectionPersister(array $association) { $type = $association['type']; - if ( ! isset($this->collectionPersisters[$type])) { - if ($type == ClassMetadata::ONE_TO_MANY) { - $persister = new Persisters\OneToManyPersister($this->em); - } else if ($type == ClassMetadata::MANY_TO_MANY) { - $persister = new Persisters\ManyToManyPersister($this->em); - } - $this->collectionPersisters[$type] = $persister; + + if (isset($this->collectionPersisters[$type])) { + return $this->collectionPersisters[$type]; } + + switch ($type) { + case ClassMetadata::ONE_TO_MANY: + $persister = new Persisters\OneToManyPersister($this->em); + break; + + case ClassMetadata::MANY_TO_MANY: + $persister = new Persisters\ManyToManyPersister($this->em); + break; + } + + $this->collectionPersisters[$type] = $persister; + return $this->collectionPersisters[$type]; } @@ -2456,9 +2567,11 @@ class UnitOfWork implements PropertyChangedListener public function registerManaged($entity, array $id, array $data) { $oid = spl_object_hash($entity); - $this->entityIdentifiers[$oid] = $id; - $this->entityStates[$oid] = self::STATE_MANAGED; + + $this->entityIdentifiers[$oid] = $id; + $this->entityStates[$oid] = self::STATE_MANAGED; $this->originalEntityData[$oid] = $data; + $this->addToIdentityMap($entity); } @@ -2485,7 +2598,7 @@ class UnitOfWork implements PropertyChangedListener */ public function propertyChanged($entity, $propertyName, $oldValue, $newValue) { - $oid = spl_object_hash($entity); + $oid = spl_object_hash($entity); $class = $this->em->getClassMetadata(get_class($entity)); $isAssocField = isset($class->associationMappings[$propertyName]); @@ -2496,6 +2609,7 @@ class UnitOfWork implements PropertyChangedListener // Update changeset and mark entity for synchronization $this->entityChangeSets[$oid][$propertyName] = array($oldValue, $newValue); + if ( ! isset($this->scheduledForDirtyCheck[$class->rootEntityName][$oid])) { $this->scheduleForDirtyCheck($entity); } @@ -2561,7 +2675,11 @@ class UnitOfWork implements PropertyChangedListener { if ($obj instanceof Proxy) { $obj->__load(); - } else if ($obj instanceof PersistentCollection) { + + return; + } + + if ($obj instanceof PersistentCollection) { $obj->initialize(); } } @@ -2592,6 +2710,7 @@ class UnitOfWork implements PropertyChangedListener if ( ! is_object($object) || ! $this->isInIdentityMap($object)) { throw new InvalidArgumentException("Managed entity required"); } + $this->readOnlyObjects[spl_object_hash($object)] = true; } @@ -2607,6 +2726,7 @@ class UnitOfWork implements PropertyChangedListener if ( ! is_object($object) ) { throw new InvalidArgumentException("Managed entity required"); } + return isset($this->readOnlyObjects[spl_object_hash($object)]); } } From 32b8d77580294af45b59166fcda171d79ba31f18 Mon Sep 17 00:00:00 2001 From: Guilherme Blanco Date: Mon, 7 Nov 2011 01:27:20 -0200 Subject: [PATCH 081/135] Fixed CS issues. More tiny optimizations in UnitOfWork. --- lib/Doctrine/ORM/UnitOfWork.php | 63 ++++++++++++++++++++++++--------- 1 file changed, 47 insertions(+), 16 deletions(-) diff --git a/lib/Doctrine/ORM/UnitOfWork.php b/lib/Doctrine/ORM/UnitOfWork.php index 01473f875..fab0df4a1 100644 --- a/lib/Doctrine/ORM/UnitOfWork.php +++ b/lib/Doctrine/ORM/UnitOfWork.php @@ -366,6 +366,7 @@ class UnitOfWork implements PropertyChangedListener { foreach ($this->entityInsertions as $entity) { $class = $this->em->getClassMetadata(get_class($entity)); + $this->computeChangeSet($class, $entity); } } @@ -548,27 +549,37 @@ class UnitOfWork implements PropertyChangedListener foreach ($actualData as $propName => $actualValue) { // skip field, its a partially omitted one! - if ( ! (isset($originalData[$propName]) || array_key_exists($propName, $originalData))) continue; + if ( ! (isset($originalData[$propName]) || array_key_exists($propName, $originalData))) { + continue; + } $orgValue = $originalData[$propName]; // skip if value havent changed - if ($orgValue === $actualValue) continue; + if ($orgValue === $actualValue) { + continue; + } // if regular field if ( ! isset($class->associationMappings[$propName])) { - if ($isChangeTrackingNotify) continue; + if ($isChangeTrackingNotify) { + continue; + } $changeSet[$propName] = array($orgValue, $actualValue); continue; } + $assoc = $class->associationMappings[$propName]; + if ($orgValue instanceof PersistentCollection) { // A PersistentCollection was de-referenced, so delete it. $coid = spl_object_hash($orgValue); - if (isset($this->collectionDeletions[$coid])) continue; + if (isset($this->collectionDeletions[$coid])) { + continue; + } $this->collectionDeletions[$coid] = $orgValue; $changeSet[$propName] = $orgValue; // Signal changeset, to-many assocs will be ignored. @@ -576,8 +587,6 @@ class UnitOfWork implements PropertyChangedListener continue; } - $assoc = $class->associationMappings[$propName]; - if ($assoc['type'] & ClassMetadata::TO_ONE) { if ($assoc['isOwningSide']) { $changeSet[$propName] = array($orgValue, $actualValue); @@ -619,7 +628,9 @@ class UnitOfWork implements PropertyChangedListener $class = $this->em->getClassMetadata($className); // Skip class if instances are read-only - if ($class->isReadOnly) continue; + if ($class->isReadOnly) { + continue; + } // If change tracking is explicit or happens through notification, then only compute // changes on entities of that type that are explicitly marked for synchronization. @@ -639,7 +650,9 @@ class UnitOfWork implements PropertyChangedListener foreach ($entitiesToProcess as $entity) { // Ignore uninitialized proxy objects - if ($entity instanceof Proxy && ! $entity->__isInitialized__) continue; + if ($entity instanceof Proxy && ! $entity->__isInitialized__) { + continue; + } // Only MANAGED entities that are NOT SCHEDULED FOR INSERTION are processed here. $oid = spl_object_hash($entity); @@ -673,8 +686,8 @@ class UnitOfWork implements PropertyChangedListener $this->visitedCollections[$coid] = $value; } - // Look through the entities, and in any of their associations, for transient (new) - // entities, recursively. ("Persistence by reachability") + // Look through the entities, and in any of their associations, + // for transient (new) entities, recursively. ("Persistence by reachability") // Unwrap. Uninitialized collections will simply be empty. $unwrappedValue = ($assoc['type'] & ClassMetadata::TO_ONE) ? array($value) : $value->unwrap(); $targetClass = $this->em->getClassMetadata($assoc['targetEntity']); @@ -830,7 +843,9 @@ class UnitOfWork implements PropertyChangedListener $hasListeners = $this->evm->hasListeners(Events::postPersist); foreach ($this->entityInsertions as $oid => $entity) { - if (get_class($entity) !== $className) continue; + if (get_class($entity) !== $className) { + continue; + } $persister->addInsert($entity); @@ -987,7 +1002,9 @@ class UnitOfWork implements PropertyChangedListener foreach ($entityChangeSet as $oid => $entity) { $className = get_class($entity); - if ($calc->hasClass($className)) continue; + if ($calc->hasClass($className)) { + continue; + } $class = $this->em->getClassMetadata($className); $calc->addClass($class); @@ -998,7 +1015,9 @@ class UnitOfWork implements PropertyChangedListener // Calculate dependencies for new nodes while ($class = array_pop($newNodes)) { foreach ($class->associationMappings as $assoc) { - if ( ! ($assoc['isOwningSide'] && $assoc['type'] & ClassMetadata::TO_ONE)) continue; + if ( ! ($assoc['isOwningSide'] && $assoc['type'] & ClassMetadata::TO_ONE)) { + continue; + } $targetClass = $this->em->getClassMetadata($assoc['targetEntity']); @@ -1011,7 +1030,9 @@ class UnitOfWork implements PropertyChangedListener $calc->addDependency($targetClass, $class); // If the target class has mapped subclasses, these share the same dependency. - if ( ! $targetClass->subClasses) continue; + if ( ! $targetClass->subClasses) { + continue; + } foreach ($targetClass->subClasses as $subClassName) { $targetSubClass = $this->em->getClassMetadata($subClassName); @@ -1704,6 +1725,7 @@ class UnitOfWork implements PropertyChangedListener } if ($assoc2['isCascadeMerge']) { $managedCol->initialize(); + // clear and set dirty a managed collection if its not also the same collection to merge from. if (!$managedCol->isEmpty() && $managedCol != $mergeCol) { $managedCol->unwrap()->clear(); @@ -1968,6 +1990,7 @@ class UnitOfWork implements PropertyChangedListener private function cascadeRemove($entity, array &$visited) { $class = $this->em->getClassMetadata(get_class($entity)); + foreach ($class->associationMappings as $assoc) { if ( ! $assoc['isCascadeRemove']) { continue; @@ -1978,6 +2001,7 @@ class UnitOfWork implements PropertyChangedListener } $relatedEntities = $class->reflFields[$assoc['fieldName']]->getValue($entity); + if ($relatedEntities instanceof Collection || is_array($relatedEntities)) { // If its a PersistentCollection initialization is intended! No unwrap! foreach ($relatedEntities as $relatedEntity) { @@ -2065,6 +2089,7 @@ class UnitOfWork implements PropertyChangedListener $this->collectionUpdates = $this->extraUpdates = $this->orphanRemovals = array(); + if ($this->commitOrderCalculator !== null) { $this->commitOrderCalculator->clear(); } @@ -2106,9 +2131,15 @@ class UnitOfWork implements PropertyChangedListener */ public function scheduleCollectionDeletion(PersistentCollection $coll) { + $coid = spl_object_hash($coll); + //TODO: if $coll is already scheduled for recreation ... what to do? // Just remove $coll from the scheduled recreations? - $this->collectionDeletions[spl_object_hash($coll)] = $coll; + if (isset($this->collectionUpdates[$coid])) { + unset($this->collectionUpdates[$coid]); + } + + $this->collectionDeletions[$coid] = $coll; } public function isCollectionScheduledForDeletion(PersistentCollection $coll) @@ -2338,7 +2369,7 @@ class UnitOfWork implements PropertyChangedListener $eagerLoadingEntities = $this->eagerLoadingEntities; $this->eagerLoadingEntities = array(); - foreach ($eagerLoadingEntities AS $entityName => $ids) { + foreach ($eagerLoadingEntities as $entityName => $ids) { $class = $this->em->getClassMetadata($entityName); $this->getEntityPersister($entityName)->loadAll( From 6707129a3ee557b24c957e86d9d5f77e1b9c72e9 Mon Sep 17 00:00:00 2001 From: Alexander Date: Tue, 8 Nov 2011 09:43:06 +0100 Subject: [PATCH 082/135] Added type casts to 'non-lazy' identifiers in proxies. --- lib/Doctrine/ORM/Proxy/ProxyFactory.php | 7 +++++- .../ORM/Functional/ReferenceProxyTest.php | 24 +++++++++++++++++++ 2 files changed, 30 insertions(+), 1 deletion(-) diff --git a/lib/Doctrine/ORM/Proxy/ProxyFactory.php b/lib/Doctrine/ORM/Proxy/ProxyFactory.php index 490c3a119..2a6cd1af9 100644 --- a/lib/Doctrine/ORM/Proxy/ProxyFactory.php +++ b/lib/Doctrine/ORM/Proxy/ProxyFactory.php @@ -210,8 +210,12 @@ class ProxyFactory $methods .= $parameterString . ')'; $methods .= "\n" . ' {' . "\n"; if ($this->isShortIdentifierGetter($method, $class)) { + $identifier = lcfirst(substr($method->getName(), 3)); + + $cast = in_array($class->fieldMappings[$identifier]['type'], array('integer', 'bigint', 'smallint')) ? '(int) ' : ''; + $methods .= ' if ($this->__isInitialized__ === false) {' . "\n"; - $methods .= ' return $this->_identifier["' . lcfirst(substr($method->getName(), 3)) . '"];' . "\n"; + $methods .= ' return ' . $cast . '$this->_identifier["' . $identifier . '"];' . "\n"; $methods .= ' }' . "\n"; } $methods .= ' $this->__load();' . "\n"; @@ -237,6 +241,7 @@ class ProxyFactory in_array($identifier, $class->identifier, true) && $class->hasField($identifier) && (($method->getEndLine() - $method->getStartLine()) <= 4) + && in_array($class->fieldMappings[$identifier]['type'], array('integer', 'bigint', 'smallint', 'string')) ); } diff --git a/tests/Doctrine/Tests/ORM/Functional/ReferenceProxyTest.php b/tests/Doctrine/Tests/ORM/Functional/ReferenceProxyTest.php index b89f3d04e..96427600b 100644 --- a/tests/Doctrine/Tests/ORM/Functional/ReferenceProxyTest.php +++ b/tests/Doctrine/Tests/ORM/Functional/ReferenceProxyTest.php @@ -5,6 +5,7 @@ namespace Doctrine\Tests\ORM\Functional; use Doctrine\ORM\Proxy\ProxyFactory; use Doctrine\ORM\Proxy\ProxyClassGenerator; use Doctrine\Tests\Models\ECommerce\ECommerceProduct; +use Doctrine\Tests\Models\ECommerce\ECommerceShipping; require_once __DIR__ . '/../../TestInit.php'; @@ -160,6 +161,29 @@ class ReferenceProxyTest extends \Doctrine\Tests\OrmFunctionalTestCase $this->assertFalse($entity->__isInitialized__, "Getting the identifier doesn't initialize the proxy."); } + public function testDoNotInitializeProxyOnGettingTheIdentifierAndReturnTheRightType() + { + $product = new ECommerceProduct(); + $product->setName('Doctrine Cookbook'); + + $shipping = new ECommerceShipping(); + $shipping->setDays(1); + $product->setShipping($shipping); + $this->_em->persist($product); + $this->_em->flush(); + $this->_em->clear(); + + $id = $shipping->getId(); + + $product = $this->_em->getRepository('Doctrine\Tests\Models\ECommerce\ECommerceProduct')->find($product->getId()); + + $entity = $product->getShipping(); + $this->assertFalse($entity->__isInitialized__, "Pre-Condition: Object is unitialized proxy."); + $this->assertEquals($id, $entity->getId()); + $this->assertTrue($id === $entity->getId(), "Check that the id's are the same value, and type."); + $this->assertFalse($entity->__isInitialized__, "Getting the identifier doesn't initialize the proxy."); + } + public function testInitializeProxyOnGettingSomethingOtherThanTheIdentifier() { $id = $this->createProduct(); From 0cc176aae202b7142a09ca2d49ceb74c7cedb32b Mon Sep 17 00:00:00 2001 From: Alexander Date: Tue, 8 Nov 2011 09:47:33 +0100 Subject: [PATCH 083/135] Do not cast BigInt to (int) --- lib/Doctrine/ORM/Proxy/ProxyFactory.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/Doctrine/ORM/Proxy/ProxyFactory.php b/lib/Doctrine/ORM/Proxy/ProxyFactory.php index 2a6cd1af9..95b935b91 100644 --- a/lib/Doctrine/ORM/Proxy/ProxyFactory.php +++ b/lib/Doctrine/ORM/Proxy/ProxyFactory.php @@ -212,7 +212,7 @@ class ProxyFactory if ($this->isShortIdentifierGetter($method, $class)) { $identifier = lcfirst(substr($method->getName(), 3)); - $cast = in_array($class->fieldMappings[$identifier]['type'], array('integer', 'bigint', 'smallint')) ? '(int) ' : ''; + $cast = in_array($class->fieldMappings[$identifier]['type'], array('integer', 'smallint')) ? '(int) ' : ''; $methods .= ' if ($this->__isInitialized__ === false) {' . "\n"; $methods .= ' return ' . $cast . '$this->_identifier["' . $identifier . '"];' . "\n"; From e99b8004065fe80c27af20c15ebad923619a5c09 Mon Sep 17 00:00:00 2001 From: Alexander Date: Tue, 8 Nov 2011 10:01:22 +0100 Subject: [PATCH 084/135] Throw exception on invalid fetch mode in annotations --- .../ORM/Mapping/Driver/AnnotationDriver.php | 24 ++++++++++++++--- lib/Doctrine/ORM/Mapping/MappingException.php | 7 ++++- .../ORM/Mapping/AnnotationDriverTest.php | 27 ++++++++++++++++++- 3 files changed, 52 insertions(+), 6 deletions(-) diff --git a/lib/Doctrine/ORM/Mapping/Driver/AnnotationDriver.php b/lib/Doctrine/ORM/Mapping/Driver/AnnotationDriver.php index 36e3dcb01..d886b5d04 100644 --- a/lib/Doctrine/ORM/Mapping/Driver/AnnotationDriver.php +++ b/lib/Doctrine/ORM/Mapping/Driver/AnnotationDriver.php @@ -331,7 +331,7 @@ class AnnotationDriver implements Driver $mapping['inversedBy'] = $oneToOneAnnot->inversedBy; $mapping['cascade'] = $oneToOneAnnot->cascade; $mapping['orphanRemoval'] = $oneToOneAnnot->orphanRemoval; - $mapping['fetch'] = constant('Doctrine\ORM\Mapping\ClassMetadata::FETCH_' . $oneToOneAnnot->fetch); + $mapping['fetch'] = $this->getFetchMode($className, $oneToOneAnnot->fetch); $metadata->mapOneToOne($mapping); } else if ($oneToManyAnnot = $this->_reader->getPropertyAnnotation($property, 'Doctrine\ORM\Mapping\OneToMany')) { $mapping['mappedBy'] = $oneToManyAnnot->mappedBy; @@ -339,7 +339,7 @@ class AnnotationDriver implements Driver $mapping['cascade'] = $oneToManyAnnot->cascade; $mapping['indexBy'] = $oneToManyAnnot->indexBy; $mapping['orphanRemoval'] = $oneToManyAnnot->orphanRemoval; - $mapping['fetch'] = constant('Doctrine\ORM\Mapping\ClassMetadata::FETCH_' . $oneToManyAnnot->fetch); + $mapping['fetch'] = $this->getFetchMode($className, $oneToManyAnnot->fetch); if ($orderByAnnot = $this->_reader->getPropertyAnnotation($property, 'Doctrine\ORM\Mapping\OrderBy')) { $mapping['orderBy'] = $orderByAnnot->value; @@ -355,7 +355,7 @@ class AnnotationDriver implements Driver $mapping['cascade'] = $manyToOneAnnot->cascade; $mapping['inversedBy'] = $manyToOneAnnot->inversedBy; $mapping['targetEntity'] = $manyToOneAnnot->targetEntity; - $mapping['fetch'] = constant('Doctrine\ORM\Mapping\ClassMetadata::FETCH_' . $manyToOneAnnot->fetch); + $mapping['fetch'] = $this->getFetchMode($className, $manyToOneAnnot->fetch); $metadata->mapManyToOne($mapping); } else if ($manyToManyAnnot = $this->_reader->getPropertyAnnotation($property, 'Doctrine\ORM\Mapping\ManyToMany')) { $joinTable = array(); @@ -395,7 +395,7 @@ class AnnotationDriver implements Driver $mapping['inversedBy'] = $manyToManyAnnot->inversedBy; $mapping['cascade'] = $manyToManyAnnot->cascade; $mapping['indexBy'] = $manyToManyAnnot->indexBy; - $mapping['fetch'] = constant('Doctrine\ORM\Mapping\ClassMetadata::FETCH_' . $manyToManyAnnot->fetch); + $mapping['fetch'] = $this->getFetchMode($className, $manyToManyAnnot->fetch); if ($orderByAnnot = $this->_reader->getPropertyAnnotation($property, 'Doctrine\ORM\Mapping\OrderBy')) { $mapping['orderBy'] = $orderByAnnot->value; @@ -536,6 +536,22 @@ class AnnotationDriver implements Driver return $classes; } + /** + * Attempts to resolve the fetch mode. + * + * @param string $className The class name + * @param string $fetchMode The fetch mode + * @return integer The fetch mode as defined in ClassMetadata + * @throws MappingException If the fetch mode is not valid + */ + private function getFetchMode($className, $fetchMode) + { + if(!defined('Doctrine\ORM\Mapping\ClassMetadata::FETCH_' . $fetchMode)) { + throw MappingException::invalidFetchMode($className, $fetchMode); + } + + return constant('Doctrine\ORM\Mapping\ClassMetadata::FETCH_' . $fetchMode); + } /** * Factory method for the Annotation Driver * diff --git a/lib/Doctrine/ORM/Mapping/MappingException.php b/lib/Doctrine/ORM/Mapping/MappingException.php index fcdd5c575..e32e34c16 100644 --- a/lib/Doctrine/ORM/Mapping/MappingException.php +++ b/lib/Doctrine/ORM/Mapping/MappingException.php @@ -298,4 +298,9 @@ class MappingException extends \Doctrine\ORM\ORMException { return new self("Entity '" . $className . "' has no method '" . $methodName . "' to be registered as lifecycle callback."); } -} \ No newline at end of file + + public static function invalidFetchMode($className, $annotation) + { + return new self("Entity '" . $className . "' has a mapping with invalid fetch mode '" . $annotation . "'"); + } +} diff --git a/tests/Doctrine/Tests/ORM/Mapping/AnnotationDriverTest.php b/tests/Doctrine/Tests/ORM/Mapping/AnnotationDriverTest.php index e946a2628..9273f7c6b 100644 --- a/tests/Doctrine/Tests/ORM/Mapping/AnnotationDriverTest.php +++ b/tests/Doctrine/Tests/ORM/Mapping/AnnotationDriverTest.php @@ -199,6 +199,20 @@ class AnnotationDriverTest extends AbstractMappingDriverTest $cm = $factory->getMetadataFor('Doctrine\Tests\ORM\Mapping\ChildEntity'); } + + public function testInvalidFetchOptionThrowsException() + { + $annotationDriver = $this->_loadDriver(); + + $em = $this->_getTestEntityManager(); + $em->getConfiguration()->setMetadataDriverImpl($annotationDriver); + $factory = new \Doctrine\ORM\Mapping\ClassMetadataFactory(); + $factory->setEntityManager($em); + + $this->setExpectedException('Doctrine\ORM\Mapping\MappingException', + "Entity 'Doctrine\Tests\ORM\Mapping\InvalidFetchOption' has a mapping with invalid fetch mode 'eager"); + $cm = $factory->getMetadataFor('Doctrine\Tests\ORM\Mapping\InvalidFetchOption'); + } } /** @@ -310,4 +324,15 @@ class ChildEntity extends MiddleMappedSuperclass * @Column(type="string") */ private $text; -} \ No newline at end of file +} + +/** + * @Entity + */ +class InvalidFetchOption +{ + /** + * @OneToMany(targetEntity="Doctrine\Tests\Models\CMS\CmsUser", fetch="eager") + */ + private $collection; +} From 39ed719c4cda4466f234b80543f3b2ec0b83b31e Mon Sep 17 00:00:00 2001 From: Alexander Date: Tue, 8 Nov 2011 13:15:09 +0100 Subject: [PATCH 085/135] Remove invalid(?) exception for now --- lib/Doctrine/ORM/Query/ResultSetMapping.php | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/lib/Doctrine/ORM/Query/ResultSetMapping.php b/lib/Doctrine/ORM/Query/ResultSetMapping.php index fcfc9b834..5ac99ca82 100644 --- a/lib/Doctrine/ORM/Query/ResultSetMapping.php +++ b/lib/Doctrine/ORM/Query/ResultSetMapping.php @@ -165,10 +165,13 @@ class ResultSetMapping break; } } + + /* TODO: check if this exception can be put back, for now it's gone because of assumptions made by some ORM internals if (!$found) { throw new \LogicException("Cannot add index by for dql alias " . $alias . " and field " . $fieldName . " without calling addFieldResult() for them before."); } + */ } /** @@ -435,4 +438,4 @@ class ResultSetMapping $this->isIdentifierColumn[$alias][$columnName] = true; } } -} \ No newline at end of file +} From c391287cc4909f38f6114403f466f2b3c83b0c48 Mon Sep 17 00:00:00 2001 From: Guilherme Blanco Date: Tue, 8 Nov 2011 18:36:18 -0200 Subject: [PATCH 086/135] More optimizations and increased code readability in Id Generators. --- lib/Doctrine/ORM/Id/SequenceGenerator.php | 9 ++++++--- lib/Doctrine/ORM/Id/TableGenerator.php | 8 +++++--- 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/lib/Doctrine/ORM/Id/SequenceGenerator.php b/lib/Doctrine/ORM/Id/SequenceGenerator.php index 0d564ed32..dd7de60ba 100644 --- a/lib/Doctrine/ORM/Id/SequenceGenerator.php +++ b/lib/Doctrine/ORM/Id/SequenceGenerator.php @@ -59,10 +59,12 @@ class SequenceGenerator extends AbstractIdGenerator implements Serializable if ($this->_maxValue === null || $this->_nextValue == $this->_maxValue) { // Allocate new values $conn = $em->getConnection(); - $sql = $conn->getDatabasePlatform()->getSequenceNextValSQL($this->_sequenceName); + $sql = $conn->getDatabasePlatform()->getSequenceNextValSQL($this->_sequenceName); + $this->_nextValue = $conn->fetchColumn($sql); - $this->_maxValue = $this->_nextValue + $this->_allocationSize; + $this->_maxValue = $this->_nextValue + $this->_allocationSize; } + return $this->_nextValue++; } @@ -90,13 +92,14 @@ class SequenceGenerator extends AbstractIdGenerator implements Serializable { return serialize(array( 'allocationSize' => $this->_allocationSize, - 'sequenceName' => $this->_sequenceName + 'sequenceName' => $this->_sequenceName )); } public function unserialize($serialized) { $array = unserialize($serialized); + $this->_sequenceName = $array['sequenceName']; $this->_allocationSize = $array['allocationSize']; } diff --git a/lib/Doctrine/ORM/Id/TableGenerator.php b/lib/Doctrine/ORM/Id/TableGenerator.php index 5c46f8b5c..5c49344fe 100644 --- a/lib/Doctrine/ORM/Id/TableGenerator.php +++ b/lib/Doctrine/ORM/Id/TableGenerator.php @@ -50,11 +50,12 @@ class TableGenerator extends AbstractIdGenerator if ($this->_maxValue === null || $this->_nextValue == $this->_maxValue) { // Allocate new values $conn = $em->getConnection(); - if ($conn->getTransactionNestingLevel() == 0) { - + + if ($conn->getTransactionNestingLevel() === 0) { // use select for update - $sql = $conn->getDatabasePlatform()->getTableHiLoCurrentValSql($this->_tableName, $this->_sequenceName); + $sql = $conn->getDatabasePlatform()->getTableHiLoCurrentValSql($this->_tableName, $this->_sequenceName); $currentLevel = $conn->fetchColumn($sql); + if ($currentLevel != null) { $this->_nextValue = $currentLevel; $this->_maxValue = $this->_nextValue + $this->_allocationSize; @@ -74,6 +75,7 @@ class TableGenerator extends AbstractIdGenerator // or do we want to work with table locks exclusively? } } + return $this->_nextValue++; } } \ No newline at end of file From 2ddfc6af5a81123b45c5e19fd4e1699366c0fc11 Mon Sep 17 00:00:00 2001 From: Alexander Date: Wed, 9 Nov 2011 08:17:46 +0100 Subject: [PATCH 087/135] Fixed failing tests in PHPUnit 3.6.2 (expecting \Exception was deprecated) --- .../OneToManyUnidirectionalAssociationTest.php | 13 ++++++++++--- .../Tests/ORM/Functional/Ticket/DDC117Test.php | 11 +++++++++-- 2 files changed, 19 insertions(+), 5 deletions(-) diff --git a/tests/Doctrine/Tests/ORM/Functional/OneToManyUnidirectionalAssociationTest.php b/tests/Doctrine/Tests/ORM/Functional/OneToManyUnidirectionalAssociationTest.php index fd7a22a5a..42fd29235 100644 --- a/tests/Doctrine/Tests/ORM/Functional/OneToManyUnidirectionalAssociationTest.php +++ b/tests/Doctrine/Tests/ORM/Functional/OneToManyUnidirectionalAssociationTest.php @@ -73,7 +73,14 @@ class OneToManyUnidirectionalAssociationTest extends \Doctrine\Tests\OrmFunction $this->_em->persist($routeA); $this->_em->persist($routeB); - $this->setExpectedException('Exception'); // depends on the underyling Database Driver - $this->_em->flush(); // Exception + $exceptionThrown = false; + try { + // exception depending on the underyling Database Driver + $this->_em->flush(); + } catch(\Exception $e) { + $exceptionThrown = true; + } + + $this->assertTrue($exceptionThrown, "The underlying database driver throws an exception."); } -} \ No newline at end of file +} diff --git a/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC117Test.php b/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC117Test.php index 7057b765d..9e1b5b074 100644 --- a/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC117Test.php +++ b/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC117Test.php @@ -209,8 +209,15 @@ class DDC117Test extends \Doctrine\Tests\OrmFunctionalTestCase $this->article1->addTranslation('en', 'Bar'); $this->article1->addTranslation('en', 'Baz'); - $this->setExpectedException('Exception'); - $this->_em->flush(); + $exceptionThrown = false; + try { + // exception depending on the underyling Database Driver + $this->_em->flush(); + } catch(\Exception $e) { + $exceptionThrown = true; + } + + $this->assertTrue($exceptionThrown, "The underlying database driver throws an exception."); } /** From 3131103801bb77ecf9a42dd812fcd0630b2e3c88 Mon Sep 17 00:00:00 2001 From: Alexander Date: Wed, 9 Nov 2011 22:09:35 +0100 Subject: [PATCH 088/135] Failing test case --- .../ORM/Functional/Ticket/DDC1458Test.php | 133 ++++++++++++++++++ 1 file changed, 133 insertions(+) create mode 100644 tests/Doctrine/Tests/ORM/Functional/Ticket/DDC1458Test.php diff --git a/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC1458Test.php b/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC1458Test.php new file mode 100644 index 000000000..3b13a540c --- /dev/null +++ b/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC1458Test.php @@ -0,0 +1,133 @@ +_schemaTool->createSchema(array( + $this->_em->getClassMetadata(__NAMESPACE__ . '\TestEntity'), + $this->_em->getClassMetadata(__NAMESPACE__ . '\TestAdditionalEntity') + )); + } + + public function testIssue() + { + $testEntity = new TestEntity(); + $testEntity->setValue(3); + $testEntity->setAdditional(new TestAdditionalEntity()); + $this->_em->persist($testEntity); + $this->_em->flush(); + $this->_em->clear(); + + // So here the value is 3 + $this->assertEquals(3, $testEntity->getValue()); + + $test = $this->_em->getRepository(__NAMESPACE__ . '\TestEntity')->find(1); + + // New value is set + $test->setValue(5); + + // So here the value is 5 + $this->assertEquals(5, $test->getValue()); + + // Get the additional entity + $additional = $test->getAdditional(); + + // Still 5.. + $this->assertEquals(5, $test->getValue()); + + // Force the proxy to load + $additional->getBool(); + + // The value should still be 5 + $this->assertEquals(5, $test->getValue()); + } +} + + +/** + * @Entity + */ +class TestEntity +{ + /** + * @Id + * @Column(type="integer") + * @GeneratedValue(strategy="AUTO") + */ + protected $id; + /** + * @Column(type="integer") + */ + protected $value; + /** + * @OneToOne(targetEntity="TestAdditionalEntity", inversedBy="entity", orphanRemoval=true, cascade={"persist", "remove"}) + */ + protected $additional; + + private $i = 0; + + public function getValue() + { + return $this->value; + } + + public function setValue($value) + { + $this->value = $value; + } + + public function getAdditional() + { + return $this->additional; + } + + public function setAdditional($additional) + { + $this->additional = $additional; + } +} +/** + * @Entity + */ +class TestAdditionalEntity +{ + /** + * @Id + * @Column(type="integer") + * @GeneratedValue(strategy="AUTO") + */ + protected $id; + /** + * @OneToOne(targetEntity="TestEntity", mappedBy="additional") + */ + protected $entity; + /** + * @Column(type="boolean") + */ + protected $bool; + + public function __construct() + { + $this->bool = false; + } + + public function getBool() + { + return $this->bool; + } + + public function setBool($bool) + { + $this->bool = $bool; + } +} From 9c9f85ed4b78218844a45d52d2e37613fb1e77f5 Mon Sep 17 00:00:00 2001 From: Alexander Date: Wed, 9 Nov 2011 22:13:06 +0100 Subject: [PATCH 089/135] Only refresh the given entity if an entity is specified in the query hints --- lib/Doctrine/ORM/UnitOfWork.php | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/lib/Doctrine/ORM/UnitOfWork.php b/lib/Doctrine/ORM/UnitOfWork.php index fab0df4a1..11280b597 100644 --- a/lib/Doctrine/ORM/UnitOfWork.php +++ b/lib/Doctrine/ORM/UnitOfWork.php @@ -2195,6 +2195,11 @@ class UnitOfWork implements PropertyChangedListener } } else { $overrideLocalValues = isset($hints[Query::HINT_REFRESH]); + + // If a only a specific entity is set to refresh, check that it's the one + if(isset($hints[Query::HINT_REFRESH_ENTITY])) { + $overrideLocalValues = $hints[Query::HINT_REFRESH_ENTITY] === $entity; + } } if ($overrideLocalValues) { From 1f55351f194df3317aa6648ceb568018c4ac94df Mon Sep 17 00:00:00 2001 From: Alexander Date: Thu, 10 Nov 2011 16:16:55 +0100 Subject: [PATCH 090/135] Cleanup --- lib/Doctrine/ORM/UnitOfWork.php | 2 +- tests/Doctrine/Tests/ORM/Functional/Ticket/DDC1458Test.php | 2 -- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/lib/Doctrine/ORM/UnitOfWork.php b/lib/Doctrine/ORM/UnitOfWork.php index 11280b597..c3bbe0fe8 100644 --- a/lib/Doctrine/ORM/UnitOfWork.php +++ b/lib/Doctrine/ORM/UnitOfWork.php @@ -2196,7 +2196,7 @@ class UnitOfWork implements PropertyChangedListener } else { $overrideLocalValues = isset($hints[Query::HINT_REFRESH]); - // If a only a specific entity is set to refresh, check that it's the one + // If only a specific entity is set to refresh, check that it's the one if(isset($hints[Query::HINT_REFRESH_ENTITY])) { $overrideLocalValues = $hints[Query::HINT_REFRESH_ENTITY] === $entity; } diff --git a/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC1458Test.php b/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC1458Test.php index 3b13a540c..2a0541afd 100644 --- a/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC1458Test.php +++ b/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC1458Test.php @@ -74,8 +74,6 @@ class TestEntity */ protected $additional; - private $i = 0; - public function getValue() { return $this->value; From a14ba1e561e6e37c54593662d4f3f8f55bd1945f Mon Sep 17 00:00:00 2001 From: Benjamin Eberlei Date: Sat, 12 Nov 2011 09:43:37 +0100 Subject: [PATCH 091/135] DDC-1237 - Remove dependency to mbstring --- lib/Doctrine/ORM/Query/Expr/Composite.php | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/lib/Doctrine/ORM/Query/Expr/Composite.php b/lib/Doctrine/ORM/Query/Expr/Composite.php index 0d606a9b0..036b241a5 100644 --- a/lib/Doctrine/ORM/Query/Expr/Composite.php +++ b/lib/Doctrine/ORM/Query/Expr/Composite.php @@ -39,30 +39,30 @@ class Composite extends Base if ($this->count() === 1) { return (string) $this->_parts[0]; } - + $components = array(); - + foreach ($this->_parts as $part) { $components[] = $this->processQueryPart($part); } - + return implode($this->_separator, $components); } - - + + private function processQueryPart($part) { $queryPart = (string) $part; - + if (is_object($part) && $part instanceof self && $part->count() > 1) { return $this->_preSeparator . $queryPart . $this->_postSeparator; } - + // Fixes DDC-1237: User may have added a where item containing nested expression (with "OR" or "AND") - if (mb_stripos($queryPart, ' OR ') !== false || mb_stripos($queryPart, ' AND ') !== false) { + if (stripos($queryPart, ' OR ') !== false || stripos($queryPart, ' AND ') !== false) { return $this->_preSeparator . $queryPart . $this->_postSeparator; } - + return $queryPart; } } \ No newline at end of file From 450d92872aa55e484024a48c7bd474d98270b9f0 Mon Sep 17 00:00:00 2001 From: Benjamin Eberlei Date: Sat, 12 Nov 2011 12:56:44 +0100 Subject: [PATCH 092/135] Forward compatibility with DBAL master --- tests/Doctrine/Tests/Mocks/ConnectionMock.php | 22 +++++----- .../Tests/Mocks/DatabasePlatformMock.php | 11 ++++- .../Tests/Mocks/HydratorMockStatement.php | 40 ++++++++++++------- 3 files changed, 45 insertions(+), 28 deletions(-) diff --git a/tests/Doctrine/Tests/Mocks/ConnectionMock.php b/tests/Doctrine/Tests/Mocks/ConnectionMock.php index fabecf87a..c1c84d174 100644 --- a/tests/Doctrine/Tests/Mocks/ConnectionMock.php +++ b/tests/Doctrine/Tests/Mocks/ConnectionMock.php @@ -8,7 +8,7 @@ class ConnectionMock extends \Doctrine\DBAL\Connection private $_platformMock; private $_lastInsertId = 0; private $_inserts = array(); - + public function __construct(array $params, $driver, $config = null, $eventManager = null) { $this->_platformMock = new DatabasePlatformMock(); @@ -18,7 +18,7 @@ class ConnectionMock extends \Doctrine\DBAL\Connection // Override possible assignment of platform to database platform mock $this->_platform = $this->_platformMock; } - + /** * @override */ @@ -26,15 +26,15 @@ class ConnectionMock extends \Doctrine\DBAL\Connection { return $this->_platformMock; } - + /** * @override */ - public function insert($tableName, array $data) + public function insert($tableName, array $data, array $types = array()) { $this->_inserts[$tableName][] = $data; } - + /** * @override */ @@ -50,7 +50,7 @@ class ConnectionMock extends \Doctrine\DBAL\Connection { return $this->_fetchOneResult; } - + /** * @override */ @@ -61,29 +61,29 @@ class ConnectionMock extends \Doctrine\DBAL\Connection } return $input; } - + /* Mock API */ public function setFetchOneResult($fetchOneResult) { $this->_fetchOneResult = $fetchOneResult; } - + public function setDatabasePlatform($platform) { $this->_platformMock = $platform; } - + public function setLastInsertId($id) { $this->_lastInsertId = $id; } - + public function getInserts() { return $this->_inserts; } - + public function reset() { $this->_inserts = array(); diff --git a/tests/Doctrine/Tests/Mocks/DatabasePlatformMock.php b/tests/Doctrine/Tests/Mocks/DatabasePlatformMock.php index b2954cf55..b634408be 100644 --- a/tests/Doctrine/Tests/Mocks/DatabasePlatformMock.php +++ b/tests/Doctrine/Tests/Mocks/DatabasePlatformMock.php @@ -57,7 +57,7 @@ class DatabasePlatformMock extends \Doctrine\DBAL\Platforms\AbstractPlatform /** @override */ public function getVarcharTypeDeclarationSQL(array $field) {} - + /** @override */ public function getClobTypeDeclarationSQL(array $field) {} @@ -85,6 +85,13 @@ class DatabasePlatformMock extends \Doctrine\DBAL\Platforms\AbstractPlatform protected function initializeDoctrineTypeMappings() { - + + } + /** + * Gets the SQL Snippet used to declare a BLOB column type. + */ + public function getBlobTypeDeclarationSQL(array $field) + { + throw DBALException::notSupported(__METHOD__); } } \ No newline at end of file diff --git a/tests/Doctrine/Tests/Mocks/HydratorMockStatement.php b/tests/Doctrine/Tests/Mocks/HydratorMockStatement.php index 555982765..b5f5e3b47 100644 --- a/tests/Doctrine/Tests/Mocks/HydratorMockStatement.php +++ b/tests/Doctrine/Tests/Mocks/HydratorMockStatement.php @@ -8,10 +8,10 @@ namespace Doctrine\Tests\Mocks; * * @author Roman Borschel */ -class HydratorMockStatement implements \Doctrine\DBAL\Driver\Statement +class HydratorMockStatement implements \IteratorAggregate, \Doctrine\DBAL\Driver\Statement { - private $_resultSet; - + private $_resultSet; + /** * Creates a new mock statement that will serve the provided fake result set to clients. * @@ -21,7 +21,7 @@ class HydratorMockStatement implements \Doctrine\DBAL\Driver\Statement { $this->_resultSet = $resultSet; } - + /** * Fetches all rows from the result set. * @@ -31,7 +31,7 @@ class HydratorMockStatement implements \Doctrine\DBAL\Driver\Statement { return $this->_resultSet; } - + public function fetchColumn($columnNumber = 0) { $row = current($this->_resultSet); @@ -39,10 +39,10 @@ class HydratorMockStatement implements \Doctrine\DBAL\Driver\Statement $val = array_shift($row); return $val !== null ? $val : false; } - + /** * Fetches the next row in the result set. - * + * */ public function fetch($fetchStyle = null) { @@ -50,7 +50,7 @@ class HydratorMockStatement implements \Doctrine\DBAL\Driver\Statement next($this->_resultSet); return $current; } - + /** * Closes the cursor, enabling the statement to be executed again. * @@ -60,13 +60,13 @@ class HydratorMockStatement implements \Doctrine\DBAL\Driver\Statement { return true; } - + public function setResultSet(array $resultSet) { reset($resultSet); $this->_resultSet = $resultSet; } - + public function bindColumn($column, &$param, $type = null) { } @@ -78,7 +78,7 @@ class HydratorMockStatement implements \Doctrine\DBAL\Driver\Statement public function bindParam($column, &$variable, $type = null, $length = null, $driverOptions = array()) { } - + public function columnCount() { } @@ -86,16 +86,26 @@ class HydratorMockStatement implements \Doctrine\DBAL\Driver\Statement public function errorCode() { } - + public function errorInfo() { } - + public function execute($params = array()) { } - + public function rowCount() { - } + } + + public function getIterator() + { + return $this->_resultSet; + } + + public function setFetchMode($fetchMode) + { + + } } \ No newline at end of file From 01697fee3db000b317e4ecb13dc9b0f0acf7abb0 Mon Sep 17 00:00:00 2001 From: Benjamin Eberlei Date: Sat, 12 Nov 2011 22:16:39 +0100 Subject: [PATCH 093/135] Fix failing PostgreSQL tests --- .../ORM/Functional/ReferenceProxyTest.php | 12 ++-- .../SchemaTool/PostgreSqlSchemaToolTest.php | 56 ++++++++++--------- .../ORM/Functional/Ticket/DDC1151Test.php | 12 ++-- 3 files changed, 42 insertions(+), 38 deletions(-) diff --git a/tests/Doctrine/Tests/ORM/Functional/ReferenceProxyTest.php b/tests/Doctrine/Tests/ORM/Functional/ReferenceProxyTest.php index 96427600b..9cd216066 100644 --- a/tests/Doctrine/Tests/ORM/Functional/ReferenceProxyTest.php +++ b/tests/Doctrine/Tests/ORM/Functional/ReferenceProxyTest.php @@ -98,7 +98,7 @@ class ReferenceProxyTest extends \Doctrine\Tests\OrmFunctionalTestCase $this->assertTrue($clone->isCloned); $this->assertFalse($entity->isCloned); } - + /** * @group DDC-733 */ @@ -108,12 +108,12 @@ class ReferenceProxyTest extends \Doctrine\Tests\OrmFunctionalTestCase /* @var $entity Doctrine\Tests\Models\ECommerce\ECommerceProduct */ $entity = $this->_em->getReference('Doctrine\Tests\Models\ECommerce\ECommerceProduct' , $id); - + $this->assertFalse($entity->__isInitialized__, "Pre-Condition: Object is unitialized proxy."); $this->_em->getUnitOfWork()->initializeObject($entity); $this->assertTrue($entity->__isInitialized__, "Should be initialized after called UnitOfWork::initializeObject()"); } - + /** * @group DDC-1163 */ @@ -124,10 +124,10 @@ class ReferenceProxyTest extends \Doctrine\Tests\OrmFunctionalTestCase /* @var $entity Doctrine\Tests\Models\ECommerce\ECommerceProduct */ $entity = $this->_em->getReference('Doctrine\Tests\Models\ECommerce\ECommerceProduct' , $id); $entity->setName('Doctrine 2 Cookbook'); - + $this->_em->flush(); $this->_em->clear(); - + $entity = $this->_em->getReference('Doctrine\Tests\Models\ECommerce\ECommerceProduct' , $id); $this->assertEquals('Doctrine 2 Cookbook', $entity->getName()); } @@ -180,7 +180,7 @@ class ReferenceProxyTest extends \Doctrine\Tests\OrmFunctionalTestCase $entity = $product->getShipping(); $this->assertFalse($entity->__isInitialized__, "Pre-Condition: Object is unitialized proxy."); $this->assertEquals($id, $entity->getId()); - $this->assertTrue($id === $entity->getId(), "Check that the id's are the same value, and type."); + $this->assertSame($id, $entity->getId(), "Check that the id's are the same value, and type."); $this->assertFalse($entity->__isInitialized__, "Getting the identifier doesn't initialize the proxy."); } diff --git a/tests/Doctrine/Tests/ORM/Functional/SchemaTool/PostgreSqlSchemaToolTest.php b/tests/Doctrine/Tests/ORM/Functional/SchemaTool/PostgreSqlSchemaToolTest.php index b93753ce0..79e7af99a 100644 --- a/tests/Doctrine/Tests/ORM/Functional/SchemaTool/PostgreSqlSchemaToolTest.php +++ b/tests/Doctrine/Tests/ORM/Functional/SchemaTool/PostgreSqlSchemaToolTest.php @@ -21,7 +21,7 @@ class PostgreSqlSchemaToolTest extends \Doctrine\Tests\OrmFunctionalTestCase $address = $this->_em->getClassMetadata('Doctrine\Tests\Models\CMS\CmsAddress'); $this->assertEquals(1, $address->sequenceGeneratorDefinition['allocationSize']); } - + public function testGetCreateSchemaSql() { $classes = array( @@ -32,26 +32,30 @@ class PostgreSqlSchemaToolTest extends \Doctrine\Tests\OrmFunctionalTestCase $tool = new SchemaTool($this->_em); $sql = $tool->getCreateSchemaSql($classes); - - $this->assertEquals("CREATE TABLE cms_addresses (id INT NOT NULL, user_id INT DEFAULT NULL, country VARCHAR(50) NOT NULL, zip VARCHAR(50) NOT NULL, city VARCHAR(50) NOT NULL, PRIMARY KEY(id))", $sql[0]); - $this->assertEquals("CREATE UNIQUE INDEX UNIQ_ACAC157BA76ED395 ON cms_addresses (user_id)", $sql[1]); - $this->assertEquals("CREATE TABLE cms_users (id INT NOT NULL, status VARCHAR(50) NOT NULL, username VARCHAR(255) NOT NULL, name VARCHAR(255) NOT NULL, PRIMARY KEY(id))", $sql[2]); - $this->assertEquals("CREATE UNIQUE INDEX UNIQ_3AF03EC5F85E0677 ON cms_users (username)", $sql[3]); - $this->assertEquals("CREATE TABLE cms_users_groups (user_id INT NOT NULL, group_id INT NOT NULL, PRIMARY KEY(user_id, group_id))", $sql[4]); - $this->assertEquals("CREATE INDEX IDX_7EA9409AA76ED395 ON cms_users_groups (user_id)", $sql[5]); - $this->assertEquals("CREATE INDEX IDX_7EA9409AFE54D947 ON cms_users_groups (group_id)", $sql[6]); - $this->assertEquals("CREATE TABLE cms_phonenumbers (phonenumber VARCHAR(50) NOT NULL, user_id INT DEFAULT NULL, PRIMARY KEY(phonenumber))", $sql[7]); - $this->assertEquals("CREATE INDEX IDX_F21F790FA76ED395 ON cms_phonenumbers (user_id)", $sql[8]); - $this->assertEquals("CREATE SEQUENCE cms_addresses_id_seq INCREMENT BY 1 MINVALUE 1 START 1", $sql[9]); - $this->assertEquals("CREATE SEQUENCE cms_users_id_seq INCREMENT BY 1 MINVALUE 1 START 1", $sql[10]); - $this->assertEquals("ALTER TABLE cms_addresses ADD FOREIGN KEY (user_id) REFERENCES cms_users(id) NOT DEFERRABLE INITIALLY IMMEDIATE", $sql[11]); - $this->assertEquals("ALTER TABLE cms_users_groups ADD FOREIGN KEY (user_id) REFERENCES cms_users(id) NOT DEFERRABLE INITIALLY IMMEDIATE", $sql[12]); - $this->assertEquals("ALTER TABLE cms_users_groups ADD FOREIGN KEY (group_id) REFERENCES cms_groups(id) NOT DEFERRABLE INITIALLY IMMEDIATE", $sql[13]); - $this->assertEquals("ALTER TABLE cms_phonenumbers ADD FOREIGN KEY (user_id) REFERENCES cms_users(id) NOT DEFERRABLE INITIALLY IMMEDIATE", $sql[14]); - - $this->assertEquals(count($sql), 15); + $sqlCount = count($sql); + + $this->assertEquals("CREATE TABLE cms_addresses (id INT NOT NULL, user_id INT DEFAULT NULL, country VARCHAR(50) NOT NULL, zip VARCHAR(50) NOT NULL, city VARCHAR(50) NOT NULL, PRIMARY KEY(id))", array_shift($sql)); + $this->assertEquals("CREATE UNIQUE INDEX UNIQ_ACAC157BA76ED395 ON cms_addresses (user_id)", array_shift($sql)); + $this->assertEquals("CREATE TABLE cms_users (id INT NOT NULL, email_id INT DEFAULT NULL, status VARCHAR(50) DEFAULT NULL, username VARCHAR(255) NOT NULL, name VARCHAR(255) NOT NULL, PRIMARY KEY(id))", array_shift($sql)); + $this->assertEquals("CREATE UNIQUE INDEX UNIQ_3AF03EC5F85E0677 ON cms_users (username)", array_shift($sql)); + $this->assertEquals("CREATE UNIQUE INDEX UNIQ_3AF03EC5A832C1C9 ON cms_users (email_id)", array_shift($sql)); + $this->assertEquals("CREATE TABLE cms_users_groups (user_id INT NOT NULL, group_id INT NOT NULL, PRIMARY KEY(user_id, group_id))", array_shift($sql)); + $this->assertEquals("CREATE INDEX IDX_7EA9409AA76ED395 ON cms_users_groups (user_id)", array_shift($sql)); + $this->assertEquals("CREATE INDEX IDX_7EA9409AFE54D947 ON cms_users_groups (group_id)", array_shift($sql)); + $this->assertEquals("CREATE TABLE cms_phonenumbers (phonenumber VARCHAR(50) NOT NULL, user_id INT DEFAULT NULL, PRIMARY KEY(phonenumber))", array_shift($sql)); + $this->assertEquals("CREATE INDEX IDX_F21F790FA76ED395 ON cms_phonenumbers (user_id)", array_shift($sql)); + $this->assertEquals("CREATE SEQUENCE cms_addresses_id_seq INCREMENT BY 1 MINVALUE 1 START 1", array_shift($sql)); + $this->assertEquals("CREATE SEQUENCE cms_users_id_seq INCREMENT BY 1 MINVALUE 1 START 1", array_shift($sql)); + $this->assertEquals("ALTER TABLE cms_addresses ADD CONSTRAINT FK_ACAC157BA76ED395 FOREIGN KEY (user_id) REFERENCES cms_users (id) NOT DEFERRABLE INITIALLY IMMEDIATE", array_shift($sql)); + $this->assertEquals("ALTER TABLE cms_users ADD CONSTRAINT FK_3AF03EC5A832C1C9 FOREIGN KEY (email_id) REFERENCES cms_emails (id) NOT DEFERRABLE INITIALLY IMMEDIATE", array_shift($sql)); + $this->assertEquals("ALTER TABLE cms_users_groups ADD CONSTRAINT FK_7EA9409AA76ED395 FOREIGN KEY (user_id) REFERENCES cms_users (id) NOT DEFERRABLE INITIALLY IMMEDIATE", array_shift($sql)); + $this->assertEquals("ALTER TABLE cms_users_groups ADD CONSTRAINT FK_7EA9409AFE54D947 FOREIGN KEY (group_id) REFERENCES cms_groups (id) NOT DEFERRABLE INITIALLY IMMEDIATE", array_shift($sql)); + $this->assertEquals("ALTER TABLE cms_phonenumbers ADD CONSTRAINT FK_F21F790FA76ED395 FOREIGN KEY (user_id) REFERENCES cms_users (id) NOT DEFERRABLE INITIALLY IMMEDIATE", array_shift($sql)); + + $this->assertEquals(array(), $sql, "SQL Array should be empty now."); + $this->assertEquals(17, $sqlCount, "Total of 17 queries should be executed"); } - + public function testGetCreateSchemaSql2() { $classes = array( @@ -62,11 +66,11 @@ class PostgreSqlSchemaToolTest extends \Doctrine\Tests\OrmFunctionalTestCase $sql = $tool->getCreateSchemaSql($classes); $this->assertEquals(2, count($sql)); - + $this->assertEquals('CREATE TABLE decimal_model (id INT NOT NULL, "decimal" NUMERIC(5, 2) NOT NULL, "high_scale" NUMERIC(14, 4) NOT NULL, PRIMARY KEY(id))', $sql[0]); $this->assertEquals("CREATE SEQUENCE decimal_model_id_seq INCREMENT BY 1 MINVALUE 1 START 1", $sql[1]); } - + public function testGetCreateSchemaSql3() { $classes = array( @@ -75,12 +79,12 @@ class PostgreSqlSchemaToolTest extends \Doctrine\Tests\OrmFunctionalTestCase $tool = new SchemaTool($this->_em); $sql = $tool->getCreateSchemaSql($classes); - + $this->assertEquals(2, count($sql)); $this->assertEquals("CREATE TABLE boolean_model (id INT NOT NULL, booleanField BOOLEAN NOT NULL, PRIMARY KEY(id))", $sql[0]); $this->assertEquals("CREATE SEQUENCE boolean_model_id_seq INCREMENT BY 1 MINVALUE 1 START 1", $sql[1]); } - + public function testGetDropSchemaSql() { $classes = array( @@ -91,8 +95,8 @@ class PostgreSqlSchemaToolTest extends \Doctrine\Tests\OrmFunctionalTestCase $tool = new SchemaTool($this->_em); $sql = $tool->getDropSchemaSQL($classes); - - $this->assertEquals(13, count($sql)); + + $this->assertEquals(14, count($sql)); $dropSequenceSQLs = 0; foreach ($sql AS $stmt) { if (strpos($stmt, "DROP SEQUENCE") === 0) { diff --git a/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC1151Test.php b/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC1151Test.php index 589edb048..51a2d4555 100644 --- a/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC1151Test.php +++ b/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC1151Test.php @@ -8,18 +8,18 @@ require_once __DIR__ . '/../../../TestInit.php'; * @group DDC-1151 */ class DDC1151Test extends \Doctrine\Tests\OrmFunctionalTestCase -{ +{ public function testQuoteForeignKey() { if ($this->_em->getConnection()->getDatabasePlatform()->getName() != 'postgresql') { $this->markTestSkipped("This test is useful for all databases, but designed only for postgresql."); } - + $sql = $this->_schemaTool->getCreateSchemaSql(array( $this->_em->getClassMetadata(__NAMESPACE__ . '\DDC1151User'), $this->_em->getClassMetadata(__NAMESPACE__ . '\DDC1151Group'), )); - + $this->assertEquals("CREATE TABLE \"User\" (id INT NOT NULL, PRIMARY KEY(id))", $sql[0]); $this->assertEquals("CREATE TABLE ddc1151user_ddc1151group (ddc1151user_id INT NOT NULL, ddc1151group_id INT NOT NULL, PRIMARY KEY(ddc1151user_id, ddc1151group_id))", $sql[1]); $this->assertEquals("CREATE INDEX IDX_88A3259AC5AD08A ON ddc1151user_ddc1151group (ddc1151user_id)", $sql[2]); @@ -27,8 +27,8 @@ class DDC1151Test extends \Doctrine\Tests\OrmFunctionalTestCase $this->assertEquals("CREATE TABLE \"Group\" (id INT NOT NULL, PRIMARY KEY(id))", $sql[4]); $this->assertEquals("CREATE SEQUENCE User_id_seq INCREMENT BY 1 MINVALUE 1 START 1", $sql[5]); $this->assertEquals("CREATE SEQUENCE Group_id_seq INCREMENT BY 1 MINVALUE 1 START 1", $sql[6]); - $this->assertEquals("ALTER TABLE ddc1151user_ddc1151group ADD FOREIGN KEY (ddc1151user_id) REFERENCES \"User\"(id) ON DELETE CASCADE NOT DEFERRABLE INITIALLY IMMEDIATE", $sql[7]); - $this->assertEquals("ALTER TABLE ddc1151user_ddc1151group ADD FOREIGN KEY (ddc1151group_id) REFERENCES \"Group\"(id) ON DELETE CASCADE NOT DEFERRABLE INITIALLY IMMEDIATE", $sql[8]); + $this->assertEquals("ALTER TABLE ddc1151user_ddc1151group ADD CONSTRAINT FK_88A3259AC5AD08A FOREIGN KEY (ddc1151user_id) REFERENCES \"User\" (id) ON DELETE CASCADE NOT DEFERRABLE INITIALLY IMMEDIATE", $sql[7]); + $this->assertEquals("ALTER TABLE ddc1151user_ddc1151group ADD CONSTRAINT FK_88A32597357E0B1 FOREIGN KEY (ddc1151group_id) REFERENCES \"Group\" (id) ON DELETE CASCADE NOT DEFERRABLE INITIALLY IMMEDIATE", $sql[8]); } } @@ -40,7 +40,7 @@ class DDC1151User { /** @Id @Column(type="integer") @GeneratedValue */ public $id; - + /** @ManyToMany(targetEntity="DDC1151Group") */ public $groups; } From 5aeabcb445acf0f3ae3809ca07094d44d86f59b6 Mon Sep 17 00:00:00 2001 From: Benjamin Eberlei Date: Sun, 13 Nov 2011 15:36:48 +0100 Subject: [PATCH 094/135] DDC-1490 - Fix id generation of sequence and identity to cast values to int --- lib/Doctrine/ORM/Id/IdentityGenerator.php | 2 +- lib/Doctrine/ORM/Id/SequenceGenerator.php | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/lib/Doctrine/ORM/Id/IdentityGenerator.php b/lib/Doctrine/ORM/Id/IdentityGenerator.php index 75da2733d..d244871f2 100644 --- a/lib/Doctrine/ORM/Id/IdentityGenerator.php +++ b/lib/Doctrine/ORM/Id/IdentityGenerator.php @@ -46,7 +46,7 @@ class IdentityGenerator extends AbstractIdGenerator */ public function generate(EntityManager $em, $entity) { - return $em->getConnection()->lastInsertId($this->_seqName); + return (int)$em->getConnection()->lastInsertId($this->_seqName); } /** diff --git a/lib/Doctrine/ORM/Id/SequenceGenerator.php b/lib/Doctrine/ORM/Id/SequenceGenerator.php index dd7de60ba..b02331e6b 100644 --- a/lib/Doctrine/ORM/Id/SequenceGenerator.php +++ b/lib/Doctrine/ORM/Id/SequenceGenerator.php @@ -46,7 +46,7 @@ class SequenceGenerator extends AbstractIdGenerator implements Serializable $this->_sequenceName = $sequenceName; $this->_allocationSize = $allocationSize; } - + /** * Generates an ID for the given entity. * @@ -60,11 +60,11 @@ class SequenceGenerator extends AbstractIdGenerator implements Serializable // Allocate new values $conn = $em->getConnection(); $sql = $conn->getDatabasePlatform()->getSequenceNextValSQL($this->_sequenceName); - - $this->_nextValue = $conn->fetchColumn($sql); + + $this->_nextValue = (int)$conn->fetchColumn($sql); $this->_maxValue = $this->_nextValue + $this->_allocationSize; } - + return $this->_nextValue++; } @@ -99,7 +99,7 @@ class SequenceGenerator extends AbstractIdGenerator implements Serializable public function unserialize($serialized) { $array = unserialize($serialized); - + $this->_sequenceName = $array['sequenceName']; $this->_allocationSize = $array['allocationSize']; } From f7c46c7b3330dc5e56c5241de67b2abe66501548 Mon Sep 17 00:00:00 2001 From: Benjamin Eberlei Date: Sun, 13 Nov 2011 15:45:06 +0100 Subject: [PATCH 095/135] DDC-1491 - Fix Schema Validator bug --- lib/Doctrine/ORM/Tools/SchemaValidator.php | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/lib/Doctrine/ORM/Tools/SchemaValidator.php b/lib/Doctrine/ORM/Tools/SchemaValidator.php index 2c0bd94ff..cb3c9e515 100644 --- a/lib/Doctrine/ORM/Tools/SchemaValidator.php +++ b/lib/Doctrine/ORM/Tools/SchemaValidator.php @@ -173,14 +173,14 @@ class SchemaValidator if (count($targetMetadata->getIdentifierColumnNames()) != count($assoc['joinTable']['inverseJoinColumns'])) { $ce[] = "The inverse join columns of the many-to-many table '" . $assoc['joinTable']['name'] . "' " . "have to contain to ALL identifier columns of the target entity '". $targetMetadata->name . "', " . - "however '" . implode(", ", array_diff($targetMetadata->getIdentifierColumnNames(), $assoc['relationToTargetKeyColumns'])) . + "however '" . implode(", ", array_diff($targetMetadata->getIdentifierColumnNames(), array_values($assoc['relationToTargetKeyColumns']))) . "' are missing."; } if (count($class->getIdentifierColumnNames()) != count($assoc['joinTable']['joinColumns'])) { $ce[] = "The join columns of the many-to-many table '" . $assoc['joinTable']['name'] . "' " . "have to contain to ALL identifier columns of the source entity '". $class->name . "', " . - "however '" . implode(", ", array_diff($class->getIdentifierColumnNames(), $assoc['relationToSourceKeyColumns'])) . + "however '" . implode(", ", array_diff($class->getIdentifierColumnNames(), array_values($assoc['relationToSourceKeyColumns']))) . "' are missing."; } @@ -200,9 +200,14 @@ class SchemaValidator } if (count($class->getIdentifierColumnNames()) != count($assoc['joinColumns'])) { + $ids = array(); + foreach ($assoc['joinColumns'] AS $joinColumn) { + $ids[] = $joinColumn['name']; + } + $ce[] = "The join columns of the association '" . $assoc['fieldName'] . "' " . "have to match to ALL identifier columns of the source entity '". $class->name . "', " . - "however '" . implode(", ", array_diff($class->getIdentifierColumnNames(), $assoc['joinColumns'])) . + "however '" . implode(", ", array_diff($class->getIdentifierColumnNames(), $ids)) . "' are missing."; } } From 4571e498b4da9aaa24c27e0a7f4288bb2e182cb6 Mon Sep 17 00:00:00 2001 From: Benjamin Eberlei Date: Sun, 13 Nov 2011 17:16:43 +0100 Subject: [PATCH 096/135] DDC-1477 - Adjust patch to really fix bug in Proxy generation --- lib/Doctrine/ORM/Proxy/ProxyFactory.php | 6 ++++-- .../Doctrine/Tests/ORM/Functional/MappedSuperclassTest.php | 1 + 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/lib/Doctrine/ORM/Proxy/ProxyFactory.php b/lib/Doctrine/ORM/Proxy/ProxyFactory.php index 0e37b3fac..cf7048549 100644 --- a/lib/Doctrine/ORM/Proxy/ProxyFactory.php +++ b/lib/Doctrine/ORM/Proxy/ProxyFactory.php @@ -165,11 +165,13 @@ class ProxyFactory { $methods = ''; + $methodNames = array(); foreach ($class->reflClass->getMethods() as $method) { /* @var $method ReflectionMethod */ - if ($method->isConstructor() || in_array(strtolower($method->getName()), array("__sleep", "__clone")) || $class->reflClass->getName() != $method->class) { + if ($method->isConstructor() || in_array(strtolower($method->getName()), array("__sleep", "__clone")) || isset($methodNames[$method->getName()])) { continue; } + $methodNames[$method->getName()] = true; if ($method->isPublic() && ! $method->isFinal() && ! $method->isStatic()) { $methods .= "\n" . ' public function '; @@ -234,7 +236,7 @@ class ProxyFactory */ private function isShortIdentifierGetter($method, $class) { - $identifier = lcfirst(substr($method->getName(), 3)); + $identifier = lcfirst(substr($method->getName(), 3)); return ( $method->getNumberOfParameters() == 0 && substr($method->getName(), 0, 3) == "get" && diff --git a/tests/Doctrine/Tests/ORM/Functional/MappedSuperclassTest.php b/tests/Doctrine/Tests/ORM/Functional/MappedSuperclassTest.php index bb324bf67..f4439b9ad 100644 --- a/tests/Doctrine/Tests/ORM/Functional/MappedSuperclassTest.php +++ b/tests/Doctrine/Tests/ORM/Functional/MappedSuperclassTest.php @@ -39,6 +39,7 @@ class MappedSuperclassTest extends \Doctrine\Tests\OrmFunctionalTestCase $cleanFile = $this->_em->find(get_class($file), $file->getId()); $this->assertInstanceOf('Doctrine\Tests\Models\DirectoryTree\Directory', $cleanFile->getParent()); + $this->assertInstanceOf('Doctrine\ORM\Proxy\Proxy', $cleanFile->getParent()); $this->assertEquals($directory->getId(), $cleanFile->getParent()->getId()); $this->assertInstanceOf('Doctrine\Tests\Models\DirectoryTree\Directory', $cleanFile->getParent()->getParent()); $this->assertEquals($root->getId(), $cleanFile->getParent()->getParent()->getId()); From c2bb281e8074248f387cf672cf9357a5e74b2c5f Mon Sep 17 00:00:00 2001 From: Benjamin Eberlei Date: Sun, 13 Nov 2011 17:44:37 +0100 Subject: [PATCH 097/135] Merge travis support from @pborreli - thanks! --- .travis.yml | 19 +++++++++++++++++ README.markdown | 7 +++++-- tests/travis/mysql.travis.xml | 35 ++++++++++++++++++++++++++++++++ tests/travis/postgres.travis.xml | 34 +++++++++++++++++++++++++++++++ tests/travis/sqlite.travis.xml | 15 ++++++++++++++ 5 files changed, 108 insertions(+), 2 deletions(-) create mode 100644 .travis.yml create mode 100644 tests/travis/mysql.travis.xml create mode 100644 tests/travis/postgres.travis.xml create mode 100644 tests/travis/sqlite.travis.xml diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 000000000..716b9b640 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,19 @@ +language: php + +php: + - 5.3 + - 5.4 +env: + - DB=mysql + - DB=pgsql + - DB=sqlite + +before_script: + - sh -c "if [ '$DB' = 'pgsql' ]; then psql -c 'DROP DATABASE IF EXISTS doctrine_tests;' -U postgres; fi" + - sh -c "if [ '$DB' = 'pgsql' ]; then psql -c 'DROP DATABASE IF EXISTS doctrine_tests_tmp;' -U postgres; fi" + - sh -c "if [ '$DB' = 'pgsql' ]; then psql -c 'create database doctrine_tests;' -U postgres; fi" + - sh -c "if [ '$DB' = 'pgsql' ]; then psql -c 'create database doctrine_tests_tmp;' -U postgres; fi" + - sh -c "if [ '$DB' = 'mysql' ]; then mysql -e 'create database IF NOT EXISTS doctrine_tests_tmp;create database IF NOT EXISTS doctrine_tests;'; fi" + - git submodule update --init + +script: phpunit --configuration tests/travis/$DB.travis.xml \ No newline at end of file diff --git a/README.markdown b/README.markdown index a0b5f2a20..dabcc015c 100644 --- a/README.markdown +++ b/README.markdown @@ -1,14 +1,17 @@ # Doctrine 2 ORM +[![Build Status](https://secure.travis-ci.org/doctrine/doctrine2.png)](http://travis-ci.org/doctrine/doctrine2) + Doctrine 2 is an object-relational mapper (ORM) for PHP 5.3.2+ that provides transparent persistence for PHP objects. It sits on top of a powerful database abstraction layer (DBAL). One of its key features is the option to write database queries in a proprietary object oriented SQL dialect called Doctrine Query Language (DQL), inspired by Hibernates HQL. This provides developers with a powerful alternative to SQL that maintains flexibility without requiring unnecessary code duplication. -More resources: +## More resources: * [Website](http://www.doctrine-project.org) * [Documentation](http://www.doctrine-project.org/projects/orm/2.0/docs/reference/introduction/en) * [Issue Tracker](http://www.doctrine-project.org/jira/browse/DDC) -* [Downloads](http://github.com/doctrine/doctrine2/downloads) \ No newline at end of file +* [Downloads](http://github.com/doctrine/doctrine2/downloads) + diff --git a/tests/travis/mysql.travis.xml b/tests/travis/mysql.travis.xml new file mode 100644 index 000000000..8812dcad7 --- /dev/null +++ b/tests/travis/mysql.travis.xml @@ -0,0 +1,35 @@ + + + + + + + + + + + + + + + + + + + + + + + + ./../Doctrine/Tests/ORM + + + + + performance + locking_functional + + + + + diff --git a/tests/travis/postgres.travis.xml b/tests/travis/postgres.travis.xml new file mode 100644 index 000000000..5f72721d9 --- /dev/null +++ b/tests/travis/postgres.travis.xml @@ -0,0 +1,34 @@ + + + + + + + + + + + + + + + + + + + + + + + ./../Doctrine/Tests/ORM + + + + + + performance + locking_functional + + + + \ No newline at end of file diff --git a/tests/travis/sqlite.travis.xml b/tests/travis/sqlite.travis.xml new file mode 100644 index 000000000..5d310c327 --- /dev/null +++ b/tests/travis/sqlite.travis.xml @@ -0,0 +1,15 @@ + + + + + ./../Doctrine/Tests/ORM + + + + + performance + locking_functional + + + + \ No newline at end of file From 0fdffb9dbc206945a0f298b1a6c8256d0624296d Mon Sep 17 00:00:00 2001 From: Benjamin Eberlei Date: Sun, 13 Nov 2011 17:57:54 +0100 Subject: [PATCH 098/135] Fix postgresql travis phpunit configuration file --- tests/travis/{postgres.travis.xml => pgsql.travis.xml} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename tests/travis/{postgres.travis.xml => pgsql.travis.xml} (100%) diff --git a/tests/travis/postgres.travis.xml b/tests/travis/pgsql.travis.xml similarity index 100% rename from tests/travis/postgres.travis.xml rename to tests/travis/pgsql.travis.xml From 077d4a0e1587b49e3aa60b98d51520798dff0c5c Mon Sep 17 00:00:00 2001 From: Benjamin Eberlei Date: Sun, 13 Nov 2011 22:04:48 +0100 Subject: [PATCH 099/135] Fix travis configuration files --- tests/travis/mysql.travis.xml | 47 +++++++++++++++----------------- tests/travis/pgsql.travis.xml | 50 +++++++++++++++++------------------ 2 files changed, 47 insertions(+), 50 deletions(-) diff --git a/tests/travis/mysql.travis.xml b/tests/travis/mysql.travis.xml index 8812dcad7..f17a4b87d 100644 --- a/tests/travis/mysql.travis.xml +++ b/tests/travis/mysql.travis.xml @@ -1,34 +1,31 @@ - - - - - - - - - + + + + + + + - - - - - - - - + + + + + + + - - - ./../Doctrine/Tests/ORM - + + + ./../Doctrine/Tests/ORM + - - performance - locking_functional - + + performance + locking_functional + diff --git a/tests/travis/pgsql.travis.xml b/tests/travis/pgsql.travis.xml index 5f72721d9..fa0581acb 100644 --- a/tests/travis/pgsql.travis.xml +++ b/tests/travis/pgsql.travis.xml @@ -1,34 +1,34 @@ - + - - - - - - + + + + + + - - - - - - - - - - ./../Doctrine/Tests/ORM - - + + + + + + + + + + ./../Doctrine/Tests/ORM + + - - - performance - locking_functional - - + + + performance + locking_functional + + \ No newline at end of file From c87d5af243d17b688f46e5f594ddf85d9cd279d6 Mon Sep 17 00:00:00 2001 From: Benjamin Eberlei Date: Sun, 13 Nov 2011 22:40:27 +0100 Subject: [PATCH 100/135] Specialize build status on versions --- README.markdown | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.markdown b/README.markdown index dabcc015c..00458ca04 100644 --- a/README.markdown +++ b/README.markdown @@ -1,6 +1,7 @@ # Doctrine 2 ORM -[![Build Status](https://secure.travis-ci.org/doctrine/doctrine2.png)](http://travis-ci.org/doctrine/doctrine2) +Master: [![Build Status](https://secure.travis-ci.org/doctrine/doctrine2.png?branch=master)](http://travis-ci.org/doctrine/doctrine2) +2.1.x: [![Build Status](https://secure.travis-ci.org/doctrine/doctrine2.png?branch=2.1.x)](http://travis-ci.org/doctrine/doctrine2) Doctrine 2 is an object-relational mapper (ORM) for PHP 5.3.2+ that provides transparent persistence for PHP objects. It sits on top of a powerful database abstraction layer (DBAL). One of its key features From c648981f28bd6279ba6dbcfe9ae3adf61dfc6f00 Mon Sep 17 00:00:00 2001 From: Benjamin Eberlei Date: Sun, 13 Nov 2011 23:14:31 +0100 Subject: [PATCH 101/135] DDC-1461 - Verified deferred explicit works --- .../Doctrine/Tests/Models/CMS/CmsAddress.php | 6 +- .../ORM/Functional/Ticket/DDC1461Test.php | 112 ++++++++++++++++++ 2 files changed, 115 insertions(+), 3 deletions(-) create mode 100644 tests/Doctrine/Tests/ORM/Functional/Ticket/DDC1461Test.php diff --git a/tests/Doctrine/Tests/Models/CMS/CmsAddress.php b/tests/Doctrine/Tests/Models/CMS/CmsAddress.php index 9db2b6475..9119a6f58 100644 --- a/tests/Doctrine/Tests/Models/CMS/CmsAddress.php +++ b/tests/Doctrine/Tests/Models/CMS/CmsAddress.php @@ -38,7 +38,7 @@ class CmsAddress public $street; /** - * @OneToOne(targetEntity="CmsUser", inversedBy="address") + * @OneToOne(targetEntity="CmsUser", inversedBy="address", cascade={"persist"}) * @JoinColumn(referencedColumnName="id") */ public $user; @@ -46,7 +46,7 @@ class CmsAddress public function getId() { return $this->id; } - + public function getUser() { return $this->user; } @@ -62,7 +62,7 @@ class CmsAddress public function getCity() { return $this->city; } - + public function setUser(CmsUser $user) { if ($this->user !== $user) { $this->user = $user; diff --git a/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC1461Test.php b/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC1461Test.php new file mode 100644 index 000000000..2f59ede23 --- /dev/null +++ b/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC1461Test.php @@ -0,0 +1,112 @@ +useModelSet('cms'); + parent::setUp(); + + try { + $this->_schemaTool->createSchema(array( + $this->_em->getClassMetadata(__NAMESPACE__ . '\DDC1461TwitterAccount'), + $this->_em->getClassMetadata(__NAMESPACE__ . '\DDC1461User') + )); + } catch(\Exception $e) { + + } + } + + public function testChangeDetectionDeferredImplitic() + { + $address = new \Doctrine\Tests\Models\CMS\CmsAddress(); + $address->city = "Karlsruhe"; + $address->country = "Germany"; + $address->street = "somestreet"; + $address->zip = 12345; + + $this->_em->persist($address); + $this->_em->flush(); + + $user = new CmsUser(); + $user->name = "schmittjoh"; + $user->username = "schmittjoh"; + $user->status = "active"; + + $address->setUser($user); + $this->_em->flush(); + $this->_em->clear(); + + $user = $this->_em->find(get_class($user), $user->getId()); + $this->assertNotNull($user->getAddress()); + $this->assertEquals("Karlsruhe", $user->getAddress()->getCity()); + } + + public function testChangeDetectionDeferredExplicit() + { + $user = new DDC1461User; + $this->_em->persist($user); + $this->_em->flush(); + + $this->assertEquals(\Doctrine\ORM\UnitOfWork::STATE_MANAGED, $this->_em->getUnitOfWork()->getEntityState($user, \Doctrine\ORM\UnitOfWork::STATE_NEW), "Entity should be managed."); + $this->assertEquals(\Doctrine\ORM\UnitOfWork::STATE_MANAGED, $this->_em->getUnitOfWork()->getEntityState($user), "Entity should be managed."); + + $acc = new DDC1461TwitterAccount; + $user->twitterAccount = $acc; + + $this->_em->persist($user); + $this->_em->flush(); + + $user = $this->_em->find(get_class($user), $user->id); + $this->assertNotNull($user->twitterAccount); + } +} + +/** + * @Entity + * @ChangeTrackingPolicy("DEFERRED_EXPLICIT") + */ +class DDC1461User +{ + /** + * @Id + * @GeneratedValue(strategy="AUTO") + * @Column(type="integer") + */ + public $id; + + /** + * @OneToOne(targetEntity="DDC1461TwitterAccount", orphanRemoval=true, fetch="EAGER", cascade = {"persist"}, inversedBy="user") + * @var TwitterAccount + */ + public $twitterAccount; +} + +/** + * @Entity + * @ChangeTrackingPolicy("DEFERRED_EXPLICIT") + */ +class DDC1461TwitterAccount +{ + /** + * @Id + * @GeneratedValue(strategy="AUTO") + * @Column(type="integer") + */ + public $id; + + /** + * @OneToOne(targetEntity="DDC1461User", fetch="EAGER") + */ + public $user; +} \ No newline at end of file From 81cc6d9da83d217ea62bd467053fd9885d1083cd Mon Sep 17 00:00:00 2001 From: Guilherme Blanco Date: Mon, 14 Nov 2011 01:36:39 -0200 Subject: [PATCH 102/135] Implemented alias support for EntityResult. This addresses DDC-1096 and DDC-1424. Improved DQL Parser, SQL Walker and Hydrators in general. Performance is generally improved by a factor of 20%. There is still more to be done, like remove the isMixed in ResultSetMapping, mainly because this query - SELECT u AS user FROM User u -, it should return an array('user' => [User object]), while currently it doesn't due to this before mentioned 'bug' in RSM. Will open a separate ticket for this. Also, UnitOfWork and Hydrators share code that could be abstracted/improved. --- .gitignore | 3 + .../Internal/Hydration/AbstractHydrator.php | 165 ++-- .../ORM/Internal/Hydration/ArrayHydrator.php | 74 +- .../ORM/Internal/Hydration/ObjectHydrator.php | 122 ++- lib/Doctrine/ORM/Query/Parser.php | 811 ++++++++++-------- lib/Doctrine/ORM/Query/ResultSetMapping.php | 115 +-- lib/Doctrine/ORM/Query/SqlWalker.php | 130 +-- .../Tests/ORM/Hydration/ArrayHydratorTest.php | 459 +++++----- .../ORM/Hydration/ObjectHydratorTest.php | 706 ++++++++------- 9 files changed, 1436 insertions(+), 1149 deletions(-) diff --git a/.gitignore b/.gitignore index 04f63f22d..329249d72 100644 --- a/.gitignore +++ b/.gitignore @@ -7,3 +7,6 @@ download/ lib/api/ lib/Doctrine/Common lib/Doctrine/DBAL +/.settings/ +.buildpath +.project diff --git a/lib/Doctrine/ORM/Internal/Hydration/AbstractHydrator.php b/lib/Doctrine/ORM/Internal/Hydration/AbstractHydrator.php index 493b1461c..146dfb5c5 100644 --- a/lib/Doctrine/ORM/Internal/Hydration/AbstractHydrator.php +++ b/lib/Doctrine/ORM/Internal/Hydration/AbstractHydrator.php @@ -74,7 +74,7 @@ abstract class AbstractHydrator * * @param object $stmt * @param object $resultSetMapping - * + * * @return IterableResult */ public function iterate($stmt, $resultSetMapping, array $hints = array()) @@ -82,9 +82,9 @@ abstract class AbstractHydrator $this->_stmt = $stmt; $this->_rsm = $resultSetMapping; $this->_hints = $hints; - + $this->prepare(); - + return new IterableResult($this); } @@ -100,13 +100,13 @@ abstract class AbstractHydrator $this->_stmt = $stmt; $this->_rsm = $resultSetMapping; $this->_hints = $hints; - + $this->prepare(); - + $result = $this->hydrateAllData(); - + $this->cleanup(); - + return $result; } @@ -119,17 +119,17 @@ abstract class AbstractHydrator public function hydrateRow() { $row = $this->_stmt->fetch(PDO::FETCH_ASSOC); - + if ( ! $row) { $this->cleanup(); - + return false; } - + $result = array(); - + $this->hydrateRowData($row, $this->_cache, $result); - + return $result; } @@ -147,7 +147,7 @@ abstract class AbstractHydrator protected function cleanup() { $this->_rsm = null; - + $this->_stmt->closeCursor(); $this->_stmt = null; } @@ -195,34 +195,44 @@ abstract class AbstractHydrator foreach ($data as $key => $value) { // Parse each column name only once. Cache the results. if ( ! isset($cache[$key])) { - if (isset($this->_rsm->scalarMappings[$key])) { - $cache[$key]['fieldName'] = $this->_rsm->scalarMappings[$key]; - $cache[$key]['isScalar'] = true; - } else if (isset($this->_rsm->fieldMappings[$key])) { - $fieldName = $this->_rsm->fieldMappings[$key]; - $classMetadata = $this->_em->getClassMetadata($this->_rsm->declaringClasses[$key]); - $cache[$key]['fieldName'] = $fieldName; - $cache[$key]['type'] = Type::getType($classMetadata->fieldMappings[$fieldName]['type']); - $cache[$key]['isIdentifier'] = $classMetadata->isIdentifier($fieldName); - $cache[$key]['dqlAlias'] = $this->_rsm->columnOwnerMap[$key]; - } else if (!isset($this->_rsm->metaMappings[$key])) { - // this column is a left over, maybe from a LIMIT query hack for example in Oracle or DB2 - // maybe from an additional column that has not been defined in a NativeQuery ResultSetMapping. - continue; - } else { - // Meta column (has meaning in relational schema only, i.e. foreign keys or discriminator columns). - $fieldName = $this->_rsm->metaMappings[$key]; - $cache[$key]['isMetaColumn'] = true; - $cache[$key]['fieldName'] = $fieldName; - $cache[$key]['dqlAlias'] = $this->_rsm->columnOwnerMap[$key]; - $classMetadata = $this->_em->getClassMetadata($this->_rsm->aliasMap[$cache[$key]['dqlAlias']]); - $cache[$key]['isIdentifier'] = isset($this->_rsm->isIdentifierColumn[$cache[$key]['dqlAlias']][$key]); + switch (true) { + // NOTE: Most of the times it's a field mapping, so keep it first!!! + case (isset($this->_rsm->fieldMappings[$key])): + $fieldName = $this->_rsm->fieldMappings[$key]; + $classMetadata = $this->_em->getClassMetadata($this->_rsm->declaringClasses[$key]); + + $cache[$key]['fieldName'] = $fieldName; + $cache[$key]['type'] = Type::getType($classMetadata->fieldMappings[$fieldName]['type']); + $cache[$key]['isIdentifier'] = $classMetadata->isIdentifier($fieldName); + $cache[$key]['dqlAlias'] = $this->_rsm->columnOwnerMap[$key]; + break; + + case (isset($this->_rsm->scalarMappings[$key])): + $cache[$key]['fieldName'] = $this->_rsm->scalarMappings[$key]; + $cache[$key]['isScalar'] = true; + break; + + case (isset($this->_rsm->metaMappings[$key])): + // Meta column (has meaning in relational schema only, i.e. foreign keys or discriminator columns). + $fieldName = $this->_rsm->metaMappings[$key]; + $classMetadata = $this->_em->getClassMetadata($this->_rsm->aliasMap[$this->_rsm->columnOwnerMap[$key]]); + + $cache[$key]['isMetaColumn'] = true; + $cache[$key]['fieldName'] = $fieldName; + $cache[$key]['dqlAlias'] = $this->_rsm->columnOwnerMap[$key]; + $cache[$key]['isIdentifier'] = isset($this->_rsm->isIdentifierColumn[$cache[$key]['dqlAlias']][$key]); + break; + + default: + // this column is a left over, maybe from a LIMIT query hack for example in Oracle or DB2 + // maybe from an additional column that has not been defined in a NativeQuery ResultSetMapping. + continue 2; } } if (isset($cache[$key]['isScalar'])) { $rowData['scalars'][$cache[$key]['fieldName']] = $value; - + continue; } @@ -233,10 +243,10 @@ abstract class AbstractHydrator } if (isset($cache[$key]['isMetaColumn'])) { - if (!isset($rowData[$dqlAlias][$cache[$key]['fieldName']]) || $value !== null) { + if ( ! isset($rowData[$dqlAlias][$cache[$key]['fieldName']]) || $value !== null) { $rowData[$dqlAlias][$cache[$key]['fieldName']] = $value; } - + continue; } @@ -259,7 +269,7 @@ abstract class AbstractHydrator /** * Processes a row of the result set. - * + * * Used for HYDRATE_SCALAR. This is a variant of _gatherRowData() that * simply converts column names to field names and properly converts the * values according to their types. The resulting row has the same number @@ -267,7 +277,7 @@ abstract class AbstractHydrator * * @param array $data * @param array $cache - * + * * @return array The processed row. */ protected function gatherScalarRowData(&$data, &$cache) @@ -277,48 +287,65 @@ abstract class AbstractHydrator foreach ($data as $key => $value) { // Parse each column name only once. Cache the results. if ( ! isset($cache[$key])) { - if (isset($this->_rsm->scalarMappings[$key])) { - $cache[$key]['fieldName'] = $this->_rsm->scalarMappings[$key]; - $cache[$key]['isScalar'] = true; - } else if (isset($this->_rsm->fieldMappings[$key])) { - $fieldName = $this->_rsm->fieldMappings[$key]; - $classMetadata = $this->_em->getClassMetadata($this->_rsm->declaringClasses[$key]); - $cache[$key]['fieldName'] = $fieldName; - $cache[$key]['type'] = Type::getType($classMetadata->fieldMappings[$fieldName]['type']); - $cache[$key]['dqlAlias'] = $this->_rsm->columnOwnerMap[$key]; - } else if (!isset($this->_rsm->metaMappings[$key])) { - // this column is a left over, maybe from a LIMIT query hack for example in Oracle or DB2 - // maybe from an additional column that has not been defined in a NativeQuery ResultSetMapping. - continue; - } else { - // Meta column (has meaning in relational schema only, i.e. foreign keys or discriminator columns). - $cache[$key]['isMetaColumn'] = true; - $cache[$key]['fieldName'] = $this->_rsm->metaMappings[$key]; - $cache[$key]['dqlAlias'] = $this->_rsm->columnOwnerMap[$key]; + switch (true) { + // NOTE: During scalar hydration, most of the times it's a scalar mapping, keep it first!!! + case (isset($this->_rsm->scalarMappings[$key])): + $cache[$key]['fieldName'] = $this->_rsm->scalarMappings[$key]; + $cache[$key]['isScalar'] = true; + break; + + case (isset($this->_rsm->fieldMappings[$key])): + $fieldName = $this->_rsm->fieldMappings[$key]; + $classMetadata = $this->_em->getClassMetadata($this->_rsm->declaringClasses[$key]); + + $cache[$key]['fieldName'] = $fieldName; + $cache[$key]['type'] = Type::getType($classMetadata->fieldMappings[$fieldName]['type']); + $cache[$key]['dqlAlias'] = $this->_rsm->columnOwnerMap[$key]; + break; + + case (isset($this->_rsm->metaMappings[$key])): + // Meta column (has meaning in relational schema only, i.e. foreign keys or discriminator columns). + $cache[$key]['isMetaColumn'] = true; + $cache[$key]['fieldName'] = $this->_rsm->metaMappings[$key]; + $cache[$key]['dqlAlias'] = $this->_rsm->columnOwnerMap[$key]; + break; + + default: + // this column is a left over, maybe from a LIMIT query hack for example in Oracle or DB2 + // maybe from an additional column that has not been defined in a NativeQuery ResultSetMapping. + continue 2; } } $fieldName = $cache[$key]['fieldName']; - if (isset($cache[$key]['isScalar'])) { - $rowData[$fieldName] = $value; - } else if (isset($cache[$key]['isMetaColumn'])) { - $rowData[$cache[$key]['dqlAlias'] . '_' . $fieldName] = $value; - } else { - $rowData[$cache[$key]['dqlAlias'] . '_' . $fieldName] = $cache[$key]['type'] - ->convertToPHPValue($value, $this->_platform); + switch (true) { + case (isset($cache[$key]['isScalar'])): + $rowData[$fieldName] = $value; + break; + + case (isset($cache[$key]['isMetaColumn'])): + $rowData[$cache[$key]['dqlAlias'] . '_' . $fieldName] = $value; + break; + + default: + $value = $cache[$key]['type']->convertToPHPValue($value, $this->_platform); + + $rowData[$cache[$key]['dqlAlias'] . '_' . $fieldName] = $value; } } return $rowData; } - + /** * Register entity as managed in UnitOfWork. - * + * * @param Doctrine\ORM\Mapping\ClassMetadata $class * @param object $entity - * @param array $data + * @param array $data + * + * @todo The "$id" generation is the same of UnitOfWork#createEntity. Remove this duplication somehow */ protected function registerManaged(ClassMetadata $class, $entity, array $data) { @@ -338,7 +365,7 @@ abstract class AbstractHydrator $id = array($class->identifier[0] => $data[$class->identifier[0]]); } } - + $this->_em->getUnitOfWork()->registerManaged($entity, $id, $data); } } diff --git a/lib/Doctrine/ORM/Internal/Hydration/ArrayHydrator.php b/lib/Doctrine/ORM/Internal/Hydration/ArrayHydrator.php index e25df2fea..817e30baf 100644 --- a/lib/Doctrine/ORM/Internal/Hydration/ArrayHydrator.php +++ b/lib/Doctrine/ORM/Internal/Hydration/ArrayHydrator.php @@ -28,6 +28,11 @@ use PDO, Doctrine\DBAL\Connection, Doctrine\ORM\Mapping\ClassMetadata; * @since 2.0 * @author Roman Borschel * @author Guilherme Blanco + * + * @todo General behavior is "wrong" if you define an alias to selected IdentificationVariable. + * Example: SELECT u AS user FROM User u + * The result should contains an array where each array index is an array: array('user' => [User object]) + * Problem must be solved somehow by removing the isMixed in ResultSetMapping */ class ArrayHydrator extends AbstractHydrator { @@ -39,8 +44,8 @@ class ArrayHydrator extends AbstractHydrator private $_idTemplate = array(); private $_resultCounter = 0; - /** - * {@inheritdoc} + /** + * {@inheritdoc} */ protected function prepare() { @@ -49,7 +54,7 @@ class ArrayHydrator extends AbstractHydrator $this->_resultPointers = array(); $this->_idTemplate = array(); $this->_resultCounter = 0; - + foreach ($this->_rsm->aliasMap as $dqlAlias => $className) { $this->_identifierMap[$dqlAlias] = array(); $this->_resultPointers[$dqlAlias] = array(); @@ -57,14 +62,14 @@ class ArrayHydrator extends AbstractHydrator } } - /** - * {@inheritdoc} + /** + * {@inheritdoc} */ protected function hydrateAllData() { $result = array(); $cache = array(); - + while ($data = $this->_stmt->fetch(PDO::FETCH_ASSOC)) { $this->hydrateRowData($data, $cache, $result); } @@ -85,8 +90,9 @@ class ArrayHydrator extends AbstractHydrator // Extract scalar values. They're appended at the end. if (isset($rowData['scalars'])) { $scalars = $rowData['scalars']; + unset($rowData['scalars']); - + if (empty($rowData)) { ++$this->_resultCounter; } @@ -100,7 +106,7 @@ class ArrayHydrator extends AbstractHydrator // It's a joined result $parent = $this->_rsm->parentAliasMap[$dqlAlias]; - $path = $parent . '.' . $dqlAlias; + $path = $parent . '.' . $dqlAlias; // missing parent data, skipping as RIGHT JOIN hydration is not supported. if ( ! isset($nonemptyComponents[$parent]) ) { @@ -119,22 +125,23 @@ class ArrayHydrator extends AbstractHydrator unset($this->_resultPointers[$dqlAlias]); // Ticket #1228 continue; } - + $relationAlias = $this->_rsm->relationMap[$dqlAlias]; $relation = $this->getClassMetadata($this->_rsm->aliasMap[$parent])->associationMappings[$relationAlias]; // Check the type of the relation (many or single-valued) if ( ! ($relation['type'] & ClassMetadata::TO_ONE)) { $oneToOne = false; + if (isset($nonemptyComponents[$dqlAlias])) { if ( ! isset($baseElement[$relationAlias])) { $baseElement[$relationAlias] = array(); } - - $indexExists = isset($this->_identifierMap[$path][$id[$parent]][$id[$dqlAlias]]); - $index = $indexExists ? $this->_identifierMap[$path][$id[$parent]][$id[$dqlAlias]] : false; + + $indexExists = isset($this->_identifierMap[$path][$id[$parent]][$id[$dqlAlias]]); + $index = $indexExists ? $this->_identifierMap[$path][$id[$parent]][$id[$dqlAlias]] : false; $indexIsValid = $index !== false ? isset($baseElement[$relationAlias][$index]) : false; - + if ( ! $indexExists || ! $indexIsValid) { $element = $data; if (isset($this->_rsm->indexByMap[$dqlAlias])) { @@ -142,15 +149,17 @@ class ArrayHydrator extends AbstractHydrator } else { $baseElement[$relationAlias][] = $element; } + end($baseElement[$relationAlias]); - $this->_identifierMap[$path][$id[$parent]][$id[$dqlAlias]] = - key($baseElement[$relationAlias]); + + $this->_identifierMap[$path][$id[$parent]][$id[$dqlAlias]] = key($baseElement[$relationAlias]); } } else if ( ! isset($baseElement[$relationAlias])) { $baseElement[$relationAlias] = array(); } } else { $oneToOne = true; + if ( ! isset($nonemptyComponents[$dqlAlias]) && ! isset($baseElement[$relationAlias])) { $baseElement[$relationAlias] = null; } else if ( ! isset($baseElement[$relationAlias])) { @@ -166,13 +175,14 @@ class ArrayHydrator extends AbstractHydrator } else { // It's a root result element - + $this->_rootAliases[$dqlAlias] = true; // Mark as root + $entityKey = $this->_rsm->entityMappings[$dqlAlias] ?: 0; // if this row has a NULL value for the root result id then make it a null result. if ( ! isset($nonemptyComponents[$dqlAlias]) ) { if ($this->_rsm->isMixed) { - $result[] = array(0 => null); + $result[] = array($entityKey => null); } else { $result[] = null; } @@ -180,12 +190,12 @@ class ArrayHydrator extends AbstractHydrator ++$this->_resultCounter; continue; } - + // Check for an existing element if ($this->_isSimpleQuery || ! isset($this->_identifierMap[$dqlAlias][$id[$dqlAlias]])) { $element = $rowData[$dqlAlias]; if ($this->_rsm->isMixed) { - $element = array(0 => $element); + $element = array($entityKey => $element); } if (isset($this->_rsm->indexByMap[$dqlAlias])) { @@ -240,37 +250,37 @@ class ArrayHydrator extends AbstractHydrator { if ($coll === null) { unset($this->_resultPointers[$dqlAlias]); // Ticket #1228 - + return; } - + if ($index !== false) { $this->_resultPointers[$dqlAlias] =& $coll[$index]; - + return; - } - + } + if ( ! $coll) { return; } - + if ($oneToOne) { $this->_resultPointers[$dqlAlias] =& $coll; - + return; } - + end($coll); $this->_resultPointers[$dqlAlias] =& $coll[key($coll)]; - + return; } - + /** * Retrieve ClassMetadata associated to entity class name. - * + * * @param string $className - * + * * @return Doctrine\ORM\Mapping\ClassMetadata */ private function getClassMetadata($className) @@ -278,7 +288,7 @@ class ArrayHydrator extends AbstractHydrator if ( ! isset($this->_ce[$className])) { $this->_ce[$className] = $this->_em->getClassMetadata($className); } - + return $this->_ce[$className]; } } \ No newline at end of file diff --git a/lib/Doctrine/ORM/Internal/Hydration/ObjectHydrator.php b/lib/Doctrine/ORM/Internal/Hydration/ObjectHydrator.php index d7d0281c4..c56b6eb22 100644 --- a/lib/Doctrine/ORM/Internal/Hydration/ObjectHydrator.php +++ b/lib/Doctrine/ORM/Internal/Hydration/ObjectHydrator.php @@ -32,8 +32,13 @@ use PDO, * @since 2.0 * @author Roman Borschel * @author Guilherme Blanco - * + * * @internal Highly performance-sensitive code. + * + * @todo General behavior is "wrong" if you define an alias to selected IdentificationVariable. + * Example: SELECT u AS user FROM User u + * The result should contains an array where each array index is an array: array('user' => [User object]) + * Problem must be solved somehow by removing the isMixed in ResultSetMapping */ class ObjectHydrator extends AbstractHydrator { @@ -60,52 +65,67 @@ class ObjectHydrator extends AbstractHydrator $this->_identifierMap = $this->_resultPointers = $this->_idTemplate = array(); + $this->_resultCounter = 0; - if (!isset($this->_hints['deferEagerLoad'])) { + + if ( ! isset($this->_hints['deferEagerLoad'])) { $this->_hints['deferEagerLoad'] = true; } foreach ($this->_rsm->aliasMap as $dqlAlias => $className) { $this->_identifierMap[$dqlAlias] = array(); - $this->_idTemplate[$dqlAlias] = ''; - $class = $this->_em->getClassMetadata($className); + $this->_idTemplate[$dqlAlias] = ''; if ( ! isset($this->_ce[$className])) { - $this->_ce[$className] = $class; + $this->_ce[$className] = $this->_em->getClassMetadata($className); } // Remember which associations are "fetch joined", so that we know where to inject // collection stubs or proxies and where not. - if (isset($this->_rsm->relationMap[$dqlAlias])) { - if ( ! isset($this->_rsm->aliasMap[$this->_rsm->parentAliasMap[$dqlAlias]])) { - throw HydrationException::parentObjectOfRelationNotFound($dqlAlias, $this->_rsm->parentAliasMap[$dqlAlias]); + if ( ! isset($this->_rsm->relationMap[$dqlAlias])) { + continue; + } + + if ( ! isset($this->_rsm->aliasMap[$this->_rsm->parentAliasMap[$dqlAlias]])) { + throw HydrationException::parentObjectOfRelationNotFound($dqlAlias, $this->_rsm->parentAliasMap[$dqlAlias]); + } + + $sourceClassName = $this->_rsm->aliasMap[$this->_rsm->parentAliasMap[$dqlAlias]]; + $sourceClass = $this->_getClassMetadata($sourceClassName); + $assoc = $sourceClass->associationMappings[$this->_rsm->relationMap[$dqlAlias]]; + + $this->_hints['fetched'][$sourceClassName][$assoc['fieldName']] = true; + + if ($sourceClass->subClasses) { + foreach ($sourceClass->subClasses as $sourceSubclassName) { + $this->_hints['fetched'][$sourceSubclassName][$assoc['fieldName']] = true; + } + } + + if ($assoc['type'] === ClassMetadata::MANY_TO_MANY) { + continue; + } + + // Mark any non-collection opposite sides as fetched, too. + if ($assoc['mappedBy']) { + $this->_hints['fetched'][$className][$assoc['mappedBy']] = true; + + continue; + } + + if ($assoc['inversedBy']) { + $class = $this->_ce[$className]; + $inverseAssoc = $class->associationMappings[$assoc['inversedBy']]; + + if ( ! ($inverseAssoc['type'] & ClassMetadata::TO_ONE)) { + continue; } - $sourceClassName = $this->_rsm->aliasMap[$this->_rsm->parentAliasMap[$dqlAlias]]; - $sourceClass = $this->_getClassMetadata($sourceClassName); - $assoc = $sourceClass->associationMappings[$this->_rsm->relationMap[$dqlAlias]]; - $this->_hints['fetched'][$sourceClassName][$assoc['fieldName']] = true; - if ($sourceClass->subClasses) { - foreach ($sourceClass->subClasses as $sourceSubclassName) { - $this->_hints['fetched'][$sourceSubclassName][$assoc['fieldName']] = true; - } - } - if ($assoc['type'] != ClassMetadata::MANY_TO_MANY) { - // Mark any non-collection opposite sides as fetched, too. - if ($assoc['mappedBy']) { - $this->_hints['fetched'][$className][$assoc['mappedBy']] = true; - } else { - if ($assoc['inversedBy']) { - $inverseAssoc = $class->associationMappings[$assoc['inversedBy']]; - if ($inverseAssoc['type'] & ClassMetadata::TO_ONE) { - $this->_hints['fetched'][$className][$inverseAssoc['fieldName']] = true; - if ($class->subClasses) { - foreach ($class->subClasses as $targetSubclassName) { - $this->_hints['fetched'][$targetSubclassName][$inverseAssoc['fieldName']] = true; - } - } - } - } + $this->_hints['fetched'][$className][$inverseAssoc['fieldName']] = true; + + if ($class->subClasses) { + foreach ($class->subClasses as $targetSubclassName) { + $this->_hints['fetched'][$targetSubclassName][$inverseAssoc['fieldName']] = true; } } } @@ -120,7 +140,7 @@ class ObjectHydrator extends AbstractHydrator $eagerLoad = (isset($this->_hints['deferEagerLoad'])) && $this->_hints['deferEagerLoad'] == true; parent::cleanup(); - + $this->_identifierMap = $this->_initializedCollections = $this->_existingCollections = @@ -137,7 +157,7 @@ class ObjectHydrator extends AbstractHydrator protected function hydrateAllData() { $result = array(); - $cache = array(); + $cache = array(); while ($row = $this->_stmt->fetch(PDO::FETCH_ASSOC)) { $this->hydrateRowData($row, $cache, $result); @@ -159,31 +179,34 @@ class ObjectHydrator extends AbstractHydrator */ private function _initRelatedCollection($entity, $class, $fieldName) { - $oid = spl_object_hash($entity); + $oid = spl_object_hash($entity); $relation = $class->associationMappings[$fieldName]; + $value = $class->reflFields[$fieldName]->getValue($entity); - $value = $class->reflFields[$fieldName]->getValue($entity); if ($value === null) { $value = new ArrayCollection; } if ( ! $value instanceof PersistentCollection) { $value = new PersistentCollection( - $this->_em, - $this->_ce[$relation['targetEntity']], - $value + $this->_em, $this->_ce[$relation['targetEntity']], $value ); $value->setOwner($entity, $relation); + $class->reflFields[$fieldName]->setValue($entity, $value); $this->_uow->setOriginalEntityProperty($oid, $fieldName, $value); + $this->_initializedCollections[$oid . $fieldName] = $value; - } else if (isset($this->_hints[Query::HINT_REFRESH]) || - isset($this->_hints['fetched'][$class->name][$fieldName]) && - ! $value->isInitialized()) { + } else if ( + isset($this->_hints[Query::HINT_REFRESH]) || + isset($this->_hints['fetched'][$class->name][$fieldName]) && + ! $value->isInitialized() + ) { // Is already PersistentCollection, but either REFRESH or FETCH-JOIN and UNINITIALIZED! $value->setDirty(false); $value->setInitialized(true); $value->unwrap()->clear(); + $this->_initializedCollections[$oid . $fieldName] = $value; } else { // Is already PersistentCollection, and DON'T REFRESH or FETCH-JOIN! @@ -203,6 +226,7 @@ class ObjectHydrator extends AbstractHydrator private function _getEntity(array $data, $dqlAlias) { $className = $this->_rsm->aliasMap[$dqlAlias]; + if (isset($this->_rsm->discriminatorColumns[$dqlAlias])) { $discrColumn = $this->_rsm->metaMappings[$this->_rsm->discriminatorColumns[$dqlAlias]]; @@ -211,12 +235,12 @@ class ObjectHydrator extends AbstractHydrator } $className = $this->_ce[$className]->discriminatorMap[$data[$discrColumn]]; + unset($data[$discrColumn]); } if (isset($this->_hints[Query::HINT_REFRESH_ENTITY]) && isset($this->_rootAliases[$dqlAlias])) { - $class = $this->_ce[$className]; - $this->registerManaged($class, $this->_hints[Query::HINT_REFRESH_ENTITY], $data); + $this->registerManaged($this->_ce[$className], $this->_hints[Query::HINT_REFRESH_ENTITY], $data); } return $this->_uow->createEntity($className, $data, $this->_hints); @@ -226,6 +250,7 @@ class ObjectHydrator extends AbstractHydrator { // TODO: Abstract this code and UnitOfWork::createEntity() equivalent? $class = $this->_ce[$className]; + /* @var $class ClassMetadata */ if ($class->isIdentifierComposite) { $idHash = ''; @@ -257,6 +282,7 @@ class ObjectHydrator extends AbstractHydrator if ( ! isset($this->_ce[$className])) { $this->_ce[$className] = $this->_em->getClassMetadata($className); } + return $this->_ce[$className]; } @@ -387,6 +413,7 @@ class ObjectHydrator extends AbstractHydrator $reflField->setValue($parentObject, $element); $this->_uow->setOriginalEntityProperty($oid, $relationField, $element); $targetClass = $this->_ce[$relation['targetEntity']]; + if ($relation['isOwningSide']) { //TODO: Just check hints['fetched'] here? // If there is an inverse mapping on the target class its bidirectional @@ -417,11 +444,12 @@ class ObjectHydrator extends AbstractHydrator } else { // PATH C: Its a root result element $this->_rootAliases[$dqlAlias] = true; // Mark as root alias + $entityKey = $this->_rsm->entityMappings[$dqlAlias] ?: 0; // if this row has a NULL value for the root result id then make it a null result. if ( ! isset($nonemptyComponents[$dqlAlias]) ) { if ($this->_rsm->isMixed) { - $result[] = array(0 => null); + $result[] = array($entityKey => null); } else { $result[] = null; } @@ -434,7 +462,7 @@ class ObjectHydrator extends AbstractHydrator if ( ! isset($this->_identifierMap[$dqlAlias][$id[$dqlAlias]])) { $element = $this->_getEntity($rowData[$dqlAlias], $dqlAlias); if ($this->_rsm->isMixed) { - $element = array(0 => $element); + $element = array($entityKey => $element); } if (isset($this->_rsm->indexByMap[$dqlAlias])) { diff --git a/lib/Doctrine/ORM/Query/Parser.php b/lib/Doctrine/ORM/Query/Parser.php index 9fc30bb8c..2af4bb452 100644 --- a/lib/Doctrine/ORM/Query/Parser.php +++ b/lib/Doctrine/ORM/Query/Parser.php @@ -141,9 +141,9 @@ class Parser */ public function __construct(Query $query) { - $this->_query = $query; - $this->_em = $query->getEntityManager(); - $this->_lexer = new Lexer($query->getDql()); + $this->_query = $query; + $this->_em = $query->getEntityManager(); + $this->_lexer = new Lexer($query->getDql()); $this->_parserResult = new ParserResult(); } @@ -226,6 +226,11 @@ class Parser $this->_processDeferredResultVariables(); } + $this->_processRootEntityAliasSelected(); + + // TODO: Is there a way to remove this? It may impact the mixed hydration resultset a lot! + $this->fixIdentificationVariableOrder($AST); + return $AST; } @@ -241,11 +246,10 @@ class Parser */ public function match($token) { + $lookaheadType = $this->_lexer->lookahead['type']; + // short-circuit on first condition, usually types match - if ($this->_lexer->lookahead['type'] !== $token && - $token !== Lexer::T_IDENTIFIER && - $this->_lexer->lookahead['type'] <= Lexer::T_IDENTIFIER - ) { + if ($lookaheadType !== $token && $token !== Lexer::T_IDENTIFIER && $lookaheadType <= Lexer::T_IDENTIFIER) { $this->syntaxError($this->_lexer->getLiteral($token)); } @@ -281,9 +285,6 @@ class Parser { $AST = $this->getAST(); - $this->fixIdentificationVariableOrder($AST); - $this->assertSelectEntityRootAliasRequirement(); - if (($customWalkers = $this->_query->getHint(Query::HINT_CUSTOM_TREE_WALKERS)) !== false) { $this->_customTreeWalkers = $customWalkers; } @@ -300,68 +301,57 @@ class Parser $treeWalkerChain->addTreeWalker($walker); } - if ($AST instanceof AST\SelectStatement) { - $treeWalkerChain->walkSelectStatement($AST); - } else if ($AST instanceof AST\UpdateStatement) { - $treeWalkerChain->walkUpdateStatement($AST); - } else { - $treeWalkerChain->walkDeleteStatement($AST); + switch (true) { + case ($AST instanceof AST\UpdateStatement): + $treeWalkerChain->walkUpdateStatement($AST); + break; + + case ($AST instanceof AST\DeleteStatement): + $treeWalkerChain->walkDeleteStatement($AST); + break; + + case ($AST instanceof AST\SelectStatement): + default: + $treeWalkerChain->walkSelectStatement($AST); } } - if ($this->_customOutputWalker) { - $outputWalker = new $this->_customOutputWalker( - $this->_query, $this->_parserResult, $this->_queryComponents - ); - } else { - $outputWalker = new SqlWalker( - $this->_query, $this->_parserResult, $this->_queryComponents - ); - } + $outputWalkerClass = $this->_customOutputWalker ?: __NAMESPACE__ . '\SqlWalker'; + $outputWalker = new $outputWalkerClass($this->_query, $this->_parserResult, $this->_queryComponents); // Assign an SQL executor to the parser result $this->_parserResult->setSqlExecutor($outputWalker->getExecutor($AST)); return $this->_parserResult; } - - private function assertSelectEntityRootAliasRequirement() - { - if ( count($this->_identVariableExpressions) > 0) { - $foundRootEntity = false; - foreach ($this->_identVariableExpressions AS $dqlAlias => $expr) { - if (isset($this->_queryComponents[$dqlAlias]) && $this->_queryComponents[$dqlAlias]['parent'] === null) { - $foundRootEntity = true; - } - } - - if (!$foundRootEntity) { - $this->semanticalError('Cannot select entity through identification variables without choosing at least one root entity alias.'); - } - } - } - + /** * Fix order of identification variables. - * + * * They have to appear in the select clause in the same order as the * declarations (from ... x join ... y join ... z ...) appear in the query * as the hydration process relies on that order for proper operation. - * + * * @param AST\SelectStatement|AST\DeleteStatement|AST\UpdateStatement $AST * @return void */ private function fixIdentificationVariableOrder($AST) { - if ( count($this->_identVariableExpressions) > 1) { - foreach ($this->_queryComponents as $dqlAlias => $qComp) { - if (isset($this->_identVariableExpressions[$dqlAlias])) { - $expr = $this->_identVariableExpressions[$dqlAlias]; - $key = array_search($expr, $AST->selectClause->selectExpressions); - unset($AST->selectClause->selectExpressions[$key]); - $AST->selectClause->selectExpressions[] = $expr; - } + if (count($this->_identVariableExpressions) <= 1) { + return; + } + + foreach ($this->_queryComponents as $dqlAlias => $qComp) { + if ( ! isset($this->_identVariableExpressions[$dqlAlias])) { + continue; } + + $expr = $this->_identVariableExpressions[$dqlAlias]; + $key = array_search($expr, $AST->selectClause->selectExpressions); + + unset($AST->selectClause->selectExpressions[$key]); + + $AST->selectClause->selectExpressions[] = $expr; } } @@ -380,19 +370,10 @@ class Parser } $tokenPos = (isset($token['position'])) ? $token['position'] : '-1'; + $message = "line 0, col {$tokenPos}: Error: "; - - if ($expected !== '') { - $message .= "Expected {$expected}, got "; - } else { - $message .= 'Unexpected '; - } - - if ($this->_lexer->lookahead === null) { - $message .= 'end of string.'; - } else { - $message .= "'{$token['value']}'"; - } + $message .= ($expected !== '') ? "Expected {$expected}, got " : 'Unexpected '; + $message .= ($this->_lexer->lookahead === null) ? 'end of string.' : "'{$token['value']}'"; throw QueryException::syntaxError($message); } @@ -415,18 +396,19 @@ class Parser $distance = 12; // Find a position of a final word to display in error string - $dql = $this->_query->getDql(); + $dql = $this->_query->getDql(); $length = strlen($dql); - $pos = $token['position'] + $distance; - $pos = strpos($dql, ' ', ($length > $pos) ? $pos : $length); + $pos = $token['position'] + $distance; + $pos = strpos($dql, ' ', ($length > $pos) ? $pos : $length); $length = ($pos !== false) ? $pos - $token['position'] : $distance; - // Building informative message - $message = 'line 0, col ' . ( - (isset($token['position']) && $token['position'] > 0) ? $token['position'] : '-1' - ) . " near '" . substr($dql, $token['position'], $length) . "': Error: " . $message; + $tokenPos = (isset($token['position']) && $token['position'] > 0) ? $token['position'] : '-1'; + $tokenStr = substr($dql, $token['position'], $length); - throw \Doctrine\ORM\Query\QueryException::semanticalError($message); + // Building informative message + $message = 'line 0, col ' . $tokenPos . " near '" . $tokenStr . "': Error: " . $message; + + throw QueryException::semanticalError($message); } /** @@ -460,15 +442,22 @@ class Parser $numUnmatched = 1; while ($numUnmatched > 0 && $token !== null) { - if ($token['value'] == ')') { - --$numUnmatched; - } else if ($token['value'] == '(') { - ++$numUnmatched; + switch ($token['type']) { + case Lexer::T_OPEN_PARENTHESIS: + ++$numUnmatched; + break; + + case Lexer::T_CLOSE_PARENTHESIS: + --$numUnmatched; + break; + + default: + // Do nothing } $token = $this->_lexer->peek(); } - + $this->_lexer->resetPeek(); return $token; @@ -481,7 +470,7 @@ class Parser */ private function _isMathOperator($token) { - return in_array($token['value'], array("+", "-", "/", "*")); + return in_array($token['type'], array(Lexer::T_PLUS, Lexer::T_MINUS, Lexer::T_DIVIDE, Lexer::T_MULTIPLY)); } /** @@ -491,12 +480,13 @@ class Parser */ private function _isFunction() { - $peek = $this->_lexer->peek(); + $peek = $this->_lexer->peek(); $nextpeek = $this->_lexer->peek(); + $this->_lexer->resetPeek(); // We deny the COUNT(SELECT * FROM User u) here. COUNT won't be considered a function - return ($peek['value'] === '(' && $nextpeek['type'] !== Lexer::T_SELECT); + return ($peek['type'] === Lexer::T_OPEN_PARENTHESIS && $nextpeek['type'] !== Lexer::T_SELECT); } /** @@ -506,35 +496,17 @@ class Parser */ private function _isAggregateFunction($tokenType) { - return $tokenType == Lexer::T_AVG || $tokenType == Lexer::T_MIN || - $tokenType == Lexer::T_MAX || $tokenType == Lexer::T_SUM || - $tokenType == Lexer::T_COUNT; + return in_array($tokenType, array(Lexer::T_AVG, Lexer::T_MIN, Lexer::T_MAX, Lexer::T_SUM, Lexer::T_COUNT)); } /** - * Checks whether the current lookahead token of the lexer has the type - * T_ALL, T_ANY or T_SOME. + * Checks whether the current lookahead token of the lexer has the type T_ALL, T_ANY or T_SOME. * * @return boolean */ private function _isNextAllAnySome() { - return $this->_lexer->lookahead['type'] === Lexer::T_ALL || - $this->_lexer->lookahead['type'] === Lexer::T_ANY || - $this->_lexer->lookahead['type'] === Lexer::T_SOME; - } - - /** - * Checks whether the next 2 tokens start a subselect. - * - * @return boolean TRUE if the next 2 tokens start a subselect, FALSE otherwise. - */ - private function _isSubselect() - { - $la = $this->_lexer->lookahead; - $next = $this->_lexer->glimpse(); - - return ($la['value'] === '(' && $next['type'] === Lexer::T_SELECT); + return in_array($this->_lexer->lookahead['type'], array(Lexer::T_ALL, Lexer::T_ANY, Lexer::T_SOME)); } /** @@ -586,12 +558,13 @@ class Parser $class = $this->_queryComponents[$expr->identificationVariable]['metadata']; foreach ($expr->partialFieldSet as $field) { - if ( ! isset($class->fieldMappings[$field])) { - $this->semanticalError( - "There is no mapped field named '$field' on class " . $class->name . ".", - $deferredItem['token'] - ); + if (isset($class->fieldMappings[$field])) { + continue; } + + $this->semanticalError( + "There is no mapped field named '$field' on class " . $class->name . ".", $deferredItem['token'] + ); } if (array_intersect($class->identifier, $expr->partialFieldSet) != $class->identifier) { @@ -662,7 +635,7 @@ class Parser if (($field = $pathExpression->field) === null) { $field = $pathExpression->field = $class->identifier[0]; } - + // Check if field or association exists if ( ! isset($class->associationMappings[$field]) && ! isset($class->fieldMappings[$field])) { $this->semanticalError( @@ -671,17 +644,14 @@ class Parser ); } - if (isset($class->fieldMappings[$field])) { - $fieldType = AST\PathExpression::TYPE_STATE_FIELD; - } else { - $assoc = $class->associationMappings[$field]; - $class = $this->_em->getClassMetadata($assoc['targetEntity']); + $fieldType = AST\PathExpression::TYPE_STATE_FIELD; - if ($assoc['type'] & ClassMetadata::TO_ONE) { - $fieldType = AST\PathExpression::TYPE_SINGLE_VALUED_ASSOCIATION; - } else { - $fieldType = AST\PathExpression::TYPE_COLLECTION_VALUED_ASSOCIATION; - } + if (isset($class->associationMappings[$field])) { + $assoc = $class->associationMappings[$field]; + + $fieldType = ($assoc['type'] & ClassMetadata::TO_ONE) + ? AST\PathExpression::TYPE_SINGLE_VALUED_ASSOCIATION + : AST\PathExpression::TYPE_COLLECTION_VALUED_ASSOCIATION; } // Validate if PathExpression is one of the expected types @@ -717,12 +687,31 @@ class Parser $this->semanticalError($semanticalError, $deferredItem['token']); } - + // We need to force the type in PathExpression $pathExpression->type = $fieldType; } } + private function _processRootEntityAliasSelected() + { + if ( ! count($this->_identVariableExpressions)) { + return; + } + + $foundRootEntity = false; + + foreach ($this->_identVariableExpressions AS $dqlAlias => $expr) { + if (isset($this->_queryComponents[$dqlAlias]) && $this->_queryComponents[$dqlAlias]['parent'] === null) { + $foundRootEntity = true; + } + } + + if ( ! $foundRootEntity) { + $this->semanticalError('Cannot select entity through identification variables without choosing at least one root entity alias.'); + } + } + /** * QueryLanguage ::= SelectStatement | UpdateStatement | DeleteStatement * @@ -738,12 +727,15 @@ class Parser case Lexer::T_SELECT: $statement = $this->SelectStatement(); break; + case Lexer::T_UPDATE: $statement = $this->UpdateStatement(); break; + case Lexer::T_DELETE: $statement = $this->DeleteStatement(); break; + default: $this->syntaxError('SELECT, UPDATE or DELETE'); break; @@ -766,17 +758,10 @@ class Parser { $selectStatement = new AST\SelectStatement($this->SelectClause(), $this->FromClause()); - $selectStatement->whereClause = $this->_lexer->isNextToken(Lexer::T_WHERE) - ? $this->WhereClause() : null; - - $selectStatement->groupByClause = $this->_lexer->isNextToken(Lexer::T_GROUP) - ? $this->GroupByClause() : null; - - $selectStatement->havingClause = $this->_lexer->isNextToken(Lexer::T_HAVING) - ? $this->HavingClause() : null; - - $selectStatement->orderByClause = $this->_lexer->isNextToken(Lexer::T_ORDER) - ? $this->OrderByClause() : null; + $selectStatement->whereClause = $this->_lexer->isNextToken(Lexer::T_WHERE) ? $this->WhereClause() : null; + $selectStatement->groupByClause = $this->_lexer->isNextToken(Lexer::T_GROUP) ? $this->GroupByClause() : null; + $selectStatement->havingClause = $this->_lexer->isNextToken(Lexer::T_HAVING) ? $this->HavingClause() : null; + $selectStatement->orderByClause = $this->_lexer->isNextToken(Lexer::T_ORDER) ? $this->OrderByClause() : null; return $selectStatement; } @@ -789,8 +774,8 @@ class Parser public function UpdateStatement() { $updateStatement = new AST\UpdateStatement($this->UpdateClause()); - $updateStatement->whereClause = $this->_lexer->isNextToken(Lexer::T_WHERE) - ? $this->WhereClause() : null; + + $updateStatement->whereClause = $this->_lexer->isNextToken(Lexer::T_WHERE) ? $this->WhereClause() : null; return $updateStatement; } @@ -803,8 +788,8 @@ class Parser public function DeleteStatement() { $deleteStatement = new AST\DeleteStatement($this->DeleteClause()); - $deleteStatement->whereClause = $this->_lexer->isNextToken(Lexer::T_WHERE) - ? $this->WhereClause() : null; + + $deleteStatement->whereClause = $this->_lexer->isNextToken(Lexer::T_WHERE) ? $this->WhereClause() : null; return $deleteStatement; } @@ -842,9 +827,7 @@ class Parser $exists = isset($this->_queryComponents[$aliasIdentVariable]); if ($exists) { - $this->semanticalError( - "'$aliasIdentVariable' is already defined.", $this->_lexer->token - ); + $this->semanticalError("'$aliasIdentVariable' is already defined.", $this->_lexer->token); } return $aliasIdentVariable; @@ -863,6 +846,7 @@ class Parser if (strrpos($schemaName, ':') !== false) { list($namespaceAlias, $simpleClassName) = explode(':', $schemaName); + $schemaName = $this->_em->getConfiguration()->getEntityNamespace($namespaceAlias) . '\\' . $simpleClassName; } @@ -888,9 +872,7 @@ class Parser $exists = isset($this->_queryComponents[$resultVariable]); if ($exists) { - $this->semanticalError( - "'$resultVariable' is already defined.", $this->_lexer->token - ); + $this->semanticalError("'$resultVariable' is already defined.", $this->_lexer->token); } return $resultVariable; @@ -924,11 +906,13 @@ class Parser */ public function JoinAssociationPathExpression() { - $token = $this->_lexer->lookahead; + $token = $this->_lexer->lookahead; $identVariable = $this->IdentificationVariable(); - if (!isset($this->_queryComponents[$identVariable])) { - $this->semanticalError('Identification Variable ' . $identVariable .' used in join path expression but was not defined before.'); + 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); @@ -968,7 +952,7 @@ class Parser $field = $this->_lexer->token['value']; } - + // Creating AST node $pathExpr = new AST\PathExpression($expectedTypes, $identVariable, $field); @@ -1051,6 +1035,7 @@ class Parser // Check for DISTINCT if ($this->_lexer->isNextToken(Lexer::T_DISTINCT)) { $this->match(Lexer::T_DISTINCT); + $isDistinct = true; } @@ -1060,6 +1045,7 @@ class Parser while ($this->_lexer->isNextToken(Lexer::T_COMMA)) { $this->match(Lexer::T_COMMA); + $selectExpressions[] = $this->SelectExpression(); } @@ -1078,6 +1064,7 @@ class Parser if ($this->_lexer->isNextToken(Lexer::T_DISTINCT)) { $this->match(Lexer::T_DISTINCT); + $isDistinct = true; } @@ -1112,6 +1099,7 @@ class Parser 'nestingLevel' => $this->_nestingLevel, 'token' => $token, ); + $this->_queryComponents[$aliasIdentificationVariable] = $queryComponent; $this->match(Lexer::T_SET); @@ -1121,6 +1109,7 @@ class Parser while ($this->_lexer->isNextToken(Lexer::T_COMMA)) { $this->match(Lexer::T_COMMA); + $updateItems[] = $this->UpdateItem(); } @@ -1164,6 +1153,7 @@ class Parser 'nestingLevel' => $this->_nestingLevel, 'token' => $token, ); + $this->_queryComponents[$aliasIdentificationVariable] = $queryComponent; return $deleteClause; @@ -1177,11 +1167,13 @@ class Parser public function FromClause() { $this->match(Lexer::T_FROM); + $identificationVariableDeclarations = array(); $identificationVariableDeclarations[] = $this->IdentificationVariableDeclaration(); while ($this->_lexer->isNextToken(Lexer::T_COMMA)) { $this->match(Lexer::T_COMMA); + $identificationVariableDeclarations[] = $this->IdentificationVariableDeclaration(); } @@ -1196,11 +1188,13 @@ class Parser public function SubselectFromClause() { $this->match(Lexer::T_FROM); + $identificationVariables = array(); $identificationVariables[] = $this->SubselectIdentificationVariableDeclaration(); while ($this->_lexer->isNextToken(Lexer::T_COMMA)) { $this->match(Lexer::T_COMMA); + $identificationVariables[] = $this->SubselectIdentificationVariableDeclaration(); } @@ -1245,6 +1239,7 @@ class Parser while ($this->_lexer->isNextToken(Lexer::T_COMMA)) { $this->match(Lexer::T_COMMA); + $groupByItems[] = $this->GroupByItem(); } @@ -1266,6 +1261,7 @@ class Parser while ($this->_lexer->isNextToken(Lexer::T_COMMA)) { $this->match(Lexer::T_COMMA); + $orderByItems[] = $this->OrderByItem(); } @@ -1284,17 +1280,10 @@ class Parser $subselect = new AST\Subselect($this->SimpleSelectClause(), $this->SubselectFromClause()); - $subselect->whereClause = $this->_lexer->isNextToken(Lexer::T_WHERE) - ? $this->WhereClause() : null; - - $subselect->groupByClause = $this->_lexer->isNextToken(Lexer::T_GROUP) - ? $this->GroupByClause() : null; - - $subselect->havingClause = $this->_lexer->isNextToken(Lexer::T_HAVING) - ? $this->HavingClause() : null; - - $subselect->orderByClause = $this->_lexer->isNextToken(Lexer::T_ORDER) - ? $this->OrderByClause() : null; + $subselect->whereClause = $this->_lexer->isNextToken(Lexer::T_WHERE) ? $this->WhereClause() : null; + $subselect->groupByClause = $this->_lexer->isNextToken(Lexer::T_GROUP) ? $this->GroupByClause() : null; + $subselect->havingClause = $this->_lexer->isNextToken(Lexer::T_HAVING) ? $this->HavingClause() : null; + $subselect->orderByClause = $this->_lexer->isNextToken(Lexer::T_ORDER) ? $this->OrderByClause() : null; // Decrease query nesting level $this->_nestingLevel--; @@ -1331,11 +1320,11 @@ class Parser if ($glimpse['type'] == Lexer::T_DOT) { return $this->SingleValuedPathExpression(); } - + $token = $this->_lexer->lookahead; $identVariable = $this->IdentificationVariable(); - if (!isset($this->_queryComponents[$identVariable])) { + if ( ! isset($this->_queryComponents[$identVariable])) { $this->semanticalError('Cannot group by undefined identification variable.'); } @@ -1353,21 +1342,26 @@ class Parser // We need to check if we are in a ResultVariable or StateFieldPathExpression $glimpse = $this->_lexer->glimpse(); - - $expr = ($glimpse['type'] != Lexer::T_DOT) - ? $this->ResultVariable() - : $this->SingleValuedPathExpression(); + $expr = ($glimpse['type'] != Lexer::T_DOT) ? $this->ResultVariable() : $this->SingleValuedPathExpression(); $item = new AST\OrderByItem($expr); - if ($this->_lexer->isNextToken(Lexer::T_ASC)) { - $this->match(Lexer::T_ASC); - } else if ($this->_lexer->isNextToken(Lexer::T_DESC)) { - $this->match(Lexer::T_DESC); - $type = 'DESC'; + switch (true) { + case ($this->_lexer->isNextToken(Lexer::T_DESC)): + $this->match(Lexer::T_DESC); + $type = 'DESC'; + break; + + case ($this->_lexer->isNextToken(Lexer::T_ASC)): + $this->match(Lexer::T_ASC); + break; + + default: + // Do nothing } $item->type = $type; + return $item; } @@ -1386,9 +1380,13 @@ class Parser { if ($this->_lexer->isNextToken(Lexer::T_NULL)) { $this->match(Lexer::T_NULL); + return null; - } else if ($this->_lexer->isNextToken(Lexer::T_INPUT_PARAMETER)) { + } + + if ($this->_lexer->isNextToken(Lexer::T_INPUT_PARAMETER)) { $this->match(Lexer::T_INPUT_PARAMETER); + return new AST\InputParameter($this->_lexer->token['value']); } @@ -1451,9 +1449,8 @@ class Parser */ public function JoinVariableDeclaration() { - $join = $this->Join(); - $indexBy = $this->_lexer->isNextToken(Lexer::T_INDEX) - ? $this->IndexBy() : null; + $join = $this->Join(); + $indexBy = $this->_lexer->isNextToken(Lexer::T_INDEX) ? $this->IndexBy() : null; return new AST\JoinVariableDeclaration($join, $indexBy); } @@ -1484,6 +1481,7 @@ class Parser 'nestingLevel' => $this->_nestingLevel, 'token' => $token ); + $this->_queryComponents[$aliasIdentificationVariable] = $queryComponent; return new AST\RangeVariableDeclaration($abstractSchemaName, $aliasIdentificationVariable); @@ -1502,18 +1500,20 @@ class Parser $partialFieldSet = array(); $identificationVariable = $this->IdentificationVariable(); - $this->match(Lexer::T_DOT); + $this->match(Lexer::T_DOT); $this->match(Lexer::T_OPEN_CURLY_BRACE); $this->match(Lexer::T_IDENTIFIER); + $partialFieldSet[] = $this->_lexer->token['value']; while ($this->_lexer->isNextToken(Lexer::T_COMMA)) { $this->match(Lexer::T_COMMA); $this->match(Lexer::T_IDENTIFIER); + $partialFieldSet[] = $this->_lexer->token['value']; } - + $this->match(Lexer::T_CLOSE_CURLY_BRACE); $partialObjectExpression = new AST\PartialObjectExpression($identificationVariable, $partialFieldSet); @@ -1539,18 +1539,26 @@ class Parser // Check Join type $joinType = AST\Join::JOIN_TYPE_INNER; - if ($this->_lexer->isNextToken(Lexer::T_LEFT)) { - $this->match(Lexer::T_LEFT); + switch (true) { + case ($this->_lexer->isNextToken(Lexer::T_LEFT)): + $this->match(Lexer::T_LEFT); - // Possible LEFT OUTER join - if ($this->_lexer->isNextToken(Lexer::T_OUTER)) { - $this->match(Lexer::T_OUTER); - $joinType = AST\Join::JOIN_TYPE_LEFTOUTER; - } else { $joinType = AST\Join::JOIN_TYPE_LEFT; - } - } else if ($this->_lexer->isNextToken(Lexer::T_INNER)) { - $this->match(Lexer::T_INNER); + + // Possible LEFT OUTER join + if ($this->_lexer->isNextToken(Lexer::T_OUTER)) { + $this->match(Lexer::T_OUTER); + + $joinType = AST\Join::JOIN_TYPE_LEFTOUTER; + } + break; + + case ($this->_lexer->isNextToken(Lexer::T_INNER)): + $this->match(Lexer::T_INNER); + break; + + default: + // Do nothing } $this->match(Lexer::T_JOIN); @@ -1566,7 +1574,7 @@ class Parser // Verify that the association exists. $parentClass = $this->_queryComponents[$joinPathExpression->identificationVariable]['metadata']; - $assocField = $joinPathExpression->associationField; + $assocField = $joinPathExpression->associationField; if ( ! $parentClass->hasAssociation($assocField)) { $this->semanticalError( @@ -1585,6 +1593,7 @@ class Parser 'nestingLevel' => $this->_nestingLevel, 'token' => $token ); + $this->_queryComponents[$aliasIdentificationVariable] = $joinQueryComponent; // Create AST node @@ -1593,6 +1602,7 @@ class Parser // Check for ad-hoc Join conditions if ($this->_lexer->isNextToken(Lexer::T_WITH)) { $this->match(Lexer::T_WITH); + $join->conditionalExpression = $this->ConditionalExpression(); } @@ -1626,180 +1636,198 @@ class Parser public function ScalarExpression() { $lookahead = $this->_lexer->lookahead['type']; - if ($lookahead === Lexer::T_IDENTIFIER) { - $this->_lexer->peek(); // lookahead => '.' - $this->_lexer->peek(); // lookahead => token after '.' - $peek = $this->_lexer->peek(); // lookahead => token after the token after the '.' - $this->_lexer->resetPeek(); - if ($this->_isMathOperator($peek)) { + switch ($lookahead) { + case Lexer::T_IDENTIFIER: + $this->_lexer->peek(); // lookahead => '.' + $this->_lexer->peek(); // lookahead => token after '.' + $peek = $this->_lexer->peek(); // lookahead => token after the token after the '.' + $this->_lexer->resetPeek(); + + if ($this->_isMathOperator($peek)) { + return $this->SimpleArithmeticExpression(); + } + + return $this->StateFieldPathExpression(); + + case Lexer::T_INTEGER: + case Lexer::T_FLOAT: return $this->SimpleArithmeticExpression(); - } - return $this->StateFieldPathExpression(); - } else if ($lookahead == Lexer::T_INTEGER || $lookahead == Lexer::T_FLOAT) { - return $this->SimpleArithmeticExpression(); - } else if ($lookahead == Lexer::T_CASE || $lookahead == Lexer::T_COALESCE || $lookahead == Lexer::T_NULLIF) { - // Since NULLIF and COALESCE can be identified as a function, - // we need to check if before check for FunctionDeclaration - return $this->CaseExpression(); - } else if ($this->_isFunction() || $this->_isAggregateFunction($this->_lexer->lookahead['type'])) { - // We may be in an ArithmeticExpression (find the matching ")" and inspect for Math operator) - $this->_lexer->peek(); // "(" - $peek = $this->_peekBeyondClosingParenthesis(); + case Lexer::T_STRING: + return $this->StringPrimary(); - if ($this->_isMathOperator($peek)) { - return $this->SimpleArithmeticExpression(); - } + case Lexer::T_TRUE: + case Lexer::T_FALSE: + $this->match($lookahead); - if ($this->_isAggregateFunction($this->_lexer->lookahead['type'])) { - return $this->AggregateExpression(); - } - - return $this->FunctionDeclaration(); - } else if ($lookahead == Lexer::T_STRING) { - return $this->StringPrimary(); - } else if ($lookahead == Lexer::T_INPUT_PARAMETER) { - return $this->InputParameter(); - } else if ($lookahead == Lexer::T_TRUE || $lookahead == Lexer::T_FALSE) { - $this->match($lookahead); - return new AST\Literal(AST\Literal::BOOLEAN, $this->_lexer->token['value']); - } else { - $this->syntaxError(); + return new AST\Literal(AST\Literal::BOOLEAN, $this->_lexer->token['value']); + + case Lexer::T_INPUT_PARAMETER: + return $this->InputParameter(); + + case Lexer::T_CASE: + case Lexer::T_COALESCE: + case Lexer::T_NULLIF: + // Since NULLIF and COALESCE can be identified as a function, + // we need to check if before check for FunctionDeclaration + return $this->CaseExpression(); + + default: + if ( ! ($this->_isFunction() || $this->_isAggregateFunction($lookahead))) { + $this->syntaxError(); + } + + // We may be in an ArithmeticExpression (find the matching ")" and inspect for Math operator) + $this->_lexer->peek(); // "(" + $peek = $this->_peekBeyondClosingParenthesis(); + + if ($this->_isMathOperator($peek)) { + return $this->SimpleArithmeticExpression(); + } + + if ($this->_isAggregateFunction($this->_lexer->lookahead['type'])) { + return $this->AggregateExpression(); + } + + return $this->FunctionDeclaration(); } } /** - * CaseExpression ::= GeneralCaseExpression | SimpleCaseExpression | CoalesceExpression | NullifExpression - * GeneralCaseExpression ::= "CASE" WhenClause {WhenClause}* "ELSE" ScalarExpression "END" - * WhenClause ::= "WHEN" ConditionalExpression "THEN" ScalarExpression - * SimpleCaseExpression ::= "CASE" CaseOperand SimpleWhenClause {SimpleWhenClause}* "ELSE" ScalarExpression "END" - * CaseOperand ::= StateFieldPathExpression | TypeDiscriminator - * SimpleWhenClause ::= "WHEN" ScalarExpression "THEN" ScalarExpression - * CoalesceExpression ::= "COALESCE" "(" ScalarExpression {"," ScalarExpression}* ")" + * CaseExpression ::= GeneralCaseExpression | SimpleCaseExpression | CoalesceExpression | NullifExpression + * GeneralCaseExpression ::= "CASE" WhenClause {WhenClause}* "ELSE" ScalarExpression "END" + * WhenClause ::= "WHEN" ConditionalExpression "THEN" ScalarExpression + * SimpleCaseExpression ::= "CASE" CaseOperand SimpleWhenClause {SimpleWhenClause}* "ELSE" ScalarExpression "END" + * CaseOperand ::= StateFieldPathExpression | TypeDiscriminator + * SimpleWhenClause ::= "WHEN" ScalarExpression "THEN" ScalarExpression + * CoalesceExpression ::= "COALESCE" "(" ScalarExpression {"," ScalarExpression}* ")" * NullifExpression ::= "NULLIF" "(" ScalarExpression "," ScalarExpression ")" - * + * * @return mixed One of the possible expressions or subexpressions. */ public function CaseExpression() { $lookahead = $this->_lexer->lookahead['type']; - + switch ($lookahead) { case Lexer::T_NULLIF: return $this->NullIfExpression(); - + case Lexer::T_COALESCE: return $this->CoalesceExpression(); - + case Lexer::T_CASE: $this->_lexer->resetPeek(); $peek = $this->_lexer->peek(); - - return ($peek['type'] === Lexer::T_WHEN) - ? $this->GeneralCaseExpression() - : $this->SimpleCaseExpression(); - + + if ($peek['type'] === Lexer::T_WHEN) { + return $this->GeneralCaseExpression(); + } + + return $this->SimpleCaseExpression(); + default: // Do nothing break; } - + $this->syntaxError(); } - + /** * CoalesceExpression ::= "COALESCE" "(" ScalarExpression {"," ScalarExpression}* ")" - * - * @return Doctrine\ORM\Query\AST\CoalesceExpression + * + * @return Doctrine\ORM\Query\AST\CoalesceExpression */ public function CoalesceExpression() { $this->match(Lexer::T_COALESCE); $this->match(Lexer::T_OPEN_PARENTHESIS); - + // Process ScalarExpressions (1..N) $scalarExpressions = array(); $scalarExpressions[] = $this->ScalarExpression(); while ($this->_lexer->isNextToken(Lexer::T_COMMA)) { $this->match(Lexer::T_COMMA); + $scalarExpressions[] = $this->ScalarExpression(); } - + $this->match(Lexer::T_CLOSE_PARENTHESIS); - + return new AST\CoalesceExpression($scalarExpressions); } - + /** * NullIfExpression ::= "NULLIF" "(" ScalarExpression "," ScalarExpression ")" - * - * @return Doctrine\ORM\Query\AST\NullIfExpression + * + * @return Doctrine\ORM\Query\AST\NullIfExpression */ public function NullIfExpression() { $this->match(Lexer::T_NULLIF); $this->match(Lexer::T_OPEN_PARENTHESIS); - + $firstExpression = $this->ScalarExpression(); $this->match(Lexer::T_COMMA); $secondExpression = $this->ScalarExpression(); - + $this->match(Lexer::T_CLOSE_PARENTHESIS); return new AST\NullIfExpression($firstExpression, $secondExpression); } - + /** - * GeneralCaseExpression ::= "CASE" WhenClause {WhenClause}* "ELSE" ScalarExpression "END" - * - * @return Doctrine\ORM\Query\AST\GeneralExpression + * GeneralCaseExpression ::= "CASE" WhenClause {WhenClause}* "ELSE" ScalarExpression "END" + * + * @return Doctrine\ORM\Query\AST\GeneralExpression */ public function GeneralCaseExpression() { $this->match(Lexer::T_CASE); - + // Process WhenClause (1..N) $whenClauses = array(); - + do { $whenClauses[] = $this->WhenClause(); } while ($this->_lexer->isNextToken(Lexer::T_WHEN)); - + $this->match(Lexer::T_ELSE); $scalarExpression = $this->ScalarExpression(); $this->match(Lexer::T_END); - + return new AST\GeneralCaseExpression($whenClauses, $scalarExpression); } - + /** - * SimpleCaseExpression ::= "CASE" CaseOperand SimpleWhenClause {SimpleWhenClause}* "ELSE" ScalarExpression "END" - * CaseOperand ::= StateFieldPathExpression | TypeDiscriminator + * SimpleCaseExpression ::= "CASE" CaseOperand SimpleWhenClause {SimpleWhenClause}* "ELSE" ScalarExpression "END" + * CaseOperand ::= StateFieldPathExpression | TypeDiscriminator */ public function SimpleCaseExpression() { $this->match(Lexer::T_CASE); $caseOperand = $this->StateFieldPathExpression(); - + // Process SimpleWhenClause (1..N) $simpleWhenClauses = array(); - + do { $simpleWhenClauses[] = $this->SimpleWhenClause(); } while ($this->_lexer->isNextToken(Lexer::T_WHEN)); - + $this->match(Lexer::T_ELSE); $scalarExpression = $this->ScalarExpression(); $this->match(Lexer::T_END); - + return new AST\SimpleCaseExpression($caseOperand, $simpleWhenClauses, $scalarExpression); } - + /** - * WhenClause ::= "WHEN" ConditionalExpression "THEN" ScalarExpression - * + * WhenClause ::= "WHEN" ConditionalExpression "THEN" ScalarExpression + * * @return Doctrine\ORM\Query\AST\WhenExpression */ public function WhenClause() @@ -1807,13 +1835,13 @@ class Parser $this->match(Lexer::T_WHEN); $conditionalExpression = $this->ConditionalExpression(); $this->match(Lexer::T_THEN); - + return new AST\WhenClause($conditionalExpression, $this->ScalarExpression()); } - + /** - * SimpleWhenClause ::= "WHEN" ScalarExpression "THEN" ScalarExpression - * + * SimpleWhenClause ::= "WHEN" ScalarExpression "THEN" ScalarExpression + * * @return Doctrine\ORM\Query\AST\SimpleWhenExpression */ public function SimpleWhenClause() @@ -1821,109 +1849,105 @@ class Parser $this->match(Lexer::T_WHEN); $conditionalExpression = $this->ScalarExpression(); $this->match(Lexer::T_THEN); - + return new AST\SimpleWhenClause($conditionalExpression, $this->ScalarExpression()); } /** - * SelectExpression ::= - * IdentificationVariable | StateFieldPathExpression | - * (AggregateExpression | "(" Subselect ")" | ScalarExpression) [["AS"] ["HIDDEN"] AliasResultVariable] + * SelectExpression ::= ( + * IdentificationVariable | ScalarExpression | AggregateExpression | FunctionDeclaration | + * PartialObjectExpression | "(" Subselect ")" | CaseExpression + * ) [["AS"] ["HIDDEN"] AliasResultVariable] * * @return Doctrine\ORM\Query\AST\SelectExpression */ public function SelectExpression() { - $expression = null; + $expression = null; $identVariable = null; - $hiddenAliasResultVariable = false; - $fieldAliasIdentificationVariable = null; - $peek = $this->_lexer->glimpse(); + $peek = $this->_lexer->glimpse(); - $supportsAlias = true; - - if ($peek['value'] != '(' && $this->_lexer->lookahead['type'] === Lexer::T_IDENTIFIER) { - if ($peek['value'] == '.') { - // ScalarExpression - $expression = $this->ScalarExpression(); - } else { - $supportsAlias = false; - $expression = $identVariable = $this->IdentificationVariable(); - } - } else if ($this->_lexer->lookahead['value'] == '(') { - if ($peek['type'] == Lexer::T_SELECT) { - // Subselect - $this->match(Lexer::T_OPEN_PARENTHESIS); - $expression = $this->Subselect(); - $this->match(Lexer::T_CLOSE_PARENTHESIS); - } else { - // Shortcut: ScalarExpression => SimpleArithmeticExpression - $expression = $this->SimpleArithmeticExpression(); - } + if ($this->_lexer->lookahead['type'] === Lexer::T_IDENTIFIER && $peek['type'] === Lexer::T_DOT) { + // ScalarExpression (u.name) + $expression = $this->ScalarExpression(); + } else if ($this->_lexer->lookahead['type'] === Lexer::T_IDENTIFIER && $peek['type'] !== Lexer::T_OPEN_PARENTHESIS) { + // IdentificationVariable (u) + $expression = $identVariable = $this->IdentificationVariable(); + } else if (in_array($this->_lexer->lookahead['type'], array(Lexer::T_CASE, Lexer::T_COALESCE, Lexer::T_NULLIF))) { + // CaseExpression (CASE ... or NULLIF(...) or COALESCE(...)) + $expression = $this->CaseExpression(); } else if ($this->_isFunction()) { + // DQL Function (SUM(u.value) or SUM(u.value) + 1) $this->_lexer->peek(); // "(" - + $lookaheadType = $this->_lexer->lookahead['type']; $beyond = $this->_peekBeyondClosingParenthesis(); - + if ($this->_isMathOperator($beyond)) { + // SUM(u.id) + COUNT(u.id) $expression = $this->ScalarExpression(); } else if ($this->_isAggregateFunction($this->_lexer->lookahead['type'])) { + // COUNT(u.id) $expression = $this->AggregateExpression(); - } else if (in_array($lookaheadType, array(Lexer::T_COALESCE, Lexer::T_NULLIF))) { - $expression = $this->CaseExpression(); } else { - // Shortcut: ScalarExpression => Function + // SUM(u.id) $expression = $this->FunctionDeclaration(); } - } else if ($this->_lexer->lookahead['type'] == Lexer::T_PARTIAL) { - $supportsAlias = false; + } else if ($this->_lexer->lookahead['type'] === Lexer::T_PARTIAL) { + // PartialObjectExpression (PARTIAL u.{id, name}) $expression = $this->PartialObjectExpression(); $identVariable = $expression->identificationVariable; - } else if ($this->_lexer->lookahead['type'] == Lexer::T_INTEGER || - $this->_lexer->lookahead['type'] == Lexer::T_FLOAT || - $this->_lexer->lookahead['type'] == Lexer::T_STRING) { + } else if ($this->_lexer->lookahead['type'] === Lexer::T_OPEN_PARENTHESIS && $peek['type'] === Lexer::T_SELECT) { + // Subselect + $this->match(Lexer::T_OPEN_PARENTHESIS); + $expression = $this->Subselect(); + $this->match(Lexer::T_CLOSE_PARENTHESIS); + } else if (in_array($this->_lexer->lookahead['type'], array(Lexer::T_OPEN_PARENTHESIS, Lexer::T_INTEGER, Lexer::T_FLOAT, Lexer::T_STRING))) { // Shortcut: ScalarExpression => SimpleArithmeticExpression $expression = $this->SimpleArithmeticExpression(); - } else if ($this->_lexer->lookahead['type'] == Lexer::T_CASE) { - $expression = $this->CaseExpression(); } else { $this->syntaxError( - 'IdentificationVariable | StateFieldPathExpression | AggregateExpression | "(" Subselect ")" | ScalarExpression', + 'IdentificationVariable | ScalarExpression | AggregateExpression | FunctionDeclaration | PartialObjectExpression | "(" Subselect ")" | CaseExpression', $this->_lexer->lookahead ); } - if ($supportsAlias) { - if ($this->_lexer->isNextToken(Lexer::T_AS)) { - $this->match(Lexer::T_AS); - } - - if ($this->_lexer->isNextToken(Lexer::T_HIDDEN)) { - $this->match(Lexer::T_HIDDEN); - - $hiddenAliasResultVariable = true; - } + // [["AS"] ["HIDDEN"] AliasResultVariable] - if ($this->_lexer->isNextToken(Lexer::T_IDENTIFIER)) { - $token = $this->_lexer->lookahead; - $fieldAliasIdentificationVariable = $this->AliasResultVariable(); - - // Include AliasResultVariable in query components. - $this->_queryComponents[$fieldAliasIdentificationVariable] = array( - 'resultVariable' => $expression, - 'nestingLevel' => $this->_nestingLevel, - 'token' => $token, - ); - } + if ($this->_lexer->isNextToken(Lexer::T_AS)) { + $this->match(Lexer::T_AS); } - $expr = new AST\SelectExpression($expression, $fieldAliasIdentificationVariable, $hiddenAliasResultVariable); - - if ( ! $supportsAlias) { + $hiddenAliasResultVariable = false; + + if ($this->_lexer->isNextToken(Lexer::T_HIDDEN)) { + $this->match(Lexer::T_HIDDEN); + + $hiddenAliasResultVariable = true; + } + + $aliasResultVariable = null; + + if ($this->_lexer->isNextToken(Lexer::T_IDENTIFIER)) { + $token = $this->_lexer->lookahead; + $aliasResultVariable = $this->AliasResultVariable(); + + // Include AliasResultVariable in query components. + $this->_queryComponents[$aliasResultVariable] = array( + 'resultVariable' => $expression, + 'nestingLevel' => $this->_nestingLevel, + 'token' => $token, + ); + } + + // AST + + $expr = new AST\SelectExpression($expression, $aliasResultVariable, $hiddenAliasResultVariable); + + if ($identVariable) { $this->_identVariableExpressions[$identVariable] = $expr; } - + return $expr; } @@ -1940,11 +1964,9 @@ class Parser if ($peek['value'] != '(' && $this->_lexer->lookahead['type'] === Lexer::T_IDENTIFIER) { // SingleValuedPathExpression | IdentificationVariable - if ($peek['value'] == '.') { - $expression = $this->StateFieldPathExpression(); - } else { - $expression = $this->IdentificationVariable(); - } + $expression = ($peek['value'] == '.') + ? $this->StateFieldPathExpression() + : $this->IdentificationVariable(); return new AST\SimpleSelectExpression($expression); } else if ($this->_lexer->lookahead['value'] == '(') { @@ -1964,8 +1986,7 @@ class Parser $this->_lexer->peek(); $expression = $this->ScalarExpression(); - - $expr = new AST\SimpleSelectExpression($expression); + $expr = new AST\SimpleSelectExpression($expression); if ($this->_lexer->isNextToken(Lexer::T_AS)) { $this->match(Lexer::T_AS); @@ -1999,6 +2020,7 @@ class Parser while ($this->_lexer->isNextToken(Lexer::T_OR)) { $this->match(Lexer::T_OR); + $conditionalTerms[] = $this->ConditionalTerm(); } @@ -2023,6 +2045,7 @@ class Parser while ($this->_lexer->isNextToken(Lexer::T_AND)) { $this->match(Lexer::T_AND); + $conditionalFactors[] = $this->ConditionalFactor(); } @@ -2046,9 +2069,10 @@ class Parser if ($this->_lexer->isNextToken(Lexer::T_NOT)) { $this->match(Lexer::T_NOT); + $not = true; } - + $conditionalPrimary = $this->ConditionalPrimary(); // Phase 1 AST optimization: Prevent AST\ConditionalFactor @@ -2203,13 +2227,13 @@ class Parser */ public function CollectionMemberExpression() { - $not = false; - + $not = false; $entityExpr = $this->EntityExpression(); if ($this->_lexer->isNextToken(Lexer::T_NOT)) { - $not = true; $this->match(Lexer::T_NOT); + + $not = true; } $this->match(Lexer::T_MEMBER); @@ -2374,7 +2398,7 @@ class Parser $this->match(($isPlus) ? Lexer::T_PLUS : Lexer::T_MINUS); $sign = $isPlus; } - + $primary = $this->ArithmeticPrimary(); // Phase 1 AST optimization: Prevent AST\ArithmeticFactor @@ -2407,7 +2431,7 @@ class Parser case Lexer::T_NULLIF: case Lexer::T_CASE: return $this->CaseExpression(); - + case Lexer::T_IDENTIFIER: $peek = $this->_lexer->glimpse(); @@ -2418,11 +2442,11 @@ class Parser if ($peek['value'] == '.') { return $this->SingleValuedPathExpression(); } - + if (isset($this->_queryComponents[$this->_lexer->lookahead['value']]['resultVariable'])) { return $this->ResultVariable(); } - + return $this->StateFieldPathExpression(); case Lexer::T_INPUT_PARAMETER: @@ -2703,47 +2727,47 @@ class Parser $this->match(Lexer::T_INSTANCE); $this->match(Lexer::T_OF); - + $exprValues = array(); - + if ($this->_lexer->isNextToken(Lexer::T_OPEN_PARENTHESIS)) { $this->match(Lexer::T_OPEN_PARENTHESIS); - + $exprValues[] = $this->InstanceOfParameter(); while ($this->_lexer->isNextToken(Lexer::T_COMMA)) { $this->match(Lexer::T_COMMA); - + $exprValues[] = $this->InstanceOfParameter(); } - + $this->match(Lexer::T_CLOSE_PARENTHESIS); - + $instanceOfExpression->value = $exprValues; - + return $instanceOfExpression; } $exprValues[] = $this->InstanceOfParameter(); $instanceOfExpression->value = $exprValues; - + return $instanceOfExpression; } - + /** * InstanceOfParameter ::= AbstractSchemaName | InputParameter - * + * * @return mixed */ public function InstanceOfParameter() { if ($this->_lexer->isNextToken(Lexer::T_INPUT_PARAMETER)) { $this->match(Lexer::T_INPUT_PARAMETER); - + return new AST\InputParameter($this->_lexer->token['value']); } - + return $this->AliasIdentificationVariable(); } @@ -2829,8 +2853,10 @@ class Parser $this->match(Lexer::T_EXISTS); $this->match(Lexer::T_OPEN_PARENTHESIS); + $existsExpression = new AST\ExistsExpression($this->Subselect()); $existsExpression->not = $not; + $this->match(Lexer::T_CLOSE_PARENTHESIS); return $existsExpression; @@ -2894,26 +2920,45 @@ class Parser $funcName = strtolower($token['value']); // Check for built-in functions first! - if (isset(self::$_STRING_FUNCTIONS[$funcName])) { - return $this->FunctionsReturningStrings(); - } else if (isset(self::$_NUMERIC_FUNCTIONS[$funcName])) { - return $this->FunctionsReturningNumerics(); - } else if (isset(self::$_DATETIME_FUNCTIONS[$funcName])) { - return $this->FunctionsReturningDatetime(); + switch (true) { + case (isset(self::$_STRING_FUNCTIONS[$funcName])): + return $this->FunctionsReturningStrings(); + + case (isset(self::$_NUMERIC_FUNCTIONS[$funcName])): + return $this->FunctionsReturningNumerics(); + + case (isset(self::$_DATETIME_FUNCTIONS[$funcName])): + return $this->FunctionsReturningDatetime(); + + default: + return $this->CustomFunctionDeclaration(); } + } + + /** + * Helper function for FunctionDeclaration grammar rule + */ + private function CustomFunctionDeclaration() + { + $token = $this->_lexer->lookahead; + $funcName = strtolower($token['value']); // Check for custom functions afterwards $config = $this->_em->getConfiguration(); - if ($config->getCustomStringFunction($funcName) !== null) { - return $this->CustomFunctionsReturningStrings(); - } else if ($config->getCustomNumericFunction($funcName) !== null) { - return $this->CustomFunctionsReturningNumerics(); - } else if ($config->getCustomDatetimeFunction($funcName) !== null) { - return $this->CustomFunctionsReturningDatetime(); - } + switch (true) { + case ($config->getCustomStringFunction($funcName) !== null): + return $this->CustomFunctionsReturningStrings(); - $this->syntaxError('known function', $token); + case ($config->getCustomNumericFunction($funcName) !== null): + return $this->CustomFunctionsReturningNumerics(); + + case ($config->getCustomDatetimeFunction($funcName) !== null): + return $this->CustomFunctionsReturningDatetime(); + + default: + $this->syntaxError('known function', $token); + } } /** @@ -2928,7 +2973,8 @@ class Parser public function FunctionsReturningNumerics() { $funcNameLower = strtolower($this->_lexer->lookahead['value']); - $funcClass = self::$_NUMERIC_FUNCTIONS[$funcNameLower]; + $funcClass = self::$_NUMERIC_FUNCTIONS[$funcNameLower]; + $function = new $funcClass($funcNameLower); $function->parse($this); @@ -2937,9 +2983,10 @@ class Parser public function CustomFunctionsReturningNumerics() { - $funcName = strtolower($this->_lexer->lookahead['value']); // getCustomNumericFunction is case-insensitive + $funcName = strtolower($this->_lexer->lookahead['value']); $funcClass = $this->_em->getConfiguration()->getCustomNumericFunction($funcName); + $function = new $funcClass($funcName); $function->parse($this); @@ -2952,7 +2999,8 @@ class Parser public function FunctionsReturningDatetime() { $funcNameLower = strtolower($this->_lexer->lookahead['value']); - $funcClass = self::$_DATETIME_FUNCTIONS[$funcNameLower]; + $funcClass = self::$_DATETIME_FUNCTIONS[$funcNameLower]; + $function = new $funcClass($funcNameLower); $function->parse($this); @@ -2961,9 +3009,10 @@ class Parser public function CustomFunctionsReturningDatetime() { - $funcName = $this->_lexer->lookahead['value']; // getCustomDatetimeFunction is case-insensitive + $funcName = $this->_lexer->lookahead['value']; $funcClass = $this->_em->getConfiguration()->getCustomDatetimeFunction($funcName); + $function = new $funcClass($funcName); $function->parse($this); @@ -2981,7 +3030,8 @@ class Parser public function FunctionsReturningStrings() { $funcNameLower = strtolower($this->_lexer->lookahead['value']); - $funcClass = self::$_STRING_FUNCTIONS[$funcNameLower]; + $funcClass = self::$_STRING_FUNCTIONS[$funcNameLower]; + $function = new $funcClass($funcNameLower); $function->parse($this); @@ -2990,9 +3040,10 @@ class Parser public function CustomFunctionsReturningStrings() { - $funcName = $this->_lexer->lookahead['value']; // getCustomStringFunction is case-insensitive + $funcName = $this->_lexer->lookahead['value']; $funcClass = $this->_em->getConfiguration()->getCustomStringFunction($funcName); + $function = new $funcClass($funcName); $function->parse($this); diff --git a/lib/Doctrine/ORM/Query/ResultSetMapping.php b/lib/Doctrine/ORM/Query/ResultSetMapping.php index 5ac99ca82..0d9fed0b9 100644 --- a/lib/Doctrine/ORM/Query/ResultSetMapping.php +++ b/lib/Doctrine/ORM/Query/ResultSetMapping.php @@ -26,7 +26,7 @@ namespace Doctrine\ORM\Query; * The properties of this class are only public for fast internal READ access and to (drastically) * reduce the size of serialized instances for more effective caching due to better (un-)serialization * performance. - * + * * Users should use the public methods. * * @author Roman Borschel @@ -36,87 +36,79 @@ namespace Doctrine\ORM\Query; class ResultSetMapping { /** - * Whether the result is mixed (contains scalar values together with field values). - * * @ignore - * @var boolean + * @var boolean Whether the result is mixed (contains scalar values together with field values). */ public $isMixed = false; + /** - * Maps alias names to class names. - * * @ignore - * @var array + * @var array Maps alias names to class names. */ public $aliasMap = array(); + /** - * Maps alias names to related association field names. - * * @ignore - * @var array + * @var array Maps alias names to related association field names. */ public $relationMap = array(); + /** - * Maps alias names to parent alias names. - * * @ignore - * @var array + * @var array Maps alias names to parent alias names. */ public $parentAliasMap = array(); + /** - * Maps column names in the result set to field names for each class. - * * @ignore - * @var array + * @var array Maps column names in the result set to field names for each class. */ public $fieldMappings = array(); + /** - * Maps column names in the result set to the alias/field name to use in the mapped result. - * * @ignore - * @var array + * @var array Maps column names in the result set to the alias/field name to use in the mapped result. */ public $scalarMappings = array(); + /** - * Maps column names of meta columns (foreign keys, discriminator columns, ...) to field names. - * * @ignore - * @var array + * @var array Maps entities in the result set to the alias name to use in the mapped result. + */ + public $entityMappings = array(); + + /** + * @ignore + * @var array Maps column names of meta columns (foreign keys, discriminator columns, ...) to field names. */ public $metaMappings = array(); + /** - * Maps column names in the result set to the alias they belong to. - * * @ignore - * @var array + * @var array Maps column names in the result set to the alias they belong to. */ public $columnOwnerMap = array(); + /** - * List of columns in the result set that are used as discriminator columns. - * * @ignore - * @var array + * @var array List of columns in the result set that are used as discriminator columns. */ public $discriminatorColumns = array(); + /** - * Maps alias names to field names that should be used for indexing. - * * @ignore - * @var array + * @var array Maps alias names to field names that should be used for indexing. */ public $indexByMap = array(); + /** - * Map from column names to class names that declare the field the column is mapped to. - * * @ignore - * @var array + * @var array Map from column names to class names that declare the field the column is mapped to. */ public $declaringClasses = array(); - + /** - * This is necessary to hydrate derivate foreign keys correctly. - * - * @var array + * @var array This is necessary to hydrate derivate foreign keys correctly. */ public $isIdentifierColumn = array(); @@ -126,11 +118,15 @@ class ResultSetMapping * @param string $class The class name of the entity. * @param string $alias The alias for the class. The alias must be unique among all entity * results or joined entity results within this ResultSetMapping. + * @param string $resultAlias The result alias with which the entity result should be + * placed in the result structure. + * * @todo Rename: addRootEntity */ - public function addEntityResult($class, $alias) + public function addEntityResult($class, $alias, $resultAlias = null) { $this->aliasMap[$alias] = $class; + $this->entityMappings[$alias] = $resultAlias; } /** @@ -141,6 +137,7 @@ class ResultSetMapping * @param string $alias The alias of the entity result or joined entity result the discriminator * column should be used for. * @param string $discrColumn The name of the discriminator column in the SQL result set. + * * @todo Rename: addDiscriminatorColumn */ public function setDiscriminatorColumn($alias, $discrColumn) @@ -158,20 +155,27 @@ class ResultSetMapping public function addIndexBy($alias, $fieldName) { $found = false; + foreach ($this->fieldMappings AS $columnName => $columnFieldName) { - if ($columnFieldName === $fieldName && $this->columnOwnerMap[$columnName] == $alias) { - $this->addIndexByColumn($alias, $columnName); - $found = true; - break; - } + if ( ! ($columnFieldName === $fieldName && $this->columnOwnerMap[$columnName] === $alias)) continue; + + $this->addIndexByColumn($alias, $columnName); + $found = true; + + break; } /* TODO: check if this exception can be put back, for now it's gone because of assumptions made by some ORM internals - if (!$found) { - throw new \LogicException("Cannot add index by for dql alias " . $alias . " and field " . - $fieldName . " without calling addFieldResult() for them before."); + if ( ! $found) { + $message = sprintf( + 'Cannot add index by for DQL alias %s and field %s without calling addFieldResult() for them before.', + $alias, + $fieldName + ); + + throw new \LogicException($message); } - */ + */ } /** @@ -244,6 +248,7 @@ class ResultSetMapping $this->columnOwnerMap[$columnName] = $alias; // field name => class name of declaring class $this->declaringClasses[$columnName] = $declaringClass ?: $this->aliasMap[$alias]; + if ( ! $this->isMixed && $this->scalarMappings) { $this->isMixed = true; } @@ -260,11 +265,11 @@ class ResultSetMapping */ public function addJoinedEntityResult($class, $alias, $parentAlias, $relation) { - $this->aliasMap[$alias] = $class; + $this->aliasMap[$alias] = $class; $this->parentAliasMap[$alias] = $parentAlias; - $this->relationMap[$alias] = $relation; + $this->relationMap[$alias] = $relation; } - + /** * Adds a scalar result mapping. * @@ -275,6 +280,7 @@ class ResultSetMapping public function addScalarResult($columnName, $alias) { $this->scalarMappings[$columnName] = $alias; + if ( ! $this->isMixed && $this->fieldMappings) { $this->isMixed = true; } @@ -282,7 +288,7 @@ class ResultSetMapping /** * Checks whether a column with a given name is mapped as a scalar result. - * + * * @param string $columName The name of the column in the SQL result set. * @return boolean * @todo Rename: isScalar @@ -421,10 +427,10 @@ class ResultSetMapping { return $this->isMixed; } - + /** * Adds a meta column (foreign key or discriminator column) to the result set. - * + * * @param string $alias * @param string $columnName * @param string $fieldName @@ -434,6 +440,7 @@ class ResultSetMapping { $this->metaMappings[$columnName] = $fieldName; $this->columnOwnerMap[$columnName] = $alias; + if ($isIdentifierColumn) { $this->isIdentifierColumn[$alias][$columnName] = true; } diff --git a/lib/Doctrine/ORM/Query/SqlWalker.php b/lib/Doctrine/ORM/Query/SqlWalker.php index 03933d3d9..ba5d29714 100644 --- a/lib/Doctrine/ORM/Query/SqlWalker.php +++ b/lib/Doctrine/ORM/Query/SqlWalker.php @@ -28,9 +28,10 @@ use Doctrine\DBAL\LockMode, * The SqlWalker is a TreeWalker that walks over a DQL AST and constructs * the corresponding SQL. * + * @author Guilherme Blanco * @author Roman Borschel * @author Benjamin Eberlei - * @since 2.0 + * @since 2.0 * @todo Rename: SQLWalker */ class SqlWalker implements TreeWalker @@ -257,13 +258,13 @@ class SqlWalker implements TreeWalker // If this is a joined association we must use left joins to preserve the correct result. $sql .= isset($this->_queryComponents[$dqlAlias]['relation']) ? ' LEFT ' : ' INNER '; $sql .= 'JOIN ' . $parentClass->getQuotedTableName($this->_platform) . ' ' . $tableAlias . ' ON '; - + $sqlParts = array(); foreach ($class->getQuotedIdentifierColumnNames($this->_platform) as $columnName) { $sqlParts[] = $baseTableAlias . '.' . $columnName . ' = ' . $tableAlias . '.' . $columnName; } - + $sql .= implode(' AND ', $sqlParts); } @@ -271,7 +272,7 @@ class SqlWalker implements TreeWalker if ($this->_query->getHint(Query::HINT_FORCE_PARTIAL_LOAD)) { return $sql; } - + // LEFT JOIN child class tables foreach ($class->subClasses as $subClassName) { $subClass = $this->_em->getClassMetadata($subClassName); @@ -294,18 +295,19 @@ class SqlWalker implements TreeWalker private function _generateOrderedCollectionOrderByItems() { $sqlParts = array(); - - foreach ($this->_selectedClasses AS $dqlAlias => $class) { - $qComp = $this->_queryComponents[$dqlAlias]; + + foreach ($this->_selectedClasses AS $selectedClass) { + $dqlAlias = $selectedClass['dqlAlias']; + $qComp = $this->_queryComponents[$dqlAlias]; if ( ! isset($qComp['relation']['orderBy'])) continue; - + foreach ($qComp['relation']['orderBy'] AS $fieldName => $orientation) { $columnName = $qComp['metadata']->getQuotedColumnName($fieldName, $this->_platform); $tableName = ($qComp['metadata']->isInheritanceTypeJoined()) - ? $this->_em->getUnitOfWork()->getEntityPersister($class->name)->getOwningTable($fieldName) + ? $this->_em->getUnitOfWork()->getEntityPersister($qComp['metadata']->name)->getOwningTable($fieldName) : $qComp['metadata']->getTableName(); - + $sqlParts[] = $this->getSQLTableAlias($tableName, $dqlAlias) . '.' . $columnName . ' ' . $orientation; } } @@ -327,7 +329,7 @@ class SqlWalker implements TreeWalker $class = $this->_queryComponents[$dqlAlias]['metadata']; if ( ! $class->isInheritanceTypeSingleTable()) continue; - + $conn = $this->_em->getConnection(); $values = array(); @@ -344,7 +346,7 @@ class SqlWalker implements TreeWalker } $sql = implode(' AND ', $sqlParts); - + return (count($sqlParts) > 1) ? '(' . $sql . ')' : $sql; } @@ -376,19 +378,19 @@ class SqlWalker implements TreeWalker case LockMode::PESSIMISTIC_READ: $sql .= ' ' . $this->_platform->getReadLockSQL(); break; - + case LockMode::PESSIMISTIC_WRITE: $sql .= ' ' . $this->_platform->getWriteLockSQL(); break; - + case LockMode::PESSIMISTIC_OPTIMISTIC: - foreach ($this->_selectedClasses AS $class) { + foreach ($this->_selectedClasses AS $selectedClass) { if ( ! $class->isVersioned) { - throw \Doctrine\ORM\OptimisticLockException::lockFailed($class->name); + throw \Doctrine\ORM\OptimisticLockException::lockFailed($selectedClass['class']->name); } } break; - + default: throw \Doctrine\ORM\Query\QueryException::invalidLockMode(); } @@ -521,13 +523,18 @@ class SqlWalker implements TreeWalker $this->_query->getHydrationMode() != Query::HYDRATE_OBJECT && $this->_query->getHint(Query::HINT_INCLUDE_META_COLUMNS); - foreach ($this->_selectedClasses as $dqlAlias => $class) { + foreach ($this->_selectedClasses as $selectedClass) { + $class = $selectedClass['class']; + $dqlAlias = $selectedClass['dqlAlias']; + $resultAlias = $selectedClass['resultAlias']; + // Register as entity or joined entity result if ($this->_queryComponents[$dqlAlias]['relation'] === null) { - $this->_rsm->addEntityResult($class->name, $dqlAlias); + $this->_rsm->addEntityResult($class->name, $dqlAlias, $resultAlias); } else { $this->_rsm->addJoinedEntityResult( - $class->name, $dqlAlias, + $class->name, + $dqlAlias, $this->_queryComponents[$dqlAlias]['parent'], $this->_queryComponents[$dqlAlias]['relation']['fieldName'] ); @@ -545,10 +552,10 @@ class SqlWalker implements TreeWalker $this->_rsm->setDiscriminatorColumn($dqlAlias, $columnAlias); $this->_rsm->addMetaResult($dqlAlias, $columnAlias, $discrColumn['fieldName']); } - + // Add foreign key columns to SQL, if necessary if ( ! $addMetaColumns) continue; - + // Add foreign key columns of class and also parent classes foreach ($class->associationMappings as $assoc) { if ( ! ($assoc['isOwningSide'] && $assoc['type'] & ClassMetadata::TO_ONE)) continue; @@ -564,7 +571,7 @@ class SqlWalker implements TreeWalker $this->_rsm->addMetaResult($dqlAlias, $columnAlias, $srcColumn, (isset($assoc['id']) && $assoc['id'] === true)); } } - + // Add foreign key columns of subclasses foreach ($class->subClasses as $subClassName) { $subClass = $this->_em->getClassMetadata($subClassName); @@ -573,12 +580,12 @@ class SqlWalker implements TreeWalker foreach ($subClass->associationMappings as $assoc) { // Skip if association is inherited if (isset($assoc['inherited'])) continue; - + if ( ! ($assoc['isOwningSide'] && $assoc['type'] & ClassMetadata::TO_ONE)) continue; - + foreach ($assoc['targetToSourceKeyColumns'] as $srcColumn) { $columnAlias = $this->getSQLColumnAlias($srcColumn); - + $sqlSelectExpressions[] = $sqlTableAlias . '.' . $srcColumn . ' AS ' . $columnAlias; $this->_rsm->addMetaResult($dqlAlias, $columnAlias, $srcColumn); @@ -661,11 +668,11 @@ class SqlWalker implements TreeWalker public function walkOrderByClause($orderByClause) { $orderByItems = array_map(array($this, 'walkOrderByItem'), $orderByClause->orderByItems); - + if (($collectionOrderByItems = $this->_generateOrderedCollectionOrderByItems()) !== '') { $orderByItems = array_merge($orderByItems, (array) $collectionOrderByItems); } - + return ' ORDER BY ' . implode(', ', $orderByItems); } @@ -709,7 +716,7 @@ class SqlWalker implements TreeWalker $sql = ($joinType == AST\Join::JOIN_TYPE_LEFT || $joinType == AST\Join::JOIN_TYPE_LEFTOUTER) ? ' LEFT JOIN ' : ' INNER JOIN '; - + if ($joinVarDecl->indexBy) { // For Many-To-One or One-To-One associations this obviously makes no sense, but is ignored silently. $this->_rsm->addIndexBy( @@ -995,7 +1002,7 @@ class SqlWalker implements TreeWalker $sqlTableAlias = $this->getSQLTableAlias($tableName, $dqlAlias); $columnName = $class->getQuotedColumnName($fieldName, $this->_platform); $columnAlias = $this->getSQLColumnAlias($columnName); - + $sql .= $sqlTableAlias . '.' . $columnName . ' AS ' . $columnAlias; if ( ! $hidden) { @@ -1003,7 +1010,7 @@ class SqlWalker implements TreeWalker $this->_scalarFields[$dqlAlias][$fieldName] = $columnAlias; } break; - + case ($expr instanceof AST\AggregateExpression): case ($expr instanceof AST\Functions\FunctionNode): case ($expr instanceof AST\SimpleArithmeticExpression): @@ -1017,29 +1024,29 @@ class SqlWalker implements TreeWalker case ($expr instanceof AST\SimpleCaseExpression): $columnAlias = $this->getSQLColumnAlias('sclr'); $resultAlias = $selectExpression->fieldIdentificationVariable ?: $this->_scalarResultCounter++; - + $sql .= $expr->dispatch($this) . ' AS ' . $columnAlias; - + $this->_scalarResultAliasMap[$resultAlias] = $columnAlias; if ( ! $hidden) { $this->_rsm->addScalarResult($columnAlias, $resultAlias); } break; - + case ($expr instanceof AST\Subselect): $columnAlias = $this->getSQLColumnAlias('sclr'); $resultAlias = $selectExpression->fieldIdentificationVariable ?: $this->_scalarResultCounter++; - - $sql .= '(' . $this->walkSubselect($expr) . ') AS '.$columnAlias; - + + $sql .= '(' . $this->walkSubselect($expr) . ') AS ' . $columnAlias; + $this->_scalarResultAliasMap[$resultAlias] = $columnAlias; if ( ! $hidden) { $this->_rsm->addScalarResult($columnAlias, $resultAlias); } break; - + default: // IdentificationVariable or PartialObjectExpression if ($expr instanceof AST\PartialObjectExpression) { @@ -1050,18 +1057,23 @@ class SqlWalker implements TreeWalker $partialFieldSet = array(); } - $queryComp = $this->_queryComponents[$dqlAlias]; - $class = $queryComp['metadata']; + $queryComp = $this->_queryComponents[$dqlAlias]; + $class = $queryComp['metadata']; + $resultAlias = $selectExpression->fieldIdentificationVariable ?: null; if ( ! isset($this->_selectedClasses[$dqlAlias])) { - $this->_selectedClasses[$dqlAlias] = $class; + $this->_selectedClasses[$dqlAlias] = array( + 'class' => $class, + 'dqlAlias' => $dqlAlias, + 'resultAlias' => $resultAlias + ); } - $beginning = true; + $sqlParts = array(); // Select all fields from the queried class foreach ($class->fieldMappings as $fieldName => $mapping) { - if ($partialFieldSet && !in_array($fieldName, $partialFieldSet)) { + if ($partialFieldSet && ! in_array($fieldName, $partialFieldSet)) { continue; } @@ -1069,12 +1081,11 @@ class SqlWalker implements TreeWalker ? $this->_em->getClassMetadata($mapping['inherited'])->getTableName() : $class->getTableName(); - if ($beginning) $beginning = false; else $sql .= ', '; + $sqlTableAlias = $this->getSQLTableAlias($tableName, $dqlAlias); + $columnAlias = $this->getSQLColumnAlias($mapping['columnName']); + $quotedColumnName = $class->getQuotedColumnName($fieldName, $this->_platform); - $sqlTableAlias = $this->getSQLTableAlias($tableName, $dqlAlias); - $columnAlias = $this->getSQLColumnAlias($mapping['columnName']); - $sql .= $sqlTableAlias . '.' . $class->getQuotedColumnName($fieldName, $this->_platform) - . ' AS ' . $columnAlias; + $sqlParts[] = $sqlTableAlias . '.' . $quotedColumnName . ' AS '. $columnAlias; $this->_rsm->addFieldResult($dqlAlias, $columnAlias, $fieldName, $class->name); } @@ -1085,7 +1096,7 @@ class SqlWalker implements TreeWalker // since it requires outer joining subtables. if ($class->isInheritanceTypeSingleTable() || ! $this->_query->getHint(Query::HINT_FORCE_PARTIAL_LOAD)) { foreach ($class->subClasses as $subClassName) { - $subClass = $this->_em->getClassMetadata($subClassName); + $subClass = $this->_em->getClassMetadata($subClassName); $sqlTableAlias = $this->getSQLTableAlias($subClass->getTableName(), $dqlAlias); foreach ($subClass->fieldMappings as $fieldName => $mapping) { @@ -1093,16 +1104,17 @@ class SqlWalker implements TreeWalker continue; } - if ($beginning) $beginning = false; else $sql .= ', '; + $columnAlias = $this->getSQLColumnAlias($mapping['columnName']); + $quotedColumnName = $subClass->getQuotedColumnName($fieldName, $this->_platform); - $columnAlias = $this->getSQLColumnAlias($mapping['columnName']); - $sql .= $sqlTableAlias . '.' . $subClass->getQuotedColumnName($fieldName, $this->_platform) - . ' AS ' . $columnAlias; + $sqlParts[] = $sqlTableAlias . '.' . $quotedColumnName . ' AS ' . $columnAlias; $this->_rsm->addFieldResult($dqlAlias, $columnAlias, $fieldName, $subClassName); } } } + + $sql .= implode(', ', $sqlParts); } return $sql; @@ -1116,8 +1128,7 @@ class SqlWalker implements TreeWalker */ public function walkQuantifiedExpression($qExpr) { - return ' ' . strtoupper($qExpr->type) - . '(' . $this->walkSubselect($qExpr->subselect) . ')'; + return ' ' . strtoupper($qExpr->type) . '(' . $this->walkSubselect($qExpr->subselect) . ')'; } /** @@ -1128,20 +1139,21 @@ class SqlWalker implements TreeWalker */ public function walkSubselect($subselect) { - $useAliasesBefore = $this->_useSqlTableAliases; + $useAliasesBefore = $this->_useSqlTableAliases; $rootAliasesBefore = $this->_rootAliases; $this->_rootAliases = array(); // reset the rootAliases for the subselect $this->_useSqlTableAliases = true; - $sql = $this->walkSimpleSelectClause($subselect->simpleSelectClause); + $sql = $this->walkSimpleSelectClause($subselect->simpleSelectClause); $sql .= $this->walkSubselectFromClause($subselect->subselectFromClause); $sql .= $this->walkWhereClause($subselect->whereClause); + $sql .= $subselect->groupByClause ? $this->walkGroupByClause($subselect->groupByClause) : ''; $sql .= $subselect->havingClause ? $this->walkHavingClause($subselect->havingClause) : ''; $sql .= $subselect->orderByClause ? $this->walkOrderByClause($subselect->orderByClause) : ''; - $this->_rootAliases = $rootAliasesBefore; // put the main aliases back + $this->_rootAliases = $rootAliasesBefore; // put the main aliases back $this->_useSqlTableAliases = $useAliasesBefore; return $sql; @@ -1162,7 +1174,7 @@ class SqlWalker implements TreeWalker $sql = ''; $rangeDecl = $subselectIdVarDecl->rangeVariableDeclaration; - $dqlAlias = $rangeDecl->aliasIdentificationVariable; + $dqlAlias = $rangeDecl->aliasIdentificationVariable; $class = $this->_em->getClassMetadata($rangeDecl->abstractSchemaName); $sql .= $class->getQuotedTableName($this->_platform) . ' ' diff --git a/tests/Doctrine/Tests/ORM/Hydration/ArrayHydratorTest.php b/tests/Doctrine/Tests/ORM/Hydration/ArrayHydratorTest.php index 616f19146..dfd40b9ff 100644 --- a/tests/Doctrine/Tests/ORM/Hydration/ArrayHydratorTest.php +++ b/tests/Doctrine/Tests/ORM/Hydration/ArrayHydratorTest.php @@ -9,13 +9,34 @@ require_once __DIR__ . '/../../TestInit.php'; class ArrayHydratorTest extends HydrationTestCase { + public function provideDataForUserEntityResult() + { + return array( + array(0), + array('user'), + ); + } + + public function provideDataForMultipleRootEntityResult() + { + return array( + array(0, 0), + array('user', 0), + array(0, 'article'), + array('user', 'article'), + ); + } + /** - * Select u.id, u.name from Doctrine\Tests\Models\CMS\CmsUser u + * SELECT PARTIAL u.{id, name} + * FROM Doctrine\Tests\Models\CMS\CmsUser u + * + * @dataProvider provideDataForUserEntityResult */ - public function testSimpleEntityQuery() + public function testSimpleEntityQuery($userEntityKey) { $rsm = new ResultSetMapping; - $rsm->addEntityResult('Doctrine\Tests\Models\CMS\CmsUser', 'u'); + $rsm->addEntityResult('Doctrine\Tests\Models\CMS\CmsUser', 'u', $userEntityKey ?: null); $rsm->addFieldResult('u', 'u__id', 'id'); $rsm->addFieldResult('u', 'u__name', 'name'); @@ -24,35 +45,39 @@ class ArrayHydratorTest extends HydrationTestCase array( 'u__id' => '1', 'u__name' => 'romanb' - ), + ), array( 'u__id' => '2', 'u__name' => 'jwage' - ) - ); + ) + ); - - $stmt = new HydratorMockStatement($resultSet); + $stmt = new HydratorMockStatement($resultSet); $hydrator = new \Doctrine\ORM\Internal\Hydration\ArrayHydrator($this->_em); - - $result = $hydrator->hydrateAll($stmt, $rsm); + $result = $hydrator->hydrateAll($stmt, $rsm); $this->assertEquals(2, count($result)); + $this->assertTrue(is_array($result)); + $this->assertEquals(1, $result[0]['id']); $this->assertEquals('romanb', $result[0]['name']); + $this->assertEquals(2, $result[1]['id']); $this->assertEquals('jwage', $result[1]['name']); } /** - * + * SELECT PARTIAL u.{id, name}, PARTIAL a.{id, topic} + * FROM Doctrine\Tests\Models\CMS\CmsUser u, Doctrine\Tests\Models\CMS\CmsArticle a + * + * @dataProvider provideDataForMultipleRootEntityResult */ - public function testSimpleMultipleRootEntityQuery() + public function testSimpleMultipleRootEntityQuery($userEntityKey, $articleEntityKey) { $rsm = new ResultSetMapping; - $rsm->addEntityResult('Doctrine\Tests\Models\CMS\CmsUser', 'u'); - $rsm->addEntityResult('Doctrine\Tests\Models\CMS\CmsArticle', 'a'); + $rsm->addEntityResult('Doctrine\Tests\Models\CMS\CmsUser', 'u', $userEntityKey ?: null); + $rsm->addEntityResult('Doctrine\Tests\Models\CMS\CmsArticle', 'a', $articleEntityKey ?: null); $rsm->addFieldResult('u', 'u__id', 'id'); $rsm->addFieldResult('u', 'u__name', 'name'); $rsm->addFieldResult('a', 'a__id', 'id'); @@ -65,47 +90,51 @@ class ArrayHydratorTest extends HydrationTestCase 'u__name' => 'romanb', 'a__id' => '1', 'a__topic' => 'Cool things.' - ), + ), array( 'u__id' => '2', 'u__name' => 'jwage', 'a__id' => '2', 'a__topic' => 'Cool things II.' - ) - ); + ) + ); - - $stmt = new HydratorMockStatement($resultSet); + $stmt = new HydratorMockStatement($resultSet); $hydrator = new \Doctrine\ORM\Internal\Hydration\ArrayHydrator($this->_em); - - $result = $hydrator->hydrateAll($stmt, $rsm); + $result = $hydrator->hydrateAll($stmt, $rsm); $this->assertEquals(4, count($result)); $this->assertEquals(1, $result[0]['id']); $this->assertEquals('romanb', $result[0]['name']); + $this->assertEquals(1, $result[1]['id']); $this->assertEquals('Cool things.', $result[1]['topic']); + $this->assertEquals(2, $result[2]['id']); $this->assertEquals('jwage', $result[2]['name']); + $this->assertEquals(2, $result[3]['id']); $this->assertEquals('Cool things II.', $result[3]['topic']); } /** - * select u.id, u.status, p.phonenumber, upper(u.name) nameUpper from User u - * join u.phonenumbers p - * = - * select u.id, u.status, p.phonenumber, upper(u.name) as u__0 from USERS u - * INNER JOIN PHONENUMBERS p ON u.id = p.user_id + * SELECT PARTIAL u.{id, status}, PARTIAL p.{phonenumber}, UPPER(u.name) AS nameUpper + * FROM Doctrine\Tests\Models\CMS\CmsUser u + * JOIN u.phonenumbers p + * + * @dataProvider provideDataForUserEntityResult */ - public function testMixedQueryFetchJoin() + public function testMixedQueryFetchJoin($userEntityKey) { $rsm = new ResultSetMapping; - $rsm->addEntityResult('Doctrine\Tests\Models\CMS\CmsUser', 'u'); + $rsm->addEntityResult('Doctrine\Tests\Models\CMS\CmsUser', 'u', $userEntityKey ?: null); $rsm->addJoinedEntityResult( - 'Doctrine\Tests\Models\CMS\CmsPhonenumber', 'p', - 'u', 'phonenumbers'); + 'Doctrine\Tests\Models\CMS\CmsPhonenumber', + 'p', + 'u', + 'phonenumbers' + ); $rsm->addFieldResult('u', 'u__id', 'id'); $rsm->addFieldResult('u', 'u__status', 'status'); $rsm->addScalarResult('sclr0', 'nameUpper'); @@ -119,54 +148,56 @@ class ArrayHydratorTest extends HydrationTestCase 'u__status' => 'developer', 'sclr0' => 'ROMANB', 'p__phonenumber' => '42', - ), + ), array( 'u__id' => '1', 'u__status' => 'developer', 'sclr0' => 'ROMANB', 'p__phonenumber' => '43', - ), + ), array( 'u__id' => '2', 'u__status' => 'developer', 'sclr0' => 'JWAGE', 'p__phonenumber' => '91' - ) - ); + ) + ); - $stmt = new HydratorMockStatement($resultSet); + $stmt = new HydratorMockStatement($resultSet); $hydrator = new \Doctrine\ORM\Internal\Hydration\ArrayHydrator($this->_em); - - $result = $hydrator->hydrateAll($stmt, $rsm); + $result = $hydrator->hydrateAll($stmt, $rsm); $this->assertEquals(2, count($result)); + $this->assertTrue(is_array($result)); $this->assertTrue(is_array($result[0])); $this->assertTrue(is_array($result[1])); // first user => 2 phonenumbers - $this->assertEquals(2, count($result[0][0]['phonenumbers'])); + $this->assertEquals(2, count($result[0][$userEntityKey]['phonenumbers'])); $this->assertEquals('ROMANB', $result[0]['nameUpper']); + // second user => 1 phonenumber - $this->assertEquals(1, count($result[1][0]['phonenumbers'])); + $this->assertEquals(1, count($result[1][$userEntityKey]['phonenumbers'])); $this->assertEquals('JWAGE', $result[1]['nameUpper']); - $this->assertEquals(42, $result[0][0]['phonenumbers'][0]['phonenumber']); - $this->assertEquals(43, $result[0][0]['phonenumbers'][1]['phonenumber']); - $this->assertEquals(91, $result[1][0]['phonenumbers'][0]['phonenumber']); + $this->assertEquals(42, $result[0][$userEntityKey]['phonenumbers'][0]['phonenumber']); + $this->assertEquals(43, $result[0][$userEntityKey]['phonenumbers'][1]['phonenumber']); + $this->assertEquals(91, $result[1][$userEntityKey]['phonenumbers'][0]['phonenumber']); } /** - * select u.id, u.status, count(p.phonenumber) numPhones from User u - * join u.phonenumbers p group by u.status, u.id - * = - * select u.id, u.status, count(p.phonenumber) as p__0 from USERS u - * INNER JOIN PHONENUMBERS p ON u.id = p.user_id group by u.id, u.status + * SELECT PARTIAL u.{id, status}, COUNT(p.phonenumber) AS numPhones + * FROM Doctrine\Tests\Models\CMS\CmsUser u + * JOIN u.phonenumbers p + * GROUP BY u.status, u.id + * + * @dataProvider provideDataForUserEntityResult */ - public function testMixedQueryNormalJoin() + public function testMixedQueryNormalJoin($userEntityKey) { $rsm = new ResultSetMapping; - $rsm->addEntityResult('Doctrine\Tests\Models\CMS\CmsUser', 'u'); + $rsm->addEntityResult('Doctrine\Tests\Models\CMS\CmsUser', 'u', $userEntityKey ?: null); $rsm->addFieldResult('u', 'u__id', 'id'); $rsm->addFieldResult('u', 'u__status', 'status'); $rsm->addScalarResult('sclr0', 'numPhones'); @@ -178,45 +209,50 @@ class ArrayHydratorTest extends HydrationTestCase 'u__id' => '1', 'u__status' => 'developer', 'sclr0' => '2', - ), + ), array( 'u__id' => '2', 'u__status' => 'developer', 'sclr0' => '1', - ) - ); + ) + ); - $stmt = new HydratorMockStatement($resultSet); + $stmt = new HydratorMockStatement($resultSet); $hydrator = new \Doctrine\ORM\Internal\Hydration\ArrayHydrator($this->_em); - - $result = $hydrator->hydrateAll($stmt, $rsm); + $result = $hydrator->hydrateAll($stmt, $rsm); $this->assertEquals(2, count($result)); $this->assertTrue(is_array($result)); $this->assertTrue(is_array($result[0])); $this->assertTrue(is_array($result[1])); + // first user => 2 phonenumbers + $this->assertArrayHasKey($userEntityKey, $result[0]); $this->assertEquals(2, $result[0]['numPhones']); + // second user => 1 phonenumber + $this->assertArrayHasKey($userEntityKey, $result[1]); $this->assertEquals(1, $result[1]['numPhones']); } /** - * select u.id, u.status, upper(u.name) nameUpper from User u index by u.id - * join u.phonenumbers p indexby p.phonenumber - * = - * select u.id, u.status, upper(u.name) as p__0 from USERS u - * INNER JOIN PHONENUMBERS p ON u.id = p.user_id + * SELECT PARTIAL u.{id, status}, UPPER(u.name) nameUpper + * FROM Doctrine\Tests\Models\CMS\CmsUser u + * INDEX BY u.id + * JOIN u.phonenumbers p + * INDEX BY p.phonenumber + * + * @dataProvider provideDataForUserEntityResult */ - public function testMixedQueryFetchJoinCustomIndex() + public function testMixedQueryFetchJoinCustomIndex($userEntityKey) { $rsm = new ResultSetMapping; - $rsm->addEntityResult('Doctrine\Tests\Models\CMS\CmsUser', 'u'); + $rsm->addEntityResult('Doctrine\Tests\Models\CMS\CmsUser', 'u', $userEntityKey ?: null); $rsm->addJoinedEntityResult( - 'Doctrine\Tests\Models\CMS\CmsPhonenumber', - 'p', - 'u', - 'phonenumbers' + 'Doctrine\Tests\Models\CMS\CmsPhonenumber', + 'p', + 'u', + 'phonenumbers' ); $rsm->addFieldResult('u', 'u__id', 'id'); $rsm->addFieldResult('u', 'u__status', 'status'); @@ -233,28 +269,28 @@ class ArrayHydratorTest extends HydrationTestCase 'u__status' => 'developer', 'sclr0' => 'ROMANB', 'p__phonenumber' => '42', - ), + ), array( 'u__id' => '1', 'u__status' => 'developer', 'sclr0' => 'ROMANB', 'p__phonenumber' => '43', - ), + ), array( 'u__id' => '2', 'u__status' => 'developer', 'sclr0' => 'JWAGE', 'p__phonenumber' => '91' - ) - ); + ) + ); - $stmt = new HydratorMockStatement($resultSet); + $stmt = new HydratorMockStatement($resultSet); $hydrator = new \Doctrine\ORM\Internal\Hydration\ArrayHydrator($this->_em); - - $result = $hydrator->hydrateAll($stmt, $rsm); + $result = $hydrator->hydrateAll($stmt, $rsm); $this->assertEquals(2, count($result)); + $this->assertTrue(is_array($result)); $this->assertTrue(is_array($result[1])); $this->assertTrue(is_array($result[2])); @@ -262,14 +298,17 @@ class ArrayHydratorTest extends HydrationTestCase // test the scalar values $this->assertEquals('ROMANB', $result[1]['nameUpper']); $this->assertEquals('JWAGE', $result[2]['nameUpper']); + // first user => 2 phonenumbers. notice the custom indexing by user id - $this->assertEquals(2, count($result[1][0]['phonenumbers'])); + $this->assertEquals(2, count($result[1][$userEntityKey]['phonenumbers'])); + // second user => 1 phonenumber. notice the custom indexing by user id - $this->assertEquals(1, count($result[2][0]['phonenumbers'])); + $this->assertEquals(1, count($result[2][$userEntityKey]['phonenumbers'])); + // test the custom indexing of the phonenumbers - $this->assertTrue(isset($result[1][0]['phonenumbers']['42'])); - $this->assertTrue(isset($result[1][0]['phonenumbers']['43'])); - $this->assertTrue(isset($result[2][0]['phonenumbers']['91'])); + $this->assertTrue(isset($result[1][$userEntityKey]['phonenumbers']['42'])); + $this->assertTrue(isset($result[1][$userEntityKey]['phonenumbers']['43'])); + $this->assertTrue(isset($result[2][$userEntityKey]['phonenumbers']['91'])); } /** @@ -288,16 +327,16 @@ class ArrayHydratorTest extends HydrationTestCase $rsm = new ResultSetMapping; $rsm->addEntityResult('Doctrine\Tests\Models\CMS\CmsUser', 'u'); $rsm->addJoinedEntityResult( - 'Doctrine\Tests\Models\CMS\CmsPhonenumber', - 'p', - 'u', - 'phonenumbers' + 'Doctrine\Tests\Models\CMS\CmsPhonenumber', + 'p', + 'u', + 'phonenumbers' ); $rsm->addJoinedEntityResult( - 'Doctrine\Tests\Models\CMS\CmsArticle', - 'a', - 'u', - 'articles' + 'Doctrine\Tests\Models\CMS\CmsArticle', + 'a', + 'u', + 'articles' ); $rsm->addFieldResult('u', 'u__id', 'id'); $rsm->addFieldResult('u', 'u__status', 'status'); @@ -316,15 +355,15 @@ class ArrayHydratorTest extends HydrationTestCase 'p__phonenumber' => '42', 'a__id' => '1', 'a__topic' => 'Getting things done!' - ), - array( + ), + array( 'u__id' => '1', 'u__status' => 'developer', 'sclr0' => 'ROMANB', 'p__phonenumber' => '43', 'a__id' => '1', 'a__topic' => 'Getting things done!' - ), + ), array( 'u__id' => '1', 'u__status' => 'developer', @@ -332,15 +371,15 @@ class ArrayHydratorTest extends HydrationTestCase 'p__phonenumber' => '42', 'a__id' => '2', 'a__topic' => 'ZendCon' - ), - array( + ), + array( 'u__id' => '1', 'u__status' => 'developer', 'sclr0' => 'ROMANB', 'p__phonenumber' => '43', 'a__id' => '2', 'a__topic' => 'ZendCon' - ), + ), array( 'u__id' => '2', 'u__status' => 'developer', @@ -348,21 +387,20 @@ class ArrayHydratorTest extends HydrationTestCase 'p__phonenumber' => '91', 'a__id' => '3', 'a__topic' => 'LINQ' - ), - array( + ), + array( 'u__id' => '2', 'u__status' => 'developer', 'sclr0' => 'JWAGE', 'p__phonenumber' => '91', 'a__id' => '4', 'a__topic' => 'PHP6' - ), - ); + ), + ); - $stmt = new HydratorMockStatement($resultSet); + $stmt = new HydratorMockStatement($resultSet); $hydrator = new \Doctrine\ORM\Internal\Hydration\ArrayHydrator($this->_em); - - $result = $hydrator->hydrateAll($stmt, $rsm); + $result = $hydrator->hydrateAll($stmt, $rsm); $this->assertEquals(2, count($result)); $this->assertTrue(is_array($result)); @@ -408,22 +446,22 @@ class ArrayHydratorTest extends HydrationTestCase $rsm = new ResultSetMapping; $rsm->addEntityResult('Doctrine\Tests\Models\CMS\CmsUser', 'u'); $rsm->addJoinedEntityResult( - 'Doctrine\Tests\Models\CMS\CmsPhonenumber', - 'p', - 'u', - 'phonenumbers' + 'Doctrine\Tests\Models\CMS\CmsPhonenumber', + 'p', + 'u', + 'phonenumbers' ); $rsm->addJoinedEntityResult( - 'Doctrine\Tests\Models\CMS\CmsArticle', - 'a', - 'u', - 'articles' + 'Doctrine\Tests\Models\CMS\CmsArticle', + 'a', + 'u', + 'articles' ); $rsm->addJoinedEntityResult( - 'Doctrine\Tests\Models\CMS\CmsComment', - 'c', - 'a', - 'comments' + 'Doctrine\Tests\Models\CMS\CmsComment', + 'c', + 'a', + 'comments' ); $rsm->addFieldResult('u', 'u__id', 'id'); $rsm->addFieldResult('u', 'u__status', 'status'); @@ -446,8 +484,8 @@ class ArrayHydratorTest extends HydrationTestCase 'a__topic' => 'Getting things done!', 'c__id' => '1', 'c__topic' => 'First!' - ), - array( + ), + array( 'u__id' => '1', 'u__status' => 'developer', 'sclr0' => 'ROMANB', @@ -456,7 +494,7 @@ class ArrayHydratorTest extends HydrationTestCase 'a__topic' => 'Getting things done!', 'c__id' => '1', 'c__topic' => 'First!' - ), + ), array( 'u__id' => '1', 'u__status' => 'developer', @@ -466,8 +504,8 @@ class ArrayHydratorTest extends HydrationTestCase 'a__topic' => 'ZendCon', 'c__id' => null, 'c__topic' => null - ), - array( + ), + array( 'u__id' => '1', 'u__status' => 'developer', 'sclr0' => 'ROMANB', @@ -476,7 +514,7 @@ class ArrayHydratorTest extends HydrationTestCase 'a__topic' => 'ZendCon', 'c__id' => null, 'c__topic' => null - ), + ), array( 'u__id' => '2', 'u__status' => 'developer', @@ -486,8 +524,8 @@ class ArrayHydratorTest extends HydrationTestCase 'a__topic' => 'LINQ', 'c__id' => null, 'c__topic' => null - ), - array( + ), + array( 'u__id' => '2', 'u__status' => 'developer', 'sclr0' => 'JWAGE', @@ -496,13 +534,12 @@ class ArrayHydratorTest extends HydrationTestCase 'a__topic' => 'PHP6', 'c__id' => null, 'c__topic' => null - ), - ); + ), + ); - $stmt = new HydratorMockStatement($resultSet); + $stmt = new HydratorMockStatement($resultSet); $hydrator = new \Doctrine\ORM\Internal\Hydration\ArrayHydrator($this->_em); - - $result = $hydrator->hydrateAll($stmt, $rsm); + $result = $hydrator->hydrateAll($stmt, $rsm); $this->assertEquals(2, count($result)); $this->assertTrue(is_array($result)); @@ -565,11 +602,12 @@ class ArrayHydratorTest extends HydrationTestCase $rsm = new ResultSetMapping; $rsm->addEntityResult('Doctrine\Tests\Models\Forum\ForumCategory', 'c'); $rsm->addJoinedEntityResult( - 'Doctrine\Tests\Models\Forum\ForumBoard', - 'b', - 'c', - 'boards' + 'Doctrine\Tests\Models\Forum\ForumBoard', + 'b', + 'c', + 'boards' ); + $rsm->addFieldResult('c', 'c__id', 'id'); $rsm->addFieldResult('c', 'c__position', 'position'); $rsm->addFieldResult('c', 'c__name', 'name'); @@ -585,15 +623,15 @@ class ArrayHydratorTest extends HydrationTestCase 'b__id' => '1', 'b__position' => '0', //'b__category_id' => '1' - ), - array( + ), + array( 'c__id' => '2', 'c__position' => '0', 'c__name' => 'Second', 'b__id' => '2', 'b__position' => '0', //'b__category_id' => '2' - ), + ), array( 'c__id' => '1', 'c__position' => '0', @@ -601,21 +639,20 @@ class ArrayHydratorTest extends HydrationTestCase 'b__id' => '3', 'b__position' => '1', //'b__category_id' => '1' - ), - array( + ), + array( 'c__id' => '1', 'c__position' => '0', 'c__name' => 'First', 'b__id' => '4', 'b__position' => '2', //'b__category_id' => '1' - ) - ); + ) + ); - $stmt = new HydratorMockStatement($resultSet); + $stmt = new HydratorMockStatement($resultSet); $hydrator = new \Doctrine\ORM\Internal\Hydration\ArrayHydrator($this->_em); - - $result = $hydrator->hydrateAll($stmt, $rsm); + $result = $hydrator->hydrateAll($stmt, $rsm); $this->assertEquals(2, count($result)); $this->assertTrue(is_array($result)); @@ -626,15 +663,19 @@ class ArrayHydratorTest extends HydrationTestCase $this->assertTrue(isset($result[1]['boards'])); $this->assertEquals(1, count($result[1]['boards'])); } - + /** - * DQL: select partial u.{id,status}, a.id, a.topic, c.id as cid, c.topic as ctopic from CmsUser u left join u.articles a left join a.comments c - * + * SELECT PARTIAL u.{id,status}, a.id, a.topic, c.id as cid, c.topic as ctopic + * FROM Doctrine\Tests\Models\CMS\CmsUser u + * LEFT JOIN u.articles a + * LEFT JOIN a.comments c + * + * @dataProvider provideDataForUserEntityResult */ - /*public function testChainedJoinWithScalars() + public function testChainedJoinWithScalars($entityKey) { $rsm = new ResultSetMapping; - $rsm->addEntityResult('Doctrine\Tests\Models\CMS\CmsUser', 'u'); + $rsm->addEntityResult('Doctrine\Tests\Models\CMS\CmsUser', 'u', $entityKey ?: null); $rsm->addFieldResult('u', 'u__id', 'id'); $rsm->addFieldResult('u', 'u__status', 'status'); $rsm->addScalarResult('a__id', 'id'); @@ -652,7 +693,7 @@ class ArrayHydratorTest extends HydrationTestCase 'a__topic' => 'The First', 'c__id' => '1', 'c__topic' => 'First Comment' - ), + ), array( 'u__id' => '1', 'u__status' => 'developer', @@ -660,47 +701,52 @@ class ArrayHydratorTest extends HydrationTestCase 'a__topic' => 'The First', 'c__id' => '2', 'c__topic' => 'Second Comment' - ), - array( + ), + array( 'u__id' => '1', 'u__status' => 'developer', 'a__id' => '42', 'a__topic' => 'The Answer', 'c__id' => null, 'c__topic' => null - ), - ); + ), + ); - $stmt = new HydratorMockStatement($resultSet); + $stmt = new HydratorMockStatement($resultSet); $hydrator = new \Doctrine\ORM\Internal\Hydration\ArrayHydrator($this->_em); + $result = $hydrator->hydrateAll($stmt, $rsm); - $result = $hydrator->hydrateAll($stmt, $rsm); - $this->assertEquals(3, count($result)); - - $this->assertEquals(2, count($result[0][0])); // User array + + $this->assertEquals(2, count($result[0][$entityKey])); // User array $this->assertEquals(1, $result[0]['id']); $this->assertEquals('The First', $result[0]['topic']); $this->assertEquals(1, $result[0]['cid']); $this->assertEquals('First Comment', $result[0]['ctopic']); - - $this->assertEquals(2, count($result[1][0])); // User array, duplicated + + $this->assertEquals(2, count($result[1][$entityKey])); // User array, duplicated $this->assertEquals(1, $result[1]['id']); // duplicated $this->assertEquals('The First', $result[1]['topic']); // duplicated $this->assertEquals(2, $result[1]['cid']); $this->assertEquals('Second Comment', $result[1]['ctopic']); - - $this->assertEquals(2, count($result[2][0])); // User array, duplicated + + $this->assertEquals(2, count($result[2][$entityKey])); // User array, duplicated $this->assertEquals(42, $result[2]['id']); $this->assertEquals('The Answer', $result[2]['topic']); $this->assertNull($result[2]['cid']); $this->assertNull($result[2]['ctopic']); - }*/ + } - public function testResultIteration() + /** + * SELECT PARTIAL u.{id, status}, UPPER(u.name) AS nameUpper + * FROM Doctrine\Tests\Models\CMS\CmsUser u + * + * @dataProvider provideDataForUserEntityResult + */ + public function testResultIteration($userEntityKey) { $rsm = new ResultSetMapping; - $rsm->addEntityResult('Doctrine\Tests\Models\CMS\CmsUser', 'u'); + $rsm->addEntityResult('Doctrine\Tests\Models\CMS\CmsUser', 'u', $userEntityKey ?: null); $rsm->addFieldResult('u', 'u__id', 'id'); $rsm->addFieldResult('u', 'u__name', 'name'); @@ -709,23 +755,22 @@ class ArrayHydratorTest extends HydrationTestCase array( 'u__id' => '1', 'u__name' => 'romanb' - ), + ), array( 'u__id' => '2', 'u__name' => 'jwage' - ) - ); + ) + ); - - $stmt = new HydratorMockStatement($resultSet); + $stmt = new HydratorMockStatement($resultSet); $hydrator = new \Doctrine\ORM\Internal\Hydration\ArrayHydrator($this->_em); + $iterator = $hydrator->iterate($stmt, $rsm); + $rowNum = 0; - $iterableResult = $hydrator->iterate($stmt, $rsm); - - $rowNum = 0; - while (($row = $iterableResult->next()) !== false) { + while (($row = $iterator->next()) !== false) { $this->assertEquals(1, count($row)); $this->assertTrue(is_array($row[0])); + if ($rowNum == 0) { $this->assertEquals(1, $row[0]['id']); $this->assertEquals('romanb', $row[0]['name']); @@ -733,17 +778,22 @@ class ArrayHydratorTest extends HydrationTestCase $this->assertEquals(2, $row[0]['id']); $this->assertEquals('jwage', $row[0]['name']); } + ++$rowNum; } } /** + * SELECT PARTIAL u.{id, name} + * FROM Doctrine\Tests\Models\CMS\CmsUser u + * * @group DDC-644 + * @dataProvider provideDataForUserEntityResult */ - public function testSkipUnknownColumns() + public function testSkipUnknownColumns($userEntityKey) { $rsm = new ResultSetMapping; - $rsm->addEntityResult('Doctrine\Tests\Models\CMS\CmsUser', 'u'); + $rsm->addEntityResult('Doctrine\Tests\Models\CMS\CmsUser', 'u', $userEntityKey ?: null); $rsm->addFieldResult('u', 'u__id', 'id'); $rsm->addFieldResult('u', 'u__name', 'name'); @@ -753,24 +803,30 @@ class ArrayHydratorTest extends HydrationTestCase 'u__id' => '1', 'u__name' => 'romanb', 'foo' => 'bar', // unknown! - ), + ), ); - $stmt = new HydratorMockStatement($resultSet); + $stmt = new HydratorMockStatement($resultSet); $hydrator = new \Doctrine\ORM\Internal\Hydration\ArrayHydrator($this->_em); - - $result = $hydrator->hydrateAll($stmt, $rsm); + $result = $hydrator->hydrateAll($stmt, $rsm); $this->assertEquals(1, count($result)); + $this->assertArrayHasKey('id', $result[0]); + $this->assertArrayHasKey('name', $result[0]); + $this->assertArrayNotHasKey('foo', $result[0]); } /** + * SELECT PARTIAL u.{id, status}, UPPER(u.name) AS nameUpper + * FROM Doctrine\Tests\Models\CMS\CmsUser u + * * @group DDC-1358 + * @dataProvider provideDataForUserEntityResult */ - public function testMissingIdForRootEntity() + public function testMissingIdForRootEntity($userEntityKey) { $rsm = new ResultSetMapping; - $rsm->addEntityResult('Doctrine\Tests\Models\CMS\CmsUser', 'u'); + $rsm->addEntityResult('Doctrine\Tests\Models\CMS\CmsUser', 'u', $userEntityKey ?: null); $rsm->addFieldResult('u', 'u__id', 'id'); $rsm->addFieldResult('u', 'u__status', 'status'); $rsm->addScalarResult('sclr0', 'nameUpper'); @@ -782,28 +838,27 @@ class ArrayHydratorTest extends HydrationTestCase 'u__id' => '1', 'u__status' => 'developer', 'sclr0' => 'ROMANB', - ), + ), array( 'u__id' => null, 'u__status' => null, 'sclr0' => 'ROMANB', - ), + ), array( 'u__id' => '2', 'u__status' => 'developer', 'sclr0' => 'JWAGE', - ), + ), array( 'u__id' => null, 'u__status' => null, 'sclr0' => 'JWAGE', - ), - ); + ), + ); - $stmt = new HydratorMockStatement($resultSet); + $stmt = new HydratorMockStatement($resultSet); $hydrator = new \Doctrine\ORM\Internal\Hydration\ArrayHydrator($this->_em); - - $result = $hydrator->hydrateAll($stmt, $rsm); + $result = $hydrator->hydrateAll($stmt, $rsm); $this->assertEquals(4, count($result), "Should hydrate four results."); @@ -812,19 +867,24 @@ class ArrayHydratorTest extends HydrationTestCase $this->assertEquals('JWAGE', $result[2]['nameUpper']); $this->assertEquals('JWAGE', $result[3]['nameUpper']); - $this->assertEquals(array('id' => 1, 'status' => 'developer'), $result[0][0]); - $this->assertNull($result[1][0]); - $this->assertEquals(array('id' => 2, 'status' => 'developer'), $result[2][0]); - $this->assertNull($result[3][0]); + $this->assertEquals(array('id' => 1, 'status' => 'developer'), $result[0][$userEntityKey]); + $this->assertNull($result[1][$userEntityKey]); + $this->assertEquals(array('id' => 2, 'status' => 'developer'), $result[2][$userEntityKey]); + $this->assertNull($result[3][$userEntityKey]); } /** + * SELECT PARTIAL u.{id, status}, UPPER(u.name) AS nameUpper + * FROM Doctrine\Tests\Models\CMS\CmsUser u + * INDEX BY u.id + * * @group DDC-1385 + * @dataProvider provideDataForUserEntityResult */ - public function testIndexByAndMixedResult() + public function testIndexByAndMixedResult($userEntityKey) { $rsm = new ResultSetMapping; - $rsm->addEntityResult('Doctrine\Tests\Models\CMS\CmsUser', 'u'); + $rsm->addEntityResult('Doctrine\Tests\Models\CMS\CmsUser', 'u', $userEntityKey ?: null); $rsm->addFieldResult('u', 'u__id', 'id'); $rsm->addFieldResult('u', 'u__status', 'status'); $rsm->addScalarResult('sclr0', 'nameUpper'); @@ -837,23 +897,24 @@ class ArrayHydratorTest extends HydrationTestCase 'u__id' => '1', 'u__status' => 'developer', 'sclr0' => 'ROMANB', - ), + ), array( 'u__id' => '2', 'u__status' => 'developer', 'sclr0' => 'JWAGE', - ), - ); + ), + ); - $stmt = new HydratorMockStatement($resultSet); + $stmt = new HydratorMockStatement($resultSet); $hydrator = new \Doctrine\ORM\Internal\Hydration\ArrayHydrator($this->_em); - - $result = $hydrator->hydrateAll($stmt, $rsm); + $result = $hydrator->hydrateAll($stmt, $rsm); $this->assertEquals(2, count($result)); + $this->assertTrue(isset($result[1])); - $this->assertEquals(1, $result[1][0]['id']); + $this->assertEquals(1, $result[1][$userEntityKey]['id']); + $this->assertTrue(isset($result[2])); - $this->assertEquals(2, $result[2][0]['id']); + $this->assertEquals(2, $result[2][$userEntityKey]['id']); } } \ No newline at end of file diff --git a/tests/Doctrine/Tests/ORM/Hydration/ObjectHydratorTest.php b/tests/Doctrine/Tests/ORM/Hydration/ObjectHydratorTest.php index 268118d72..c3d6a9412 100644 --- a/tests/Doctrine/Tests/ORM/Hydration/ObjectHydratorTest.php +++ b/tests/Doctrine/Tests/ORM/Hydration/ObjectHydratorTest.php @@ -15,14 +15,42 @@ require_once __DIR__ . '/../../TestInit.php'; class ObjectHydratorTest extends HydrationTestCase { + public function provideDataForUserEntityResult() + { + return array( + array(0), + array('user'), + ); + } + + public function provideDataForMultipleRootEntityResult() + { + return array( + array(0, 0), + array('user', 0), + array(0, 'article'), + array('user', 'article'), + ); + } + + public function provideDataForProductEntityResult() + { + return array( + array(0), + array('product'), + ); + } + /** - * SELECT PARTIAL u.{id,name} + * SELECT PARTIAL u.{id,name} * FROM Doctrine\Tests\Models\CMS\CmsUser u + * + * @dataProvider provideDataForUserEntityResult */ - public function testSimpleEntityScalarFieldsQuery() + public function testSimpleEntityScalarFieldsQuery($userEntityKey) { $rsm = new ResultSetMapping; - $rsm->addEntityResult('Doctrine\Tests\Models\CMS\CmsUser', 'u'); + $rsm->addEntityResult('Doctrine\Tests\Models\CMS\CmsUser', 'u', $userEntityKey ?: null); $rsm->addFieldResult('u', 'u__id', 'id'); $rsm->addFieldResult('u', 'u__name', 'name'); @@ -43,26 +71,28 @@ class ObjectHydratorTest extends HydrationTestCase $result = $hydrator->hydrateAll($stmt, $rsm, array(Query::HINT_FORCE_PARTIAL_LOAD => true)); $this->assertEquals(2, count($result)); - + $this->assertInstanceOf('Doctrine\Tests\Models\CMS\CmsUser', $result[0]); $this->assertInstanceOf('Doctrine\Tests\Models\CMS\CmsUser', $result[1]); - + $this->assertEquals(1, $result[0]->id); $this->assertEquals('romanb', $result[0]->name); + $this->assertEquals(2, $result[1]->id); $this->assertEquals('jwage', $result[1]->name); } /** - * SELECT PARTIAL u.{id,name} + * SELECT PARTIAL u.{id,name} * FROM Doctrine\Tests\Models\CMS\CmsUser u - * + * * @group DDC-644 + * @dataProvider provideDataForUserEntityResult */ - public function testSkipUnknownColumns() + public function testSkipUnknownColumns($userEntityKey) { $rsm = new ResultSetMapping; - $rsm->addEntityResult('Doctrine\Tests\Models\CMS\CmsUser', 'u'); + $rsm->addEntityResult('Doctrine\Tests\Models\CMS\CmsUser', 'u', $userEntityKey ?: null); $rsm->addFieldResult('u', 'u__id', 'id'); $rsm->addFieldResult('u', 'u__name', 'name'); @@ -83,17 +113,18 @@ class ObjectHydratorTest extends HydrationTestCase } /** - * SELECT u.id, - * u.name + * SELECT u.id, u.name * FROM Doctrine\Tests\Models\CMS\CmsUser u + * + * @dataProvider provideDataForUserEntityResult */ - public function testScalarQueryWithoutResultVariables() + public function testScalarQueryWithoutResultVariables($userEntityKey) { $rsm = new ResultSetMapping; - $rsm->addEntityResult('Doctrine\Tests\Models\CMS\CmsUser', 'u'); + $rsm->addEntityResult('Doctrine\Tests\Models\CMS\CmsUser', 'u', $userEntityKey ?: null); $rsm->addScalarResult('sclr0', 'id'); $rsm->addScalarResult('sclr1', 'name'); - + // Faked result set $resultSet = array( array( @@ -111,27 +142,28 @@ class ObjectHydratorTest extends HydrationTestCase $result = $hydrator->hydrateAll($stmt, $rsm, array(Query::HINT_FORCE_PARTIAL_LOAD => true)); $this->assertEquals(2, count($result)); - + $this->assertInternalType('array', $result[0]); $this->assertInternalType('array', $result[1]); - + $this->assertEquals(1, $result[0]['id']); $this->assertEquals('romanb', $result[0]['name']); + $this->assertEquals(2, $result[1]['id']); $this->assertEquals('jwage', $result[1]['name']); } - + /** - * SELECT PARTIAL u.{id, name} - * PARTIAL a.{id, topic} - * FROM Doctrine\Tests\Models\CMS\CmsUser u, - * Doctrine\Tests\Models\CMS\CmsArticle a + * SELECT PARTIAL u.{id, name}, PARTIAL a.{id, topic} + * FROM Doctrine\Tests\Models\CMS\CmsUser u, Doctrine\Tests\Models\CMS\CmsArticle a + * + * @dataProvider provideDataForMultipleRootEntityResult */ - public function testSimpleMultipleRootEntityQuery() + public function testSimpleMultipleRootEntityQuery($userEntityKey, $articleEntityKey) { $rsm = new ResultSetMapping; - $rsm->addEntityResult('Doctrine\Tests\Models\CMS\CmsUser', 'u'); - $rsm->addEntityResult('Doctrine\Tests\Models\CMS\CmsArticle', 'a'); + $rsm->addEntityResult('Doctrine\Tests\Models\CMS\CmsUser', 'u', $userEntityKey ?: null); + $rsm->addEntityResult('Doctrine\Tests\Models\CMS\CmsArticle', 'a', $articleEntityKey ?: null); $rsm->addFieldResult('u', 'u__id', 'id'); $rsm->addFieldResult('u', 'u__name', 'name'); $rsm->addFieldResult('a', 'a__id', 'id'); @@ -166,22 +198,27 @@ class ObjectHydratorTest extends HydrationTestCase $this->assertEquals(1, $result[0]->id); $this->assertEquals('romanb', $result[0]->name); + $this->assertEquals(1, $result[1]->id); $this->assertEquals('Cool things.', $result[1]->topic); + $this->assertEquals(2, $result[2]->id); $this->assertEquals('jwage', $result[2]->name); + $this->assertEquals(2, $result[3]->id); $this->assertEquals('Cool things II.', $result[3]->topic); } /** - * SELECT p + * SELECT p * FROM Doctrine\Tests\Models\ECommerce\ECommerceProduct p + * + * @dataProvider provideDataForProductEntityResult */ - public function testCreatesProxyForLazyLoadingWithForeignKeys() + public function testCreatesProxyForLazyLoadingWithForeignKeys($productEntityKey) { $rsm = new ResultSetMapping; - $rsm->addEntityResult('Doctrine\Tests\Models\ECommerce\ECommerceProduct', 'p'); + $rsm->addEntityResult('Doctrine\Tests\Models\ECommerce\ECommerceProduct', 'p', $productEntityKey ?: null); $rsm->addFieldResult('p', 'p__id', 'id'); $rsm->addFieldResult('p', 'p__name', 'name'); $rsm->addMetaResult('p', 'p__shipping_id', 'shipping_id'); @@ -215,21 +252,21 @@ class ObjectHydratorTest extends HydrationTestCase $result = $hydrator->hydrateAll($stmt, $rsm); $this->assertEquals(1, count($result)); - + $this->assertInstanceOf('Doctrine\Tests\Models\ECommerce\ECommerceProduct', $result[0]); } /** - * SELECT PARTIAL u.{id, status}, - * PARTIAL p.{phonenumber}, - * UPPER(u.name) nameUpper - * FROM User u + * SELECT PARTIAL u.{id, status}, PARTIAL p.{phonenumber}, UPPER(u.name) nameUpper + * FROM Doctrine\Tests\Models\CMS\CmsUser u * JOIN u.phonenumbers p + * + * @dataProvider provideDataForUserEntityResult */ - public function testMixedQueryFetchJoin() + public function testMixedQueryFetchJoin($userEntityKey) { $rsm = new ResultSetMapping; - $rsm->addEntityResult('Doctrine\Tests\Models\CMS\CmsUser', 'u'); + $rsm->addEntityResult('Doctrine\Tests\Models\CMS\CmsUser', 'u', $userEntityKey ?: null); $rsm->addJoinedEntityResult( 'Doctrine\Tests\Models\CMS\CmsPhonenumber', 'p', @@ -269,42 +306,44 @@ class ObjectHydratorTest extends HydrationTestCase $result = $hydrator->hydrateAll($stmt, $rsm, array(Query::HINT_FORCE_PARTIAL_LOAD => true)); $this->assertEquals(2, count($result)); - + $this->assertInternalType('array', $result); $this->assertInternalType('array', $result[0]); $this->assertInternalType('array', $result[1]); - $this->assertInstanceOf('Doctrine\Tests\Models\CMS\CmsUser', $result[0][0]); - $this->assertInstanceOf('Doctrine\ORM\PersistentCollection', $result[0][0]->phonenumbers); - $this->assertInstanceOf('Doctrine\Tests\Models\CMS\CmsPhonenumber', $result[0][0]->phonenumbers[0]); - $this->assertInstanceOf('Doctrine\Tests\Models\CMS\CmsPhonenumber', $result[0][0]->phonenumbers[1]); - $this->assertInstanceOf('Doctrine\Tests\Models\CMS\CmsUser', $result[1][0]); - $this->assertInstanceOf('Doctrine\ORM\PersistentCollection', $result[1][0]->phonenumbers); + $this->assertInstanceOf('Doctrine\Tests\Models\CMS\CmsUser', $result[0][$userEntityKey]); + $this->assertInstanceOf('Doctrine\ORM\PersistentCollection', $result[0][$userEntityKey]->phonenumbers); + $this->assertInstanceOf('Doctrine\Tests\Models\CMS\CmsPhonenumber', $result[0][$userEntityKey]->phonenumbers[0]); + + $this->assertInstanceOf('Doctrine\Tests\Models\CMS\CmsUser', $result[1][$userEntityKey]); + $this->assertInstanceOf('Doctrine\ORM\PersistentCollection', $result[1][$userEntityKey]->phonenumbers); + $this->assertInstanceOf('Doctrine\Tests\Models\CMS\CmsPhonenumber', $result[0][$userEntityKey]->phonenumbers[1]); // first user => 2 phonenumbers - $this->assertEquals(2, count($result[0][0]->phonenumbers)); + $this->assertEquals(2, count($result[0][$userEntityKey]->phonenumbers)); $this->assertEquals('ROMANB', $result[0]['nameUpper']); - + // second user => 1 phonenumber - $this->assertEquals(1, count($result[1][0]->phonenumbers)); + $this->assertEquals(1, count($result[1][$userEntityKey]->phonenumbers)); $this->assertEquals('JWAGE', $result[1]['nameUpper']); - $this->assertEquals(42, $result[0][0]->phonenumbers[0]->phonenumber); - $this->assertEquals(43, $result[0][0]->phonenumbers[1]->phonenumber); - $this->assertEquals(91, $result[1][0]->phonenumbers[0]->phonenumber); + $this->assertEquals(42, $result[0][$userEntityKey]->phonenumbers[0]->phonenumber); + $this->assertEquals(43, $result[0][$userEntityKey]->phonenumbers[1]->phonenumber); + $this->assertEquals(91, $result[1][$userEntityKey]->phonenumbers[0]->phonenumber); } /** - * SELECT PARTIAL u.{id, status}, - * COUNT(p.phonenumber) numPhones + * SELECT PARTIAL u.{id, status}, COUNT(p.phonenumber) numPhones * FROM User u - * JOIN u.phonenumbers p + * JOIN u.phonenumbers p * GROUP BY u.id + * + * @dataProvider provideDataForUserEntityResult */ - public function testMixedQueryNormalJoin() + public function testMixedQueryNormalJoin($userEntityKey) { $rsm = new ResultSetMapping; - $rsm->addEntityResult('Doctrine\Tests\Models\CMS\CmsUser', 'u'); + $rsm->addEntityResult('Doctrine\Tests\Models\CMS\CmsUser', 'u', $userEntityKey ?: null); $rsm->addFieldResult('u', 'u__id', 'id'); $rsm->addFieldResult('u', 'u__status', 'status'); $rsm->addScalarResult('sclr0', 'numPhones'); @@ -329,33 +368,33 @@ class ObjectHydratorTest extends HydrationTestCase $result = $hydrator->hydrateAll($stmt, $rsm, array(Query::HINT_FORCE_PARTIAL_LOAD => true)); $this->assertEquals(2, count($result)); - + $this->assertInternalType('array', $result); $this->assertInternalType('array', $result[0]); $this->assertInternalType('array', $result[1]); // first user => 2 phonenumbers $this->assertEquals(2, $result[0]['numPhones']); - + $this->assertInstanceOf('Doctrine\Tests\Models\CMS\CmsUser', $result[0][$userEntityKey]); + // second user => 1 phonenumber $this->assertEquals(1, $result[1]['numPhones']); - $this->assertInstanceOf('Doctrine\Tests\Models\CMS\CmsUser', $result[0][0]); - $this->assertInstanceOf('Doctrine\Tests\Models\CMS\CmsUser', $result[1][0]); + $this->assertInstanceOf('Doctrine\Tests\Models\CMS\CmsUser', $result[1][$userEntityKey]); } /** - * SELECT u, - * p, - * UPPER(u.name) nameUpper - * FROM User u + * SELECT u, p, UPPER(u.name) nameUpper + * FROM User u * INDEX BY u.id - * JOIN u.phonenumbers p + * JOIN u.phonenumbers p * INDEX BY p.phonenumber + * + * @dataProvider provideDataForUserEntityResult */ - public function testMixedQueryFetchJoinCustomIndex() + public function testMixedQueryFetchJoinCustomIndex($userEntityKey) { $rsm = new ResultSetMapping; - $rsm->addEntityResult('Doctrine\Tests\Models\CMS\CmsUser', 'u'); + $rsm->addEntityResult('Doctrine\Tests\Models\CMS\CmsUser', 'u', $userEntityKey ?: null); $rsm->addJoinedEntityResult( 'Doctrine\Tests\Models\CMS\CmsPhonenumber', 'p', @@ -398,7 +437,7 @@ class ObjectHydratorTest extends HydrationTestCase $result = $hydrator->hydrateAll($stmt, $rsm, array(Query::HINT_FORCE_PARTIAL_LOAD => true)); $this->assertEquals(2, count($result)); - + $this->assertInternalType('array', $result); $this->assertInternalType('array', $result[1]); $this->assertInternalType('array', $result[2]); @@ -407,43 +446,45 @@ class ObjectHydratorTest extends HydrationTestCase $this->assertEquals('ROMANB', $result[1]['nameUpper']); $this->assertEquals('JWAGE', $result[2]['nameUpper']); - $this->assertInstanceOf('Doctrine\Tests\Models\CMS\CmsUser', $result[1][0]); - $this->assertInstanceOf('Doctrine\Tests\Models\CMS\CmsUser', $result[2][0]); - $this->assertInstanceOf('Doctrine\ORM\PersistentCollection', $result[1][0]->phonenumbers); - + $this->assertInstanceOf('Doctrine\Tests\Models\CMS\CmsUser', $result[1][$userEntityKey]); + $this->assertInstanceOf('Doctrine\Tests\Models\CMS\CmsUser', $result[2][$userEntityKey]); + $this->assertInstanceOf('Doctrine\ORM\PersistentCollection', $result[1][$userEntityKey]->phonenumbers); + // first user => 2 phonenumbers. notice the custom indexing by user id - $this->assertEquals(2, count($result[1][0]->phonenumbers)); - + $this->assertEquals(2, count($result[1][$userEntityKey]->phonenumbers)); + // second user => 1 phonenumber. notice the custom indexing by user id - $this->assertEquals(1, count($result[2][0]->phonenumbers)); - + $this->assertEquals(1, count($result[2][$userEntityKey]->phonenumbers)); + // test the custom indexing of the phonenumbers - $this->assertTrue(isset($result[1][0]->phonenumbers['42'])); - $this->assertTrue(isset($result[1][0]->phonenumbers['43'])); - $this->assertTrue(isset($result[2][0]->phonenumbers['91'])); + $this->assertTrue(isset($result[1][$userEntityKey]->phonenumbers['42'])); + $this->assertTrue(isset($result[1][$userEntityKey]->phonenumbers['43'])); + $this->assertTrue(isset($result[2][$userEntityKey]->phonenumbers['91'])); } /** - * select u, p, upper(u.name) nameUpper, a - * from User u - * join u.phonenumbers p - * join u.articles a + * SELECT u, p, UPPER(u.name) nameUpper, a + * FROM User u + * JOIN u.phonenumbers p + * JOIN u.articles a + * + * @dataProvider provideDataForUserEntityResult */ - public function testMixedQueryMultipleFetchJoin() + public function testMixedQueryMultipleFetchJoin($userEntityKey) { $rsm = new ResultSetMapping; - $rsm->addEntityResult('Doctrine\Tests\Models\CMS\CmsUser', 'u'); + $rsm->addEntityResult('Doctrine\Tests\Models\CMS\CmsUser', 'u', $userEntityKey ?: null); $rsm->addJoinedEntityResult( - 'Doctrine\Tests\Models\CMS\CmsPhonenumber', - 'p', - 'u', - 'phonenumbers' + 'Doctrine\Tests\Models\CMS\CmsPhonenumber', + 'p', + 'u', + 'phonenumbers' ); $rsm->addJoinedEntityResult( - 'Doctrine\Tests\Models\CMS\CmsArticle', - 'a', - 'u', - 'articles' + 'Doctrine\Tests\Models\CMS\CmsArticle', + 'a', + 'u', + 'articles' ); $rsm->addFieldResult('u', 'u__id', 'id'); $rsm->addFieldResult('u', 'u__status', 'status'); @@ -462,15 +503,15 @@ class ObjectHydratorTest extends HydrationTestCase 'p__phonenumber' => '42', 'a__id' => '1', 'a__topic' => 'Getting things done!' - ), - array( + ), + array( 'u__id' => '1', 'u__status' => 'developer', 'sclr0' => 'ROMANB', 'p__phonenumber' => '43', 'a__id' => '1', 'a__topic' => 'Getting things done!' - ), + ), array( 'u__id' => '1', 'u__status' => 'developer', @@ -478,15 +519,15 @@ class ObjectHydratorTest extends HydrationTestCase 'p__phonenumber' => '42', 'a__id' => '2', 'a__topic' => 'ZendCon' - ), - array( + ), + array( 'u__id' => '1', 'u__status' => 'developer', 'sclr0' => 'ROMANB', 'p__phonenumber' => '43', 'a__id' => '2', 'a__topic' => 'ZendCon' - ), + ), array( 'u__id' => '2', 'u__status' => 'developer', @@ -494,70 +535,72 @@ class ObjectHydratorTest extends HydrationTestCase 'p__phonenumber' => '91', 'a__id' => '3', 'a__topic' => 'LINQ' - ), - array( + ), + array( 'u__id' => '2', 'u__status' => 'developer', 'sclr0' => 'JWAGE', 'p__phonenumber' => '91', 'a__id' => '4', 'a__topic' => 'PHP6' - ), - ); + ), + ); - $stmt = new HydratorMockStatement($resultSet); + $stmt = new HydratorMockStatement($resultSet); $hydrator = new \Doctrine\ORM\Internal\Hydration\ObjectHydrator($this->_em); - - $result = $hydrator->hydrateAll($stmt, $rsm, array(Query::HINT_FORCE_PARTIAL_LOAD => true)); + $result = $hydrator->hydrateAll($stmt, $rsm, array(Query::HINT_FORCE_PARTIAL_LOAD => true)); $this->assertEquals(2, count($result)); + $this->assertTrue(is_array($result)); $this->assertTrue(is_array($result[0])); $this->assertTrue(is_array($result[1])); - $this->assertInstanceOf('Doctrine\Tests\Models\CMS\CmsUser', $result[0][0]); - $this->assertInstanceOf('Doctrine\ORM\PersistentCollection', $result[0][0]->phonenumbers); - $this->assertInstanceOf('Doctrine\Tests\Models\CMS\CmsPhonenumber', $result[0][0]->phonenumbers[0]); - $this->assertInstanceOf('Doctrine\Tests\Models\CMS\CmsPhonenumber', $result[0][0]->phonenumbers[1]); - $this->assertInstanceOf('Doctrine\ORM\PersistentCollection', $result[0][0]->articles); - $this->assertInstanceOf('Doctrine\Tests\Models\CMS\CmsArticle', $result[0][0]->articles[0]); - $this->assertInstanceOf('Doctrine\Tests\Models\CMS\CmsArticle', $result[0][0]->articles[1]); - $this->assertInstanceOf('Doctrine\Tests\Models\CMS\CmsUser', $result[1][0]); - $this->assertInstanceOf('Doctrine\ORM\PersistentCollection', $result[1][0]->phonenumbers); - $this->assertInstanceOf('Doctrine\Tests\Models\CMS\CmsPhonenumber', $result[1][0]->phonenumbers[0]); - $this->assertInstanceOf('Doctrine\Tests\Models\CMS\CmsArticle', $result[1][0]->articles[0]); - $this->assertInstanceOf('Doctrine\Tests\Models\CMS\CmsArticle', $result[1][0]->articles[1]); + $this->assertInstanceOf('Doctrine\Tests\Models\CMS\CmsUser', $result[0][$userEntityKey]); + $this->assertInstanceOf('Doctrine\ORM\PersistentCollection', $result[0][$userEntityKey]->phonenumbers); + $this->assertInstanceOf('Doctrine\Tests\Models\CMS\CmsPhonenumber', $result[0][$userEntityKey]->phonenumbers[0]); + $this->assertInstanceOf('Doctrine\Tests\Models\CMS\CmsPhonenumber', $result[0][$userEntityKey]->phonenumbers[1]); + $this->assertInstanceOf('Doctrine\ORM\PersistentCollection', $result[0][$userEntityKey]->articles); + $this->assertInstanceOf('Doctrine\Tests\Models\CMS\CmsArticle', $result[0][$userEntityKey]->articles[0]); + $this->assertInstanceOf('Doctrine\Tests\Models\CMS\CmsArticle', $result[0][$userEntityKey]->articles[1]); + + $this->assertInstanceOf('Doctrine\Tests\Models\CMS\CmsUser', $result[1][$userEntityKey]); + $this->assertInstanceOf('Doctrine\ORM\PersistentCollection', $result[1][$userEntityKey]->phonenumbers); + $this->assertInstanceOf('Doctrine\Tests\Models\CMS\CmsPhonenumber', $result[1][$userEntityKey]->phonenumbers[0]); + $this->assertInstanceOf('Doctrine\Tests\Models\CMS\CmsArticle', $result[1][$userEntityKey]->articles[0]); + $this->assertInstanceOf('Doctrine\Tests\Models\CMS\CmsArticle', $result[1][$userEntityKey]->articles[1]); } /** - * select u, p, upper(u.name) nameUpper, a, c - * c.id, c.topic - * from User u - * join u.phonenumbers p - * join u.articles a - * left join a.comments c + * SELECT u, p, UPPER(u.name) nameUpper, a, c + * FROM User u + * JOIN u.phonenumbers p + * JOIN u.articles a + * LEFT JOIN a.comments c + * + * @dataProvider provideDataForUserEntityResult */ - public function testMixedQueryMultipleDeepMixedFetchJoin() + public function testMixedQueryMultipleDeepMixedFetchJoin($userEntityKey) { $rsm = new ResultSetMapping; - $rsm->addEntityResult('Doctrine\Tests\Models\CMS\CmsUser', 'u'); + $rsm->addEntityResult('Doctrine\Tests\Models\CMS\CmsUser', 'u', $userEntityKey ?: null); $rsm->addJoinedEntityResult( - 'Doctrine\Tests\Models\CMS\CmsPhonenumber', - 'p', - 'u', - 'phonenumbers' + 'Doctrine\Tests\Models\CMS\CmsPhonenumber', + 'p', + 'u', + 'phonenumbers' ); $rsm->addJoinedEntityResult( - 'Doctrine\Tests\Models\CMS\CmsArticle', - 'a', - 'u', - 'articles' + 'Doctrine\Tests\Models\CMS\CmsArticle', + 'a', + 'u', + 'articles' ); $rsm->addJoinedEntityResult( - 'Doctrine\Tests\Models\CMS\CmsComment', - 'c', - 'a', - 'comments' + 'Doctrine\Tests\Models\CMS\CmsComment', + 'c', + 'a', + 'comments' ); $rsm->addFieldResult('u', 'u__id', 'id'); $rsm->addFieldResult('u', 'u__status', 'status'); @@ -580,8 +623,8 @@ class ObjectHydratorTest extends HydrationTestCase 'a__topic' => 'Getting things done!', 'c__id' => '1', 'c__topic' => 'First!' - ), - array( + ), + array( 'u__id' => '1', 'u__status' => 'developer', 'sclr0' => 'ROMANB', @@ -590,7 +633,7 @@ class ObjectHydratorTest extends HydrationTestCase 'a__topic' => 'Getting things done!', 'c__id' => '1', 'c__topic' => 'First!' - ), + ), array( 'u__id' => '1', 'u__status' => 'developer', @@ -600,8 +643,8 @@ class ObjectHydratorTest extends HydrationTestCase 'a__topic' => 'ZendCon', 'c__id' => null, 'c__topic' => null - ), - array( + ), + array( 'u__id' => '1', 'u__status' => 'developer', 'sclr0' => 'ROMANB', @@ -610,7 +653,7 @@ class ObjectHydratorTest extends HydrationTestCase 'a__topic' => 'ZendCon', 'c__id' => null, 'c__topic' => null - ), + ), array( 'u__id' => '2', 'u__status' => 'developer', @@ -620,8 +663,8 @@ class ObjectHydratorTest extends HydrationTestCase 'a__topic' => 'LINQ', 'c__id' => null, 'c__topic' => null - ), - array( + ), + array( 'u__id' => '2', 'u__status' => 'developer', 'sclr0' => 'JWAGE', @@ -630,43 +673,50 @@ class ObjectHydratorTest extends HydrationTestCase 'a__topic' => 'PHP6', 'c__id' => null, 'c__topic' => null - ), - ); + ), + ); - $stmt = new HydratorMockStatement($resultSet); + $stmt = new HydratorMockStatement($resultSet); $hydrator = new \Doctrine\ORM\Internal\Hydration\ObjectHydrator($this->_em); - - $result = $hydrator->hydrateAll($stmt, $rsm, array(Query::HINT_FORCE_PARTIAL_LOAD => true)); + $result = $hydrator->hydrateAll($stmt, $rsm, array(Query::HINT_FORCE_PARTIAL_LOAD => true)); $this->assertEquals(2, count($result)); + $this->assertTrue(is_array($result)); $this->assertTrue(is_array($result[0])); $this->assertTrue(is_array($result[1])); - $this->assertInstanceOf('Doctrine\Tests\Models\CMS\CmsUser', $result[0][0]); - $this->assertInstanceOf('Doctrine\Tests\Models\CMS\CmsUser', $result[1][0]); + $this->assertInstanceOf('Doctrine\Tests\Models\CMS\CmsUser', $result[0][$userEntityKey]); + $this->assertInstanceOf('Doctrine\Tests\Models\CMS\CmsUser', $result[1][$userEntityKey]); + // phonenumbers - $this->assertInstanceOf('Doctrine\ORM\PersistentCollection', $result[0][0]->phonenumbers); - $this->assertInstanceOf('Doctrine\Tests\Models\CMS\CmsPhonenumber', $result[0][0]->phonenumbers[0]); - $this->assertInstanceOf('Doctrine\Tests\Models\CMS\CmsPhonenumber', $result[0][0]->phonenumbers[1]); - $this->assertInstanceOf('Doctrine\ORM\PersistentCollection', $result[1][0]->phonenumbers); - $this->assertInstanceOf('Doctrine\Tests\Models\CMS\CmsPhonenumber', $result[1][0]->phonenumbers[0]); + $this->assertInstanceOf('Doctrine\ORM\PersistentCollection', $result[0][$userEntityKey]->phonenumbers); + $this->assertInstanceOf('Doctrine\Tests\Models\CMS\CmsPhonenumber', $result[0][$userEntityKey]->phonenumbers[0]); + $this->assertInstanceOf('Doctrine\Tests\Models\CMS\CmsPhonenumber', $result[0][$userEntityKey]->phonenumbers[1]); + + $this->assertInstanceOf('Doctrine\ORM\PersistentCollection', $result[1][$userEntityKey]->phonenumbers); + $this->assertInstanceOf('Doctrine\Tests\Models\CMS\CmsPhonenumber', $result[1][$userEntityKey]->phonenumbers[0]); + // articles - $this->assertInstanceOf('Doctrine\ORM\PersistentCollection', $result[0][0]->articles); - $this->assertInstanceOf('Doctrine\Tests\Models\CMS\CmsArticle', $result[0][0]->articles[0]); - $this->assertInstanceOf('Doctrine\Tests\Models\CMS\CmsArticle', $result[0][0]->articles[1]); - $this->assertInstanceOf('Doctrine\Tests\Models\CMS\CmsArticle', $result[1][0]->articles[0]); - $this->assertInstanceOf('Doctrine\Tests\Models\CMS\CmsArticle', $result[1][0]->articles[1]); + $this->assertInstanceOf('Doctrine\ORM\PersistentCollection', $result[0][$userEntityKey]->articles); + $this->assertInstanceOf('Doctrine\Tests\Models\CMS\CmsArticle', $result[0][$userEntityKey]->articles[0]); + $this->assertInstanceOf('Doctrine\Tests\Models\CMS\CmsArticle', $result[0][$userEntityKey]->articles[1]); + + $this->assertInstanceOf('Doctrine\Tests\Models\CMS\CmsArticle', $result[1][$userEntityKey]->articles[0]); + $this->assertInstanceOf('Doctrine\Tests\Models\CMS\CmsArticle', $result[1][$userEntityKey]->articles[1]); + // article comments - $this->assertInstanceOf('Doctrine\ORM\PersistentCollection', $result[0][0]->articles[0]->comments); - $this->assertInstanceOf('Doctrine\Tests\Models\CMS\CmsComment', $result[0][0]->articles[0]->comments[0]); + $this->assertInstanceOf('Doctrine\ORM\PersistentCollection', $result[0][$userEntityKey]->articles[0]->comments); + $this->assertInstanceOf('Doctrine\Tests\Models\CMS\CmsComment', $result[0][$userEntityKey]->articles[0]->comments[0]); + // empty comment collections - $this->assertInstanceOf('Doctrine\ORM\PersistentCollection', $result[0][0]->articles[1]->comments); - $this->assertEquals(0, count($result[0][0]->articles[1]->comments)); - $this->assertInstanceOf('Doctrine\ORM\PersistentCollection', $result[1][0]->articles[0]->comments); - $this->assertEquals(0, count($result[1][0]->articles[0]->comments)); - $this->assertInstanceOf('Doctrine\ORM\PersistentCollection', $result[1][0]->articles[1]->comments); - $this->assertEquals(0, count($result[1][0]->articles[1]->comments)); + $this->assertInstanceOf('Doctrine\ORM\PersistentCollection', $result[0][$userEntityKey]->articles[1]->comments); + $this->assertEquals(0, count($result[0][$userEntityKey]->articles[1]->comments)); + + $this->assertInstanceOf('Doctrine\ORM\PersistentCollection', $result[1][$userEntityKey]->articles[0]->comments); + $this->assertEquals(0, count($result[1][$userEntityKey]->articles[0]->comments)); + $this->assertInstanceOf('Doctrine\ORM\PersistentCollection', $result[1][$userEntityKey]->articles[1]->comments); + $this->assertEquals(0, count($result[1][$userEntityKey]->articles[1]->comments)); } /** @@ -692,10 +742,10 @@ class ObjectHydratorTest extends HydrationTestCase $rsm = new ResultSetMapping; $rsm->addEntityResult('Doctrine\Tests\Models\Forum\ForumCategory', 'c'); $rsm->addJoinedEntityResult( - 'Doctrine\Tests\Models\Forum\ForumBoard', - 'b', - 'c', - 'boards' + 'Doctrine\Tests\Models\Forum\ForumBoard', + 'b', + 'c', + 'boards' ); $rsm->addFieldResult('c', 'c__id', 'id'); $rsm->addFieldResult('c', 'c__position', 'position'); @@ -712,15 +762,15 @@ class ObjectHydratorTest extends HydrationTestCase 'b__id' => '1', 'b__position' => '0', //'b__category_id' => '1' - ), - array( + ), + array( 'c__id' => '2', 'c__position' => '0', 'c__name' => 'Second', 'b__id' => '2', 'b__position' => '0', //'b__category_id' => '2' - ), + ), array( 'c__id' => '1', 'c__position' => '0', @@ -728,50 +778,62 @@ class ObjectHydratorTest extends HydrationTestCase 'b__id' => '3', 'b__position' => '1', //'b__category_id' => '1' - ), - array( + ), + array( 'c__id' => '1', 'c__position' => '0', 'c__name' => 'First', 'b__id' => '4', 'b__position' => '2', //'b__category_id' => '1' - ) - ); + ) + ); - $stmt = new HydratorMockStatement($resultSet); + $stmt = new HydratorMockStatement($resultSet); $hydrator = new \Doctrine\ORM\Internal\Hydration\ObjectHydrator($this->_em); - - $result = $hydrator->hydrateAll($stmt, $rsm, array(Query::HINT_FORCE_PARTIAL_LOAD => true)); + $result = $hydrator->hydrateAll($stmt, $rsm, array(Query::HINT_FORCE_PARTIAL_LOAD => true)); $this->assertEquals(2, count($result)); + $this->assertInstanceOf('Doctrine\Tests\Models\Forum\ForumCategory', $result[0]); $this->assertInstanceOf('Doctrine\Tests\Models\Forum\ForumCategory', $result[1]); + $this->assertTrue($result[0] !== $result[1]); + $this->assertEquals(1, $result[0]->getId()); $this->assertEquals(2, $result[1]->getId()); + $this->assertTrue(isset($result[0]->boards)); $this->assertEquals(3, count($result[0]->boards)); + $this->assertTrue(isset($result[1]->boards)); $this->assertEquals(1, count($result[1]->boards)); } - public function testChainedJoinWithEmptyCollections() + /** + * SELECT PARTIAL u.{id, status}, PARTIAL a.{id, topic}, PARTIAL c.{id, topic} + * FROM Doctrine\Tests\Models\CMS\CmsUser u + * LEFT JOIN u.articles a + * LEFT JOIN a.comments c + * + * @dataProvider provideDataForUserEntityResult + */ + public function testChainedJoinWithEmptyCollections($userEntityKey) { $rsm = new ResultSetMapping; - $rsm->addEntityResult('Doctrine\Tests\Models\CMS\CmsUser', 'u'); + $rsm->addEntityResult('Doctrine\Tests\Models\CMS\CmsUser', 'u', $userEntityKey ?: null); $rsm->addJoinedEntityResult( - 'Doctrine\Tests\Models\CMS\CmsArticle', - 'a', - 'u', - 'articles' + 'Doctrine\Tests\Models\CMS\CmsArticle', + 'a', + 'u', + 'articles' ); $rsm->addJoinedEntityResult( - 'Doctrine\Tests\Models\CMS\CmsComment', - 'c', - 'a', - 'comments' + 'Doctrine\Tests\Models\CMS\CmsComment', + 'c', + 'a', + 'comments' ); $rsm->addFieldResult('u', 'u__id', 'id'); $rsm->addFieldResult('u', 'u__status', 'status'); @@ -790,38 +852,43 @@ class ObjectHydratorTest extends HydrationTestCase 'a__topic' => null, 'c__id' => null, 'c__topic' => null - ), - array( + ), + array( 'u__id' => '2', 'u__status' => 'developer', 'a__id' => null, 'a__topic' => null, 'c__id' => null, 'c__topic' => null - ), - ); + ), + ); - $stmt = new HydratorMockStatement($resultSet); + $stmt = new HydratorMockStatement($resultSet); $hydrator = new \Doctrine\ORM\Internal\Hydration\ObjectHydrator($this->_em); - - $result = $hydrator->hydrateAll($stmt, $rsm, array(Query::HINT_FORCE_PARTIAL_LOAD => true)); + $result = $hydrator->hydrateAll($stmt, $rsm, array(Query::HINT_FORCE_PARTIAL_LOAD => true)); $this->assertEquals(2, count($result)); + $this->assertInstanceOf('Doctrine\Tests\Models\CMS\CmsUser', $result[0]); $this->assertInstanceOf('Doctrine\Tests\Models\CMS\CmsUser', $result[1]); + $this->assertEquals(0, $result[0]->articles->count()); $this->assertEquals(0, $result[1]->articles->count()); } /** - * DQL: select partial u.{id,status}, a.id, a.topic, c.id as cid, c.topic as ctopic from CmsUser u left join u.articles a left join a.comments c + * SELECT PARTIAL u.{id,status}, a.id, a.topic, c.id as cid, c.topic as ctopic + * FROM CmsUser u + * LEFT JOIN u.articles a + * LEFT JOIN a.comments c * * @group bubu + * @dataProvider provideDataForUserEntityResult */ - /*public function testChainedJoinWithScalars() + /*public function testChainedJoinWithScalars($userEntityKey) { $rsm = new ResultSetMapping; - $rsm->addEntityResult('Doctrine\Tests\Models\CMS\CmsUser', 'u'); + $rsm->addEntityResult('Doctrine\Tests\Models\CMS\CmsUser', 'u', $userEntityKey ?: null); $rsm->addFieldResult('u', 'u__id', 'id'); $rsm->addFieldResult('u', 'u__status', 'status'); $rsm->addScalarResult('a__id', 'id'); @@ -839,7 +906,7 @@ class ObjectHydratorTest extends HydrationTestCase 'a__topic' => 'The First', 'c__id' => '1', 'c__topic' => 'First Comment' - ), + ), array( 'u__id' => '1', 'u__status' => 'developer', @@ -847,51 +914,39 @@ class ObjectHydratorTest extends HydrationTestCase 'a__topic' => 'The First', 'c__id' => '2', 'c__topic' => 'Second Comment' - ), - array( + ), + array( 'u__id' => '1', 'u__status' => 'developer', 'a__id' => '42', 'a__topic' => 'The Answer', 'c__id' => null, 'c__topic' => null - ), - ); + ), + ); - $stmt = new HydratorMockStatement($resultSet); + $stmt = new HydratorMockStatement($resultSet); $hydrator = new \Doctrine\ORM\Internal\Hydration\ObjectHydrator($this->_em); + $result = $hydrator->hydrateAll($stmt, $rsm, array(Query::HINT_FORCE_PARTIAL_LOAD => true)); - $result = $hydrator->hydrateAll($stmt, $rsm, array(Query::HINT_FORCE_PARTIAL_LOAD => true)); + \Doctrine\Common\Util\Debug::dump($result, 3); - $this->assertEquals(3, count($result)); + $this->assertEquals(1, count($result)); - $this->assertInstanceOf('Doctrine\Tests\Models\CMS\CmsUser', $result[0][0]); // User object - $this->assertEquals(1, $result[0]['id']); + $this->assertInstanceOf('Doctrine\Tests\Models\CMS\CmsUser', $result[0][$userEntityKey]); // User object + $this->assertEquals(42, $result[0]['id']); $this->assertEquals('The First', $result[0]['topic']); $this->assertEquals(1, $result[0]['cid']); $this->assertEquals('First Comment', $result[0]['ctopic']); - - $this->assertInstanceOf('Doctrine\Tests\Models\CMS\CmsUser', $result[1][0]); // Same User object - $this->assertEquals(1, $result[1]['id']); // duplicated - $this->assertEquals('The First', $result[1]['topic']); // duplicated - $this->assertEquals(2, $result[1]['cid']); - $this->assertEquals('Second Comment', $result[1]['ctopic']); - - $this->assertInstanceOf('Doctrine\Tests\Models\CMS\CmsUser', $result[2][0]); // Same User object - $this->assertEquals(42, $result[2]['id']); - $this->assertEquals('The Answer', $result[2]['topic']); - $this->assertNull($result[2]['cid']); - $this->assertNull($result[2]['ctopic']); - - $this->assertTrue($result[0][0] === $result[1][0]); - $this->assertTrue($result[1][0] === $result[2][0]); - $this->assertTrue($result[0][0] === $result[2][0]); }*/ - public function testResultIteration() + /** + * @dataProvider provideDataForUserEntityResult + */ + public function testResultIteration($userEntityKey) { $rsm = new ResultSetMapping; - $rsm->addEntityResult('Doctrine\Tests\Models\CMS\CmsUser', 'u'); + $rsm->addEntityResult('Doctrine\Tests\Models\CMS\CmsUser', 'u', $userEntityKey ?: null); $rsm->addFieldResult('u', 'u__id', 'id'); $rsm->addFieldResult('u', 'u__name', 'name'); @@ -900,23 +955,22 @@ class ObjectHydratorTest extends HydrationTestCase array( 'u__id' => '1', 'u__name' => 'romanb' - ), + ), array( 'u__id' => '2', 'u__name' => 'jwage' - ) - ); - - - $stmt = new HydratorMockStatement($resultSet); - $hydrator = new \Doctrine\ORM\Internal\Hydration\ObjectHydrator($this->_em); + ) + ); + $stmt = new HydratorMockStatement($resultSet); + $hydrator = new \Doctrine\ORM\Internal\Hydration\ObjectHydrator($this->_em); $iterableResult = $hydrator->iterate($stmt, $rsm, array(Query::HINT_FORCE_PARTIAL_LOAD => true)); + $rowNum = 0; - $rowNum = 0; while (($row = $iterableResult->next()) !== false) { $this->assertEquals(1, count($row)); $this->assertInstanceOf('Doctrine\Tests\Models\CMS\CmsUser', $row[0]); + if ($rowNum == 0) { $this->assertEquals(1, $row[0]->id); $this->assertEquals('romanb', $row[0]->name); @@ -924,21 +978,24 @@ class ObjectHydratorTest extends HydrationTestCase $this->assertEquals(2, $row[0]->id); $this->assertEquals('jwage', $row[0]->name); } + ++$rowNum; } } /** - * This issue tests if, with multiple joined multiple-valued collections the hydration is done correctly. + * Checks if multiple joined multiple-valued collections is hydrated correctly. * - * User x Phonenumbers x Groups blow up the resultset quite a bit, however the hydration should correctly assemble those. + * SELECT PARTIAL u.{id, status}, PARTIAL g.{id, name}, PARTIAL p.{phonenumber} + * FROM Doctrine\Tests\Models\CMS\CmsUser u * * @group DDC-809 + * @dataProvider provideDataForUserEntityResult */ - public function testManyToManyHydration() + public function testManyToManyHydration($userEntityKey) { $rsm = new ResultSetMapping; - $rsm->addEntityResult('Doctrine\Tests\Models\CMS\CmsUser', 'u'); + $rsm->addEntityResult('Doctrine\Tests\Models\CMS\CmsUser', 'u', $userEntityKey ?: null); $rsm->addFieldResult('u', 'u__id', 'id'); $rsm->addFieldResult('u', 'u__name', 'name'); $rsm->addJoinedEntityResult('Doctrine\Tests\Models\CMS\CmsGroup', 'g', 'u', 'groups'); @@ -955,106 +1012,112 @@ class ObjectHydratorTest extends HydrationTestCase 'g__id' => '3', 'g__name' => 'TestGroupB', 'p__phonenumber' => 1111, - ), + ), array( 'u__id' => '1', 'u__name' => 'romanb', 'g__id' => '5', 'g__name' => 'TestGroupD', 'p__phonenumber' => 1111, - ), + ), array( 'u__id' => '1', 'u__name' => 'romanb', 'g__id' => '3', 'g__name' => 'TestGroupB', 'p__phonenumber' => 2222, - ), + ), array( 'u__id' => '1', 'u__name' => 'romanb', 'g__id' => '5', 'g__name' => 'TestGroupD', 'p__phonenumber' => 2222, - ), + ), array( 'u__id' => '2', 'u__name' => 'jwage', 'g__id' => '2', 'g__name' => 'TestGroupA', 'p__phonenumber' => 3333, - ), + ), array( 'u__id' => '2', 'u__name' => 'jwage', 'g__id' => '3', 'g__name' => 'TestGroupB', 'p__phonenumber' => 3333, - ), + ), array( 'u__id' => '2', 'u__name' => 'jwage', 'g__id' => '4', 'g__name' => 'TestGroupC', 'p__phonenumber' => 3333, - ), + ), array( 'u__id' => '2', 'u__name' => 'jwage', 'g__id' => '5', 'g__name' => 'TestGroupD', 'p__phonenumber' => 3333, - ), + ), array( 'u__id' => '2', 'u__name' => 'jwage', 'g__id' => '2', 'g__name' => 'TestGroupA', 'p__phonenumber' => 4444, - ), + ), array( 'u__id' => '2', 'u__name' => 'jwage', 'g__id' => '3', 'g__name' => 'TestGroupB', 'p__phonenumber' => 4444, - ), + ), array( 'u__id' => '2', 'u__name' => 'jwage', 'g__id' => '4', 'g__name' => 'TestGroupC', 'p__phonenumber' => 4444, - ), + ), array( 'u__id' => '2', 'u__name' => 'jwage', 'g__id' => '5', 'g__name' => 'TestGroupD', 'p__phonenumber' => 4444, - ), - ); + ), + ); - $stmt = new HydratorMockStatement($resultSet); + $stmt = new HydratorMockStatement($resultSet); $hydrator = new \Doctrine\ORM\Internal\Hydration\ObjectHydrator($this->_em); - - $result = $hydrator->hydrateAll($stmt, $rsm, array(Query::HINT_FORCE_PARTIAL_LOAD => true)); + $result = $hydrator->hydrateAll($stmt, $rsm, array(Query::HINT_FORCE_PARTIAL_LOAD => true)); $this->assertEquals(2, count($result)); + $this->assertContainsOnly('Doctrine\Tests\Models\CMS\CmsUser', $result); + $this->assertEquals(2, count($result[0]->groups)); $this->assertEquals(2, count($result[0]->phonenumbers)); + $this->assertEquals(4, count($result[1]->groups)); $this->assertEquals(2, count($result[1]->phonenumbers)); } /** + * SELECT PARTIAL u.{id, status}, UPPER(u.name) as nameUpper + * FROM Doctrine\Tests\Models\CMS\CmsUser u + * * @group DDC-1358 + * @dataProvider provideDataForUserEntityResult */ - public function testMissingIdForRootEntity() + public function testMissingIdForRootEntity($userEntityKey) { $rsm = new ResultSetMapping; - $rsm->addEntityResult('Doctrine\Tests\Models\CMS\CmsUser', 'u'); + $rsm->addEntityResult('Doctrine\Tests\Models\CMS\CmsUser', 'u', $userEntityKey ?: null); $rsm->addFieldResult('u', 'u__id', 'id'); $rsm->addFieldResult('u', 'u__status', 'status'); $rsm->addScalarResult('sclr0', 'nameUpper'); @@ -1066,28 +1129,27 @@ class ObjectHydratorTest extends HydrationTestCase 'u__id' => '1', 'u__status' => 'developer', 'sclr0' => 'ROMANB', - ), + ), array( 'u__id' => null, 'u__status' => null, 'sclr0' => 'ROMANB', - ), + ), array( 'u__id' => '2', 'u__status' => 'developer', 'sclr0' => 'JWAGE', - ), + ), array( 'u__id' => null, 'u__status' => null, 'sclr0' => 'JWAGE', - ), - ); + ), + ); - $stmt = new HydratorMockStatement($resultSet); + $stmt = new HydratorMockStatement($resultSet); $hydrator = new \Doctrine\ORM\Internal\Hydration\ObjectHydrator($this->_em); - - $result = $hydrator->hydrateAll($stmt, $rsm, array(Query::HINT_FORCE_PARTIAL_LOAD => true)); + $result = $hydrator->hydrateAll($stmt, $rsm, array(Query::HINT_FORCE_PARTIAL_LOAD => true)); $this->assertEquals(4, count($result), "Should hydrate four results."); @@ -1096,25 +1158,30 @@ class ObjectHydratorTest extends HydrationTestCase $this->assertEquals('JWAGE', $result[2]['nameUpper']); $this->assertEquals('JWAGE', $result[3]['nameUpper']); - $this->assertInstanceOf('Doctrine\Tests\Models\CMS\CmsUser', $result[0][0]); - $this->assertNull($result[1][0]); - $this->assertInstanceOf('Doctrine\Tests\Models\CMS\CmsUser', $result[2][0]); - $this->assertNull($result[3][0]); + $this->assertInstanceOf('Doctrine\Tests\Models\CMS\CmsUser', $result[0][$userEntityKey]); + $this->assertNull($result[1][$userEntityKey]); + + $this->assertInstanceOf('Doctrine\Tests\Models\CMS\CmsUser', $result[2][$userEntityKey]); + $this->assertNull($result[3][$userEntityKey]); } /** + * SELECT PARTIAL u.{id, status}, PARTIAL p.{phonenumber}, UPPER(u.name) AS nameUpper + * FROM Doctrine\Tests\Models\CMS\CmsUser u + * LEFT JOIN u.phonenumbers u + * * @group DDC-1358 - * @return void + * @dataProvider provideDataForUserEntityResult */ - public function testMissingIdForCollectionValuedChildEntity() + public function testMissingIdForCollectionValuedChildEntity($userEntityKey) { $rsm = new ResultSetMapping; - $rsm->addEntityResult('Doctrine\Tests\Models\CMS\CmsUser', 'u'); + $rsm->addEntityResult('Doctrine\Tests\Models\CMS\CmsUser', 'u', $userEntityKey ?: null); $rsm->addJoinedEntityResult( - 'Doctrine\Tests\Models\CMS\CmsPhonenumber', - 'p', - 'u', - 'phonenumbers' + 'Doctrine\Tests\Models\CMS\CmsPhonenumber', + 'p', + 'u', + 'phonenumbers' ); $rsm->addFieldResult('u', 'u__id', 'id'); $rsm->addFieldResult('u', 'u__status', 'status'); @@ -1129,49 +1196,54 @@ class ObjectHydratorTest extends HydrationTestCase 'u__status' => 'developer', 'sclr0' => 'ROMANB', 'p__phonenumber' => '42', - ), + ), array( 'u__id' => '1', 'u__status' => 'developer', 'sclr0' => 'ROMANB', 'p__phonenumber' => null - ), + ), array( 'u__id' => '2', 'u__status' => 'developer', 'sclr0' => 'JWAGE', 'p__phonenumber' => '91' - ), + ), array( 'u__id' => '2', 'u__status' => 'developer', 'sclr0' => 'JWAGE', 'p__phonenumber' => null - ) - ); + ) + ); - $stmt = new HydratorMockStatement($resultSet); + $stmt = new HydratorMockStatement($resultSet); $hydrator = new \Doctrine\ORM\Internal\Hydration\ObjectHydrator($this->_em); - - $result = $hydrator->hydrateAll($stmt, $rsm, array(Query::HINT_FORCE_PARTIAL_LOAD => true)); + $result = $hydrator->hydrateAll($stmt, $rsm, array(Query::HINT_FORCE_PARTIAL_LOAD => true)); $this->assertEquals(2, count($result)); - $this->assertEquals(1, $result[0][0]->phonenumbers->count()); - $this->assertEquals(1, $result[1][0]->phonenumbers->count()); + + $this->assertEquals(1, $result[0][$userEntityKey]->phonenumbers->count()); + $this->assertEquals(1, $result[1][$userEntityKey]->phonenumbers->count()); } /** + * SELECT PARTIAL u.{id, status}, PARTIAL a.{id, city}, UPPER(u.name) AS nameUpper + * FROM Doctrine\Tests\Models\CMS\CmsUser u + * JOIN u.address a + * * @group DDC-1358 + * @dataProvider provideDataForUserEntityResult */ - public function testMissingIdForSingleValuedChildEntity() + public function testMissingIdForSingleValuedChildEntity($userEntityKey) { $rsm = new ResultSetMapping; - $rsm->addEntityResult('Doctrine\Tests\Models\CMS\CmsUser', 'u'); + $rsm->addEntityResult('Doctrine\Tests\Models\CMS\CmsUser', 'u', $userEntityKey ?: null); $rsm->addJoinedEntityResult( - 'Doctrine\Tests\Models\CMS\CmsAddress', - 'a', - 'u', - 'address' + 'Doctrine\Tests\Models\CMS\CmsAddress', + 'a', + 'u', + 'address' ); $rsm->addFieldResult('u', 'u__id', 'id'); $rsm->addFieldResult('u', 'u__status', 'status'); @@ -1199,23 +1271,28 @@ class ObjectHydratorTest extends HydrationTestCase ), ); - $stmt = new HydratorMockStatement($resultSet); + $stmt = new HydratorMockStatement($resultSet); $hydrator = new \Doctrine\ORM\Internal\Hydration\ObjectHydrator($this->_em); - - $result = $hydrator->hydrateAll($stmt, $rsm, array(Query::HINT_FORCE_PARTIAL_LOAD => true)); + $result = $hydrator->hydrateAll($stmt, $rsm, array(Query::HINT_FORCE_PARTIAL_LOAD => true)); $this->assertEquals(2, count($result)); - $this->assertInstanceOf('Doctrine\Tests\Models\CMS\CmsAddress', $result[0][0]->address); - $this->assertNull($result[1][0]->address); + + $this->assertInstanceOf('Doctrine\Tests\Models\CMS\CmsAddress', $result[0][$userEntityKey]->address); + $this->assertNull($result[1][$userEntityKey]->address); } /** + * SELECT PARTIAL u.{id, status}, UPPER(u.name) AS nameUpper + * FROM Doctrine\Tests\Models\CMS\CmsUser u + * INDEX BY u.id + * * @group DDC-1385 + * @dataProvider provideDataForUserEntityResult */ - public function testIndexByAndMixedResult() + public function testIndexByAndMixedResult($userEntityKey) { $rsm = new ResultSetMapping; - $rsm->addEntityResult('Doctrine\Tests\Models\CMS\CmsUser', 'u'); + $rsm->addEntityResult('Doctrine\Tests\Models\CMS\CmsUser', 'u', $userEntityKey ?: null); $rsm->addFieldResult('u', 'u__id', 'id'); $rsm->addFieldResult('u', 'u__status', 'status'); $rsm->addScalarResult('sclr0', 'nameUpper'); @@ -1236,24 +1313,30 @@ class ObjectHydratorTest extends HydrationTestCase ), ); - $stmt = new HydratorMockStatement($resultSet); + $stmt = new HydratorMockStatement($resultSet); $hydrator = new \Doctrine\ORM\Internal\Hydration\ObjectHydrator($this->_em); - - $result = $hydrator->hydrateAll($stmt, $rsm, array(Query::HINT_FORCE_PARTIAL_LOAD => true)); + $result = $hydrator->hydrateAll($stmt, $rsm, array(Query::HINT_FORCE_PARTIAL_LOAD => true)); $this->assertEquals(2, count($result)); + $this->assertTrue(isset($result[1])); - $this->assertEquals(1, $result[1][0]->id); + $this->assertEquals(1, $result[1][$userEntityKey]->id); + $this->assertTrue(isset($result[2])); - $this->assertEquals(2, $result[2][0]->id); + $this->assertEquals(2, $result[2][$userEntityKey]->id); } /** + * SELECT UPPER(u.name) AS nameUpper + * FROM Doctrine\Tests\Models\CMS\CmsUser u + * * @group DDC-1385 + * @dataProvider provideDataForUserEntityResult */ - public function testIndexByScalarsOnly() + public function testIndexByScalarsOnly($userEntityKey) { $rsm = new ResultSetMapping; + $rsm->addEntityResult('Doctrine\Tests\Models\CMS\CmsUser', 'u', $userEntityKey ?: null); $rsm->addScalarResult('sclr0', 'nameUpper'); $rsm->addIndexByScalar('sclr0'); @@ -1270,9 +1353,14 @@ class ObjectHydratorTest extends HydrationTestCase $stmt = new HydratorMockStatement($resultSet); $hydrator = new \Doctrine\ORM\Internal\Hydration\ObjectHydrator($this->_em); - $result = $hydrator->hydrateAll($stmt, $rsm, array(Query::HINT_FORCE_PARTIAL_LOAD => true)); - $this->assertEquals(array('ROMANB' => array('nameUpper' => 'ROMANB'), 'JWAGE' => array('nameUpper' => 'JWAGE')), $result); + $this->assertEquals( + array( + 'ROMANB' => array('nameUpper' => 'ROMANB'), + 'JWAGE' => array('nameUpper' => 'JWAGE') + ), + $result + ); } } From 0632b3749272d9b0784f4d3e2479baba97410062 Mon Sep 17 00:00:00 2001 From: "Fabio B. Silva" Date: Mon, 14 Nov 2011 13:15:43 -0200 Subject: [PATCH 103/135] fix default field type --- lib/Doctrine/ORM/Mapping/Driver/XmlDriver.php | 12 ++- .../ORM/Mapping/Driver/YamlDriver.php | 35 ++++----- .../DDC1476EntityWithDefaultFieldType.php | 76 +++++++++++++++++++ .../ORM/Mapping/AbstractMappingDriverTest.php | 45 +++++++++++ ...1476.DDC1476EntityWithDefaultFieldType.php | 12 +++ ....DDC1476EntityWithDefaultFieldType.dcm.xml | 15 ++++ ....DDC1476EntityWithDefaultFieldType.dcm.yml | 8 ++ 7 files changed, 183 insertions(+), 20 deletions(-) create mode 100644 tests/Doctrine/Tests/Models/DDC1476/DDC1476EntityWithDefaultFieldType.php create mode 100644 tests/Doctrine/Tests/ORM/Mapping/php/Doctrine.Tests.Models.DDC1476.DDC1476EntityWithDefaultFieldType.php create mode 100644 tests/Doctrine/Tests/ORM/Mapping/xml/Doctrine.Tests.Models.DDC1476.DDC1476EntityWithDefaultFieldType.dcm.xml create mode 100644 tests/Doctrine/Tests/ORM/Mapping/yaml/Doctrine.Tests.Models.DDC1476.DDC1476EntityWithDefaultFieldType.dcm.yml diff --git a/lib/Doctrine/ORM/Mapping/Driver/XmlDriver.php b/lib/Doctrine/ORM/Mapping/Driver/XmlDriver.php index fd6bf081a..2b7657763 100644 --- a/lib/Doctrine/ORM/Mapping/Driver/XmlDriver.php +++ b/lib/Doctrine/ORM/Mapping/Driver/XmlDriver.php @@ -166,9 +166,12 @@ class XmlDriver extends AbstractFileDriver foreach ($xmlRoot->field as $fieldMapping) { $mapping = array( 'fieldName' => (string)$fieldMapping['name'], - 'type' => (string)$fieldMapping['type'] ); + if (isset($fieldMapping['type'])) { + $mapping['type'] = (string)$fieldMapping['type']; + } + if (isset($fieldMapping['column'])) { $mapping['columnName'] = (string)$fieldMapping['column']; } @@ -219,9 +222,12 @@ class XmlDriver extends AbstractFileDriver $mapping = array( 'id' => true, - 'fieldName' => (string)$idElement['name'], - 'type' => (string)$idElement['type'] + 'fieldName' => (string)$idElement['name'] ); + + if (isset($fieldMapping['type'])) { + $mapping['type'] = (string)$idElement['type']; + } if (isset($idElement['column'])) { $mapping['columnName'] = (string)$idElement['column']; diff --git a/lib/Doctrine/ORM/Mapping/Driver/YamlDriver.php b/lib/Doctrine/ORM/Mapping/Driver/YamlDriver.php index a2c5402b6..5979ae73f 100644 --- a/lib/Doctrine/ORM/Mapping/Driver/YamlDriver.php +++ b/lib/Doctrine/ORM/Mapping/Driver/YamlDriver.php @@ -165,15 +165,14 @@ class YamlDriver extends AbstractFileDriver continue; } - if (!isset($idElement['type'])) { - throw MappingException::propertyTypeIsRequired($className, $name); - } - $mapping = array( 'id' => true, - 'fieldName' => $name, - 'type' => $idElement['type'] + 'fieldName' => $name ); + + if (isset($idElement['type'])) { + $mapping['type'] = $idElement['type']; + } if (isset($idElement['column'])) { $mapping['columnName'] = $idElement['column']; @@ -201,19 +200,21 @@ class YamlDriver extends AbstractFileDriver // Evaluate fields if (isset($element['fields'])) { foreach ($element['fields'] as $name => $fieldMapping) { - if (!isset($fieldMapping['type'])) { - throw MappingException::propertyTypeIsRequired($className, $name); - } - - $e = explode('(', $fieldMapping['type']); - $fieldMapping['type'] = $e[0]; - if (isset($e[1])) { - $fieldMapping['length'] = substr($e[1], 0, strlen($e[1]) - 1); - } + $mapping = array( - 'fieldName' => $name, - 'type' => $fieldMapping['type'] + 'fieldName' => $name ); + + if (isset($fieldMapping['type'])) { + $e = explode('(', $fieldMapping['type']); + $fieldMapping['type'] = $e[0]; + $mapping['type'] = $fieldMapping['type']; + + if (isset($e[1])) { + $fieldMapping['length'] = substr($e[1], 0, strlen($e[1]) - 1); + } + } + if (isset($fieldMapping['id'])) { $mapping['id'] = true; if (isset($fieldMapping['generator']['strategy'])) { diff --git a/tests/Doctrine/Tests/Models/DDC1476/DDC1476EntityWithDefaultFieldType.php b/tests/Doctrine/Tests/Models/DDC1476/DDC1476EntityWithDefaultFieldType.php new file mode 100644 index 000000000..f06199b4d --- /dev/null +++ b/tests/Doctrine/Tests/Models/DDC1476/DDC1476EntityWithDefaultFieldType.php @@ -0,0 +1,76 @@ +. + */ + +namespace Doctrine\Tests\Models\DDC1476; + +/** + * @Entity() + */ +class DDC1476EntityWithDefaultFieldType +{ + + /** + * @Id + * @Column() + * @GeneratedValue("NONE") + */ + protected $id; + + /** @column() */ + protected $name; + + /** + * @return integer + */ + public function getId() + { + return $this->id; + } + + /** + * @return string + */ + public function getName() + { + return $this->name; + } + + /** + * @param string $name + */ + public function setName($name) + { + $this->name = $name; + } + + public static function loadMetadata(\Doctrine\ORM\Mapping\ClassMetadataInfo $metadata) + { + $metadata->mapField(array( + 'id' => true, + 'fieldName' => 'id', + )); + $metadata->mapField(array( + 'fieldName' => 'name', + )); + + $metadata->setIdGeneratorType(\Doctrine\ORM\Mapping\ClassMetadataInfo::GENERATOR_TYPE_NONE); + } + +} \ No newline at end of file diff --git a/tests/Doctrine/Tests/ORM/Mapping/AbstractMappingDriverTest.php b/tests/Doctrine/Tests/ORM/Mapping/AbstractMappingDriverTest.php index 98c43a5b9..c95af376e 100644 --- a/tests/Doctrine/Tests/ORM/Mapping/AbstractMappingDriverTest.php +++ b/tests/Doctrine/Tests/ORM/Mapping/AbstractMappingDriverTest.php @@ -327,6 +327,51 @@ abstract class AbstractMappingDriverTest extends \Doctrine\Tests\OrmTestCase $em->getRepository("Doctrine\Tests\Models\DDC869\DDC869ChequePayment")); $this->assertTrue($em->getRepository("Doctrine\Tests\Models\DDC869\DDC869ChequePayment")->isTrue()); } + + /** + * @group DDC-1476 + */ + public function testDefaultFieldType() + { + $driver = $this->_loadDriver(); + $em = $this->_getTestEntityManager(); + $factory = new \Doctrine\ORM\Mapping\ClassMetadataFactory(); + + $em->getConfiguration()->setMetadataDriverImpl($driver); + $factory->setEntityManager($em); + + + $class = $factory->getMetadataFor('Doctrine\Tests\Models\DDC1476\DDC1476EntityWithDefaultFieldType'); + + + $this->assertArrayHasKey('id', $class->fieldMappings); + $this->assertArrayHasKey('name', $class->fieldMappings); + + + $this->assertArrayHasKey('type', $class->fieldMappings['id']); + $this->assertArrayHasKey('type', $class->fieldMappings['name']); + + $this->assertEquals('string', $class->fieldMappings['id']['type']); + $this->assertEquals('string', $class->fieldMappings['name']['type']); + + + + $this->assertArrayHasKey('fieldName', $class->fieldMappings['id']); + $this->assertArrayHasKey('fieldName', $class->fieldMappings['name']); + + $this->assertEquals('id', $class->fieldMappings['id']['fieldName']); + $this->assertEquals('name', $class->fieldMappings['name']['fieldName']); + + + + $this->assertArrayHasKey('columnName', $class->fieldMappings['id']); + $this->assertArrayHasKey('columnName', $class->fieldMappings['name']); + + $this->assertEquals('id', $class->fieldMappings['id']['columnName']); + $this->assertEquals('name', $class->fieldMappings['name']['columnName']); + + $this->assertEquals(ClassMetadataInfo::GENERATOR_TYPE_NONE, $class->generatorType); + } } /** diff --git a/tests/Doctrine/Tests/ORM/Mapping/php/Doctrine.Tests.Models.DDC1476.DDC1476EntityWithDefaultFieldType.php b/tests/Doctrine/Tests/ORM/Mapping/php/Doctrine.Tests.Models.DDC1476.DDC1476EntityWithDefaultFieldType.php new file mode 100644 index 000000000..56a99633a --- /dev/null +++ b/tests/Doctrine/Tests/ORM/Mapping/php/Doctrine.Tests.Models.DDC1476.DDC1476EntityWithDefaultFieldType.php @@ -0,0 +1,12 @@ +mapField(array( + 'id' => true, + 'fieldName' => 'id', +)); +$metadata->mapField(array( + 'fieldName' => 'name' +)); +$metadata->setIdGeneratorType(ClassMetadataInfo::GENERATOR_TYPE_NONE); \ No newline at end of file diff --git a/tests/Doctrine/Tests/ORM/Mapping/xml/Doctrine.Tests.Models.DDC1476.DDC1476EntityWithDefaultFieldType.dcm.xml b/tests/Doctrine/Tests/ORM/Mapping/xml/Doctrine.Tests.Models.DDC1476.DDC1476EntityWithDefaultFieldType.dcm.xml new file mode 100644 index 000000000..29b5f1db5 --- /dev/null +++ b/tests/Doctrine/Tests/ORM/Mapping/xml/Doctrine.Tests.Models.DDC1476.DDC1476EntityWithDefaultFieldType.dcm.xml @@ -0,0 +1,15 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/tests/Doctrine/Tests/ORM/Mapping/yaml/Doctrine.Tests.Models.DDC1476.DDC1476EntityWithDefaultFieldType.dcm.yml b/tests/Doctrine/Tests/ORM/Mapping/yaml/Doctrine.Tests.Models.DDC1476.DDC1476EntityWithDefaultFieldType.dcm.yml new file mode 100644 index 000000000..3437a9b37 --- /dev/null +++ b/tests/Doctrine/Tests/ORM/Mapping/yaml/Doctrine.Tests.Models.DDC1476.DDC1476EntityWithDefaultFieldType.dcm.yml @@ -0,0 +1,8 @@ +Doctrine\Tests\Models\DDC1476\DDC1476EntityWithDefaultFieldType: + type: entity + id: + id: + generator: + strategy: NONE + fields: + name: \ No newline at end of file From 8af0f9d0711a7dcba8364e86c29cc3cc6e426010 Mon Sep 17 00:00:00 2001 From: "Fabio B. Silva" Date: Mon, 14 Nov 2011 16:07:37 -0200 Subject: [PATCH 104/135] added support for Inherited Named Queries --- .../ORM/Mapping/ClassMetadataFactory.php | 23 ++++ .../ORM/Mapping/ClassMetadataInfo.php | 13 +- .../ORM/Functional/Ticket/DDC1404Test.php | 129 ++++++++++++++++++ .../Tests/ORM/Mapping/ClassMetadataTest.php | 2 +- 4 files changed, 163 insertions(+), 4 deletions(-) create mode 100644 tests/Doctrine/Tests/ORM/Functional/Ticket/DDC1404Test.php diff --git a/lib/Doctrine/ORM/Mapping/ClassMetadataFactory.php b/lib/Doctrine/ORM/Mapping/ClassMetadataFactory.php index 239022b0f..19d319dbe 100644 --- a/lib/Doctrine/ORM/Mapping/ClassMetadataFactory.php +++ b/lib/Doctrine/ORM/Mapping/ClassMetadataFactory.php @@ -312,6 +312,10 @@ class ClassMetadataFactory implements ClassMetadataFactoryInterface if ($parent && $parent->containsForeignIdentifier) { $class->containsForeignIdentifier = true; } + + if ($parent && !empty ($parent->namedQueries)) { + $this->addInheritedNamedQueries($class, $parent); + } $class->setParentClasses($visited); @@ -428,6 +432,25 @@ class ClassMetadataFactory implements ClassMetadataFactoryInterface $subClass->addInheritedAssociationMapping($mapping); } } + + /** + * Adds inherited named queries to the subclass mapping. + * + * @since 2.2 + * @param Doctrine\ORM\Mapping\ClassMetadata $subClass + * @param Doctrine\ORM\Mapping\ClassMetadata $parentClass + */ + private function addInheritedNamedQueries(ClassMetadata $subClass, ClassMetadata $parentClass) + { + foreach ($parentClass->namedQueries as $name => $query) { + if (!isset ($subClass->namedQueries[$name])) { + $subClass->addNamedQuery(array( + 'name' => $query['name'], + 'query' => $query['query'] + )); + } + } + } /** * Completes the ID generator mapping. If "auto" is specified we choose the generator diff --git a/lib/Doctrine/ORM/Mapping/ClassMetadataInfo.php b/lib/Doctrine/ORM/Mapping/ClassMetadataInfo.php index 592367ab7..b8c4ef2f1 100644 --- a/lib/Doctrine/ORM/Mapping/ClassMetadataInfo.php +++ b/lib/Doctrine/ORM/Mapping/ClassMetadataInfo.php @@ -685,7 +685,7 @@ class ClassMetadataInfo implements ClassMetadata if ( ! isset($this->namedQueries[$queryName])) { throw MappingException::queryNotFound($this->name, $queryName); } - return $this->namedQueries[$queryName]; + return $this->namedQueries[$queryName]['dql']; } /** @@ -1448,8 +1448,15 @@ class ClassMetadataInfo implements ClassMetadata if (isset($this->namedQueries[$queryMapping['name']])) { throw MappingException::duplicateQueryMapping($this->name, $queryMapping['name']); } - $query = str_replace('__CLASS__', $this->name, $queryMapping['query']); - $this->namedQueries[$queryMapping['name']] = $query; + + $name = $queryMapping['name']; + $query = $queryMapping['query']; + $dql = str_replace('__CLASS__', $this->name, $query); + $this->namedQueries[$name] = array( + 'name' => $name, + 'query' => $query, + 'dql' => $dql + ); } /** diff --git a/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC1404Test.php b/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC1404Test.php new file mode 100644 index 000000000..49a282772 --- /dev/null +++ b/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC1404Test.php @@ -0,0 +1,129 @@ +_schemaTool->createSchema(array( + $this->_em->getClassMetadata(__NAMESPACE__ . '\DDC1404ParentEntity'), + $this->_em->getClassMetadata(__NAMESPACE__ . '\DDC1404ChildEntity'), + )); + + $this->loadFixtures(); + + } catch (Exception $exc) { + } + } + + public function testTicket() + { + $repository = $this->_em->getRepository(__NAMESPACE__ . '\DDC1404ChildEntity'); + $queryAll = $repository->createNamedQuery('all'); + $queryFirst = $repository->createNamedQuery('first'); + $querySecond = $repository->createNamedQuery('second'); + + + $this->assertEquals('SELECT p FROM Doctrine\Tests\ORM\Functional\Ticket\DDC1404ChildEntity p', $queryAll->getDQL()); + $this->assertEquals('SELECT p FROM Doctrine\Tests\ORM\Functional\Ticket\DDC1404ChildEntity p WHERE p.id = 1', $queryFirst->getDQL()); + $this->assertEquals('SELECT p FROM Doctrine\Tests\ORM\Functional\Ticket\DDC1404ChildEntity p WHERE p.id = 2', $querySecond->getDQL()); + + + $this->assertEquals(sizeof($queryAll->getResult()), 2); + $this->assertEquals(sizeof($queryFirst->getResult()), 1); + $this->assertEquals(sizeof($querySecond->getResult()), 1); + } + + + public function loadFixtures() + { + $c1 = new DDC1404ChildEntity("ChildEntity 1"); + $c2 = new DDC1404ChildEntity("ChildEntity 2"); + + $this->_em->persist($c1); + $this->_em->persist($c2); + + $this->_em->flush(); + } + +} + +/** + * @MappedSuperclass + * + * @NamedQueries({ + * @NamedQuery(name="all", query="SELECT p FROM __CLASS__ p"), + * @NamedQuery(name="first", query="SELECT p FROM __CLASS__ p WHERE p.id = 1"), + * }) + */ +class DDC1404ParentEntity +{ + + /** + * @Id + * @Column(type="integer") + * @GeneratedValue() + */ + protected $id; + + /** + * @return integer + */ + public function getId() + { + return $this->id; + } + +} + +/** + * @Entity + * + * @NamedQueries({ + * @NamedQuery(name="first", query="SELECT p FROM __CLASS__ p WHERE p.id = 1"), + * @NamedQuery(name="second", query="SELECT p FROM __CLASS__ p WHERE p.id = 2") + * }) + */ +class DDC1404ChildEntity extends DDC1404ParentEntity +{ + + /** + * @column(type="string") + */ + private $name; + + /** + * @param string $name + */ + public function __construct($name) + { + $this->name = $name; + } + + /** + * @return string + */ + public function getName() + { + return $this->name; + } + + /** + * @param string $name + */ + public function setName($name) + { + $this->name = $name; + } + +} diff --git a/tests/Doctrine/Tests/ORM/Mapping/ClassMetadataTest.php b/tests/Doctrine/Tests/ORM/Mapping/ClassMetadataTest.php index fed31d9c5..55726e387 100644 --- a/tests/Doctrine/Tests/ORM/Mapping/ClassMetadataTest.php +++ b/tests/Doctrine/Tests/ORM/Mapping/ClassMetadataTest.php @@ -54,7 +54,7 @@ class ClassMetadataTest extends \Doctrine\Tests\OrmTestCase $this->assertEquals('phonenumbers', $oneOneMapping['fieldName']); $this->assertEquals('Doctrine\Tests\Models\CMS\Bar', $oneOneMapping['targetEntity']); $this->assertTrue($cm->isReadOnly); - $this->assertEquals(array('dql' => 'foo'), $cm->namedQueries); + $this->assertEquals(array('dql' => array('name'=>'dql','query'=>'foo','dql'=>'foo')), $cm->namedQueries); } public function testFieldIsNullable() From 909504c0743c8344173ac8c208989baaa4836b34 Mon Sep 17 00:00:00 2001 From: Benjamin Eberlei Date: Mon, 14 Nov 2011 21:05:44 +0100 Subject: [PATCH 105/135] DDC-1461 - Fix test failures --- .../Doctrine/Tests/Models/CMS/CmsAddress.php | 2 +- .../ORM/Functional/Ticket/DDC1461Test.php | 28 +------------------ 2 files changed, 2 insertions(+), 28 deletions(-) diff --git a/tests/Doctrine/Tests/Models/CMS/CmsAddress.php b/tests/Doctrine/Tests/Models/CMS/CmsAddress.php index 9119a6f58..d32416a5e 100644 --- a/tests/Doctrine/Tests/Models/CMS/CmsAddress.php +++ b/tests/Doctrine/Tests/Models/CMS/CmsAddress.php @@ -38,7 +38,7 @@ class CmsAddress public $street; /** - * @OneToOne(targetEntity="CmsUser", inversedBy="address", cascade={"persist"}) + * @OneToOne(targetEntity="CmsUser", inversedBy="address") * @JoinColumn(referencedColumnName="id") */ public $user; diff --git a/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC1461Test.php b/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC1461Test.php index 2f59ede23..a47571a75 100644 --- a/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC1461Test.php +++ b/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC1461Test.php @@ -14,7 +14,6 @@ class DDC1461Test extends \Doctrine\Tests\OrmFunctionalTestCase { public function setUp() { - $this->useModelSet('cms'); parent::setUp(); try { @@ -26,32 +25,7 @@ class DDC1461Test extends \Doctrine\Tests\OrmFunctionalTestCase } } - - public function testChangeDetectionDeferredImplitic() - { - $address = new \Doctrine\Tests\Models\CMS\CmsAddress(); - $address->city = "Karlsruhe"; - $address->country = "Germany"; - $address->street = "somestreet"; - $address->zip = 12345; - - $this->_em->persist($address); - $this->_em->flush(); - - $user = new CmsUser(); - $user->name = "schmittjoh"; - $user->username = "schmittjoh"; - $user->status = "active"; - - $address->setUser($user); - $this->_em->flush(); - $this->_em->clear(); - - $user = $this->_em->find(get_class($user), $user->getId()); - $this->assertNotNull($user->getAddress()); - $this->assertEquals("Karlsruhe", $user->getAddress()->getCity()); - } - + public function testChangeDetectionDeferredExplicit() { $user = new DDC1461User; From 34c94dbd94dd20b6a4782c42c4cc18dd9f263e06 Mon Sep 17 00:00:00 2001 From: Benjamin Eberlei Date: Mon, 14 Nov 2011 23:05:33 +0100 Subject: [PATCH 106/135] DDC-1452 - Fixed bug with multiple fetch joins of the same "propery-path" of Class+field name combinations --- .../ORM/Internal/Hydration/ObjectHydrator.php | 30 +- lib/Doctrine/ORM/UnitOfWork.php | 572 +++++++++--------- .../ORM/Functional/Ticket/DDC1335Test.php | 22 +- .../ORM/Functional/Ticket/DDC1452Test.php | 64 +- 4 files changed, 367 insertions(+), 321 deletions(-) diff --git a/lib/Doctrine/ORM/Internal/Hydration/ObjectHydrator.php b/lib/Doctrine/ORM/Internal/Hydration/ObjectHydrator.php index c56b6eb22..fc41f067f 100644 --- a/lib/Doctrine/ORM/Internal/Hydration/ObjectHydrator.php +++ b/lib/Doctrine/ORM/Internal/Hydration/ObjectHydrator.php @@ -94,13 +94,7 @@ class ObjectHydrator extends AbstractHydrator $sourceClass = $this->_getClassMetadata($sourceClassName); $assoc = $sourceClass->associationMappings[$this->_rsm->relationMap[$dqlAlias]]; - $this->_hints['fetched'][$sourceClassName][$assoc['fieldName']] = true; - - if ($sourceClass->subClasses) { - foreach ($sourceClass->subClasses as $sourceSubclassName) { - $this->_hints['fetched'][$sourceSubclassName][$assoc['fieldName']] = true; - } - } + $this->_hints['fetched'][$this->_rsm->parentAliasMap[$dqlAlias]][$assoc['fieldName']] = true; if ($assoc['type'] === ClassMetadata::MANY_TO_MANY) { continue; @@ -108,11 +102,12 @@ class ObjectHydrator extends AbstractHydrator // Mark any non-collection opposite sides as fetched, too. if ($assoc['mappedBy']) { - $this->_hints['fetched'][$className][$assoc['mappedBy']] = true; + $this->_hints['fetched'][$dqlAlias][$assoc['mappedBy']] = true; continue; } + // handle fetch-joined owning side bi-directional one-to-one associations if ($assoc['inversedBy']) { $class = $this->_ce[$className]; $inverseAssoc = $class->associationMappings[$assoc['inversedBy']]; @@ -121,13 +116,7 @@ class ObjectHydrator extends AbstractHydrator continue; } - $this->_hints['fetched'][$className][$inverseAssoc['fieldName']] = true; - - if ($class->subClasses) { - foreach ($class->subClasses as $targetSubclassName) { - $this->_hints['fetched'][$targetSubclassName][$inverseAssoc['fieldName']] = true; - } - } + $this->_hints['fetched'][$dqlAlias][$inverseAssoc['fieldName']] = true; } } } @@ -175,9 +164,11 @@ class ObjectHydrator extends AbstractHydrator * Initializes a related collection. * * @param object $entity The entity to which the collection belongs. + * @param ClassMetadata $class * @param string $name The name of the field on the entity that holds the collection. + * @param string $parentDqlAlias Alias of the parent fetch joining this collection. */ - private function _initRelatedCollection($entity, $class, $fieldName) + private function _initRelatedCollection($entity, $class, $fieldName, $parentDqlAlias) { $oid = spl_object_hash($entity); $relation = $class->associationMappings[$fieldName]; @@ -199,7 +190,7 @@ class ObjectHydrator extends AbstractHydrator $this->_initializedCollections[$oid . $fieldName] = $value; } else if ( isset($this->_hints[Query::HINT_REFRESH]) || - isset($this->_hints['fetched'][$class->name][$fieldName]) && + isset($this->_hints['fetched'][$parentDqlAlias][$fieldName]) && ! $value->isInitialized() ) { // Is already PersistentCollection, but either REFRESH or FETCH-JOIN and UNINITIALIZED! @@ -243,6 +234,7 @@ class ObjectHydrator extends AbstractHydrator $this->registerManaged($this->_ce[$className], $this->_hints[Query::HINT_REFRESH_ENTITY], $data); } + $this->_hints['fetchAlias'] = $dqlAlias; return $this->_uow->createEntity($className, $data, $this->_hints); } @@ -367,7 +359,7 @@ class ObjectHydrator extends AbstractHydrator if (isset($this->_initializedCollections[$collKey])) { $reflFieldValue = $this->_initializedCollections[$collKey]; } else if ( ! isset($this->_existingCollections[$collKey])) { - $reflFieldValue = $this->_initRelatedCollection($parentObject, $parentClass, $relationField); + $reflFieldValue = $this->_initRelatedCollection($parentObject, $parentClass, $relationField, $parentAlias); } $indexExists = isset($this->_identifierMap[$path][$id[$parentAlias]][$id[$dqlAlias]]); @@ -402,7 +394,7 @@ class ObjectHydrator extends AbstractHydrator $this->_resultPointers[$dqlAlias] = $reflFieldValue[$index]; } } else if ( ! $reflField->getValue($parentObject)) { - $reflFieldValue = $this->_initRelatedCollection($parentObject, $parentClass, $relationField); + $reflFieldValue = $this->_initRelatedCollection($parentObject, $parentClass, $relationField, $parentAlias); } } else { // PATH B: Single-valued association diff --git a/lib/Doctrine/ORM/UnitOfWork.php b/lib/Doctrine/ORM/UnitOfWork.php index 1404569c8..513f58b5a 100644 --- a/lib/Doctrine/ORM/UnitOfWork.php +++ b/lib/Doctrine/ORM/UnitOfWork.php @@ -116,7 +116,7 @@ class UnitOfWork implements PropertyChangedListener * Map of entities that are scheduled for dirty checking at commit time. * This is only used for entities with a change tracking policy of DEFERRED_EXPLICIT. * Keys are object ids (spl_object_hash). - * + * * @var array * @todo rename: scheduledForSynchronization */ @@ -135,10 +135,10 @@ class UnitOfWork implements PropertyChangedListener * @var array */ private $entityUpdates = array(); - + /** * Any pending extra updates that have been scheduled by persisters. - * + * * @var array */ private $extraUpdates = array(); @@ -201,17 +201,17 @@ class UnitOfWork implements PropertyChangedListener * @var array */ private $collectionPersisters = array(); - + /** * The EventManager used for dispatching events. - * + * * @var EventManager */ private $evm; - + /** * Orphaned entities that are scheduled for removal. - * + * * @var array */ private $orphanRemovals = array(); @@ -225,7 +225,7 @@ class UnitOfWork implements PropertyChangedListener /** * Map of Entity Class-Names and corresponding IDs that should eager loaded when requested. - * + * * @var array */ private $eagerLoadingEntities = array(); @@ -245,9 +245,9 @@ class UnitOfWork implements PropertyChangedListener * Commits the UnitOfWork, executing all operations that have been postponed * up to this point. The state of all managed entities will be synchronized with * the database. - * + * * The operations are executed in the following order: - * + * * 1) All entity insertions * 2) All entity updates * 3) All collection deletions @@ -285,18 +285,18 @@ class UnitOfWork implements PropertyChangedListener $this->remove($orphan); } } - + // Raise onFlush if ($this->evm->hasListeners(Events::onFlush)) { $this->evm->dispatchEvent(Events::onFlush, new Event\OnFlushEventArgs($this->em)); } - + // Now we need a commit order to maintain referential integrity $commitOrder = $this->getCommitOrder(); $conn = $this->em->getConnection(); $conn->beginTransaction(); - + try { if ($this->entityInsertions) { foreach ($commitOrder as $class) { @@ -335,10 +335,10 @@ class UnitOfWork implements PropertyChangedListener } catch (Exception $e) { $this->em->close(); $conn->rollback(); - + throw $e; } - + // Take new snapshots from visited collections foreach ($this->visitedCollections as $coll) { $coll->takeSnapshot(); @@ -371,7 +371,7 @@ class UnitOfWork implements PropertyChangedListener { foreach ($this->entityInsertions as $entity) { $class = $this->em->getClassMetadata(get_class($entity)); - + $this->computeChangeSet($class, $entity); } } @@ -413,12 +413,12 @@ class UnitOfWork implements PropertyChangedListener // Only MANAGED entities that are NOT SCHEDULED FOR INSERTION are processed here. $oid = spl_object_hash($entity); - + if ( ! isset($this->entityInsertions[$oid]) && isset($this->entityStates[$oid])) { $this->computeChangeSet($class, $entity); } } - + /** * Executes any extra updates that have been scheduled. */ @@ -426,7 +426,7 @@ class UnitOfWork implements PropertyChangedListener { foreach ($this->extraUpdates as $oid => $update) { list ($entity, $changeset) = $update; - + $this->entityChangeSets[$oid] = $changeset; $this->getEntityPersister(get_class($entity))->update($entity); } @@ -440,11 +440,11 @@ class UnitOfWork implements PropertyChangedListener public function getEntityChangeSet($entity) { $oid = spl_object_hash($entity); - + if (isset($this->entityChangeSets[$oid])) { return $this->entityChangeSets[$oid]; } - + return array(); } @@ -489,39 +489,39 @@ class UnitOfWork implements PropertyChangedListener if ( ! $class->isInheritanceTypeNone()) { $class = $this->em->getClassMetadata(get_class($entity)); } - + // Fire PreFlush lifecycle callbacks if (isset($class->lifecycleCallbacks[Events::preFlush])) { $class->invokeLifecycleCallbacks(Events::preFlush, $entity); } $actualData = array(); - + foreach ($class->reflFields as $name => $refProp) { $value = $refProp->getValue($entity); - + if ($class->isCollectionValuedAssociation($name) && $value !== null && ! ($value instanceof PersistentCollection)) { // If $value is not a Collection then use an ArrayCollection. if ( ! $value instanceof Collection) { $value = new ArrayCollection($value); } - + $assoc = $class->associationMappings[$name]; - + // Inject PersistentCollection $value = new PersistentCollection( $this->em, $this->em->getClassMetadata($assoc['targetEntity']), $value ); $value->setOwner($entity, $assoc); $value->setDirty( ! $value->isEmpty()); - + $class->reflFields[$name]->setValue($entity, $value); - + $actualData[$name] = $value; - + continue; } - + if (( ! $class->isIdentifier($name) || ! $class->isIdGeneratorIdentity()) && ($name !== $class->versionField)) { $actualData[$name] = $value; } @@ -532,29 +532,29 @@ class UnitOfWork implements PropertyChangedListener // These result in an INSERT. $this->originalEntityData[$oid] = $actualData; $changeSet = array(); - + foreach ($actualData as $propName => $actualValue) { if ( ! isset($class->associationMappings[$propName])) { $changeSet[$propName] = array(null, $actualValue); - + continue; } - + $assoc = $class->associationMappings[$propName]; - + if ($assoc['isOwningSide'] && $assoc['type'] & ClassMetadata::TO_ONE) { $changeSet[$propName] = array(null, $actualValue); } } - + $this->entityChangeSets[$oid] = $changeSet; } else { // Entity is "fully" MANAGED: it was already fully persisted before // and we have a copy of the original data $originalData = $this->originalEntityData[$oid]; $isChangeTrackingNotify = $class->isChangeTrackingNotify(); - $changeSet = ($isChangeTrackingNotify && isset($this->entityChangeSets[$oid])) - ? $this->entityChangeSets[$oid] + $changeSet = ($isChangeTrackingNotify && isset($this->entityChangeSets[$oid])) + ? $this->entityChangeSets[$oid] : array(); foreach ($actualData as $propName => $actualValue) { @@ -562,38 +562,38 @@ class UnitOfWork implements PropertyChangedListener if ( ! (isset($originalData[$propName]) || array_key_exists($propName, $originalData))) { continue; } - + $orgValue = $originalData[$propName]; - + // skip if value havent changed if ($orgValue === $actualValue) { continue; } - + // if regular field if ( ! isset($class->associationMappings[$propName])) { if ($isChangeTrackingNotify) { continue; } - + $changeSet[$propName] = array($orgValue, $actualValue); - + continue; } - + $assoc = $class->associationMappings[$propName]; - + if ($orgValue instanceof PersistentCollection) { // A PersistentCollection was de-referenced, so delete it. $coid = spl_object_hash($orgValue); - + if (isset($this->collectionDeletions[$coid])) { continue; } - + $this->collectionDeletions[$coid] = $orgValue; $changeSet[$propName] = $orgValue; // Signal changeset, to-many assocs will be ignored. - + continue; } @@ -601,13 +601,13 @@ class UnitOfWork implements PropertyChangedListener if ($assoc['isOwningSide']) { $changeSet[$propName] = array($orgValue, $actualValue); } - + if ($orgValue !== null && $assoc['orphanRemoval']) { $this->scheduleOrphanRemoval($orgValue); } } } - + if ($changeSet) { $this->entityChangeSets[$oid] = $changeSet; $this->originalEntityData[$oid] = $actualData; @@ -648,14 +648,14 @@ class UnitOfWork implements PropertyChangedListener case ($class->isChangeTrackingDeferredImplicit()): $entitiesToProcess = $entities; break; - + case (isset($this->scheduledForDirtyCheck[$className])): $entitiesToProcess = $this->scheduledForDirtyCheck[$className]; break; - + default: $entitiesToProcess = array(); - + } foreach ($entitiesToProcess as $entity) { @@ -663,10 +663,10 @@ class UnitOfWork implements PropertyChangedListener if ($entity instanceof Proxy && ! $entity->__isInitialized__) { continue; } - + // Only MANAGED entities that are NOT SCHEDULED FOR INSERTION are processed here. $oid = spl_object_hash($entity); - + if ( ! isset($this->entityInsertions[$oid]) && isset($this->entityStates[$oid])) { $this->computeChangeSet($class, $entity); } @@ -685,60 +685,60 @@ class UnitOfWork implements PropertyChangedListener if ($value instanceof Proxy && ! $value->__isInitialized__) { return; } - + if ($value instanceof PersistentCollection && $value->isDirty()) { $coid = spl_object_hash($value); - + if ($assoc['isOwningSide']) { $this->collectionUpdates[$coid] = $value; } - + $this->visitedCollections[$coid] = $value; } - - // Look through the entities, and in any of their associations, + + // Look through the entities, and in any of their associations, // for transient (new) entities, recursively. ("Persistence by reachability") // Unwrap. Uninitialized collections will simply be empty. $unwrappedValue = ($assoc['type'] & ClassMetadata::TO_ONE) ? array($value) : $value->unwrap(); $targetClass = $this->em->getClassMetadata($assoc['targetEntity']); - + foreach ($unwrappedValue as $key => $entry) { $state = $this->getEntityState($entry, self::STATE_NEW); $oid = spl_object_hash($entry); - + switch ($state) { case self::STATE_NEW: if ( ! $assoc['isCascadePersist']) { - $message = "A new entity was found through the relationship '%s#%s' that was not configured " . - ' to cascade persist operations for entity: %s. Explicitly persist the new entity or ' . + $message = "A new entity was found through the relationship '%s#%s' that was not configured " . + ' to cascade persist operations for entity: %s. Explicitly persist the new entity or ' . 'configure cascading persist operations on tbe relationship. If you cannot find out ' . 'which entity causes the problem, implement %s#__toString() to get a clue.'; - + throw new InvalidArgumentException(sprintf( $message, $assoc['sourceEntity'], $assoc['fieldName'], self::objToStr($entry), $assoc['targetEntity'] )); } - + $this->persistNew($targetClass, $entry); $this->computeChangeSet($targetClass, $entry); break; - + case self::STATE_REMOVED: - // Consume the $value as array (it's either an array or an ArrayAccess) + // Consume the $value as array (it's either an array or an ArrayAccess) // and remove the element from Collection. if ($assoc['type'] & ClassMetadata::TO_MANY) { unset($value[$key]); } break; - + case self::STATE_DETACHED: // Can actually not happen right now as we assume STATE_NEW, // so the exception will be raised from the DBAL layer (constraint violation). $message = 'A detached entity was found through a relationship during cascading a persist operation.'; - + throw new InvalidArgumentException($message); break; - + default: // MANAGED associated entities are already taken into account // during changeset calculation anyway, since they are in the identity map. @@ -749,43 +749,43 @@ class UnitOfWork implements PropertyChangedListener private function persistNew($class, $entity) { $oid = spl_object_hash($entity); - + if (isset($class->lifecycleCallbacks[Events::prePersist])) { $class->invokeLifecycleCallbacks(Events::prePersist, $entity); } - + if ($this->evm->hasListeners(Events::prePersist)) { $this->evm->dispatchEvent(Events::prePersist, new LifecycleEventArgs($entity, $this->em)); } $idGen = $class->idGenerator; - + if ( ! $idGen->isPostInsertGenerator()) { $idValue = $idGen->generate($this->em, $entity); - + if ( ! $idGen instanceof \Doctrine\ORM\Id\AssignedGenerator) { $idValue = array($class->identifier[0] => $idValue); - + $class->setIdentifierValues($entity, $idValue); } - + $this->entityIdentifiers[$oid] = $idValue; } - + $this->entityStates[$oid] = self::STATE_MANAGED; $this->scheduleForInsert($entity); } - + /** * INTERNAL: * Computes the changeset of an individual entity, independently of the * computeChangeSets() routine that is used at the beginning of a UnitOfWork#commit(). - * + * * The passed entity must be a managed entity. If the entity already has a change set * because this method is invoked during a commit cycle then the change sets are added. * whereby changes detected in this method prevail. - * + * * @ignore * @param ClassMetadata $class The class descriptor of the entity. * @param object $entity The entity for which to (re)calculate the change set. @@ -794,11 +794,11 @@ class UnitOfWork implements PropertyChangedListener public function recomputeSingleEntityChangeSet(ClassMetadata $class, $entity) { $oid = spl_object_hash($entity); - + if ( ! isset($this->entityStates[$oid]) || $this->entityStates[$oid] != self::STATE_MANAGED) { throw new InvalidArgumentException('Entity must be managed.'); } - + // skip if change tracking is "NOTIFY" if ($class->isChangeTrackingNotify()) { return; @@ -809,7 +809,7 @@ class UnitOfWork implements PropertyChangedListener } $actualData = array(); - + foreach ($class->reflFields as $name => $refProp) { if ( ! $class->isIdentifier($name) || ! $class->isIdGeneratorIdentity()) { $actualData[$name] = $refProp->getValue($entity); @@ -821,7 +821,7 @@ class UnitOfWork implements PropertyChangedListener foreach ($actualData as $propName => $actualValue) { $orgValue = isset($originalData[$propName]) ? $originalData[$propName] : null; - + if (is_object($orgValue) && $orgValue !== $actualValue) { $changeSet[$propName] = array($orgValue, $actualValue); } else if ($orgValue != $actualValue || ($orgValue === null ^ $actualValue === null)) { @@ -833,7 +833,7 @@ class UnitOfWork implements PropertyChangedListener if (isset($this->entityChangeSets[$oid])) { $this->entityChangeSets[$oid] = array_merge($this->entityChangeSets[$oid], $changeSet); } - + $this->originalEntityData[$oid] = $actualData; } } @@ -848,19 +848,19 @@ class UnitOfWork implements PropertyChangedListener $className = $class->name; $persister = $this->getEntityPersister($className); $entities = array(); - + $hasLifecycleCallbacks = isset($class->lifecycleCallbacks[Events::postPersist]); $hasListeners = $this->evm->hasListeners(Events::postPersist); - + foreach ($this->entityInsertions as $oid => $entity) { if (get_class($entity) !== $className) { continue; } - + $persister->addInsert($entity); - + unset($this->entityInsertions[$oid]); - + if ($hasLifecycleCallbacks || $hasListeners) { $entities[] = $entity; } @@ -873,17 +873,17 @@ class UnitOfWork implements PropertyChangedListener foreach ($postInsertIds as $id => $entity) { $oid = spl_object_hash($entity); $idField = $class->identifier[0]; - + $class->reflFields[$idField]->setValue($entity, $id); - + $this->entityIdentifiers[$oid] = array($idField => $id); $this->entityStates[$oid] = self::STATE_MANAGED; $this->originalEntityData[$oid][$idField] = $id; - + $this->addToIdentityMap($entity); } } - + foreach ($entities as $entity) { if ($hasLifecycleCallbacks) { $class->invokeLifecycleCallbacks(Events::postPersist, $entity); @@ -907,24 +907,24 @@ class UnitOfWork implements PropertyChangedListener $hasPreUpdateLifecycleCallbacks = isset($class->lifecycleCallbacks[Events::preUpdate]); $hasPreUpdateListeners = $this->evm->hasListeners(Events::preUpdate); - + $hasPostUpdateLifecycleCallbacks = isset($class->lifecycleCallbacks[Events::postUpdate]); $hasPostUpdateListeners = $this->evm->hasListeners(Events::postUpdate); - + foreach ($this->entityUpdates as $oid => $entity) { if ( ! (get_class($entity) === $className || $entity instanceof Proxy && get_parent_class($entity) === $className)) { continue; } - + if ($hasPreUpdateLifecycleCallbacks) { $class->invokeLifecycleCallbacks(Events::preUpdate, $entity); - + $this->recomputeSingleEntityChangeSet($class, $entity); } if ($hasPreUpdateListeners) { $this->evm->dispatchEvent( - Events::preUpdate, + Events::preUpdate, new Event\PreUpdateEventArgs($entity, $this->em, $this->entityChangeSets[$oid]) ); } @@ -932,13 +932,13 @@ class UnitOfWork implements PropertyChangedListener if ($this->entityChangeSets[$oid]) { $persister->update($entity); } - + unset($this->entityUpdates[$oid]); if ($hasPostUpdateLifecycleCallbacks) { $class->invokeLifecycleCallbacks(Events::postUpdate, $entity); } - + if ($hasPostUpdateListeners) { $this->evm->dispatchEvent(Events::postUpdate, new LifecycleEventArgs($entity, $this->em)); } @@ -954,24 +954,24 @@ class UnitOfWork implements PropertyChangedListener { $className = $class->name; $persister = $this->getEntityPersister($className); - + $hasLifecycleCallbacks = isset($class->lifecycleCallbacks[Events::postRemove]); $hasListeners = $this->evm->hasListeners(Events::postRemove); - + foreach ($this->entityDeletions as $oid => $entity) { if ( ! (get_class($entity) == $className || $entity instanceof Proxy && get_parent_class($entity) == $className)) { continue; } - + $persister->delete($entity); - + unset( $this->entityDeletions[$oid], $this->entityIdentifiers[$oid], $this->originalEntityData[$oid], $this->entityStates[$oid] ); - + // Entity with this $oid after deletion treated as NEW, even if the $oid // is obtained by a new entity because the old one went out of scope. //$this->entityStates[$oid] = self::STATE_NEW; @@ -982,7 +982,7 @@ class UnitOfWork implements PropertyChangedListener if ($hasLifecycleCallbacks) { $class->invokeLifecycleCallbacks(Events::postRemove, $entity); } - + if ($hasListeners) { $this->evm->dispatchEvent(Events::postRemove, new LifecycleEventArgs($entity, $this->em)); } @@ -999,60 +999,60 @@ class UnitOfWork implements PropertyChangedListener if ($entityChangeSet === null) { $entityChangeSet = array_merge($this->entityInsertions, $this->entityUpdates, $this->entityDeletions); } - + $calc = $this->getCommitOrderCalculator(); - + // See if there are any new classes in the changeset, that are not in the // commit order graph yet (dont have a node). // We have to inspect changeSet to be able to correctly build dependencies. - // It is not possible to use IdentityMap here because post inserted ids + // It is not possible to use IdentityMap here because post inserted ids // are not yet available. $newNodes = array(); - + foreach ($entityChangeSet as $oid => $entity) { $className = get_class($entity); - + if ($calc->hasClass($className)) { continue; } - + $class = $this->em->getClassMetadata($className); $calc->addClass($class); - + $newNodes[] = $class; } - + // Calculate dependencies for new nodes while ($class = array_pop($newNodes)) { foreach ($class->associationMappings as $assoc) { if ( ! ($assoc['isOwningSide'] && $assoc['type'] & ClassMetadata::TO_ONE)) { continue; } - + $targetClass = $this->em->getClassMetadata($assoc['targetEntity']); - + if ( ! $calc->hasClass($targetClass->name)) { $calc->addClass($targetClass); - + $newNodes[] = $targetClass; } - + $calc->addDependency($targetClass, $class); - + // If the target class has mapped subclasses, these share the same dependency. if ( ! $targetClass->subClasses) { continue; } - + foreach ($targetClass->subClasses as $subClassName) { $targetSubClass = $this->em->getClassMetadata($subClassName); - + if ( ! $calc->hasClass($subClassName)) { $calc->addClass($targetSubClass); - + $newNodes[] = $targetSubClass; } - + $calc->addDependency($targetSubClass, $class); } } @@ -1074,11 +1074,11 @@ class UnitOfWork implements PropertyChangedListener if (isset($this->entityUpdates[$oid])) { throw new InvalidArgumentException("Dirty entity can not be scheduled for insertion."); } - + if (isset($this->entityDeletions[$oid])) { throw new InvalidArgumentException("Removed entity can not be scheduled for insertion."); } - + if (isset($this->entityInsertions[$oid])) { throw new InvalidArgumentException("Entity can not be scheduled for insertion twice."); } @@ -1109,11 +1109,11 @@ class UnitOfWork implements PropertyChangedListener public function scheduleForUpdate($entity) { $oid = spl_object_hash($entity); - + if ( ! isset($this->entityIdentifiers[$oid])) { throw new InvalidArgumentException("Entity has no identity."); } - + if (isset($this->entityDeletions[$oid])) { throw new InvalidArgumentException("Entity is removed."); } @@ -1122,14 +1122,14 @@ class UnitOfWork implements PropertyChangedListener $this->entityUpdates[$oid] = $entity; } } - + /** * INTERNAL: * Schedules an extra update that will be executed immediately after the * regular entity updates within the currently running commit cycle. - * + * * Extra updates for entities are stored as (entity, changeset) tuples. - * + * * @ignore * @param object $entity The entity for which to schedule an extra update. * @param array $changeset The changeset of the entity (what to update). @@ -1138,13 +1138,13 @@ class UnitOfWork implements PropertyChangedListener { $oid = spl_object_hash($entity); $extraUpdate = array($entity, $changeset); - + if (isset($this->extraUpdates[$oid])) { list($ignored, $changeset2) = $this->extraUpdates[$oid]; - + $extraUpdate = array($entity, $changeset + $changeset2); } - + $this->extraUpdates[$oid] = $extraUpdate; } @@ -1161,7 +1161,7 @@ class UnitOfWork implements PropertyChangedListener return isset($this->entityUpdates[spl_object_hash($entity)]); } - + /** * Checks whether an entity is registered to be checked in the unit of work. * @@ -1171,40 +1171,40 @@ class UnitOfWork implements PropertyChangedListener public function isScheduledForDirtyCheck($entity) { $rootEntityName = $this->em->getClassMetadata(get_class($entity))->rootEntityName; - + return isset($this->scheduledForDirtyCheck[$rootEntityName][spl_object_hash($entity)]); } /** * INTERNAL: * Schedules an entity for deletion. - * + * * @param object $entity */ public function scheduleForDelete($entity) { $oid = spl_object_hash($entity); - + if (isset($this->entityInsertions[$oid])) { if ($this->isInIdentityMap($entity)) { $this->removeFromIdentityMap($entity); } - + unset($this->entityInsertions[$oid], $this->entityStates[$oid]); - + return; // entity has not been persisted yet, so nothing more to do. } if ( ! $this->isInIdentityMap($entity)) { return; } - + $this->removeFromIdentityMap($entity); - + if (isset($this->entityUpdates[$oid])) { unset($this->entityUpdates[$oid]); } - + if ( ! isset($this->entityDeletions[$oid])) { $this->entityDeletions[$oid] = $entity; $this->entityStates[$oid] = self::STATE_REMOVED; @@ -1225,16 +1225,16 @@ class UnitOfWork implements PropertyChangedListener /** * Checks whether an entity is scheduled for insertion, update or deletion. - * + * * @param $entity * @return boolean */ public function isEntityScheduled($entity) { $oid = spl_object_hash($entity); - - return isset($this->entityInsertions[$oid]) - || isset($this->entityUpdates[$oid]) + + return isset($this->entityInsertions[$oid]) + || isset($this->entityUpdates[$oid]) || isset($this->entityDeletions[$oid]); } @@ -1253,23 +1253,23 @@ class UnitOfWork implements PropertyChangedListener { $classMetadata = $this->em->getClassMetadata(get_class($entity)); $idHash = implode(' ', $this->entityIdentifiers[spl_object_hash($entity)]); - + if ($idHash === '') { throw new InvalidArgumentException('The given entity has no identity.'); } - + $className = $classMetadata->rootEntityName; - + if (isset($this->identityMap[$className][$idHash])) { return false; } - + $this->identityMap[$className][$idHash] = $entity; - + if ($entity instanceof NotifyPropertyChanged) { $entity->addPropertyChangedListener($this); } - + return true; } @@ -1286,26 +1286,26 @@ class UnitOfWork implements PropertyChangedListener public function getEntityState($entity, $assume = null) { $oid = spl_object_hash($entity); - + if (isset($this->entityStates[$oid])) { return $this->entityStates[$oid]; } - + if ($assume !== null) { return $assume; } - + // State can only be NEW or DETACHED, because MANAGED/REMOVED states are known. // Note that you can not remember the NEW or DETACHED state in _entityStates since // the UoW does not hold references to such objects and the object hash can be reused. // More generally because the state may "change" between NEW/DETACHED without the UoW being aware of it. $class = $this->em->getClassMetadata(get_class($entity)); $id = $class->getIdentifierValues($entity); - + if ( ! $id) { return self::STATE_NEW; } - + switch (true) { case ($class->isIdentifierNatural()); // Check for a version field, if available, to avoid a db lookup. @@ -1314,19 +1314,19 @@ class UnitOfWork implements PropertyChangedListener ? self::STATE_DETACHED : self::STATE_NEW; } - + // Last try before db lookup: check the identity map. if ($this->tryGetById($id, $class->rootEntityName)) { return self::STATE_DETACHED; } - + // db lookup if ($this->getEntityPersister(get_class($entity))->exists($entity)) { return self::STATE_DETACHED; } - + return self::STATE_NEW; - + case ( ! $class->idGenerator->isPostInsertGenerator()): // if we have a pre insert generator we can't be sure that having an id // really means that the entity exists. We have to verify this through @@ -1335,15 +1335,15 @@ class UnitOfWork implements PropertyChangedListener // Last try before db lookup: check the identity map. if ($this->tryGetById($id, $class->rootEntityName)) { return self::STATE_DETACHED; - } - + } + // db lookup if ($this->getEntityPersister(get_class($entity))->exists($entity)) { return self::STATE_DETACHED; } - + return self::STATE_NEW; - + default: return self::STATE_DETACHED; } @@ -1363,18 +1363,18 @@ class UnitOfWork implements PropertyChangedListener $oid = spl_object_hash($entity); $classMetadata = $this->em->getClassMetadata(get_class($entity)); $idHash = implode(' ', $this->entityIdentifiers[$oid]); - + if ($idHash === '') { throw new InvalidArgumentException('The given entity has no identity.'); } - + $className = $classMetadata->rootEntityName; - + if (isset($this->identityMap[$className][$idHash])) { unset($this->identityMap[$className][$idHash]); - + //$this->entityStates[$oid] = self::STATE_DETACHED; - + return true; } @@ -1410,7 +1410,7 @@ class UnitOfWork implements PropertyChangedListener if (isset($this->identityMap[$rootClassName][$idHash])) { return $this->identityMap[$rootClassName][$idHash]; } - + return false; } @@ -1423,18 +1423,18 @@ class UnitOfWork implements PropertyChangedListener public function isInIdentityMap($entity) { $oid = spl_object_hash($entity); - + if ( ! isset($this->entityIdentifiers[$oid])) { return false; } - + $classMetadata = $this->em->getClassMetadata(get_class($entity)); $idHash = implode(' ', $this->entityIdentifiers[$oid]); - + if ($idHash === '') { return false; } - + return isset($this->identityMap[$classMetadata->rootEntityName][$idHash]); } @@ -1460,13 +1460,13 @@ class UnitOfWork implements PropertyChangedListener public function persist($entity) { $visited = array(); - + $this->doPersist($entity, $visited); } /** * Persists an entity as part of the current unit of work. - * + * * This method is internally called during persist() cascades as it tracks * the already visited entities to prevent infinite recursions. * @@ -1476,7 +1476,7 @@ class UnitOfWork implements PropertyChangedListener private function doPersist($entity, array &$visited) { $oid = spl_object_hash($entity); - + if (isset($visited[$oid])) { return; // Prevent infinite recursion } @@ -1498,22 +1498,22 @@ class UnitOfWork implements PropertyChangedListener $this->scheduleForDirtyCheck($entity); } break; - + case self::STATE_NEW: $this->persistNew($class, $entity); break; - + case self::STATE_REMOVED: // Entity becomes managed again unset($this->entityDeletions[$oid]); - + $this->entityStates[$oid] = self::STATE_MANAGED; break; - + case self::STATE_DETACHED: // Can actually not happen right now since we assume STATE_NEW. throw new InvalidArgumentException('Detached entity passed to persist().'); - + default: throw new UnexpectedValueException(sprintf('Unexpected entity state: %s', $entityState)); } @@ -1529,7 +1529,7 @@ class UnitOfWork implements PropertyChangedListener public function remove($entity) { $visited = array(); - + $this->doRemove($entity, $visited); } @@ -1546,41 +1546,41 @@ class UnitOfWork implements PropertyChangedListener private function doRemove($entity, array &$visited) { $oid = spl_object_hash($entity); - + if (isset($visited[$oid])) { return; // Prevent infinite recursion } $visited[$oid] = $entity; // mark visited - - // Cascade first, because scheduleForDelete() removes the entity from the identity map, which + + // Cascade first, because scheduleForDelete() removes the entity from the identity map, which // can cause problems when a lazy proxy has to be initialized for the cascade operation. $this->cascadeRemove($entity, $visited); $class = $this->em->getClassMetadata(get_class($entity)); $entityState = $this->getEntityState($entity); - + switch ($entityState) { case self::STATE_NEW: case self::STATE_REMOVED: // nothing to do break; - + case self::STATE_MANAGED: if (isset($class->lifecycleCallbacks[Events::preRemove])) { $class->invokeLifecycleCallbacks(Events::preRemove, $entity); } - + if ($this->evm->hasListeners(Events::preRemove)) { $this->evm->dispatchEvent(Events::preRemove, new LifecycleEventArgs($entity, $this->em)); } - + $this->scheduleForDelete($entity); break; - + case self::STATE_DETACHED: throw new InvalidArgumentException('A detached entity can not be removed.'); - + default: throw new UnexpectedValueException(sprintf('Unexpected entity state: %s', $entityState)); } @@ -1600,7 +1600,7 @@ class UnitOfWork implements PropertyChangedListener public function merge($entity) { $visited = array(); - + return $this->doMerge($entity, $visited); } @@ -1617,7 +1617,7 @@ class UnitOfWork implements PropertyChangedListener private function doMerge($entity, array &$visited, $prevManagedCopy = null, $assoc = null) { $oid = spl_object_hash($entity); - + if (isset($visited[$oid])) { return; // Prevent infinite recursion } @@ -1631,7 +1631,7 @@ class UnitOfWork implements PropertyChangedListener // we need to fetch it from the db anyway in order to merge. // MANAGED entities are ignored by the merge operation. $managedCopy = $entity; - + if ($this->getEntityState($entity, self::STATE_DETACHED) !== self::STATE_MANAGED) { if ($entity instanceof Proxy && ! $entity->__isInitialized__) { $entity->__load(); @@ -1643,11 +1643,11 @@ class UnitOfWork implements PropertyChangedListener // If there is no ID, it is actually NEW. if ( ! $id) { $managedCopy = $class->newInstance(); - + $this->persistNew($class, $managedCopy); } else { $managedCopy = $this->tryGetById($id, $class->rootEntityName); - + if ($managedCopy) { // We have the entity in-memory already, just make sure its not removed. if ($this->getEntityState($managedCopy) == self::STATE_REMOVED) { @@ -1665,10 +1665,10 @@ class UnitOfWork implements PropertyChangedListener if ( ! $class->isIdentifierNatural()) { throw new EntityNotFoundException; } - + $managedCopy = $class->newInstance(); $class->setIdentifierValues($managedCopy, $id); - + $this->persistNew($class, $managedCopy); } } @@ -1676,7 +1676,7 @@ class UnitOfWork implements PropertyChangedListener if ($class->isVersioned) { $managedCopyVersion = $class->reflFields[$class->versionField]->getValue($managedCopy); $entityVersion = $class->reflFields[$class->versionField]->getValue($entity); - + // Throw exception if versions dont match. if ($managedCopyVersion != $entityVersion) { throw OptimisticLockException::lockFailedVersionMissmatch($entity, $entityVersion, $managedCopyVersion); @@ -1735,12 +1735,12 @@ class UnitOfWork implements PropertyChangedListener } if ($assoc2['isCascadeMerge']) { $managedCol->initialize(); - + // clear and set dirty a managed collection if its not also the same collection to merge from. if (!$managedCol->isEmpty() && $managedCol != $mergeCol) { $managedCol->unwrap()->clear(); $managedCol->setDirty(true); - + if ($assoc2['isOwningSide'] && $assoc2['type'] == ClassMetadata::MANY_TO_MANY && $class->isChangeTrackingNotify()) { $this->scheduleForDirtyCheck($managedCopy); } @@ -1748,13 +1748,13 @@ class UnitOfWork implements PropertyChangedListener } } } - + if ($class->isChangeTrackingNotify()) { // Just treat all properties as changed, there is no other choice. $this->propertyChanged($managedCopy, $name, null, $prop->getValue($managedCopy)); } } - + if ($class->isChangeTrackingDeferredExplicit()) { $this->scheduleForDirtyCheck($entity); } @@ -1763,12 +1763,12 @@ class UnitOfWork implements PropertyChangedListener if ($prevManagedCopy !== null) { $assocField = $assoc['fieldName']; $prevClass = $this->em->getClassMetadata(get_class($prevManagedCopy)); - + if ($assoc['type'] & ClassMetadata::TO_ONE) { $prevClass->reflFields[$assocField]->setValue($prevManagedCopy, $managedCopy); } else { $prevClass->reflFields[$assocField]->getValue($prevManagedCopy)->add($managedCopy); - + if ($assoc['type'] == ClassMetadata::ONE_TO_MANY) { $class->reflFields[$assoc['mappedBy']]->setValue($managedCopy, $prevManagedCopy); } @@ -1782,7 +1782,7 @@ class UnitOfWork implements PropertyChangedListener return $managedCopy; } - + /** * Detaches an entity from the persistence management. It's persistence will * no longer be managed by Doctrine. @@ -1794,10 +1794,10 @@ class UnitOfWork implements PropertyChangedListener $visited = array(); $this->doDetach($entity, $visited); } - + /** * Executes a detach operation on the given entity. - * + * * @param object $entity * @param array $visited * @param boolean $noCascade if true, don't cascade detach operation @@ -1810,7 +1810,7 @@ class UnitOfWork implements PropertyChangedListener } $visited[$oid] = $entity; // mark visited - + switch ($this->getEntityState($entity, self::STATE_DETACHED)) { case self::STATE_MANAGED: if ($this->isInIdentityMap($entity)) { @@ -1829,11 +1829,11 @@ class UnitOfWork implements PropertyChangedListener $this->cascadeDetach($entity, $visited); } } - + /** * Refreshes the state of the given entity from the database, overwriting * any local, unpersisted changes. - * + * * @param object $entity The entity to refresh. * @throws InvalidArgumentException If the entity is not MANAGED. */ @@ -1842,10 +1842,10 @@ class UnitOfWork implements PropertyChangedListener $visited = array(); $this->doRefresh($entity, $visited); } - + /** * Executes a refresh operation on an entity. - * + * * @param object $entity The entity to refresh. * @param array $visited The already visited entities during cascades. * @throws InvalidArgumentException If the entity is not MANAGED. @@ -1868,10 +1868,10 @@ class UnitOfWork implements PropertyChangedListener } else { throw new InvalidArgumentException("Entity is not MANAGED."); } - + $this->cascadeRefresh($entity, $visited); } - + /** * Cascades a refresh operation to associated entities. * @@ -1899,7 +1899,7 @@ class UnitOfWork implements PropertyChangedListener } } } - + /** * Cascades a detach operation to associated entities. * @@ -1975,7 +1975,7 @@ class UnitOfWork implements PropertyChangedListener if ( ! $assoc['isCascadePersist']) { continue; } - + $relatedEntities = $class->reflFields[$assoc['fieldName']]->getValue($entity); if (($relatedEntities instanceof Collection || is_array($relatedEntities))) { if ($relatedEntities instanceof PersistentCollection) { @@ -2000,18 +2000,18 @@ class UnitOfWork implements PropertyChangedListener private function cascadeRemove($entity, array &$visited) { $class = $this->em->getClassMetadata(get_class($entity)); - + foreach ($class->associationMappings as $assoc) { if ( ! $assoc['isCascadeRemove']) { continue; } - + if ($entity instanceof Proxy && !$entity->__isInitialized__) { $entity->__load(); } - + $relatedEntities = $class->reflFields[$assoc['fieldName']]->getValue($entity); - + if ($relatedEntities instanceof Collection || is_array($relatedEntities)) { // If its a PersistentCollection initialization is intended! No unwrap! foreach ($relatedEntities as $relatedEntity) { @@ -2035,7 +2035,7 @@ class UnitOfWork implements PropertyChangedListener if ($this->getEntityState($entity, self::STATE_DETACHED) != self::STATE_MANAGED) { throw new InvalidArgumentException("Entity is not MANAGED."); } - + $entityName = get_class($entity); $class = $this->em->getClassMetadata($entityName); @@ -2055,7 +2055,7 @@ class UnitOfWork implements PropertyChangedListener if (!$this->em->getConnection()->isTransactionActive()) { throw TransactionRequiredException::transactionRequired(); } - + $oid = spl_object_hash($entity); $this->getEntityPersister($class->name)->lock( @@ -2099,7 +2099,7 @@ class UnitOfWork implements PropertyChangedListener $this->collectionUpdates = $this->extraUpdates = $this->orphanRemovals = array(); - + if ($this->commitOrderCalculator !== null) { $this->commitOrderCalculator->clear(); } @@ -2118,13 +2118,13 @@ class UnitOfWork implements PropertyChangedListener $this->evm->dispatchEvent(Events::onClear, new Event\OnClearEventArgs($this->em, $entityName)); } } - + /** * INTERNAL: * Schedules an orphaned entity for removal. The remove() operation will be * invoked on that entity at the beginning of the next commit of this * UnitOfWork. - * + * * @ignore * @param object $entity */ @@ -2132,7 +2132,7 @@ class UnitOfWork implements PropertyChangedListener { $this->orphanRemovals[spl_object_hash($entity)] = $entity; } - + /** * INTERNAL: * Schedules a complete collection for removal when this UnitOfWork commits. @@ -2142,13 +2142,13 @@ class UnitOfWork implements PropertyChangedListener public function scheduleCollectionDeletion(PersistentCollection $coll) { $coid = spl_object_hash($coll); - + //TODO: if $coll is already scheduled for recreation ... what to do? // Just remove $coll from the scheduled recreations? if (isset($this->collectionUpdates[$coid])) { unset($this->collectionUpdates[$coid]); } - + $this->collectionDeletions[$coid] = $coll; } @@ -2167,7 +2167,7 @@ class UnitOfWork implements PropertyChangedListener * @param array $hints Any hints to account for during reconstitution/lookup of the entity. * @return object The managed entity instance. * @internal Highly performance-sensitive method. - * + * * @todo Rename: getOrCreateEntity */ public function createEntity($className, array $data, &$hints = array()) @@ -2193,8 +2193,8 @@ class UnitOfWork implements PropertyChangedListener } $id = array($class->identifier[0] => $idHash); } - - if (isset($this->identityMap[$class->rootEntityName][$idHash])) { + + if (isset($this->identityMap[$class->rootEntityName][$idHash])) { $entity = $this->identityMap[$class->rootEntityName][$idHash]; $oid = spl_object_hash($entity); if ($entity instanceof Proxy && ! $entity->__isInitialized__) { @@ -2237,17 +2237,17 @@ class UnitOfWork implements PropertyChangedListener // Loading the entity right here, if its in the eager loading map get rid of it there. unset($this->eagerLoadingEntities[$class->rootEntityName][$idHash]); - - if (isset($this->eagerLoadingEntities[$class->rootEntityName]) && + + if (isset($this->eagerLoadingEntities[$class->rootEntityName]) && ! $this->eagerLoadingEntities[$class->rootEntityName]) { unset($this->eagerLoadingEntities[$class->rootEntityName]); } - + // Properly initialize any unfetched associations, if partial objects are not allowed. if ( ! isset($hints[Query::HINT_FORCE_PARTIAL_LOAD])) { foreach ($class->associationMappings as $field => $assoc) { // Check if the association is not among the fetch-joined associations already. - if (isset($hints['fetched'][$className][$field])) { + if (isset($hints['fetchAlias']) && isset($hints['fetched'][$hints['fetchAlias']][$field])) { continue; } @@ -2283,7 +2283,7 @@ class UnitOfWork implements PropertyChangedListener $relatedIdHash = implode(' ', $associatedId); if (isset($this->identityMap[$targetClass->rootEntityName][$relatedIdHash])) { $newValue = $this->identityMap[$targetClass->rootEntityName][$relatedIdHash]; - + // if this is an uninitialized proxy, we are deferring eager loads, // this association is marked as eager fetch, and its an uninitialized proxy (wtf!) // then we cann append this entity for eager loading! @@ -2292,7 +2292,7 @@ class UnitOfWork implements PropertyChangedListener !$targetClass->isIdentifierComposite && $newValue instanceof Proxy && $newValue->__isInitialized__ === false) { - + $this->eagerLoadingEntities[$targetClass->rootEntityName][$relatedIdHash] = current($associatedId); } } else { @@ -2328,7 +2328,7 @@ class UnitOfWork implements PropertyChangedListener } $this->originalEntityData[$oid][$field] = $newValue; $class->reflFields[$field]->setValue($entity, $newValue); - + if ($assoc['inversedBy'] && $assoc['type'] & ClassMetadata::ONE_TO_ONE) { $inverseAssoc = $targetClass->associationMappings[$assoc['inversedBy']]; $targetClass->reflFields[$inverseAssoc['fieldName']]->setValue($newValue, $entity); @@ -2358,12 +2358,12 @@ class UnitOfWork implements PropertyChangedListener } } } - + //TODO: These should be invoked later, after hydration, because associations may not yet be loaded here. if (isset($class->lifecycleCallbacks[Events::postLoad])) { $class->invokeLifecycleCallbacks(Events::postLoad, $entity); } - + if ($this->evm->hasListeners(Events::postLoad)) { $this->evm->dispatchEvent(Events::postLoad, new LifecycleEventArgs($entity, $this->em)); } @@ -2386,7 +2386,7 @@ class UnitOfWork implements PropertyChangedListener foreach ($eagerLoadingEntities as $entityName => $ids) { $class = $this->em->getClassMetadata($entityName); - + $this->getEntityPersister($entityName)->loadAll( array_combine($class->identifier, array(array_values($ids))) ); @@ -2403,12 +2403,12 @@ class UnitOfWork implements PropertyChangedListener { $assoc = $collection->getMapping(); $persister = $this->getEntityPersister($assoc['targetEntity']); - + switch ($assoc['type']) { case ClassMetadata::ONE_TO_MANY: $persister->loadOneToManyCollection($assoc, $collection->getOwner(), $collection); break; - + case ClassMetadata::MANY_TO_MANY: $persister->loadManyToManyCollection($assoc, $collection->getOwner(), $collection); break; @@ -2435,14 +2435,14 @@ class UnitOfWork implements PropertyChangedListener public function getOriginalEntityData($entity) { $oid = spl_object_hash($entity); - + if (isset($this->originalEntityData[$oid])) { return $this->originalEntityData[$oid]; } - + return array(); } - + /** * @ignore */ @@ -2475,7 +2475,7 @@ class UnitOfWork implements PropertyChangedListener * @return array The identifier values. */ public function getEntityIdentifier($entity) - { + { return $this->entityIdentifiers[spl_object_hash($entity)]; } @@ -2491,11 +2491,11 @@ class UnitOfWork implements PropertyChangedListener public function tryGetById($id, $rootClassName) { $idHash = implode(' ', (array) $id); - + if (isset($this->identityMap[$rootClassName][$idHash])) { return $this->identityMap[$rootClassName][$idHash]; } - + return false; } @@ -2508,7 +2508,7 @@ class UnitOfWork implements PropertyChangedListener public function scheduleForDirtyCheck($entity) { $rootClassName = $this->em->getClassMetadata(get_class($entity))->rootEntityName; - + $this->scheduledForDirtyCheck[$rootClassName][spl_object_hash($entity)] = $entity; } @@ -2531,7 +2531,7 @@ class UnitOfWork implements PropertyChangedListener public function size() { $countArray = array_map(function ($item) { return count($item); }, $this->identityMap); - + return array_sum($countArray); } @@ -2547,28 +2547,28 @@ class UnitOfWork implements PropertyChangedListener if (isset($this->persisters[$entityName])) { return $this->persisters[$entityName]; } - + $class = $this->em->getClassMetadata($entityName); - + switch (true) { case ($class->isInheritanceTypeNone()): $persister = new Persisters\BasicEntityPersister($this->em, $class); break; - + case ($class->isInheritanceTypeSingleTable()): $persister = new Persisters\SingleTablePersister($this->em, $class); break; - + case ($class->isInheritanceTypeJoined()): $persister = new Persisters\JoinedSubclassPersister($this->em, $class); break; - + default: $persister = new Persisters\UnionSubclassPersister($this->em, $class); } - + $this->persisters[$entityName] = $persister; - + return $this->persisters[$entityName]; } @@ -2582,23 +2582,23 @@ class UnitOfWork implements PropertyChangedListener public function getCollectionPersister(array $association) { $type = $association['type']; - + if (isset($this->collectionPersisters[$type])) { return $this->collectionPersisters[$type]; } - - switch ($type) { + + switch ($type) { case ClassMetadata::ONE_TO_MANY: $persister = new Persisters\OneToManyPersister($this->em); break; - + case ClassMetadata::MANY_TO_MANY: $persister = new Persisters\ManyToManyPersister($this->em); break; } - + $this->collectionPersisters[$type] = $persister; - + return $this->collectionPersisters[$type]; } @@ -2613,11 +2613,11 @@ class UnitOfWork implements PropertyChangedListener public function registerManaged($entity, array $id, array $data) { $oid = spl_object_hash($entity); - + $this->entityIdentifiers[$oid] = $id; $this->entityStates[$oid] = self::STATE_MANAGED; $this->originalEntityData[$oid] = $data; - + $this->addToIdentityMap($entity); } @@ -2655,7 +2655,7 @@ class UnitOfWork implements PropertyChangedListener // Update changeset and mark entity for synchronization $this->entityChangeSets[$oid][$propertyName] = array($oldValue, $newValue); - + if ( ! isset($this->scheduledForDirtyCheck[$class->rootEntityName][$oid])) { $this->scheduleForDirtyCheck($entity); } @@ -2663,27 +2663,27 @@ class UnitOfWork implements PropertyChangedListener /** * Gets the currently scheduled entity insertions in this UnitOfWork. - * + * * @return array */ public function getScheduledEntityInsertions() { return $this->entityInsertions; } - + /** * Gets the currently scheduled entity updates in this UnitOfWork. - * + * * @return array */ public function getScheduledEntityUpdates() { return $this->entityUpdates; } - + /** * Gets the currently scheduled entity deletions in this UnitOfWork. - * + * * @return array */ public function getScheduledEntityDeletions() @@ -2710,10 +2710,10 @@ class UnitOfWork implements PropertyChangedListener { return $this->collectionUpdates; } - + /** * Helper method to initialize a lazy loading proxy or persistent collection. - * + * * @param object * @return void */ @@ -2721,20 +2721,20 @@ class UnitOfWork implements PropertyChangedListener { if ($obj instanceof Proxy) { $obj->__load(); - + return; - } - + } + if ($obj instanceof PersistentCollection) { $obj->initialize(); } } - + /** * Helper method to show an object as string. - * + * * @param object $obj - * @return string + * @return string */ private static function objToStr($obj) { @@ -2756,7 +2756,7 @@ class UnitOfWork implements PropertyChangedListener if ( ! is_object($object) || ! $this->isInIdentityMap($object)) { throw new InvalidArgumentException("Managed entity required"); } - + $this->readOnlyObjects[spl_object_hash($object)] = true; } @@ -2772,7 +2772,7 @@ class UnitOfWork implements PropertyChangedListener if ( ! is_object($object) ) { throw new InvalidArgumentException("Managed entity required"); } - + return isset($this->readOnlyObjects[spl_object_hash($object)]); } } diff --git a/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC1335Test.php b/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC1335Test.php index e283b9f1c..0500a0e00 100644 --- a/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC1335Test.php +++ b/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC1335Test.php @@ -49,17 +49,21 @@ class DDC1335Test extends \Doctrine\Tests\OrmFunctionalTestCase $this->assertEquals(sizeof($result['bar@bar.com']->phones), 3); $this->assertEquals(sizeof($result['foobar@foobar.com']->phones), 3); - $this->assertArrayHasKey(1, $result['foo@foo.com']->phones->toArray()); - $this->assertArrayHasKey(2, $result['foo@foo.com']->phones->toArray()); - $this->assertArrayHasKey(3, $result['foo@foo.com']->phones->toArray()); + $foo = $result['foo@foo.com']->phones->toArray(); + $bar = $result['bar@bar.com']->phones->toArray(); + $foobar = $result['foobar@foobar.com']->phones->toArray(); - $this->assertArrayHasKey(4, $result['bar@bar.com']->phones->toArray()); - $this->assertArrayHasKey(5, $result['bar@bar.com']->phones->toArray()); - $this->assertArrayHasKey(6, $result['bar@bar.com']->phones->toArray()); + $this->assertArrayHasKey(1, $foo); + $this->assertArrayHasKey(2, $foo); + $this->assertArrayHasKey(3, $foo); - $this->assertArrayHasKey(7, $result['foobar@foobar.com']->phones->toArray()); - $this->assertArrayHasKey(8, $result['foobar@foobar.com']->phones->toArray()); - $this->assertArrayHasKey(9, $result['foobar@foobar.com']->phones->toArray()); + $this->assertArrayHasKey(4, $bar); + $this->assertArrayHasKey(5, $bar); + $this->assertArrayHasKey(6, $bar); + + $this->assertArrayHasKey(7, $foobar); + $this->assertArrayHasKey(8, $foobar); + $this->assertArrayHasKey(9, $foobar); } public function testTicket() diff --git a/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC1452Test.php b/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC1452Test.php index 33241fad2..aef2d10a9 100644 --- a/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC1452Test.php +++ b/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC1452Test.php @@ -1,6 +1,8 @@ useModelSet('cms'); parent::setUp(); try { @@ -25,23 +28,60 @@ class DDC1452Test extends \Doctrine\Tests\OrmFunctionalTestCase public function testIssue() { - $a = new DDC1452EntityA(); - $a->title = "foo"; + $a1 = new DDC1452EntityA(); + $a1->title = "foo"; + + $a2 = new DDC1452EntityA(); + $a2->title = "bar"; $b = new DDC1452EntityB(); - $b->entityAFrom = $a; - $b->entityATo = $a; + $b->entityAFrom = $a1; + $b->entityATo = $a2; - $this->_em->persist($a); + $this->_em->persist($a1); + $this->_em->persist($a2); $this->_em->persist($b); $this->_em->flush(); $this->_em->clear(); $dql = "SELECT a, b, ba FROM " . __NAMESPACE__ . "\DDC1452EntityA AS a LEFT JOIN a.entitiesB AS b LEFT JOIN b.entityATo AS ba"; - $results = $this->_em->createQuery($dql)->getResult(); + $results = $this->_em->createQuery($dql)->setMaxResults(1)->getResult(); $this->assertSame($results[0], $results[0]->entitiesB[0]->entityAFrom); - $this->assertSame($results[0], $results[0]->entitiesB[0]->entityATo); + $this->assertFalse( $results[0]->entitiesB[0]->entityATo instanceof \Doctrine\ORM\Proxy\Proxy ); + $this->assertInstanceOf('Doctrine\Common\Collections\Collection', $results[0]->entitiesB[0]->entityATo->getEntitiesB()); + } + + public function testFetchJoinOneToOneFromInverse() + { + $address = new \Doctrine\Tests\Models\CMS\CmsAddress(); + $address->city = "Bonn"; + $address->country = "Germany"; + $address->street = "Somestreet"; + $address->zip = 12345; + + $user = new \Doctrine\Tests\Models\CMS\CmsUser(); + $user->name = "beberlei"; + $user->username = "beberlei"; + $user->status = "active"; + $user->address = $address; + $address->user = $user; + + $this->_em->persist($address); + $this->_em->persist($user); + $this->_em->flush(); + $this->_em->clear(); + + $dql = "SELECT a, u FROM Doctrine\Tests\Models\CMS\CmsAddress a INNER JOIN a.user u"; + $data = $this->_em->createQuery($dql)->getResult(); + $this->_em->clear(); + + $this->assertFalse($data[0]->user instanceof \Doctrine\ORM\Proxy\Proxy); + + $dql = "SELECT u, a FROM Doctrine\Tests\Models\CMS\CmsUser u INNER JOIN u.address a"; + $data = $this->_em->createQuery($dql)->getResult(); + + $this->assertFalse($data[0]->address instanceof \Doctrine\ORM\Proxy\Proxy); } } @@ -56,6 +96,16 @@ class DDC1452EntityA public $title; /** @ManyToMany(targetEntity="DDC1452EntityB", mappedBy="entityAFrom") */ public $entitiesB; + + public function __construct() + { + $this->entitiesB = new ArrayCollection(); + } + + public function getEntitiesB() + { + return $this->entitiesB; + } } /** From 9b32a2d87aa72030652659840bbc12d45d5487bc Mon Sep 17 00:00:00 2001 From: Benjamin Eberlei Date: Mon, 14 Nov 2011 23:37:02 +0100 Subject: [PATCH 107/135] DDC-1452 - Fix missing fetched parameter in BasicEntityPersister --- .../ORM/Persisters/BasicEntityPersister.php | 16 +++++----------- 1 file changed, 5 insertions(+), 11 deletions(-) diff --git a/lib/Doctrine/ORM/Persisters/BasicEntityPersister.php b/lib/Doctrine/ORM/Persisters/BasicEntityPersister.php index 8663f3014..4deece2e9 100644 --- a/lib/Doctrine/ORM/Persisters/BasicEntityPersister.php +++ b/lib/Doctrine/ORM/Persisters/BasicEntityPersister.php @@ -628,13 +628,7 @@ class BasicEntityPersister $hints = array(); if ($isInverseSingleValued) { - $hints['fetched'][$targetClass->name][$assoc['inversedBy']] = true; - - if ($targetClass->subClasses) { - foreach ($targetClass->subClasses as $targetSubclassName) { - $hints['fetched'][$targetSubclassName][$assoc['inversedBy']] = true; - } - } + $hints['fetched']["r"][$assoc['inversedBy']] = true; } /* cascade read-only status @@ -1011,7 +1005,7 @@ class BasicEntityPersister if ( ! $first) { $this->_selectJoinSql .= ' AND '; } - $this->_selectJoinSql .= $this->_getSQLTableAlias($assoc['sourceEntity']) . '.' . $sourceCol . ' = ' + $this->_selectJoinSql .= $this->_getSQLTableAlias($assoc['sourceEntity']) . '.' . $sourceCol . ' = ' . $this->_getSQLTableAlias($assoc['targetEntity'], $assocAlias) . '.' . $targetCol; $first = false; } @@ -1020,7 +1014,7 @@ class BasicEntityPersister $owningAssoc = $eagerEntity->getAssociationMapping($assoc['mappedBy']); $this->_selectJoinSql .= ' LEFT JOIN'; - $this->_selectJoinSql .= ' ' . $eagerEntity->getQuotedTableName($this->_platform) . ' ' + $this->_selectJoinSql .= ' ' . $eagerEntity->getQuotedTableName($this->_platform) . ' ' . $this->_getSQLTableAlias($eagerEntity->name, $assocAlias) . ' ON '; foreach ($owningAssoc['sourceToTargetKeyColumns'] AS $sourceCol => $targetCol) { @@ -1060,7 +1054,7 @@ class BasicEntityPersister if ($columnList) $columnList .= ', '; $resultColumnName = $this->getSQLColumnAlias($srcColumn); - $columnList .= $this->_getSQLTableAlias($class->name, ($alias == 'r' ? '' : $alias) ) + $columnList .= $this->_getSQLTableAlias($class->name, ($alias == 'r' ? '' : $alias) ) . '.' . $srcColumn . ' AS ' . $resultColumnName; $this->_rsm->addMetaResult($alias, $resultColumnName, $srcColumn, isset($assoc['id']) && $assoc['id'] === true); } @@ -1178,7 +1172,7 @@ class BasicEntityPersister */ protected function _getSelectColumnSQL($field, ClassMetadata $class, $alias = 'r') { - $sql = $this->_getSQLTableAlias($class->name, $alias == 'r' ? '' : $alias) + $sql = $this->_getSQLTableAlias($class->name, $alias == 'r' ? '' : $alias) . '.' . $class->getQuotedColumnName($field, $this->_platform); $columnAlias = $this->getSQLColumnAlias($class->columnNames[$field]); From 45d95ad13044249ac4e6182490f471d16ff1ca9b Mon Sep 17 00:00:00 2001 From: Guilherme Blanco Date: Tue, 15 Nov 2011 01:09:48 -0200 Subject: [PATCH 108/135] Fixed wrong indentation by my previous commit. --- lib/Doctrine/ORM/Internal/Hydration/ObjectHydrator.php | 2 ++ lib/Doctrine/ORM/Query/SqlWalker.php | 6 +++--- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/lib/Doctrine/ORM/Internal/Hydration/ObjectHydrator.php b/lib/Doctrine/ORM/Internal/Hydration/ObjectHydrator.php index fc41f067f..308397c16 100644 --- a/lib/Doctrine/ORM/Internal/Hydration/ObjectHydrator.php +++ b/lib/Doctrine/ORM/Internal/Hydration/ObjectHydrator.php @@ -310,7 +310,9 @@ class ObjectHydrator extends AbstractHydrator // Extract scalar values. They're appended at the end. if (isset($rowData['scalars'])) { $scalars = $rowData['scalars']; + unset($rowData['scalars']); + if (empty($rowData)) { ++$this->_resultCounter; } diff --git a/lib/Doctrine/ORM/Query/SqlWalker.php b/lib/Doctrine/ORM/Query/SqlWalker.php index ba5d29714..99dce64a8 100644 --- a/lib/Doctrine/ORM/Query/SqlWalker.php +++ b/lib/Doctrine/ORM/Query/SqlWalker.php @@ -524,9 +524,9 @@ class SqlWalker implements TreeWalker $this->_query->getHint(Query::HINT_INCLUDE_META_COLUMNS); foreach ($this->_selectedClasses as $selectedClass) { - $class = $selectedClass['class']; - $dqlAlias = $selectedClass['dqlAlias']; - $resultAlias = $selectedClass['resultAlias']; + $class = $selectedClass['class']; + $dqlAlias = $selectedClass['dqlAlias']; + $resultAlias = $selectedClass['resultAlias']; // Register as entity or joined entity result if ($this->_queryComponents[$dqlAlias]['relation'] === null) { From 77e076f1fdbe24fdb2f5a1fb1842c0b9cb7d95a4 Mon Sep 17 00:00:00 2001 From: Guilherme Blanco Date: Tue, 15 Nov 2011 01:10:27 -0200 Subject: [PATCH 109/135] Fixed DDC-1492. --- lib/Doctrine/ORM/Internal/Hydration/ObjectHydrator.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/Doctrine/ORM/Internal/Hydration/ObjectHydrator.php b/lib/Doctrine/ORM/Internal/Hydration/ObjectHydrator.php index 308397c16..896de0caa 100644 --- a/lib/Doctrine/ORM/Internal/Hydration/ObjectHydrator.php +++ b/lib/Doctrine/ORM/Internal/Hydration/ObjectHydrator.php @@ -339,7 +339,7 @@ class ObjectHydrator extends AbstractHydrator // Get a reference to the parent object to which the joined element belongs. if ($this->_rsm->isMixed && isset($this->_rootAliases[$parentAlias])) { $first = reset($this->_resultPointers); - $parentObject = $this->_resultPointers[$parentAlias][key($first)]; + $parentObject = $first[key($first)]; } else if (isset($this->_resultPointers[$parentAlias])) { $parentObject = $this->_resultPointers[$parentAlias]; } else { From 3dd5d14977b920ff3c2863907b444eaf675b051b Mon Sep 17 00:00:00 2001 From: "Fabio B. Silva" Date: Tue, 15 Nov 2011 14:28:57 -0200 Subject: [PATCH 110/135] Fixed DDC-1430 --- lib/Doctrine/ORM/Query/SqlWalker.php | 9 +- .../ORM/Functional/Ticket/DDC1430Test.php | 294 ++++++++++++++++++ .../ORM/Query/SelectSqlGenerationTest.php | 2 +- 3 files changed, 299 insertions(+), 6 deletions(-) create mode 100644 tests/Doctrine/Tests/ORM/Functional/Ticket/DDC1430Test.php diff --git a/lib/Doctrine/ORM/Query/SqlWalker.php b/lib/Doctrine/ORM/Query/SqlWalker.php index 99dce64a8..d85fdde65 100644 --- a/lib/Doctrine/ORM/Query/SqlWalker.php +++ b/lib/Doctrine/ORM/Query/SqlWalker.php @@ -1302,11 +1302,10 @@ class SqlWalker implements TreeWalker continue; } - foreach ($this->_queryComponents[$groupByItem]['metadata']->identifier AS $idField) { - $groupByItem = new AST\PathExpression(AST\PathExpression::TYPE_STATE_FIELD, $groupByItem, $idField); - $groupByItem->type = AST\PathExpression::TYPE_STATE_FIELD; - - $sqlParts[] = $this->walkGroupByItem($groupByItem); + foreach ($this->_queryComponents[$groupByItem]['metadata']->fieldNames AS $field) { + $item = new AST\PathExpression(AST\PathExpression::TYPE_STATE_FIELD, $groupByItem, $field); + $item->type = AST\PathExpression::TYPE_STATE_FIELD; + $sqlParts[] = $this->walkGroupByItem($item); } } diff --git a/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC1430Test.php b/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC1430Test.php new file mode 100644 index 000000000..4e08a904c --- /dev/null +++ b/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC1430Test.php @@ -0,0 +1,294 @@ +_schemaTool->createSchema(array( + $this->_em->getClassMetadata(__NAMESPACE__ . '\DDC1430Order'), + $this->_em->getClassMetadata(__NAMESPACE__ . '\DDC1430OrderProduct'), + )); + $this->loadFixtures(); + } catch (\Exception $exc) { + + } + } + + public function testOrderByFields() + { + $repository = $this->_em->getRepository(__NAMESPACE__ . '\DDC1430Order'); + $builder = $repository->createQueryBuilder('o'); + $query = $builder->select('o.id, o.date, COUNT(p.id) AS p_count') + ->leftJoin('o.products', 'p') + ->groupBy('o.id, o.date') + ->getQuery(); + + $this->assertEquals('SELECT o.id, o.date, COUNT(p.id) AS p_count FROM Doctrine\Tests\ORM\Functional\Ticket\DDC1430Order o LEFT JOIN o.products p GROUP BY o.id, o.date', $query->getDQL()); + $this->assertEquals('SELECT d0_.order_id AS order_id0, d0_.created_at AS created_at1, COUNT(d1_.id) AS sclr2 FROM DDC1430Order d0_ LEFT JOIN DDC1430OrderProduct d1_ ON d0_.order_id = d1_.order_id GROUP BY d0_.order_id, d0_.created_at', $query->getSQL()); + + + $result = $query->getResult(); + + $this->assertEquals(2, sizeof($result)); + + $this->assertArrayHasKey('id', $result[0]); + $this->assertArrayHasKey('id', $result[1]); + + $this->assertArrayHasKey('p_count', $result[0]); + $this->assertArrayHasKey('p_count', $result[1]); + + $this->assertEquals(1, $result[0]['id']); + $this->assertEquals(2, $result[1]['id']); + + $this->assertEquals(2, $result[0]['p_count']); + $this->assertEquals(3, $result[1]['p_count']); + } + + public function testOrderByAllObjectFields() + { + $repository = $this->_em->getRepository(__NAMESPACE__ . '\DDC1430Order'); + $builder = $repository->createQueryBuilder('o'); + $query = $builder->select('o, COUNT(p.id) AS p_count') + ->leftJoin('o.products', 'p') + ->groupBy('o.id, o.date') + ->getQuery(); + + + $this->assertEquals('SELECT o, COUNT(p.id) AS p_count FROM Doctrine\Tests\ORM\Functional\Ticket\DDC1430Order o LEFT JOIN o.products p GROUP BY o.id, o.date', $query->getDQL()); + $this->assertEquals('SELECT d0_.order_id AS order_id0, d0_.created_at AS created_at1, d0_.order_status AS order_status2, COUNT(d1_.id) AS sclr3 FROM DDC1430Order d0_ LEFT JOIN DDC1430OrderProduct d1_ ON d0_.order_id = d1_.order_id GROUP BY d0_.order_id, d0_.created_at', $query->getSQL()); + + $result = $query->getResult(); + + + $this->assertEquals(2, sizeof($result)); + + $this->assertTrue($result[0][0] instanceof DDC1430Order); + $this->assertTrue($result[1][0] instanceof DDC1430Order); + + $this->assertEquals($result[0][0]->getId(), 1); + $this->assertEquals($result[1][0]->getId(), 2); + + $this->assertEquals($result[0]['p_count'], 2); + $this->assertEquals($result[1]['p_count'], 3); + } + + public function testTicket() + { + $repository = $this->_em->getRepository(__NAMESPACE__ . '\DDC1430Order'); + $builder = $repository->createQueryBuilder('o'); + $query = $builder->select('o, COUNT(p.id) AS p_count') + ->leftJoin('o.products', 'p') + ->groupBy('o') + ->getQuery(); + + + $this->assertEquals('SELECT o, COUNT(p.id) AS p_count FROM Doctrine\Tests\ORM\Functional\Ticket\DDC1430Order o LEFT JOIN o.products p GROUP BY o', $query->getDQL()); + $this->assertEquals('SELECT d0_.order_id AS order_id0, d0_.created_at AS created_at1, d0_.order_status AS order_status2, COUNT(d1_.id) AS sclr3 FROM DDC1430Order d0_ LEFT JOIN DDC1430OrderProduct d1_ ON d0_.order_id = d1_.order_id GROUP BY d0_.order_id, d0_.created_at, d0_.order_status', $query->getSQL()); + + + $result = $query->getResult(); + + $this->assertEquals(2, sizeof($result)); + + $this->assertTrue($result[0][0] instanceof DDC1430Order); + $this->assertTrue($result[1][0] instanceof DDC1430Order); + + $this->assertEquals($result[0][0]->getId(), 1); + $this->assertEquals($result[1][0]->getId(), 2); + + $this->assertEquals($result[0]['p_count'], 2); + $this->assertEquals($result[1]['p_count'], 3); + } + + public function loadFixtures() + { + $o1 = new DDC1430Order('NEW'); + $o2 = new DDC1430Order('OK'); + + $o1->addProduct(new DDC1430OrderProduct(1.1)); + $o1->addProduct(new DDC1430OrderProduct(1.2)); + + $o2->addProduct(new DDC1430OrderProduct(2.1)); + $o2->addProduct(new DDC1430OrderProduct(2.2)); + $o2->addProduct(new DDC1430OrderProduct(2.3)); + + $this->_em->persist($o1); + $this->_em->persist($o2); + + $this->_em->flush(); + } + +} + +/** + * @Entity + */ +class DDC1430Order +{ + + /** + * @Id + * @Column(name="order_id", type="integer") + * @GeneratedValue() + */ + protected $id; + + /** + * @Column(name="created_at", type="datetime") + */ + private $date; + + /** + * @Column(name="order_status", type="string") + */ + private $status; + + /** + * @OneToMany(targetEntity="DDC1430OrderProduct", mappedBy="order", cascade={"persist", "remove"}) + * + * @var \Doctrine\Common\Collections\ArrayCollection $products + */ + private $products; + + /** + * @return integer + */ + public function getId() + { + return $this->id; + } + + public function __construct($status) + { + $this->status = $status; + $this->date = new \DateTime(); + $this->products = new \Doctrine\Common\Collections\ArrayCollection(); + } + /** + * @return \DateTime + */ + public function getDate() + { + return $this->date; + } + + /** + * @return string + */ + public function getStatus() + { + return $this->status; + } + + /** + * @param string $status + */ + public function setStatus($status) + { + $this->status = $status; + } + + /** + * @return \Doctrine\Common\Collections\ArrayCollection + */ + public function getProducts() + { + return $this->products; + } + + /** + * @param DDC1430OrderProduct $product + */ + public function addProduct(DDC1430OrderProduct $product) + { + $product->setOrder($this); + $this->products->add($product); + } +} + +/** + * @Entity + */ +class DDC1430OrderProduct +{ + + /** + * @Id + * @Column(type="integer") + * @GeneratedValue() + */ + protected $id; + + /** + * @var DDC1430Order $order + * + * @ManyToOne(targetEntity="DDC1430Order", inversedBy="products") + * @JoinColumn(name="order_id", referencedColumnName="order_id", nullable = false) + */ + private $order; + + /** + * @column(type="float") + */ + private $value; + + /** + * @param float $value + */ + public function __construct($value) + { + $this->value = $value; + } + + /** + * @return integer + */ + public function getId() + { + return $this->id; + } + + /** + * @return DDC1430Order + */ + public function getOrder() + { + return $this->order; + } + + /** + * @param DDC1430Order $order + */ + public function setOrder(DDC1430Order $order) + { + $this->order = $order; + } + + /** + * @return float + */ + public function getValue() + { + return $this->value; + } + + /** + * @param float $value + */ + public function setValue($value) + { + $this->value = $value; + } + +} \ No newline at end of file diff --git a/tests/Doctrine/Tests/ORM/Query/SelectSqlGenerationTest.php b/tests/Doctrine/Tests/ORM/Query/SelectSqlGenerationTest.php index 8eaaf37bb..7ac622f6f 100644 --- a/tests/Doctrine/Tests/ORM/Query/SelectSqlGenerationTest.php +++ b/tests/Doctrine/Tests/ORM/Query/SelectSqlGenerationTest.php @@ -945,7 +945,7 @@ class SelectSqlGenerationTest extends \Doctrine\Tests\OrmTestCase { $this->assertSqlGeneration( 'SELECT g, count(u.id) FROM Doctrine\Tests\Models\CMS\CmsGroup g JOIN g.users u GROUP BY g', - 'SELECT c0_.id AS id0, c0_.name AS name1, count(c1_.id) AS sclr2 FROM cms_groups c0_ INNER JOIN cms_users_groups c2_ ON c0_.id = c2_.group_id INNER JOIN cms_users c1_ ON c1_.id = c2_.user_id GROUP BY c0_.id' + 'SELECT c0_.id AS id0, c0_.name AS name1, count(c1_.id) AS sclr2 FROM cms_groups c0_ INNER JOIN cms_users_groups c2_ ON c0_.id = c2_.group_id INNER JOIN cms_users c1_ ON c1_.id = c2_.user_id GROUP BY c0_.id, c0_.name' ); } From 14f20c16bc5798fb53bbd59e80cfac63af2cb81f Mon Sep 17 00:00:00 2001 From: Guilherme Blanco Date: Tue, 15 Nov 2011 15:14:57 -0200 Subject: [PATCH 111/135] Changed the RSM to make is behavior as mixed if you alias an entity. --- lib/Doctrine/ORM/Query/ResultSetMapping.php | 4 + .../Tests/ORM/Hydration/ArrayHydratorTest.php | 383 +++++++-- .../ORM/Hydration/ObjectHydratorTest.php | 778 ++++++++++++++---- 3 files changed, 948 insertions(+), 217 deletions(-) diff --git a/lib/Doctrine/ORM/Query/ResultSetMapping.php b/lib/Doctrine/ORM/Query/ResultSetMapping.php index 0d9fed0b9..5c6305e25 100644 --- a/lib/Doctrine/ORM/Query/ResultSetMapping.php +++ b/lib/Doctrine/ORM/Query/ResultSetMapping.php @@ -127,6 +127,10 @@ class ResultSetMapping { $this->aliasMap[$alias] = $class; $this->entityMappings[$alias] = $resultAlias; + + if ($resultAlias !== null) { + $this->isMixed = true; + } } /** diff --git a/tests/Doctrine/Tests/ORM/Hydration/ArrayHydratorTest.php b/tests/Doctrine/Tests/ORM/Hydration/ArrayHydratorTest.php index dfd40b9ff..b72e36153 100644 --- a/tests/Doctrine/Tests/ORM/Hydration/ArrayHydratorTest.php +++ b/tests/Doctrine/Tests/ORM/Hydration/ArrayHydratorTest.php @@ -17,26 +17,14 @@ class ArrayHydratorTest extends HydrationTestCase ); } - public function provideDataForMultipleRootEntityResult() - { - return array( - array(0, 0), - array('user', 0), - array(0, 'article'), - array('user', 'article'), - ); - } - /** * SELECT PARTIAL u.{id, name} * FROM Doctrine\Tests\Models\CMS\CmsUser u - * - * @dataProvider provideDataForUserEntityResult */ - public function testSimpleEntityQuery($userEntityKey) + public function testSimpleEntityQuery() { $rsm = new ResultSetMapping; - $rsm->addEntityResult('Doctrine\Tests\Models\CMS\CmsUser', 'u', $userEntityKey ?: null); + $rsm->addEntityResult('Doctrine\Tests\Models\CMS\CmsUser', 'u'); $rsm->addFieldResult('u', 'u__id', 'id'); $rsm->addFieldResult('u', 'u__name', 'name'); @@ -68,16 +56,54 @@ class ArrayHydratorTest extends HydrationTestCase } /** - * SELECT PARTIAL u.{id, name}, PARTIAL a.{id, topic} - * FROM Doctrine\Tests\Models\CMS\CmsUser u, Doctrine\Tests\Models\CMS\CmsArticle a - * - * @dataProvider provideDataForMultipleRootEntityResult + * SELECT PARTIAL u.{id, name} AS user + * FROM Doctrine\Tests\Models\CMS\CmsUser u */ - public function testSimpleMultipleRootEntityQuery($userEntityKey, $articleEntityKey) + public function testSimpleEntityQueryWithAliasedUserEntity() { $rsm = new ResultSetMapping; - $rsm->addEntityResult('Doctrine\Tests\Models\CMS\CmsUser', 'u', $userEntityKey ?: null); - $rsm->addEntityResult('Doctrine\Tests\Models\CMS\CmsArticle', 'a', $articleEntityKey ?: null); + $rsm->addEntityResult('Doctrine\Tests\Models\CMS\CmsUser', 'u', 'user'); + $rsm->addFieldResult('u', 'u__id', 'id'); + $rsm->addFieldResult('u', 'u__name', 'name'); + + // Faked result set + $resultSet = array( + array( + 'u__id' => '1', + 'u__name' => 'romanb' + ), + array( + 'u__id' => '2', + 'u__name' => 'jwage' + ) + ); + + $stmt = new HydratorMockStatement($resultSet); + $hydrator = new \Doctrine\ORM\Internal\Hydration\ArrayHydrator($this->_em); + $result = $hydrator->hydrateAll($stmt, $rsm); + + $this->assertEquals(2, count($result)); + + $this->assertTrue(is_array($result)); + + $this->assertArrayHasKey('user', $result[0]); + $this->assertEquals(1, $result[0]['user']['id']); + $this->assertEquals('romanb', $result[0]['user']['name']); + + $this->assertArrayHasKey('user', $result[1]); + $this->assertEquals(2, $result[1]['user']['id']); + $this->assertEquals('jwage', $result[1]['user']['name']); + } + + /** + * SELECT PARTIAL u.{id, name}, PARTIAL a.{id, topic} + * FROM Doctrine\Tests\Models\CMS\CmsUser u, Doctrine\Tests\Models\CMS\CmsArticle a + */ + public function testSimpleMultipleRootEntityQuery() + { + $rsm = new ResultSetMapping; + $rsm->addEntityResult('Doctrine\Tests\Models\CMS\CmsUser', 'u'); + $rsm->addEntityResult('Doctrine\Tests\Models\CMS\CmsArticle', 'a'); $rsm->addFieldResult('u', 'u__id', 'id'); $rsm->addFieldResult('u', 'u__name', 'name'); $rsm->addFieldResult('a', 'a__id', 'id'); @@ -118,6 +144,214 @@ class ArrayHydratorTest extends HydrationTestCase $this->assertEquals('Cool things II.', $result[3]['topic']); } + /** + * SELECT PARTIAL u.{id, name} AS user, PARTIAL a.{id, topic} + * FROM Doctrine\Tests\Models\CMS\CmsUser u, Doctrine\Tests\Models\CMS\CmsArticle a + */ + public function testSimpleMultipleRootEntityQueryWithAliasedUserEntity() + { + $rsm = new ResultSetMapping; + $rsm->addEntityResult('Doctrine\Tests\Models\CMS\CmsUser', 'u', 'user'); + $rsm->addEntityResult('Doctrine\Tests\Models\CMS\CmsArticle', 'a'); + $rsm->addFieldResult('u', 'u__id', 'id'); + $rsm->addFieldResult('u', 'u__name', 'name'); + $rsm->addFieldResult('a', 'a__id', 'id'); + $rsm->addFieldResult('a', 'a__topic', 'topic'); + + // Faked result set + $resultSet = array( + array( + 'u__id' => '1', + 'u__name' => 'romanb', + 'a__id' => '1', + 'a__topic' => 'Cool things.' + ), + array( + 'u__id' => '2', + 'u__name' => 'jwage', + 'a__id' => '2', + 'a__topic' => 'Cool things II.' + ) + ); + + $stmt = new HydratorMockStatement($resultSet); + $hydrator = new \Doctrine\ORM\Internal\Hydration\ArrayHydrator($this->_em); + $result = $hydrator->hydrateAll($stmt, $rsm); + + $this->assertEquals(4, count($result)); + + $this->assertArrayHasKey('user', $result[0]); + $this->assertEquals(1, $result[0]['user']['id']); + $this->assertEquals('romanb', $result[0]['user']['name']); + + $this->assertArrayHasKey(0, $result[1]); + $this->assertEquals(1, $result[1][0]['id']); + $this->assertEquals('Cool things.', $result[1][0]['topic']); + + $this->assertArrayHasKey('user', $result[2]); + $this->assertEquals(2, $result[2]['user']['id']); + $this->assertEquals('jwage', $result[2]['user']['name']); + + $this->assertArrayHasKey(0, $result[3]); + $this->assertEquals(2, $result[3][0]['id']); + $this->assertEquals('Cool things II.', $result[3][0]['topic']); + } + + /** + * SELECT PARTIAL u.{id, name}, PARTIAL a.{id, topic} AS article + * FROM Doctrine\Tests\Models\CMS\CmsUser u, Doctrine\Tests\Models\CMS\CmsArticle a + */ + public function testSimpleMultipleRootEntityQueryWithAliasedArticleEntity() + { + $rsm = new ResultSetMapping; + $rsm->addEntityResult('Doctrine\Tests\Models\CMS\CmsUser', 'u'); + $rsm->addEntityResult('Doctrine\Tests\Models\CMS\CmsArticle', 'a', 'article'); + $rsm->addFieldResult('u', 'u__id', 'id'); + $rsm->addFieldResult('u', 'u__name', 'name'); + $rsm->addFieldResult('a', 'a__id', 'id'); + $rsm->addFieldResult('a', 'a__topic', 'topic'); + + // Faked result set + $resultSet = array( + array( + 'u__id' => '1', + 'u__name' => 'romanb', + 'a__id' => '1', + 'a__topic' => 'Cool things.' + ), + array( + 'u__id' => '2', + 'u__name' => 'jwage', + 'a__id' => '2', + 'a__topic' => 'Cool things II.' + ) + ); + + $stmt = new HydratorMockStatement($resultSet); + $hydrator = new \Doctrine\ORM\Internal\Hydration\ArrayHydrator($this->_em); + $result = $hydrator->hydrateAll($stmt, $rsm); + + $this->assertEquals(4, count($result)); + + $this->assertArrayHasKey(0, $result[0]); + $this->assertEquals(1, $result[0][0]['id']); + $this->assertEquals('romanb', $result[0][0]['name']); + + $this->assertArrayHasKey('article', $result[1]); + $this->assertEquals(1, $result[1]['article']['id']); + $this->assertEquals('Cool things.', $result[1]['article']['topic']); + + $this->assertArrayHasKey(0, $result[2]); + $this->assertEquals(2, $result[2][0]['id']); + $this->assertEquals('jwage', $result[2][0]['name']); + + $this->assertArrayHasKey('article', $result[3]); + $this->assertEquals(2, $result[3]['article']['id']); + $this->assertEquals('Cool things II.', $result[3]['article']['topic']); + } + + /** + * SELECT PARTIAL u.{id, name} AS user, PARTIAL a.{id, topic} AS article + * FROM Doctrine\Tests\Models\CMS\CmsUser u, Doctrine\Tests\Models\CMS\CmsArticle a + */ + public function testSimpleMultipleRootEntityQueryWithAliasedEntities() + { + $rsm = new ResultSetMapping; + $rsm->addEntityResult('Doctrine\Tests\Models\CMS\CmsUser', 'u', 'user'); + $rsm->addEntityResult('Doctrine\Tests\Models\CMS\CmsArticle', 'a', 'article'); + $rsm->addFieldResult('u', 'u__id', 'id'); + $rsm->addFieldResult('u', 'u__name', 'name'); + $rsm->addFieldResult('a', 'a__id', 'id'); + $rsm->addFieldResult('a', 'a__topic', 'topic'); + + // Faked result set + $resultSet = array( + array( + 'u__id' => '1', + 'u__name' => 'romanb', + 'a__id' => '1', + 'a__topic' => 'Cool things.' + ), + array( + 'u__id' => '2', + 'u__name' => 'jwage', + 'a__id' => '2', + 'a__topic' => 'Cool things II.' + ) + ); + + $stmt = new HydratorMockStatement($resultSet); + $hydrator = new \Doctrine\ORM\Internal\Hydration\ArrayHydrator($this->_em); + $result = $hydrator->hydrateAll($stmt, $rsm); + + $this->assertEquals(4, count($result)); + + $this->assertArrayHasKey('user', $result[0]); + $this->assertEquals(1, $result[0]['user']['id']); + $this->assertEquals('romanb', $result[0]['user']['name']); + + $this->assertArrayHasKey('article', $result[1]); + $this->assertEquals(1, $result[1]['article']['id']); + $this->assertEquals('Cool things.', $result[1]['article']['topic']); + + $this->assertArrayHasKey('user', $result[2]); + $this->assertEquals(2, $result[2]['user']['id']); + $this->assertEquals('jwage', $result[2]['user']['name']); + + $this->assertArrayHasKey('article', $result[3]); + $this->assertEquals(2, $result[3]['article']['id']); + $this->assertEquals('Cool things II.', $result[3]['article']['topic']); + } + + /** + * SELECT PARTIAL u.{id, status}, COUNT(p.phonenumber) AS numPhones + * FROM Doctrine\Tests\Models\CMS\CmsUser u + * JOIN u.phonenumbers p + * GROUP BY u.status, u.id + * + * @dataProvider provideDataForUserEntityResult + */ + public function testMixedQueryNormalJoin($userEntityKey) + { + $rsm = new ResultSetMapping; + $rsm->addEntityResult('Doctrine\Tests\Models\CMS\CmsUser', 'u', $userEntityKey ?: null); + $rsm->addFieldResult('u', 'u__id', 'id'); + $rsm->addFieldResult('u', 'u__status', 'status'); + $rsm->addScalarResult('sclr0', 'numPhones'); + + // Faked result set + $resultSet = array( + //row1 + array( + 'u__id' => '1', + 'u__status' => 'developer', + 'sclr0' => '2', + ), + array( + 'u__id' => '2', + 'u__status' => 'developer', + 'sclr0' => '1', + ) + ); + + $stmt = new HydratorMockStatement($resultSet); + $hydrator = new \Doctrine\ORM\Internal\Hydration\ArrayHydrator($this->_em); + $result = $hydrator->hydrateAll($stmt, $rsm); + + $this->assertEquals(2, count($result)); + $this->assertTrue(is_array($result)); + $this->assertTrue(is_array($result[0])); + $this->assertTrue(is_array($result[1])); + + // first user => 2 phonenumbers + $this->assertArrayHasKey($userEntityKey, $result[0]); + $this->assertEquals(2, $result[0]['numPhones']); + + // second user => 1 phonenumber + $this->assertArrayHasKey($userEntityKey, $result[1]); + $this->assertEquals(1, $result[1]['numPhones']); + } + /** * SELECT PARTIAL u.{id, status}, PARTIAL p.{phonenumber}, UPPER(u.name) AS nameUpper * FROM Doctrine\Tests\Models\CMS\CmsUser u @@ -186,55 +420,6 @@ class ArrayHydratorTest extends HydrationTestCase $this->assertEquals(91, $result[1][$userEntityKey]['phonenumbers'][0]['phonenumber']); } - /** - * SELECT PARTIAL u.{id, status}, COUNT(p.phonenumber) AS numPhones - * FROM Doctrine\Tests\Models\CMS\CmsUser u - * JOIN u.phonenumbers p - * GROUP BY u.status, u.id - * - * @dataProvider provideDataForUserEntityResult - */ - public function testMixedQueryNormalJoin($userEntityKey) - { - $rsm = new ResultSetMapping; - $rsm->addEntityResult('Doctrine\Tests\Models\CMS\CmsUser', 'u', $userEntityKey ?: null); - $rsm->addFieldResult('u', 'u__id', 'id'); - $rsm->addFieldResult('u', 'u__status', 'status'); - $rsm->addScalarResult('sclr0', 'numPhones'); - - // Faked result set - $resultSet = array( - //row1 - array( - 'u__id' => '1', - 'u__status' => 'developer', - 'sclr0' => '2', - ), - array( - 'u__id' => '2', - 'u__status' => 'developer', - 'sclr0' => '1', - ) - ); - - $stmt = new HydratorMockStatement($resultSet); - $hydrator = new \Doctrine\ORM\Internal\Hydration\ArrayHydrator($this->_em); - $result = $hydrator->hydrateAll($stmt, $rsm); - - $this->assertEquals(2, count($result)); - $this->assertTrue(is_array($result)); - $this->assertTrue(is_array($result[0])); - $this->assertTrue(is_array($result[1])); - - // first user => 2 phonenumbers - $this->assertArrayHasKey($userEntityKey, $result[0]); - $this->assertEquals(2, $result[0]['numPhones']); - - // second user => 1 phonenumber - $this->assertArrayHasKey($userEntityKey, $result[1]); - $this->assertEquals(1, $result[1]['numPhones']); - } - /** * SELECT PARTIAL u.{id, status}, UPPER(u.name) nameUpper * FROM Doctrine\Tests\Models\CMS\CmsUser u @@ -738,15 +923,13 @@ class ArrayHydratorTest extends HydrationTestCase } /** - * SELECT PARTIAL u.{id, status}, UPPER(u.name) AS nameUpper + * SELECT PARTIAL u.{id, status} * FROM Doctrine\Tests\Models\CMS\CmsUser u - * - * @dataProvider provideDataForUserEntityResult */ - public function testResultIteration($userEntityKey) + public function testResultIteration() { $rsm = new ResultSetMapping; - $rsm->addEntityResult('Doctrine\Tests\Models\CMS\CmsUser', 'u', $userEntityKey ?: null); + $rsm->addEntityResult('Doctrine\Tests\Models\CMS\CmsUser', 'u'); $rsm->addFieldResult('u', 'u__id', 'id'); $rsm->addFieldResult('u', 'u__name', 'name'); @@ -783,17 +966,61 @@ class ArrayHydratorTest extends HydrationTestCase } } + /** + * SELECT PARTIAL u.{id, status} + * FROM Doctrine\Tests\Models\CMS\CmsUser u + */ + public function testResultIterationWithAliasedUserEntity() + { + $rsm = new ResultSetMapping; + $rsm->addEntityResult('Doctrine\Tests\Models\CMS\CmsUser', 'u', 'user'); + $rsm->addFieldResult('u', 'u__id', 'id'); + $rsm->addFieldResult('u', 'u__name', 'name'); + + // Faked result set + $resultSet = array( + array( + 'u__id' => '1', + 'u__name' => 'romanb' + ), + array( + 'u__id' => '2', + 'u__name' => 'jwage' + ) + ); + + $stmt = new HydratorMockStatement($resultSet); + $hydrator = new \Doctrine\ORM\Internal\Hydration\ArrayHydrator($this->_em); + $iterator = $hydrator->iterate($stmt, $rsm); + $rowNum = 0; + + while (($row = $iterator->next()) !== false) { + $this->assertEquals(1, count($row)); + $this->assertArrayHasKey(0, $row); + $this->assertArrayHasKey('user', $row[0]); + + if ($rowNum == 0) { + $this->assertEquals(1, $row[0]['user']['id']); + $this->assertEquals('romanb', $row[0]['user']['name']); + } else if ($rowNum == 1) { + $this->assertEquals(2, $row[0]['user']['id']); + $this->assertEquals('jwage', $row[0]['user']['name']); + } + + ++$rowNum; + } + } + /** * SELECT PARTIAL u.{id, name} * FROM Doctrine\Tests\Models\CMS\CmsUser u * * @group DDC-644 - * @dataProvider provideDataForUserEntityResult */ - public function testSkipUnknownColumns($userEntityKey) + public function testSkipUnknownColumns() { $rsm = new ResultSetMapping; - $rsm->addEntityResult('Doctrine\Tests\Models\CMS\CmsUser', 'u', $userEntityKey ?: null); + $rsm->addEntityResult('Doctrine\Tests\Models\CMS\CmsUser', 'u'); $rsm->addFieldResult('u', 'u__id', 'id'); $rsm->addFieldResult('u', 'u__name', 'name'); diff --git a/tests/Doctrine/Tests/ORM/Hydration/ObjectHydratorTest.php b/tests/Doctrine/Tests/ORM/Hydration/ObjectHydratorTest.php index c3d6a9412..b6bc7cd38 100644 --- a/tests/Doctrine/Tests/ORM/Hydration/ObjectHydratorTest.php +++ b/tests/Doctrine/Tests/ORM/Hydration/ObjectHydratorTest.php @@ -44,13 +44,11 @@ class ObjectHydratorTest extends HydrationTestCase /** * SELECT PARTIAL u.{id,name} * FROM Doctrine\Tests\Models\CMS\CmsUser u - * - * @dataProvider provideDataForUserEntityResult */ - public function testSimpleEntityScalarFieldsQuery($userEntityKey) + public function testSimpleEntityQuery() { $rsm = new ResultSetMapping; - $rsm->addEntityResult('Doctrine\Tests\Models\CMS\CmsUser', 'u', $userEntityKey ?: null); + $rsm->addEntityResult('Doctrine\Tests\Models\CMS\CmsUser', 'u'); $rsm->addFieldResult('u', 'u__id', 'id'); $rsm->addFieldResult('u', 'u__name', 'name'); @@ -83,16 +81,13 @@ class ObjectHydratorTest extends HydrationTestCase } /** - * SELECT PARTIAL u.{id,name} + * SELECT PARTIAL u.{id,name} AS user * FROM Doctrine\Tests\Models\CMS\CmsUser u - * - * @group DDC-644 - * @dataProvider provideDataForUserEntityResult */ - public function testSkipUnknownColumns($userEntityKey) + public function testSimpleEntityQueryWithAliasedUserEntity() { $rsm = new ResultSetMapping; - $rsm->addEntityResult('Doctrine\Tests\Models\CMS\CmsUser', 'u', $userEntityKey ?: null); + $rsm->addEntityResult('Doctrine\Tests\Models\CMS\CmsUser', 'u', 'user'); $rsm->addFieldResult('u', 'u__id', 'id'); $rsm->addFieldResult('u', 'u__name', 'name'); @@ -100,40 +95,11 @@ class ObjectHydratorTest extends HydrationTestCase $resultSet = array( array( 'u__id' => '1', - 'u__name' => 'romanb', - 'foo' => 'bar', // unknown! - ), - ); - - $stmt = new HydratorMockStatement($resultSet); - $hydrator = new \Doctrine\ORM\Internal\Hydration\ObjectHydrator($this->_em); - $result = $hydrator->hydrateAll($stmt, $rsm, array(Query::HINT_FORCE_PARTIAL_LOAD => true)); - - $this->assertEquals(1, count($result)); - } - - /** - * SELECT u.id, u.name - * FROM Doctrine\Tests\Models\CMS\CmsUser u - * - * @dataProvider provideDataForUserEntityResult - */ - public function testScalarQueryWithoutResultVariables($userEntityKey) - { - $rsm = new ResultSetMapping; - $rsm->addEntityResult('Doctrine\Tests\Models\CMS\CmsUser', 'u', $userEntityKey ?: null); - $rsm->addScalarResult('sclr0', 'id'); - $rsm->addScalarResult('sclr1', 'name'); - - // Faked result set - $resultSet = array( - array( - 'sclr0' => '1', - 'sclr1' => 'romanb' + 'u__name' => 'romanb' ), array( - 'sclr0' => '2', - 'sclr1' => 'jwage' + 'u__id' => '2', + 'u__name' => 'jwage' ) ); @@ -143,27 +109,28 @@ class ObjectHydratorTest extends HydrationTestCase $this->assertEquals(2, count($result)); - $this->assertInternalType('array', $result[0]); - $this->assertInternalType('array', $result[1]); + $this->assertArrayHasKey('user', $result[0]); + $this->assertInstanceOf('Doctrine\Tests\Models\CMS\CmsUser', $result[0]['user']); - $this->assertEquals(1, $result[0]['id']); - $this->assertEquals('romanb', $result[0]['name']); + $this->assertArrayHasKey('user', $result[1]); + $this->assertInstanceOf('Doctrine\Tests\Models\CMS\CmsUser', $result[1]['user']); - $this->assertEquals(2, $result[1]['id']); - $this->assertEquals('jwage', $result[1]['name']); + $this->assertEquals(1, $result[0]['user']->id); + $this->assertEquals('romanb', $result[0]['user']->name); + + $this->assertEquals(2, $result[1]['user']->id); + $this->assertEquals('jwage', $result[1]['user']->name); } /** * SELECT PARTIAL u.{id, name}, PARTIAL a.{id, topic} * FROM Doctrine\Tests\Models\CMS\CmsUser u, Doctrine\Tests\Models\CMS\CmsArticle a - * - * @dataProvider provideDataForMultipleRootEntityResult */ - public function testSimpleMultipleRootEntityQuery($userEntityKey, $articleEntityKey) + public function testSimpleMultipleRootEntityQuery() { $rsm = new ResultSetMapping; - $rsm->addEntityResult('Doctrine\Tests\Models\CMS\CmsUser', 'u', $userEntityKey ?: null); - $rsm->addEntityResult('Doctrine\Tests\Models\CMS\CmsArticle', 'a', $articleEntityKey ?: null); + $rsm->addEntityResult('Doctrine\Tests\Models\CMS\CmsUser', 'u'); + $rsm->addEntityResult('Doctrine\Tests\Models\CMS\CmsArticle', 'a'); $rsm->addFieldResult('u', 'u__id', 'id'); $rsm->addFieldResult('u', 'u__name', 'name'); $rsm->addFieldResult('a', 'a__id', 'id'); @@ -210,50 +177,236 @@ class ObjectHydratorTest extends HydrationTestCase } /** - * SELECT p - * FROM Doctrine\Tests\Models\ECommerce\ECommerceProduct p - * - * @dataProvider provideDataForProductEntityResult + * SELECT PARTIAL u.{id, name} AS user, PARTIAL a.{id, topic} + * FROM Doctrine\Tests\Models\CMS\CmsUser u, Doctrine\Tests\Models\CMS\CmsArticle a */ - public function testCreatesProxyForLazyLoadingWithForeignKeys($productEntityKey) + public function testSimpleMultipleRootEntityQueryWithAliasedUserEntity() { $rsm = new ResultSetMapping; - $rsm->addEntityResult('Doctrine\Tests\Models\ECommerce\ECommerceProduct', 'p', $productEntityKey ?: null); - $rsm->addFieldResult('p', 'p__id', 'id'); - $rsm->addFieldResult('p', 'p__name', 'name'); - $rsm->addMetaResult('p', 'p__shipping_id', 'shipping_id'); + $rsm->addEntityResult('Doctrine\Tests\Models\CMS\CmsUser', 'u', 'user'); + $rsm->addEntityResult('Doctrine\Tests\Models\CMS\CmsArticle', 'a'); + $rsm->addFieldResult('u', 'u__id', 'id'); + $rsm->addFieldResult('u', 'u__name', 'name'); + $rsm->addFieldResult('a', 'a__id', 'id'); + $rsm->addFieldResult('a', 'a__topic', 'topic'); // Faked result set $resultSet = array( array( - 'p__id' => '1', - 'p__name' => 'Doctrine Book', - 'p__shipping_id' => 42 + 'u__id' => '1', + 'u__name' => 'romanb', + 'a__id' => '1', + 'a__topic' => 'Cool things.' + ), + array( + 'u__id' => '2', + 'u__name' => 'jwage', + 'a__id' => '2', + 'a__topic' => 'Cool things II.' ) ); - $proxyInstance = new \Doctrine\Tests\Models\ECommerce\ECommerceShipping(); + $stmt = new HydratorMockStatement($resultSet); + $hydrator = new \Doctrine\ORM\Internal\Hydration\ObjectHydrator($this->_em); + $result = $hydrator->hydrateAll($stmt, $rsm, array(Query::HINT_FORCE_PARTIAL_LOAD => true)); - // mocking the proxy factory - $proxyFactory = $this->getMock('Doctrine\ORM\Proxy\ProxyFactory', array('getProxy'), array(), '', false, false, false); - $proxyFactory->expects($this->once()) - ->method('getProxy') - ->with($this->equalTo('Doctrine\Tests\Models\ECommerce\ECommerceShipping'), array('id' => 42)) - ->will($this->returnValue($proxyInstance)); + $this->assertEquals(4, count($result)); - $this->_em->setProxyFactory($proxyFactory); + $this->assertArrayHasKey('user', $result[0]); + $this->assertArrayNotHasKey(0, $result[0]); + $this->assertInstanceOf('Doctrine\Tests\Models\CMS\CmsUser', $result[0]['user']); + $this->assertEquals(1, $result[0]['user']->id); + $this->assertEquals('romanb', $result[0]['user']->name); - // configuring lazy loading - $metadata = $this->_em->getClassMetadata('Doctrine\Tests\Models\ECommerce\ECommerceProduct'); - $metadata->associationMappings['shipping']['fetch'] = ClassMetadata::FETCH_LAZY; + $this->assertArrayHasKey(0, $result[1]); + $this->assertArrayNotHasKey('user', $result[1]); + $this->assertInstanceOf('Doctrine\Tests\Models\CMS\CmsArticle', $result[1][0]); + $this->assertEquals(1, $result[1][0]->id); + $this->assertEquals('Cool things.', $result[1][0]->topic); + + $this->assertArrayHasKey('user', $result[2]); + $this->assertArrayNotHasKey(0, $result[2]); + $this->assertInstanceOf('Doctrine\Tests\Models\CMS\CmsUser', $result[2]['user']); + $this->assertEquals(2, $result[2]['user']->id); + $this->assertEquals('jwage', $result[2]['user']->name); + + $this->assertArrayHasKey(0, $result[3]); + $this->assertArrayNotHasKey('user', $result[3]); + $this->assertInstanceOf('Doctrine\Tests\Models\CMS\CmsArticle', $result[3][0]); + $this->assertEquals(2, $result[3][0]->id); + $this->assertEquals('Cool things II.', $result[3][0]->topic); + } + + /** + * SELECT PARTIAL u.{id, name}, PARTIAL a.{id, topic} AS article + * FROM Doctrine\Tests\Models\CMS\CmsUser u, Doctrine\Tests\Models\CMS\CmsArticle a + */ + public function testSimpleMultipleRootEntityQueryWithAliasedArticleEntity() + { + $rsm = new ResultSetMapping; + $rsm->addEntityResult('Doctrine\Tests\Models\CMS\CmsUser', 'u'); + $rsm->addEntityResult('Doctrine\Tests\Models\CMS\CmsArticle', 'a', 'article'); + $rsm->addFieldResult('u', 'u__id', 'id'); + $rsm->addFieldResult('u', 'u__name', 'name'); + $rsm->addFieldResult('a', 'a__id', 'id'); + $rsm->addFieldResult('a', 'a__topic', 'topic'); + + // Faked result set + $resultSet = array( + array( + 'u__id' => '1', + 'u__name' => 'romanb', + 'a__id' => '1', + 'a__topic' => 'Cool things.' + ), + array( + 'u__id' => '2', + 'u__name' => 'jwage', + 'a__id' => '2', + 'a__topic' => 'Cool things II.' + ) + ); $stmt = new HydratorMockStatement($resultSet); $hydrator = new \Doctrine\ORM\Internal\Hydration\ObjectHydrator($this->_em); - $result = $hydrator->hydrateAll($stmt, $rsm); + $result = $hydrator->hydrateAll($stmt, $rsm, array(Query::HINT_FORCE_PARTIAL_LOAD => true)); - $this->assertEquals(1, count($result)); + $this->assertEquals(4, count($result)); - $this->assertInstanceOf('Doctrine\Tests\Models\ECommerce\ECommerceProduct', $result[0]); + $this->assertArrayHasKey(0, $result[0]); + $this->assertArrayNotHasKey('article', $result[0]); + $this->assertInstanceOf('Doctrine\Tests\Models\CMS\CmsUser', $result[0][0]); + $this->assertEquals(1, $result[0][0]->id); + $this->assertEquals('romanb', $result[0][0]->name); + + $this->assertArrayHasKey('article', $result[1]); + $this->assertArrayNotHasKey(0, $result[1]); + $this->assertInstanceOf('Doctrine\Tests\Models\CMS\CmsArticle', $result[1]['article']); + $this->assertEquals(1, $result[1]['article']->id); + $this->assertEquals('Cool things.', $result[1]['article']->topic); + + $this->assertArrayHasKey(0, $result[2]); + $this->assertArrayNotHasKey('article', $result[2]); + $this->assertInstanceOf('Doctrine\Tests\Models\CMS\CmsUser', $result[2][0]); + $this->assertEquals(2, $result[2][0]->id); + $this->assertEquals('jwage', $result[2][0]->name); + + $this->assertArrayHasKey('article', $result[3]); + $this->assertArrayNotHasKey(0, $result[3]); + $this->assertInstanceOf('Doctrine\Tests\Models\CMS\CmsArticle', $result[3]['article']); + $this->assertEquals(2, $result[3]['article']->id); + $this->assertEquals('Cool things II.', $result[3]['article']->topic); + } + + /** + * SELECT PARTIAL u.{id, name} AS user, PARTIAL a.{id, topic} AS article + * FROM Doctrine\Tests\Models\CMS\CmsUser u, Doctrine\Tests\Models\CMS\CmsArticle a + */ + public function testSimpleMultipleRootEntityQueryWithAliasedEntities() + { + $rsm = new ResultSetMapping; + $rsm->addEntityResult('Doctrine\Tests\Models\CMS\CmsUser', 'u', 'user'); + $rsm->addEntityResult('Doctrine\Tests\Models\CMS\CmsArticle', 'a', 'article'); + $rsm->addFieldResult('u', 'u__id', 'id'); + $rsm->addFieldResult('u', 'u__name', 'name'); + $rsm->addFieldResult('a', 'a__id', 'id'); + $rsm->addFieldResult('a', 'a__topic', 'topic'); + + // Faked result set + $resultSet = array( + array( + 'u__id' => '1', + 'u__name' => 'romanb', + 'a__id' => '1', + 'a__topic' => 'Cool things.' + ), + array( + 'u__id' => '2', + 'u__name' => 'jwage', + 'a__id' => '2', + 'a__topic' => 'Cool things II.' + ) + ); + + $stmt = new HydratorMockStatement($resultSet); + $hydrator = new \Doctrine\ORM\Internal\Hydration\ObjectHydrator($this->_em); + $result = $hydrator->hydrateAll($stmt, $rsm, array(Query::HINT_FORCE_PARTIAL_LOAD => true)); + + $this->assertEquals(4, count($result)); + + $this->assertArrayHasKey('user', $result[0]); + $this->assertArrayNotHasKey('article', $result[0]); + $this->assertInstanceOf('Doctrine\Tests\Models\CMS\CmsUser', $result[0]['user']); + $this->assertEquals(1, $result[0]['user']->id); + $this->assertEquals('romanb', $result[0]['user']->name); + + $this->assertArrayHasKey('article', $result[1]); + $this->assertArrayNotHasKey('user', $result[1]); + $this->assertInstanceOf('Doctrine\Tests\Models\CMS\CmsArticle', $result[1]['article']); + $this->assertEquals(1, $result[1]['article']->id); + $this->assertEquals('Cool things.', $result[1]['article']->topic); + + $this->assertArrayHasKey('user', $result[2]); + $this->assertArrayNotHasKey('article', $result[2]); + $this->assertInstanceOf('Doctrine\Tests\Models\CMS\CmsUser', $result[2]['user']); + $this->assertEquals(2, $result[2]['user']->id); + $this->assertEquals('jwage', $result[2]['user']->name); + + $this->assertArrayHasKey('article', $result[3]); + $this->assertArrayNotHasKey('user', $result[3]); + $this->assertInstanceOf('Doctrine\Tests\Models\CMS\CmsArticle', $result[3]['article']); + $this->assertEquals(2, $result[3]['article']->id); + $this->assertEquals('Cool things II.', $result[3]['article']->topic); + } + + /** + * SELECT PARTIAL u.{id, status}, COUNT(p.phonenumber) numPhones + * FROM User u + * JOIN u.phonenumbers p + * GROUP BY u.id + * + * @dataProvider provideDataForUserEntityResult + */ + public function testMixedQueryNormalJoin($userEntityKey) + { + $rsm = new ResultSetMapping; + $rsm->addEntityResult('Doctrine\Tests\Models\CMS\CmsUser', 'u', $userEntityKey ?: null); + $rsm->addFieldResult('u', 'u__id', 'id'); + $rsm->addFieldResult('u', 'u__status', 'status'); + $rsm->addScalarResult('sclr0', 'numPhones'); + + // Faked result set + $resultSet = array( + //row1 + array( + 'u__id' => '1', + 'u__status' => 'developer', + 'sclr0' => '2', + ), + array( + 'u__id' => '2', + 'u__status' => 'developer', + 'sclr0' => '1', + ) + ); + + $stmt = new HydratorMockStatement($resultSet); + $hydrator = new \Doctrine\ORM\Internal\Hydration\ObjectHydrator($this->_em); + $result = $hydrator->hydrateAll($stmt, $rsm, array(Query::HINT_FORCE_PARTIAL_LOAD => true)); + + $this->assertEquals(2, count($result)); + + $this->assertInternalType('array', $result); + $this->assertInternalType('array', $result[0]); + $this->assertInternalType('array', $result[1]); + + // first user => 2 phonenumbers + $this->assertEquals(2, $result[0]['numPhones']); + $this->assertInstanceOf('Doctrine\Tests\Models\CMS\CmsUser', $result[0][$userEntityKey]); + + // second user => 1 phonenumber + $this->assertEquals(1, $result[1]['numPhones']); + $this->assertInstanceOf('Doctrine\Tests\Models\CMS\CmsUser', $result[1][$userEntityKey]); } /** @@ -332,56 +485,6 @@ class ObjectHydratorTest extends HydrationTestCase $this->assertEquals(91, $result[1][$userEntityKey]->phonenumbers[0]->phonenumber); } - /** - * SELECT PARTIAL u.{id, status}, COUNT(p.phonenumber) numPhones - * FROM User u - * JOIN u.phonenumbers p - * GROUP BY u.id - * - * @dataProvider provideDataForUserEntityResult - */ - public function testMixedQueryNormalJoin($userEntityKey) - { - $rsm = new ResultSetMapping; - $rsm->addEntityResult('Doctrine\Tests\Models\CMS\CmsUser', 'u', $userEntityKey ?: null); - $rsm->addFieldResult('u', 'u__id', 'id'); - $rsm->addFieldResult('u', 'u__status', 'status'); - $rsm->addScalarResult('sclr0', 'numPhones'); - - // Faked result set - $resultSet = array( - //row1 - array( - 'u__id' => '1', - 'u__status' => 'developer', - 'sclr0' => '2', - ), - array( - 'u__id' => '2', - 'u__status' => 'developer', - 'sclr0' => '1', - ) - ); - - $stmt = new HydratorMockStatement($resultSet); - $hydrator = new \Doctrine\ORM\Internal\Hydration\ObjectHydrator($this->_em); - $result = $hydrator->hydrateAll($stmt, $rsm, array(Query::HINT_FORCE_PARTIAL_LOAD => true)); - - $this->assertEquals(2, count($result)); - - $this->assertInternalType('array', $result); - $this->assertInternalType('array', $result[0]); - $this->assertInternalType('array', $result[1]); - - // first user => 2 phonenumbers - $this->assertEquals(2, $result[0]['numPhones']); - $this->assertInstanceOf('Doctrine\Tests\Models\CMS\CmsUser', $result[0][$userEntityKey]); - - // second user => 1 phonenumber - $this->assertEquals(1, $result[1]['numPhones']); - $this->assertInstanceOf('Doctrine\Tests\Models\CMS\CmsUser', $result[1][$userEntityKey]); - } - /** * SELECT u, p, UPPER(u.name) nameUpper * FROM User u @@ -808,7 +911,168 @@ class ObjectHydratorTest extends HydrationTestCase $this->assertTrue(isset($result[1]->boards)); $this->assertEquals(1, count($result[1]->boards)); + } + /** + * SELECT PARTIAL u.{id,name} + * FROM Doctrine\Tests\Models\CMS\CmsUser u + * + * @group DDC-644 + */ + public function testSkipUnknownColumns() + { + $rsm = new ResultSetMapping; + $rsm->addEntityResult('Doctrine\Tests\Models\CMS\CmsUser', 'u'); + $rsm->addFieldResult('u', 'u__id', 'id'); + $rsm->addFieldResult('u', 'u__name', 'name'); + + // Faked result set + $resultSet = array( + array( + 'u__id' => '1', + 'u__name' => 'romanb', + 'foo' => 'bar', // unknown! + ), + ); + + $stmt = new HydratorMockStatement($resultSet); + $hydrator = new \Doctrine\ORM\Internal\Hydration\ObjectHydrator($this->_em); + $result = $hydrator->hydrateAll($stmt, $rsm, array(Query::HINT_FORCE_PARTIAL_LOAD => true)); + + $this->assertEquals(1, count($result)); + $this->assertInstanceOf('Doctrine\Tests\Models\CMS\CmsUser', $result[0]); + } + + /** + * SELECT u.id, u.name + * FROM Doctrine\Tests\Models\CMS\CmsUser u + * + * @dataProvider provideDataForUserEntityResult + */ + public function testScalarQueryWithoutResultVariables($userEntityKey) + { + $rsm = new ResultSetMapping; + $rsm->addEntityResult('Doctrine\Tests\Models\CMS\CmsUser', 'u', $userEntityKey ?: null); + $rsm->addScalarResult('sclr0', 'id'); + $rsm->addScalarResult('sclr1', 'name'); + + // Faked result set + $resultSet = array( + array( + 'sclr0' => '1', + 'sclr1' => 'romanb' + ), + array( + 'sclr0' => '2', + 'sclr1' => 'jwage' + ) + ); + + $stmt = new HydratorMockStatement($resultSet); + $hydrator = new \Doctrine\ORM\Internal\Hydration\ObjectHydrator($this->_em); + $result = $hydrator->hydrateAll($stmt, $rsm, array(Query::HINT_FORCE_PARTIAL_LOAD => true)); + + $this->assertEquals(2, count($result)); + + $this->assertInternalType('array', $result[0]); + $this->assertInternalType('array', $result[1]); + + $this->assertEquals(1, $result[0]['id']); + $this->assertEquals('romanb', $result[0]['name']); + + $this->assertEquals(2, $result[1]['id']); + $this->assertEquals('jwage', $result[1]['name']); + } + + /** + * SELECT p + * FROM Doctrine\Tests\Models\ECommerce\ECommerceProduct p + */ + public function testCreatesProxyForLazyLoadingWithForeignKeys() + { + $rsm = new ResultSetMapping; + $rsm->addEntityResult('Doctrine\Tests\Models\ECommerce\ECommerceProduct', 'p'); + $rsm->addFieldResult('p', 'p__id', 'id'); + $rsm->addFieldResult('p', 'p__name', 'name'); + $rsm->addMetaResult('p', 'p__shipping_id', 'shipping_id'); + + // Faked result set + $resultSet = array( + array( + 'p__id' => '1', + 'p__name' => 'Doctrine Book', + 'p__shipping_id' => 42 + ) + ); + + $proxyInstance = new \Doctrine\Tests\Models\ECommerce\ECommerceShipping(); + + // mocking the proxy factory + $proxyFactory = $this->getMock('Doctrine\ORM\Proxy\ProxyFactory', array('getProxy'), array(), '', false, false, false); + $proxyFactory->expects($this->once()) + ->method('getProxy') + ->with($this->equalTo('Doctrine\Tests\Models\ECommerce\ECommerceShipping'), array('id' => 42)) + ->will($this->returnValue($proxyInstance)); + + $this->_em->setProxyFactory($proxyFactory); + + // configuring lazy loading + $metadata = $this->_em->getClassMetadata('Doctrine\Tests\Models\ECommerce\ECommerceProduct'); + $metadata->associationMappings['shipping']['fetch'] = ClassMetadata::FETCH_LAZY; + + $stmt = new HydratorMockStatement($resultSet); + $hydrator = new \Doctrine\ORM\Internal\Hydration\ObjectHydrator($this->_em); + $result = $hydrator->hydrateAll($stmt, $rsm); + + $this->assertEquals(1, count($result)); + + $this->assertInstanceOf('Doctrine\Tests\Models\ECommerce\ECommerceProduct', $result[0]); + } + + /** + * SELECT p AS product + * FROM Doctrine\Tests\Models\ECommerce\ECommerceProduct p + */ + public function testCreatesProxyForLazyLoadingWithForeignKeysWithAliasedProductEntity() + { + $rsm = new ResultSetMapping; + $rsm->addEntityResult('Doctrine\Tests\Models\ECommerce\ECommerceProduct', 'p', 'product'); + $rsm->addFieldResult('p', 'p__id', 'id'); + $rsm->addFieldResult('p', 'p__name', 'name'); + $rsm->addMetaResult('p', 'p__shipping_id', 'shipping_id'); + + // Faked result set + $resultSet = array( + array( + 'p__id' => '1', + 'p__name' => 'Doctrine Book', + 'p__shipping_id' => 42 + ) + ); + + $proxyInstance = new \Doctrine\Tests\Models\ECommerce\ECommerceShipping(); + + // mocking the proxy factory + $proxyFactory = $this->getMock('Doctrine\ORM\Proxy\ProxyFactory', array('getProxy'), array(), '', false, false, false); + $proxyFactory->expects($this->once()) + ->method('getProxy') + ->with($this->equalTo('Doctrine\Tests\Models\ECommerce\ECommerceShipping'), array('id' => 42)) + ->will($this->returnValue($proxyInstance)); + + $this->_em->setProxyFactory($proxyFactory); + + // configuring lazy loading + $metadata = $this->_em->getClassMetadata('Doctrine\Tests\Models\ECommerce\ECommerceProduct'); + $metadata->associationMappings['shipping']['fetch'] = ClassMetadata::FETCH_LAZY; + + $stmt = new HydratorMockStatement($resultSet); + $hydrator = new \Doctrine\ORM\Internal\Hydration\ObjectHydrator($this->_em); + $result = $hydrator->hydrateAll($stmt, $rsm); + + $this->assertEquals(1, count($result)); + + $this->assertInternalType('array', $result[0]); + $this->assertInstanceOf('Doctrine\Tests\Models\ECommerce\ECommerceProduct', $result[0]['product']); } /** @@ -816,13 +1080,11 @@ class ObjectHydratorTest extends HydrationTestCase * FROM Doctrine\Tests\Models\CMS\CmsUser u * LEFT JOIN u.articles a * LEFT JOIN a.comments c - * - * @dataProvider provideDataForUserEntityResult */ - public function testChainedJoinWithEmptyCollections($userEntityKey) + public function testChainedJoinWithEmptyCollections() { $rsm = new ResultSetMapping; - $rsm->addEntityResult('Doctrine\Tests\Models\CMS\CmsUser', 'u', $userEntityKey ?: null); + $rsm->addEntityResult('Doctrine\Tests\Models\CMS\CmsUser', 'u'); $rsm->addJoinedEntityResult( 'Doctrine\Tests\Models\CMS\CmsArticle', 'a', @@ -876,6 +1138,72 @@ class ObjectHydratorTest extends HydrationTestCase $this->assertEquals(0, $result[1]->articles->count()); } + /** + * SELECT PARTIAL u.{id, status} AS user, PARTIAL a.{id, topic}, PARTIAL c.{id, topic} + * FROM Doctrine\Tests\Models\CMS\CmsUser u + * LEFT JOIN u.articles a + * LEFT JOIN a.comments c + */ + public function testChainedJoinWithEmptyCollectionsWithAliasedUserEntity() + { + $rsm = new ResultSetMapping; + $rsm->addEntityResult('Doctrine\Tests\Models\CMS\CmsUser', 'u', 'user'); + $rsm->addJoinedEntityResult( + 'Doctrine\Tests\Models\CMS\CmsArticle', + 'a', + 'u', + 'articles' + ); + $rsm->addJoinedEntityResult( + 'Doctrine\Tests\Models\CMS\CmsComment', + 'c', + 'a', + 'comments' + ); + $rsm->addFieldResult('u', 'u__id', 'id'); + $rsm->addFieldResult('u', 'u__status', 'status'); + $rsm->addFieldResult('a', 'a__id', 'id'); + $rsm->addFieldResult('a', 'a__topic', 'topic'); + $rsm->addFieldResult('c', 'c__id', 'id'); + $rsm->addFieldResult('c', 'c__topic', 'topic'); + + // Faked result set + $resultSet = array( + //row1 + array( + 'u__id' => '1', + 'u__status' => 'developer', + 'a__id' => null, + 'a__topic' => null, + 'c__id' => null, + 'c__topic' => null + ), + array( + 'u__id' => '2', + 'u__status' => 'developer', + 'a__id' => null, + 'a__topic' => null, + 'c__id' => null, + 'c__topic' => null + ), + ); + + $stmt = new HydratorMockStatement($resultSet); + $hydrator = new \Doctrine\ORM\Internal\Hydration\ObjectHydrator($this->_em); + $result = $hydrator->hydrateAll($stmt, $rsm, array(Query::HINT_FORCE_PARTIAL_LOAD => true)); + + $this->assertEquals(2, count($result)); + + $this->assertInternalType('array', $result[0]); + $this->assertInstanceOf('Doctrine\Tests\Models\CMS\CmsUser', $result[0]['user']); + + $this->assertInternalType('array', $result[1]); + $this->assertInstanceOf('Doctrine\Tests\Models\CMS\CmsUser', $result[1]['user']); + + $this->assertEquals(0, $result[0]['user']->articles->count()); + $this->assertEquals(0, $result[1]['user']->articles->count()); + } + /** * SELECT PARTIAL u.{id,status}, a.id, a.topic, c.id as cid, c.topic as ctopic * FROM CmsUser u @@ -941,12 +1269,13 @@ class ObjectHydratorTest extends HydrationTestCase }*/ /** - * @dataProvider provideDataForUserEntityResult + * SELECT PARTIAL u.{id, name} + * FROM Doctrine\Tests\Models\CMS\CmsUser u */ - public function testResultIteration($userEntityKey) + public function testResultIteration() { $rsm = new ResultSetMapping; - $rsm->addEntityResult('Doctrine\Tests\Models\CMS\CmsUser', 'u', $userEntityKey ?: null); + $rsm->addEntityResult('Doctrine\Tests\Models\CMS\CmsUser', 'u'); $rsm->addFieldResult('u', 'u__id', 'id'); $rsm->addFieldResult('u', 'u__name', 'name'); @@ -983,6 +1312,52 @@ class ObjectHydratorTest extends HydrationTestCase } } + /** + * SELECT PARTIAL u.{id, name} + * FROM Doctrine\Tests\Models\CMS\CmsUser u + */ + public function testResultIterationWithAliasedUserEntity() + { + $rsm = new ResultSetMapping; + $rsm->addEntityResult('Doctrine\Tests\Models\CMS\CmsUser', 'u', 'user'); + $rsm->addFieldResult('u', 'u__id', 'id'); + $rsm->addFieldResult('u', 'u__name', 'name'); + + // Faked result set + $resultSet = array( + array( + 'u__id' => '1', + 'u__name' => 'romanb' + ), + array( + 'u__id' => '2', + 'u__name' => 'jwage' + ) + ); + + $stmt = new HydratorMockStatement($resultSet); + $hydrator = new \Doctrine\ORM\Internal\Hydration\ObjectHydrator($this->_em); + $iterableResult = $hydrator->iterate($stmt, $rsm, array(Query::HINT_FORCE_PARTIAL_LOAD => true)); + $rowNum = 0; + + while (($row = $iterableResult->next()) !== false) { + $this->assertEquals(1, count($row)); + $this->assertArrayHasKey(0, $row); + $this->assertArrayHasKey('user', $row[0]); + $this->assertInstanceOf('Doctrine\Tests\Models\CMS\CmsUser', $row[0]['user']); + + if ($rowNum == 0) { + $this->assertEquals(1, $row[0]['user']->id); + $this->assertEquals('romanb', $row[0]['user']->name); + } else if ($rowNum == 1) { + $this->assertEquals(2, $row[0]['user']->id); + $this->assertEquals('jwage', $row[0]['user']->name); + } + + ++$rowNum; + } + } + /** * Checks if multiple joined multiple-valued collections is hydrated correctly. * @@ -990,12 +1365,11 @@ class ObjectHydratorTest extends HydrationTestCase * FROM Doctrine\Tests\Models\CMS\CmsUser u * * @group DDC-809 - * @dataProvider provideDataForUserEntityResult */ - public function testManyToManyHydration($userEntityKey) + public function testManyToManyHydration() { $rsm = new ResultSetMapping; - $rsm->addEntityResult('Doctrine\Tests\Models\CMS\CmsUser', 'u', $userEntityKey ?: null); + $rsm->addEntityResult('Doctrine\Tests\Models\CMS\CmsUser', 'u'); $rsm->addFieldResult('u', 'u__id', 'id'); $rsm->addFieldResult('u', 'u__name', 'name'); $rsm->addJoinedEntityResult('Doctrine\Tests\Models\CMS\CmsGroup', 'g', 'u', 'groups'); @@ -1107,6 +1481,132 @@ class ObjectHydratorTest extends HydrationTestCase $this->assertEquals(2, count($result[1]->phonenumbers)); } + /** + * Checks if multiple joined multiple-valued collections is hydrated correctly. + * + * SELECT PARTIAL u.{id, status} As user, PARTIAL g.{id, name}, PARTIAL p.{phonenumber} + * FROM Doctrine\Tests\Models\CMS\CmsUser u + * + * @group DDC-809 + */ + public function testManyToManyHydrationWithAliasedUserEntity() + { + $rsm = new ResultSetMapping; + $rsm->addEntityResult('Doctrine\Tests\Models\CMS\CmsUser', 'u', 'user'); + $rsm->addFieldResult('u', 'u__id', 'id'); + $rsm->addFieldResult('u', 'u__name', 'name'); + $rsm->addJoinedEntityResult('Doctrine\Tests\Models\CMS\CmsGroup', 'g', 'u', 'groups'); + $rsm->addFieldResult('g', 'g__id', 'id'); + $rsm->addFieldResult('g', 'g__name', 'name'); + $rsm->addJoinedEntityResult('Doctrine\Tests\Models\CMS\CmsPhonenumber', 'p', 'u', 'phonenumbers'); + $rsm->addFieldResult('p', 'p__phonenumber', 'phonenumber'); + + // Faked result set + $resultSet = array( + array( + 'u__id' => '1', + 'u__name' => 'romanb', + 'g__id' => '3', + 'g__name' => 'TestGroupB', + 'p__phonenumber' => 1111, + ), + array( + 'u__id' => '1', + 'u__name' => 'romanb', + 'g__id' => '5', + 'g__name' => 'TestGroupD', + 'p__phonenumber' => 1111, + ), + array( + 'u__id' => '1', + 'u__name' => 'romanb', + 'g__id' => '3', + 'g__name' => 'TestGroupB', + 'p__phonenumber' => 2222, + ), + array( + 'u__id' => '1', + 'u__name' => 'romanb', + 'g__id' => '5', + 'g__name' => 'TestGroupD', + 'p__phonenumber' => 2222, + ), + array( + 'u__id' => '2', + 'u__name' => 'jwage', + 'g__id' => '2', + 'g__name' => 'TestGroupA', + 'p__phonenumber' => 3333, + ), + array( + 'u__id' => '2', + 'u__name' => 'jwage', + 'g__id' => '3', + 'g__name' => 'TestGroupB', + 'p__phonenumber' => 3333, + ), + array( + 'u__id' => '2', + 'u__name' => 'jwage', + 'g__id' => '4', + 'g__name' => 'TestGroupC', + 'p__phonenumber' => 3333, + ), + array( + 'u__id' => '2', + 'u__name' => 'jwage', + 'g__id' => '5', + 'g__name' => 'TestGroupD', + 'p__phonenumber' => 3333, + ), + array( + 'u__id' => '2', + 'u__name' => 'jwage', + 'g__id' => '2', + 'g__name' => 'TestGroupA', + 'p__phonenumber' => 4444, + ), + array( + 'u__id' => '2', + 'u__name' => 'jwage', + 'g__id' => '3', + 'g__name' => 'TestGroupB', + 'p__phonenumber' => 4444, + ), + array( + 'u__id' => '2', + 'u__name' => 'jwage', + 'g__id' => '4', + 'g__name' => 'TestGroupC', + 'p__phonenumber' => 4444, + ), + array( + 'u__id' => '2', + 'u__name' => 'jwage', + 'g__id' => '5', + 'g__name' => 'TestGroupD', + 'p__phonenumber' => 4444, + ), + ); + + $stmt = new HydratorMockStatement($resultSet); + $hydrator = new \Doctrine\ORM\Internal\Hydration\ObjectHydrator($this->_em); + $result = $hydrator->hydrateAll($stmt, $rsm, array(Query::HINT_FORCE_PARTIAL_LOAD => true)); + + $this->assertEquals(2, count($result)); + + $this->assertInternalType('array', $result[0]); + $this->assertInstanceOf('Doctrine\Tests\Models\CMS\CmsUser', $result[0]['user']); + $this->assertInternalType('array', $result[1]); + $this->assertInstanceOf('Doctrine\Tests\Models\CMS\CmsUser', $result[1]['user']); + + $this->assertEquals(2, count($result[0]['user']->groups)); + $this->assertEquals(2, count($result[0]['user']->phonenumbers)); + + $this->assertEquals(4, count($result[1]['user']->groups)); + $this->assertEquals(2, count($result[1]['user']->phonenumbers)); + } + /** * SELECT PARTIAL u.{id, status}, UPPER(u.name) as nameUpper * FROM Doctrine\Tests\Models\CMS\CmsUser u From 3f8347a4d998958d4d41b7c50c56d21af3bcd2e4 Mon Sep 17 00:00:00 2001 From: "Fabio B. Silva" Date: Tue, 15 Nov 2011 17:00:19 -0200 Subject: [PATCH 112/135] fixed DDC-1474 --- lib/Doctrine/ORM/Query/Parser.php | 3 + .../ORM/Functional/Ticket/DDC1474Test.php | 122 ++++++++++++++++++ 2 files changed, 125 insertions(+) create mode 100644 tests/Doctrine/Tests/ORM/Functional/Ticket/DDC1474Test.php diff --git a/lib/Doctrine/ORM/Query/Parser.php b/lib/Doctrine/ORM/Query/Parser.php index 2af4bb452..1a34963f2 100644 --- a/lib/Doctrine/ORM/Query/Parser.php +++ b/lib/Doctrine/ORM/Query/Parser.php @@ -1905,6 +1905,9 @@ class Parser } else if (in_array($this->_lexer->lookahead['type'], array(Lexer::T_OPEN_PARENTHESIS, Lexer::T_INTEGER, Lexer::T_FLOAT, Lexer::T_STRING))) { // Shortcut: ScalarExpression => SimpleArithmeticExpression $expression = $this->SimpleArithmeticExpression(); + } else if (in_array($this->_lexer->lookahead['type'], array(Lexer::T_PLUS, Lexer::T_MINUS))) { + // SimpleArithmeticExpression : (- u.value ) or ( + u.value ) + $expression = $this->SimpleArithmeticExpression(); } else { $this->syntaxError( 'IdentificationVariable | ScalarExpression | AggregateExpression | FunctionDeclaration | PartialObjectExpression | "(" Subselect ")" | CaseExpression', diff --git a/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC1474Test.php b/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC1474Test.php new file mode 100644 index 000000000..e1256f3c3 --- /dev/null +++ b/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC1474Test.php @@ -0,0 +1,122 @@ +_schemaTool->createSchema(array( + $this->_em->getClassMetadata(__NAMESPACE__ . '\DDC1474Entity'), + )); + + $this->loadFixtures(); + } catch (\Exception $exc) { + + } + } + + public function testTicket() + { + $query = $this->_em->createQuery('SELECT - e.value AS value, e.id FROM ' . __NAMESPACE__ . '\DDC1474Entity e'); + $this->assertEquals('SELECT -d0_.value AS sclr0, d0_.id AS id1 FROM DDC1474Entity d0_', $query->getSQL()); + $result = $query->getResult(); + + $this->assertEquals(2, sizeof($result)); + + $this->assertEquals(1, $result[0]['id']); + $this->assertEquals(2, $result[1]['id']); + + $this->assertEquals(-10, $result[0]['value']); + $this->assertEquals(20, $result[1]['value']); + + + + + + $query = $this->_em->createQuery('SELECT e.id, + e.value AS value FROM ' . __NAMESPACE__ . '\DDC1474Entity e'); + $this->assertEquals('SELECT d0_.id AS id0, +d0_.value AS sclr1 FROM DDC1474Entity d0_', $query->getSQL()); + $result = $query->getResult(); + + $this->assertEquals(2, sizeof($result)); + + $this->assertEquals(1, $result[0]['id']); + $this->assertEquals(2, $result[1]['id']); + + $this->assertEquals(10, $result[0]['value']); + $this->assertEquals(-20, $result[1]['value']); + } + + public function loadFixtures() + { + $e1 = new DDC1474Entity(10); + $e2 = new DDC1474Entity(-20); + + $this->_em->persist($e1); + $this->_em->persist($e2); + + $this->_em->flush(); + } + +} + +/** + * @Entity + */ +class DDC1474Entity +{ + + /** + * @Id + * @Column(type="integer") + * @GeneratedValue() + */ + protected $id; + + /** + * @column(type="float") + */ + private $value; + + /** + * @param string $float + */ + public function __construct($float) + { + $this->value = $float; + } + + /** + * @return integer + */ + public function getId() + { + return $this->id; + } + + /** + * @return float + */ + public function getValue() + { + return $this->value; + } + + /** + * @param float $value + */ + public function setValue($value) + { + $this->value = $value; + } + +} From a0ee72f26460957d0008fec0c5f78bbe0a6b49a4 Mon Sep 17 00:00:00 2001 From: Benjamin Eberlei Date: Tue, 15 Nov 2011 20:03:13 +0100 Subject: [PATCH 113/135] Fix bug introduced in recent XmlDriver commit --- lib/Doctrine/ORM/Mapping/Driver/XmlDriver.php | 6 +- .../ORM/Mapping/AbstractMappingDriverTest.php | 89 ++++++++++--------- 2 files changed, 48 insertions(+), 47 deletions(-) diff --git a/lib/Doctrine/ORM/Mapping/Driver/XmlDriver.php b/lib/Doctrine/ORM/Mapping/Driver/XmlDriver.php index 2b7657763..6f66337db 100644 --- a/lib/Doctrine/ORM/Mapping/Driver/XmlDriver.php +++ b/lib/Doctrine/ORM/Mapping/Driver/XmlDriver.php @@ -171,7 +171,7 @@ class XmlDriver extends AbstractFileDriver if (isset($fieldMapping['type'])) { $mapping['type'] = (string)$fieldMapping['type']; } - + if (isset($fieldMapping['column'])) { $mapping['columnName'] = (string)$fieldMapping['column']; } @@ -224,8 +224,8 @@ class XmlDriver extends AbstractFileDriver 'id' => true, 'fieldName' => (string)$idElement['name'] ); - - if (isset($fieldMapping['type'])) { + + if (isset($idElement['type'])) { $mapping['type'] = (string)$idElement['type']; } diff --git a/tests/Doctrine/Tests/ORM/Mapping/AbstractMappingDriverTest.php b/tests/Doctrine/Tests/ORM/Mapping/AbstractMappingDriverTest.php index c95af376e..7c2887301 100644 --- a/tests/Doctrine/Tests/ORM/Mapping/AbstractMappingDriverTest.php +++ b/tests/Doctrine/Tests/ORM/Mapping/AbstractMappingDriverTest.php @@ -64,7 +64,7 @@ abstract class AbstractMappingDriverTest extends \Doctrine\Tests\OrmTestCase { $this->assertArrayHasKey('uniqueConstraints', $class->table, 'ClassMetadata should have uniqueConstraints key in table property when Unique Constraints are set.'); - + $this->assertEquals(array( "search_idx" => array("columns" => array("name", "user_email")) ), $class->table['uniqueConstraints']); @@ -138,6 +138,7 @@ abstract class AbstractMappingDriverTest extends \Doctrine\Tests\OrmTestCase public function testIdentifier($class) { $this->assertEquals(array('id'), $class->identifier); + $this->assertEquals('integer', $class->fieldMappings['id']['type']); $this->assertEquals(ClassMetadata::GENERATOR_TYPE_AUTO, $class->generatorType, "ID-Generator is not ClassMetadata::GENERATOR_TYPE_AUTO"); return $class; @@ -291,7 +292,7 @@ abstract class AbstractMappingDriverTest extends \Doctrine\Tests\OrmTestCase $class->discriminatorColumn ); } - + /** * @group DDC-869 */ @@ -300,34 +301,34 @@ abstract class AbstractMappingDriverTest extends \Doctrine\Tests\OrmTestCase $driver = $this->_loadDriver(); $em = $this->_getTestEntityManager(); $factory = new \Doctrine\ORM\Mapping\ClassMetadataFactory(); - + $em->getConfiguration()->setMetadataDriverImpl($driver); $factory->setEntityManager($em); - - + + $class = $factory->getMetadataFor('Doctrine\Tests\Models\DDC869\DDC869CreditCardPayment'); - + $this->assertTrue(isset($class->fieldMappings['id'])); $this->assertTrue(isset($class->fieldMappings['value'])); $this->assertTrue(isset($class->fieldMappings['creditCardNumber'])); $this->assertEquals($class->customRepositoryClassName, "Doctrine\Tests\Models\DDC869\DDC869PaymentRepository"); - $this->assertInstanceOf("Doctrine\Tests\Models\DDC869\DDC869PaymentRepository", + $this->assertInstanceOf("Doctrine\Tests\Models\DDC869\DDC869PaymentRepository", $em->getRepository("Doctrine\Tests\Models\DDC869\DDC869CreditCardPayment")); $this->assertTrue($em->getRepository("Doctrine\Tests\Models\DDC869\DDC869ChequePayment")->isTrue()); - - - + + + $class = $factory->getMetadataFor('Doctrine\Tests\Models\DDC869\DDC869ChequePayment'); - + $this->assertTrue(isset($class->fieldMappings['id'])); $this->assertTrue(isset($class->fieldMappings['value'])); $this->assertTrue(isset($class->fieldMappings['serialNumber'])); $this->assertEquals($class->customRepositoryClassName, "Doctrine\Tests\Models\DDC869\DDC869PaymentRepository"); - $this->assertInstanceOf("Doctrine\Tests\Models\DDC869\DDC869PaymentRepository", + $this->assertInstanceOf("Doctrine\Tests\Models\DDC869\DDC869PaymentRepository", $em->getRepository("Doctrine\Tests\Models\DDC869\DDC869ChequePayment")); $this->assertTrue($em->getRepository("Doctrine\Tests\Models\DDC869\DDC869ChequePayment")->isTrue()); } - + /** * @group DDC-1476 */ @@ -336,40 +337,40 @@ abstract class AbstractMappingDriverTest extends \Doctrine\Tests\OrmTestCase $driver = $this->_loadDriver(); $em = $this->_getTestEntityManager(); $factory = new \Doctrine\ORM\Mapping\ClassMetadataFactory(); - + $em->getConfiguration()->setMetadataDriverImpl($driver); $factory->setEntityManager($em); - - + + $class = $factory->getMetadataFor('Doctrine\Tests\Models\DDC1476\DDC1476EntityWithDefaultFieldType'); - - + + $this->assertArrayHasKey('id', $class->fieldMappings); $this->assertArrayHasKey('name', $class->fieldMappings); - - + + $this->assertArrayHasKey('type', $class->fieldMappings['id']); $this->assertArrayHasKey('type', $class->fieldMappings['name']); - + $this->assertEquals('string', $class->fieldMappings['id']['type']); $this->assertEquals('string', $class->fieldMappings['name']['type']); - - - + + + $this->assertArrayHasKey('fieldName', $class->fieldMappings['id']); $this->assertArrayHasKey('fieldName', $class->fieldMappings['name']); - + $this->assertEquals('id', $class->fieldMappings['id']['fieldName']); $this->assertEquals('name', $class->fieldMappings['name']['fieldName']); - - - + + + $this->assertArrayHasKey('columnName', $class->fieldMappings['id']); $this->assertArrayHasKey('columnName', $class->fieldMappings['name']); - + $this->assertEquals('id', $class->fieldMappings['id']['columnName']); $this->assertEquals('name', $class->fieldMappings['name']['columnName']); - + $this->assertEquals(ClassMetadataInfo::GENERATOR_TYPE_NONE, $class->generatorType); } } @@ -480,15 +481,15 @@ class User $metadata->mapOneToOne(array( 'fieldName' => 'address', 'targetEntity' => 'Doctrine\\Tests\\ORM\\Mapping\\Address', - 'cascade' => + 'cascade' => array( 0 => 'remove', ), 'mappedBy' => NULL, 'inversedBy' => 'user', - 'joinColumns' => + 'joinColumns' => array( - 0 => + 0 => array( 'name' => 'address_id', 'referencedColumnName' => 'id', @@ -500,13 +501,13 @@ class User $metadata->mapOneToMany(array( 'fieldName' => 'phonenumbers', 'targetEntity' => 'Doctrine\\Tests\\ORM\\Mapping\\Phonenumber', - 'cascade' => + 'cascade' => array( 1 => 'persist', ), 'mappedBy' => 'user', 'orphanRemoval' => true, - 'orderBy' => + 'orderBy' => array( 'number' => 'ASC', ), @@ -514,7 +515,7 @@ class User $metadata->mapManyToMany(array( 'fieldName' => 'groups', 'targetEntity' => 'Doctrine\\Tests\\ORM\\Mapping\\Group', - 'cascade' => + 'cascade' => array( 0 => 'remove', 1 => 'persist', @@ -523,12 +524,12 @@ class User 4 => 'detach', ), 'mappedBy' => NULL, - 'joinTable' => + 'joinTable' => array( 'name' => 'cms_users_groups', - 'joinColumns' => + 'joinColumns' => array( - 0 => + 0 => array( 'name' => 'user_id', 'referencedColumnName' => 'id', @@ -536,9 +537,9 @@ class User 'nullable' => false, ), ), - 'inverseJoinColumns' => + 'inverseJoinColumns' => array( - 0 => + 0 => array( 'name' => 'group_id', 'referencedColumnName' => 'id', @@ -576,7 +577,7 @@ abstract class Animal public static function loadMetadata(ClassMetadataInfo $metadata) { - + } } @@ -585,7 +586,7 @@ class Cat extends Animal { public static function loadMetadata(ClassMetadataInfo $metadata) { - + } } @@ -594,6 +595,6 @@ class Dog extends Animal { public static function loadMetadata(ClassMetadataInfo $metadata) { - + } } \ No newline at end of file From 08edf3405705fda847dfa3e3151968237d0d8fd4 Mon Sep 17 00:00:00 2001 From: "Fabio B. Silva" Date: Tue, 15 Nov 2011 17:27:17 -0200 Subject: [PATCH 114/135] move tests to SelectSqlGenerationTest --- .../ORM/Functional/Ticket/DDC1474Test.php | 122 ------------------ .../ORM/Query/SelectSqlGenerationTest.php | 70 ++++++++++ 2 files changed, 70 insertions(+), 122 deletions(-) delete mode 100644 tests/Doctrine/Tests/ORM/Functional/Ticket/DDC1474Test.php diff --git a/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC1474Test.php b/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC1474Test.php deleted file mode 100644 index e1256f3c3..000000000 --- a/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC1474Test.php +++ /dev/null @@ -1,122 +0,0 @@ -_schemaTool->createSchema(array( - $this->_em->getClassMetadata(__NAMESPACE__ . '\DDC1474Entity'), - )); - - $this->loadFixtures(); - } catch (\Exception $exc) { - - } - } - - public function testTicket() - { - $query = $this->_em->createQuery('SELECT - e.value AS value, e.id FROM ' . __NAMESPACE__ . '\DDC1474Entity e'); - $this->assertEquals('SELECT -d0_.value AS sclr0, d0_.id AS id1 FROM DDC1474Entity d0_', $query->getSQL()); - $result = $query->getResult(); - - $this->assertEquals(2, sizeof($result)); - - $this->assertEquals(1, $result[0]['id']); - $this->assertEquals(2, $result[1]['id']); - - $this->assertEquals(-10, $result[0]['value']); - $this->assertEquals(20, $result[1]['value']); - - - - - - $query = $this->_em->createQuery('SELECT e.id, + e.value AS value FROM ' . __NAMESPACE__ . '\DDC1474Entity e'); - $this->assertEquals('SELECT d0_.id AS id0, +d0_.value AS sclr1 FROM DDC1474Entity d0_', $query->getSQL()); - $result = $query->getResult(); - - $this->assertEquals(2, sizeof($result)); - - $this->assertEquals(1, $result[0]['id']); - $this->assertEquals(2, $result[1]['id']); - - $this->assertEquals(10, $result[0]['value']); - $this->assertEquals(-20, $result[1]['value']); - } - - public function loadFixtures() - { - $e1 = new DDC1474Entity(10); - $e2 = new DDC1474Entity(-20); - - $this->_em->persist($e1); - $this->_em->persist($e2); - - $this->_em->flush(); - } - -} - -/** - * @Entity - */ -class DDC1474Entity -{ - - /** - * @Id - * @Column(type="integer") - * @GeneratedValue() - */ - protected $id; - - /** - * @column(type="float") - */ - private $value; - - /** - * @param string $float - */ - public function __construct($float) - { - $this->value = $float; - } - - /** - * @return integer - */ - public function getId() - { - return $this->id; - } - - /** - * @return float - */ - public function getValue() - { - return $this->value; - } - - /** - * @param float $value - */ - public function setValue($value) - { - $this->value = $value; - } - -} diff --git a/tests/Doctrine/Tests/ORM/Query/SelectSqlGenerationTest.php b/tests/Doctrine/Tests/ORM/Query/SelectSqlGenerationTest.php index 8eaaf37bb..b2fdb59ad 100644 --- a/tests/Doctrine/Tests/ORM/Query/SelectSqlGenerationTest.php +++ b/tests/Doctrine/Tests/ORM/Query/SelectSqlGenerationTest.php @@ -1301,6 +1301,22 @@ class SelectSqlGenerationTest extends \Doctrine\Tests\OrmTestCase "SELECT d0_.article_id AS article_id0, d0_.title AS title1 FROM DDC117Article d0_ WHERE EXISTS (SELECT d1_.source_id, d1_.target_id FROM DDC117Reference d1_ WHERE d1_.source_id = d0_.article_id)" ); } + + /** + * @group DDC-1474 + */ + public function testSelectWithArithmeticExpressionBeforeField() + { + $this->assertSqlGeneration( + 'SELECT - e.value AS value, e.id FROM ' . __NAMESPACE__ . '\DDC1474Entity e', + 'SELECT -d0_.value AS sclr0, d0_.id AS id1 FROM DDC1474Entity d0_' + ); + + $this->assertSqlGeneration( + 'SELECT e.id, + e.value AS value FROM ' . __NAMESPACE__ . '\DDC1474Entity e', + 'SELECT d0_.id AS id0, +d0_.value AS sclr1 FROM DDC1474Entity d0_' + ); + } } @@ -1343,3 +1359,57 @@ class DDC1384Model */ protected $aVeryLongIdentifierThatShouldBeShortenedByTheSQLWalker_fooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo; } + + +/** + * @Entity + */ +class DDC1474Entity +{ + + /** + * @Id + * @Column(type="integer") + * @GeneratedValue() + */ + protected $id; + + /** + * @column(type="float") + */ + private $value; + + /** + * @param string $float + */ + public function __construct($float) + { + $this->value = $float; + } + + /** + * @return integer + */ + public function getId() + { + return $this->id; + } + + /** + * @return float + */ + public function getValue() + { + return $this->value; + } + + /** + * @param float $value + */ + public function setValue($value) + { + $this->value = $value; + } + +} + From 4cbd5eac9567c3c1715153fd3ccc8f847d72f566 Mon Sep 17 00:00:00 2001 From: "Fabio B. Silva" Date: Tue, 15 Nov 2011 18:54:53 -0200 Subject: [PATCH 115/135] Test Foreign Keys --- .../Tests/ORM/Functional/Ticket/DDC1430Test.php | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC1430Test.php b/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC1430Test.php index 4e08a904c..423a73649 100644 --- a/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC1430Test.php +++ b/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC1430Test.php @@ -110,9 +110,20 @@ class DDC1430Test extends \Doctrine\Tests\OrmFunctionalTestCase $this->assertEquals($result[0]['p_count'], 2); $this->assertEquals($result[1]['p_count'], 3); } + + + + public function testWithForeignKeys() + { + $query = $this->_em->createQuery('SELECT u FROM Doctrine\Tests\Models\CMS\CmsUser u GROUP BY u'); + $this->assertEquals('SELECT c0_.id AS id0, c0_.status AS status1, c0_.username AS username2, c0_.name AS name3, c0_.email_id AS email_id4 FROM cms_users c0_ GROUP BY c0_.id, c0_.status, c0_.username, c0_.name', $query->getSQL()); + + $query = $this->_em->createQuery('SELECT e FROM Doctrine\Tests\Models\CMS\CmsEmployee e GROUP BY e'); + $this->assertEquals('SELECT c0_.id AS id0, c0_.name AS name1, c0_.spouse_id AS spouse_id2 FROM cms_employees c0_ GROUP BY c0_.id, c0_.name', $query->getSQL()); + } public function loadFixtures() - { + { $o1 = new DDC1430Order('NEW'); $o2 = new DDC1430Order('OK'); From aeb2ab132b3f96a91def5ecbf82dbfb4e5c94bf7 Mon Sep 17 00:00:00 2001 From: "Fabio B. Silva" Date: Tue, 15 Nov 2011 20:27:45 -0200 Subject: [PATCH 116/135] group by all fields when entity has foreign keys --- lib/Doctrine/ORM/Query/SqlWalker.php | 11 ++++++++++- .../Tests/ORM/Functional/Ticket/DDC1430Test.php | 13 +------------ .../Tests/ORM/Query/SelectSqlGenerationTest.php | 16 ++++++++++++++++ 3 files changed, 27 insertions(+), 13 deletions(-) diff --git a/lib/Doctrine/ORM/Query/SqlWalker.php b/lib/Doctrine/ORM/Query/SqlWalker.php index d85fdde65..624f14b89 100644 --- a/lib/Doctrine/ORM/Query/SqlWalker.php +++ b/lib/Doctrine/ORM/Query/SqlWalker.php @@ -22,7 +22,8 @@ namespace Doctrine\ORM\Query; use Doctrine\DBAL\LockMode, Doctrine\ORM\Mapping\ClassMetadata, Doctrine\ORM\Query, - Doctrine\ORM\Query\QueryException; + Doctrine\ORM\Query\QueryException, + Doctrine\ORM\Mapping\ClassMetadataInfo; /** * The SqlWalker is a TreeWalker that walks over a DQL AST and constructs @@ -1307,6 +1308,14 @@ class SqlWalker implements TreeWalker $item->type = AST\PathExpression::TYPE_STATE_FIELD; $sqlParts[] = $this->walkGroupByItem($item); } + + foreach ($this->_queryComponents[$groupByItem]['metadata']->associationMappings AS $mapping) { + if ($mapping['isOwningSide'] && $mapping['type'] & ClassMetadataInfo::TO_ONE) { + $item = new AST\PathExpression(AST\PathExpression::TYPE_SINGLE_VALUED_ASSOCIATION, $groupByItem, $mapping['fieldName']); + $item->type = AST\PathExpression::TYPE_SINGLE_VALUED_ASSOCIATION; + $sqlParts[] = $this->walkGroupByItem($item); + } + } } return ' GROUP BY ' . implode(', ', $sqlParts); diff --git a/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC1430Test.php b/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC1430Test.php index 423a73649..4e08a904c 100644 --- a/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC1430Test.php +++ b/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC1430Test.php @@ -110,20 +110,9 @@ class DDC1430Test extends \Doctrine\Tests\OrmFunctionalTestCase $this->assertEquals($result[0]['p_count'], 2); $this->assertEquals($result[1]['p_count'], 3); } - - - - public function testWithForeignKeys() - { - $query = $this->_em->createQuery('SELECT u FROM Doctrine\Tests\Models\CMS\CmsUser u GROUP BY u'); - $this->assertEquals('SELECT c0_.id AS id0, c0_.status AS status1, c0_.username AS username2, c0_.name AS name3, c0_.email_id AS email_id4 FROM cms_users c0_ GROUP BY c0_.id, c0_.status, c0_.username, c0_.name', $query->getSQL()); - - $query = $this->_em->createQuery('SELECT e FROM Doctrine\Tests\Models\CMS\CmsEmployee e GROUP BY e'); - $this->assertEquals('SELECT c0_.id AS id0, c0_.name AS name1, c0_.spouse_id AS spouse_id2 FROM cms_employees c0_ GROUP BY c0_.id, c0_.name', $query->getSQL()); - } public function loadFixtures() - { + { $o1 = new DDC1430Order('NEW'); $o2 = new DDC1430Order('OK'); diff --git a/tests/Doctrine/Tests/ORM/Query/SelectSqlGenerationTest.php b/tests/Doctrine/Tests/ORM/Query/SelectSqlGenerationTest.php index 62ded22bf..7ddfe77b8 100644 --- a/tests/Doctrine/Tests/ORM/Query/SelectSqlGenerationTest.php +++ b/tests/Doctrine/Tests/ORM/Query/SelectSqlGenerationTest.php @@ -1317,6 +1317,22 @@ class SelectSqlGenerationTest extends \Doctrine\Tests\OrmTestCase 'SELECT d0_.id AS id0, +d0_.value AS sclr1 FROM DDC1474Entity d0_' ); } + + /** + * @group DDC-1430 + */ + public function testGroupByAllFieldsWhenObjectHasForeignKeys() + { + $this->assertSqlGeneration( + 'SELECT u FROM Doctrine\Tests\Models\CMS\CmsUser u GROUP BY u', + 'SELECT c0_.id AS id0, c0_.status AS status1, c0_.username AS username2, c0_.name AS name3 FROM cms_users c0_ GROUP BY c0_.id, c0_.status, c0_.username, c0_.name, c0_.email_id' + ); + + $this->assertSqlGeneration( + 'SELECT e FROM Doctrine\Tests\Models\CMS\CmsEmployee e GROUP BY e', + 'SELECT c0_.id AS id0, c0_.name AS name1 FROM cms_employees c0_ GROUP BY c0_.id, c0_.name, c0_.spouse_id' + ); + } } From f4da4591fa998e77caf47914a6511de8f7b8aab7 Mon Sep 17 00:00:00 2001 From: "Fabio B. Silva" Date: Wed, 16 Nov 2011 09:48:11 -0200 Subject: [PATCH 117/135] fix broken test on postgres --- .../ORM/Functional/Ticket/DDC1430Test.php | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC1430Test.php b/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC1430Test.php index 4e08a904c..ec2c89a09 100644 --- a/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC1430Test.php +++ b/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC1430Test.php @@ -32,10 +32,11 @@ class DDC1430Test extends \Doctrine\Tests\OrmFunctionalTestCase $query = $builder->select('o.id, o.date, COUNT(p.id) AS p_count') ->leftJoin('o.products', 'p') ->groupBy('o.id, o.date') + ->orderBy('o.id') ->getQuery(); - $this->assertEquals('SELECT o.id, o.date, COUNT(p.id) AS p_count FROM Doctrine\Tests\ORM\Functional\Ticket\DDC1430Order o LEFT JOIN o.products p GROUP BY o.id, o.date', $query->getDQL()); - $this->assertEquals('SELECT d0_.order_id AS order_id0, d0_.created_at AS created_at1, COUNT(d1_.id) AS sclr2 FROM DDC1430Order d0_ LEFT JOIN DDC1430OrderProduct d1_ ON d0_.order_id = d1_.order_id GROUP BY d0_.order_id, d0_.created_at', $query->getSQL()); + $this->assertEquals('SELECT o.id, o.date, COUNT(p.id) AS p_count FROM Doctrine\Tests\ORM\Functional\Ticket\DDC1430Order o LEFT JOIN o.products p GROUP BY o.id, o.date ORDER BY o.id ASC', $query->getDQL()); + $this->assertEquals('SELECT d0_.order_id AS order_id0, d0_.created_at AS created_at1, COUNT(d1_.id) AS sclr2 FROM DDC1430Order d0_ LEFT JOIN DDC1430OrderProduct d1_ ON d0_.order_id = d1_.order_id GROUP BY d0_.order_id, d0_.created_at ORDER BY d0_.order_id ASC', $query->getSQL()); $result = $query->getResult(); @@ -61,15 +62,16 @@ class DDC1430Test extends \Doctrine\Tests\OrmFunctionalTestCase $builder = $repository->createQueryBuilder('o'); $query = $builder->select('o, COUNT(p.id) AS p_count') ->leftJoin('o.products', 'p') - ->groupBy('o.id, o.date') + ->groupBy('o.id, o.date, o.status') + ->orderBy('o.id') ->getQuery(); - $this->assertEquals('SELECT o, COUNT(p.id) AS p_count FROM Doctrine\Tests\ORM\Functional\Ticket\DDC1430Order o LEFT JOIN o.products p GROUP BY o.id, o.date', $query->getDQL()); - $this->assertEquals('SELECT d0_.order_id AS order_id0, d0_.created_at AS created_at1, d0_.order_status AS order_status2, COUNT(d1_.id) AS sclr3 FROM DDC1430Order d0_ LEFT JOIN DDC1430OrderProduct d1_ ON d0_.order_id = d1_.order_id GROUP BY d0_.order_id, d0_.created_at', $query->getSQL()); + $this->assertEquals('SELECT o, COUNT(p.id) AS p_count FROM Doctrine\Tests\ORM\Functional\Ticket\DDC1430Order o LEFT JOIN o.products p GROUP BY o.id, o.date, o.status ORDER BY o.id ASC', $query->getDQL()); + $this->assertEquals('SELECT d0_.order_id AS order_id0, d0_.created_at AS created_at1, d0_.order_status AS order_status2, COUNT(d1_.id) AS sclr3 FROM DDC1430Order d0_ LEFT JOIN DDC1430OrderProduct d1_ ON d0_.order_id = d1_.order_id GROUP BY d0_.order_id, d0_.created_at, d0_.order_status ORDER BY d0_.order_id ASC', $query->getSQL()); $result = $query->getResult(); - + $this->assertEquals(2, sizeof($result)); @@ -90,11 +92,12 @@ class DDC1430Test extends \Doctrine\Tests\OrmFunctionalTestCase $query = $builder->select('o, COUNT(p.id) AS p_count') ->leftJoin('o.products', 'p') ->groupBy('o') + ->orderBy('o.id') ->getQuery(); - $this->assertEquals('SELECT o, COUNT(p.id) AS p_count FROM Doctrine\Tests\ORM\Functional\Ticket\DDC1430Order o LEFT JOIN o.products p GROUP BY o', $query->getDQL()); - $this->assertEquals('SELECT d0_.order_id AS order_id0, d0_.created_at AS created_at1, d0_.order_status AS order_status2, COUNT(d1_.id) AS sclr3 FROM DDC1430Order d0_ LEFT JOIN DDC1430OrderProduct d1_ ON d0_.order_id = d1_.order_id GROUP BY d0_.order_id, d0_.created_at, d0_.order_status', $query->getSQL()); + $this->assertEquals('SELECT o, COUNT(p.id) AS p_count FROM Doctrine\Tests\ORM\Functional\Ticket\DDC1430Order o LEFT JOIN o.products p GROUP BY o ORDER BY o.id ASC', $query->getDQL()); + $this->assertEquals('SELECT d0_.order_id AS order_id0, d0_.created_at AS created_at1, d0_.order_status AS order_status2, COUNT(d1_.id) AS sclr3 FROM DDC1430Order d0_ LEFT JOIN DDC1430OrderProduct d1_ ON d0_.order_id = d1_.order_id GROUP BY d0_.order_id, d0_.created_at, d0_.order_status ORDER BY d0_.order_id ASC', $query->getSQL()); $result = $query->getResult(); From 2187f334777314548e582a4d20425b3ac60b7b6b Mon Sep 17 00:00:00 2001 From: Benjamin Eberlei Date: Fri, 18 Nov 2011 13:28:56 +0100 Subject: [PATCH 118/135] Add test for DDC-1436 and DDC-1452 showing they are the same issues --- .../ORM/Functional/Ticket/DDC1436Test.php | 89 +++++++++++++++++++ 1 file changed, 89 insertions(+) create mode 100644 tests/Doctrine/Tests/ORM/Functional/Ticket/DDC1436Test.php diff --git a/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC1436Test.php b/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC1436Test.php new file mode 100644 index 000000000..906290ce3 --- /dev/null +++ b/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC1436Test.php @@ -0,0 +1,89 @@ +_schemaTool->createSchema(array( + $this->_em->getClassMetadata(__NAMESPACE__ . '\DDC1436Page'), + )); + } catch (\Exception $ignored) { + } + } + + public function testIdentityMap() + { + // fixtures + $parent = null; + for ($i = 0; $i < 3; $i++) { + $page = new DDC1436Page(); + $page->setParent($parent); + $this->_em->persist($page); + $parent = $page; + } + $this->_em->flush(); + $this->_em->clear(); + + $id = $parent->getId(); + + // step 1 + $page = $this->_em + ->createQuery('SELECT p, parent FROM ' . __NAMESPACE__ . '\DDC1436Page p LEFT JOIN p.parent parent WHERE p.id = :id') + ->setParameter('id', $id) + ->getOneOrNullResult(); + + $this->assertInstanceOf(__NAMESPACE__ . '\DDC1436Page', $page); + + // step 2 + $page = $this->_em->find(__NAMESPACE__ . '\DDC1436Page', $id); + $this->assertInstanceOf(__NAMESPACE__ . '\DDC1436Page', $page); + $this->assertInstanceOf(__NAMESPACE__ . '\DDC1436Page', $page->getParent()); + $this->assertInstanceOf(__NAMESPACE__ . '\DDC1436Page', $page->getParent()->getParent()); + } +} + +/** + * @Entity + */ +class DDC1436Page +{ + /** + * @Id + * @GeneratedValue + * @Column(type="integer", name="id") + */ + protected $id; + /** + * @ManyToOne(targetEntity="DDC1436Page") + * @JoinColumn(name="pid", referencedColumnName="id") + */ + protected $parent; + + public function getId() + { + return $this->id; + } + + /** + * @return DDC1436Page + */ + public function getParent() + { + return $this->parent; + } + + public function setParent($parent) + { + $this->parent = $parent; + } +} + From 61e371cbdc8c4ec4465ee12c4357de1b61240c89 Mon Sep 17 00:00:00 2001 From: Benjamin Eberlei Date: Fri, 18 Nov 2011 14:41:44 +0100 Subject: [PATCH 119/135] DDC-1069 - Fix error in docblocks of query builder --- lib/Doctrine/ORM/QueryBuilder.php | 118 +++++++++++++++--------------- 1 file changed, 59 insertions(+), 59 deletions(-) diff --git a/lib/Doctrine/ORM/QueryBuilder.php b/lib/Doctrine/ORM/QueryBuilder.php index 938a429ae..d033eaff2 100644 --- a/lib/Doctrine/ORM/QueryBuilder.php +++ b/lib/Doctrine/ORM/QueryBuilder.php @@ -74,7 +74,7 @@ class QueryBuilder * @var string The complete DQL string for this query. */ private $_dql; - + /** * @var array The query parameters. */ @@ -84,12 +84,12 @@ class QueryBuilder * @var array The parameter type map of this query. */ private $_paramTypes = array(); - + /** * @var integer The index of the first result to retrieve. */ private $_firstResult = null; - + /** * @var integer The maximum number of results to retrieve. */ @@ -97,7 +97,7 @@ class QueryBuilder /** * Initializes a new QueryBuilder that uses the given EntityManager. - * + * * @param EntityManager $em The EntityManager to use. */ public function __construct(EntityManager $em) @@ -217,7 +217,7 @@ class QueryBuilder ->setFirstResult($this->_firstResult) ->setMaxResults($this->_maxResults); } - + /** * Gets the FIRST root alias of the query. This is the first entity alias involved * in the construction of the query. @@ -256,7 +256,7 @@ class QueryBuilder public function getRootAliases() { $aliases = array(); - + foreach ($this->_dqlParts['from'] as &$fromClause) { if (is_string($fromClause)) { $spacePos = strrpos($fromClause, ' '); @@ -265,10 +265,10 @@ class QueryBuilder $fromClause = new Query\Expr\From($from, $alias); } - + $aliases[] = $fromClause->getAlias(); } - + return $aliases; } @@ -289,7 +289,7 @@ class QueryBuilder public function getRootEntities() { $entities = array(); - + foreach ($this->_dqlParts['from'] as &$fromClause) { if (is_string($fromClause)) { $spacePos = strrpos($fromClause, ' '); @@ -298,10 +298,10 @@ class QueryBuilder $fromClause = new Query\Expr\From($from, $alias); } - + $entities[] = $fromClause->getFrom(); } - + return $entities; } @@ -313,7 +313,7 @@ class QueryBuilder * ->select('u') * ->from('User', 'u') * ->where('u.id = :user_id') - * ->setParameter(':user_id', 1); + * ->setParameter('user_id', 1); * * * @param string|integer $key The parameter position or name. @@ -324,17 +324,17 @@ class QueryBuilder public function setParameter($key, $value, $type = null) { $key = trim($key, ':'); - + if ($type === null) { $type = Query\ParameterTypeInferer::inferType($value); } - + $this->_paramTypes[$key] = $type; $this->_params[$key] = $value; - + return $this; } - + /** * Sets a collection of query parameters for the query being constructed. * @@ -344,8 +344,8 @@ class QueryBuilder * ->from('User', 'u') * ->where('u.id = :user_id1 OR u.id = :user_id2') * ->setParameters(array( - * ':user_id1' => 1, - * ':user_id2' => 2 + * 'user_id1' => 1, + * 'user_id2' => 2 * )); * * @@ -376,7 +376,7 @@ class QueryBuilder /** * Gets a (previously set) query parameter of the query being constructed. - * + * * @param mixed $key The key (index or name) of the bound parameter. * @return mixed The value of the bound parameter. */ @@ -400,17 +400,17 @@ class QueryBuilder /** * Gets the position of the first result the query object was set to retrieve (the "offset"). * Returns NULL if {@link setFirstResult} was not applied to this QueryBuilder. - * + * * @return integer The position of the first result. */ public function getFirstResult() { return $this->_firstResult; } - + /** * Sets the maximum number of results to retrieve (the "limit"). - * + * * @param integer $maxResults The maximum number of results to retrieve. * @return QueryBuilder This QueryBuilder instance. */ @@ -419,11 +419,11 @@ class QueryBuilder $this->_maxResults = $maxResults; return $this; } - + /** * Gets the maximum number of results the query object was set to retrieve (the "limit"). * Returns NULL if {@link setMaxResults} was not applied to this query builder. - * + * * @return integer Maximum number of results. */ public function getMaxResults() @@ -437,15 +437,15 @@ class QueryBuilder * The available parts are: 'select', 'from', 'join', 'set', 'where', * 'groupBy', 'having' and 'orderBy'. * - * @param string $dqlPartName - * @param string $dqlPart - * @param string $append + * @param string $dqlPartName + * @param string $dqlPart + * @param string $append * @return QueryBuilder This QueryBuilder instance. */ public function add($dqlPartName, $dqlPart, $append = false) { $isMultiple = is_array($this->_dqlParts[$dqlPartName]); - + // This is introduced for backwards compatibility reasons. // TODO: Remove for 3.0 if ($dqlPartName == 'join') { @@ -459,11 +459,11 @@ class QueryBuilder } $dqlPart = $newDqlPart; } - + if ($append && $isMultiple) { if (is_array($dqlPart)) { $key = key($dqlPart); - + $this->_dqlParts[$dqlPartName][$key][] = $dqlPart[$key]; } else { $this->_dqlParts[$dqlPartName][] = $dqlPart; @@ -494,11 +494,11 @@ class QueryBuilder public function select($select = null) { $this->_type = self::SELECT; - + if (empty($select)) { return $this; } - + $selects = is_array($select) ? $select : func_get_args(); return $this->add('select', new Expr\Select($selects), false); @@ -521,11 +521,11 @@ class QueryBuilder public function addSelect($select = null) { $this->_type = self::SELECT; - + if (empty($select)) { return $this; } - + $selects = is_array($select) ? $select : func_get_args(); return $this->add('select', new Expr\Select($selects), true); @@ -539,7 +539,7 @@ class QueryBuilder * $qb = $em->createQueryBuilder() * ->delete('User', 'u') * ->where('u.id = :user_id'); - * ->setParameter(':user_id', 1); + * ->setParameter('user_id', 1); * * * @param string $delete The class/type whose instances are subject to the deletion. @@ -631,7 +631,7 @@ class QueryBuilder /** * Creates and adds a join over an entity association to the query. - * + * * The entities in the joined association will be fetched as part of the query * result if the alias used for the joined association is placed in the select * expressions. @@ -655,7 +655,7 @@ class QueryBuilder if (!in_array($rootAlias, $this->getRootAliases())) { $rootAlias = $this->getRootAlias(); } - + return $this->add('join', array( $rootAlias => new Expr\Join(Expr\Join::INNER_JOIN, $join, $alias, $conditionType, $condition, $indexBy) ), true); @@ -688,7 +688,7 @@ class QueryBuilder if (!in_array($rootAlias, $this->getRootAliases())) { $rootAlias = $this->getRootAlias(); } - + return $this->add('join', array( $rootAlias => new Expr\Join(Expr\Join::LEFT_JOIN, $join, $alias, $conditionType, $condition, $indexBy) ), true); @@ -743,7 +743,7 @@ class QueryBuilder if ( ! (func_num_args() == 1 && $predicates instanceof Expr\Composite)) { $predicates = new Expr\Andx(func_get_args()); } - + return $this->add('where', $predicates); } @@ -767,14 +767,14 @@ class QueryBuilder { $where = $this->getDQLPart('where'); $args = func_get_args(); - + if ($where instanceof Expr\Andx) { $where->addMultiple($args); - } else { + } else { array_unshift($args, $where); $where = new Expr\Andx($args); } - + return $this->add('where', $where, true); } @@ -798,14 +798,14 @@ class QueryBuilder { $where = $this->getDqlPart('where'); $args = func_get_args(); - + if ($where instanceof Expr\Orx) { $where->addMultiple($args); - } else { + } else { array_unshift($args, $where); $where = new Expr\Orx($args); } - + return $this->add('where', $where, true); } @@ -860,7 +860,7 @@ class QueryBuilder if ( ! (func_num_args() == 1 && ($having instanceof Expr\Andx || $having instanceof Expr\Orx))) { $having = new Expr\Andx(func_get_args()); } - + return $this->add('having', $having); } @@ -875,14 +875,14 @@ class QueryBuilder { $having = $this->getDqlPart('having'); $args = func_get_args(); - + if ($having instanceof Expr\Andx) { $having->addMultiple($args); - } else { + } else { array_unshift($args, $having); $having = new Expr\Andx($args); } - + return $this->add('having', $having); } @@ -897,10 +897,10 @@ class QueryBuilder { $having = $this->getDqlPart('having'); $args = func_get_args(); - + if ($having instanceof Expr\Orx) { $having->addMultiple($args); - } else { + } else { array_unshift($args, $having); $having = new Expr\Orx($args); } @@ -977,15 +977,15 @@ class QueryBuilder private function _getDQLForSelect() { $dql = 'SELECT' . $this->_getReducedDQLQueryPart('select', array('pre' => ' ', 'separator' => ', ')); - + $fromParts = $this->getDQLPart('from'); $joinParts = $this->getDQLPart('join'); $fromClauses = array(); - + // Loop through all FROM clauses if ( ! empty($fromParts)) { $dql .= ' FROM '; - + foreach ($fromParts as $from) { $fromClause = (string) $from; @@ -998,24 +998,24 @@ class QueryBuilder $fromClauses[] = $fromClause; } } - - $dql .= implode(', ', $fromClauses) + + $dql .= implode(', ', $fromClauses) . $this->_getReducedDQLQueryPart('where', array('pre' => ' WHERE ')) . $this->_getReducedDQLQueryPart('groupBy', array('pre' => ' GROUP BY ', 'separator' => ', ')) . $this->_getReducedDQLQueryPart('having', array('pre' => ' HAVING ')) . $this->_getReducedDQLQueryPart('orderBy', array('pre' => ' ORDER BY ', 'separator' => ', ')); - + return $dql; } private function _getReducedDQLQueryPart($queryPartName, $options = array()) { $queryPart = $this->getDQLPart($queryPartName); - + if (empty($queryPart)) { return (isset($options['empty']) ? $options['empty'] : ''); } - + return (isset($options['pre']) ? $options['pre'] : '') . (is_array($queryPart) ? implode($options['separator'], $queryPart) : $queryPart) . (isset($options['post']) ? $options['post'] : ''); From ceadc95439bd84c4d0bd955b8f2959ab4eb08207 Mon Sep 17 00:00:00 2001 From: Benjamin Eberlei Date: Fri, 18 Nov 2011 15:44:06 +0100 Subject: [PATCH 120/135] DDC-1496 - Fix bug with OneToMany collections having orphanRemoval=true and Collection#clear() being called. --- lib/Doctrine/ORM/PersistentCollection.php | 3 ++ .../Functional/OneToManyOrphanRemovalTest.php | 53 ++++++++++++------- 2 files changed, 38 insertions(+), 18 deletions(-) diff --git a/lib/Doctrine/ORM/PersistentCollection.php b/lib/Doctrine/ORM/PersistentCollection.php index b64cacfdc..06617ff9b 100644 --- a/lib/Doctrine/ORM/PersistentCollection.php +++ b/lib/Doctrine/ORM/PersistentCollection.php @@ -567,6 +567,9 @@ final class PersistentCollection implements Collection return; } if ($this->association['type'] == ClassMetadata::ONE_TO_MANY && $this->association['orphanRemoval']) { + // we need to initialize here, as orphan removal acts like implicit cascadeRemove, + // hence for event listeners we need the objects in memory. + $this->initialize(); foreach ($this->coll as $element) { $this->em->getUnitOfWork()->scheduleOrphanRemoval($element); } diff --git a/tests/Doctrine/Tests/ORM/Functional/OneToManyOrphanRemovalTest.php b/tests/Doctrine/Tests/ORM/Functional/OneToManyOrphanRemovalTest.php index 78b2fe517..28101ccc7 100644 --- a/tests/Doctrine/Tests/ORM/Functional/OneToManyOrphanRemovalTest.php +++ b/tests/Doctrine/Tests/ORM/Functional/OneToManyOrphanRemovalTest.php @@ -13,46 +13,63 @@ require_once __DIR__ . '/../../TestInit.php'; */ class OneToManyOrphanRemovalTest extends \Doctrine\Tests\OrmFunctionalTestCase { + protected $userId; + protected function setUp() { $this->useModelSet('cms'); - + parent::setUp(); - } - - public function testOrphanRemoval() - { + $user = new CmsUser; $user->status = 'dev'; $user->username = 'romanb'; $user->name = 'Roman B.'; - + $phone = new CmsPhonenumber; $phone->phonenumber = '123456'; - + $user->addPhonenumber($phone); - + $this->_em->persist($user); $this->_em->flush(); - - $userId = $user->getId(); - + + $this->userId = $user->getId(); $this->_em->clear(); - - $userProxy = $this->_em->getReference('Doctrine\Tests\Models\CMS\CmsUser', $userId); - + } + + public function testOrphanRemoval() + { + $userProxy = $this->_em->getReference('Doctrine\Tests\Models\CMS\CmsUser', $this->userId); + $this->_em->remove($userProxy); $this->_em->flush(); $this->_em->clear(); - + $query = $this->_em->createQuery('SELECT u FROM Doctrine\Tests\Models\CMS\CmsUser u'); $result = $query->getResult(); - + $this->assertEquals(0, count($result), 'CmsUser should be removed by EntityManager'); - + $query = $this->_em->createQuery('SELECT p FROM Doctrine\Tests\Models\CMS\CmsPhonenumber p'); $result = $query->getResult(); - + + $this->assertEquals(0, count($result), 'CmsPhonenumber should be removed by orphanRemoval'); + } + + /** + * @group DDC-1496 + */ + public function testOrphanRemovalUnitializedCollection() + { + $user = $this->_em->find('Doctrine\Tests\Models\CMS\CmsUser', $this->userId); + + $user->phonenumbers->clear(); + $this->_em->flush(); + + $query = $this->_em->createQuery('SELECT p FROM Doctrine\Tests\Models\CMS\CmsPhonenumber p'); + $result = $query->getResult(); + $this->assertEquals(0, count($result), 'CmsPhonenumber should be removed by orphanRemoval'); } } \ No newline at end of file From 9e8a950f2eba86a655b7446735049596b9cf03ac Mon Sep 17 00:00:00 2001 From: Benjamin Eberlei Date: Fri, 18 Nov 2011 17:29:31 +0100 Subject: [PATCH 121/135] DBAL-171 - Fix bug where params where resorted but types where not in DQL Query --- lib/Doctrine/ORM/Query.php | 89 +++++++++-------- lib/Doctrine/ORM/Query/QueryException.php | 7 +- .../Tests/ORM/Functional/QueryTest.php | 98 +++++++++++++------ 3 files changed, 121 insertions(+), 73 deletions(-) diff --git a/lib/Doctrine/ORM/Query.php b/lib/Doctrine/ORM/Query.php index ac1eb75d7..7fbafce7b 100644 --- a/lib/Doctrine/ORM/Query.php +++ b/lib/Doctrine/ORM/Query.php @@ -44,28 +44,28 @@ final class Query extends AbstractQuery * is called. */ const STATE_DIRTY = 2; - + /* Query HINTS */ /** * The refresh hint turns any query into a refresh query with the result that * any local changes in entities are overridden with the fetched values. - * + * * @var string */ const HINT_REFRESH = 'doctrine.refresh'; - - + + /** * Internal hint: is set to the proxy entity that is currently triggered for loading - * + * * @var string */ const HINT_REFRESH_ENTITY = 'doctrine.refresh.entity'; - + /** * The forcePartialLoad query hint forces a particular query to return * partial objects. - * + * * @var string * @todo Rename: HINT_OPTIMIZE */ @@ -73,9 +73,9 @@ final class Query extends AbstractQuery /** * The includeMetaColumns query hint causes meta columns like foreign keys and * discriminator columns to be selected and returned as part of the query result. - * + * * This hint does only apply to non-object queries. - * + * * @var string */ const HINT_INCLUDE_META_COLUMNS = 'doctrine.includeMetaColumns'; @@ -122,12 +122,12 @@ final class Query extends AbstractQuery * @var Doctrine\ORM\Query\ParserResult The parser result that holds DQL => SQL information. */ private $_parserResult; - + /** * @var integer The first result to return (the "offset"). */ private $_firstResult = null; - + /** * @var integer The maximum number of results to return (the "limit"). */ @@ -147,7 +147,7 @@ final class Query extends AbstractQuery * @var int Query Cache lifetime. */ private $_queryCacheTTL; - + /** * @var boolean Whether to use a query cache, if available. Defaults to TRUE. */ @@ -191,7 +191,7 @@ final class Query extends AbstractQuery /** * Parses the DQL query, if necessary, and stores the parser result. - * + * * Note: Populates $this->_parserResult as a side-effect. * * @return Doctrine\ORM\Query\ParserResult @@ -201,12 +201,12 @@ final class Query extends AbstractQuery if ($this->_state === self::STATE_CLEAN) { return $this->_parserResult; } - + // Check query cache. if ($this->_useQueryCache && ($queryCache = $this->getQueryCacheDriver())) { $hash = $this->_getQueryCacheId(); $cached = $this->_expireQueryCache ? false : $queryCache->fetch($hash); - + if ($cached === false) { // Cache miss. $parser = new Parser($this); @@ -220,9 +220,9 @@ final class Query extends AbstractQuery $parser = new Parser($this); $this->_parserResult = $parser->parse(); } - + $this->_state = self::STATE_CLEAN; - + return $this->_parserResult; } @@ -244,55 +244,62 @@ final class Query extends AbstractQuery } list($sqlParams, $types) = $this->processParameterMappings($paramMappings); - + if ($this->_resultSetMapping === null) { $this->_resultSetMapping = $this->_parserResult->getResultSetMapping(); } return $executor->execute($this->_em->getConnection(), $sqlParams, $types); } - + /** * Processes query parameter mappings - * + * * @param array $paramMappings * @return array */ private function processParameterMappings($paramMappings) { $sqlParams = $types = array(); - + foreach ($this->_params as $key => $value) { if ( ! isset($paramMappings[$key])) { throw QueryException::unknownParameter($key); } - + if (isset($this->_paramTypes[$key])) { foreach ($paramMappings[$key] as $position) { $types[$position] = $this->_paramTypes[$key]; } } - + $sqlPositions = $paramMappings[$key]; $value = array_values($this->processParameterValue($value)); $countValue = count($value); - + for ($i = 0, $l = count($sqlPositions); $i < $l; $i++) { $sqlParams[$sqlPositions[$i]] = $value[($i % $countValue)]; } } - + + if (count($sqlParams) != count($types)) { + throw QueryException::parameterTypeMissmatch(); + } + if ($sqlParams) { ksort($sqlParams); $sqlParams = array_values($sqlParams); + + ksort($types); + $types = array_values($types); } return array($sqlParams, $types); } - + /** * Process an individual parameter value - * + * * @param mixed $value * @return array */ @@ -308,7 +315,7 @@ final class Query extends AbstractQuery } return array($value); - + case is_object($value) && $this->_em->getMetadataFactory()->hasMetadataFor(get_class($value)): if ($this->_em->getUnitOfWork()->getEntityState($value) === UnitOfWork::STATE_MANAGED) { return array_values($this->_em->getUnitOfWork()->getEntityIdentifier($value)); @@ -317,7 +324,7 @@ final class Query extends AbstractQuery $class = $this->_em->getClassMetadata(get_class($value)); return array_values($class->getIdentifierValues($value)); - + default: return array($value); } @@ -334,10 +341,10 @@ final class Query extends AbstractQuery $this->_queryCache = $queryCache; return $this; } - + /** * Defines whether the query should make use of a query cache, if available. - * + * * @param boolean $bool * @return @return Query This query instance. */ @@ -471,7 +478,7 @@ final class Query extends AbstractQuery { return stripos($this->getDQL(), $dql) === false ? false : true; } - + /** * Sets the position of the first result to retrieve (the "offset"). * @@ -484,21 +491,21 @@ final class Query extends AbstractQuery $this->_state = self::STATE_DIRTY; return $this; } - + /** * Gets the position of the first result the query object was set to retrieve (the "offset"). * Returns NULL if {@link setFirstResult} was not applied to this query. - * + * * @return integer The position of the first result. */ public function getFirstResult() { return $this->_firstResult; } - + /** * Sets the maximum number of results to retrieve (the "limit"). - * + * * @param integer $maxResults * @return Query This query object. */ @@ -508,11 +515,11 @@ final class Query extends AbstractQuery $this->_state = self::STATE_DIRTY; return $this; } - + /** * Gets the maximum number of results the query object was set to retrieve (the "limit"). * Returns NULL if {@link setMaxResults} was not applied to this query. - * + * * @return integer Maximum number of results. */ public function getMaxResults() @@ -533,7 +540,7 @@ final class Query extends AbstractQuery $this->setHint(self::HINT_INTERNAL_ITERATION, true); return parent::iterate($params, $hydrationMode); } - + /** * {@inheritdoc} */ @@ -542,7 +549,7 @@ final class Query extends AbstractQuery $this->_state = self::STATE_DIRTY; return parent::setHint($name, $value); } - + /** * {@inheritdoc} */ @@ -597,7 +604,7 @@ final class Query extends AbstractQuery ksort($this->_hints); return md5( - $this->getDql() . var_export($this->_hints, true) . + $this->getDql() . var_export($this->_hints, true) . '&firstResult=' . $this->_firstResult . '&maxResult=' . $this->_maxResults . '&hydrationMode='.$this->_hydrationMode.'DOCTRINE_QUERY_CACHE_SALT' ); diff --git a/lib/Doctrine/ORM/Query/QueryException.php b/lib/Doctrine/ORM/Query/QueryException.php index b9ab2bb07..f581ecc56 100644 --- a/lib/Doctrine/ORM/Query/QueryException.php +++ b/lib/Doctrine/ORM/Query/QueryException.php @@ -77,6 +77,11 @@ class QueryException extends \Doctrine\ORM\ORMException return new self("Invalid parameter: token ".$key." is not defined in the query."); } + public static function parameterTypeMissmatch() + { + return new self("DQL Query parameter and type numbers missmatch, but have to be exactly equal."); + } + public static function invalidPathExpression($pathExpr) { return new self( @@ -140,7 +145,7 @@ class QueryException extends \Doctrine\ORM\ORMException "in the query." ); } - + public static function instanceOfUnrelatedClass($className, $rootClass) { return new self("Cannot check if a child of '" . $rootClass . "' is instanceof '" . $className . "', " . diff --git a/tests/Doctrine/Tests/ORM/Functional/QueryTest.php b/tests/Doctrine/Tests/ORM/Functional/QueryTest.php index 596d929f6..687771e83 100644 --- a/tests/Doctrine/Tests/ORM/Functional/QueryTest.php +++ b/tests/Doctrine/Tests/ORM/Functional/QueryTest.php @@ -34,9 +34,9 @@ class QueryTest extends \Doctrine\Tests\OrmFunctionalTestCase $this->_em->clear(); $query = $this->_em->createQuery("select u, upper(u.name) from Doctrine\Tests\Models\CMS\CmsUser u where u.username = 'gblanco'"); - + $result = $query->getResult(); - + $this->assertEquals(1, count($result)); $this->assertInstanceOf('Doctrine\Tests\Models\CMS\CmsUser', $result[0][0]); $this->assertEquals('Guilherme', $result[0][0]->name); @@ -109,7 +109,7 @@ class QueryTest extends \Doctrine\Tests\OrmFunctionalTestCase $q = $this->_em->createQuery('SELECT u FROM Doctrine\Tests\Models\CMS\CmsUser u WHERE u.username = ?0'); $q->setParameter(0, 'jwage'); $user = $q->getSingleResult(); - + $this->assertNotNull($user); } @@ -216,7 +216,7 @@ class QueryTest extends \Doctrine\Tests\OrmFunctionalTestCase $identityMap = $this->_em->getUnitOfWork()->getIdentityMap(); $identityMapCount = count($identityMap['Doctrine\Tests\Models\CMS\CmsArticle']); $this->assertTrue($identityMapCount>$iteratedCount); - + $iteratedCount++; } @@ -235,7 +235,7 @@ class QueryTest extends \Doctrine\Tests\OrmFunctionalTestCase $query = $this->_em->createQuery("SELECT u, a FROM Doctrine\Tests\Models\CMS\CmsUser u JOIN u.articles a"); $articles = $query->iterate(); } - + /** * @expectedException Doctrine\ORM\NoResultException */ @@ -366,7 +366,7 @@ class QueryTest extends \Doctrine\Tests\OrmFunctionalTestCase $this->assertInstanceOf('Doctrine\ORM\Proxy\Proxy', $result[0]->user); $this->assertFalse($result[0]->user->__isInitialized__); } - + /** * @group DDC-952 */ @@ -386,11 +386,11 @@ class QueryTest extends \Doctrine\Tests\OrmFunctionalTestCase } $this->_em->flush(); $this->_em->clear(); - + $articles = $this->_em->createQuery('select a from Doctrine\Tests\Models\CMS\CmsArticle a') ->setFetchMode('Doctrine\Tests\Models\CMS\CmsArticle', 'user', ClassMetadata::FETCH_EAGER) ->getResult(); - + $this->assertEquals(10, count($articles)); foreach ($articles AS $article) { $this->assertNotInstanceOf('Doctrine\ORM\Proxy\Proxy', $article); @@ -456,7 +456,43 @@ class QueryTest extends \Doctrine\Tests\OrmFunctionalTestCase $query = $this->_em->createQuery("select u.username from Doctrine\Tests\Models\CMS\CmsUser u where u.username = 'gblanco'"); $this->assertNull($query->getOneOrNullResult(Query::HYDRATE_SCALAR)); } - + + /** + * @group DBAL-171 + */ + public function testParameterOrder() + { + $user = new CmsUser; + $user->name = 'Benjamin'; + $user->username = 'beberlei'; + $user->status = 'developer'; + $this->_em->persist($user); + + $user = new CmsUser; + $user->name = 'Roman'; + $user->username = 'romanb'; + $user->status = 'developer'; + $this->_em->persist($user); + + $user = new CmsUser; + $user->name = 'Jonathan'; + $user->username = 'jwage'; + $user->status = 'developer'; + $this->_em->persist($user); + + $this->_em->flush(); + $this->_em->clear(); + + $query = $this->_em->createQuery("SELECT u FROM Doctrine\Tests\Models\CMS\CmsUser u WHERE u.status = :a AND u.id IN (:b)"); + $query->setParameters(array( + 'b' => array(1,2,3), + 'a' => 'developer', + )); + $result = $query->getResult(); + + $this->assertEquals(3, count($result)); + } + public function testDqlWithAutoInferOfParameters() { $user = new CmsUser; @@ -464,30 +500,30 @@ class QueryTest extends \Doctrine\Tests\OrmFunctionalTestCase $user->username = 'beberlei'; $user->status = 'developer'; $this->_em->persist($user); - + $user = new CmsUser; $user->name = 'Roman'; $user->username = 'romanb'; $user->status = 'developer'; $this->_em->persist($user); - + $user = new CmsUser; $user->name = 'Jonathan'; $user->username = 'jwage'; $user->status = 'developer'; $this->_em->persist($user); - + $this->_em->flush(); $this->_em->clear(); - + $query = $this->_em->createQuery("SELECT u FROM Doctrine\Tests\Models\CMS\CmsUser u WHERE u.username IN (?0)"); $query->setParameter(0, array('beberlei', 'jwage')); - + $users = $query->execute(); - + $this->assertEquals(2, count($users)); } - + public function testQueryBuilderWithStringWhereClauseContainingOrAndConditionalPrimary() { $qb = $this->_em->createQueryBuilder(); @@ -495,13 +531,13 @@ class QueryTest extends \Doctrine\Tests\OrmFunctionalTestCase ->from('Doctrine\Tests\Models\CMS\CmsUser', 'u') ->innerJoin('u.articles', 'a') ->where('(u.id = 0) OR (u.id IS NULL)'); - + $query = $qb->getQuery(); $users = $query->execute(); - + $this->assertEquals(0, count($users)); } - + public function testQueryWithArrayOfEntitiesAsParameter() { $userA = new CmsUser; @@ -509,31 +545,31 @@ class QueryTest extends \Doctrine\Tests\OrmFunctionalTestCase $userA->username = 'beberlei'; $userA->status = 'developer'; $this->_em->persist($userA); - + $userB = new CmsUser; $userB->name = 'Roman'; $userB->username = 'romanb'; $userB->status = 'developer'; $this->_em->persist($userB); - + $userC = new CmsUser; $userC->name = 'Jonathan'; $userC->username = 'jwage'; $userC->status = 'developer'; $this->_em->persist($userC); - + $this->_em->flush(); $this->_em->clear(); - + $query = $this->_em->createQuery("SELECT u FROM Doctrine\Tests\Models\CMS\CmsUser u WHERE u IN (?0) OR u.username = ?1"); $query->setParameter(0, array($userA, $userC)); $query->setParameter(1, 'beberlei'); - + $users = $query->execute(); - + $this->assertEquals(2, count($users)); } - + public function testQueryWithHiddenAsSelectExpression() { $userA = new CmsUser; @@ -541,25 +577,25 @@ class QueryTest extends \Doctrine\Tests\OrmFunctionalTestCase $userA->username = 'beberlei'; $userA->status = 'developer'; $this->_em->persist($userA); - + $userB = new CmsUser; $userB->name = 'Roman'; $userB->username = 'romanb'; $userB->status = 'developer'; $this->_em->persist($userB); - + $userC = new CmsUser; $userC->name = 'Jonathan'; $userC->username = 'jwage'; $userC->status = 'developer'; $this->_em->persist($userC); - + $this->_em->flush(); $this->_em->clear(); - + $query = $this->_em->createQuery("SELECT u, (SELECT COUNT(u2.id) FROM Doctrine\Tests\Models\CMS\CmsUser u2) AS HIDDEN total FROM Doctrine\Tests\Models\CMS\CmsUser u"); $users = $query->execute(); - + $this->assertEquals(3, count($users)); $this->assertInstanceOf('Doctrine\Tests\Models\CMS\CmsUser', $users[0]); } From 166c05dfaab2ece2da49edf09d17ba911f1018d6 Mon Sep 17 00:00:00 2001 From: Benjamin Eberlei Date: Fri, 18 Nov 2011 17:42:32 +0100 Subject: [PATCH 122/135] Fix tests to be more stable --- .../Tests/ORM/Functional/QueryTest.php | 32 +++++++++---------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/tests/Doctrine/Tests/ORM/Functional/QueryTest.php b/tests/Doctrine/Tests/ORM/Functional/QueryTest.php index 687771e83..31399754b 100644 --- a/tests/Doctrine/Tests/ORM/Functional/QueryTest.php +++ b/tests/Doctrine/Tests/ORM/Functional/QueryTest.php @@ -462,30 +462,30 @@ class QueryTest extends \Doctrine\Tests\OrmFunctionalTestCase */ public function testParameterOrder() { - $user = new CmsUser; - $user->name = 'Benjamin'; - $user->username = 'beberlei'; - $user->status = 'developer'; - $this->_em->persist($user); + $user1 = new CmsUser; + $user1->name = 'Benjamin'; + $user1->username = 'beberlei'; + $user1->status = 'developer'; + $this->_em->persist($user1); - $user = new CmsUser; - $user->name = 'Roman'; - $user->username = 'romanb'; - $user->status = 'developer'; - $this->_em->persist($user); + $user2 = new CmsUser; + $user2->name = 'Roman'; + $user2->username = 'romanb'; + $user2->status = 'developer'; + $this->_em->persist($user2); - $user = new CmsUser; - $user->name = 'Jonathan'; - $user->username = 'jwage'; - $user->status = 'developer'; - $this->_em->persist($user); + $user3 = new CmsUser; + $user3->name = 'Jonathan'; + $user3->username = 'jwage'; + $user3->status = 'developer'; + $this->_em->persist($user3); $this->_em->flush(); $this->_em->clear(); $query = $this->_em->createQuery("SELECT u FROM Doctrine\Tests\Models\CMS\CmsUser u WHERE u.status = :a AND u.id IN (:b)"); $query->setParameters(array( - 'b' => array(1,2,3), + 'b' => array($user1->id, $user2->id, $user3->id), 'a' => 'developer', )); $result = $query->getResult(); From f9a4dcb2d0b9ceab4766c8e812df603e5a43cd05 Mon Sep 17 00:00:00 2001 From: Benjamin Eberlei Date: Fri, 18 Nov 2011 18:33:03 +0100 Subject: [PATCH 123/135] Remove code that could allow users of xml and yaml to define orphan removal on the wrong association sides. --- .../ORM/Mapping/ClassMetadataInfo.php | 123 +++++++++--------- lib/Doctrine/ORM/Mapping/Driver/XmlDriver.php | 8 -- .../ORM/Mapping/Driver/YamlDriver.php | 20 +-- lib/Doctrine/ORM/Mapping/MappingException.php | 12 +- 4 files changed, 80 insertions(+), 83 deletions(-) diff --git a/lib/Doctrine/ORM/Mapping/ClassMetadataInfo.php b/lib/Doctrine/ORM/Mapping/ClassMetadataInfo.php index b8c4ef2f1..2734d3647 100644 --- a/lib/Doctrine/ORM/Mapping/ClassMetadataInfo.php +++ b/lib/Doctrine/ORM/Mapping/ClassMetadataInfo.php @@ -119,7 +119,7 @@ class ClassMetadataInfo implements ClassMetadata const FETCH_LAZY = 2; /** * Specifies that an association is to be fetched when the owner of the - * association is fetched. + * association is fetched. */ const FETCH_EAGER = 3; /** @@ -206,7 +206,7 @@ class ClassMetadataInfo implements ClassMetadata /** * READ-ONLY: The named queries allowed to be called directly from Repository. - * + * * @var array */ public $namedQueries = array(); @@ -361,7 +361,7 @@ class ClassMetadataInfo implements ClassMetadata * - mappedBy (string, required for bidirectional associations) * The name of the field that completes the bidirectional association on the owning side. * This key must be specified on the inverse side of a bidirectional association. - * + * * - inversedBy (string, required for bidirectional associations) * The name of the field that completes the bidirectional association on the inverse side. * This key must be specified on the owning side of a bidirectional association. @@ -388,7 +388,7 @@ class ClassMetadataInfo implements ClassMetadata * Specification of a field on target-entity that is used to index the collection by. * This field HAS to be either the primary key or a unique column. Otherwise the collection * does not contain all the entities that are actually related. - * + * * A join table definition has the following structure: *
      * array(
@@ -430,7 +430,7 @@ class ClassMetadataInfo implements ClassMetadata
     /**
      * READ-ONLY: The definition of the sequence generator of this class. Only used for the
      * SEQUENCE generation strategy.
-     * 
+     *
      * The definition has the following structure:
      * 
      * array(
@@ -774,15 +774,22 @@ class ClassMetadataInfo implements ClassMetadata
         // If targetEntity is unqualified, assume it is in the same namespace as
         // the sourceEntity.
         $mapping['sourceEntity'] = $this->name;
-        
+
         if (isset($mapping['targetEntity'])) {
             if (strlen($this->namespace) > 0 && strpos($mapping['targetEntity'], '\\') === false) {
                 $mapping['targetEntity'] = $this->namespace . '\\' . $mapping['targetEntity'];
             }
-            
+
             $mapping['targetEntity'] = ltrim($mapping['targetEntity'], '\\');
         }
 
+        if ( ($mapping['type'] & (self::MANY_TO_ONE|self::MANY_TO_MANY)) > 0 &&
+                isset($mapping['orphanRemoval']) &&
+                $mapping['orphanRemoval'] == true) {
+
+            throw MappingException::illegalOrphanRemoval($this->name, $mapping['fieldName']);
+        }
+
         // Complete id mapping
         if (isset($mapping['id']) && $mapping['id'] === true) {
             if (isset($mapping['orphanRemoval']) && $mapping['orphanRemoval'] == true) {
@@ -813,7 +820,7 @@ class ClassMetadataInfo implements ClassMetadata
         if ( ! isset($mapping['targetEntity'])) {
             throw MappingException::missingTargetEntity($mapping['fieldName']);
         }
-        
+
         // Mandatory and optional attributes for either side
         if ( ! $mapping['mappedBy']) {
             if (isset($mapping['joinTable']) && $mapping['joinTable']) {
@@ -829,7 +836,7 @@ class ClassMetadataInfo implements ClassMetadata
         if (isset($mapping['id']) && $mapping['id'] === true && $mapping['type'] & self::TO_MANY) {
             throw MappingException::illegalToManyIdentifierAssoaction($this->name, $mapping['fieldName']);
         }
-        
+
         // Fetch mode. Default fetch mode to LAZY, if not set.
         if ( ! isset($mapping['fetch'])) {
             $mapping['fetch'] = self::FETCH_LAZY;
@@ -837,18 +844,18 @@ class ClassMetadataInfo implements ClassMetadata
 
         // Cascades
         $cascades = isset($mapping['cascade']) ? array_map('strtolower', $mapping['cascade']) : array();
-        
+
         if (in_array('all', $cascades)) {
             $cascades = array('remove', 'persist', 'refresh', 'merge', 'detach');
         }
-        
+
         $mapping['cascade'] = $cascades;
         $mapping['isCascadeRemove'] = in_array('remove',  $cascades);
         $mapping['isCascadePersist'] = in_array('persist',  $cascades);
         $mapping['isCascadeRefresh'] = in_array('refresh',  $cascades);
         $mapping['isCascadeMerge'] = in_array('merge',  $cascades);
         $mapping['isCascadeDetach'] = in_array('detach',  $cascades);
-        
+
         return $mapping;
     }
 
@@ -862,11 +869,11 @@ class ClassMetadataInfo implements ClassMetadata
     protected function _validateAndCompleteOneToOneMapping(array $mapping)
     {
         $mapping = $this->_validateAndCompleteAssociationMapping($mapping);
-        
+
         if (isset($mapping['joinColumns']) && $mapping['joinColumns']) {
             $mapping['isOwningSide'] = true;
         }
-        
+
         if ($mapping['isOwningSide']) {
             if ( ! isset($mapping['joinColumns']) || ! $mapping['joinColumns']) {
                 // Apply default join column
@@ -933,7 +940,7 @@ class ClassMetadataInfo implements ClassMetadata
         if ( ! isset($mapping['mappedBy'])) {
             throw MappingException::oneToManyRequiresMappedBy($mapping['fieldName']);
         }
-        
+
         $mapping['orphanRemoval']   = isset($mapping['orphanRemoval']) ? (bool) $mapping['orphanRemoval'] : false;
         $mapping['isCascadeRemove'] = $mapping['orphanRemoval'] ? true : $mapping['isCascadeRemove'];
 
@@ -942,7 +949,7 @@ class ClassMetadataInfo implements ClassMetadata
                 throw new \InvalidArgumentException("'orderBy' is expected to be an array, not ".gettype($mapping['orderBy']));
             }
         }
-        
+
         return $mapping;
     }
 
@@ -960,7 +967,7 @@ class ClassMetadataInfo implements ClassMetadata
             } else {
                 $targetShortName = strtolower($mapping['targetEntity']);
             }
-            
+
             // owning side MUST have a join table
             if ( ! isset($mapping['joinTable']['name'])) {
                 $mapping['joinTable']['name'] = $sourceShortName .'_' . $targetShortName;
@@ -1112,24 +1119,24 @@ class ClassMetadataInfo implements ClassMetadata
     public function getIdentifierColumnNames()
     {
         $columnNames = array();
-        
+
         foreach ($this->identifier as $idProperty) {
             if (isset($this->fieldMappings[$idProperty])) {
                 $columnNames[] = $this->fieldMappings[$idProperty]['columnName'];
-                
+
                 continue;
             }
-            
+
             // Association defined as Id field
             $joinColumns      = $this->associationMappings[$idProperty]['joinColumns'];
             $assocColumnNames = array_map(function ($joinColumn) { return $joinColumn['name']; }, $joinColumns);
-            
+
             $columnNames = array_merge($columnNames, $assocColumnNames);
         }
-        
+
         return $columnNames;
     }
-    
+
     /**
      * Sets the type of Id generator to use for the mapped class.
      */
@@ -1369,11 +1376,11 @@ class ClassMetadataInfo implements ClassMetadata
                 $this->table['name'] = $table['name'];
             }
         }
-        
+
         if (isset($table['indexes'])) {
             $this->table['indexes'] = $table['indexes'];
         }
-        
+
         if (isset($table['uniqueConstraints'])) {
             $this->table['uniqueConstraints'] = $table['uniqueConstraints'];
         }
@@ -1448,7 +1455,7 @@ class ClassMetadataInfo implements ClassMetadata
         if (isset($this->namedQueries[$queryMapping['name']])) {
             throw MappingException::duplicateQueryMapping($this->name, $queryMapping['name']);
         }
-        
+
         $name   = $queryMapping['name'];
         $query  = $queryMapping['query'];
         $dql    = str_replace('__CLASS__', $this->name, $query);
@@ -1516,11 +1523,11 @@ class ClassMetadataInfo implements ClassMetadata
     protected function _storeAssociationMapping(array $assocMapping)
     {
         $sourceFieldName = $assocMapping['fieldName'];
-        
+
         if (isset($this->fieldMappings[$sourceFieldName]) || isset($this->associationMappings[$sourceFieldName])) {
             throw MappingException::duplicateFieldMapping($this->name, $sourceFieldName);
         }
-        
+
         $this->associationMappings[$sourceFieldName] = $assocMapping;
     }
 
@@ -1531,7 +1538,7 @@ class ClassMetadataInfo implements ClassMetadata
      */
     public function setCustomRepositoryClass($repositoryClassName)
     {
-        if ($repositoryClassName !== null && strpos($repositoryClassName, '\\') === false 
+        if ($repositoryClassName !== null && strpos($repositoryClassName, '\\') === false
                 && strlen($this->namespace) > 0) {
             $repositoryClassName = $this->namespace . '\\' . $repositoryClassName;
         }
@@ -1731,7 +1738,7 @@ class ClassMetadataInfo implements ClassMetadata
 
     /**
      * Return the single association join column (if any).
-     * 
+     *
      * @param string $fieldName
      * @return string
      */
@@ -1773,7 +1780,7 @@ class ClassMetadataInfo implements ClassMetadata
             foreach ($this->associationMappings AS $assocName => $mapping) {
                 if ($this->isAssociationWithSingleJoinColumn($assocName) &&
                     $this->associationMappings[$assocName]['joinColumns'][0]['name'] == $columnName) {
-                    
+
                     return $assocName;
                 }
             }
@@ -1863,34 +1870,34 @@ class ClassMetadataInfo implements ClassMetadata
     {
         $this->isReadOnly = true;
     }
-    
+
     /**
      * A numerically indexed list of field names of this persistent class.
-     * 
+     *
      * This array includes identifier fields if present on this class.
-     * 
+     *
      * @return array
      */
     public function getFieldNames()
     {
         return array_keys($this->fieldMappings);
     }
-    
+
     /**
      * A numerically indexed list of association names of this persistent class.
-     * 
+     *
      * This array includes identifier associations if present on this class.
-     * 
+     *
      * @return array
      */
     public function getAssociationNames()
     {
         return array_keys($this->associationMappings);
     }
-    
+
     /**
      * Returns the target class name of the given association.
-     * 
+     *
      * @param string $assocName
      * @return string
      */
@@ -1899,13 +1906,13 @@ class ClassMetadataInfo implements ClassMetadata
         if ( ! isset($this->associationMappings[$assocName])) {
             throw new \InvalidArgumentException("Association name expected, '" . $assocName ."' is not an association.");
         }
-        
+
         return $this->associationMappings[$assocName]['targetEntity'];
     }
-    
+
     /**
      * Get fully-qualified class name of this persistent class.
-     * 
+     *
      * @return string
      */
     public function getName()
@@ -1915,59 +1922,59 @@ class ClassMetadataInfo implements ClassMetadata
 
     /**
      * Gets the (possibly quoted) identifier column names for safe use in an SQL statement.
-     * 
+     *
      * @param AbstractPlatform $platform
      * @return array
      */
     public function getQuotedIdentifierColumnNames($platform)
     {
         $quotedColumnNames = array();
-        
+
         foreach ($this->identifier as $idProperty) {
             if (isset($this->fieldMappings[$idProperty])) {
-                $quotedColumnNames[] = isset($this->fieldMappings[$idProperty]['quoted']) 
-                    ? $platform->quoteIdentifier($this->fieldMappings[$idProperty]['columnName']) 
+                $quotedColumnNames[] = isset($this->fieldMappings[$idProperty]['quoted'])
+                    ? $platform->quoteIdentifier($this->fieldMappings[$idProperty]['columnName'])
                     : $this->fieldMappings[$idProperty]['columnName'];
-                
+
                 continue;
             }
-            
+
             // Association defined as Id field
             $joinColumns            = $this->associationMappings[$idProperty]['joinColumns'];
             $assocQuotedColumnNames = array_map(
                 function ($joinColumn) {
-                    return isset($joinColumn['quoted']) 
-                        ? $platform->quoteIdentifier($joinColumn['name']) 
+                    return isset($joinColumn['quoted'])
+                        ? $platform->quoteIdentifier($joinColumn['name'])
                         : $joinColumn['name'];
-                }, 
+                },
                 $joinColumns
             );
-            
+
             $quotedColumnNames = array_merge($quotedColumnNames, $assocQuotedColumnNames);
         }
-        
+
         return $quotedColumnNames;
     }
 
     /**
      * Gets the (possibly quoted) column name of a mapped field for safe use
      * in an SQL statement.
-     * 
+     *
      * @param string $field
      * @param AbstractPlatform $platform
      * @return string
      */
     public function getQuotedColumnName($field, $platform)
     {
-        return isset($this->fieldMappings[$field]['quoted']) 
-            ? $platform->quoteIdentifier($this->fieldMappings[$field]['columnName']) 
+        return isset($this->fieldMappings[$field]['quoted'])
+            ? $platform->quoteIdentifier($this->fieldMappings[$field]['columnName'])
             : $this->fieldMappings[$field]['columnName'];
     }
-    
+
     /**
      * Gets the (possibly quoted) primary table name of this class for safe use
      * in an SQL statement.
-     * 
+     *
      * @param AbstractPlatform $platform
      * @return string
      */
diff --git a/lib/Doctrine/ORM/Mapping/Driver/XmlDriver.php b/lib/Doctrine/ORM/Mapping/Driver/XmlDriver.php
index 6f66337db..6f655d0db 100644
--- a/lib/Doctrine/ORM/Mapping/Driver/XmlDriver.php
+++ b/lib/Doctrine/ORM/Mapping/Driver/XmlDriver.php
@@ -377,10 +377,6 @@ class XmlDriver extends AbstractFileDriver
                     $mapping['cascade'] = $this->_getCascadeMappings($manyToOneElement->cascade);
                 }
 
-                if (isset($manyToOneElement->{'orphan-removal'})) {
-                    $mapping['orphanRemoval'] = (bool)$manyToOneElement->{'orphan-removal'};
-                }
-
                 $metadata->mapManyToOne($mapping);
             }
         }
@@ -428,10 +424,6 @@ class XmlDriver extends AbstractFileDriver
                     $mapping['cascade'] = $this->_getCascadeMappings($manyToManyElement->cascade);
                 }
 
-                if (isset($manyToManyElement->{'orphan-removal'})) {
-                    $mapping['orphanRemoval'] = (bool)$manyToManyElement->{'orphan-removal'};
-                }
-
                 if (isset($manyToManyElement->{'order-by'})) {
                     $orderBy = array();
                     foreach ($manyToManyElement->{'order-by'}->{'order-by-field'} AS $orderByField) {
diff --git a/lib/Doctrine/ORM/Mapping/Driver/YamlDriver.php b/lib/Doctrine/ORM/Mapping/Driver/YamlDriver.php
index 5979ae73f..a33c19b6b 100644
--- a/lib/Doctrine/ORM/Mapping/Driver/YamlDriver.php
+++ b/lib/Doctrine/ORM/Mapping/Driver/YamlDriver.php
@@ -47,7 +47,7 @@ class YamlDriver extends AbstractFileDriver
 
         if ($element['type'] == 'entity') {
             if (isset($element['repositoryClass'])) {
-                $metadata->setCustomRepositoryClass($element['repositoryClass']);  
+                $metadata->setCustomRepositoryClass($element['repositoryClass']);
             }
             if (isset($element['readOnly']) && $element['readOnly'] == true) {
                 $metadata->markReadOnly();
@@ -169,7 +169,7 @@ class YamlDriver extends AbstractFileDriver
                     'id' => true,
                     'fieldName' => $name
                 );
-                
+
                 if (isset($idElement['type'])) {
                     $mapping['type'] = $idElement['type'];
                 }
@@ -200,21 +200,21 @@ class YamlDriver extends AbstractFileDriver
         // Evaluate fields
         if (isset($element['fields'])) {
             foreach ($element['fields'] as $name => $fieldMapping) {
-                
+
                 $mapping = array(
                     'fieldName' => $name
                 );
-                
+
                 if (isset($fieldMapping['type'])) {
                     $e = explode('(', $fieldMapping['type']);
                     $fieldMapping['type'] = $e[0];
                     $mapping['type']      = $fieldMapping['type'];
-                    
+
                     if (isset($e[1])) {
                         $fieldMapping['length'] = substr($e[1], 0, strlen($e[1]) - 1);
                     }
                 }
-                
+
                 if (isset($fieldMapping['id'])) {
                     $mapping['id'] = true;
                     if (isset($fieldMapping['generator']['strategy'])) {
@@ -379,10 +379,6 @@ class YamlDriver extends AbstractFileDriver
                     $mapping['cascade'] = $manyToOneElement['cascade'];
                 }
 
-                if (isset($manyToOneElement['orphanRemoval'])) {
-                    $mapping['orphanRemoval'] = (bool)$manyToOneElement['orphanRemoval'];
-                }
-
                 $metadata->mapManyToOne($mapping);
             }
         }
@@ -438,10 +434,6 @@ class YamlDriver extends AbstractFileDriver
                     $mapping['cascade'] = $manyToManyElement['cascade'];
                 }
 
-                if (isset($manyToManyElement['orphanRemoval'])) {
-                    $mapping['orphanRemoval'] = (bool)$manyToManyElement['orphanRemoval'];
-                }
-
                 if (isset($manyToManyElement['orderBy'])) {
                     $mapping['orderBy'] = $manyToManyElement['orderBy'];
                 }
diff --git a/lib/Doctrine/ORM/Mapping/MappingException.php b/lib/Doctrine/ORM/Mapping/MappingException.php
index e32e34c16..e89347d42 100644
--- a/lib/Doctrine/ORM/Mapping/MappingException.php
+++ b/lib/Doctrine/ORM/Mapping/MappingException.php
@@ -184,7 +184,7 @@ class MappingException extends \Doctrine\ORM\ORMException
         if ( ! empty($path)) {
             $path = '[' . $path . ']';
         }
-        
+
         return new self(
             'File mapping drivers must have a valid directory path, ' .
             'however the given path ' . $path . ' seems to be incorrect!'
@@ -270,6 +270,12 @@ class MappingException extends \Doctrine\ORM\ORMException
             "part of the identifier in '$className#$field'.");
     }
 
+    public static function illegalOrphanRemoval($className, $field)
+    {
+        return new self("Orphan removal is only allowed on one-to-one and one-to-many ".
+                "associations, but " . $className."#" .$field . " is not.");
+    }
+
     public static function illegalInverseIdentifierAssocation($className, $field)
     {
         return new self("An inverse association is not allowed to be identifier in '$className#$field'.");
@@ -279,12 +285,12 @@ class MappingException extends \Doctrine\ORM\ORMException
     {
         return new self("Many-to-many or one-to-many associations are not allowed to be identifier in '$className#$field'.");
     }
-    
+
     public static function noInheritanceOnMappedSuperClass($className)
     {
         return new self("Its not supported to define inheritance information on a mapped superclass '" . $className . "'.");
     }
-    
+
     public static function mappedClassNotPartOfDiscriminatorMap($className, $rootClassName)
     {
         return new self(

From 6f356799115b1edf17309f826345da82b16ce7f2 Mon Sep 17 00:00:00 2001
From: Jan Sorgalla 
Date: Sat, 19 Nov 2011 00:35:29 +0100
Subject: [PATCH 124/135] Initial implementation of
 Doctrine\DBAL\Types\Type::convertToDatabaseValueSQL() and
 Doctrine\DBAL\Types\Type::convertToPHPValueSQL() integration

---
 .../AbstractEntityInheritancePersister.php    |   5 +
 .../ORM/Persisters/BasicEntityPersister.php   |  43 +++++++-
 lib/Doctrine/ORM/Query/SqlWalker.php          |  82 ++++++++++++--
 .../DbalTypes/NegativeToPositiveType.php      |  34 ++++++
 .../Tests/DbalTypes/UpperCaseStringType.php   |  29 +++++
 tests/Doctrine/Tests/Mocks/ConnectionMock.php |  14 +++
 .../Models/CustomType/CustomTypeChild.php     |  21 ++++
 .../Models/CustomType/CustomTypeParent.php    |  68 ++++++++++++
 .../Models/CustomType/CustomTypeUpperCase.php |  21 ++++
 .../Tests/ORM/Functional/TypeValueSqlTest.php | 104 ++++++++++++++++++
 .../BasicEntityPersisterTypeValueSqlTest.php  |  73 ++++++++++++
 .../ORM/Query/SelectSqlGenerationTest.php     |  43 ++++++++
 .../ORM/Query/UpdateSqlGenerationTest.php     |  24 ++++
 .../Doctrine/Tests/OrmFunctionalTestCase.php  |  12 ++
 14 files changed, 559 insertions(+), 14 deletions(-)
 create mode 100644 tests/Doctrine/Tests/DbalTypes/NegativeToPositiveType.php
 create mode 100644 tests/Doctrine/Tests/DbalTypes/UpperCaseStringType.php
 create mode 100644 tests/Doctrine/Tests/Models/CustomType/CustomTypeChild.php
 create mode 100644 tests/Doctrine/Tests/Models/CustomType/CustomTypeParent.php
 create mode 100644 tests/Doctrine/Tests/Models/CustomType/CustomTypeUpperCase.php
 create mode 100644 tests/Doctrine/Tests/ORM/Functional/TypeValueSqlTest.php
 create mode 100644 tests/Doctrine/Tests/ORM/Persisters/BasicEntityPersisterTypeValueSqlTest.php

diff --git a/lib/Doctrine/ORM/Persisters/AbstractEntityInheritancePersister.php b/lib/Doctrine/ORM/Persisters/AbstractEntityInheritancePersister.php
index 84540a337..4f45f0877 100644
--- a/lib/Doctrine/ORM/Persisters/AbstractEntityInheritancePersister.php
+++ b/lib/Doctrine/ORM/Persisters/AbstractEntityInheritancePersister.php
@@ -65,6 +65,11 @@ abstract class AbstractEntityInheritancePersister extends BasicEntityPersister
         $columnAlias = $this->getSQLColumnAlias($columnName);
         $this->_rsm->addFieldResult($alias, $columnAlias, $field, $class->name);
 
+        if (!$class->isIdentifier($field)) {
+            $type = Type::getType($class->getTypeOfField($field));
+            $sql = $type->convertToPHPValueSQL($sql, $this->_platform);
+      }
+
         return $sql . ' AS ' . $columnAlias;
     }
 
diff --git a/lib/Doctrine/ORM/Persisters/BasicEntityPersister.php b/lib/Doctrine/ORM/Persisters/BasicEntityPersister.php
index 4deece2e9..41fb01a75 100644
--- a/lib/Doctrine/ORM/Persisters/BasicEntityPersister.php
+++ b/lib/Doctrine/ORM/Persisters/BasicEntityPersister.php
@@ -338,10 +338,19 @@ class BasicEntityPersister
         $set = $params = $types = array();
 
         foreach ($updateData as $columnName => $value) {
-            $set[] = (isset($this->_class->fieldNames[$columnName]))
-                ? $this->_class->getQuotedColumnName($this->_class->fieldNames[$columnName], $this->_platform) . ' = ?'
-                : $columnName . ' = ?';
+            $column = $columnName;
+            $placeholder = '?';
+            
+            if (isset($this->_class->fieldNames[$columnName])) {
+                $column = $this->_class->getQuotedColumnName($this->_class->fieldNames[$columnName], $this->_platform);
 
+                if (!$this->_class->isIdentifier($this->_class->fieldNames[$columnName])) {
+                    $type = Type::getType($this->_columnTypes[$columnName]);
+                    $placeholder = $type->convertToDatabaseValueSQL('?', $this->_platform);
+                }
+            }
+
+            $set[] = $column . ' = ' . $placeholder;
             $params[] = $value;
             $types[] = $this->_columnTypes[$columnName];
         }
@@ -1117,7 +1126,18 @@ class BasicEntityPersister
                 );
             } else {
                 $columns = array_unique($columns);
-                $values = array_fill(0, count($columns), '?');
+
+                $values = array();
+                foreach ($columns AS $column) {
+                    $placeholder = '?';
+
+                    if (isset($this->_columnTypes[$column])) {
+                        $type = Type::getType($this->_columnTypes[$column]);
+                        $placeholder = $type->convertToDatabaseValueSQL('?', $this->_platform);
+                    }
+
+                    $values[] = $placeholder;
+                }
 
                 $insertSql = 'INSERT INTO ' . $this->_class->getQuotedTableName($this->_platform)
                         . ' (' . implode(', ', $columns) . ') VALUES (' . implode(', ', $values) . ')';
@@ -1156,6 +1176,7 @@ class BasicEntityPersister
                 }
             } else if ($this->_class->generatorType != ClassMetadata::GENERATOR_TYPE_IDENTITY || $this->_class->identifier[0] != $name) {
                 $columns[] = $this->_class->getQuotedColumnName($name, $this->_platform);
+                $this->_columnTypes[$name] = $this->_class->fieldMappings[$name]['type'];
             }
         }
 
@@ -1177,6 +1198,11 @@ class BasicEntityPersister
         $columnAlias = $this->getSQLColumnAlias($class->columnNames[$field]);
 
         $this->_rsm->addFieldResult($alias, $columnAlias, $field);
+        
+        if (!$class->isIdentifier($field)) {
+            $type = Type::getType($class->getTypeOfField($field));
+            $sql = $type->convertToPHPValueSQL($sql, $this->_platform);
+        }
 
         return $sql . ' AS ' . $columnAlias;
     }
@@ -1259,6 +1285,8 @@ class BasicEntityPersister
 
         foreach ($criteria as $field => $value) {
             $conditionSql .= $conditionSql ? ' AND ' : '';
+            
+            $placeholder = '?';
 
             if (isset($this->_class->columnNames[$field])) {
                 $className = (isset($this->_class->fieldMappings[$field]['inherited']))
@@ -1266,6 +1294,11 @@ class BasicEntityPersister
                     : $this->_class->name;
 
                 $conditionSql .= $this->_getSQLTableAlias($className) . '.' . $this->_class->getQuotedColumnName($field, $this->_platform);
+
+                if (!$this->_class->isIdentifier($field)) {
+                    $type = Type::getType($this->_class->getTypeOfField($field));
+                    $placeholder = $type->convertToDatabaseValueSQL($placeholder, $this->_platform);
+                }
             } else if (isset($this->_class->associationMappings[$field])) {
                 if ( ! $this->_class->associationMappings[$field]['isOwningSide']) {
                     throw ORMException::invalidFindByInverseAssociation($this->_class->name, $field);
@@ -1286,7 +1319,7 @@ class BasicEntityPersister
                 throw ORMException::unrecognizedField($field);
             }
 
-            $conditionSql .= (is_array($value)) ? ' IN (?)' : (($value === null) ? ' IS NULL' : ' = ?');
+            $conditionSql .= (is_array($value)) ? ' IN (?)' : (($value === null) ? ' IS NULL' : ' = ' . $placeholder);
         }
         return $conditionSql;
     }
diff --git a/lib/Doctrine/ORM/Query/SqlWalker.php b/lib/Doctrine/ORM/Query/SqlWalker.php
index 624f14b89..382796631 100644
--- a/lib/Doctrine/ORM/Query/SqlWalker.php
+++ b/lib/Doctrine/ORM/Query/SqlWalker.php
@@ -20,6 +20,7 @@
 namespace Doctrine\ORM\Query;
 
 use Doctrine\DBAL\LockMode,
+    Doctrine\DBAL\Types\Type,
     Doctrine\ORM\Mapping\ClassMetadata,
     Doctrine\ORM\Query,
     Doctrine\ORM\Query\QueryException,
@@ -96,6 +97,17 @@ class SqlWalker implements TreeWalker
      * These should only be generated for SELECT queries, not for UPDATE/DELETE.
      */
     private $_useSqlTableAliases = true;
+    
+    /**
+     * Flag that indicates whether to pass columns through Type::convertToPHPValueSQL().
+     * These should only be done for SELECT queries, not for UPDATE.
+     */
+    private $_useDbalTypeValueSql = true;
+    
+    /**
+     * Holds the current columns type.
+     */
+    private $_currentColumnType;
 
     /**
      * The database platform abstraction.
@@ -409,6 +421,7 @@ class SqlWalker implements TreeWalker
     public function walkUpdateStatement(AST\UpdateStatement $AST)
     {
         $this->_useSqlTableAliases = false;
+        $this->_useDbalTypeValueSql = false;
 
         return $this->walkUpdateClause($AST->updateClause) . $this->walkWhereClause($AST->whereClause);
     }
@@ -464,11 +477,20 @@ class SqlWalker implements TreeWalker
                 $dqlAlias = $pathExpr->identificationVariable;
                 $class = $this->_queryComponents[$dqlAlias]['metadata'];
 
+                $column = '';
+
                 if ($this->_useSqlTableAliases) {
-                    $sql .= $this->walkIdentificationVariable($dqlAlias, $fieldName) . '.';
+                    $column .= $this->walkIdentificationVariable($dqlAlias, $fieldName) . '.';
                 }
 
-                $sql .= $class->getQuotedColumnName($fieldName, $this->_platform);
+                $column .= $class->getQuotedColumnName($fieldName, $this->_platform);
+
+                if ($this->_useDbalTypeValueSql && !$class->isIdentifier($fieldName)) {
+                    $type = Type::getType($class->getTypeOfField($fieldName));
+                    $column = $type->convertToPHPValueSQL($column, $this->_conn->getDatabasePlatform());
+                }
+
+                $sql .= $column;
                 break;
 
             case AST\PathExpression::TYPE_SINGLE_VALUED_ASSOCIATION:
@@ -1002,9 +1024,16 @@ class SqlWalker implements TreeWalker
 
                 $sqlTableAlias = $this->getSQLTableAlias($tableName, $dqlAlias);
                 $columnName    = $class->getQuotedColumnName($fieldName, $this->_platform);
-                $columnAlias = $this->getSQLColumnAlias($columnName);
+                $columnAlias   = $this->getSQLColumnAlias($columnName);
 
-                $sql .= $sqlTableAlias . '.' . $columnName . ' AS ' . $columnAlias;
+                $col = $sqlTableAlias . '.' . $columnName;
+
+                if (!$class->isIdentifier($fieldName)) {
+                    $type = Type::getType($class->getTypeOfField($fieldName));
+                    $col  = $type->convertToPHPValueSQL($col, $this->_conn->getDatabasePlatform());
+                }
+
+                $sql .= $col . ' AS ' . $columnAlias;
 
                 if ( ! $hidden) {
                     $this->_rsm->addScalarResult($columnAlias, $resultAlias);
@@ -1086,7 +1115,14 @@ class SqlWalker implements TreeWalker
                     $columnAlias      = $this->getSQLColumnAlias($mapping['columnName']);
                     $quotedColumnName = $class->getQuotedColumnName($fieldName, $this->_platform);
 
-                    $sqlParts[] = $sqlTableAlias . '.' . $quotedColumnName . ' AS '. $columnAlias;
+                    $col = $sqlTableAlias . '.' . $quotedColumnName;
+
+                    if (!$class->isIdentifier($fieldName)) {
+                        $type = Type::getType($class->getTypeOfField($fieldName));
+                        $col = $type->convertToPHPValueSQL($col, $this->_platform);
+                    }
+
+                    $sqlParts[] = $col . ' AS '. $columnAlias;
 
                     $this->_rsm->addFieldResult($dqlAlias, $columnAlias, $fieldName, $class->name);
                 }
@@ -1108,7 +1144,14 @@ class SqlWalker implements TreeWalker
                             $columnAlias      = $this->getSQLColumnAlias($mapping['columnName']);
                             $quotedColumnName = $subClass->getQuotedColumnName($fieldName, $this->_platform);
 
-                            $sqlParts[] = $sqlTableAlias . '.' . $quotedColumnName . ' AS ' . $columnAlias;
+                            $col = $sqlTableAlias . '.' . $quotedColumnName;
+
+                            if (!$subClass->isIdentifier($fieldName)) {
+                                $type = Type::getType($subClass->getTypeOfField($fieldName));
+                                $col = $type->convertToPHPValueSQL($col, $this->_platform);
+                            }
+
+                            $sqlParts[] = $col . ' AS ' . $columnAlias;
 
                             $this->_rsm->addFieldResult($dqlAlias, $columnAlias, $fieldName, $subClassName);
                         }
@@ -1386,7 +1429,18 @@ class SqlWalker implements TreeWalker
 
         switch (true) {
             case ($newValue instanceof AST\Node):
+                $currentColumnTypeBefore = $this->_currentColumnType;
+                $this->_currentColumnType  = null;
+
+                if ($updateItem->pathExpression->type == AST\PathExpression::TYPE_STATE_FIELD) {
+                    $class = $this->_queryComponents[$updateItem->pathExpression->identificationVariable]['metadata'];
+                    if (!$class->isIdentifier($updateItem->pathExpression->field)) {
+                        $this->_currentColumnType = $class->getTypeOfField($updateItem->pathExpression->field);
+                    }
+                }
+
                 $sql .= $newValue->dispatch($this);
+                $this->_currentColumnType = $currentColumnTypeBefore;
                 break;
 
             case ($newValue === null):
@@ -1759,20 +1813,30 @@ class SqlWalker implements TreeWalker
     {
         switch ($literal->type) {
             case AST\Literal::STRING:
-                return $this->_conn->quote($literal->value);
+                $value = $this->_conn->quote($literal->value);
+                break;
 
             case AST\Literal::BOOLEAN:
                 $bool = strtolower($literal->value) == 'true' ? true : false;
                 $boolVal = $this->_conn->getDatabasePlatform()->convertBooleans($bool);
 
-                return $boolVal;
+                $value = $boolVal;
+                break;
 
             case AST\Literal::NUMERIC:
-                return $literal->value;
+                $value = $literal->value;
+                break;
 
             default:
                 throw QueryException::invalidLiteral($literal);
         }
+        
+        if ($this->_currentColumnType !== null) {
+            $type  = Type::getType($this->_currentColumnType);
+            $value = $type->convertToDatabaseValueSQL($value, $this->_conn->getDatabasePlatform());
+        }
+        
+        return $value;
     }
 
     /**
diff --git a/tests/Doctrine/Tests/DbalTypes/NegativeToPositiveType.php b/tests/Doctrine/Tests/DbalTypes/NegativeToPositiveType.php
new file mode 100644
index 000000000..e477ecd3c
--- /dev/null
+++ b/tests/Doctrine/Tests/DbalTypes/NegativeToPositiveType.php
@@ -0,0 +1,34 @@
+getIntegerTypeDeclarationSQL($fieldDeclaration);
+    }
+
+    public function canRequireSQLConversion()
+    {
+        return true;
+    }
+
+    public function convertToDatabaseValueSQL($sqlExpr, AbstractPlatform $platform)
+    {
+        return 'ABS(' . $sqlExpr . ')';
+    }
+
+    public function convertToPHPValueSQL($sqlExpr, $platform)
+    {
+        return '((' . $sqlExpr . ') * -1)';
+    }
+}
diff --git a/tests/Doctrine/Tests/DbalTypes/UpperCaseStringType.php b/tests/Doctrine/Tests/DbalTypes/UpperCaseStringType.php
new file mode 100644
index 000000000..47e8c790d
--- /dev/null
+++ b/tests/Doctrine/Tests/DbalTypes/UpperCaseStringType.php
@@ -0,0 +1,29 @@
+_inserts[$tableName][] = $data;
     }
 
+    /**
+     * @override
+     */
+    public function executeUpdate($query, array $params = array(), array $types = array())
+    {
+        $this->_executeUpdates[] = array('query' => $query, 'params' => $params, 'types' => $types);
+    }
+
     /**
      * @override
      */
@@ -84,6 +93,11 @@ class ConnectionMock extends \Doctrine\DBAL\Connection
         return $this->_inserts;
     }
 
+    public function getExecuteUpdates()
+    {
+        return $this->_executeUpdates;
+    }
+
     public function reset()
     {
         $this->_inserts = array();
diff --git a/tests/Doctrine/Tests/Models/CustomType/CustomTypeChild.php b/tests/Doctrine/Tests/Models/CustomType/CustomTypeChild.php
new file mode 100644
index 000000000..799ad5016
--- /dev/null
+++ b/tests/Doctrine/Tests/Models/CustomType/CustomTypeChild.php
@@ -0,0 +1,21 @@
+friendsWithMe = new \Doctrine\Common\Collections\ArrayCollection();
+        $this->myFriends = new \Doctrine\Common\Collections\ArrayCollection();
+    }
+
+    public function addMyFriend(CustomTypeParent $friend)
+    {
+        $this->getMyFriends()->add($friend);
+        $friend->addFriendWithMe($this);
+    }
+
+    public function getMyFriends()
+    {
+        return $this->myFriends;
+    }
+
+    public function addFriendWithMe(CustomTypeParent $friend)
+    {
+        $this->getFriendsWithMe()->add($friend);
+    }
+
+    public function getFriendsWithMe()
+    {
+        return $this->friendsWithMe;
+    }
+}
diff --git a/tests/Doctrine/Tests/Models/CustomType/CustomTypeUpperCase.php b/tests/Doctrine/Tests/Models/CustomType/CustomTypeUpperCase.php
new file mode 100644
index 000000000..26e0ec115
--- /dev/null
+++ b/tests/Doctrine/Tests/Models/CustomType/CustomTypeUpperCase.php
@@ -0,0 +1,21 @@
+useModelSet('customtype');
+        parent::setUp();
+    }
+
+    public function testUpperCaseStringType()
+    {
+        $entity = new CustomTypeUpperCase();
+        $entity->lowerCaseString = 'foo';
+
+        $this->_em->persist($entity);
+        $this->_em->flush();
+        
+        $id = $entity->id;
+        
+        $this->_em->clear();
+
+        $entity = $this->_em->find('\Doctrine\Tests\Models\CustomType\CustomTypeUpperCase', $id);
+
+        $this->assertEquals('foo', $entity->lowerCaseString, 'Entity holds lowercase string');
+        $this->assertEquals('FOO', $this->_em->getConnection()->fetchColumn("select lowerCaseString from customtype_uppercases where id=".$entity->id.""), 'Database holds uppercase string');
+    }
+ 
+    public function testTypeValueSqlWithAssociations()
+    {
+        $parent = new CustomTypeParent();
+        $parent->customInteger = -1;
+        $parent->child = new CustomTypeChild();
+
+        $friend1 = new CustomTypeParent();
+        $friend2 = new CustomTypeParent();
+
+        $parent->addMyFriend($friend1);
+        $parent->addMyFriend($friend2);
+
+        $this->_em->persist($parent);
+        $this->_em->persist($friend1);
+        $this->_em->persist($friend2);
+        $this->_em->flush();
+
+        $parentId = $parent->id;
+
+        $this->_em->clear();
+
+        $entity = $this->_em->find('Doctrine\Tests\Models\CustomType\CustomTypeParent', $parentId);
+
+        $this->assertTrue($entity->customInteger < 0, 'Fetched customInteger negative');
+        $this->assertEquals(1, $this->_em->getConnection()->fetchColumn("select customInteger from customtype_parents where id=".$entity->id.""), 'Database has stored customInteger positive');
+
+        $this->assertNotNull($parent->child, 'Child attached');
+        $this->assertCount(2, $entity->getMyFriends(), '2 friends attached');
+    }
+
+    public function testSelectDQL()
+    {
+        $parent = new CustomTypeParent();
+        $parent->customInteger = -1;
+        $parent->child = new CustomTypeChild();
+
+        $this->_em->persist($parent);
+        $this->_em->flush();
+
+        $parentId = $parent->id;
+
+        $this->_em->clear();
+
+        $query = $this->_em->createQuery("SELECT p, p.customInteger, c from Doctrine\Tests\Models\CustomType\CustomTypeParent p JOIN p.child c where p.id = " . $parentId . " AND p.customInteger = -1");
+
+        $result = $query->getResult();
+
+        $this->assertEquals(1, count($result));
+        $this->assertInstanceOf('Doctrine\Tests\Models\CustomType\CustomTypeParent', $result[0][0]);
+
+        $this->assertEquals(-1, $result[0]['customInteger']);
+
+        $this->assertEquals('foo', $result[0][0]->child->lowerCaseString);
+    }
+}
diff --git a/tests/Doctrine/Tests/ORM/Persisters/BasicEntityPersisterTypeValueSqlTest.php b/tests/Doctrine/Tests/ORM/Persisters/BasicEntityPersisterTypeValueSqlTest.php
new file mode 100644
index 000000000..e608e6ba8
--- /dev/null
+++ b/tests/Doctrine/Tests/ORM/Persisters/BasicEntityPersisterTypeValueSqlTest.php
@@ -0,0 +1,73 @@
+_em = $this->_getTestEntityManager();
+
+        $this->_persister = new BasicEntityPersister($this->_em, $this->_em->getClassMetadata("Doctrine\Tests\Models\CustomType\CustomTypeParent"));
+
+        if (DBALType::hasType('negative_to_positive')) {
+            DBALType::overrideType('negative_to_positive', '\Doctrine\Tests\DbalTypes\NegativeToPositiveType');
+        } else {
+            DBALType::addType('negative_to_positive', '\Doctrine\Tests\DbalTypes\NegativeToPositiveType');
+        }
+    }
+
+    public function testGetInsertSQLUsesTypeValuesSQL()
+    {
+        $method = new \ReflectionMethod($this->_persister, '_getInsertSQL');
+        $method->setAccessible(true);
+
+        $sql = $method->invoke($this->_persister);
+
+        $this->assertEquals('INSERT INTO customtype_parents (customInteger, child_id) VALUES (ABS(?), ?)', $sql);
+    }
+
+    public function testUpdateUsesTypeValuesSQL()
+    {
+        $child = new CustomTypeChild();
+
+        $parent = new CustomTypeParent();
+        $parent->customInteger = 1;
+        $parent->child = $child;
+
+        $this->_em->getUnitOfWork()->registerManaged($parent, array('id' => 1), array('customInteger' => 0, 'child' => null));
+        $this->_em->getUnitOfWork()->registerManaged($child, array('id' => 1), array());
+
+        $this->_em->getUnitOfWork()->propertyChanged($parent, 'customInteger', 0, 1);
+        $this->_em->getUnitOfWork()->propertyChanged($parent, 'child', null, $child);
+
+        $this->_persister->update($parent);
+
+        $executeUpdates = $this->_em->getConnection()->getExecuteUpdates();
+
+        $this->assertEquals('UPDATE customtype_parents SET customInteger = ABS(?), child_id = ? WHERE id = ?', $executeUpdates[0]['query']);
+    }
+
+    public function testGetSelectConditionSQLUsesTypeValuesSQL()
+    {
+        $method = new \ReflectionMethod($this->_persister, '_getSelectConditionSQL');
+        $method->setAccessible(true);
+
+        $sql = $method->invoke($this->_persister,  array('customInteger' => 1, 'child' => 1));
+
+        $this->assertEquals('t0.customInteger = ABS(?) AND t0.child_id = ?', $sql);
+    }
+}
diff --git a/tests/Doctrine/Tests/ORM/Query/SelectSqlGenerationTest.php b/tests/Doctrine/Tests/ORM/Query/SelectSqlGenerationTest.php
index 7ddfe77b8..028406068 100644
--- a/tests/Doctrine/Tests/ORM/Query/SelectSqlGenerationTest.php
+++ b/tests/Doctrine/Tests/ORM/Query/SelectSqlGenerationTest.php
@@ -2,6 +2,7 @@
 
 namespace Doctrine\Tests\ORM\Query;
 
+use Doctrine\DBAL\Types\Type as DBALType;
 use Doctrine\ORM\Query;
 
 require_once __DIR__ . '/../../TestInit.php';
@@ -1333,6 +1334,48 @@ class SelectSqlGenerationTest extends \Doctrine\Tests\OrmTestCase
             'SELECT c0_.id AS id0, c0_.name AS name1 FROM cms_employees c0_ GROUP BY c0_.id, c0_.name, c0_.spouse_id'
         );
     }
+
+    public function testCustomTypeValueSql()
+    {
+        if (DBALType::hasType('negative_to_positive')) {
+            DBALType::overrideType('negative_to_positive', 'Doctrine\Tests\DbalTypes\NegativeToPositiveType');
+        } else {
+            DBALType::addType('negative_to_positive', 'Doctrine\Tests\DbalTypes\NegativeToPositiveType');
+        }
+
+        $this->assertSqlGeneration(
+            'SELECT p.customInteger FROM Doctrine\Tests\Models\CustomType\CustomTypeParent p WHERE p.id = 1',
+            'SELECT ((c0_.customInteger) * -1) AS customInteger0 FROM customtype_parents c0_ WHERE c0_.id = 1'
+        );
+    }
+
+    public function testCustomTypeValueSqlIgnoresIdentifierColumn()
+    {
+        if (DBALType::hasType('negative_to_positive')) {
+            DBALType::overrideType('negative_to_positive', 'Doctrine\Tests\DbalTypes\NegativeToPositiveType');
+        } else {
+            DBALType::addType('negative_to_positive', 'Doctrine\Tests\DbalTypes\NegativeToPositiveType');
+        }
+
+        $this->assertSqlGeneration(
+            'SELECT p.id FROM Doctrine\Tests\Models\CustomType\CustomTypeParent p WHERE p.id = 1',
+            'SELECT c0_.id AS id0 FROM customtype_parents c0_ WHERE c0_.id = 1'
+        );
+    }
+
+    public function testCustomTypeValueSqlForAllFields()
+    {
+        if (DBALType::hasType('negative_to_positive')) {
+            DBALType::overrideType('negative_to_positive', 'Doctrine\Tests\DbalTypes\NegativeToPositiveType');
+        } else {
+            DBALType::addType('negative_to_positive', 'Doctrine\Tests\DbalTypes\NegativeToPositiveType');
+        }
+
+        $this->assertSqlGeneration(
+            'SELECT p FROM Doctrine\Tests\Models\CustomType\CustomTypeParent p',
+            'SELECT c0_.id AS id0, ((c0_.customInteger) * -1) AS customInteger1 FROM customtype_parents c0_'
+        );
+    }
 }
 
 
diff --git a/tests/Doctrine/Tests/ORM/Query/UpdateSqlGenerationTest.php b/tests/Doctrine/Tests/ORM/Query/UpdateSqlGenerationTest.php
index a8a59ff63..34658c95a 100644
--- a/tests/Doctrine/Tests/ORM/Query/UpdateSqlGenerationTest.php
+++ b/tests/Doctrine/Tests/ORM/Query/UpdateSqlGenerationTest.php
@@ -21,6 +21,8 @@
 
 namespace Doctrine\Tests\ORM\Query;
 
+use Doctrine\DBAL\Types\Type as DBALType;
+
 require_once __DIR__ . '/../../TestInit.php';
 
 /**
@@ -42,6 +44,12 @@ class UpdateSqlGenerationTest extends \Doctrine\Tests\OrmTestCase
     private $_em;
 
     protected function setUp() {
+        if (DBALType::hasType('negative_to_positive')) {
+            DBALType::overrideType('negative_to_positive', 'Doctrine\Tests\DbalTypes\NegativeToPositiveType');
+        } else {
+            DBALType::addType('negative_to_positive', 'Doctrine\Tests\DbalTypes\NegativeToPositiveType');
+        }
+
         $this->_em = $this->_getTestEntityManager();
     }
 
@@ -186,4 +194,20 @@ class UpdateSqlGenerationTest extends \Doctrine\Tests\OrmTestCase
             "UPDATE cms_users SET status = 'inactive' WHERE (SELECT COUNT(*) FROM cms_users_groups c0_ WHERE c0_.user_id = cms_users.id) = 10"
         );
     }
+
+    public function testCustomTypeValueSql()
+    {
+        $this->assertSqlGeneration(
+            'UPDATE Doctrine\Tests\Models\CustomType\CustomTypeParent p SET p.customInteger = 1 WHERE p.id = 1',
+            'UPDATE customtype_parents SET customInteger = ABS(1) WHERE id = 1'
+        );
+    }
+
+    public function testCustomTypeValueSqlIgnoresIdentifierColumns()
+    {
+        $this->assertSqlGeneration(
+            'UPDATE Doctrine\Tests\Models\CustomType\CustomTypeParent p SET p.id = 2 WHERE p.id = 1',
+            'UPDATE customtype_parents SET id = 2 WHERE id = 1'
+        );
+    }
 }
diff --git a/tests/Doctrine/Tests/OrmFunctionalTestCase.php b/tests/Doctrine/Tests/OrmFunctionalTestCase.php
index 0900e4e99..e0b9d7bee 100644
--- a/tests/Doctrine/Tests/OrmFunctionalTestCase.php
+++ b/tests/Doctrine/Tests/OrmFunctionalTestCase.php
@@ -112,6 +112,11 @@ abstract class OrmFunctionalTestCase extends OrmTestCase
             'Doctrine\Tests\Models\Legacy\LegacyArticle',
             'Doctrine\Tests\Models\Legacy\LegacyCar',
         ),
+        'customtype' => array(
+            'Doctrine\Tests\Models\CustomType\CustomTypeChild',
+            'Doctrine\Tests\Models\CustomType\CustomTypeParent',
+            'Doctrine\Tests\Models\CustomType\CustomTypeUpperCase',
+        ),
     );
 
     protected function useModelSet($setName)
@@ -219,6 +224,13 @@ abstract class OrmFunctionalTestCase extends OrmTestCase
             $conn->executeUpdate('DELETE FROM legacy_users');
         }
 
+        if (isset($this->_usedModelSets['customtype'])) {
+            $conn->executeUpdate('DELETE FROM customtype_parent_friends');
+            $conn->executeUpdate('DELETE FROM customtype_parents');
+            $conn->executeUpdate('DELETE FROM customtype_children');
+            $conn->executeUpdate('DELETE FROM customtype_uppercases');
+        }
+
         $this->_em->clear();
     }
 

From dc0a03ab3074c090153050c117dc828d37855abc Mon Sep 17 00:00:00 2001
From: Benjamin Eberlei 
Date: Sat, 19 Nov 2011 08:50:49 +0100
Subject: [PATCH 125/135] DDC-1400 working testcase

---
 .../ORM/Functional/Ticket/DDC1400Test.php     | 136 ++++++++++++++++++
 1 file changed, 136 insertions(+)
 create mode 100644 tests/Doctrine/Tests/ORM/Functional/Ticket/DDC1400Test.php

diff --git a/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC1400Test.php b/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC1400Test.php
new file mode 100644
index 000000000..1639f98bd
--- /dev/null
+++ b/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC1400Test.php
@@ -0,0 +1,136 @@
+_schemaTool->createSchema(array(
+                $this->_em->getClassMetadata(__NAMESPACE__ . '\DDC1400Article'),
+                $this->_em->getClassMetadata(__NAMESPACE__ . '\DDC1400User'),
+                $this->_em->getClassMetadata(__NAMESPACE__ . '\DDC1400UserState'),
+            ));
+        } catch (\Exception $ignored) {
+        }
+    }
+
+    public function testFailingCase()
+    {
+        $article = new DDC1400Article;
+        $user1 = new DDC1400User;
+        $user2 = new DDC1400User;
+
+        $this->_em->persist($article);
+        $this->_em->persist($user1);
+        $this->_em->persist($user2);
+        $this->_em->flush();
+
+        $userState1 = new DDC1400UserState;
+        $userState1->article = $article;
+        $userState1->articleId = $article->id;
+        $userState1->user = $user1;
+        $userState1->userId = $user1->id;
+
+        $userState2 = new DDC1400UserState;
+        $userState2->article = $article;
+        $userState2->articleId = $article->id;
+        $userState2->user = $user2;
+        $userState2->userId = $user2->id;
+
+        $this->_em->persist($userState1);
+        $this->_em->persist($userState2);
+
+        $this->_em->flush();
+        $this->_em->clear();
+
+        $user1 = $this->_em->getReference(__NAMESPACE__.'\DDC1400User', $user1->id);
+
+        $q = $this->_em->createQuery("SELECT a, s FROM ".__NAMESPACE__."\DDC1400Article a JOIN a.userStates s WITH s.user = :activeUser");
+        $q->setParameter('activeUser', $user1);
+        $articles = $q->getResult();
+
+        var_dump(array_keys($articles[0]->userStates->toArray()));
+
+        $this->_em->flush();
+        var_dump($this->_sqlLoggerStack);
+
+
+    }
+}
+
+/**
+ * @Entity
+ */
+class DDC1400Article
+{
+    /**
+     * @Id
+     * @Column(type="integer")
+     * @GeneratedValue
+     */
+    public $id;
+
+    /**
+     * @OneToMany(targetEntity="DDC1400UserState", mappedBy="article", indexBy="userId", fetch="EXTRA_LAZY")
+     */
+    public $userStates;
+}
+
+/**
+ * @Entity
+ */
+class DDC1400User
+{
+
+    /**
+     * @Id
+     * @Column(type="integer")
+     * @GeneratedValue
+     */
+    public $id;
+
+    /**
+     * @OneToMany(targetEntity="DDC1400UserState", mappedBy="user", indexBy="articleId", fetch="EXTRA_LAZY")
+     */
+    public $userStates;
+}
+
+/**
+ * @Entity
+ */
+class DDC1400UserState
+{
+
+    /**
+      * @Id
+     *  @ManyToOne(targetEntity="DDC1400Article", inversedBy="userStates")
+     */
+    public $article;
+
+    /**
+      * @Id
+     *  @ManyToOne(targetEntity="DDC1400User", inversedBy="userStates")
+     */
+    public $user;
+
+    /**
+     * @Column(name="user_id", type="integer")
+     */
+    public $userId;
+
+    /**
+     * @Column(name="article_id", type="integer")
+     */
+    public $articleId;
+
+}
\ No newline at end of file

From 53b3030aa2da893f11ee4cfbadad27e7827d871a Mon Sep 17 00:00:00 2001
From: Benjamin Eberlei 
Date: Sat, 19 Nov 2011 08:58:58 +0100
Subject: [PATCH 126/135] Clarify EntityManager#transactional() docblock

---
 lib/Doctrine/ORM/EntityManager.php | 1 +
 1 file changed, 1 insertion(+)

diff --git a/lib/Doctrine/ORM/EntityManager.php b/lib/Doctrine/ORM/EntityManager.php
index b3583de8e..8bb7a5896 100644
--- a/lib/Doctrine/ORM/EntityManager.php
+++ b/lib/Doctrine/ORM/EntityManager.php
@@ -199,6 +199,7 @@ class EntityManager implements ObjectManager
      * the transaction is rolled back, the EntityManager closed and the exception re-thrown.
      *
      * @param Closure $func The function to execute transactionally.
+     * @return mixed Returns the non-empty value returned from the closure or true instead
      */
     public function transactional(Closure $func)
     {

From 8eaf160eaddcd6099565bae8764612f3e2f6fd1f Mon Sep 17 00:00:00 2001
From: Benjamin Eberlei 
Date: Sat, 19 Nov 2011 09:29:32 +0100
Subject: [PATCH 127/135] Update Doctrine Common Vendor

---
 lib/vendor/doctrine-common | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/lib/vendor/doctrine-common b/lib/vendor/doctrine-common
index b2fd909b4..3052115f2 160000
--- a/lib/vendor/doctrine-common
+++ b/lib/vendor/doctrine-common
@@ -1 +1 @@
-Subproject commit b2fd909b4b5476df01744c9d34c7a23723a687b6
+Subproject commit 3052115f241020b67326cf1368540d33baa991f3

From bda593a66dcceed8c32b25843c69289e6ba3cefd Mon Sep 17 00:00:00 2001
From: Benjamin Eberlei 
Date: Sat, 19 Nov 2011 12:41:06 +0100
Subject: [PATCH 128/135] DDC-1448 - Add support for ObjectManagerAware
 interface and PersistentObject in ORM

---
 .../ORM/Mapping/ClassMetadataInfo.php         |  18 +++
 lib/Doctrine/ORM/UnitOfWork.php               |  24 +++-
 lib/vendor/doctrine-common                    |   2 +-
 .../ORM/Functional/PersistentObjectTest.php   | 105 ++++++++++++++++++
 4 files changed, 145 insertions(+), 4 deletions(-)
 create mode 100644 tests/Doctrine/Tests/ORM/Functional/PersistentObjectTest.php

diff --git a/lib/Doctrine/ORM/Mapping/ClassMetadataInfo.php b/lib/Doctrine/ORM/Mapping/ClassMetadataInfo.php
index 2734d3647..21643be96 100644
--- a/lib/Doctrine/ORM/Mapping/ClassMetadataInfo.php
+++ b/lib/Doctrine/ORM/Mapping/ClassMetadataInfo.php
@@ -1993,4 +1993,22 @@ class ClassMetadataInfo implements ClassMetadata
     {
         return isset($assoc['joinTable']['quoted']) ? $platform->quoteIdentifier($assoc['joinTable']['name']) : $assoc['joinTable']['name'];
     }
+
+    /**
+     * @param string $fieldName
+     * @return bool
+     */
+    public function isAssociationInverseSide($fieldName)
+    {
+        return isset($this->associationMappings[$fieldName]) && ! $this->associationMappings[$fieldName]['isOwningSide'];
+    }
+
+    /**
+     * @param string $fieldName
+     * @return string
+     */
+    public function getAssociationMappedByTargetField($fieldName)
+    {
+        return $this->associationMappings[$fieldName]['mappedBy'];
+    }
 }
diff --git a/lib/Doctrine/ORM/UnitOfWork.php b/lib/Doctrine/ORM/UnitOfWork.php
index 513f58b5a..2e096f890 100644
--- a/lib/Doctrine/ORM/UnitOfWork.php
+++ b/lib/Doctrine/ORM/UnitOfWork.php
@@ -24,6 +24,7 @@ use Exception, InvalidArgumentException, UnexpectedValueException,
     Doctrine\Common\Collections\Collection,
     Doctrine\Common\NotifyPropertyChanged,
     Doctrine\Common\PropertyChangedListener,
+    Doctrine\Common\Persistence\ObjectManagerAware,
     Doctrine\ORM\Event\LifecycleEventArgs,
     Doctrine\ORM\Mapping\ClassMetadata,
     Doctrine\ORM\Proxy\Proxy;
@@ -1642,7 +1643,7 @@ class UnitOfWork implements PropertyChangedListener
 
             // If there is no ID, it is actually NEW.
             if ( ! $id) {
-                $managedCopy = $class->newInstance();
+                $managedCopy = $this->newInstance($class);
 
                 $this->persistNew($class, $managedCopy);
             } else {
@@ -1666,7 +1667,7 @@ class UnitOfWork implements PropertyChangedListener
                         throw new EntityNotFoundException;
                     }
 
-                    $managedCopy = $class->newInstance();
+                    $managedCopy = $this->newInstance($class);
                     $class->setIdentifierValues($managedCopy, $id);
 
                     $this->persistNew($class, $managedCopy);
@@ -2157,6 +2158,18 @@ class UnitOfWork implements PropertyChangedListener
         return isset($this->collectionsDeletions[spl_object_hash($coll)]);
     }
 
+    /**
+     * @param ClassMetadata $class
+     */
+    private function newInstance($class)
+    {
+        $entity = $class->newInstance();
+        if ($entity instanceof \Doctrine\Common\Persistence\ObjectManagerAware) {
+            $entity->injectObjectManager($this->em, $class);
+        }
+        return $entity;
+    }
+
     /**
      * INTERNAL:
      * Creates an entity. Used for reconstitution of persistent entities.
@@ -2209,6 +2222,11 @@ class UnitOfWork implements PropertyChangedListener
                 // If only a specific entity is set to refresh, check that it's the one
                 if(isset($hints[Query::HINT_REFRESH_ENTITY])) {
                     $overrideLocalValues = $hints[Query::HINT_REFRESH_ENTITY] === $entity;
+
+                    // inject ObjectManager into just loaded proxies.
+                    if ($overrideLocalValues && $entity instanceof ObjectManagerAware) {
+                        $entity->injectObjectManager($this->em, $class);
+                    }
                 }
             }
 
@@ -2216,7 +2234,7 @@ class UnitOfWork implements PropertyChangedListener
                 $this->originalEntityData[$oid] = $data;
             }
         } else {
-            $entity = $class->newInstance();
+            $entity = $this->newInstance($class);
             $oid = spl_object_hash($entity);
             $this->entityIdentifiers[$oid] = $id;
             $this->entityStates[$oid] = self::STATE_MANAGED;
diff --git a/lib/vendor/doctrine-common b/lib/vendor/doctrine-common
index 3052115f2..9c880cf9a 160000
--- a/lib/vendor/doctrine-common
+++ b/lib/vendor/doctrine-common
@@ -1 +1 @@
-Subproject commit 3052115f241020b67326cf1368540d33baa991f3
+Subproject commit 9c880cf9ae2c14102568520b5ee885b03bda93e4
diff --git a/tests/Doctrine/Tests/ORM/Functional/PersistentObjectTest.php b/tests/Doctrine/Tests/ORM/Functional/PersistentObjectTest.php
new file mode 100644
index 000000000..88e54ae2e
--- /dev/null
+++ b/tests/Doctrine/Tests/ORM/Functional/PersistentObjectTest.php
@@ -0,0 +1,105 @@
+_schemaTool->createSchema(array(
+                $this->_em->getClassMetadata('Doctrine\Tests\ORM\Functional\PersistentEntity'),
+            ));
+        } catch (\Exception $e) {
+
+        }
+        PersistentObject::setObjectManager($this->_em);
+    }
+
+    public function testPersist()
+    {
+        $entity = new PersistentEntity();
+        $entity->setName("test");
+
+        $this->_em->persist($entity);
+        $this->_em->flush();
+    }
+
+    public function testFind()
+    {
+        $entity = new PersistentEntity();
+        $entity->setName("test");
+
+        $this->_em->persist($entity);
+        $this->_em->flush();
+        $this->_em->clear();
+
+        $entity = $this->_em->find(__NAMESPACE__ . '\PersistentEntity', $entity->getId());
+
+        $this->assertEquals('test', $entity->getName());
+        $entity->setName('foobar');
+
+        $this->_em->flush();
+    }
+
+    public function testGetReference()
+    {
+        $entity = new PersistentEntity();
+        $entity->setName("test");
+
+        $this->_em->persist($entity);
+        $this->_em->flush();
+        $this->_em->clear();
+
+        $entity = $this->_em->getReference(__NAMESPACE__ . '\PersistentEntity', $entity->getId());
+
+        $this->assertEquals('test', $entity->getName());
+    }
+
+    public function testSetAssociation()
+    {
+        $entity = new PersistentEntity();
+        $entity->setName("test");
+        $entity->setParent($entity);
+
+        $this->_em->persist($entity);
+        $this->_em->flush();
+        $this->_em->clear();
+
+        $entity = $this->_em->getReference(__NAMESPACE__ . '\PersistentEntity', $entity->getId());
+        $this->assertSame($entity, $entity->getParent());
+    }
+}
+
+/**
+ * @Entity
+ */
+class PersistentEntity extends PersistentObject
+{
+    /**
+     * @Id @Column(type="integer") @GeneratedValue
+     * @var int
+     */
+    protected $id;
+
+    /**
+     * @Column(type="string")
+     * @var string
+     */
+    protected $name;
+
+    /**
+     * @ManyToOne(targetEntity="PersistentEntity")
+     * @var PersistentEntity
+     */
+    protected $parent;
+}

From 841d12e9b6efebc08a76687c94e963376d223444 Mon Sep 17 00:00:00 2001
From: Jan Sorgalla 
Date: Sun, 20 Nov 2011 19:50:51 +0100
Subject: [PATCH 129/135] Move check for conversion SQL to ClassMetadataInfo

---
 lib/Doctrine/ORM/Mapping/ClassMetadataInfo.php   | 11 ++++++++++-
 lib/Doctrine/ORM/Mapping/MappingException.php    |  5 +++++
 .../AbstractEntityInheritancePersister.php       |  4 ++--
 .../ORM/Persisters/BasicEntityPersister.php      | 11 ++++++-----
 lib/Doctrine/ORM/Query/SqlWalker.php             | 10 +++++-----
 .../Tests/Models/CustomType/CustomTypeChild.php  |  2 +-
 .../Tests/Models/CustomType/CustomTypeParent.php |  2 +-
 .../BasicEntityPersisterTypeValueSqlTest.php     | 16 +++++++++++-----
 8 files changed, 41 insertions(+), 20 deletions(-)

diff --git a/lib/Doctrine/ORM/Mapping/ClassMetadataInfo.php b/lib/Doctrine/ORM/Mapping/ClassMetadataInfo.php
index b8c4ef2f1..c15b2d066 100644
--- a/lib/Doctrine/ORM/Mapping/ClassMetadataInfo.php
+++ b/lib/Doctrine/ORM/Mapping/ClassMetadataInfo.php
@@ -20,6 +20,7 @@
 namespace Doctrine\ORM\Mapping;
 
 use Doctrine\Common\Persistence\Mapping\ClassMetadata;
+use Doctrine\DBAL\Types\Type;
 use ReflectionClass;
 
 /**
@@ -735,7 +736,7 @@ class ClassMetadataInfo implements ClassMetadata
         // Complete id mapping
         if (isset($mapping['id']) && $mapping['id'] === true) {
             if ($this->versionField == $mapping['fieldName']) {
-                throw MappingException::cannotVersionIdField($this->name, $mapping['fieldName']);
+                throw MappingException::cannotVersionIdField($this->name, $mapping['fieldName'], $mapping['type']);
             }
 
             if ( ! in_array($mapping['fieldName'], $this->identifier)) {
@@ -746,6 +747,14 @@ class ClassMetadataInfo implements ClassMetadata
                 $this->isIdentifierComposite = true;
             }
         }
+
+        if (Type::hasType($mapping['type']) && Type::getType($mapping['type'])->canRequireSQLConversion()) {
+            if (isset($mapping['id']) && $mapping['id'] === true) {
+                 throw MappingException::sqlConversionNotAllowedForIdentifiers($this->name, $mapping['fieldName']);
+            }
+
+            $mapping['requireSQLConversion'] = true;
+        }
     }
 
     /**
diff --git a/lib/Doctrine/ORM/Mapping/MappingException.php b/lib/Doctrine/ORM/Mapping/MappingException.php
index e32e34c16..9c23d6f1a 100644
--- a/lib/Doctrine/ORM/Mapping/MappingException.php
+++ b/lib/Doctrine/ORM/Mapping/MappingException.php
@@ -226,6 +226,11 @@ class MappingException extends \Doctrine\ORM\ORMException
         return new self("Setting Id field '$fieldName' as versionale in entity class '$className' is not supported.");
     }
 
+    public static function sqlConversionNotAllowedForIdentifiers($className, $fieldName, $type)
+    {
+        return new self("It is not possible to set id field '$fieldName' to type '$type' in entity class '$className'. The type '$type' requires conversion SQL which is not allowed for identifiers.");
+    }
+
     /**
      * @param  string $className
      * @param  string $columnName
diff --git a/lib/Doctrine/ORM/Persisters/AbstractEntityInheritancePersister.php b/lib/Doctrine/ORM/Persisters/AbstractEntityInheritancePersister.php
index 4f45f0877..e3bb9a943 100644
--- a/lib/Doctrine/ORM/Persisters/AbstractEntityInheritancePersister.php
+++ b/lib/Doctrine/ORM/Persisters/AbstractEntityInheritancePersister.php
@@ -65,10 +65,10 @@ abstract class AbstractEntityInheritancePersister extends BasicEntityPersister
         $columnAlias = $this->getSQLColumnAlias($columnName);
         $this->_rsm->addFieldResult($alias, $columnAlias, $field, $class->name);
 
-        if (!$class->isIdentifier($field)) {
+        if (isset($class->fieldMappings[$field]['requireSQLConversion'])) {
             $type = Type::getType($class->getTypeOfField($field));
             $sql = $type->convertToPHPValueSQL($sql, $this->_platform);
-      }
+        }
 
         return $sql . ' AS ' . $columnAlias;
     }
diff --git a/lib/Doctrine/ORM/Persisters/BasicEntityPersister.php b/lib/Doctrine/ORM/Persisters/BasicEntityPersister.php
index 41fb01a75..16dace847 100644
--- a/lib/Doctrine/ORM/Persisters/BasicEntityPersister.php
+++ b/lib/Doctrine/ORM/Persisters/BasicEntityPersister.php
@@ -344,7 +344,7 @@ class BasicEntityPersister
             if (isset($this->_class->fieldNames[$columnName])) {
                 $column = $this->_class->getQuotedColumnName($this->_class->fieldNames[$columnName], $this->_platform);
 
-                if (!$this->_class->isIdentifier($this->_class->fieldNames[$columnName])) {
+                if (isset($this->_class->fieldMappings[$this->_class->fieldNames[$columnName]]['requireSQLConversion'])) {
                     $type = Type::getType($this->_columnTypes[$columnName]);
                     $placeholder = $type->convertToDatabaseValueSQL('?', $this->_platform);
                 }
@@ -1131,7 +1131,8 @@ class BasicEntityPersister
                 foreach ($columns AS $column) {
                     $placeholder = '?';
 
-                    if (isset($this->_columnTypes[$column])) {
+                    if (isset($this->_columnTypes[$column]) &&
+                        isset($this->_class->fieldMappings[$this->_class->fieldNames[$column]]['requireSQLConversion'])) {
                         $type = Type::getType($this->_columnTypes[$column]);
                         $placeholder = $type->convertToDatabaseValueSQL('?', $this->_platform);
                     }
@@ -1198,8 +1199,8 @@ class BasicEntityPersister
         $columnAlias = $this->getSQLColumnAlias($class->columnNames[$field]);
 
         $this->_rsm->addFieldResult($alias, $columnAlias, $field);
-        
-        if (!$class->isIdentifier($field)) {
+
+        if (isset($class->fieldMappings[$field]['requireSQLConversion'])) {
             $type = Type::getType($class->getTypeOfField($field));
             $sql = $type->convertToPHPValueSQL($sql, $this->_platform);
         }
@@ -1295,7 +1296,7 @@ class BasicEntityPersister
 
                 $conditionSql .= $this->_getSQLTableAlias($className) . '.' . $this->_class->getQuotedColumnName($field, $this->_platform);
 
-                if (!$this->_class->isIdentifier($field)) {
+                if (isset($this->_class->fieldMappings[$field]['requireSQLConversion'])) {
                     $type = Type::getType($this->_class->getTypeOfField($field));
                     $placeholder = $type->convertToDatabaseValueSQL($placeholder, $this->_platform);
                 }
diff --git a/lib/Doctrine/ORM/Query/SqlWalker.php b/lib/Doctrine/ORM/Query/SqlWalker.php
index 382796631..ccf6f2ca8 100644
--- a/lib/Doctrine/ORM/Query/SqlWalker.php
+++ b/lib/Doctrine/ORM/Query/SqlWalker.php
@@ -485,7 +485,7 @@ class SqlWalker implements TreeWalker
 
                 $column .= $class->getQuotedColumnName($fieldName, $this->_platform);
 
-                if ($this->_useDbalTypeValueSql && !$class->isIdentifier($fieldName)) {
+                if ($this->_useDbalTypeValueSql && isset($class->fieldMappings[$fieldName]['requireSQLConversion'])) {
                     $type = Type::getType($class->getTypeOfField($fieldName));
                     $column = $type->convertToPHPValueSQL($column, $this->_conn->getDatabasePlatform());
                 }
@@ -1028,7 +1028,7 @@ class SqlWalker implements TreeWalker
 
                 $col = $sqlTableAlias . '.' . $columnName;
 
-                if (!$class->isIdentifier($fieldName)) {
+                if (isset($class->fieldMappings[$fieldName]['requireSQLConversion'])) {
                     $type = Type::getType($class->getTypeOfField($fieldName));
                     $col  = $type->convertToPHPValueSQL($col, $this->_conn->getDatabasePlatform());
                 }
@@ -1117,7 +1117,7 @@ class SqlWalker implements TreeWalker
 
                     $col = $sqlTableAlias . '.' . $quotedColumnName;
 
-                    if (!$class->isIdentifier($fieldName)) {
+                    if (isset($class->fieldMappings[$fieldName]['requireSQLConversion'])) {
                         $type = Type::getType($class->getTypeOfField($fieldName));
                         $col = $type->convertToPHPValueSQL($col, $this->_platform);
                     }
@@ -1146,7 +1146,7 @@ class SqlWalker implements TreeWalker
 
                             $col = $sqlTableAlias . '.' . $quotedColumnName;
 
-                            if (!$subClass->isIdentifier($fieldName)) {
+                            if (isset($subClass->fieldMappings[$fieldName]['requireSQLConversion'])) {
                                 $type = Type::getType($subClass->getTypeOfField($fieldName));
                                 $col = $type->convertToPHPValueSQL($col, $this->_platform);
                             }
@@ -1434,7 +1434,7 @@ class SqlWalker implements TreeWalker
 
                 if ($updateItem->pathExpression->type == AST\PathExpression::TYPE_STATE_FIELD) {
                     $class = $this->_queryComponents[$updateItem->pathExpression->identificationVariable]['metadata'];
-                    if (!$class->isIdentifier($updateItem->pathExpression->field)) {
+                    if (isset($class->fieldMappings[$updateItem->pathExpression->field]['requireSQLConversion'])) {
                         $this->_currentColumnType = $class->getTypeOfField($updateItem->pathExpression->field);
                     }
                 }
diff --git a/tests/Doctrine/Tests/Models/CustomType/CustomTypeChild.php b/tests/Doctrine/Tests/Models/CustomType/CustomTypeChild.php
index 799ad5016..e178ab51c 100644
--- a/tests/Doctrine/Tests/Models/CustomType/CustomTypeChild.php
+++ b/tests/Doctrine/Tests/Models/CustomType/CustomTypeChild.php
@@ -9,7 +9,7 @@ namespace Doctrine\Tests\Models\CustomType;
 class CustomTypeChild
 {
     /**
-     * @Id @Column(type="negative_to_positive")
+     * @Id @Column(type="integer")
      * @GeneratedValue(strategy="AUTO")
      */
     public $id;
diff --git a/tests/Doctrine/Tests/Models/CustomType/CustomTypeParent.php b/tests/Doctrine/Tests/Models/CustomType/CustomTypeParent.php
index 0ade61c91..1cc3126e4 100644
--- a/tests/Doctrine/Tests/Models/CustomType/CustomTypeParent.php
+++ b/tests/Doctrine/Tests/Models/CustomType/CustomTypeParent.php
@@ -9,7 +9,7 @@ namespace Doctrine\Tests\Models\CustomType;
 class CustomTypeParent
 {
     /**
-     * @Id @Column(type="negative_to_positive")
+     * @Id @Column(type="integer")
      * @GeneratedValue(strategy="AUTO")
      */
     public $id;
diff --git a/tests/Doctrine/Tests/ORM/Persisters/BasicEntityPersisterTypeValueSqlTest.php b/tests/Doctrine/Tests/ORM/Persisters/BasicEntityPersisterTypeValueSqlTest.php
index e608e6ba8..04c47893d 100644
--- a/tests/Doctrine/Tests/ORM/Persisters/BasicEntityPersisterTypeValueSqlTest.php
+++ b/tests/Doctrine/Tests/ORM/Persisters/BasicEntityPersisterTypeValueSqlTest.php
@@ -18,16 +18,22 @@ class BasicEntityPersisterTypeValueSqlTest extends \Doctrine\Tests\OrmTestCase
     protected function setUp()
     {
         parent::setUp();
-
-        $this->_em = $this->_getTestEntityManager();
-
-        $this->_persister = new BasicEntityPersister($this->_em, $this->_em->getClassMetadata("Doctrine\Tests\Models\CustomType\CustomTypeParent"));
-
+        
         if (DBALType::hasType('negative_to_positive')) {
             DBALType::overrideType('negative_to_positive', '\Doctrine\Tests\DbalTypes\NegativeToPositiveType');
         } else {
             DBALType::addType('negative_to_positive', '\Doctrine\Tests\DbalTypes\NegativeToPositiveType');
         }
+        
+        if (DBALType::hasType('upper_case_string')) {
+            DBALType::overrideType('upper_case_string', '\Doctrine\Tests\DbalTypes\UpperCaseStringType');
+        } else {
+            DBALType::addType('upper_case_string', '\Doctrine\Tests\DbalTypes\UpperCaseStringType');
+        }
+
+        $this->_em = $this->_getTestEntityManager();
+
+        $this->_persister = new BasicEntityPersister($this->_em, $this->_em->getClassMetadata("Doctrine\Tests\Models\CustomType\CustomTypeParent"));
     }
 
     public function testGetInsertSQLUsesTypeValuesSQL()

From 4042bc53ce9c98124d24a652b1ef5c5a311296b9 Mon Sep 17 00:00:00 2001
From: Jan Sorgalla 
Date: Sun, 20 Nov 2011 19:57:04 +0100
Subject: [PATCH 130/135] Fix argument on wrong method call

---
 lib/Doctrine/ORM/Mapping/ClassMetadataInfo.php | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/lib/Doctrine/ORM/Mapping/ClassMetadataInfo.php b/lib/Doctrine/ORM/Mapping/ClassMetadataInfo.php
index c15b2d066..80349fc3c 100644
--- a/lib/Doctrine/ORM/Mapping/ClassMetadataInfo.php
+++ b/lib/Doctrine/ORM/Mapping/ClassMetadataInfo.php
@@ -736,7 +736,7 @@ class ClassMetadataInfo implements ClassMetadata
         // Complete id mapping
         if (isset($mapping['id']) && $mapping['id'] === true) {
             if ($this->versionField == $mapping['fieldName']) {
-                throw MappingException::cannotVersionIdField($this->name, $mapping['fieldName'], $mapping['type']);
+                throw MappingException::cannotVersionIdField($this->name, $mapping['fieldName']);
             }
 
             if ( ! in_array($mapping['fieldName'], $this->identifier)) {
@@ -750,7 +750,7 @@ class ClassMetadataInfo implements ClassMetadata
 
         if (Type::hasType($mapping['type']) && Type::getType($mapping['type'])->canRequireSQLConversion()) {
             if (isset($mapping['id']) && $mapping['id'] === true) {
-                 throw MappingException::sqlConversionNotAllowedForIdentifiers($this->name, $mapping['fieldName']);
+                 throw MappingException::sqlConversionNotAllowedForIdentifiers($this->name, $mapping['fieldName'], $mapping['type']);
             }
 
             $mapping['requireSQLConversion'] = true;

From 248c9bdeffde99c0f66cc323b6b6786ea9eca8ac Mon Sep 17 00:00:00 2001
From: Jonathan Ingram 
Date: Mon, 21 Nov 2011 12:34:20 +1100
Subject: [PATCH 131/135] Fixed typo

---
 lib/Doctrine/ORM/UnitOfWork.php | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/lib/Doctrine/ORM/UnitOfWork.php b/lib/Doctrine/ORM/UnitOfWork.php
index 2e096f890..91440eacf 100644
--- a/lib/Doctrine/ORM/UnitOfWork.php
+++ b/lib/Doctrine/ORM/UnitOfWork.php
@@ -712,7 +712,7 @@ class UnitOfWork implements PropertyChangedListener
                     if ( ! $assoc['isCascadePersist']) {
                         $message = "A new entity was found through the relationship '%s#%s' that was not configured " .
                             ' to cascade persist operations for entity: %s. Explicitly persist the new entity or ' .
-                            'configure cascading persist operations on tbe relationship. If you cannot find out ' .
+                            'configure cascading persist operations on the relationship. If you cannot find out ' .
                             'which entity causes the problem, implement %s#__toString() to get a clue.';
 
                         throw new InvalidArgumentException(sprintf(

From 135e515e7f57a832c52802f42247dbefe974f855 Mon Sep 17 00:00:00 2001
From: Benjamin Eberlei 
Date: Mon, 21 Nov 2011 15:04:14 +0100
Subject: [PATCH 132/135] DDC-1500 - Fix potential security problem in
 EntityRepository ORDER BY orientations

---
 lib/Doctrine/ORM/ORMException.php                      |  9 +++++++++
 lib/Doctrine/ORM/Persisters/BasicEntityPersister.php   |  6 +++++-
 .../Tests/ORM/Functional/EntityRepositoryTest.php      | 10 ++++++++++
 3 files changed, 24 insertions(+), 1 deletion(-)

diff --git a/lib/Doctrine/ORM/ORMException.php b/lib/Doctrine/ORM/ORMException.php
index c156893c5..f15e0fbe1 100644
--- a/lib/Doctrine/ORM/ORMException.php
+++ b/lib/Doctrine/ORM/ORMException.php
@@ -59,6 +59,15 @@ class ORMException extends Exception
         return new self("Unrecognized field: $field");
     }
 
+    /**
+     * @param string $className
+     * @param string $field
+     */
+    public static function invalidOrientation($className, $field)
+    {
+        return new self("Invalid order by orientation specified for " . $className . "#" . $field);
+    }
+
     public static function invalidFlushMode($mode)
     {
         return new self("'$mode' is an invalid flush mode.");
diff --git a/lib/Doctrine/ORM/Persisters/BasicEntityPersister.php b/lib/Doctrine/ORM/Persisters/BasicEntityPersister.php
index 4deece2e9..6b883ac13 100644
--- a/lib/Doctrine/ORM/Persisters/BasicEntityPersister.php
+++ b/lib/Doctrine/ORM/Persisters/BasicEntityPersister.php
@@ -906,7 +906,6 @@ class BasicEntityPersister
      * @param array $orderBy
      * @param string $baseTableAlias
      * @return string
-     * @todo Rename: _getOrderBySQL
      */
     protected final function _getOrderBySQL(array $orderBy, $baseTableAlias)
     {
@@ -917,6 +916,11 @@ class BasicEntityPersister
                 throw ORMException::unrecognizedField($fieldName);
             }
 
+            $orientation = strtoupper(trim($orientation));
+            if ($orientation != 'ASC' && $orientation != 'DESC') {
+                throw ORMException::invalidOrientation($this->_class->name, $fieldName);
+            }
+
             $tableAlias = isset($this->_class->fieldMappings[$fieldName]['inherited']) ?
                     $this->_getSQLTableAlias($this->_class->fieldMappings[$fieldName]['inherited'])
                     : $baseTableAlias;
diff --git a/tests/Doctrine/Tests/ORM/Functional/EntityRepositoryTest.php b/tests/Doctrine/Tests/ORM/Functional/EntityRepositoryTest.php
index 719a9f993..b14ccaa1f 100644
--- a/tests/Doctrine/Tests/ORM/Functional/EntityRepositoryTest.php
+++ b/tests/Doctrine/Tests/ORM/Functional/EntityRepositoryTest.php
@@ -491,5 +491,15 @@ class EntityRepositoryTest extends \Doctrine\Tests\OrmFunctionalTestCase
         $this->_em->getConfiguration()->setDefaultRepositoryClassName("Doctrine\Tests\Models\DDC753\DDC753InvalidRepository");
     }
 
+    /**
+     * @group DDC-1500
+     */
+    public function testInvalidOrientation()
+    {
+        $this->setExpectedException('Doctrine\ORM\ORMException', 'Invalid order by orientation specified for Doctrine\Tests\Models\CMS\CmsUser#username');
+
+        $repo = $this->_em->getRepository('Doctrine\Tests\Models\CMS\CmsUser');
+        $repo->findBy(array('status' => 'test'), array('username' => 'INVALID'));
+    }
 }
 

From 16aa558292d8513c27add2dd7ed0574b3d6820db Mon Sep 17 00:00:00 2001
From: jsor 
Date: Mon, 21 Nov 2011 15:08:36 +0100
Subject: [PATCH 133/135] Remove sql conversion from where clauses and update
 statements

---
 lib/Doctrine/ORM/Query/SqlWalker.php          | 52 ++-----------------
 .../DbalTypes/NegativeToPositiveType.php      |  2 +-
 .../Tests/ORM/Functional/TypeValueSqlTest.php |  5 +-
 .../ORM/Query/SelectSqlGenerationTest.php     | 18 ++++++-
 .../ORM/Query/UpdateSqlGenerationTest.php     | 12 +----
 5 files changed, 27 insertions(+), 62 deletions(-)

diff --git a/lib/Doctrine/ORM/Query/SqlWalker.php b/lib/Doctrine/ORM/Query/SqlWalker.php
index ccf6f2ca8..3b58dfba2 100644
--- a/lib/Doctrine/ORM/Query/SqlWalker.php
+++ b/lib/Doctrine/ORM/Query/SqlWalker.php
@@ -97,17 +97,6 @@ class SqlWalker implements TreeWalker
      * These should only be generated for SELECT queries, not for UPDATE/DELETE.
      */
     private $_useSqlTableAliases = true;
-    
-    /**
-     * Flag that indicates whether to pass columns through Type::convertToPHPValueSQL().
-     * These should only be done for SELECT queries, not for UPDATE.
-     */
-    private $_useDbalTypeValueSql = true;
-    
-    /**
-     * Holds the current columns type.
-     */
-    private $_currentColumnType;
 
     /**
      * The database platform abstraction.
@@ -421,7 +410,6 @@ class SqlWalker implements TreeWalker
     public function walkUpdateStatement(AST\UpdateStatement $AST)
     {
         $this->_useSqlTableAliases = false;
-        $this->_useDbalTypeValueSql = false;
 
         return $this->walkUpdateClause($AST->updateClause) . $this->walkWhereClause($AST->whereClause);
     }
@@ -477,20 +465,11 @@ class SqlWalker implements TreeWalker
                 $dqlAlias = $pathExpr->identificationVariable;
                 $class = $this->_queryComponents[$dqlAlias]['metadata'];
 
-                $column = '';
-
                 if ($this->_useSqlTableAliases) {
-                    $column .= $this->walkIdentificationVariable($dqlAlias, $fieldName) . '.';
+                    $sql .= $this->walkIdentificationVariable($dqlAlias, $fieldName) . '.';
                 }
 
-                $column .= $class->getQuotedColumnName($fieldName, $this->_platform);
-
-                if ($this->_useDbalTypeValueSql && isset($class->fieldMappings[$fieldName]['requireSQLConversion'])) {
-                    $type = Type::getType($class->getTypeOfField($fieldName));
-                    $column = $type->convertToPHPValueSQL($column, $this->_conn->getDatabasePlatform());
-                }
-
-                $sql .= $column;
+                $sql .= $class->getQuotedColumnName($fieldName, $this->_platform);
                 break;
 
             case AST\PathExpression::TYPE_SINGLE_VALUED_ASSOCIATION:
@@ -1429,18 +1408,7 @@ class SqlWalker implements TreeWalker
 
         switch (true) {
             case ($newValue instanceof AST\Node):
-                $currentColumnTypeBefore = $this->_currentColumnType;
-                $this->_currentColumnType  = null;
-
-                if ($updateItem->pathExpression->type == AST\PathExpression::TYPE_STATE_FIELD) {
-                    $class = $this->_queryComponents[$updateItem->pathExpression->identificationVariable]['metadata'];
-                    if (isset($class->fieldMappings[$updateItem->pathExpression->field]['requireSQLConversion'])) {
-                        $this->_currentColumnType = $class->getTypeOfField($updateItem->pathExpression->field);
-                    }
-                }
-
                 $sql .= $newValue->dispatch($this);
-                $this->_currentColumnType = $currentColumnTypeBefore;
                 break;
 
             case ($newValue === null):
@@ -1813,30 +1781,20 @@ class SqlWalker implements TreeWalker
     {
         switch ($literal->type) {
             case AST\Literal::STRING:
-                $value = $this->_conn->quote($literal->value);
-                break;
+                return $this->_conn->quote($literal->value);
 
             case AST\Literal::BOOLEAN:
                 $bool = strtolower($literal->value) == 'true' ? true : false;
                 $boolVal = $this->_conn->getDatabasePlatform()->convertBooleans($bool);
 
-                $value = $boolVal;
-                break;
+                return $boolVal;
 
             case AST\Literal::NUMERIC:
-                $value = $literal->value;
-                break;
+                return $literal->value;
 
             default:
                 throw QueryException::invalidLiteral($literal);
         }
-        
-        if ($this->_currentColumnType !== null) {
-            $type  = Type::getType($this->_currentColumnType);
-            $value = $type->convertToDatabaseValueSQL($value, $this->_conn->getDatabasePlatform());
-        }
-        
-        return $value;
     }
 
     /**
diff --git a/tests/Doctrine/Tests/DbalTypes/NegativeToPositiveType.php b/tests/Doctrine/Tests/DbalTypes/NegativeToPositiveType.php
index e477ecd3c..ae704f8bd 100644
--- a/tests/Doctrine/Tests/DbalTypes/NegativeToPositiveType.php
+++ b/tests/Doctrine/Tests/DbalTypes/NegativeToPositiveType.php
@@ -29,6 +29,6 @@ class NegativeToPositiveType extends Type
 
     public function convertToPHPValueSQL($sqlExpr, $platform)
     {
-        return '((' . $sqlExpr . ') * -1)';
+        return '-(' . $sqlExpr . ')';
     }
 }
diff --git a/tests/Doctrine/Tests/ORM/Functional/TypeValueSqlTest.php b/tests/Doctrine/Tests/ORM/Functional/TypeValueSqlTest.php
index 6102812d7..5a05d76d2 100644
--- a/tests/Doctrine/Tests/ORM/Functional/TypeValueSqlTest.php
+++ b/tests/Doctrine/Tests/ORM/Functional/TypeValueSqlTest.php
@@ -46,7 +46,7 @@ class TypeValueSqlTest extends \Doctrine\Tests\OrmFunctionalTestCase
         $this->assertEquals('foo', $entity->lowerCaseString, 'Entity holds lowercase string');
         $this->assertEquals('FOO', $this->_em->getConnection()->fetchColumn("select lowerCaseString from customtype_uppercases where id=".$entity->id.""), 'Database holds uppercase string');
     }
- 
+
     public function testTypeValueSqlWithAssociations()
     {
         $parent = new CustomTypeParent();
@@ -90,12 +90,13 @@ class TypeValueSqlTest extends \Doctrine\Tests\OrmFunctionalTestCase
 
         $this->_em->clear();
 
-        $query = $this->_em->createQuery("SELECT p, p.customInteger, c from Doctrine\Tests\Models\CustomType\CustomTypeParent p JOIN p.child c where p.id = " . $parentId . " AND p.customInteger = -1");
+        $query = $this->_em->createQuery("SELECT p, p.customInteger, c from Doctrine\Tests\Models\CustomType\CustomTypeParent p JOIN p.child c where p.id = " . $parentId);
 
         $result = $query->getResult();
 
         $this->assertEquals(1, count($result));
         $this->assertInstanceOf('Doctrine\Tests\Models\CustomType\CustomTypeParent', $result[0][0]);
+        $this->assertEquals(-1, $result[0][0]->customInteger);
 
         $this->assertEquals(-1, $result[0]['customInteger']);
 
diff --git a/tests/Doctrine/Tests/ORM/Query/SelectSqlGenerationTest.php b/tests/Doctrine/Tests/ORM/Query/SelectSqlGenerationTest.php
index 028406068..ceb1a3729 100644
--- a/tests/Doctrine/Tests/ORM/Query/SelectSqlGenerationTest.php
+++ b/tests/Doctrine/Tests/ORM/Query/SelectSqlGenerationTest.php
@@ -1345,7 +1345,7 @@ class SelectSqlGenerationTest extends \Doctrine\Tests\OrmTestCase
 
         $this->assertSqlGeneration(
             'SELECT p.customInteger FROM Doctrine\Tests\Models\CustomType\CustomTypeParent p WHERE p.id = 1',
-            'SELECT ((c0_.customInteger) * -1) AS customInteger0 FROM customtype_parents c0_ WHERE c0_.id = 1'
+            'SELECT -(c0_.customInteger) AS customInteger0 FROM customtype_parents c0_ WHERE c0_.id = 1'
         );
     }
 
@@ -1373,7 +1373,21 @@ class SelectSqlGenerationTest extends \Doctrine\Tests\OrmTestCase
 
         $this->assertSqlGeneration(
             'SELECT p FROM Doctrine\Tests\Models\CustomType\CustomTypeParent p',
-            'SELECT c0_.id AS id0, ((c0_.customInteger) * -1) AS customInteger1 FROM customtype_parents c0_'
+            'SELECT c0_.id AS id0, -(c0_.customInteger) AS customInteger1 FROM customtype_parents c0_'
+        );
+    }
+
+    public function testCustomTypeValueSqlForPartialObject()
+    {
+        if (DBALType::hasType('negative_to_positive')) {
+            DBALType::overrideType('negative_to_positive', 'Doctrine\Tests\DbalTypes\NegativeToPositiveType');
+        } else {
+            DBALType::addType('negative_to_positive', 'Doctrine\Tests\DbalTypes\NegativeToPositiveType');
+        }
+
+        $this->assertSqlGeneration(
+            'SELECT partial p.{id, customInteger} FROM Doctrine\Tests\Models\CustomType\CustomTypeParent p',
+            'SELECT c0_.id AS id0, -(c0_.customInteger) AS customInteger1 FROM customtype_parents c0_'
         );
     }
 }
diff --git a/tests/Doctrine/Tests/ORM/Query/UpdateSqlGenerationTest.php b/tests/Doctrine/Tests/ORM/Query/UpdateSqlGenerationTest.php
index 34658c95a..a65efe079 100644
--- a/tests/Doctrine/Tests/ORM/Query/UpdateSqlGenerationTest.php
+++ b/tests/Doctrine/Tests/ORM/Query/UpdateSqlGenerationTest.php
@@ -195,19 +195,11 @@ class UpdateSqlGenerationTest extends \Doctrine\Tests\OrmTestCase
         );
     }
 
-    public function testCustomTypeValueSql()
+    public function testCustomTypeValueSqlCompletelyIgnoredInUpdateStatements()
     {
         $this->assertSqlGeneration(
             'UPDATE Doctrine\Tests\Models\CustomType\CustomTypeParent p SET p.customInteger = 1 WHERE p.id = 1',
-            'UPDATE customtype_parents SET customInteger = ABS(1) WHERE id = 1'
-        );
-    }
-
-    public function testCustomTypeValueSqlIgnoresIdentifierColumns()
-    {
-        $this->assertSqlGeneration(
-            'UPDATE Doctrine\Tests\Models\CustomType\CustomTypeParent p SET p.id = 2 WHERE p.id = 1',
-            'UPDATE customtype_parents SET id = 2 WHERE id = 1'
+            'UPDATE customtype_parents SET customInteger = 1 WHERE id = 1'
         );
     }
 }

From b80ef58cab801d2c6cd3e26d757e047e43b79c1d Mon Sep 17 00:00:00 2001
From: warezthebeef 
Date: Wed, 23 Nov 2011 12:15:23 +1300
Subject: [PATCH 134/135] Fixed array_flip breaking discriminator map SQL
 generation

---
 lib/Doctrine/ORM/Persisters/SingleTablePersister.php | 8 ++++----
 1 file changed, 4 insertions(+), 4 deletions(-)

diff --git a/lib/Doctrine/ORM/Persisters/SingleTablePersister.php b/lib/Doctrine/ORM/Persisters/SingleTablePersister.php
index b39d3bf75..897900d22 100644
--- a/lib/Doctrine/ORM/Persisters/SingleTablePersister.php
+++ b/lib/Doctrine/ORM/Persisters/SingleTablePersister.php
@@ -120,10 +120,10 @@ class SingleTablePersister extends AbstractEntityInheritancePersister
             $values[] = $this->_conn->quote($this->_class->discriminatorValue);
         }
 
-        $discrValues = array_flip($this->_class->discriminatorMap);
+        $discrValues = array_keys($this->_class->discriminatorMap);
         
-        foreach ($this->_class->subClasses as $subclassName) {
-            $values[] = $this->_conn->quote($discrValues[$subclassName]);
+        foreach ($this->_class->subClasses as $i => $subclassName) {
+            $values[] = $this->_conn->quote($discrValues[$i]);
         }
         
         $conditionSql .= $this->_getSQLTableAlias($this->_class->name) . '.' . $this->_class->discriminatorColumn['name']
@@ -131,4 +131,4 @@ class SingleTablePersister extends AbstractEntityInheritancePersister
 
         return $conditionSql;
     }
-}
\ No newline at end of file
+}

From ef33454301a5fdd5579a04968a4f56b48d25d844 Mon Sep 17 00:00:00 2001
From: Guilherme Blanco 
Date: Wed, 23 Nov 2011 08:40:47 -0500
Subject: [PATCH 135/135] Reverted PR which broke suite. Issue is still valid,
 but it requires more investigation.

---
 lib/Doctrine/ORM/Persisters/SingleTablePersister.php | 6 +++---
 lib/Doctrine/ORM/UnitOfWork.php                      | 6 ++++++
 2 files changed, 9 insertions(+), 3 deletions(-)

diff --git a/lib/Doctrine/ORM/Persisters/SingleTablePersister.php b/lib/Doctrine/ORM/Persisters/SingleTablePersister.php
index 897900d22..0f1b9e3de 100644
--- a/lib/Doctrine/ORM/Persisters/SingleTablePersister.php
+++ b/lib/Doctrine/ORM/Persisters/SingleTablePersister.php
@@ -120,10 +120,10 @@ class SingleTablePersister extends AbstractEntityInheritancePersister
             $values[] = $this->_conn->quote($this->_class->discriminatorValue);
         }
 
-        $discrValues = array_keys($this->_class->discriminatorMap);
+        $discrValues = array_flip($this->_class->discriminatorMap);
         
-        foreach ($this->_class->subClasses as $i => $subclassName) {
-            $values[] = $this->_conn->quote($discrValues[$i]);
+        foreach ($this->_class->subClasses as $subclassName) {
+            $values[] = $this->_conn->quote($discrValues[$subclassName]);
         }
         
         $conditionSql .= $this->_getSQLTableAlias($this->_class->name) . '.' . $this->_class->discriminatorColumn['name']
diff --git a/lib/Doctrine/ORM/UnitOfWork.php b/lib/Doctrine/ORM/UnitOfWork.php
index 91440eacf..0a04d05ae 100644
--- a/lib/Doctrine/ORM/UnitOfWork.php
+++ b/lib/Doctrine/ORM/UnitOfWork.php
@@ -2202,6 +2202,12 @@ class UnitOfWork implements PropertyChangedListener
             if (isset($class->associationMappings[$class->identifier[0]])) {
                 $idHash = $data[$class->associationMappings[$class->identifier[0]]['joinColumns'][0]['name']];
             } else {
+                /*echo $className;
+                \Doctrine\Common\Util\Debug::dump($data);
+                \Doctrine\Common\Util\Debug::dump($class->identifier);
+                ob_end_flush();
+                ob_start();*/
+                
                 $idHash = $data[$class->identifier[0]];
             }
             $id = array($class->identifier[0] => $idHash);