Init System

Zen Cart Init System

The term initSystem, apart from being a tag used to group certain PHP files together in the new documentation, is meant to embrace all of those files that are automatically included/initialised before any ‘command’ scripts can be run.

Zen Cart v1.x uses a (non Object Oriented) page controller pattern to decide the scripts to run, based on HTTP_GET parameters. The most important of these is the main_page HTTP_GET parameter. Depending on that parameter, a command script is then run. Each commmand script resides in a directory in /includes/modules/pages.

For example if main_page=login the command script would be taken from the /includes/modules/pages/login/ directory. However the first thing every command script always does is require() the /includes/application_top.php file. This is the heart of the initSystem.

It is application_top.php that is responsible for initialising basic subsystems (database abstraction/sessions/languages etc.) and loading global configuration data. In the past this was done using a hard-coded script. From v1.3.0 on, however, Zen Cart now uses a control array to decide which functions/classes/data files are to be included and initialised. This allows contribution authors and third party developers to gain access to and extend the initSystem without compromising upgradeability.

In the following sections we will work through exactly how the Zen Cart engine uses application_top.php to initialise core systems.

application_top.php - A little bit of history

In terms of its osCommerce roots, application_top.php was the file included on every page/script needed to invoke and handle basic core sub-systems. Any function/class that was needed globally by any page needed to be initialised here.

From a customization perspective this was a bad thing. If third party code (contributions) needed to access a new global function/class then application_top.php would need to be ‘hacked’. This would obviously cause problems on upgrades, when application_top.php would be overwritten, and any customizations would be lost.

Zen Cart attempted to mitigate this by providing certain override directories where extra data/functions files could be placed that would be automatically included when application_top.php was run.

The problem with this system is that it only provides for a very few places within the running order of application_top.php where new code can be introduced. It also did not provide at all for the addition of new classes. What was required was an application_top.php that allowed for the placing of any new function/class/script that was completely under the developer’s control. Furthermore, some method of loading and invoking classes was also required.

Since v1.3, Zen Cart achieves this by abstracting the code run by application_top.php into a control array. This array stores details of functions/classes/init_scripts that need to be run, and the order in which they are run in a special PHP array. Given this it is now possible for third party developers to ‘hook’ into application_top.php and be confident that any future code upgrades will not normally overwrite their own code.

application_top.php - Breakpoints

In Zen Cart there is now almost no procedural code in application_top.php. The small amount that is there will be discussed later. The bulk of procedural code in application_top.php is now given over to handling breakpoints. Breakpoints can simply be described as points of importance. We currently have approximately 20 breakpoints in application_top.php. At each breakpoint something important happens - we may load a function or class, initialise a class, load a script fragment, and so on. The important point is to recognise that at each breakpoint, third party code can, by adding to the control array, also load functions, load classes, initialise classes, run a class method or load (require) a script fragment.

The Control Array

Control arrays are automatically loaded from the directory /includes/auto_loaders. Every *.php file within that directory is expected to have a certain structure. In v1.3 we use a file called config.core.php as the main file for governing application_top.php. Third party developers can add their own control array files. The structure of each file should look like this:

 
$autoLoadConfig[0] = array(); 

The value after $autoLoadConfig (in this case [0]) represents the order in which the actions happen (e.g. the Breakpoint), such that $autoLoadConfig[0] will occur before $autoLoadConfig[1]. Note also that any two entries where the breakpoint is the same will occur in the order they appear within the file. The actual contents of the array() part depends upon what effect is needed. Let’s consider a number of different scenarios.

First I just want to require a file to be loaded. For this, the control array entry would be:

$autoLoadConfig[0][] = array('autoType'=>'require', 'loadFile'=> DIR_WS_INCLUDES . 'somefile.php'); 

The autotype parameter tells us that we just want to require a file

The loadFile parameter tells us which file we want to load.

Loading function files can also obviously be done using the above.

Similarly if we want to ‘include’ a file:

$autoLoadConfig[0][] = array('autoType'=>'include', 'loadFile'=> DIR_WS_INCLUDES . 'somefile.php'); 

We then have a special type of ‘require’. The initSystem introduces a special class of .php files called init scripts. These are stored in the includes/init_includes directory. Each of these contains a small amount of procedural code that can be run as part of the initSystem process. The reason for separating them out into a special directory is to allow for those init_scripts to be overridden, more of which will be discussed later. For now, to load an init_script we use the following control array structure.

 $autoLoadConfig[] = array(array('autoType'=>'init_script', 'loadFile'=> 'init_database.php'));

