Ist es möglich, das Abrufen von Posts durch WP_Query vollständig zu stoppen?

8

Ich versuche, WP Redis zu verwenden, um das gesamte $ wp_query- Objekt mit dem Schlüssel $ query_vars_hash zwischenzuspeichern .

So $wp_querywurde hinzugefügt zu $wp_object_cache:

add_action('wp', function($wp)
{
    if ( is_admin() ) return;

    global $wp_query;

    if ( !wp_cache_get($wp_query->query_vars_hash, 'globals') )
    {
        wp_cache_add($wp_query->query_vars_hash, $wp_query, 'globals');
    }
});

Dann muss ich überprüfen, ob eine Abfrage bereits zwischengespeichert wurde, bevor WP_QueryBeiträge abgerufen werden können:

add_action('pre_get_posts', function($query)
{
    if ( is_admin() ) return;

    $cached_query = wp_cache_get($query->query_vars_hash, 'globals');

    if ($cached_query)
    {
        $GLOBALS['wp_query'] = &$cached_query;

        return; // Return immediately to prevent retrieving posts again.
    }
});

Problem :

returnoder exitfunktioniert in diesem Fall nicht. Dann WP_Querywird immer noch Datenbank getroffen, um Beiträge wieder abzurufen.

Frage :

Ist es unabhängig vom Plugin möglich, das WP_QueryAbrufen von Posts vollständig zu beenden ?

SarahCoding
quelle
Ich denke, das Plugin sollte damit umgehen ... Bist du sicher, dass du das richtig machst? Haben Sie in ihren Foren danach gefragt? Zu ihren Github-Themen?
Howdy_McGee
@Howdy_McGee Das Plugin verwendet dieselben Funktionen wie die Standard- WordPress-Caching-API . Der einzige Unterschied besteht darin, dass eine Verbindung zum Redis-Server hergestellt werden kann. Natürlich versuche ich auch, den richtigen Weg zu finden.
SarahCoding
Ich bin mir nicht sicher, warum die Abfrage Ihrer Meinung nach nicht ausgelöst wird. Rückkehr von der Aktion nicht durch Magie von der aufrufenden Funktion zurückkehren
Mark Kaplun
@ MarkKaplun Ich verdopple das auch, returnkönnte aber der einzige Befehl sein, den wir in diesem Fall aufrufen können.
SarahCoding
@ Dan, ich verstehe nicht, was Sie annehmen, Sie nehmen offensichtlich etwas an, was nicht wahr ist, wahrscheinlich auf PHP-Ebene
Mark Kaplun

Antworten:

11

Im Moment ist es nicht möglich.

Wenn ausgeführt 'pre_get_posts', ist es zu spät WP_Query, um eine Abfrage durchzuführen.

Wenn Sie selbst versuchen, eine nicht vorhandene Taxonomie abzufragen, wird WordPress selbst AND (0 = 1)zur WHEREKlausel der SQL-Abfrage hinzugefügt, um sicherzustellen, dass sehr schnell keine Ergebnisse zurückgegeben werden ...

Es gibt ein Trac-Ticket mit einem Patch, der wahrscheinlich im Kern von WP 4.6 landen wird und einen neuen Filter einführt : 'posts_pre_query'. Wenn Sie ein Array für diesen Filter zurückgeben, wird die WP_QueryVerarbeitung gestoppt und das bereitgestellte Array als Posts-Array verwendet.

Dies könnte Ihnen irgendwie helfen, das umzusetzen, was Sie versuchen zu tun.

Warten fot das, was Sie tun können , ist irgendwie hackish , der Trick selbst Kern nutzt auch ganz hackish ist.

Vor kurzem verwende ich einen Trick, wenn ich WordPress stoppen möchte, um Dinge zu tun, die ich nicht sauber stoppen kann: Ich löse eine Ausnahme und fange sie ab, um den Anwendungsfluss fortzusetzen.

Ich zeige Ihnen ein Beispiel. Beachten Sie, dass der gesamte Code hier vollständig ungetestet ist.

