To Loop Or Not To Loop :: 2018-05-24 14:16:03

[excerpt]There are 4 fundamental methods for “Looping” in WordPress: Default, query_posts(), new WP_Query, get_posts(). What does it mean to be “In The Loop” concerning WordPress?[/excerpt]

yes I know this article is too long and needs maintenance, but as is you will find all pertinent data regarding being “in the loop” and what to do if you find yourself “loopless” in need of “globals” support… 🙂

keep in mind the word context

the default loop is possible because all the WordPress globals have been prepared

<?php if (have_posts()) : while (have_posts()) : the_post(); ?>
<div <?php post_class(); ?> id="post-<?php the_ID(); ?>">
<h1><a href="<?php the_permalink(); ?>"><?php the_title(); ?></a></h1>
<?php the_content(); ?>
</div>
<?php endwhile; ?>
<div class="navigation">
/** Yes! Navigation & Pagination are driven by the "Manna" provided by "The Loop" ( Otherwise we have to conjure up Demon "globals"! ) **/
<div class="next-posts"><?php next_posts_link(); ?></div>
<div class="prev-posts"><?php previous_posts_link(); ?></div>
</div>
<?php else : ?>
<div <?php post_class(); ?> id="post-<?php the_ID(); ?>">
<h1>Not Found</h1>
</div>
<?php endif; ?>
from the codex: This function enables someone to hook into `pre_get_posts` and modify *only* the main query. This is a boolean function, meaning it returns either TRUE or FALSE. NOTE: admin screens also have a main query and this function can be used to detect it there.

The “pre_get_posts” hook is called after the query variable object is created, but before the actual query is run.

this code should be used (the method, not the function), to detect the main (default) query, and modify it with pre_get_posts()

more is said in the article concerning query_posts() and why you should use the detect & modify strategy of $query->is_main_query()/pre_get_posts hook

$query->is_main_query();
this code will display all globals used in the page processing in the source code of the page

<?php 
/** in functions.php **/
function print_globals() {
    ksort( $GLOBALS );
	print '<!-- ';
	print implode( '--><!-- ', array_keys( $GLOBALS ) );
	print ' -->';
}add_action( 'shutdown', 'print_globals' );
?>
<!--  in footer.php -->

<?php 
    print_globals(); 
?>
query_posts uses ‘querystring’ style: the parameters

the important thing here to observe is the use of the global

without prepending the global $query_string to the first parameter, you wipe out the “default query” string

<?php 
<span class="very_important">global $query_string</span>; // required
$posts = query_posts($query_string.'&cat=-9'); // exclude category 9 ?>
<?php // DEFAULT LOOP GOES HERE ?>
<?php wp_reset_query(); // reset the query ?>

from the code comments themselves

<?php
/**
 * Sets up The Loop with query parameters.
 *
 * Note: This function will completely override the main query and isn't intended for use
 * by plugins or themes. Its overly-simplistic approach to modifying the main query can be
 * problematic and should be avoided wherever possible. In most cases, there are better,
 * more performant options for modifying the main query such as via the {@see 'pre_get_posts'}
 * action within WP_Query.
 *
 * This must not be used within the WordPress Loop.
 *
 * @since 1.5.0
 *
 * @global WP_Query $wp_query Global WP_Query instance.
 *
 * @param array|string $query Array or string of WP_Query arguments.
 * @return array List of post objects.
 */
function query_posts($query) {
	$GLOBALS['wp_query'] = new WP_Query();
	return $GLOBALS['wp_query']->query($query);
}
?>

the recommended alternative to using query_posts() to modify the default/main query

<?php
// display only custom post types from the default query
function detect_and_modify_default_query( $query ) {
	// is frontend(! is_admin()) and default(is_main_query()) query?
    if ( ! is_admin() &amp;&amp; $query->is_main_query() ) :
        $query->set( 'post_type', 'cpt' );
    endif;
}add_action( 'pre_get_posts', 'detect_and_modify_default_query' );
?>
first off, study wp-includes/class-wp-query.php

Object Oriented, so unlike trolling for other comments and code, this will be found with the files prepended with “class”. This is the workhorse of the WordPress mechanism. Love it, learn it, live it. No really, there is a lot of “connect-the-dots” type information in here….

