Skip to content

LC modular architecture

beatnbite edited this page Mar 9, 2012 · 4 revisions

LC and modular design

One of LiteCommerce's primary concepts is the invariability of its files. This makes it much easier to implement the automatic upgrade routine, find errors in the code, etc.

Since nobody is allowed to modify the PHP code, as well as the template code, the customization of LC is to be done via modules. A module is a set of PHP files, templates, CSS/JS files, image files, etc. In the compressed form, a module is a TAR archive; in the decompressed form it is a set of folders with files within the LC root folder. Their structure is described below.

PHP code customization principles

Since nobody is allowed to modify the code, some other ways to customize the algorithms must be used. The way that has been chosen for that is one of the basic principles of object oriented programming, inheritance.

Once you have inherited your class from the core, you can override its methods and modify their behavior. Here is a functionality modification example, user class: \XLite\Module\CDev\MarketPrice\View\Details extends the core class \XLite\View\Product\Details\Customer\Page\APage and registers its own CSS file:

namespace XLite\Module\CDev\MarketPrice\View;

/**
* Details
*
* @see   ____class_see____
* @since 1.0.0
*/
abstract class Details extends \XLite\View\Product\Details\Customer\Page\APage <...>
{
    /**

    * Register CSS files
    *
    * @return array
    * @see    ____func_see____
    * @since  1.0.0
    */
    public function getCSSFiles()
    {
        $list = parent::getCSSFiles();
        $list[] = 'modules/CDev/MarketPrice/style.css';

        return $list;
    }
}

The problem is that the core code uses the name of the base class \XLite\View\Product\Details\Customer\Page\APage, not the extending one \XLite\Module\CDev\MarketPrice\View\Details. The solution to this is given in the next chapter.

Decorator

When executing, LiteCommerce doesn't actually use the core code; instead it uses cache built for that code, which are copies of the PHP files in the var/run/ directory. Decorator is the utility used for building the cache.

Decorator's primary purpose is to support the extensibility of the functionality through class inheritance. In particular, solving the problem described in the previous chapter. It renames classes in the inheritance chains so that the application begins to use the extending class too. Suffix “Abstract” is appended to the base class name, and an empty dummy class with the original base class name is inherited from the custom class. Example:

  1. Core code: SomeCoreClass (base class) -> SomeCustomClass extends SomeCoreClass (custom class)

  2. Generated cache: SomeCoreClassAbstract (renamed base class) -> SomeCustomClass extends SomeCoreClassAbstract (custom class) -> SomeCoreClass extends SomeCustomClass (blank dummy class)

Thus, the functionality of the custom class gets into the base (modifyable) class.

To indicate that a class is to get in the decorator chain, use the \Xlite\Base\IDecorator interface:

/**
* Details
*
* @see   ____class_see____
* @since 1.0.0
*/
abstract class Details extends \XLite\View\Product\Details\Customer\Page\APage implements \XLite\Base\IDecorator

Module controllers

Here is a moment specific to controllers declared in a module. It applies only to new controllers and does not affect the decorating classes.

The LC navigation system is based on matching the query parameter target to the controller class name. In other words, a request like admin.php?target=product_list launches the controller \XLite\Controller\Admin\ProductList.

This may cause a problem if the module wants to create its own controller, as according to the naming convention, applicable to autoloading, classes of all the controllers defined by a module must have the format \XLite\Module\<AuthorName>\<ModuleName>\Controller\{Admin|Customer}\<ControllerName>. Obviously, matching a target and a class with a name of this kind is a bit of a problem.

In the current implementation, this is solved by moving the module's controllers to the folder classes/XLite/Controller. The moving and renaming take place during the rebuilding of the cache; they do not affect the source files and are completely transparent to developer. Just remember that in the cache module controller files may reside outside their module's main directory.

Module dependencies

A module can define its dependency list, which is the list of modules required for its proper operation. A module cannot be enabled while at least one of its dependencies is not remains unsatisfied (the module is instaleld and enabled). Dependencies are set in the getDependencies() function of the module's main class (see below).

Module priorities

The same core class can extend several modules. The principle remains the same, only the decorator chains become longer:

  1. Core code: SomeCoreClass -> SomeCustomClassModule1 extends SomeCoreClass SomeCoreClass -> SomeCustomClassModule2 extends SomeCoreClass

SomeCoreClass -> SomeCustomClassModuleN extends SomeCoreClass

  1. Generated cache: SomeCoreClassAbsract -> SomeCustomClassModule1 extends SomeCoreClassAbstract -> SomeCustomClassModule2 extends SomeCustomClassModule1 ... -> SomeCoreClass extends SomeCustomClassModuleN

The position (priority) of a class in a chain is determined by Decorator based on the dependencies of the module. Thus, if Module A depends on Module B, Module A's classes will always go “above” Module B's classes in the chains. This ensures that all the Module B's functionality will be available to Module A.

If a module does not have dependencies, its classes can be placed at any position in the inheritance chain.

Template lists

All templates in LC are arranged into lists. The list approach is used for customizing templates, as they, just as PHP files, may not be modified.

The belonging to a list is determined by the @ListChild tag. This tag can be specified in either widget's PHP class or the actual template. Thus, templates can be inserted into a list, deleted from it or replaced in it.

Example: “Product details page” widget:

namespace XLite\View\Product\Details\Customer\Page;

/**
* Main
*
* @see   ____class_see____
* @since 1.0.0
*
* @ListChild (list="center", zone="customer")
*/
class Main extends \XLite\View\Product\Details\Customer\Page\APage

Example template (skins/default/en/modules/CDev/MarketPrice/details.tpl):

