diff --git a/doctrine-mapping.xsd b/doctrine-mapping.xsd index c212c8671..95fd62425 100644 --- a/doctrine-mapping.xsd +++ b/doctrine-mapping.xsd @@ -219,6 +219,7 @@ + @@ -230,6 +231,7 @@ + diff --git a/lib/Doctrine/ORM/Mapping/Driver/AnnotationDriver.php b/lib/Doctrine/ORM/Mapping/Driver/AnnotationDriver.php index b10be89f4..de2375ce6 100644 --- a/lib/Doctrine/ORM/Mapping/Driver/AnnotationDriver.php +++ b/lib/Doctrine/ORM/Mapping/Driver/AnnotationDriver.php @@ -302,6 +302,11 @@ class AnnotationDriver implements Driver $mapping['cascade'] = $oneToManyAnnot->cascade; $mapping['orphanRemoval'] = $oneToManyAnnot->orphanRemoval; $mapping['fetch'] = constant('Doctrine\ORM\Mapping\AssociationMapping::FETCH_' . $oneToManyAnnot->fetch); + + if ($orderByAnnot = $this->_reader->getPropertyAnnotation($property, 'Doctrine\ORM\Mapping\OrderBy')) { + $mapping['orderBy'] = $orderByAnnot->value; + } + $metadata->mapOneToMany($mapping); } else if ($manyToOneAnnot = $this->_reader->getPropertyAnnotation($property, 'Doctrine\ORM\Mapping\ManyToOne')) { $mapping['joinColumns'] = $joinColumns; @@ -348,6 +353,11 @@ class AnnotationDriver implements Driver $mapping['mappedBy'] = $manyToManyAnnot->mappedBy; $mapping['cascade'] = $manyToManyAnnot->cascade; $mapping['fetch'] = constant('Doctrine\ORM\Mapping\AssociationMapping::FETCH_' . $manyToManyAnnot->fetch); + + if ($orderByAnnot = $this->_reader->getPropertyAnnotation($property, 'Doctrine\ORM\Mapping\OrderBy')) { + $mapping['orderBy'] = $orderByAnnot->value; + } + $metadata->mapManyToMany($mapping); } } diff --git a/lib/Doctrine/ORM/Mapping/Driver/DoctrineAnnotations.php b/lib/Doctrine/ORM/Mapping/Driver/DoctrineAnnotations.php index 152a1719f..5ad0eb3f2 100644 --- a/lib/Doctrine/ORM/Mapping/Driver/DoctrineAnnotations.php +++ b/lib/Doctrine/ORM/Mapping/Driver/DoctrineAnnotations.php @@ -124,6 +124,8 @@ final class SequenceGenerator extends Annotation { } final class ChangeTrackingPolicy extends Annotation {} +final class OrderBy extends Annotation {} + /* Annotations for lifecycle callbacks */ final class HasLifecycleCallbacks extends Annotation {} final class PrePersist extends Annotation {} diff --git a/lib/Doctrine/ORM/Mapping/Driver/XmlDriver.php b/lib/Doctrine/ORM/Mapping/Driver/XmlDriver.php index 9e625a29c..f32ada9a0 100644 --- a/lib/Doctrine/ORM/Mapping/Driver/XmlDriver.php +++ b/lib/Doctrine/ORM/Mapping/Driver/XmlDriver.php @@ -266,6 +266,10 @@ class XmlDriver extends AbstractFileDriver if (isset($oneToManyElement->{'orphan-removal'})) { $mapping['orphanRemoval'] = (bool)$oneToManyElement->{'orphan-removal'}; } + + if (isset($oneToManyElement['order-by'])) { + $mapping['orderBy'] = (string)$oneToManyElement['order-by']; + } $metadata->mapOneToMany($mapping); } @@ -353,6 +357,10 @@ class XmlDriver extends AbstractFileDriver if (isset($manyToManyElement->{'orphan-removal'})) { $mapping['orphanRemoval'] = (bool)$manyToManyElement->{'orphan-removal'}; } + + if (isset($manyToManyElement['order-by'])) { + $mapping['orderBy'] = (string)$manyToManyElement['order-by']; + } $metadata->mapManyToMany($mapping); } diff --git a/lib/Doctrine/ORM/Mapping/Driver/YamlDriver.php b/lib/Doctrine/ORM/Mapping/Driver/YamlDriver.php index 36a21f87a..4e4196130 100644 --- a/lib/Doctrine/ORM/Mapping/Driver/YamlDriver.php +++ b/lib/Doctrine/ORM/Mapping/Driver/YamlDriver.php @@ -278,6 +278,10 @@ class YamlDriver extends AbstractFileDriver if (isset($oneToManyElement['cascade'])) { $mapping['cascade'] = $oneToManyElement['cascade']; } + + if (isset($oneToManyElement['orderBy'])) { + $mapping['orderBy'] = $oneToManyElement['orderBy']; + } $metadata->mapOneToMany($mapping); } @@ -366,6 +370,10 @@ class YamlDriver extends AbstractFileDriver $mapping['cascade'] = $manyToManyElement['cascade']; } + if (isset($manyToManyElement['orderBy'])) { + $mapping['orderBy'] = $manyToManyElement['orderBy']; + } + $metadata->mapManyToMany($mapping); } } diff --git a/lib/Doctrine/ORM/Mapping/ManyToManyMapping.php b/lib/Doctrine/ORM/Mapping/ManyToManyMapping.php index 3a576c1de..0fbb4df8a 100644 --- a/lib/Doctrine/ORM/Mapping/ManyToManyMapping.php +++ b/lib/Doctrine/ORM/Mapping/ManyToManyMapping.php @@ -59,6 +59,11 @@ class ManyToManyMapping extends AssociationMapping /** FUTURE: The key column mapping, if any. The key column holds the keys of the Collection. */ //public $keyColumn; + + /** + * Order this collection by the given SQL snippet. + */ + public $orderBy = null; /** * Initializes a new ManyToManyMapping. @@ -135,6 +140,10 @@ class ManyToManyMapping extends AssociationMapping $this->joinTableColumns[] = $inverseJoinColumn['name']; } } + + if (isset($mapping['orderBy'])) { + $this->orderBy = $mapping['orderBy']; + } } public function getJoinTableColumnNames() diff --git a/lib/Doctrine/ORM/Mapping/OneToManyMapping.php b/lib/Doctrine/ORM/Mapping/OneToManyMapping.php index e6bf21339..45a3cf75d 100644 --- a/lib/Doctrine/ORM/Mapping/OneToManyMapping.php +++ b/lib/Doctrine/ORM/Mapping/OneToManyMapping.php @@ -51,6 +51,11 @@ class OneToManyMapping extends AssociationMapping /** FUTURE: The key column mapping, if any. The key column holds the keys of the Collection. */ //public $keyColumn; + /** + * Order this collection by the given SQL snippet. + */ + public $orderBy = null; + /** * Initializes a new OneToManyMapping. * @@ -79,6 +84,10 @@ class OneToManyMapping extends AssociationMapping $this->orphanRemoval = isset($mapping['orphanRemoval']) ? (bool) $mapping['orphanRemoval'] : false; + + if (isset($mapping['orderBy'])) { + $this->orderBy = $mapping['orderBy']; + } } /** diff --git a/tests/Doctrine/Tests/ORM/Mapping/AbstractMappingDriverTest.php b/tests/Doctrine/Tests/ORM/Mapping/AbstractMappingDriverTest.php index 3cc253d9f..b7c9b108f 100644 --- a/tests/Doctrine/Tests/ORM/Mapping/AbstractMappingDriverTest.php +++ b/tests/Doctrine/Tests/ORM/Mapping/AbstractMappingDriverTest.php @@ -18,9 +18,6 @@ abstract class AbstractMappingDriverTest extends \Doctrine\Tests\OrmTestCase $mappingDriver = $this->_loadDriver(); $class = new ClassMetadata($className); - - $this->assertFalse($mappingDriver->isTransient($className)); - $mappingDriver->loadMetadataForClass($className, $class); return $class; @@ -64,7 +61,7 @@ abstract class AbstractMappingDriverTest extends \Doctrine\Tests\OrmTestCase public function testIdentifier($class) { $this->assertEquals(array('id'), $class->identifier); - $this->assertEquals(ClassMetadata::GENERATOR_TYPE_AUTO, $class->getIdGeneratorType()); + $this->assertEquals(ClassMetadata::GENERATOR_TYPE_AUTO, $class->getIdGeneratorType(), "ID-Generator is not ClassMetadata::GENERATOR_TYPE_AUTO"); return $class; } @@ -116,6 +113,9 @@ abstract class AbstractMappingDriverTest extends \Doctrine\Tests\OrmTestCase $this->assertFalse($class->associationMappings['phonenumbers']->isCascadeDetach); $this->assertFalse($class->associationMappings['phonenumbers']->isCascadeMerge); + // Test Order By + $this->assertEquals('%alias%.number ASC', $class->associationMappings['phonenumbers']->orderBy); + return $class; } @@ -135,6 +135,8 @@ abstract class AbstractMappingDriverTest extends \Doctrine\Tests\OrmTestCase $this->assertTrue($class->associationMappings['groups']->isCascadeDetach); $this->assertTrue($class->associationMappings['groups']->isCascadeMerge); + $this->assertNull($class->associationMappings['groups']->orderBy); + return $class; } @@ -177,20 +179,57 @@ abstract class AbstractMappingDriverTest extends \Doctrine\Tests\OrmTestCase } } -class User { - private $id; - private $name; - private $email; - private $address; - private $phonenumbers; - private $groups; +/** + * @Entity + * @HasLifecycleCallbacks + * @Table(name="cms_users") + */ +class User +{ + /** @Id @Column(type="int") @generatedValue(strategy="AUTO") */ + public $id; - // ... rest of code omitted, irrelevant for the mapping tests + /** + * @Column(length=50, nullable=true, unique=true) + */ + public $name; + /** + * @Column(name="user_email", columnDefinition="CHAR(32) NOT NULL") + */ + public $email; + + /** + * @OneToOne(targetEntity="Address", cascade={"remove"}) + */ + public $address; + + /** + * + * @OneToMany(targetEntity="Phonenumber", mappedBy="user", cascade={"persist"}) + * @OrderBy("%alias%.number ASC") + */ + public $phonenumbers; + + /** + * @ManyToMany(targetEntity="Group", cascade={"all"}) + * @JoinTable(name="cms_user_groups", + * joinColumns={@JoinColumn(name="user_id", referencedColumnName="id", nullable=false, unique=false)}, + * inverseJoinColumns={@JoinColumn(name="group_id", referencedColumnName="id", columnDefinition="INT NULL")} + * ) + */ + public $groups; + + /** + * @PrePersist + */ public function doStuffOnPrePersist() { } + /** + * @PostPersist + */ public function doStuffOnPostPersist() { diff --git a/tests/Doctrine/Tests/ORM/Mapping/AnnotationDriverTest.php b/tests/Doctrine/Tests/ORM/Mapping/AnnotationDriverTest.php index b99c72986..5c3341619 100644 --- a/tests/Doctrine/Tests/ORM/Mapping/AnnotationDriverTest.php +++ b/tests/Doctrine/Tests/ORM/Mapping/AnnotationDriverTest.php @@ -7,7 +7,7 @@ use Doctrine\ORM\Events; require_once __DIR__ . '/../../TestInit.php'; -class AnnotationDriverTest extends \Doctrine\Tests\OrmTestCase +class AnnotationDriverTest extends AbstractMappingDriverTest { /** * @group DDC-268 @@ -27,7 +27,7 @@ class AnnotationDriverTest extends \Doctrine\Tests\OrmTestCase */ public function testColumnWithMissingTypeDefaultsToString() { - $cm = new ClassMetadata('Doctrine\Tests\ORM\Mapping\InvalidColumn'); + $cm = new ClassMetadata('Doctrine\Tests\ORM\Mapping\ColumnWithoutType'); $reader = new \Doctrine\Common\Annotations\AnnotationReader(new \Doctrine\Common\Cache\ArrayCache()); $reader->setDefaultAnnotationNamespace('Doctrine\ORM\Mapping\\'); $annotationDriver = new \Doctrine\ORM\Mapping\Driver\AnnotationDriver($reader); @@ -35,14 +35,22 @@ class AnnotationDriverTest extends \Doctrine\Tests\OrmTestCase $annotationDriver->loadMetadataForClass('Doctrine\Tests\ORM\Mapping\InvalidColumn', $cm); $this->assertEquals('string', $cm->fieldMappings['id']['type']); } + + + protected function _loadDriver() + { + $cache = new \Doctrine\Common\Cache\ArrayCache(); + $reader = new \Doctrine\Common\Annotations\AnnotationReader($cache); + $reader->setDefaultAnnotationNamespace('Doctrine\ORM\Mapping\\'); + return new \Doctrine\ORM\Mapping\Driver\AnnotationDriver($reader); + } } /** * @Entity */ -class InvalidColumn +class ColumnWithoutType { /** @Id @Column */ public $id; } - diff --git a/tests/Doctrine/Tests/ORM/Mapping/xml/Doctrine.Tests.ORM.Mapping.User.dcm.xml b/tests/Doctrine/Tests/ORM/Mapping/xml/Doctrine.Tests.ORM.Mapping.User.dcm.xml index 193227faf..1a344790e 100644 --- a/tests/Doctrine/Tests/ORM/Mapping/xml/Doctrine.Tests.ORM.Mapping.User.dcm.xml +++ b/tests/Doctrine/Tests/ORM/Mapping/xml/Doctrine.Tests.ORM.Mapping.User.dcm.xml @@ -24,7 +24,7 @@ - + diff --git a/tests/Doctrine/Tests/ORM/Mapping/yaml/Doctrine.Tests.ORM.Mapping.User.dcm.yml b/tests/Doctrine/Tests/ORM/Mapping/yaml/Doctrine.Tests.ORM.Mapping.User.dcm.yml index 846e071cf..450dfd7e5 100644 --- a/tests/Doctrine/Tests/ORM/Mapping/yaml/Doctrine.Tests.ORM.Mapping.User.dcm.yml +++ b/tests/Doctrine/Tests/ORM/Mapping/yaml/Doctrine.Tests.ORM.Mapping.User.dcm.yml @@ -27,6 +27,7 @@ Doctrine\Tests\ORM\Mapping\User: phonenumbers: targetEntity: Phonenumber mappedBy: user + orderBy: %alias%.number ASC cascade: [ persist ] manyToMany: groups: