Можно ли полностью остановить WP_Query при получении сообщений?

8

Я пытаюсь использовать WP Redis для кэширования всего объекта $ wp_query с ключом $ query_vars_hash .

Вот как $wp_queryбыло добавлено $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');
    }
});

Затем мне нужно проверить, кэшировался ли уже запрос, прежде чем WP_Queryможно будет получать сообщения:

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

Проблема :

returnили exitне работает в этом случае. Затем WP_Queryвсе равно ударит базу данных, чтобы получить сообщения снова.

Вопрос :

Независимо от плагина, возможно ли полностью прекратить WP_Queryполучение сообщений?

SarahCoding
источник
Я чувствую, что плагин должен справиться с этим ... Вы уверены, что делаете это правильно? Вы спрашивали об этом на своих форумах? На их вопросы GitHub?
Howdy_McGee
@Howdy_McGee плагин использует те же функциональные возможности, что и API кеширования WordPress по умолчанию . Разница лишь в том, что он помогает подключиться к серверу Redis. Конечно, я тоже пытаюсь найти правильный путь.
SarahCoding
Не уверен, почему вы думаете, что запрос не будет запущен. возвращаясь из действия не возвращайся по волшебству из вызывающей функции
Марк Каплун
@MarkKaplun Я тоже об этом вдвойне, но, returnвозможно, это единственная команда, которую мы можем вызвать в этом случае.
SarahCoding
@ Дэн, я не понимаю, что вы предполагаете, вы, очевидно, предполагаете что-то, что не соответствует действительности, вероятно, на уровне php
Марк Каплун

Ответы:

11

На данный момент это невозможно.

Когда 'pre_get_posts'запускается, уже слишком поздно останавливаться WP_Queryдля выполнения запроса.

Сам WordPress, когда вы пытаетесь запросить таксономию, которая не существует, добавляет AND (0 = 1)к WHEREпредложению SQL-запроса, чтобы убедиться, что он очень быстро не возвращает результатов ...

Там есть билет ПРОФ с патчем , который, вероятно , приземляется в ядре с WP 4.6, который вводит новый фильтр 'posts_pre_query'. Возвращение массива в этом фильтре приведет к WP_Queryостановке обработки и использованию предоставленного массива в качестве массива сообщений.

Это может как-то помочь вам в реализации того, что вы пытаетесь сделать.

Ожидание фота это все, что вы могли бы сделать , это как - то хак , то трюк ядро сам по себе использует довольно хак , а также.

Недавно я начал использовать трюк, когда я хочу остановить WordPress, чтобы делать вещи, которые я не могу остановить чистым способом: я выбрасываю исключение и ловлю его, чтобы продолжить выполнение приложения.

Я покажу вам пример. Обратите внимание, что весь код здесь полностью не проверен.

Прежде всего, давайте напишем пользовательское исключение:

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

Исключение предназначено для работы в качестве своего рода DTO для транспортировки объекта запроса, так что в catchблоке вы можете получить и использовать его.

Лучше объяснить с помощью кода:

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

Это должно более или менее работать, однако, есть много хуков, которые вы не собираетесь использовать, например, "the_posts"и многое другое ... если у вас есть код, который использует один из этих хуков для запуска, он сломается.

Вы можете использовать эту cached_query_setфункцию для запуска некоторых хуков, которые могут потребоваться вашей теме / плагинам.

Gmazzap
источник
Почему не работает с классом исключений по умолчанию? Это показывает мне ошибку uncaught исключения?
Sumit
Он должен работать со стандартным исключением и открытым свойством, но вам нужно перехватить стандартное исключение, если вы его выбросите @Sumit
gmazzap
Ну, я сделал это, просто используя этот пример. Но я получаю необнаруженную ошибку исключения. Я запустил самый простой пример исключения, но, похоже, do_actionдолжен быть в tryблоке.
Sumit
Интересный подход, который можно применять в разных местах в WordPress, я буду иметь в виду ;-) ps: DTO = Data Transfer Object ?
Birgire
@birgire да :)
gmazzap
2

Это вопрос PHP больше, чем вопрос WordPress.

Как прокомментировал @Mark :

возвращение из действия не вернется по волшебству из вызывающей функции

Это правда. Размещение returnв функции означает выход из функции, а размещение возврата в PHP-файле означает выход из файла. Не путайте конструкцию PHP exit(): P (вы можете найти лучший ответ на SO о PHP return).

И ответить на ваш вопрос

Вы можете уменьшить нагрузку на запрос, выбрав один столбец вместо полной таблицы. Как @birgire сделал здесь Удалить запрос главной страницы

Может быть, лучший ответ еще впереди. Я просто поделился тем, что знаю :)

Sumit
источник
1
@ Дан, вы получили много обращений к базе данных после нейтрализации запроса через posts_requestфильтр? При таком подходе «+ один столбец» мы выходим WP_Queryраньше, чем при использовании posts_pre_queryфильтра. Также следите за липкими сообщениями, posts_pre_queryно мы можем удалить их, $q->set( 'ignore_sticky_posts', 1 );например, в примере здесь .
Биргире
Похоже, posts_pre_queryэто не помогает. Ваше решение пока лучшее. :) Если вы знаете, как мы можем выйти из запроса сразу же pre_get_posts, это было бы здорово. Спасибо!
SarahCoding
@Dan posts_pre_queryбудет доступен с 4.6;)
Sumit
Другой подход, который приходит на ум, состоит в том, чтобы попытаться расширить WP_Queryкласс с помощью пользовательского get_posts()метода с возможным ранним существованием, который вызывает parent::get_posts() и пытается переопределить соответствующий запрос. Но я не знаю, будет ли это работать или иметь смысл в вашем случае ;-) @Dan
birgire
1
Может быть, немного Aerosmith - Livin 'On The Edge может помочь с этим ;-) @Dan
birgire
2

Это станет возможным в 4.6 (при условии отсутствия изменений до выпуска) с новым posts_pre_queryфильтром https://core.trac.wordpress.org/ticket/36687

Марк Каплун
источник
@ Дэн, это то, что происходит, когда ты хочешь завершить мысль, которая у тебя была, перед тем, как идти спать, а не читать сначала другие ответы;)
Марк Каплун
Братан, уже слишком поздно. Я прочитаю эти ответы позже ;-)
SarahCoding
2

Да, это возможно в зависимости от того, что вы хотите кешировать. Я сделал аналогичную вещь, чтобы кэшировать основной цикл на нашей домашней странице. По сути, вы можете использовать posts_requestи posts_resultsдля перехвата запроса и попадания в кеш, а затем также использовать found_postsдля исправления нумерации страниц.

Действительно грубый пример взят из нашего кода (не проверено), но вы должны помочь вам понять:

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

Подробнее здесь: https://www.reddit.com/r/Wordpress/comments/19crcn/best_practice_for_hijacking_main_loop_and_caching/

OzTheGreat
источник