Schreiben wir zunächst eine benutzerdefinierte Ausnahme:

class My_StopWpQueryException extends Exception {

   private $query;

   public static forQuery(WP_Query $query) {
     $instance = new static();
     $instance->query = $query;

     return $instance;
   }

   public function wpQuery() {
     return $this->query;
   }
}

Die Ausnahme dient als eine Art DTO zum Transportieren eines Abfrageobjekts, sodass Sie es in einem catchBlock abrufen und verwenden können.

Besser mit Code erklärt:

function maybe_cached_query(WP_Query $query) {
    $cached_query = wp_cache_get($query->query_vars_hash, 'globals');
    if ($cached_query instanceof WP_Query)
       throw My_StopWpQueryException::forQuery($cached_query);
}

function cached_query_set(WP_Query $query) {
    $GLOBALS['wp_query'] = $query;
    $GLOBALS['wp_the_query'] = $query;
    // maybe some more fine-tuning here...
}

add_action('pre_get_posts', function(WP_Query $query) {
    if ($query->is_main_query() && ! is_admin()) {
        try {
           maybe_cached_query($query);
        } catch(My_StopWpQueryException $e) {
           cached_query_set($e->wpQuery());
        }
    }
});

Dies sollte mehr oder weniger funktionieren, es gibt jedoch viele Hooks, die Sie beispielsweise nicht "the_posts"auslösen werden, und vieles mehr. Wenn Sie Code haben, der einen dieser Hooks zum Auslösen verwendet, wird dieser unterbrochen.

Sie können die cached_query_setFunktion verwenden, um einige der Hooks auszulösen, die Ihr Theme / Plugin möglicherweise benötigt.

gmazzap
quelle
Warum funktioniert die Standardausnahmeklasse nicht? Es zeigt mir einen Fehler der nicht erfassten Ausnahme?
Summe
Es sollte mit Standardausnahme und einer öffentlichen Eigenschaft funktionieren, aber Sie müssen die Standardausnahme abfangen, wenn Sie sie @Sumit
gmazzap
Nun, ich habe es nur anhand dieses Beispiels gemacht. Aber ich bekomme einen nicht erfassten Ausnahmefehler. Ich habe das einfachste Beispiel für eine Ausnahme ausgeführt, do_actionsollte aber im tryBlock sein.
Sumit
Interessanter Ansatz, der an verschiedenen Stellen in WordPress angewendet werden kann, werde ich mir merken ;-) ps: DTO = Datenübertragungsobjekt ?
Birgire
@ Birgire ja :)
gmazzap
2

Dies ist eine PHP-Frage mehr als eine WordPress-Frage.

Wie @Mark kommentierte:

Rückkehr von der Aktion nicht durch Magie von der aufrufenden Funktion zurückkehren

Das ist wahr. Durch Platzieren returnin der Funktion wird die Funktion beendet, und durch Platzieren der Rückgabe in einer PHP-Datei wird die Datei beendet. Verwechseln Sie sich nicht mit dem PHP-Konstrukt exit(): P (Möglicherweise finden Sie auf SO eine bessere Antwort zu PHP return).

Und um deine Frage zu beantworten

Sie können die Last der Abfrage reduzieren, indem Sie eine einzelne Spalte anstelle der vollständigen Tabelle abrufen. Wie @birgire hier Entfernen Sie die Homepage-Abfrage

Vielleicht ist eine bessere Antwort noch zu kommen. Ich habe gerade das geteilt, was ich weiß :)

