SkillAgentSearch skills...

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/Wporm

README

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.

wporm

Documentation

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, and hasManyThrough relationships.
  • 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

  1. Place the ORM directory in your plugin folder.
  2. 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 $table in 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 is true).
  • 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 true if the insert(s) succeeded or were ignored due to duplicate keys.
  • Returns false on other errors.
  • Uses MySQL's INSERT IGNORE for 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 is true).

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 is true).

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

View on GitHub
GitHub Stars4
CategoryDevelopment
Updated1mo ago
Forks0

Languages

PHP

Security Score

90/100

Audited on Feb 26, 2026

No findings