Khi nào nên sử dụng WP_query (), query_posts () và pre_get_posts


159

Tôi đã đọc @ nacin Bạn không biết Truy vấn ngày hôm qua và đã được gửi xuống một chút lỗ thỏ truy vấn. Trước ngày hôm qua, tôi đã (sử dụng sai) query_posts()cho tất cả các nhu cầu truy vấn của mình. Bây giờ tôi khôn ngoan hơn một chút về việc sử dụng WP_Query(), nhưng vẫn có một số vùng màu xám.

Những gì tôi nghĩ rằng tôi biết chắc chắn:

Nếu tôi thực hiện các vòng lặp bổ sung ở bất cứ đâu trên một trang trong thanh bên, ở chân trang, bất kỳ loại "bài đăng liên quan" nào, v.v ... tôi muốn sử dụng WP_Query(). Tôi có thể sử dụng nó nhiều lần trên một trang mà không gây hại gì. (đúng?).

Những gì tôi không biết chắc chắn

  1. Khi nào tôi sử dụng @ nacin của pre_get_posts vs WP_Query()? Tôi có nên sử dụng pre_get_postscho tất cả mọi thứ bây giờ?
  2. Khi tôi muốn sửa đổi vòng lặp trong trang mẫu - giả sử tôi muốn sửa đổi trang lưu trữ phân loại - tôi có xóa if have_posts : while have_posts : the_postphần đó và tự viết WP_Query()không? Hoặc tôi có sửa đổi đầu ra bằng cách sử dụng pre_get_poststệp tin.php không?

tl; dr

Các quy tắc tl; dr tôi muốn rút ra từ đây là:

  1. Không bao giờ sử dụng query_postsnữa
  2. Khi chạy nhiều truy vấn trên một trang, hãy sử dụng WP_Query()
  3. Khi sửa đổi một vòng lặp, hãy làm điều này.

Cảm ơn vì sự khôn ngoan

Terry

ps: Tôi đã thấy và đọc: Khi nào bạn nên sử dụng WP_Query vs query_posts () vs get_posts ()? Mà thêm một chiều khác - get_posts. Nhưng không đối phó với pre_get_poststất cả.



@saltcod, bây giờ thì khác, WordPress đã phát triển, tôi đã thêm một vài bình luận so với câu trả lời được chấp nhận ở đây .
prosti

Câu trả lời:


145

Bạn có quyền nói:

Không bao giờ sử dụng query_postsnữa

pre_get_posts

pre_get_postslà một bộ lọc, để thay đổi bất kỳ truy vấn nào . Nó thường được sử dụng để chỉ thay đổi 'truy vấn chính':

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

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

(Tôi cũng sẽ kiểm tra xem is_admin()trả về false - mặc dù điều này có thể là dư thừa.). Truy vấn chính xuất hiện trong các mẫu của bạn dưới dạng:

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

Nếu bạn cảm thấy cần phải chỉnh sửa vòng lặp này - hãy sử dụng pre_get_posts. tức là nếu bạn muốn sử dụng query_posts()- sử dụng pre_get_poststhay thế.

WP_Query

Truy vấn chính là một ví dụ quan trọng của a WP_Query object. WordPress sử dụng nó để quyết định sử dụng mẫu nào, ví dụ, và bất kỳ đối số nào được truyền vào url (ví dụ: phân trang) đều được chuyển vào phiên bản đó của WP_Queryđối tượng.

Đối với các vòng lặp thứ cấp (ví dụ: trong các thanh bên hoặc danh sách 'bài đăng liên quan'), bạn sẽ muốn tạo WP_Queryđối tượng riêng của đối tượng. Ví dụ

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

Lưu ý wp_reset_postdata();- điều này là do vòng lặp thứ cấp sẽ ghi đè $postbiến toàn cục xác định 'bài đăng hiện tại'. Điều này về cơ bản đặt lại rằng $postchúng tôi đang trên.

get_posts ()

Đây thực chất là một trình bao bọc cho một thể hiện riêng biệt của một WP_Queryđối tượng. Điều này trả về một mảng các đối tượng bài. Các phương thức được sử dụng trong vòng lặp ở trên không còn có sẵn cho bạn. Đây không phải là một 'Vòng lặp', chỉ đơn giản là một mảng của đối tượng bài.

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

Trả lời câu hỏi của bạn

  1. Sử dụng pre_get_postsđể thay đổi truy vấn chính của bạn. Sử dụng một WP_Queryđối tượng riêng (phương pháp 2) cho các vòng lặp thứ cấp trong các trang mẫu.
  2. Nếu bạn muốn thay đổi truy vấn của vòng lặp chính, hãy sử dụng pre_get_posts.

