Wporm
WPORM is a lightweight, Eloquent-inspired ORM for WordPress that brings expressive query building, model relationships, schema management, and attribute casting to your plugins and themes, all while fully supporting the native WordPress database API.
Install / Use
/learn @mjkhajeh/WpormREADME
WPORM - Lightweight WordPress ORM
WPORM is a lightweight Object-Relational Mapping (ORM) library for WordPress plugins. It provides an Eloquent-like API for defining models, querying data, and managing database schema, all while leveraging WordPress's native $wpdb database layer.
Documentation
- Methods list and documents
- Blueprint and column types documents
- Casts types and define custom casts
- DB usage and raw queries
- Debugging tips
Features
- Model-based data access: Define models for your tables and interact with them using PHP objects.
- Schema management: Create and modify tables using a fluent schema builder.
- Query builder: Chainable query builder for flexible and safe SQL queries.
- Attribute casting: Automatic type casting for model attributes.
- Relationships: Define
hasOne,hasMany,belongsTo,belongsToMany, andhasManyThroughrelationships. - Events: Hooks for model lifecycle events (creating, updating, deleting).
- Global scopes: Add global query constraints to models.
Installation
With Composer (Recommended)
You can install WPORM via Composer. In your plugin or theme directory, run:
composer require mjkhajeh/wporm
Then include Composer's autoloader in your plugin bootstrap file:
require_once __DIR__ . '/vendor/autoload.php';
Manual Installation
- Place the
ORMdirectory in your plugin folder. - Include the ORM in your plugin bootstrap:
require_once __DIR__ . '/ORM/Helpers.php';
require_once __DIR__ . '/ORM/Model.php';
require_once __DIR__ . '/ORM/QueryBuilder.php';
require_once __DIR__ . '/ORM/Blueprint.php';
require_once __DIR__ . '/ORM/SchemaBuilder.php';
require_once __DIR__ . '/ORM/ColumnDefinition.php';
require_once __DIR__ . '/ORM/DB.php';
require_once __DIR__ . '/ORM/Collection.php';
Defining a Model
Create a model class extending MJ\WPORM\Model:
use MJ\WPORM\Model;
use MJ\WPORM\Blueprint;
class Parts extends Model {
protected $table = 'parts';
protected $fillable = ['id', 'part_id', 'qty', 'product_id'];
protected $timestamps = false;
public function up(Blueprint $blueprint) {
$blueprint->id();
$blueprint->integer('part_id');
$blueprint->integer('product_id');
$blueprint->integer('qty');
$blueprint->index('product_id');
$this->schema = $blueprint->toSql();
}
}
Note: When using
$tablein custom SQL queries, do not manually add the WordPress prefix (e.g.,$wpdb->prefix). The ORM automatically handles table prefixing. Use$table = (new User)->getTable();as shown in the next, which returns the fully-prefixed table name.
Schema Management
Create or update tables using the model's up method and the SchemaBuilder:
use MJ\WPORM\SchemaBuilder;
$schema = new SchemaBuilder($wpdb);
$schema->create('parts', function($table) {
$table->id();
$table->integer('part_id');
$table->integer('product_id');
$table->integer('qty');
$table->index('product_id');
});
Unique Indexes (Eloquent-style)
You can add a unique index to a column using Eloquent-style chaining:
$table->string('email')->unique();
$table->integer('user_id')->unique('custom_index_name');
For multi-column unique indexes, use:
$table->unique(['col1', 'col2']);
This works for all column types and matches Eloquent's API.
Basic Usage
Creating a Record
$part = new Parts(['part_id' => 1, 'product_id' => 2, 'qty' => 10]);
$part->save();
Querying Records
// Get all parts
$all = Parts::all();
// Find by primary key
$part = Parts::find(1);
// Where clause
$parts = Parts::query()->where('qty', '>', 5)->orderBy('qty', 'desc')->limit(10)->get(); // Limit to 10 results
// Raw ORDER BY example
$parts = Parts::query()->where('qty', '>', 5)
->orderByRaw('FIELD(name, ?, ?)', ['Widget', 'Gadget'])
->limit(10)
->get();
// This allows custom SQL ordering, e.g. sorting by a specific value list. Bindings are safely passed to $wpdb->prepare.
// First result
$first = Parts::query()->where('product_id', 2)->first();
Querying by a Specific Column
You can easily retrieve records by a specific column using the query builder's where method. For example, to get all parts with a specific product_id:
$parts = Parts::query()->where('product_id', 123)->get();
Or, to get the first user by email:
$user = User::query()->where('email', 'user@example.com')->first();
You can also use other comparison operators:
$recentUsers = User::query()->where('created_at', '>=', '2025-01-01')->get();
This approach works for any column in your table.
Creating or Updating Records: updateOrCreate
WPORM provides an updateOrCreate method, similar to Laravel Eloquent, for easily updating an existing record or creating a new one if it doesn't exist.
Usage:
// Update if a user with this email exists, otherwise create a new one
$user = User::updateOrCreate(
['email' => 'user@example.com'],
['name' => 'John Doe', 'country' => 'US']
);
// Disable global scopes for this call
$user = User::updateOrCreate(
['email' => 'user@example.com'],
['name' => 'John Doe', 'country' => 'US'],
false // disables global scopes
);
- The first argument is an array of attributes to search for.
- The second argument is an array of values to update or set if creating.
- The optional third argument disables global scopes if set to
false(default istrue). - Returns the updated or newly created model instance.
This is useful for upsert operations, such as syncing data or ensuring a record exists with certain values.
Creating or Getting Records: firstOrCreate and firstOrNew
Inserting Records: insertOrIgnore
WPORM provides an insertOrIgnore method, similar to Laravel Eloquent, for inserting one or multiple records and ignoring duplicate key errors (such as unique constraint violations).
Usage:
// Insert a single user, ignore if email already exists
$success = User::insertOrIgnore([
'email' => 'user@example.com',
'name' => 'Jane Doe',
'country' => 'US'
]);
// Insert multiple users, ignore duplicates
$data = [
['email' => 'user1@example.com', 'name' => 'User One'],
['email' => 'user2@example.com', 'name' => 'User Two'],
['email' => 'user1@example.com', 'name' => 'User One Duplicate'], // duplicate email
];
$success = User::insertOrIgnore($data);
- Returns
trueif the insert(s) succeeded or were ignored due to duplicate keys. - Returns
falseon other errors. - Uses MySQL's
INSERT IGNOREfor safe upsert-like behavior.
This is useful for bulk imports or situations where you want to avoid errors on duplicate records.
WPORM also provides firstOrCreate and firstOrNew methods, similar to Laravel Eloquent, for convenient record retrieval or creation.
firstOrCreate Usage:
// Get the first user with this email, or create if not found
$user = User::firstOrCreate(
['email' => 'user@example.com'],
['name' => 'Jane Doe', 'country' => 'US']
);
// Disable global scopes for this call
$user = User::firstOrCreate(
['email' => 'user@example.com'],
['name' => 'Jane Doe', 'country' => 'US'],
false // disables global scopes
);
- Returns the first matching record, or creates and saves a new one if none exists.
- The optional third argument disables global scopes if set to
false(default istrue).
firstOrNew Usage:
// Get the first user with this email, or instantiate (but do not save) if not found
$user = User::firstOrNew(
['email' => 'user@example.com'],
['name' => 'Jane Doe', 'country' => 'US']
);
// Disable global scopes for this call
$user = User::firstOrNew(
['email' => 'user@example.com'],
['name' => 'Jane Doe', 'country' => 'US'],
false // disables global scopes
);
if (!$user->exists) {
$user->save(); // Save if you want to persist
}
- Returns the first matching record, or a new (unsaved) instance if none exists.
- The optional third argument disables global scopes if set to
false(default istrue).
These methods are useful for ensuring a record exists, or for preparing a new record with default values if not found.
Updating a Record
$part = Parts::find(1);
$part->qty = 20;
$part->save();
Deleting a Record
$part = Parts::find(1);
$part->delete();
Truncating a Table
You can quickly remove all rows from a model's table using truncate() on the model query builder:
// Remove all records from the table
Parts::query()->truncate();
Pagination
WPORM supports Eloquent-style pagination with the following methods on the query builder:
paginate($perPage = 15, $page = null)
Returns a paginated result array with total count and page info:
$result = User::query()->where('active', true)->paginate(10, 2);
// $result = [
// 'data' => Collection,
// 'total' => int,
// 'per_page' => int,
// 'current_page' => int,
// 'last_page' => int,
// 'from' => int,
// 'to' => int
// ]
simplePaginate($perPage = 15, $page = null)
Returns a paginated result array without total count (more efficient for large tables):
$result = User::query()->where('active', true)->simplePaginate(10, 2);
// $result = [
// 'data' => Collection,
// 'per_page' => int,
// 'current_page' => int,
// 'next_page' => int|null
// ]
See Methods.md for more details and options.
Attribute Casting
Add a $casts property to your model:
protected $casts = [
'qty' => 'int',
'meta' => 'json',
];
Array Conversion and Casting
- Call
->toArray()on a model or a collection to get an array representation with al
Related Skills
node-connect
343.3kDiagnose OpenClaw node connection and pairing failures for Android, iOS, and macOS companion apps
frontend-design
92.1kCreate distinctive, production-grade frontend interfaces with high design quality. Use this skill when the user asks to build web components, pages, or applications. Generates creative, polished code that avoids generic AI aesthetics.
openai-whisper-api
343.3kTranscribe audio via OpenAI Audio Transcriptions API (Whisper).
qqbot-media
343.3kQQBot 富媒体收发能力。使用 <qqmedia> 标签,系统根据文件扩展名自动识别类型(图片/语音/视频/文件)。
