From bfc7986b20f66cd8cc96cc3f6f0b25feccb8f7d9 Mon Sep 17 00:00:00 2001 From: "Fabio B. Silva" Date: Sun, 26 Feb 2012 16:51:39 -0300 Subject: [PATCH] annotation driver and basic support --- lib/Doctrine/ORM/EntityRepository.php | 16 +++++ .../ORM/Mapping/ClassMetadataInfo.php | 19 ++++-- .../ORM/Mapping/Driver/AnnotationDriver.php | 58 ++++++++++++++++- .../Mapping/Driver/DoctrineAnnotations.php | 7 ++ lib/Doctrine/ORM/Mapping/EntityResult.php | 2 +- lib/Doctrine/ORM/Mapping/MappingException.php | 2 +- .../ORM/Mapping/NamedNativeQueries.php | 2 +- lib/Doctrine/ORM/Mapping/NamedNativeQuery.php | 2 +- .../ORM/Mapping/SqlResultSetMapping.php | 6 +- .../ORM/Mapping/SqlResultSetMappings.php | 2 +- .../ORM/Query/ResultSetMappingBuilder.php | 35 +++++++++- .../Doctrine/Tests/Models/CMS/CmsAddress.php | 25 +++++++ .../Tests/ORM/Functional/NativeQueryTest.php | 36 ++++++++++ .../ORM/Hydration/ResultSetMappingTest.php | 65 +++++++++++++++++++ 14 files changed, 261 insertions(+), 16 deletions(-) diff --git a/lib/Doctrine/ORM/EntityRepository.php b/lib/Doctrine/ORM/EntityRepository.php index 75cbf029e..6c61855ef 100644 --- a/lib/Doctrine/ORM/EntityRepository.php +++ b/lib/Doctrine/ORM/EntityRepository.php @@ -89,6 +89,22 @@ class EntityRepository implements ObjectRepository return $this->_em->createQuery($this->_class->getNamedQuery($queryName)); } + /** + * Creates a native SQL query. + * + * @param string $queryName + * @return NativeQuery + */ + public function createNativeNamedQuery($queryName) + { + $queryMapping = $this->_class->getNamedNativeQuery($queryName); + $resultMapping = $this->_class->getSqlResultSetMapping($queryMapping['resultSetMapping']); + $rsm = new Query\ResultSetMappingBuilder($this->_em); + $rsm->addSqlResultSetMapping($resultMapping); + + return $this->_em->createNativeQuery($queryMapping['query'], $rsm); + } + /** * Clears the repository, causing all managed entities to become detached. */ diff --git a/lib/Doctrine/ORM/Mapping/ClassMetadataInfo.php b/lib/Doctrine/ORM/Mapping/ClassMetadataInfo.php index 3c4796307..798b011e7 100644 --- a/lib/Doctrine/ORM/Mapping/ClassMetadataInfo.php +++ b/lib/Doctrine/ORM/Mapping/ClassMetadataInfo.php @@ -1983,8 +1983,15 @@ class ClassMetadataInfo implements ClassMetadata throw MappingException::missingQueryMapping($this->name, $queryMapping['name']); } - if (isset($queryMapping['resultClass']) && $queryMapping['resultClass'] === '__CLASS__') { - $queryMapping['resultClass'] = $this->name; + if (isset($queryMapping['resultClass'])) { + + if($queryMapping['resultClass'] === '__CLASS__') { + $queryMapping['resultClass'] = $this->name; + } else if (strlen($this->namespace) > 0 && strpos($queryMapping['resultClass'], '\\') === false) { + $queryMapping['resultClass'] = $this->namespace . '\\' . $queryMapping['resultClass']; + } + + $mapping['targetEntity'] = ltrim($queryMapping['resultClass'], '\\'); } $this->namedNativeQueries[$queryMapping['name']] = $queryMapping; @@ -2013,9 +2020,13 @@ class ClassMetadataInfo implements ClassMetadata throw MappingException::missingResultSetMappingEntity($this->name, $resultMapping['name']); } - if ($entityResult['entityClass'] === '__CLASS__') { - $resultMapping['entities'][$key]['entityClass'] = $this->name; + if($entityResult['entityClass'] === '__CLASS__') { + $entityResult['entityClass'] = $this->name; + } else if (strlen($this->namespace) > 0 && strpos($entityResult['entityClass'], '\\') === false) { + $entityResult['entityClass'] = $this->namespace . '\\' . $entityResult['entityClass']; } + + $resultMapping['entities'][$key]['entityClass'] = ltrim($entityResult['entityClass'], '\\'); } } diff --git a/lib/Doctrine/ORM/Mapping/Driver/AnnotationDriver.php b/lib/Doctrine/ORM/Mapping/Driver/AnnotationDriver.php index 22a1250ba..59b1733e0 100644 --- a/lib/Doctrine/ORM/Mapping/Driver/AnnotationDriver.php +++ b/lib/Doctrine/ORM/Mapping/Driver/AnnotationDriver.php @@ -204,15 +204,67 @@ class AnnotationDriver implements Driver $metadata->setPrimaryTable($primaryTable); } + // Evaluate NamedNativeQueries annotation + if (isset($classAnnotations['Doctrine\ORM\Mapping\NamedNativeQueries'])) { + $namedNativeQueriesAnnot = $classAnnotations['Doctrine\ORM\Mapping\NamedNativeQueries']; + + foreach ($namedNativeQueriesAnnot->value as $namedNativeQuery) { + $metadata->addNamedNativeQuery(array( + 'name' => $namedNativeQuery->name, + 'query' => $namedNativeQuery->query, + 'resultClass' => $namedNativeQuery->resultClass, + 'resultSetMapping' => $namedNativeQuery->resultSetMapping, + )); + } + } + + // Evaluate SqlResultSetMappings annotation + if (isset($classAnnotations['Doctrine\ORM\Mapping\SqlResultSetMappings'])) { + $sqlResultSetMappingsAnnot = $classAnnotations['Doctrine\ORM\Mapping\SqlResultSetMappings']; + + foreach ($sqlResultSetMappingsAnnot->value as $resultSetMapping) { + $entities = array(); + foreach ($resultSetMapping->entities as $entityResultAnnot) { + $entityResult = array( + 'fields' => array(), + 'entityClass' => $entityResultAnnot->entityClass, + 'discriminatorColumn' => $entityResultAnnot->discriminatorColumn, + ); + + foreach ($entityResultAnnot->fields as $fieldResultAnnot) { + $entityResult['fields'][] = array( + 'name' => $fieldResultAnnot->name, + 'column' => $fieldResultAnnot->column + ); + } + + $entities[] = $entityResult; + } + + $columns = array(); + foreach ($resultSetMapping->columns as $columnResultAnnot) { + $columns[] = array( + 'name' => $resultSetMapping->name, + ); + } + + $metadata->addSqlResultSetMapping(array( + 'name' => $resultSetMapping->name, + 'entities' => $entities, + 'columns' => $columns + )); + } + } + // Evaluate NamedQueries annotation if (isset($classAnnotations['Doctrine\ORM\Mapping\NamedQueries'])) { - $namedQueriesAnnot = $classAnnotations['Doctrine\ORM\Mapping\NamedQueries']; + $namedNativeQueriesAnnot = $classAnnotations['Doctrine\ORM\Mapping\NamedQueries']; - if (!is_array($namedQueriesAnnot->value)) { + if (!is_array($namedNativeQueriesAnnot->value)) { throw new \UnexpectedValueException("@NamedQueries should contain an array of @NamedQuery annotations."); } - foreach ($namedQueriesAnnot->value as $namedQuery) { + foreach ($namedNativeQueriesAnnot->value as $namedQuery) { if (!($namedQuery instanceof \Doctrine\ORM\Mapping\NamedQuery)) { throw new \UnexpectedValueException("@NamedQueries should contain an array of @NamedQuery annotations."); } diff --git a/lib/Doctrine/ORM/Mapping/Driver/DoctrineAnnotations.php b/lib/Doctrine/ORM/Mapping/Driver/DoctrineAnnotations.php index bd1632b2c..46fa1551b 100644 --- a/lib/Doctrine/ORM/Mapping/Driver/DoctrineAnnotations.php +++ b/lib/Doctrine/ORM/Mapping/Driver/DoctrineAnnotations.php @@ -53,3 +53,10 @@ require_once __DIR__.'/../PreRemove.php'; require_once __DIR__.'/../PostRemove.php'; require_once __DIR__.'/../PostLoad.php'; require_once __DIR__.'/../PreFlush.php'; +require_once __DIR__.'/../FieldResult.php'; +require_once __DIR__.'/../ColumnResult.php'; +require_once __DIR__.'/../EntityResult.php'; +require_once __DIR__.'/../NamedNativeQuery.php'; +require_once __DIR__.'/../NamedNativeQueries.php'; +require_once __DIR__.'/../SqlResultSetMapping.php'; +require_once __DIR__.'/../SqlResultSetMappings.php'; \ No newline at end of file diff --git a/lib/Doctrine/ORM/Mapping/EntityResult.php b/lib/Doctrine/ORM/Mapping/EntityResult.php index ae83d52c2..1be9206e7 100644 --- a/lib/Doctrine/ORM/Mapping/EntityResult.php +++ b/lib/Doctrine/ORM/Mapping/EntityResult.php @@ -46,7 +46,7 @@ final class EntityResult implements Annotation * * @var array<\Doctrine\ORM\Mapping\FieldResult> */ - public $fields; + public $fields = array(); /** * Specifies the column name of the column in the SELECT list that is used to determine the type of the entity instance. diff --git a/lib/Doctrine/ORM/Mapping/MappingException.php b/lib/Doctrine/ORM/Mapping/MappingException.php index fffbbcaae..c76138582 100644 --- a/lib/Doctrine/ORM/Mapping/MappingException.php +++ b/lib/Doctrine/ORM/Mapping/MappingException.php @@ -117,7 +117,7 @@ class MappingException extends \Doctrine\ORM\ORMException { return new self('Result set mapping named "'.$resultName.'" in "'.$entity.' requires a entity class name.'); } - + public static function nameIsMandatoryForSqlResultSetMapping($className) { return new self("Result set mapping name on entity class '$className' is not defined."); diff --git a/lib/Doctrine/ORM/Mapping/NamedNativeQueries.php b/lib/Doctrine/ORM/Mapping/NamedNativeQueries.php index 840d29694..f01090ee6 100644 --- a/lib/Doctrine/ORM/Mapping/NamedNativeQueries.php +++ b/lib/Doctrine/ORM/Mapping/NamedNativeQueries.php @@ -36,5 +36,5 @@ final class NamedNativeQueries implements Annotation * * @var array<\Doctrine\ORM\Mapping\NamedNativeQuery> */ - public $value; + public $value = array(); } \ No newline at end of file diff --git a/lib/Doctrine/ORM/Mapping/NamedNativeQuery.php b/lib/Doctrine/ORM/Mapping/NamedNativeQuery.php index 022d9801e..052eded64 100644 --- a/lib/Doctrine/ORM/Mapping/NamedNativeQuery.php +++ b/lib/Doctrine/ORM/Mapping/NamedNativeQuery.php @@ -27,7 +27,7 @@ namespace Doctrine\ORM\Mapping; * @since 2.3 * * @Annotation - * @Target("CLASS") + * @Target("ANNOTATION") */ final class NamedNativeQuery implements Annotation { diff --git a/lib/Doctrine/ORM/Mapping/SqlResultSetMapping.php b/lib/Doctrine/ORM/Mapping/SqlResultSetMapping.php index a3bc5b4c9..e009ceea2 100644 --- a/lib/Doctrine/ORM/Mapping/SqlResultSetMapping.php +++ b/lib/Doctrine/ORM/Mapping/SqlResultSetMapping.php @@ -27,7 +27,7 @@ namespace Doctrine\ORM\Mapping; * @since 2.3 * * @Annotation - * @Target("CLASS") + * @Target("ANNOTATION") */ final class SqlResultSetMapping implements Annotation { @@ -44,13 +44,13 @@ final class SqlResultSetMapping implements Annotation * * @var array<\Doctrine\ORM\Mapping\EntityResult> */ - public $entities; + public $entities = array(); /** * Specifies the result set mapping to scalar values. * * @var array<\Doctrine\ORM\Mapping\ColumnResult> */ - public $columns; + public $columns = array(); } \ No newline at end of file diff --git a/lib/Doctrine/ORM/Mapping/SqlResultSetMappings.php b/lib/Doctrine/ORM/Mapping/SqlResultSetMappings.php index c8c82a916..fa04387a2 100644 --- a/lib/Doctrine/ORM/Mapping/SqlResultSetMappings.php +++ b/lib/Doctrine/ORM/Mapping/SqlResultSetMappings.php @@ -36,5 +36,5 @@ final class SqlResultSetMappings implements Annotation * * @var array<\Doctrine\ORM\Mapping\SqlResultSetMapping> */ - public $value; + public $value = array(); } \ No newline at end of file diff --git a/lib/Doctrine/ORM/Query/ResultSetMappingBuilder.php b/lib/Doctrine/ORM/Query/ResultSetMappingBuilder.php index 316a2012c..138b51ede 100644 --- a/lib/Doctrine/ORM/Query/ResultSetMappingBuilder.php +++ b/lib/Doctrine/ORM/Query/ResultSetMappingBuilder.php @@ -106,4 +106,37 @@ class ResultSetMappingBuilder extends ResultSetMapping } } } -} + + + /** + * @param array $mapping + */ + public function addSqlResultSetMapping(array $mapping) + { + if (isset($mapping['entities'])) { + foreach ($mapping['entities'] as $key => $map) { + $simpleName = $map['entityClass']; + if (strpos($simpleName, '\\') !== false) { + $simpleName = substr($simpleName, strrpos($simpleName, '\\') + 1); + } + + $className = $map['entityClass']; + $alias = strtolower($simpleName[0]) . $key; + + $this->addEntityResult($className, $alias); + + if (isset($map['fields'])) { + foreach ($map['fields'] as $field) { + $this->addFieldResult($alias, $field['column'], $field['name'], $className); + } + } + } + } + + if (isset($mapping['columns'])) { + foreach ($mapping['columns'] as $map) { + $this->addScalarResult($map['name'], $map['name']); + } + } + } +} \ No newline at end of file diff --git a/tests/Doctrine/Tests/Models/CMS/CmsAddress.php b/tests/Doctrine/Tests/Models/CMS/CmsAddress.php index d32416a5e..48084cc86 100644 --- a/tests/Doctrine/Tests/Models/CMS/CmsAddress.php +++ b/tests/Doctrine/Tests/Models/CMS/CmsAddress.php @@ -8,6 +8,31 @@ namespace Doctrine\Tests\Models\CMS; * @author Roman S. Borschel * @Entity * @Table(name="cms_addresses") + * + * @NamedNativeQueries({ + * @NamedNativeQuery( + * name = "find-all", + * resultSetMapping = "mapping-find-all", + * query = "SELECT id, country, city FROM cms_addresses" + * ) + * }) + * + * @SqlResultSetMappings({ + * @SqlResultSetMapping( + * name = "mapping-find-all", + * entities= { + * @EntityResult( + * entityClass = "CmsAddress", + * fields = { + * @FieldResult(name = "id", column="id"), + * @FieldResult(name = "city", column="city"), + * @FieldResult(name = "country", column="country") + * } + * ) + * } + * ) + * }) + * */ class CmsAddress { diff --git a/tests/Doctrine/Tests/ORM/Functional/NativeQueryTest.php b/tests/Doctrine/Tests/ORM/Functional/NativeQueryTest.php index eb46329f1..db02b3d4b 100644 --- a/tests/Doctrine/Tests/ORM/Functional/NativeQueryTest.php +++ b/tests/Doctrine/Tests/ORM/Functional/NativeQueryTest.php @@ -329,5 +329,41 @@ class NativeQueryTest extends \Doctrine\Tests\OrmFunctionalTestCase ); $users = $query->getResult(); } + + + /** + * @group DDC-1663 + */ + public function testBasicNativeNamedQuery() + { + $user = new CmsUser; + $user->name = 'Fabio B. Silva'; + $user->username = 'FabioBatSilva'; + $user->status = 'dev'; + + $addr = new CmsAddress; + $addr->country = 'Brazil'; + $addr->zip = 10827; + $addr->city = 'São Paulo'; + + $user->setAddress($addr); + + $this->_em->clear(); + $this->_em->persist($user); + $this->_em->flush(); + + $this->_em->clear(); + + + $repository = $this->_em->getRepository('Doctrine\Tests\Models\CMS\CmsAddress'); + $query = $repository->createNativeNamedQuery('find-all'); + $result = $query->getResult(); + + $this->assertCount(1, $result); + $this->assertInstanceOf('Doctrine\Tests\Models\CMS\CmsAddress', $result[0]); + $this->assertEquals($addr->id, $result[0]->id); + $this->assertEquals($addr->city, $result[0]->city); + $this->assertEquals($addr->country, $result[0]->country); + } } diff --git a/tests/Doctrine/Tests/ORM/Hydration/ResultSetMappingTest.php b/tests/Doctrine/Tests/ORM/Hydration/ResultSetMappingTest.php index 785c19b74..ba7421c7a 100644 --- a/tests/Doctrine/Tests/ORM/Hydration/ResultSetMappingTest.php +++ b/tests/Doctrine/Tests/ORM/Hydration/ResultSetMappingTest.php @@ -93,5 +93,70 @@ class ResultSetMappingTest extends \Doctrine\Tests\OrmTestCase $this->assertTrue($rms->hasParentAlias('p')); $this->assertTrue($rms->isMixedResult()); } + + /** + * @group DDC-1663 + */ + public function testAddSqlResultSetMapping() + { + $cm = new ClassMetadata('Doctrine\Tests\Models\CMS\CmsUser'); + $cm->initializeReflection(new \Doctrine\Common\Persistence\Mapping\RuntimeReflectionService); + + $cm->addSqlResultSetMapping(array( + 'name' => 'find-all', + 'entities' => array( + array( + 'entityClass' => '__CLASS__', + 'fields' => array( + array( + 'name' => 'id', + 'column'=> 'user_id' + ), + array( + 'name' => 'name', + 'column'=> 'user_name' + ) + ) + ), + array( + 'entityClass' => 'CmsEmail', + 'fields' => array( + array( + 'name' => 'id', + 'column'=> 'email_id' + ), + array( + 'name' => 'email', + 'column'=> 'email_email' + ) + ) + ) + ), + 'columns' => array( + array( + 'name' => 'scalarColumn' + ) + ) + )); + + + $rsm = new \Doctrine\ORM\Query\ResultSetMappingBuilder($this->_em); + $rsm->addSqlResultSetMapping($cm->getSqlResultSetMapping('find-all')); + + $this->assertEquals('scalarColumn', $rsm->getScalarAlias('scalarColumn')); + + $this->assertEquals('c0', $rsm->getEntityAlias('user_id')); + $this->assertEquals('c0', $rsm->getEntityAlias('user_name')); + $this->assertEquals('Doctrine\Tests\Models\CMS\CmsUser', $rsm->getClassName('c0')); + $this->assertEquals('Doctrine\Tests\Models\CMS\CmsUser', $rsm->getDeclaringClass('user_id')); + $this->assertEquals('Doctrine\Tests\Models\CMS\CmsUser', $rsm->getDeclaringClass('user_name')); + + + $this->assertEquals('c1', $rsm->getEntityAlias('email_id')); + $this->assertEquals('c1', $rsm->getEntityAlias('email_email')); + $this->assertEquals('Doctrine\Tests\Models\CMS\CmsEmail', $rsm->getClassName('c1')); + $this->assertEquals('Doctrine\Tests\Models\CMS\CmsEmail', $rsm->getDeclaringClass('email_id')); + $this->assertEquals('Doctrine\Tests\Models\CMS\CmsEmail', $rsm->getDeclaringClass('email_email')); + } }