Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/Ahondev/portfolio-v2/llms.txt

Use this file to discover all available pages before exploring further.

EloquentCPT is WP SSR’s thin ORM layer that maps WordPress posts and their ACF fields into plain PHP objects. Instead of wrangling WP_Query arrays directly, you call expressive static methods like Article::query()->orderBy('date', DESC)->limit(6)->get() and receive a Laravel Collection of strongly-typed model instances. The QueryBuilder fluent builder translates each chained call into the correct WP_Query arguments before executing.

Defining a Model

Every post type gets one PHP class in app/PostTypes/. The only required member is the static $model string that maps the class to a WordPress post type slug.
<?php
// app/PostTypes/Article.php

namespace Ahon\WPCMS\App\PostTypes;

class Article extends \Ahon\WPCMS\Eloquent\EloquentCPT
{
    public static string $model = 'article';
}
Model files are generated automatically by EloquentManager::generate(). You rarely need to write them by hand unless you want to add custom instance methods.

Constructor Behaviour

public function __construct(\WP_Post $post)
When a WP_Post object is passed, the constructor maps these core properties directly from WP_Post:
PropertySource
$id$post->ID
$type$post->post_type
$title$post->post_title
$slug$post->post_name
$date$post->post_date
$excerpt$post->post_excerpt
The $content and $thumbnail properties are declared on the class but are not populated from WP_Post in the constructor. They are only set if a corresponding ACF field named content or thumbnail exists and is returned by get_fields().
After mapping core properties, initACFFields() is called. It calls get_fields($this->id) and iterates over every ACF field returned, assigning each value as a dynamic property on the instance. This means all your registered ACF fields — including nested Repeater arrays — become first-class properties.
$article = new Article($wpPost);

echo $article->title;      // WP post title (from WP_Post)
echo $article->slug;       // WP post slug (from WP_Post)
echo $article->readTime;   // ACF Text field (from get_fields)
echo $article->content;    // ACF WYSIWYGEditor field (from get_fields, if registered)

Static Methods

EloquentCPT::query()

Returns a QueryBuilder scoped to the calling class’s $model post type. Uses late static binding so subclasses get the correct model automatically.
public static function query(): QueryBuilder
// Returns QueryBuilder for 'article' post type, hydrates as Article objects
$articles = Article::query()->all();

EloquentCPT::queryTerms()

Fetches taxonomy terms and returns a clean array of {id, name, slug} objects, automatically excluding the WordPress default “Non classé” / “uncategorized” term.
public static function queryTerms(
    string $taxonomy,
    bool   $hideEmpty = false
): array|\WP_Error
taxonomy
string
required
The taxonomy slug to query, e.g. 'category'.
hideEmpty
bool
When true, terms with zero associated posts are excluded. Defaults to false.
$categories = Article::queryTerms('category');
// [
//   ['id' => 3, 'name' => 'Technology', 'slug' => 'technology'],
//   ['id' => 7, 'name' => 'Design',     'slug' => 'design'],
// ]

Instance Methods

toArray()

Returns all public properties of the model as an associative array, equivalent to get_object_vars($this).
public function toArray(): array
Useful for serialising a model to JSON inside an ApiController:
return $this->success($article->toArray());

QueryBuilder

QueryBuilder is constructed internally by EloquentCPT::query(). Chain as many methods as you need before calling get() or all() to execute the query.

all()

Fetches every post with no limit (sets posts_per_page = -1).
public function all(): \Illuminate\Support\Collection

limit()

Sets the maximum number of posts to return.
public function limit(int $limit): QueryBuilder

offset()

Skips the first $offset posts. Useful for manual pagination.
public function offset(int $offset): QueryBuilder

orderBy()

public function orderBy(string $value, string $order): QueryBuilder
value
string
required
The WP_Query orderby field, e.g. 'date', 'title', 'menu_order', 'meta_value'.
order
string
required
Either ASC or DESC. The constants Ahon\WPCMS\Eloquent\ASC and DESC are exported from the namespace.

random()

Returns $limit posts in random order (sets orderby = 'rand').
public function random(int $limit): QueryBuilder
Performs a WordPress keyword search (WP_Query 's' parameter). The query string is sanitised with sanitize_text_field().
public function search(string $query): QueryBuilder

