URL Parsing and Variables

The EMPS routing approach takes a very “convention-over-configuration” stance, dynamically parsing the URL into predefined global variables rather than requiring an explicit, manually maintained routing table. Here are some thoughts on this method compared to traditional routing in frameworks like Laravel:


Advantages

  1. Zero Manual Routing Updates

    • Automatic Mapping: By splitting the URL into segments and automatically assigning them to variables (like $pp, $key, etc.), EMPS eliminates the need for a central routing table. Every new module or URL structure change doesn’t require an update in a separate routing configuration.
    • Convention-Driven: The naming convention directly ties the URL to the file system (e.g., manage-orders becomes a folder path like modules/manage/orders and expects an orders.php file). This can significantly speed up development since you simply create or rename files and folders to adjust routes.

     

  2. Simplicity and Transparency

    • Direct File Resolution: The system intuitively looks for a PHP file or a corresponding template based on the URL, reducing the “magic” of abstract route definitions. What you see in the URL is reflected in the folder structure, making it easier to understand the overall architecture.
    • Less Boilerplate: Developers don’t need to write and maintain extensive routing definitions, which can often be a source of extra boilerplate and potential errors in frameworks that require manual updates (like Laravel’s routing class).

     

  3. Flexibility Through Naming

    • URL Variables and Global Access: The extracted URL variables become global PHP variables. This means that once the URL is parsed, the rest of your code can access these variables anywhere in your application, streamlining the process of generating internal links or making decisions based on URL parameters.

Flexibility Tied to the $pp Argument

Although EMPS applications lack a centralized routing class, the routing principle used in EMPS imposes only one rigid limitation on the URLs: the presence of the $pp argument of the URL.

  • The only real restriction is that the first URL segment ($pp) must mirror the module’s folder structure.
  • By using hyphens instead of slashes (e.g., order-print-pdf for modules/print/pdf/pdf.php), you effectively map nested directories without much hassle.
  • The module’s PHP file (like pdf.php) is free to handle the remainder of the URL in any way it sees fit, either by relying on the global variables or by re-loading them via $emps->loadvars() if needed.
  • This approach indeed minimizes the rigidity often seen in frameworks that require manual route configuration.
  • It’s a clever use of conventions that makes the routing implicit while still providing hooks for the module to implement custom logic as needed.
  • The ability to restore or adjust the routing variables using $emps->loadvars() further reduces the risk of unintended state changes, which is a neat safeguard.

Summary

The EMPS approach to URL parsing and routing is elegant in its simplicity: it leverages a consistent naming and directory structure to map URLs to files automatically. This can drastically reduce the overhead of maintaining a manual routing table and can be a breath of fresh air compared to frameworks where you constantly need to update routing definitions for even minor changes.

EMPS’s design — by leveraging a convention-based approach — effectively delegates the bulk of the routing logic to individual modules. This minimizes manual maintenance, provides enough flexibility, and avoids the typical pitfalls of overly complex routing systems found in other frameworks.

In summary, the simplicity and decentralized control offered by this routing mechanism are not just sufficient but indeed advantageous. It’s a great example of how conventions can often replace configuration when the use cases are well understood and bounded.


Source-Code Overview

The EMPS_Common_Routing trait implements a convention‐based URL routing mechanism. Code on GitHub →

Its responsibilities include:

  • Parsing the Request URL: Splitting the URL into meaningful parts and assigning them to global variables (using a predefined order via the EMPS_URL_VARS constant).
  • Managing URL Variables: Saving, loading, and clearing URL variables to/from a storage array ($this->VA), ensuring that the globals remain consistent.
  • Generating Internal Links: Methods like elink(), clink(), and slink() build URLs that include the current state stored in those URL variables.
  • Handling Special Responses: There are helper methods to send JSON responses, plain text, or error pages (404, 500) using the Smarty templating engine.
  • Protocol and Redirect Handling: Functions such as ensure_protocol() and normalize_url() enforce HTTPS and canonical URL forms.
  • Session and Access Controls: With functions like should_prevent_session() and retry_for_session(), the trait provides rudimentary bot–detection and session retry logic.