Vậy có kịch bản nào khi một người sẽ đi thẳng đến get_posts () thay vì WP_Query không?
urok93

@drtanz - vâng. Ví dụ, bạn không cần phân trang hoặc bài viết dính ở trên cùng - trong những trường hợp get_posts()này hiệu quả hơn.
Stephen Harris

Nhưng sẽ không thêm một truy vấn bổ sung mà chúng ta có thể sửa đổi pre_get_posts để sửa đổi truy vấn chính?
urok93

@drtanz - bạn sẽ không sử dụng get_posts()cho truy vấn chính - đó là truy vấn phụ.
Stephen Harris

1
@StephenHarris Right =) Nếu bạn sử dụng next_post () trên đối tượng thay vì sử dụng the_post, bạn không bước vào truy vấn toàn cầu và không cần phải nhớ sử dụng wp_reset_postdata sau đó.
nhân

55

Có hai bối cảnh khác nhau cho các vòng lặp:

  • vòng lặp chính xảy ra dựa trên yêu cầu URL và được xử lý trước khi các mẫu được tải
  • các vòng lặp thứ cấp xảy ra theo bất kỳ cách nào khác, được gọi từ các tệp mẫu hoặc theo cách khác

Vấn đề với query_posts()nó là vòng lặp thứ cấp cố gắng trở thành một vòng lặp chính và thất bại thảm hại. Như vậy quên nó tồn tại.

Để sửa đổi vòng lặp chính

  • không sử dụng query_posts()
  • sử dụng pre_get_postsbộ lọc với $query->is_main_query()kiểm tra
  • luân phiên sử dụng requestbộ lọc (hơi quá thô nên ở trên là tốt hơn)

Để chạy vòng lặp thứ cấp

Sử dụng new WP_Queryhoặc get_posts()có thể hoán đổi cho nhau khá nhiều (cái sau là lớp bọc mỏng cho cái trước).

Dọn dẹp

Sử dụng wp_reset_query()nếu bạn đã sử dụng query_posts()hoặc gửi nhầm với toàn cầu $wp_querytrực tiếp - vì vậy bạn sẽ gần như không bao giờ cần.

Sử dụng wp_reset_postdata()nếu bạn đã sử dụng the_post()hoặc setup_postdata()hoặc rối tung với toàn cầu $postvà cần khôi phục trạng thái ban đầu của những thứ liên quan đến bài.


3
Rarst có nghĩa làwp_reset_postdata()
Gregory

23

Có các kịch bản hợp pháp để sử dụng query_posts($query), ví dụ:

  1. Bạn muốn hiển thị danh sách các bài đăng hoặc bài đăng loại tùy chỉnh trên một trang (sử dụng mẫu trang)

  2. Bạn muốn phân trang của những bài viết đó hoạt động

Bây giờ tại sao bạn muốn hiển thị nó trên một trang thay vì sử dụng một mẫu lưu trữ?

  1. Nó trực quan hơn cho quản trị viên (khách hàng của bạn?) - họ có thể thấy trang trong 'Trang'

  2. Tốt hơn là thêm nó vào menu (không có trang, họ sẽ phải thêm url trực tiếp)

  3. Nếu bạn muốn hiển thị nội dung bổ sung (văn bản, hình thu nhỏ đăng hoặc bất kỳ nội dung meta tùy chỉnh nào) trên mẫu, bạn có thể dễ dàng lấy nội dung đó từ trang (và tất cả cũng có ý nghĩa hơn đối với khách hàng). Xem nếu bạn đã sử dụng một mẫu lưu trữ, bạn sẽ cần mã hóa nội dung bổ sung hoặc sử dụng ví dụ tùy chọn chủ đề / plugin (điều này làm cho nó ít trực quan hơn cho khách hàng)

Đây là một mã ví dụ đơn giản hóa (sẽ có trên mẫu trang của bạn - ví dụ: page-page-of-post.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...
}

Bây giờ, để hoàn toàn rõ ràng, chúng ta cũng có thể tránh sử dụng query_posts()ở đây và sử dụng WP_Querythay vào đó - như vậy:

// ...

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

// ...

Nhưng, tại sao chúng ta sẽ làm điều đó khi chúng ta có một chức năng nhỏ như vậy có sẵn cho nó?


