Cum să unifici două interogări în WordPress

16 ian. 2014, 17:12:41
Vizualizări: 14.4K
Voturi: 14

Încerc să ordonez postările dintr-o categorie afișând mai întâi postările cu imagini și apoi cele fără imagini. Am reușit să fac asta rulând două interogări separate și acum vreau să le unific.

Am următorul cod:

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

Când încerc să vizualizez pagina, primesc următoarea eroare:

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

Am încercat apoi să transform rezultatul array_merge într-un obiect, dar am primit altă eroare:

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

Cum pot rezolva această problemă?

0
Toate răspunsurile la întrebare 4
0

Un singur query

M-am gândit puțin la asta și există șansa să poți merge cu un singur query/query-ul principal. Cu alte cuvinte: Nu este nevoie de două query-uri suplimentare atunci când poți lucra cu cel implicit. Și în cazul în care nu poți lucra cu cel implicit, nu vei avea nevoie de mai mult de un singur query indiferent de câte loop-uri vrei să împarți query-ul.

Cerințe preliminare

Mai întâi trebuie să setezi (după cum este arătat în celălalt răspuns al meu) valorile necesare într-un filtru pre_get_posts. Acolo probabil vei seta posts_per_page și cat. Exemplu fără filtrul pre_get_posts:

$catID = 1;
$catQuery = new WP_Query( array(
    'posts_per_page' => -1,
    'cat'            => $catID,
) );
// Adaugă un titlu:
printf( '<h1>%s</h1>', number_format_i18n( $catQuery->found_posts )
    .__( " Postări în categoria ", 'YourTextdomain' )
    .get_cat_name( $catID ) );

Construirea unei baze

Următorul lucru de care avem nevoie este un mic plugin personalizat (sau pur și simplu pune-l în fișierul tău functions.php dacă nu te deranjează să îl muți în timpul actualizărilor sau schimbărilor de teme):

