เมื่อใดที่จะใช้ WP_query (), query_posts () และ pre_get_posts


159

ฉันอ่าน@ nacin คุณไม่รู้จัก Queryเมื่อวานนี้และถูกส่งลงมาเล็กน้อยจากช่องกระต่ายสอบถาม ก่อนหน้าเมื่อวานนี้ฉันใช้ผิด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 เทียบกับ query_posts () vs get_posts ()? ซึ่งจะเพิ่มอีกมิติหนึ่ง get_posts- แต่ก็ไม่ได้เกี่ยวข้องpre_get_postsอะไรเลย



@saltcod ตอนนี้จะแตกต่างกัน WordPress พัฒนาผมเพิ่มความคิดเห็นไม่กี่ในการเปรียบเทียบกับคำตอบที่ได้รับการยอมรับที่นี่
prosti

คำตอบ:


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()ผลตอบแทนที่เป็นเท็จ - แม้ว่านี่อาจซ้ำซ้อน) ข้อความค้นหาหลักจะปรากฏในแม่แบบของคุณดังนี้:

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ตัวแปรส่วนกลางซึ่งระบุ 'โพสต์ปัจจุบัน' นี่เป็นการรีเซ็ตที่$postเราอยู่

get_posts ()

นี่คือเสื้อคลุมสำหรับอินสแตนซ์ที่แยกจากกันของWP_Queryวัตถุ ส่งคืนอาร์เรย์ของวัตถุที่โพสต์ วิธีการที่ใช้ในการวนซ้ำด้านบนไม่สามารถใช้ได้กับคุณ นี่ไม่ใช่ 'วน' เพียงแค่อาเรย์ของวัตถุโพสต์

<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

@drtanz - ใช่ พูดเช่นคุณไม่จำเป็นต้องมีเลขหน้าหรือโพสต์เหนียวที่ด้านบน - ในกรณีเหล่านี้get_posts()มีประสิทธิภาพมากขึ้น
Stephen Harris

แต่นั่นจะไม่เพิ่มข้อความค้นหาพิเศษที่เราสามารถแก้ไข pre_get_posts เพื่อแก้ไขข้อความค้นหาหลักได้หรือไม่
urok93

@drtanz - คุณจะไม่ใช้get_posts()กับข้อความค้นหาหลัก - สำหรับข้อความค้นหารอง
Stephen Harris

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_Queryหรือget_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. หากคุณต้องการแสดงเนื้อหาเพิ่มเติม (ข้อความโพสต์รูปขนาดย่อหรือเนื้อหาเมตาแบบกำหนดเอง) บนเทมเพลตคุณสามารถนำเนื้อหานั้นมาจากหน้าเว็บได้อย่างง่ายดาย ดูว่าคุณใช้เทมเพลตการเก็บถาวรหรือไม่คุณต้องฮาร์ดโค้ดเนื้อหาเพิ่มเติมหรือใช้ตัวอย่างเช่นตัวเลือกชุดรูปแบบ / ปลั๊กอิน (ซึ่งทำให้ลูกค้าใช้งานง่ายกว่า)

ต่อไปนี้เป็นตัวอย่างโค้ดแบบง่าย (ซึ่งจะอยู่ในเทมเพลตหน้าของคุณ - เช่นหน้าของหน้าโพสต์)

