Application Modules - Creating a Simple Module / Step by Step Instructions

These step-by-step instructions will help you to create a simple (basic) module.

Take in account that we use following terms and signs:

[MODULE_NAME] - module name, e.g Blog or Orders
[MODULE_NAME_LC] - module lowercase name, e.g blog or orders
[MODULE_DESCRIPTION] - module text description
[CONTROLLER_NAME] - the main controller for module (module may have few controller), e.g Posts
[CONTROLLER_NAME_LC] - the main controller lowercase name, e.g posts
[MODEL_NAME] - the main model for module (module may have few models), e.g Posts
[PROPERTY_KEY] - property key, e.g. shortcode
[PROPERTY_VALUE] - property value, e.g. {module:blog}
[PROPERTY_NAME] - property name, e.g. Shortcode
[PROPERTY_DESCRIPTION] - property description, e.g. This shortcode allows you to display...
[PRIVILEGE_CODE] - the code of privilege, e.g: menus_add, menus_edit or menus_delete

Content



Step 1. Creating Module Directory.

Create a new directory named [MODULE_NAME_LC] in protected/modules/

Step 2. Creating CHANGELOG File.

Create in protected/modules/[MODULE_NAME_LC]/ a CHANGELOG file with a following content:
Version 0.0.1
----------------------------
Initial release


Step 3. Creating info.xml File.

This file contains information about the new module. It defines the files that need to be installed by the ApPHP Framework installer and specifies configuration parameters for the module. Below the content of this file:

Step 4. Creating Main Component.

Create directory components/ and create there an empty component file, called [MODULE_NAME]Component.php.
<?php
/**
* [MODULE_NAME]Component
*
* PUBLIC:                  PRIVATE
* -----------              ------------------
* prepareTab
* drawShortcode
* 
* STATIC
* -------------------------------------------
* init
* 
*/

class [MODULE_NAME]Component extends CComponent{

    const NL = "\n";

    public static function init()
    {
        return parent::init(__CLASS__);
    }

    /**
     * Prepares [MODULE_NAME] module tabs
     * @param string $activeTab
     */
    public static function prepareTab($activeTab = 'info')
    {
        return CWidget::create('CTabs', array(
            'tabsWrapper'=>array('tag'=>'div', 'class'=>'title'),
            'tabsWrapperInner'=>array('tag'=>'div', 'class'=>'tabs'),
            'contentWrapper'=>array(),
            'contentMessage'=>'',
            'tabs'=>array(
                A::t('[MODULE_CODE]', 'Settings') => array(
                    'href'=>'modules/settings/code/[MODULE_CODE]',
                    'id'=>'tabSettings', 'content'=>'', 'active'=>false,
                    'htmlOptions'=>array('class'=>'modules-settings-tab')
                ),
                A::t('[MODULE_CODE]', '[CONTROLLER_NAME]') => array(
                    'href'=>'[CONTROLLER_NAME]/index', 'id'=>'tabInfo', 'content'=>'',
                    'active'=>($activeTab == 'info' ? true : false)
                ),
            ),
            'events'=>array(
                //'click'=>array('field'=>$errorField)
            ),
            'return'=>true,
        ));
    }

    /**
     * Draws shortcode output
     * @param array $params
     */
    public static function drawShortcode($params = array())
    {
        $output = '';
        // Your code is here ...
        
        return $output;
    }
    
}    


Step 5. Creating Config Files.

There are minimum two (2) types of configuration files, that present in module: Lets see the examples of such files, here the main.php:
<?php

return array(
    // Module classes
    'classes' => array(
        '[MODULE_NAME]Component',
        '[MODEL_NAME]',
    ),
    // Management links
    'managementLinks' => array(
        A::t('[MODULE_CODE]', '[MODULE_NAME]') => '[MODEL_NAME]/manage'
    ),    
);
and here the [MODULE_CODE].php:
<?php

return array(
    // Module components
    'components' => array(
        '[MODULE_NAME]Component' => array('enable' => true, 'class' => '[MODULE_NAME]Component'),
    ),

);    


Step 6. Creating SQL Dump Files.