<?php
/**
 * Plugin Name: (#130009) Merge Two Queries
 * Description: "Îmbină" două query-uri folosind un <code>RecursiveFilterIterator</code> pentru a împărți un query principal în două query-uri
 * 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;

        // Salvează timp de procesare salvând-o o singură dată
        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;

        // Schimbă indexul, Configurează datele postării, etc.
        $this->wp_query->the_post();

        // Ultima postare WP_Post atinsă: Configurează WP_Query pentru următorul loop
        $this->wp_query->current_post === $this->wp_query->query_vars['posts_per_page'] -1
            AND $this->wp_query->rewind_posts();

        // Nu îndeplinește criteriile? Renunță.
        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;
    }
}

Acest plugin face un singur lucru: Utilizează PHP SPL (Standard PHP Library) și interfețele și iteratorii acesteia. Ceea ce avem acum este un FilterIterator care ne permite să eliminăm convenabil elemente din loop-ul nostru. Extinde iteratorul de filtrare PHP SPL, așa că nu trebuie să setăm totul. Codul este bine comentat, dar iată câteva note:

  1. Metoda accept() permite definirea criteriilor care permit bucla elementului - sau nu.
  2. În această metodă folosim WP_Query::the_post(), astfel încât poți folosi simplu orice tag de șablon în loop-ul fișierelor tale de șablon.
  3. Și de asemenea monitorizăm loop-ul și resetăm postările când atingem ultimul element. Acest lucru permite parcurgerea unui număr infinit de loop-uri fără a reseta query-ul nostru.
  4. Există o metodă personalizată care nu face parte din specificațiile FilterIterator: deny(). Această metodă este deosebit de convenabilă, deoarece conține doar declarația noastră "procesează sau nu" și o putem suprascrie ușor în clasele ulterioare fără a fi nevoie să știm altceva decât tag-urile de șablon WordPress.

Cum să faci loop?

Cu acest nou Iterator, nu mai avem nevoie de if ( $customQuery->have_posts() ) și while ( $customQuery->have_posts() ). Putem folosi o simplă declarație foreach, deoarece toate verificările necesare sunt deja făcute pentru noi. Exemplu:

global $wp_query;
// Mai întâi avem nevoie de un ArrayObject făcut din postările actuale
$arrayObj = new ArrayObject( $wp_query->get_posts() );
// Apoi trebuie să îl aruncăm în noul nostru Iterator de filtrare personalizat
// Trecem obiectul $wp_query ca al doilea argument pentru a ține evidența acestuia
$primaryQuery = new ThumbnailFilter( $arrayObj->getIterator(), $wp_query );

În final, nu avem nevoie de mai mult decât un loop foreach standard. Putem chiar să renunțăm la the_post() și să folosim în continuare toate tag-urile de șablon. Obiectul global $post va rămâne întotdeauna sincronizat.

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

Loop-uri secundare

Acum, partea frumoasă este că fiecare filtru de query ulterior este destul de ușor de gestionat: Pur și simplu definește metoda deny() și ești gata să mergi pentru următorul tău loop. $this->current() va indica întotdeauna spre postarea noastră curentă din loop.

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

Deoarece am definit că acum deny() loop-ul pentru fiecare postare care are o miniatură, putem parcurge instantaneu toate postările fără miniatură:

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

Testează-l.

Următorul plugin de test este disponibil ca Gist pe GitHub. Pur și simplu încarcă și activează-l. Acesta afișează/dump-ează ID-ul fiecărei postări din loop ca callback pe acțiunea loop_start. Acest lucru înseamnă că poți obține o cantitate destul de mare de output în funcție de configurația ta, numărul de postări și configurație. Te rog adaugă câteva declarații de întrerupere și modifică var_dump()-urile de la final pentru a vedea ceea ce dorești și unde dorești să le vezi. Este doar o dovadă a conceptului.

17 ian. 2014 00:45:31
0

Deși aceasta nu este cea mai bună metodă de a rezolva această problemă (răspunsul lui @kaiser este mai bun), pentru a răspunde direct la întrebare, rezultatele efective ale interogării se vor afla în $loop->posts și $loop2->posts, deci...

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

...ar trebui să funcționeze, dar va trebui să utilizați o buclă foreach și nu structura standard bazată pe WP_Query, deoarece îmbinarea interogărilor în acest fel va afecta datele "meta" ale obiectului WP_Query despre buclă.

De asemenea, puteți face asta:

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

Desigur, aceste soluții implică interogări multiple, motiv pentru care abordarea lui @Kaiser este mai bună pentru cazuri de acest fel, în care WP_Query poate gestiona logica necesară.

16 ian. 2014 18:01:20
2

Ceea ce aveți nevoie este de fapt o a treia interogare pentru a obține toate articolele simultan. Apoi modificați primele două interogări pentru a nu returna articolele, ci doar ID-urile articolelor într-un format cu care puteți lucra.

Parametrul 'fields'=>'ids' va face ca o interogare să returneze de fapt un array cu numerele ID ale articolelor care se potrivesc. Dar nu dorim întregul obiect de interogare, așa că folosim get_posts pentru acestea.

Mai întâi, obțineți ID-urile articolelor de care avem nevoie:

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

$imageposts și $nonimageposts vor fi acum ambele un array cu numere ID ale articolelor, așa că le vom combina

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

Eliminăm ID-urile duplicate...

$mypostids = array_unique( $mypostids );

Acum, creați o interogare pentru a obține articolele reale în ordinea specificată:

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

Variabila $loop este acum un obiect WP_Query cu articolele dvs. în el.

16 ian. 2014 21:30:38
Comentarii

Mulțumesc pentru asta. Am constatat că aceasta este cea mai simplă soluție pentru a păstra o structură de buclă unică și calcule de paginare necomplicate.

Jay Neely Jay Neely
7 mar. 2016 19:46:29

Aceasta pare a fi o soluție simplă și mai versatilă. +1 soluție de la dl. WordPress în persoană.

samjco-com samjco-com
19 ian. 2021 08:49:43
0

De fapt, există meta_query (sau WP_Meta_Query) - care primește un array de array-uri - unde poți căuta rândurile _thumbnail_id. Dacă verifici pentru EXISTS, poți obține doar cele care au acest câmp. Combinând asta cu argumentul cat, vei obține doar postările care sunt atribuite categoriei cu ID-ul 1 și care au o imagine miniatură atașată. Dacă le ordonezi după meta_value_num, atunci le vei ordona efectiv după ID-ul imaginii miniatură de la cel mai mic la cel mai mare (precum este specificat cu order și ASC). Nu este necesar să specifici value când folosești EXISTS ca valoare pentru compare.

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

Acum, când parcurgi aceste postări, poți colecta toate ID-urile și să le folosești într-o declarație exclusivă pentru interogarea secundară:

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

        // colectează-le
        $postsWithThumbnails[] = get_the_ID();

        // faci aici afișarea/randarea
    }
}

Acum poți adăuga a doua interogare. Nu este nevoie de wp_reset_postdata() aici - totul este în variabilă și nu în interogarea principală.

$noThumbnails = new WP_Query( array(
    'cat'          => 1,
    'post__not_in' => $postsWithThumbnails
) );
// Parcurge aceste postări

Desigur, poți fi mult mai inteligent și să modifici direct instrucțiunea SQL în interiorul pre_get_posts pentru a nu irosi interogarea principală. De asemenea, poți face pur și simplu prima interogare ($thumbsUp de mai sus) în interiorul unui callback de filtru 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;

    // Necesar doar dacă această interogare este pentru arhiva categoriei cu 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' );

    // În cazul în care nu suntem pe pagina de arhivă a categoriei cat = 1, avem nevoie de următoarele:
    $query->set( 'category__in', 1 );

    return $query;
}

Aceasta modifică interogarea principală, așa că vom obține doar postările care au o imagine miniatură atașată. Acum putem (așa cum este arătat în prima interogare de mai sus) colecta ID-urile în timpul buclei principale și apoi adăuga o a doua interogare care afișează restul postărilor (fără imagine miniatură).

În afară de asta, poți fi și mai inteligent și să modifici posts_clauses pentru a modifica direct interogarea și a ordona după valoarea meta. Aruncă o privire la acest răspuns deoarece cel actual este doar un punct de plecare.

16 ian. 2014 18:00:53