[2.0] DDC-336 - Support specification of an OrderBy SQL snippet in OneToMany and ManyToMany Associations in Annotation, XML and YAML Drivers
This commit is contained in:
parent
e83f1517ad
commit
ab3a6cc16e
@ -219,6 +219,7 @@
|
|||||||
<xs:attribute name="field" type="xs:NMTOKEN" use="required" />
|
<xs:attribute name="field" type="xs:NMTOKEN" use="required" />
|
||||||
<xs:attribute name="mapped-by" type="xs:NMTOKEN" />
|
<xs:attribute name="mapped-by" type="xs:NMTOKEN" />
|
||||||
<xs:attribute name="fetch" type="orm:fetch-type" default="LAZY" />
|
<xs:attribute name="fetch" type="orm:fetch-type" default="LAZY" />
|
||||||
|
<xs:attribute name="order-by" type="xs:string" />
|
||||||
</xs:complexType>
|
</xs:complexType>
|
||||||
|
|
||||||
<xs:complexType name="one-to-many">
|
<xs:complexType name="one-to-many">
|
||||||
@ -230,6 +231,7 @@
|
|||||||
<xs:attribute name="field" type="xs:NMTOKEN" use="required" />
|
<xs:attribute name="field" type="xs:NMTOKEN" use="required" />
|
||||||
<xs:attribute name="orphan-removal" type="xs:boolean" default="false" />
|
<xs:attribute name="orphan-removal" type="xs:boolean" default="false" />
|
||||||
<xs:attribute name="fetch" type="orm:fetch-type" default="LAZY" />
|
<xs:attribute name="fetch" type="orm:fetch-type" default="LAZY" />
|
||||||
|
<xs:attribute name="order-by" type="xs:string" />
|
||||||
</xs:complexType>
|
</xs:complexType>
|
||||||
|
|
||||||
<xs:complexType name="many-to-one">
|
<xs:complexType name="many-to-one">
|
||||||
|
@ -302,6 +302,11 @@ class AnnotationDriver implements Driver
|
|||||||
$mapping['cascade'] = $oneToManyAnnot->cascade;
|
$mapping['cascade'] = $oneToManyAnnot->cascade;
|
||||||
$mapping['orphanRemoval'] = $oneToManyAnnot->orphanRemoval;
|
$mapping['orphanRemoval'] = $oneToManyAnnot->orphanRemoval;
|
||||||
$mapping['fetch'] = constant('Doctrine\ORM\Mapping\AssociationMapping::FETCH_' . $oneToManyAnnot->fetch);
|
$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);
|
$metadata->mapOneToMany($mapping);
|
||||||
} else if ($manyToOneAnnot = $this->_reader->getPropertyAnnotation($property, 'Doctrine\ORM\Mapping\ManyToOne')) {
|
} else if ($manyToOneAnnot = $this->_reader->getPropertyAnnotation($property, 'Doctrine\ORM\Mapping\ManyToOne')) {
|
||||||
$mapping['joinColumns'] = $joinColumns;
|
$mapping['joinColumns'] = $joinColumns;
|
||||||
@ -348,6 +353,11 @@ class AnnotationDriver implements Driver
|
|||||||
$mapping['mappedBy'] = $manyToManyAnnot->mappedBy;
|
$mapping['mappedBy'] = $manyToManyAnnot->mappedBy;
|
||||||
$mapping['cascade'] = $manyToManyAnnot->cascade;
|
$mapping['cascade'] = $manyToManyAnnot->cascade;
|
||||||
$mapping['fetch'] = constant('Doctrine\ORM\Mapping\AssociationMapping::FETCH_' . $manyToManyAnnot->fetch);
|
$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);
|
$metadata->mapManyToMany($mapping);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -124,6 +124,8 @@ final class SequenceGenerator extends Annotation {
|
|||||||
}
|
}
|
||||||
final class ChangeTrackingPolicy extends Annotation {}
|
final class ChangeTrackingPolicy extends Annotation {}
|
||||||
|
|
||||||
|
final class OrderBy extends Annotation {}
|
||||||
|
|
||||||
/* 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 {}
|
||||||
|
@ -266,6 +266,10 @@ class XmlDriver extends AbstractFileDriver
|
|||||||
if (isset($oneToManyElement->{'orphan-removal'})) {
|
if (isset($oneToManyElement->{'orphan-removal'})) {
|
||||||
$mapping['orphanRemoval'] = (bool)$oneToManyElement->{'orphan-removal'};
|
$mapping['orphanRemoval'] = (bool)$oneToManyElement->{'orphan-removal'};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (isset($oneToManyElement['order-by'])) {
|
||||||
|
$mapping['orderBy'] = (string)$oneToManyElement['order-by'];
|
||||||
|
}
|
||||||
|
|
||||||
$metadata->mapOneToMany($mapping);
|
$metadata->mapOneToMany($mapping);
|
||||||
}
|
}
|
||||||
@ -353,6 +357,10 @@ class XmlDriver extends AbstractFileDriver
|
|||||||
if (isset($manyToManyElement->{'orphan-removal'})) {
|
if (isset($manyToManyElement->{'orphan-removal'})) {
|
||||||
$mapping['orphanRemoval'] = (bool)$manyToManyElement->{'orphan-removal'};
|
$mapping['orphanRemoval'] = (bool)$manyToManyElement->{'orphan-removal'};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (isset($manyToManyElement['order-by'])) {
|
||||||
|
$mapping['orderBy'] = (string)$manyToManyElement['order-by'];
|
||||||
|
}
|
||||||
|
|
||||||
$metadata->mapManyToMany($mapping);
|
$metadata->mapManyToMany($mapping);
|
||||||
}
|
}
|
||||||
|
@ -278,6 +278,10 @@ class YamlDriver extends AbstractFileDriver
|
|||||||
if (isset($oneToManyElement['cascade'])) {
|
if (isset($oneToManyElement['cascade'])) {
|
||||||
$mapping['cascade'] = $oneToManyElement['cascade'];
|
$mapping['cascade'] = $oneToManyElement['cascade'];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (isset($oneToManyElement['orderBy'])) {
|
||||||
|
$mapping['orderBy'] = $oneToManyElement['orderBy'];
|
||||||
|
}
|
||||||
|
|
||||||
$metadata->mapOneToMany($mapping);
|
$metadata->mapOneToMany($mapping);
|
||||||
}
|
}
|
||||||
@ -366,6 +370,10 @@ class YamlDriver extends AbstractFileDriver
|
|||||||
$mapping['cascade'] = $manyToManyElement['cascade'];
|
$mapping['cascade'] = $manyToManyElement['cascade'];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (isset($manyToManyElement['orderBy'])) {
|
||||||
|
$mapping['orderBy'] = $manyToManyElement['orderBy'];
|
||||||
|
}
|
||||||
|
|
||||||
$metadata->mapManyToMany($mapping);
|
$metadata->mapManyToMany($mapping);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -59,6 +59,11 @@ class ManyToManyMapping extends AssociationMapping
|
|||||||
|
|
||||||
/** FUTURE: The key column mapping, if any. The key column holds the keys of the Collection. */
|
/** FUTURE: The key column mapping, if any. The key column holds the keys of the Collection. */
|
||||||
//public $keyColumn;
|
//public $keyColumn;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Order this collection by the given SQL snippet.
|
||||||
|
*/
|
||||||
|
public $orderBy = null;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Initializes a new ManyToManyMapping.
|
* Initializes a new ManyToManyMapping.
|
||||||
@ -135,6 +140,10 @@ class ManyToManyMapping extends AssociationMapping
|
|||||||
$this->joinTableColumns[] = $inverseJoinColumn['name'];
|
$this->joinTableColumns[] = $inverseJoinColumn['name'];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (isset($mapping['orderBy'])) {
|
||||||
|
$this->orderBy = $mapping['orderBy'];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getJoinTableColumnNames()
|
public function getJoinTableColumnNames()
|
||||||
|
@ -51,6 +51,11 @@ class OneToManyMapping extends AssociationMapping
|
|||||||
/** FUTURE: The key column mapping, if any. The key column holds the keys of the Collection. */
|
/** FUTURE: The key column mapping, if any. The key column holds the keys of the Collection. */
|
||||||
//public $keyColumn;
|
//public $keyColumn;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Order this collection by the given SQL snippet.
|
||||||
|
*/
|
||||||
|
public $orderBy = null;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Initializes a new OneToManyMapping.
|
* Initializes a new OneToManyMapping.
|
||||||
*
|
*
|
||||||
@ -79,6 +84,10 @@ class OneToManyMapping extends AssociationMapping
|
|||||||
|
|
||||||
$this->orphanRemoval = isset($mapping['orphanRemoval']) ?
|
$this->orphanRemoval = isset($mapping['orphanRemoval']) ?
|
||||||
(bool) $mapping['orphanRemoval'] : false;
|
(bool) $mapping['orphanRemoval'] : false;
|
||||||
|
|
||||||
|
if (isset($mapping['orderBy'])) {
|
||||||
|
$this->orderBy = $mapping['orderBy'];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -18,9 +18,6 @@ abstract class AbstractMappingDriverTest extends \Doctrine\Tests\OrmTestCase
|
|||||||
$mappingDriver = $this->_loadDriver();
|
$mappingDriver = $this->_loadDriver();
|
||||||
|
|
||||||
$class = new ClassMetadata($className);
|
$class = new ClassMetadata($className);
|
||||||
|
|
||||||
$this->assertFalse($mappingDriver->isTransient($className));
|
|
||||||
|
|
||||||
$mappingDriver->loadMetadataForClass($className, $class);
|
$mappingDriver->loadMetadataForClass($className, $class);
|
||||||
|
|
||||||
return $class;
|
return $class;
|
||||||
@ -64,7 +61,7 @@ abstract class AbstractMappingDriverTest extends \Doctrine\Tests\OrmTestCase
|
|||||||
public function testIdentifier($class)
|
public function testIdentifier($class)
|
||||||
{
|
{
|
||||||
$this->assertEquals(array('id'), $class->identifier);
|
$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;
|
return $class;
|
||||||
}
|
}
|
||||||
@ -116,6 +113,9 @@ abstract class AbstractMappingDriverTest extends \Doctrine\Tests\OrmTestCase
|
|||||||
$this->assertFalse($class->associationMappings['phonenumbers']->isCascadeDetach);
|
$this->assertFalse($class->associationMappings['phonenumbers']->isCascadeDetach);
|
||||||
$this->assertFalse($class->associationMappings['phonenumbers']->isCascadeMerge);
|
$this->assertFalse($class->associationMappings['phonenumbers']->isCascadeMerge);
|
||||||
|
|
||||||
|
// Test Order By
|
||||||
|
$this->assertEquals('%alias%.number ASC', $class->associationMappings['phonenumbers']->orderBy);
|
||||||
|
|
||||||
return $class;
|
return $class;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -135,6 +135,8 @@ abstract class AbstractMappingDriverTest extends \Doctrine\Tests\OrmTestCase
|
|||||||
$this->assertTrue($class->associationMappings['groups']->isCascadeDetach);
|
$this->assertTrue($class->associationMappings['groups']->isCascadeDetach);
|
||||||
$this->assertTrue($class->associationMappings['groups']->isCascadeMerge);
|
$this->assertTrue($class->associationMappings['groups']->isCascadeMerge);
|
||||||
|
|
||||||
|
$this->assertNull($class->associationMappings['groups']->orderBy);
|
||||||
|
|
||||||
return $class;
|
return $class;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -177,20 +179,57 @@ abstract class AbstractMappingDriverTest extends \Doctrine\Tests\OrmTestCase
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class User {
|
/**
|
||||||
private $id;
|
* @Entity
|
||||||
private $name;
|
* @HasLifecycleCallbacks
|
||||||
private $email;
|
* @Table(name="cms_users")
|
||||||
private $address;
|
*/
|
||||||
private $phonenumbers;
|
class User
|
||||||
private $groups;
|
{
|
||||||
|
/** @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()
|
public function doStuffOnPrePersist()
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @PostPersist
|
||||||
|
*/
|
||||||
public function doStuffOnPostPersist()
|
public function doStuffOnPostPersist()
|
||||||
{
|
{
|
||||||
|
|
||||||
|
@ -7,7 +7,7 @@ use Doctrine\ORM\Events;
|
|||||||
|
|
||||||
require_once __DIR__ . '/../../TestInit.php';
|
require_once __DIR__ . '/../../TestInit.php';
|
||||||
|
|
||||||
class AnnotationDriverTest extends \Doctrine\Tests\OrmTestCase
|
class AnnotationDriverTest extends AbstractMappingDriverTest
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* @group DDC-268
|
* @group DDC-268
|
||||||
@ -27,7 +27,7 @@ class AnnotationDriverTest extends \Doctrine\Tests\OrmTestCase
|
|||||||
*/
|
*/
|
||||||
public function testColumnWithMissingTypeDefaultsToString()
|
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 = new \Doctrine\Common\Annotations\AnnotationReader(new \Doctrine\Common\Cache\ArrayCache());
|
||||||
$reader->setDefaultAnnotationNamespace('Doctrine\ORM\Mapping\\');
|
$reader->setDefaultAnnotationNamespace('Doctrine\ORM\Mapping\\');
|
||||||
$annotationDriver = new \Doctrine\ORM\Mapping\Driver\AnnotationDriver($reader);
|
$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);
|
$annotationDriver->loadMetadataForClass('Doctrine\Tests\ORM\Mapping\InvalidColumn', $cm);
|
||||||
$this->assertEquals('string', $cm->fieldMappings['id']['type']);
|
$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
|
* @Entity
|
||||||
*/
|
*/
|
||||||
class InvalidColumn
|
class ColumnWithoutType
|
||||||
{
|
{
|
||||||
/** @Id @Column */
|
/** @Id @Column */
|
||||||
public $id;
|
public $id;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -24,7 +24,7 @@
|
|||||||
<join-column name="address_id" referenced-column-name="id"/>
|
<join-column name="address_id" referenced-column-name="id"/>
|
||||||
</one-to-one>
|
</one-to-one>
|
||||||
|
|
||||||
<one-to-many field="phonenumbers" target-entity="Phonenumber" mapped-by="user">
|
<one-to-many field="phonenumbers" target-entity="Phonenumber" mapped-by="user" order-by="%alias%.number ASC">
|
||||||
<cascade>
|
<cascade>
|
||||||
<cascade-persist/>
|
<cascade-persist/>
|
||||||
</cascade>
|
</cascade>
|
||||||
|
@ -27,6 +27,7 @@ Doctrine\Tests\ORM\Mapping\User:
|
|||||||
phonenumbers:
|
phonenumbers:
|
||||||
targetEntity: Phonenumber
|
targetEntity: Phonenumber
|
||||||
mappedBy: user
|
mappedBy: user
|
||||||
|
orderBy: %alias%.number ASC
|
||||||
cascade: [ persist ]
|
cascade: [ persist ]
|
||||||
manyToMany:
|
manyToMany:
|
||||||
groups:
|
groups:
|
||||||
|
Loading…
x
Reference in New Issue
Block a user