Comment fusionner deux requêtes

10

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

J'ai les informations suivantes:

<?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 d'afficher la page, le message d'erreur suivant s'affiche:

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

J'ai ensuite essayé de transtyper array_merge en un objet, mais le message d'erreur suivant s'affiche:

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

Comment puis-je corriger cette erreur?

    
posée Howli 16.01.2014 - 16:12

4 réponses

8

Une seule requête

Si vous y réfléchissez un peu plus, il est possible que vous puissiez utiliser une requête unique / principale. Ou en d'autres termes: vous n'avez pas besoin de deux requêtes supplémentaires lorsque vous pouvez utiliser la requête par défaut. Et si vous ne pouvez pas utiliser une commande par défaut, vous n'aurez besoin que de plusieurs requêtes, quel que soit le nombre de boucles pour lesquelles vous souhaitez scinder la requête.

Prérequis

Tout d'abord, vous devez définir (comme indiqué dans mon autre réponse) les valeurs nécessaires dans un filtre pre_get_posts . Là, vous allez probablement définir posts_per_page et cat . Exemple sans le 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 insérez-le simplement dans votre fichier functions.php si cela ne vous dérange pas de le déplacer lors des mises à jour ou des 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 plug-in fait une chose: il utilise le PHP SPL (bibliothèque standard PHP) et ses interfaces et itérateurs. Nous avons maintenant un FilterIterator qui nous permet de supprimer facilement des éléments de notre boucle. Il étend le filtre itérateur PHP SPL afin que nous n'ayons pas à tout configurer. Le code est bien commenté, mais voici quelques notes:

  1. La méthode accept() permet de définir des critères autorisant ou non la mise en boucle de l'élément.
  2. À l'intérieur de cette méthode, nous utilisons WP_Query::the_post() . Vous pouvez donc utiliser simplement toutes les balises de modèle de votre boucle de fichiers de modèle.
  3. Nous surveillons également la boucle et rembobinons les messages lorsque nous arrivons au dernier élément. Cela permet de parcourir une quantité infinie de boucles sans réinitialiser notre requête.
  4. Il existe une méthode personnalisée qui ne fait pas partie des spécifications FilterIterator : deny() . Cette méthode est particulièrement pratique car elle ne contient que notre instruction "process or not" et nous pouvons facilement l’écraser dans les classes ultérieures sans avoir besoin de rien savoir en dehors des balises de modèle WordPress.

Comment faire une boucle?

Avec ce nouvel itérateur, nous n'avons plus besoin de if ( $customQuery->have_posts() ) et while ( $customQuery->have_posts() ) . Nous pouvons utiliser une simple déclaration foreach car toutes les vérifications nécessaires sont déjà effectuées 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 de rien de plus qu'une boucle foreach par défaut. Nous pouvons même abandonner the_post() et continuer à utiliser toutes les balises de modèle. L’objet global $post restera toujours synchronisé.

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

Boucles subsidiaires

Ce qui est bien, c’est que tous les filtres de requête ultérieurs sont assez faciles à manipuler: définissez simplement la méthode deny() et vous êtes prêt pour votre prochaine boucle. $this->current() désignera toujours notre publication en boucle.

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

Comme nous avons défini le deny() en boucle pour chaque publication contenant une vignette, nous pouvons alors boucler instantanément toutes les publications sans vignette:

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

Testez-le.

Le plug-in de test suivant est disponible en tant que Gist sur GitHub. Il suffit de télécharger et de l'activer. Il génère / affiche l'ID de chaque publication en boucle en tant que rappel sur l'action loop_start . Cela signifie que la sortie peut être assez lourde en fonction de votre configuration, du nombre de publications et de la configuration. Ajoutez quelques instructions d'abandon et modifiez le var_dump() s à la fin en ce que vous voulez voir et où vous voulez le voir. C'est juste une preuve de concept.

    
réponse donnée kaiser 16.01.2014 - 23:45
6

Bien que ce ne soit pas la meilleure façon 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 en $loop->posts et $loop2->posts , donc ...

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

... devrait fonctionner, mais vous auriez besoin d'une boucle foreach et non de la structure de boucle standard basée sur WP_Query , car la fusion de telles requêtes romprait les données "méta" de l'objet WP_Query concernant la boucle. .

Vous pouvez également faire ceci:

$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 entendu, ces solutions représentent plusieurs requêtes. C'est pourquoi @ Kaiser est la meilleure approche pour les cas de ce type où WP_Query peut gérer la logique nécessaire.

    
réponse donnée s_ha_dum 16.01.2014 - 17:01
3

En fait, il existe meta_query (ou WP_Meta_Query ) - qui contient un tableau de tableaux - dans lequel vous pouvez effectuer une recherche. pour les _thumbnail_id rangées. Si vous vérifiez ensuite EXISTS , vous ne pouvez obtenir que ceux qui possèdent ce champ. En combinant ceci avec l'argument cat , vous obtiendrez uniquement les publications attribuées à la catégorie avec l'ID de 1 et auxquelles une vignette est attachée. Si vous les commandez ensuite en fonction du meta_value_num , vous les commanderez en fonction de l'ID de vignette allant du plus petit au plus élevé (comme indiqué avec order et ASC ). Vous n'avez pas à spécifier le value lorsque vous utilisez EXISTS en tant que compare valeur.

$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 passez en boucle entre eux, vous pouvez collecter tous les identifiants 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 seconde requête. Pas besoin de 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 dans pre_get_posts pour ne pas gaspiller la requête principale. Vous pouvez également effectuer simplement la première requête ( $thumbsUp ci-dessus) dans un rappel de filtre pre_get_posts filter.

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

Ceci a modifié la requête principale, nous n'obtiendrons que les publications auxquelles une vignette est jointe. Nous pouvons maintenant (comme indiqué dans la 1ère requête ci-dessus) collecter les identifiants lors de la boucle principale, puis ajouter une seconde requête affichant le reste des publications (sans vignette).

En dehors de cela, vous pouvez être encore plus intelligent, modifier posts_clauses et modifier la requête directement en fonction de la méta-valeur. Jetez un coup d’œil à cette réponse , car la réponse actuelle n’est qu’un point de départ.

    
réponse donnée kaiser 16.01.2014 - 17:00
3

Ce dont vous avez besoin est en réalité une troisième requête pour obtenir tous les articles en même temps. Ensuite, vous modifiez vos deux premières requêtes pour ne pas renvoyer les publications, mais uniquement les identifiants de publication dans un format avec lequel vous pouvez travailler.

Le paramètre 'fields'=>'ids' fera en sorte qu'une requête retourne en réalité un tableau de numéros d'ID de publication correspondants. Mais nous ne voulons pas de l'objet de requête entier, nous utilisons donc get_posts pour ceux-ci.

Tout d'abord, obtenez les identifiants 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 constitueront désormais un tableau de numéros d'identification, nous les fusionnons donc

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

Éliminez les identifiants en double ...

$mypostids = array_unique( $mypostids );

Maintenant, lancez une requête pour obtenir les publications réelles 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 contenant vos publications.

    
réponse donnée Otto 16.01.2014 - 20:30

Lire d'autres questions sur les étiquettes