community.egroupware.org: Community wiki

  
Community wiki
day four
the day before | Up | the day after



We stepped into the details of DB-Tool and the integration of our eTemplate application into the database.
Basicly we did the same as I did the day before.
  • we choosed etemplates -> dbtools
  • we selected our application
    • we noted, that the applications marked with a * are available, but not installed
  • since I had a table already, I choosed this table
  • I added a column test_dts with type int and percision 8 to store a eGroupware timestamp, and checked NOT NULL
  • I indexed the test_property
  • I checked NOT NULL for test_name
  • then I clicked on write table this
    • writes setup/tables_current.inc.php
    • writes setup/tables_update.inc.php as it is needed.
  • I went to eGroupaware/setup to refresh/upgrade my test-application

I list the three files involved (setup_inc.php, tables_current.inc.php and tables_update.inc.php)
setup.inc.php:


<?php
        $setup_info['test']['name']      = 'test';
        $setup_info['test']['title']     = 'Test';
        $setup_info['test']['version']   = '0.0.0.002';  //anything you like, as long as it is fitting the schema of a version number
        $setup_info['test']['app_order'] = 100;      // the position of your app in the applist/iconbar; 100=at the end
        $setup_info['test']['tables']    = array('egw_test'); // if there are any
        $setup_info['test']['enable']    = 1;
        // app_title, author, maintainer, description, note, version, license are available
        // app_title, description, notes, author, version and license will be shown in 'about application'
        // there is the possibility for hooks
        //
        /* Dependencies for this app to work */
        // if you define dependencies, you MUST meet them to get that baby on the road
        $setup_info['test']['depends'][] = array(
                 'appname' => 'phpgwapi',
                 'versions' => Array('1.3','1.4'),
         );
        $setup_info['test']['depends'][] = array(   // this is only necessary as long the etemplate-class is not in the api
                 'appname' => 'etemplate',
                 'versions' => Array('1.3','1.4'),
        );


The depends part of the setup_info array, is usually the critical one. If you detail the stuff there too much (e.g.: 1.3.7.6) then this
version must be present at setup time. If you provide a less detailed number (e.g.: 1.4) this part will be matched against the version present
and the chances for successful setup will increase. So detail the versionnumer for the dependecies only if you must.

I think with the docu contained in the source the rest pretty much explains itself.



tables_current.inc.php:


<?php
	/**
	 * eGroupWare - Setup
	 * http://www.egroupware.org
	 * Created by eTemplates DB-Tools written by ralfbecker@outdoor-training.de
	 *
	 * @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License
	 * @package test
	 * @subpackage setup
	 * @version $Id: class.db_tools.inc.php 21408 2006-04-21 10:31:06Z nelius_weiss $
	 */


	$phpgw_baseline = array(
		'egw_test' => array( //table name
			'fd' => array( // fields of your table, with type and properties
				'test_id' => array('type' => 'auto','nullable' => False),
				'test_name' => array('type' => 'varchar','precision' => '40','nullable' => False),
				'test_firstname' => array('type' => 'varchar','precision' => '40'),
				'test_property' => array('type' => 'varchar','precision' => '10'),
				'test_dts' => array('type' => 'int','precision' => '8','nullable' => False)
			), // keys and indexes
			'pk' => array('test_id'), // primary key
			'fk' => array(),  // foreign key, not supported yet.
			'ix' => array('test_property'), // indexes
			'uc' => array() // unique keys
		)
	);

Tables current defines your current application related tables. If you ever created tables with SQL even this explains itself.
This file has multiple purposes. It is used for setup as well as for definition lookup of tables assigned to the application.


tables_update.inc.php:


<?php
// upgrade of table current version (app version from database to app version of setup.inc.php)
// upgrade searches of a function [app]_upgrade[versionsnumber in database] to
// use this function, or this and following functions to step through to  current-version
	$test[] = '0.0.0.001';
	function test_upgrade0_0_0_001()
	{
		$GLOBALS['egw_setup']->oProc->AlterColumn('egw_test','test_name',array(
			'type' => 'varchar',
			'precision' => '40',
			'nullable' => False
		));
		$GLOBALS['egw_setup']->oProc->AddColumn('egw_test','test_dts',array(
			'type' => 'int',
			'precision' => '8',
			'nullable' => False
		));

		return $GLOBALS['setup_info']['test']['currentver'] = '0.0.0.002';
	}
?>
The upgrade file enables setup to get from the stored version (in the eGW Database) to the current version of your table (documented in the tables_current.inc.php). This supposes that you handle version numbers consequently.
Upgrade is searching for a function [app]_upgrade[versionsnumber in database] to use this function, or this and following functions to step through to current-version.
You have to take care of all necessary actions, additional to the altering of the table.


There can be an additional file default_records.inc.php. If it exists it will be used to do some initial population of a new table.
The set of commands and eGroupware specific functions is very limited, but you can always use the other applications setup-files to have a look.

Connecting to the database - the very basics

Now that we have a database-table and a functioning eTemplate we have to make them working together.

There are different approaches to deal with the database in eGroupware.
phpgwapi/inc/class.egw_db_inc.php
uses current tables as info
  • function Select
  • function insert
  • function delete
  • function expression
Within the so class of your application
  • you define the tabelename as class variable
  • you clone the database object
	$this->db = clone($GLOBALS['egw']->db);
	$this->db->set_app('test'); // binds the database connect to the application
A code example from the infolog soclass:
		function read($info_id)		// did _not_ ensure ACL
		{
			$info_id = (int) $info_id;
			if ($info_id && $info_id == $this->data['info_id'])
			{
				return $this->data;		// return the already read entry
			}
			if ($info_id <= 0 || !$this->db->select($this->info_table,'*',array('info_id'=>$info_id),__LINE__,__FILE__) ||
				 !(($this->data = $this->db->row(true))))
			{
				$this->init( );
				return False;
			}
			if (!is_array($this->data['info_responsible']))
			{
				$this->data['info_responsible'] = $this->data['info_responsible'] ? explode(',',$this->data['info_responsible']) : array();
			}
                        $this->db->select($this->extra_table, 'info_extra_name, info_extra_value', array('info_id' => $info_id), __LINE__, __FILE__);
			while ($this->db->next_record())
			{
				$this->data['#'.$this->db->f(0)] = $this->db->f(1);
			}
			return $this->data;
		}

etemplate/inc/class.so_sql.inc.php Official documentation
Using the eTemplate we have now splitted the display of the userinterface from its processing code.
The structured developing techniques of the CodingRules - the 3-tier-approach (UI > BO > SO !!!) - enables us to split the layers of our application, and handle certain stuff at the right level (e.g. time -> user/system) and just there.
Now it comes into action at full speed.

Thats how the classes work together.



Here is the code to the three classfiles:

class.test_so.inc.php
<?php
  include_once(EGW_INCLUDE_ROOT . '/etemplate/inc/class.so_sql.inc.php');

  /**
   * General storage class for test (database)
   */
  if (!defined('TEST_APP'))
	{
		define('TEST_APP','test');
	}

   class test_so extends so_sql
   {
	var $maintable='egw_test';

	function test_so()
	{
		$this->so_sql(TEST_APP,$this->maintable);
		//$this->empty_on_write = "''";
	}


   }
Usually the SO-Class is the class where the storage related functions are handeled. Since we extend the Storage Class of eTemplate we have to handle
only the stuff, which is not handeled there. Since it is the base class of the 3-tier approach, here is the right place for application wide defines as well.