{**
* Product market price
*
<...>
*
* @ListChild (list="product.details.page.info", weight="45")
* @ListChild (list="product.details.quicklook.info", weight="45")
*}

The weight (weight parameter) determines the position of the template on the list. Thus, in the last example, the template that displays market price embeds in 2 pages: Product Details Page and Quick Look Popup, taking a place between item 40 and item 50.

Module structure

A module's files can be located in the following folders:

  • classes/XLite/Module/<AuthorName>/<ModuleName>/ (PHP-файлы, main folder)
  • skins/common/en/modules/<AuthorName>/<ModuleName>/ (common templates)
  • skins/default/en/modules/<AuthorName>/<ModuleName>/ (customer zone templates)
  • skins/admin/en/modules/<AuthorName>/<ModuleName>/ (admin zone templates)
  • skins/mail/en/modules/<AuthorName>/<ModuleName>/ (message templates)

It is recommended that the folder structure in the main directory ("classes/XLite/Module/<AuthorName>/<ModuleName>/") repeats the structure of “classes/XLite/”.

Particularly noteworthy are the following two files in the module's main directory:

  • Main.php. The main class that describes the module. Mandatory.
  • icon.png. If the file is available, it will be used as the module's icon.

The process of creating a module consists of three parts:

  • Installing LC
  • Creating files on the file system, within the root folder of the installed online store. Any of the above listed folders, except the main one, is optional. NB: All changes are to be accompanied by rebuilding the cache.
  • Parking the module. This requires switching to the so-called “developer” mode (option [performance]developer_mode = On) and then go to the admin zone. The “Pack it” button will appear on the “Add-ons” -> “Manage add-ons” page. The module must be enabled.

Module and author names

You should distinguish two types of module names and author names.

The first pair is the internal names, in the essence, names of the folders on the file system (and, respectively, components of name spaces and class names). These are what the above chapter mentions: <AuthorName>, <ModuleName>. Such names must meet the “\w+” pattern; Pascal Case notation is recommended. Example: “CDev” and “MarketPrice”.

The second pair is the names that would appear in the interface (readable names). These carry out a purely informative function, are not bound to folder or class names and may have any format. They are set within functions of the module's main class (see the next chapter).

Also, there is a term that means the “full” or “actual” module name (Actual Name). These are the internal author and module names, linked with a backslash. Example: “CDev\Marketplace”.

Main class

The main class with the description of the module is located in classes/XLite/Module/<AuthorName>/<ModuleName>/Main.php. The class must be inherited from \XLite\Module\AModule

This is an abstract class; it contains static methods only. The complete list of those can be found in the parent class.

The class must define 4 methods:

  • getModuleName(). Returns a readable module name. Example: “Market price”.

  • getAuthorName(). Returns a readable author name. Example: “Creative Development LLC”

  • getDescription(). Returns module description. Example: “Add support for the product market price.”

  • getMinorVersion(). Returns module minor version (revision) – an integer, greater than or equal to zero. Example: “3”.

It is highly recommended to redefine the getMajorVersion() method, which returns module major version. This version, in the essence, is the major version of the core this module is compatible with. Example: “1.0”.

Other methods (optional):

  • getAuthorPageURL(). Author page URL.

  • getPageURL(). Module description page URL.

  • showSettingsForm(). Returns a Boolean value, whether to show the module configuration form. By default – false.

  • getSettingsForm(). Used only if the above method returns true. Override it only when a non-standard settings form is necessary (otherwise, the page URL will be generated automatically). Thus, for example, the “CDev\AustraliaPost” module uses its own settings page controller - admin.php?target=aupost

  • getIconURL(). Module icon URL. Still, the recommended way is using the “icon.png” file in the module's main folder rather than overriding this method.

  • getDependencies(). Returns module dependencies (if any) array. Dependencies are to be returned as an one-dimensional array of full module names; e.g., array(‘CDev\ProductOptions’, ‘CDev\AustraliaPost’)

  • init(). The function is called for each active module when the application starts. It can be used for some service actions.

YAML file

Sometimes a module needs to add its data to the database. This can be done using the install.yaml file, located in the module's main folder. Certainly, having that file is not mandatory. For example, the module “Cdev\Bestsellers” has that file.

The structure of the file must match the structure of the modules. The YAML format is not going to be covered here.

Note: To upload data from this file, a module must be installed, i.e. packed and uploaded via the form on the “Manage add-ons” page. Data is not uploaded to the database when creating a file, rebuilding cache or enabling/disabling a module.

Settings page

A module may have a settings page. As it has been mentioned, this is determined by the main class method, Main::showSettingsForm(). The page is available by the “Settings” link, located by the module on the “Addons” -> “Manage add-ons” page.

Normally, adding module settings does not require any additional classes or templates. It is only necessary to add the required records for the Config model. That is done using the install.yaml file. Here is a sample from the “Cdev\Bestsellers” module:

XLite\Model\Config:
- name: bestsellers_menu
    category: CDev\Bestsellers
    type: select
    orderby: 115
    value: 1
    translations:
    - code: en
        option_name: Display the list of bestsellers in
- name: number_of_bestsellers
    category: CDev\Bestsellers
    type: text
    orderby: 115
    value: 5
    translations:
    - code: en
        option_name: Number of products in the bestsellers list

The category field must match the full module name, and type may take the following values: '','text','textarea','checkbox','country','state','select','serialized', and 'separator'.

If a more sophisticated settings page is required, one should use the Main::getSettingsForm() function. It returns the module's custom settings page URL. Certainly, in this case you would have to create the page manually: define the controller, the viewer and the template. Particular example of such are available, for example, in the modules “CDev\AustraliaPost” and “CDev\USPS”.

See the next chapter: HOW TO: Creating “MarketPrice” module

Clone this wiki locally