ThemeShaper Forums » Thematic

[closed]

Multiple loops demystified

(10 posts)
  • Started 8 years ago by middlesister
  • Latest reply from lastraw
  • This topic is not a support question
  1. middlesister
    Member

    Inspired by helgatheviking's demystified threads, I decided to start one regarding loops. Not how to manipulate the displaying of the loop contents, but how to add custom loops outside the main one.

    There are already a lot of threads with answers to similar questions, and I thought it would be nice to gather some of them in one place. Most of this information is already written in the wordpress codex or in the different threads, but sometimes repeating or rewording the same information can make it easier to understand. This thread is just as much a result of me being curious and digging for information, as a hope that what I've gathered so far will be useful to others.

    As with the other 'demystified' threads, tips and solutions and code corrections are most welcome. Post links to threads or code snippets with examples of multiple loops in action. But if you come across issues you need to solve, it is best to start your own thread with your questions.

    I'll start with

    1. modifying the main loop with query_posts()
    2. making a second loop with query_posts() (not recommended)
    3. making a second loop with WP_Query()
    4. making a second loop using get_posts()
    5. the difference between get_posts() and WP_Query()

    Bear with me, this will be a lot to read ;)

    Posted 8 years ago #
  2. middlesister
    Member

    1. Modifying the main loop with query_posts()

    The simplest way of changing what to display on a page is modifying the query with query_posts().
    http://codex.wordpress.org/Function_Reference/query_posts

    The code need to be added above the loop. This example would remove all posts in the category 3 from the home page

    function modify_query() {
    	query_posts('cat=-3&posts_per_page=10');
    }
    add_action('thematic_above_indexloop', 'modify_query');

    There are two ways to send your query - as a string and as an array. This code does the same as the above

    function modify_query() {
    	$args = array (
    		'cat'            => -3,
    		'posts_per_page' => 10
    	);
    	query_posts($args);
    }
    add_action('thematic_above_indexloop', 'modify_query');

    These two ways are equivalent and you can choose whichever you want. The array method is a bit more powerful and has more options when it comes to more complex queries, but in most cases the string method is good enough.

    Both of these examples are overwriting the existing query. If you want to add to the existing query instead of replacing it you do it like this:

    function modify_query() {
    	global $query_string;
    	query_posts( $query_string . '&order=ASC' );
    }
    add_action('thematic_above_indexloop', 'modify_query');

    or

    function modify_query() {
    	global $wp_query;
    	$my_args = array (
    		'order' => 'ASC'
    	);
    	$args = array_merge( $wp_query->query, $my_args );
    	query_posts( $args );
    }
    add_action('thematic_above_indexloop', 'modify_query');

    You can also use contitionals to only modify the loop under certain circumstances. This will show all posts on one page if we are on category 5.

    function modify_query() {
    	if( is_category('5') ) {
    		query_posts('posts_per_page=-1');
    	}
    }
    add_action('thematic_above_categoryloop', 'modify_query');
    Posted 8 years ago #
  3. middlesister
    Member

    2. making a second loop with query_posts() (not recommended)

    Making a second loop using query_posts() is certainly possible to do, and there are lot of examples and tutorials floating around the internet that will explain how to do that. However, it is not generally a good idea and not how the function was intended. The wordpress codex specifically states that query_posts() is intended for use on the main loop only.

    In wordpress, every page you load has a global $wp_query object. This object is how wordpress keeps track of where you are and what to show - it takes care of the conditionals is_page(), is_home() and so forth, and retrieves the posts from the database. Among many other things. If you are not careful, you might get unexpected results if you are trying to use for example conditionals after your second loop since you are messing with the global values.

    That said, it is possible to do. You need to store the original query in a temporary variable and restore it after you are done with the loop. I was hesitating if I should include code that is not considered best practice, but for the sake of completeness, here it is.

    $temp_query = $wp_query;
    query_posts('cat=7&posts_per_page=5');
    while (have_posts()) : the_post();
        // post formatting goes here
    endwhile;
    $wp_query = $temp_query;
    Posted 8 years ago #
  4. middlesister
    Member

    3. making a second loop with WP_Query()

    http://codex.wordpress.org/Function_Reference/WP_Query

    The recommended way of creating a second loop is creating a new WP_Query object for that loop. This will make a second, local query and leave the global one alone.

    $second_query = new WP_Query('cat=7&posts_per_page=5');
    while ($second_query->have_posts()) : $second_query->the_post();
    	// post formatting goes here
    endwhile;
    wp_reset_postdata();

    The important bit here is wp_reset_postdata() (available since 3.0). The while loop makes all the template tags like the_title(), the_content() etc refer to the local query. wp_reset_postdata() makes sure that everything in $post now is referring to the global $wp_query again and you can move along with your business like nothing happened.

    Your second loop can be added wherever you need it and as usual you can control when to display it with conditionals. You can add as many loops as you like and just like query_posts() you can choose to pass the arguments as an array. This example will only show the extra loop on the page named testimonials and fetch posts that have both category 2 and 6 assigned, in random order.

    function my_second_loop() {
    	if( is_page('testimonials') ) {
    		$args = array (
    			'category__and' => array( 2, 6 ),
    			'orderby'       => 'rand'
    		);
    
    		$second_query = new WP_Query( $args );
    		while ($second_query->have_posts()) : $second_query->the_post();
    			// post formatting goes here
    		endwhile;
    		wp_reset_postdata();
    	}
    }
    add_action('thematic_belowcontent', 'my_second_loop' );
    Posted 8 years ago #
  5. middlesister
    Member

    4. making a second loop using get_posts()

    http://codex.wordpress.org/Template_Tags/get_posts

    There is another "approved" way of making a second loop, and that is using get_posts(). Despite the name you are not restricted to only regular posts but can fetch any post type available to you - including custom post types. It will return the results in an array.

    This loop construct looks only a little different than the others:

    global $post;
    $my_pages = get_posts('numberposts=6&post_type=page');
    foreach ($my_pages as $post ) : setup_postdata($post);
    	// post formatting goes here
    endforeach;
    wp_reset_postdata();

    In this case, the important part is the setup_postdata(). Without it, you will not be able to use the_content() etc in your post formatting code. And, like with WP_Query, you will need to use wp_reset_postdata() afterwards to make sure that things are referring to the global $wp_query object again.

    If you want keep any global values untouched, you can skip the setup_postdata() part, and reference the array content directly.

    $my_pages = get_posts('numberposts=6&post_type=page');
    foreach ($my_pages as $page ) :
    	echo $page->post_title;
    	echo $page->post_content;
    endforeach;

    Why would you want to do this? Well for one, you might want to make nested loops and this way you will avoid confusion around which title and which content are referred to. Or, you just want to leave all globals alone for one reason or the other.

    And just as with the WP_Query loop, you can pass parameters as an array, add loops to whichever hook you want, add as many loops you want yada yada yada. So, what is the difference then between using get_posts() and WP_Query()?

    Posted 8 years ago #
  6. middlesister
    Member

    5. The difference between get_posts() and WP_Query()

    Well I have been asking myself this question for a while and I think I finally found a good answer at the wordpress hackers mailing list. http://old.nabble.com/new-WP_Query-vs.-get_posts%28%29---any-difference--td26904075.html

    Actually, get_posts() calls WP_Query behind the scenes. In short, get_posts() returns an array of posts, and WP_Query() returns an entire query object with all the information that carries with it. In a way get_posts() is a shortcut to just the posts and their content.

    One thing to keep in mind is that get_posts() is suppressing query filters by default and ignores sticky posts. This can make an impact if you are using certain plugins that filter the query in some way, like the WPML multilingual plugin. If you need the filters, you can use suppress_filters=0 in your query string. On the other hand, if you want to bypass the filters then using get_posts() is an easy way to do so.

    Query filters and some uses for them are discussed here http://codex.wordpress.org/Custom_Queries

    If you don't know what all this means, then you don't have to worry too much about it. To developers the difference is important, but for most cases we can basically say that you can choose the method that makes most sense to you - WP_Query() or get_posts().

    Posted 8 years ago #
  7. AWESOME SAUCE!! demystification continues!

    Posted 8 years ago #
  8. nice work!

    Posted 8 years ago #
  9. this is key!

    Posted 8 years ago #
  10. this is great. thanks.

    Posted 8 years ago #

RSS feed for this topic

Topic Closed

This topic has been closed to new replies.