Openbiz Cubi: A Robust PHP Application Framework, Part 2

This entry is part 2 of 2 in the series Openbiz Cubi: A Robust PHP Application Framework

Openbiz Cubi: A Robust PHP Application Framework

In the first part of this series we talked about the development challenges we face and how Openbiz Cubi can help by providing a solid, ready-to-use web application framework. In this part we’ll see how to build our own module and dive a bit deeper into the core architecture of the framework.

Build the First Module

Let’s get to the exciting part – creating a module; we’ll start with a simple customer editing page. On this page users can create, update, and delete customers.

First let’s make a database table customer. The SQL to create the table is:

CREATE TABLE customer (
  id INTEGER NOT NULL AUTO_INCREMENT,
  name VARCHAR(64) NOT NULL,
  description VARCHAR(255) DEFAULT NULL,
  address VARCHAR(255) DEFAULT NULL,
  phone VARCHAR(20) DEFAULT NULL,
  fax VARCHAR(20) DEFAULT NULL,
  status INTEGER DEFAULT NULL,
  create_by INTEGER DEFAULT '1',
  create_time DATETIME DEFAULT NULL,
  update_by INTEGER DEFAULT '1',
  update_time DATETIME DEFAULT NULL,
  PRIMARY KEY (id)
)
ENGINE=InnoDB

The next step to creating a module is to create the XML metadata files for the customer editing page. Here we use gen_meta, the metadata generation command under the cubi/bin/tools directory.

/dev/cubi/bin/tools$ php gen_meta.php Default customer

You can simply press the Enter key to complete the wizard. After the command executes, the following files are generated:

Module configuration file

  • modules/customer/mod.xml

Module DO file

  • modules/customer/do/CustomerDO.xml

Module Form file

  • modules/customer/form/CustomerListForm.xml
  • modules/customer/form/CustomerDetailForm.xml
  • modules/customer/form/CustomerEditForm.xml
  • modules/customer/form/CustomerNewForm.xml
  • modules/customer/form/CustomerCopyForm.xml

Module View file

  • modules/customer/view/CustomerListView.xml

Module Widget files

  • modules/customer/widget/DashboardForm.xml
  • modules/customer/widget/LeftMenu.xml

Module Template files

  • modules/customer/template/detail.tpl
  • modules/customer/template/detail_elementset.tpl
  • modules/customer/template/grid.tpl
  • modules/customer/template/view.tpl

Next we load the newly created module with the load_module command.

/dev/cubi/bin/tools/php$ load_module.php customer
Start loading customer module ...
--------------------------------------------------------
[2013-01-26T17:57:16+08:00] Loading module customer
[2013-01-26T17:57:16+08:00] Install Module customer
[2013-01-26T17:57:16+08:00] Install Module ACL.
[2013-01-26T17:57:16+08:00] Install Module Menu.
[2013-01-26T17:57:16+08:00] Install Module Widget.
[2013-01-26T17:57:16+08:00] Install Module Resource.
[2013-01-26T17:57:16+08:00] Install Module Change Logs.
[2013-01-26T17:57:16+08:00] Copy resource files to /cubi/resources folder.
[2013-01-26T17:57:16+08:00] customer is loaded.

Give admin to access all actions of module 'customer'
--------------------------------------------------------
End loading customer module

Let’s test the module. If you are already logged in to Cubi, log out and log back in again. You should see a new tab named “Customer” appearing in the header section. Click the tab to enter the Customer Dashboard page, then click the Customer Manage link to enter the customer management page. Now we’re able to create, update, search, and delete customers.

Under the Hood

You may wonder without any programming, how does Openbiz Cubi make everything work? In the previous steps, gen_meta created XML files under the customer module folder. Cubi then knows how to interpret these files. Let’s visit the module description file mod.xml first.

Module Description File

We created the customer module with mod.xml under the modules/customer directory.

<?xml version="1.0" standalone="no"?>
<Module Name="customer" Description="customer module" Version="0.1.0" OpenbizVersion="3.0">
 <ACL>
  <Resource Name="customer">
   <Action Name="Access" Description="Access Customer Module Dashboard"/>
  </Resource>
  <Resource Name="customer">
   <Action Name="Access" Description="Access Customer"/>
   <Action Name="Manage" Description="Manage Customer"/>
  </Resource>
 </ACL>
 <Menu>
  <MenuItem Name="CustomerTop" Title="Customer" Description="Customer Description" URL="/customer/dashboard" Parent="" Order="10">
   <MenuItem Name="Customer" Title="Customer" Description="Customer description" URL="" Order="10">
    <MenuItem Name="Customer.List" Title="Customer Manage" Description=""  URL="/customer/customer_list" Order="10"/>
   </MenuItem>  
  </MenuItem>  
 </Menu>
 <Dependency>
  <Module Name="system"/>
 </Dependency>
