Rapid Application Development toolkit for building Administrative Web Applications

FAQ on the Radicore Development Infrastructure

By Tony Marston

2nd August 2003
Amended 1st July 2008

Introduction
Frequently Asked Questions
References
Amendment History
Why...
Why do you ...?
Why don't you ...?
Why don't you use javascript?
Why don't you use a Front Controller?
Why don't you use SETTERS and GETTERS for individual database fields?
Why don't you use another templating system instead of XSL?
Why does your infrastructure contain so many different components?
Why don't you use hyperlinks to jump to child screens?
Why don't you put the table structure in a separate Mapper class?
Why do you use your own DAO instead of an existing one, like PEAR?
Why is your design centered around data instead of functions?
Why, in the RBAC system, is task_id different from script_id?
Why can't I have anonymous users?
Why can't I bookmark pages?
Why don't the column hyperlinks sort the data as I expect?
Why doesn't the Workflow system have a facility for sending emails?
How...
How do I install Radicore?
How do fields appear in the HTML output?
How do you deal with pagination?
How do you sort the output by different columns?
How to extend the sql SELECT statement
How to manually extend the automatically extended sql SELECT statement
How to incorporate dropdown lists or radio groups
How to incorporate a dropdown list with multiple selections
How to have different sets of dropdown/radio options for different database rows
How to incorporate a 'popup' control into a form
How does the processing of a 'popup' form actually work?
How can you switch a popup between accepting single and multiple selections?
How can I access a POPUP2 screen?
How can I enter a value before calling a POPUP form?
How can I call the same POPUP more than once in a form?
How to make a normally writable field read-only or even invisible
How do you define 'secondary' validation?
How do you handle error messages?
How do you handle referential integrity?
How do you handle candidate keys?
How do you handle a JOIN in the application?
How do you handle a JOIN in the database?
How can you handle the switch to the 'improved' MySQL extension?
How can you handle the switch to PHP 5?
How can you deal with a table that is related to itself?
How easy is it to dynamically change the HTML control for a field?
How can business logic and data access logic be separate if they exist in the same class?
How do you deal with non-database fields?
How do you use subclasses?
How do you use subclasses to provide alias names?
How do you validate user input?
How do you provide dynamic selection criteria in the $where variable?
How can I define preset/static search criteria for the $where variable?
How do you deal with database transactions?
How do you deal with database locking?
How do you deal with task-specific behaviour?
How can you enter ranges of values prior to a search?
How can I search for records with historic, current or future dates?
How can I make the system inaccessible during periods of maintenance?
How can I run a batch job?
How can I display data from a virtual table?
How can I use a secure server in my application?
How does the HELP facility work?
How to deal with ENUM fields
How to hide menu options from certain users
How do I use the Workflow system?
How can I prevent simultaneous updates of the same database record?
How can I change the display attributes for individual fields?
How are blank entries put into lookup arrays?
How do navigation buttons work?
How do entries appear in the navigation bar?
How to perform a search on an aggregate or aliased column
How can I make global environmental changes for individual subsystems?
How can I implement Row Level Security (RLS)?
How can I access different databases on different servers?
How can I turn on authentication via a RADIUS server?
How is context passed to a child task?
How is context passed to a child object in the same task?
How can I add my company's logo to all web pages?
How can I build a separate logon screen?
How can I remove the 'show nn' options from the navigation bar?
How can I remove the 'select all/unselect all' options from the navigation bar?
How can I make a single row in a multi-row area non-editable?
How can I remove the select box from a single row?
How can I hide/remove columns in a multi-row display?
How can I modify column labels at runtime?
How can I make all the fields in a particular zone non-editable?
What...
What are the benefits of the 3-tier architecture?
What are the benefits of an infrastructure such as yours?
What is the difference between a 'menu bar' and a 'navigation bar'?
What happens when a button in the navigation bar is pressed?
What do you mean by 'primary' and 'secondary' validation?
What is a 'popup'?
What is the purpose of your generic table class?
What is the purpose of your DML (DAO) class?
What is the purpose of each database table class?
What do I do to start a new application/project/subsystem?
What do I do to build new components?
What parts of the infrastructure are case sensitive?
What is a JOIN and how is it handled?
What is a subclass and how do you use it?
What is your naming convention for subclasses?
What debugging aids exist in this framework?
What programming guidelines exist for Radicore?
What data types are supported across the various databases?
What reserved words are used within the Radicore framework?
What are the CREATED_DATE/USER and REVISED_DATE/USER fields?
What is the best way to perform an SQL aggregate function?
Can...
Can you access tables from more than one database?
Can you access tables from more than one database engine?
Can you pass additional parameters to the XSL stylesheet?
Can I add javascript to my application?
Can I produce output in CSV or PDF format?
Can I change search criteria with a navigation button and without user dialog?
Can I add barcodes to my PDF documents?
Can I alter a screen's structure at runtime?
Can I have different initial values for different users?
Can I restrict update and delete operations to a record's creator?
Can I logon without seeing the LOGON screen?
Can I use images instead of text for the hyperlinks above the menu bar?
Aren't/Isn't...
Aren't the MVC and 3-Tier architectures the same thing?
Isn't every web application automatically 3-tier?
Is it really possible to separate business logic from data access logic?
Does...
Does your infrastructure deal with Internationalisation (I18N)?

 

Introduction

Since I wrote my original article I have received comments from various people, either via personal e-mail or through postings in the PHP newsgroup, concerning the efficacy of my endeavours. Some people ask intelligent questions while others say "your work is no good because it cannot do so-and-so". These people either haven't studied my work or cannot work out how it is done because I use a technique which is totally different from theirs. I cannot help being different because it is only by being different that I have a chance to be better, but I can explain some of the finer points of my approach in the hope that it may bring enlightenment to a few confused minds.


Frequently Asked Questions

1. Why don't you use javascript?

  1. It is not necessary to use javascript in any web page. All you need is HTML and CSS. Anyone who says you must use javascript is making a mistake, and anyone who creates a web application which cannot run without it is making an even bigger mistake. It is an option, not a requirement, and it is an option which some users choose to disable.
  2. Unlike HTML and CSS there is no W3C standard for javascript, so different browsers have different implementations and different proprietary extensions.
  3. Because there are so many different implementations it is often necessary to write different variations of the same code for different browsers. This requires even more code to detect which browser is being used so that the correct variation can be invoked, and even the browser detection techniques are not guaranteed to be foolproof as the results can often be spoofed.
  4. A significant number of users turn javascript off for security reasons, therefore any web page that cannot operate without javascript will become useless.
  5. It is only by using standards-compliant software that you can produce web pages which can be correctly rendered by as many browsers as possible. Writing web pages that rely on proprietary extensions is not considered to be 'user friendly'.

NOTE: Although I do not use any javascript in the core framework I have provided the ability for developers to add javascript into their own application subsystems should they so desire. Please refer to Can I add javascript to my application? for details.

2. Why don't you use a Front Controller?

