WordPress - A minimalist post loader

For most of WordPress projects, we need to load posts, that’s why I’d like to share with you a useful class to handle this.

Without a post loader

I’m pretty sure you came across code like this through your WordPress projects :

$query = new WP_Query([
    'post_type' => 'custom_post_type_name',
    'order' => 'ASC',
    'posts_per_page' => 5,
]);
$posts = $query->get_posts();
 
/** making stuff with these posts... */

It’s basically the most standard way of retrieving posts, it’s efficient and convenient most of the time.

Now, to spice things a bit, I would like to paginate the results of this query. So first I’ll have to check if any parameter is set (like page in the url, or directly the get_query_var('page') available from WordPress) and then if it’s the case, then I’ll to edit query’s arguments. I would also like to retrieve my posts by years, then I’ll need another condition and edit query’s arguments…

Well, let’s see how it looks :

$args = [
    'post_type' => 'custom_post_type_name',
    'order' => 'ASC',
    'posts_per_page' => 5,
];
 
if (!empty($_GET['yr'])) {
    $year = (int) $_GET['yr'];
    $args['date_query'] = [[
        'year' => $year,
    ]];
}
 
$currentPage = (int)(get_query_var('page')) ? get_query_var('page') : 1;
 
$args['paged'] = $currentPage;
 
$query = new WP_Query($args);
$posts = $query->get_posts();
 
/** making stuff with these posts... */

If this kind of stuff happens in your templates or functions then this will getting out of hand pretty quickly if it needs more customization like adding ACF argument aswell, or any kind of maintenance (by you or others developers). Also, note that we didn’t even render pagination based on the query results...

Also, if you need to repeat queries like this then it’s even more difficult to maintain through your whole project.

That’s why I’d like to share a very minimalist class to handle these kind of operations.

With a post loader

First, let’s see how we use it :

<?php
use App\PostLoader;
 
$loader = new PostLoader('custom_post_type_name', get_permalink(get_the_ID()), 5);
$posts = $loader->getPosts();
 
/** making stuff with these posts... */

This example is the same as the first, you retrieve the same posts based on the parameters you gave to the constructor.

Let’s see now how to add arguments to our loader :

$loader = new PostLoader('custom_post_type_name', get_permalink(get_the_ID()), 5);
 
if (!empty($_GET['yr'])) {
    $loader->setArgument('date_query', [[
        'year' => (int)$_GET['yr'],
    ]]);
}
 
$posts = $loader->getPosts();

We can retrieve every post of the year specified, another benefit that this class offer is the rendering of the pagination, simply call it like this :

echo $loader->getPagination();

Which builds for you all the html needed for pagination. You also need to inject some parameters in the pagination links ? You can render pagination like this aswell :

echo $loader->getPagination(['yr' => (int)$_GET['yr']]);

So when you go through pages, you won’t lose any parameters already set.

PostLoader class

Here’s the class:

<?php

namespace App;
 
use WP_Query;
 
class PostLoader
{
    /** @var string */
    private $postType;
    
    /** @var string */
    private $basePageUrl;
    
    /** @var int */
    private $postsPerPage;
    
    /** @var int */
    private $page;
    
    /** @var int */
    private $totalPosts;
    
    /** @var int */
    private $totalPages;
    
    /** @var array */
    private $params;
    
    /**
     * @param string $postType Post type to retrieve
     * @param string $basePageUrl Url of the page that handles your posts archives
     * @param int $postsPerPage How many posts to retrieve per page
     */
    public function __construct(string $postType, string $basePageUrl, int $postsPerPage)
    {
        $this->postType = $postType;
        $this->basePageUrl = $basePageUrl;
        $this->postsPerPage = $postsPerPage;
        $this->params = [
            'post_type' => $this->postType,
            'posts_per_page' => $this->postsPerPage,
        ];
    }
    
    /**
     * @param int|null $page Specify which page to retrieve if needed, fallback to "page" query var
     * 
     * @return array
     */
    public function getPosts(?int $page = null): array
    {
        if ($page === null) {
            $this->page = (int)(get_query_var('page')) ? get_query_var('page') : 1;
        } else {
            $this->page = $page;
        }
        
        $this->setArgument('paged', $this->page);
 
        $query = new WP_Query($this->getParams());
 
        $this->totalPosts = (int)$query->found_posts;
        $this->totalPages = (int)$query->max_num_pages;
 
        return $query->get_posts();
    }
    
    /**
     * @param array|null $params If parameters needs to be injected inside the link
     * 
     * @return string
     */
    public function getPagination(?array $params = null): string
    {
        $paramsString = '';
 
        if (!empty($params)) {
            $paramsString = '?' . http_build_query($params);
        }
 
        $html = '<ul class="paginationContainer">';
 
        // If we haven't already call the getPosts method then we do it now
        if ($this->totalPages === null) $this->getPosts();
 
        for ($i = 1; $i <= $this->totalPages; $i++) {
            $addedClasses = '';
 
            if ($i === $this->page) {
                $addedClasses .= ' activeItem';
            }
 
            $html .= "
                <li class='paginationItem {$addedClasses}'>
                    <a href='{$this->basePageUrl}{$i}{$paramsString}' class='paginationLink'>{$i}</a>
                </li>
            ";
        }
 
        $html .= '</ul>';
 
        return $html;
    }
    
    /**
     * @param string $key Argument key
     * @param mixed $value Argument value
     * 
     * @return self
     */
    public function setArgument(string $key, $value): self
    {
        $this->params[$key] = $value;
 
        return $this;
    }
    
    /**
     * @return array
     */
    public function getParams(): array
    {
        return $this->params;
    }
}

It really aims to stay simple and to evolve or extend depending on project. As you can see, you can easily edit how pagination is built (you could even use a view if needed), change how things are implemented and add your own logic.

Using an object-oriented approach allow us to easily edit the behaviour of the loader but also add new to it. (with other benefits like being reusable, easier to test, cleaner...)

Aswell, you can note that we can override the page being called with the getPosts() method, this is quite useful when you doing ajax loading and you need to call a precise page through your request. (otherwise it fallback to get_query_var('page') if set or first page )

Queries being handled directly from this post loader, this lighten your templates (or functions) and allow you to have a constant and easier api to work with. (it also encourages you to work with objects ! :D)

I hope you’ll see some uses for this tiny code, it’s totally free to use ! :)

Thanks for reading !

October 01, 2019