</Module>

mod.xml contains 3 sections:

  • Access control – the ACL section defines the resources and their actions. These definitions are used to control permissions for given roles.
  • Menu – the Menu section defines the page links in the navigation system (application tabs, breadcrumb, and menus).
  • Dependencies – the Dependency section defines modules that the current module depends on. The depended modules should be installed first.

Usually each module includes its own data models and presentation XML files. The framework has a metadata engine that can then understand the XML and load the data and UI objects on the fly. The framework mainly work with two types of metadata objects:

  • Data objects – Cubi maps physical data stores (such as a database table) to logic objects.
  • Form and view objects – Form objects describe how to present data objects’ data on a block in a page, while view objects define a container of Form objects. In a browser, a view is the same as a web page.

Data Object

Now let’s take a look at the data object XML in modules/customer/do/CustomerDO.xml:

<?xml version="1.0" standalone="no"?>
<BizDataObj Name="CustomerDO" Description="" Class="BizDataObj" DBName="Default" Table="customer" SearchRule="" SortRule="" OtherSQLRule="" Uniqueness="" Stateless="N" IdGeneration="Identity" CacheLifeTime="0" CreateCondition="customer.Manage" UpdateCondition="customer.Manage" DeleteCondition="customer.Manage">
 <BizFieldList>
  <BizField Name="Id" Column="id" Type="Number"/>
  <BizField Name="name" Column="name" Length="64" Required="Y" Type="Text"/>
  <BizField Name="description" Column="description" Length="255" Required="N" Type="Text"/>
  <BizField Name="address" Column="address" Length="255" Required="N" Type="Text"/>
  <BizField Name="phone" Column="phone" Length="20" Required="N" Type="Text"/>
  <BizField Name="fax" Column="fax" Length="20"   Required="N" Type="Text"/>
  <BizField Name="status" Column="status"    Required="N" Type="Number"/>
  <BizField Name="create_by" Column="create_by" Type="Number" ValueOnCreate="{@profile:Id}"/>
  <BizField Name="create_time" Column="create_time"  Type="Datetime" ValueOnCreate="{date('Y-m-d H:i:s')}"/>
  <BizField Name="update_by" Column="update_by" Type="Number" ValueOnCreate="{@profile:Id}" ValueOnUpdate="{@profile:Id}"/>		
  <BizField Name="update_time" Column="update_time" Type="Datetime" ValueOnCreate="{date('Y-m-d H:i:s')}" ValueOnUpdate="{date('Y-m-d H:i:s')}"/>
 </BizFieldList>
 <TableJoins>
 </TableJoins>
 <ObjReferences>
 </ObjReferences>
</BizDataObj>

The XML defines a mapping between the customer table to the CustomerDO object. It also defines certain rules on the objects as well as validation, type, and value of each field.

Form Object

Let’s also take a look at the form object XML in modules/customer/form/CustomerListForm.xml.

<?xml version="1.0" encoding="UTF-8"?>
<EasyForm Name="CustomerListForm" Class="EasyForm" FormType="List" jsClass="jbForm" Title="Customer Management" Description="" BizDataObj="customer.do.CustomerDO" PageSize="10" DefaultForm="Y" TemplateEngine="Smarty" TemplateFile="grid.tpl" Access="customer.Access">
 <DataPanel>
  <Element Name="row_selections" Class="RowCheckbox" Label="" FieldName="Id"/>
  <Element Name="fld_Id" Class="ColumnText" FieldName="Id" Label="Id" Sortable="Y"/>
  <Element Name="fld_name" Class="ColumnText" FieldName="name" Label="Name" DefaultValue="New Customer" Sortable="Y" Link="javascript:">
  <EventHandler Name="fld_name_onclick" Event="onclick" Function="SwitchForm(customer.form.CustomerDetailForm,{@:Elem[fld_Id].Value})"   />
 </Element>
 <Element Name="fld_description" Class="ColumnText" FieldName="description" Label="Description"  Sortable="Y"/>
  <Element Name="fld_address" Class="ColumnText" FieldName="address" Label="Address"  Sortable="Y"/>
  <Element Name="fld_phone" Class="ColumnText" FieldName="phone" Label="Phone" Sortable="Y"/>
  <Element Name="fld_fax" Class="ColumnText" FieldName="fax" Label="Fax" Sortable="Y"/>
  <Element Name="fld_status" Class="ColumnText" FieldName="status" Label="Status" Sortable="Y"/>
 </DataPanel>
 <ActionPanel>
