RADICORE programming guidelines
Here are a few guidelines if you wish to start developing with the RADICORE framework.
If you wish to create a new project/application to run under the RADICORE framework then you must first create a new subdirectory to contain all all the files for the new project. Please refer to How do I start a new project with RADICORE?
Database design tips
Before you can build components to manipulate the contents of your database tables you must first construct your database. Here are a few tips:
- Make sure the database is properly normalised. Pease refer to The Relational Data Model, Normalisation and effective Database Design for details.
- Use only the characters a-z, 0-9 and '_' (underscore) in any database, table and column names. Any other characters would require all names to be quoted in every SQL statement, and this is more effort than it is worth.
- SQL is totally case-insensitive, so don't waste time in using any particular case for your database, table and column names. All names will be shifted to lower case within the framework anyway, so anything else will be ignored. In particular this means that StudlyCaps (as in "FirstName") or CamelCaps (as in "firstName") should NOT be used. I prefer the "lower_under" (Snake Case) format (lower case with underscore), as in "first_name". For more details on this please refer to Case Sensitive Software is EVIL.
- Do not use special prefixes (aka Hungarian Notation) to identify databases, tables, columns, primary keys, candidate keys or foreign keys. The SQL language is intelligent enough to work without such prefixes, and so should you. They have absolutely no meaning in any DBMS, no meaning in modern IDE's, and have absolutely no meaning in RADICORE. This form of notation is now considered to be obsolete, and even Microsoft themselves, who originated this convention, now recommend that it not be used.
- Database names should be short but meaningful, as documented in database names.
- Table names should be short but meaningful, and preferably singular, as documented in table names.
- Column names should be short but meaningful, as documented in field names, and help identify their content. Wherever possible fields with the same content should have the same name, and fields with different content should have different names, as described in Field names should identify their content.
- Do NOT use the name
ID
for every primary key field, for reasons which are documented in primary keys. Do not automatically use a technical primary key on every database table without first reading Technical Keys - Their Uses and Abuses and Database Design - are you Novice, Ninja or Nincompoop?. - Foreign key columns, wherever possible, should be given the same name as the primary key columns to which they are related, for reasons which are documented in foreign keys.
- If you wish to employ a method of obtaining unique sequence numbers please take a look at generating unique ids first.
- It may be a good idea to include a prefix on each table name which helps identify the application subsystem. For example, in my MENU database all tables are prefixed with "mnu_". This will be of use in those situations where there is a restriction on the number of different databases which can be created, in which case you will have to merge the tables for different application subsystems into a single database. If two different tables have the same name you immediately have a big problem, but having a different prefix will circumvent this problem.
- Note that Radicore uses a different database for the MENU, DICT, WORKFLOW and AUDIT subsystems, so you will need at least one more database for your own application. If necessary you can merge all these databases into a single database using the instructions found in How can I consolidate all the Radicore databases into a single database?.
- The RADICORE framework does not use database stored procedures, database triggers or database constraints. The database is a place for storing data, not application logic. All business rules are best defined within the application code.
- Avoid creating a column which has the same name as the table in which it resides. It has come to light that there is a bug in the XSLT processor on some linux systems which, when looking up the data for an element name, gets confused if that element name exists both as a table and a column. This causes some elements within a multi-line screen to be shown containing the wrong data.
See also the guidelines which appear in SQL Style Guide.
PHP programming tips
The primary purpose of source code is that it be readable by human beings and only incidentally executed by a computer. Code which works, but which cannot be maintained or enhanced because it is completely unreadable, is not much use to anybody.
All software has two basic elements - structure and logic. Code without either of those is unlikely to perform efficiently and also unlikely to be maintainable. When somebody looks at your code for the first time (and that includes you after a six month break) then they should be able to grasp and understand its structure and its logic in a short space of time. A complex task is broken down into a series of simpler sub-tasks or modules, and each module may be further broken down into smaller sub-modules to perform specific sub-tasks. The ability to quickly identify which module performs which task, as well as where that module is called in the processing sequence, is therefore absolutely essential. If there is a bug in the code, but you cannot work out where the bug is being caused, then how can you fix that bug? If you have to change the logic for a particular task, but you cannot work out which piece of code performs that task, then how can you implement that change?
Here are a few simple guidelines to make code both readable and maintainable:
- Use meaningful names for both functions and variables.
- Use a proper structure, preferably one which can easily be pictured (see Infrastructure Overview as an example). If your structure is so complex that it cannot be drawn then it is probably TOO complex.
- KISS (Keep it Simple, Stupid), which has been updated to Do The Simplest Thing That Could Possibly Work. In other words: look for simple solutions instead of complex ones.
- IIABDFI (If It Ain't Broke Don't Fix It). In other words: if something is working then don't try to tinker with it to make it "better" as you are liable to break it.
- DRY (Don't Repeat Yourself). In other words: code should be modularized rather than copied and pasted.
- YAGNI (You Ain't Gonna Need It). In other words: only implement solutions which you know you are going to need today, not ones which you think you may need tomorrow. When tomorrow eventually comes you will discover a completely new set of requirements, in which case the solution which you built yesterday will have to be totally redesigned and rewritten.
Note that these are "guidelines" and not "rules" and should not be followed mindlessly or with religious fanaticism. You should follow the spirit of the law and not just the letter of the law. Be prepared to Ignore All The Rules when new circumstances arise, but be prepared to justify any deviation from the rules.
Here are some more detailed guidelines:
- Use meaningful function/method names which convey to the reader an idea of what the function/method actually does. These names may be defined using either the "lower_under" or Snake Case format (lower case with underscore separator) as in:
function do_something_clever ()
or with the first letter of each word in upper case (known as StudlyCaps) as in:function DoSomethingClever ()
or a variation with the first letter always in lower case (known as camelCase), as in:function doSomethingClever ()
I programmed using "lower_under" in several different languages for decades before I started using PHP, so that is what I prefer, but being intelligent and reasonable I am capable of reading names in StudlyCaps/camelCase as well, but not if the names are too long. Remember that StudleyCaps/CamelCase only came into being because some early software had very small size limits on names, or the keyboard did not have an underscore, so the use of capitals made it possible to fit a meaningful name into a short space. As modern software does not have such small limits the reason for using capitals instead of underscores has diminished, so don't try to tell me that capitals are the new standard and underscores are outlawed as that would be unproductive and a waste of time.
With function names it is common practice to put the verb before the subject, so use 'getData' instead of 'dataGet', and 'insertRecord' instead of 'recordInsert'.
Instead of using long names try using common abbreviations instead, so instead of 'generateHyperTextMarkupLanguage()' use 'generateHTML()' instead. Avoid the use of abbreviations and acronyms that no-one else understands.
- PHP is currently case-insensitive when it comes to function names, which means that it is possible to define a function using one mixture of case but refer to it in a different mixture of case. Although this is technically possible it looks sloppy, and could prove disastrous should the PHP developers suddenly decide to make the language case sensitive, so use the same case throughout. IDEs such as Microsoft's Visual Studio are case preserving, which means that once a function has been defined all references to that function name will automatically be adjusted to the same case.
- When defining a function put the opening brace on its own line so that a comment can be inserted between the function name and the opening brace, as in:
function getChanges ($newarray, $oldarray) // compare two arrays of 'name=value' pairs and remove items from $newarray // which have the same value in $oldarray. { // step through each 'item=value' entry in $newarray foreach ($newarray as $item => $value) { // remove if item with same value exists in $oldarray if (array_key_exists($item, $oldarray)) { if (stripslashes($value) == stripslashes($oldarray[$item])) { unset ($newarray[$item]); } // if } // if } // foreach return $newarray; } // getChanges
Note that I place the function's description after the function name and before the opening brace. This has always been the standard since I started programming all those decades ago, and I see no reason to change.
Note also that each closing brace is followed by a comment which identifies the construct which is being closed. I personally find it very annoying to see a closing brace, or even several closing braces, without knowing what is being closed.
- If the function does too many things then consider breaking it down into smaller units. This does *NOT* mean that a function should not contain more than N lines (where N is an arbitrary number) as sometimes a single function *CAN* contain a large number of lines, and breaking it down into smaller units may make it less readable. The idea that a function should not contain more lines than can fit into a single page on your monitor is preposterous as different people can have different sized monitors. If a function overflows your screen then learn to use the scroll wheel on your mouse.
- Do not try to create shortcuts by having more than one function in a line. Not only is it *NOT* more efficient as N instructions on one line will take exactly the same time to execute as N instructions on N lines, but they will be less readable and therefore less maintainable. In other words try to avoid this:
$result = $foo->doSomething($object1->getFirstVariable(),$object2->getSecondVariable(),$object3->getThirdVariable());
Although the following requires more typing, in my opinion it is more readable and therefore the preferred option:$foo = $object1->getFirstVariable(); $bar = $object2->getSecondVariable(); $snafu = $object3->getThirdVariable(); $result = $foo->doSomething($foo, $bar, $snafu);
This thought is echoed in Writing highly readable code by Dylan Bridgman.
Do not waste time trying to type commands with as few keystrokes as possible in the mistaken belief that it is more efficient. When you consider that more time is spent in READING code than WRITING it, it is more important that the code is actually READABLE. Using short-hand notation defeats this objective and is therefore to be avoided. It is for this reason that the RADICORE framework does not use such constructs as the ternary operator.
Do not try to write as many statements as you can onto a single line as it makes the code more difficult to read. You may think that it's clever, but I do not.
- There is a universal convention that all constants are defined in UPPER CASE while all other variables are defined in lower case. This makes it easier to spot which one you are using, plus it makes mistakes easier to spot. Consider the following:
(1) BAD : $foo = $array[foobar]; (2) good : $foo = $array[FOOBAR]; (3) good : $foo = $array['foobar'];
Line (1) is probably a mistake as a genuine constant would have been written in uppercase as in line (2). Something which is *NOT* a constant should therefore be written as shown in line (3). - Variable names - use meaningful names wherever possible, and only use single-character names for temporary variables. Always use lower case with the underscore ('_') separator between each word. Do NOT use CamelCaps and UNDER NO CIRCUMSTANCES use Hungarian Notation as it has been corrupted into something which is far from what the original author intended, as explained in Making Wrong Code Look Wrong.
$variable_name // is good $varName // is bad $sVarName // is EVIL!
- PHP is currently case-sensitive when it comes to variable names, which makes
$FOO
totally different from$foo
or$Foo
. As I worked for several decades using both programming languages and operating systems which were case-INsensitive I consider the introduction of case sensitivity to be counter-intuitive and a recipe for disaster, as explained in Case Sensitive Software is EVIL. - Control structures - it is possible to define these either with or without curly braces. RADICORE uses curly braces, as in
if () { ... } // if
and not theif (): ... endif;
alternative. This is because the vast majority of PHP programmers follow this convention, and the code-completion feature of my IDE also follows this convention, so to switch between the two would be annoyingly inconsistent.Note also that after each closing brace I like to put a comment which identifies which control structure has been closed.
Do not use too much white space as it does not increase readability. Please use:
if (condition) { ... do something ... } else { ... do something else ... }
instead of:if (condition) { ... do something ... } else { ... do something else ... }
Note also that there is a space between the 'if' and the opening bracket, the closing bracket and the opening brace, but not between the brackets and the condition. So ...if (condition) {
is good whereas:if( condition ){
is not so good. - Indentation - statements following conditions should always be indented so that the <begin> and <end> statements start at the same column position while the <body> is indented by 1 tab position (or 4 spaces). The following is acceptable:
foreach ($newarray as $item => $value) { if (array_key_exists($item, $oldarray)) { if (stripslashes($value) == stripslashes($oldarray[$item])) { unset ($newarray[$item]); } // if } // if } // foreach
While the following is NOT acceptable:foreach ($newarray as $item => $value) { if (array_key_exists($item, $oldarray)) { if (stripslashes($value) == stripslashes($oldarray[$item])) { unset ($newarray[$item]); }}}
- One line conditions. Do NOT use them unless it is to execute a simple
return
statement.if (condition) return;
is acceptable, but anything more complicated is not. - Tabs or Spaces? This only matters when different people use different editors/IDEs with different settings. I have always used a tab to represent 4 spaces, and I see no reason to change this for anyone. It is possible to configure the tab size in most IDEs, and to automatically convert tabs to spaces as you type, but at the end of the day it all looks the same so there is no point in making a fuss over it.
- Regular expressions - all usage of the ereg* functions has been removed from the RADICORE framework and replaced with the preg* functions. Although simple patterns are relatively easy to comprehend, when they start to be complicated it is a good idea to split them up into separate parts which can be properly commented, as in the following:
$pattern = "/" // start pattern . "^[a-z0-9_-]+" // valid chars (at least once) . "(\.[a-z0-9_-]+)*" // dot valid chars (0-n times) . "@" // at . "[a-z0-9][a-z0-9-]*" // valid chars (at least once) . "(\.[a-z0-9-]+)*" // dot valid chars (0-n times) . "\.([a-z]{2,6})$" // dot valid chars . "/i"; // end pattern, case insensitive
As an alternative you can also use:
$pattern1b = <<< END_OF_REGEX / ^[a-z0-9_-]+ # valid chars (at least once) (\.[a-z0-9_-]+)* # dot valid chars (0-n times) @ # at [a-z0-9][a-z0-9-]* # valid chars (at least once) (\.[a-z0-9-]+)* # dot valid chars (0-n times) \.([a-z]{2,6})$ # dot valid chars /xi END_OF_REGEX;
Either of these formats is a LOT easier to understand than:
$pattern = "/^[a-z0-9_-]+(\.[a-z0-9_-]+)*@[a-z0-9][a-z0-9-]*(\.[a-z0-9-]+)*\.([a-z]{2,6})$/i";
- Strings - use single quotes unless there is a good reason to use double quotes (string contains variables or \n) as it gives PHP less work to do. String literal keys in array statements should always be contained within single quotes, as in:
$foo = $bar['foobar'];
- Comments/Documentation - code which is not well commented shows a lack of consideration for others. Those who say that PHP code is so easy that it is self-documenting are showing a lack of experience. It may be easy to say what a piece of code does, but not so easy to say how it fits into the current context. Comments are usually put immediately before a line of code, as in:
if (!is_string(key($array1))) { // indexed by row, so use row zero only $array1 = $array1[0]; } // if
or may be put at the end of the line, as in:if (!is_string(key($array1))) { $array1 = $array1[0]; // indexed by row, so use row zero only } // if
- Comment style - PHP provides several different ways to write comments:
// single line comment # another single line comment /* multi line comment multi line comment */
Switching between different styles looks messy and inconsistent, so RADICORE uses nothing but:// single line comment
Don't waste space by using three lines for a single line comment, as in:/* * here is a single-line comment which takes up 3 lines */
- Documentation using phpdoc - there is a universal assumption that any documentation produced by phpdoc (which is derived from javadoc) is automatically acceptable. However, in my experience all documentation produced by this method has been bland and sterile, full of facts but lacking in useful information. For that reason RADICORE does not use phpdoc. All my documentation is hand crafted and therefore vastly superior, as demonstrated in RADICORE - Functions, Methods and Variables.
- Alignment of '=' - where there is a series of statements which perform assignments it always looks better if the '=' appears in the same column. So instead of:
$foo = 'foo'; $foobar = 'foobar'; $stuff = 'stuff'; $morestuff = 'morestuff'; $rubbish = 'rubbish';
try this instead:$foo = 'foo'; $foobar = 'foobar'; $stuff = 'stuff'; $morestuff = 'morestuff'; $rubbish = 'rubbish';
Code which looks clean and tidy shows that the author has a clean and tidy mind, and a clean and tidy mind generally produces better code than that of an untidy and disorganised slob. - Line width - although it is possible to write lines of infinite length they become unmanageable in the viewing area of most IDEs. The simplest solution is to restrict line length to whatever will fit in your viewing area. This can easily be achieved using PHP's concatenation facilities, as in:
$query = 'SELECT user_id, user_name, start_date, end_date, is_disabled, pswd_count' .', pswd_chg_date, language_code, email_addr, role_id ' .'FROM mnu_user ' .'LEFT JOIN mnu_role ON (mnu_role.role_id=mnu_user.role_id)' ."WHERE user_id='$user_id'";
You can also use a slightly different variation, as in:$query = "SELECT user_id, user_name, start_date, end_date, is_disabled , pswd_count, pswd_chg_date, language_code, email_addr, role_id FROM mnu_user LEFT JOIN mnu_role ON (mnu_role.role_id=mnu_user.role_id) WHERE user_id='$user_id'" ;
- Objects - these are an essential part of Object Oriented Programming (OOP), but there are two ways in which they can be used - indiscriminately and intelligently. Some people believe that you must use objects at every possible opportunity, but my philosophy is to only use them sparingly, and only when they provide an obvious benefit. It is therefore perfectly acceptable to continue using procedural functions.
- Class sizes - Some people like to limit their classes to no than N methods (where N is a totally arbitrary number). This is an artificial limitation which I do not accept. The principle of encapsulation is that ALL the properties and ALL the methods for an entity are placed in the SAME class, so if a class ends up with 100s of properties and 100s of methods then so be it. Breaking it down into smaller classes is not only breaking encapsulation, it actually makes it more difficult for a newcomer to understand how all those classes fit together.
- Function/Method sizes - Some people like to limit their methods/functions to no more than N lines of code (where N is a totally arbitrary number). This is an artificial limitation which I do not accept. If the function I am looking at has more lines than will fit in a single screen then do what I do - use the scroll wheel on the mouse.
- Design Patterns - there are two ways in which these can be used - indiscriminately and intelligently. Each pattern is a particular solution to a particular problem, and not necessarily the only solution, so if you don't have the problem there is no point in implementing the solution. I initially designed RADICORE as an implementation of the 3-Tier Architecture, and the Model-View-Controller design pattern just appeared without any conscious effort on my part. The only design pattern that I ever read about and then implemented was the Singleton. When it comes to building transactions for the end-user I prefer to use my library of Transaction Patterns as they are more powerful.
- RADICORE works with register_globals and magic_quotes_gpc both turned OFF. Directives in the
.htaccess
file override anything which is set in yourphp.ini
file. - RADICORE works with default_charset set to "UTF-8" which means that data containing accented characters can be used without requiring the multi byte string functions to be enabled. Again there is a setting in the
.htaccess
file which overrides anything which is set in yourphp.ini
file. - Session variables - these are all referenced through the $_SESSION array and not via session_register.
- Global variables - although it is good practice to pass variables in and out of functions by using function arguments, there are a small number of variables which are used by the framework which may be of use within any other function. It would be just too messy to pass these around as separate function arguments, so it is permissible to address them using:
$GLOBALS['foobar']
- The RADICORE framework works in both PHP 4 and PHP 5. Where there are differences they are enclosed in user-defined functions and held in separate files. For example, when running with PHP 4 the framework uses the DOM XML and Sablotron XSLT extensions which are accessed from within file
include.xml.php4.inc
, but when running with PHP 5 the framework uses the DOM and XSL extensions which are accessed from within fileinclude.xml.php5.inc
. The framework then loads in the correct file with the following code:if (version_compare(phpversion(), '5.0.0', '<')) { require 'include.xml.php4.inc'; } else { require 'include.xml.php5.inc'; } // if
NOTE: The RADICORE framework dropped support for PHP4 on October 1st 2016 in order to provide support for PHP7. What worked for PHP5 also works for PHP7.
- PHP tags - the RADICORE framework does not use short tags. The RADICORE philosophy is to do things properly in long-hand instead of trying to make false economies with piddlng little short-cuts.
- error_reporting - should be set to E_ALL in the development environment, with display_errors and log_errors also set on to help track any errors.
You may also be interested in some articles which I wrote several years ago regarding programming standards:
Amended: 12 December 2020 (Database design tips)
Published: 21 April 2006