/**
 * 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 ทำงานบนหน้าเว็บในสถานการณ์ที่คุณอธิบาย: ลูกค้าต้องเพิ่มฟิลด์ / เนื้อหาที่กำหนดเองในสิ่งที่จะเป็นหน้าเก็บถาวรดังนั้นจึงจำเป็นต้องสร้าง "หน้า" ลูกค้าจำเป็นต้องเห็นสิ่งที่จะเพิ่มในเมนู nav ขณะที่การเพิ่มลิงค์ที่กำหนดเองหนีออกมา เป็นต้น +1 จากฉัน!
Will Lanni

2
สามารถทำได้โดยใช้ "pre_get_posts" ฉันทำเช่นนั้นเพื่อให้ "หน้าคงที่" แสดงประเภทโพสต์ที่กำหนดเองของฉันในการสั่งซื้อที่กำหนดเองและด้วยตัวกรองที่กำหนดเอง หน้านี้ยังให้เลขหน้าด้วย ตรวจสอบคำถามนี้เพื่อดูว่ามันทำงานอย่างไร: wordpress.stackexchange.com/questions/30851/ ดังนั้นในระยะสั้นยังไม่มีสถานการณ์ที่ถูกกฎหมายสำหรับการใช้ query_posts;)
2ndkauboy

1
เนื่องจาก "ควรสังเกตว่าการใช้สิ่งนี้เพื่อแทนที่คิวรีหลักในหน้าสามารถเพิ่มเวลาในการโหลดหน้าในกรณีที่เลวร้ายที่สุดมากกว่าการเพิ่มจำนวนงานที่ต้องการเป็นสองเท่าหรือมากกว่าในขณะที่ใช้งานง่ายฟังก์ชั่นก็มีแนวโน้มที่จะสับสน และปัญหาในภายหลัง " Source codex.wordpress.org/Function_Reference/query_posts
Claudiu Creanga

คำตอบนี้ผิดทุกชนิด คุณสามารถสร้าง "หน้า" ใน WP ด้วย URL เดียวกับประเภทโพสต์ที่กำหนดเอง EG หาก CPT ของคุณเป็น Bananas คุณสามารถรับหน้าชื่อ Bananas ด้วย URL เดียวกัน จากนั้นคุณจะจบลงด้วย siteurl.com/bananas ตราบใดที่คุณมีไฟล์เก็บถาวร - bananas.php ในโฟลเดอร์ชุดรูปแบบของคุณก็จะใช้เทมเพลตและ "แทนที่" หน้านั้นแทน ตามที่ระบุในข้อคิดเห็นอื่นอย่างใดอย่างหนึ่งการใช้ "เมธอด" นี้จะสร้างเวิร์กโหลดสำหรับ WP สองเท่าดังนั้นจึงไม่ควรใช้
Hybrid Web Dev

8

ฉันแก้ไขเคียวรี WordPress จาก functions.php:

//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%')"; 
}

จะมีความสนใจที่จะเห็นตัวอย่างนี้ แต่ข้อที่อยู่ในเมตาที่กำหนดเอง
Andrew Welch

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 ()

นี่คือ wrapper สำหรับอินสแตนซ์ที่แยกต่างหากของวัตถุ WP_Query

จริงๆแล้วทุกวันนี้WP_Queryเป็นคลาสดังนั้นเราจึงมีตัวอย่างของคลาส


เพื่อสรุป: ณ เวลาที่ @StephenHarris เขียนส่วนใหญ่ทั้งหมดนี้เป็นจริง แต่เมื่อเวลาผ่านไปสิ่งต่าง ๆ ใน WordPress มีการเปลี่ยนแปลง


ในทางเทคนิคมันเป็นตัวกรองทั้งหมดภายใต้ประทุนการกระทำเป็นเพียงตัวกรองอย่างง่าย แต่คุณถูกต้องที่นี่มันเป็นการกระทำที่ผ่านการโต้แย้งโดยการอ้างอิงซึ่งเป็นวิธีที่แตกต่างจากการกระทำที่ง่ายขึ้น
Milo

get_postsส่งคืนอาร์เรย์ของวัตถุที่โพสต์ไม่ใช่WP_Queryวัตถุดังนั้นจึงยังคงถูกต้อง และWP_Queryมักจะเป็นคลาสอินสแตนซ์ของ class = object
Milo

ขอบคุณ @Milo แก้ไขด้วยเหตุผลบางอย่างที่ทำให้ฉันมีแบบจำลองมากเกินไปในหัวของฉัน
prosti
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.