Before I answer this, what exactly is a Front Controller? Is his book Patterns of Enterprise Application Architecture Martin Fowler offers this definition:

The term 'Web handler' refers to the logic which examines each incoming HTTP request to gather just enough information to know what to do with it while the 'command hierarchy' is some kind of organisational structure which the Front Controller can refer to, to decide what to do next, based on the information gathered by the 'Web handler'.

As a design pattern the Front Controller is described as:

The Front Controller design pattern defines a single component that is responsible for processing application requests. A front controller centralizes functions such as view selection, security, and templating, and applies them consistently across all pages or views. Consequently, when the behavior of these functions need to change, only a small part of the application needs to be changed: the controller and its helper classes.

Figure 1 - Diagram of a single Front Controller

infrastructure-faq-01 (3K)

A Front Controller would be invoked using a URL such as http://www.blah.com/controller.php?action=blah which will then do something, such as redirect to another script, depending on the value of the action parameter.

A front controller may be common practice with compiled languages for the simple reason that a compiled language normally produces a single executable program. When that program is executed processing always starts from a single fixed point, and can only branch to a particular subprogram by code at that start point.

PHP is not a compiled language, and the components within an application are not combined into a single executable with a single start point. PHP components are small, self-contained scripts, and it is possible for the web server (a separate product such as Apache) to activate any of these scripts directly without having to pass through a single control script beforehand. The URL http://www.blah.com/blah.php will cause the web server to invoke the script blah.php without any intermediate steps. If the web server can execute the desired page immediately, why should I introduce a potential bottleneck in the form of a front controller? Instead of a single Front Controller for the whole application I have a series of separate Transaction Controllers (aka Page Controllers), as shown in the following diagram:

Figure 2 - Diagram of multiple Transaction Controllers

infrastructure-faq-02 (2K)

This structure works as follows:

One argument put forward for using a front controller is that it becomes easy to perform 'standard' processing before invoking each individual page. This argument is pretty weak considering that it is also possible to perform any 'standard' processing as the very first action within each page (transaction) controller as soon as it has been activated. Provided that this 'standard' processing is performed before the page controller performs any other actions the result is the same.

A Front Controller may be used by some people to solve their particular problems, but as far as I am concerned it is not the only solution and it is certainly not the best solution, especially when I don't have those problems in the first place:

As I can achieve all the commonality and reusability I desire without using a Front Controller I consider its use to be superfluous, redundant, unnecessary and a complete waste of time. I am not the only one who shares this opinion - take a look at The Front Controller and PHP.

3. Why don't you use SETTERS and GETTERS for individual database fields?

First, here are some definitions:

After working with PHP for a short while I noticed that data coming from the client arrives in the format of an associative array (refer to $_POST and $_GET). I discovered that I could pass the whole array into the object with a single method, as in

$object->insertRecord($_POST);

Inside the class it is just as easy to examine a variable with

$this->array['name']

as it is with

$this->name

without any loss of functionality. This avoids the need to unpick the array and pass in each field one at a time, which uses less code and which is therefore more efficient. It also means that the component which feeds the data into an object can do so with a single generic method instead of requiring knowledge of the particular setters within that object. This is how I put the principal of polymorphism into practice.

Here is an example of code written the 'traditional' way within a controller that accesses an object:-

<?php 

