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.
Model Structure
Models are typically located in the app/controllers/user/model directory. A basic model
structure
might look like this:
<?php
namespace Model;
class User extends \Fuwafuwa\BaseModel {
function __construct(\Fuwafuwa\Db $db) {
parent::__construct($db, 'user', ['ai_field' => 'login', 'created_field' => 'created', 'modified_field' => 'updated', 'deleted_field' => 'deleted', ]);
// https://github.com/rakit/validation
// $this->validation = [
// 'rules' => [
// ],
// 'custom_message' => [
// ]
// ];
// $this->preProcess = function(array $data): array {
// // do something to data before insert/update
// return $data;
// };
// $this->preCreate = function(array $self, array $pkeys): void { };
// $this->preUpdate = function(array $self, array $pkeys): void { };
// $this->preDelete = function(array $self, array $pkeys): void { };
// $this->postCreate = function(array $self, array $pkeys): void { };
// $this->postUpdate = function(array $self, array $pkeys): void { };
// $this->postDelete = function(array $self, array $pkeys): void { };
// $this->postView = function(array $data): array {
// // do something to SQL, usually for increase view count
// return $data;
// };
}
}
Fuwafuwa Framework simplifies model creation through a code generator. By running the following command from the root directory:
php index.php fuwafuwa/generator/model --table=user > app/views/user/model/user.php
Model Configuration
The provided code snippet showcases the User model class and its configuration options passed to the
parent constructor (\Fuwafuwa\BaseModel):
$dbAn instance of the database connection.$tableThe name of the database table the model represents (user in this case).$optionsAn associative array containing additional configuration optionsai_field(Optional) Specifies the auto-increment field name (if applicable). Setting this eliminates the need for the keys option.created_field(Optional) When set, the framework automatically populates this field with the current timestamp during record insertion.modified_field(Optional) If defined, the framework updates this field with the current timestamp whenever a record is modified.deleted_field(Optional) With this option enabled, the framework won't permanently delete records. Instead, it sets the deleted_field value to the current timestamp upon deletion.keys(Optional) An array defining table key(s) if the table doesn't have an auto-increment field or uses compound keys.
$fields(Optional) comma separated, fields that will be used for operation. For example, you don't want to includepasswordinusermodel, so that the password will not accidentally changed with request data.
Additional Considerations
The code also demonstrates commented-out sections for:
- Validation rules: Utilizing a library like Rakit/Validation, you can define validation rules to ensure data integrity.
- Data pre-processing: The preProcess method allows you to manipulate data before insertion or update.
- Custom hooks: Pre and post hooks can be implemented for various events like creation, update, deletion, and viewing to perform custom actions. By understanding these configuration options and hooks, developers can tailor models to meet specific application requirements.
Rejecting & Accepting Request Values
For security reasons, Fuwafuwa Framework allows you to control which request values are accepted or rejected by the model. You can achieve this during model construction using one of the following approaches:
$this->setConfig('except_fields', ["token", "push_token"]); // Reject the "token" and "push_token" value from the request.
$this->only_fields = "login,password"; // Only accept "login" and "password" values from the request.
Validation
Fuwafuwa Framework models leverage Rakit Validation library (https://github.com/rakit/validation) to provide built-in validation features. Here's an example:
$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',
]
];
You might be wondering why the User table doesn't have password1 and
password2 fields. These fields
are temporary and serve the purpose of validating the password during user input. Later, in the
preProcess method, the actual password value is hashed and stored securely.
Additionally, the rule
for password1 utilizes a function to determine if the operation is an insertion (where
the password
is required) or an update (where the password is optional). This allows you to display a message
like "Leave blank if not changing" near the password input field.
PreProcess
The preProcess method is a callback function that executes every time the model receives data, right before performing an insert or update operation.
$this->preProcess = function (array $data): array {
if ($data['password1']) {
$data['password'] = password_hash($data['password1'], PASSWORD_BCRYPT);
}
$data['fullname'] = trim($data['fullname']);
return $data;
};
In the provided example, the preProcess function checks if the password1 field has a value. If so, it hashes the password using password_hash and stores it in the password field. Additionally, it trims any leading or trailing whitespace characters from the fullname field before saving.
Pre and Post Events
Fuwafuwa Framework provides seven events (pre and post) for each CRUD (Create, Read, Update, Delete) operation on a table. There's also a special "view" event typically used to increment post view counts. Here's an example of setting a token for a newly created user using the preCreate event:
$this->preCreate = function(array $self, array $pkeys): void {
$self['token'] = md5((string) time());
};
Working with Data
Once you've defined your model with its configuration, you can start using it within your controllers to interact with the database.
$user = m('\User\Model');
Here, m() is a utility function that leverages Dependency Injection (DI) to retrieve an
instance of
the \Fuwafuwa\BaseModel class for the specified model. As you can see, we don't need to
explicitly
provide a database instance. This is because the model's constructor accepts the database connection
information during initialization, and the DI container automatically resolves the required
dependencies.
Fuwafuwa Framework utilizes the Dice library (https://github.com/Level-2/Dice) to manage dependencies within the application.
$user->retrieve(['user']);
if($user->dry()) {
// no record
$user->copyfrom('GET');
$user->created = gmdate('Y-m-d');
} else {
// has record
$user->copyfrom('GET');
$user->updated = gmdate('Y-m-d');
}
$user->save();
In the provided code example, we perform an insert or update operation on the user table using the key 'user'. Here's a breakdown of the steps:
-
$user->retrieve(['user']): This line attempts to retrieve a record from the user table using the key 'user'. -
if($user->dry()): Thedry()method checks if the retrieval resulted in an empty recordset.-
// no record: If no record is found, the code assumes it's a new record insertion scenario.$user->copyfrom('GET'): This copies data from the GET request parameters to the model's properties.$user->created = gmdate('Y-m-d'): Sets the created field to the current date in GMT format.
$user->copyfrom('GET'): Similar to the insertion case, this copies data from the GET request.$user->updated = gmdate('Y-m-d'): Sets the updated field to the current date in GMT format.
// has record: If a record is found, the code assumes it's an update scenario. -
-
$user->save(): Finally, the save() method is called to persist the data (either insert or update) to the database.