-
Notifications
You must be signed in to change notification settings - Fork 0
model
A model represents a single database table and provides all operations needed to work with its rows. You always work with a singleton instance, so internal caches are shared throughout the request.
<?php
use WPTechnix\WPModels\AbstractModel;
class ProductModel extends AbstractModel
{
/** Table name without the WordPress prefix */
protected string $table = 'products';
/** The entity class rows are hydrated into */
protected string $entityClass = ProductEntity::class;
/** Primary key column name */
protected string $primaryKey = 'id';
/**
* Columns allowed in WHERE and ORDER BY clauses.
* This is a security allow-list. Any column not listed here will
* throw an InvalidArgumentException if referenced in a query.
*/
protected array $queryableColumns = [
'id', 'name', 'status', 'is_active', 'price', 'created_at',
];
}Always use ::instance() — constructing a model directly with new is not supported.
$products = ProductModel::instance();Inserts a single row. Returns the new primary key on success, false on failure.
$id = $products->create([
'name' => 'Wireless Mouse',
'price' => '29.99',
'is_active' => true,
'status' => 'draft',
]);
if ($id === false) {
$error = $products->getLastError();
}Inserts multiple rows in a single SQL statement. Returns the number of rows inserted. More efficient than calling create() in a loop for bulk data.
$count = $products->createMany([
['name' => 'USB Hub', 'price' => '19.99', 'status' => 'draft'],
['name' => 'Webcam', 'price' => '49.99', 'status' => 'draft'],
['name' => 'Desk Lamp', 'price' => '34.99', 'status' => 'draft'],
]);Fetches one entity by primary key. Returns null if the row does not exist.
$product = $products->find(42);
if ($product === null) {
wp_die('Product not found.');
}Returns true if a row with the given primary key exists, without loading the full row.
if (! $products->exists(42)) {
wp_die('Product not found.');
}Fetches multiple entities by primary key in a single query. Returns array<int, TEntity> keyed by primary key. IDs not found in the database are silently omitted.
$items = $products->findMany([1, 2, 3]);
foreach ($items as $id => $product) {
echo "{$id}: {$product->name}";
}Returns all rows matching the given conditions. See Query Conditions for the full syntax.
// All active products
$active = $products->findWhere([
['column' => 'is_active', 'value' => true],
]);
// With ordering and a row limit
$cheap = $products->findWhere(
conditions: [
['column' => 'is_active', 'value' => true],
['column' => 'price', 'operator' => '<=', 'value' => '20.00'],
],
orderBy: ['price' => 'ASC'],
limit: 10,
);Like findWhere but returns only the first match, or null.
$featured = $products->findOneWhere(
[['column' => 'status', 'value' => 'featured']],
['created_at' => 'DESC'],
);Shorthand for a single equality condition across all matching rows.
$drafts = $products->findBy('status', 'draft');Shorthand for a single equality condition, first result only.
$product = $products->findOneBy('status', 'featured');Finds the first row matching $attributes. If none exists, creates a new row using array_merge($attributes, $values).
$product = $products->firstOrCreate(
['name' => 'New Gadget', 'status' => 'draft'], // look for this
['price' => '0.00'], // add this on create
);Finds the first row matching $attributes and updates it with $values. If no row exists, creates one with array_merge($attributes, $values).
$product = $products->updateOrCreate(
['name' => 'New Gadget'], // find by
['price' => '39.99', 'status' => 'published'], // apply these
);Updates specific columns on a single row by primary key. Only the columns you pass are affected.
$products->update(42, [
'price' => '24.99',
'is_active' => true,
]);Updates all rows matching the conditions. Returns the number of rows affected.
Cache note: Invalidates the query cache but does not clear individual entity caches. See Caching — Bulk Operations if you need full cache consistency after a bulk update.
$affected = $products->updateWhere(
['status' => 'archived'],
[['column' => 'is_active', 'value' => false]],
);Increments a numeric column atomically. Safe against concurrent updates.
$products->increment(42, 'view_count');
$products->increment(42, 'stock', 10);Decrements a numeric column atomically.
$products->decrement(42, 'stock');
$products->decrement(42, 'stock', 3);Deletes a single row by primary key.
$products->delete(42);Deletes all rows matching the conditions. Returns the number of deleted rows.
Cache note: Same behaviour as
updateWhere— query cache is cleared, individual entity caches are not.
$deleted = $products->deleteWhere([
['column' => 'status', 'value' => 'archived'],
]);Counts rows matching the conditions. Omit conditions or pass [] to count all rows in the table.
$total = $products->countWhere();
$active = $products->countWhere([['column' => 'is_active', 'value' => true]]);Returns a flat array of a single column's values, optionally indexed by another column.
// Flat list of all product names
$names = $products->pluck('name');
// ['Wireless Mouse', 'USB Hub', ...]
// Names indexed by primary key
$nameById = $products->pluck('name', [], 'id');
// [42 => 'Wireless Mouse', 43 => 'USB Hub', ...]
// Names of active products only
$activeNames = $products->pluck('name', [
['column' => 'is_active', 'value' => true],
]);Returns a PaginatedResult containing the current page's entities and pagination metadata. See PaginatedResult Reference for everything you can do with the result.
$page = $products->paginate(
page: absint($_GET['paged'] ?? 1),
perPage: 20,
conditions: [['column' => 'is_active', 'value' => true]],
orderBy: ['created_at' => 'DESC'],
);
foreach ($page->items as $product) {
echo $product->name;
}
echo "Page {$page->page} of {$page->totalPages}";
echo "Showing {$page->getFromNumber()}–{$page->getToNumber()} of {$page->total}";For tables with many rows, findWhere will load all results into memory at once. Use chunking instead.
Processes rows in batches, calling $callback with each batch. Return false from the callback to stop early.
$products->chunk(
conditions: [['column' => 'status', 'value' => 'draft']],
callback: function (array $batch): void {
foreach ($batch as $product) {
// process product
}
},
chunkSize: 500,
);Returns a Generator that yields one entity per iteration, fetching rows from the database in batches internally.
foreach ($products->chunkGenerator([]) as $product) {
process($product);
}When a write operation fails, the model stores the last database error:
$id = $products->create(['name' => 'Gadget']);
if ($id === false) {
$error = $products->getLastError(); // string|null
error_log("Insert failed: {$error}");
}$products->getTableName(); // 'wp_products' — full name with site prefix
$products->getPrimaryKey(); // 'id'