Comment fusionner deux requêtes ensemble

10

J'essaie de classer les articles dans une catégorie en affichant d'abord les articles avec des images, puis les articles sans images en dernier. J'ai réussi à le faire en exécutant deux requêtes et maintenant je veux fusionner les deux requêtes ensemble.

J'ai les éléments suivants:

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

Mais lorsque j'essaie de visualiser la page, j'obtiens l'erreur suivante:

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

J'ai ensuite essayé de convertir array_merge en un objet, mais j'ai eu l'erreur suivante:

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

Comment puis-je corriger cette erreur?

Howli
la source

Réponses:

8

Une seule requête

J'ai réfléchi un peu plus à cela et il y a une chance que vous puissiez aller avec une seule / la requête principale. Ou en d'autres termes: pas besoin de deux requêtes supplémentaires lorsque vous pouvez travailler avec celle par défaut. Et dans le cas où vous ne pouvez pas travailler avec une valeur par défaut, vous n'aurez pas besoin de plus d'une seule requête, peu importe le nombre de boucles que vous souhaitez diviser.

Conditions préalables

Vous devez d'abord définir (comme indiqué dans mon autre réponse) les valeurs nécessaires dans un pre_get_postsfiltre. Là, vous aurez probablement mis posts_per_pageet cat. Exemple sans 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 ) );

Construire une base

La prochaine chose dont nous avons besoin est un petit plugin personnalisé (ou simplement le mettre dans votre functions.phpfichier si cela ne vous dérange pas de le déplacer pendant les mises à jour ou les changements de thème):

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

Ce plugin fait une chose: il utilise PHP SPL (Standard PHP Library) et ses interfaces et itérateurs. Ce que nous avons maintenant est un FilterIteratorqui nous permet de supprimer facilement les éléments de notre boucle. Il étend l'itérateur de filtre PHP SPL afin que nous n'ayons pas à tout régler. Le code est bien commenté, mais voici quelques notes:

  1. La accept()méthode permet de définir des critères permettant de boucler l'élément - ou non.
  2. À l'intérieur de cette méthode que nous utilisons WP_Query::the_post(), vous pouvez simplement utiliser chaque balise de modèle dans votre boucle de fichiers de modèle.
  3. Et nous surveillons également la boucle et rembobinons les messages lorsque nous atteignons le dernier élément. Cela permet de boucler à travers une quantité infinie de boucles sans réinitialiser notre requête.
  4. Il y a une méthode personnalisée qui ne fait pas partie des FilterIteratorspécifications: deny(). Cette méthode est particulièrement pratique car elle ne contient que notre déclaration "processus ou non" et nous pouvons facilement la remplacer dans les classes ultérieures sans avoir besoin de savoir quoi que ce soit en dehors des balises de modèle WordPress.

Comment boucler?

Avec ce nouveau Iterator, nous ne avons pas besoin if ( $customQuery->have_posts() )et while ( $customQuery->have_posts() )plus. Nous pouvons aller avec une simple foreachdéclaration car tous les contrôles nécessaires sont déjà effectués pour nous. Exemple:

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

Enfin, nous n'avons besoin que d'une foreachboucle par défaut . Nous pouvons même supprimer the_post()et toujours utiliser toutes les balises de modèle. L' $postobjet global restera toujours synchronisé.

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

Boucles subsidiaires

Maintenant, la bonne chose est que chaque filtre de requête ultérieur est assez facile à gérer: définissez simplement la deny()méthode et vous êtes prêt à passer à votre prochaine boucle. $this->current()pointera toujours vers notre message actuellement en boucle.

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

Comme nous avons défini que nous deny()bouclons maintenant chaque publication qui a une vignette, nous pouvons alors instantanément boucler toutes les publications sans vignette:

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

Essaye-le.

Le plugin de test suivant est disponible en tant que Gist sur GitHub. Téléchargez-le et activez-le simplement. Il génère / exporte l'ID de chaque message en boucle en tant que rappel sur l' loop_startaction. Cela signifie que cela pourrait obtenir un peu de sortie en fonction de votre configuration, du nombre de publications et de la configuration. Veuillez ajouter quelques instructions d'abandon et modifier le var_dump()s à la fin pour ce que vous voulez voir et où vous voulez le voir. Ce n'est qu'une preuve de concept.

kaiser
la source
6

Bien que ce ne soit pas le meilleur moyen de résoudre ce problème (la réponse de @ kaiser est), pour répondre directement à la question, les résultats de la requête seront $loop->postset $loop2->posts, donc ...

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

... devrait fonctionner, mais vous devez utiliser une foreachboucle et non la WP_Querystructure de boucle standard basée, car la fusion de requêtes comme celle-ci cassera les WP_Query"métadonnées" de l' objet concernant la boucle.

Vous pouvez également le faire:

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

Bien sûr, ces solutions représentent plusieurs requêtes, c'est pourquoi @ Kaiser est la meilleure approche pour des cas comme celui-ci où WP_Querypeut gérer la logique nécessaire.

s_ha_dum
la source
3

En fait, il y a meta_query(ou WP_Meta_Query) - qui prend un tableau de tableaux - où vous pouvez rechercher les _thumbnail_idlignes. Si vous vérifiez ensuite EXISTS, vous ne pouvez obtenir que ceux qui ont ce champ. En combinant cela avec l' catargument, vous n'obtiendrez que des publications affectées à la catégorie avec l'ID de 1et auxquelles une vignette est attachée. Si vous les commandez ensuite par le meta_value_num, vous les commanderez en fait par l'ID de miniature du plus petit au plus grand (comme indiqué avec orderet ASC). Vous n'avez pas besoin de spécifier valuequand vous utilisez EXISTScomme comparevaleur.

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

Maintenant, lorsque vous les parcourez en boucle, vous pouvez collecter tous les ID et les utiliser dans une instruction exclusive pour la requête subsidiaire:

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

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

        // do display/rendering stuff here
    }
}

Vous pouvez maintenant ajouter votre deuxième requête. Pas besoin wp_reset_postdata()ici - tout est dans la variable et non dans la requête principale.

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

Bien sûr, vous pouvez être beaucoup plus intelligent et simplement modifier l'instruction SQL à l'intérieur pre_get_postspour ne pas gaspiller la requête principale. Vous pouvez tout aussi bien faire la première requête ( $thumbsUpci-dessus) dans un pre_get_postsrappel de filtre.

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

Cela a modifié la requête principale, nous n'obtiendrons donc que des publications auxquelles une vignette est jointe. Maintenant, nous pouvons (comme indiqué dans la 1ère requête ci-dessus) collecter les ID pendant la boucle principale, puis ajouter une deuxième requête qui affiche le reste des messages (sans miniature).

En plus de cela, vous pouvez devenir encore plus intelligent et altérer posts_clauseset modifier la requête directement par la méta-valeur. Jetez un oeil à cette réponse car la réponse actuelle n'est qu'un point de départ.

kaiser
la source
3

Ce dont vous avez besoin est en fait une troisième requête pour obtenir tous les messages à la fois. Ensuite, vous modifiez vos deux premières requêtes pour ne pas renvoyer les publications, mais uniquement les ID de publication dans un format avec lequel vous pouvez travailler.

Le 'fields'=>'ids'paramètre fera qu'une requête retournera en fait un tableau de numéros ID de publication correspondants. Mais nous ne voulons pas l'intégralité de l'objet de requête, nous utilisons donc get_posts à la place.

Tout d'abord, obtenez les ID de publication dont nous avons besoin:

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

$ imageposts et $ nonimageposts seront désormais tous deux un tableau de numéros d'identification de publication, nous les fusionnons donc

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

Éliminez les numéros d'identification en double ...

$mypostids = array_unique( $mypostids );

Maintenant, faites une requête pour obtenir les messages réels dans l'ordre spécifié:

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

La variable $ loop est maintenant un objet WP_Query avec vos publications dedans.

Otto
la source
Merci pour cela. J'ai trouvé que c'était la solution la moins compliquée pour conserver une structure de boucle et des calculs de pagination simples.
Jay Neely