from the codex: In each iteration, the_post(), which calls $wp_query->the_post() is called, setting up internal variables within $wp_query and the global $post variable (which the Template Tags rely on). If you use the_post() with your query, you need to run wp_reset_postdata() afterwards to have Template Tags use the main query’s current post again.

standard how to instantiate WP_Query oject, note pagination in the loop…

<?php 
// the query
$the_query = new WP_Query( $args ); ?>

<?php if ( $the_query->have_posts() ) : ?>

	<!-- pagination here -->

	<!-- the loop -->
	<?php while ( $the_query->have_posts() ) : $the_query->the_post(); ?>
		<h2><<?php the_title(); ?></h2>
	<?php endwhile; ?>
	<!-- end of the loop -->

	<!-- pagination here -->

	<?php wp_reset_postdata(); ?>

<?php else : ?>
	<p><<?php esc_html_e( 'Sorry, no posts matched your criteria.' ); ?></p>
<?php endif; ?>

<!-- code class="php_code" -->
<?php 

?>

WP_Querys can use either PHP function style argument or querystring style.

get_posts requires the use of an array for the parameters.

<?php
$params = array('numberposts'=>-1/** all **/, 'category'=>-1 /** exclude "hello world" 🙂 **/);
?>
<?php
/**
 * Retrieve list of latest posts or posts matching criteria.
 *
 * The defaults are as follows:
 *
 * @since 1.2.0
 *
 * @see WP_Query::parse_query()
 *
 * @param array $args {
 *     Optional. Arguments to retrieve posts. See WP_Query::parse_query() for all
 *     available arguments.
 *
 *     @type int        $numberposts      Total number of posts to retrieve. Is an alias of $posts_per_page
 *                                        in WP_Query. Accepts -1 for all. Default 5.
 *     @type int|string $category         Category ID or comma-separated list of IDs (this or any children).
 *                                        Is an alias of $cat in WP_Query. Default 0.
 *     @type array      $include          An array of post IDs to retrieve, sticky posts will be included.
 *                                        Is an alias of $post__in in WP_Query. Default empty array.
 *     @type array      $exclude          An array of post IDs not to retrieve. Default empty array.
 *     @type bool       $suppress_filters Whether to suppress filters. Default true.
 * }
 * @return array List of posts.
 */
function get_posts( $args = null ) {
	$defaults = array(
		'numberposts' => 5,
		'category' => 0, 'orderby' => 'date',
		'order' => 'DESC', 'include' => array(),
		'exclude' => array(), 'meta_key' => '',
		'meta_value' =>'', 'post_type' => 'post',
		'suppress_filters' => true
	);

	$r = wp_parse_args( $args, $defaults );
	if ( empty( $r['post_status'] ) )
		$r['post_status'] = ( 'attachment' == $r['post_type'] ) ? 'inherit' : 'publish';
	if ( ! empty($r['numberposts']) && empty($r['posts_per_page']) )
		$r['posts_per_page'] = $r['numberposts'];
	if ( ! empty($r['category']) )
		$r['cat'] = $r['category'];
	if ( ! empty($r['include']) ) {
		$incposts = wp_parse_id_list( $r['include'] );
		$r['posts_per_page'] = count($incposts);  // only the number of posts included
		$r['post__in'] = $incposts;
	} elseif ( ! empty($r['exclude']) )
		$r['post__not_in'] = wp_parse_id_list( $r['exclude'] );

	$r['ignore_sticky_posts'] = true;
	$r['no_found_rows'] = true;

	$get_posts = new WP_Query;
	return $get_posts->query($r);

}
?>

substitute one for the other

Retrieve list of latest posts

<?php
numberposts
posts_per_page
from wp-includes/class-wp-query.php

wp-includes/posts.php#get_posts() says “read ‘parse_query()’ for all args

