2014-05-09 175 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

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