何时使用WP_query(),query_posts()和pre_get_posts


159

我读了@nacin的《 昨天不知道查询》,并被发送了一个查询兔子洞。昨天之前,我(错误地)使用query_posts()了所有查询需求。现在,我对使用会更明智WP_Query(),但是仍然有一些灰色区域。

我想我肯定知道的是:

如果我要在页面上的任何地方(在侧边栏中,在页脚中,任何类型的“相关文章”等)进行附加循环,我想使用WP_Query()。我可以在单个页面上重复使用它而不会造成任何伤害。(对?)。

我不确定的是

  1. 什么时候使用@ nacin的 pre_get_posts对比WP_Query()?我pre_get_posts现在应该用所有东西吗?
  2. 当我想修改模板页面中的循环时(假设我要修改分类档案页面),是否要删除if have_posts : while have_posts : the_post零件并编写自己的零件WP_Query()?还是pre_get_posts在我的functions.php文件中使用修改输出?

tl; dr

我想从中得出的tl; dr规则是:

  1. 切勿使用query_posts
  2. 在单个页面上运行多个查询时,请使用 WP_Query()
  3. 修改循环时,请执行____。

谢谢你的智慧

特里

ps:我已经看过并读过:什么时候应该使用WP_Query vs query_posts()vs get_posts()?这增加了另一个维度- get_posts。但根本不处理pre_get_posts



@saltcod,现在有所不同,WordPress不断发展,与此处接受的答案相比,我添加了一些评论。
prosti

Answers:


145

您说对了:

切勿使用query_posts

pre_get_posts

pre_get_posts是一个过滤器,用于更改任何查询。它最常用于仅更改“主查询”:

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

      if( $query->is_main_query() ){
        //Do something to main query
      }
}

(尽管这可能是多余的,但我还将检查is_admin()返回的false)。主要查询在您的模板中显示为:

if( have_posts() ):
    while( have_posts() ): the_post();
       //The loop
    endwhile;
endif;

如果您需要编辑此循环,请使用pre_get_posts。即,如果您想使用query_posts()-请pre_get_posts改为使用。

WP_Query

主要查询是的重要实例WP_Query object。WordPress使用它来决定使用哪个模板,例如,传递到url中的任何参数(例如,分页)都将被引导到该WP_Query对象的实例中。

对于辅助循环(例如,在侧栏或“相关文章”列表中),您需要创建自己的WP_Query对象单独实例。例如

$my_secondary_loop = new WP_Query(...);
if( $my_secondary_loop->have_posts() ):
    while( $my_secondary_loop->have_posts() ): $my_secondary_loop->the_post();
       //The secondary loop
    endwhile;
endif;
wp_reset_postdata();

注意wp_reset_postdata();-这是因为辅助循环将覆盖$post标识“当前帖子” 的全局变量。这实质上将其重置为“ $poston on”。

get_posts()

实际上,这是WP_Query对象的单独实例的包装。这将返回一个post对象数组。上面循环中使用的方法不再对您可用。这不是一个“循环”,仅仅是一个post对象数组。

<ul>
<?php
global $post;
$args = array( 'numberposts' => 5, 'offset'=> 1, 'category' => 1 );
$myposts = get_posts( $args );
foreach( $myposts as $post ) :  setup_postdata($post); ?>
    <li><a href="<?php the_permalink(); ?>"><?php the_title(); ?></a></li>
<?php endforeach; wp_reset_postdata(); ?>
</ul>

回答您的问题

  1. 使用pre_get_posts改变你的主查询。WP_Query模板页面中的辅助循环使用单独的对象(方法2)。
  2. 如果要更改主循环的查询,请使用pre_get_posts

那么,有什么方案可以直接使用get_posts()而不是WP_Query吗?
urok93 2012年

@drtanz-是的。举例来说,您不需要分页,也不需要顶部的粘帖-在这种情况下get_posts(),效率更高。
史蒂芬·哈里斯

但是,这不会在我们可以修改pre_get_posts来修改主查询的地方添加额外的查询吗?
urok93 2012年

@drtanz-您将不会使用get_posts()主查询-用于辅助查询。
史蒂芬·哈里斯

1
@StephenHarris Right =)如果在对象上使用next_post()而不是使用the_post,则无需执行全局查询,也不必记住以后要使用wp_reset_postdata。
私有者

55

循环有两种不同的上下文:

  • 基于URL请求发生的循环,在加载模板之前已进行处理
  • 以任何其他方式发生的次级循环,从模板文件或其他方式调用

问题query_posts()在于它是试图成为主要循环而不幸失败的次级循环。因此忘记它的存在。

修改主循环

  • 不要使用 query_posts()
  • pre_get_posts$query->is_main_query()检查一起使用过滤器
  • 交替使用request滤镜(太粗略,所以上面的效果更好)

运行辅助循环

使用new WP_Queryget_posts()可以互换使用(后者是前者的薄包装)。

清理

使用wp_reset_query()如果使用query_posts()或与全球混乱$wp_query直接-所以你几乎从来没有需要。

使用wp_reset_postdata()如果使用the_post()setup_postdata()或全球性混乱$post和需要恢复的职位相关的事情初始状态。


3
Rarst的意思是wp_reset_postdata()
Gregory

23

有使用的合法方案query_posts($query),例如:

  1. 您要在页面上显示帖子列表或自定义帖子类型的帖子(使用页面模板)

  2. 您想对这些帖子进行分页

现在,为什么要在页面上显示它而不使用存档模板?

  1. 对于管理员(您的客户?)来说更直观-他们可以在“页面”中查看页面

  2. 最好将其添加到菜单中(没有页面,他们必须直接添加url)

  3. 如果您想在模板上显示其他内容(文本,缩略图或任何自定义元内容),则可以轻松地从页面上获取它(这对客户来说也更有意义)。查看是否使用了存档模板,您是否需要对其他内容进行硬编码或使用例如主题/插件选项(这会使客户不太直观)