class.test_ui.inc.php
<?php
require_once(EGW_INCLUDE_ROOT.'/test/inc/class.test_so.inc.php');
 /**
  * Business Object for test
  */
  class test_ui extends test_ui
  {
  	// the types are not needed yet, nor supported by the interface and database

     var $types = array(
         ''      => 'Select one ...',
         'con'    => 'Contact',
         'bus'   => 'Business',
     );
     var $timestamps=array('test_dts');
	/**
	 * Offset in secconds between user and server-time,	it need to be add to a server-time to get the user-time
	 * or substracted from a user-time to get the server-time
	 *
	 * @var int
	 */
	var $tz_offset_s;
	/**
	 * Current time as timestamp in user-time
	 *
	 * @var int
	 */
	var $now;


    function test_ui()
    {
      //discarded as of extension of the class
      //$this->so =& CreateObject('test.test_so');
	    $this->test_so();

		if (!is_object($GLOBALS['egw']->datetime))
		{
			$GLOBALS['egw']->datetime =& CreateObject('phpgwapi.datetime');
		}
		$this->tz_offset_s = $GLOBALS['egw']->datetime->tz_offset;
		$this->now = time() + $this->tz_offset_s;	// time() is server-time and we need a user-time

    }

	/**
	 * changes the data from the db-format to your work-format
	 *
	 * reimplemented to adjust the timezone of the timestamps (adding $this->tz_offset_s to get user-time)
	 * Please note, we do NOT call the method of the parent so_sql !!!
	 *
	 * @param array $data if given works on that array and returns result, else works on internal data-array
	 * @return array with changed data
	 */
	function db2data($data=null)
	{
		if (!is_array($data))
		{
			$data = &$this->data;
		}
		foreach($this->timestamps as $name)
		{
			if (isset($data[$name]) && $data[$name]) $data[$name] += $this->tz_offset_s;
		}
		return $data;
	}

	/**
	 * changes the data from your work-format to the db-format
	 *
	 * reimplemented to adjust the timezone of the timestamps (subtraction $this->tz_offset_s to get server-time)
	 * Please note, we do NOT call the method of the parent so_sql !!!
	 *
	 * @param array $data if given works on that array and returns result, else works on internal data-array
	 * @return array with changed data
	 */
	function data2db($data=null)
	{
		if ($intern = !is_array($data))
		{
			$data = &$this->data;
		}
		foreach($this->timestamps as $name)
		{
			if (isset($data[$name]) && $data[$name]) $data[$name] -= $this->tz_offset_s;
		}
		return $data;
	}
  }
The BO-Class is the class that does the talking between storage-Class and UserInterface? Class, that does the translation between storage and interface,
and provides globals for the userinterface.
As the class extension does NOT imply the call of the constructor of the extended class, we must call the constructor of the extended class in the
construction process of the current (BO) class.



class.test_ui.inc.php
<?php
require_once(EGW_INCLUDE_ROOT.'/test/inc/class.test_ui.inc.php');
class test_ui extends test_ui
{
	var $public_functions = array(
		'testinterface'	=> True,
		'index'	=> True,
		);
	/**
	 * instantiation of the etemplate as classenvariable
	 *
 	 * @var etemplate
	 */
	var $tmpl;

    /*
    	constructor of the class
    */
     function test_ui()
    {
      $GLOBALS['egw_info']['flags']['app_header']='test-Application';
      // instantiation of etemplate as class variable
      $this->tmpl =& CreateObject('etemplate.etemplate');
      $this->html =& $GLOBALS['egw']->html;
      /*
         since we extend the boclass, we do not have to instantiate an object of the boclass.
         none the less, we have to call the constructor of that class, to ensure, everything
         done there is done as we call the test_ui class
      */
      //$this->bo   =& CreateObject('test.test_ui');
      $this->test_ui();


      if(!@is_object($GLOBALS['egw']->js))
      {
           $GLOBALS['egw']->js =& CreateObject('phpgwapi.javascript');
      }

    }

    /*
    	this is creating the header for our non e-template approach
    */
	public function create_header ()
	{
                common::egw_header();
		echo parse_navbar();

	}

    /*
    	this is creating the footer for our non e-template approach
    */
	public function create_footer ()
	{
		common::egw_footer();
	}

