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

我想我肯定知道的内容:

如果我要在页面上的任何地方(在侧边栏中,在页脚中)进行任何其他循环,则需要进行各种“相关帖子” ”等-我想使用WP_Query()。我可以在单个页面上重复使用它而不会造成任何伤害。 (对?)。

我不确定的是什么


何时使用@nacin的pre_get_postsWP_Query()?我现在应该使用pre_get_posts吗?
当我想修改模板页面中的循环时(假设我要修改分类档案页面),是否要删除if have_posts : while have_posts : the_post部分并编写自己的WP_Query()?还是在我的functions.php文件中使用pre_get_posts修改输出?

tl; dr

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


不再使用query_posts
在单个页面上运行多个查询时,请使用WP_Query()

修改循环时,请执行__________________。

感谢任何智慧

特里

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

评论

何时应该使用WP_Query与query_posts()与get_posts()的可能重复项?

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

#1 楼

您说对了:


不再使用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对象的该实例中。

用于辅助循环(例如,在侧面) -bars或“相关帖子”列表),则需要创建自己的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变量。这实际上将其重置为我们所处的$post

get_posts()

这实际上是WP_Query对象的单独实例的包装。这将返回一个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>


回答您的问题


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


评论


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

–urok93
2012年8月25日16:09

@drtanz-是的。举例来说,您不需要分页,也不需要顶部的粘性帖子-在这种情况下,get_posts()效率更高。

–斯蒂芬·哈里斯(Stephen Harris)
2012年8月25日18:00

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

–urok93
2012年8月26日在20:54

@drtanz-您不会在主查询中使用get_posts()-在辅助查询中使用它。

–斯蒂芬·哈里斯(Stephen Harris)
2012年8月26日在22:42

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

–私有
15年9月19日在18:01

#2 楼

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



基于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

评论


Rarst的意思是wp_reset_postdata()

–格雷戈里
2012年6月1日上午9:18

#3 楼

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


要在页面上显示帖子列表或自定义帖子类型的帖子(使用页面模板)
您想对这些帖子进行分页

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


更多对于管理员(您的客户?)很直观-他们可以在“页面”中看到该页面
最好将其添加到菜单中(没有页面,他们必须直接添加url)
如果您想在模板上显示其他内容(文本,缩略图或任何自定义元内容),则可以轻松地从页面上获取它(这对客户来说也更有意义)。看看是否使用了存档模板,您是否需要对其他内容进行硬编码或使用例如主题/插件选项(这会使客户不那么直观)

这是一个简化的示例代码(这将在您的页面模板上-例如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-像这样:什么时候有这么好的功能可用?

评论


布莱恩,谢谢你。我一直在努力使pre_get_posts在您所描述的场景中的页面上正常工作:客户端需要将自定义字段/内容添加到原本为存档的页面中,因此需要创建“页面”;客户需要查看一些要添加到导航菜单的内容,因为添加自定义链接会使它们转义;等我+1!

–拉尼(Will Lanni)
2012年12月13日上午11:07

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

– 2ndkauboy
2015年1月12日14:47

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

– Claudiu Creanga
15年3月9日在16:31

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

–混合Web开发
15年6月19日在20:07

#4 楼

我从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子句位于自定义元上。

–安德鲁·韦尔奇(Andrew Welch)
17 Mar 3 '17 at 16:20

#5 楼

只是为了概述自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中的情况已经发生了变化。

评论


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

–米洛
16 Dec 28'在2:16

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

–米洛
16 Dec 28'在2:16

谢谢@Milo,由于某种原因我的脑子里简化了模型,这是正确的。

–prosti
16年12月28日在8:51