1
0
mirror of synced 2025-01-19 06:51:40 +03:00

Merge branch 'NamedQueries'

This commit is contained in:
Guilherme Blanco 2011-03-09 14:44:56 -03:00
commit 925f1c281c
15 changed files with 225 additions and 3 deletions

View File

@ -56,6 +56,17 @@
</xs:sequence> </xs:sequence>
</xs:complexType> </xs:complexType>
<xs:complexType name="named-query">
<xs:attribute name="name" type="xs:string" use="required" />
<xs:attribute name="query" type="xs:string" use="required" />
</xs:complexType>
<xs:complexType name="named-queries">
<xs:sequence>
<xs:element name="named-query" type="orm:named-query" minOccurs="1" maxOccurs="unbounded" />
</xs:sequence>
</xs:complexType>
<xs:complexType name="entity"> <xs:complexType name="entity">
<xs:sequence> <xs:sequence>
<xs:element name="indexes" type="orm:indexes" minOccurs="0"/> <xs:element name="indexes" type="orm:indexes" minOccurs="0"/>
@ -63,6 +74,7 @@
<xs:element name="discriminator-column" type="orm:discriminator-column" minOccurs="0"/> <xs:element name="discriminator-column" type="orm:discriminator-column" minOccurs="0"/>
<xs:element name="discriminator-map" type="orm:discriminator-map" minOccurs="0"/> <xs:element name="discriminator-map" type="orm:discriminator-map" minOccurs="0"/>
<xs:element name="lifecycle-callbacks" type="orm:lifecycle-callbacks" minOccurs="0" maxOccurs="1" /> <xs:element name="lifecycle-callbacks" type="orm:lifecycle-callbacks" minOccurs="0" maxOccurs="1" />
<xs:element name="named-queries" type="orm:named-queries" minOccurs="0" maxOccurs="1" />
<xs:element name="id" type="orm:id" minOccurs="0" maxOccurs="1" /> <xs:element name="id" type="orm:id" minOccurs="0" maxOccurs="1" />
<xs:element name="field" type="orm:field" minOccurs="0" maxOccurs="unbounded"/> <xs:element name="field" type="orm:field" minOccurs="0" maxOccurs="unbounded"/>
<xs:element name="one-to-one" type="orm:one-to-one" minOccurs="0" maxOccurs="unbounded"/> <xs:element name="one-to-one" type="orm:one-to-one" minOccurs="0" maxOccurs="unbounded"/>

View File

@ -78,6 +78,17 @@ class EntityRepository implements ObjectRepository
->from($this->_entityName, $alias); ->from($this->_entityName, $alias);
} }
/**
* Create a new Query instance based on a predefined metadata named query.
*
* @param string $queryName
* @return Query
*/
public function createNamedQuery($queryName)
{
return $this->_em->createQuery($this->_class->getNamedQuery($queryName));
}
/** /**
* Clears the repository, causing all managed entities to become detached. * Clears the repository, causing all managed entities to become detached.
*/ */

View File

