7. Case Study: Managing a Product Database on the Web
The code for this case study is available |HERE|.
Objectives:
- Write a class to manage a product database
- Write a web application based on this class
- introduce style sheets
- propose an initial development methodology for simple web applications
- introduce JavaScript in the client browser
Credits: The essence of this case study was drawn from the book "Les cahiers du programmeur - PHP/MySQL" by Jean-Philippe Leboeuf, published by Eyrolles.
7.1. Introduction
A retailer wants to manage the items he sells in his store. He already has an Access application at home that does this job, but he is tempted by the world of the web. He has an account with an internet service provider that allows its customers to install PHP scripts in their personal folders. This enables them to create dynamic websites. In addition, these same customers have a MySQL account that allows them to create tables to provide data to their PHP scripts. Thus, the shop owner has a MySQL account with the login "adarticles" and password "mdparticles." He has a database named "darticles" over which he has full access rights. Our merchant therefore has everything needed to put his product management system online. With your help—since you have web development skills—he is embarking on this venture.
7.2. The database
Our merchant has created the following mockup of the web homepage interface he would like:

There would be two types of users:
- administrators who would have full access to the product table (add, edit, delete, view, etc.). They would be able to use all the menu items listed above. In particular, they would be able to execute any SQL query via the [SQL Query] option.
- Regular users (non-administrators) who would have restricted rights: the right to add, edit, delete, and view. They may have only some of these rights, such as the right to view only.
Since there are various types of database users with different permissions, authentication is required. This is why the homepage begins with this step. To determine who is who and who has permission to do what, two tables—USERS and PERMISSIONS—will be used. The USERS table would have the following structure:
![]() |
|
The table contents could be as follows:

The RIGHTS table specifies the rights of non-administrator users listed in the USERS table. Its structure is as follows:
![]() |
|
The table contents could be as follows:

Notes:
- A user U who is in the USERS table but not in the RIGHTS table has no rights.
- In our example, users will only have access to a single table, the ARTICLES table. But our forward-thinking merchant has nevertheless added the table field to the structure of the RIGHTS table in order to give himself the option of adding new tables to his application later on.
- Why manage permissions in our own tables when we assume we’ll be using a MySQL database capable of managing these permissions in its own tables (and better than we can)? Simply because our merchant lacks administrative privileges on the MySQL database that would allow him to create users and grant them permissions. Let’s not forget that the MySQL database is hosted by an internet service provider and that the merchant is merely a user of it with no administrative rights (fortunately). However, he does have full access to a database called dbarticles, which he currently accesses using the username admarticles and the password mdparticles. This database contains all the application’s tables.
The ARTICLES table contains information about the items sold by the merchant. Its structure is as follows:
![]() |
|
Its content, initially used for testing purposes, could be as follows:

7.3. Project constraints
The merchant is migrating a local ACCESS application to a web application. He does not know what will become of it or how it will evolve. However, he would like the new application to be easy to use and scalable. For this reason, his IT consultant envisioned, during the design of the tables, that there could be:
- various users with different permissions: this will allow the merchant to delegate certain tasks to others without granting them administrative rights
- in the future, tables other than the ARTICLES table
The same consultant makes other suggestions:
- he knows that in software development, the presentation layer and the processing layer must be clearly separated. The architecture of a web application is often as follows:
![]() |
The user interface here is a web browser, but it could also be a standalone application that sends HTTP requests to the web service over the network and formats the results it receives. The application logic consists of scripts that process user requests, in this case PHP scripts. The data source is often a database, but it can also be an LDAP directory or a remote web service. It is in the developer’s best interest to maintain a high degree of independence between these three entities so that if one of them changes, the other two do not have to change, or only minimally. The merchant’s IT consultant then makes the following proposals:
- We will place the application's business logic in a PHP class. Thus, the [Application Logic] block above will consist of the following elements:
![]() |
Within the [Application Logic] block, we can distinguish
- the [IE=Input Interface] block, which serves as the application’s entry point. It remains the same regardless of the client type.
- the [Business Classes] block, which contains the classes necessary for the application’s logic. These are independent of the client.
- the block of response page generators [IS1 IS2 ... IS=Output Interface]. Each generator is responsible for formatting the results provided by the application logic for a given client type: HTML code for a browser or a WAP phone, XML code for a standalone application, ...
This model ensures a high degree of independence from clients. Whether the client changes or we wish to evolve the way results are presented, it is the output generators [IS] that will need to be created or adapted.
- In a web application, the separation between the presentation layer and the business logic layer can be enhanced by using style sheets. These govern the presentation of a web page within a browser. To change this presentation, simply modify the associated style sheet. There is no need to alter the business logic. We will therefore use a style sheet here.
- In the diagram above, the business class will interface with the data source. By assumption, this source is a MySQL database. To allow for migration to another database, we will use the PEAR library, which provides database access classes independent of the actual database type. Thus, if our merchant becomes wealthy enough to install a Microsoft IIS web server in their company, they will be able to replace the MySQL database with SQL Server without having to (or with very little need to) change the business class.
7.4. The Products Class
The **articles** class could be defined as follows:
<?php
// Items class working with an item database consisting of the following tables
// items: (code, name, price, currentStock, minimumStock)
// users: (username, password, admin)
// rights: (username, table, add, update, delete, view)
// It is the user of the class who must provide the login/password required to perform any operation on the database
// Therefore, they already have full access to the database. This means there is no need to take
// take any special security precautions here
// libraries
require_once 'DB.php';
class articles{
// attributes
var $sDSN; // connection string
var $sDatabase; // database name
var $oDB; // database connection
var $errors; // list of errors
var $oResults; // result of a SELECT query
var $connected; // Boolean indicating whether or not we are connected to the database
var $sQuery; // the last query executed
var $sUser; // ID of the logged-in user
var $bAdmin; // true if the user is an administrator
var $permissions; // dictionary of their permissions table ->> array(view, add, delete, edit)
// constructor
function articles($dDSN, $sUser, $sMdp) {
// $dDSN: dictionary defining the connection to be established
// $dDSN['dbms']: the type of database management system to connect to
// $dDSN['host']: the name of the host machine
// $dDSN['database']: the name of the database to connect to
// $dDSN['admin']: the login of the database owner to connect to
// $dDSN['mdpadmin']: their password
// $sUser: the username of the user who wants to use the article database
// $sMdp: their password
// creates a connection in $oDB to the database defined by $dDSN using the credentials of $dDSN['admin']
// if the connection is successful and the user $sUser is authenticated
// loads the user $sUser's permissions into $bAdmin and $dDroits
// sets the database connection string in $sDSN
// sets $sDataBase to the name of the database being connected to
// sets $connected to true
// if the connection fails or if the user $sUser is not properly identified
// stores the appropriate error messages in the $aErrors list
// closes the connection if necessary
// sets $connected to false
...
}//constructor
// ------------------------------------------------------------------
function connect(){
// (re)connect to the database
...
}//connect
// ------------------------------------------------------------------
function disconnect(){
// close the connection to the database $sDSN
...
}//disconnect
// -------------------------------------------------------------------
function execute($sQuery, $bAdmin) {
// $sQuery: query to execute
// $bAdmin: true if execution is requested as an administrator
...
}//execute
// --------------------------------------------------------------------------
function addArticle($dArticle){
// adds an item $dArticle (code, name, price, currentStock, minimumStock) to the items table
...
}//add
// ----------------------------------------------------------------------
function modifyArticle($dArticle){
// modifies an item $dArticle (code, name, price, currentStock, minimumStock) in the items table
...
}//update
// ----------------------------------------------------------------------
function deleteArticle($sCode){
// deletes an item from the items table
// for which we have the code $sCode
...
}//delete
// ----------------------------------------------------------------------
function checkItem(&$dItem){
// checks the validity of an item $dArticle (code, name, price, currentStock, minimumStock)
...
}//check
// --------------------------------------------------------------------------
function selectArticles($dQuery){
// executes a SELECT query on the articles table
// this query has three components
// list of columns in $dQuery['columns']
// filtering in $dQuery['where']
// Sort order in $dQuery['orderby']
...
}//selectArticles
// --------------------------------
function existsArticle($sCode){
// returns TRUE if the item with code $sCode exists in the items table
...
}//existsArticle
// --------------------------------------
function existsUser($sUser, $sPassword) {
// checks if the user $sUser with password $sMdp exists
// returns (int $iError, string $sAdmin, hashtable $dRights)
// $iError = -1 for any database operation error - the $aErrors list is then populated
// $iError = 1 if the user is not found (does not exist or incorrect password)
// $iError = 2 if the user exists but has no permissions in the permissions table
// $iError = 3 if the user exists and is an administrator
// $iError = 0 if the user exists and is not an administrator
// $sAdmin="y" if the user exists and is an administrator ($iError==3), otherwise it is equal to the empty string
// $dRights is the user's permissions dictionary if they are not an administrator ($iError == 0)
// otherwise it is an empty array
// the keys of the dictionary are the tables on which the user has rights
// the value associated with this table is in turn a dictionary where the keys are the permissions
// (view, add, edit, delete) and the values are the strings 'y' (yes) or 'n' (no) as appropriate
...
}//existeUser
// --------------------------------------
function getCodes(){
// returns the array of codes
....
}//getCodes
}//class
?>
Comments
- The articles class uses the PEAR::DB library to access the database, hence the command
This inclusion assumes that the DB.php script is located in one of the directories specified in the include_path option of the PHP configuration file.
- The builder needs to know which database to connect to and under which identity. This information is provided in the $dDSN dictionary. Recall that the initial assumption was that the database was named dbarticles and belonged to a user named admarticles with the password mdparticles. Also note that this application supports multiple users with different permissions. There is an ambiguity here that needs to be resolved. The connection is indeed opened under the identity of admarticles, and ultimately, all operations on the dbarticles database will be performed under this identity, since it is the only name recognized by the MySQL DBMS that has sufficient privileges to manage the dbarticles database. To "simulate" the existence of different users, we will have the admarticles user operate with the permissions of the user whose login ($sUser) and password ($sMdp) are passed as parameters to the constructor. Thus, before performing an operation on the articles database, we will verify that the user ($sUser, $sMdp) has the necessary permissions to do so. If so, the admarticles user will perform the operation on their behalf.
- The login and password of the product database administrator must be passed to the constructor. This is a sound precaution. If these two pieces of information were hardcoded into the class, any user of the class could easily impersonate the product database administrator. In fact, a PHP class is not protected. Therefore, the class’s $bAdmin attribute—which indicates whether the user ($sUser, $sMdp) for whom we are working is an administrator or not—could very well be set directly from the outside, as in the following example:
$oArticles = new Articles($dDSN, $sUser, $sMdp)
// here $sUser has been recognized as a non-database administrator user
$oArticle->bAdmin=TRUE;
// now $sUser has become an administrator
PHP is not Java or C#, and a PHP class is merely a data structure slightly more advanced than a dictionary, but one that does not offer the security of a true class where the bAdmin attribute would have been declared private or protected, making it impossible to modify from the outside. Because the user of the class must know the login and password of the article database administrator, only the latter can use the class. The previous operation is therefore no longer of any interest to him. The class is there solely to provide him with development conveniences. An important consequence is that there is no need to take security precautions. Once again, whoever uses the *articles* class is necessarily the administrator of the product database.
- The class handles database connection errors or any other errors in a uniform manner by populating the $aErrors attribute with the error message(s). After each operation, the user of the class must therefore check this list.
- The methods addArticle, updateArticle, deleteArticle, selectArticles, and execute are directly derived from the web interface mockup presented earlier. They correspond to the options in the menu. The addArticle and modifyArticle methods rely on the verifyArticle method to ensure that the item to be added or modified contains correct data. In the same vein, the existsArticle method ensures that we are not about to add an article that already exists. We could do without this method if we use an articles table where the code is the primary key. In that case, the DBMS itself will signal the failure of the addition due to a duplicate. It will likely do so with an error message that is hard to read and in English.
- An item to be modified or deleted will be identified by its unique code. The `getCodes` method retrieves all these codes.
- The disconnect method closes the connection to the database, which was opened when the object was created. We don’t see the point here of the connect method, which would re-establish a connection to the database. This allows us to open and close this connection at will using the same object. The benefit only becomes apparent when used in conjunction with the web application. The application will create an items object and store it in a session. While the session will be able to retain most of the object’s attributes across successive client-server exchanges, it cannot retain the attribute representing the open connection. This connection must therefore be reopened with each new client-server exchange. We will request a persistent connection so that the open connection is stored in a connection pool and remains open permanently. Thus, when the script requests a new connection, it will be retrieved from the connection pool. We therefore achieve the same result as if the session had been able to store the open connection.
- The `existeUser` method allows the constructor to determine whether the user `$sUser`, identified by the password `$sMdp`, actually exists. If so, the method checks whether the user is an administrator (as indicated in the `USERS` table) and stores this information in the `$bAdmin` attribute. If they are not an administrator, the method retrieves their permissions from the DROITS table and stores them in the $dDroits attribute, which is a double-indexed dictionary: $dDroits[$table][$permission] is 'y' if the user $sUser has the $permission on the $table and 'n' otherwise.
Write the articles class. Database access will be handled using the PEAR::DB library, which allows you to work independently of the exact database type.
7.5. The structure of the web application
Now that we have the "business logic" class for managing the article database, we can use it in various environments. Here, we propose using it in a web application. Let’s explore it through these different pages:
7.5.1. The application's main page
Let’s return to the home page we’ve already seen:
1234