	/*
		our original , non eTemplate approach
		Form with two inputfields, which is switched to a greeting form if there is some input in fname or sname
		two links at the bottom of the form to switch to the eTemplate Version of the application, or to
		reload the form, via href/link.
	*/
    function testinterface ()
    {
        $this->create_header();
        if (trim($_POST['fname'].$_POST['sname'])!='')
        {
            //echo ''.$_SERVER['PHP_SELF'].'<br>';
            echo "<br>Hello ".$_POST['fname']." ".$_POST['sname']."<br>";
            echo "<form action='".$GLOBALS['egw']->link('/index.php',array(
        'menuaction' => 'test.test_ui.testinterface','message'=>'YES'))."' method='post'>
            <input type='submit' value=' Reload '>
            </form>";
        }
        else
        {
            echo "Type a name to be greeted accordingly <br>";
            echo "<form action='".$GLOBALS['egw']->link('/index.php',array(
        'menuaction' => 'test.test_ui.testinterface','message'=>'NO'))."' method='post'>
              <p>first name:<br><input name='fname' type='text' size='30' maxlength='30'></p>
              <p>name:<br><input name='sname' type='text' size='30' maxlength='40'></p>
                    <input type='submit' value=' Submit '>
                    <input type='reset' value=' Cancel'>
            </form>";

        }
	    echo "<br><a href='".$GLOBALS['egw']->link('/index.php',array('menuaction' => 'test.test_ui.testinterface','message'=>'YES'))."'> Test Application </a><br>";
	    echo "<a href='".$GLOBALS['egw']->link('/index.php',array('menuaction' => 'test.test_ui.index'))."'> Call the eTemplate version  </a> <br>";
        $this->create_footer();
    }
	/*
		function call index
		the eTemplate version of the application. basically the same
		a form with two  input-fields an additional selectbox and a checkbox to enable content-debugging within a predefined area of the
		eTemplate
		The function handles the behavior of the eTemplate in accordance to the content of the content array
		If the data-handling part is completed, the eTemplate is read and executed with content and select-option arrays.
	*/
	function index($content=null)
	{
		if ($content['clear'])
		{
			//$content=array();

			$content['debug']=0;
			$content['who']='';
			$content['test_name']='';
			$content['test_firstname']='';

		}
		$debug=$content['debug'];
		$separator=$content['debug'];
                $sel_options = array(
                    'test_property' => $this->types
                );
                if (is_array($content) && !$content['clear']){
			// after submit
			$content['debug']=$debug;
			$content['message']=print_r($sel_options,true).print_r($content,true);
			$content['datetime']=time();
			if (trim($content['test_firstname'].$content['test_name'])!='')
			{
				$this->data['test_name']=$content['test_name'];
				$this->data['test_firstname']=$content['test_firstname'];
				$this->data['test_dts']=$this->now;

				$this->save();
				$content['who']=$content['test_firstname']." ".$content['test_name'] ;
				$separator=1;
			}
			$content['test_name']='';
			$content['test_firstname']='';
		} else {
			// first call
			/*
			$content=array(
				'who'=>', please type a name ...',
			);
			*/
		}
		//$tmpl=new etemplate('test.index'); //this is discarded since we do that while constructing the class
		$this->tmpl->read('test.index');
		$this->tmpl->set_cell_attribute('debuginfo','disabled',!$debug);
		$this->tmpl->set_cell_attribute('myhrule','disabled',!$separator);
		$this->tmpl->exec('test.test_ui.index',$content,$sel_options);
		// the debug info will be displayed at the very end of the page
		//_debug_array($content);

	}

}



As the class extension does NOT imply the call of the constructor of the extended class, we must call the constructor of the extended class in the
construction process of the current (UI) class


The outcome of Day 4 as ZiP File


Go back in time to the first day, to the second day,
to the third day or to the day before

Go on to the fifth day

Back to Code Corner
You are here