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

Php 如何在Doctrine2中的单个查询中加载结果集的所有关联,php,optimization,orm,doctrine-orm,Php,Optimization,Orm,Doctrine Orm,我有一些非常基本的实体,包含故事和标签,我正在尽可能高效地加载它们 当我查询这样的故事时: 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

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

当我查询这样的故事时:

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;

}

我自己考虑的一种可能的方法是将类似这样的东西添加到我的repository类中。但现在感觉还不太好。我想要一种对其他关联更具可移植性的东西,它可以在每次查询实体时工作,也可以用于分页查询

可能是类似postLoad的事件中的某个内容,它覆盖了整个结果集(而不是每个实体的postLoad)


如果将
fetch=“LAZY”
更改为
fetch=“EAGER”
,会发生什么情况?那将是我的第一个停靠港。我还要检查doctrine是如何生成查询的,因为很可能正在大量生成单个查询以改进缓存。如果所有其他操作都失败了,您可以编写自己的实体管理器,并拥有一个带有自定义DQL查询的方法,该查询可以满足您的需要。当我将fetch更改为EAGER on Tag::$article时,对标记的查询将加入到文章中。导致更大的开销。
JOIN
并不像您想象的那样糟糕:根据存储引擎以及与服务器设置有关的其他参数,
JOIN
可能是最好的方法。如果您没有使用
ndbcluster
,并且已经正确地为表编制了索引(这一原则应该适合您),那么
JOIN
查询很可能是最有效的方法。这样看:PHP/Doctrine必须生成1个查询,而不是4个(如果您编写自己的EM方法,最多2个)。查询越多,两边涉及的资源就越多(对于DB和PHP),这是因为DB必须分别处理每个查询(散列、检查执行计划、解析、编译、创建执行计划+优化、存储计划、创建结果集等)。老实说,如果您没有使用MySQL集群,那么简单的连接很可能是最好的选择,我还没有详细看过它,但乍一看,它似乎相当准确。我不认为连接是邪恶的。我喜欢加入。我不喜欢的是,条令首先加载文章的,然后为每一篇文章加载关联的标记,其中数据库结果包含文章中与每个标记连接在一起的所有字段。这就是太多的开销,也是为什么我将标记::$article标记为lazy。
$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";
    }
}