...
  <Element Name="btn_delete" Class="Button" Text="Delete" CssClass="button_gray_m" Access="customer.Manage">
   <EventHandler Name="del_onclick" Event="onclick" EventLogMsg="" Function="DeleteRecord()" ShortcutKey="Ctrl+Delete" ContextMenu="Delete"/>
  </Element>
...
 </ActionPanel>
 <NavPane>
...
 </NavPanel>
 <SearchPane>
...
 </SearchPanel>
</EasyForm>

The XML maps fields from data objects to UI elements. It also defines panels for the logical layout of elements. For each UI element, interaction behavior can be described within its EventHandler section.

Customize Objects

Each Metadata element comes with a Class attribute. In the metadata generated by gen_meta, the attribute is set to a core framework class (BizDataObj for data objects, EasyForm for form objects). Developers can simply replace this to implement special business logic. For example, to add special logic for deleting a customer record, you can create the file /modules/customer/form/CustomerForm.php, and set the Metadata element’s Class attribute to it. The PHP class might look like:

<?php
class CustomerForm extends EasyForm
{
    public function deleteRecord($id = null) {
        // add your logic here
        Parent::deleteRecord($id); // call parent deleteRecord method
    }
}

The Cubi Execution Flow

So what happens when the http://host/cubi/index.php/customer/customer_list is called in the browser?

  1. index.php calls _forward.php to parse the URL. It understands that /customer/customer_list points to cubi/modules/customer/view/CustomerListView.xml based on Cubi’s URL naming convention.
  2. The render method of the view is invoked.
  3. The render method calls the render method of its included form (cubi/modules/customer/form/CustomerListForm.xml).
  4. The render method of the form calls its data object’s data query methods.
  5. The query methods prepare the SQL and executes it against the database.
  6. The form uses the returned dataset from the DO to generate HTML with its template.
  7. The view uses the output of the forms to generate HTML with its template.
  8. The server sends the HTML output to browser.

Once a view is loaded in the browser, most user interactions happen on forms through Ajax (no page reload). The diagram below shows how the Ajax call works:

cubi-2-1

Conclusion

In the series we’ve learned how to install Cubi, set up the system, and create a module. You may also find that the XML metadata file can be easier to learn and maintain than programming code. I hope you’ve enjoyed the series and wish you luck with your very own Openbiz Cubi powered business applications.

Image via Fotolia

Openbiz Cubi: A Robust PHP Application Framework

<< Openbiz Cubi: A Robust PHP Application Framework, Part 1

Free book: Jump Start HTML5 Basics

Grab a free copy of one our latest ebooks! Packed with hints and tips on HTML5's most powerful new features.

  • http://doig.com.au Steven Doig

    Can this tutorial be performed in a Windows environment? I see you have some commands that are performed in a Unix/Linux shell.

    • Rocky Swen

      Yes, you can run the command from Windows. Assume your cubi is installed under c:cubi, you can enter c:cubibintools, type same command > php gen_meta.php Default customer to generate metadata files. Many developers use Windows to develop and Linux to deploy.

  • http://think-a-doo.net Andre Venter

    After Generating the module (choosing all default values) and loading the module with these commands:
    php gen_meta.php Default customer
    php load_module.php customer
    There is no TAB after loggin out and logging in again.
    The module exists and is visible in the “Manage Modules” section, but not visible or accessible anywhere.
    “Let’s test the module. If you are already logged in to Cubi, log out and log back in again. You should see a new tab named “Customer” appearing in the header section. Click the tab to enter the Customer Dashboard page, then click the Customer Manage link to enter the customer management page. Now we’re able to create, update, search, and delete customers.”

    • Rocky Swen

      One way to check the new Tab is to navigate menu admin page, say http://localhost/cubi/index.php/menu/menu_list. Check if the Customer menu is added in the menu list. Sometimes there are too many tabs such that some tabs are wrapped outside the window. You can turn it off by clicking the select icon in the Published column.

  • http://think-a-doo.net Andre Venter

    When calling http://localhost/cubi/index.php/customer/customer_list in the browser I get the following:

    Fatal error: Metadata file parsing error for object customer.view.CustomerListView. Name attribut [customerListView] not same with object name. Please double check your metadata xml file again. in /Applications/MAMP/htdocs/cubi/openbiz/bin/ObjectFactory.php on line 131

    • Rocky Swen

      When you see “Fatal error: Metadata file parsing error for object customer.view.CustomerListView. Name attribute [customerListView] not same with object name…”, you should check to see if there is a typo of the Name attribute of the XML file. In your case, you have a lowercase ‘c’, while the object name has uppercase ‘C’ for CustomerListView.

  • mike

    Isn’t this just an ad for Openbiz? This seems pretty cool but I noticed the English translations within the framework could be weird at times. :P

  • Richard

    This is a really interesting framework, thank you for posting this useful information!