2014-05-09 4 views
5

Ho un'entità Account che ha una raccolta di entità Section. Ogni entità Section ha una raccolta di entità Element (associazione OneToMany). Il mio problema è che invece di recuperare tutti gli elementi appartenenti a una sezione, voglio recuperare tutti gli elementi che appartengono a una sezione e sono associati a un account specifico. Di seguito è riportato il mio modello di database.Filtro sull'associazione molti a molti con Doctrine2

Database model

Così, quando prelevo un account, voglio essere in grado di scorrere le sezioni associate (questa parte non è un problema), e per ogni sezione, voglio scorrere gli elementi che sono associato all'account recuperato. In questo momento ho il seguente codice.

$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; 
    } 
} 

Il problema è che recupera tutti gli elementi appartenenti a una determinata sezione, indipendentemente dall'account a cui sono associati. L'SQL generato per il recupero degli elementi di una sezione è il seguente.

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

Quello che mi serve è qualcosa di simile al seguente (potrebbe essere qualsiasi altro approccio). È importante che il filtraggio avvenga con 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 = ? 

so che posso scrivere un metodo getElementsBySection($accountId) o simile in un repository personalizzato e utilizzare DQL. Se posso farlo e in qualche modo sovrascrivere il metodo getElements() sull'entità Section, sarebbe perfetto. Preferisco di gran lunga se ci fosse un modo per farlo attraverso le associazioni di associazioni o almeno usando i metodi getter esistenti. Idealmente, quando si utilizza un oggetto account, mi piacerebbe essere in grado di eseguire il ciclo come nel frammento di codice sopra in modo che il "vincolo dell'account" sia astratto quando si utilizza l'oggetto. Cioè, l'utente dell'oggetto non ha bisogno di chiamare getElementsByAccount() o simili su un oggetto Section, perché sembra meno intuitivo.

Ho esaminato l'oggetto Criteria, ma per quanto mi ricordo, non può essere utilizzato per il filtro sulle associazioni.

Quindi, qual è il modo migliore per realizzare questo? È possibile senza "manualmente" assemblare l'entità Section con elementi attraverso l'uso di query DQL? Il mio codice sorgente corrente (e abbreviato) può essere visto sotto. Grazie mille in anticipo!

/** 
* @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 
} 

risposta

1

Se ho capito bene, si vuole essere in grado di recuperare tutti i Element s di tutti Section s di un Account, ma solo se tali Element s sono associati a quel Account, e questo da un getter Account.

Primo: Un'entità non deve mai sapere di repository. Questo rompe un principio di progettazione che ti aiuta a scambiare il livello di persistenza. Questo è il motivo per cui non è possibile accedere facilmente a un repository all'interno di un'entità.

Getters solo

Se si desidera utilizzare solo getter nelle entità, è possibile risolvere questo problema aggiungendo al seguente 2 metodi:

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; 
    } 
} 

Repository

La soluzione sopra è probabile che esegua diverse query, l'importo esatto dipende da quanti Section se Element s sono associati assegnato allo Account.

È probabile che si ottenga un aumento delle prestazioni quando si utilizza un metodo di repository, in modo da poter ottimizzare la query/query utilizzate per recuperare ciò che si desidera.

Un esempio:

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: Per questa query funzioni, è necessario definire l'associazione tra il ManyToMany Section e Account come uno bidirezionale.

metodo Proxy

Una soluzione ibrida potrebbe essere quella di aggiungere un metodo proxy per Account, che inoltra la chiamata al repository si passa ad esso.

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

In questo modo l'entità continua a non conoscere i repository, ma ne consente di passarne uno.

Quando si implementa questo, non è necessario ElementRepository estendere EntityRepository, mail EntityRepository al momento della creazione. In questo modo puoi ancora scambiare il livello di persistenza senza alterare le tue entità.

+0

La prima soluzione dovrebbe funzionare, ma non sembra così attraente perché il filtro viene eseguito in memoria e in SQL se ho capito bene. Con l'approccio del repository, vorrei "assemblare" manualmente gli oggetti 'Section', giusto? Cioè, impostando me stesso gli elementi invece di essere recuperati automaticamente tramite un'associazione. Forse [qualcosa di simile] (http://pastebin.com/CWAadsbB)? In tal caso, potrei anche rimuovere l'associazione sull'entità per garantire che le sezioni errate non vengano recuperate per errore se non si è assemblato l'oggetto prima di utilizzarlo. Buoni punti, a proposito! – Andy0708

+0

Con la prima soluzione il db preleverà semplicemente le entità in base alle associazioni definite. Il filtro su uno specifico account verrà eseguito solo in PHP. Questo è il motivo per cui ti suggerisco di usare un repository, perché puoi permettere al db di recuperare solo quegli 'Element' che vuoi realmente (quindi il db fa il filtro). –

+1

Ho aggiornato la mia risposta con una query di esempio. –