問題描述

技術上有兩個 query_posts()功能。一個 query_posts()實際上是 WP_Query::query_posts(),另一個在全球空間。

要求理智:

如果全球 query_posts()是”evil” 為什麼不被棄用?

或為什麼沒有標記為_doing_it_wong

最佳解決方案

我剛剛建立了一個新的 trac 門票,ticket #36874,提出了 query_posts()的棄用。是否接受仍然是一個很好的問題。

query_posts()的真正的大問題是,它仍然被外掛和主題廣泛使用,儘管在這個主題上,為什麼你永遠不會使用它是非常好的著作。我認為 WPSE 上最史詩的帖子如下:

deprecation!==刪除,所以不贊成 query_posts()不會停止使用質量差的開發人員和一般人誰不知道 WordPress 和誰使用質量差的教程作為指導。就像一些證明,我們還有幾個問題在這裡人們在 WP_Query 中使用 caller_get_posts?它已經被淘汰多年了。

但是,隨著核心開發人員可以隨時刪除已棄用的函式和引數,但這絕對不會發生在 query_posts()上,因為這將破壞數百萬個站點。所以是的,我們可能永遠不會看到完全刪除 query_posts() – 這可能導致事實上它很可能永遠不會被棄用。

這是一個起點,但是必須記住,棄用 WordPress 中的東西不會停止使用。

2016 年 5 月 19 日更新

我提出的機票現在已關閉,並標記為與 4 年票相同的影印件,該票已被關閉,並重新開放,仍然保持開放和尚未解決。

似乎核心的開發人員正在堅持這個老忠實的小惡魔。每個人都有興趣,這裡是重複的 4 年票

次佳解決方案

基本問題

讓我們深入三人:::query_posts::get_postsclass WP_Query 更好地瞭解::query_posts

在 WordPress 中獲取資料的基石是 WP_Query 類。兩種方法::query_posts::get_posts 都使用該類。

Note that the class WP_Query also contains the methods with the same name: WP_Query::query_posts and WP_Query::get_posts, but we actually only consider the global methods, so don’t get confused.

瞭解 WP_Query

The class called WP_Query has been introduced back in 2004. All fields having the ☂ (umbrella) mark where present back in 2004. The additional fields were added later.

這是 WP_Query 結構:

class WP_Query (as in WordPress v4.7)
    public $query; ☂
    public $query_vars = array(); ☂
    public $tax_query;
    public $meta_query = false;
    public $date_query = false;
    public $queried_object; ☂
    public $queried_object_id; ☂
    public $request;
    public $posts; ☂
    public $post_count = 0; ☂
    public $current_post = -1; ☂
    public $in_the_loop = false;
    public $post; ☂
    public $comments;
    public $comment_count = 0;
    public $current_comment = -1;
    public $comment;
    public $found_posts = 0;
    public $max_num_pages = 0;
    public $max_num_comment_pages = 0;
    public $is_single = false; ☂
    public $is_preview = false; ☂
    public $is_page = false; ☂
    public $is_archive = false; ☂
    public $is_date = false; ☂
    public $is_year = false; ☂
    public $is_month = false; ☂
    public $is_day = false; ☂
    public $is_time = false; ☂
    public $is_author = false; ☂
    public $is_category = false; ☂
    public $is_tag = false;
    public $is_tax = false;
    public $is_search = false; ☂
    public $is_feed = false; ☂
    public $is_comment_feed = false;
    public $is_trackback = false; ☂
    public $is_home = false; ☂
    public $is_404 = false; ☂
    public $is_embed = false;
    public $is_paged = false;
    public $is_admin = false; ☂
    public $is_attachment = false;
    public $is_singular = false;
    public $is_robots = false;
    public $is_posts_page = false;
    public $is_post_type_archive = false;
    private $query_vars_hash = false;
    private $query_vars_changed = true;
    public $thumbnails_cached = false;
    private $stopwords;
    private $compat_fields = array('query_vars_hash', 'query_vars_changed');
    private $compat_methods = array('init_query_flags', 'parse_tax_query');
    private function init_query_flags()

WP_Query 是瑞士軍刀。

關於 WP_Query 的一些事情:

  • 這是你可以透過你透過的引數來控制的

  • 預設情況下是貪心

  • 它擁有迴圈的物質

  • 它被儲存在全域性空間 x2 中

  • 它可以是主要或次要的

  • 它使用輔助類

  • 它有一個方便的 pre_get_posts

  • 它甚至支援巢狀迴圈

  • 它包含 SQL 查詢字串

  • 它擁有結果的數量

  • 它持有結果

  • 它包含所有可能的查詢引數的列表

  • 它儲存模板標誌