Sumit
quelle
1
@ Haben Sie viele Datenbanktreffer erhalten, nachdem Sie die Abfrageanforderung über den posts_requestFilter neutralisiert haben ? Mit diesem + einspaltigen Ansatz beenden wir WP_Queryfrüher als mit dem posts_pre_queryFilter. Achten Sie auch auf die klebrigen Pfosten mit, posts_pre_queryaber wir können sie mit $q->set( 'ignore_sticky_posts', 1 );z . B. im Beispiel hier entfernen .
Birgire
@birgire Sieht aus wie posts_pre_querynicht hilft. Ihre Lösung ist die bisher beste. :) Wenn Sie wissen, wie wir die Abfrage direkt danach beenden können pre_get_posts, könnte das großartig sein. Vielen Dank!
SarahCoding
@ Dan posts_pre_querywird ab 4.6 verfügbar sein;)
Sumit
Ein anderer Ansatz, der in den Sinn kommt, besteht darin, zu versuchen, die WP_QueryKlasse mit einer benutzerdefinierten get_posts()Methode zu erweitern, die möglicherweise frühzeitig vorhanden ist, parent::get_posts() und die die entsprechende Abfrage aufruft und damit überschreibt. Aber ich weiß nicht, ob das mit Ihrem Fall hier funktionieren oder Sinn machen würde ;-) @Dan
Birgire
1
Vielleicht könnte ein bisschen Aerosmith - Livin 'On The Edge dabei helfen ;-) @Dan
birgire
2

Dies wird in 4.6 (sofern bis zur Veröffentlichung keine Änderungen vorgenommen wurden) mit dem neuen posts_pre_queryFilter https://core.trac.wordpress.org/ticket/36687 ermöglicht

Mark Kaplun
quelle
@ Dan, das ist, was passiert, wenn Sie den Gedanken vervollständigen möchten, den Sie hatten, bevor Sie schlafen gingen und nicht die anderen Antworten zuerst lesen;)
Mark Kaplun
Bro, jetzt ist es zu spät. Ich werde diese Antworten später lesen
;-)
2

Ja, es ist möglich, je nachdem, was Sie zwischenspeichern möchten. Ich habe etwas Ähnliches getan, um die Hauptschleife auf unserer Homepage zwischenzuspeichern. Im Wesentlichen können Sie das posts_requestund verwenden posts_results, um die Abfrage zu entführen und stattdessen auf den Cache zuzugreifen, und dann auch found_posts, um die Paginierung zu korrigieren.

Wirklich grobes Beispiel aus unserem Code (ungetestet), aber Sie sollten Ihnen helfen, die Idee zu bekommen:

<?php
/**
 * Kill the query if we have the result in the cache
 * @var [type]
 */
add_filter( 'posts_request', function( $request, $query ) {
    if ( is_home() && $query->is_main_query() ) {

        $page = ( get_query_var( 'paged' ) ) ? get_query_var( 'paged' ) : 1;

        $key = 'homepage_query_cache_' . $page;

        if ( wp_cache_get( $key, 'cache_group' ) )
            $request = null;

    }

    return $request;
}, 10, 2 );

/**
 * Get the result from the cache and set it as the query result
 * Or add the query result to the cache if it's not there
 * @var [type]
 */
add_filter( 'posts_results', function( $posts, $query ) {

    if ( is_home() && $query->is_main_query() ) {

        $page = ( get_query_var( 'paged' ) ) ? get_query_var( 'paged' ) : 1;

        $key = 'homepage_query_cache_' . $page;

        if ( $cached_posts = wp_cache_get( $key, 'cache_group' ) ) {
            $posts = $cached_posts;
        } else {
            wp_cache_set( $key . '_found_posts', $query->found_posts, 'cache_group', HOUR_IN_SECONDS );
            wp_cache_set( $key, $posts, 'cache_group', HOUR_IN_SECONDS );
        }
    }

    return $posts;

}, 10, 2 );

/**
 * Correct the found posts number if we've hijacked the query results
 * @var [type]
 */
add_filter( 'found_posts', function( $num, $query ) {
    if ( is_home() && $query->is_main_query() ) {
        $page = ( get_query_var( 'paged' ) ) ? get_query_var( 'paged' ) : 1;

        $key = 'homepage_query_cache_' . $page;

        if ( $found_posts = wp_cache_get( $key . '_found_posts', 'cache_group' ) )
            $num = $found_posts;
    }

    return $num;
}, 10, 2 );

Mehr hier: https://www.reddit.com/r/Wordpress/comments/19crcn/best_practice_for_hijacking_main_loop_and_caching/

OzTheGreat
quelle