Migrate Module to Component

From Cloudrexx Development Wiki
Jump to: navigation, search

Introduction

Contrexx 3.1 did introduce the Component-Framework. A quick explanation of what a Component is, can be found in the article New Component. To put it short, a Component is basically the architectural successor of what used to be a Module in Contrexx.

The Component-Framework comes with a Legacy-mode (implemented by Cx\Core\Core\Controller\LegacyComponentHandler), which provides a backwards-compatibility-layer to run older Modules that are based on the previous (Contrexx 3.0 and older) architectural structure (see article about the old module architecture).

It is noted here that at some point in a future release of Contrexx the Legacy-mode will be removed.

This article is a step-by-step guide on how a Module (old architecture) can be migrated to a Component (new architecture) according to the specification of Contrexx v4.

Migration Guide

1. Register as a Component

The first step in the migration process is to register the Module as a Component. This is done by adding a new entry to the database table contrexx_component. Set the values according to the following explanation:

id
This must be set to Module-ID of the module. The ID for each module can be found in the database table contrexx_modules.
name
This must be set to the same name that has been set for the Module in the database table contrexx_modules.
Important: In case the name of the Module varies from the name of the Module's folder in the file system, then either the Module's name must be adjusted to use the same name used in the file system or vise versa.
Note: In case the name of the Module has to be changed, don't forget to adjust all library-calls, file-system usages and URL requests/redirections to the Module within the Module's own code as well as within the whole source code of Contrexx.
type
This must be set to the same type that has been defined in the database table contrexx_modules by the attribute is_core.
If attribute is_core is set to 1, then type must be set to core_module, otherwise to module. If Module is actually a core-component, set type to core.

2. Setup component folder

Create the folder /Controller within the Module's folder.

I.e.: /modules/shop/Controller

Then, as a general practice, move the files index.class.php and admin.class.php, as well as other PHP files that are located in your Module's folder into the new folder /Controller.

3. Setup template folder

All template files are to be stored in the directory /View/Template of the Component's folder. If this directory does not yet exist in the Module's folder, it has to be created now.

3.1 Backend

The storage location for the backend templates is in the sub-directory /View/Template/Backend of the Component's folder. If the backend templates are still located in the folder /templates they have to be moved to the new storage location /View/Template/Backend.

3.2 Frontend

The storage location for the frontend templates is in the sub-directory /View/Template/Frontend of the Component's folder.

3.3 Generic

The storage location for generic templates is in the sub-directory /View/Template/Generic of the Component's folder.

Generic templates are templates that are used for decorating GUI elements. I.e. the templates used for the _Toolbar_ of the _Frontend-Editing_ are such generic templates.

4. Put into namespace

As the old Module architecture did not make any use of namespaces, it is most likely that the Module you're about to convert to a Component is not located within a namespace yet. If your Module is already within a namespace, you might need to adjust the namespace according to the new file structure of the Component (i.e. new location of classes in folder /Controler).

All PHP-classes of your Module must now be put into the new Component's namespace. The common format of the namespace is as follows:

Cx\[Core|Core_Modules|Modules]\{COMPONENT-NAME}\Controller Please refer to the article Namespaces for a complete understanding of the conventions used in Contrexx.

The namespace definition has to be put into each PHP-class of your Module. An example of a namespace definition follows:

namespace Cx\Modules\Shop\Controller;

After all PHP-classes have been put into the proper namespace, the source code (PHP-code) of the Module must most likely be adjusted to the new namespace location. In general, globally used classes within the Module must be put into the global namespace. I.e. a common use case would be the usage of the Permission-class. A code-snippet might look like the following:

Permission::checkAccess(20, 'static');

This line of code must now be extended by adding a leading backslash "\" in front of the class Permission to ensure it's continuous functionality within the new namespace of the Module. The adjusted code will then look like the following:

\Permission::checkAccess(20, 'static');

5. Create Component Controller

In the last step you will have to create a ComponentController that will act as a component-wrapper for the existing Module. This will allow you to integrate your Module as a Component without having it completely refactored. Use the following ComponentController-class template and store it as ComponentController.class.php in the newly created folder /Controller of your Module/Component:

<?php
/**
 * Main controller for {COMPONENT-NAME}
 * 
 * @copyright   {COPYRIGHT-HOLDER}
 * @author {YOUR-NAME} <{YOUR-E-MAIL}>
 * @package contrexx
 * @subpackage {SUBPACKAGE}
 */

namespace {NAMESPACE};

/**
 * Main controller for {COMPONENT-NAME}
 * 
 * @copyright   {COPYRIGHT-HOLDER}
 * @author {YOUR-NAME} <{YOUR-E-MAIL}>
 * @package contrexx
 * @subpackage {SUBPACKAGE}
 */
class ComponentController extends \Cx\Core\Core\Model\Entity\SystemComponentController {
    public function getControllerClasses() {
        // Return an empty array here to let the component handler know that there
        // does not exist a backend, nor a frontend controller of this component.
        return array();
    }

     /**
     * Load your component.
     * 
     * @param \Cx\Core\ContentManager\Model\Entity\Page $page       The resolved page
     */
    public function load(\Cx\Core\ContentManager\Model\Entity\Page $page) {
        switch ($this->cx->getMode()) {
            case \Cx\Core\Core\Controller\Cx::MODE_FRONTEND:
                {FRONTEND_CODE_LOAD}
                break;

            case \Cx\Core\Core\Controller\Cx::MODE_BACKEND:
                $this->cx->getTemplate()->addBlockfile('CONTENT_OUTPUT', 'content_master', 'content_master.html');
                $cachedRoot = $this->cx->getTemplate()->getRoot();
                $this->cx->getTemplate()->setRoot($this->getDirectory() . '/View/Template/Backend');

                {BACKEND_CODE_LOAD}

                $this->cx->getTemplate()->setRoot($cachedRoot);
                break;

            default:
                break;
        }
    }

    /**
     * Do something before resolving is done
     * 
     * @param \Cx\Core\Routing\Url                      $request    The URL object for this request
     */
    public function preResolve(\Cx\Core\Routing\Url $request) {
        switch ($this->cx->getMode()) {
            case \Cx\Core\Core\Controller\Cx::MODE_FRONTEND:
                {FRONTEND_CODE_PRE_RESOLVE}
                break;

            case \Cx\Core\Core\Controller\Cx::MODE_BACKEND:
                $this->cx->getTemplate()->addBlockfile('CONTENT_OUTPUT', 'content_master', 'content_master.html');
                {BACKEND_CODE_PRE_RESOLVE}
                break;

            default:
                break;
        }
    }

    /**
     * Do something after resolving is done
     * 
     * @param \Cx\Core\ContentManager\Model\Entity\Page $page       The resolved page
     */
    public function postResolve(\Cx\Core\ContentManager\Model\Entity\Page $page) {
        switch ($this->cx->getMode()) {
            case \Cx\Core\Core\Controller\Cx::MODE_FRONTEND:
                {FRONTEND_CODE_POST_RESOLVE}
                break;

            case \Cx\Core\Core\Controller\Cx::MODE_BACKEND:
                $this->cx->getTemplate()->addBlockfile('CONTENT_OUTPUT', 'content_master', 'content_master.html');
                {BACKEND_CODE_POST_RESOLVE}
                break;

            default:
                break;
        }
    }

    /**
     * Do something before content is loaded from DB
     * 
     * @param \Cx\Core\ContentManager\Model\Entity\Page $page       The resolved page
     */
    public function preContentLoad(\Cx\Core\ContentManager\Model\Entity\Page $page) {
        switch ($this->cx->getMode()) {
            case \Cx\Core\Core\Controller\Cx::MODE_FRONTEND:
                {FRONTEND_CODE_PRE_CONTENT_LOAD}
                break;

            case \Cx\Core\Core\Controller\Cx::MODE_BACKEND:
                $this->cx->getTemplate()->addBlockfile('CONTENT_OUTPUT', 'content_master', 'content_master.html');
                {BACKEND_CODE_PRE_CONTENT_LOAD}
                break;

            default:
                break;
        }
    }

    /**
     * Do something after content is loaded from DB
     * 
     * @param \Cx\Core\ContentManager\Model\Entity\Page $page       The resolved page
     */
    public function postContentLoad(\Cx\Core\ContentManager\Model\Entity\Page $page) {
        switch ($this->cx->getMode()) {
            case \Cx\Core\Core\Controller\Cx::MODE_FRONTEND:
                {FRONTEND_CODE_POST_CONTENT_LOAD}
                break;

            case \Cx\Core\Core\Controller\Cx::MODE_BACKEND:
                $this->cx->getTemplate()->addBlockfile('CONTENT_OUTPUT', 'content_master', 'content_master.html');
                {BACKEND_CODE_POST_CONTENT_LOAD}
                break;

            default:
                break;
        }
    }
}

Replace the placeholders in the above template according to the following explanations:

Placeholder Description Example
{COMPONENT-NAME} Name of the module/component FrontendEditing
{COPYRIGHT-HOLDER} Copyright holder of the module/component CONTREXX CMS - COMVATION AG
{YOUR-NAME} Your name, First- and Lastname John Smith
{YOUR-E-MAIL} Your e-mail address john.smith@example.com
{SUBPACKAGE} The phpDocumentor subpackage name. Written in lower-case only. coremodule_frontendediting
{NAMESPACE} The namespace of the ComponentController. The common format of the namespace is as follows:

Cx\[Core|Core_Modules|Modules]\{COMPONENT-NAME}\Controller

Cx\Core_Modules\FrontendEditing\Controller
{FRONTEND_CODE_LOAD} Paste the PHP code of the variable $this->exceptions['frontend']['load'] located in file core/Core/Controller/LegacyComponentHandler.class.php Notes:

If for a specific event (load, preResolve, postResolve, preContentLoad or postContentLoad) neither a frontend, nor a backend code-snippet is present in the variable $this->exceptions, then the whole method in the above template can be removed from the ComponentController-class.

Due to the fact that the Modules of the old architecture were not organized by namespaces, their main controller-class had to be included manually in the LegacyComponentHandler. Such an inclusion looks like the following code:

/** @ignore */
if (!$cl->loadFile(ASCMS_MODULE_PATH.'/shop/index.class.php'))
    die($_CORELANG['TXT_THIS_MODULE_DOESNT_EXISTS']);

As the Module has been put into a proper namespace in step 3, the above code-snippets to manually include the required PHP-classes are now obsolete and can be removed in the new ComponentController-class. Additionally, also related to the change of putting the Module into a proper namespace, the object-instantiations in the code-snippets must be adjusted accordingly. The object instantiation of a Module looks usually like the following:

$objShopManager = new \shopmanager();
$objShopManager->getPage();

To comply with the new namespace, the backslash "\" to address the Module's class name must be removed in the object instantiation call. The adjusted code would then look like the following:

$objShopManager = new shopmanager();
$objShopManager->getPage();


Important:

All code-snippets related to the Module/Component that have been moved to the ComponentController must be removed from the variable $this->exceptions (including the associated array-key). After the migration, the variable $this->exceptions must not contain any code-snippets of the converted Module/Component anymore.

{BACKEND_CODE_LOAD} Paste the PHP-snippet of the variable $this->exceptions['backend']['load'] located in file core/Core/Controller/LegacyComponentHandler.class.php
{FRONTEND_CODE_PRE_RESOLVE} Paste the PHP-snippet of the variable $this->exceptions['frontend']['preResolve'] located in file core/Core/Controller/LegacyComponentHandler.class.php
{BACKEND_CODE_PRE_RESOLVE} Paste the PHP-snippet of the variable $this->exceptions['backend']['preResolve'] located in file core/Core/Controller/LegacyComponentHandler.class.php
{FRONTEND_CODE_POST_RESOLVE} Paste the PHP-snippet of the variable $this->exceptions['frontend']['postResolve'] located in file core/Core/Controller/LegacyComponentHandler.class.php
{BACKEND_CODE_POST_RESOLVE} Paste the PHP-snippet of the variable $this->exceptions['backend']['postResolve'] located in file core/Core/Controller/LegacyComponentHandler.class.php
{FRONTEND_CODE_PRE_CONTENT_LOAD} Paste the PHP-snippet of the variable $this->exceptions['frontend']['preContentLoad'] located in file core/Core/Controller/LegacyComponentHandler.class.php
{BACKEND_CODE_PRE_CONTENT_LOAD} Paste the PHP-snippet of the variable $this->exceptions['backend']['preContentLoad'] located in file core/Core/Controller/LegacyComponentHandler.class.php
{FRONTEND_CODE_POST_CONTENT_LOAD} Paste the PHP-snippet of the variable $this->exceptions['frontend']['postContentLoad'] located in file core/Core/Controller/LegacyComponentHandler.class.php
{BACKEND_CODE_POST_CONTENT_LOAD} Paste the PHP-snippet of the variable $this->exceptions['backend']['postContentLoad'] located in file core/Core/Controller/LegacyComponentHandler.class.php

6. Verify Component

By completing the previous step, the conversion to a Component has been completed. Your newly created Component (formerly a Module) should now be working and getting properly loaded by the ComponentHandler.

If for any reason your Component does not work, it is advised to activate the debugging and follow the debugging output instructions/messages on how to resolve the issue(s).