2014-10-08 121 views
1

我正在尝试创建Symfony Bundle,其中定义的实体可用于一对多/多对一的关系,而无需手动重写映射。动态映射manyToOne关系

我这样做是通过订阅loadClassMetadata事件并添加基于它们实现的接口的映射。这并不像使用ResolveTargetEntityListener那么简单,因为它只是简单地将接口替换为具体的类。

一个例子。我有一个地址和一个客户实体。客户有很多地址。 但是另一个包可能会重新定义客户(或者可能有多个地址的完全不同的实体)。为此,客户实施了AddressableInterface。为了便于使用,我在特性中实现了这个接口。

在用户中,我检查该类是否实现了AddressableInterface。如果是这样,则将OneToMany添加到Address,并将ManyToOne添加到实现AddressableInterface的类。 (在这个例子中Customer类)

然而这留下了以下错误:

The association Entity\Customer#addresses refers to the owning side field Entity\Address#subject which does not exist.

但我设置到协会在我的用户两种方式。

下面是我的代码的本质。

namespace Entity; 
class Address 
{ 
    public $subject; 
} 

namespace Entity; 
class Customer implements AddressableInterface 
{ 
    use Traits/Addressable; 
} 

namespace Traits; 
trait Addressable //Implements all methods from AddressableInterface 
{ 
    protected $addresses; 

    public function getAddresses() 
    { 
     return $this->addresses; 
    } 

    public function addAddress(AddressInterface $address) 
    { 
     $this->addresses->add($address); 
    } 

    public function removeAddress(AddressInterface $address) 
    { 
     $this->addresses->removeElement($address); 
    } 
} 

而事件订阅

class DynamicAddressBindingSubscriber implements EventSubscriber 
{ 
    public function getSubscribedEvents() 
    { 
     return [Events::loadClassMetadata]; 
    } 

    public function loadClassMetadata(LoadClassMetadataEventArgs $eventArgs) 
    { 
     $metadata = $eventArgs->getClassMetadata(); 

     $class = $metadata->getReflectionClass(); 
     if (!in_array(AddressableInterface::class, $class->getInterfaceNames())) { 
      return; 
     } 

     $factory = new \Doctrine\ORM\Mapping\ClassMetadataFactory; 
     $factory->setEntityManager($eventArgs->getEntityManager()); 
     $addressMetadata = $factory->getMetadataFor(Address::class); 

     $addressMetadata->mapManyToOne(
      [ 
       "targetEntity" => $class->getName(), 
       "fieldName" => "subject", 
       "inversedBy" => "addresses" 
      ] 
     ); 

     $metadata->mapOneToMany(
      [ 
       'targetEntity' => Address::class, 
       'fieldName' => 'addresses', 
       'mappedBy'  => 'subject' 
      ] 
     ); 
    } 
} 

我已经看了多个例子并根据我的大部分上this article代码和教条捆绑源。但是我坚持这一点,因为我不知道协会为什么找不到欠款方。

+1

尼斯的问题,我只是想知道,如果你真的需要反向关系。否则,您不需要增加“地址”的元数据。此外,如果多个实体在同一应用程序中具有地址(例如公司,人员),则它们将避免冲突,它们不能由“主体”映射)。 – fejese 2014-10-08 09:15:41

+0

确实看起来不像我需要反向关系,当我删除“Address”的元数据时,错误仍然保持不变。 – Waaghals 2014-10-08 09:28:44

+0

您是否已从'mapOneToMany'调用中移除'mappedBy'选项? – fejese 2014-10-08 09:31:46

回答

2

您的地址类不具有主题字段的getter/setter。

另一件事是,如果你想绑定地址到任何类,你可能更喜欢使它成为manyToMany关系。我喜欢这个附件,这样做:

$metadata->mapManyToMany([ 
    'targetEntity' => '...\FilesBundle\Entity\Attachment', 
    'fieldName' => 'attachments', 
    'cascade' => array('persist'), 
    'joinTable' => array(
     'name' => strtolower($namingStrategy->classToTableName($metadata->getName())) . '_attachment', 
     'joinColumns' => array(
      array(
       'name' => $namingStrategy->joinKeyColumnName($metadata->getName()), 
       'referencedColumnName' => $namingStrategy->referenceColumnName(), 
       'onDelete' => 'CASCADE', 
       'onUpdate' => 'CASCADE', 
      ), 
     ), 
     'inverseJoinColumns' => array(
      array(
       'name' => 'file_id', 
       'referencedColumnName' => $namingStrategy->referenceColumnName(), 
       'onDelete' => 'CASCADE', 
       'onUpdate' => 'CASCADE', 
      ), 
     ) 
    ) 
]); 

其中namingStrategy来自事件:

$namingStrategy = $eventArgs 
     ->getEntityManager() 
     ->getConfiguration() 
     ->getNamingStrategy() 
; 
+0

$主题实际上是保护与getter和setter,但为了简洁,我留下了它们。 ManyToMany的任何具体原因,在我提到的文章中他们还定义了ManyToMany而不是OneToMany? – Waaghals 2014-10-08 12:22:30

+1

如果允许标记与接口的关系,则允许地址与任何类关联。虽然您可以在给定类的LoadClassMetadata上动态引入OneToMany,但不能在数据库中动态更改OneToMany,这意味着您不能说“此时的地址与客户有关,所以'主题'是指客户并且在另一个执行更改它“ 你只能通过M2M实现它,并为每个关系地址创建一个连接表<> another_class – 2014-10-08 12:26:22