Skip to content

WordPress Joy #1

Ran across an issue today while re-finalizing some code for this site, and happened to break the loop on the blogroll such that it would continue listing the posts until the code timed-out.

Here's the skeleton of the code in question:

if( have_posts() ){ // Perform some pre-loop setup code, if any do{ the_post(); // Do something with the active post if( have_posts() ){ // Emit a divider between posts } }while( have_posts() ); }

As it happens, when the inner-most if gets a false, it also inadvertently rewinds the loop. This differs from the "traditional" iterator behavior in which the iterator stays ended once it has ended — if the iterator is somehow resetable, you must manually request that it does so. This (dead things stay dead) is actually the better choice from a guaranteeing correctness standpoint: Some arbitrarilty nested code could freely check whether the loop had ended — completely outside the knowledge of the main loop — without the risk of breaking the main thread.

The way that WordPress handles its automatic rewind does make things easier if you plan to run through the main loop more than once. Is this a particularly common operation on an average template? I'd wager 'no' and remember that the savings of one function call (rewind_posts) comes at a cost of creating a difficult-to-diagnose trap that can be stumbled upon, especially if there are hooks in the body of your main loop. That's overall a non-win to me. But futher, neither have_posts nor WP_Query::have_posts document this fact. In fairness, it does get a small mention on https://codex.wordpress.org/Function_Reference/have_posts on the second line of the description.

In the end, tho, this is a public-facing part of the API, which means we're stuck with it. I wanted to toss this out there for anyone who may be searching for the same error. Here's the rough solution I've settled on (it still runs the risk of arbitrary hooks messing this up):

if( have_posts() ){ // Perform some pre-loop setup code, if any do{ the_post(); // Do something with the active post $have_posts = have_posts(); if( $have_posts ){ // Emit a divider between posts } }while( $have_posts ); }

P.S. While everything I listed above was framed in terms of the main loop, it does also apply to any loop created via WP_Query.

Related Posts