有两个query_posts()
从技术上讲,功能。一query_posts()
实际上是WP_Query::query_posts()
另一个是在全球空间。
发自理智的询问:
如果是全局的query_posts()
这是“邪恶”吗?为什么不反对?
或者为什么没有标记为_doing_it_wong
.
有两个query_posts()
从技术上讲,功能。一query_posts()
实际上是WP_Query::query_posts()
另一个是在全球空间。
发自理智的询问:
如果是全局的query_posts()
这是“邪恶”吗?为什么不反对?
或者为什么没有标记为_doing_it_wong
.
Let\'s dig into the trio: ::query_posts
, ::get_posts
and class WP_Query
to understand ::query_posts
better.
The cornerstone for getting the data in WordPress is the WP_Query
class. Both methods ::query_posts
and ::get_posts
use that class.
Note that the class
WP_Query
also contains the methods with the same name:WP_Query::query_posts
andWP_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.
Here is the WP_Query
structure:
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
is the Swiss army knife.Some things about WP_Query
:
pre_get_posts
hookI cannot explain all these, but some of these are tricky, so let\'s provide short tips.
WP_Query
is something you can control via arguments you passThe 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.
This would be the minimal example creating the WP_Query
object from the arguments:
// 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
is greedyCreated on the idea get all you can
WordPress developers decided to get all possible data early as this is good for the performance.
This is why by default when the query takes 10 posts from the database it will also get the terms and the metadata for these posts via separate queries. Terms and metadata will be cached (prefetched).
Note the caching is just for the single request lifetime.
You can disable the caching if you set update_post_meta_cache
and update_post_term_cache
to false
while setting the WP_Query
arguments. When caching is disabled the data will be requested from the database only on demand.
For the majority of WordPress blogs caching works well, but there are some occasions when you may disable the caching.
WP_Query
uses helper classesIf you checked WP_Query
fields there you have these three:
public $tax_query;
public $meta_query;
public $date_query;
You can imagine adding new in the future.
WP_Query
holds the substance for loopingIn this code:
$query = new WP_Query( $args )
if ( $query->have_posts() ) {
while ( $query->have_posts() ) {
$query->the_post();
you may notice the WP_Query
has the substance you can iterate. The helper methods are there also. You just set the while
loop.
Note.
for
andwhile
loops are semantically equivalent.
WP_Query
primary and secondaryIn WordPress you have one primary and zero or more secondary queries.
It is possible not to have the primary query, but this is beyond the scope of this article.
Primary query known as the main query or the regular query. Secondary query also called a custom query.
WordPress uses WP_Rewrite
class early to create the query arguments based on the URL. Based on these arguments it stores the two identical objects in the global space. Both of these will hold the main query.
global $wp_query @since WordPress 1.5
global $wp_the_query @since WordPress 2.1
When we say main query we think of these variables. Other queries can be called secondary or custom.
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
has the handy pre_get_posts
hook.This is the action hook. It will apply to any WP_Query
instance. You call it like:
add_action( \'pre_get_posts\', function($query){
if ( is_category() && $query->is_main_query() ) {
// set your improved arguments
$query->set( ... );
...
}
return $query;
});
This hook is great and it can alter any query arguments.
Here is what you can read:
Fires after the query variable object is created, but before the actual query is run.
So this hook is arguments manager but cannot create new WP_Query
objects. If you had one primary and one secondary query, pre_get_posts
cannot create the third one. Or if you just had one primary it cannot create the secondary.
Note in case you need to alter the main query only you can use the
request
hook also.
WP_Query
supports nested loopsThis scenario may happen if you use plugins, and you call plugin functions from the template.
Here is the showcase example WordPress have helper functions even for the nested loops:
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;
The output will be like this since I installed 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!
Even though I requested 5 posts in the custom $query it will return me six, because the sticky post will go along.
If there no wp_reset_postdata
in the previous example the output will be like this, because of the $GLOBALS[\'post\']
will be invalid.
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
has wp_reset_query
functionThis is like a reset button. $GLOBALS[\'wp_the_query\']
should be frozen all the time, and plugins or themes should never alter it.
Here is what wp_reset_query
do:
function wp_reset_query() {
$GLOBALS[\'wp_query\'] = $GLOBALS[\'wp_the_query\'];
wp_reset_postdata();
}
get_posts
get_posts
looks like
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.
It is just a wrapper around WP_Query
that returns the query object posts.
The ignore_sticky_posts
set to true means the sticky posts may show up only in a natural position. There will be no sticky posts in the front. The other no_found_rows
set to true means WordPress database API will not use SQL_CALC_FOUND_ROWS
in order to implement pagination, reducing the load on the database to execute found rows count.
This is handy when you don\'t need pagination. We understand now we can mimic this function with this query:
$args = array ( \'ignore_sticky_posts\' => true, \'no_found_rows\' => true);
$query = new WP_Query( $args );
print( $query->request );
Here is the corresponding SQL request:
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
Compare what we have now with the previous SQL request where SQL_CALC_FOUND_ROWS
exists.
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
The request without SQL_CALC_FOUND_ROWS
will be faster.
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()
is WP_Query
wrapper. It returns the reference to the main WP_Query
object, and at the same time it will set the global $wp_query
.
File: /wp-includes/query.php
function query_posts($args) {
$GLOBALS[\'wp_query\'] = new WP_Query();
return $GLOBALS[\'wp_query\']->query($args);
}
In PHP4 everything, including objects, was passed by value. query_posts
was like this:
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);
}
Please note in typical scenario with one primary and one secondary query we have these three variables:
$GLOBALS[\'wp_the_query\']
$GLOBALS[\'wp_query\'] // should be the copy of first one
$custom_query // secondary
Let\'s say each of these three takes 1M of memory. Total would be 3M of memory.
If we use query_posts
, $GLOBALS[\'wp_query\']
will be unset and created again.
PHP5+ should be smart emptying the $GLOBALS[\'wp_query\']
object, just like in PHP4 we did it with the unset($GLOBALS[\'wp_query\']);
function query_posts($args) {
$GLOBALS[\'wp_query\'] = new WP_Query();
return $GLOBALS[\'wp_query\']->query($args);
}
As a result query_posts
consumes 2M of memory in total, while get_posts
consumes 3M of memory.
Note in query_posts
we are not returning the actual object, but a reference to the object.
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.
Here is one example
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\'] ) ) );
Will result:
f14153cab65abf1ea23224a1068563ef
f14153cab65abf1ea23224a1068563ef
f14153cab65abf1ea23224a1068563ef
d6db1c6bfddac328442e91b6059210b5
Try to reset the query:
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\'] ) ) );
Will result:
f14153cab65abf1ea23224a1068563ef
f14153cab65abf1ea23224a1068563ef
f14153cab65abf1ea23224a1068563ef
f14153cab65abf1ea23224a1068563ef
You can create problems even if you use 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\'] ) ) );
Of course, the solution would be to use wp_reset_query
function again.
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\'] ) ) );
This is why I think query_posts
may be better from the memory standpoint. But you should always do wp_reset_query
trick.
我刚刚创建了一个新的trac票证,ticket #36874, 建议反对query_posts()
. 它是否会被接受仍然是一个好问题。
真正的大问题是query_posts()
是的,它仍然被插件和主题广泛使用,尽管有关于为什么应该NEVER EVER 使用它。我认为WPSE上最史诗般的帖子如下:
query_posts()
不会阻止低质量的开发人员和一般不了解WordPress的人以及使用低质量教程作为指导的人使用它。作为一些证据,我们在人们使用的地方还有多少问题caller_get_posts
在里面WP_Query
? 它已经被弃用多年了。但是,在核心开发人员认为合适的任何时候,都可以删除不推荐使用的函数和参数,但在query_posts()
因为这将破坏数百万个网站。所以是的,我们可能永远不会看到query_posts()
- 这可能会导致这样一个事实,即它很可能永远不会被弃用。
虽然这是一个起点,但我们必须记住,在WordPress中不推荐某些东西并不会阻止它的使用。
看来核心开发人员正紧紧抓住这个古老而忠实的小恶魔。所有感兴趣的人,这是4年前的复票
[有点咆哮]
在这一点上,没有什么是真正不受欢迎的,这是一贯的核心理念。尽管这是一个很好的建议,但如果函数在某个时候不会真正被删除,那么它将被忽略。有许多人没有与WP_DEBUG
如果没有实际破损,则不会通知通知。
OTOH hand,这个函数是goto
陈述就我个人而言,我从未使用过goto
但我能理解那些指向某种情况的论点,在这种情况下,默认情况下这不是坏事。同样适用于query_posts
, 这是一种设置生成简单循环所需的所有全局变量的简单方法,在ajax或rest api上下文中非常有用。我永远不会在这些上下文中使用它,但我可以看到,这更多的是一个编码风格的问题,而不是一个函数本身是邪恶的。
再深入一点,主要问题是需要设置全局变量。这是主要问题,而不是帮助设置它们的功能。