Developers API Tutorials

From Zen Cart(tm) Wiki
Revision as of 23:09, 15 March 2006 by Wilt (talk | contribs)
Jump to: navigation, search

Tutorials

InitSystem

initSystem Introduction

Why initSystem ??

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

Zen Cart uses a (non Object Oriented) page controller pattern to decide the scripts run, based on HTTP_GET parameters. The most important of these being the 'main_page' HTTP_GET parameter. Dependant 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 /inlcudes/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 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 3rd party developers to gain access to 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 neeeded globally by any page needed to be initialised here.

From a customisation perspective this was a bad thing. If 3rd 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 over written, and any customisations would be lost.

Zen Cart attempted to mitigate this by providing certain override directories where extra data files/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 addittion 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 developers control. Futhermore some method of loading and invoking classes was also required.

v1.3.0 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 3rd party developers to 'hook' into application_top.php and be confident that any future code upgrades will not affect their own code.

application_top.php - Breakpoints

In Zen Cart v1.3.0, 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. The important point is to recognise that at each beakpoint 3rd party code by adding to the control array, can also load functions, load classes, initialise classes, run a class method, load a (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 1.3.0 we use a file called config.core.php as the main file for governing application_top.php, 3rd party developers can add their own control arrays. 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 2 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. lets 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_scripts directory. Each of these contain 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 later. For now, to load an init_script we use the following control array structure.

 $autoLoadConfig[] = 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 within 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 we, in this case draw the file from the includes/classes (DIR_WS_CLASS) directory.

Where autotype='instantiate' execute 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, Noramlly 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.0 is to eventually remove and refactor all function 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 catelog 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 a admin specific class we do

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

Overriding/Extending the system autoloader

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

The ususal method would be to simply add a new file to the /includes/auto_loader/ directory. The file you add here should have an .php extension and should contain 1 or more control array definitions.

This is the recommended method to use for adding code to be executed witin application_top.php, and allows contribution authors to customise the code here in a way that will be unaffected by system upgrades.

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. init_scripts allow us to do this.

init_scripts

There are currently 17 init_scripts in the base 1.3.0 release. These init_scripts are in the includes/init_includes direcrtory.

  • init_add_crumbs.php (Responsible for initialising the Breadcrumb)
  • init_cart_handler.php (Responsible for handling Cart actions)
  • init_category_path.php (Reponsible for initialising Category Paths)
  • init_currencies.php (Responsible for initialising the Currencies Sub-System)
  • init_customer.php (Responsible for checking customer status, either thru down for maintenance or the Approval level)
  • init_database.php (Resposible for initialising the DB layer)
  • init_db_config_read.php (Responsible for reading SB configuration data)
  • init_file_db_names.php (Responsibel for loading File and Dateabase tablename Defines)
  • init_general_funcs.php (Resposible for loading general functions from the includes/functions directory)
  • init_gzip.php (Responsible for loading Gzip functions)
  • init_languages.php (Responsible for loading language based defines)
  • init_sanitize.php (Responsible for loading inout 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 funcions)
  • init_templates.php (Responsible for initilaising the template System)
  • init_tlds.php (Responsible for setting Top Level Domain Variables)

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');
define('WARN_INSTALL_EXISTENCE', 'true');
define('WARN_CONFIG_WRITEABLE', 'true');
define('WARN_SESSION_DIRECTORY_NOT_WRITEABLE', 'true');
define('WARN_SQL_CACHE_DIRECTORY_NOT_WRITEABLE', 'true');
define('WARN_SESSION_AUTO_START', 'true');
define('WARN_DOWNLOAD_DIRECTORY_NOT_READABLE', 'true');
define('WARN_DATABASE_VERSION_PROBLEM','true');
// get customers unique IP that paypal does not touch
$customers_ip_address = $_SERVER['REMOTE_ADDR'];
if (!isset($_SESSION['customers_ip_address'])) {
  $_SESSION['customers_ip_address'] = $customers_ip_address;
}

Overriding init_scripts

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

Observer Class

DB Class