Как объединить два запроса вместе

10

Я пытаюсь упорядочить сообщения в категории, показывая сначала сообщения с изображениями, а затем сообщения без изображений. Мне удалось сделать это, выполнив два запроса, и теперь я хочу объединить два запроса вместе.

У меня есть следующее:

<?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...

Как я могу исправить эту ошибку?

Howli
источник

Ответы:

8

Один запрос

Подумайте об этом немного больше, и есть шанс, что вы сможете выполнить один / главный запрос. Или другими словами: нет необходимости в двух дополнительных запросах, когда вы можете работать с заданным по умолчанию. И в случае, если вы не можете работать с заданным по умолчанию, вам не потребуется больше одного запроса, независимо от того, на сколько циклов вы хотите разбить запрос.

Предпосылки

Сначала вам нужно установить (как показано в моем другом ответе) необходимые значения внутри pre_get_postsфильтра. Там вы, скорее всего, установите posts_per_pageи cat. Пример без pre_get_posts-Filter:

$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 (стандартную библиотеку PHP) и его интерфейсы и итераторы. То, что мы теперь получили, это то, FilterIteratorчто позволяет нам удобно удалять элементы из нашего цикла. Он расширяет PHP SPL Filter Iterator, поэтому нам не нужно все устанавливать. Код хорошо прокомментирован, но вот некоторые замечания:

  1. accept()Метод позволяет определить критерии, позволяющие Зацикливание элемента - или нет.
  2. Внутри этого метода мы используем WP_Query::the_post(), так что вы можете просто использовать каждый тег шаблона в цикле файлов шаблона.
  3. А также мы отслеживаем цикл и перематываем сообщения, когда достигаем последнего пункта. Это позволяет выполнять бесконечное количество циклов без сброса нашего запроса.
  4. Там в один пользовательский метод , который не является частью FilterIteratorспецификации: deny(). Этот метод особенно удобен, так как он содержит только наше «обработано или нет» заявление, и мы можем легко перезаписать его в последующих классах без необходимости знать что-либо кроме тегов шаблона WordPress.

Как зациклить?

С этим новым итератора, нам не нужно 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() ) );
}

Попробуй это.

Следующий тестовый плагин доступен как Gist на GitHub. Просто загрузите и активируйте его. Он выводит / сбрасывает идентификатор каждого зацикленного сообщения в качестве обратного вызова для loop_startдействия. Это означает, что в зависимости от настроек, количества постов и конфигурации может получиться совсем немного. Пожалуйста, добавьте некоторые операторы отмены и var_dump()в конце измените то, что вы хотите увидеть и где вы хотите это увидеть. Это просто доказательство концепции.

кайзер
источник
6

Хотя это не лучший способ решить эту проблему (ответ @ kaiser), чтобы ответить на вопрос напрямую, фактические результаты запроса будут, $loop->postsи $loop2->posts, таким образом ...

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

... должен работать, но вам нужно будет использовать foreachцикл, а не WP_Queryоснованную на нем стандартную структуру цикла, поскольку слияние таких запросов приведет к нарушению WP_Queryмета-данных объекта о цикле.

Вы также можете сделать это:

$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'));

Конечно, эти решения представляют собой множественные запросы, поэтому @ Kaiser является лучшим подходом для таких случаев, когда WP_Queryможно обрабатывать необходимую логику.

s_ha_dum
источник
3

На самом деле есть meta_query(или WP_Meta_Query) - который принимает массив массивов - где вы можете искать _thumbnail_idстроки. Если затем вы проверите EXISTS, вы можете получить только те, которые имеют это поле. Объединяя это с catаргументом, вы будете получать только сообщения, которые назначены категории с идентификатором 1и с прикрепленным миниатюрой. Если вы затем упорядочите их по значку meta_value_num, то вы фактически упорядочите их по идентификатору миниатюры от наименьшего к наибольшему (как указано с помощью orderи ASC). Вам не нужно указывать, valueкогда вы используете в EXISTSкачестве compareзначения.

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

Теперь, зацикливая их, вы можете собрать все идентификаторы и использовать их в эксклюзивном выражении для вспомогательного запроса:

$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

Конечно, вы можете быть намного умнее и просто изменить SQL-оператор внутри, pre_get_postsчтобы не тратить основной запрос. Вы также можете просто выполнить первый запрос ( $thumbsUpвыше) внутри pre_get_postsобратного вызова фильтра.

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;
}

Это изменило основной запрос, поэтому мы получим только сообщения с миниатюрами. Теперь мы можем (как показано в первом запросе выше) собрать идентификаторы во время основного цикла, а затем добавить второй запрос, который отображает остальные сообщения (без миниатюры).

Кроме того, вы можете стать еще умнее, изменить posts_clausesи изменить запрос непосредственно по мета-значению. Посмотрите на этот ответ, так как текущий является лишь отправной точкой.

кайзер
источник
3

На самом деле вам нужен третий запрос, чтобы получить все сообщения одновременно. Затем вы изменяете первые два запроса, чтобы не возвращать сообщения, а только идентификаторы сообщений в формате, с которым вы можете работать.

'fields'=>'ids'Параметр будет сделать запрос на самом деле возвращает массив соответствующих почтовых идентификационных номеров. Но нам не нужен весь объект запроса, поэтому мы используем вместо них get_posts.

Сначала получите идентификаторы сообщений, которые нам нужны:

$imageposts = get_posts( array('fields'=>'ids', 'meta_key' => '_thumbnail_id', 'cat' => 1 ) );
$nonimageposts = get_posts( array('fields'=>'ids', 'meta_key' => '', 'cat' => 1 ) );

$ imageposts и $ nonimageposts теперь будут массивом идентификаторов записей, поэтому мы объединяем их

$mypostids = array_merge( $imageposts, $nonimageposts );

Удалите дублирующиеся идентификационные номера ...

$mypostids = array_unique( $mypostids );

Теперь сделайте запрос, чтобы получить фактические сообщения в указанном порядке:

$loop = new WP_Query( array('post__in' => $mypostids, 'ignore_sticky_posts' => true, 'orderby' => 'post__in' ) );

Переменная $ loop теперь является объектом WP_Query с вашими сообщениями в нем.

эфирное масло
источник
Спасибо за это. Обнаружено, что это наименее сложное решение для хранения одноконтурной структуры и несложных расчетов разбиения на страницы.
Джей Нили