BaseAction
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}.php
modules/{MODULE}/{ACTION}/{ACTION}.php
modules/{MODULE}/controllers/{ACTION}.php (NEW)
modules/VteCore/{ACTION}.php
modules/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:
IndexRouter checks if the file contains a class that extends BaseAction
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:
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:
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
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:
$outputFormat property (if set)
The format or _format parameter in the request
The Accept header of the request
Whether the request is AJAX (default: json) or not (default: html)
Supported formats
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:
?format=json or ?_format=json to 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();
}
}
Summary of differences
{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
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 $outputFormat only when necessaryUse
beforeProcess() for initialization and afterProcess() for post-processing