学说2:按标签检索实体,保留结果中的所有标签

时间:2014-07-17 12:47:06

标签: doctrine many-to-many query-builder

我有三个Doctrine2实体,交互如下:

  1. 用户
  2. 书签(一个用户到多个书签)
  3. 标记(很多书签到很多标记)
  4. 书签实体与Tag实体具有ManyToMany关系,如下所示:

    /**
     * @var ArrayCollection
     * 
     * @ORM\ManyToMany(targetEntity="MyPackage\MyBundle\Entity\Tag",cascade={"persist"},
     *      inversedBy="bookmarks")
     * @ORM\JoinTable(name="bookmark_tag",
     *      joinColumns={@ORM\JoinColumn(name="bkm_id", referencedColumnName="id")},
     *      inverseJoinColumns={@ORM\JoinColumn(name="tag_id", referencedColumnName="id")}
     *      )
     */
    protected $tags;
    

    我需要使用特定标记检索所有带有标记的书签,并遵守以下规则:

    1. 书签可以是私人或公开的:私人书签必须仅显示给其所有者;
    2. 我需要检索书签和所有标签。
    3. 我使用的是Doctrine QueryBuilder(下面代码中的$qb变量)。我的代码如下所示:

          $qb ->select( array('r','t') )
              ->from('Bookmark', 'r')
              ->leftJoin('r.tags', 't')
              ->where( 'r.public = 1')
              ->andWhere( 't.slug= :slug' )
              ->orWhere(
                  $qb->expr()->andX(
                      'r.user=:uid',
                      't.slug=:slug'
                  ))
              ->setParameter('uid', $userId )
              ->setParameter('slug', $tagSlug );
      

      此查询的问题是,当我运行$qb->getQuery()->execute()时,返回的Bookmark实体在$tags下只有一个标记,即由$tagSlug(因此,不符合规则#2)。

2 个答案:

答案 0 :(得分:3)

您在书签中只获得1个标记(而不是所有标记)的原因是因为您的数据库供应商只会给您一个(因为WHERE子句中的t.slug = :slug部分)。尝试Doctrine在数据库上生成的实际SQL查询,您将看到相同的结果。学说不能保持不存在的东西。

一种可能的解决方案是使用子查询:

$qbOuter
    ->select(array('ob', 'ot'))
    ->from('Bookmark', 'ob')
    ->leftJoin('ob.tags', 'ot')
    ->where(
        $qbOuter->expr()->in(
            'ob.id',
            $qbInner
                ->select('ib.id')
                ->from('Bookmark', 'ib')
                ->join('ib.tags', 'it')
                ->where(
                    $qbInner->expr()->andX(
                        $qbInner->expr()->eq('it.slug', ':slug'),
                        $qbInner->expr()->orX(
                            $qbInner->expr()->eq('ib.public', ':public')
                            $qbInner->expr()->eq('ib.user', ':user')
                        )
                    )
                )
                ->getDql()
        )
    )
    ->setParameters(array(
        'slug'   => $tagSlug,
        'public' => true,
        'user'   => $userId
    ));

这里需要注意的一些事项:

  • 正在使用2个QueryBuilder:一个是内部的,另一个是外部的。
  • expr()->in()可以接受DQL形式的子查询,这就是为什么在内部QueryBuilder末端进行getDql()调用的原因。
  • 您在内部和外部查询中使用的别名必须彼此不同。这就是我将内部查询中的书签与ib或外部查询中的ob混淆的原因。 Tag也是如此。
  • 内部QueryBuilder中使用的参数应绑定在外部参数上。内部QueryBuilder仅用于为IN表达式生成DQL。

这里会发生的是内部查询将查找您想要的所有书签ID,外部查询将使用所有相关标签获取这些书签,以便Doctrine可以对它们进行水合。

PS:我还改进了你的查询(内部查询)并充分利用了表达式库。

答案 1 :(得分:0)

如果您希望获得每个条目的所有代码,可以使用aggregate field执行此操作 例如,您有带有字段标记的书签实体

   /**
    * @ORM\OneToMany(targetEntity="Tag", mappedBy="bookmark", cascade={"persist"})
    */
    private $tags;

并参考书签

    /**
     * @ORM\ManyToOne(targetEntity="Bookmark",inversedBy="tags")
     * @ORM\JoinColumn(name="bookmark_id", referencedColumnName="id")
     */
    private $bookmark;

最后一步是创建数组集合

public function __construct()
{
    $this->tags = new ArrayCollection();
}

就是这样。现在,您可以访问每个书签的所有代码。

{% for bookmark in bookmarks %}
    <p>{{ bookmark.title }}</p>
    {% for tag in bookmark.tags %}
        <b>{{ tag.name }}</b>
    {% endfor %}
{% endfor %}