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

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

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

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

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

Один запрос

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

Предпосылки

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

  1. Метод accept() позволяет определить критерии, которые позволяют циклически перемещать элемент – или нет.
  2. Внутри этого метода мы используем WP_Query::the_post() , поэтому вы можете просто использовать каждый тег шаблона в цикле файлов шаблонов.
  3. А также мы отслеживаем цикл и перематываем сообщения, когда мы достигаем последнего элемента. Это позволяет прокручивать бесконечное количество циклов без сброса нашего запроса.
  4. Существует один настраиваемый метод, который не является частью спецификаций FilterIterator : deny() . Этот метод особенно удобен, поскольку он содержит только наше «процесс или нет» -статье, и мы можем легко перезаписать его в последующих классах, не требуя ничего знать, кроме тегов шаблона 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() всегда будет указывать на наш $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() в конце var_dump() s то, что вы хотите увидеть, и где вы хотите его увидеть. Это просто доказательство концепции.

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

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

… должен работать, но вам нужно использовать цикл foreach а не стандартную структуру цикла на основе WP_Query как слияние запросов, подобных этому, приведет к 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 может обрабатывать необходимую логику.

На самом деле есть meta_query (или WP_Meta_Query ) – который принимает массив массивов – где вы можете искать строки _thumbnail_id . Если вы затем проверите EXISTS , вы сможете получить только те, у которых есть это поле. Объединяя это с аргументом cat , вы получите только сообщения, присваиваемые категории с идентификатором 1 и прикрепленным миниатюром. Если вы затем закажете их с помощью meta_value_num , тогда вы фактически закажете их по наименьшему наименьшему 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 и изменить запрос непосредственно по мета-значению. Взгляните на этот ответ, поскольку текущий является лишь отправной точкой.

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

Параметр '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 с вашими сообщениями в нем.