<?php
/**
	 * Parse a query string and set query type booleans.
	 *
	 * @since 1.5.0
	 * @since 4.2.0 Introduced the ability to order by specific clauses of a `$meta_query`, by passing the clause's
	 *              array key to `$orderby`.
	 * @since 4.4.0 Introduced `$post_name__in` and `$title` parameters. `$s` was updated to support excluded
	 *              search terms, by prepending a hyphen.
	 * @since 4.5.0 Removed the `$comments_popup` parameter.
	 *              Introduced the `$comment_status` and `$ping_status` parameters.
	 *              Introduced `RAND(x)` syntax for `$orderby`, which allows an integer seed value to random sorts.
	 * @since 4.6.0 Added 'post_name__in' support for `$orderby`. Introduced the `$lazy_load_term_meta` argument.
	 * @since 4.9.0 Introduced the `$comment_count` parameter.
	 *
	 * @param string|array $query {
	 *     Optional. Array or string of Query parameters.
	 *
	 *     @type int          $attachment_id           Attachment post ID. Used for 'attachment' post_type.
	 *     @type int|string   $author                  Author ID, or comma-separated list of IDs.
	 *     @type string       $author_name             User 'user_nicename'.
	 *     @type array        $author__in              An array of author IDs to query from.
	 *     @type array        $author__not_in          An array of author IDs not to query from.
	 *     @type bool         $cache_results           Whether to cache post information. Default true.
	 *     @type int|string   $cat                     Category ID or comma-separated list of IDs (this or any children).
	 *     @type array        $category__and           An array of category IDs (AND in).
	 *     @type array        $category__in            An array of category IDs (OR in, no children).
	 *     @type array        $category__not_in        An array of category IDs (NOT in).
	 *     @type string       $category_name           Use category slug (not name, this or any children).
	 *     @type array|int    $comment_count           Filter results by comment count. Provide an integer to match
	 *                                                 comment count exactly. Provide an array with integer 'value'
	 *                                                 and 'compare' operator ('=', '!=', '>', '>=', '<', '<=' ) to
	 *                                                 compare against comment_count in a specific way.
	 *     @type string       $comment_status          Comment status.
	 *     @type int          $comments_per_page       The number of comments to return per page.
	 *                                                 Default 'comments_per_page' option.
	 *     @type array        $date_query              An associative array of WP_Date_Query arguments.
	 *                                                 See WP_Date_Query::__construct().
	 *     @type int          $day                     Day of the month. Default empty. Accepts numbers 1-31.
	 *     @type bool         $exact                   Whether to search by exact keyword. Default false.
	 *     @type string|array $fields                  Which fields to return. Single field or all fields (string),
	 *                                                 or array of fields. 'id=>parent' uses 'id' and 'post_parent'.
	 *                                                 Default all fields. Accepts 'ids', 'id=>parent'.
	 *     @type int          $hour                    Hour of the day. Default empty. Accepts numbers 0-23.
	 *     @type int|bool     $ignore_sticky_posts     Whether to ignore sticky posts or not. Setting this to false
	 *                                                 excludes stickies from 'post__in'. Accepts 1|true, 0|false.
	 *                                                 Default 0|false.
	 *     @type int          $m                       Combination YearMonth. Accepts any four-digit year and month
	 *                                                 numbers 1-12. Default empty.
	 *     @type string       $meta_compare            Comparison operator to test the 'meta_value'.
	 *     @type string       $meta_key                Custom field key.
	 *     @type array        $meta_query              An associative array of WP_Meta_Query arguments. See WP_Meta_Query.
	 *     @type string       $meta_value              Custom field value.
	 *     @type int          $meta_value_num          Custom field value number.
	 *     @type int          $menu_order              The menu order of the posts.
	 *     @type int          $monthnum                The two-digit month. Default empty. Accepts numbers 1-12.
	 *     @type string       $name                    Post slug.
	 *     @type bool         $nopaging                Show all posts (true) or paginate (false). Default false.
	 *     @type bool         $no_found_rows           Whether to skip counting the total rows found. Enabling can improve
	 *                                                 performance. Default false.
	 *     @type int          $offset                  The number of posts to offset before retrieval.
	 *     @type string       $order                   Designates ascending or descending order of posts. Default 'DESC'.
	 *                                                 Accepts 'ASC', 'DESC'.
	 *     @type string|array $orderby                 Sort retrieved posts by parameter. One or more options may be
	 *                                                 passed. To use 'meta_value', or 'meta_value_num',
	 *                                                 'meta_key=keyname' must be also be defined. To sort by a
	 *                                                 specific `$meta_query` clause, use that clause's array key.
	 *                                                 Accepts 'none', 'name', 'author', 'date', 'title',
	 *                                                 'modified', 'menu_order', 'parent', 'ID', 'rand',
	 *                                                 'relevance', 'RAND(x)' (where 'x' is an integer seed value),
	 *                                                 'comment_count', 'meta_value', 'meta_value_num', 'post__in',
	 *                                                 'post_name__in', 'post_parent__in', and the array keys
	 *                                                 of `$meta_query`. Default is 'date', except when a search
	 *                                                 is being performed, when the default is 'relevance'.
	 *
	 *     @type int          $p                       Post ID.
	 *     @type int          $page                    Show the number of posts that would show up on page X of a
	 *                                                 static front page.
	 *     @type int          $paged                   The number of the current page.
	 *     @type int          $page_id                 Page ID.
	 *     @type string       $pagename                Page slug.
	 *     @type string       $perm                    Show posts if user has the appropriate capability.
	 *     @type string       $ping_status             Ping status.
	 *     @type array        $post__in                An array of post IDs to retrieve, sticky posts will be included
	 *     @type string       $post_mime_type          The mime type of the post. Used for 'attachment' post_type.
	 *     @type array        $post__not_in            An array of post IDs not to retrieve. Note: a string of comma-
	 *                                                 separated IDs will NOT work.
	 *     @type int          $post_parent             Page ID to retrieve child pages for. Use 0 to only retrieve
	 *                                                 top-level pages.
	 *     @type array        $post_parent__in         An array containing parent page IDs to query child pages from.
	 *     @type array        $post_parent__not_in     An array containing parent page IDs not to query child pages from.
	 *     @type string|array $post_type               A post type slug (string) or array of post type slugs.
	 *                                                 Default 'any' if using 'tax_query'.
	 *     @type string|array $post_status             A post status (string) or array of post statuses.
	 *     @type int          $posts_per_page          The number of posts to query for. Use -1 to request all posts.
	 *     @type int          $posts_per_archive_page  The number of posts to query for by archive page. Overrides
	 *                                                 'posts_per_page' when is_archive(), or is_search() are true.
	 *     @type array        $post_name__in           An array of post slugs that results must match.
	 *     @type string       $s                       Search keyword(s). Prepending a term with a hyphen will
	 *                                                 exclude posts matching that term. Eg, 'pillow -sofa' will
	 *                                                 return posts containing 'pillow' but not 'sofa'. The
	 *                                                 character used for exclusion can be modified using the
	 *                                                 the 'wp_query_search_exclusion_prefix' filter.
	 *     @type int          $second                  Second of the minute. Default empty. Accepts numbers 0-60.
	 *     @type bool         $sentence                Whether to search by phrase. Default false.
	 *     @type bool         $suppress_filters        Whether to suppress filters. Default false.
	 *     @type string       $tag                     Tag slug. Comma-separated (either), Plus-separated (all).
	 *     @type array        $tag__and                An array of tag ids (AND in).
	 *     @type array        $tag__in                 An array of tag ids (OR in).
	 *     @type array        $tag__not_in             An array of tag ids (NOT in).
	 *     @type int          $tag_id                  Tag id or comma-separated list of IDs.
	 *     @type array        $tag_slug__and           An array of tag slugs (AND in).
	 *     @type array        $tag_slug__in            An array of tag slugs (OR in). unless 'ignore_sticky_posts' is
	 *                                                 true. Note: a string of comma-separated IDs will NOT work.
	 *     @type array        $tax_query               An associative array of WP_Tax_Query arguments.
	 *                                                 See WP_Tax_Query->queries.
	 *     @type string       $title                   Post title.
	 *     @type bool         $update_post_meta_cache  Whether to update the post meta cache. Default true.
	 *     @type bool         $update_post_term_cache  Whether to update the post term cache. Default true.
	 *     @type bool         $lazy_load_term_meta     Whether to lazy-load term meta. Setting to false will
	 *                                                 disable cache priming for term meta, so that each
	 *                                                 get_term_meta() call will hit the database.
	 *                                                 Defaults to the value of `$update_post_term_cache`.
	 *     @type int          $w                       The week number of the year. Default empty. Accepts numbers 0-53.
	 *     @type int          $year                    The four-digit year. Default empty. Accepts any four-digit year.
	 * }
	 */