我不能解釋所有這些,但其中一些是棘手的,所以讓我們提供一些簡短的提示。

WP_Query 是你可以透過你透過的引數來控制的

The list of the arguments
---
 attachment
 attachment_id
 author
 author__in
 author__not_in
 author_name
 cache_results
 cat
 category__and
 category__in
 category__not_in
 category_name
 comments_per_page
 day
 embed
 error
 feed
 fields
 hour
 ignore_sticky_posts
 lazy_load_term_meta
 m
 menu_order
 meta_key
 meta_value
 minute
 monthnum
 name
 no_found_rows
 nopaging
 order
 p
 page_id
 paged
 pagename
 post__in
 post__not_in
 post_name__in
 post_parent
 post_parent__in
 post_parent__not_in
 post_type
 posts_per_page
 preview
 s
 second
 sentence
 static
 subpost
 subpost_id
 suppress_filters
 tag
 tag__and
 tag__in
 tag__not_in
 tag_id
 tag_slug__and
 tag_slug__in
 tb
 title
 update_post_meta_cache
 update_post_term_cache
 w
 year

This list from WordPress version 4.7 will certainly change in the future.

這將是從引數建立 WP_Query 物件的最小示例:

// WP_Query arguments
$args = array ( /* arguments*/ );
// creating the WP_Query object
$query = new WP_Query( $args );
// print full list of arguments WP_Query can take
print ( $query->query_vars );

WP_Query 是貪心的

創意 get all you can WordPress 開發人員決定儘早獲得所有可能的資料,因為這是 good for the performance 。這就是為什麼預設情況下,該查詢從資料庫中獲取 10 個帖子,它還將透過單獨的查詢獲取這些帖子的條款和後設資料。條款和後設資料將被快取 (預取) 。

Note the caching is just for the single request lifetime.

如果在設定 WP_Query 引數時將 update_post_meta_cacheupdate_post_term_cache 設定為 false,則可以停用快取。當快取被停用時,資料庫將僅在需要時從資料庫中請求。

對於大多數 WordPress 部落格快取工作得很好,但有些情況下可能會停用快取。

WP_Query 使用輔助類

如果你檢查了 WP_Query 欄位,那麼你有三個:

public $tax_query;
public $meta_query;
public $date_query;

你可以想象在未來新增新的。

WP_Query 含有迴圈物質

在這段程式碼中:

$query = new WP_Query( $args )
if ( $query->have_posts() ) {
        while ( $query->have_posts() ) {
            $query->the_post();

您可能會注意到,WP_Query 具有您可以迭代的實質。幫助方法還有。您只需設定 while 迴圈。

Note. for and while loops are semantically equivalent.

WP_Query 一級和二級

在 WordPress 中,您有一個主要和零個或多個次要查詢。

It is possible not to have the primary query, but this is beyond the scope of this article.

主查詢稱為主查詢或常規查詢。輔助查詢也稱為自定義查詢。

WordPress 早期使用 WP_Rewrite 類來建立基於 URL 的查詢引數。基於這些引數,它將兩個相同的物件儲存在全域性空間中。這兩個都將保持主要查詢。

global $wp_query   @since WordPress 1.5
global $wp_the_query @since WordPress 2.1

當我們說主查詢時,我們想到這些變數。其他查詢可以稱為輔助或自定義。

It is completely legal to use either global $wp_query or $GLOBALS['wp_query'], but using the second notation is much more notable, and saves typing an extra line inside the scope of the functions.

$GLOBALS['wp_query'] and $GLOBALS['wp_the_query'] are separate objects. $GLOBALS['wp_the_query'] should remain frozen.

WP_Query 具有方便的 pre_get_posts 鉤。

這是動作鉤。它將適用於任何 WP_Query 例項。你稱之為:

add_action( 'pre_get_posts', function($query){

 if ( is_category() && $query->is_main_query() ) {
    // set your improved arguments
    $query->set( ... );
    ...
 }

 return $query;
});

這個鉤子很棒,它可以改變任何查詢引數。

這是你可以 read

Fires after the query variable object is created, but before the actual query is run.

所以這個鉤子是引數管理器,但不能建立新的 WP_Query 物件。如果您有一個主要和一個輔助查詢,pre_get_posts 無法建立第三個。或者如果你只有一個主要的,它不能建立次要的。

Note in case you need to alter the main query only you can use the request hook also.

WP_Query 支援巢狀迴圈

This scenario may happen if you use plugins, and you call plugin functions from the template.

這是展示示例 WordPress 有幫助函式,即使是巢狀迴圈:

global $id;
while ( have_posts() ) : the_post();

    // the custom $query
    $query = new WP_Query( array(   'posts_per_page' => 5   ) );
    if ( $query->have_posts() ) {

        while ( $query->have_posts() ) : $query->the_post();
            echo '<li>Custom ' . $id . '. ' . get_the_title() . '</li>';
        endwhile;
    }

    wp_reset_postdata();
    echo '<li>Main Query ' . $id . '. ' . get_the_title() . '</li>';

endwhile;

輸出將是這樣,因為我安裝了 theme unit test data

Custom 100. Template: Sticky
Custom 1. Hello world!
Custom 10. Markup: HTML Tags and Formatting
Custom 11. Markup: Image Alignment
Custom 12. Markup: Text Alignment
Custom 13. Markup: Title With Special Characters
Main Query 1. Hello world!

即使我在自定義 $查詢中請求了 5 個帖子,它將返回給我六個,因為貼上的帖子將會繼續。如果在前面的例子中沒有 wp_reset_postdata,輸出將會像這樣,因為 $GLOBALS['post']將無效。

Custom 1001. Template: Sticky
Custom 1. Hello world!
Custom 10. Markup: HTML Tags and Formatting
Custom 11. Markup: Image Alignment
Custom 12. Markup: Text Alignment
Custom 13. Markup: Title With Special Characters
Main Query 13. Markup: Title With Special Characters

WP_Query 具有 wp_reset_query 功能

這就像一個重置按鈕。 $GLOBALS['wp_the_query']應該一直凍結,外掛或主題不應該改變它。

這是 wp_reset_query 做的:

function wp_reset_query() {
    $GLOBALS['wp_query'] = $GLOBALS['wp_the_query'];
    wp_reset_postdata();
}

備註 get_posts

get_posts 看起來像

File: /wp-includes/post.php
1661: function get_posts( $args = null ) {
1662:   $defaults = array(
1663:       'numberposts' => 5,
1664:       'category' => 0, 'orderby' => 'date',
1665:       'order' => 'DESC', 'include' => array(),
1666:       'exclude' => array(), 'meta_key' => '',
1667:       'meta_value' =>'', 'post_type' => 'post',
1668:       'suppress_filters' => true
1669:   );
... // do some argument parsing
1685:   $r['ignore_sticky_posts'] = true;
1686:   $r['no_found_rows'] = true;
1687:
1688:   $get_posts = new WP_Query;
1689:   return $get_posts->query($r);

The line numbers may change in the future.

它只是 WP_Query 上的一個包裝,返回查詢物件的帖子。

ignore_sticky_posts 設定為 true 表示粘性帖子可能僅在自然位置顯示。前面沒有貼上的帖子。另一個 no_found_rows 設定為 true 意味著 WordPress 資料庫 API 不會使用 SQL_CALC_FOUND_ROWS 來實現分頁,減少資料庫上的負載來執行查詢行計數。

當您不需要分頁時,這很方便。我們現在明白,我們可以用這個查詢來模擬這個函式:

$args = array ( 'ignore_sticky_posts' => true, 'no_found_rows' => true);
$query = new WP_Query( $args );
print( $query->request );

這是相應的 SQL 請求:

SELECT wp_posts.ID FROM wp_posts WHERE 1=1 AND wp_posts.post_type = 'post' AND (wp_posts.post_status = 'publish' OR wp_posts.post_status = 'private') ORDER BY wp_posts.post_date DESC LIMIT 0, 10

比較我們現在和以前的 SQL_CALC_FOUND_ROWS 存在的 SQL 請求。

SELECT SQL_CALC_FOUND_ROWS wp_posts.ID FROM wp_posts WHERE 1=1 AND wp_posts.post_type = 'post' AND (wp_posts.post_status = 'publish' OR wp_posts.post_status = 'private')  ORDER BY wp_posts.post_date DESC LIMIT 0, 10

沒有 SQL_CALC_FOUND_ROWS 的請求會更快。

備註 query_posts

Tip: At first in 2004 there was only global $wp_query. As of WordPress 2.1 version $wp_the_query came. Tip: $GLOBALS['wp_query'] and $GLOBALS['wp_the_query'] are separate objects.

query_posts()WP_Query 包裝。它返回對主 WP_Query 物件的引用,同時它將設定 global $wp_query

File: /wp-includes/query.php
function query_posts($args) {
    $GLOBALS['wp_query'] = new WP_Query();
    return $GLOBALS['wp_query']->query($args);
}

在 PHP4 中,包括物件在內的所有內容都按值傳遞。 query_posts 是這樣的:

File: /wp-includes/query.php (WordPress 3.1)
function &query_posts($args) {
    unset($GLOBALS['wp_query']);
    $GLOBALS['wp_query'] =& new WP_Query();
    return $GLOBALS['wp_query']->query($args);
}

請注意,在典型情況下,一個主要和一個二次查詢我們有這三個變數:

$GLOBALS['wp_the_query']
$GLOBALS['wp_query'] // should be the copy of first one
$custom_query // secondary

我們來說,這三者中的每一個都需要 1M 的記憶體。總計將是 3M 的 memory 。如果我們使用 query_posts$GLOBALS['wp_query']將被取消設定並再次建立。

PHP5 +應該是聰明的清空 $GLOBALS['wp_query']物件,就像在 PHP4 中,我們用 unset($GLOBALS['wp_query']);

function query_posts($args) {
    $GLOBALS['wp_query'] = new WP_Query();
    return $GLOBALS['wp_query']->query($args);
}

因此,query_posts 總共消耗 2M 的記憶體,而 get_posts 消耗 3M 的記憶體。

注意在 query_posts 中我們不是返回實際的物件,而是對物件的引用。

From php.net: A PHP reference is an alias, which allows two different variables to write to the same value. As of PHP 5, an object variable doesn’t contain the object itself as value anymore. It only contains an object identifier which allows object accessors to find the actual object. When an object is sent by argument, returned or assigned to another variable, the different variables are not aliases: they hold a copy of the identifier, which points to the same object.

Also in PHP5+ the assign (=) operator is smart. It will use shallow copy and not hard object copy. When we write like this $GLOBALS['wp_query'] = $GLOBALS['wp_the_query']; only the data will be copied, not the whole object since these share the same object type.

這是一個例子

print( md5(serialize($GLOBALS['wp_the_query']) ) );
print( md5(serialize($GLOBALS['wp_query'] ) ) );
query_posts( '' );
print( md5(serialize($GLOBALS['wp_the_query']) ) );
print( md5(serialize($GLOBALS['wp_query'] ) ) );

會結果:

f14153cab65abf1ea23224a1068563ef
f14153cab65abf1ea23224a1068563ef
f14153cab65abf1ea23224a1068563ef
d6db1c6bfddac328442e91b6059210b5

嘗試重置查詢:

print( md5(serialize($GLOBALS['wp_the_query'] ) ) );
print( md5(serialize($GLOBALS['wp_query'] ) ) );
query_posts( '' );
wp_reset_query();
print( md5(serialize($GLOBALS['wp_the_query'] ) ) );
print( md5(serialize($GLOBALS['wp_query'] ) ) );

會結果:

f14153cab65abf1ea23224a1068563ef
f14153cab65abf1ea23224a1068563ef
f14153cab65abf1ea23224a1068563ef
f14153cab65abf1ea23224a1068563ef

即使使用 WP_Query,也可以建立問題

print( md5(serialize($GLOBALS['wp_the_query'] ) ) );
print( md5(serialize($GLOBALS['wp_query'] ) ) );
global $wp_query;
$wp_query = new WP_Query( array( 'post_type' => 'post' ) );
print( md5(serialize($GLOBALS['wp_the_query'] ) ) );
print( md5(serialize($GLOBALS['wp_query'] ) ) );

當然,解決辦法是再次使用 wp_reset_query 功能。

print( md5(serialize($GLOBALS['wp_the_query'] ) ) );
print( md5(serialize($GLOBALS['wp_query'] ) ) );
global $wp_query;
$wp_query = new WP_Query( array( 'post_type' => 'post' ) );
wp_reset_query();
print( md5(serialize($GLOBALS['wp_the_query'] ) ) );
print( md5(serialize($GLOBALS['wp_query'] ) ) );

這就是為什麼我認為 query_posts 可能從記憶體的角度來看更好。但是你應該總是做 wp_reset_query 的技巧。

第三種解決方案

[有點] t]

這是現在的核心理念,沒有什麼是真正的棄用。棄用通知,雖然它是一個很好的,只是將被忽略,如果該功能實際上不會在某些時候被刪除。有很多人不會用 WP_DEBUG 開發,如果沒有實際的破損,也不會注意到通知。

OTOH 手,這個功能就像 goto 語句。我個人從來沒有 (對於較小的定義,然後預期) 使用 goto,但我可以理解指出一些情況下,它不是預設的邪惡的論據。與 query_posts 相同,它是一種簡單的方法來設定所需的全域性變數,以便進行簡單迴圈,並且可以在 ajax 或 rest-api 上下文中使用。我也不會在這些上下文中使用它,但是我可以看到,在編碼風格上更是一個問題,那麼一個功能本身就是邪惡的。

更深一層,主要的問題是全球人需要設定。這是主要的問題,而不是幫助設定它們的一個功能。

參考文獻

注:本文內容整合自 Google/Baidu/Bing 輔助翻譯的英文資料結果。如果您對結果不滿意,可以加入我們改善翻譯效果:薇曉朵技術論壇。