Overview

Architecture

WPZylos Model follows the Active Record pattern, where each model class maps to a database table and each instance represents a single row. The model combines data access and business logic in one object.

┌─────────────────────────────────────────────────┐
|                  Your Model                     |
|         (extends Model abstract class)          |
+--───────────────────────────────────────────────┤
|  HasAttributes    | Casting, accessors, dirty   |
|  HasTimestamps    | created_at, updated_at      |
|  HasEvents        | Lifecycle hooks, boot       |
|  HasRelationships | hasOne, hasMany, belongsTo  |
|  SoftDeletes (opt)| Soft delete support         |
+--───────────────────────────────────────────────┤
|              QueryBuilder                       |
|        (from wpzylos-database)                  |
+--───────────────────────────────────────────────┤
|              Connection                         |
|           (wraps $wpdb)                         |
+--───────────────────────────────────────────────┘

Design Patterns

Active Record

Each model class knows how to persist itself:

$user = User::create(['name' => 'John']);  // INSERT
$user->name = 'Jane';
$user->save();                              // UPDATE
$user->delete();                            // DELETE

Trait Composition

The base Model class is kept lean by extracting concerns into traits:

  • HasAttributes — Attribute storage, type casting, accessors/mutators, dirty tracking
  • HasTimestamps — Auto-managed created_at / updated_at
  • HasEvents — Static boot pattern, lifecycle event registration and firing
  • HasRelationshipshasOne, hasMany, belongsTo with relation caching
  • SoftDeletes — Optional trait for soft-delete behavior

Service Provider Pattern

ModelServiceProvider registers into the WPZylos container and injects the ApplicationInterface into the base Model so it can resolve database connections and table names.

Factory / Resolver

ModelResolver serves as a central factory registered as a singleton, allowing the container to create or find model instances by class name.

How It Works

1. Model Registration

When your plugin boots, register the ModelServiceProvider:

$app->register(new ModelServiceProvider());

This:

  • Registers ModelResolver as a singleton in the container
  • Calls Model::setApplication($app) so all models can access the container

2. Table Resolution

Each model declares its table name (without WP prefix):

protected static string $table = 'orders';

At runtime, Model::resolveTableName() uses the application context to prepend the WordPress table prefix (e.g., wp_orders).

3. Query Proxy

Static methods like Model::where(), Model::find(), and Model::all() create a QueryBuilder instance bound to the model's table. The QueryBuilder (from wpzylos-database) handles SQL generation and execution through $wpdb.

4. Hydration

When rows are fetched from the database, Model::newFromRow() creates a new model instance, fills its attributes from the stdClass row, syncs the original snapshot, and marks $exists = true.

5. Lifecycle Events

The static boot() method is called once per model class on first instantiation. Child models override it to register event listeners. Events like creating, updating, deleting can cancel operations by returning false.

Dependencies

PackagePurpose
wpzylos-coreApplication container, ServiceProvider base, ApplicationInterface
wpzylos-databaseConnection (wraps $wpdb) and QueryBuilder