Routing system
Previously, index.php directly included the module file to process the action, based on the action and file request parameters.
With the new architecture, the resolution logic has been moved to the IndexRouter class, which handles action path resolution and processing.
Action resolution
Searched paths (in order)
When an action is requested, IndexRouter searches for the file in the following paths:
modules/{MODULE}/{ACTION}.phpmodules/{MODULE}/{ACTION}/{ACTION}.phpmodules/{MODULE}/controllers/{ACTION}.php(NEW)modules/VteCore/{ACTION}.phpmodules/VteCore/controllers/{ACTION}.php(NEW)
Removal of {MODULE}Ajax.php
It is no longer necessary to create a {MODULE}Ajax.php file in the module folder. AJAX requests can be handled directly by BaseAction classes, which automatically detect the request type and desired output format.
Execution process
After resolving and including the action file:
IndexRouterchecks if the file contains a class that extendsBaseAction- If found, it calls the
fromRequest()method to create an instance of the class - During
fromRequest(), class properties are automatically populated from request parameters processFromIndex()is called, which performs validation and processing- The result is formatted according to the requested content-type and sent to the client
If the file included in index.php does not contain a BaseAction class, then the included code is executed and entirely managed by the file (previous behavior).
Creating a new BaseAction
Basic structure
To create a new action, create a file in:
modules/{MODULE}/controllers/{ACTION}.php- or
modules/VteCore/controllers/{ACTION}.php(for global actions)
The class must:
- Extend
BaseAction - Implement the
process()method - Declare properties with appropriate attributes
<?php
class MyAction extends BaseAction {
protected function process() {
// Your logic here
return ['success' => true, 'data' => []];
}
}
Declaring properties
Class properties can be automatically populated from the request using PHP 8 attributes:
| Attribute | Source | Description |
|---|---|---|
RequestParam |
$_REQUEST | Parameter from request (GET or POST) |
RequestRawParam |
$_REQUEST | Raw parameter from request |
GetParam |
$_GET | Parameter from GET |
GetRawParam |
$_GET | Raw parameter from GET |
PostParam |
$_POST | Parameter from POST |
PostRawParam |
$_POST | Raw parameter from POST |
CookieParam |
$_COOKIE | Parameter from cookie |
Attribute parameters
- name: Parameter name in the request (default: property name)
- filter: Filter to apply (e.g.
F::int,F::str,F::bool). For more details, see Basic filters. - required: Whether the parameter is required (default: based on nullable type)
- default: Default value if the parameter is not present
- validation: Regex or callable to validate the value
- description: Parameter description (for documentation)
<?php
class MyAction extends BaseAction {
// Required parameter (not nullable)
#[RequestParam()]
protected int $record;
// Optional parameter (nullable)
#[RequestParam()]
protected ?string $searchText;
// Parameter with different name
#[RequestParam(name: 'return_module', filter: F::mod)]
protected ?string $returnModule;
// Parameter with regex validation
#[RequestParam(validation: '/^(true|false)$/')]
protected ?string $isDuplicate;
// Parameter with callable validation
#[RequestParam(validation: 'is_numeric')]
protected ?string $amount;
// Parameter with default value
#[RequestParam(default: 10)]
protected int $limit;
// Parameter from POST
#[PostParam()]
protected ?string $comment;
// Parameter from GET
#[GetParam()]
protected ?int $page;
protected function process() {
// Properties are already populated here
return [
'record' => $this->record,
'search' => $this->searchText,
];
}
}
Main methods
process()
Main method that contains the action logic. Must return data that will be formatted and sent to the client.
protected abstract function process();
validate(?string &$error): bool
Called before process() to validate the request. If it returns false, execution stops and an error is sent.
protected function validate(?string &$error): bool {
if ($this->record <= 0) {
$error = "Invalid record ID";
return false;
}
return true;
}
beforeProcess()
Called before process(), after validation.
protected function beforeProcess(): void {
// Initialization, logging, etc.
}
afterProcess(&$result)
Called after process(), allows modifying the result before sending.
protected function afterProcess(&$result): void {
// Modify result, logging, etc.
$result['timestamp'] = time();
}
Output
The output format is automatically determined based on:
- The class's
$outputFormatproperty (if set) - The
formator_formatparameter in the request - The
Acceptheader of the request - Whether the request is AJAX (default:
json) or not (default:html)
Supported formats
- json - JSON response (automatic for AJAX)
- html - HTML response (automatic for standard requests)
You can force a specific format by setting the $outputFormat property:
class MyAction extends BaseAction {
protected ?string $outputFormat = 'json';
protected function process() {
return ['data' => 'Always JSON'];
}
}
Or by using the setOutputFormat() method:
class MyAction extends BaseAction {
protected function beforeProcess(): void {
$this->setOutputFormat('html');
}
protected function process() {
return '<h1>HTML Output</h1>';
}
}
JSON response format
On success:
{
"success": true,
"result": { /* data returned by process() */ }
}
On error:
{
"success": false,
"error": {
"message": "Error message",
"code": 400
}
}
Requesting JSON from client
To request a JSON response, the client can:
- Add the parameter
?format=jsonor?_format=jsonto the URL - Set the header
Accept: application/json - Make an AJAX request (automatically detected)
Example
<?php
use RequestParam;
use PostParam;
class SaveData extends BaseAction {
// We don't specify outputFormat, it will be determined automatically
#[RequestParam()]
protected ?int $record;
#[PostParam(required: true)]
protected string $name;
#[PostParam(filter: F::int, default: 1)]
protected int $status;
#[PostParam()]
protected ?string $description;
protected function validate(?string &$error): bool {
if (strlen($this->name) < 3) {
$error = "Name must be at least 3 characters long";
return false;
}
return true;
}
protected function beforeProcess(): void {
// Log the operation
global $log;
$log->info("Saving record: " . $this->name);
}
protected function process() {
global $adb;
// Save logic
if ($this->record) {
// Update logic
} else {
// Insert logic
}
// Return data
// If the request is AJAX, it will be automatically formatted as JSON
// Otherwise as HTML
return [
'record_id' => $id,
'message' => 'Save completed'
];
}
protected function afterProcess(&$result): void {
// Add timestamp to result
$result['timestamp'] = time();
}
}
Differences from the previous version
| Aspect | Before | Now |
|---|---|---|
| Action resolution | In index.php | In IndexRouter.php |
| Ajax files | {MODULE}Ajax.php required |
No longer necessary |
| Controllers paths | Did not exist | modules/{MODULE}/controllers/modules/VteCore/controllers/ |
| Parameter population | Manual with $_REQUEST | Automatic with PHP attributes |
| Validation | Manual | validate() method + attribute validation |
| Output format | Manually handled | Automatic |
| Error handling | Manual | Automatic with try/catch in processFromIndex() |
Best Practices
- Create new actions as BaseAction classes in
modules/{MODULE}/controllers/ - Use attributes to declare parameters explicitly
- Always specify the appropriate filter (
F::int,F::str, etc.) - Implement the
validate()method for business logic validations - Use attribute validation for simple validations (regex, callable)
- Let the system automatically determine the output format (JSON/HTML)
- Force
$outputFormatonly when necessary - Use
beforeProcess()for initialization andafterProcess()for post-processing