1
0
mirror of synced 2025-01-17 22:11:41 +03:00

added support for @AssociationOverride

This commit is contained in:
Fabio B. Silva 2011-12-14 16:28:01 -02:00
parent d5d47222c1
commit 30fdf8dd1b
11 changed files with 704 additions and 37 deletions

View File

@ -0,0 +1,56 @@
<?php
/*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* This software consists of voluntary contributions made by many individuals
* and is licensed under the LGPL. For more information, see
* <http://www.doctrine-project.org>.
*/
namespace Doctrine\ORM\Mapping;
/**
* This annotation is used to override association mapping of property for an entity relationship.
*
* @author Fabio B. Silva <fabio.bat.silva@gmail.com>
* @since 2.2
*
* @Annotation
* @Target({"PROPERTY","ANNOTATION"})
*/
final class AssociationOverride implements Annotation
{
/**
* The name of the relationship property whose mapping is being overridden
*
* @var string
*/
public $name;
/**
* The join column that is being mapped to the persistent attribute.
*
* @var array<\Doctrine\ORM\Mapping\JoinColumn>
*/
public $joinColumns;
/**
* The join table that maps the relationship.
*
* @var \Doctrine\ORM\Mapping\JoinTable
*/
public $joinTable;
}

View File

@ -0,0 +1,41 @@
<?php
/*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* This software consists of voluntary contributions made by many individuals
* and is licensed under the LGPL. For more information, see
* <http://www.doctrine-project.org>.
*/
namespace Doctrine\ORM\Mapping;
/**
* This annotation is used to override association mappings of relationship properties.
*
* @author Fabio B. Silva <fabio.bat.silva@gmail.com>
* @since 2.2
*
* @Annotation
* @Target("CLASS")
*/
final class AssociationOverrides implements Annotation
{
/**
* Mapping overrides of relationship properties
*
* @var array<\Doctrine\ORM\Mapping\AssociationOverride>
*/
public $value;
}

View File

