过滤与Doctrine2的多对多关联

And*_*708 5 php orm doctrine-orm

我有一个拥有Account实体集合的Section实体.每个Section实体都有一个实体集合Element(OneToMany关联).我的问题是,我想获取属于某个部分与特定帐户相关联的所有元素,而不是获取属于某个部分的所有元素.下面是我的数据库模型.

数据库模型

因此,当我获取一个帐户时,我希望能够遍历其关联的部分(这部分没有问题),并且对于每个部分,我想循环遍历与获取的帐户关联的元素.现在我有以下代码.

$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;
    }
}
Run Code Online (Sandbox Code Playgroud)

问题是它获取属于给定部分的所有元素,无论它们与哪个帐户相关联.生成的用于获取节的元素的SQL如下所示.

SELECT t0.id AS id1, t0.name AS name2, t0.section_id AS section_id3
FROM mydb.element t0
WHERE t0.section_id = ?
Run Code Online (Sandbox Code Playgroud)

我需要它做的事情如下(可能是任何其他方法).使用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 = ?
Run Code Online (Sandbox Code Playgroud)

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

我查看了Criteria对象,但据我记忆,它不能用于过滤关联.

那么,实现这一目标的最佳方法是什么?是否可以Section通过使用DQL查询"手动"组合实体元素?我目前(和缩短)的源代码如下所示.非常感谢提前!

/**
 * @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
}
Run Code Online (Sandbox Code Playgroud)

Jas*_*wer 2

如果我理解正确的话,您希望能够检索an 的所有Elements 的所有s ,但前提是这些s 与 that 关联,并且来自 Account 中的 getter。SectionAccountElementAccount

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

仅吸气剂

如果您只想在实体中使用 getter,可以通过添加以下 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;
    }
}
Run Code Online (Sandbox Code Playgroud)

存储库

上面的解决方案可能会执行多个查询,具体数量取决于有多少Sections 和Elements 与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();
    }
}
Run Code Online (Sandbox Code Playgroud)

PS:要使此查询正常工作,您需要将Section和之间的多对多关联定义Account为双向关联。

代理方法

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

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

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

实现此方法时,不要使用ElementRepository扩展EntityRepository,而是在创建时注入EntityRepository。这样您仍然可以交换持久层而不改变您的实体。