@ -275,6 +275,7 @@ class ClassMetadata extends ClassMetadataInfo
{ {
// This metadata is always serialized/cached. // This metadata is always serialized/cached.
$serialized = array( $serialized = array(
'namedQueries',
'associationMappings', 'associationMappings',
'columnNames', //TODO: Not really needed. Can use fieldMappings[$fieldName]['columnName'] 'columnNames', //TODO: Not really needed. Can use fieldMappings[$fieldName]['columnName']
'fieldMappings', 'fieldMappings',

View File

@ -204,6 +204,13 @@ class ClassMetadataInfo implements ClassMetadata
*/ */
public $subClasses = array(); public $subClasses = array();
/**
* READ-ONLY: The named queries allowed to be called directly from Repository.
*
* @var array
*/
public $namedQueries = array();
/** /**
* READ-ONLY: The field names of all fields that are part of the identifier/primary key * READ-ONLY: The field names of all fields that are part of the identifier/primary key
* of the mapped entity class. * of the mapped entity class.
@ -655,6 +662,32 @@ class ClassMetadataInfo implements ClassMetadata
$this->fieldNames[$columnName] : $columnName; $this->fieldNames[$columnName] : $columnName;
} }
/**
* Gets the named query.
*
* @see ClassMetadataInfo::$namedQueries
* @throws MappingException
* @param string $queryName The query name
* @return string
*/
public function getNamedQuery($queryName)
{
if ( ! isset($this->namedQueries[$queryName])) {
throw MappingException::queryNotFound($this->name, $queryName);
}
return $this->namedQueries[$queryName];
}
/**
* Gets all named queries of the class.
*
* @return array
*/
public function getNamedQueries()
{
return $this->namedQueries;
}
/** /**
* Validates & completes the given field mapping. * Validates & completes the given field mapping.
* *
@ -1368,8 +1401,7 @@ class ClassMetadataInfo implements ClassMetadata
* Adds an association mapping without completing/validating it. * Adds an association mapping without completing/validating it.
* This is mainly used to add inherited association mappings to derived classes. * This is mainly used to add inherited association mappings to derived classes.
* *
* @param AssociationMapping $mapping * @param array $mapping
* @param string $owningClassName The name of the class that defined this mapping.
*/ */
public function addInheritedAssociationMapping(array $mapping/*, $owningClassName = null*/) public function addInheritedAssociationMapping(array $mapping/*, $owningClassName = null*/)
{ {
@ -1385,7 +1417,6 @@ class ClassMetadataInfo implements ClassMetadata
* This is mainly used to add inherited field mappings to derived classes. * This is mainly used to add inherited field mappings to derived classes.
* *
* @param array $mapping * @param array $mapping
* @todo Rename: addInheritedFieldMapping
*/ */
public function addInheritedFieldMapping(array $fieldMapping) public function addInheritedFieldMapping(array $fieldMapping)
{ {
@ -1394,6 +1425,22 @@ class ClassMetadataInfo implements ClassMetadata
$this->fieldNames[$fieldMapping['columnName']] = $fieldMapping['fieldName']; $this->fieldNames[$fieldMapping['columnName']] = $fieldMapping['fieldName'];
} }
/**
* INTERNAL:
* Adds a named query to this class.
*
* @throws MappingException
* @param array $queryMapping
*/
public function addNamedQuery(array $queryMapping)
{
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;
}
/** /**
* Adds a one-to-one mapping. * Adds a one-to-one mapping.
* *
@ -1584,6 +1631,17 @@ class ClassMetadataInfo implements ClassMetadata
} }
} }
/**
* Checks whether the class has a named query with the given query name.
*
* @param string $fieldName
* @return boolean
*/
public function hasNamedQuery($queryName)
{
return isset($this->namedQueries[$queryName]);
}
/** /**
* Checks whether the class has a mapped association with the given field name. * Checks whether the class has a mapped association with the given field name.
* *

View File

@ -165,6 +165,18 @@ class AnnotationDriver implements Driver
$metadata->setPrimaryTable($primaryTable); $metadata->setPrimaryTable($primaryTable);
} }
// Evaluate NamedQueries annotation
if (isset($classAnnotations['Doctrine\ORM\Mapping\NamedQueries'])) {
$namedQueriesAnnot = $classAnnotations['Doctrine\ORM\Mapping\NamedQueries'];
foreach ($namedQueriesAnnot->value as $namedQuery) {
$metadata->addNamedQuery(array(
'name' => $namedQuery->name,
'query' => $namedQuery->query
));
}
}
// Evaluate InheritanceType annotation // Evaluate InheritanceType annotation
if (isset($classAnnotations['Doctrine\ORM\Mapping\InheritanceType'])) { if (isset($classAnnotations['Doctrine\ORM\Mapping\InheritanceType'])) {
$inheritanceTypeAnnot = $classAnnotations['Doctrine\ORM\Mapping\InheritanceType']; $inheritanceTypeAnnot = $classAnnotations['Doctrine\ORM\Mapping\InheritanceType'];

View File

@ -127,6 +127,12 @@ final class SequenceGenerator extends Annotation {
final class ChangeTrackingPolicy extends Annotation {} final class ChangeTrackingPolicy extends Annotation {}
final class OrderBy extends Annotation {} final class OrderBy extends Annotation {}
final class NamedQueries extends Annotation {}
final class NamedQuery extends Annotation {
public $name;
public $query;
}
/* Annotations for lifecycle callbacks */ /* Annotations for lifecycle callbacks */
final class HasLifecycleCallbacks extends Annotation {} final class HasLifecycleCallbacks extends Annotation {}
final class PrePersist extends Annotation {} final class PrePersist extends Annotation {}

View File

@ -69,6 +69,16 @@ class XmlDriver extends AbstractFileDriver
$metadata->setPrimaryTable($table); $metadata->setPrimaryTable($table);
// Evaluate named queries
if (isset($xmlRoot['named-queries'])) {
foreach ($xmlRoot->{'named-queries'}->{'named-query'} as $namedQueryElement) {
$metadata->addNamedQuery(array(
'name' => (string)$namedQueryElement['name'],
'query' => (string)$namedQueryElement['query']
));
}
}
/* not implemented specially anyway. use table = schema.table /* not implemented specially anyway. use table = schema.table
if (isset($xmlRoot['schema'])) { if (isset($xmlRoot['schema'])) {
$metadata->table['schema'] = (string)$xmlRoot['schema']; $metadata->table['schema'] = (string)$xmlRoot['schema'];

View File

@ -62,6 +62,21 @@ class YamlDriver extends AbstractFileDriver
} }
$metadata->setPrimaryTable($table); $metadata->setPrimaryTable($table);
// Evaluate named queries
if (isset($element['namedQueries'])) {
foreach ($element['namedQueries'] as $name => $queryMapping) {
if (is_string($queryMapping)) {
$queryMapping = array('query' => $queryMapping);
}
if ( ! isset($queryMapping['name'])) {
$queryMapping['name'] = $name;
}
$metadata->addNamedQuery($queryMapping);
}
}
/* not implemented specially anyway. use table = schema.table /* not implemented specially anyway. use table = schema.table
if (isset($element['schema'])) { if (isset($element['schema'])) {
$metadata->table['schema'] = $element['schema']; $metadata->table['schema'] = $element['schema'];

View File

@ -73,6 +73,11 @@ class MappingException extends \Doctrine\ORM\ORMException
return new self("No mapping found for field '$fieldName' on class '$className'."); return new self("No mapping found for field '$fieldName' on class '$className'.");
} }
public static function queryNotFound($className, $queryName)
{
return new self("No query found named '$queryName' on class '$className'.");
}
public static function oneToManyRequiresMappedBy($fieldName) public static function oneToManyRequiresMappedBy($fieldName)
{ {
return new self("OneToMany mapping on field '$fieldName' requires the 'mappedBy' attribute."); return new self("OneToMany mapping on field '$fieldName' requires the 'mappedBy' attribute.");
@ -160,6 +165,10 @@ class MappingException extends \Doctrine\ORM\ORMException
return new self('Property "'.$fieldName.'" in "'.$entity.'" was already declared, but it must be declared only once'); return new self('Property "'.$fieldName.'" in "'.$entity.'" was already declared, but it must be declared only once');
} }
public static function duplicateQueryMapping($entity, $queryName) {
return new self('Query named "'.$queryName.'" in "'.$entity.'" was already declared, but it must be declared only once');
}
public static function singleIdNotAllowedOnCompositePrimaryKey($entity) { public static function singleIdNotAllowedOnCompositePrimaryKey($entity) {
return new self('Single id is not allowed on composite primary key in entity '.$entity); return new self('Single id is not allowed on composite primary key in entity '.$entity);
} }

View File

@ -7,6 +7,9 @@ use Doctrine\Common\Collections\ArrayCollection;
/** /**
* @Entity * @Entity
* @Table(name="cms_users") * @Table(name="cms_users")
* @NamedQueries({
* @NamedQuery(name="all", query="SELECT u FROM __CLASS__ u")
* })
*/ */
class CmsUser class CmsUser
{ {

View File

@ -288,5 +288,24 @@ class EntityRepositoryTest extends \Doctrine\Tests\OrmFunctionalTestCase
$this->assertType('Doctrine\Tests\Models\CMS\CmsAddress', $address); $this->assertType('Doctrine\Tests\Models\CMS\CmsAddress', $address);
$this->assertEquals($addressId, $address->id); $this->assertEquals($addressId, $address->id);
} }
public function testValidNamedQueryRetrieval()
{
$repos = $this->_em->getRepository('Doctrine\Tests\Models\CMS\CmsUser');
$query = $repos->createNamedQuery('all');
$this->assertType('Doctrine\ORM\Query', $query);
$this->assertEquals('SELECT u FROM Doctrine\Tests\Models\CMS\CmsUser u', $query->getDQL());
}
public function testInvalidNamedQueryRetrieval()
{
$repos = $this->_em->getRepository('Doctrine\Tests\Models\CMS\CmsUser');
$this->setExpectedException('Doctrine\ORM\Mapping\MappingException');
$repos->createNamedQuery('invalidNamedQuery');
}
} }

View File

@ -390,4 +390,60 @@ class ClassMetadataTest extends \Doctrine\Tests\OrmTestCase
$cm = new ClassMetadata('Doctrine\Tests\Models\CMS\CmsUser'); $cm = new ClassMetadata('Doctrine\Tests\Models\CMS\CmsUser');
$cm->mapField(array('fieldName' => '')); $cm->mapField(array('fieldName' => ''));
} }
public function testRetrievalOfNamedQueries()
{
$cm = new ClassMetadata('Doctrine\Tests\Models\CMS\CmsUser');
$this->assertEquals(0, count($cm->getNamedQueries()));
$cm->addNamedQuery(array(
'name' => 'userById',
'query' => 'SELECT u FROM __CLASS__ u WHERE u.id = ?1'
));
$this->assertEquals(1, count($cm->getNamedQueries()));
}
public function testExistanceOfNamedQuery()
{
$cm = new ClassMetadata('Doctrine\Tests\Models\CMS\CmsUser');
$cm->addNamedQuery(array(
'name' => 'all',
'query' => 'SELECT u FROM __CLASS__ u'
));
$this->assertTrue($cm->hasNamedQuery('all'));
$this->assertFalse($cm->hasNamedQuery('userById'));
}
public function testRetrieveOfNamedQuery()
{
$cm = new ClassMetadata('Doctrine\Tests\Models\CMS\CmsUser');
$cm->addNamedQuery(array(
'name' => 'userById',
'query' => 'SELECT u FROM __CLASS__ u WHERE u.id = ?1'
));
$this->assertEquals('SELECT u FROM Doctrine\Tests\Models\CMS\CmsUser u WHERE u.id = ?1', $cm->getNamedQuery('userById'));
}
public function testNamingCollisionNamedQueryShouldThrowException()
{
$cm = new ClassMetadata('Doctrine\Tests\Models\CMS\CmsUser');
$this->setExpectedException('Doctrine\ORM\Mapping\MappingException');
$cm->addNamedQuery(array(
'name' => 'userById',
'query' => 'SELECT u FROM __CLASS__ u WHERE u.id = ?1'
));
$cm->addNamedQuery(array(
'name' => 'userById',
'query' => 'SELECT u FROM __CLASS__ u WHERE u.id = ?1'
));
}
} }

