简体   繁体   English

过滤与Doctrine2的多对多关联

[英]Filtering on many-to-many association with Doctrine2

I have an Account entity which has a collection of Section entities. 我有一个Account实体,它有一个Section实体的集合。 Each Section entity has a collection of Element entities (OneToMany association). 每个Section实体都有一个Element实体的集合(OneToMany关联)。 My problem is that instead of fetching all elements belonging to a section, I want to fetch all elements that belong to a section and are associated with a specific account. 我的问题是,我想获取属于某个部分与特定帐户相关联的所有元素,而不是获取属于某个部分的所有元素。 Below is my database model. 下面是我的数据库模型。

数据库模型

Thus, when I fetch an account, I want to be able to loop through its associated sections (this part is no problem), and for each section, I want to loop through its elements that are associated with the fetched account. 因此,当我获取一个帐户时,我希望能够遍历其关联的部分(这部分没有问题),并且对于每个部分,我想循环遍历与获取的帐户关联的元素。 Right now I have the following code. 现在我有以下代码。

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

The problem is that it fetches all elements belonging to a given section, regardless of which account they are associated with. 问题是它获取属于给定部分的所有元素,无论它们与哪个帐户相关联。 The generated SQL for fetching a section's elements is as follows. 生成的用于获取节的元素的SQL如下所示。

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

What I need it to do is something like the below (could be any other approach). 我需要它做的事情如下(可能是任何其他方法)。 It is important that the filtering is done with SQL. 使用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 = ?

I do know that I can write a method getElementsBySection($accountId) or similar in a custom repository and use DQL. 我知道我可以在自定义存储库中编写方法getElementsBySection($accountId)或类似方法并使用DQL。 If I can do that and somehow override the getElements() method on the Section entity, then that would be perfect. 如果我可以这样做并以某种方式覆盖Section实体上的getElements()方法,那么这将是完美的。 I would just very much prefer if there would be a way to do this through association mappings or at least by using existing getter methods. 我非常希望是否有办法通过关联映射或至少通过使用现有的getter方法来实现这一点。 Ideally, when using an account object, I would like to be able to loop like in the code snippet above so that the "account constraint" is abstracted when using the object. 理想情况下,当使用帐户对象时,我希望能够像上面的代码片段一样循环,以便在使用对象时抽象出“帐户约束”。 That is, the user of the object does not need to call getElementsByAccount() or similar on a Section object, because it seems less intuitive. 也就是说,对象的用户不需要在Section对象上调用getElementsByAccount()或类似的东西,因为它看起来不太直观。

I looked into the Criteria object, but as far as I remember, it cannot be used for filtering on associations. 我查看了Criteria对象,但据我记忆,它不能用于过滤关联。

So, what is the best way to accomplish this? 那么,实现这一目标的最佳方法是什么? Is it possible without "manually" assembling the Section entity with elements through the use of DQL queries? 是否可以通过使用DQL查询“手动”组合Section实体和元素? My current (and shortened) source code can be seen below. 我目前(和缩短)的源代码如下所示。 Thanks a lot in advance! 非常感谢提前!

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

If I understand correctly, you want to be able to retrieve all Element s of all Section s of an Account , but only if those Element s are associated with that Account , and this from a getter in Account. 如果我理解正确,您希望能够检索Account所有Section的所有Element ,但前提是这些Element与该Account相关联,并且这是来自帐户中的获取者。

First off: An entity should never know of repositories. 首先:实体永远不应该知道存储库。 This breaks a design principle that helps you swap out the persistence layer. 这打破了一个帮助您更换持久层的设计原则。 That's why you cannot simple access a repository from within an entity. 这就是为什么你不能从实体内部简单地访问存储库的原因。

Getters only 只有吸气剂

If you only want to use getters in the entities, you can solve this by adding to following 2 methods: 如果您只想在实体中使用getter,可以通过添加以下两种方法来解决此问题:

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 知识库

The solution above is likely to perform several queries, the exact amount depending on how many Section s and Element s are associated to the Account . 上面的解决方案可能会执行多个查询,具体数量取决于与Account关联的SectionElement的数量。

You're likely to get a performance boost when you do use a Repository method, so you can optimize the query/queries used to retrieve what you want. 当您使用Repository方法时,您可能会获得性能提升,因此您可以优化用于检索所需内容的查询/查询。

An example: 一个例子:

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: For this query to work, you'll need to define the ManyToMany association between Section and Account as a bidirectional one. PS:要使此查询起作用,您需要将SectionAccount之间的ManyToMany关联定义为双向关联。

Proxy method 代理方法

A hybrid solution would be to add a proxy method to Account , that forwards the call to the repository you pass to it. 混合解决方案是向Account添加代理方法,将调用转发到您传递给它的存储库。

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

This way the entity still doesn't know of repositories, but you allow one to be passed to it. 这样,实体仍然不知道存储库,但您允许将其传递给它。

When implementing this, don't have ElementRepository extend EntityRepository , but inject the EntityRepository upon creation. 实现此功能时,不要让ElementRepository扩展EntityRepository ,而是在创建时注入 EntityRepository This way you can still swap out the persistence layer without altering your entities. 这样,您仍然可以在不更改实体的情况下更换持久层。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM