Mastering Constructors
Trongate v2 handles constructors differently than most frameworks. This page explains the rules, the reasons, and the patterns you need to know to build reliable, high-performance modules.
The Golden Rule
If your class extends Trongate AND has a constructor, you MUST call parent::__construct($module_name) as the first line. For example:
<?php
class Items extends Trongate {
public function __construct(?string $module_name = null) {
parent::__construct($module_name); // First line!
// Your custom code here
}
}Forgetting this will cause errors when using framework features like $this->db.
In PHP, the __construct() method is not allowed to declare a return type - not even void.
Understanding $module_name
Before we dive into constructors, let's clarify what $module_name actually is and where it comes from. This is fundamental to understanding why constructors work the way they do.
What is $module_name?
The $module_name is a lowercase string that identifies your module. It matches:
- The folder name in the
modulesdirectory - The first part of your URL (i.e., the first URL segment)
A Concrete Example
Let's say you create a module for managing products. Here's how it all connects:
Your project structure:
modules/
└─ products/ ← This folder name
└─ Products.php ← Controller class inside
User visits this URL:
https://yoursite.com/products/index
^^^^^^^^
This is the $module_nameWhen someone visits https://yoursite.com/products/index:
- Trongate looks at the URL and extracts
'products' - It sets
$module_name = 'products' - It looks for the folder
modules/products/ - It loads the file
modules/products/Products.php - It instantiates the class:
new Products('products')
So $module_name is simply the string 'products' - the identifier for your module.
Simple Rule: The module_name is always the lowercase version of your folder name and the first URL segment.
Examples:
- URL:
yoursite.com/users→$module_name:'users' - URL:
yoursite.com/admin_panel→$module_name:'admin_panel' - URL:
yoursite.com/api→$module_name:'api'
Where Does It Come From?
The framework extracts $module_name from the URL automatically. So, let's assume you're building a Trongate module and you write code like this:
parent::__construct($module_name);At first glance, it may look as though you've plucked some random variable out of thin air. That may be a bit worrying. However, there's no need to worry!
The $module_name variable is created automatically by the framework and passed to your constructor. You don't create it - you simply receive it and pass it along to the parent class.
How It Gets To You
Here's what happens behind the scenes. Inside the framework's Core.php file, there's code that looks like this:
// Extract module name from URL
$module_name = 'products'; // Simplified for clarity
// Instantiate your controller
$controller_instance = new Products($module_name);Notice how the framework passes $module_name when creating your controller? That's where it comes from!
Your job is simply to accept it in your constructor and pass it to the parent:
public function __construct(?string $module_name = null) {
parent::__construct($module_name);
}Think of it like a relay race - the framework hands you the baton ($module_name), and you pass it to the next runner (the parent class).
When the framework automatically loads modules like DB, you might notice it passes $this->module_name as an argument:
// Inside Trongate.php when you access $this->db:
$module_instance = new Db($this->module_name);Why pass the module name? Why not just new Db() with no arguments?
The reason is simple: these framework modules need to know which module is using them.
For example, the Db class stores the current module name in its $current_module property. This helps the Db class to figure out the name of the target database table.
Without passing the module name, the framework wouldn't have this contextual information!
Parameter vs Property: Two Different Things
Here's something that trips up a lot of developers: there are actually two separate variables called module_name in play.
The Parameter (Local Variable)
This is what you receive in your constructor:
public function __construct(?string $module_name = null) {
// ^^^^^^^^^^^^
// This is a parameter
// A local variable
}It only exists inside the constructor function. Once the constructor finishes, it disappears.
The Property (Class Variable)
This is what gets stored on your object:
$this->module_name // This is a property
// Available throughout your classThis exists for the entire lifetime of your object and can be accessed from any method.
Why Not Just Use $this->module_name?
You might be thinking: "Why don't we just pass $this->module_name to the parent constructor?"
// Why not this?
public function __construct() {
parent::__construct($this->module_name); // Surely this?
}Great question! The problem is timing. At the point where you're calling parent::__construct(), the property $this->module_name doesn't exist yet. The parent constructor is the thing that creates it!
It's like asking someone to hand you a cup of tea that they're about to make - they can't give you something that doesn't exist yet.
The Correct Sequence
Here's what actually happens, step by step:
public function __construct(?string $module_name = null) {
// 1. Framework passes 'products' to your parameter
// Local variable $module_name = 'products'
// 2. You pass it to parent
parent::__construct($module_name);
// 3. Parent creates the property
// Inside Trongate.php:
// $this->module_name = $module_name;
// 4. NOW you can use the property
echo $this->module_name; // Prints 'products'
}So you receive it as a parameter, pass it to parent, and then parent converts it into a property.
Breaking Down the Constructor Signature
Let's look at the recommended constructor signature piece by piece:
public function __construct(?string $module_name = null)Why ?string (Nullable String)?
The question mark means "this can be a string OR null". This gives you flexibility:
// Framework usage (normal):
new Products('products'); // Passes a string
// Direct instantiation (testing):
new Products(); // No parameter - uses null
new Products(null); // Explicitly passes nullWithout the ?, you'd be forced to always pass a string, which would make testing awkward.
Why = null (Default Value)?
The = null makes the parameter optional. This means all of these work:
new Products('products'); // Framework does this
new Products(null); // You could do this
new Products(); // Or thisWithout = null, that last one would throw an error about a missing argument.
Can't We Just Omit the Parameter Entirely?
You might wonder: "If the framework knows what module this is, why not just have an empty constructor?"
// Can't we do this?
public function __construct() {
parent::__construct($module_name); // Where does this come from?
}This doesn't work for two reasons:
- The variable
$module_namewouldn't exist - you'd get an "undefined variable" error - Your constructor wouldn't accept the parameter the framework is trying to pass
You must accept the parameter in order to receive it and pass it along.
Why the "Awkward" Syntax?
When you start writing constructors in Trongate v2, you might find yourself asking: "Why can't I just write a simple constructor? Is this extra code actually necessary?"
First, a Reminder: You might not need a constructor at all! If you don't have custom initialization logic to run, simply omit the constructor entirely. PHP will automatically handle the parent call for you, keeping your code clean and minimal.
Common Developer Questions
1. Why the "awkward" constructor signature?
Trongate v2 is built for Context Awareness. By using the standard ?string $module_name = null signature, you are accepting a "context baton" from the framework. This baton allows utility modules (like the Database or Validation classes) to automatically know which module is calling them without you having to configure them manually.
2. What are the benefits of this approach?
- Zero-Config Database:
$this->dbautomatically knows your table names. - Smart Validation:
$this->validationinstantly finds your specific language files. - Traceability: It makes your code easier to debug because the "flow of information" is explicit and linear.
- Performance: It enables "Lazy Loading," ensuring you only load exactly what you need.
3. Am I allowed to write constructors like this?
public function __construct() {
parent::__construct();
}Technically, yes - but it is fragile. If you use this empty version, the framework has to "guess" your module name using PHP class reflection. This works for basic, standalone controllers, but it will break if you ever load that module as a service or try to use internal security features like .
4. Is this a "MUST" or a "SHOULD"?
Think of it as a Professional Standard. While the framework tries to be helpful if you use the simple version, using the recommended signature is a "MUST" if you want your module to be robust, secure, and fully compatible with advanced features like internal callbacks and cross-module service loading.
5. Is there any scenario where the simple version is acceptable?
The simple parent::__construct() is only acceptable for very basic controllers that will never interact with other modules. However, since the "correct" way only takes an extra few seconds to type, we recommend the full pattern to future-proof your application against "Ghost" 403 errors and context-loss bugs.
The Golden Rule: 5 seconds of "awkward" code now saves hours of manual configuration and troubleshooting later. Stick to the standard, and the framework handles the heavy lifting for you.
When You Don't Need a Constructor
Here's the good news: most of the time, you don't need to write a constructor at all!
<?php
class Products extends Trongate {
// No constructor needed!
public function index() {
$data = $this->db->query('SELECT * FROM products');
$this->view('list', $data);
}
}When you don't define a constructor, PHP automatically calls the parent constructor for you. It's like saying "just do whatever the parent class does" - which is usually exactly what you want.
Don't add a constructor unless you have a specific reason. Let PHP handle it automatically.
When You DO Need a Constructor
Only add a constructor if you need to run some code before your methods execute. Common reasons include:
- Setting up properties that all methods will use
- Performing initialization checks
- Configuring settings
Example: Setting a Property
<?php
class Products extends Trongate {
protected $items_per_page = 20;
public function __construct(?string $module_name = null) {
parent::__construct($module_name); // ALWAYS first!
// Set items per page based on configuration
$this->items_per_page = 50;
}
public function index() {
// items_per_page is already set
echo "Displaying " . $this->items_per_page . " items per page";
}
}What Happens If You Forget parent::__construct()?
If you add a constructor but forget to call the parent, things will break when you try to use framework features:
<?php
class Products extends Trongate {
public function __construct(?string $module_name = null) {
// Oops! Forgot parent::__construct($module_name);
$this->some_property = 'value';
}
public function index() {
$data = $this->db->query('SELECT * FROM products'); // ERROR!
}
}You'll see an error like:
Fatal error: Uncaught Exception: Undefined property: Products::dbThis happens because the parent constructor is responsible for setting up the $this->module_name property, which the framework needs for automatic loading of modules.
Remember: If you write a constructor, parent::__construct($module_name) must be the first line.
How Automatic Loading Works
Understanding what happens behind the scenes helps clarify why constructors matter.
When you write this:
$results = $this->db->query('SELECT * FROM products');Here's what the framework does:
- Looks for a property called
$dbon your class - Doesn't find one, so calls the
__get('db')magic method - The magic method checks if the Db module exists
- Loads the file
modules/db/Db.php - Instantiates it:
new Db($this->module_name) - Returns the Db instance to you
Notice step 5? The framework needs $this->module_name to be set. That's what the parent constructor does!
The same process works for any module you access via $this->modulename. The framework automatically loads it and passes your module name during instantiation.
Testing Your Constructor
If you're not sure whether your constructor is set up correctly, try this test method:
public function test() {
// Check module_name is set
echo "Module name: " . $this->module_name . "";
// Try accessing the db module (loads automatically)
echo "Db class: " . get_class($this->db) . "";
// Try a database query
$result = $this->db->query('SELECT 1 as test');
echo "Database connected successfully!";
}Visit yoursite.com/products/test and if you see output instead of errors, your constructor is working properly.
Performance
You might wonder: "Does all this constructor stuff slow things down?"
Not at all! The parent constructor does exactly one thing:
$this->module_name = $module_name ?? strtolower(get_class($this));This is a simple variable assignment - one of the fastest operations in PHP. The overhead is negligible.
Speed Matters: Trongate v2 is designed to be the fastest PHP framework. The constructor pattern is optimized for both speed and clarity.
Common Questions
"What if I rename my class?"
The $module_name comes from the URL and folder structure, not your class name. So even if you rename your class to ProductsController, it would still receive 'products' as the module name.
That said, stick to Trongate conventions: your class name should match your folder name (capitalized).
"Can I use a different parameter name?"
Yes! PHP doesn't care what you call it:
public function __construct(?string $name = null) {
parent::__construct($name); // Works fine
}But using $module_name makes your code clearer for other developers.
"What about child modules?"
The same rules apply. If you have a module at modules/products/categories/Categories.php, the framework handles it automatically. You still just write the same constructor pattern.
Quick Reference
No Constructor Needed (Most Common)
<?php
class Products extends Trongate {
public function index() {
// Just write your methods
}
}With Custom Initialization
<?php
class Products extends Trongate {
public function __construct(?string $module_name = null) {
parent::__construct($module_name); // First line!
// Your custom code here
}
}Summary
Let's recap the key points:
$module_nameis a lowercase string from the URL (e.g., 'products')- The framework passes it to your constructor automatically
- Most controllers don't need a custom constructor
- If you add a constructor, call
parent::__construct($module_name)first - Use the signature:
?string $module_name = null - Don't confuse the parameter with the property (
$this->module_name)
Follow these guidelines and your constructors will work perfectly every time.
We're continually improving the Trongate documentation. If anything is incorrect, unclear, incomplete, or could be better, we'd genuinely appreciate your input.
Share your thoughts in the Documentation Feedback.