Radicle 2: Using Eloquent Models alongside WP_Query, caching, pagination, and extensibility.

Hi Roots,

We’re working with Radicle 2 and using Illuminate\Database\Eloquent\Model alongside WordPress.

I have a few questions / concerns about how this integrates with WordPress internals (WP_Query($args)), especially in when extending the query $args. For example in combination with pagination or with plugins like FacetWP.


(1.) WP_Query integration with Eloquent models

Is it possible (or intended) to integrate Eloquent model queries with WP_Query arguments?

An Eloquent model is not related to WP_Query by default. In order to opt in to WordPress core functionality such as pagination or WP_Query-style arguments, we currently handle it like this:

class Employee extends Model
{
    public static function byTermId(int $termId, ?string $taxonomy = null, int $limit = -1): Collection
    {
        if (empty($taxonomy)) {
            return collect();
        }

        $postIds = get_posts([
            'post_type'      => 'employee',
            'post_status'    => 'publish',
            'posts_per_page' => $limit,
            'fields'         => 'ids',
            'facetwp'        => true,
            'tax_query'      => [
                [
                    'taxonomy' => $taxonomy,
                    'field'    => 'term_id',
                    'terms'    => $termId,
                ],
            ],
        ]);

        if (empty($postIds)) {
            return collect();
        }

        return static::query()->whereIn('ID', $postIds)->get();
    }
}

Ideally, we would like to express this kind of query more naturally alongside the model itself, instead of splitting logic between get_posts() / WP_Query and Eloquent.

Is this something that is intended or recommended within Radicle? Or is there a known limitation / best practice / design pattern when comparing or combining these two approaches?


(2) Query caching for Eloquent models

Is there any built-in support for caching Eloquent model queries in this setup, or is caching expected to be handled separately (e.g. WordPress object cache, transients, or a custom caching layer)?

Is this something that is intended or recommended within Radicle? Or is there a known limitation / best practice / design pattern?


Any guidance or best practices on how to structure this cleanly in Radicle 2 would be greatly appreciated.

Looking forward to your insights.

Best regards,
Lex

Hey @Stackie, thanks for the topic! Short version: Radicle ships the Eloquent models as a convenience, not a WP_Query replacement

WP_Query keeps owning what it should: tax_query/meta_query operators, pre_get_posts, FacetWP, archive pagination with rewrite rules and max_num_pages, anything that hooks query vars. Use Eloquent where owning the query surface matters: API endpoints, admin tools, reports, complex joins, scopes, observers.

Your get_posts(['fields' => 'ids']) -> Employee::whereIn('ID', $ids) is the usual pattern, and there are two gotchas: whereIn doesn’t preserve the order WP_Query returned, and you lose found_posts/max_num_pages which kills archive pagination.

I’ve got an open PR that handles both: Post::fromWpQuery($args)->with('meta')->get() runs the WP_Query, hydrates Eloquent in order, returns a LengthAwarePaginator aligned with paged for archive/taxonomy/search contexts, static front-page pagination can use page. Subclasses (Seed) inherit it: https://github.com/roots/radicle/pull/337

If you want taxonomy queries native in Eloquent you’d still need Term/TermTaxonomy/TermRelationship models and the plumbing (tracking as a follow-up issue: https://github.com/roots/radicle/issues/338)

Caching: wrap with Cache::tags('wp-posts')->remember(...) through Acorn. Same PR adds a provider that flushes the tag on save_post/deleted_post/term writes (Redis/memcached, graceful fallback) — configurable via config/wp-cache.php or WP_CACHE_INVALIDATION.

Hey Ben, thanks a lot for the detailed explanation — really helpful.

I’m looking forward to the new release with fromWpQuery(), WP cache invalidation, and the addition of Term, TermTaxonomy, and TermRelationship. That should make things a lot smoother to work with.

Appreciate you taking the time to share all this!