问题描述

我正在尝试通过首先显示图片的帖子,然后最后发布没有图像的帖子来排序类别中的帖子。我已经设法通过运行两个查询,现在我想将两个查询合并在一起。

我有以下几点:

<?php
$loop = new WP_Query( array('meta_key' => '_thumbnail_id', 'cat' => 1 ) );
$loop2 = new WP_Query( array('meta_key' => '', 'cat' => 1 ) );
$mergedloops = array_merge($loop, $loop2);

while($mergedloops->have_posts()): $mergedloops->the_post(); ?>

但是当我尝试查看该页面时,我会收到以下错误:

 Fatal error: Call to a member function have_posts() on a non-object in...

然后我尝试将 array_merge 转换为对象,但是我收到以下错误:

Fatal error: Call to undefined method stdClass::have_posts() in...

如何解决这个错误?

最佳解决方案

单个查询

想一想,有一个机会,你可以去单/主查询。或者换句话说:当您可以使用默认值时,不需要另外两个查询。如果您无法使用默认值,则不需要单个查询,无论您要分割查询的循环数为多少。

Prerequisites

首先,您需要在 pre_get_posts 过滤器中设置所需的值 (如我的其他答案所示) 。在那里你可能会设置 posts_per_pagecat 。没有 pre_get_posts 的过滤器 – 过滤器:

$catID = 1;
$catQuery = new WP_Query( array(
    'posts_per_page' => -1,
    'cat'            => $catID,
) );
// Add a headline:
printf( '<h1>%s</h1>', number_format_i18n( $catQuery->found_posts )
    .__( " Posts filed under ", 'YourTextdomain' )
    .get_cat_name( $catID ) );

建立基地

接下来我们需要的是一个小型的自定义插件 (或者如果你不介意在更新或主题更改期间移动它,或者将它放入 functions.php 文件):

<?php
/**
 * Plugin Name: (#130009) Merge Two Queries
 * Description: "Merges" two queries by using a <code>RecursiveFilterIterator</code> to divide one main query into two queries
 * Plugin URl:  http://wordpress.stackexchange.com/questions/130009/how-to-merge-two-queries-together
 */

class ThumbnailFilter extends FilterIterator implements Countable
{
    private $wp_query;

    private $allowed;

    private $counter = 0;

    public function __construct( Iterator $iterator, WP_Query $wp_query )
    {
        NULL === $this->wp_query AND $this->wp_query = $wp_query;

        // Save some processing time by saving it once
        NULL === $this->allowed
            AND $this->allowed = $this->wp_query->have_posts();

        parent::__construct( $iterator );
    }

    public function accept()
    {
        if (
            ! $this->allowed
            OR ! $this->current() instanceof WP_Post
        )
            return FALSE;

        // Switch index, Setup post data, etc.
        $this->wp_query->the_post();

        // Last WP_Post reached: Setup WP_Query for next loop
        $this->wp_query->current_post === $this->wp_query->query_vars['posts_per_page'] -1
            AND $this->wp_query->rewind_posts();

        // Doesn't meet criteria? Abort.
        if ( $this->deny() )
            return FALSE;

        $this->counter++;
        return TRUE;
    }

    public function deny()
    {
        return ! has_post_thumbnail( $this->current()->ID );
    }

    public function count()
    {
        return $this->counter;
    }
}

这个插件有一件事情:它使用 PHP SPL (Standard PHP Library)及其接口和迭代器。我们现在得到的是一个 FilterIterator,可以方便地从我们的循环中移除物品。它扩展了 PHP SPL Filter Iterator,所以我们不必设置所有内容。代码很好评论,但这里有一些注释:

  1. accept()方法允许定义允许循环该项目的标准 – 否则。

  2. 在这种方法中,我们使用 WP_Query::the_post(),因此您可以简单地使用模板文件循环中的每个模板标签。

  3. 当我们到达最后一个项目时,我们也监控循环和倒回帖子。这允许循环无限量的循环,而不重置我们的查询。

  4. 有一种不是 FilterIterator 规范的自定义方法:deny()。这种方法特别方便,因为它只包含我们的 「process or not」-statement,我们可以轻松地在后面的类中覆盖它,而不需要知道除 WordPress 模板标签之外的任何东西。

如何循环?

有了这个新的 Iterator,我们不再需要 if ( $customQuery->have_posts() )while ( $customQuery->have_posts() )了。我们可以使用简单的 foreach 语句,因为我们已经完成了所有必要的检查。例:

global $wp_query;
// First we need an ArrayObject made out of the actual posts
$arrayObj = new ArrayObject( $wp_query->get_posts() );
// Then we need to throw it into our new custom Filter Iterator
// We pass the $wp_query object in as second argument to keep track with it
$primaryQuery = new ThumbnailFilter( $arrayObj->getIterator(), $wp_query );

最后,我们只需要一个默认的 foreach 循环。我们甚至可以删除 the_post()并仍然使用所有的模板标签。全局 $post 对象将始终保持同步。

foreach ( $primaryQuery as $post )
{
    var_dump( get_the_ID() );
}

辅助回路

现在好的是,每个后来的查询过滤器都很容易处理:简单地定义 deny()方法,你可以开始下一个循环了。 $this->current()将始终指向我们目前循环的帖子。

class NoThumbnailFilter extends ThumbnailFilter
{
    public function deny()
    {
        return has_post_thumbnail( $this->current()->ID );
    }
}

正如我们定义的,我们现在 deny()循环每个具有缩略图的帖子,然后我们可以立即循环所有没有缩略图的帖子:

foreach ( $secondaryQuery as $post )
{
    var_dump( get_the_title( get_the_ID() ) );
}

测试它

以下 test plugin 可作为 GitHub 上的 Gist 使用。只需上传并激活它。它在 loop_start 操作上输出/转储每个循环的帖子的 ID 作为回调。这意味着根据您的设置,帖子数量和配置可能会产生相当多的输出。请添加一些中止语句,并将 var_dump()更改为要查看的内容以及您想要查看的内容。这只是一个概念证明。

次佳解决方案

虽然这不是解决这个问题的最好办法 (@凯撒的答案是),要直接回答问题,实际的查询结果将在 $loop->posts$loop2->posts 中,所以…

$mergedloops = array_merge($loop->posts, $loop2->posts);

… 应该工作,但是您需要使用 foreach 循环,而不是基于 WP_Query 的标准循环结构作为合并查询将会打破 WP_Query 对象”meta” 有关循环的数据。

你也可以这样做:

$loop = new WP_Query( array('fields' => 'ids','meta_key' => '_thumbnail_id', 'cat' => 1 ) );
$loop2 = new WP_Query( array('fields' => 'ids','meta_key' => '', 'cat' => 1 ) );
$ids = array_merge($loop->posts, $loop2->posts);
$merged = new WP_Query(array('post__in' => $ids,'orderby' => 'post__in'));

当然,这些解决方案代表了多个查询,这就是为什么 @凯撒对于 WP_Query 可以处理所需逻辑的更好的方法。

第三种解决方案

实际上有 meta_query(或 WP_Meta_Query) – 它可以搜索_thumbnail_id 行数组。如果您检查 EXISTS,您只能获得那些具有此字段的那些。将其与 cat 参数相结合,您将只会收到分配给 ID 为 1 并附有缩略图的类别的帖子。如果您由 meta_value_num 订购,那么您实际上将按缩略图 ID 从最低到最高 (如 orderASC 所述) 。当您使用 EXISTS 作为 compare 值时,不必指定 value

$thumbsUp = new WP_Query( array(
    'cat'        => 1,
    'meta_query' => array(
        array(
            'key'     => '_thumbnail_id',
            'compare' => 'EXISTS',
        ),
    ),
    'orderby'    => 'meta_value_num',
    'order'      => 'ASC',
) );

现在,当循环使用它们时,您可以收集所有的 ID,并在辅助查询的独家声明中使用它们:

$postsWithThumbnails = array();
if ( $thumbsUp->have_posts() )
{
    while ( $thumbsUp->have_posts() )
    {
        $thumbsUp->the_post();

        // collect them
        $postsWithThumbnails[] = get_the_ID();

        // do display/rendering stuff here
    }
}

现在可以添加第二个查询。不需要 wp_reset_postdata()这里 – 一切都在变量而不是主查询。

$noThumbnails = new WP_Query( array(
    'cat'          => 1,
    'post__not_in' => $postsWithThumbnails
) );
// Loop through this posts

当然,你可以更聪明,简单地改变 pre_get_posts 中的 SQL 语句,不浪费主查询。您也可以简单地在 pre_get_posts 过滤器回调中执行第一个查询 ($thumbsUp) 。

add_filter( 'pre_get_posts', 'wpse130009excludeThumbsPosts' );
function wpse130009excludeThumbsPosts( $query )
{
    if ( $query->is_admin() )
        return $query;

    if ( ! $query->is_main_query() )
        return $query;

    if ( 'post' !== $query->get( 'post_type' ) )
        return $query;

    // Only needed if this query is for the category archive for cat 1
    if (
        $query->is_archive()
        AND ! $query->is_category( 1 )
    )
        return $query;

    $query->set( 'meta_query', array(
        array(
            'key'     => '_thumbnail_id',
            'compare' => 'EXISTS',
        ),
    ) );
    $query->set( 'orderby', 'meta_value_num' );

    // In case we're not on the cat = 1 category archive page, we need the following:
    $query->set( 'category__in', 1 );

    return $query;
}

这改变了主查询,所以我们只会收到附有缩略图的帖子。现在我们可以 (如上面的第一个查询所示) 在主循环中收集 ID,然后添加显示其余帖子 (没有缩略图) 的第二个查询。

除此之外,您可以更智能地更改 posts_clauses,并通过元值直接修改查询。看看 this answer,因为目前只是一个起点。

参考文献

注:本文内容整合自 Google/Baidu/Bing 辅助翻译的英文资料结果。如果您对结果不满意,可以加入我们改善翻译效果:薇晓朵技术论坛。