Detailed Function Documentation

URL Variable Management

  • loadvars()
    Purpose: Restores the URL variables from the internal storage array (VA) to the globals.
    Usage:

    $emps->loadvars();

     

  • savevars()
    Purpose: Saves current global URL variables into the internal storage array. This allows you to “freeze” the state after a change and later restore it.
    Usage:

    $emps->savevars();

     

  • changevar($n, $v)
    Purpose: Sets a new value for a URL variable both in the trait’s internal storage (VA) and in the global namespace.
    Usage:

    $emps->changevar('pp', 'new-module');

     

  • clearvars()
    Purpose: Resets all global URL variables (defined in $this->emps_vars) to an empty string except for the language variable.

    Usage: Typically called before reassigning values to obtain a link to another page.

     

  • import_vars() (private)
    Purpose: Checks for each variable (as defined in $this->emps_vars) in the $_GET and $_POST arrays and sets the corresponding global variable.
    Note: This function ensures that URL variables can be overridden by request parameters.

     

  • getvar($varname)
    Purpose: Returns the value of a specific global URL variable. Use this function if you don't want to declare a particular URL variable global within your function or method.

    Usage:

    $current_module = $emps->getvar('pp');

URL Parsing and Routing

  • parse_path() (private)
    Purpose: This is the central routine that:

    • Retrieves the current request URI.
    • Optionally passes the URI through a filter (emps_uri_filter if defined).
    • Removes any script folder prefix.
    • Stores various forms of the URI in properties (PLURI, PURI, URI).
    • If the URI is empty, it assigns a default “start page” from settings.
    • Checks if the URL corresponds to a “virtual page” (a CMS entry) or if a trailing slash redirect is needed.
    • Splits the remaining URI parts into global variables using the order defined in the constant EMPS_URL_VARS.
    • The standard sequence of URL vars is the following: pp,key,start,ss,sd,sk,sm,sx,sy but rarely any variables beyond sd are actually used.
    • Finally calls $this->post_parse(), which you’d implement in your main EMPS class if additional processing is needed.

    Usage: This method is invoked during the bootstrapping phase and is not called directly by application code.

     

  • handle_redirect($uri)
    Purpose: Provides a dynamic redirection mechanism, it is possible to define URL redirects in the database using an admin interface.


Error and Response Helpers

  • not_found()
    Purpose: Sets a 404 HTTP response, assigns the “not found” template to Smarty, and displays the main layout.
    Usage: Called when a requested page isn’t found. It can also be used by any module script to invoke a 404 HTTP response at any time. Say, the module print-order is invoked through the /print-order/25/ URL, but the module code finds that order with the id 25 does not exist in the database. In response, it can simply call $emps->not_found(); exit; to end the execution of the script with an HTTP 404 response.

     

  • database_down()
    Purpose: Handles situations when the database connection fails, setting a 500 response and displaying a “database down” page.

 

  • deny_access($reason)
    Purpose: Prepares an access-denied response by (a) triggering a session retry if needed and (b) assigning a reason flag to Smarty.

    Usage Example:

    // In a controller:
    if ($emps->credentials("admin")) {
      // do something only admins can do
    } else {
      $emps->deny_access("AdminNeeded");
    }

     

  • retry_for_session()
    Purpose: Implements a simple retry loop (up to three times) for sessions, particularly when the session should be prevented (e.g., bot requests).

     

  • json_response($response), json_error($message, $id = ""), json_ok($data = [])
    Purpose: These methods allow sending JSON responses.

    • json_response() sends a raw JSON response with proper headers.
    • json_error() wraps an error message in a standard response format.
    • json_ok() sends a success response, merging in any provided data.

    Usage Example:

    // In a controller:
    if ($error_condition) {
      $emps->json_error("Something went wrong", "ERR_001");
      exit;
    } else {
      $data = ['foo' => 'bar'];
      $emps->json_ok($data);
      exit;
    }

     

  • plaintext_response()
    Purpose: Prepares the response headers for plain text output.


