如何在Doctrine2的单个查询中加载结果集的所有关联


How to load all associations for a result set in a single query in Doctrine2

我有一些非常基本的实体,包含故事和标签,我试图尽可能高效地加载它们。

当我查询这样的故事时:

SELECT a FROM Foo'Article a WHERE a.id IN (1,2,3,4,5)

我看到下面的SQL查询正在运行:

SELECT f0_.id AS id_0, f0_.title AS title_1 FROM foo_article f0_ WHERE f0_.id IN (1, 2, 3)
SELECT t0.name AS name_1, t0.article_id AS article_id_2 FROM foo_tag t0 WHERE t0.article_id = 1
SELECT t0.name AS name_1, t0.article_id AS article_id_2 FROM foo_tag t0 WHERE t0.article_id = 2
SELECT t0.name AS name_1, t0.article_id AS article_id_2 FROM foo_tag t0 WHERE t0.article_id = 3

我想看到的地方:

SELECT f0_.id AS id_0, f0_.title AS title_1 FROM foo_article f0_ WHERE f0_.id IN (1, 2, 3)
SELECT t0.name AS name_1, t0.article_id AS article_id_2 FROM foo_tag t0 WHERE t0.article_id IN (1, 2, 3);

源代码如下所示。根据实际代码缩写。

<?php
namespace Foo;
use Doctrine'ORM'Mapping as ORM;
/**
 * Class Tag
 *
 * @ORM'Entity()
 * @ORM'Table(name="foo_tag")
 *
 * @package Foo
 */
class Tag {
    /**
     * @ORM'Column(type="string")
     * @ORM'Id()
     */
    protected $name;
    /**
     * @ORM'ManyToOne(targetEntity="'Foo'Article",cascade={"persist"},fetch="LAZY",inversedBy="tags")
     * @ORM'Id()
     */
    protected $article;
}
/**
 * Class Article
 *
 * @ORM'Entity()
 * @ORM'Table(name="foo_article")
 *
 * @package Foo
 */
class Article {
    /**
     * @ORM'Id @ORM'Column(type="integer", name="id") @ORM'GeneratedValue
     */
    protected $id;
    /**
     * @ORM'Column(type="string")
     */
    protected $title;
    /**
     * @ORM'OneToMany(targetEntity="'Foo'Tag",mappedBy="article",cascade={"persist"},fetch="EAGER")
     */
    protected $tags;
}

我自己正在考虑的一种可能的方法是将这样的东西添加到我的存储库类中。但现在感觉还不太对。我想要一些更易于移植到其他关联的东西,每次查询实体时都能工作,并且还能与分页查询一起工作。

可能是类似postLoad的事件,覆盖整个结果集(而不是每个实体的postLoad)。

$qb = $entityManager->getRepository('Foo'Article')->createQueryBuilder('a')
    ->where('a.id IN (1,2,3)');
$list = $qb->getQuery()->execute();
/** @var Foo'Article[] $indexed */
$indexed = array_reduce($list, function($result, 'Foo'Article $article) {
    $result[$article->getId()] = $article;
    return $result;
}, Array());
$tags = $entityManager->getRepository('Foo'Tag')->createQueryBuilder('t')
    ->where('t.article IN (:ids)')
    ->setParameter('ids', $indexed)
    ->getQuery()->execute();
array_map(function('Foo'Tag $tag) use ($indexed) {
    /** @var 'Doctrine'ORM'PersistentCollection $collection */
    $collection = $tag->getArticle()->getTags();
    $collection->add($tag);
    $collection->setInitialized(true);
}, $tags);
foreach($indexed as $article) {
    $article->getTags()->takeSnapshot();
    $article->getTags()->setInitialized(true);
}
/** @var Foo'Article $article */
// and now use the Articles and Tags, to make sure that everything is loaded
foreach($list as $article) {
    $tags = $article->getTags();
    print " - ".$article->getTitle()."'n";
    foreach($tags as $tag) {
        print "   - ".$tag."'n";
    }
}