过滤与Doctrine2的多对多关联

时间:2014-05-09 12:39:12

标签: php orm doctrine-orm

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

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。如果我可以这样做并以某种方式覆盖getElements()实体上的Section方法,那么这将是完美的。我非常希望是否有办法通过关联映射或至少通过使用现有的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 个答案:

答案 0 :(得分:1)

如果我理解正确,您希望能够检索Element的所有Section中的所有Account,但前提是这些Element是关联的使用Account,这来自帐户中的getter。

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

仅限吸毒者

如果您只想在实体中使用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;
    }
}

<强>存储库

上述解决方案可能会执行多次查询,具体金额取决于与Section相关联的ElementAccount的数量。

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

一个例子:

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之间的ManyToMany关联定义为双向关联。

代理方法

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

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

通过这种方式,实体仍然不知道存储库,但您可以将其传递给它。

执行此操作时,请勿ElementRepository EntityRepository EntityRepository {{1}} {{1}}创建{{1}}。这样,您仍然可以在不改变实体的情况下更换持久层。