这是一个简化的示例代码(将在您的页面模板上-例如page-page-of-posts.php):

/**
 * Template Name: Page of Posts
 */

while(have_posts()) { // original main loop - page content
  the_post();
  the_title(); // title of the page
  the_content(); // content of the page
  // etc...
}

// now we display list of our custom-post-type posts

// first obtain pagination parametres
$paged = 1;
if(get_query_var('paged')) {
  $paged = get_query_var('paged');
} elseif(get_query_var('page')) {
  $paged = get_query_var('page');
}

// query posts and replace the main query (page) with this one (so the pagination works)
query_posts(array('post_type' => 'my_post_type', 'post_status' => 'publish', 'paged' => $paged));

// pagination
next_posts_link();
previous_posts_link();

// loop
while(have_posts()) {
  the_post();
  the_title(); // your custom-post-type post's title
  the_content(); // // your custom-post-type post's content
}

wp_reset_query(); // sets the main query (global $wp_query) to the original page query (it obtains it from global $wp_the_query variable) and resets the post data

// So, now we can display the page-related content again (if we wish so)
while(have_posts()) { // original main loop - page content
  the_post();
  the_title(); // title of the page
  the_content(); // content of the page
  // etc...
}

现在,非常清楚地说,我们可以避免query_posts()在这里使用,WP_Query而是改为使用-像这样:

// ...

global $wp_query;
$wp_query = new WP_Query(array('your query vars here')); // sets the new custom query as a main query

// your custom-post-type loop here

wp_reset_query();

// ...

但是,当我们有这么好的小功能可用时,为什么要这样做呢?


1
布莱恩,谢谢你。我一直在努力使pre_get_posts可以在您所描述的情况下的页面上正常工作:客户需要将自定义字段/内容添加到原本为存档的页面中,因此需要创建“页面”;客户需要看到一些要添加到导航菜单的内容,因为添加自定义链接会使其脱离;等我+1!
Will Lanni 2012年

2
也可以使用“ pre_get_posts”来完成。我这样做是为了让“静态首页”以自定义顺序和自定义过滤器列出我的自定义帖子类型。此页面也是分页的。看看这个问题,看看它是如何工作的:wordpress.stackexchange.com/questions/30851/… 简而言之,仍然没有更多使用query_posts的合法方案;)
2ndkauboy 2015年

1
因为“应该注意,用它代替页面上的主查询会增加页面加载时间,在最坏的情况下,所需的工作量增加一倍以上或更多。虽然易于使用,但该功能也容易造成混乱和以后的问题。” 源代码x.wordpress.org/Function_Reference/query_posts
Claudiu Creanga 2015年

答案是各种各样的错误。您可以在WP中使用与自定义帖子类型相同的URL创建一个“页面”。例如,如果您的CPT是Bananas,则可以获得具有相同URL的名为Bananas的页面。然后,您将获得siteurl.com/bananas。只要您的主题文件夹中有archive-bananas.php,它就会使用模板并“覆盖”该页面。如其他评论之一所述,使用此“方法”会为WP造成两倍的工作负载,因此永远不要使用。
Hybrid Web Dev

8

我从functions.php修改WordPress查询:

//unfortunately, "IS_PAGE" condition doesn't work in pre_get_posts (it's WORDPRESS behaviour)
//so you can use `add_filter('posts_where', ....);`    OR   modify  "PAGE" query directly into template file

add_action( 'pre_get_posts', 'myFunction' );
function myFunction($query) {
    if ( ! is_admin() && $query->is_main_query() )  {
        if (  $query->is_category ) {
            $query->set( 'post_type', array( 'post', 'page', 'my_postType' ) );
            add_filter( 'posts_where' , 'MyFilterFunction_1' ) && $GLOBALS['call_ok']=1; 
        }
    }
}
function MyFilterFunction_1($where) {
   return (empty($GLOBALS['call_ok']) || !($GLOBALS['call_ok']=false)  ? $where :  $where . " AND ({$GLOBALS['wpdb']->posts}.post_name NOT LIKE 'Journal%')"; 
}

希望看到此示例,但where子句位于自定义元上。
安德鲁·韦尔奇

6

只是概述自WordPress随时间演变以来所接受答案的一些改进,并且现在(五年后)有所不同:

pre_get_posts是一个过滤器,用于更改任何查询。它最常用于仅更改“主查询”:

实际上是一个动作挂钩。不是过滤器,它将影响任何查询。

主要查询在您的模板中显示为:

if( have_posts() ):
    while( have_posts() ): the_post();
       //The loop
    endwhile;
endif;

实际上,这也不是事实。该函数have_posts迭代与主查询global $wp_query不相关的对象。 可能还会被辅助查询更改。global $wp_query;

function have_posts() {
    global $wp_query;
    return $wp_query->have_posts();
}

get_posts()

本质上,这是WP_Query对象的单独实例的包装。

实际上,今天WP_Query是一个类,因此我们有一个类的实例。


总结一下:当时@StephenHarris编写的所有内容很可能都是真的,但是随着时间的流逝,WordPress中的情况已经发生了变化。


从技术上讲,所有过滤器都在幕后,动作只是一个简单的过滤器。但是您在这里是对的,它是通过引用传递参数的操作,这与更简单的操作有所不同。
米洛

get_posts返回一个post对象数组,而不是一个WP_Query对象数组,因此这仍然是正确的。并且WP_Query一直是一个类,一个class = object的实例。
米洛

谢谢@Milo,由于某种原因我的脑子里简化了模型,这是正确的。
prosti
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.