Each module must have at least two (2) SQL dump files for successfull installation: install.sql and uninstall.sql. As an option you may also create a file for updating update.sql (if you're planning to release updates for your module in the future).

All these files must be placed in data/ directory and will be used by ApPHP Framework and Directy CMF for installation of the module.

Each SQL dump file must include at minimum SQL commands, lets check them.

install.mysql.sql:
INSERT INTO `<DB_PREFIX>modules` (`id`, `code`, `name`, `description`, `version`, `icon`, `show_on_dashboard`, `show_in_menu`, `is_installed`, `is_system`, `is_active`, `sort_order`)
VALUES (NULL, '[MODULE_CODE]', '[MODULE_NAME]', '[MODULE_DESCRIPTION]', '0.0.1', 'icon.png', 0, 0, 1, 0, 1, 0);

INSERT INTO `<DB_PREFIX>module_settings` (`id`, `module_code`, `property_key`, `property_value`, `name`, `description`, `property_type`, `property_source`, `is_required`) VALUES
(NULL, '[MODULE_CODE]', '[PROPERTY_KEY]', '[PROPERTY_VALUE]', '[PROPERTY_NAME]', '[PROPERTY_DESCRIPTION]', 'label', '', '0');

INSERT INTO `<DB_PREFIX>privileges` (`id`, `category`, `code`, `name`, `description`) VALUES (NULL, '[MODULE_CODE]', '[PRIVILEGE_ACTION]', '[PRIVILEGE_NAME]', '[PRIVILEGE_DESCRIPTION]'); 
INSERT INTO `<DB_PREFIX>role_privileges` (`id`, `role_id`, `privilege_id`, `is_active`) VALUES (NULL, 1, (SELECT MAX(id) FROM `<DB_PREFIX>privileges`), 1), (NULL, 2, (SELECT MAX(id) FROM `<DB_PREFIX>privileges`), 1), (NULL, 3, (SELECT MAX(id) FROM `<DB_PREFIX>privileges`), 0);
update.mysql.sql:
# any update, insert, delete, etc. operations related to module changes 
uninstall.mysql.sql:
DELETE FROM `<DB_PREFIX>modules` WHERE `code` = '[MODULE_CODE]';
DELETE FROM `<DB_PREFIX>module_settings` WHERE `module_code` = '[MODULE_CODE]';

DELETE FROM `<DB_PREFIX>role_privileges` WHERE `privilege_id` IN (SELECT id FROM `<DB_PREFIX>privileges` WHERE `category` = '[MODULE_CODE]');
DELETE FROM `<DB_PREFIX>privileges` WHERE `category` = '[MODULE_CODE]';

# delete other tables related to this module


Step 7. Creating Controllers.

Generally each module has at least one (main) controller class (there is only one exception, when you create a module that has no public access at all).

Below you may see the standard definition of the controller class ([CONTROLLER_NAME]Controller.php file).
Remember that this is only a sample code for controller class, more examples available in the module archive dummy_module.zip.
<?php
/**
* Class [CONTROLLER_NAME]Controller
*
* PUBLIC:                  PRIVATE
* -----------              ------------------
* __construct              
* indexAction
*/
class [CONTROLLER_NAME]Controller extends CController
{
    /**
     * Class default constructor
     */
    public function __construct()
    {
        parent::__construct();
        
        // Block access if the module is not installed
		if(!Modules::model()->isInstalled([MODULE_CODE])){
            if(CAuth::isLoggedInAsAdmin()){
                $this->redirect('modules/index');
            }else{
                $this->redirect('index/index');
            }
        }

        // Set meta tags according to active language
        Website::setMetaTags(array('title'=>A::t('[MODULE_CODE]', '[CONTROLLER_NAME] Management')));
    }

    /**
     * Controller default action handler
     */
    public function indexAction()
    {
        //$this->redirect('.../manage');  
    }    
    
}


Step 8. Creating Models.

Like in case of controllers, module has at least one (main) controller class (there is only one exception, when your module doesn't store information at all).

Here the example of the standard model class ([MODEL_NAME].php file).
Remember that this is only a sample code for model class, more examples available in the module archive dummy_module.zip.
<?php
/**
 * Template of [MODEL_NAME] model 
 *
 * PUBLIC:                 PRIVATE
 * -----------             ------------------
 * __construct
 * relations
 *
 * STATIC:
 * ---------------------------------------------------------------
 * model
 *
 */
class [MODEL_NAME] extends CActiveRecord
{

    /** @var string */    
    protected $_table = '[MODEL_TABLE]';

    public function __construct()
    {
        parent::__construct();
    }

    /**
     * Returns the static model of the specified AR class
     */
    public static function model()
    {
        return parent::model(__CLASS__);
    }
    
	/**
     * Used to define relations between different tables in database and current $_table
	 * This method should be overridden
	 */
    protected function _relations()
    {
       /* return array(
        *    'field_name' => array(
        *        self::BELONGS_TO,
        *        'table_name',
        *        'field_name',
        *        'condition'=>'',
        *        'joinType'=>self::LEFT_OUTER_JOIN,
        *        'fields'=>array('name'=>'language_name')
        *    ),
        * );
        */
    }
}


Step 9. Creating Views.

View presentation file must be created for each controller's action (there is only exception when you're planning to use view from another action or perform redirection).

Here the example of the standard view file (manage.php file).
Remember that this is only a sample code for view files, more examples available in the module archive dummy_module.zip.
<?php
    $this->_activeMenu = '[MODULE_CODE]/manage';
    $this->_breadCrumbs = array(
        array('label'=>A::t('[MODULE_CODE]', 'Modules'), 'url'=>'modules/'),
        array('label'=>A::t('[MODULE_CODE]', '[MODULE_NAME]'), 'url'=>'modules/settings/code/[MODULE_CODE]'),
        array('label'=>A::t('[MODULE_CODE]', '[CONTROLLER_NAME] Management'), 'url'=>'[MODULE_CODE]/manage'),
    );    
?>
<h1><?php echo A::t('[MODULE_CODE]', '[CONTROLLER_NAME] Management'); ?></h1>
<div class="bloc">
    <?php echo $tabs; ?>

    <div class="content">
    <?php 
        echo $actionMessage;

        if(Admins::hasPrivilege('modules', 'edit') && Admins::hasPrivilege('[MODEL_CODE]', 'add')){
            echo '<a href="[CONTROLLER_NAME]/add" class="add-new">'.A::t('[MODULE_CODE]', 'Add [CONTROLLER_NAME]').'</a>';
        }
        
        echo CWidget::create('CGridView', array(
            'model'=>'[MODEL_NAME]',
            'actionPath'=>'[CONTROLLER_NAME_LC]/manage',
            'condition'=>'',
            //'defaultOrder'=>array('field_1'=>'DESC'),
            'passParameters'=>true,
            'pagination'=>array('enable'=>true, 'pageSize'=>20),
            'sorting'=>true,
            'filters'=>array(
                'field_1' => array('title'=>'Field 1', 'type'=>'textbox', 'operator'=>'=', 'width'=>'', 'maxLength'=>''),
                'field_2' => array('title'=>'Field 2', 'type'=>'enum', 'operator'=>'=', 'width'=>'', 'source'=>array('0'=>'No', '1'=>'Yes')),
                'field_3' => array('title'=>'Field 3', 'type'=>'datetime', 'operator'=>'=', 'width'=>'80px', 'maxLength'=>'', 'format'=>''),
            ),
            'fields'=>array(
                'field_1' => array('title'=>'Field 1', 'type'=>'concat', 'align'=>'', 'width'=>'', 'class'=>'left', 'headerClass'=>'left', 'isSortable'=>true, 'concatFields'=>array('first_name', 'last_name'), 'concatSeparator'=>', ',),
                'field_2' => array('title'=>'Field 2', 'type'=>'decimal', 'align'=>'', 'width'=>'', 'class'=>'right', 'headerClass'=>'right', 'isSortable'=>true, 'format'=>'american|european'),
                'field_3' => array('title'=>'Field 3', 'type'=>'enum', 'align'=>'', 'width'=>'', 'class'=>'center', 'headerClass'=>'center', 'isSortable'=>true, 'source'=>array('0'=>'No', '1'=>'Yes')),
                'field_4' => array('title'=>'Field 4', 'type'=>'image', 'align'=>'', 'width'=>'', 'class'=>'center', 'headerClass'=>'center', 'isSortable'=>false, 'imagePath'=>'images/flags/', 'defaultImage'=>'', 'imageWidth'=>'16px', 'imageHeight'=>'16px', 'alt'=>''),
                'field_5' => array('title'=>'Field 5', 'type'=>'label', 'align'=>'', 'width'=>'', 'class'=>'left', 'headerClass'=>'left', 'isSortable'=>true, 'definedValues'=>array(), 'format'=>''),
                'field_6' => array('title'=>'Field 6', 'type'=>'link', 'align'=>'', 'width'=>'', 'class'=>'center', 'headerClass'=>'center', 'isSortable'=>false, 'linkUrl'=>'path/to/param/{id}', 'linkText'=>''),
            ),
            'actions'=>array(
                'edit'    => array(
                    'disabled'=>!Admins::hasPrivilege('modules', 'edit') || !Admins::hasPrivilege('[MODEL_CODE]', 'edit'),
                    'link'=>'[CONTROLLER_NAME_LC]/edit/id/{id}', 'imagePath'=>'templates/backend/images/edit.png', 'title'=>A::t('[MODULE_CODE]', 'Edit this record')
                ),
                'delete'  => array(
                    'disabled'=>!Admins::hasPrivilege('modules', 'edit') || !Admins::hasPrivilege('[MODEL_CODE]', 'delete'),
                    'link'=>'[CONTROLLER_NAME_LC]/delete/id/{id}', 'imagePath'=>'templates/backend/images/delete.png', 'title'=>A::t('[MODULE_CODE]', 'Delete this record'), 'onDeleteAlert'=>true
                )
            ),
            'return'=>true,
        ));        
    ?>        
    </div>
</div>


Download Module Code.

Here you may download code examples for "dummy module".

Remember, that this only an example, you may need to add more features and procedures to make your module works as expected.

Download Module ▼