All pages in the application will have the structure shown above: a table with two rows and three columns comprising four sections:
- Zone 1 forms the first row of the table. It is reserved for the title, possibly accompanied by an image. The three columns of this row are merged here.
- The second row has three areas, one per column:
- Zone 2 contains the menu options. It, in turn, contains a table with one column and several rows. The menu options are placed in the rows of the table.
- Zone 3 is empty and serves only to separate zones 2 and 4. We could have done this differently to achieve this separation.
- Zone 4 is the one that contains the dynamic part of the page. This is the part that changes from one action to another, while the others remain the same.
The PHP script generating this template page will be called main.php and could look like this:
<html>
<head>
<title>Article Management</title>
<link type="text/css" href="<?php echo $dConfig['urlPageStyle'] ?>" rel="stylesheet" />
</head>
<body background="<?php echo $dConfig['urlBackGround'] ?>">
<table>
<tr height="60">
<td colspan="3" align="left" valign="top" >
<h1><?php echo $main["title"] ?></h1>
</td>
</tr>
<tr>
<td>
<table>
<tr>
<td class="menutitle" >
<a href="<?php echo $main["links"]["login"] ?>" ?>Login</a>
</td>
</tr>
<tr>
<td><br /></td>
</tr>
<tr>
<td class="menutitle" >
Usage
</td>
</tr>
<tr height="10"></tr>
<tr>
<td class="menublock" >
<img alt="-" src="../images/radio.gif" />
<a href="<?php echo $main["links"]["addArticle"] ?>" ?>
Add an article
</a>
</td>
</tr>
<tr>
<td class="menublock" >
<img alt="-" src="../images/radio.gif" />
<a href="<?php echo $main["links"]["updateArticle"] ?>">
Edit an article
</a>
</td>
</tr>
<td class="menublock" >
<img alt="-" src="../images/radio.gif" />
<a href="<?php echo $main["links"]["deleteArticle"] ?>">
Delete an article
</a>
</td>
</tr>
<tr>
<td class="menublock" >
<img alt="-" src="../images/radio.gif" />
<a href="<?php echo $main["links"]["selectArticle"] ?>">
List articles
</a>
</td>
</tr>
<tr>
<td><br /></td>
</tr>
<tr>
<td class="menutitle" >
Administration
</td>
</tr>
<tr height="10"></tr>
<tr>
<td class="menublock" >
<img alt="-" src="../images/radio.gif" />
<a href="<?php echo $main["links"]["sql"] ?>" >
SQL query
</a>
</td>
</tr>
</table>
</td>
<td>
<img alt="/" src="../images/pix.gif" width="10" height="1" />
</td>
<td>
<fieldset>
<legend><?php echo $main["caption"] ?></legend>
<?php
include $main["content"];
?>
</fieldset>
</td>
</tr>
</table>
</body>
</html>
The configured sections of the page have been highlighted in the listing above. The template page is configured in several ways:
- by a $main dictionary with the following keys:
- title: title to be placed in zone 1 of the page
- links: dictionaries of links to be generated in the menu column. These links are associated with the menu options in zone 2
- content: URL of the page to display in zone 4
- by a $dConfig dictionary containing information extracted from an application configuration file named config.php
- by classes that are part of the stylesheet used by the page:
The page uses the following style classes here:
- menutitle: for a main menu option
- menublock: for a secondary menu option
Changing any of these parameters changes the page's appearance. For example, changing $main['title'] will change the title of zone 1.
7.5.2. Typical processing of a client request
The client interacts with the application via the links in area 2 of the template page. These links will be of the following type:
refers to the current action from among the following:
| |||||||||||
an action can be performed in several steps - refers to the current step | |||||||||||
session token when the session has started - allows the server to retrieve information stored in the session during previous exchanges |
Similarly, the action attribute in forms will have the same format. For example, on the home page there is a login form in section 4. The HTML tag for this form is defined as follows:
The client’s request is processed by the application’s main script, called apparticles.php. Its job is to construct the response to the client. It will always proceed in the same way:
- Based on the action name and the current phase, it will forward the request to a specialized function. This function will process the request and generate the appropriate response page. For each client request, there may be several possible response pages: page1, page2, ..., pagen. These pages contain information that must be calculated by the function. They are therefore parameterized pages. They will be generated by the scripts page1.php, page2.php, ..., pagen.php.
- For consistency, the variable parts of the pages to be displayed in zone 4 of the template page will also be placed in the $main dictionary.
Suppose that, in response to a request, the server must send the page pagex.php to the client. It will proceed as follows:
- It will place the values required for the page pagex.php into the $main dictionary
- it will set $main['content'], which specifies the URL of the page to be displayed in zone 4 of the template page, to the URL of pagex.php
- it will request the display of the template page with the instruction
The template page will then be displayed with the code from the pagex.php script in zone 4, which will be evaluated to generate the content of zone 4. Recall that this is a simple cell in an array. Therefore, the HTML code generated by pagex.php must not begin with the tags <HTML>, <HEAD>, <BODY>, .... These have already been output at the beginning of the template page. Here is an example of what the login.php script that generates zone 4 of the home page might look like:
<form name="frmLogin" method="post" action="<?php echo $main["post"] ?>">
<table>
<tr>
<td>login</td>
<td><input type="text" value="<?php echo $main["login"] ?>" name="txtLogin" class="text"></td>
</tr>
<tr>
<td>password</td>
<td><input type="password" value="" name="txtMdp" class="text"></td>
<td><input type="submit" value="Login" class="submit"></td>
</tr>
</table>
</form>
We can see that the page:
- is reduced to a form
- is styled by both the $main dictionary and the stylesheet.
7.5.3. The configuration file
It is always best to configure applications as much as possible to avoid having to modify the code simply because, for example, you decided to change the path of a script or an image. The main application, apparticles.php, will therefore load a configuration file, config.php, upon startup:
In this file, we will include configuration directives for PHP and initializations of global variables:
<?php
// PHP configuration
ini_set("register_globals", "off");
ini_set("display_errors", "off");
ini_set("expose_php", "off");
ini_set("session.use_cookies", "0"); // no cookies
// basic article configuration
$dConfig["DSN"]=array(
"dbms"=>"mysql",
"admin"=>"admarticles",
"mdpadmin"=>"mdparticles",
"host"=>"localhost",
"database"=>"dbarticles"
);
// page URLs
$dConfig['urlBackGround']="../images/standard.jpg";
$dConfig["urlPageStyle"]="mystyle.css";
$dConfig["urlAppArticles"]="apparticles.php";
$dConfig["urlPageMain"] = "main.php";
$dConfig["urlPageLogin"]="login.php";
$dConfig["urlPageErrors"] = "errors.php";
$dConfig["urlPageInfo"] = "info.php";
$dConfig["urlPageAddArticle"] = "addarticle.php";
$dConfig["urlPageUpdateArticle1"] = "updatearticle1.php";
$dConfig["urlPageUpdateArticle2"] = "updatearticle2.php";
$dConfig["deleteArticle1.php"] = "deletearticle1.php";
$dConfig["urlPageDeleteArticle2"] = "deletearticle2.php";
$dConfig["urlPageSelectArticle1"] = "selectarticle1.php";
$dConfig["urlPageSelectArticle2"] = "selectarticle2.php";
$dConfig["urlPageSQL1"] = "sql1.php";
$dConfig["urlPageSQL2"] = "sql2.php";
$dConfig["urlPageSQL3"]="sql3.php";
// links on the main page
$main["links"]["login"]="$sUrlAppArticles?action=authenticate&phase=0";
$main["links"]["addArticle"] = "$sUrlAppArticles?action=addArticle&phase=0";
$main["links"]["updateArticle"] = "$sUrlAppArticles?action=updateArticle&phase=0";
$main["links"]["deleteArticle"] = "$sUrlAppArticles?action=deleteArticle&phase=0";
$main["links"]["selectArticle"]="$sUrlAppArticles?action=selectArticle&phase=0";
$main["links"]["sql"] = "$sUrlAppArticles?action=sql&phase=0";
// store $main in the configuration
$dConfig["main"] = $main;
?>
7.5.4. The stylesheet associated with the template page
We saw that the server’s response had a single format: that of main.php. You may have noticed that this script produces a plain page devoid of any visual styling. This is a good thing for several reasons:
- the developer doesn’t have to worry about the page’s visual presentation. They may not necessarily have the skills to create visually appealing pages. Here, they can focus entirely on the code.
- script maintenance is simplified. If the scripts included presentation attributes, neither the code structure nor the presentation structure would be clearly visible. The visual design of the pages is often delegated to a graphic designer. The designer would likely not want to have to search through a script they don’t understand to find the presentation attributes they need to modify.
However, the visual appearance of the pages must be carefully considered. After all, this is what attracts users to a website. Here, the presentation is delegated to a stylesheet. The main.php page specifies in its code which stylesheet to use for rendering it:
<head>
<title>Article Management</title>
<link type="text/css" href="<?php echo $dConfig['urlPageStyle'] ?>" rel="stylesheet" />
</head>
The stylesheet used in this document is as follows:
BODY {
background: url(../images/standard.jpg);
border: 2px none #FFDAB9;
font-family: Garamond;
font-size: 16px;
margin-left: 0px;
padding-left: 20px;
}
INPUT {
background: #EEE8AA;
border: 1px solid #EE82EE;
font-family: Garamond;
font-size: 18px;
}
INPUT.submit{
font-family: "Times New Roman";
font-size: 16px;
background: #FA8072;
border: 2px double Green;
font-weight: bold;
text-align: center;
vertical-align: middle;
cursor: pointer;
}
TD.menutitle{
background-image: url(../images/menugelgd.gif);
height: 23px;
text-align: center;
vertical-align: middle;
background: url(../images/menugelgd.gif) no-repeat center;
}
TD.menublock{
background: url(../images/bandegrismenugd.gif) repeat-x;
text-align: left;
vertical-align: middle;
}
A {
font-family: "Comic Sans MS";
color: #FF7F50;
font-size: 15px;
text-decoration: none;
}
A:hover {
background: #FFA07A;
color: Red;
}
FIELDSET {
border: 1px solid #A0522D;
background: #FFE4C4;
margin: 10px 10px 10px 10px;
padding-left: 10px;
padding-right: 10px;
padding-bottom: 10px;
}
LEGEND{
background: #FFA500;
}
TH {
background: #228B22;
text-align: center;
vertical-align: middle;
}
TD.label{
border: 1px solid #008B8B;
color: #339966;
}
H1 {
font: bold 20px/30px Garamond;
color: #FF7F50;
background: #D1E1F8;
background-attachment: fixed;
text-align: center;
vertical-align: middle;
font-family: Garamond;
}
SELECT.TEXT {
background: #6495ED;
text-align: center;
color: Aqua;
}
We won’t go into the details of this stylesheet. We’ll accept it as is. A little later, we’ll see how to build and modify it. There is software available for that. Nevertheless, let’s outline the role of the presentation attributes used in the sheet:
Attribute: | controls the presentation of the HTML tag: |
<BODY> | |
<H1> (Header1) | |
<A> (Anchor) | |
sets the anchor's display attributes when the user hovers over it | |
<FIELDSET> - this tag is not recognized by all browsers | |
<LEGEND> - this tag is not recognized by all browsers | |
<INPUT> | |
<INPUT class="TEXT"> | |
<INPUT class="SUBMIT"> | |
<TH> (Table Header) | |
<TD class="menutitle"> (Table Data) | |
<TD class="menublock"> | |
<TD class="label"> |
Let’s look at an example of how these presentation rules can be written. In this example, we will use the TopStyle Lite software, available for free at http://www.bradsoft.com. Once the style sheet is loaded, a window with three sections appears:
- a text editing area. Styling attributes can be defined manually provided you know the rules for writing style sheets, which follow a standard called CSS (Cascading Style Sheets).
- Area 2 displays the editable properties of the attribute currently being created. This is the simplest method. It avoids the need to know the exact names of the numerous presentation attributes
- Zone 3 shows the visual appearance of the attribute being created
![]() |
In area 1 above, let’s copy and paste the INPUT.submit attribute into an INPUT.fantasy attribute. This attribute will set the presentation of the HTML tag <INPUT class="fantasy">
![]() |
Let’s use Area 2 to modify some of the properties of the INPUT.fantaisie attribute:
![]() |
From now on, any <INPUT ... class="fantaisie"> tag found in an HTML page linked to the previous stylesheet will be displayed as shown in the example in area 3 above.
Style sheets are very useful. Using them allows you to change the "look" of a web application by modifying just one thing: its style sheet. Style sheets are not supported by older browsers. The <link ..> directive below will be ignored by some of them:
<head>
<title>Article Management</title>
<link type="text/css" href="<?php echo $dConfig['urlPageStyle'] ?>" rel="stylesheet" />
</head>
In our application, this will result in the following home page:

This is a minimal page with no graphical elements. It could be worse. Some browser versions recognize style sheets but interpret them incorrectly. This can result in a distorted and unusable page. This raises the question of the client browser type. There are techniques that help determine the client browser type. They are not entirely reliable. We could then write different style sheets for different browsers or even create a version without a style sheet for browsers that ignore them. This, of course, complicates the development process. This significant issue has been ignored here.
With style sheets, we can envision offering a personalized environment to our application’s users. We could present them with a page offering several possible layout styles. They could choose the one that suits them best. This choice could be stored in a database. When the user logs in again, we could then launch the application with the style sheet they selected.
7.5.5. The application’s entry module
Clients will only be aware of the application’s entry module: apparticles.php. The general outline of its operation is as follows:
- The client’s request is retrieved and analyzed. It may or may not be parameterized. When it is parameterized, the expected parameters are as follows: action=[action]&phase=[phase]&PHPSESSID=[PHPSESSID]
- If the request is not parameterized or if the retrieved parameters do not match the expected ones, the server responds by sending the authentication page (username, password). Once the user has successfully logged in, a session is created. This session will be used to store information throughout client-server interactions.
- If a request is correctly recognized, it is processed by a module that depends on both the action and the current phase.
- All database access is handled through the business class articles.php.
- The processing of a request always ends by sending the main.php page to the client, in which the URL of the page to be placed in zone 4 of the template page has been specified in $main['content'].
The skeleton of the **apparticles.php** script could be as follows:
<?php
// management of an articles table
include "config.php";
include "articles.php";
// action to take
$sAction = $_POST["action"] ? $_POST["action"] : $_GET["action"] ? $_GET["action"] : "authenticate";
$sAction = strtolower($sAction);
// optional phase
$sPhase = $_POST["phase"] ? $_POST["phase"] : $_GET["phase"] ? $_GET["phase"] : "0";
// session
session_start();
$dSession = $_SESSION["session"];
// Is there a current session?
if(!isset($dSession)){
// user authentication
if($sAction=="authenticate" && $sPhase=="0") authenticate_0($dConfig);
if($sAction=="authenticate" && $sPhase=="1") authenticate_1($dConfig);
if($sAction=="authenticate" && $sPhase=="2") authenticate_2($dConfig);
// Invalid request
authenticate_0($dConfig);
}//if - no session
// retrieve the session
$dSession = unserialize($dSession);
// process the request
// ----- authentication
if($sAction=="authenticate" && $sPhase=="0") authenticate_0($dConfig);
if($sAction=="authenticate" && $sPhase=="1") authenticate_1($dConfig);
if($sAction=="authenticate" && $sPhase=="2") authenticate_2($dConfig);
// ----- add article
if($sAction=="addarticle" && $sPhase=="0") addArticle_0($dConfig,$dSession);
if($sAction=="addarticle" && $sPhase=="1") addArticle_1($dConfig,$dSession);
if($sAction=="addarticle" && $sPhase=="2") addArticle_2($dConfig,$dSession);
// ----- update article
if($sAction=="updatearticle" && $sPhase=="0") updateArticle_0($dConfig,$dSession);
if($sAction=="updatearticle" && $sPhase=="1") updateArticle_1($dConfig,$dSession);
if($sAction=="updatearticle" && $sPhase=="2") updateArticle_2($dConfig,$dSession);
if($sAction=="updatearticle" && $sPhase=="3") updateArticle_3($dConfig,$dSession);
// ----- delete article
if($sAction=="deletearticle" && $sPhase=="0") deleteArticle_0($dConfig,$dSession);
if($sAction=="deletearticle" && $sPhase=="1") deleteArticle_1($dConfig,$dSession);
if($sAction=="deletearticle" && $sPhase=="2") deleteArticle_2($dConfig,$dSession);
// ----- view articles
if($sAction=="selectarticle" && $sPhase=="0") selectArticle_0($dConfig,$dSession);
if($sAction=="selectarticle" && $sPhase=="1") selectArticle_1($dConfig,$dSession);
if($sAction=="selectarticle" && $sPhase=="2") selectArticle_2($dConfig,$dSession);
// ----- sending an SQL query
if($sAction=="sql" && $sPhase=="0") sql_0($dConfig,$dSession);
if($sAction=="sql" && $sPhase=="1") sql_1($dConfig,$dSession);
if($sAction=="sql" && $sPhase=="2") sql_2($dConfig,$dSession);
// invalid action - display the authentication page
session_destroy();
authenticate_0($dConfig,"0");
...
?>
Note the following points:
- Functions handling a specific client request end by generating the response page and with an exit statement that terminates the execution of the apparticles.php script. In other words, there is no "return" from these functions.
- The functions accept one or two parameters:
- $dConfig is a dictionary containing information from the config.php configuration file. All functions use it.
- $dSession is a dictionary containing session information. It exists only when the session has been created, i.e., after the user has successfully authenticated. This is why the authentication functions do not have this parameter.
7.5.6. The error page
Every software application must be able to properly handle errors that may occur. A web application is no exception to this rule. Here, in the event of an error, we will place the following errors.php page in zone 4 of the template page:
The following errors occurred:
<ul>
<?php
for($i=0;$i<count($main["errors"]);$i++){
echo "<li>".$main["errors"][$i]."</li>\n";
}//for
?>
</ul>
<a href="<?php echo $main["href"] ?>"><?php echo $main["link"] ?></a>
It displays the list of errors defined in $main['errors']. Additionally, it can provide a backlink, typically to the page that preceded the error page. This link is defined by a label $main['link'] and a URL $main['href']. To omit this link, simply set $main['link'] to an empty string. Here is an example of an error page in the event that the user logs in incorrectly:

7.5.7. The Information Page
Sometimes you may want to provide the user with a simple message, such as confirming that their login was successful. To do this, use the following infos.php page:
To display information in response to a client request,
- put the information in $main['infos']
- and place the URL of infos.php in $main['content']
Here is an example of the information returned when the user has logged in successfully:

7.6. How the application works
We now have a good idea of the general structure of the application to be written. We still need to outline the user’s flow through the application, the actions they can perform, and the responses they receive from the server. Once this is done, we can write the functions that handle the various client requests. In the following, we will describe how the application works through the pages presented to the user in response to certain actions. We will specify the following points each time:
the user’s initial action that led to the displayed response | |
the parameters sent by the client browser to the server in response to the user’s manual action | |
the script that generates section 4 of the template page |
7.6.1. Authentication
Before being able to use the application, the user must log in using the following page:

1 - Initial request for the URL apparticles.php 2 - Use of the Authentication option in the menu 3 - Direct request for the URL articles.php with incorrect parameters | |
1 - no parameters 2 - action=authenticate?phase=0 3 - a list of incorrect parameters | |
login.php |
On the home page, the [Add an article] link is in the following format: action=addarticle?phase=0. The other links follow the same format with action=(authenticate, updatearticle, deletearticle, selectarticle, sql). The user fills out the form and clicks the [Login] button:

The response is as follows:

[Login] button | |
action=authenticate?phase=1 | |
infos.php |
The page title has been modified to display the user's login and their administrator/user privileges. Additionally, all links in Zone 2 have been modified to reflect that a session has started. The parameter PHPSESSID=[PHPSESSID] has been added to them.
If the server was unable to identify the client, the client will receive a different response:

[Login] button | |
action=authenticate?phase=1 | |
errors.php |
The [Back to login page] link is a link to the URL apparticles.php?action=authenticate&phase=2&txtLogin=x. This link takes the client back to the login page where the login field is populated with the value of the txtLogin parameter:

[Back to login page] link | |
action=authenticate?phase=2&txtLogin=x | |
login.php |
7.6.2. Add an Article
The [Add an article] menu link takes you to the following page in section 4 of the template page:

[Add an article] link | |
action=addArticle?phase=0&PHPSESSID=[PHPSESSID] | |
addarticle.php |
The user fills in the fields and submits them to the server using the [Add] button, which is a submit button. No validation is performed on the client side. The server handles this. It may return an error page in response, as shown in the example below:
Request | Response |
![]() | ![]() |
[Add] button | |
action=addArticle?phase=1&PHPSESSID=[PHPSESSID] | |
errors.php |
The [Back to the article addition page] link allows you to return to the entry page:
Request | Response |
![]() | ![]() |
[Back to the article addition page] link | |
action=addArticle?phase=2&PHPSESSID=[PHPSESSID] | |
article.php |
If the addition is successful, the user receives a confirmation message:
Request | Response |
![]() | ![]() |
[Add] button | |
action=addArticle?phase=1&PHPSESSID=[PHPSESSID] | |
infos.php |
7.6.3. Viewing articles
The menu link [List articles] takes the following page to zone 4 of the template page:

[List Articles] menu link | |
action=selectArticle?phase=0&PHPSESSID=[PHPSESSID] | |
select1.php |
A SELECT [columns] FROM articles WHERE [where] ORDER BY [orderby] query will be issued on the articles table, where [columns], [where], and [orderby] are the contents of the fields above. For example:
Request |
![]() |
Response |
![]() |
[Display] button | |
action=selectArticle?phase=1&PHPSESSID=[PHPSESSID] | |
select2.php |
The request may be invalid, in which case the client receives an error page:
Request |
![]() |
Response |
![]() |
In both cases (whether there are errors or not), the [Back to the item selection page] link takes you back to the select1.php page:
Request |
![]() |
Response |
![]() |
[Back to the item selection page] link | |
action=selectArticle?phase=2&PHPSESSID=[PHPSESSID] | |
select1.php |
7.6.4. Editing articles
The [Edit an article] menu link takes you to the following page in section 4 of the template page:

menu link [Edit an article] | |
action=updateArticle?phase=0&PHPSESSID=[PHPSESSID] | |
updatearticle1.php |
Select the article code to edit from the drop-down list and click [OK] to edit the article with that code:
Request | Response |
![]() | ![]() |
[OK] button | |
action=updateArticle?phase=1&PHPSESSID=[PHPSESSID] | |
updatearticle2.php |
Once the article page to be edited has been retrieved, the user can make changes:
Request | Response |
![]() | ![]() |
[Edit] button | |
action=updateArticle?phase=2&PHPSESSID=[PHPSESSID] | |
infos.php |
The user may make mistakes while editing:
Request | Response |
![]() | ![]() |
The [Back to article edit page] link allows you to return to the entry page:

[Back to article edit page] link | |
action=updateArticle?phase=3&PHPSESSID=[PHPSESSID] | |
updatearticle2.php |
7.6.5. Deleting an article
The [Delete an article] menu link takes you to the following page in section 4 of the template page:

[Delete an article] menu link | |
action=deleteArticle?phase=0&PHPSESSID=[PHPSESSID] | |
deletearticle1.php |
The user selects the code of the item to be deleted from a drop-down list:
Request | Response |
![]() | ![]() |
[OK] button | |
action=deleteArticle?phase=1&PHPSESSID=[PHPSESSID] | |
deletearticle2.php |
The user confirms the deletion of the article by clicking the [Delete] button:
Request | Response |
![]() | ![]() |
[Delete] button | |
action=deleteArticle?phase=2&PHPSESSID=[PHPSESSID] | |
infos.php |
7.6.6. Administrator queries
The [SQL Query] menu link takes you to the following page in section 4 of the template page:

[SQL Query] menu link | |
action=sql?phase=0&PHPSESSID=[PHPSESSID] | |
sql1.php |
Type the SQL query text into the input field and use the [Execute] button to run it. Only an administrator can submit these queries, as shown in the following example:
Request | Response |
![]() | ![]() |
[Execute] button | |
action=sql?phase=1&PHPSESSID=[PHPSESSID] | |
errors.php |
The [Back to SQL Query Submission Page] link takes you back to the input page:

link [Back to the SQL query submission page] | |
action=sql?phase=2&PHPSESSID=[PHPSESSID] | |
sql1.php |
If you are an administrator and the query is syntactically correct:
Request |
![]() |
you get the result of the query:
Response |
![]() |
[Execute] button | |
action=sql?phase=1&PHPSESSID=[PHPSESSID] | |
sql2.php |
You can send table update requests:
Request |
![]() |
Response |
![]() |
[Run] button | |
action=sql?phase=1&PHPSESSID=[PHPSESSID] | |
infos.php |
7.6.7. Work to be done
Write the scripts and functions required for the application:
username | type | role |
script | the entry point for processing client requests | |
function | processes the request with parameters action=authenticate&phase=0 | |
function | processes the request with parameters action=authenticate&phase=1 | |
function | processes the request with parameters action=authenticate&phase=2 | |
function | processes the request with parameters action=addArticle&phase=0 | |
function | processes the request with parameters action=addArticle&phase=1 | |
function | processes the request with parameters action=addArticle&phase=2 | |
function | processes the request with parameters action=updatearticle&phase=0 | |
function | processes the request with parameters action=updatearticle&phase=1 | |
function | processes the request with parameters action=updatearticle&phase=2 | |
function | processes the request with parameters action=updatearticle&phase=3 | |
function | processes the request with parameters action=deletearticle&phase=0 | |
function | processes the request with parameters action=deletearticle&phase=1 | |
function | processes the request with parameters action=deletearticle&phase=2 | |
function | processes the request with parameters action=selectarticle&phase=0 | |
function | processes the request with parameters action=selectarticle&phase=1 | |
function | processes the request with parameters action=selectarticle&phase=2 | |
function | processes the request with parameters action=sql&phase=0 | |
function | processes the request with parameters action=sql&phase=1 | |
function | processes the request with parameters action=sql&phase=2 | |
script | generates the template page | |
script | generates the login page | |
script | generates the error page | |
script | generates the information page | |
script | generates the page for adding an article | |
script | generates page 1 of the article edit form | |
script | generates page 2 of the article edit | |
script | generates page 1 of the article deletion | |
script | generates page 2 of the article deletion process | |
script | generates page 1 of the article selection | |
script | generates page 2 of the article selection | |
script | generates page 1 of the query submission | |
script | generates page 2 of the query output |
7.7. Upgrading the application
At this point, we have an application that does what it’s supposed to do with acceptable usability. We’re going to improve it in several areas:
- the DBMS
- its security
- its appearance
- its performance
7.7.1. Changing the database type
Our study assumed that the DBMS used was MySQL. Switch to a different DBMS and demonstrate that the only change required is to the definition of the $dDSN variable in the config.php configuration file.
7.7.2. Improving security
When developing a web application, you should never assume that the client is a browser and that the request it sends is controlled by the form you sent it prior to that request. Any program can act as a client for a web application and thus send any request—whether parameterized or not—to the application. The application must therefore verify everything.
If we look at the code in the apparticles.php script, we see
- that no action other than authentication can take place without a session. A session exists only if the user has successfully authenticated. Recall that a session is identified by a fairly long string of characters called the session token, which has the following form: 176a43609572907333118333edf6d1fb. This token can be sent to the application in various ways, for example by using a parameterized URL:
apparticles.php?PHPSESSID=176a43609572907333118333edf6d1fb.
A program that repeatedly requests the previous URL while randomly varying the token in the hope of finding the correct one would likely take many days to generate the correct combination, given the sheer number of possible combinations. By that time, since the session has a limited duration, it will most likely have expired. Another risk is that the token, transmitted in plain text over the network, could be intercepted. This is a real risk. An encrypted connection between the server and its client can therefore be used.
- Once the session is started, only certain actions are allowed. A URL configured as action=cheat&phase=0&PHPSESSID=[PHPSESSID] would be rejected because the "cheat" action is not an authorized action. When the parameters (action, phase) are not recognized, our application responds with the authentication page.
However, the application does not verify whether the authorized actions follow the correct sequence. For example, the following two actions:
- action=addArticle&phase=0&PHPSESSID=[PHPSESSID]
- action=updateArticle&phase=1&PHPSESSID=[PHPSESSID]
are two authorized actions. However, action 2 is not authorized to follow action 1.
How can we track the sequence of URLs requested by the client browser?
We can use two PHP variables: $_SERVER['REQUEST_URI'] and $_SERVER['HTTP_REFERER'], which are two pieces of information sent by client browsers in their HTTP headers.
$_SERVER['REQUEST_URI']: This is the URI requested by the client. For example
$_SERVER['HTTP_REFERER']: This is the URL that was displayed in the browser before the new URL that the browser is currently requesting (the previous URI). For example, if the browser that displayed the URI mentioned above makes a new request to a server, the $_SERVER['HTTP_REFERER'] variable for that request will have the value
To verify that two actions in our application follow one another in order, we can proceed as follows:
During action 1:
- we note the requested URI (URI1) and store it in the session
During action 2:
- retrieve the HTTP-REFERER for action 2. From this, we derive the URI (URI2) of the URL that was previously displayed in the browser making the request.
- retrieve the URI (URI1) that was stored in the session, which is the URI of the action previously requested from the server
- If Action 2 follows Action 1, then URI2 must equal URI1. If this is not the case, we would refuse to perform the requested action and display the authentication page.
- We store the URI URI2 of the current action in the session for verification of the next action. And so on.
Here is an example. After authentication, we click the [Add an article] link:

The URL of this page is:
http://localhost:81/st/php/articles/gestion/articles8/apparticles.php?action=addArticle&phase=0&PHPSESSID=006a63e6027f16c70b63cdae93405eeb
Directly in the browser's [Address] field, we modify the URL as follows:
http://localhost:81/st/php/articles/gestion/articles8/apparticles.php?action=deleteArticle&phase=1&PHPSESSID=006a63e6027f16c70b63cdae93405eeb
We then get the authentication page:

This warrants an explanation. When a URL is requested by typing it directly into the browser’s address bar, the browser does not send the HTTP_REFERER header. Our application therefore cannot find the URI of the previous action, which it had stored in the session. It then returns the authentication page in response.
This mechanism works well for web browsers but not at all for a programmed client. A programmed client can send whatever HTTP_REFERER header it wants. It can therefore "cheat" by claiming it went through a certain step when it actually did not. You must therefore ensure that the sequence of steps is followed. Thus, if the requested action is action=addArticle&phase=1 (entry), then the previous action must necessarily be action=deleteArticle&phase=0 (initial request for the entry page) or action=addArticle&phase=2 (return to entry after an incorrect addition). Similarly, if the requested action is action=addArticle&phase=2 (add), then the previous action must be action=addArticle&phase=1 (entry). We can force the user to follow these sequences.
While the first mechanism is general and can be applied to any application, the second requires application-specific coding and is more complex: you must review all possible user actions and their sequences. These can be stored in a dictionary, as shown in the following code:
// authentication
$dPrec['authenticate']['0']=array();
$dPrec['authenticate']['1']=array(
array('action'=>'authenticate','phase'=>'0'),
array('action'=>'authenticate','phase'=>'2')
);
$dPrec['authenticate']['2']=array(
array('action'=>'authenticate','phase'=>'1'),
);
// add article
$dPrec['addarticle']['0']=array();
$dPrec['addarticle']['1']=array(
array('action'=>'addarticle','phase'=>'0'),
array('action'=>'addarticle','phase'=>'2')
);
$dPrec['addarticle']['2']=array(
array('action'=>'addarticle','phase'=>'1'),
);
// edit article
$dPrec['updatearticle']['0']=array();
$dPrec['updatearticle']['1']=array(
array('action'=>'updatearticle','phase'=>'0'),
);
$dPrec['updatearticle']['2']=array(
array('action'=>'updatearticle','phase'=>'1'),
array('action'=>'updatearticle','phase'=>'3')
);
$dPrec['updatearticle']['3'] = array(
array('action'=>'updatearticle','phase'=>'2'),
);
// delete article
$dPrec['deletearticle']['0'] = array();
$dPrec['deletearticle']['1']=array(
array('action'=>'deletearticle','phase'=>'0'),
);
$dPrec['deletearticle']['2']=array(
array('action'=>'deletearticle','phase'=>'1'),
);
// select articles
$dPrec['selectarticle']['0']=array();
$dPrec['selectarticle']['1']=array(
array('action'=>'selectarticle','phase'=>'0'),
array('action'=>'selectarticle','phase'=>'2')
);
$dPrec['selectarticle']['2']=array(
array('action'=>'selectarticle','phase'=>'1'),
);
// admin query
$dPrec['sql']['0']=array();
$dPrec['sql']['1']=array(
array('action'=>'sql','phase'=>'0'),
array('action'=>'sql','phase'=>'2')
);
$dPrec['sql']['2']=array(
array('action'=>'sql','phase'=>'1'),
);
$dPrec['action']['phase'] is an array containing the actions that can precede the action and the phase used as an index for the dictionary. These preceding actions are also represented by a dictionary with two keys: 'action' and 'phase'. If an action can be preceded by any action, then $dPrec['action']['phase'] will be an empty array. The absence of an action in the dictionary means that it is not allowed. Consider the "authenticate" action above:
// authentication
$dPrec['authenticate']['0']=array();
$dPrec['authenticate']['1']=array(
array('action'=>'authenticate','phase'=>'0'),
array('action'=>'authenticate','phase'=>'2')
);
$dPrec['authenticate']['2'] = array(
array('action'=>'authenticate','phase'=>'1'),
);
The code above means that the action action=authenticate&phase=0 can be preceded by any action, that action=authenticate&phase=1 can be preceded by action=authenticate&phase=0 or action=authenticate&phase=2, and that action=authenticate&phase=2 can be preceded by the action action=authenticate&phase=1.
Write the following function:
// ---------------------------------------------------------------
function chainingOK(&$dConfig, &$dSession, $sAction, $sPhase){
// checks if the current action ($sAction, $sPhase) can follow the previous action
// stored in $dSession['previous']
// the dictionary of allowed sequences is in $dConfig['previous']
// returns TRUE if the sequence is possible, FALSE otherwise
....
This function allows the main application to verify that the sequence of actions is correct:
<?php
// managing a table of articles
include "config.php";
include "articles.php";
// session
session_start();
$dSession = $_SESSION["session"];
// action to take
$sAction = $_POST["action"] ? $_POST["action"] : $_GET["action"] ? $_GET["action"] : "authenticate";
$sAction = strtolower($sAction);
// possible phase of the action
$sPhase = $_POST["phase"] ? $_POST["phase"] : $_GET["phase"] ? $_GET["phase"] : "0";
// Is there a current session?
if(!isset($dSession)){
// user authentication
if($sAction=="authenticate" && $sPhase=="0") authenticate_0($dConfig);
if($sAction == "authenticate" && $sPhase == "1") authenticate_1($dConfig);
if($sAction=="authenticate" && $sPhase=="2") authenticate_2($dConfig);
// abnormal action
authenticate_0($dConfig);
}//if - no session
// retrieve the session
$dSession = unserialize($dSession);
// Is the sequence of actions normal?
if( ! sequenceOK($dConfig, $dSession, $sAction, $sPhase)){
// abnormal sequence
authenticate_0($dConfig);
}//if
// Process actions
if($sAction=="authenticate"){
if($sPhase=="0") authenticate_0($dConfig);
if($sPhase=="1") authenticate_1($dConfig);
if($sPhase=="2") authenticate_2($dConfig);
}//if
if($sAction=="addarticle"){
...
7.7.3. Updating the "look"
Remember that one of the requirements set when designing this application was that it had to be scalable. Suppose that after a few weeks, we realize that the application’s usability needs to be improved. Modify the application so that the structure and layout of the template page are changed. The changes will be made in two places:
- in the main.php script, which defines the structure of the template page. Update this script.
- in the stylesheet that determines the application’s “look.” Change this.
7.7.4. Improving Performance
For now, we have opted for a thin-client browser: it does nothing but display content. We can make it perform processing by including scripts in the web pages we send to it. These can be written in various languages, notably VBScript and JavaScript. Internet Explorer and Netscape dominate the browser market in a ratio of roughly 60/40. Furthermore, IE is only available on Windows and not on Unix, for example, where Netscape predominates. Netscape does not natively execute VBScript, whereas both browsers execute JavaScript. Since Netscape still holds a significant share of the browser market, VBScript should be avoided. JavaScript is therefore generally used in client-side scripts.
Processing tasks that do not require server intervention are delegated to client-side scripts. In our application, it would be beneficial for the client browser to send a request to the server only after verifying it. Thus, there is no point in sending an authentication request to the server if the user has left the [login] field blank in the authentication form. It is preferable to notify the user that their request is invalid:

Note that this will not prevent the server from verifying that the login field is not empty, since the client is not necessarily a browser, and the previous verification may not have been performed. Assuming that the client is a browser poses a major security risk for the application.
Review the various points at which the browser sends information to the server and, when this information can be verified, write one or more JavaScript functions that will allow the browser to verify the validity of the information before sending it to the server.
To revisit the previous example, the login.php script that generates the authentication page becomes the following:
<script language="javascript">
function check(){
// check that a login form is present
with(document.frmLogin){
champs=/^\s*$/.exec(txtLogin.value);
if(champs!=null){
// no login
alert("You did not enter a login");
txtLogin.focus();
return;
}//if
// the data is there - we send it to the server
submit();
}//with
}//check
</script>
<form name="frmLogin" method="post" action="<?php echo $main["post"] ?>">
<table>
<tr>
<td>login</td>
<td><input type="text" value="<?php echo $main["login"] ?>" name="txtLogin" class="text"></td>
</tr>
<tr>
<td>password</td>
<td><input type="password" value="" name="txtMdp" class="text"></td>
<td><input type="button" onclick="check()" value="Login" class="submit"></td>
</tr>
</table>
</form>
7.8. Further Reading
To conclude, here are a few ideas for expanding on this case study:
- It would be interesting to see if the standard page of this application could be turned into a class. This class could then be used in other applications.
- Our application is well-suited for browser-based clients but less so for "standalone application" clients. These must:
- establish a TCP connection with the server
- "communicate" with it via HTTP
- parse its HTML responses to find the desired information, since the standalone client will likely not be interested in the presentation HTML code intended for browsers.
It would be beneficial for our application to generate XML rather than HTML. Its clients could then be either browsers (recent ones, at least) or standalone applications. The latter would have no trouble finding the information they seek, since the server’s XML response would contain no presentation information, only content.
- We would certainly need to address simultaneous access to the article database. There are at least two points to clarify:
- Does the DBMS used by the application properly handle concurrent access to the same article? For example, what happens if two users edit the same article at the same time (they click the [Edit] button simultaneously)? This likely depends on the underlying DBMS.
- Currently, our application does not handle concurrent access. However, the database should remain in a consistent state, even if some unexpected behavior is to be expected. Consider the following sequence of events:
- User U1 begins editing an article
- User U2 begins deleting the same article shortly after
- Each of these actions requires client-server communication. Depending on how each user works, User U2 may finish their task before U1. When U1 finishes their edits and clicks [Edit] to save them, they will see the information page in response, with the DBMS indicating that [0 row(s) were modified], because the page they intended to edit was deleted in the meantime. The user will likely be surprised. From a usability standpoint, it would probably be better to display a page that more clearly indicates the error. Furthermore, one could consider granting the user exclusive access to an article as soon as they begin updating it. Another user attempting to edit the same article would be informed that another edit is in progress. This will pose a problem if the first user delays in saving their changes: others will be blocked. Solutions need to be found here, and these will depend largely on the capabilities of the DBMS being used. Oracle, for example, has greater capabilities in this area than MySQL.