$client = new Client(); 
$client->setUserID    ( $_POST['userID'   ); 
$client->setEmail     ( $_POST['email'    ); 
$client->setFirstname ( $_POST['firstname'); 
$client->setLastname  ( $_POST['lastname' ); 
$client->setAddress1  ( $_POST['address1' ); 
$client->setAddress2  ( $_POST['address2' ); 
$client->setCity      ( $_POST['city'     ); 
$client->setProvince  ( $_POST['province' ); 
$client->setCountry   ( $_POST['country'  ); 

if ($client->submit($db) !== true) 
{ 
    // do error handling 
} 
?> 

This is the code that I use in my controller:-

<?php 

$dbobject = new Client;
$dbobject->updateRecord($_POST);
$errors = $dbobject->getErrors();

?> 

What are the benefits of my method?

I am not the only one who thinks this way. Take a look at Why getter and setter methods are evil.

Similarly the data coming out of the database can easily be converted into an associative array, as in

$result = mysql_query($query, $link);
// convert result set into a simple associative array for each row
while ($row = mysql_fetch_assoc($result)) {
    $array[] = $row;
} // while

As the data array which is retrieved by an object does not need to be unpicked into individual fields it means that the receiving component can achieve this with a single generic method, as in

$array = $object->getData($where);

Nothing is done with this data array except to pass it as-is to a function which simply writes it out to an XML file. This means that the component which receives data from an object can do so with a single generic method instead of requiring knowledge of the particular getters within that object.

In my infrastructure the transaction controllers can feed data into and out of an object without any knowledge of the individual items of data contained within that object. This means that my transaction controllers are not tied to any individual object and can be used on any object. The level of reusability for my generic controllers is therefore far higher than in alternative systems where each individual object needs its own controller as it needs a different collection of getters and setters.

In article More on Getters and Setters the author explains why the use of getters and setters may expose implementation details which in the OO world is not considered to be 'a good thing'. In my method I do not need to know the internal representation of a field - a string, a date, an integer, a float, et cetera - as everything goes in and out as a string. Everything in the $_POST array is a string, and everything I put into the XML file is a string. Any necessary conversion between one data type and another is done within the object using information defined within the object.

4. Why don't you use another templating system instead of XSL?

Having experienced first hand the benefits of the 3 Tier architecture I wanted to completely separate the business logic from the presentation logic, so I looked for some sort of templating system to generate the HTML output. I had heard several HTML templating systems for PHP (such as Smarty) but I chose XSLT for the following reasons:

During the development of my infrastructure I found that there was nothing I wanted to do with XML/XSL that could not be done (although sometimes it took several attempts to find the right approach). It was also very useful that the order in which I retrieved data from the XML file during the XSL transformation process was not restricted by the order in which that data was written to the XML file in the first place. This meant that I could resequence the output without having to resequence the input.

I also found it very easy to put common code in reusable files, and with a subsequent enhancement I found that instead of having a separate XSL stylesheet for each database table where the table names, field names and field labels were hard-coded I could use a common stylesheet and supply the table names, field names and field labels as part of the XML data. This is documented in Reusable XSL Stylesheets and Templates.

For another opinion on this very subject I invite you to take a look at Proprietary template systems versus the standard - XSLT.

5. Why does your infrastructure contain so many different components?

It is only by breaking down the whole thing into small parts that you can create parts that can be reused, and it is the number of reusable parts that makes an infrastructure more efficient for the developer. Although my infrastructure looks complicated with its fourteen different components the most important fact is that each component is responsible for a single aspect of the application, and this produces a level of reusability which is extremely high. This means that the developer need only create a small number of new scripts in order to create working components. The process is documented in FAQ 36.

6. How do you deal with pagination?

For screens which show multiple occurrences (rows) from the database it is generally not a good idea to show all available occurrences as that may be a huge number. This will result in a huge screen which the users will probably find to be unmanageable. It is therefore good practice to break down the total number of occurrences into smaller chunks of, say, 10 or 20. These chunks are often referred to as 'pages', hence the term 'pagination'. This facility makes use of the LIMIT and OFFSET clauses of the sql SELECT statement and is described in Pagination - what it is and how to do it. There is a default page size ($rows_per_page) defined for use within each multi-line screen, but this can be overridden by hyperlinks on the navigation bar.

The actual stages are performed in the following sequence:

  1. When a multi-line screen is first invoked without a specific page number being requested it will start at page 1.
  2. After the data has be retrieved by my DML class the following pieces of information will be available as well as the data array:
  3. The values for $pageno and $lastpage will be written out to the XML file, and during the XSL transformation process a standard XSL stylesheet will use this information to create the pagination area in the HTML output.

    The data in the XML file will resemble the following:

    <pagination>
      <page id="main" numrows="12" curpage="2" lastpage="2"/>
    </pagination>
    

    A sample of the XSL template used for pagination can be found here.

  4. Each of the hyperlinks in the pagination area contains an absolute page number, not a reference to 'previous' or 'next', so whichever hyperlink is selected it will result in a URL similar to http://www.domain.com/script.php?page=3.
  5. When the controller script runs it looks for the existence of this parameter, and if found it passes it to the database object for action. This is done using code similar to the following:
    // obtain the required page number (optional)
    if (isset($_GET['page'])) {
        $dbobject->setPageNo($_GET['page']);
    } // if
    
  6. The whole cycle repeats from step (2).

After a script has run for the first time the contents of each object is serialised into the $_SESSION array so that it can be retrieved, unserialised and reused for all subsequent invocations. This means that any settings (page number, sorting, selection criteria, et cetera) which are established will be reused until they are changed.

As you can see the process is quite straightforward, but it does require some action in the presentation layer as well as some action in the data layer. There are some people who insist that this process should take place entirely in the presentation layer, but I find the notion totally impractical and without merit.

7. How do you sort the output by different columns?

For screens which show multiple occurrences (rows) from the database it is often useful to be able to sort the details in a different order, either by a different column, or descending instead of ascending. This ability is provide in my infrastructure by means of the following:

  1. When a multi-line screen is constructed the column headings will be constructed as hyperlinks which, when pressed, will result in a URL similar to http://www.domain.com/script.php?orderby=name1.
  2. When the controller script is run it looks for the existence of this parameter and if found it passes it to the database object for action. This is done using code similar to the following:
    // obtain the 'orderby' field (optional)
    if (isset($_GET['orderby'])) {
        $dbobject->setOrderBy($_GET['orderby']);
    } // if
    
  3. The setOrderBy() method will load the field name into variable $orderby, and the variable $order will toggle between 'ascending' and 'descending'.
  4. The values for $orderby and $order will be passed down to the DML object where, if not blank, will be built into the sql SELECT statement.
  5. The contents of $orderby and $order ('asc' or 'desc') will be added to the <params> area of the XML file. This information will be used by a standard XSL stylesheet to insert a gif image after the selected column heading in the HTML output.

After a script has run for the first time the contents of each object is serialised into the $_SESSION array so that it can be retrieved, unserialised and reused for all subsequent invocations. This means that any settings (page number, sorting, selection criteria, et cetera) which are established will be reused until they are changed.

8. How to extend the SQL SELECT statement

By default the sql SELECT statement which is created when using the getData($where) method on a database object will be as follows:

$query = "SELECT * FROM $this->tablename $where_str" 

If a value has been supplied in the $where parameter then $where_str will contain WHERE ..., otherwise it will be empty. As you can see this will result in all columns being retrieved from a single table, but what happens if the developer wants something more complicated?

If any relationships with parent tables have been defined in the Data Dictionary then it may not be necessary to insert any custom code as the framework can use this information to automatically construct an SQL query containing JOINs to all the foreign tables, as described in Using Parent Relations to construct sql JOINs. It is also possible to take this automatically extended query and append manual extensions as described in How to manually extend the automatically extended sql SELECT statement.

The ability to create more complicated sql SELECT statements is provided as follows:

  1. The generic table class contains a series of variables as follows:
    class Default_Table
    {
       var $pageno;          // used as OFFSET
       var $rows_per_page;   // used as LIMIT
       var $sql_select;      // list of column names
       var $sql_from;        // table names in a JOIN statement
       var $sql_where;       // fixed portion of WHERE clause
       var $sql_groupby;     // contents of GROUP BY clause
       var $sql_having;      // contents of HAVING clause
       var $sql_orderby;     // contents of ORDER BY clause
       var $sql_orderby_seq; // contents of ORDER BY clause
       ...
    
  2. Within each component script it is possible to supply values for any of these variables using code similar to the following:
    // identify extra parameters for SELECT statement
    $sql_select = 'person.*, pers_type.pers_type_desc';
    $sql_from   = 'person '
                 .'LEFT JOIN pers_type ON (person.pers_type_id = pers_type.pers_type_id)';
    $sql_groupby = '';
    $sql_having  = '';
    $sql_where   = '';
    

    Note that it is also possible to put these changes in the database table class instead, as described in step (6) below.

  3. Within the controller script these values will be transferred into the database object using code similar to the following:
    $dbobject->sql_select  = &$sql_select;
    $dbobject->sql_from    = &$sql_from;
    $dbobject->sql_where   = &$sql_where;
    $dbobject->sql_groupby = &$sql_groupby;
    $dbobject->sql_having  = &$sql_having;
    // the following values may be supplied by the user
    if (isset($_GET['pagesize'])) {
        $dbobject->setRowsPerPage($_GET['pagesize']);
    } // if
    if (isset($_GET['page'])) {
        $dbobject->setPageNo($_GET['page']);
    } // if
    if (isset($_GET['orderby'])) {
        $dbobject->setOrderBy($_GET['orderby']);
    } // if
    $where = $_SESSION['where'];  // created by previous page
    $data  = $dbobject->getData($where);
    
  4. Within the database object the contents of $sql_where (fixed) and $where (variable) are combined into a single string, then all these variables are passed to the DML object using code similar to the following:
        function _dml_getData ($where)
        // Get data from the specified database table.
        // Results may be affected by $where and $pageno.
        {
            $DML = $this->_getDBMSengine();
            
            $DML->pageno           = $this->pageno;
            $DML->rows_per_page    = $this->rows_per_page;
            $DML->sql_from         = $this->sql_from;
            $DML->sql_groupby      = $this->sql_groupby;
            $DML->sql_having       = $this->sql_having;
            $DML->sql_orderby      = $this->sql_orderby;
            $DML->sql_orderby_seq  = $this->sql_orderby_seq;
            $DML->sql_select       = $this->sql_select;
            $DML->sql_where        = $this->sql_where;
            
            $array = $DML->getData($this->dbname, $this->tablename, $where);
            
            $this->errors   = array_merge($DML->getErrors(), $this->errors);
            $this->numrows  = $DML->getNumRows();
            $this->pageno   = $DML->getPageNo();
            $this->lastpage = $DML->getLastPage();
            
            return $array;
        
        } // _dml_getData
    
  5. Within the DML object these variables are used to construct an sql SELECT statement with the following structure:
    $query = 'SELECT ' .$select_str
            .' FROM ' .$from_str
            .' ' .$where_str
            .' ' .$group_str
            .' ' .$having_str
            .' ' .$sort_str
            .' ' .$limit_str;
    
    where each $..._str is a string constructed from the relevant variables passed down by the calling database object. Note that some of these may be empty. The end result will (should?) always be a valid sql SELECT statement.
  6. It is also possible to bypass step (2) above and insert the changes in the _cm_pre_getData() method of the database table class.

9. How to incorporate dropdown lists or radio groups

By default each field will appear in the HTML output as a textbox control, but this can be changed to a dropdown list or radio group quite easily. To achieve this it is necessary to have the XML file contain data similar to the following:

<?xml version="1.0"?>
<root>
  <person>
    <person_id size="8" pkey="y" required="y">FB</person_id>
    <pers_type_id size="6" required="y"
                           control="dropdown" 
                           optionlist="pers_type_id">ANON</pers_type_id>
    <first_name size="20" required="y">Fred</first_name>
    <last_name size="30" required="y">Bloggs</last_name>
    ....
  </person>
  <lookup>
    <pers_type_id>
      <option id=" "></option>
      <option id="ACTOR">Actor/Artiste</option>
      <option id="ANON">Anne Oni Mouse</option>
      <option id="BORING">Boring Person</option>
      <option id="CARTOO">Cartoon Character</option>
      ....
    </pers_type_id>
  </lookup
</root>

Notice the following:

Sample XSL code can be located here:

To set the control type to dropdown or radio group you must do the following:

  1. Within the Data Dictionary use the Update Column task to set the control type to dropdown or radio group, and supply a value for option list.
  2. Within the Data Dictionary use the Export Table function to rebuild the <tablename>.dict.inc file. This should then contain values similar to the following:
    $fieldspec['pers_type_id'] = array('type' => 'string',
                                       'size' => 6,
                                       'required' => 'y',
                                       'control' => 'dropdown',
                                       'optionlist' => 'pers_type_id');
    

To supply values for the lookup element in the XML file you must do the following:

  1. Within the database table class put some custom code inside the dummy _cm_getExtraData() method similar to the following:
        function _cm_getExtraData ($where, $fieldarray) 
        // Perform custom processing for the getExtraData method.
        // $where = a string in SQL 'where' format.
        // $fieldarray = the contents of $where as an array.
        {
            // get contents of foreign table PERS_TYPE and add to lookup array
            $pers_type =& singleton::getInstance('x_pers_type');
            $array = $pers_type->getValRep('pers_type_id');
            $this->lookup_data['pers_type_id'] = $array;
            
            return $fieldarray;
            
        } // _cm_getExtraData
    

    The code inside this method is used to communicate with a foreign table and obtain its contents for inclusion in an array of lookup (picklist) data. The term ValRep is short for Value+Representation where Value is what is used internally and Representation is what is displayed to the user.

  2. Within the class for the foreign table create a method called _cm_getValRep() with contents similar to the following:-
       function _cm_getValRep ($item=NULL, $where=NULL)
       // get Value/Representation list from this table
       {
          $array = array();
    	   
          if (strtolower($item) == 'pers_type_id') {
             // get data from the database
             $this->sql_select      = 'pers_type_id, pers_type_desc';
             $this->sql_orderby     = 'pers_type_desc';
             $this->sql_orderby_seq = 'asc';
             $data = $this->getData($where);
             
             // convert each row into 'id=desc' in the output array
             foreach ($data as $row => $rowdata) {
                $rowvalues = array_values($rowdata);
                $array[$rowvalues[0]] = $rowvalues[1];
             } // foreach
    	   
             return $array;
          } // if
          
          return $array;
    	   
       } // _cm_getValRep
    

If the data for the dropdown list is not supplied from the contents of a database table but from a fixed list (such as signs of the zodiac) then code similar to the following will be required instead:

  1. Within the database table class put some custom code inside the dummy _cm_getExtraData() method similar to the following:
        function _cm_getExtraData ($where, $fieldarray) 
        // Perform custom processing for the getExtraData method.
        // $where = a string in SQL 'where' format.
        // $fieldarray = the contents of $where as an array.
        {
            // get list for star_sign and insert into lookup array
            $array = $this->getValRep('star_sign');
            $this->lookup_data['star_sign'] = $array;
            
            return $fieldarray;
            
        } // _cm_getExtraData
    
  2. Within the same table class create a method called _cm_getValRep() with contents similar to the following:-
       function _cm_getValRep ($item=NULL)
       // get Value/Representation list from this table
       {
          $array = array();
    	   
          if (strtolower($item) == 'star_sign') {
             $array = getLanguageArray('star_sign');
             return $array;
          } // if
          
          return $array;
    	   
       } // _cm_getValRep
    

    Note that this uses the getLanguageArray() function which is part of my Internationalisation (I18N) facility. This will supply user text in the language of the user. The entry in each <subsystem>/text/<language>/language_array.inc file should look something like the following:

    $array['star_sign'] = array('ARI' => 'Aries',
                                'AQU' => 'Aquarius',
                                'CAN' => 'Cancer',
                                'CAP' => 'Capricorn',
                                'GEM' => 'Gemini',
                                'LEO' => 'Leo',
                                'LIB' => 'Libra',
                                'PIS' => 'Pisces',
                                'SAG' => 'Sagittarius',
                                'SCO' => 'Scorpio',
                                'TAU' => 'Taurus',
                                'VIR' => 'Virgo');
    

Note that the lookup array need not contain a blank entry to signify "no selection" as this can be inserted automatically by the framework, as described in FAQ 75.

The information which decides on which output control is to be used for each field is held within the $fieldspec array within the database table class, but what is defined within this array can be regarded as being the default value as it can be changed at runtime. It is therefore possible to change the HTML control for any field to suit whatever circumstances are encountered.

Note that dropdown lists and radio groups will only allow the user to make a single selection. If multiple selections are required then take a look at How to incorporate a dropdown list with multiple selections.

10. How to make a normally writable field read-only or even invisible

There may be some circumstances in which a field that can normally be amended by the user must be made read-only or even hidden completely from view. As all the information regarding each database field from its validation rules to its display format is held with the $fieldspec array within each database table class then it is a simple matter to change the contents of this array.

To make these settings the default you should do the following:

  1. Within the Data Dictionary use the Update Column task to set the NOEDIT/NODISPLAY field to either noedit or nodisplay.
  2. Within the Data Dictionary use the Export Table function to rebuild the <tablename>.dict.inc file. This should then contain values similar to the following:
        $fieldspec['field1']              = array('type' => 'string',
                                                  'size' => 20,
                                                  'noedit' => 'y');
        
        $fieldspec['field2']              = array('type' => 'string',
                                                  'size' => 16,
                                                  'nodisplay' => 'y');
    

These keywords will then be included in the XML file as attributes for their respective fields. Code within a standard XSL template will detect the existence of these attributes and take the appropriate action.

To change these settings temporarily during the execution of a particular script you may use code similar to the following:

  $this->fieldspec['field1']['noedit'] = 'y';
  $this->fieldspec['field2']['nodisplay'] = 'y';

To clear these settings at runtime you may use code similar to the following:

  unset($this->fieldspec['field1']['noedit']);
  unset($this->fieldspec['field2']['nodisplay']);

11. What is the difference between a 'menu bar' and a 'navigation bar'?

While both contain hyperlinks (or buttons) which will enable you to jump to another task (or transaction) within the system there is a difference between them.

The Menu bar has the following characteristics:

The Navigation bar has the following characteristics:

12. What do you mean by 'primary' and 'secondary' validation?

Primary validation of user input is handled automatically by using the field specifications contained within the $fieldspec array within each database table class. This will examine each field in isolation of the others to ensure that required fields are not empty and that the data for each field is valid for that field type (number, date, time, et cetera). This is all handled in my data validation class.

Secondary validation is that which cannot be handled by my standard validation class, such as comparing the contents of one field with another, so must be handled by custom code within the database table class. It is also possible to extend this custom validation to perform lookups on other database tables.

13. How do you define 'secondary' validation?

When a controller script accesses a database table class to to insert or update a record, standard code which is inherited from the generic table class will, after performing all standard validation, pass control to one or more customisable methods.

For a pictorial representation of the processing flow of various transactions please take a look at UML diagrams for the Radicore Development Infrastructure.

The prefix '_cm_' is used to signify a method which is defined within the superclass but which contains no code. In order to achieve anything this method must be copied into the individual subclass where it can then be filled with custom code. The customised version in the subclass will then override the empty (or abstract) version in the superclass.

These dummy (or abstract) methods have the following definitions:

    function _cm_commonValidation ($fieldarray, $originaldata)
    // perform validation that is common to INSERT and UPDATE.
    {
        return $fieldarray;
    } // _cm_commonValidation

    // ****************************************************************************
    function _cm_validateInsert ($fieldarray)
    // perform custom validation before insert.
    {
        return $fieldarray;
    } // _cm_validateInsert

    // ****************************************************************************
    function _cm_validateUpdate ($fieldarray, $originaldata)
    // perform custom validation before update.
    {
        return $fieldarray;
    } // _cm_validateUpdate
    
    // ****************************************************************************

Here is an example of one containing customised code:

    function _cm_commonValidation ($fieldarray, $originaldata) 
    // perform validation that is common to INSERT and UPDATE.
    {
        if ($fieldarray['start_date'] > $fieldarray['end_date']) {
            $this->errors['start_date'] = 'Start Date cannot be later than End Date';
            $this->errors['end_date']   = 'End Date cannot be earlier than Start Date';
        } // if
                
        return $fieldarray;
        
    } // _cm_commonValidation

Note that two input arrays are made available:

The 'before' and 'after' sets of data is for those situations where action need only be taken when a field value is actually changed.

It is also possible to define secondary validation in separate classes which can be shared among several application subsystems. Please refer to Extending the Validation class for more details.

14. How do you handle error messages?

Within each database table class there is a standard variable called $errors which should be use to hold all error messages. As more than one error message may be generated this variable should be treated as an array and not a string.

Error messages which are related to particular fields should be inserted as follows:

    $this->errors['fieldname'] = 'error message';

Error messages which are not related to particular fields should be inserted as follows:

    $this->errors[] = 'error message';

Note: with the implementation of my Internationalisation feature it is possible to obtain a message in the user's language using code similar to the following:

    $this->errors[] = getLanguageText('e1234');

During the construction of the XML file as the value for each individual field is copied from the database object the $errors array is examined for an entry with a key which matches the field name. If one is found it is added to the XML file as an error attribute. Any error messages which are left over after all the fields have been processed will be added as separate lines to the message area. This is shown in the following example:

<root>
  <person>
    ....    
    <start_date size="12"
                required="y" 
                error="Start Date cannot be later than End Date"
                >02 Jan 2006</start_date>
    <end_date size="12"
              error="End Date cannot be earlier than Start Date"
              >02 Jan 2005</end_date>
    ....
  </person>
  <message>
    <line>This message is not attached to any field</line>
  </message>
</root>

During the XSL transformation process as each field is written to the HTML output the contents of the error attribute, if present, will appear immediately below the field value as shown in this screen sample. Any messages will be shown in the message area at the bottom of the screen, as shown in this screen sample.

15. How do you handle referential integrity?

Referential integrity refers to the rules that need to be applied when dealing with a relationship between two tables. This comes in two flavours - foreign key integrity and delete integrity.

Foreign Key Integrity If Table B (the child table) has a foreign key that points to a field in Table A (the parent table) referential integrity would prevent you from adding a record to Table B that cannot be linked to Table A. This is handled by the fact that foreign key values are never keyed in directly - they are either chosen from dropdown lists or popups or passed down as context from the previous script.
Delete Integrity If a parent table has related entries on a child table then some action may need to be taken when deleting, or attempting to delete, an entry from the parent table. There are three possibilities:
  • RESTRICTED - Cannot delete parent entry if any child entries exist.
  • CASCADE - Will delete all child entries as well as the parent entry.
  • NULLIFY - The foreign key on all child entries will be set to NULL (initialised).
The rules required for each relationship must be defined in the $child_relations array within each table structure file.

Note that I do not rely on the database engine to deal with referential integrity, so the fact that MySQL does not (currently) have any method of enforcing referential integrity is of absolutely no consequence. Even if I were to use a database engine that had such capabilities I would probably avoid them, for the following reasons:

  1. I have spent most of my career dealing with database engines that had no referential integrity, so I am used to dealing with it in my application code. Programmers who complain that the database engine should do it for them are demonstrating their own lack of ability.
  2. I prefer to keep all rules regarding each database table within the class for that database table. Anything else will break encapsulation.
  3. One advantage of having the rules processed within my code instead of the database engine is that I have complete control over how the rules are actually executed. For example, in a cascade delete I have the opportunity to read each child record before it is deleted so that I can enter its details into my audit log. This would not be possible if the delete were to be handled by the database engine.
  4. Another advantage of having the rules defined within my application code is that as well as hard-coding the rules within each database class I have the option to change any of those rules at runtime should I so desire. This is a powerful feature that would not be available if the rules were maintained within the database engine.

16. How do you handle candidate keys?

A candidate key is a unique key which is in addition to the primary key. Each table must have a primary key, but candidate keys are entirely optional. Any number of candidate keys may be defined, and each key may be comprised of any number of fields.

Dealing with candidate keys in the Radicore infrastructure is straightforward - just identify the candidate keys in the $unique_keys array within the table structure file (this is handled automatically by the dictionary IMPORT and EXPORT functions) and the standard code within the DML class will take care of the rest as follows:

17. What is a 'popup'?

A popup is a type of picklist. When options are to be chosen from a foreign table and there are too many to display in a radio group or a dropdown list, then the only alternative is to use to another form instead of a control or widget within the current form. The popup form will display the contents of the foreign table and allow the user to choose either a single entry, or in some cases multiple entries.

A popup form is identical to a LIST form, but with the addition of a CHOOSE button in the action bar.

The availability of a popup in a form is signified with a popup button popup (1K) situated to the right of the data field. By pressing this the current form is suspended and a new form, the popup form, will appear in its place.

By default the user cannot enter any text before activating the POPUP form, but this behaviour can be amended using the information provided in FAQ 81.

If the SELECT column of the popup form contains radio buttons the user may only select a single entry, but if it contains checkboxes then multiple selections will be allowed. The user selects the entry or entries required and presses the CHOOSE button. This will cause the selection details to be passed back to the previous form where they will be processed.

17a. How to incorporate a 'popup' control into a form

In order to populate a field using the popup control you must perform the following steps:

  1. Within the Data Dictionary use the Add Relationship task to create a relationship between the foreign table (the parent) and the object table (the child), and supply a value for parent field. This may either be the name of an actual field, or it may be a calculated (derived) field as in:
    CONCAT(field1, ' ', field2) AS foreign_desc
    When this information is exported from the Data Dictionary it will appear in the <tablename>.dict.inc file similar to:
    $this->parent_relations[] = array('parent' => 'foreign_table',
                                      'parent_field' => 'foreign_desc',
                                      'fields' => array('primary_key' => 'foreign_key'));
    
  2. Within the Data Dictionary use the Update Column task to set the target field's control type to popup, and supply a values for the following: When this information is exported from the Data Dictionary it will appear in the <tablename>.dict.inc file similar to:
    $fieldspec['foreign_id'] = array('type' => 'integer',
                                     'size' => 4,
                                     'required' => 'y',
                                     'control' => 'popup',
                                     'task_id' => 'task_identity',
                                     'foreign_field' => 'foreign_desc');
    

When the form containing the popup is processed the generated XML document will contain something which is similar to the following:

  <foreign_id size="4" 
              control="popup" 
              foreign_field="foreign_desc" 
              task_id="tran#task_identity">5</foreign_id>
  <foreign_desc noedit="y">Description from foreign table</foreign_desc>

The HTML which is generated for this control will look similar to the following:

  <div class="popuptext">
    <input type="hidden" 
           name="foreign_id" 
           value="5"/>Description from foreign table</div>
  <div class="popupbutton">
    <input type="image" 
           name="task#task_identity" 
           src="images/popup.gif" 
           alt="Call popup form to obtain value"/>
  </div>

17b. How does the processing of a 'popup' form actually work?

Whenever a popup button is pressed the following processing takes place:

  1. When the script receives the $_POST array it is merged with the current data within the object. The amended object is then saved in the $_SESSION array so that all data, saved or unsaved, can be displayed back to the user. This ensures that no unsaved data is lost and has to be re-entered.
  2. The script then examines the $_POST array looking for a field name which begins with task#. This tells it that either a popup button or navigation button has been pressed. The characters which follow task# provide the identity of the task which is to be run.
  3. The task details are retrieved from the TASK table within the RBAC system, and the value for PATTERN_ID identifies whether the task is a popup or not. If it is a popup then custom method _cm_popupCall() is called in order to perform any pre-processing. This includes defining a $where string to be passed to the popup form, and defining any settings which can be passed to the popup form.
  4. The current form is suspended while the designated popup form is activated. The popup form will use whatever $where string was passed to it when retrieving data from the database, and will use the select_one setting to determine if the select column should be populated with check boxes (when select_one=FALSE) or radio buttons (when select_one=TRUE).
  5. If only a single row is retrieved from the database and the setting choose_single_row has been set then that row will automatically be selected without waiting for the user to press the CHOOSE button.
  6. When the CHOOSE button is pressed in the popup form all the records which were selected will have their primary keys inserted into the $selection string which will be returned to the calling form. This string will be in the format of the WHERE clause of an SQL query, and can deal with single selections or multiple selections where the primary keys are comprised of single or multiple fields, as shown in the following examples:
    field1='value1'
    
    field1='value1' AND field2='value2'
    
    (field1='value1' AND field2='value2') OR (field1='value3' AND field2='value4') OR ... 
    

    By default only those fields which form the primary key will be included in the $selection string, but sometimes it may be useful to return a non-key field as well. This can be achieved by using the _cm_getPkeyNames() method to temporarily alter the list of key fields before the $selection string is constructed.

  7. The popup form then terminates, and the previous form is reactivated with the following variables set:
  8. The script then calls the popupReturn() method on the relevant object. This performs the following steps:
  9. The screen is then presented to the user with the value for foreign_field displayed in front of the popup button.

18. What is a JOIN and how is it handled?

By default the output from each table class contains values which are only from the database table with which it is associated. This output may include calculated fields such as those created by means of CONCAT or a similar function. There may be occasions when it is desired to incorporate values from other database tables, such as to replace a foreign key with a description from the foreign table. In order to gather information from more than one database table it is necessary to perform what is known in the database world as a JOIN, and within this framework a JOIN can be performed in any of the following ways.

18a. How to handle a JOIN in the application

In this method the table object reads data from its own table, which results in an array of zero or more entries, but before this array is passed back to the presentation layer it is modified to include additional data from one or more other tables. To do this the database object must iterate through its array of database data, and for each occurrence it must fetch an additional array of data from another database table (using another database object), then merge this additional array with the original array. This could be achieved with code similar to the following:

    function _cm_getForeignData ($fieldarray) 
    // Retrieve data from foreign entities.
    {
        require_once 'tree_node.class.inc';
        $dbobject = new Tree_Node;
        
        foreach ($fieldarray as $row => $rowdata) {
            if (!empty($rowdata['node_id']) and empty($rowdata['node_desc'])) {
                // get description for selected node
                $dbobject->sql_select = 'node_desc';
                $foreign_data = $dbobject->getData("node_id='{$rowdata['node_id']}'"); 
                // merge with existing data
                $fieldarray[$row] = array_merge($rowdata, $foreign_data[0]);
            } // if
        } // foreach
        
        return $fieldarray;
        
    } // _cm_getForeignData

Note that in most cases such code is redundant by virtue of the fact that the framework can use the contents of the $parent_relations array (which is constructed using data entered via the Add/Update Relationship task) to generate and execute the relevant code automatically at runtime by calling the getForeignData() method.

Also note that it is not very efficient to obtain data from parent tables after the child table has been retrieved, especially if there are multiple occurrences of the child table and multiple parent tables. It is far more efficient to get the database to perform all this processing in a single operation by constructing an SQL query which contains the relevant JOIN clauses, as documented in How to handle a JOIN in the database.

18b. How to handle a JOIN in the database

When it is necessary to obtain data from more than one table the most efficient method is to construct an SQL query which contains the relevant JOIN clauses so that the database can retrieve the data in a single operation and return that data in a single result set. Within this framework there are two methods of constructing such a query:

  1. Manually, as described in How to extend the sql SELECT statement.
  2. Automatically, as described in Using Parent Relations to construct sql JOINs. This uses the contents of the $parent_relations array which is constructed inside the Data Dictionary and then exported to the application.

19. Can you access tables from more than one database?

Yes. When you access a database table through its own table class the name of the database is built into the class and does not have to be specified again. When the database table object communicates with the DML object it will supply the table name and database name as well as the table data. The DML object will use this information to select the correct database. It is therefore possible within the same transaction to access a number of database table objects where each table exists within a different database.

When using a JOIN with an sql SELECT statement you must remember to use the format databasename.tablename otherwise the database engine will look for the table within the database associated with the current database table object.

20. Can you access tables from more than one database engine?

Yes. It is normal practice for an installation to have all its databases in a single database server, and the identity of this server is defined in a single place in the CONFIG.INC file as $GLOBALS['dbms']. Within the constructor of each database table class is code similar to the following:

    $this->dbms_engine = $GLOBALS['dbms'];
    $this->dbname      = 'foo';
    $this->tablename   = 'bar';

Whenever a database table class needs to communicate with the database it does so by communicating with a DML object for the specified DBMS engine. This object handles the connection to that DBMS engine, and the construction and execution of all SQL queries. Regardless of how many database table classes are used in a script there will only ever be a single instance of the DML object for a particular DBMS engine.

In my development environment I have the same data held on a MySQL server, a PostgreSQL server and an Oracle server, and it is possible for me to switch from one server to another simply by changing the value for $GLOBALS['dbms'] within the CONFIG.INC file.

However, it is also possible for an installation to have different databases on different servers, and to switch from one DBMS engine to another on a per database basis instead of per installation. In order to achieve this the following steps are necessary:

As a single Radicore installation is comprised of several subsystems, this procedure will allow each subsystem to have its database handled by a different DBMS engine. It is also possible for a single script to access more than one database, with each of those databases served by a different DBMS engine, but this does impose the following limitations:

21. How can you handle the switch to the 'improved' MySQL extension?

When accessing a MySQL database which is earlier than version 4.1 you use the MySQL functions, but to access version 4.1 and above you will need to use the Improved MySQL Extension instead. This could present some difficulties to developers of lesser ability, but due to the fact that my infrastructure design is based on the 3-Tier Architecture where all data access is through a Data Access layer all I have to do is switch a single component, my DML class, and everything is tickety-boo, hunky-dory, and smelling of roses.

Because it would be unusual for a PHP installation to have both the MySQL functions and Improved MySQL Extension installed at the same time it is possible to detect which is available at runtime and to create an object from the relevant class. I have amended the code described in FAQ 20 as follows::

    if ($engine == 'mysql') {
        if (function_exists('mysqli_connect')) {
            // use 'improved' mysql functions
            require_once "dml.mysqli.class.inc";
        } else {
            // use standard mysql functions
            require_once "dml.mysql.class.inc";
        } // if
    } else {
        require_once "dml.$engine.class.inc";
    } // if

This means that when I change my version of MySQL to 4.1 or above I do not have to take any further action as my code will detect the change and automatically switch to the correct functions. So when someone tells you that implementing the 3-Tier Architecture is an unnecessary investment just ask them how much effort it will take them to upgrade their software to use the new extension.

22. How can you handle the switch to PHP 5?

I originally built this infrastructure to run with PHP 4, so I used the DOM XML functions to construct my XML files and the XSLT (Sablotron) functions to perform the XSL Transformations. Now that PHP 5 is here I discover that these two extensions have been moved out to the PECL repository, and I have to use the DOM and XSL extensions instead.

This could present some difficulties to developers of lesser ability, but as I had the foresight to put the function calls to these extensions in a set of user-defined functions within their own include() file I found that all I had to do was create a new version of this include() file to contain the calls to the alternate functions. I thus ended up with one file for PHP 4 and another for PHP 5. As it is possible to detect at runtime which version of PHP is being used it is an easy process to load the file which is relevant to that PHP version. The code that I use is similar to the following:

  // detect which version of PHP is being used
  if (version_compare(phpversion(), '5.0.0', '<')) {
      require 'include.xml.php4.inc';
  } else {
      require 'include.xml.php5.inc';
  } // if

Each of these two files contains the same user-defined function names, so none of the code which calls these functions needs to be changed. The important thing is that the contents of these user-defined functions is relevant to the version of PHP which is being used. I can now switch my application between PHP 4 and PHP 5 at the drop of a hat without having to worry about any incompatibilities.

23. What is the purpose of your generic table class?

When I started to teach myself to access a database with PHP using samples found in various books and online tutorials I noticed that in all cases each of the sql SELECT, INSERT, UPDATE and DELETE statements was individually hard-coded for each database table. After generating these statements for a small number of tables myself I asked a simple question - would it be possible to automate the generation of these statements?

When you consider that each of these sql statements is nothing more than a string variable which is passed to the database engine, and that PHP's string manipulation functions are very powerful, it did not take me long to find the answer.

Take a look at the structure of the various statements:

INSERT INTO <tablename> SET fieldname='value', fieldname='value', ...
UPDATE <tablename> SET fieldname='value', fieldname='value', ... WHERE primarykey='value'
DELETE FROM <tablename> WHERE primarykey='value'
SELECT <select> FROM <from> <where> <group> <having> <sort> <limit>

As the data I pass into the INSERT, UPDATE and DELETE methods is an associative array of name=value pairs you should see that it is a simple exercise to iterate through this array to construct the SET fieldname='value' portion of each statement. As the $fieldspec array within each table structure file identifies the primary key field(s) it is just as simple to construct the WHERE primarykey='value' portion.

The SELECT statement is a little more complicated as there are potentially more components. In my getData() method the where is supplied as an optional argument, but the select, from, group, having, sort and limit portions are object variables which are set with appropriate values by the calling script. These are then processed at runtime and merged into a single string. This is described in more detail in FAQ 8.

The purpose of my generic table class can be summarised as follows:

24. What is the purpose of your DML (DAO) class?

The purpose of my DML class (or Data Access Object) is to isolate the construction and execution of all SQL queries from objects in the business layer (sometimes referred to as 'domain' objects). This means that I can switch from one DBMS engine to another simply by switching to an alternative DML class.

When I first produced my generic table class it also included all calls to the MySQL functions to communicate with the database. I knew that at some point in the future I may want to use a different database engine, such as PostgreSQL or Oracle, so I wanted a mechanism which would make this switch as simple and painless as possible.

The first step was to extract all the database function calls and put them into a separate class. As these function calls deal with the Data Manipulation Language I called it the DML class. As the first of these was for MySQL I named it dml.mysql.class.inc. I then changed my generic table class to pass control to my DML class whenever it wanted to communicate with the database.

Instead of being passed a complete SQL query for execution I decided it would give me greater flexibility if the final assembly of each query were to be left to entirely to the DML object. Thus it is only SQL fragments that are passed to the DML object where they are assembled immediately before being executed. An example of how this is done for a SELECT is shown in FAQ 8. Example for INSERT, UPDATE and DELETE are shown in Using PHP Objects to access your Database Tables (Part 1).

The advantage that this particular method has given me over other methods I have seen is that should a particular query need to be assembled slightly differently for any DBMS engine then I only have to adjust the code in a single place - within the DML class for that particular DBMS engine. In other infrastructures dealing with such a change may mean applying updates to multiple components.

Whenever I wish to use a different database engine all I have to do now is as follows:

The code I use to load the relevant class file is described in FAQ 20.

Another advantage of this design is the fact that I have been able to incorporate a audit logging facility into all my applications simply by modifying the code within my DAO. This is far more efficient than having to modify individual table classes one by one.

Notice that this code also deals with the switch between the 'original' and 'improved' MySQL functions, as documented in FAQ 21.

As has been stated in FAQ 20 this DML class isolates all database function calls within a single object, which makes the switching from one database engine to another very easy. It is also possible to access different database tables through different engines within the same transaction.

25. What is the purpose of each database table class?

The generic table class contains code which is common to every database table, but it cannot be instantiated into an object because it does not contain such details as database name, table name, table structure, validation rules, et cetera. This type of class is known as an abstract class, and it needs the addition of a subclass before it can be instantiated into a usable object.

The implementation details for each individual database table are therefore supplied in separate database table classes (subclasses) which extend the generic table class (superclass) and combine with the generic code through the process of inheritance. All the knowledge required to access a database table is contained or 'encapsulated' within its database table class.

Whenever a component needs to communicate with a database table all it need do is create an object from that table's class and then call one of the standard methods and everything is handled within that object, either by the generic code within the superclass or the custom code within the subclass.

There are some people who say that it is 'not good OOP' to have a separate class for each database table, but I wholeheartedly disagree.

26. Aren't the MVC and 3-Tier architectures the same thing?

Some people seem to thinks so as they each break down the application into 3 separate areas or layers, but if you examine their descriptions carefully you will see the differences:

As you can see there is some overlap between the two, but not an exact match. As there is no rule in either architecture that says there can be only one script (or program or module) in each area, it is possible to split any of these areas down into smaller parts for convenience (that is why some people refer to 3-Tier as N-Tier where 'N' can be any number). It is therefore possible to create a development infrastructure which contains the features of both architectures, as shown in the following diagram:

Figure 5 - The MVC and 3-Tier architectures combined

infrastructure-faq-05 (5K)

By combining both of these architectures it is therefore possible to create an application infrastructure which has more features and advantages than either one on its own.

27. How can you deal with a table that is related to itself?

In a screen which deals with two database tables in a parent-child relationship, such as a LIST 2 screen, there is no problem if they are different tables as the table names are used to identify which data goes where in the screen. But what happens if the relationship is actually between a table and itself? How can you keep the data from the parent and child parts separate?

Although it is possible to reference the same table through both a $parent and $child object within the PHP code this will cause a problem when trying to build the screen during the XSL transformation as the two entity names within the XML data will be the same. This will result in both sets of data being written to both areas in the screen instead of each set of data being written out to its own area.

The solution is to change one of the table references to a different name, but how can this be done? The solution in a previous language was to create a reference to a database table using an alias or subtype or subclass, and I have built a similar solution into my infrastructure which is described in Using subclasses to provide alias names.

The important thing to note is that when transferring data out of an object into the XML data what I actually use is the class name and not the physical table name. In order to reference a database table using an alias name all I have to do is create a subclass from the original database table class. Here is an example taken from my sample application:

Contents of file tree_node_snr.class.inc:

<?php
require_once 'tree_node.class.inc';
class Tree_Node_Snr extends Tree_Node
{
}
?>

When I reference an object from the tree_node_snr class I will actually be referencing the same database table as an object from the tree_node class. When the data is written to the XML file one set of data will be labelled tree_node_snr and the other tree_node, thus it will be easy to keep the two sets of data separate from one another.

You will notice in the above example of extending a class into a subclass all I am doing is supplying an alternative class name - I am not changing any properties or methods, although I could if I wanted to. It would be possible for me to supply new properties and methods, or even to provide replacement properties and methods to override those which exist in the superclass.

28. Why don't you use hyperlinks to jump to child screens?

On some web applications that I have seen the way to navigate from a parent LIST screen to a child screen is to click on a detail line where the whole line has been coded as a hyperlink. This then jumps immediately to a child screen with the details of the selected entry pre-loaded. This process involves a single step whereas in my infrastructure it is two stages - select an entry, then press a button. Why is this? My reasons are as follows:

  1. The hyperlink has to be coded into the HTML page before it is sent, therefore the name of the target child screen has to be hard-coded and cannot be varied by the user. In my method I can display a choice of target child screens as buttons.
  2. The hyperlink for each entry contains the identifier for that entry, therefore the child screen can only process that single entry. The user will have to return to the parent screen in order to make another choice.
  3. A hyperlink will always use the GET method, which means that any arguments (such as the primary key) will be visible in the browser's address bar. This is felt by many to be a security risk.
  4. A hyperlink can only perform its action on a single object. This means that if you wish to perform the same action on several objects you have to keep returning to the screen in order to select another object.
  5. A hyperlink which causes a change in state, such as with an UPDATE or DELETE operation, breaks one of the rules of the internet that all GET requests be idempotent. If a search engine scans your page it will try to follow every hyperlink on that page, and if the page is full of hyperlinks which delete records then guess what? Those hyperlinks will be activated, and those records will be deleted.

My method has the following advantages:

It would be possible for me to use a combination of hyperlinks for one child screen and buttons for the others, but this would be inconsistent and probably confusing to the users. As the poor dears are often confused enough I do not wish to add to their burden.

29. How easy is it to dynamically change the HTML control for a field?

This situation arose while I was building a prototype for a web-base survey application. A survey could have any number of questions, and the answer to each question could be one of the following:

This means that when building the HTML output for the screen I need to know what type of answer is expected so that I can generate the correct HTML tags. In some development infrastructures this may be a complex process, but in my infrastructure it is incredibly easy. This is made possible because of the following facets of my design:

This means that the HTML control which is to be used for each field is defined with the $fieldspec array of each database table class, therefore to change the type of control all you have to do is change the contents of this array. This can be done using code similar to the following:

  function _cm_getExtraData ($where, $fieldarray) 
  // Perform custom processing after the getData method.
  {
    ...
    
    switch ($fieldarray['answer_type']) {
      case 'M':
        // answer is multiple choice from a dropdown list
        $this->fieldspec['answer_text']     = array('type' => 'string',
                                                    'size' => 12,
                                                    'required' => 'y',
                                                    'control' => 'dropdown',
                                                    'optionlist' => 'answer_id'); 
        break;