Plus:
Book Reviews, Product Reviews and much more..
Conquer JpGraphConquer JpGraph
XML Transformation With PEAR
Using PEAR::XML_Transformer
Advanced Features Revealed
Form Validation From the Outside In
A New And Interesting Perspective
Practical Web Services with PHP
APRIL 2003
www.phparch.com
VOLUME II - ISSUE 4
The Magazine For PHP Professionals
php|architect
Migrating WebMigrating Web
Applications to PHPApplications to PHP
Computer Science Concepts With PHPComputer Science Concepts With PHP
Writing A Parser And Expression Evaluator
As PHP’s importance grows on the IT
scene—something that is happening every
day—it’s clear that its true capabilities go
well beyond what it’s being used for
today. The PHP platform itself has a lot of
potential as a general-purpose language,
and not just a scripting tool; just its basic
extensions, even discounting repositories
like PEAR and PECL, provide a high-
quality array of functionality that most of
its commercial competitors can’t afford
without expensive external components.
At php|a, we’ve always felt that our mis-
sion is not limited to try our best to pro-
vide the PHP community with a publica-
tion of the highest possible quality. We
think that our role is also that of reinvest-
ing in the community that we serve in a
way that leads to tangible results.
To that end, this month we’re launching
the php|architect Grant Program, a new
initiative that will see us award two
$1,000 (US) grants to PHP-related proj-
ects at the end of June.
Participating to the program is easy. We
invite all the leaders of PHP projects to
register with our website at
http://www
.phparch.com/grant
and submit
their applications for a grant. Our goal is
to provide a financial incentive to those
projects that, in our opinion, have the
opportunity to revolutionize PHP and its
position in the IT world.
In order to be eligible for the Grant
Program, a project must be strictly related
to PHP, but not necessarily written in PHP.
For example, a new PHP extension written
in C, or a new program in any language
that lends itself to using PHP in new and
interesting ways would also be acceptable.
The only other important restriction is that
the project must be released under either
the LGPL, the GPL or the PHP/Zend
license. Thus, commercial products are
not eligible.
Submit Your Project Today!
Visit http://www
.phparch.com/grant
for more information
Introducing
the php|architect Grant
Program
3
IINNDDEEXX
Departments
php|architect
Features
10 Advanced Features in JpGraph
By Jason E. Sweat
21 Form Validation From the
Outside In
A New Perspective on Form Validation
By Peter James
30 The Realization of Freedom
Migrating From Proprietary Tools to PHP
By Dave Palmer
41 Practical Web ServicesWith PHP
and XML-RPC
By Stuart Herbert
52 Using The PEAR::XML_Transformer
By Bruno Pedro
61 When A Meets B
Writing a Parser and Expression Evaluator
in PHP
By Marco Tabini
5 EDITORIAL RANTS
php|architect: A New Community
7 NEW STUFF
35 REVIEW
From Electronic Microsystems
PostgreSQL Manager and MySQL Manager
66 BOOK REVIEWS
• MySQL
• Administering and Securing the Apache
Server
69 exit(0);
PHP For Suits: The Neverending Saga
By Marco Tabini
TABLE OF CONTENT
April 2003 · PHP Architect · www.phparch.com
Technologies Ltd.
Visit
www.zend.com
for evaluation version and ROI calculator
Zend Performance Suite
Reliable Performance Management for PHP
Serve More.
With Less.
The designers of PHP offer you the full spectrum of PHP solutionsThe designers of PHP offer you the full spectrum of PHP solutions
5
EEDDIITTOORRIIAALL RRAANNTTSS
php|architect
Volume II - Issue 4
April, 2003
Publisher
Marco Tabini
Editor-in-Chief
Brian K. Jones
brian@phpar
ch.com
Editorial Team
Arbi Arzoumani
Brian Jones
Peter James
Marco Tabini
Graphics & Layout
Arbi Arzoumani
Administration
Emanuela Corso
Authors
Stuart Herbert, Peter James, Dave Palmer, Bruno
Pedro, Jason E. Sweat, Marco Tabini
php|architect (ISSN 1705-1142) is published twelve times a year by Marco
Tabini & Associates, Inc., P.O. Box. 3342, Markham, ON L3R 6G6,
Canada.
Although all possible care has been placed in assuring the accuracy of the
contents of this magazine, including all associated source code, listings
and figures, the publisher assumes no responsibilities with regards of use
of the information contained herein or in all associated material.
Contact Information:
General mailbox:
Editorial:
Subscriptions:
Sales & advertising:
Technical support:
Copyright © 2002-2003 Marco Tabini & Associates,
Inc. — All Rights Reserved
pphhpp||aarrcchhiitteecctt:: AA NNeeww
CCoommmmuunniittyy
Since I first came on board as an editor
here (only days after the launch of
php|architect’s first issue), I’ve had the dis-
tinct pleasure of forming relationships
with some of the greatest minds in the
PHP community. As a budding Editor in
Chief, it has become quite clear to me that
our ability to form synergistic, collabora-
tive relationships with our authors is
something of an anomaly in the magazine
publishing industry. Lucky for me, it’s one
that is a welcome change to the authors
I’ve interacted with. I’m proud to have a
part in creating this apparently new para-
digm in publishing, and this seemingly
new perspective on the editor-author rela-
tionship.
I like to think of the editorial process as
the creation of what I am now dubbing ‘A
New Community’; A new community of
people from different walks of life, having
vastly different views on how different
technologies should work together, differ-
ent experiences to share, and from dis-
parate cultures and geographic regions of
the Earth. A community bonding on the
common goals of furthering both the
development of PHP as a complete, stable,
mature development platform, and the
progress of other PHP developers.
The end result of this highly iterative,
interactive process is what is ultimately
delivered to you by php|architect every
month. Not just a magazine, but a some-
what interactive experience in itself. Let’s
not forget that in addition to working hard
to produce the magazine each month, the
members of our editorial staff are also con-
sumers of this magazine! As a result, we
experience along with all of you the feel-
ing that we are really being taken by the
hand through what seems like a tour of a
particular project undertaken by a particu-
EDITORIAL
April 2003 · PHP Architect · www.phparch.com
April 2003 · PHP Architect · www.phparch.com
6
EDITORIAL
lar developer. At the end of the tour, we’re
handed the bits of code which are out in
production somewhere, making things
tick. As a consumer, I find this amazingly
useful. I hope anyone reading this agrees,
or will send me email telling me how we
can make things even better.
In addition, I invite those who might cur-
rently be passive consumers of information
to get involved! If you’re reading this,
you’re quite likely a PHP developer.
Chances are also good that you have a
unique opinion or perspective on some
aspect of technology that touches us here
in the PHP community. We invite you to
participate more actively in the ‘New
Community’ by sharing your knowledge,
thoughts, and ideas with the rest of us.
We’ve just released a new version of our
author’
s guidelines, which can help get
you started. Anything that this leaves you
wondering about can be addressed by ask-
ing our editorial staff at
write@phpar
ch.com.
It could very well be that you haven’t
participated yet because you haven’t seen
a product offered by php|architect that
looks like a good fit for your own work.
Maybe you want to write a book, or start a
new project of unforeseeable dimensions. I
urge anyone in this position to write to
editors@phpar
ch.com with your concerns
in this area. It’s quite possible that we have
a project in the works that could benefit
from your input.
The plain and unequivocal fact is that
php|architect is growing quite rapidly. This
growth stems from the ideas of those col-
laborating on the direction of php|archi-
tect as a diversified company – not just a
magazine. For example, php|architect has
recently been translated and redistributed
in Japanese for that market. This has been
a successful venture for all of those
involved, as well as the community at
large, and we urge any other parties inter-
ested in localized versions of php|architect
to contact us –
What’s more, php|architect will soon see
the release of our first full-fledged book,
with more work in this area ongoing. I
won’t use this space to divulge the details
now, but stay tuned to my monthly edito-
rial for more as things progress. In the
wake of Wrox’s disappearance from the
publishing world (a result of their parent
company’s apparent insolvency), we at
php|architect feel some obligation to at
least partially fill the gap that will
inevitably result in the area of authoritative
PHP coverage. From someone who owns
several Wrox books, I can sincerely say that
their work is well appreciated, and will be
sorely missed. We can only hope to do our
best to keep up with the needs of a con-
stantly growing developer base. Luckily,
we don’t have to have the breadth of cov-
erage in our books that Wrox had in theirs
– I have no desire to work on a Java book!
These are just a couple of the ideas we’re
currently pursuing. Since no entity can
claim monopoly ownership over all good
ideas, it’s only natural that we reach out
and make it known that we are a compa-
ny which fosters and values ideas. If you
have an idea for which an outlet has yet to
be identified, we hope you’ll consider con-
tacting us for help in discovering how best
to pursue your goals.
Now on with the show! I’m excited by
what lies between this editorial and
Marco’s exit(0) (actually, I kinda like the
editorial and exit(0) as well). I hope you
can find enlightenment and inspiration
from the articles which follow. Let us
know your thoughts:
editors@phpar
ch.com
NNEEWW SSTTUUFFFF
NEW STUFF
The PHING is Loose!
PHING (PHing Is Not Gnumake) is a make tool
written in PHP and based on the ideas and con-
cept of Apache Ant. It can basically do anything
you could do with a build system like Gnumake,
for example, but uses XML for storing the targets
and commands. Using XML, Phing avoids prob-
lems like the “Space before tab problem” in
Gnumake. Current features include processing
files while copying (e.g. do Token Replacement,
XSLT transformation etc.) and various file system
operations.
Phing is written in the PHP scripting language
and designed to be extensible. Thus, you can eas-
ily add the behaviour you need for your project by
adding a class (known as tasks). Additionally,
Phing is platform independent and runs on Unix
like systems and Windows. Phing is currently used
to build binarycloud, a PHP Application
Framework, and is maintained by its developers.
For more information, or to download Phing,
visit its website at http://binar
ycloud.com/phing/.
April 2003 · PHP Architect · www.phparch.com
7
PHP Architect Wo Yomitai!
We’re happy to announce the introduction of the Japanese edition of php|architect!
Published by Asial Corporation, the best PHP company in Japan, the publication is called PHP
Programmer’s Magazine and it pro-
vides all the great content of php|a,
plus many localized features specif-
ic to the Japanese market.
Asial (http://www
.asial.co.jp) is a
Tokyo-based company that special-
izes in the production of PHP sys-
tems and the localization of soft-
ware and documentation for the
Japanese market.
You can find the PHP
Programmer’s Magazine website at
http://www
.phppro.jp
If you are interested in localizing
php|a in your language, don’t hesi-
tate to drop us a note at
What’s NNew!
ionCube Announces
Special Offer
The ionCube standalone PHP encoder is a high per-
formance and complete PHP encoding solution,
employing the technique of compiled code encod-
ing to maximize both runtime performance and
code protection. Encoded scripts can be run by the
free Loader product with a standard PHP server in
one of two ways. The Loader can be installed in
php.ini, and this delivers the best performance and
is compatible with safe mode. For users with no
access to php.ini, on many systems the Loader can
be installed “on demand” by the scripts themselves.
This requires no php.ini edits or server restart.
The Base Edition comes with a single user license
and, as with all current ionCube products, full sup-
port and upgrades are included for Free.
To download an evaluation or purchase of the
standalone encoder, you can visit the ionCube web-
site at
.
NEW STUFF
eZ publish 3 Released
ezPublish 3 is a professional open source content
management system and development frame-
work.
As a CMS its most notable feature is its revolu-
tionary, fully customizable and extendable content
model. This is also what makes it suitable as a plat-
form for general web development. Its stand-
alone libraries can be used for cross-platform,
database independent PHP projects. eZ publish is
also well suited for news publishing, e-commerce
(B2B and B2C), portals, and corporate web sites,
intranets, and extranets.
For more info, visit the eZPublish website at
.
phpOpenTracker 1.1.1. Is
Unleashed!
phpOpenTracker is a framework solution for the
analysis of Web site traffic and visitor behaviour.
It features a logging engine that, either invoked
as a Web bug by an HTML image tag or embed-
ded with two lines of code into your PHP applica-
tion, logs each request to a Web site into a data-
base. One installation can track an arbitrary num-
ber of Web sites. Through its API, you can easily
access the gathered data and perform complex
operations on it (for instance, the analysis of your
visitors’ click paths).
For more information, visit the phpOpenTracker
website at http://www
.phpopentracker.de/.
MySQL AB Launches MySQL
4.0, Announces Certification
Program
MySQL AB, producers of the popular
MySQL database management system,
have announced the release of version
4.0 of their flagship product, which is
now officially ready for production. Meanwhile,
they have started development of version 4.1,
which will include such long-awaited goodies as
subqueries.
Through the MySQL certification program,
MySQL software developers can earn one or more
formal credentials that validate their knowledge,
experience and skill with the MySQL database and
related MySQL AB products.
The MySQL certification program consists of sev-
eral unique certifications. The first, which is now
generally available, is called the MySQL Core
Certification. The Core Certification provides
MySQL users with a formal credential that demon-
strates proficiency in SQL, data entry and mainte-
nance, data extraction for reporting and more.
The MySQL Professional Certification, which will
be available in Q3 of this year, is for the more
experienced MySQL user who wants to certify his
or her knowledge in MySQL database manage-
ment, installation, security, disaster prevention
and optimization. MySQL Core certification will be
a prerequisite for taking the Professional
Certification exam.
MySQL also plans to offer a MySQL PHP
Certification by the end of the year, which is
designed for the MySQL and PHP developer who
wants to simultaneously certify his knowledge of
MySQL and of the PHP Web development tool. In
addition, a MySQL DBA Certification, a top-level
certification for the most accomplished MySQL
gurus, will be offered in 2004.
If you are interested and want to know more,
check out the MySQL website at
http://www
.mysql.com.
PHP 4.3.2RC1 Released
The first public release candidate of the lat-
est version of PHP was posted for down-
load on the PHP.Net website earlier this
month. The new version includes several
bug fixes and a few new features com-
pared to version 4.3.1, which was released
in February in response to a CGI-related
security issue.
For more information, visit the PHP web-
site at http://www
.php.net.
April 2003 · PHP Architect · www.phparch.com
8
php|a
FFEEAATTUURREESS
Advanced FFeatures iin
JpGraph
FEATURES
April 2003 · PHP Architect · www.phparch.com
10
J
pGraph (http://www
.aditus.nu/jpgraph/) is a PHP
class library that easily enables you to generate profes-
sional quality graphs using a minimal amount of code.
This article is a case study illustrating some of JpGraph’s
advanced features; specifically, it covers the following:
• a generalized methodology for JpGraph script
development
• the evolutionary process of developing a
graph (in contrast to presenting only the final
product)
• the use of server-side caching with JpGraph
for performance
• the use of Client Side Image Maps (CSIM) to
implement “drill-down” functionality in your
graphs
Installation and Environment
The easiest way to get started with JpGraph is to down-
load the source, available at
http://www
.aditus.nu/jpgraph/jpdownload.php. Next,
unpack the source archive into a directory in PHP’s
include path. Now you can modify the paths to your
installed fonts, as well as to the cache directory. These
settings are found in jpgraph.php.
To verify that your installation is working, view the
testsuit.php file in the examples directory. This page
generates over 200 example graphs using JpGraph,
and allows you to review the code for each of them.
If you’re just learning JpGraph and exploring its capa-
bilities, you will find the manual very handy. It’s avail-
able at
http://www
.aditus.nu/jpgraph/jpdownload.php, and
has both a narrative text and an excellent class refer-
ence. You might also want to visit the JpGraph support
forum at />.
All of the scripts in this article were developed and
tested using PHP 4.3.0 (with the built-in GD2 library)
running as a module under Apache 1.3.27 on RedHat
Linux 7.2.
The code in this article was developed using MySQL
(http://www
.mysql.com/) as a database, and ADOdb
( />) as a database
abstraction layer. To this end, all of the scripts include
a common file called phpa_db.inc.php, shown in
Listing 1.
Advanced FFeatures iin
JpGraph
By Jason E. Sweat
This article originated as a case study in the newest book
in the Wrox Handbook series: PHP Graphics
( />This material was omitted in the final publication of the
book, and was modified for presentation here.
PHP Version: 4.0.4 minimum, 4.1 recommended
O/S: Any
Additional Software: JpGraph, GD Enabled PHP
REQUIREMENTS
FEATURES
April 2003 · PHP Architect · www.phparch.com
11
Advanced Features in JpGraph
This include file creates an ADOdb connection object
named $conn which is used to access the database
throughout the rest of the scripts. You'll want to
change the Connect() call's parameters to reflect
your setup, including the name of the database you
create for the examples.
ADOdb needs to be installed in a directory in PHP's
include path. For readers unfamiliar with the ADOdb
API, I'll give a very brief overview of some of the basic
functionality. In ADOdb you can fetch the results of an
SQL statement using
If your query is successful, this method will return an
ADOdb resultset object. If not, the method will return
false. Two resultset methods that are useful are
GetArray() and GetAssoc(). GetArray() will
return a vector of rows where each row is an associative
array of 'COLUMN' => 'VALUE'. The GetAssoc()
method will return an associative array, instead of a vec-
tor, where the index of the returned array for each row
is the value of the first column.
The examples below assume that you have the true-
type 'Arial' font installed. If this is not the case, JpGraph
will emit an error to this effect. An easy way to get
around this is change all of the references to the
FF_ARIAL font constant to FF_FONT1. FF_FONT1 is
a built-in system font, and while it won't look as pretty
as FF_ARIAL, it will allow you to view the examples.
That should be enough to get you through the exam-
ples ahead. Let's take a look at the case study.
Case Study
Our case study will look at the sales data for the ABC
Company, a fictitious manufacturer of widgets. ABC
makes everything from the economical $12.99 Widget
B through to the ultra-deluxe $1,499.50 Widget E. This
study focuses on the sales in the continental United
States, in which the company is divided into four
regions. The company sells to resellers via three chan-
nels: the Internet, a call center and various retail outlets.
You are asked to create a graph depicting the sales in
units and dollars. You'll need to further split this graph
by catalog item for each sales region, and make a com-
parison to the forecasted sales. ABC also requires that
you create a graph showing the year-to-date sales by
channel for each region. They need to be able to nav-
igate quickly between the two graphs.
Database Design
Six tables compose ABC's data model. The central
table is abc_sales. This table tracks information
about sales, including the time, the sales channel, the
location, the item, the quantity sold, and the revenue
generated.
$rs = $conn->Execute($sql_statement,
$bind_array);
<?php
error_reporting(E_ALL);
require_once 'adodb/adodb.inc.php';
define('MYSQL_DT_FMT', '%Y-%m-%d');
$conn = &ADONewConnection('mysql');
//$conn->debug=true;
$conn->Connect('localhost', 'phpa', 'phpapass',
'phpa');
$ADODB_FETCH_MODE = ADODB_FETCH_ASSOC;
?>
Listing 1: phpa_db.inc.php
db_inc.php
FEATURES
April 2003 · PHP Architect · www.phparch.com
12
Advanced Features in JpGraph
The other tables making up the data model are
abc_catalog, abc_channel, abc_forecast,
abc_region, and abc_state_region. The
abc_catalog table provides a surrogate key for par-
ticular items customers can purchase, a description of
the item, and the current unit price of the item. The
abc_channel table provides a surrogate key, name
and description of the market channel an item can be
purchased through (web, phone or retail in this study).
The abc_forecast table stores a forecasted sales plan
by item, channel, region and month. For each of these
"slices" of data, the quantity and revenue expected are
also stored. The abc_region table stores the surro-
gate key and description for each of the company's
sales regions. The abc_state_region table is a map-
ping between state abbreviation code and the sales
region to which the state belongs.
The SQL used to create the tables, along with 'INSERT'
statements to populate the smaller tables, is included in
this article's source directory in the mysql_ddl.sql
file. As for the other tables, the sales and sales forecast
data for the year can be simulated using the scripts
abc_gen_sales.php and abc_fcst_ins.php,
respectively. abc_gen_sales.php needs to be run
first, as abc_fcst_ins.php depends on that data it
creates.
Sales vs. Forecast Graph
Our first task is to create a dynamic graph comparing
the number of units sold and the revenue of those sales
to the forecasts made for each region. This information
is considered proprietary by the company, and is not
intended for public distribution. They would like the
chart to indicate this.
In developing PHP scripts to generate graphs using
JpGraph, I have found the following four-step process
useful:
1. Retrieve and manipulate the data for plot-
ting
2. Create the Graph object and set general
graph properties (like colors and plot axes)
3. Create the Plots to add to the Graph object
4. Finalize the Graph object and output the
graph
Following this process for graph development, you
first need to retrieve the sales and forecast data. To see
how you use ADOdb to retrieve the data for these
graphs, please review lines 26-122 of the
abc_reg_sales_graph.php file. The focus of this
article is on graphing, so the database retrieval and
graph data construction will not be covered in detail.
Because the graphs being produced are by region,
your script will need to accept and validate a parame-
ter for the region. If the value passed is a valid region,
assign it to the variable $region_id. If it is not valid,
generate an error.
There could be a little problem here. Since the out-
put of this script is a graph, your site will most likely
refer to this script using an HTML <img> tag, like so:
<img src="abc_reg_sales_graph.php">
If your script outputs a text message (like a PHP
error), this would result in an invalid image, and the
only thing a user will see is the missing image symbol
where your graph was supposed to be. The code
shown in Listing 2 validates the region, and shows how
to create a "graphical" error message. This code
assumes you have already queried the database for
valid regions, and stored them in a global array called
$regions. It is also assumed that you have included
Note: These scripts were originally written in
December of 2002. The data is simulated as if the
current date is December 15, 2002 in order to show
graphs with nearly complete data for the year.
Because it is now 2003, the scripts have been modi-
fied to query and show the prior year.
1 $region_id = check_passed_region('region');
2 if (!$region_id) {
3 graph_error('region parameter incorrect');
4 }
5
6 function check_passed_region( $parm )
7 {
8 global $regions;
9
10 if (array_key_exists($parm,$_GET)) {
11 $val = $_GET[$parm];
12 if (array_key_exists($val, $regions)) {
13 return $val;
14 }
15 }
16 return false;
17 }
18
19 function graph_error($msg)
20 {
21 $graph = new CanvasGraph(WIDTH, HEIGHT);
22
23 $t1 = new Text($msg);
24 $t1->Pos(0.05, 0.5);
25 $t1->SetOrientation('h');
26 $t1->SetFont(FF_ARIAL, FS_BOLD);
27 $t1->SetColor('red');
28 $graph->AddText($t1);
29
30 $graph->Stroke();
31 exit;
32 }
Listing 2: Generating a graphical error
region_grapherror.php
FEATURES
April 2003 · PHP Architect · www.phparch.com
13
Advanced Features in JpGraph
the jpgraph.php and jpgraph_canvas.php
library files.
After retrieving the "raw" data from your database,
you often need to manipulate it into a format better
suited for graphing. This often means making a series
of zero-indexed arrays of single data series'. Rather
than have different global arrays for each series, I pre-
fer to have a single associative array named
$graphData that contains indices naming each of the
individual arrays. The code in Listing 3 assembles these
pieces into the $graphData array, which will be used
for graphing.
Now let's take a look at some of the data by drawing
your first graph. This graph
will compare the number
of units moved to the fore-
casted number of units
that should have moved.
Listing 4 shows the code
for this.
This code highlights the
lightweight nature of the
JpGraph API. As you
enhance the appearance
of your graphs, the code
will expand, but the lines
of code required to pro-
duce a functional graph
really are minimal.
These few lines of code
complete the four-step
process outlined earlier. In
step 2 you create and con-
figure the Graph object.
1 $graphData['f_qty'] = array();
2 $graphData['labelX'] = array();
3 for ($i=0,$j=count($salesData); $i<$j; $i++) {
4 $row = $salesData[$i];
5 if ('A'==$row['short_desc']) {
6 $graphData['labelX'][] = strftime('%b', mktime(0, 0, 0, $row['m'], 1, $row['y']));
7 }
8 if (!array_key_exists($row['m']-1, $graphData['f_qty'])) {
9 $graphData['f_qty'][$row['m']-1] = $fcstData[$row['f_key']]['qty'];
10 $graphData['f_rev'][$row['m']-1] = $fcstData[$row['f_key']]['rev'];
11 $graphData['qty'][$row['m']-1] = $row['qty'];
12 $graphData['rev'][$row['m']-1] = $row['rev'];
13 } else {
14 $graphData['f_qty'][$row['m']-1] += $fcstData[$row['f_key']]['qty'];
15 $graphData['f_rev'][$row['m']-1] += $fcstData[$row['f_key']]['rev'];
16 $graphData['qty'][$row['m']-1] += $row['qty'];
17 $graphData['rev'][$row['m']-1] += $row['rev'];
18 }
19 if(!array_key_exists($row['short_desc'], $graphData)) {
20 $graphData[$row['short_desc']]['qty'] = array();
21 $graphData[$row['short_desc']]['rev'] = array();
22 }
23 $graphData[$row['short_desc']]['qty'][] = $row['qty'];
24 $graphData[$row['short_desc']]['rev'][] = $row['rev'];
25 }
Listing 3: Composing a single $graphData array
sales_graphdata_loop.php
$graph = new graph(WIDTH, HEIGHT);
$graph->SetScale('textlin');
$b1 = new BarPlot($graphData['qty']);
$l1 = new LinePlot($graphData['f_qty']);
$graph->Add($b1);
$graph->Add($l1);
$graph->Stroke();
Listing 4: Your first graph.
first_graph.php
Figure 1: Your first graph
FEATURES
April 2003 · PHP Architect · www.phparch.com
14
Advanced Features in JpGraph
In step 3, you create a BarPlot object and a LinePlot
object. You complete the process in step 4 by adding
the plots to the graph and using the
Graph::Stroke() method to output the graph.
Calling Graph::Stroke() with no arguments will
make JpGraph stream the image directly back to the
browser from the PHP script. The resulting graph is
shown in Figure 1.
The regular style line graph does not really seem
appropriate for this graph, since the data is not really
changing over the course of the month (forecasts are
fixed for the entire month). The forecasts might be bet-
ter represented using the 'step' line graph.
Let's incorporate this change and, while we're at it,
take a look at the revenue numbers instead. You can
do this by changing the two plots as shown in Listing
5. The output is shown in Figure 2.
The next step is to look at both units and revenue on
the same graph. Management is more concerned with
meeting the revenue forecast, so forecast revenue
should be the only line graph superimposed on the
chart (as opposed to forecasted units). There are two
challenges here. First, by looking at the two charts, you
can see that units and revenue are definitely on differ-
ent scales. Second, it would be nice to figure out a way
to have the last step of the line graph plot over the
December bar.
To represent units and revenue you will need to use
the second Y-axis feature of Jpgraph. Ideally you would
use a grouped bar graph. JpGraph, however, does not
allow us to add bars from different scales to the same
grouped bar plot. What you can do is use a little
deception to accomplish this. Make them two different
grouped bar plots, and then trick JpGraph into believ-
ing that they are next to another bar plot on the same
scale by adding a plot to each with all zero values. The
effect here is that one scale has the zero-value plot on
the right, pushing the real plot to the left, while the
other scale has the zero-value plot on the left, pushing
the real plot to the right. When they are finally com-
bined on the graph, they will appear to be a single
grouped bar plot, but will actually be scaled on their
respective axes. You will need some new data to
accomplish this effect, which we can get in Listing 6.
$b1 = new BarPlot($graphData['rev']);
$l1 = new LinePlot($graphData['f_rev']);
$l1->SetStepStyle();
$l1->SetColor('darkgreen');
$l1->SetWeight(3);
Listing 5: Plots to make the graph in Figure 2.
second_graph.php
Figure 2: Using a step-style line plot
for ($i=0,$j=count($graphData['labelX']); $i<$j; $i++) {
$graphData['zero'][$i] = 0;
}
//extend the forecast revenue line by repeating the last value
$graphData['f_rev'][$j] = $graphData['f_rev'][$j-1];
Listing 6: Creating a zerio-value plot.
zero_mod_graphdata_loop.php
FEATURES
April 2003 · PHP Architect · www.phparch.com
15
Advanced Features in JpGraph
You can create a callback function for formatting the
second 'Y-axis' labels. JpGraph will then call this func-
tion for each value to be labeled on the axis, and will
use the value returned by the function rather than the
raw number. This allows us to format numbers to dol-
lars, for instance, as shown in Listing 7.
To generate the plot (shown in Figure 3), modify your
graph generation script to include the code in Listing 8.
You have a very presentable graph now, but some
additional information would be useful. The company
would like to be able to differentiate between the dif-
ferent widgets' contributions to unit sales and revenue.
This can be accomplished by generating a 'stacked' bar
graph, to be added to each of the grouped bar plots.
This is shown in Listing 9.
This now results in the graph shown in Figure 4.
We're almost there. Just a little more formatting to
the plot area, axis titles and location of the legend is
needed. Also, we need make sure that viewers are
aware this graph is for internal purposes only.
One technique for marking an image as proprietary is
to add a background image to it. In this case, we'll use
the string "ABC Co. Proprietary" turned diagonally and
repeated. Note the color is much darker than you
would normally want to see on a "watermark". You can
adjust this with the
Graph::AdjBackgroundImage() method, which
can adjust the brightness, contrast and saturation of
the image prior to use in the graph. This can save you
the effort of doing this externally in an image-process-
ing program. See the img/abc-background.png
“...the lines of code required to
produce a functional graph
really is minimal.”
$graph->y2axis->SetLabelFormatCallback
('y_fmt_dol_thou');
function y_fmt_dol_thou($val)
{
return '$'.number_format($val/1000);
}
Listing 7: A label format callback.
format_callback.php
1 $graph->SetY2Scale('lin');
2 $graph->SetY2OrderBack(false);
3
4 //generate the individual plots
5 $b1 = new BarPlot($graphData['qty']);
6 $b2 = new BarPlot($graphData['rev']);
7 $b2->SetFillColor('lightgreen');
8 $b1z = new BarPlot($graphData['zero']);
9 $b2z = new BarPlot($graphData['zero']);
10 $l1 = new LinePlot($graphData['f_rev']);
11 $l1->SetStepStyle();
12 $l1->SetColor('darkgreen');
13 $l1->SetWeight(3);
14
15 //create the grouped plots
16 $gb1 = new GroupBarPlot(array($b1, $b1z));
17 $gb2 = new GroupBarPlot(array($b2z, $b2));
18
19 //add the plots to the graph object
20 $graph->Add($gb1);
21 $graph->AddY2($gb2);
22 $graph->AddY2($l1);
Listing 8: Code to generate the graph in Figure 3
third_graph.php
Figure 3: Grouped bar plot on different scales
FEATURES
April 2003 · PHP Architect · www.phparch.com
16
Advanced Features in JpGraph
file in this article's source directory for an example of
what this background image could look like.
You can use this image as the background by using
the code shown in Listing 10.
You can use the code in Listing 11 to add the finish-
ing touches like graph and axis titles, and to adjust the
legend placement. The output of this code is shown in
Figure 5.
For performance reasons, you decide to implement
the JpGraph image-caching mechanism on this graph.
This caching is accomplished by saving a copy of your
graph as a file on the server. Instead of generating the
image on-the-fly, this cached file is streamed back (if
the cached copy is still valid). Note that the web serv-
er must have write access to the directory in which the
cached images are saved.
Instead of creating the Graph class instance with just
the width and height parameters, you will need to also
pass in a name for the cached image, a timeout value
in minutes (how long the image is valid), and a final
parameter telling JpGraph to continue to stream the
images. This means that you will still use the PHP script
itself as the image tag's 'src' parameter. The code nec-
essary to implement caching is shown in Listing 12.
Note: There is a conflict with the
Graph::AdjBackgroundImage()
method and
the GD2 library that comes bundled with PHP 4.3.0.
If you have upgraded to this version, you will have to
fall back to the method of adjusting the image with
an editor until the conflict is resolved.
Figure 4: Using stacked bars
1 $colors = array('pink', 'orange', 'yellow',
'lightgreen', 'lightblue');
2
3 $abqAdd = array();
4 $abrAdd = array();
5 for($i=0,$j=count($items); $i<$j; $i++) {
6 $key = $items[$i]['short_desc'];
7 $b1 = new BarPlot($graphData[$key]['qty']);
8 $b1->SetFillColor($colors[$i]);
9 $b1->SetLegend($items[$i]['item_desc']);
10 $abqAdd[] = $b1;
11
12 $b2 = new BarPlot($graphData[$key]['rev']);
13 $b2->SetFillColor($colors[$i]);
14 $abrAdd[] = $b2;
15 }
16 $ab1 = new AccBarPlot($abqAdd);
17 $ab2 = new AccBarPlot($abrAdd);
18 $b1z = new BarPlot($graphData['zero']);
19 $b2z = new BarPlot($graphData['zero']);
20
21 $gb1 = new GroupBarPlot(array($ab1, $b1z));
22 $gb2 = new GroupBarPlot(array($b2z, $ab2));
23
24 $graph->Add($gb1);
25 $graph->AddY2($gb2);
Listing 9: Code to generate stacked bar graphs in
Figure 4.
fourth_graph.php
“Coupling JpGraph with
PHP's database access capa-
bilities provides you with a
powerful toolset for the gener-
ation of dynamic graphs on the
web.”
FEATURES
April 2003 · PHP Architect · www.phparch.com
17
Advanced Features in JpGraph
Now if the same graph - for the same region - is
requested more than once within 24 hours (our time-
out value of 60 minutes * 24 hours) , the cached ver-
sion will be streamed back to the browser, and no code
after the 'new Graph()' line will be executed. This
means that in order to maximize the gains from
caching you will want to move the Graph object instan-
tiation prior to any expensive database queries.
if (USING_TRUECOLOR) {
$graph->SetBackgroundImage('img/abc-background_prefade.png', BGIMG_FILLFRAME);
} else {
//AdjBackgroundImage only works with GD, not GD2 true color
$graph->SetBackgroundImage('img/abc-background.png', BGIMG_FILLFRAME);
$graph->AdjBackgroundImage(0.9, 0.3);
}
Listing 10: Code to use and adjust the background image.
background_image.php
1 $graph->title->Set(date('Y')." Sales for {$regions[$region_id]} Region");
2 $graph->title->SetFont(FF_ARIAL, FS_BOLD, 12);
3 $graph->SetMarginColor('white');
4 $graph->yaxis->title->Set('Left Bar Units Sold');
5 $graph->yaxis->title->SetFont(FF_ARIAL, FS_BOLD, 10);
6 $graph->yaxis->SetLabelFormatCallback('y_fmt');
7 $graph->yaxis->SetTitleMargin(48);
8 $graph->y2axis->title->Set('Right Bar Revenue ( $ 000 )');
9 $graph->y2axis->title->SetFont(FF_ARIAL, FS_BOLD, 10);
10 $graph->y2axis->SetTitleMargin(45);
11 $graph->y2axis->SetLabelFormatCallback('y_fmt_dol_thou');
12 $graph->xaxis->SetTickLabels($graphData['labelX']);
13
14 $graph->legend->Pos(0.5, 0.95, 'center', 'center');
15 $graph->legend->SetLayout(LEGEND_HOR);
16 $graph->legend->SetFillColor('white');
17 $graph->legend->SetShadow(false);
18 $graph->legend->SetLineWeight(0);
Listing 11: Code to finalize the graph in Figure 5.
fifth_graph.php
Figure 5: Completed regional graph
define('GRAPH_NAME', 'abc_reg_sales');
$graphName = GRAPH_NAME.$region_id.'.png';
$graphTimeout = 60*24;
$graph = new graph(WIDTH, HEIGHT, $graphName,
$graphTimeout, true);
Listing 12: Code to implement graph caching.
cache_graph.php
FEATURES
April 2003 · PHP Architect · www.phparch.com
18
Advanced Features in JpGraph
Region-by-Channel Graph
The second type of graph you were asked to create
shows the sales for each region by channel, and needs to
provide an easy way of navigating to the first graph you
constructed. This type of report means viewing the infor-
mation in proportions, so a pie graph may be effective.
Again, please review lines 22-82 of the
abc_map_graph.php file in this article's source directo-
ry to understand the database queries and array creation
for the following graphs.
Listing 13 shows the code necessary to generate the
pie graph shown in Figure 6.
You can expand on the use of background images that
was introduced in the first set of graphs, and add addi-
tional information to your graph. Consider a map of the
United States showing the divisions of each region for
the ABC Company. If you use this map image as a back-
ground for your graph, you can actually place the pie
charts on each of the regions to make it clear what
region the pie chart represents. See the img/abc-
regions.png file in this article's source directory for
the example background image used here.
To use this example, you will need to add a couple of
more lines to the $graphData construction loop to
allow for dynamic placement of the pie charts for each
region:
Now let's look at the entire code, shown in Listing 14,
to generate the graph in Figure 7.
The company's final request was to be able to drill
down from these pie charts to the regional sales data
graphs you constructed earlier. You can implement this
feature using Client Side Image Maps (CSIM). CSIM is
an HTML technology allowing you to specify regions of
an image to associate with a hyperlink. To implement
CSIM for this chart, you will need to make the CSIM tar-
gets (hyperlinks) and image alts (tips for the user).
First we'll define a constant containing most of the
link to drill down to.
Now in the $graphData loop we'll populate the tar-
gets and alts:
$graphData['r'.$rIndex]['targets'][] =
DRILL_GRAPH.$regionData[$i]['region_id'];
$graphData['r'.$rIndex]['alts'][] =
"Click for more information regarding
{$regions[$rIndex]['region']} sales.";
define('DRILL_GRAPH',
'abc_reg_sales_graph.php?region=');
$graphData['r'.$rIndex]['map_x'] =
$regionData[$i]['map_x'];
$graphData['r'.$rIndex]['map_y'] =
$regionData[$i]['map_y'];
Figure 6: Simple pie chart
$sliceColors = array('lightgreen', 'pink',
'lightblue');
$graph = new PieGraph(WIDTH, HEIGHT);
$graph->title->Set($regions[$region]['region']
.' Region');
$graph->subtitle->Set('Sales by Channel since '
.GRAPH_START);
$p1 = new PiePlot($graphData[$pickRegion]['rev']);
$p1->SetLegends($graphData[$pickRegion]['label']);
$p1->SetSliceColors($sliceColors);
$graph->Add($p1);
$graph->Stroke();
Listing 13: Code to generate the pie chart in
Figure 6.
simple_pie.php
FEATURES
April 2003 · PHP Architect · www.phparch.com
19
Advanced Features in JpGraph
Because there is now HTML information (the CSIM)
in addition to the binary image content, you can't
stream it back to the browser like you normally would.
The CSIM and the image depend on each other too
much. In order to make this work, you need to cache
the image. This allows you to output the image map,
as well as the image tag used to fetch the cached
image. Instead of using the JpGraph cache outlined
above, we'll use another form of image caching and
store it in a place where we can request it directly. To
accomplish this you will need to create a directory
called img immediately below the script directory. This
directory must be writeable by the web server.
When you are creating the graph, treat it as if you
were going to stream the image. In the pie chart loop,
add the CSIM information like so:
To output the graph, use the code in Listing 15.
This code instructs JpGraph to output the image to a
file called img/abc_channel_graph.png. You then
fetch the image map generated by the graph into the
$imgMap variable. The last print statement should be
incorporated into a larger valid HTML document, but
here you can see the coupling between the image map
and the image tag. The image tag specified allows the
$p1->SetCSIMTargets(
$graphData[$pickRegion]['targets']
$graphData[$pickRegion]['alts']
);
1 $graph = new PieGraph(WIDTH, HEIGHT);
2 $graph->SetBackgroundImage('img/abc-regions.png', BGIMG_FILLFRAME);
3
4 for ($i=0; $i<$rIndex+1; $i++) {
5 $pickRegion = 'r'.$i;
6
7 $p1 = new PiePlot($graphData[$pickRegion]['rev']);
8 $p1->SetCenter($graphData[$pickRegion]['map_x'],
9 $graphData[$pickRegion]['map_y']);
10 $p1->SetSize(PIE_SIZE);
11 $p1->SetLabels($graphData[$pickRegion]['revFmt']);
12 $p1->SetSliceColors($sliceColors);
13 if (!$i) {
14 $p1->SetLegends($graphData['label']);
15 }
16
17 $graph->Add($p1);
18 }
19
20 $graph->legend->Pos(0.9, 0.85, 'center', 'center');
21 $graph->Stroke();
Listing 14: Code to generate region graph in Figure 7.
region_pie_graphs.php
Figure 7: Using a background to show regions
FEATURES
April 2003 · PHP Architect · www.phparch.com
20
Advanced Features in JpGraph
image to use the generated map.
Key Concepts
The following concepts, related to graphing and
JpGraph, were introduced or emphasized in this case
study:
• the use of the JpGraph Bar, Line and Pie plot
types
• the use of stacked and grouped bar graphs
• the use of an alternative scale on the Y axis
• the use of a callback function to perform for-
matting of labels
• the generation of error messages in an image
• the creation of a "watermark" using a back-
ground image
• the use of the JpGraph image caching - for
both performance, as well as to facilitate use
of the CSIM feature
• the use of a background image as part of the
charts information content (pie chart location
on the sales by region chart)
• the use of CSIM for drill-down capability on
graphs
Conclusion
JpGraph is a lightweight API that allows you to quickly
generate professional looking graphs. Coupling
JpGraph with PHP's database access capabilities pro-
vides you with a powerful toolset for the generation of
dynamic graphs on the web. This article has intro-
duced you to some of the more advanced features of
JpGraph like caching, background images and Client
Side Image Maps. Hopefully you are now familiar
enough with these technologies for you to consider
using PHP and JpGraph for your next data mining proj-
ect.
define('IMG_DIR', 'img/');
$graphName = IMG_DIR.'abc_channel_graph.png';
$graph = new PieGraph(WIDTH, HEIGHT);
//the rest of the graph code...
$graph->Stroke($graphName);
$mapName = 'ABC_Region_Drill';
$imgMap = $graph->GetHTMLImageMap($mapName);
print <<<EOS
$imgMap
<img src="$graphName" alt="ABC Sales by Channel"
ismap usemap="#$mapName" border="0">
EOS;
Listing 15: Code to handle CSIM's.
csim_stroke.php
About The Author ?>
Jason has been an IT professional for over ten years. He is currently an
application developer and intranet webmaster for a Fortune 100 compa-
ny. He has written several tutorials and articles for the Zend website,
and has recently contributed to the Wrox "PHP Graphics" handbook. He
resides in Iowa with his wife and two children.
Click HERE To Discuss This Article
/>Publish your data fast with PHPLens
PHPLens is the fastest rapid application tool you can find for
publishing your databases and creating sophisticated web
applications. Here’s what a satisfied customer, Ajit Dixit of Shreya
Life Sciences Private Ltd has to say:
I have written more than 650 programs and have almost covered 70% of MIS,
Collaboration, Project Management, Workflow based system just in two
months. This was only possible due to PHPLens. You can develop high
quality programs at the speed of thinking with PHPLens
Visit phplens.com for more details. Free download.
Connect
with your database
FFEEAATTUURREESS
Form VValidation FFrom
the OOutside IIn
FEATURES
April 2003 · PHP Architect · www.phparch.com
21
I
t seems like every book I read on PHP has a chapter on
forms. Of course, form handling is what server-side
languages are usually employed for, so it is good to see
it get this kind of coverage. Unfortunately, most books
only provide slightly more information on form pro-
cessing than you’d find in a good HTML book. They
usually only cover how to build a form, what the data
comes through as on the other side, and some com-
mon uses for it.
So, while I know what forms are, what they’re for,
and how I can use them, I have yet to read a book (or
article) that answers my age-old question: What’s a
good way to validate forms?
Obviously, ‘good’ is the operative word here, and is
based solely on my opinion. To be fair, I have seen
books and articles that talk about form validation. They
just don’t talk about it the way I think it needs to be:
atomic. I mean that the validation engine should be
fully-encapsulated. Validation atomicity comes part-
and-parcel with functional separation. You shouldn’t
be doing a check here and then a check there, inter-
spersed in the same script that will be using the data to
perform some operation. Form validation needs to be
a separate entity. You should know if it succeeds or
fails, and then be free to take the appropriate action.
Over the course of this article, I’m going to talk about
the form validation system that I have developed. This
system may be a little different than what you’re used
to, but it’s robust, reliable, and very extensible.
I will first examine an evil necessity for our purposes:
dynamic form generation. Following this will be a dis-
cussion of the form validation techniques presented
here. After all of this theory, I’ll throw a couple of class-
es at you, and tie it all together with a nice example.
This should leave you feeling refreshed and invigorated,
and ready to tackle your next form validation project.
Let’s go.
Dynamic Form Generation
In order to fully encapsulate the process, we will want
to build our form elements dynamically. If you follow
phpclasses.org or hotscripts.com, you probably know
that everybody and their dog has created a class to
make form creation ‘easier’. While I’m not personally
convinced that this is necessarily better or easier by
itself, it will make the rest of our process easier. I’ll dis-
cuss, in detail, the reason for this a little later.
In recognition of the composite nature of a form, we
will (just like everybody else’s dog) use a set of classes
for this task. A number of element classes (Text, Button,
Form VValidation FFrom
the OOutside IIn
By Peter James
Staff editor and veteran PHP pro Peter James approaches
the age old problem of form validation from a new and
interesting perspective.
PHP Version: PHP 4.3
O/S: Any
Additional Software: N/A
REQUIREMENTS
A NNew PPerspective oon FForm VValidation
FEATURES
April 2003 · PHP Architect · www.phparch.com
22
Form Validation From the Outside In
Checkbox, etc) and a main form class should provide all
of the functionality we need.
A form usually contains one or more form elements,
and one or more of a number of form tag attributes. In
order to provide the most value, the form class needs
to follow a few guidelines, listed below. It should
• provide intuitive defaults for the standard form
tag attributes (like method, etc), so that sim-
ple forms are easy to create
• provide an easy way to add extra attributes to
the form tag
• provide a simple way to add form elements to
the form
• be flexible enough to handle new form ele-
ment types transparently
Following the above guidelines will make the class
more robust and durable. Let’s look at how the form
element classes fit into the picture.
Each form element type can be represented with a
class. The interface to the element classes should be
loose and generic. Methods
should be provided for
things like setting the value
of the element, getting the
HTML representation of the
element, and adding tag
attributes to the element.
Because there will be a
large amount of functionality
that is common across the
different element types, it makes sense to build an
abstract form element class, and have each type of ele-
ment class extend it. This will also ensure that it is easy
to add a new element type. You just extend the base
class and add to it to meet the needs of the new type.
Easy as pie!
Once the form composition is done, the user should
be able to get the HTML representation of the form,
including all elements, for layout. Some form classes
provide built-in layout capability. In the interests of not
introducing any unnecessary limitations, I won’t deal
with this functionality here.
“So,” you ask, “why do we need to dynamically gen-
erate the form to do validation?”
It all comes down to control. It’s the same reason
that when you sell your house, your lawyer takes care
of all disbursements. What if they gave you all the
money, and you decided that you’d rather live in a nice
tropical country with no extradition agreements? The
lawyer wants to make sure that they get paid, and that
the bank gets paid, and that the realtor gets paid.
Likewise, while we technically don’t need to dynami-
cally generate the form, it makes sure that things oper-
ate in a consistent way, and that everything about the
form falls under one umbrella. If we dynamically gen-
erate the form, we know what type of form elements,
as well as how many, are present. We know their val-
ues, attributes, and names. We are omniscient about
the form, and that is invaluable when we want to vali-
date that form.
Now that we have an idea of how the dynamic form
generation will work, let’s explore how we’re going to
validate forms.
Form Validation in a Nutshell
Although web applications are state machines, the pro-
tocol the web is built on (HTTP) is ironically stateless.
Sessions are most commonly used to circumvent this
limitation, and they will play a large role in how we will
go about validating forms.
Sessions allow us to maintain state, which means we
can develop our applications in a manner more similar
to client-side (desktop) development. Client-side appli-
cations start and end with the user. When the user
starts MS Word, an environment is created that knows
as much as it needs to about what they are doing. This
environment, containing their document, undo levels,
comments, an so on, is alive
for as long as they are work-
ing. When they are done,
the environment is
destroyed.
Compare this to a web
application such as a con-
tent management system.
Every single time a user per-
forms an action, the system
starts from zero. It must recognize who the user is,
determine what actions they are allowed to take,
understand what action they are trying to take, load
any context necessary, perform the action, store any
context, output to the user, and exit back to zero. This
is a single action?
“So,” you ask, “what does this have to do with form
validation?”
That’s a good question, and the answer depends. If
you set up the form validation on the page receiving
the request, like almost everyone does, the above com-
parison is moot. The receiving page gets the form
data, checks it out, and does what it needs to. If, how-
ever, you set up the form validation when you create
the form (which rarely seems to be the case, and may
seem illogical at first), this definitely will matter to you.
Your form validation code knows what to do when the
form is created, but that won’t help you on the other
side unless you can transfer that knowledge to the
receiving page. See where I’m going here? Bear with
me.
There are many ways to do most things, and form
processing is no different Some people, for instance,
like to mix the screen and action code. This means that
you have the same script display the form as will later
"...You shouldn't be doing a check
here and then a check there, inter-
spersed in the same script that will
be using the data to perform some
operation."
FEATURES
April 2003 · PHP Architect · www.phparch.com
23
Form Validation From the Outside In
process it. A short example of this is shown in Listing
1.
I have done a lot of this in my career. It’s very effi-
cient, and allows you to display errors easily. If the val-
idation code runs into a problem, you can abort pro-
cessing of the action code, and just redisplay the form
with the errors. This is nice for most forms where there
are required fields. If your validation code executes
cleanly and you don’t want to display the form, then
you can take any number of actions (exit, return, or
include another file, etc). This provides lots of options.
The bad thing about this is that it promotes the mix-
ing of validation and processing with content. You
might feel that there is a strong coupling between val-
idation code and processing code, but that is similar to
saying that there is a strong coupling between the
application and the presentation layer in a web applica-
tion. A strong link between them is undeniable, but
there are big advantages to achieving maximum sepa-
ration.
Another common way to process forms is to have
separate action pages and screen pages. This means
that the screen resides in one file, and the form’s action
will point to the action page (whereas in the above
example it pointed to itself). This achieves better sepa-
ration, but still mixes the validation and processing. An
even better way to process forms is to split it all out,
and have screen pages, validation pages, and action
pages. This could quickly become unwieldy, though.
All of the above methods suffer from redundancy and
duplication of effort. You will no doubt need to do sim-
ilar types of checking on many forms. With form check-
ing code all over the place, you may end up writing in
the same functionality needlessly. Function libraries are
an obvious solution to this.
While there is nothing technically wrong with the
above solutions, they all miss the boat in terms of
encapsulation and atomicity. No matter how long you
look at them, they are not clean, oiled form-validating
machines. I think that perhaps the above methods are
looking at things from the wrong angle. They are look-
ing at it on a need-by-need basis. “I need a function
that checks this...”, rather than asking “what do I need
to check this form element for?” While we can slice up
validation functionality along other lines, I believe that
the best way is to slice it along form element lines.
Validating on an element-by-element basis gives us a
great deal of power. Attaching form validation ability
to each element means that an element can essentially
validate itself. By extension, his means that a form can
essentially validate itself.
Unfortunately, the reality of validating a form in this
way requires that we know everything about what ele-
ments are in the form. How can we do this on the
receiving side without any of the above methods? We
really can’t. Sure, we can check for what variables
came through, but we’d have to set up some expectant
code on the receiving side, and that really wouldn’t be
any different than the above methods.
It seems that we’ve come full circle. The reason that
we need the dynamic form generation might now be
making itself clear. We build the form using our form
object, specifying how to validate each form element
when we create it. Now we’ve got all of the informa-
tion about the form, as well as how to validate it, in one
object. Unfortunately, this object is only available when
we make the form, not when we validate it.
The need for the session now becomes obvious. In
order to validate the form, we need the form validation
code on the receiving side of the request. Remember
that we set up the form validation code on the initiat-
ing side of the request, when the form was created. By
storing the validation configuration (the form object) in
the session, we are able to validate the form on the
receiving side atomically.
In the end, this method might look a little complicat-
ed. It really isn’t, especially considering the complexity
savings we’ll experience when we use it. Not surpris-
ingly, doing form validation this way satisfies the atom-
icity and logical separation requirements that were
specified earlier. It also means that when a form
changes, the validation changes are made in the same
spot, reducing the risk of introducing subtle bugs and
inconsistency.
Specifying Form Validation
Earlier, we set up a bunch of requirements for form
generation. Now let’s look at some requirements for
form validation.
Specifying form element constraints should be easy
when it’s simple, and possible when it’s not. If it’s a
simple check, it should be easy to specify. If it’s a com-
plex check, like a multi-step dependency check, it
should still be possible to do. This simply is not a use-
ful solution if we can’t extend it to handle the special
cases.
Constraint specification (what makes a form element
valid) should be consistent, too. One form element
type shouldn’t implement a common check differently
1 <?php
2
3 // start action code
4 if (isset($_REQUEST['foo']))
5 {
6 // peform validation and processing
7 }
8
9 // start screen code
10
11 ?>
12
13 <form action="<?php print $_SERVER['PHP_SELF'];
14 <input type="text" name="foo" value="" />
15 </form>
Listing 1
FEATURES
April 2003 · PHP Architect · www.phparch.com
24
Form Validation From the Outside In
than another. The constraint specification interface to
a text box should be intuitively similar to a text area,
since they are similar in function.
Because we are looking for atomicity, there must be
an easy way to see if a form is valid or not. By exten-
sion, this is also a form element requirement.
I think that we’ve talked enough talk, and we should
get busy on walking the walk. Let’s get to some code.
The Classes
The first class we’ll develop is the main form class. It is
listed in Listing 2 (provided in this month’s code pack-
age).
Let’s examine the code listing in Listing 2, and talk
about the method (pardon the pun) to our madness.
The constructor, Form(), simply takes an identifier as
an argument. The identifier is the value of a form vari-
able that will identify the form. Basically this translates
into a hidden field in the form with a name of “_form”.
It’s really helpful when you have multiple forms on a
page. It is used to identify the particular form object in
the session, so you know which form object to validate
with. This should become clear a little later.
The add() function is used to add form elements to
the form. The $element variable is an object derived
from the abstract form element class.
The set_name() function is used to change the
name attribute for the form. The name of the form
defaults to ‘form1’. The form name is really only useful
for client-side scripting, such as JavaScript, and can usu-
ally be ignored.
The set_method() function is used to set the
method used to transfer the data to the server, and
defaults to ‘get’. This is generally either ‘get’ or ‘post’.
The set_action() function is used to set the action
associated with the form. It defaults to
$_SERVER[‘PHP_SELF’], which is the path to the
current page. This is a common action, especially if you
are using a central controller script that delegates by
inclusion.
The set_target() function is used to set the target
window or frame of the form. This is the window or
frame that will display the results of the submission. It
defaults to ‘_self’.
The set_attribute() function is used to set extra
form tag attributes. It accepts an attribute name and
an attribute value, which will be entered into the tag as
name=”value”. You can add as many extra attributes
as you need. As an example, you might add an “enc-
type” attribute with a value of “multipart/form-
data” if you were creating a file upload form.
The _is_form_submitted() private function
checks as to whether the form that was created by this
object was actually the one submitted on the last
request. This is what the form’s identifier (from the
constructor, above) is used for. If
$_REQUEST[‘_form’] is set to the form’s identifier,
then the form was submitted. This function is used on
the receiving side of the form submission.
The is_form_valid() function is the deliverer of
our atomicity. This function checks first if the form was
actually submitted (using the above function) and, if
so, loops through each element in the form to deter-
mine if the form itself is valid. If any of the form ele-
ments are not valid according to their own definitions,
the form is not valid. This function is also used on the
receiving side of the form submission.
The _get_form_tag() private function gets the
HTML representation of the form’s tag, including all of
the specified attributes.
The _get_identifier_tag() private function
gets the HTML representation of the hidden field that
contains the identifier variable passed into the con-
structor.
The get_form() function
gets an array of the HTML
representations of the form
and all of its elements. It first
gets the form tag and the
identifier tag, then it loops
through all of the elements in
the order in which they were
added. Finally it adds the
closing form tag to the array.
This array is indexed by ele-
ment name for layout pur-
poses.
The get_messages()
function returns an array of messages produced by the
form validation.
The get_form_value() function allows you to use
the form object to access a form element’s value. This
function is generally used on the receiving side of the
form submission.
As you can see, this is a simple but powerful class for
form creation and validation. Now let’s see some of its
partners in crime: the form element classes.
The abstract form element class is shown in Listing 3
(provided in this month’s code package).
It’s not meant to be instantiated and implements a lot
of the element functionality. Let’s walk through it.
The class constructor, FormElement(), is used to
set the properties common to all elements: type and
name.
The set_attribute() function works identically
to the function by the same name in the form class. It
just allows us to add extra attributes to the form ele-
ment tag.
The set_format() function may be puzzling. I
said earlier that we were going to avoid any layout
capabilities in the classes, but this looks suspiciously like
a layout capability. Actually, this is mainly for the radio
button elements. Since radio buttons are really a num-
ber of elements making up one bigger element, this
"A strong link
between (validation
code and content)
is undeniable, but
there are big
advantages to
achieving maxi-
mum separation."
FEATURES
April 2003 · PHP Architect · www.phparch.com
25
Form Validation From the Outside In
allows you to layout the radio buttons. If you’re adven-
turous, you could use this to provide limited layout
capabilities for other element types. A sample layout for
radio buttons forming a boolean entry are shown
below. This simply augments the HTML being generat-
ed for a set of radio buttons. It takes a standard
printf()
format.
The apply_format() function applies the format
provided by set_format() to the element’s HTML
representation. Any class, like the radio button element
class, that has special formatting
requirements would override this
function.
The set_use_request() func-
tion specifies whether the form should
look for a $_REQUEST variable with
the same name as the element, and
use this as the element’s value when
displaying it. This is handy when
redisplaying a form after an invalid
submission. The default is to use the
$_REQUEST value, so an invalid form
will display the invalid values back to
the user automatically, which is nice.
Any call to the set_value() function (described
below) will override this setting and use the value pro-
vided to it.
The add_check_method() is the function that pro-
vides these classes with their immense extensibility. It
allows the user to specify either an element-specific
check such as ‘regex’ (for textboxes) or ‘selected’ (for
dropdowns), or an external function. An external func-
tion receives the validating element’s type, name and
value. It returns true or false based on if the value was
found to be acceptable or not. You can add as many
checking methods as you want. This function accepts
an optional fourth parameter that is useful when speci-
fying an external checking function. This parameter
allows you to specify the external file containing the
checking function (in case it’s not normally loaded).
The private _run_check_function() function is
used internally to actually implement the call to the
specified checking function(s). This function returns
true or false based on whether the check came back
successful or not.
The set_value() method allows the manual set-
ting of element values. You might use this with the
radio button form element to specify a default selec-
tion. If no value is specified to this function, any avail-
able $_REQUEST value will be used if allowed by
set_use_request().
The get_value() function returns the value of the
element.
The get_name() function returns the name of the
element.
The get_element() function is the first of two
abstract method. It is meant to be implemented in
subclasses, and should return the HTML representation
specific to the element. In most cases this will just
return one HTML tag (an <input> tag). For radio
buttons, select boxes, and text areas, however, it
returns a set of HTML tags with some text.
The validate() function is the key to self-valida-
tion. It is called by the form class when the form is
asked if it’s valid. This function runs through all of the
checking methods that were specified for the element
(if any). If a checking method returns false, which
means the value was not acceptable,
validate() stops running, checks on
this element, and returns false to the
calling class (the form class).
The final function in this class,
check(), is our second abstract
method. This is the function that
validate() calls to run the checking,
and must be implemented in the sub-
class according to the particular ele-
ment type’s special checking require-
ments. A select box form element, for
example, exposes three checking
types: ‘selected’, ‘func’ and ‘’. The first
type checks whether a selection was made from the the
box (applies when the default selection is not a valid
selection – like ‘select one’). The second type is present
$e->set_format(‘True %s False %s’);
1 <?php
2
3 require_once 'FormElement.php';
4
5 class FormTextbox extends FormElement
6 {
7 // allows checking types:
8 // * 'regex' - pattern matching
9 // * 'func' - user-defined function
10 // * '' - don't care
11
12 // constructor
13 function FormTextbox($name)
14 {
15 // call parent constructor
16 $this->FormElement('text', $name);
17 }
18
19 // check pattern
20 function is_pattern_ok($index)
21 {
22 if (preg_match(
$this->check_extras[$index],
$this->value))
23 {
24 return true;
25 }
26 return false;
27 }
Listing 4
Continued on page 26
"If any of the
form elements are
not valid accord-
ing to their own
definitions, the
form is not
valid."