0

I wanted to add a random post type to be displayed on my index.php page so that it displays a random custom post type per day. However when I implemented the code it didn't show up anything. Any thoughts?

<?php 
      if ( false === ( $hadices = get_transient( 'random_hadiz' ) ) ) {
      // It wasn't there, so regenerate the data and save the transient

      $args = array(
         'post_type' => 'hadices',
         'orderby'   => 'rand',
         'posts_per_page' => '1'
      );

      $hadices = get_posts( $args );

      //Now we store the array for one day.
      //Just change the last parameter for another timespan in seconds.
      $seconds_until_next_day = strtotime('tomorrow') - time();
      set_transient( 'random_hadiz', $hadices , $seconds_until_next_day );
    }

    foreach ( $hadices as $post ) : setup_postdata( $post );
    if ( have_posts()) : while (have_posts()) : the_post();

        setup_postdata( $post ); ?>
<h2><a href="<?php the_permalink(); ?>"><?php the_title(); ?></a></h2>
<?php the_content(); ?>
<?php endwhile;
endif;
endforeach; 
wp_reset_postdata();

        ?>
  • Keep in mind that the performance characteristics of rand are painfully slow. Consider instead fetching 50 posts and randomly picking one in PHP instead, it'll be 100x faster – Tom J Nowell May 7 '18 at 14:39
2

The problem is the loop:

    foreach ( $hadices as $post ) : setup_postdata( $post );
    if ( have_posts()) : while (have_posts()) : the_post();

        setup_postdata( $post ); ?>
<h2><a href="<?php the_permalink(); ?>"><?php the_title(); ?></a></h2>
<?php the_content(); ?>
<?php endwhile;
endif;
endforeach; 
wp_reset_postdata();

If we fix the indenting and format it properly we get:

foreach ( $hadices as $post ) {
    setup_postdata( $post );
    if ( have_posts()) {
        while (have_posts()) {
            the_post();
            setup_postdata( $post );
            ?>
            <h2>
                <a href="<?php the_permalink(); ?>">
                    <?php the_title(); ?>
                </a>
            </h2>
            <?php
            the_content();
        }
    }
}
wp_reset_postdata();

Now lets write it out as pseudocode/plain english:

for each hadices post
    Set the current post
    if the main query has posts
        for each post in the main query
            Set the current post as the post from the main query
            Set the current post as the current post
            output the post
After the loop, reset the post data

We can see that for some reason there's 2 post loops, and the current post is set 3 times. On top of that, the code uses $post which is the name of a global variable.

So instead, lets rewrite this as a standard WP_Query loop:

$q = new WP_Query([
    'post_type' => 'hadices',
    'orderby' => 'rand',
    'posts_per_page' => 1,
]);

if ( $q->have_posts() ) {
    while( $q->have_posts() ) {
        $q->the_post();
        // output the post
    }
    wp_reset_postdata();
} else {
    echo '<p>None were found</p>';
}

Notes:

  • the_post calls setup_postdata internally, you don't have to call it again
  • get_posts calls WP_Query internally, cut out the middle man and go straight for WP_Query
  • WP_Query makes use of caching mechanisms, making it a little faster
  • posts_per_page expects a number, you don't have to wrap it in quotes, 1 will do, it doesn't have to be "1"
  • Indent Indent indent! Your editor should be indenting for you, so ti should be effortless. Use an editor such as SublimeText or Atom if you want a free program, or opt for a more advanced IDE such as PHPStorm. All 3 have packages that can reformat entire files at the press of a button
  • Don't use <?php ?> spam, this: echo '<br/>'; is the same as ?><?php echo '<br/>';?><?php but easier to read
  • Your original code never checked if any posts were found
  • You should put the wp_reset_postdata call inside the if statement
  • Your post type is named as a plural, hadices, but technically it should be hadice, or I presume hadith, you can change the URL used when registering the post type via the rewrite option

Improving Performance

rand isn't just slow, it's one of the slowest things you can ask a server to do. Some implementations will create a brand new table with the same data randomly ordered, then perform a query on the new table and destroy it afterwards, which becomes extremely slow with large numbers of posts.

So instead, lets do the random part in PHP, the plan:

  • Ask for a single post from the database
  • Figure out which post to pick from PHP

First, lets figure out how many hadices posts there are using wp_count_posts:

$counts = wp_count_posts( 'hadices', false );

Then, lets generate a number between 0 and the maximum number of posts:

$counts = wp_count_posts( 'hadices', false );
$offset = mt_rand( 1, $counts->publish );

Then, lets tell it to use the number we generated as an offset, so if the offset is 12, then we want the 12th hadices post, if the number is 212, we want the 212th hadices post.

So lets plug that into our improved query, by using the offset to request the page, and having 1 post per page:

$q = new WP_Query([
    'post_type' => 'hadices',
    'posts_per_page' => 1,
    'page' => $offset,
]);
  • Brilliant answer, Tom. – jdm2112 May 7 '18 at 15:10

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.