Use Transient API to cache queries for all posts in all categories?

The question:

I use this to display all posts in all categories.

$args_cat = array(
    // order by category name ascending
    'orderby' => 'name',
    'order' => 'ASC',
    // get only top level categories
    'parent' => 0
);
$categories = get_categories($args_cat);

// Full posts query
// if there are categories filled with posts
if (!empty ($categories) && !is_wp_error( $categories )) {

    foreach ($categories as $category) {

        // Query all posts by slug inside each category
        $args_category_posts = array(
            'post_type' => 'post',
            // The category slug and category name we get from the foreach over all categories
            'category_name' => $category->slug
        );

        $query = new WP_Query($args_category_posts);
        if ($query->have_posts()) {
            while ($query->have_posts()) {
                $query->the_post(); ?>
                <article class="<?php echo $category->slug ?>-article">
                    <h2 class="<?php echo $category->slug ?>-article-title">
                        <a href="<?php echo get_permalink() ?>" rel="nofollow noreferrer noopener" rel="nofollow noreferrer noopener" rel="nofollow noreferrer noopener"><?php echo get_the_title() ?></a>
                    </h2>
                    <p class="<?php echo $category->slug ?>-post-info">
                        <?php the_time('d. m. Y') ?>
                    </p>
                    <div <?php post_class() ?> >
                        <?php the_content(); ?>
                    </div>
                </article> <?php
            }
        } // end loop
    } // end foreach
wp_reset_postdata() ;
} // end if there are categories filled with posts

Having read this, this, this and this I am wondering if I am doing the right thing below.

// Transient API all categories and all posts
$query_categories = get_transient('cached_categories');
if ( false === $query_categories){
    $args_cat = array(
        // order by category name ascending
        'orderby' => 'name',
        'order' => 'ASC',
        // get only top level categories
        'parent' => 0
    );
    // Instead of caching a WP Query I cache 'get_categories($args_cat)', OK to use it like this?
    $query_categories = get_categories($args_cat);
    set_transient('cached_categories', $query_categories, DAY_IN_SECONDS );
}

// Full posts query
// if there are categories filled with posts
if (!empty ($query_categories) && !is_wp_error( $query_categories )) {

    foreach ($query_categories as $category) {

        $query_category_posts = get_transient('cached_posts');
        if ( false === $query_category_posts ){

            // Query all posts by slug inside each category
            $args_category_posts = array(
                'post_type' => 'post',
                // The category slug and category name we get from the foreach over all categories
                'category_name' => $category->slug
            );

            // Here I cache the WP_Query, though this runs for all categories.
            // So am I storing multiple transients here?
            $query_category_posts = new WP_Query($args_category_posts);
            set_transient( 'cached_posts', $query_category_posts, DAY_IN_SECONDS );
        }

        if ($query_category_posts->have_posts()) {
            while ($query_category_posts->have_posts()) {
                $query_category_posts->the_post(); ?>
                <article class="<?php echo $category->slug ?>-article">
                    <h2 class="<?php echo $category->slug ?>-article-title">
                        <a href="<?php echo get_permalink() ?>" rel="nofollow noreferrer noopener" rel="nofollow noreferrer noopener" rel="nofollow noreferrer noopener"><?php echo get_the_title() ?></a>
                    </h2>
                    <p class="<?php echo $category->slug ?>-post-info">
                        <?php the_time('d. m. Y') ?>
                    </p>
                    <div <?php post_class() ?> >
                        <?php the_content(); ?>
                    </div>
                </article> <?php
            }
        } // end loop
    } // end foreach
wp_reset_postdata() ;
} // end if there are categories filled with posts

The output is ok, all categories and posts display fine, though are the categories and posts properly cached now?

In the code I commented the two places where I set the transient but am not sure if this can be used with get_categories() or inside the foreach for all posts in the category.

Edit
The transient works for get_categories(). The var_dump output shows me an array of the WP_Term objects holding the categories available.

