Doctrine 原则2:按标签检索实体,将所有标签保留在结果中

Doctrine 原则2:按标签检索实体,将所有标签保留在结果中,doctrine,many-to-many,query-builder,Doctrine,Many To Many,Query Builder,我有三个Doctrine2实体,相互作用如下: /** * @var ArrayCollection * * @ORM\ManyToMany(targetEntity="MyPackage\MyBundle\Entity\Tag",cascade={"persist"}, * inversedBy="bookmarks") * @ORM\JoinTable(name="bookmark_tag", * joinColumns={@ORM\JoinColumn(n

我有三个Doctrine2实体,相互作用如下:

/**
 * @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;
  • 使用者
  • 书签(一个用户对多个书签)
  • 标记(many书签到many标记)
  • 书签实体与标记实体有很多关系,如下所示:

    /**
     * @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;
    
    我需要检索所有带有特定标记的书签,遵守以下规则:

  • 书签可以是私有的,也可以是公共的:私有书签必须仅向其所有者显示
  • 我需要检索书签和它们的标签
  • 我正在使用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()
    时,返回的
    书签
    实体在
    $tags
    下只有一个标记
    ,即由
    $tagSlug
    标识的标记(因此,不符合规则#2)。

    如果您想获得每个条目的所有标记,您可以使用它。
    例如,您有带有字段标记的书签实体

       /**
        * @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 %}
    
    {%用于书签中的书签%}
    {{bookmark.title}

    {bookmark.tags%中标记的%s} {{tag.name} {%endfor%} {%endfor%}
    如果您想获得每个条目的所有标签,您可以使用。
    例如,您有带有字段标记的书签实体

       /**
        * @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 %}
    
    {%用于书签中的书签%}
    {{bookmark.title}

    {bookmark.tags%中标记的%s} {{tag.name} {%endfor%} {%endfor%}
    如果您想获得每个条目的所有标签,您可以使用。
    例如,您有带有字段标记的书签实体

       /**
        * @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 %}
    
    {%用于书签中的书签%}
    {{bookmark.title}

    {bookmark.tags%中标记的%s} {{tag.name} {%endfor%} {%endfor%}
    如果您想获得每个条目的所有标签,您可以使用。
    例如,您有带有字段标记的书签实体

       /**
        * @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 %}
    
    {%用于书签中的书签%}
    {{bookmark.title}

    {bookmark.tags%中标记的%s} {{tag.name} {%endfor%} {%endfor%}
    一个书签中只有一个标记(而不是所有标记)是因为数据库供应商只会给你一个(因为WHERE子句中的
    t.slug=:slug
    部分)。尝试在数据库上生成实际的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
        ));
    
    这里有几点需要注意:

    • 有两个QueryBuilder在起作用:一个是内部的,一个是外部的
    • expr()->in()
      可以接受DQL形式的子查询,这就是为什么在内部QueryBuilder的末尾有一个
      getDql()
      调用
    • 在内部查询和外部查询中使用的别名必须彼此不同。这就是为什么我在内部查询中将书签别名为
      ib
      ,在外部查询中将书签别名为
      ob
      。Tag也是如此
    • 内部QueryBuilder中使用的参数应绑定在外部QueryBuilder上。内部QueryBuilder仅用于为表达式中的
      生成DQL
    这里将发生的事情是,内部查询将查找您想要的所有书签ID,而外部查询将获取这些书签以及所有关联的标记,以便Dority能够找到它们


    PS:我还对您的查询进行了一些改进(内部查询),并充分利用了表达式库。

    一个书签中只有一个标记(而不是所有标记)是因为您的数据库供应商只会给您一个(因为WHERE子句中的
    t.slug=:slug
    部分)。尝试在数据库上生成实际的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
        ));
    
    这里有几点需要注意:

    • 有两个QueryBuilder在起作用:一个是内部的,一个是外部的
    • expr()->in()
      可以接受DQL形式的子查询,这就是为什么在内部QueryBuilder的末尾有一个
      getDql()
      调用
    • 在内部查询和外部查询中使用的别名必须彼此不同。这就是为什么我在内部查询中将书签别名为
      ib
      ,在外部查询中将书签别名为
      ob
      。Tag也是如此
    • 内部QueryBuilder中使用的参数应绑定在外部QueryBuilder上。内部QueryBuilder仅用于为表达式中的
      生成DQL
    这里将发生的事情是,内部查询将查找您想要的所有书签ID,而外部查询将获取这些书签以及所有关联的标记,以便Dority能够找到它们


    PS:我还对您的查询进行了一些改进(内部查询),并充分利用了表达式库。

    一个书签中只有一个标记(而不是所有标记)是因为您的数据库供应商只会给您一个(因为WHERE子句中的
    t.slug=:slug
    部分)。尝试在数据库上生成实际的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
        ));
    
    这里有几点需要注意:

    • 那里