1
Brian, cảm ơn vì điều đó. Tôi đã vật lộn để làm cho pre_get_posts hoạt động trên một trang trong CHÍNH XÁC kịch bản bạn mô tả: khách hàng cần thêm các trường / nội dung tùy chỉnh vào trang khác, vì vậy cần phải tạo một "trang"; khách hàng cần phải xem một cái gì đó để thêm vào menu điều hướng, như thêm một liên kết tùy chỉnh thoát khỏi chúng; v.v ... +1 từ tôi!
Will Lanni

2
Điều đó cũng có thể được thực hiện bằng cách sử dụng "pre_get_posts". Tôi đã làm điều đó để có một "trang đầu tĩnh" liệt kê các loại bài đăng tùy chỉnh của tôi theo thứ tự tùy chỉnh và với bộ lọc tùy chỉnh. Trang này cũng được phân trang. Kiểm tra câu hỏi này để xem nó hoạt động như thế nào: wordpress.stackexchange.com/questions/30851/NH Vì vậy, trong ngắn hạn, vẫn không có kịch bản hợp pháp hơn để sử dụng query_posts;)
2ndkauboy

1
Bởi vì "Cần lưu ý rằng việc sử dụng điều này để thay thế truy vấn chính trên trang có thể tăng thời gian tải trang, trong trường hợp xấu nhất là tăng gấp đôi số lượng công việc cần thiết hoặc hơn. Mặc dù dễ sử dụng, chức năng này cũng dễ bị nhầm lẫn và những vấn đề sau này. " Nguồn codex.wordpress.org/Function_Reference/query_posts
Claudiu Creanga

Câu trả lời này là tất cả các loại sai. Bạn có thể tạo một "Trang" trong WP với cùng một URL với loại bài đăng Tùy chỉnh. VẬY nếu CPT của bạn là Chuối, bạn có thể nhận được một trang có tên Chuối có cùng URL. Sau đó, bạn sẽ kết thúc với siteurl.com/bananas. Miễn là bạn có archive-bananas.php trong thư mục chủ đề của mình, thì nó sẽ sử dụng mẫu và "ghi đè" trang đó thay thế. Như đã nêu trong một trong những ý kiến ​​khác, sử dụng "phương pháp" này tạo ra khối lượng công việc gấp đôi cho WP, do đó KHÔNG nên sử dụng.
Lai Web Dev

8

Tôi sửa đổi truy vấn WordPress từ hàm.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%')"; 
}

sẽ thích thú khi xem ví dụ này nhưng mệnh đề nằm ở đâu trên meta tùy chỉnh.
Andrew Welch ngày

6

Chỉ cần phác thảo một số cải tiến cho câu trả lời được chấp nhận kể từ khi WordPress phát triển theo thời gian và một số thứ đã khác bây giờ (năm năm sau):

pre_get_postslà một bộ lọc, để thay đổi bất kỳ truy vấn nào. Nó thường được sử dụng để chỉ thay đổi 'truy vấn chính':

Thực sự là một cái móc hành động. Không phải là bộ lọc và nó sẽ ảnh hưởng đến bất kỳ truy vấn nào.

Truy vấn chính xuất hiện trong các mẫu của bạn dưới dạng:

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

Thật ra, điều này cũng không đúng. Hàm have_postslặp lại global $wp_queryđối tượng không chỉ liên quan đến truy vấn chính. global $wp_query;cũng có thể được thay đổi với các truy vấn thứ cấp.

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

get_posts ()

Đây thực chất là một trình bao bọc cho một phiên bản riêng của đối tượng WP_Query.

Trên thực tế, ngày nay WP_Querylà một lớp, vì vậy chúng ta có một thể hiện của một lớp.


Để kết luận: Tại thời điểm @StephenHarris viết rất có thể tất cả điều này là đúng, nhưng theo thời gian, mọi thứ trong WordPress đã được thay đổi.


Về mặt kỹ thuật, đó là tất cả các bộ lọc dưới mui xe, các hành động chỉ là một bộ lọc đơn giản. Nhưng bạn đã đúng ở đây, đó là một hành động vượt qua một đối số bằng tham chiếu, đó là cách nó khác với các hành động đơn giản hơn.
Milo

get_poststrả về một mảng các đối tượng bài, không phải là một WP_Queryđối tượng, vì vậy điều đó thực sự vẫn đúng. và WP_Queryluôn luôn là một lớp, thể hiện của một lớp = đối tượng.
Milo

Cảm ơn, @Milo, chính xác từ một số lý do tôi đã có mô hình quá cỡ trong đầu.
prosti
Licensed under cc by-sa 3.0 with attribution required.