matchIds()

Restricts results to a specific list of post IDs. Results are returned in the order of the provided array.
public function matchIds(array $ids): QueryBuilder

matchSlugs()

Restricts results to a specific list of post slugs. Each slug is sanitised with sanitize_title(). Results are returned in slug-array order.
public function matchSlugs(array $slugs): QueryBuilder

where()

Adds an ACF meta_query condition. If $value is omitted, the operator defaults to =.
public function where(
    string $key,
    string $operator,
    mixed  $value = null
): QueryBuilder
key
string
required
The ACF field name / meta key.
operator
string
required
Comparison operator: =, !=, >, >=, <, <=, LIKE, NOT LIKE, IN, NOT IN, BETWEEN, NOT BETWEEN.
value
mixed
The value to compare against. When omitted, $operator is treated as the value and = is used.
// Shorthand — implicit '='
Article::query()->where('featured', true);

// Explicit operator
Article::query()->where('readTime', '>=', 5);

orWhere() / andWhere()

Identical to where() but set the meta query relation to OR or AND respectively. The default relation is AND.
public function orWhere(string $key, string $operator, mixed $value = null): QueryBuilder
public function andWhere(string $key, string $operator, mixed $value = null): QueryBuilder

taxonomy()

Filter posts by taxonomy term. Pass '*' as $term to skip the filter entirely (useful for conditional logic).
public function taxonomy(
    string       $taxonomy,
    string|array $term
): QueryBuilder
taxonomy
string
required
The taxonomy slug. Calls wp_die() if the taxonomy does not exist.
term
string|array
required
A single term slug, an array of term slugs, or '*' to match all.
Article::query()
    ->taxonomy('category', 'technology')
    ->orderBy('date', DESC)
    ->get();

with()

Eager-load taxonomy terms onto each result post. Uses a single optimised SQL query per taxonomy instead of N+1 calls.
public function with(string $relationType, string ...$relations): QueryBuilder
relationType
string
required
Currently only 'taxonomy' is supported.
relations
string
required
One or more taxonomy slugs to eager-load. Each is validated with taxonomy_exists().
Article::query()
    ->with('taxonomy', 'category', 'tag')
    ->all();

// Each Article instance will have:
// $article->category = [...term objects...]
// $article->tag      = [...term objects...]

get()

Executes the WP_Query, hydrates each post as an EloquentCPT subclass instance, performs any eager-loaded taxonomy lookups, and returns an Illuminate Collection.
public function get(): \Illuminate\Support\Collection
Returns an empty collection when no posts match.

Full Query Examples

use Ahon\WPCMS\Eloquent\ASC;
use Ahon\WPCMS\Eloquent\DESC;

// Latest 6 published articles
$latest = Article::query()
    ->orderBy('date', DESC)
    ->limit(6)
    ->get();

// Articles in 'technology' category with reading time >= 5 min
$technical = Article::query()
    ->taxonomy('category', 'technology')
    ->where('readTime', '>=', 5)
    ->orderBy('date', DESC)
    ->get();

// Random 3 services
$featured = Service::query()
    ->random(3)
    ->get();

// Search with pagination (page 2, 10 per page)
$results = Article::query()
    ->search('headless wordpress')
    ->limit(10)
    ->offset(10)
    ->get();

// Specific posts by ID
$pinned = Article::query()
    ->matchIds([42, 17, 55])
    ->get();

// Articles with categories and tags eager-loaded
$richPosts = Article::query()
    ->with('taxonomy', 'category', 'tag')
    ->orderBy('date', DESC)
    ->all();

EloquentManager

EloquentManager is the internal registry that tracks which slug maps to which class name. You do not interact with it directly — PostType::create() calls EloquentManager::__add() automatically.

EloquentManager::generate()

Generates PHP class files in app/PostTypes/ for every registered post type. It also regenerates the Post model for the built-in WordPress post type.
public static function generate(): void
Invoke this whenever you add or rename a post type via the WP-CLI command:
wp eloquent:generate
generate() deletes all existing files in app/PostTypes/ before writing new ones. Any custom instance methods you have added to a generated file will be lost. Extend the generated class in a separate file if you need to add custom behaviour.

Build docs developers (and LLMs) love