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 like modules/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.)

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:

  1. 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, the EMPS_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).

  2. Script Paths and Fallback to EMPS Paths:
    If not found, it looks under the core script paths (EMPS_SCRIPT_PATH or EMPS_PATH_PREFIX). In most cases, EMPS_SCRIPT_PATH is the actual document root of the website, and EMPS_PATH_PREFIX is the root folder or the current EMPS version. So, if, say, modules/order/pdf is not found in the EMPS_SCRIPT_PATH, it will be sought in the EMPS_PATH_PREFIX which is often equal to EMPS6/6.5 (but can be EMPS6/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 →

  3. Language Fallback:
    For Smarty template file names, EMPS tries both the current language (e.g., en) and a neutral fallback language (nn).

  4. Root-Level Paths:
    Finally, for common modules or shared components, EMPS will search in the common path (EMPS_COMMON_PATH_PREFIX), which is EMPS6/6.X. The difference from EMPS_PATH_PREFIX lies in the 2-level structure of EMPS: the root level is EMPS6/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, or inc), it constructs the file name (for example, appending .php for controllers or .htm for views).
  • 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 uses resolve_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 named order-print-pdf becomes order/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 like common_module(), core_module(), and plain_file() provide mechanisms to resolve file names in shared or core parts of the framework.

  • Static Assets:
    The static_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 as load_enums_from_file() and load_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 commonly row_controls.nn.htm.

     

  • Example 2:

    {{include file="db:_bk,date_filter" with_subaccounts=$account}}

    This includes a file from the _bk module named date_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 named vue_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 the templates directory on in the database. You can go to /admin-set/ of your website and create a setting like page/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 the page/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 using require_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.