@ -23,7 +23,8 @@ use Doctrine\Common\Cache\ArrayCache,
Doctrine\Common\Annotations\AnnotationReader,
Doctrine\Common\Annotations\AnnotationRegistry,
Doctrine\ORM\Mapping\ClassMetadataInfo,
Doctrine\ORM\Mapping\MappingException;
Doctrine\ORM\Mapping\MappingException,
Doctrine\ORM\Mapping\JoinColumn;
/**
* The AnnotationDriver reads the mapping metadata from docblock annotations.
@ -275,6 +276,43 @@ class AnnotationDriver implements Driver
}
}
$associationOverrides = array();
// Evaluate AssociationOverrides annotation
if (isset($classAnnotations['Doctrine\ORM\Mapping\AssociationOverrides'])) {
$associationOverridesAnnot = $classAnnotations['Doctrine\ORM\Mapping\AssociationOverrides'];
foreach ($associationOverridesAnnot->value as $associationOverride) {
// Check for JoinColummn/JoinColumns annotations
if ($associationOverride->joinColumns) {
$joinColumns = array();
foreach ($associationOverride->joinColumns as $joinColumn) {
$joinColumns[] = $this->joinColumnToArray($joinColumn);
}
$associationOverrides[$associationOverride->name]['joinColumns'] = $joinColumns;
}
// Check for JoinTable annotations
if ($associationOverride->joinTable) {
$joinTable = null;
$joinTableAnnot = $associationOverride->joinTable;
$joinTable = array(
'name' => $joinTableAnnot->name,
'schema' => $joinTableAnnot->schema
);
foreach ($joinTableAnnot->joinColumns as $joinColumn) {
$joinTable['joinColumns'][] = $this->joinColumnToArray($joinColumn);
}
foreach ($joinTableAnnot->inverseJoinColumns as $joinColumn) {
$joinTable['inverseJoinColumns'][] = $this->joinColumnToArray($joinColumn);
}
$associationOverrides[$associationOverride->name]['joinTable'] = $joinTable;
}
}
}
// Evaluate InheritanceType annotation
if (isset($classAnnotations['Doctrine\ORM\Mapping\InheritanceType'])) {
$inheritanceTypeAnnot = $classAnnotations['Doctrine\ORM\Mapping\InheritanceType'];
@ -325,25 +363,13 @@ class AnnotationDriver implements Driver
// Check for JoinColummn/JoinColumns annotations
$joinColumns = array();
if ($joinColumnAnnot = $this->_reader->getPropertyAnnotation($property, 'Doctrine\ORM\Mapping\JoinColumn')) {
$joinColumns[] = array(
'name' => $joinColumnAnnot->name,
'referencedColumnName' => $joinColumnAnnot->referencedColumnName,
'unique' => $joinColumnAnnot->unique,
'nullable' => $joinColumnAnnot->nullable,
'onDelete' => $joinColumnAnnot->onDelete,
'columnDefinition' => $joinColumnAnnot->columnDefinition,
);
if (isset($associationOverrides[$property->name]['joinColumns'])) {
$joinColumns = $associationOverrides[$property->name]['joinColumns'];
} else if ($joinColumnAnnot = $this->_reader->getPropertyAnnotation($property, 'Doctrine\ORM\Mapping\JoinColumn')) {
$joinColumns[] = $this->joinColumnToArray($joinColumnAnnot);
} else if ($joinColumnsAnnot = $this->_reader->getPropertyAnnotation($property, 'Doctrine\ORM\Mapping\JoinColumns')) {
foreach ($joinColumnsAnnot->value as $joinColumn) {
$joinColumns[] = array(
'name' => $joinColumn->name,
'referencedColumnName' => $joinColumn->referencedColumnName,
'unique' => $joinColumn->unique,
'nullable' => $joinColumn->nullable,
'onDelete' => $joinColumn->onDelete,
'columnDefinition' => $joinColumn->columnDefinition,
);
$joinColumns[] = $this->joinColumnToArray($joinColumn);
}
}
@ -440,32 +466,20 @@ class AnnotationDriver implements Driver
} else if ($manyToManyAnnot = $this->_reader->getPropertyAnnotation($property, 'Doctrine\ORM\Mapping\ManyToMany')) {
$joinTable = array();
if ($joinTableAnnot = $this->_reader->getPropertyAnnotation($property, 'Doctrine\ORM\Mapping\JoinTable')) {
if (isset($associationOverrides[$property->name]['joinTable'])) {
$joinTable = $associationOverrides[$property->name]['joinTable'];
} else if ($joinTableAnnot = $this->_reader->getPropertyAnnotation($property, 'Doctrine\ORM\Mapping\JoinTable')) {
$joinTable = array(
'name' => $joinTableAnnot->name,
'schema' => $joinTableAnnot->schema
);
foreach ($joinTableAnnot->joinColumns as $joinColumn) {
$joinTable['joinColumns'][] = array(
'name' => $joinColumn->name,
'referencedColumnName' => $joinColumn->referencedColumnName,
'unique' => $joinColumn->unique,
'nullable' => $joinColumn->nullable,
'onDelete' => $joinColumn->onDelete,
'columnDefinition' => $joinColumn->columnDefinition,
);
$joinTable['joinColumns'][] = $this->joinColumnToArray($joinColumn);
}
foreach ($joinTableAnnot->inverseJoinColumns as $joinColumn) {
$joinTable['inverseJoinColumns'][] = array(
'name' => $joinColumn->name,
'referencedColumnName' => $joinColumn->referencedColumnName,
'unique' => $joinColumn->unique,
'nullable' => $joinColumn->nullable,
'onDelete' => $joinColumn->onDelete,
'columnDefinition' => $joinColumn->columnDefinition,
);
$joinTable['inverseJoinColumns'][] = $this->joinColumnToArray($joinColumn);
}
}
@ -635,6 +649,25 @@ class AnnotationDriver implements Driver
return constant('Doctrine\ORM\Mapping\ClassMetadata::FETCH_' . $fetchMode);
}
/**
* Parse the given JoinColumn as array
*
* @param JoinColumn $joinColumn
* @return array
*/
private function joinColumnToArray(JoinColumn $joinColumn)
{
return array(
'name' => $joinColumn->name,
'unique' => $joinColumn->unique,
'nullable' => $joinColumn->nullable,
'onDelete' => $joinColumn->onDelete,
'columnDefinition' => $joinColumn->columnDefinition,
'referencedColumnName' => $joinColumn->referencedColumnName,
);
}
/**
* Factory method for the Annotation Driver
*

View File

@ -59,4 +59,6 @@ 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';
require_once __DIR__.'/../SqlResultSetMappings.php';
require_once __DIR__.'/../AssociationOverride.php';
require_once __DIR__.'/../AssociationOverrides.php';

View File

@ -21,7 +21,7 @@ namespace Doctrine\ORM\Mapping;
/**
* @Annotation
* @Target("PROPERTY")
* @Target({"PROPERTY","ANNOTATION"})
*/
final class JoinTable implements Annotation
{

View File

@ -0,0 +1,123 @@
<?php
namespace Doctrine\Tests\Models\DDC964;
/**
* @Entity
*/
class DDC964Address
{
/**
* @GeneratedValue
* @Id @Column(type="integer")
*/
private $id;
/**
* @Column
*/
private $country;
/**
* @Column
*/
private $zip;
/**
* @Column
*/
private $city;
/**
* @Column
*/
private $street;
/**
* @param string $zip
* @param string $country
* @param string $city
* @param string $street
*/
public function __construct($zip = null, $country = null, $city = null, $street = null)
{
$this->zip = $zip;
$this->country = $country;
$this->city = $city;
$this->street = $street;
}
/**
* @return integer
*/
public function getId()
{
return $this->id;
}
/**
* @return string
*/
public function getCountry()
{
return $this->country;
}
/**
* @param string $country
*/
public function setCountry($country)
{
$this->country = $country;
}
/**
* @return string
*/
public function getZip()
{
return $this->zip;
}
/**
* @param string $zip
*/
public function setZip($zip)
{
$this->zip = $zip;
}
/**
* @return string
*/
public function getCity()
{
return $this->city;
}
/**
* @param string $city
*/
public function setCity($city)
{
$this->city = $city;
}
/**
* @return string
*/
public function getStreet()
{
return $this->street;
}
/**
* @param string $street
*/
public function setStreet($street)
{
$this->street = $street;
}
}

View File

@ -0,0 +1,27 @@
<?php
namespace Doctrine\Tests\Models\DDC964;
use Doctrine\Common\Collections\ArrayCollection;
/**
* @Entity
* @AssociationOverrides({
* @AssociationOverride(name="groups",
* joinTable=@JoinTable(
* name="ddc964_users_admingroups",
* joinColumns=@JoinColumn(name="adminuser_id"),
* inverseJoinColumns=@JoinColumn(name="admingroup_id")
* )
* ),
* @AssociationOverride(name="address",
* joinColumns=@JoinColumn(
* name="adminaddress_id", referencedColumnName="id"
* )
* )
* })
*/
class DDC964Admin extends DDC964User
{
}

View File

@ -0,0 +1,70 @@
<?php
namespace Doctrine\Tests\Models\DDC964;
use Doctrine\Common\Collections\ArrayCollection;
/**
* @Entity
*/
class DDC964Group
{
/**
* @GeneratedValue
* @Id @Column(type="integer")
*/
private $id;
/**
* @Column
*/
private $name;
/**
* @ArrayCollection
*
* @ManyToMany(targetEntity="DDC964User", mappedBy="groups")
*/
private $users;
public function __construct($name = null)
{
$this->name = $name;
$this->users = new ArrayCollection();
}
/**
* @param string $name
*/
public function setName($name)
{
$this->name = $name;
}
/**
* @return string
*/
public function getName()
{
return $this->name;
}
/**
* @param DDC964User $user
*/
public function addUser(DDC964User $user)
{
$this->users[] = $user;
}
/**
* @return ArrayCollection
*/
public function getUsers()
{
return $this->users;
}
}

View File

@ -0,0 +1,14 @@
<?php
namespace Doctrine\Tests\Models\DDC964;
use Doctrine\Common\Collections\ArrayCollection;
/**
* @Entity
*/
class DDC964Guest extends DDC964User
{
}

View File

@ -0,0 +1,108 @@
<?php
namespace Doctrine\Tests\Models\DDC964;
use Doctrine\Common\Collections\ArrayCollection;
/**
* @MappedSuperclass
*/
class DDC964User
{
/**
* @GeneratedValue
* @Id @Column(type="integer")
*/
protected $id;
/**
* @Column
*/
protected $name;
/**
* @var ArrayCollection
*
* @ManyToMany(targetEntity="DDC964Group", inversedBy="users", cascade={"persist", "merge", "detach"})
* @JoinTable(name="ddc964_users_groups",
* joinColumns={@JoinColumn(name="user_id", referencedColumnName="id")},
* inverseJoinColumns={@JoinColumn(name="group_id", referencedColumnName="id")}
* )
*/
protected $groups;
/**
* @var DDC964Address
*
* @ManyToOne(targetEntity="DDC964Address", cascade={"persist", "merge"})
* @JoinColumn(name="address_id", referencedColumnName="id")
*/
protected $address;
/**
* @param string $name
*/
public function __construct($name = null)
{
$this->name = $name;
$this->groups = new ArrayCollection;
}
/**
* @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;
}
/**
* @param DDC964Group $group
*/
public function addGroup(DDC964Group $group)
{
$this->groups->add($group);
$group->addUser($this);
}
/**
* @return ArrayCollection
*/
public function getGroups()
{
return $this->groups;
}
/**
* @return DDC964Address
*/
public function getAddress()
{
return $this->address;
}
/**
* @param DDC964Address $address
*/
public function setAddress(DDC964Address $address)
{
$this->address = $address;
}
}

View File

@ -0,0 +1,193 @@
<?php
namespace Doctrine\Tests\ORM\Functional\Ticket;
use Doctrine\ORM\Query;
require_once __DIR__ . '/../../../TestInit.php';
use Doctrine\Tests\Models\DDC964\DDC964Address;
use Doctrine\Tests\Models\DDC964\DDC964Group;
use Doctrine\Tests\Models\DDC964\DDC964Admin;
use Doctrine\Tests\Models\DDC964\DDC964Guest;
use Doctrine\Tests\Models\DDC964\DDC964User;
/**
* @group DDC-964
*/
class DDC964Test extends \Doctrine\Tests\OrmFunctionalTestCase
{
const NS = 'Doctrine\Tests\Models\DDC964';
public function testAssociationOverridesMapping()
{
$adminMetadata = $this->_em->getClassMetadata('Doctrine\Tests\Models\DDC964\DDC964Admin');
$guestMetadata = $this->_em->getClassMetadata('Doctrine\Tests\Models\DDC964\DDC964Guest');
// assert groups association mappings
$this->assertArrayHasKey('groups', $guestMetadata->associationMappings);
$this->assertArrayHasKey('groups', $adminMetadata->associationMappings);
$guestGroups = $guestMetadata->associationMappings['groups'];
$adminGroups = $adminMetadata->associationMappings['groups'];
$this->assertEquals('ddc964_users_groups', $guestGroups['joinTable']['name']);
$this->assertEquals('user_id', $guestGroups['joinTable']['joinColumns'][0]['name']);
$this->assertEquals('group_id', $guestGroups['joinTable']['inverseJoinColumns'][0]['name']);
$this->assertEquals(array('user_id'=>'id'), $guestGroups['relationToSourceKeyColumns']);
$this->assertEquals(array('group_id'=>'id'), $guestGroups['relationToTargetKeyColumns']);
$this->assertEquals(array('user_id','group_id'), $guestGroups['joinTableColumns']);
$this->assertEquals('ddc964_users_admingroups', $adminGroups['joinTable']['name']);
$this->assertEquals('adminuser_id', $adminGroups['joinTable']['joinColumns'][0]['name']);
$this->assertEquals('admingroup_id', $adminGroups['joinTable']['inverseJoinColumns'][0]['name']);
$this->assertEquals(array('adminuser_id'=>'id'), $adminGroups['relationToSourceKeyColumns']);
$this->assertEquals(array('admingroup_id'=>'id'), $adminGroups['relationToTargetKeyColumns']);
$this->assertEquals(array('adminuser_id','admingroup_id'), $adminGroups['joinTableColumns']);
// assert address association mappings
$this->assertArrayHasKey('address', $guestMetadata->associationMappings);
$this->assertArrayHasKey('address', $adminMetadata->associationMappings);
$guestAddress = $guestMetadata->associationMappings['address'];
$adminAddress = $adminMetadata->associationMappings['address'];
$this->assertEquals('address_id', $guestAddress['joinColumns'][0]['name']);
$this->assertEquals(array('address_id'=>'id'), $guestAddress['sourceToTargetKeyColumns']);
$this->assertEquals(array('address_id'=>'address_id'), $guestAddress['joinColumnFieldNames']);
$this->assertEquals(array('id'=>'address_id'), $guestAddress['targetToSourceKeyColumns']);
$this->assertEquals('adminaddress_id', $adminAddress['joinColumns'][0]['name']);
$this->assertEquals(array('adminaddress_id'=>'id'), $adminAddress['sourceToTargetKeyColumns']);
$this->assertEquals(array('adminaddress_id'=>'adminaddress_id'), $adminAddress['joinColumnFieldNames']);
$this->assertEquals(array('id'=>'adminaddress_id'), $adminAddress['targetToSourceKeyColumns']);
}
public function testShouldCreateAndRetrieveOverriddenAssociation()
{
list($admin1,$admin2, $guest1,$guest2) = $this->loadFixtures();
$this->_em->clear();
$this->assertNotNull($admin1->getId());
$this->assertNotNull($admin2->getId());
$this->assertNotNull($guest1->getId());
$this->assertNotNull($guest1->getId());
$adminCount = $this->_em
->createQuery('SELECT COUNT(a) FROM ' . self::NS . '\DDC964Admin a')
->getSingleScalarResult();
$guestCount = $this->_em
->createQuery('SELECT COUNT(g) FROM ' . self::NS . '\DDC964Guest g')
->getSingleScalarResult();
$this->assertEquals(2, $adminCount);
$this->assertEquals(2, $guestCount);
$admin1 = $this->_em->find(self::NS . '\DDC964Admin', $admin1->getId());
$admin2 = $this->_em->find(self::NS . '\DDC964Admin', $admin2->getId());
$guest1 = $this->_em->find(self::NS . '\DDC964Guest', $guest1->getId());
$guest2 = $this->_em->find(self::NS . '\DDC964Guest', $guest2->getId());
$this->assertUser($admin1, self::NS . '\DDC964Admin', '11111-111', 2);
$this->assertUser($admin2, self::NS . '\DDC964Admin', '22222-222', 2);
$this->assertUser($guest1, self::NS . '\DDC964Guest', '33333-333', 2);
$this->assertUser($guest2, self::NS . '\DDC964Guest', '44444-444', 1);
}
/**
* @param DDC964User $user
* @param string $addressZip
* @param integer $groups
*/
private function assertUser(DDC964User $user, $className, $addressZip, $groups)
{
$this->assertInstanceOf($className, $user);
$this->assertInstanceOf(self::NS . '\DDC964User', $user);
$this->assertInstanceOf(self::NS . '\DDC964Address', $user->getAddress());
$this->assertEquals($addressZip, $user->getAddress()->getZip());
$this->assertEquals($groups, $user->getGroups()->count());
}
private function createSchemaDDC964()
{
try {
$this->_schemaTool->createSchema(array(
$this->_em->getClassMetadata(self::NS . '\DDC964Address'),
$this->_em->getClassMetadata(self::NS . '\DDC964Group'),
$this->_em->getClassMetadata(self::NS . '\DDC964Guest'),
$this->_em->getClassMetadata(self::NS . '\DDC964Admin'),
));
} catch (\Exception $exc) {
}
}
/**
* @return array
*/
private function loadFixtures()
{
$this->createSchemaDDC964();
$group1 = new DDC964Group('Foo Admin Group');
$group2 = new DDC964Group('Bar Admin Group');
$group3 = new DDC964Group('Foo Guest Group');
$group4 = new DDC964Group('Bar Guest Group');
$this->_em->persist($group1);
$this->_em->persist($group2);
$this->_em->persist($group3);
$this->_em->persist($group4);
$this->_em->flush();
$admin1 = new DDC964Admin('Admin 1');
$admin2 = new DDC964Admin('Admin 2');
$guest1 = new DDC964Guest('Guest 1');
$guest2 = new DDC964Guest('Guest 2');
$admin1->setAddress(new DDC964Address('11111-111', 'Some Country', 'Some City', 'Some Street'));
$admin2->setAddress(new DDC964Address('22222-222', 'Some Country', 'Some City', 'Some Street'));
$guest1->setAddress(new DDC964Address('33333-333', 'Some Country', 'Some City', 'Some Street'));
$guest2->setAddress(new DDC964Address('44444-444', 'Some Country', 'Some City', 'Some Street'));
$admin1->addGroup($group1);
$admin1->addGroup($group2);
$admin2->addGroup($group1);
$admin2->addGroup($group2);
$guest1->addGroup($group3);
$guest1->addGroup($group4);
$guest2->addGroup($group2);
$this->_em->persist($admin1);
$this->_em->persist($admin2);
$this->_em->persist($guest1);
$this->_em->persist($guest2);
$this->_em->flush();
return array($admin1,$admin2, $guest1,$guest2);
}
}