Models
Models are classes that handle data operations such as retrieving, inserting, updating, and deleting records in your database. They provide a clean separation of database logic from controllers and views, following the MVC pattern.
Creating a Model
A basic model class should extend LavaLust’s base Model class:
<?php
class UserModel extends Model
{
protected $table = 'users';
protected $primary_key = 'id';
}
Explanation:
$tablespecifies the database table associated with the model.The model inherits methods for common database operations like
find(),all(),insert(),update(), anddelete().
Model Properties
You can configure the following protected properties in your model:
<?php
class UserModel extends Model
{
protected $table = 'users';
protected $primary_key = 'id';
// Whitelist of columns allowed for mass assignment
protected $fillable = ['name', 'email', 'role'];
// Blacklist of columns protected from mass assignment
protected $guarded = ['id'];
// Automatically manage created_at and updated_at
protected $timestamps = true;
// Customize timestamp column names
protected $created_at_column = 'created_at';
protected $updated_at_column = 'updated_at';
// Soft delete settings
protected $has_soft_delete = true;
protected $soft_delete_column = 'deleted_at';
}
Note
If both $fillable and $guarded are empty, all columns are allowed (legacy mode).
Always define $fillable or $guarded in production models.
Loading a Model in a Controller
In LavaLust, you can load a model inside a controller using the $this->call->model() method:
<?php
class UserController extends Controller
{
public function __construct()
{
// Load the UserModel
parent::__construct();
$this->call->model('UserModel');
}
public function listUsers()
{
$users = $this->UserModel->all();
$this->call->view('user/list', ['users' => $users]);
}
public function showUser($id)
{
$user = $this->UserModel->find($id);
$this->call->view('user/profile', ['user' => $user]);
}
}
Note
$this->call->model('UserModel') returns an instance of the model ready to use.
Basic CRUD Operations in Controller
1. Retrieve all records:
<?php
public function listUsers()
{
$users = $this->UserModel->all();
foreach ($users as $user) {
echo $user['name'] . " - " . $user['email'] . "<br>";
}
}
2. Insert a new record:
<?php
public function addUser()
{
$data = [
'name' => 'Alice Smith',
'email' => 'alice@example.com'
];
$id = $this->UserModel->insert($data);
echo "User added with ID: " . $id;
}
Note
insert() returns the last inserted ID. It respects $fillable / $guarded and
automatically sets created_at and updated_at if timestamps are enabled.
3. Update a record:
<?php
public function updateUser($id)
{
$data = [
'email' => 'alice.smith@example.com'
];
$this->UserModel->update($id, $data);
echo "User updated successfully!";
}
4. Delete a record:
<?php
public function deleteUser($id)
{
$this->UserModel->delete($id);
echo "User deleted successfully!";
}
5. Find a single record by primary key:
<?php
$user = $this->UserModel->find(5);
// Include soft-deleted records
$user = $this->UserModel->find(5, true);
6. Find a single record by column:
<?php
$user = $this->UserModel->find_by('email', 'alice@example.com');
7. Find all records matching a column value:
<?php
$admins = $this->UserModel->find_all_by('role', 'admin');
8. Update records matching conditions:
<?php
$this->UserModel->update_where(
['role' => 'guest'],
['role' => 'member']
);
9. Delete records matching conditions:
<?php
$this->UserModel->delete_where(['active' => 0]);
Retrieving Records
Get first record:
<?php
$user = $this->UserModel->first();
Get last record:
<?php
$user = $this->UserModel->last();
Get a single column value:
<?php
$email = $this->UserModel->value('email', ['id' => 5]);
Get a flat array of column values:
<?php
$emails = $this->UserModel->pluck('email');
// With conditions
$emails = $this->UserModel->pluck('email', ['role' => 'admin']);
Check if a record exists:
<?php
$exists = $this->UserModel->exists(['email' => 'alice@example.com']);
Row count:
<?php
$total = $this->UserModel->count();
// Include soft-deleted rows
$total = $this->UserModel->count(true);
Order results:
<?php
$users = $this->UserModel->order_by('name', 'ASC');
Limit results:
<?php
$users = $this->UserModel->limit(10);
Filter with a WHERE clause:
<?php
$activeAdmins = $this->UserModel
->filter(['role' => 'admin', 'active' => 1])
->get_all();
Get table columns:
<?php
$columns = $this->UserModel->get_columns();
Raw SQL query:
<?php
$results = $this->UserModel->raw(
'SELECT * FROM users WHERE created_at > ?',
['2024-01-01']
);
Access the query builder directly:
<?php
$this->UserModel->query()->where('active', 1)->get_all();
Find or Create / Update or Create
first_or_create — finds the first matching record or inserts one if none exists:
<?php
$result = $this->UserModel->first_or_create(
['email' => 'alice@example.com'],
['name' => 'Alice Smith']
);
$user = $result['record'];
$created = $result['created']; // true if a new record was inserted
update_or_create — updates the first matching record, or creates one if none found:
<?php
$id = $this->UserModel->update_or_create(
['email' => 'alice@example.com'],
['name' => 'Alice Smith', 'role' => 'editor']
);
Aggregate Methods
All aggregate methods accept optional $conditions and a $with_deleted flag.
Sum:
<?php
$total = $this->OrderModel->sum('amount', ['user_id' => 5]);
Max:
<?php
$max = $this->ProductModel->max('price', ['category_id' => 3]);
Min:
<?php
$min = $this->ProductModel->min('price');
Average:
<?php
$avg = $this->ReviewModel->avg('rating', ['product_id' => 12]);
Grouping and Having
<?php
$this->UserModel
->group_by('role')
->having('COUNT(id)', '>', 5)
->query()
->get_all();
Scopes
Use scope() to apply a reusable query closure to the underlying database builder:
<?php
$users = $this->UserModel->scope(function($db) {
$db->where('verified', 1)->order_by('name', 'ASC');
})->query()->get_all();
Pagination
<?php
$page = $_GET['page'] ?? 1;
$result = $this->UserModel->paginate(
15, // per page
$page, // current page
['active' => 1] // optional conditions
);
// Result structure:
// [
// 'data' => [...], // rows for this page
// 'total' => 150, // total matching rows
// 'per_page' => 15,
// 'current_page' => 2,
// 'last_page' => 10,
// ]
Bulk Operations
Bulk insert:
<?php
$this->UserModel->bulk_insert([
['name' => 'Alice', 'email' => 'alice@example.com'],
['name' => 'Bob', 'email' => 'bob@example.com'],
['name' => 'Carol', 'email' => 'carol@example.com'],
]);
Bulk update:
<?php
$affected = $this->UserModel->bulk_update([
['id' => 1, 'role' => 'admin'],
['id' => 2, 'role' => 'editor'],
]);
// Returns: int (number of rows updated)
Empty a table:
<?php
$this->UserModel->truncate();
Soft Delete
LavaLust supports soft deletes, allowing you to mark a record as deleted without permanently removing it from the database. This is useful for recovering deleted records later.
Enabling Soft Delete
To enable soft deletes, add the $has_soft_delete property in your model:
<?php
class UserModel extends Model
{
protected $table = 'users';
protected $has_soft_delete = true; // Enable soft deletes
// Optional: customize the column name (default: deleted_at)
protected $soft_delete_column = 'removed_at';
}
Note
When $has_soft_delete is true, deleted records are not permanently removed.
Instead, a timestamp in the deleted_at column (or your custom column) marks the record as deleted.
All queries automatically exclude rows where this column is not NULL.
Note
If $has_soft_delete is false and you call soft_delete(), it will fall back to a
hard delete() automatically.
Soft Delete Operations
1. Soft delete a record:
<?php
$this->UserModel->soft_delete(5); // Sets deleted_at timestamp on user ID 5
2. Restore a soft-deleted record:
<?php
$this->UserModel->restore(5); // Nullifies deleted_at for user ID 5
3. Query including soft-deleted records:
<?php
// Pass true to any query method to include soft-deleted rows
$all = $this->UserModel->all(true);
$user = $this->UserModel->find(5, true);
4. Hard delete (bypasses soft delete):
<?php
$this->UserModel->delete(5);
Using Relationships
Models can define relationships with other models:
<?php
class PostModel extends Model
{
protected $table = 'posts';
protected $primary_key = 'id';
// A post has one author (FK: user_id on users table)
public function author(): array
{
return $this->has_one('UserModel', 'user_id');
}
// A post has many comments (FK: post_id on comments table)
public function comments(): array
{
return $this->has_many('CommentModel', 'post_id');
}
// A post belongs to a category (FK: category_id on posts table)
public function category(): array
{
return $this->belongs_to('CategoryModel', 'category_id');
}
// A post has many tags via pivot table
public function tags(): array
{
return $this->many_to_many('TagModel', 'post_tag', 'post_id', 'tag_id');
}
}
Available relationship types:
has_one($related, $foreign_key)— current model owns one related record.has_many($related, $foreign_key)— current model owns many related records.belongs_to($related, $foreign_key)— current model holds the foreign key.many_to_many($related, $pivot_table, $current_key, $related_key)— relationship via an intermediate pivot table.
Controller example using relationships:
<?php
class PostController extends Controller
{
public function __construct()
{
parent::__construct();
$this->call->model('PostModel');
}
public function showPost($id)
{
$post = $this->PostModel->find($id);
$author = $this->PostModel->author($id);
$comments = $this->PostModel->comments($id);
$this->call->view('post/show', [
'post' => $post,
'author' => $author,
'comments' => $comments
]);
}
}
Note
Relationships allow you to fetch related data cleanly without writing complex joins manually.
Eager Loading
Use with() before all() to automatically attach related records to every row, avoiding N+1 query problems:
<?php
// Load all posts with their author and comments attached
$posts = $this->PostModel
->with(['author', 'comments'])
->all();
// Each post now has $post['author'] and $post['comments']
foreach ($posts as $post) {
echo $post['title'] . ' by ' . $post['author']['name'];
}
Note
The $with array is automatically reset after each all() call, so eager-loading state
does not leak between requests.
Transactions
Wrap multiple write operations in a transaction to ensure atomicity:
<?php
$this->UserModel->transaction();
try {
$userId = $this->UserModel->insert([
'name' => 'Alice',
'email' => 'alice@example.com',
]);
$this->ProfileModel->insert([
'user_id' => $userId,
'bio' => 'Hello world',
]);
$this->UserModel->commit();
} catch (Exception $e) {
$this->UserModel->rollback();
// Handle error...
}
Loading Multiple Models
If you would like to load multiple models at a time you can use this:
<?php
$this->call->model(['model_1', 'model_2']);
$this->model_1->method();
$this->model_2->method();
// Or with aliases
$this->call->model(['model_1' => 'm1', 'model_2' => 'm2']);
$this->m1->method();
$this->m2->method();
Auto-loading Models
If you want certain models to always be available in your app (without manually loading them every time), you can auto-load them in the configuration:
Open
app/config/autoload.php.Add your model names in the
$autoload['models']array.
Example:
$autoload['models'] = ['UserModel', 'PostModel'];
These models will now be loaded automatically for every request.
Assigning Model to Different Name
If you would like your model assigned to a different object name you can specify it via the second parameter of the loading method:
<?php
$this->call->model('model_name', 'foobar');
$this->foobar->method();
// Or using array syntax
$this->call->model(['foobar' => 'model_name']);
$this->foobar->method();
Tips and Best Practices
Load models in the constructor if they are used across multiple methods.
Keep all database queries inside models — controllers should only coordinate data flow.
Always define
$fillableor$guarded— never rely on the open legacy mode in production.Validate all input data before inserting or updating records.
Use
with()for eager loading rather than calling relationship methods inside loops to avoid N+1 queries.Use model relationships to simplify fetching related data.
Prefer
soft_delete()overdelete()for any user-facing data that may need recovery.Wrap multi-step write operations in
transaction()/commit()/rollback().Use
paginate()instead ofall()on large tables to avoid loading thousands of rows into memory.Leverage built-in ORM features like
has_one,has_many,many_to_many, and soft deletes when available.