Stop Scattering WP_Query: Use a Repository Pattern Instead

I got a call from a client with a major headache. They run a big events portal, built on a custom plugin we inherited. Over the years, half a dozen developers had touched it. The problem? A “simple” request to hide past events from a specific calendar view was quoted at 20 hours. Total mess. Why? Because instead of a central way to fetch events, there were dozens of raw WP_Query calls scattered across template files, widgets, and shortcodes. Changing one meant hunting them all down. It was a disaster waiting to happen.

This is a classic sign of a codebase that’s grown without a plan. Everyone just copies and pastes the last query they saw that worked. If you’re a developer, you’ve seen this. You’ve probably done it. The core of the problem is a failure to properly abstract the data layer. We can fix this by implementing a simple WordPress Repository Pattern.

My first thought was to just use the pre_get_posts hook to modify the queries globally. And yeah, that worked for the main archive page. But then it started hiding events in admin dashboards where the client needed to see them. Broke a dashboard widget completely. That’s the problem with hooks like that—they’re a sledgehammer when you often need a scalpel.

Building a Better Way: The WordPress Repository Pattern

The solution is to stop putting WP_Query everywhere. Instead, you create a dedicated PHP class whose only job is to handle all database operations for your custom post type. In this case, an EventRepository. Think of it as a gatekeeper. Your theme files or widgets don’t talk to the database directly; they ask the repository, and the repository handles the dirty work. This approach is a practical application of concepts I first saw detailed by Carl Alexander, and it’s a lifesaver for complex projects.

When you do this, all your complex query logic, your meta queries, your tax queries—it all lives in one place. One file. Need to exclude past events everywhere? You change one method in the repository. Done.

class EventRepository
{
    /**
     * Find a single event by its ID.
     *
     * @param int $id
     * @return WP_Post|null
     */
    public function find_by_id($id)
    {
        $args = [
            'p'                        => $id,
            'post_type'                => 'event',
            'posts_per_page'           => 1,
            'no_found_rows'            => true,
            'update_post_meta_cache'   => false,
            'update_post_term_cache'   => false,
        ];

        $query = new WP_Query($args);

        return !empty($query->posts) ? $query->posts[0] : null;
    }

    /**
     * Find upcoming events, excluding specific categories.
     *
     * @param array $excluded_cats
     * @return WP_Post[]
     */
    public function find_upcoming_events(array $excluded_cats = [])
    {
        $args = [
            'post_type'      => 'event',
            'posts_per_page' => 10,
            'meta_key'       => 'event_date',
            'orderby'        => 'meta_value_num',
            'order'          => 'ASC',
            'meta_query'     => [
                [
                    'key'     => 'event_date',
                    'value'   => date('Ymd'),
                    'compare' => '>=',
                ],
            ],
            'category__not_in' => $excluded_cats,
        ];

        $query = new WP_Query($args);

        return $query->posts;
    }
}

So, What’s the Point?

Look at that code. Now, when the client asks to exclude another category, you don’t go on a treasure hunt. You add an ID to an array in one place. This isn’t about writing less code today; it’s about saving your future self from a world of pain. Trust me on this.

  • It’s Maintainable: All your event-related database logic is in one place. Easy to find, easy to update.
  • It’s Readable: Your template files become way cleaner. Instead of a 20-line WP_Query args array, you have $events = $repository->find_upcoming_events();. Beautiful.
  • It’s Testable: This kind of encapsulated code is far easier to write automated tests for.

Look, this stuff gets complicated fast. If you’re tired of debugging someone else’s mess and just want your site to work, drop my team a line. We’ve probably seen it before.

Leave a Reply

Your email address will not be published. Required fields are marked *