Where the auto_loader system comes into its own is in the handling of class files. With a class file we want to load the class file definition, then instantiate the class, and finally possibly run a class method (all running thus within the scope of application_top.php)

In terms of the control array we have the following entries to help us.

 $autoLoadConfig[0][] = array('autoType'=>'class',
                               'loadFile'=>'shopping_cart.php');
 $autoLoadConfig[30][] = array('autoType'=>'classInstantiate',
                               'className'=>'cache',
                               'objectName'=>'zc_cache');
 $autoLoadConfig[80][] = array('autoType'=>'classInstantiate',
                               'className'=>'shoppingCart',
                               'objectName'=>'cart',
                               'checkInstantiated'=>true,
                               'classSession'=>true);
 $autoLoadConfig[120][] = array('autoType'=>'objectMethod',
                               'objectName'=>'navigation',
                               'methodName' => 'add_current_page');

Taking these options one by one

Where autotype=>‘class’ all we are really doing here is ‘including’ the ‘loadFile’. However, in this case we draw the file from the includes/classes (DIR_WS_CLASS) directory.

Where autotype=>‘classInstantiate’ executes code of the form objectName = new className();

An example based on the code above is

 $zc_cache = new cache();

One corollary to this is that we may need to instantiate a class that is bound to a session, like the shopping_cart class. In this case as from the example above we get

$_SESSION['cart'] = new shoppingCart();

and in fact we take that one step further, Normally we only want to instantiate a session object if it is not already a session object. In this case we take advantage of the ‘checkInstantiated’ property, which would generate code:

if (!$_SESSION['cart']) {
   $_SESSION['cart'] = new shoppingCart();  
 }  

The final example, where autotype-‘objectMethod’ shows how to run a class method within application_top.php. At the time of writing there is no provision for passing method parameters, so the code generated would be (based on the example above):

$navigation->add_current_page();

Notes on admin autoloaders

The goal of v1.3.x is to eventually remove and refactor all functions into classes. Further, these classes will be common between admin and catalog code.

However this presents a problem with the autoloading of class files. Currently the autoloader code, will default to loading a class from the catalog includes/classes directory rather than admin/includes/classes.

To provide for that interim period where we still need to to load admin class files from admin/includes/classes, we provide an extra option to the ‘autotype’=>class option.

consider this

 $autoLoadConfig[0][] = array('autoType'=>'class',
                              'loadFile'=>'class.base.php');

if this is used in an admin autoloader it will load the class from the catalog classes directory. For the base class this is fine as the code can be shared between catalog and admin. However, at the moment the split_page_results class is different between admin and catalog. So in order to load an admin-specific class we use:

 $autoLoadConfig[0][] = array('autoType'=>'class',
                              'loadFile'=>'split_page_results.php',
                              'classPath'=>DIR_WS_CLASSES);

Overriding/Extending the system autoloader

There are two ways of overriding/extending the inbuilt auto_loader and thus affecting what happens during the loading of application_top.php.

The usual method would be to simply add a new file to the /includes/auto_loader/ directory. The file you add here should have start with config and have a .php extension (ie: config.yourapp_name.php), and should contain one or more control array definitions. This is the recommended method to use for adding code to be executed within application_top.php, and allows contribution authors to customise the code here in a way that will be generally unaffected by system upgrades.

Additionally, within the /includes/auto_loader/ directory is another directory called overrides. This can be used to override any autoloader file in the /includes/auto_loader/ directory. For example, the main autoloader file used in Zen Cart is config.core.php. If a file called config.core.php is placed in the overrides directory, this will be used instead of the original.

init_scripts and application_top.php

Introduction

The initSystem allows you to automate the including/requiring of files and to automate the loading/instantiating of classes. However we still also need to be able to run some procedural code. We also want to allow 3rd parties to override that procedural code. The init_scripts allow us to do this.

init_scripts

There are currently 18 init_scripts in the base 1.3.0 release. These init_scripts are in the includes/init_includes directory.