?>
how to make sense out of the settings and how to sniff whether your in “Loopy Land” or not (sly reference to Rick & Morty, “Froopy Land”)

<?php
/**
 * Is the query for the front page of the site?
 *
 * This is for what is displayed at your site's main URL.
 *
 * Depends on the site's "Front page displays" Reading Settings 'show_on_front' and 'page_on_front'.
 *
 * If you set a static page for the front page of your site, this function will return
 * true when viewing that page.
 *
 * Otherwise the same as @see is_home()
 *
 * @since 2.5.0
 *
 * @global WP_Query $wp_query Global WP_Query instance.
 *
 * @return bool True, if front of site.
 */
function is_front_page() {
	global $wp_query;

	if ( ! isset( $wp_query ) ) {
		_doing_it_wrong( __FUNCTION__, __( 'Conditional query tags do not work before the query is run. Before then, they always return false.' ), '3.1.0' );
		return false;
	}

	return $wp_query->is_front_page();
}

/**
 * Determines if the query is for the blog homepage.
 *
 * The blog homepage is the page that shows the time-based blog content of the site.
 *
 * is_home() is dependent on the site's "Front page displays" Reading Settings 'show_on_front'
 * and 'page_for_posts'.
 *
 * If a static page is set for the front page of the site, this function will return true only
 * on the page you set as the "Posts page".
 *
 * @since 1.5.0
 *
 * @see is_front_page()
 * @global WP_Query $wp_query Global WP_Query instance.
 *
 * @return bool True if blog view homepage, otherwise false.
 */
