Milk Admin

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

Table Structure Documentation: For complete information about defining table structures and using the RuleBuilder, see the RuleBuilder documentation.

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

📋 Quick Reference: This table provides a complete overview of all available methods. Click on a method name to jump to its detailed documentation.

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 $data contains 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

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: Returns true if save was successful, false otherwise.
  • Note: Use store() for saving single records immediately. Use save() 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. Use isEmpty() or count() 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. Use isEmpty() 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, otherwise null.

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: Returns true if all operations succeeded, false otherwise.
  • 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

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: Return false to cancel the save operation, otherwise return nothing or true.

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 in records_objects or an exception is thrown.
  • Return value:
    • bool: Returns true if deletion was successful, otherwise false.

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: Returns true if deletion was successful, otherwise false.

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: Returns true if an error occurred, false otherwise.

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. Use isEmpty() or count() 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: Returns true if the operation was successful, otherwise false.

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: Returns true if the operation was successful, otherwise false.

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. Use isEmpty() or count() 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: Returns true on success, false if 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
            ]);
        }
    }
    
Loading...