DRAFT REVISION
Jakarta, 7 February 2001
phpreactor developers
php(Reactor) <phpreactor@lists.3wsi.com>
Angus D Madden <angus@3wsi.com>
The following manual is a description of the methods, styles, procedures and philosophies used in building php(Reactor). This is basically an abridged copy of the 3WSI Developers' Manual.
7 February 2001 : Initial revision
none
I have included in this manual a few key concepts to make sure we all have the same mindset when at work. I start with a description of our design principles and goals, continue to talk about nitty-gritty stuff like coding style, and go on to talk about system architecture. Please use this as manual as a reference, I hope to add to it as time goes by and we grow larger.
Angus, 7 February 2001
php(Reactor) application design is based on the following principles:
Security
Maintainability
Usability
Performance
If you are unsure about any of them, please ask your team or refer to http://3wsi.com/articles/index.php?artid=1
We want to get the most use out of our existing systems. We expand that simple concept to our application design goals:
Build for Distribution
Reuse/Adaptation of Existing Code
Shared Code
Commodity Technologies
User Controlled Content
Building for Distribution means that we make all of our code flexible and customizable. The idea is that if we build an application for site A, we can easily adapt the same solution when it is requested for site B.
Granted, building for distribution takes more time at the beginning, but saves us time in the long run. In general, building for distribution requires a greater investment at the beginning of a project. But it will gives us easier maintenance schedules and give our products economies of scale.
Reuse/Adaptation of Existing Code means that we try to get the most out of our code. Generally, it means that it is better to use an existing module than create a new one. An example of this is the comment functions in the articles and polls boards - they used the existing BBS code rather than creating a new system. It saves of the time of developing a new system, and more importantly, it saves us the time it would take to maintain a new system.
So, whenever you are developing a new app, make sure you have checked our existing apps to see if there is something that can be changed/modified to create a new system. A key part of this is knowing the existing code backward and forward.
Shared Code means that everyone works on the same code. If you notice a bug in someone else's code, go ahead and change it. That's why we use CVS. Of course, you should test any changes fully before you commit them. If it is a major change, confer with the developer beforehand. If you notice a stylistic problem or something that will cause serious problems in the future, bring it to everyone's attention on the mailing list.
Commodity Technologies means that we build our solutions to run on standard web servers with standard application environments. In general, most of the products we build should be deployable on shared web servers. You'll find that we generally interpret this to means that it will run on apache with the Linux operating system. You'll also find that we favor open source software. This is not a coincidence.
User Controlled Content means that we give our users the tools they need to operate and update their own web sites. All content should be easily configurable through a browser interface. Users demand this kind of control, and we'll give it to them. It also relieves us of an unimportant maintenance burden.
This section will cover the details of how design our applications; it will also outline our coding styles.
A standardized application design is critical to our development process. The application design is a work in progress and should be continually evolving. A production example of the application design can be seen at our open source project, http://phpreactor.org/ .
To outline how script directories are organized:
inc/
contains library files and complete scipts. The scripts return no output by definition and care should be taken to make sure there is no extra whitespace at the beginning and end of the files. The naming convention is boardname.inc.php or boardnamescript.inc.php
conf/
contains configuration and language variables. Anything that can be put into a configuration variable should be put here. Any text output by your applications should have a customizable language value. The naming convention is boardname.conf.php
templates/
contains the templates for the main layout and graphic design of the site, in particular top.tpl.php and int.tpl.php . Examples of these files can be seen in the default distribution
modules/
contains small, portable snippets of code. Generally, modules are designed to be portable within the framework of other applications, allowing us to mix and match components. When building new modules remember to follow the framework set in the existing modules. Naming convention is board_in_board.module.php
sql/
contains the table definitions for the database that sits behind the applications. Naming convention is create_tablename.(my|pg)sql.php
backups/
contains a script to backup the database. Gzipped database dumps are stored here
cron/
contains a crontab file which contains any periodic jobs that should be run by the system
htdocs/
contains scripts accessible to the public. Scripts follow the following convention:
boardname/
a pseudo-directory which contains a set of apps. Example, forums/ or articles/ . The boardname concept is very important, as we shall see later. Note - when a portability of a directory is useful, scripts within the directory are symlinked or moved to the inc/ directory and included from there. With this method, it becomes very easy to maintain multiple extensions of the same application
boardname/admin/
the administration interface for a set of applications. Protected with .htaccess directtives, this directory should be accessed only with ssl-encrypted connections
img/
collection of standard images used by the applications
images/
collection of user-contributed or site-specific images used by the application
css/
collection of cascading style sheets used by the application
Let me also list some important variables with global scope:
$pathtohomedir - the path to the root directory containing the applications (inc/, conf/, htdocs/, etc).
$reactorcore - the URL of the base of the applications
$reactorname - the plaintext name of the site running the applications
$boardname - very important. The directory where the current script lies - for example, if the URL is http://phpreactor.org/forums/browse.php, then http://phpreactor.org is the $reactorcore, and forums is $boardname. The $boardname variable affects the names of the tables created, so if we copied forums/ to newforums/, we could create and entirely new set of forums which would not affect the original forums in any way.
$showmax - the number of records to return from any database query.
$start - the position of the first record to return from any database query.
$errormessage - a collector variable for any errors produced by the system. This should only be set when an error has occurred.
There are quite a few important variables for more refined customization. Whenever including customization variables, remember to check the existing variables first, and if a similar variable already exists, use it.
Wars are fought over coding style - this is not necessary. The key point is that everyone's code should look the same, to the point that we couldn't figure out who wrote it by looking at it. I have posted a guidelines here, follow them. Please note that the indentation for the code below isn't correct as I haven't taken the time to put pre tags in the html. Assume standard C emacs-mode indentation.
functions. All function definitions should look like this:
function foobar($var1 = "foo", $var2 = "bar")
{
global $reactorcore, $boardname, $errormessage;
$out = '';
if ($boardname == 'forums')
{
$out .= 'this is not forums.\n';
$errormessage .= 'get out of here man<br>\n';
}
else
{
$out .= 'this is '.$boardname.'.\n';
}
$out .= '<h2>this script is located at '.getenv("PHP_SELF").'</h2>\n';
return($out);
}
Note the following:
default values are specified for the arguments
echo is not used. This allows us to collect the output of the function for use in other functions. VERY IMPORTANT.
' is used instead of " - that way the static strings are not parsed by the server, saving server resources
variables are concatenated into statements using the . operator - making the variables stand out in the code
opening curly brace on new line - good idea
getenv() function is used to return environmental variables - this prevents worrying about global scope
newlines after markup - in general, insert newlines after <table>, <tr>, <td>, <p>, <hN>, <br>, <ul>, <ol>, <li> - newlines are not necessary after <a>, <img>, <b>, <i>
I have not put any comments in the above code. You should. Put them everywhere.
Thanks to Tim Purdue of PHPBuilder for many of these tips.
forms. Forms should have the same general structure:
<?php
/*
* filename - author - description of what it does - copright
* no warranty.
*/
//INCLUDE STATEMENTS FIRST
//ALWAYS INCLUDE THIS
if(!defined("REACTOR_INC_GLOBAL")) { include("inc/global.inc.php"); }
//INCLUDE DB FUNCTIONS
if(!defined("REACTOR_INC_BOARD")) { include($pathtohomedir."/inc/board.inc.php"); }
//initialize any variables
$out = "";
//deinfe template body function see template files for details
function tpl_body()
{
global $username, $password, $upload, $errormessage, $pathtohomedir, $out, $something;
//NOW VALIDATE VARIABLES
// username must be letters or numbers and between 4-20 chars long
if (!ereg("[[:alnum:]]{4,20}", $username))
{
$errormessage .= picklang($boardlang["bad username"]).'<br>\n';
}
// password must be letters or numbers or punctuation
if (!ereg("[[:alnum:][:punct:]]{4,20}", $password))
{
$errormessage .= picklang($boardlang["bad password"]).'<br>\n';
}
// MAKE SURE TO VALIDATE ALL VARIABLES ONE BY ONE
// I HAVE LEFT SOME OUT FOR BREVITY'S SAKE
// if uploading and no error, execute
if ($upload && !errormessage)
{
process_form($username, $password);
if (!$errormessage)
{
//now tell the user it was success
$tpl_title = picklang($boardlang["success"]);
$tpl_message = picklang($boardlang["success msg"]);
//include simple template
{ include($pathtohomedir."/templates/simple.tpl.php"); }
exit;
}
}
//if not uploading, or an error has been set, print the input form
if (!$salon || $errormessage)
{
//if an errormessage exists, show the error
if ($errormessage)
{
$out .= '<b style="color:red;">'.$errormessage.'</b><br>\n';
}
$out .= '<form action="'.getenv("PHP_SELF").'" method="post" name="pstfrm">\n';
//declare hidden variables at top of form
$out .= '<input type="hidden" value="1">\n';
$out .= 'enter username: \n';
$out .= '<input type="text" name="username" value"'.stripslashes($username).'" size="20" maxlength="20"><br>\n';
$out .= 'enter password: \n';
$out .= '<input type="password" name="password" value"'.stripslashes($password).'" size="20" maxlength="20"><br>\n';
$out .= 'choose something: ';
$out .= '<select name="something" size="1">\n';
$out .= '<option value="" '.(($something="")? 'selected' : '').'>\n';
$out .= '<option value="animal" '.(($something="animal")? 'selected' : '').'>\n';
$out .= '<option value="vegetable" '.(($something="vegetable")? 'selected' : '').'>\n';
$out .= '<option value="mineral" '.(($something="mineral")? 'selected' : '').'>\n';
$out .= '</select><br>\n';
$out .= '<input type="submit" value="'.picklang($boardlang["send"]).'"><br>\n';
$out .= '</form>\n';
}
//send to client - because this is a template function we use echo - normally we would just use return
echo $out;
}
//define other template functions
function tpl_additional_head()
{
global $reactorcore;
echo "<script type=text/javascript src=$reactorcore/global.js></script>";
}
// the following functions are rarely used - don't use them without a good reason
function tpl_page_top() {}
function tpl_page_foot() {}
function tpl_sidebar() {}
//include template
{ include($pathtohomedir."/templates/int.tpl.php"); }
?>
Note the following:
the general stucture is:
include statements
variable initialization
variable validation
logic - process or print form
any errors produced will be reported and the form return to it's submitted state. This is very important for workflow
most of the logic is carried out in subroutines leaving the general structure of the script readable
the form is its own target - rather than having distinct files for input and processing - it makes maintenance easier
picklang() is used any time text content is sent to the browser - trust me, it is easier to build this in from the beginning
This manual is most certainly incomplete - it will be added to as we grow and develop. It is a subset of the larger 3wsi Developers Manual, which I can't distribute for corporate reasons. I hope to publish the 3wsi developers manual of the 3wsi website, the manual has additional procedures for configuring a server, common development tools and utilities, and more. And finally, forgive the spelling mistakes in this text. My hands never do what I tell them.
general: