2014-05-09 139 views
5

我有一个Account实体,它具有一个Section实体的集合。每个Section实体都有一个Element实体(OneToMany关联)的集合。我的问题是,取代所有属于某个部分的元素,我想要获取属于部分的所有元素都与特定帐户相关联。以下是我的数据库模型。过滤与Doctrine2的多对多关联

Database model

因此,当我取一个帐户,我希望能够循环通过其相关的部分(这部分是没有问题的),并且对于每个部分,我要通过其元素是环与提取的帐户相关联。现在我有以下代码。

$repository = $this->objectManager->getRepository('MyModule\Entity\Account'); 
$account = $repository->find(1); 

foreach ($account->getSections() as $section) { 
    foreach ($section->getElements() as $element) { 
     echo $element->getName() . PHP_EOL; 
    } 
} 

问题是它会提取属于给定部分的所有元素,而不管它们与哪个帐户关联。生成的用于获取节的元素的SQL如下所示。

SELECT t0.id AS id1, t0.name AS name2, t0.section_id AS section_id3 
FROM mydb.element t0 
WHERE t0.section_id = ? 

我需要它做的是像下面的东西(可以是任何其他方法)。过滤使用SQL完成很重要。

SELECT e.id, e.name, e.section_id 
FROM element AS e 
INNER JOIN account_element AS ae ON (ae.element_id = e.id) 
WHERE ae.account_id = ? 
AND e.section_id = ? 

我知道我可以写一个自定义库的方法getElementsBySection($accountId)或相似,并使用DQL。如果我能做到这一点,并以某种方式覆盖Section实体上的getElements()方法,那么这将是完美的。我只是非常希望能通过关联映射或至少通过使用现有的getter方法来实现这一点。理想情况下,当使用帐户对象时,我希望能够像上面的代码片段一样循环,以便在使用对象时抽象“帐户约束”。也就是说,对象的用户无需调用getElementsByAccount()Section对象上的类似对象,因为它看起来不那么直观。

我看着Criteria对象,但据我记得,它不能用于过滤关联。

那么,完成此操作的最佳方法是什么?如果没有通过使用DQL查询“手动”组装Section实体和元素,是否有可能?我现在的(和缩短的)源代码可以在下面看到。提前感谢!

/** 
* @ORM\Entity 
*/ 
class Account 
{ 
    /** 
    * @var int 
    * @ORM\Column(type="integer") 
    * @ORM\Id 
    * @ORM\GeneratedValue 
    */ 
    protected $id; 

    /** 
    * @var string 
    * @ORM\Column(type="string", length=50, nullable=false) 
    */ 
    protected $name; 

    /** 
    * @var ArrayCollection 
    * @ORM\ManyToMany(targetEntity="MyModule\Entity\Section") 
    * @ORM\JoinTable(name="account_section", 
    *  joinColumns={@ORM\JoinColumn(name="account_id", referencedColumnName="id")}, 
    *  inverseJoinColumns={@ORM\JoinColumn(name="section_id", referencedColumnName="id")} 
    *) 
    */ 
    protected $sections; 

    public function __construct() 
    { 
     $this->sections = new ArrayCollection(); 
    } 

    // Getters and setters 
} 


/** 
* @ORM\Entity 
*/ 
class Section 
{ 
    /** 
    * @var int 
    * @ORM\Id 
    * @ORM\GeneratedValue 
    * @ORM\Column(type="integer") 
    */ 
    protected $id; 

    /** 
    * @var string 
    * @ORM\Column(type="string", length=50, nullable=false) 
    */ 
    protected $name; 

    /** 
    * @var ArrayCollection 
    * @ORM\OneToMany(targetEntity="MyModule\Entity\Element", mappedBy="section") 
    */ 
    protected $elements; 

    public function __construct() 
    { 
     $this->elements = new ArrayCollection(); 
    } 

    // Getters and setters 
} 


/** 
* @ORM\Entity 
*/ 
class Element 
{ 
    /** 
    * @var int 
    * @ORM\Id 
    * @ORM\GeneratedValue 
    * @ORM\Column(type="integer") 
    */ 
    protected $id; 

    /** 
    * @var string 
    * @ORM\Column(type="string", length=50, nullable=false) 
    */ 
    protected $name; 

    /** 
    * @var Section 
    * @ORM\ManyToOne(targetEntity="MyModule\Entity\Section", inversedBy="elements") 
    * @ORM\JoinColumn(name="section_id", referencedColumnName="id") 
    */ 
    protected $section; 

    /** 
    * @var \MyModule\Entity\Account 
    * @ORM\ManyToMany(targetEntity="MyModule\Entity\Account") 
    * @ORM\JoinTable(name="account_element", 
    *  joinColumns={@ORM\JoinColumn(name="element_id", referencedColumnName="id")}, 
    *  inverseJoinColumns={@ORM\JoinColumn(name="account_id", referencedColumnName="id")} 
    *) 
    */ 
    protected $account; 

    // Getters and setters 
} 

回答

1

如果我理解正确的话,你希望能够检索所有Element一切都Section秒的Account的,但前提是这些Element s的与Account,这从账户吸气剂有关。

首先:实体不应该知道存储库。这打破了一个设计原则,可以帮助您更换持久层。这就是为什么你不能从一个实体内简单访问一个存储库的原因。

吸气剂只

如果你只是想在实体使用干将,您可以通过添加解决这个问题,以下列2种方法:

class Section 
{ 
    /** 
    * @param Account $accout 
    * @return Element[] 
    */ 
    public function getElementsByAccount(Account $accout) 
    { 
     $elements = array(); 

     foreach ($this->getElements() as $element) { 
      if ($element->getAccount() === $account) { 
       $elements[] = $element->getAccount(); 
      } 
     } 

     return $elements; 
    } 
} 

class Account 
{ 
    /** 
    * @return Element[] 
    */ 
    public function getMyElements() 
    { 
     $elements = array() 

     foreach ($this->getSections() as $section) { 
      foreach ($section->getElementsByAccount($this) as $element) { 
       $elements[] = $element; 
      } 
     } 

     return $elements; 
    } 
} 

解决方案上面可能会执行几个查询,具体数量取决于有多少Section s和Element s是相关的登录到Account

当您使用存储库方法时,您可能会获得性能提升,因此您可以优化用于检索所需内容的查询/查询。

一个例子:

class ElementRepository extends EntityRepository 
{ 
    /** 
    * @param Account $account [description] 
    * @return Element[] 
    */ 
    public function findElementsByAccount(Account $account) 
    { 
     $dql = <<< 'EOQ' 
SELECT e FROM Element e 
JOIN e.section s 
JOIN s.accounts a 
WHERE e.account = ?1 AND a.id = ?2 
EOQ; 

     $q = $this->getEntityManager()->createQuery($dql); 
     $q->setParameters(array(
      1 => $account->getId(), 
      2 => $account->getId() 
     )); 

     return $q->getResult(); 
    } 
} 

PS:对于此查询工作,你需要SectionAccount之间定义多对多协会作为双向的。

代理方法

的混合解决方案将是一个代理方法添加到Account,即调用转发给你传递给它的存储库。

class Account 
{ 
    /** 
    * @param ElementRepository $repository 
    * @return Element[] 
    */ 
    public function getMyElements(ElementRepository $repository) 
    { 
     return $repository->findElementsByAccount($this); 
    } 
} 

这样,实体仍然不知道存储库,但您允许将其传递给它。

执行此操作时,没有ElementRepository延伸EntityRepository,但injectEntityRepository创建时。这样,您仍然可以在不更改实体的情况下交换持久层。

+0

第一种解决方案应该可以工作,但似乎不太吸引人,因为如果我理解正确,过滤在内存和SQL中完成。通过存储库方法,我会手动“组装”'Section'对象,对吧?也就是说,自己设置元素,而不是通过关联自动获取元素。也许[这样的事情](http://pastebin.com/CWAadsbB)?在这种情况下,我可能会删除实体上的关联关系,以确保在使用对象之前没有组装对象时不会误取错的部分。好的,顺便说一下! – Andy0708

+0

第一种解决方案是,db将根据定义的关联简单地获取实体。对特定的“Account”进行过滤只会以PHP完成。这就是为什么我建议你使用一个存储库,因为你可以让数据库只提取你实际需要的那些'Element's(所以db做过滤)。 –

+1

我用示例查询更新了我的答案。 –