*   init_add_crumbs.php (Responsible for initialising the Breadcrumb)
*   init_cart_handler.php (Responsible for handling Cart actions)
*   init_category_path.php (Responsible for initialising Category Paths)
*   init_currencies.php (Responsible for initialising the Currencies Sub-System)
*   init_customer_auth.php (Responsible for checking customer status, either thru Down for Maintenance or the Approval level)
*   init_database.php (Responsible for initialising the DB layer)
*   init_db_config_read.php (Responsible for reading configuration data from database)
*   init_file_db_names.php (Responsible for loading File and Database tablename Defines)
*   init_general_funcs.php (Responsible for loading general functions from the includes/functions directory as well as the extra_functions folder)
*   init_gzip.php (Responsible for loading Gzip output-buffering functions)
*   init_header.php (Responsible for running page-header procedures)
*   init_languages.php (Responsible for loading multiple-language support sub-system)
*   init_sanitize.php (Responsible for loading input-sanitising code)
*   init_sefu.php (Responsible for loading code to provide search-engine-friendly URLs)
*   init_sessions.php (Responsible for loading Session code)
*   init_special_funcs.php (Responsible for loading specialized but necessary functions)
*   init_templates.php (Responsible for initialising the template System and activating template-specific language-content defines)
*   init_tlds.php (Responsible for setting Top Level Domain Variables)

Overriding init_scripts

It is very simple to override a core init_script. The directory includes/init_includes contains a directory called overrides. If I wanted to override the includes/init_includes/init_sessions.php script then I would simply create a file called init_sessions.php in the includes/init_includes/overrides directory.

Procedural code in application_top.php

Despite the use of the autoloader system, there is still a little procedural code left in application_top.php; although most of this procedural code is given over to processing autoloaders themselves.

Below is the code from the catalog includes/application_top.php. Note: I have removed all documentation tags for clarity

define('DEBUG_AUTOLOAD', false);
define('IS_ADMIN_FLAG', false);
define('PAGE_PARSE_START_TIME', microtime());
@ini_set("arg_separator.output","&");
if (file_exists('includes/local/configure.php')) {
  include('includes/local/configure.php');
}
if (defined('STRICT_ERROR_REPORTING') && STRICT_ERROR_REPORTING == true) {
  error_reporting(E_ALL);
} else {
  error_reporting(E_ALL & ~E_NOTICE);
}
if (file_exists('includes/configure.php')) {
  include('includes/configure.php');
} else {
  header('location: zc_install/index.php');
}
if (!is_dir(DIR_FS_CATALOG.'/includes/classes'))  header('location: zc_install/index.php');
if ($za_dir = @dir(DIR_WS_INCLUDES . 'extra_configures')) {
  while ($zv_file = $za_dir->read()) {
    if (preg_match('/\.php$/', $zv_file) > 0) {
      include(DIR_WS_INCLUDES . 'extra_configures/' . $zv_file);
    }
  }
} 
$loader_file = 'config.core.php';
$base_dir = DIR_WS_INCLUDES . 'auto_loaders/';
if (file_exists(DIR_WS_INCLUDES . 'auto_loaders/overrides/' . $loader_file)) {
  $base_dir = DIR_WS_INCLUDES . 'auto_loaders/overrides/';
}
include($base_dir . $loader_file);
if ($loader_dir = dir(DIR_WS_INCLUDES . 'auto_loaders')) {
  while ($loader_file = $loader_dir->read()) {
    if ((preg_match('/^config\./', $loader_file) > 0) && (preg_match('/\.php$/', $loader_file) > 0)) {
      if ($loader_file != 'config.core.php') {
        $base_dir = DIR_WS_INCLUDES . 'auto_loaders/';
        if (file_exists(DIR_WS_INCLUDES . 'auto_loaders/overrides/' . $loader_file)) {
          $base_dir = DIR_WS_INCLUDES . 'auto_loaders/overrides/';
        }
        include($base_dir . $loader_file);
      }
    }
  }
}
if (( (!file_exists('includes/configure.php') && !file_exists('includes/local/configure.php')) ) || (DB_TYPE == `) ||    (!file_exists('includes/classes/db/' .DB_TYPE . '/query_factory.php'))) {`
  header('location: zc_install/index.php');
  exit;
}
require('includes/autoload_func.php');
require(DIR_WS_INCLUDES . 'counter.php');
$customers_ip_address = $`SERVER['REMOTE_ADDR'];
if (!isset($_SESSION['customers_ip_address'])) {
  $_SESSION['customers_ip_address'] = $customers_ip_address;
}

Last modified May 22, 2020 by Scott C Wilson (b3b576e).