Internal References in EMPS
EMPS uses a convention-over-configuration approach to locate the files that make up a website’s functionality. This system provides a standardized way of referencing:
- PHP controller files
- Smarty view/template files
- Include files and libraries
- Common modules and core scripts
- Other assets (e.g., minified files, enums, plain files)
By following a strict naming convention, you can easily predict — and control — where your source files are located and how they are included.
How File References Are Constructed
1. The Basics
Internal references in EMPS are expressed as strings that encode both a module’s logical name and hints about the file type. For example, a reference might look like:
"_docs"
"_bk/traits,accounts"
"_comp/vted,row_controls"
(for a Smarty template)
The underscore (_
) at the beginning of a reference indicates that the file belongs to a module that will be resolved to a physical path. The reference is then split into parts based on certain delimiters:
- Hyphens (
-
) are used to indicate subdirectories. For instance,order-print-pdf
maps to a folder structure likemodules/print/pdf/
. - Commas (
,
) separate the logical module path from a specific file name within that module. For example,_bk/traits,accounts
indicates:- Module directory:
_bk/traits
(with hyphens in the URL translated into slashes), the actual location of the module in the website's folder:modules/bk/traits
- Controller file:
accounts.php
(in this case it's just a script file included into the actual controller. A dependency source file.)
- Module directory:
Depending on the type of file requested (controller, view, or include), different suffixes are added:
- Controller files always use the
.php
extension. - View files (Smarty templates) are expected to have a language code appended before the
.htm
extension (e.g.,.en.htm
,.nn.htm
). - Include files follow the naming provided in the reference.
2. The File Resolution Process
When you call a function such as page_file_name()
or try_template_name()
, EMPS follows a series of steps to determine the physical file path:
-
Try Website Paths:
EMPS first checks under the primary website path (EMPS_WEBSITE_SCRIPT_PATH
) for the file. It is important to point out that in most cases it's the same as the next step, theEMPS_SCRIPT_PATH
, with the exception of "child websites" - or sub-websites that share the source code with the "parent" website, but can override its files by their own files, have a different website context (their settings and CMS objects can override the parent website's settings and objects). -
Script Paths and Fallback to EMPS Paths:
If not found, it looks under the core script paths (EMPS_SCRIPT_PATH
orEMPS_PATH_PREFIX
). In most cases,EMPS_SCRIPT_PATH
is the actual document root of the website, andEMPS_PATH_PREFIX
is the root folder or the current EMPS version. So, if, say,modules/order/pdf
is not found in theEMPS_SCRIPT_PATH
, it will be sought in theEMPS_PATH_PREFIX
which is often equal toEMPS6/6.5
(but can beEMPS6/6.0
if you are using the mongodb version of EMPS6). So, if EMPS6 is installed in, say/srv/www/lib
, the path to the module will be resolved as/srv/www/lib/EMPS6/6.5/modules/order/pdf/pdf.php
. This module doesn't actually exist in EMPS6, but here's a module that does exist in EMPS6:_json/context
: Code on GitHub → -
Language Fallback:
For Smarty template file names, EMPS tries both the current language (e.g.,en
) and a neutral fallback language (nn
). -
Root-Level Paths:
Finally, for common modules or shared components, EMPS will search in the common path (EMPS_COMMON_PATH_PREFIX
), which isEMPS6/6.X
. The difference fromEMPS_PATH_PREFIX
lies in the 2-level structure of EMPS: the root level isEMPS6/6.X
- it contains functions that do not depend on the database and can be shared among the SQL and No-SQL versions of EMPS. There aren't many modules in this level, but there are some, here is one example: Code on GitHub →This module assembles an
editor.css
file to be used in the TinyMCE editor - it assembles the contents of multiple individual CSS files in the source code.
During the file path resolution process, resolved file names are cached in the internal cache ($this->require_cache
) to speed up subsequent lookups.
3. Functions for File Resolution
The trait EMPS_Common_Files Code on GitHub → provides several helper functions. Here’s an overview of key functions and what they do:
-
page_file_name($page_name, $type)
This is the central function for resolving the filename based on the internal reference.- If the reference begins with an underscore (
_
), it is assumed to be a module file. - It splits the reference into its module path and file name parts.
- Depending on the file type (
controller
,view
, orinc
), it constructs the file name (for example, appending.php
for controllers or.htm
for views).
- If the reference begins with an underscore (
-
try_page_file_name($page_name, $first_name, $include_name, $type, $path, $lang)
Given the components of a module reference, this function builds a candidate file name and usesresolve_include_path()
to check if the file exists. -
try_template_name($path, $page_name, $lang)
Specifically for Smarty templates, this function constructs the expected file name (with the language code and.htm
extension) and resolves the path. -
hyphens_to_slashes($file_name)
Converts hyphens in the module reference into directory separators. For example, a module namedorder-print-pdf
becomesorder/print/pdf
. -
min_file_name($page_name)
Constructs a file name in a minified folder structure. This is used when creating or accessing minified resources. -
Common Modules and Core Scripts:
Functions likecommon_module()
,core_module()
, andplain_file()
provide mechanisms to resolve file names in shared or core parts of the framework. -
Static Assets:
Thestatic_svg($url)
function is used to safely load SVG files based on an internal URL, ensuring that no unwanted directories (or directory traversal) are accessed. -
Enums:
Functions such asload_enums_from_file()
andload_enums_from_file_ex()
read enumerated values from configuration files and make them available to the system.
4. Smarty Template Inclusion
When including templates in your Smarty files, EMPS uses a special “db:” syntax to reference files resolved by these internal functions. For example:
-
Example 1:
{{include file="db:_comp/vted,row_controls"}}
This tells Smarty to include the template for the
_comp/vted,row_controls
reference. EMPS will resolve this to a file (e.g.,modules/_comp/vted/row_controls.en.htm
or the equivalent for the current language), most commonlyrow_controls.nn.htm
. -
Example 2:
{{include file="db:_bk,date_filter" with_subaccounts=$account}}
This includes a file from the
_bk
module nameddate_filter
, passing the variable$account
as a parameter. -
Example 3:
{{include file="db:page/vue_paginator" pages_var="mypages"}}
This includes a file from the
templates/page
directory namedvue_paginator.nn.htm
, passing the variable$pages_var
as a parameter. Note the absence of the_
before the object name - it means the object is either in thetemplates
directory on in the database. You can go to/admin-set/
of your website and create a setting likepage/phonenumber
, set it to your company's phone number and then reference it in a Smarty template like{{include file="db:page/phonenumber"}}
. Although this particular thing can be done even easier:{{"page/phonenumber"|emps:get_setting}}
, but in this case if the value of thepage/phonenumber
contains any Smarty code, say, any further{{include ...}}
- it won't be executed.
5. Real-Life Usage Examples
Here are some examples of how these internal references are used in EMPS:
-
Loading a Controller File:
require_once $emps->page_file_name("_docs", "controller");
This loads the PHP controller for the
docs
module. The underscore indicates a module reference that is resolved to, for example,modules/docs/docs.php
.It's actually taken from the source code of this website's default (index) page:
$key = "front"; require_once $emps->page_file_name("_docs", "controller");
It makes EMPS render the
/docs/front/
when the/
root URL is requested.The corresponding
modules/front/front.nn.htm
contains only this:{{include file="db:_docs"}}
-
Loading Multiple Controller Files: Very often it is necessary to refer to other PHP files that need to be included into the controller code. If the controller uses a class that hasn't yet been loaded in the middleware (e.g. in
/modules/_common/config/project/webinit.php
), it first needs to locate the source-code file for that class. It can of course load a PHP file from a known file path usingrequire_once
, but what if it needs to load a module that might exist in the project or load the default module that exists in EMPS itself? In all cases, it's always better to rely on$emps->page_file_name()
to locate the proper file.Controller code
<?php // locate the /modules/bk/bk.class.php file and require it require_once $emps->page_file_name('_bk,bk.class', 'controller'); // create an object of this class. This is not neccessary in case // you want to use only static methods. You could use EMPS_Bookkeeping::totals_by_subaccount // instead of $bk->totals_by_subaccount - but I mostly prefer using the non-static way, // it always gives you the possibility of modifying non-static properties and having multiple // objects of the same class if you need to. $bk = new EMPS_Bookkeeping(); // get the sub account ID from the URL, say the full URL is /payments/25/ // then this controller is at /modules/payments/payments.php and the $key // variable is set to 25. But always use intval to prevent any SQL injections. $client_id = intval($key); // get the current time stamp as an integer $dt = time(); // use the $bk->dates object's methods to obtain the start and end of the current month // as Unix timestamps as well $cur_edt = $bk->dates->end_of("month", $dt); $cur_dt = $bk->dates->start_of("month", $dt); // prepare the request to the bookkeeping database: // In this example, this is the request to get the sum of all transactions // crediting the "62" account and debiting any of the "51", "60.05", or "90" // accounts in the current month, filtered by the "clients" subaccount of the credit side // of the transaction equal to $client_id and grouping the results by the "services" // subaccount of the credit side of the transaction // (i.e. how much is paid by the client for each service) // This is close to a real-life example. $tq = []; $tq['status'] = 0; $tq['$and'] = [ ['tdt' => ['$lte' => $cur_edt]], ['tdt' => ['$gte' => $cur_dt]] ]; $tq['credit'] = "62"; $tq['debit'] = ["51", "60.05", "90"]; $q = []; $q['tq'] = $tq; $q['c_analysis'] = ['clients' => $client_id]; $q['c_group'] = ['services']; // execute the request and obtain the result $payment_lst = $bk->totals_by_subaccount($q); // return the result to the client. This is a JSON-only controller (endpoint). $emps->json_ok(['lst' => $payment_lst]);
What's important in the example above is the reference to the "_bk,bk.class" source code file. Here's what the contents of the file might look like, it can in turn require its own dependencies:
<?php // to reduce the size of the class source code file, I like to split them into traits require_once $emps->page_file_name('_bk/traits,accounts', 'controller'); require_once $emps->page_file_name('_bk/traits,docs', 'controller'); // here's a standard EMPS common module that handles date and time manipulation require_once $emps->common_module('datetime/dates.class.php'); class EMPS_Bookkeeping { // use the traits that we've required in the code above use EMPS_Bookkeeping_Accounts; use EMPS_Bookkeeping_Docs; public function __construct() { // initialize EMPS_Dates as the $dates property of the EMPS_Bookkeeping class $this->dates = new EMPS_Dates(); } // other class methods and properties }
So, any PHP source code file that was included or required once can in turn include or require its own dependencies using the
page_file_name
EMPS method to locate the source code files.
-
Loading a Common Module:
require_once $emps->common_module('datetime/dates.class.php'); $dates = new EMPS_Dates();
This loads a shared class (from the common module directory) that handles date operations.
-
Smarty Template Inclusion:
{{include file="db:_comp/vted,row_controls"}} {{include file="db:_bk,date_filter" with_subaccounts=$account}}
These examples demonstrate how internal references are used to include Smarty templates. The db:
prefix tells Smarty that the file should be resolved using EMPS’s internal naming conventions.
Summary
The internal reference system in EMPS is designed to:
-
Reduce Configuration Overhead:
By basing file locations on a strict naming convention, you no longer need to maintain complex routing or mapping tables. -
Ensure Consistency:
All modules, controllers, views, includes, and assets follow a predictable pattern. For example, a module reference like_module/submodule,file
will always map to a corresponding folder and file (with appropriate extensions). -
Facilitate Multi-Language Support:
Templates are automatically looked up using the current language code (or a neutral fallback), simplifying internationalization. -
Provide Flexibility:
Each module is free to handle additional URL parameters or customize file loading as needed, while still adhering to a standard convention for the bulk of the file resolution.
By following these conventions, developers can create and maintain EMPS-based websites with minimal friction. The internal references not only simplify file organization but also make it easier to manage shared resources, common modules, and even dynamic assets like minified files or enums.
What About Autoloading and Composer?
There is no limitation on using Composer or defining your own procedures for class loading and do everything the way you prefer, store the class source files in your own folder.
Here's an example of a Composer-loaded class used in EMPS: Code on GitHub →
The approach described above (page_file_name
, common_module
) does not interfere with Composer
and class autoloading, so you can use both approaches as needed.
Why doesn't EMPS use autoloading to load its own component classes?
1) It doesn't have to.
Its classes are usually big with many methods sometimes grouped into traits.
There is no tendency to subdivide classes into smaller classes and use a dozen of source
code files where just one file can be used. Just require_once
that file and you're good to go.
2) To make overriding source code easier.
When a file like $emps->page_file_name("_comp/props", "controller")
is referred to in the source code,
by default that file is part of the EMPS framework. But you can create your own
/modules/comp/props/props.php
with your own functionality.
Additionally, a child website (with multi-level website configurations) can override a parent website's source-code file with its own version.
3) To store all source-code files of a module in a single module directory.
Autoloading is often done by translating the complex class name into folder names,
like in the example above, the PHPMailer\PHPMailer\PHPMailer
is found at
vendor/phpmailer/phpmailer/src/PHPMailer.class
. Also, you can register your
own autoloader function using spl_autoload_register
in PHP. In any case,
it's assumed that the folder structure of the source-code folder should
reflect the class hierarchy. Thus, different classes tend to be grouped under
a single directory in the source-code of the project. But, some classes come
bundled with modules which you can add to your project by
copying the appropriate module folder into your modules
folder. It would be
tedious to copy the appopriate classes to some classes
folder of the project
as well, it's better to keep everything a module supplies in a single directory.
So, the paths to the places where the class source-code files are stored can
not be easily translated into class names and vice versa.
I'm sure that with enough desire it will be possible to invent some autoloading
procedure that would spare the users from calling require_once
with
page_file_name
explicitly in the code and just referring the classes that
they want to use, but so far there has been no necessity for that.