$query_categories = get_categories($args_cat);
var_dump($query_categories);
set_transient('cached_categories', $query_categories, DAY_IN_SECONDS );

Edit
The transient inside the foreach is only getting the first category and its posts and repeating that for the number of categories. Other categories than the first are not present, this presumably because the same transient gets called for the amount of number of categories and after the first round of foreach it is full and hence the loop only shows the posts form the first category.

Now how can I make a new transient that holds the posts per category on every cycle of the foreach for the number of categories?

Totally new to the Transient API and trying to make good use of it. Trying to tune the code and keep to best practices, if this is all bad use with errors lurking or no use of all since it does not work please do let me know. A bit of guidance or if I have grasped “the way” to use transients would be highly appreciated.

Solution
Per this answer I had to give the created transient for each category the category name (slug). Otherwise only the first category gets stored in the transient and once it is full for the second cycle it will not be changed. Hence only the posts from the first category were showing.

Only using $category for the transient variable was not working since $category is an object and a string was needed, so I changed it to $category->slug and now it works.

This was done in both places, getting and setting the transient. Thank you!

// Transients API all categories and all posts
$query_categories = get_transient('cached_categories');
if ( false === $query_categories){
    $args_cat = array(
        // order by category name ascending
        'orderby' => 'name',
        'order' => 'ASC',
        // get only top level categories
        'parent' => 0
    );
    // Instead of caching a WP Query I cache 'get_categories()'.
    $query_categories = get_categories($args_cat);
    // var_dump($query_categories);
    set_transient('cached_categories', $query_categories, DAY_IN_SECONDS );
}

// Full posts query
// if there are categories filled with posts
if (!empty ($query_categories) && !is_wp_error( $query_categories )) {

    foreach ($query_categories as $category) {

        // var_dump($category);
        $query_category_posts = get_transient('cached_posts_' . $category->slug );
        if ( false === $query_category_posts ){

            // Query all posts by slug inside each category
            $args_category_posts = array(
                'post_type' => 'post',
                // The category slug and category name we get from the foreach over all categories
                'category_name' => $category->slug
            );

            // Here I cache the WP_Query, though this runs for all categories.
            // Because of that the '$category->slug' is used to serve a string and not an object.
            $query_category_posts = new WP_Query($args_category_posts);         
            set_transient( 'cached_posts_' . $category->slug , $query_category_posts, DAY_IN_SECONDS );
        }

        if ($query_category_posts->have_posts()) {
            while ($query_category_posts->have_posts()) {
                $query_category_posts->the_post(); ?>
                <article class="<?php echo $category->slug ?>-article">
                    <h2 class="<?php echo $category->slug ?>-article-title">
                        <a href="<?php echo get_permalink() ?>" rel="nofollow noreferrer noopener" rel="nofollow noreferrer noopener" rel="nofollow noreferrer noopener"><?php echo get_the_title() ?></a>
                    </h2>
                    <p class="<?php echo $category->slug ?>-post-info">
                        <?php the_time('d. m. Y') ?>
                    </p>
                    <div <?php post_class() ?> >
                        <?php the_content(); ?>
                    </div>
                </article> <?php
            }
        } // end loop
    } // end foreach
wp_reset_postdata() ;
} // end if there are categories filled with posts

The Solutions:

Below are the methods you can try. The first solution is probably the best. Try others if the first one doesn’t work. Senior developers aren’t just copying/pasting – they read the methods carefully & apply them wisely to each case.

Method 1

You’re saving every query object for each category to the same transient. Because this happens fast and time frame is one day, you’re always getting the query object for the first category back. Make your transient name variable with the category, e.g. like this:

$query_category_posts = get_transient('cached_posts_' . $category );

Of course you then need to get the transient with the variable name.


All methods was sourced from stackoverflow.com or stackexchange.com, is licensed under cc by-sa 2.5, cc by-sa 3.0 and cc by-sa 4.0

Leave a Comment