问题描述

技术上有两个 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 辅助翻译的英文资料结果。如果您对结果不满意,可以加入我们改善翻译效果:薇晓朵技术论坛。