Models
Fuwafuwa Framework provides a robust Model layer to simplify database interactions. Models offer a convenient interface for common database operations, such as retrieving, updating, deleting, and creating records.
Models are the heart of your application's data management. They encapsulate all logic related to data storage, retrieval, and manipulation, providing a clean and consistent interface for working with your database. In Fuwafuwa, models are designed to be intuitive and powerful, abstracting away the complexity of raw SQL queries while still providing full control when needed.
Whether you're building a simple blog or a complex e-commerce platform, the Model layer in Fuwafuwa will save you time and effort by providing a set of tools for common database operations. From basic CRUD (Create, Read, Update, Delete) operations to advanced query building and validation, Fuwafuwa's models have you covered.
Creating a Model
Creating a model in Fuwafuwa is simple and straightforward. Models are typically located in the app/controllers/user/model directory, where they're easily accessible by your controllers.
Each model in Fuwafuwa represents a database table and encapsulates all the logic for working with that table. By extending the \Fuwafuwa\BaseModel class, your models inherit a rich set of methods for performing common database operations.
A basic model structure looks like this:
<?php
namespace Model;
class User extends \Fuwafuwa\BaseModel {
function __construct(\Fuwafuwa\Db $db) {
parent::__construct($db, 'user', [
'ai_field' => 'id',
'primary_keys' => 'id',
'created_field' => 'created',
'modified_field' => 'updated',
'deleted_field' => 'deleted',
'except_fields' => ['token', 'push_token'],
]);
// Validation rules (using Rakit Validation)
// $this->validation = [
// 'rules' => [],
// 'custom_message' => []
// ];
// Data preprocessing
// $this->preProcess = function(array $data): array {
// return $data;
// };
// Lifecycle hooks
// $this->preCreate = function(array $self, array $pkeys): void { };
// $this->postCreate = function(array $self, array $pkeys): void { };
}
}
Fuwafuwa Framework simplifies model creation through the CLI code generator:
php index.php cli/codegenerator/model --name=User --table=users > app/controllers/user/model/user.php
See Code Generator documentation for more options including validation rules.
Model Configuration
The model constructor in Fuwafuwa accepts configuration options that are converted to a type-safe ModelConfiguration value object. This provides a flexible way to define table structure, field behavior, and other important settings with IDE autocompletion support.
By configuring your model properly, you can automate common tasks such as timestamp management, soft deletes, and field filtering, which simplifies your code and reduces the chance of errors.
The constructor accepts these configuration options:
| Option | Type | Description |
|---|---|---|
ai_field |
string | Auto-increment field name. If set and no primary_keys specified, becomes the primary key. |
primary_keys |
string | Comma-separated primary key field names. Required unless ai_field is set. |
created_field |
string | Automatically populated with current timestamp on insert. |
modified_field |
string | Automatically updated with current timestamp on modification. |
deleted_field |
string | Enables soft deletes. Sets timestamp instead of permanently deleting. |
except_fields |
array | Fields to exclude from save operations (e.g., ['token', 'password']). |
only_fields |
array | If set, only these fields will be saved. Takes precedence over except_fields. |
cache_ttl |
int | Default cache TTL in seconds (default: 60). |
Accessing Configuration
You can access the model's configuration at runtime:
$config = $user->getConfiguration();
// Access configuration properties
$aiField = $config->aiField; // 'id'
$primaryKeys = $config->primaryKeys; // 'id'
$tableName = $user->getTableName(); // 'user'
Field Filtering
For security, control which request values are accepted by configuring field filtering in the constructor:
// Exclude specific fields from save operations
parent::__construct($db, 'user', [
'primary_keys' => 'id',
'except_fields' => ['token', 'push_token', 'secret_key'],
]);
// Or only allow specific fields
parent::__construct($db, 'user', [
'primary_keys' => 'id',
'only_fields' => ['name', 'email', 'role'],
]);
The old setConfig() method for field filtering has been removed. Use constructor options instead.
Model Methods
BaseModel provides these utility methods for working with model data:
| Method | Description |
|---|---|
getConfiguration() | Returns the ModelConfiguration value object |
getPrimaryKeyFields() | Returns array of primary key field names |
getPrimaryKeyValues() | Returns array of current primary key values |
getLastInsertId() | Returns the last auto-increment ID |
getTableName() | Returns the full table name with prefix |
retrieve($keyValues) | Load record by primary key(s), returns null if not found |
retrieveOrFail($keyValues) | Load record by primary key(s), throws exception if not found |
copyExcept($fields, $data) | Copy data excluding specified fields |
copyOnly($fields, $data) | Copy only specified fields from data |
saveFromData($data) | Save from array or HIVE key, respecting field filters |
saveFromRequest() | Save from REQUEST, respecting field filters |
paginateRecords($pos, $size, ...) | Returns PaginationResult with subset, total, pages |
getByField($field, $value, $limit) | Get records by field value |
getCodeValuePairs($code, $value, ...) | Get array for dropdown options |
isValueUnique($field, $value) | Check if a field value is unique |
Retrieving Records
$user = m(\Model\User::class);
// Retrieve by primary key(s)
$user->retrieve([123]);
if (!$user->dry()) {
echo $user->name;
}
// Retrieve or throw exception
$user = m(\Model\User::class);
$user->retrieveOrFail([123]); // Throws ModelNotFoundException if not found
// Get primary key information
$keys = $user->getPrimaryKeyFields(); // ['id']
$values = $user->getPrimaryKeyValues(); // [123]
Pagination
$user = m(\Model\User::class);
$result = $user->paginateRecords(
pos: 0, // Current page (0-indexed)
size: 20, // Items per page
queryFields: '*', // Fields to select
order: 'name ASC',
condition: 'role = "admin"'
);
// Access pagination results
$users = $result->records; // Array of user data
$total = $result->total; // Total record count
$pages = $result->count; // Total pages
$current = $result->pos; // Current page
Validation
Data validation is a crucial aspect of any web application, ensuring that the data entered by users meets specific requirements before it's stored in the database. Fuwafuwa makes this process easy and efficient with built-in support for Rakit Validation, a powerful and flexible validation library for PHP.
With Rakit Validation, you can define complex validation rules in a simple and readable format. The library provides a wide range of built-in validation rules, from basic checks like "required" and "email" to more advanced rules like "unique" (checking if a value exists in a database table) and "same" (checking if two fields match).
Here's how you define validation rules in your Fuwafuwa model:
$this->validation = [
'rules' => [
'login' => 'required|alpha_num|unique:login',
'fullname' => 'required',
'password1' => fn (string $action, array $data): string =>
preg_match(static::REG_ADD, $action)
? 'required|min:6'
: 'nullable|min:6',
'password2' => 'present|same:password1',
'role' => 'required',
]
];
The password1 and password2 fields are virtual (not in the database). They're used for password validation during input, then hashed in preProcess before storage.
PreProcess
The preProcess callback is a powerful feature in Fuwafuwa models that allows you to modify and prepare data before it's inserted or updated in the database. This is particularly useful for tasks like data sanitization, password hashing, or modifying field values based on specific conditions.
PreProcess callbacks are executed automatically before every save operation (both insert and update), ensuring that your data is always properly formatted and validated before it reaches the database. This helps maintain data consistency and reduces the chance of errors.
Here's an example of how you might use the preProcess callback:
$this->preProcess = function (array $data): array {
if ($data['password1']) {
$data['password'] = password_hash($data['password1'], PASSWORD_BCRYPT);
}
$data['fullname'] = trim($data['fullname']);
return $data;
};
Lifecycle Events
Lifecycle events are another powerful feature of Fuwafuwa models. They allow you to attach custom code to specific moments in a record's lifecycle, such as before it's created, after it's updated, or before it's deleted. This provides a clean and modular way to add behavior to your models.
These events are particularly useful for tasks like sending notifications, logging activities, clearing caches, or performing additional database operations when a record is modified. By using lifecycle events, you can keep your code organized and maintainable.
Fuwafuwa provides seven events for each CRUD operation, plus a special postView event:
| Event | Timing | Use Case |
|---|---|---|
preCreate | Before insert | Set default values, generate tokens |
postCreate | After insert | Send notifications, log activity |
preUpdate | Before update | Validate changes, modify data |
postUpdate | After update | Clear cache, notify users |
preDelete | Before delete | Check dependencies, backup data |
postDelete | After delete | Clean up related files |
postView | After load | Increment view counts, log access |
Example: Setting a Token
$this->preCreate = function(array $self, array $pkeys): void {
$self['token'] = md5((string) time());
};
Working with Data
Once you've created your model, working with data in Fuwafuwa is simple and intuitive. The framework provides a set of helper functions and methods that make it easy to perform common database operations.
One of the key features of Fuwafuwa models is that they are managed through the dependency injection container, which simplifies instantiation and makes your code more testable.
Use the m() helper function to get a model instance via Dependency Injection:
$user = m(\Model\User::class);
Insert or Update Pattern
$user = m(\Model\User::class);
$user->retrieve([$userId]);
if($user->dry()) {
// No record - create new
$user->copyfrom('GET');
$user->created = gmdate('Y-m-d');
} else {
// Has record - update
$user->copyfrom('GET');
$user->updated = gmdate('Y-m-d');
}
$user->save();
Key Methods
| Method | Description |
|---|---|
retrieve($keys) | Load record by key(s) |
dry() | Check if no record was loaded |
copyfrom($source) | Copy data from request or array |
save() | Insert or update record |
erase() | Delete record (or soft delete) |
loaded() | Check if record was loaded |
Fuwafuwa uses the Dice library to automatically resolve dependencies. Use the c() utility function to get service instances:
// Get a service instance
$queueManager = c(QueueManager::class);
$user = c('\Model\User');
Migration Notes
Recent versions of Fuwafuwa Framework have modernized the BaseModel API. If you're working with older code, here are the key changes:
| Old Way (Deprecated) | New Way |
|---|---|
$model->getConfig('ai_field') |
$model->getConfiguration()->aiField |
$model->setConfig('except_fields', [...]) |
Pass in constructor opts |
$model->pk() |
$model->getPrimaryKeyFields() |
$model->pkValues() |
$model->getPrimaryKeyValues() |
$model->lastInsertId() |
$model->getLastInsertId() |
$model->unique($field, $value) |
$model->isValueUnique($field, $value) |
$model->tableName() |
$model->getTableName() |
$model->page($pos, $size, ...) |
$model->paginateRecords($pos, $size, ...) |