Abstract class router
The router class manages the module pages. The choice of the class to display is linked to the action parameter. ?page=basemodule&action=my_custom_page will try to call the corresponding method.
The action parameter must contain only numbers 0-9, lowercase letters a-z and underscore. If action=My Custom-Page, it will be converted to mycustom_page.
namespace Modules\BaseModule;
!defined('MILK_DIR') && die(); // Avoid direct access
class BaseModuleController extends AbstractController
{
}
Using Action Attributes
The #[RequestAction] attribute is used to define which methods respond to specific URL actions. This provides a clean and declarative way to map URLs to methods.
Basic Action Usage
use App\Attributes\Action;
class BaseModuleController extends AbstractController
{
#[RequestAction('home')]
protected function list() {
Response::themePage('default', 'Home Page
');
}
#[RequestAction('edit')]
protected function edit() {
$id = _absint($_REQUEST['id'] ?? 0);
Response::themePage('default', 'Edit Record ' . $id . '
');
}
#[RequestAction('settings')]
protected function moduleSettings() {
Response::themePage('default', 'Module Settings
');
}
}
URL Mapping:
?page=basemoduleor?page=basemodule&action=home→ callslist()method?page=basemodule&action=edit→ callsedit()method?page=basemodule&action=settings→ callsmoduleSettings()method
Attribute Parameters
The #[RequestAction] attribute accepts the following parameters:
action: (string) The action name that will be matched in the URLurl: (string, optional) Custom URL pattern (defaults to action name)
#[RequestAction('custom-name', 'different-url')]
protected function myMethod() {
// This method responds to ?page=module&action=different-url
}
The home page
The method with #[RequestAction('home')] attribute is called by default when no action is specified in the URL.
#[RequestAction('home')]
protected function home() {
Response::themePage('default', ''.$this->title.'
');
}
AbstractController Overview
The AbstractController class is the foundation for all module controllers in the framework. It provides automatic routing, access control, and integration with the data layer through models. Controllers handle HTTP requests, coordinate with models to fetch/update data, and render views.
Key Features
- Attribute-Based Routing: Use
#[RequestAction]attributes to map URLs to methods - Access Control: Built-in permission checking with
#[AccessLevel]attributes - Model Integration: Automatic connection to module models
- Table Management: Helper methods for building dynamic tables with pagination, sorting, and filtering
- Action Handling: Automated handling of table actions (edit, delete, custom actions)
Public Methods Reference
| Method | Description | Returns | Example |
|---|---|---|---|
| Lifecycle & Initialization | |||
__construct() |
Constructor - sets up init hook | void | new MyController() |
hookInit() |
Called after framework initialization | void | // Auto-called by framework |
init() |
Override to load assets and initialize | void | Theme::set('javascript', $url) |
| Routing & Request Handling | |||
setHandleRoutes() |
Set routing variables from module | void | $router->setHandleRoutes($module) |
handleRoutes() |
Main route handler - calls action methods | void | // Auto-called by framework |
relatedSearchField() |
Handle AJAX search for related fields | void | // Auto-called for related fields |
| Access Control | |||
access() |
Check if user has module access | bool | if ($this->access()) { ... } |
| Data & Models | |||
getAdditionalModels() |
Get additional models registered | array|object|null | $this->getAdditionalModels('users') |
getCommonData() |
Get common module data (page, title) | array | $data = $this->getCommonData() |
| Table Management (Deprecated - Use TableBuilder) | |||
defaultRequestParams() |
Get default table request parameters | array | $defaults = $this->defaultRequestParams() |
getRequestParams() |
Get and sanitize table request params | array | $params = $this->getRequestParams($id) |
callTableAction() |
Handle table actions with token verification | void | $this->callTableAction($id, $actions) |
| Default Action Methods | |||
actionHome() |
Default home page action | void | // Override in child class |
Building a dynamic table
To build a table, create a method with the #[RequestAction] attribute and use the TableBuilder for simplified table creation.
use Builders\TableBuilder;
#[RequestAction('home')]
protected function list() {
$table_id = 'table_posts';
// Create table with fluent interface
$response = TableBuilder::create($this->model, $table_id)
->setDefaultActions() // Add Edit/Delete actions
->asLink('title', '?page=posts&action=edit&id=%id%') // Make title clickable
->limit(15) // Set pagination limit
->orderBy('created_at', 'desc') // Default sorting
->getResponse();
Response::render(__DIR__ . '/Views/list_page.php', $response);
}
Advanced TableBuilder Example
For more complex tables with custom columns, filters, and styling:
#[RequestAction('home')]
protected function list() {
$table_id = 'table_posts';
$response = TableBuilder::create($this->model, $table_id)
// Query customization
->where('status != ?', ['deleted'])
->orderBy('title', 'asc')
->limit(20)
// Column customization
->asLink('title', '?page=posts&action=edit&id=%id%')
->setLabel('created_at', 'Published Date')
->setType('status', 'select')
->setOptions('status', ['draft' => 'Draft', 'published' => 'Published'])
->hideColumn('updated_at')
// Add custom column with processing function
->column('actions_custom', 'Quick Actions', 'html', [], function($row, $key) {
return 'View';
})
// Filters
->filterEquals('status', 'status')
->filterLike('search', 'title')
// Actions
->setDefaultActions([
'duplicate' => [
'label' => 'Duplicate',
'action' => [$this, 'duplicatePost'],
'confirm' => 'Duplicate this post?'
]
])
// Styling
->tableColor('striped-primary')
->headerColor('primary')
->getResponse();
Response::render(__DIR__ . '/Views/list_page.php', $response);
}
public function duplicatePost($ids, $request) {
foreach ($ids as $id) {
$post = $this->model->getById($id);
if ($post) {
$post->title = $post->title . ' (Copy)';
$post->status = 'draft';
unset($post->id);
$this->model->store((array)$post);
}
}
return true;
}
TableBuilder Benefits
The TableBuilder provides several advantages over manual table creation:
- Fluent Interface: Chain methods for readable, maintainable code
- Automatic AJAX Support: Handles JSON responses automatically
- Built-in Actions: Easy setup for edit, delete, and custom actions
- Advanced Filtering: Simple filter setup with built-in search
- Styling System: Bootstrap-compatible color and class management
- Column Management: Hide, reorder, and customize columns easily
Adding js or css
protected function init() {
Theme::set('javascript', Route::url().'/Modules/base-module/assets/base-module.js');
}
Adding footer and header graphics
$modellist_data['page_info']['footer'] = true;
$modellist_data['table_attrs'] = ['tfoot' => ['class' => 'table-footer-gray'], 'tfoot.td.title' => ['class' => 'text-end']];
$modellist_data['rows'][] = (object)['title' => 'Footer'];
// modify the graphics of the head
$modellist_data['table_attrs'] = ['thead' => ['class' => 'table-header-yellow'], 'th.title' => ['class' => 'th-title']];
Adding a link for an action
$modellist_data['row_info']['action'] = ['type' => 'action', 'label' => 'Action', 'options' => [$table_id.'-view' => 'View']];
Now you need to add the javascript to handle the action.
Here's an ajax example to show an offcanvas with record details.
registerHook('table-action-{$table_id-view}', function (id, sendform) {
window.offcanvasEnd.show()
window.offcanvasEnd.loading_show()
fetch(milk_url, {
method: 'POST',
credentials: 'same-origin',
body: getFormData('?id=' + id + '&action=single_view&page=dynamic_table_example')
}).then((response) => {
window.offcanvasEnd.loading_hide()
return response.json()
}).then((data) => {
window.offcanvasEnd.body(data.html)
window.offcanvasEnd.title(data.title)
})
// does not update the table
return false;
})
Or an example of a link to go to the detail page. In the javascript insert:
registerHook('table-action-{$table_id-view}', function (id, sendform) {
window.location.href = milk_url + '?page=basemodule&action=single_view&id=' + id + '';
return false;
})
In the router insert:
#[RequestAction('single_view')]
public function singleView() {
Response::themePage('default', __DIR__ . '/Views/edit.page.php', ['id' => _absint($_REQUEST['id'] ?? 0)]);
}
Create the edit.page.php file in the module's views folder.
Removing checkboxes
unset($modellist_data['info']['checkbox']);
Adding free text search
<div class="my-4 row">
<div class="col">
<input class="form-control ms-2 d-inline-block" type="search" placeholder="Search" aria-label="Search" spellcheck="false" data-ms-editor="true" id="table_id_search" style="width:200px">
<span class="btn btn-outline-primary" onClick="tableIdSearch()">Search</span>
</div>
</div>
The javascript for search:
function tableIdSearch() {
var comp_table = getComponent('table_id');
if (comp_table == null) return;
let val = document.getElementById('table_id_search').value;
comp_table.filter_remove_start('search:');
if (val != '') {
comp_table.filter_add('search:' + val);
}
comp_table.setPage(1);
comp_table.reload();
}
The code in this case is already preconfigured to search in all columns, but assuming we want to search in a single column inside the model we should write a filter_{filter_name} function
public function filterSearch($search) {
$query = $this->getCurrentQuery();
$query->where('`title` LIKE ? ', ['%'.$search.'%']);
}
It is possible not to use a filter_{filter_name} function, but to set a custom function using the add_filter function
$this->addFilter('search', [$this, 'filtersearch']);
AbstractController Abstract Class Documentation
The AbstractController class is the base class for managing module routing in Ito. This class handles module actions and provides methods for managing queries and data. This document describes in detail all public methods and their specifications.
Main Properties
$page: (string) The module name.$access: (string) The module access level (public,registered,authorized,admin).$title: (string) The module title.$model: (string|object) The name or instance of the associated model class.
Public Methods
__construct()
Class constructor. Registers the init hook
// Usage example in a child class:
class PostsController extends \App\Abstract\AbstractController {
public function init() {
Theme::set('javascript', Route::url().'/Modules/posts/assets/posts.js');
}
}
new PostsController();
- Input parameters:
- None
- Return value:
- None
init()
Function to override for loading functions, javascript, css or initializing anything.
/**
* This method must be overridden to initialize the router.
* This method is called by the 'init' hook.
* @return void
*/
public function init();
// Usage example in a child class:
class PostsController extends \App\Abstract\AbstractController {
public function init() {
Theme::set('javascript', Route::url().'/Modules/Posts/Assets/posts.js');
Theme::set('styles', Route::url().'/Modules/Posts/Assets/posts.css');
}
}
- Input parameters:
- None
- Return value:
void: This method does not return any value.
setHandleRoutes($module)
Sets the variables for handling page routing, and creates the route that points to the handle_routes method
/**
* @param object $module The module object
* @return void
*/
public function setHandleRoutes($module);
// Example in the module class is called:
$this->router->setHandleRoutes($this);
- Input parameters:
$module: (object) The module object.
- Return value:
void: This method does not return any value.
handleRoutes()
Handles requests to the page. Based on the action parameter, it calls the specific method with the corresponding #[RequestAction] attribute.
/**
* @return void
*/
public function handleRoutes();
// Usage example in the child class:
class PostsController extends \App\Abstract\AbstractController {
public function handleRoutes() {
if (!$this->access()) {
Route::redirect('?page=deny');
return;
}
Theme::set('header.title', Theme::get('site.title')." - ". $this->title);
// The parent class automatically handles routing to methods
// with #[RequestAction] attributes based on the 'action' parameter
parent::handleRoutes();
}
}
- Input parameters:
- None
- Return value:
void: This method does not return any value.
Action Methods with Attributes
Methods with #[RequestAction] attributes handle specific URL actions. The method with #[RequestAction('home')] is called when no action parameter is passed.
use App\Attributes\RequestAction;
// Usage example in a child class:
class PostsController extends \App\Abstract\AbstractController {
#[RequestAction('home')]
protected function home() {
Response::themePage('default', ''.$this->title.'
');
}
#[RequestAction('edit')]
protected function edit() {
$id = _absint($_REQUEST['id'] ?? 0);
Response::themePage('default', 'Edit Post ' . $id . '
');
}
}
- Input parameters:
- None
- Return value:
void: This method does not return any value.
access()
Checks if the user has permissions to access the module based on the $access property
/**
* @return bool
*/
protected function access(): bool;
// Usage example in the child class
if ($this->access()) {
// the user has permissions to access
} else {
Route::redirect('?page=deny');
}
- Input parameters:
- None
- Return value:
bool: Returnstrueif the user has permissions,falseotherwise.
defaultRequestParams()
Returns the default parameters for a table request.
/**
* @return array
*/
protected function defaultRequestParams(): array;
// Usage example in a child class
protected function getRequestParams($table_id) {
$default = $this->defaultRequestParams();
return $default;
}
- Input parameters:
- None
- Return value:
array: An associative array with default parameters (order_field, order_dir and limit).
getRequestParams($table_id)
Retrieves and sanitizes table parameters from the request, also adds default parameters
/**
* @param string $table_id The table id
* @return array Returns the sanitized parameters
*/
protected function getRequestParams($table_id): array;
// Usage example in a child class
#[RequestAction('home')]
protected function home() {
$table_id = 'table_posts';
$request = $this->getRequestParams($table_id);
//...
}
- Input parameters:
$table_id: (string) The unique table identifier.
- Return value:
array: An associative array containing the table request parameters (order_field,order_dir,limit,page).
getModellistData($table_id, $fn_filter_applier, $fn_query_applier)
Retrieves the data and structure of the table for display. You can see an example of how to use it in Dynamic Table > Add query filters
/**
* @param string $table_id The table id
* @param callable $fn_filter_applier A callback function to apply filters to the modellist
* @param callable $fn_query_applier A callback function to modify the query
* @return array
*/
protected function getModellistData($table_id, $fn_filter_applier, $fn_query_applier): array;
// Usage example in a child class
#[RequestAction('home')]
protected function home() {
$table_id = 'table_posts';
$modellist_data = $this->getModellistData($table_id, $fn_filter_applier, $fn_query_applier);
$outputType = Response::isJson() ? 'json' : 'html';
$table_html = Get::themePlugin('table', $modellist_data);
$theme_path = realpath(__DIR__.'/Views/list.page.php');
if ($outputType === 'json') {
Response::json([
'html' => $table_html,
'success' => !MessagesHandler::hasErrors(),
'msg' => MessagesHandler::errorsToString()
]);
} else {
Response::themePage('default', $theme_path, [
'table_html' => $table_html,
'table_id' => $table_id,
'page' => $this->page
]);
}
}
- Input parameters:
$table_id: (string) The unique table identifier.$fn_filter_applier: (callable) A callback function to apply filters to the modellist.$fn_query_applier: (callable) A callback function to modify the query.
- Return value:
array: Array containing the data to display (rows,info,page_info).
callTableAction($table_id, $action, $function)
Handles table group actions, such as deleting multiple records simultaneously
/**
* @param string $table_id The table id
* @param string $action The action to execute
* @param string $function The function to call
* @return void
*/
protected function callTableAction($table_id, $action, $function);
// Usage example in a child class
#[RequestAction('home')]
protected function home() {
$table_id = 'table_posts';
$request = $this->getRequestParams($table_id);
$this->callTableAction($table_id, 'delete', 'table_action_delete');
// ...
}
// Method to call dynamically
protected function tableActionDelete($id, $request) {
$this->model->delete($id);
return true;
}
- Input parameters:
$table_id: (string) The table identifier.$action: (string) The action to execute (eg: "delete").$function: (string) The name of the function that must be called dynamically.
- Return value:
void: This method does not return any value.
Dynamic Calls
The following methods make dynamic calls to functions defined in the child class:
handleRoutes(): Calls the method with the corresponding #[RequestAction] attribute for the requested action.callTableAction(): Dynamically calls the$functionfunction passed as a parameter.