Abstract Model Class
Revision: 2025/11/24
The AbstractModel abstract class is a base class for managing module data. It provides a fluent interface for building queries and performing CRUD operations on MySQL tables.
Defining a Model
To create a model, extend AbstractModel and implement the configure() method:
namespace Modules\Products;
use App\Abstracts\AbstractModel;
class ProductsModel extends AbstractModel
{
protected function configure($rule): void {
$rule->table('#__products')
->id()
->string('name', 100)->required()
->decimal('price', 10, 2)->default(0)
->text('description')->nullable()
->boolean('in_stock')->default(true)
->datetime('created_at')->nullable();
}
}
Primary must be only one field. The only function that accepts multiple primaries is schema. This is because while it's true that it must be possible to have multiple primary columns when importing data (so schema must allow creating tables with multiple primary ids) the model, as well as the template for tables etc. are used for data editing and editing happens only for internal tables and not for imported data which is used for statistics.
Basic installation
Puoi usare la shell per installare i moduli tramite
php milkadmin/cli.php module_name:install
AbstractModel Abstract Class Documentation
The AbstractModel class is a base class for data management in Ito modules. It provides methods to interact with the database, execute queries, validate and save data. This document describes in detail all public methods and their specifications.
Main Properties
$table: (string) The table name in the database (with prefix #__).$primary_key: (string) The primary key name of the table.$object_class: (string) The object class associated with the model.$db: (object) The database connection instance.$last_error: (string) The last error that occurred.$error: (bool) A flag to indicate if an error occurred.$per_page: (int) The number of records per page for pagination.
Public Methods
Understanding Query Builder and Execution
🔄 Model vs Query Return Types
The framework uses a dual execution system where methods return different types based on the calling context:
Query Builder Methods (where, order, limit, etc.)
These methods return a Query object, not a Model. You are temporarily leaving the Model context:
$query = $model->where('status = ?', ['active'])->order('name');
// $query is a Query object, not a Model
Execution Methods (getAll, get, getResults, getRow, getVar)
Important: These methods behave differently depending on how they are called:
| Called from | Return Type | Reason |
|---|---|---|
Model$model->getAll() |
Model |
The Query automatically receives the Model class via setModelClass() |
Query Builder Chain$model->where(...)->getAll() |
Model |
The Model class is propagated through the query chain |
Standalone Query$query->getAll() |
array |
No Model class was set, returns raw database array |
Example
// Case 1: Called from Model → returns Model
$products = $model->getAll();
foreach ($products as $product) {
echo $product->name; // Model instance
}
// Case 2: Query builder chain → returns Model
$products = $model->where('price > ?', [10])->getAll();
// Still returns Model because setModelClass() was set automatically
// Case 3: Standalone Query → returns array
$query = new Query($db);
$results = $query->from('products')->getAll();
// Returns array because no Model was associated
Key Takeaway: When using the Model API (which is the recommended approach), execution methods always return Model instances. They only return array when using standalone Query objects directly.
Methods Summary
| Method | Description | Returns | Example |
|---|---|---|---|
| CRUD Operations | |||
fill() |
Fill model with data from array/object | Model | $model->fill(['name' => 'Test']) |
getEmpty() |
Get empty model instance for creating new records | object | $model->getEmpty(['status' => 1]) |
getById() |
Retrieve single record by primary key (use isEmpty() to check) | Model | $model->getById(1) |
getByIds() |
Retrieve multiple records by IDs (use isEmpty() to check) | Model | $model->getByIds([1,2,3]) |
getByIdAndUpdate() |
Get record by ID or return empty if not found | object | $model->getByIdAndUpdate(123) |
getByIdForEdit() |
Retrieve record for editing with timezone conversion | object|null | $model->getByIdForEdit(1, []) |
store() |
Save single record directly (insert or update) | bool | $model->store($data, 1) |
save() |
Save batch of changes (insert/update/delete operations) | bool | $obj->save() |
beforeSave() |
Hook called before save operations | bool|void | protected function beforeSave($records) |
afterSave() |
Hook called after save operations | void | protected function afterSave($data, $results) |
delete() |
Delete record by primary key or single loaded record | bool | $model->delete(1) |
beforeDelete() |
Hook called before delete operations | bool | protected function beforeDelete($ids) |
afterDelete() |
Hook called after delete operations | void | protected function afterDelete($ids) |
deleteAll() |
Delete all stored records | bool | $model->deleteAll() |
detach() |
Mark current record for deletion | bool | $obj->detach() |
getLastInsertId() |
Get last inserted record ID | int | $model->getLastInsertId() |
getCommitResults() |
Get detailed results from last save() operation | array | $model->getCommitResults() |
getLastInsertIds() |
Get array of all inserted IDs from batch save | array | $model->getLastInsertIds() |
searchRelated() |
Search in related records | array | $model->searchRelated('John', 'name') |
setResultsByIds() |
Set results manually by array of IDs | bool | $model->setResultsByIds([1,2,3]) |
saveCurrentRecord() |
Save only the current record | bool | $model->saveCurrentRecord() |
get() |
Execute query and return results | Model|array|null | $model->get($query, $params) |
|
Query Builder Methods
⚠️ These methods return a
Query object (you leave the Model context).
Execution methods like getAll() will return the Model automatically because setModelClass() is set internally.
|
|||
where() |
Add WHERE condition to query | Query | $model->where('price > ?', [10]) |
whereIn() |
Add WHERE IN condition to query | Query | $model->whereIn('id', [1,2,3]) |
whereHas() |
Filter by relationship existence | Query | $model->whereHas('rel', 'id > ?', [1]) |
order() |
Add ORDER BY clause | Query | $model->order('name', 'asc') |
select() |
Specify columns to select | Query | $model->select(['id', 'name']) |
limit() |
Add LIMIT clause for pagination | Query | $model->limit(0, 10) |
getAll() |
Get all records without limits | Model|array | $model->getAll() |
getFirst() |
Get first record | Model|null | $model->getFirst('id', 'desc') |
total() |
Get total count of records | int | $model->total() |
from() |
Add FROM clause to query | Query | $model->from('table')->get() |
group() |
Add GROUP BY clause | Query | $model->group('category')->get() |
query() |
Get or set Query object | Query | $model->query() |
filterSearch() |
Apply search filter to query | Query | $model->filterSearch('text', $query) |
| Relationship Methods | |||
with() |
Eager load relationships | Model | $model->with(['doctor', 'patient']) |
clearRelationshipCache() |
Clear loaded relationship data | void | $model->clearRelationshipCache() |
getIncludeRelationships() |
Get list of relationships to include | array | $model->getIncludeRelationships() |
getRelationshipHandlers() |
Get handlers for relationship field | array | $model->getRelationshipHandlers('rel', 'type') |
| Navigation/Iterator Methods | |||
next() |
Move to next record | void | $results->next() |
prev() |
Move to previous record | void | $results->prev() |
first() |
Move to first record | Model|null | $results->first() |
last() |
Move to last record | Model|null | $results->last() |
moveTo() |
Move to specific record index | bool | $results->moveTo(5) |
count() |
Get number of records | int | $results->count() |
hasNext() |
Check if next record exists | bool | $results->hasNext() |
hasPrev() |
Check if previous record exists | bool | $results->hasPrev() |
getCurrentIndex() |
Get current record index | int | $results->getCurrentIndex() |
getNextCurrentIndex() |
Get next available record index | int | $model->getNextCurrentIndex() |
moveNext() |
Move to next record (alias of next) | Model|null | $results->moveNext() |
| Data Formatting Methods | |||
getRawData() |
Get raw data from database | array|object | $results->getRawData('array', true) |
getFormattedData() |
Get formatted data for display | array|object | $results->getFormattedData('array', true) |
getSqlData() |
Get data in SQL format | array|object | $results->getSqlData('array', true) |
toArray() |
Convert model to array | array | $model->toArray() |
setOutputMode() |
Set output mode ('raw', 'formatted', or 'sql') | void | $model->setOutputMode('formatted') |
getOutputMode() |
Get current output mode | string | $model->getOutputMode() |
getFormattedValue() |
Get single formatted value | mixed | $model->getFormattedValue('name') |
getSqlValue() |
Get single SQL value | mixed | $model->getSqlValue('date') |
getRawValue() |
Get single raw value | mixed | $model->getRawValue('field') |
getRecordAction() |
Get record action (insert/update/delete) | string|null | $model->getRecordAction() |
| Schema & Validation Methods | |||
validate() |
Validate model data | bool | $obj->validate() |
buildTable() |
Create or update database table | bool | $model->buildTable() |
dropTable() |
Drop database table | bool | $model->dropTable() |
getRules() |
Get schema rules | array | $model->getRules() |
getRule() |
Get single field rule | array|null | $model->getRule('price') |
getPrimaryKey() |
Get primary key field name | string | $model->getPrimaryKey() |
getRuleBuilder() |
Get RuleBuilder to modify schema | RuleBuilder | $model->getRuleBuilder() |
setRules() |
Set schema rules | void | $model->setRules($rules) |
getPrimaries() |
Get array of all primary keys | array | $model->getPrimaries() |
getSchema() |
Get Schema object (MySQL or SQLite) | Schema | $model->getSchema() |
getSchemaFieldDifferences() |
Get differences between DB and schema | array | $model->getSchemaFieldDifferences() |
getColumns() |
Get all defined columns | array | $model->getColumns() |
getQueryColumns() |
Get query columns | array | $model->getQueryColumns() |
setQueryColumns() |
Set query columns | void | $model->setQueryColumns($cols) |
getTable() |
Get table name | string | $model->getTable() |
setTable() |
Set table name | void | $model->setTable('table_name') |
getDb() |
Get database connection | MySql|SQLite | $model->getDb() |
setDb() |
Set database connection | void | $model->setDb($db) |
getDbType() |
Get database type (db or db2) | string | $model->getDbType() |
| Utility Methods | |||
isEmpty() |
Check if model is empty | bool | $model->isEmpty() |
hasError() |
Check if error occurred | bool | $model->hasError() |
getLastError() |
Get last error message | string | $model->getLastError() |
clearCache() |
Clear query results cache | void | $model->clearCache() |
setQueryParams() |
Set query parameters from request | void | $model->setQueryParams($request) |
getLoadedExtension() |
Get loaded extension instance | object|null | $model->getLoadedExtension('ext_name') |
| Method Handlers (Attribute System) | |||
registerMethodHandler() |
Register custom attribute handler | void | $model->registerMethodHandler('field', 'type', 'method') |
removeMethodHandler() |
Remove attribute handler | void | $model->removeMethodHandler('field', 'type') |
getMethodHandler() |
Get handler for field and type | callable|null | $model->getMethodHandler('field', 'type') |
hasMethodHandler() |
Check if handler exists | bool | $model->hasMethodHandler('field', 'type') |
getFieldsWithHandlers() |
Get fields with handlers of type | array | $model->getFieldsWithHandlers('ToDisplayValue') |
| Timezone Handling | |||
setDatesInUserTimezone() |
Enable/disable user timezone conversion | Model | $model->setDatesInUserTimezone(true) |
convertDatesToUserTimezone() |
Convert dates from UTC to user timezone | Model | $model->convertDatesToUserTimezone() |
convertDatesToUTC() |
Convert dates from user timezone to UTC | Model | $model->convertDatesToUTC() |
| Data Management | |||
setResults() |
Set results manually | void | $model->setResults($array) |
setRow() |
Set single row data | void | $model->setRow($data) |
| ArrayDb / Virtual Tables | |||
registerVirtualTable() |
Register current model data as an ArrayDb virtual table | bool | $model->registerVirtualTable('products') |
Detailed Method Documentation
fill(array|object|null $data = null)
Fills the model with data from an array or object. This is the primary method for loading data into a model instance.
/**
* @param array|object|null $data Data to fill the model with
* @return static Returns the model instance for method chaining
*/
public function fill(array|object|null $data = null): static;
// Example 1: Create new record
$product = new ProductsModel();
$product->fill([
'name' => 'New Product',
'price' => 29.99,
'in_stock' => true
]);
$product->save();
// Example 2: Update existing record
$product = new ProductsModel();
$product->fill([
'id' => 123,
'name' => 'Updated Product Name'
]);
$product->save(); // Will update record with id=123
// Example 3: Fill from request data
$product = new ProductsModel();
$product->fill($_POST);
if ($product->validate()) {
$product->save();
}
- Input parameters:
$data: (array|object|null) The data to fill the model with. Can be an associative array or object.
- Return value:
static: Returns the model instance for method chaining.
- Behavior:
- If
$datacontains a primary key and the record exists in database, it loads the record and marks it for update - If the primary key is not found or missing, creates a new record marked for insert
- Only fields defined in the model's schema are accepted
- Performs automatic type conversion based on field types
- If
store(array $data, $id = null)
Saves a single record directly to the database (immediate save). This is different from save() which handles batch operations.
/**
* @param array $data Data to save
* @param mixed $id Primary key for update, null for insert
* @return bool True if successful, false otherwise
*/
public function store(array $data, $id = null): bool;
// Example 1: Insert new record
$success = $this->model->store([
'name' => 'New Product',
'price' => 29.99
]);
// Example 2: Update existing record
$success = $this->model->store([
'name' => 'Updated Product',
'price' => 39.99
], 123); // Update record with id=123
if ($success) {
$new_id = $this->model->getLastInsertId();
echo "Record saved with ID: " . $new_id;
} else {
echo "Error: " . $this->model->getLastError();
}
- Input parameters:
$data: (array) An array of data to save.$id: (mixed, optional) The primary key of the record to update. If null, creates a new record.
- Return value:
bool: Returnstrueif save was successful,falseotherwise.
- Note: Use
store()for saving single records immediately. Usesave()for batch operations.
getById($id, $use_cache = true)
Retrieves a single record by primary key. Always returns a Model instance. Use isEmpty() to check if the record was found.
/**
* @param mixed $id Primary key value
* @param bool $use_cache Whether to use cache for data
* @return static Always returns Model instance (use isEmpty() to check if record exists)
*/
public function getById($id, bool $use_cache = true): static;
// Example 1: Check if record exists
$product = $this->model->getById(123);
if (!$product->isEmpty()) {
echo $product->name; // Access properties directly
echo "Price: €" . $product->price;
}
// Example 2: Using count()
$product = $this->model->getById(123);
if ($product->count() > 0) {
echo $product->name;
}
- Input parameters:
$id: (mixed) The primary key value of the record to retrieve.$use_cache: (bool, optional) Whether to use cache (default:true).
- Return value:
static: Always returns a Model instance. UseisEmpty()orcount()to check if the record was found.
- Note: This method never returns
null. It always returns a Model instance, even if no record is found. UseisEmpty()to check if the record exists.
getByIdAndUpdate($id, array $merge_data = [], $mysql_array = false)
Returns a record by primary key, otherwise returns an empty object
/**
* @param mixed $id Primary key value
* @param array $merge_data Optional data to merge with the record
* @param bool $mysql_array Whether to return the record as a MySQL array (default: false)
* @return object Returns the record object or an empty object.
*/
public function getByIdAndUpdate($id, array $merge_data = [], $mysql_array = false): object;
// Example
$post = $this->model->getByIdAndUpdate(123);
echo $post->title;
- Input parameters:
$id: (mixed) The primary key value of the record to retrieve.$merge_data: (array, optional) Data to merge with the record.$mysql_array: (bool, optional) Whether to return the record as a MySQL array (default:false).
- Return value:
object: Returns the record object, if found, otherwise an empty object.
getEmpty(array $data = [], $mysql_array = false)
Returns an empty model object for creating new records
/**
* @param array $data Data to use to initialize the object
* @param bool $mysql_array Whether to return the record as a MySQL array (default: false)
* @return object Returns an empty model object
*/
public function getEmpty(array $data = [], $mysql_array = false): object;
// Example
$new_post = $this->model->getEmpty();
$new_post->title = "New Title";
- Input parameters:
$data: (array, optional) Data to use to initialize the object.$mysql_array: (bool, optional) Whether to return the record as a MySQL array (default:false).
- Return value:
object: Returns an empty model object, possibly initialized with the provided data.
getByIdForEdit($id, array $merge_data = [])
Retrieves a record for editing, applying edit rules.
Example 1: Edit a record
protected function actionEditProject() {
$id = _absint($_REQUEST['id'] ?? 0);
// Retrieve the record and apply edit rules
$data = $this->model->getByIdForEdit($id, Route::getSessionData());
if ($data === null) {
Route::redirectError('?page='.$this->page."&action=list-projects", 'Invalid id');
}
// Display the edit form
Response::themePage('default', 'edit-project.page.php', [
'id' => $id,
'data' => $data,
'page' => $this->page,
'url_success' => '?page='.$this->page."&action=list-projects",
'action_save' => 'save_projects'
]);
}
- Input parameters:
$id: (mixed) The primary key value of the record to retrieve.$merge_data: (array, optional) Additional data to merge with the retrieved record.
- Return value:
object|null: Returns the record object, if found, otherwisenull.
save(bool $cascade = true, $reset_save_result = true)
Saves all tracked changes (batch operation). Processes all inserts, updates, and deletes marked through fill() and detach().
/**
* @param bool $cascade If true, saves related hasOne relationships
* @param bool $reset_save_result If true, reset save results after commit
* @return bool True if all operations succeeded, false otherwise
*/
public function save(bool $cascade = true, $reset_save_result = true): bool;
// Example 1: Create new record
$product = new ProductsModel();
$product->fill([
'name' => 'New Product',
'price' => 29.99
]);
if ($product->save()) {
echo "Product created with ID: " . $product->getLastInsertId();
}
// Example 2: Update existing record
$product = new ProductsModel();
$product->fill(['id' => 123, 'price' => 39.99]);
if ($product->save()) {
echo "Product updated successfully";
}
// Example 3: Batch operations
$product = new ProductsModel();
$product->fill(['name' => 'Product 1', 'price' => 10]); // Insert
$product->fill(['id' => 5, 'price' => 20]); // Update
$product->fill(['id' => 10])->detach(); // Delete
if ($product->save()) {
$results = $product->getCommitResults();
// Returns array of all operations performed
} else {
echo "Error: " . $product->getLastError();
}
- Input parameters:
$cascade: (bool, optional) If true, automatically saves hasOne relationships. Default: true.$reset_save_result: (bool, optional) If true, resets save results after commit. Default: true.
- Return value:
bool: Returnstrueif all operations succeeded,falseotherwise.
- Important:
- Use
save()for batch operations with multiple records - Use
store()for immediate single record saves - All operations are executed in order: DELETE, then INSERT, then UPDATE
- Use
getCommitResults()to get detailed information about each operation
- Use
beforeSave(array $records)
Hook method called before save operations. Override this method in your model to execute custom logic before records are saved. Return false to cancel the save operation.
/**
* @param array $records Array of records to be saved
* @return bool|void Return false to cancel save operation
*/
protected function beforeSave(array $records): bool|void;
// Example: Auto-generate slug before saving
protected function beforeSave(array $records): void {
foreach ($records as $index => $record) {
if (empty($record['slug']) && !empty($record['title'])) {
$this->records_array[$index]['slug'] = $this->generateSlug($record['title']);
}
}
}
- Input parameters:
$records: (array) Array of records that will be saved.
- Return value:
bool|void: Returnfalseto cancel the save operation, otherwise return nothing ortrue.
afterSave(array $data, array $results)
Hook method called after save operations complete. Override this method in your model to execute custom logic after records are saved (e.g., logging, sending notifications, updating related data).
/**
* @param array $data Array of saved record data
* @param array $results Array of save operation results
* @return void
*/
protected function afterSave(array $data, array $results): void;
// Example: Send notification after product is created
protected function afterSave(array $data, array $results): void {
foreach ($results as $result) {
if ($result['action'] === 'insert' && $result['result']) {
$this->sendProductCreatedNotification($result['id']);
}
}
}
- Input parameters:
$data: (array) Array containing the saved record data.$results: (array) Array of save operation results with structure: [['id' => int, 'action' => string, 'result' => bool, 'last_error' => string], ...]
- Return value:
void: This method does not return any value.
delete($id = null)
Deletes a record from the database. If $id is not provided, it only works when exactly one record is loaded in records_objects; otherwise it throws an exception to prevent accidental deletions.
protected function tableActionDeleteProject($id, $request) {
if ($this->model->delete($id)) {
return true;
} else {
MessagesHandler::addError($this->model->getLastError());
return false;
}
}
- Input parameters:
$id: (mixed|null) The primary key of the record to delete. If null, exactly one record must be loaded inrecords_objectsor an exception is thrown.
- Return value:
bool: Returnstrueif deletion was successful, otherwisefalse.
deleteAll()
Deletes all stored records from the database.
// Example: delete all stored records
if ($model->deleteAll()) {
echo "All records deleted";
} else {
echo "Delete failed: " . $model->getLastError();
}
- Input parameters:
- None
- Return value:
bool: Returnstrueif deletion was successful, otherwisefalse.
clearCache()
Clears the results cache.
/**
* @return void
*/
public function clearCache(): void;
// Example
$this->model->clearCache();
- Input parameters:
- None
- Return value:
void: This method does not return any value.
getLastError()
Returns the last error message.
/**
* @return string
*/
public function getLastError(): string;
// Example
echo $this->model->getLastError();
- Input parameters:
- None
- Return value:
string: Returns the string with the last error message.
hasError()
Checks if an error occurred during the last database operation.
/**
* @return bool
*/
public function hasError(): bool;
// Example
if ($this->model->hasError()) {
echo "An error occurred: ".$this->model->getLastError();
}
- Input parameters:
- None
- Return value:
bool: Returnstrueif an error occurred,falseotherwise.
Query building methods
These methods allow you to create and manage the query and are used for data listing in the list.page
where(string $condition, array $params = [])
Adds a WHERE clause to the current query.
/**
* @param string $condition The condition to add
* @param array $params The parameters to pass for the query with bind_params
* @return $this
*/
public function where(string $condition, array $params = []): self;
// Example
$this->model->where('title LIKE ?', ['%test%'])->get();
- Input parameters:
$condition: (string) The SQL condition to add to the WHERE clause.$params: (array, optional) An array of parameters to pass to the query to prevent SQL injection.
- Return value:
$this: Returns the current instance to allow method chaining.
order(string|array $field = '', string $dir = 'asc')
Adds an ORDER BY clause to the current query.
/**
* @param string|array $field Field or array of fields to order by.
* @param string $dir Sort direction ('asc' or 'desc')
* @return $this
*/
public function order(string|array $field = '', string $dir = 'asc'): self;
// Example
$this->model->order('title', 'desc')->get();
- Input parameters:
$field: (string|array) The field or fields to sort the results by.$dir: (string, optional) The sort direction ('asc' for ascending or 'desc' for descending), default 'asc'.
- Return value:
$this: Returns the current instance to allow method chaining.
select(array|string $fields)
Adds a SELECT clause to the current query.
/**
* @param array|string $fields The fields to select.
* @return $this
*/
public function select(array|string $fields): self;
// Example
$this->model->select('id, title')->get();
$this->model->select(['id', 'title'])->get();
- Input parameters:
$fields: (array|string) An array or string containing the fields to select.
- Return value:
$this: Returns the current instance to allow method chaining.
from(string $from)
Adds a FROM clause to the current query.
/**
* @param string $from The table to query
* @return $this
*/
public function from(string $from): self;
// Example
$this->model->from('posts')->get();
$this->model->from('posts LEFT JOIN users ON posts.user_id = user.id')->get();
- Input parameters:
$from: (string) The table or join to query.
- Return value:
$this: Returns the current instance to allow method chaining.
group(string $group)
Adds a GROUP BY clause to the current query.
/**
* @param string $group The fields to group the results by
* @return $this
*/
public function group(string $group): self;
// Example
$this->model->select('COUNT(*), user_id')->group('user_id')->get();
- Input parameters:
$group: (string) The field to group the results by.
- Return value:
$this: Returns the current instance to allow method chaining.
limit(int $start, int $limit)
Adds a LIMIT clause to the current query.
/**
* @param int $start Number of records to skip
* @param int $limit Number of records to retrieve
* @return $this
*/
public function limit(int $start, int $limit): self;
// Example
$this->model->limit(10, 10)->get();
- Input parameters:
$start: (int) The index of the first record to retrieve.$limit: (int) The number of records to retrieve.
- Return value:
$this: Returns the current instance to allow method chaining.
Table display methods
These methods allow you to retrieve data, totals and execute queries:
get($query = null, $params = [])
Executes the query and returns results. When called from a Model, it always returns a Model instance.
/**
* @param Query|string $query A Query object or SQL string
* @param array $params Parameters to pass to the query to prevent SQL injection
* @return static Model instance with query results
*/
public function get(Query $query, ?array $params = []): AbstractModel|array|null|false;
// Example 1: Execute Query object (returns Model)
$query = $this->model->query()->where('status = ?', ['active']);
$posts = $this->model->get($query);
foreach ($posts as $post) {
echo $post->title; // Model instance
}
// Example 2: Execute with raw SQL (for compatibility)
$posts = $this->model->get("SELECT * FROM posts WHERE status = ?", ['active']);
- Input parameters:
$query: (Query|string) A Query object or SQL string to execute.$params: (array, optional) Parameters for the query to prevent SQL injection.
- Return value:
Model: When called with a Query object, returns Model instance (setModelClass is automatically set).array|null: When called with raw SQL string, behavior depends on the context.
- Note: This method is primarily used internally. For most use cases, use
getAll(),getById(), or query builder methods directly.
execute($query = null, $params = [])
Executes the current query and returns raw results.
/**
* @param string $query The SQL query to execute
* @param array $params Parameters to pass to the query to prevent SQL injection
* @return array returns an array of records (associative array)
*/
public function execute($query = null, $params = []): array;
// Example
$posts = $this->model->execute();
foreach ($posts as $post) {
echo $post['title'];
}
- Input parameters:
$query: (string, optional) The SQL query to execute. If not specified, the current query is executed.$params: (array, optional) Parameters for the query to prevent SQL injection.
- Return value:
array: An array of associative arrays representing the query results.
getAll($order_field = '', $order_dir = 'asc')
Executes the current query without limits to retrieve all data. When called from a Model, returns a Model instance (not an array).
/**
* @param string $order_field Optional field to order by
* @param string $order_dir Order direction ('asc' or 'desc')
* @return static|array Model instance (when called from Model) or array (standalone Query)
*/
public function getAll($order_field = '', $order_dir = 'asc'): static|array;
// Example 1: Called from Model (returns Model instance)
$posts = $this->model->getAll();
foreach ($posts as $post) {
echo $post->title; // $post is a Model instance
}
// Example 2: With ordering
$posts = $this->model->getAll('created_at', 'desc');
// Example 3: With query builder chain (still returns Model)
$posts = $this->model->where('status = ?', ['active'])->getAll();
// Returns Model because setModelClass() is automatically set
// Example 4: Check if results are empty
$posts = $this->model->getAll();
if (!$posts->isEmpty()) {
foreach ($posts as $post) {
echo $post->title;
}
}
- Input parameters:
$order_field: (string, optional) Field name to order results by.$order_dir: (string, optional) Order direction: 'asc' or 'desc' (default: 'asc').
- Return value:
static: Model instance when called from a Model (recommended usage).array: Empty array if no database connection, or raw array if called from standalone Query.
- Important: Unlike the documentation might suggest,
getAll()returns a Model instance when called from a Model, not an array. UseisEmpty()orcount()to check results. The Model instance is iterable and acts like an array in foreach loops.
first($query = null, $params = [])
Executes the current query and returns a single object. The limit is implicitly set to 1
/**
* @param string $query The SQL query to execute
* @param array $params Parameters to pass to the query to prevent SQL injection
* @return object|null Returns the first record as object or null
*/
public function first($query = null, $params = []): ?object;
// Example
$post = $this->model->first();
if ($post) {
echo $post->title;
}
- Input parameters:
- None
- Return value:
object|null: The object corresponding to the first record or null if no data is present.
total()
Executes the current query or the last query and returns the total number of records without limitations.
/**
* @return int Returns the total number of records.
*/
public function total(): int;
// Example
$total_posts = $this->model->total();
echo "Total posts: " . $total_posts;
- Input parameters:
- None
- Return value:
int: The total number of records in the table, without limitations.
setQueryParams(array $request)
Sets the parameters for the query from the request (limit, order, filter)
/**
* @param array $request The request from the browser
* @return void
*/
public function setQueryParams(array $request): void;
// Example
$request = $this->getRequestParams('table_posts');
$this->model->setQueryParams($request);
- Input parameters:
$request: (array) An array of parameters (for example taken from the query string) to configure the query.
- Return value:
void: This method does not return any value.
buildTable()
Creates or modifies the table if it doesn't exist or if there have been changes to the object. It's executed during module installation or update.
The method then calls after_modify_table and after_create_table.
/**
* @return bool Returns true if the operation was successful
*/
public function buildTable(): bool;
// Example
$this->model->buildTable();
- Input parameters:
- None
- Return value:
bool: Returnstrueif the operation was successful, otherwisefalse.
getSchemaFieldDifferences()
Returns an array containing the differences between the current database schema and the new schema definition after calling buildTable().
dropTable()
Deletes the table if it exists. Called when running module uninstall from shell
/**
* @return bool Returns true if the operation was successful
*/
public function dropTable(): bool;
// Example
$this->model->dropTable();
- Input parameters:
- None
- Return value:
bool: Returnstrueif the operation was successful, otherwisefalse.
validate(bool $validate_all = false)
Validates data stored in the Model using internal rules. Works with current record or all records in records_array.
/**
* @param bool $validate_all If true, validates all records. If false, validates only current record
* @return bool
*/
public function validate(bool $validate_all = false): bool;
// Example - Validate current record
$obj = $this->model->getEmpty($_REQUEST);
if ($obj->validate()) {
echo "Valid data";
} else {
echo "Invalid data";
}
// Example - Validate all records in Model
if ($this->model->validate(true)) {
echo "All records are valid";
}
- Input parameters:
$validate_all: (bool) If true, validates all records in records_array. If false (default), validates only current record.
- Return value:
bool: Returns true if the data is valid, false otherwise.
getColumns($key = '')
Returns all columns defined in the object rules.
/**
* @return array Array of columns
*/
public function getColumns(): array;
// Example
$all_columns = $this->model->getColumns();
addFilter($filter_type, $fn)
Adds a custom filter function.
/**
* @param string $filter_type Filter type
* @param callable $fn Filter function
*/
public function addFilter($filter_type, $fn);
// Example
$this->model->addFilter('status', function($query, $value) {
$query->where('status = ?', [$value]);
});
Validation
The validate method has been updated to support:
- Custom validations through _validate in rules
- validate_{field} methods in the object
- Automatic type validation (int, float, email, url, datetime, enum, list)
- Length checking for strings and texts
- Required field validation
// Example of rules with custom validation
$this->rule('email', [
'type' => 'string',
'_validate' => function($value, $data) {
if (!filter_var($value, FILTER_VALIDATE_EMAIL)) {
MessagesHandler::addError('Invalid email format');
}
}
]);
with(string|array|null $relations = null)
Eager load relationships. Loads specified relationships immediately to avoid N+1 query problems.
/**
* @param string|array|null $relations Relationship(s) to include
* @return static Returns the model instance for method chaining
*/
public function with(string|array|null $relations = null): static;
// Example 1: Load single relationship
$appointments = $this->model->with('doctor')->getAll();
foreach ($appointments as $appointment) {
echo $appointment->doctor->name; // Already loaded, no extra query
}
// Example 2: Load multiple relationships
$appointments = $this->model->with(['doctor', 'patient'])->getAll();
// Example 3: Load all defined relationships
$appointments = $this->model->with()->getAll();
whereIn(string $field, array $values, string $operator = 'AND')
Add WHERE IN condition to query. Useful for filtering by multiple values.
/**
* @param string $field Field name
* @param array $values Array of values
* @param string $operator 'AND' or 'OR'
* @return Query
*/
public function whereIn(string $field, array $values, string $operator = 'AND'): Query;
// Example: Find products with specific IDs
$products = $this->model->whereIn('id', [1, 5, 10, 25])->getAll();
// Example: Find products in specific categories
$products = $this->model->whereIn('category', ['Electronics', 'Books'])->getAll();
whereHas(string $relationAlias, string $condition, array $params = [])
Filter records based on relationship existence. Uses EXISTS subquery.
/**
* @param string $relationAlias Relationship alias
* @param string $condition WHERE condition for related records
* @param array $params Parameters for the condition
* @return Query
*/
public function whereHas(string $relationAlias, string $condition, array $params = []): Query;
// Example: Find doctors who have appointments after a certain date
$doctors = $this->model->whereHas('appointments', 'date > ?', ['2024-01-01'])->getAll();
getByIds(string|array $ids)
Retrieve multiple records by their primary keys. Always returns a Model instance. Use isEmpty() to check if records were found.
/**
* @param string|array $ids Comma-separated list or array of IDs
* @return static Always returns Model instance (use isEmpty() to check if records exist)
*/
public function getByIds(string|array $ids): static;
// Example 1: Array of IDs
$products = $this->model->getByIds([1, 5, 10, 25]);
if (!$products->isEmpty()) {
foreach ($products as $product) {
echo $product->name;
}
}
// Example 2: Comma-separated string
$products = $this->model->getByIds('1,5,10,25');
foreach ($products as $product) {
echo $product->name;
}
- Note: This method never returns
null. UseisEmpty()orcount()to check if any records were found.
detach()
Mark current record for deletion. The record will be deleted when save() is called.
/**
* @return bool True if record was marked for deletion
*/
public function detach(): bool;
// Example: Mark record for deletion
$product = $this->model->getById(123);
$product->detach();
$product->save(); // Now the record is actually deleted
isEmpty()
Check if model is empty (has no data).
/**
* @return bool True if model is empty
*/
public function isEmpty(): bool;
// Example
$product = $this->model->getById(999); // Non-existent ID
if ($product->isEmpty()) {
echo "Product not found";
}
getLastInsertId()
Get the last inserted record ID from save() or store() operation.
/**
* @return int The last insert ID
*/
public function getLastInsertId(): int;
// Example
$product = new ProductsModel();
$product->fill(['name' => 'New Product', 'price' => 29.99]);
if ($product->save()) {
$new_id = $product->getLastInsertId();
echo "Created product with ID: " . $new_id;
}
getCommitResults()
Get detailed results from last save() operation. Returns array with information about each operation performed.
/**
* @return array Array of operations: [['id' => int, 'action' => string, 'result' => bool, 'last_error' => string], ...]
*/
public function getCommitResults(): array;
// Example
$product = new ProductsModel();
$product->fill(['name' => 'Product 1']);
$product->fill(['id' => 5, 'price' => 20]);
$product->save();
$results = $product->getCommitResults();
foreach ($results as $result) {
echo "ID: {$result['id']}, Action: {$result['action']}, Success: " . ($result['result'] ? 'Yes' : 'No');
if (!$result['result']) {
echo ", Error: {$result['last_error']}";
}
echo "\n";
}
getFirst($order_field = '', $order_dir = 'asc')
Get the first record from query results.
/**
* @param string $order_field Field to order by
* @param string $order_dir Direction ('asc' or 'desc')
* @return static|null First record or null
*/
public function getFirst($order_field = '', $order_dir = 'asc'): ?static;
// Example
$latest_product = $this->model->getFirst('created_at', 'desc');
if ($latest_product) {
echo $latest_product->name;
}
getRules(string $key = '', mixed $value = true)
Get schema rules defined in configure() method.
/**
* @param string $key Optional filter key
* @param mixed $value Optional filter value
* @return array Array of rules
*/
public function getRules(string $key = '', mixed $value = true): array;
// Example 1: Get all rules
$rules = $this->model->getRules();
// Example 2: Get required fields only
$required_fields = $this->model->getRules('required', true);
getRule(string $key = '')
Get rule for a specific field.
/**
* @param string $key Field name
* @return array|null Rule array or null if not found
*/
public function getRule(string $key = ''): ?array;
// Example
$price_rule = $this->model->getRule('price');
if ($price_rule) {
echo "Price type: " . $price_rule['type'];
}
getPrimaryKey()
Get the primary key field name.
/**
* @return string Primary key field name
*/
public function getPrimaryKey(): string;
// Example
$pk = $this->model->getPrimaryKey();
echo "Primary key: " . $pk; // Output: "id"
registerVirtualTable(string $tableName, ?string $autoIncrementColumn = null)
Registers the current model results as an ArrayDb virtual table so you can run SQL queries on the data. Only scalar fields are included; array and object fields are ignored.
/**
* @param string $tableName Virtual table name (can include #__ prefix token)
* @param string|null $autoIncrementColumn Auto-increment column (defaults to primary key)
* @return bool True on success, false on failure
*/
public function registerVirtualTable(string $tableName, ?string $autoIncrementColumn = null): bool;
// Example 1: Basic usage (PHP + SQL)
$modelData->registerVirtualTable('product');
$complete = \App\Get::ArrayDb()->getResults(
'SELECT * FROM product WHERE status = "COMPLETE"'
);
// Equivalent PHP
$new_data = array_filter($modelData->getFormattedData(), function ($row) {
return $row->status === 'COMPLETE';
});
// Example 2: Why SQL is more readable for joins
$modelProducts->registerVirtualTable('products');
$modelCustomers->registerVirtualTable('customers');
$db = \App\Get::ArrayDb();
// SQL - clear and concise
$result = $db->getResults('SELECT p.*, c.name AS customer_name, c.email
FROM products p
JOIN customers c ON p.customer_id = c.id
WHERE p.status = "PENDING"');
// PHP - complex and inefficient
$products = $modelProducts->getFormattedData();
$customers = $modelCustomers->getFormattedData();
$customersMap = array_column($customers, null, 'id');
$result = array_map(function ($p) use ($customersMap) {
$customer = $customersMap[$p->customer_id] ?? null;
return (object) [
...(array) $p,
'customer_name' => $customer->name ?? null,
'email' => $customer->email ?? null
];
}, array_filter($products, fn($p) => $p->status === 'PENDING'));
- Input parameters:
$tableName: (string) The virtual table name to register in ArrayDb.$autoIncrementColumn: (string|null, optional) Auto-increment column (defaults to the model primary key).
- Return value:
bool: Returnstrueon success,falseif the table name is invalid or no data is available.
Complete Usage Examples
Example 2: Saving with validation
protected function actionSaveProjects() {
$id = _absint($_REQUEST[$this->model->getPrimaryKey()] ?? 0);
// Create an object with request data (data is already inside the model)
$obj = $this->model->getEmpty($_REQUEST);
// Validate and save (data is internal to the model)
if ($obj->validate()) {
if ($obj->save()) {
// If it's a new record retrieve the id
if ($id == 0) {
$id = $obj->getLastInsertId();
Route::redirectSuccess('?page='.$this->page."&action=related-tables&id=".$id,
_r('Save success'));
}
Route::redirectSuccess($_REQUEST['url_success'], _r('Save success'));
} else {
$error = "An error occurred while saving the data. ".$this->model->getLastError();
$obj2 = $this->model->getByIdAndUpdate($id, $_REQUEST);
Route::redirectError($_REQUEST['url_error'], $error, toMysqlArray($obj2));
}
}
Route::redirectHandlerErrors($_REQUEST['url_error'], $array_to_save);
}
Example: List management with parameters
protected function actionListProjects() {
$table_id = 'table_projects';
// Retrieve request parameters for the table
$request = $this->getRequestParams($table_id);
// Register the delete action
$this->callTableAction($table_id, 'delete-project', 'table_action_delete_project');
// Set query parameters (limit, order, filters)
$this->model->setQueryParams($request);
// Retrieve data for modellist
$modellist_data = $this->getModellistData($table_id, $fn_filter_applier);
// Configuration customization
$modellist_data['page_info']['limit'] = 1000;
$modellist_data['page_info']['pagination'] = false;
// Table output
$outputType = Response::isJson() ? 'json' : 'html';
$table_html = Get::themePlugin('table', $modellist_data);
$theme_path = realpath(__DIR__.'/Views/list.page.php');
if ($outputType === 'json') {
Response::json([
'html' => $table_html,
'success' => !MessagesHandler::hasErrors(),
'msg' => MessagesHandler::errorsToString()
]);
} else {
Response::themePage('default', $theme_path, [
'table_html' => $table_html,
'table_id' => $table_id,
'page' => $this->page
]);
}
}