Openbiz Cubi: A Robust PHP Application Framework, Part 2

Share this article

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

Rocky SwenRocky Swen
View Author

Rocky Swen is the founder of the Openbiz Cubi open source project. He spent his career on cloud, mobile, video encoding and streaming, business intelligence and enterprise technologies. During weekends he enjoys hiking with his family and playing tennis with friends.

Intermediate
Share this article
Read Next
Get the freshest news and resources for developers, designers and digital creators in your inbox each week