Building Modules: Controllers vs Utilities
One of Trongate v2's most powerful features is its ability to intelligently handle both framework-dependent controllers and standalone utility classes. This page explains when to use each pattern and how the framework automatically adapts.
The Two Types of Modules
In Trongate v2, modules fall into two categories:
Controllers (Extend Trongate)
Controllers are modules that need framework features like database access, view rendering, or loading other modules.
<?php
class Products extends Trongate {
public function index() {
$data = $this->model->get_all(); // Database access
$this->view('list', $data); // View rendering
$this->module('auth'); // Module loading
}
}
Utilities (Standalone Classes)
Utilities are self-contained classes that perform specific tasks without needing any framework features.
<?php
class Image { // No extends Trongate
public function resize($width, $height) {
// Pure image processing using GD library
// No database, no views, no framework dependencies
}
}
When to Extend Trongate
Your module should extend Trongate if it needs any of these features:
- Database operations -
$this->model
- View rendering -
$this->view()
- Loading other modules -
$this->module('name') or $this->modulename
- Module name tracking -
$this->module_name
Common Controller Examples
- Products - manages product data, displays views
- Users - handles user accounts, authentication
- Orders - processes orders, generates invoices
- Admin - dashboard, reports, management interface
- Api - endpoints that query databases and return JSON
<?php
class Users extends Trongate {
public function create() {
// Uses validation (framework feature)
$this->validation->set_rules('email', 'Email', 'required|valid_email');
if ($this->validation->run()) {
// Uses model (framework feature)
$data = $this->_get_data_from_post();
$this->model->insert($data);
redirect('users/manage');
} else {
// Uses view rendering (framework feature)
$this->view('create_form');
}
}
}
When NOT to Extend Trongate
Create a standalone utility class if your module:
- Performs pure computation - math, string manipulation, data formatting
- Wraps external libraries - image processing (GD), PDFs, email
- Has no database needs - doesn't read or write data
- Has no view rendering - doesn't display HTML
- Could be used outside Trongate - framework-agnostic functionality
Common Utility Examples
- Image - resize, crop, manipulate images with GD
- Pdf - generate or manipulate PDF documents
- Calculator - perform complex calculations
- Converter - convert between units, formats, currencies
- Validator - custom validation logic (credit cards, phone numbers)
- Encryptor - encryption/decryption utilities
<?php
class Calculator { // Standalone utility - no extends
public function compound_interest($principal, $rate, $time) {
return $principal * pow((1 + $rate / 100), $time);
}
public function mortgage_payment($principal, $rate, $years) {
$monthly_rate = $rate / 100 / 12;
$num_payments = $years * 12;
return $principal * ($monthly_rate * pow(1 + $monthly_rate, $num_payments))
/ (pow(1 + $monthly_rate, $num_payments) - 1);
}
}
How Trongate Handles Both Types
Here's the brilliant part: you can use both types of modules exactly the same way in your controllers.
Using a Controller Module
<?php
class Dashboard extends Trongate {
public function index() {
// Load the 'users' controller module (extends Trongate)
$this->module('users');
$user_count = $this->users->get_count();
}
}
Using a Utility Module
<?php
class Products extends Trongate {
public function upload_image() {
// Load the 'image' utility (does NOT extend Trongate)
$config = ['max_width' => 800];
$file_info = $this->image->upload($config);
}
}
Notice: The syntax is identical! You don't need to know or care whether a module extends Trongate or not.
The Framework's Intelligence
When you access $this->image, here's what happens behind the scenes:
// Inside Trongate base class:
protected function module(string $target_module): void {
// Load the module file
require_once $controller_path;
// Check: Does this class extend Trongate?
if (is_subclass_of($controller_class, 'Trongate')) {
// Yes - pass module name to constructor
$module_instance = new $controller_class($target_module);
} else {
// No - it's a standalone utility
$module_instance = new $controller_class();
}
// Cache it for future use
$this->loaded_modules[$target_module] = $module_instance;
}
The framework automatically detects whether a module extends Trongate and instantiates it correctly.
Zero Configuration: You don't register modules as "controllers" or "utilities". The framework figures it out automatically by checking the class inheritance.
Real-World Example: Image Utility
Let's look at a complete example of a standalone utility class:
<?php
/**
* Image manipulation utility
* Standalone class - no framework dependencies
*/
class Image {
private $image;
private $image_type;
public function __construct(?string $filename = null) {
// Block direct URL access
block_url('image');
// Check for GD library
if (!extension_loaded('gd')) {
die('GD library required');
}
// Optionally load an image
if ($filename) {
$this->load($filename);
}
}
public function load($filename) {
$image_info = getimagesize($filename);
$this->image_type = $image_info[2];
switch($this->image_type) {
case IMAGETYPE_JPEG:
$this->image = imagecreatefromjpeg($filename);
break;
case IMAGETYPE_PNG:
$this->image = imagecreatefrompng($filename);
break;
// ... other formats
}
}
public function resize($width, $height) {
$new_image = imagecreatetruecolor($width, $height);
imagecopyresampled(
$new_image, $this->image,
0, 0, 0, 0,
$width, $height,
imagesx($this->image), imagesy($this->image)
);
$this->image = $new_image;
}
public function save($filename, $quality = 90) {
switch($this->image_type) {
case IMAGETYPE_JPEG:
imagejpeg($this->image, $filename, $quality);
break;
case IMAGETYPE_PNG:
imagepng($this->image, $filename);
break;
}
}
}
Notice what this class doesn't have:
- No
extends Trongate
- No
$this->model usage
- No
$this->view() calls
- No framework dependencies at all
It's pure functionality. And it works perfectly when loaded via $this->image in any controller.
Using the Image Utility in a Controller
<?php
class Products extends Trongate {
public function upload_product_image() {
// Configure upload
$config = [
'destination' => 'uploads/products',
'max_width' => 800,
'max_height' => 800
];
// Upload and resize automatically
$file_info = $this->image->upload($config);
// Save to database
$data = [
'product_id' => segment(3),
'image_path' => $file_info['file_path']
];
$this->model->insert($data, 'product_images');
set_flashdata('Image uploaded successfully!');
redirect('products/manage');
}
}
See how natural it is? The Image utility handles image processing, while the Products controller handles business logic, database, and views.
Constructor Differences
Controllers (Extending Trongate)
<?php
class Products extends Trongate {
// If you need a constructor:
public function __construct(?string $module_name = null) {
parent::__construct($module_name); // REQUIRED!
// Your initialization
}
// Most controllers don't need a constructor at all
}
Utilities (Standalone)
<?php
class Image {
// Constructor accepts whatever parameters make sense
public function __construct(?string $filename = null) {
// No parent::__construct() needed
// No module_name parameter needed
// Just your logic
}
}
Important: Standalone utilities should NOT accept $module_name as a parameter. The framework instantiates them without parameters.
Performance Benefits of Standalone Utilities
Creating standalone utilities has real performance advantages:
Lower Memory Usage
Standalone classes don't carry the framework's base class overhead:
- No
$instances cache array
- No
$loaded_modules cache array
- No
$module_name, $parent_module, $child_module properties
Faster Instantiation
The framework doesn't need to:
- Call
parent::__construct()
- Set module name properties
- Initialize framework features
Framework-Agnostic
Standalone utilities can be:
- Reused in other projects
- Tested independently
- Shared as libraries
- Used outside of Trongate entirely
Speed Priority: For Trongate v2's goal of being the fastest PHP framework, creating standalone utilities for pure functionality eliminates unnecessary overhead.
Migration Decision Tree
When creating a new module, ask yourself:
Does this module need to:
├─ Query the database?
│ └─ YES → Extend Trongate
│
├─ Render views?
│ └─ YES → Extend Trongate
│
├─ Load other modules?
│ └─ YES → Extend Trongate
│
└─ Just perform calculations/processing?
└─ NO to all above → Standalone utility
Converting Between Types
You can easily convert between controller and utility if you change your mind:
From Controller to Utility
// Before: Controller
<?php
class Calculator extends Trongate {
public function __construct(?string $module_name = null) {
parent::__construct($module_name);
}
public function add($a, $b) {
return $a + $b;
}
}
// After: Utility (remove extends, remove constructor)
<?php
class Calculator {
public function add($a, $b) {
return $a + $b;
}
}
From Utility to Controller
// Before: Utility
<?php
class Reports {
public function generate($type) {
// Just generates data
return $data;
}
}
// After: Controller (add extends, add constructor if needed)
<?php
class Reports extends Trongate {
public function generate($type) {
$data = $this->calculate_data($type);
// Now can use framework features
$this->view('report', $data);
}
}
Common Patterns
Pattern 1: Controller Uses Multiple Utilities
<?php
class Invoices extends Trongate {
public function generate($order_id) {
// Load data from database
$order = $this->model->get_where($order_id, 'orders');
// Use calculator utility
$total = $this->calculator->apply_tax($order['subtotal']);
// Use PDF utility
$pdf_path = $this->pdf->generate_invoice($order, $total);
// Return result
echo json_encode(['pdf_url' => $pdf_path]);
}
}
Pattern 2: Utility Uses Another Utility
Utilities can use other utilities, but they load them differently (no $this-> magic):
<?php
class Pdf {
private $image;
public function __construct() {
// Manually instantiate utilities
require_once '../modules/image/Image.php';
$this->image = new Image();
}
public function add_logo() {
// Use the image utility
$this->image->load('logo.png');
$this->image->resize(200, 100);
}
}
Or, convert Pdf to extend Trongate if it needs multiple utilities:
<?php
class Pdf extends Trongate {
public function add_logo() {
// Now can use automatic loading
$this->image->load('logo.png');
$this->image->resize(200, 100);
}
}
Testing Your Module Type
To verify you've chosen the right pattern:
Test 1: Can it work standalone?
// Try instantiating directly
$calc = new Calculator();
$result = $calc->add(5, 3);
// If this works, it's a good utility candidate
Test 2: Does it need framework features?
// If you see any of these in your class:
$this->model->...
$this->view(...)
$this->module(...)
// Then it MUST extend Trongate
Best Practices Summary
Controllers (extend Trongate):
- Handle business logic and user interactions
- Need database, views, or other modules
- Must call
parent::__construct($module_name) if they have a constructor
- Examples: Products, Users, Admin, Orders
Utilities (standalone):
- Perform specific, self-contained tasks
- No database, views, or framework dependencies
- Constructor accepts task-specific parameters only
- Examples: Image, Calculator, Pdf, Validator
Advanced: Hybrid Approach
Some modules might need BOTH a controller interface AND utility functions:
// products/Products.php - Controller for web interface
<?php
class Products extends Trongate {
public function create() {
$this->view('create_form');
}
}
// products/products_helper.php - Utility functions
<?php
function calculate_price_with_tax($price, $tax_rate) {
return $price * (1 + $tax_rate);
}
function format_price($price) {
return '$' . number_format($price, 2);
}
Load the helper in your controller:
<?php
class Products extends Trongate {
public function __construct(?string $module_name = null) {
parent::__construct($module_name);
require_once 'products_helper.php';
}
public function display($id) {
$product = $this->model->get_where($id);
// Use helper function
$price = calculate_price_with_tax($product['price'], 0.08);
$data['formatted_price'] = format_price($price);
$this->view('details', $data);
}
}
Conclusion
Trongate v2's dual module system gives you:
- Flexibility - Choose the right pattern for each module
- Performance - Zero framework overhead for utilities
- Simplicity - Use both types identically in controllers
- Intelligence - Framework auto-detects and adapts
Understanding when to extend Trongate vs creating standalone utilities is key to building fast, maintainable applications.
Remember: When in doubt, start with a standalone utility. You can always add extends Trongate later if you need framework features. It's easier to add framework capabilities than remove them.