Learn to build a lightweight ORM for WordPress to abstract SQL, map database rows to PHP objects, and simplify data manipulation in your plugins.
Previously in this course, we covered High-Concurrency Data Handling in WordPress Plugins, focusing on row-level locking and transaction integrity. While those techniques ensure database reliability, writing raw SQL for every CRUD operation quickly leads to unmaintainable code. In this lesson, we’ll implement an ORM Lite layer to bridge the gap between our database schema and clean, object-oriented PHP.
In a professional plugin like our Knowledge Base, you often deal with dozens of custom tables. Writing $wpdb queries everywhere creates tight coupling: if you rename a column, you have to hunt down every query. An ORM Lite approach uses Entities (representing a single record) and Query Builders (representing a collection) to encapsulate SQL logic.
By moving from raw SQL to an object-based API, you gain:
The BaseEntity acts as a data container. It should be lightweight, handling property assignment and basic data normalization.
PHPnamespace KnowledgeBase\ORM; abstract class BaseEntity { protected array $data = []; public function __construct(array $data = []) { $this->data = $data; } public function __get($key) { return $this->data[$key] ?? null; } public function toArray(): array { return $this->data; } }
By using the magic __get method, we create a flexible interface that allows us to add custom getters later (e.g., getTitle() or getFormattedDate()) without breaking existing code.
To make this useful, we need a way to transform database rows into specific entity classes. We use a Mapper or a static factory method. Let’s extend our Knowledge Base Article entity.
PHPnamespace KnowledgeBase\Entities; use KnowledgeBase\ORM\BaseEntity; class Article extends BaseEntity { public function getExcerpt(int $length = 50): string { return wp_trim_words($this->content, $length); } }
The mapping logic resides in our repository, ensuring that every database result is cast into an Article object automatically. This is significantly safer than passing raw arrays around, as it prevents Insecure Deserialization issues by strictly defining the object structure.
A Query Builder constructs SQL strings dynamically based on method calls. This allows us to chain filters without concatenating strings manually.
PHPnamespace KnowledgeBase\ORM; class QueryBuilder { protected string $table; protected array $wheres = []; public function __construct(string $table) { $this->table = $table; } public function where(string $column, string $value): self { $this->wheres[] = "$column = '$value'"; return $this; } public function get(): array { global $wpdb; $sql = "SELECT * FROM {$this->table}"; if (!empty($this->wheres)) { $sql .= " WHERE " . implode(' AND ', $this->wheres); } return $wpdb->get_results($sql, ARRAY_A); } }
Note: In production, always use $wpdb->prepare() within the get() method to prevent SQL injection.
KnowledgeBase\Repositories\ArticleRepository class.find(int $id) method that uses the QueryBuilder to fetch a single record.Article using the data returned from the database.yield generators or limit result sets when fetching large collections.$wpdb->prepare: Even when using an ORM, the underlying data should never be concatenated directly into SQL strings. Always pass values through the prepare() method.We’ve moved from raw SQL to an object-oriented approach by creating a BaseEntity for data representation and a QueryBuilder for SQL abstraction. This pattern keeps the Knowledge Base plugin modular and makes data manipulation predictable. You can now easily expand your data layer without duplicating logic or risking manual query errors.
Up next: Advanced Query Filters, where we’ll allow developers to extend our repository queries using WordPress hooks.
Master the architecture of Multisite-ready WordPress plugins. Learn to implement site-specific data isolation and network-wide management for scalable plugins.
Read moreMaster the art of Advanced Query Filters. Learn to implement filterable repository arguments, expose data through hooks, and build truly extensible plugins.
Object-Relational Mapping (ORM) Lite
Custom Hooks for React