問題描述

技術上有兩個 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 輔助翻譯的英文資料結果。如果您對結果不滿意,可以加入我們改善翻譯效果:薇曉朵技術論壇。