Come unire due query insieme


10

Sto cercando di ordinare i post in una categoria mostrando prima i post con immagini e poi i post senza immagini. Sono riuscito a farlo eseguendo due query e ora voglio unire le due query insieme.

Ho il seguente:

<?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(); ?>

Ma quando provo a visualizzare la pagina, ottengo il seguente errore:

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

Ho quindi provato a trasmettere array_merge a un oggetto, ma ho riscontrato il seguente errore:

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

Come posso correggere questo errore?

Risposte:


8

Una singola query

Ci ho pensato un po 'di più e c'è la possibilità che tu possa andare con una singola / la query principale. O in altre parole: non è necessario per altre due query quando è possibile lavorare con quella predefinita. E nel caso in cui non sia possibile lavorare con uno predefinito, non sarà necessario più di una singola query, indipendentemente dal numero di loop che si desidera dividere la query.

Prerequisiti

Per prima cosa devi impostare (come mostrato nella mia altra risposta) i valori necessari all'interno di un pre_get_postsfiltro. Lì probabilmente avrai impostato posts_per_pagee cat. Esempio senza il 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 ) );

Costruire una base

La prossima cosa di cui abbiamo bisogno è un piccolo plug-in personalizzato (o semplicemente inserirlo nel tuo functions.phpfile se non ti dispiace spostarlo durante gli aggiornamenti o le modifiche del tema):

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

Questo plugin fa una cosa: utilizza PHP SPL (Standard PHP Library) e le sue interfacce e iteratori. Ciò che abbiamo ora è un FilterIteratorche ci consente di rimuovere comodamente gli articoli dal nostro loop. Estende l'Iteratore filtro PHP SPL in modo da non dover impostare tutto. Il codice è ben commentato, ma ecco alcune note:

  1. Il accept()metodo consente di definire criteri che consentono il looping dell'articolo - oppure no.
  2. All'interno di quel metodo usiamo WP_Query::the_post(), quindi puoi semplicemente usare ogni tag template nel tuo ciclo di file template.
  3. E stiamo anche monitorando il loop e riavvolgendo i post quando raggiungiamo l'ultimo elemento. Ciò consente di eseguire il looping attraverso un numero infinito di loop senza ripristinare la nostra query.
  4. C'è un metodo personalizzato che non fa parte delle FilterIteratorspecifiche: deny(). Questo metodo è particolarmente conveniente in quanto contiene solo la nostra dichiarazione "process or not" e possiamo facilmente sovrascriverlo nelle classi successive senza bisogno di sapere nulla a parte i tag dei template di WordPress.

Come eseguire il loop?

Con questa nuova Iterator, non abbiamo bisogno if ( $customQuery->have_posts() )e while ( $customQuery->have_posts() )più. Possiamo andare con una semplice foreachdichiarazione poiché tutti i controlli necessari sono già stati eseguiti per noi. Esempio:

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

Finalmente non abbiamo bisogno di altro che un foreachloop predefinito . Possiamo persino eliminare the_post()e utilizzare ancora tutti i tag modello. L' $postoggetto globale rimarrà sempre sincronizzato.

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

Anelli secondari

Ora la cosa bella è che ogni filtro di query successivo è abbastanza facile da gestire: basta definire il deny()metodo e sei pronto per il tuo prossimo ciclo. $this->current()indicherà sempre il nostro post attualmente in loop.

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

Dato che abbiamo definito che ora eseguiamo il deny()looping di tutti i post che hanno una miniatura, possiamo quindi eseguire immediatamente il loop di tutti i post senza una miniatura:

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

Provalo.

Il seguente plug-in di prova è disponibile come Gist su GitHub. Basta caricarlo e attivarlo. Emette / scarica l'ID di ogni post in loop come callback loop_startsull'azione. Ciò significa che potrebbe ottenere un bel po 'di output a seconda della configurazione, del numero di post e della configurazione. Si prega di aggiungere alcune dichiarazioni di interruzione e modificare le var_dump()s alla fine di ciò che si desidera vedere e dove si desidera vederlo. È solo una prova di concetto.


6

Anche se questo non è il modo migliore per risolvere questo problema (la risposta di @ kaiser è), per rispondere direttamente alla domanda, i risultati effettivi della query saranno presenti $loop->postse $loop2->posts, quindi ...

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

... dovrebbe funzionare, ma dovresti usare un foreachloop e non la WP_Querystruttura di loop standard basata poiché l'unione di query del genere interromperà i WP_Query"meta" dati dell'oggetto sul loop.

Puoi anche fare questo:

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

Naturalmente, queste soluzioni rappresentano più query, motivo per cui @ Kaiser è l'approccio migliore per casi come questo in cui è WP_Querypossibile gestire la logica necessaria.


3

In realtà c'è meta_query(o WP_Meta_Query) - che accetta una matrice di array - dove puoi cercare le _thumbnail_idrighe. Se poi controlli EXISTS, sei in grado di ottenere solo quelli che hanno questo campo. Combinando questo con l' catargomento, otterrai solo post assegnati alla categoria con l'ID di 1e con un'anteprima allegata. Se poi li ordini per meta_value_num, allora li ordinerai in base all'ID della miniatura dal più basso al più alto (come indicato con ordere ASC). Non è necessario specificare valuequando si utilizza EXISTScome comparevalore.

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

Ora, quando si eseguono il ciclo attraverso di essi, è possibile raccogliere tutti gli ID e utilizzarli in un'istruzione esclusiva per la query sussidiaria:

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

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

        // do display/rendering stuff here
    }
}

Ora puoi aggiungere la tua seconda query. Non c'è bisogno di wp_reset_postdata()qui: tutto è nella variabile e non nella query principale.

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

Naturalmente puoi essere molto più intelligente e semplicemente modificare l'istruzione SQL all'interno pre_get_postsper non sprecare la query principale. Puoi anche semplicemente fare la prima query ( $thumbsUpsopra) all'interno di un pre_get_postscallback del filtro.

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

Ciò ha modificato la query principale, quindi avremo solo post a cui è allegata una miniatura. Ora possiamo (come mostrato nella prima query sopra) raccogliere gli ID durante il ciclo principale e quindi aggiungere una seconda query che visualizza il resto dei post (senza una miniatura).

A parte questo, puoi diventare ancora più intelligente e alterare posts_clausese modificare la query in modo diretto in base al meta valore. Dai un'occhiata a questa risposta poiché quella attuale è solo un punto di partenza.


3

Ciò di cui hai bisogno è in realtà una terza query per ottenere tutti i post contemporaneamente. Quindi si modificano le prime due query per non restituire i post, ma solo gli ID dei post in un formato su cui è possibile lavorare.

Il 'fields'=>'ids'parametro farà in modo che una query restituisca effettivamente una matrice di numeri ID post corrispondenti. Ma non vogliamo l'intero oggetto query, quindi utilizziamo get_posts per questi.

Innanzitutto, ottieni gli ID dei post di cui abbiamo bisogno:

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

$ imageposts e $ nonimageposts ora saranno entrambi una matrice di numeri ID post, quindi li uniamo

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

Elimina i numeri ID duplicati ...

$mypostids = array_unique( $mypostids );

Ora, fai una query per ottenere i post effettivi nell'ordine specificato:

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

La variabile $ loop ora è un oggetto WP_Query con i tuoi messaggi al suo interno.


Grazie per questo. Ho scoperto che questa è la soluzione meno complicata per mantenere una struttura ad anello e calcoli di impaginazione semplici.
Jay Neely,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.