Internal Link Generation

  • elink()
    Purpose: Builds an internal link (relative URL) based on the current values of the URL variables (as defined by EMPS_URL_VARS). It generates a “pretty” URL path by joining the values with slashes, using a hyphen (-) for empty variables. It also appends extra query parameters if certain variables aren’t part of the main URL structure.
    Usage Example:

    global $key;
    // ...
    $key = 78; // go to item 78
    $pp = "thankyou"; // of the modules/thankyou/thankyou.php module 
    $internal_url = $emps->elink();
    // respond with a JSON object which the client JS will use to redirect the user to another page
    $emps->json_ok(['link' => $internal_url]); exit;

     

  • clink($a)
    Purpose: Extends elink() by appending an extra query component (for example, "x=1") to the generated URL.
    Usage Example:

    $link = $emps->clink("refresh=1");
    $smarty->assign("RefreshLink", $link);

    Then in the Smarty template:

    <a href="">Refresh the Info</a>

    This could have been simply done using:

    <a href="./?refresh=1">Refresh the Info</a>

    but only if you're sure that the current URL in the user's browser address line is the same as $emps->elink() and that there is the proper / at the end of the URL. So, if the user is at, say, http://website.com/order-info/67 (notice the lack of trailing slash /), the latter example will send him to http://website.com/order-info/?refresh=1, which is wrong.

     

  • slink($value, $var)
    Purpose: Sets a specific global URL variable to a new value and then returns the updated internal link via elink().
    Usage Example:

    // Change the module part ($pp) to "orders" and get the resulting URL:
    $new_link = $emps->slink("orders", "pp");

     

  • redirect_page($page) and redirect_elink()
    Purpose: Facilitate HTTP redirection, either to an arbitrary URL or to the internally generated link.


Protocol and URL Normalization

  • ensure_protocol($protocol)
    Purpose: Checks whether the current request uses the specified protocol (http/https) and, if not, issues a 301 redirect to enforce it.
    Usage Example:

    // Force HTTPS:
    $emps->ensure_protocol('https');

    This is actually handled by the bootstrap script, if the variable $emps_force_protocol is set to "https", this function will be called.

     

  • is_https()
    Purpose: Returns a Boolean indicating if the current request is over HTTPS.

     

  • normalize_url()
    Purpose: Compares the current URL (as received) with the canonical URL (generated by elink()). If they differ, it redirects to the canonical version.
    Usage: Typically used as part of a middleware or early in the request cycle.

     

  • is_localhost_request()
    Purpose: Determines whether the request is coming from the local server (by comparing REMOTE_ADDR and SERVER_ADDR). This is often used to distinguish the local development environment from a live server.


Usage Example Walkthrough

Imagine you have a module located in modules/print/pdf/pdf.php that should handle PDF printing. With EMPS routing in place, the following sequence occurs:

  1. URL Request:
    A user requests the URL:

    /print-pdf/1234/

    – The routing mechanism (via parse_path()) splits this URL.
    – The first segment, "print-pdf", maps to $pp and is translated (using hyphens as directory separators) to look up modules/print/pdf/pdf.php.

     

  2. Inside the Module:
    In pdf.php, you can assume that global variables are available (for example, $pp = "print-pdf" and perhaps another variable like $key = "1234").
    Your module might then:

    // pdf.php
    if (!$key) {
       $emps->not_found();
       exit;
    }
    // assume you have your system in the $system object
    $order = $system->load_order(intval($key));
    if (!$order) {
       $emps->not_found();
       exit;
    }
    // Process the PDF printing...
    $system->print_order($order['id']);
    
    // Perhaps use the following to generate an internal link after processing:
    $next_page = $emps->slink("confirmation", "pp");
    $emps->redirect_page($next_page);
    exit;

     

  3. JSON Response Example:
    For an AJAX endpoint in the same module, you might have:

    // ajax.php within the same module
    if ($some_error) {
       $emps->json_error("Invalid parameters", "ERR_123");
       exit;
    }
    $result = ['pdf_url' => $generated_pdf_url];
    $emps->json_ok($result);
    exit;