Milk Admin

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=basemodule or ?page=basemodule&action=home → calls list() method
  • ?page=basemodule&action=edit → calls edit() method
  • ?page=basemodule&action=settings → calls moduleSettings() method
Attribute Parameters

The #[RequestAction] attribute accepts the following parameters:

  • action: (string) The action name that will be matched in the URL
  • url: (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

📋 Quick Reference: This table provides a complete overview of all available methods in AbstractController. Click on a method name to jump to its detailed documentation.
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: Returns true if the user has permissions, false otherwise.

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 $function function passed as a parameter.
Loading...