2nd August 2003
Amended 1st October 2008
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.
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.
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
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
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.
First, here are some definitions:
$foo = $object->getFoo();
$object->setFoo($foo);
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.
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.
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.
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:
$numrows - the total number of rows retrieved for this page, which may be less than the value in $rows_per_page.$pageno - the page number actually retrieved, which may be less than the one requested if records have been deleted.$lastpage - the last page number that is available using the current selection criteria.$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.
http://www.domain.com/script.php?page=3.// obtain the required page number (optional) if (isset($_GET['page'])) { $dbobject->setPageNo($_GET['page']); } // if
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.
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:
http://www.domain.com/script.php?orderby=name1.// obtain the 'orderby' field (optional) if (isset($_GET['orderby'])) { $dbobject->setOrderBy($_GET['orderby']); } // if
setOrderBy() method will load the field name into variable $orderby, and the variable $order will toggle between 'ascending' and 'descending'.$orderby and $order will be passed down to the DML object where, if not blank, will be built into the sql SELECT statement.$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.
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:
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
...
// 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.
$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);
$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
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.
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:
<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:
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.
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:
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
_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.
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:
<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']);
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:
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.
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:
$originaldata - data from the database (before being changed by the user).$fieldarray - containing data from the screen (after being changed by the user).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.
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.
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:
|
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:
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:
SELECT count(*) from $tablename WHERE ... with a WHERE clause constructed from the current record data. The code looks something like this:
// there may be several keys with several fields in each
foreach ($this->unique_keys as $key) {
$where = NULL;
foreach ($key as $fieldname) {
if (empty($where)) {
$where = "$fieldname='{$fieldarray[$fieldname]}'";
} else {
$where .= " AND $fieldname='{$fieldarray[$fieldname]}'";
} // if
} // foreach
$this->query = "SELECT count(*) FROM $tablename WHERE $where";
$count = $this->getCount($dbname, $tablename, $this->query);
if ($count <> 0) {
// set error message for each field within this key
foreach ($key as $fieldname) {
$this->errors[$fieldname] = 'A record already exists with this key.';
} // foreach
return;
} // if
} // foreach
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
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.
In order to populate a field using the popup control you must perform the following steps:
CONCAT(field1, ' ', field2) AS foreign_descWhen 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'));
<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>
Whenever a popup button is pressed the following processing takes place:
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.$where string to be passed to the popup form, and defining any settings which can be passed to the popup form.$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).choose_single_row has been set then that row will automatically be selected without waiting for the user to press the CHOOSE button.$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.
task_id set to $return_from so that it can initialise the field identified in foreign_field.foreign_field.foreign_field displayed in front of the popup button.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.
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.
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:
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.
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:
config.ini file.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:
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.
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.
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:
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:
dml.<engine>.class.inc.$dbms_engine variable in my generic table class to contain the <engine> name.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.
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.
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
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.
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.
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:
My method has the following advantages:
This information is posted back to the parent script which then does the following:
(field1='value' AND field2='value').(field='value1') OR (field='value2').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.
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:
$fieldarray which is an associative array of data obtained from the database via the DML class.$fieldspec which is an array of specifications for each field within that database table. This is defined separately within each database table class.$errors which is an array of error messages, indexed by field name, which may be generated at run-time.$fieldspec array identifies which HTML control is to be used for each field. This information is inserted into the XML document as a series of attributes for the field in question. During the XSL transformation as each field is being processed it uses the field attributes to decide which HTML control to use.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'