It’s 8:00 in the morning. I just got to work after a bike and subway ride. The smell of coffee emanating from my Dragon Ball cup is awakening my senses and I’m feeling good. It’s time to open my browser and code editor (which, of course, is Vim—there’s no way I’m using Atom) and get some work done.
While I take my first sip of coffee I read the only support ticket I have in my inbox: apparently, there’s a client who has trouble with Nelio Content—our plugin doesn’t share social messages properly. Weird! AFAIK, this issue only happens to him, so I’m sure it must be a problem with his installation… I bet is a third-party plugin. It always is.
It’s not the best way to start my day, but at least I know it’ll be an entertaining day. So now it’s time to investigate what’s happening… who said WordPress development was easy? In a couple of hours Toni and I were able to find out what was amiss: the WordPress Loop. Or, well, if I’m precise, I should say Nested WordPress Loops.
Do you want to know what happened and how we fixed it? Keep reading and discover more about the WordPress loop!
Quick Reminder—What’s the WordPress Loop?
A couple of years ago, Antonio Villegas wrote a series of posts explaining what the WordPress loop is, how it works, how you can modify it, and some advanced tricks. Unfortunately, they’re only available in Spanish… but if you’re interested in them, just let us know and we’ll translate them into English. Anyway, I liked those posts because they were a very good introduction to the topic:
The WordPress Loop determines what content (posts, pages, or custom content) will be displayed when you navigate to a page on your website. (…)
Essentially, this is how WordPress works: the Loop queries the WordPress database to obtain the posts your user is interested in taking into account a set of parameters.
In other words, the loop is nothing more than WordPress‘s way of retrieving a set of posts and “manipulating” them (usually to display them on screen). Let’s see a very simple example of the loop:
Are you able to identify all the different parts? It’s very simple, look: in lines 3-6 we set the parameters of our search (in this case, we want 5 posts), in lines 9 and 10 we go through all the posts we’ve found (we’re basically printing their titles in a list), and finally in line 15 we undo this loop and reset the query to whatever WordPress was doing.
Well, now that you understand how the loop works, let’s see what happened to us at Nelio.
Running Multiple Loops All At Once
In our plugin we defined a simple function that, given the identifier of a post, returned an array with data about it. Something like this:
As you can see, nothing new under the sun. In this function, we simply build the loop on lines 5-7, make sure the post we’re querying exists in lines 9-12, and do the usual work: load the post (line 14), create the array we’ll return, and finish. Quite straightforward, isn’t it?
Suppose, for example, that we call this function with the post ID
1 and that the result we expect is the next one:
Well, surprisingly enough, this is what we got when we run it on the installation of our customer:
As you can see, the title is no longer My First Post, but something completely different. For some inexplicable reason, our function returned Some Random Title, which, in case you’re wondering, corresponds to a different post in our blog. What the hell is going on here? Why is our seemingly simple function failing so miserably? The explanation is simple and unfortunate…
I’m so happy about Nelio Content that I will sound like a payed advocate… but here’s why you’ll love it: it works as promised, its auto-scheduling feature is top-notch, Nelio’s value for money is unmatched, and the support team feels like your own.
Damn You, Nested Loops!
Our client had a plugin installed that appended a list of related posts to the content of a post. In essence, this other plugin does the following:
That is, this other plugin basically launches a new loop to search for related posts (in the example we’re simply loading post
25), retrieves those posts, appends them to the content, and resets the “original query” using
wp_reset_postdata(). So far, so good.
Now pay attention: when does this snippet run? It’s a function hooked to
the_content, so whenever the
the_content filter runs, so will this snippet. In other words, if this third-party plugin is active, it’ll (at least) run when our plugin executes lines 18! ? So, it’s very well possible that, by the time the execution reaches line 19 in our plugin, the query has changed… What’s the title of post
What the…! So there you have it! The title I’m loading corresponds to
25, which means that, indeed, the nested loop overwrote my own loop and I lost the reference to my own post. ??
How to Work with Multiple Nested Loops and Avoid Trouble
Well, at least we finally know what’s going on. Nested loops are pretty common, so now that we know what’s wrong, the solution should be simple and straightforward. Or is it? I couldn’t find the answer in the Codex, so I asked in the WordPress Forums…
…and after a few minutes I had an answer from Pascal Birchler (how cool is that!).
Unfortunately, the answer was not too comforting. Basically, there are two issues with the snippets I shared:
wp_reset_postdatadoesn’t reset the previously-active query, but the main loop’s. In other words, the third-party plugin can’t restore our query… ?
- The recommended way to solve this issue is… not introducing it in the first place! That is, don’t use calls that might trigger filters at all. ?
Although I am very grateful for the free and disinterested support I received, truth is I wasn’t satisfied with the solution. I wanted to use my implementation and be “filter-proofed”, so I opted for a different and radical fix: every time I run a template tag, I manually reset my loop (
$query->reset_postdata()) to make sure I’m always working with the correct post:
To be honest, I’m not very proud of this solution ? It’s clearly a workaround ?… but, hey, at least it works! ?
And that’s all for today! I hoped you learned something new. And, if you happen to know a better solution to this issue, please let me know ?