function is_home() {
	global $wp_query;

	if ( ! isset( $wp_query ) ) {
		_doing_it_wrong( __FUNCTION__, __( 'Conditional query tags do not work before the query is run. Before then, they always return false.' ), '3.1.0' );
		return false;
	}

	return $wp_query->is_home();
}
?>
    setup_postdata()

  • Sets up global post data. Helps to format custom query results for using Template tags.
  • fills the global variables $id, $authordata, $currentday, $currentmonth, $page, $pages, $multipage, $more, $numpages, which help many Template Tags work in the current post context.
  • does not assign the global $post variable so it’s important that you do this yourself.
<?php 
global $post;
setup_postdata( $post ); 
/**   
global $post;
   // Assign your post details to $post (& not any other variable name!!!!)
   $post = $post_object;
   setup_postdata( $post );
**/
?>

example 1

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

example 2

<ul>
<?php
global $wpdb,$post;
$result = $wpdb->get_results("SELECT $wpdb->posts.* FROM $wpdb->posts WHERE post_type = 'post' AND post_status = 'publish'");
foreach( $result as $post ) :
  setup_postdata($post); ?>
	<li><a href="<?php the_permalink(); ?>"><?php the_title(); ?></a></li>
<?php endforeach; 
/** wp_reset_postdata();  **/ ?>
</ul>
<?php
/**
 * Destroys the previous query and sets up a new query.
 *
 * This should be used after query_posts() and before another query_posts().
 * This will remove obscure bugs that occur when the previous WP_Query object
 * is not destroyed properly before another is set up.
 *
 * @since 2.3.0
 *
 * @global WP_Query $wp_query     Global WP_Query instance.
 * @global WP_Query $wp_the_query Copy of the global WP_Query instance created during wp_reset_query().
 */
function wp_reset_query() {
	$GLOBALS['wp_query'] = $GLOBALS['wp_the_query'];
	wp_reset_postdata();
}

/**
 * After looping through a separate query, this function restores
 * the $post global to the current post in the main query.
 *
 * @since 3.0.0
 *
 * @global WP_Query $wp_query Global WP_Query instance.
 */
function wp_reset_postdata() {
	global $wp_query;

	if ( isset( $wp_query ) ) {
		$wp_query->reset_postdata();
	}
}
?>

rewind_posts()

while wp_reset_query and wp_reset_postdata reset the entire query object, rewind_posts simply resets the post count

to use the same query in two different locations on the page

<?php
// rewind the posts and reset post index
function rewind_posts() {
	$this->current_post = -1;
	if ( $this->post_count > 0 ) {
		$this->post = $this->posts[0];
	}
}
?>
  • roaringbull says:

    test comment (going to do maintenance on this page, shorten it, probably split it into “child” pages

« »