View File

@ -10,6 +10,10 @@ $metadata->setChangeTrackingPolicy(ClassMetadataInfo::CHANGETRACKING_DEFERRED_IM
$metadata->addLifecycleCallback('doStuffOnPrePersist', 'prePersist'); $metadata->addLifecycleCallback('doStuffOnPrePersist', 'prePersist');
$metadata->addLifecycleCallback('doOtherStuffOnPrePersistToo', 'prePersist'); $metadata->addLifecycleCallback('doOtherStuffOnPrePersistToo', 'prePersist');
$metadata->addLifecycleCallback('doStuffOnPostPersist', 'postPersist'); $metadata->addLifecycleCallback('doStuffOnPostPersist', 'postPersist');
$metadata->addNamedQuery(array(
'name' => 'all',
'query' => 'SELECT u FROM __CLASS__ u'
));
$metadata->mapField(array( $metadata->mapField(array(
'id' => true, 'id' => true,
'fieldName' => 'id', 'fieldName' => 'id',

View File

@ -22,6 +22,10 @@
<lifecycle-callback type="postPersist" method="doStuffOnPostPersist"/> <lifecycle-callback type="postPersist" method="doStuffOnPostPersist"/>
</lifecycle-callbacks> </lifecycle-callbacks>
<named-queries>
<named-query name="all" query="SELECT u FROM __CLASS__ u"/>
</named-queries>
<id name="id" type="integer" column="id"> <id name="id" type="integer" column="id">
<generator strategy="AUTO"/> <generator strategy="AUTO"/>
<sequence-generator sequence-name="tablename_seq" allocation-size="100" initial-value="1" /> <sequence-generator sequence-name="tablename_seq" allocation-size="100" initial-value="1" />

View File

@ -1,6 +1,8 @@
Doctrine\Tests\ORM\Mapping\User: Doctrine\Tests\ORM\Mapping\User:
type: entity type: entity
table: cms_users table: cms_users
namedQueries:
all: SELECT u FROM __CLASS__ u
id: id:
id: id:
type: integer type: integer