Tải bản đầy đủ (.pdf) (74 trang)

Tài liệu Agile software development with PHPUnit doc

Bạn đang xem bản rút gọn của tài liệu. Xem và tải ngay bản đầy đủ của tài liệu tại đây (1.79 MB, 74 trang )

The WURFL projectThe WURFL project
Implementing search with Lucene
Integrating a Java search engine
API into your PHP site
Extreme cross-platform WAP development with PHP
Getting a grip on Getting a grip on LDAPLDAP
A coder's introduction to working with
directory services
Plus:
Tips & Tricks, Product Reviews and much more...
www.phparch.com
JUNE 2003
The Magazine For PHP Professionals
Agile software development with PHPUnitAgile software development with PHPUnit
Industrial strength MVC
Building a reusable development
framework with Open Source tools
PHP Unit TestingPHP Unit Testing
Use OOP to manage your formsUse OOP to manage your forms
Implementing object-oriented form
libraries that promote uniformity and
reusability
VOLUME II - ISSUE 6
2
This is the LAST month to submit your project for approval.
The deadline is June 18th! Hurry and submit your proposal.
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
.phpar
ch.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
IINNDDEEXX
3
Departments
php|architect
Features
9 Industrial Strength MVC
Building a reusable development frame-
work with open source tools
By Jason E. Sweat
24 Agile Software Development With

PHPUnit
By Michael Hüttermann
30 Lucene
Integrating a Java search engine API into
your PHP site
By Dave Palmer
42 Tailoring W@P sites with WURFL
By Andrea Trasatti
49 Getting a grip on LDAP
By Brian K. Jones
62 Object-oriented Form Management
With PHP
By Marco Tabini
5 EDITORIAL RANTS
Rant Mode: On - the PHP/MySQL
'Platform'
7 NEW STUFF
38 REVIEW
SourceGuardian Pro
By Peter James
59 REVIEW
PHPEdit
By Peter James
70 TIPS & TRICKS
By John W. Holmes
73 exit(0);
Worlds Apart
By Marco Tabini
TABLE OF CONTENTS
June 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
EEDDIITTOORRIIAALL
5
EEDDIITTOORRIIAALL RRAANNTTSS
php|architect
Volume II - Issue 6
June, 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
Andrea Trasatti, Brian K. Jones, Dave Palmer,
Jason E. Sweat, Marco Tabini, Michael
Hüttermann, Peter James
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
Rant Mode: On - the
PHP/MySQL 'Platform'
Recently, it has come to my attention
that there are some informational chan-
nels that have taken to calling
‘PHP/MySQL’ a ‘platform’, in the same
vein as ASP.NET, J2EE and the like. This,
in my opinion, is nothing short of a trav-
esty.
I will not name names (for the most
part) regarding individual culprits,

because it would only give them public-
ity. However, there is a certain develop-
er’s website which fails to list PHP with-
out MySQL by its side. It has ‘PHP &
MySQL Tips and Tutorials’, ‘PHP &
MySQL Apps and Reviews’, and a couple
of other departments devoted to the
PHP/MySQL ‘platform’, without a single
hint that PHP can be used in other ways.
In addition, documentation for a certain
large company’s commercial IDE also
refers to this popular duo as a ‘platform’.
Probably the most surprising offenders in
the perpetuation of this stereotype are
the PHP conference organizers. MySQL
plays such a prominent role in the talks
and tutorials at PHP conferences that, if
you go to one with no experience in
PHP, you would leave thinking that PHP
is primarily an interface to the MySQL
database!
This is unfortunate, to put it lightly.
Truthfully, it disgusts me. Unfortunately,
there are a number of conditions which
currently exist in the world of PHP that
could arguably be used to justify the
actions of these groups. For example,
have you done a search for ‘PHP MySQL’
at Amazon lately? I did this recently and
EDITORIAL

June 2003 · PHP Architect · www.phparch.com
June 2003 · PHP Architect · www.phparch.com
6
EDITORIAL
counted 16 books devoted solely to the
use of PHP with MySQL as a data source!
Here is the very real truth: MySQL is
NOT the only data source PHP is capa-
ble of working with.
There are scores of developers who
need to know this. Pass it on. Quite hon-
estly, I’m tired of downloading applica-
tions from Freshmeat and elsewhere
which require MySQL, only to find that
they also contain PHP code to implement
some feature that should be offloaded to
(or better, built into) the database! This
extra code introduces bugs, makes it
more difficult to maintain, and inevitably
slows down the application, and in the
process makes PHP look unnecessarily
slow and bulky.
Note that this is not a rant targeted at
people who are using MySQL because
they have properly evaluated their needs
and found it to be the best tool for the
job. Nor is it aimed at newbies using
MySQL as an introduction to the world
of data-driven development. I’m simply
trying to enlighten some poor souls who

might think that MySQL is the only
choice they have when it comes to using
PHP for their development needs.
PHP has native support for Sybase,
Oracle, DB2, Informix, MS SQL Server,
and other databases (yes, there are other
databases). Aside from databases, PHP
has native support for alternative sources
of data such as SNMP agents and LDAP
directories. In short, PHP is a very capa-
ble development platform without the
help of MySQL.
In this month’s issue of php|architect,
you can capture a glimpse of a couple of
these different data sources in action. As
promised, I’ve written a coder’s overview
of using PHP with LDAP. It’s a very high-
level, gentle discussion, light on code
and long on cold, hard facts you’ll need
to know if a client’s environment ever
forces you to code against an LDAP
directory. In addition, Jason Sweat
returns this month with a look at using
different ‘ready made’ open source tools
and frameworks to lighten the load on
enterprise application developers. In the
article, you’ll be able to get a feel for the
kinds of things you can do with some of
the other databases out there (Jason’s
article uses PostgreSQL, in particular).

As always, php|architect will strive to
bring you the information you’d expect
from any publication of our kind. This
will include MySQL, of course. However,
we’ll also try to debunk any myths or
misstatements regarding PHP that exist
out in the wild, like the erroneous label-
ing of PHP and MySQL as some sort of
unified ‘platform’. As always, your opin-
ions on this and anything you find in the
pages of php|architect are of great inter-
est to all of us here – so make you voice
heard in our inboxes or the forums at the
php|architect website.
Enjoy!
NNEEWW SSTTUUFFFF
NEW STUFF
June 2003 · PHP Architect · www.phparch.com
7
MySQL Beta
Certification Exam
MySQL AB announced this month
that the beta version of the MySQL
Professional
Certification
exam is avail-
able. With suc-
cessful completion of
the Professional Certification beta exam, you can
earn a valid MySQL Professional Certification -- the

most advanced MySQL credential -- to demonstrate
strong proficiency in working with the MySQL data-
base.
Through the MySQL certification program, MySQL
software developers can earn one or more formal cre-
dentials that validate their knowledge, experience and
skill with the MySQL database and related MySQL AB
products. This program now includes two certifications:
MySQL Core Certification and MySQL Professional
Certification.
The MySQL Core Certification provides MySQL users
with a formal credential that demonstrates proficiency
in SQL, data entry and maintenance, data extraction for
reporting and more. The MySQL Professional
Certification is for the more experienced MySQL user
who wants to certify his or her knowledge in MySQL
database management, installation, security, disaster
prevention and optimization. For more information,
visit MySQL.com
.
Pear Info Package
Pear announced the release of a new info package.
This package generates a comprehensive information
page for your current PEAR install. The format for the
page is similar to that for phpinfo() except using
PEAR colors. The output has complete
PEAR Credits (based on the packages you
have installed) and will show if there is a
newer version than the one presently
installed (and what it's state is). Each

package has an anchor in the form
pkg_PackageName - where PackageName is a case-
sensitive PEAR package name.
Visit PEAR.php.net
to download the new package.
Mozilla Firebird
Mozilla.org has announced the release of Firebird 0.6.
Mozilla Firebird is a redesign of the Mozilla browser
component, similar to Galeon, K-Meleon and
Camino™, but written using the XUL user interface
language and designed to be cross-platform. This lat-
est version includes:A New theme, Redesigned
Preferences Window, Improved Privacy Options,
Improved Bookmarks, Talkback Enabled, Automatic
Image Resizing, Smooth Scrolling, MacOSx Support
and much more. For more information, or to down-
load, visit Mozilla.org
.
PHP and Java?
F
rom the rumor mill department—we've heard that something big is about to happen between Java and PHP,
and that it will be announced at the JavaOne Conference on June 9th in San Francisco.
We do not yet know what the announcement will be about—but rest assured that we have unleashed our
hounds. Keep an eye on our website on June 9th for more information!
WWhat’ss NNew!
+
=
?

FFEEAATTUURREESS

FEATURES
June 2003 · PHP Architect · www.phparch.com
9
Introduction
This article assumes that you have either read the afore-
mentioned “An Introduction to MVC Using PHP” article,
or that you are already somewhat familiar with the
MVC pattern, OO programming in PHP, and have at
least looked at the Phrame examples. The previous
article highlighted proper use of the MVC pattern, with
business logic in the Model classes, presentation logic
in the View classes, and application flow directed by the
Controller classes (ActionController, Action,
ActionForms and ActionForwards in Phrame). Where
the previous article only stored data in the session, this
article steps it up a notch towards the “real world” by
making extensive use of a database.
The Application
To give this article a little more “real world” flavor, I
would like to start with a hypothetical set of require-
ments for the application. The application is a manage-
ment system for hyperlinks. The people who commis-
sioned the application have identified three key sets of
requirements: users, administrators and infrastructure.
User:
• The user will access this application as a web
site
• The list of links will be organized into groups
• The main link list will contain all of the links
in the application on a single page, so they

can all be printed at once
• Each page of the application will contain the
current date
• The user will be able to view a summary of
all the link groups, and will be able to jump
directly to that group on the main listing of
links
Administrator:
• The Admin will be able to maintain the links
using the web site
• The site will be able to detect the Admin,
and display a link to the editing pages as
appropriate
• The Admin will be able to add, modify or
delete both link groups and links. The
Admin can change the sequence that both
groups and links withing groups are present-
ed in the application, ease of use is also
important
Industrial
Strength MVC
Building a Reusable Development
Framework With Open Source Tools
By Jason E. Sweat
In the May issue, “An Introduction to MVC Using PHP”
showed you the general background and a simple
demonstration script of the Model-View-Controller pat-
tern. This article aims to take you to the next step:
applying these principals in a realistic application.
PHP Version: 4.0.6

O/S: Any
Database: PostgreSQL 7.3
Additional Software: Phrame, ADOdb, Smarty, Eclipse
REQUIREMENTS
FEATURES
June 2003 · PHP Architect · www.phparch.com
10
Industrial Strength MVC
• Security should be in place to prevent unau-
thorized users from becoming the applica-
tion administrator, or from using the admin-
istrative functions without being authorized
as the administrator.
Infrastructure
• Security is a serious concern, in particular,
and credentials used by PHP scripts to access
the database should have the bare minimum
rights required to perform the tasks required
(in case the web site code is ever compro-
mised)
• The application needs to be “future proof”,
specifically this web application might not
be the only client and/or the only
source/editor for links in this application
• This application will transition to other
resources for maintenance, so it is important
that it is well structured for both flexibility
and ease of maintenance
• The application should be designed so
HTML designers can alter the appearance of

the application without changing any source
code
• The data should never become corrupted,
i.e. Links should never refer to a group that
does not exist
• To assist in debugging problems, the system
should track the date and time at which
groups and links are both created and modi-
fied
A quick review of these requirements can tell us a few
things. The fact that the application is basically a web
site means that PHP is certainly a leading candidate for
implementation. The requirement for transitioning to
other resources to maintain the application, and the
desire for a robust and flexible framework, push us in
the direction of implementing an MVC-style applica-
tion. The fact that “each page” must have a date
stamp, and possibly a link to the editing pages, tends
to indicate we should establish some sort of a site ren-
dering framework. This is often implemented with
headers and footers in templates. Finally, with website-
independent business logic, strict referential integrity,
queries across multiple tables (at least if we model links
and groups in separate tables) and date columns to be
modified on each SQL request, it looks like we have
moved somewhat beyond MySQL’s fast retrieval of sim-
ple queries sweet spot, and another RDBMS will be
required.
Developing the Application
To review the development of this application, I think

it is appropriate to take a look at the overall infrastruc-
ture first, which dovetails into Models. Next, reviewing
how the Controller (Phrame) implements application
flow in this application, and lastly how the views are
implemented in this application.
Infrastructure
The first decision is what language the web application
will be constructed in. Since this is a magazine devot-
ed to PHP, I think that is an appropriate choice ;) The
choice of PHP allows us to pull out our now familiar bag
PHP, mySQL and Curl Optimized for OSCommerce
Free Shared Certificate & Integrated SSL Server
20+ Contributions Pre-Installed on MS1 Release
Web Mail and Web Based File Manager
Full FTP and phpMyAdmin Access
Free Ongoing Hands-On Support
Web Stats by Urchin Reports
Free Installation and Configuration
USE PROMO CODE: phpa
Get an Extended Free Trial and Free Setup!
As the publishers of Ian's Loaded Snapshot we
know OSCommerce!
100's of OSCommerce powered sites rely on our
years of experience with OSCommerce, direct
866-994-7377 or
www.chainreactionweb.com
www.chainreactionweb.com/reseller.
As the publishers of Ian's Loaded Snapshot we
know OSCommerce!
100's of OSCommerce powered sites rely on our

years of experience with OSCommerce, direct
866-994-7377 or
www.chainreactionweb.com
www.chainreactionweb.com/reseller.
Nobody...
Hosts OSCommerce Better!
We Guarantee It!
FEATURES
June 2003 · PHP Architect · www.phparch.com
11
Industrial Strength MVC
of tricks: Phrame for MVC, ADOdb for database
abstraction and Smarty for templating.
The second decision is what the persistent data store
for this application will be. I don’t think there would be
much room for disagreement in saying a database is
the most appropriate technology here. The project
requirements indicate that referential integrity, triggers,
views and stored procedures will be needed in the data-
base. There are a variety of databases that have these
capabilities; Oracle, Microsoft SQL Server, Sybase and
SAPdb, to name just a few. To make this article more
accessible to readers, I am going to use the most pop-
ular open source database that supports these features:
PostgreSQL.
There is an interesting requirement to have applica-
tions other than this PHP web application be a possible
source and/or consumer of these links. This implies
that if we coded the business logic for this application
in PHP, it would have to be reimplemented in whatev-

er the other application ends up being developed in.
This could also lead to possible differences in the
implementation logic, and would be overall a bad
design choice.
Fortunately, there is an alternative available to us:
code the business logic for the system into the data-
base itself. This means the business rules are imple-
mented in a single location, accessible by any applica-
tions needing to view or modify this data. By making
data available to client applications only through
views, and modifications to the data only performed
using stored procedures, we can also address some of
the security requirements.
For long term maintenance, performance and flexi-
bility, we will implement ADOdb for a database
abstraction layer. Continuing to build on what we
learned regarding the Phrame implementation of an
MVC in PHP, we will again use Phrame for this exam-
ple application.
We can implement several of the “look and feel”
design requirements by adoption Smarty templates is
our views, and having a common “header” and “foot-
er” template inclusion for common elements. These
details will be covered further in the section of the arti-
cle dealing with Views.
With all of these infrastructure decisions in place, we
can now visualize our application as a “stack” of tech-
nologies. Viewed from this perspective, our applica-
tion can be depicted as illustrated in figure 1.
This figure is useful to help enforce some of the con-

cepts in our design. The figure identifies the concep-
tual building blocks of the application in blue, the
implementing technology in green and the specific
project, library or application used in this example in
yellow. Which portion of the Model-View-Controller
design pattern each of the application blocks is most
closely associated is depicted on the left.
Moving from the bottom of the stack up, the tech-
nology implementing our blocks is the database. You
should note the majority of the business logic is imple-
mented in the database, thus extending the Model por-
tion of our framework into the database itself.
The next block up in our application stack is the data-
base abstraction layer. In this case, I have implement-
ed this project in ADOdb, but you easily could substi-
tute PEAR::DB, Eclipse, DBX or whatever else is your
favorite db abstraction layer (or even code using the
Figure 1
FEATURES
June 2003 · PHP Architect · www.phparch.com
12
Industrial Strength MVC
native PHP db calls, eliminating the abstraction layer
benefits of long term portability, simplified calling con-
ventions and overall flexibility). The green bar also
denotes the shift in implementing technology from the
database to our scripting language of PHP.
Your Model classes make use of the database.
Remember from the previous article that only Model
classes should access your persistent data store. The

Model classes are also where you can implement data
validation, error handling, and other rules of your busi-
ness logic.
The application
flow is directed by
the Controller,
which coinciden-
tally is the middle
of our application
stack. This project
is implemented in
Phrame, but you
could substitute
any of the other
projects men-
tioned in the prior
article, or roll your
own Controller.
The chief role of the controller is to delegate the user’s
choice of actions to the appropriate Models or Views. In
this application, we have also implemented security at
this level, as each restricted action requires validation
that the user is in fact an administrator prior to per-
forming the action.
Views then perform the task of interacting with the
application Models to extract the data required for the
user. Views may need to transform this data in order to
make it fit with the presentation technology used in
your application. For this example, we continue to
make use of the Smarty template engine, as we did in

the prior article.
The last application block is the HTML that is trans-
mitted to the user’s browser via HTTP. This really is part
of our applications View logic as well, because your
application has no functionality without the pages
being rendered. At this point the green bar on our fig-
ure depicts the change in the implementing technolo-
gy from PHP to the user’s browser.
Models
Given the decision to place a good deal of our business
logic in the database, this is a good starting point for
the section on models. There are some preliminary
items I should cover. First of all, the database this was
implemented on is PostgreSQL 7.3. I created a user
named linkdbo, with the ability to create databases,
who will be the database owner for our links database.
I created another user called linkuser, who will have
minimal rights, and will be the user accessing our data
from PHP. Two groups were created, links_admin and
links_user. Groups are the Postgres equivalent of roles,
and are a convenient way to assign rights to groups of
users. It is a good database programming habit to
always implement your security through roles.
Let’s start with the tables. In our application, we
want to track links, and have them organized into
groups. In a normalized database design, this implies
that we need two tables, one for the links and one for
the groups they belong two. This first table is for the
link groups.
For readers who are not familiar with the Postgres

syntax, there are a few nuances to pay attention to
here. First of all the link_group_id field is declared as
type serial with a constraint of PRIMARY KEY. The seri-
al type is a shortcut for creating a sequence in the data-
base, and selecting the next value from the sequence as
the default to populate the field when performing and
insert operation. The PRIMARY KEY constraint enforces
that the field must be unique and not null. The next
DROP TABLE link_group CASCADE;
CREATE TABLE link_group
(
link_group_id serial PRIMARY KEY,
group_name varchar(50) UNIQUE NOT NULL,
group_desc varchar(255)NULL,
group_ord integer NULL,
date_crtd timestamp(0)
with time zone DEFAULT CURRENT_TIMESTAMP,
date_last_chngd timestamp(0)
with time zone DEFAULT CURRENT_TIMESTAMP
);
GRANT ALL ON link_group TO GROUP links_admin;
GRANT ALL ON link_group_link_group_id_seq TO GROUP
links_admin;
NOTE: To prepare your Postgres db for this
example, login as linkdbo to the links database and
run these scripts from the code bundle in the fol-
lowing order:
link_group_ddl.sql
link_ddl.sql
link_views_ddl.sql

NOTE: A developer with long term flexibility, or
a desire to completely isolate model business logic
(perhaps because the Model can be used in multi-
ple applications) might choose to implement the
business logic in a web service. In this case, the
application model classes would be implemented
as web service clients.
“Herein lies the power of ref-
erential integrity: the database
is doing housekeeping for us.
As PHP programmers, we can
focus on manipulation and
presentation of the data with-
out worrying about corrupting
the data model with our SQL
statements.”
FEATURES
June 2003 · PHP Architect · www.phparch.com
13
Industrial Strength MVC
item of interest is the date_crtd field with a constraint
of DEFAULT CURRENT_TIMESTAMP. This constraint
means that any time a record is inserted, and this field’s
value is not specified, it will instead be created with the
current data and time. The last two GRANT statements
designate our security. What is most interesting here is
that which is conspicuous by it’s absence: the links_user
group has no rights at all - not even SELECT - to the
link_group table. This fact is an important considera-
tion to remember as we address function security later

on.
In the links table, a new type of constraint is intro-
duced: REFERENCES. This constraint is how Postgres
implements referential integrity. In this case, we have
specified that this field will match the primary key from
the link_group table. With just this portion of the con-
straint alone, you will never be able to insert rows into
the link table without an appropriate value for the
link_group_fk (fk stands for foreign key). We have also
qualified this constraint to further clarify the expected
behavior of this relationship. ON UPDATE CASCADE
means if the link_group_id changed for any reason on
a row in the link_group table that was referenced in the
link table, all of the associated links would also change
(we have no intention of doing this in the application,
but it does not hurt us either). ON DELETE NO
ACTION means that the database will prevent any SQL
statement that tries to delete a row from link_group
that is referenced by one or more links from happen-
ing. Herein lies the power of referential integrity: the
database is doing housekeeping for us. As PHP pro-
grammers, we can focus on manipulation and presen-
tation of the data without worrying about corrupting
the data model with our SQL statements.
Security on the link table, as with the link_group
table, grants no SELECT privileges to the links_user
group. How is it that we will be able to query the data-
base for this data? The answer is views, which are basi-
cally a pre-defined SELECT statement that appears as if
it were another table of data you can query. The fol-

lowing SQL statements define a view to retrieve infor-
mation regarding link_groups.
Here we have now granted SELECT rights to
links_user, so this view is available to query in our PHP
scripts. This view also provides some summary infor-
mation regarding links associated with each link group
by doing a LEFT JOIN (selecting all link_groups, and
links where they match) and using aggregate functions
like count() and max().
The requirements we reviewed earlier specified hav-
ing fields to capture timestamps for both the creation
and the last update times for each row. We saw how
the DEFAULT CURRENT_TIMESTAMP constraint could
be used to automatically populate the date_crtd field,
but how can you have the database automatically
update the date_last_chngd field where rows are
updated? The answer is to use a database trigger.
In Postgres, the creation of a trigger involves two
steps: creating a function, and setting the trigger to use
the function. In this case, the trig_upd_dates() function
changes the value of the date_last_chngd field to be
the current timestamp (the result of the now() func-
tion) in the row to be updated. The CREATE TRIGGER
statement then implements the function for each row
that is updated.
DROP FUNCTION trig_upd_dates() CASCADE;
CREATE FUNCTION trig_upd_dates() RETURNS TRIGGER
AS ‘BEGIN
new.date_last_chngd := now();
RETURN new;

END;
‘ LANGUAGE ‘plpgsql’;
CREATE TRIGGER link_group_upd
BEFORE UPDATE
ON link_group
FOR EACH ROW EXECUTE PROCEDURE trig_upd_dates();
DROP VIEW groups;
CREATE VIEW groups AS
SELECT
lg.link_group_id
,lg.group_name
,lg.group_desc
,lg.group_ord
,count(l.link_id) AS link_cnt
,max(l.date_crtd) AS link_add
,max(l.date_last_chngd) AS link_upd
FROM link_group lg
LEFT JOIN link l
ON (lg.link_group_id = l.link_group_fk)
GROUP BY
lg.link_group_id
,lg.group_name
,lg.group_desc
,lg.group_ord
ORDER BY
lg.group_ord;
GRANT ALL ON groups TO GROUP links_admin;
GRANT SELECT ON groups to GROUP links_user;
DROP TABLE link CASCADE;
CREATE TABLE link

(
link_id serial PRIMARY KEY,
link_group_fk integer REFERENCES link_group
ON UPDATE CASCADE
ON DELETE NO ACTION
NOT NULL,
name varchar(50) NOT NULL,
url varchar(255) NOT NULL,
link_desc varchar(255) NULL,
link_ord integer NULL,
date_crtd timestamp(0) with time zone
DEFAULT CURRENT_TIMESTAMP,
date_last_chngd timestamp(0) with time zone
DEFAULT CURRENT_TIMESTAMP
);
GRANT ALL ON link TO GROUP links_admin;
GRANT ALL ON link_link_id_seq TO GROUP links_admin;
FEATURES
June 2003 · PHP Architect · www.phparch.com
14
Industrial Strength MVC
Now that we have seen how to view data, and how
the database itself tracks some of our data require-
ments, the question still exists-how do we modify the
data without rights to the table? The answer is to use
functions, and in particular, to take advantage of the
ability to define security for a function that executes as
the person who created the function, rather than the
user of the function. We can walk through one exam-
ple of a function that modifies data in the link table:

This one is going to take some explanation, so here
we go! First of all, near the bottom we see LANGUAGE
‘plpgsql’, so the language this function is written in is
plpgsql. This procedural SQL language is distributed
with Postgres, and you can read the Postgres documen-
tation for instructions on how to install and use plpgsql
(http://www
.postgresql.org/docs/view.php?ver-
sion=7.3&idoc=1&file=plpgsql.html).
The CREATE FUNCTION statement defines the name
of the function and the parameters it takes (Postgres
supports function overloading-multiple functions with
the same name but different input parameters-but
don’t worry, I didn’t use any), in this case, the function
accepts two integer values as parameters.
In the DECLARE section, we can create local variables
we want to use (Postgres is statically typed, so unlike
PHP, you must declare a variable and its type prior to
use). We can also create more useful names for the
input parameters than “$1” as evidenced by the use of
ALIAS. By looking at the DECLARE section we can see
that the first integer parameter is the id of the link we
want to change, and the second is the id of the group
we want to change the link to.
The rest of the func-
tion is in the statement
delimited by BEGIN and
END. The first step in
our function is to vali-
date the link requested

to change actually
exists. We perform this
step by attempting to
select the row from link
with the user specified
id into the variable
linkrec. The next state-
ment checks to see if a
record was found. If it
was, we move on with
the next step, otherwise, if you look near the bottom of
the code where the else branch for that check is, we
RAISE EXCEPTION with an error message (quotes
escaped, as noted above). By raising an EXCEPTION,
the sql statement will result in an error and no result set
will be returned. We can use this fact to trap for errors
in the PHP code, and this will be covered later in the
article.
Now that we know that the link with the correct id
NOTE: The remainder of the function definition
is enclosed in single quotes. This means that if you
want to use quotes in your function, you have to
remember to escape them!
DROP FUNCTION chgrp_link(INTEGER, INTEGER);
CREATE FUNCTION chgrp_link(INTEGER, INTEGER) RETURNS
INTEGER AS ‘
DECLARE
ch_link_id ALIAS FOR $1;
ch_group_id ALIAS FOR $2;
max_ord INTEGER;

linkrec link%ROWTYPE;
grouprec link_group%ROWTYPE;
BEGIN
SELECT INTO linkrec * FROM link WHERE link_id =
ch_link_id;
IF FOUND THEN
IF linkrec.link_group_fk = ch_group_id THEN
RAISE NOTICE ‘’link % is already in group
%’’,ch_link_id,ch_group_id;
RETURN 0;
END IF;
SELECT INTO grouprec * FROM link_group WHERE
link_group_id = ch_group_id;
IF FOUND THEN
SELECT INTO max_ord count(1) FROM link WHERE
link_group_fk = linkrec.link_group_fk;
IF linkrec.link_ord < max_ord THEN
PERFORM ord_link(ch_link_id, max_ord);
END IF;
SELECT INTO max_ord count(1) FROM link WHERE
link_group_fk = ch_group_id;
UPDATE link
SET link_group_fk = ch_group_id
,link_ord = max_ord + 1
WHERE link_id = ch_link_id;
RETURN 1;
ELSE
RAISE EXCEPTION ‘’no group with id %
found’’,ch_group_id;
RETURN 0;

END IF;
ELSE
RAISE EXCEPTION ‘’no link with id %
found’’,ch_link_id;
RETURN 0;
END IF;
END;
‘ LANGUAGE ‘plpgsql’
SECURITY DEFINER;
NOTE: Postgres has another kind of stored pro-
cedure that is activated like a trigger called a rule.
Rules are used when the trigger needs to interact
with another table. An example of this kind of
functionality might be to have an audit table track-
ing changes to an important base table, in which
the rule on the base table inserts values into the
audit table as updates take place. Having this kind
of programmatic logic in the database frees the
PHP developer from having to implement much of
the data oriented business logic in the scripts.
“This application
implements another
design pattern-the
Factory Pattern-to
retrieve a specific
subclass of a View
base class.”
FEATURES
June 2003 · PHP Architect · www.phparch.com
15

Industrial Strength MVC
exists, the next thing we check is if we are asking to
change the group to an identical value. If so, we RAISE
NOTICE, that we were asked to essentially do nothing,
and RETURN 0. Because we raised a NOTICE, a result
set will still be returned as a result of this function call.
Assuming the link group we are changing to is not
the same as the existing link’s group, the next step is to
validate that the requested link group we want to
change to exists. This is performed in a similar fashion
to checking for the existence of the link. If we do not
find the link, it is time to RAISE EXCEPTION again.
Since that all checks out, we are almost ready to
update. But first, we need to see if this link is the last
link in it’s group. If not, we move it to the end (using
another function we have already defined - ord_link).
This is done so that the sequence of links within a
group does not get a gap in it. We will also determine
the end position in the new group, so we can position
the link there. Then we perform the actual UPDATE
statement, and RETURN 1, indicating success.
This may all look like a lot of work - is it really worth
it? Remember this is all part of the requirements for
data integrity in our project. Consider this from the
perspective of coding in a PHP script. Would you want
to code and run all of the above logic in PHP, or simply
execute SELECT chgrp_link(1,2)?
The last line is SECURITY DEFINER, which specifies the
function should run with the security of linkdbo, rather
than the user executing the function. This is what

allows us to log in through PHP as link_user, have no
access to the base tables, and yet still modify the data.
The rest of our database API is similarly defined with
functions, and is summarized in Table 1.
You can review the scripts in code/mvc/sql for the
implementation of all of the tables, triggers, views,
functions and sample data used in this application.
With all that database work out of the way, we can
finally get back to the subject we all know and love:
PHP! Let’s move up the application stack a block or two
and dig into the Model classes, which will actually be
accessing the database code we just developed.
The classes that access the database break down pret-
ty easily in this application to two classes-Links and
Groups. In addition, we will want to model the user of
our system, primarily for security (to determine if this
particular user is an application administrator). Lastly,
NOTE: This feature was added in the 7.3 release
of PostgreSQL. The function should run on a less-
er version of Postgres with minor modifications,
however; you will have to grant SELECT, UPDATE,
INSERT and DELETE privileges to link_user, defeat-
ing the security purpose of these functions and
views.
Data Access
links
View providing details of
individual links and the
groups that they are associ-
ated with.

groups
View providing details of link
groups, including summary
data regarding associated
links.
Data Manipulation
Function/Description Parameters
add_link_group
adds a new link group to
the database
Varchar - group name
Varchar - group description
upd_link_group
updates an existing link
group
Integer - link group id
Varchar - group name
Varchar - group description
del_link_group
remove an existing link
group
Integer - link group id
ord_link_group
change the sequence of
an existing link group
Integer - link group id
Integer - new order
sequence
add_link
add a new link to the

database
Integer - link group id to
associate
Varchar - name for the link
Varchar - url for the link
Varchar - description for the
link
upd_link
update an existing link in
the database
Integer - link id to modify
Varchar - name for the link
Varchar - url for the link
Varchar - description for the
link
del_link
remove an existing link
from the database
Integer - link id to remove
ord_link_group
change the sequence of
an existing link within all
links associated in the
group
Integer - link id
Integer - new order
sequence within the group
chgrp_link
change the group a link
is associated with

Integer - link id
Integer - new link group id
Table 1: Database API Functions
FEATURES
June 2003 · PHP Architect · www.phparch.com
16
Industrial Strength MVC
we will have a model for Errors, similar to the previous
article. An excerpt from Groups.php can be found in
Listing 1.
I like to create constants for the SQL statements I
intend to use in the class. To avoid potential name
space conflicts, I generally prefix the constants with the
name of the class (or an abbreviation of the class name
if it is long). The heredoc syntax is used for additional
clarity on the the multi-line SQL statements. One item
to consider is how to deal with values that change at
runtime (substituting values like dates or ID fields into
the statement). How can you make a constant flexible
enough to handle this? There are two easy approach-
es I have used: format the constant for processing with
[sprintf()], or user ADOdb bind variables. The latter
method is shown in the example code and described
below.
Next we define the Model class itself.
Groups::GetInfo() and Groups::Add() are rep-
resentative examples of model methods. Each uses a
global ADOdb connection object. This database con-
nection is established in the application setup file
(links_setup.php). Groups::GetInfo() next

selects the appropriate SQL statement based on how it
was called, and then executes the SQL and stores the
results in a result set object. Next, we check for valid
execution. If so, we return the result set as an array,
otherwise, we trigger an appropriate error message.
Groups::Add() is similar, but adds the concept of
a bind array. Each of the ‘?’ in the SQL statements will
be substituted in order with values from the array. This
is an example of the handling of dynamic runtime data
with a constant SQL statement mentioned above.
The Links model is similar to the Groups model. I
encourage you to review the code bundles
code/mvc/app/models directory for the full PHP
scripts.
The concept of the User model is essential to under-
standing the security within this application, so the
whole User.php script is presented in Listing 2.
This Model class again defines some constants to be
used in the class definition. The User class has three
methods User::IsAdmin(), User::SetAdmin()
and User::ValidateAdmin(). The
User::IsAdmin() method checks for whatever con-
ditions we determine qualify a user as an administrator,
and returns a boolean value based on the result of these
checks. In this case, I have implemented logic that says
an administrator is anyone who:
• is browsing from a particular subnet,
• is browsing from the localhost, or
• has passed a cookie to the application with
the name ‘c_links_admin’ and a particular

hash value.
The User::ValidateAdmin() method makes use
of the IsAdmin() method to check the current user. If
the user is not an administrator, then we trigger an
error and redirect to a safe location in our application.
This method can now be used anywhere in our appli-
cation where this type of validation is necessary.
The User::SetAdmin() essentially implements a
password check. If the correct password is passed to
this method, it will drop a cookie with the correct name
and value to pass the IsAdmin() method checks. This
method, coupled with the AdminLogin Action, allows
us to have a “backdoor” entry into the
system as an administrator using a url like:
define('GROUPS_INFO_SQL', <<<EOS
SELECT *
FROM groups
WHERE link_cnt > 0
EOS
);

define('GROUPS_DETAIL_SQL', <<<EOS
SELECT *
FROM groups
EOS
);

define('GROUPS_ADD_SQL',
'SELECT add_link_group(?, ?)');


// ... additional SQL statements defined

class Groups
{
// ... constructor and vars defined

function GetInfo($pbEmpty=false)
{
global $go_conn;

$s_sql = ($pbEmpty) ? GROUPS_DETAIL_SQL :
GROUPS_INFO_SQL;
$o_rs = $go_conn->Execute($s_sql);
if ($o_rs) {
return $o_rs->GetArray();
} else {
trigger_error(DB_OOPS."\n"
.$go_conn->ErrorMsg());
return false;
}
}

function Add($psName, $psDesc)
{
global $go_conn;

$a_bind = array($psName, $psDesc);
$o_rs = $go_conn->Execute(GROUPS_ADD_SQL,
$a_bind);
if ($o_rs) {

return true;
} else {
trigger_error(DB_OOPS."\n"
.$go_conn->ErrorMsg());
return false;
}
}

// ... additional functions defined
}
Listing 1: Excerpt from Groups.php
FEATURES
June 2003 · PHP Architect · www.phparch.com
17
Industrial Strength MVC
/>=letMeIn. You could also code in a login page and use
the posted password to pass to this method.
Controller
This application makes use of Phrame, and is there-
fore, in many respects, very similar to the example pre-
sented in the previous article. One main difference is
the implementation of a “default action”. In my expe-
rience, I have found it to be the case that if no explicit
action is specified, then the “default action” to show a
view is implied. The revised bootstrap file (links.php)
reflects this (Listing 3).
Our revised bootstrap file is now down to four active
lines of code. require_once
‘links_setup.php’; includes the libraries, estab-
lishes global variables and defines functions used in the

application. The next ‘if’ statement implements the
“default action” discussed above. If no action is cur-
rently defined, it is explicitly set to “ShowView”. Next
we create our global controller. Finally, since we are
now always processing an action, we always call the
ActionController::Process() method.
One thing I really liked about adding the
ShowViewAction was the elimination of all the proce-
dural code to determine which view to show. This
action class is covered in more detail in the section of
the article dealing with Views in Listing 4.
Another important piece of code to review is this
application’s extension of the MappingManager class
(introduced in the previous article). This class is defined
in the code bundle code/mvc/app/LinkMap.php
file. The content of the LinkMap classes constructor
function is shown above. This class uses the default
options from the MappingManager class.
We define three forms for the application. The links
form is a pure instance of the ActionForm class, and is
therefore similar to the form I showed you in the previ-
ous article’s example application. In this application,
we have some more significant work to do in process-
ing form data, and both the link editing and group
editing pages have a specific extended ActionForm
class devoted to them.
The first mapping defined is for the default
“ShowView”. No forwards are required because this
action will terminate in the generation of HTML for the
client anyway.

The next mapping shows an example of an action
with multiple forwards. The first, “index” has no for-
ward path specified, so it will use the mapping default
of APPL_BASE.’index’. The second, “edit”, speci-
fies APPL_BASE.’groupedit’ as the forward path.
<?
define('USER_LOCAL_SUBNET', '192.168.10.');
define('USER_LOCAL_HOST', '127.0.0.1');
define('USER_ADMIN_PASS', 'letMeIn');
define('USER_ADMIN_VAL',
md5('links application administrator'));
define('USER_ADMIN_COOKIE', 'c_links_admin');

class User
{
function IsAdmin()
{
$s_user_ip = $_SERVER['REMOTE_ADDR'];
$b_admin = false;
if ( USER_LOCAL_SUBNET == substr($s_user_ip,
0,strlen(USER_LOCAL_SUBNET))
|| USER_LOCAL_HOST == substr($s_user_ip,0,
strlen(USER_LOCAL_HOST))
|| (array_key_exists(USER_ADMIN_COOKIE,
$_COOKIE)
&& USER_ADMIN_VAL ==
$_COOKIE[USER_ADMIN_COOKIE])
) {
$b_admin = true;
}

return $b_admin;
}

function SetAdmin($psPassCheck)
{
if (USER_ADMIN_PASS == $psPassCheck) {
//Set Cookie for 30 days
SetCookie(USER_ADMIN_COOKIE
, USER_ADMIN_VAL
, time()+30*24*3600
, ''
, $_SERVER['HTTP_HOST']
);
return true;
} else {
appl_error('Invalid Administrator Password');
return false;
}
}

function ValidateAdmin($psMsg='You have requested
an action reserved for application
administrators. Access denied.')
{
if (!User::IsAdmin()) {
appl_error($psMsg);
header(ERROR_VIEW);
exit;
}
}

}
?>
Listing 2: User.php
//application setup
require_once 'links_setup.php';

//set default action if none specified
if (!array_key_exists(_ACTION, $_REQUEST)) {
$_REQUEST[_ACTION] = 'ShowView';
}

//create Phrame controller
$go_controller = new ActionController(
$go_map->GetOptions());

//release control to controller for further
//processing
$go_controller->Process($go_map->GetMappings(),
$_REQUEST);
Listing 3: Revised bootstrap file (Links.php).
FEATURES
June 2003 · PHP Architect · www.phparch.com
18
Industrial Strength MVC
These are used in the action based on success or failure
of the login action. On success, you would forward to
“edit” and allow the administrator to edit the applica-
tion, otherwise, you should just forward the user to the
index page with an error message indicating the failed
login attempt. Listing 5 is the actual code for the

LoginAction::Perform() method that executes
what I just described.
The rest of the mappings defined are fairly typical of
what I see in most applications developed with this
methodology-Actions are associated with a specific
view in the application, and they generally have just a
single forward that returns the user to the view that
originated the action. As a matter of style, I tend to
group all of the mappings associated with a single view
together, as shown for both the group editing and the
link editing actions.
The last subject to be covered in relation to the
Controller is the customized form classes. The sim-
plest way to make an editing page is to have it perform
“record at a time”, i.e. you might go to the “edit-
group” view and pass a parameter of group_id=1. The
“editgroup” view would have all the fields you can
modify on the record available as inputs in a form, and
you would typically have a hidden input with the
group_id of the record being edited. Under this style
of application, the user would have to go back to a list-
ing of groups and select another group to edit to make
multiple changes.
function LinksMap()
{
$this->_SetOptions();

$this->_AddForm('links', 'ActionForm');
$this->_AddForm('updgroup', 'GroupForm');
$this->_AddForm('updlinks', 'LinkForm');


//default action to show views
// no forwards are required becuase this action displays HTML pages
$this->_AddMapping('ShowView', 'ShowViewAction', APPL_ACTN, 'links');
//admin login action
$this->_AddMapping('AdminLogin', 'LoginAction', APPL_BASE.'index', 'links');
$this->_AddForward('AdminLogin', 'index');
$this->_AddForward('AdminLogin', 'edit', APPL_BASE.'groupedit');
//group edit actions
$this->_AddMapping('AddGroup', 'AddGroupAction', APPL_BASE.'groupedit', 'links');
$this->_AddForward('AddGroup', 'edit');
$this->_AddMapping('UpdGroup', 'UpdGroupAction', APPL_BASE.'groupedit', 'updgroup');
$this->_AddForward('UpdGroup', 'edit');
$this->_AddMapping('OrdGroup', 'OrdGroupAction', APPL_BASE.'groupedit', 'links');
$this->_AddForward('OrdGroup', 'edit');
$this->_AddMapping('DelGroup', 'DelGroupAction', APPL_BASE.'groupedit', 'links');
$this->_AddForward('DelGroup', 'edit');
//link edit actions
$this->_AddMapping('AddLink', 'AddLinkAction', APPL_BASE.'linkedit', 'links');
$this->_AddForward('AddLink', 'edit');
$this->_AddMapping('UpdLink', 'UpdLinkAction', APPL_BASE.'linkedit', 'updlinks');
$this->_AddForward('UpdLink', 'edit');
$this->_AddMapping('OrdLink', 'OrdLinkAction', APPL_BASE.'linkedit', 'links');
$this->_AddForward('OrdLink', 'edit');
$this->_AddMapping('DelLink', 'DelLinkAction', APPL_BASE.'linkedit', 'links');
$this->_AddForward('DelLink', 'edit');
}
Listing 4: Action class dealing with views.
function &Perform(&$poActionMapping,
&$poActionForm)

{
$s_password = $poActionForm->Get('pw');

if (User::SetAdmin($s_password)) {
$o_action_forward =&
$poActionMapping->Get('edit');
} else {
$o_action_forward =&
$poActionMapping->Get('index');
}
return $o_action_forward;
}
Listing 5: Login action.
“Each of these tasks now has a
location within your framework,
and you can make a modification
like this, which essentially
amount to a new application
requirement, without breaking
any of the previously implement-
ed functionality and require-
ments.”
FEATURES
June 2003 · PHP Architect · www.phparch.com
19
Industrial Strength MVC
To make things easier for the user, you can imple-
ment “table at a time” editing, which
is what is shown with the GroupForm
(code/app/GroupForm.php) and UpdGroupAction

(code/app/UpdGroupAction.php) classes in
Listing 6.
Instead of a single hidden input for group_id, you will
instead make a hidden input array that is populated
with all of the group_id’s for the table as you iterate
over them in the edit view. Instead of having an input
like <input type=”text” name=”group_name”
value=”first old group name”> you will instead
code the group_id into the name for all of
the input fields: <input type=”text”
name=”group_name1” value=”first old group
name”>. All other inputs will be named similarly.
You will want to create an easy way to iterate over
these inputs in your action, and use a model class
update method for each of the different groups posted.
The Phrame controller will “load” your form class with
the $_REQUEST array. It does this using the
ActionForm::PutAll() method. This is the method
overridden in the GroupForm class above, in which a
Phrame ArrayList object is created and stored in the
GroupFrom class. This ArrayList is created in the
PutAll() method and a Phrame ListIterator is
retrieved using the GetList() method.
You can see this ListIterator being used in the ‘while’
statement in the UpdGroupAction::Perform()
method. While the ListIterator still has values, we
class GroupForm extends ActionForm
{
var $_moUpdList;


function PutAll($paIn)
{
Parent::PutAll($paIn);

$a_list = array();
$a_loop = $this->Get('groups');
if (is_array($a_loop)) {
for ($i=&new ArrayIterator($a_loop); $i->IsValid(); $i->Next()) {
$i_upd_key = (int)$i->GetCurrent();
$a_add = array(
'link_group_id' => $i_upd_key
,'group_name' => stripslashes($this->Get('group_name'.$i_upd_key))
,'group_desc' => stripslashes($this->Get('group_desc'.$i_upd_key))
);
$a_list[] = $a_add;
}
}
$this->_moUpdList =&new ArrayList($a_list);
}

function &GetList()
{
return $this->_moUpdList->ListIterator();
}
}

class UpdGroupAction extends Action
{
function &Perform(&$poActionMapping, &$poActionForm)
{

User::ValidateAdmin('You must be an administrator to Update Groups');

$o_group =& new Groups;
$o_list = $poActionForm->GetList();

while ($o_list->HasNext()) {
$a_vals = $o_list->Next();
$o_group->Update($a_vals);
}

if (!$o_group->IsChanged()) {
appl_error('Please change a value before updating.');
}

$o_action_forward =& $poActionMapping->Get('edit');
return $o_action_forward;
}
}
Listing 6: “table at a time” editing classes.
FEATURES
June 2003 · PHP Architect · www.phparch.com
20
Industrial Strength MVC
extract the next value as $a_vals and use this array of
values as a parameter to the Groups::Update()
method. It is important to note that we can not access
this method statically, because we are tracking in a class
variable whether any of these updates actually changed
the database. This is checked in the statement if
(!$o_group->IsChanged()) so we can warn the

user if they are wasting our time submitting an update
form with no changes!
Views
The View component of the MVC architecture is the
area that has changed the most from the example pre-
sented in the previous article. I have done significant
refactoring to several iterations of application, and
what I am presenting here is what I have arrived at as a
very workable solution to integrate Smarty into
Phrame. In a nutshell, there is a
ShowViewAction::Perform() method (shown in
Listing 7) initiated for every page a user will view. This
application implements the Factory Pattern to retrieve
a specific subclass of a View base class. The same
method creates a Smarty object, initializes the view
with both Smarty and the Action’s Form object, checks
security, and assigns global values for the application.
The View::Render() method is then executed to
load view specific values and generate output for the
user.
The view Factory Pattern, is implemented pretty
much by the book (“Design Patterns” that is). What we
want at runtime is a specific subclass of the View class.
There is a ViewFactory class that you must extend
in your application to make a concrete view
factory. You need to override the
NOTE: You should also note that some of the
security for this application is implemented in this
action. The first statement in the Perform()
method is User::ValidateAdmin(‘You

must be an administrator to Update
Groups’);. This statement will trigger an error
message and redirect to a public view if the user
is not an administrator. You can be confident that
any code after this statement will only be used by
the administrator of the application. Any actions
you want similarly secured should contain a call to
User::ValidateAdmin() as the first line of
your Perform() method.
function &Perform(&$poActionMapping, &$poActionForm)
global $gb_debug;

$o_view_factory =& new LinksViewFactory;
$o_smarty =& new Smarty;
$o_smarty->autoload_filters = array(//'pre' => array('trim', 'stamp'),
'output' => array('trimwhitespace'));

$s_view = strtolower($poActionForm->Get('view'));
$o_view =& $o_view_factory->Build($s_view);
$o_view->Init($o_smarty, $poActionForm);

//security check
switch (get_class($o_view)) {
case 'indexview':
case 'listview':
$b_restricted = false;
break;
default:
$b_restricted = true;
}

if ($b_restricted) {
User::ValidateAdmin('You must be an administrator view this portion of the
application');
}

//any default assignments
$o_smarty->Assign(array(
'view' => $s_view
,'view_link' => APPL_BASE
,'action_link' => APPL_ACTN
,'action' => _ACTION
,'admin' => User::IsAdmin()
,'debug' => ($gb_debug && User::IsAdmin()) ? true : false
));

//render the template
$o_view->Render();

exit;
}
Listing 7: ShowViewAction::Perform()
FEATURES
June 2003 · PHP Architect · www.phparch.com
21
Industrial Strength MVC
ViewFactory::_GetViewClass() method. This
method takes a single argument - the requested view -
and must return a valid view subclass name. The easi-
est way to implement this is a case statement, with a
default to your “index” or “main” view. The only other

assumption made by the View Factory is that the sub-
class is defined in a file in the views subdirectory, with
the class name and the php extension. The
_GetViewClass() method for LinksViewFactory is
shown in Listing 8.
How do the view subclasses work? The
View::Init() method take the Smarty object and
the ActionForm object, both by reference, and assigns
them to class vars. This is important, especially in the
case of Smarty, because assignments made to the
Smarty object after initialization are still present in the
$this->_moTpl var when used later in the
View::Render() method. The Render() method
calls a Prepare() method (where each subclass will
assign view specific data), then handles errors, and dis-
plays the subclasses Smarty template.
There are only two things to do for each subclass of
View to make another view for your application: assign
the template to the $_msTemplate var, and imple-
ment a Prepare() method. Listing 9 is a sample view
class for the groupedit view.
You might want to take a look at how the templates
in this application are organized. Each view-specific
template calls {include file=”header.tpl”} as
the first statement and {include
file=”footer.tpl”} as the final statement. These
give the site the common “look and feel” with the
header.tpl handling the site title and errors, and the
footer.tpl handling the timestamp, navigation and
some debugging code. This style of layout allows you

to easily add common elements like site navigation.
Remember that any common template variables can
be assigned in the ShowViewAction::Process()
method.
Debugging Phrame
Applications
It is worthwhile to note some of the debugging tools I
have left in the code. I used these techniques in devel-
oping this example, and they might help you in devel-
oping your own Phrame based applications.
The first habit I try to enforce is to code all my debug-
ging routines in such a way that they will not take effect
in production. This is done in case I forget to remove
the debugging code when I migrate my source to the
production location; I would not have to re-migrate.
The second affect I try to achieve is to have reasonable
looking output to work with (in some of my CSS2
absolute positioning layouts, a simple echo statement
in the wrong location can get hidden behind other divi-
sions).
NOTE: Both the ViewFactory and View classes
are only referenced from the ShowViewAction
class, and are therefore not really a part of
Phrame. I include them in the Phrame lib direc-
tory because they are abstract enough to use for
multiple projects, and therefore are useful to have
in the common library directory.
class LinksViewFactory extends ViewFactory
{
function _GetViewClass($psView)

{
switch(strtolower($psView)) {
case 'list':
$s_ret = 'ListView';
break;
case 'groupedit':
$s_ret = 'GroupEditView';
break;
case 'linkedit':
$s_ret = 'LinkEditView';
break;
case 'index':
default:
$s_ret = 'IndexView';
}
return $s_ret;
}
}
Listing 8
define('GROUPEDIT_VIEW_TEMPLATE', 'groupedit.tpl');
require_once 'models/Groups.php';
require_once 'models/Links.php';

class GroupEditView extends View
{
var $_msTemplate = GROUPEDIT_VIEW_TEMPLATE;

function Prepare()
{
$a_groups = Groups::GetInfo(true);

$a_links = array();
for($i=&new ArrayIterator($a_groups);
$i->IsValid(); $i->Next()) {
$a_group = $i->GetCurrent();
$a_links[] = Links::GetByGroup(
$a_group['link_group_id']);
}

$this->_moTpl->Assign(array(
'title_extra' => 'Editing Groups'
,'group' => $a_groups
,'link' => $a_links
,'group_opt' => Groups::Options()
,'test' => var_export(
Groups::GetInfo(true), true)
));

$this->_mbPrepared = true;
}

}
Listing 9
FEATURES
June 2003 · PHP Architect · www.phparch.com
22
Industrial Strength MVC
The easiest way I have found to achieve these results
is to dynamically determine at runtime if we should be
in debug mode. In the links_setup.php script, the glob-
al variable $gb_debug is can be set to

(strpos($_SERVER[‘SCRIPT_FILENAME’],
‘public_html’)>0) ? true : false; to
dynamically detect if the script is running from a user’s
public web directory (a sign the script is in develop-
ment in my environment). The same variable can be
coded to false to simulate the production environment.
All debugging outputs should be conditional on this
boolean, i.e. if ($gb_debug) {
var_dump($foo); }.
Another very simple means of viewing the state of
variables in your system is to trigger the
appl_error() function by hand. If you want to see
the state of a simple variable (number or string), you
can write something like if ($gb_debug)
appl_error(‘foo=’$foo);. This technique is use-
ful because the message shows up in a conveniant loca-
tion (the application error box) and the information
can be captured in the processing of an
Action::Perform() method and displayed after the
forward to the appropriate view.
Sometimes you may want to dump a larger variable,
for example, one of the data arrays you retrieve from a
model. These can sometimes be hard to look at in the
error box, so an alternative is to assign the
var_export($array, true); value to a template
variable named test, and then in the footer.tpl, detect if
we are in debugging mode and output
<pre>{$test}</pre>. At this same point, I often
enable the Smarty debugging console. I recommend
reviewing this handy feature from the Smarty project

documentation.
One final debugging comment. The user defined
error handling is very powerful, and absolutely required
for this framework where error messages must be
queued across multiple browser requests (as in any
action -> forward sequence). While this mechanism is
nice, it has one major problem, if you get a PHP fatal
error, you will end up with a blank page rather than the
default PHP error message (The PHP manual clearly
states the custom error handlers will not handle fatal
errors, but apparently it passes them anyway...?). To
alleviate this problem, I added the potential for a con-
stant named DISABLE_PHRAME_ERROR_HANDLER.
Modifications were made to the Phrame
ActionController class to detect if this constant is
defined and not set to the boolean false. When this is
the case, the normal application error handling will not
be enabled and PHP fatal errors will be visible as nor-
mal. If you end up with this “blank page” phenomena,
rather than doing “Zen Debugging”, define the above
constant as a test, in case you have accidentally intro-
duced a fatal error somewhere in your scripts. You
should note that if this constant is defined, output is
always generated, thus disabling the application’s abil-
ity to process and then forward.
Future Directions
Where can you go from here in modifying this applica-
tion? Well first of all, the table list of links is pretty bor-
ing, perhaps you could edit the links.tpl file and gener-
ate a nicer looking layout (perhaps with some CSS posi-

tioning).
You might want to extend the groups data model to
include an image source for a more graphical flair to
the list. In this case, you are altering something pretty
fundamental to the application, so you would need
make sure you hit all the blocks in the application stack
where it is affected: alter the link_group table, add
img_src to the add_group and upd_group plpgsql
functions, add the column to the groups views so the
PHP database user can query the data, the Groups Add
and Update methods to handle processing of the new
field, to the Add and Update actions to process and
add to the groupedit.tpl forms so we pass the value.
Lastly, add the img tag to the links.tpl file to display for
the user.
This might sound like you are altering a significant
portion of the system, but remember that your code is
now well organized into compact function oriented
blocks: you need to store the data somewhere, you
need to be able to securely access and modify the data,
you need to be able to edit the data as an administra-
tor and you need to retrieve and display the data for
the user. Each of these tasks now has a location within
your framework, and you can make a modification like
this, which essentially amounts to a new application
requirement, without breaking any of the previously
implemented functionality and requirements.
What else could be altered? You might want to cre-
ate a “link popularity” feature, i.e. Measuring the num-
ber of times users have followed the links. How can this

be accomplished? First of all, you can’t link directly to
the sites, because you would have no way of knowing
when the user clicks on a link. Instead, you would cre-
ate a “ViewLink” action, that would bump your count
for the link and then redirect the user to the link.
You might add an admin mode that would check for
broken links. You might add a “Submit a link” form,
giving your end users the capability to add links. This
feature might further require you to change the data
model to include a “pending” status flag so the admin-
istrator could approve submitted URLs.
If performance was a consideration, you might want
to investigate Smarty’s caching capabilities. You would
definitely want separate cache ids for regular and
admin users.
You might also consider writing unit tests for your code,
especially your model classes and the
Action::Perform() methods you have implemented.
FEATURES
June 2003 · PHP Architect · www.phparch.com
23
Industrial Strength MVC
Summary
What I have tried to present is the foundation for an
enterprise strength PHP application architecture.
Building on the strengths of the MVC design pattern by
implementing Phrame, we have fortified this with a
good database design, database abstraction in the PHP
Model classes, and implemented Views using Smarty
templates. Once familiar with this kind of application

architecture, you can deploy effective web applications
by writing rock-solid Model classes, Action::Process(),
View::Prepare() and Smarty templates. Deploying MVC
based PHP applications addresses many common func-
tional requirements: robust, flexible, maintainable,
secure.
These two articles and the example code provided
have been a whirlwind tour of PHP features, some cov-
ered in depth and others just mentioned or touched on
briefly (or even assumed). Here is a selection of some
of the PHP features, functions and concepts we have
applied in this article and example:
• the MVC design pattern
• practicing separation of business logic, appli-
cation flow and presentation logic
• the Phrame PHP implementation of the
Jakarta Struts MVC controller
• Object Oriented programming in PHP
• creating abstract base classes
• using static methods of classes
• using the PostgreSQL database
• coding in plpgsql, a procedural SQL lan-
guage
• using a database abstraction layer (ADOdb)
• practicing good security habits
• using templates to separate presentation
logic (Smarty)
• writing custom Smarty variable modifiers
• using PHP’s session to store data
• using cookies to store data

• applying the Factory design pattern
(ViewFactory)
• array manipulation
• HTTP redirection
• using web standards (well formed xhtml,
valid CSS)
If you have the luxury of having people on your
development team with SQL, PHP and HTML coding
skills, I think you can see where the MVC design pattern
will nicely break down into areas that suit each devel-
opers skill set. On the other hand, if you are solely
responsible for an application from start to finish, per-
haps following this example of coding the database,
PHP and templates will allow you to adjust your own
mental framework as you change hats during the devel-
opment of the project.
When developing your own applications, I hope the
application stack diagram from this article, the MVC
technology figure from “An Introduction to MVC Using
PHP”, and the examples provided in these articles will
give you the tools necessary to design and implement
your own MVC web application. Happy Coding!
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. Jason can be contacted at

.

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
FEATURES
June 2003 · PHP Architect · www.phparch.com
24
Introduction
It is unfortunate that so many software projects are not
successful. There can be many different reasons for this
and, of course, some circumstances cannot be prevent-
ed. By relying on the experiences of other people,
however, many common problems in the software
development process can be mitigated.
Agile software processes are “best practices” that
have been identified through experience. In this article
I want to introduce the agile approach and its benefits
for PHP developers. I focus primarily on patterns and
examples from extreme programming, which is one of
the most prevalent agile methods. With a little back-

ground, we’ll set out to discuss unit testing in detail.
We’ll look at what unit testing is, what the advantages
are, and how to implement unit testing in the PHP
world.
The problem
Process models are used to manage software develop-
ment. Without some sort of model, software develop-
ment is chaotic. The bigger projects and project risk
are, the more necessary a sound process model
becomes. One of the most popular models is the
“waterfall” model. In the waterfall model, developers
step through each phase successively. Planning and
analysis comes first, then implementation, and so on.
Although this model has many derivatives and imple-
mentations, a pure waterfall model would theoretically
forbid planning or design once the implementation
phase has begun. This has led many developers to
deem the waterfall approach cumbersome and inert for
many projects. It is fundamentally inflexible. This
makes late change requests and new features general-
ly very difficult to integrate.
The solution
Generally speaking, agile approaches are lightweight
process models that focus on the result and on the cus-
tomer. They allow you to directly profit from the expe-
rience gained through years of successful and not-so-
successful software development. One of the key deliv-
erables of agile methods is that changes are always wel-
come, ensuring customer acceptance.
There are a number of agile methodologies, includ-

ing “Scrum”, “Crystal”, and “Extreme Programming”.
Extreme Programming, or XP, was introduced by Kent
Beck and has definitely received the most attention. XP
is a process model focusing on small incremental releas-
Agile Software
Development
With PHPUnit
By Michael Hüttermann
Are you a responsible project manager who feels
depressed due to failed projects? Are you a developer
frustrated with defective applications and project stress?
Perhaps agile software processes are the cure you’ve
been waiting for.
PHP: version 4.3+
PHPUnit: version 0.5+
Code Directory: agilemethods
REQUIREMENTS
FEATURES
June 2003 · PHP Architect · www.phparch.com
25
Agile Software Development With PHPUnit
es, and iterative development. Over several iterative
cycles more and more features are added to the prod-
uct, but even the first iteration contains real functional-
ity. Customers are able to run through mini acceptance
tests, and can offer feedback very early on. This incre-
mental release cycle prevents misunderstandings, and
keeps projects on the right track.
There are a number of best practices that XP pro-
motes. Some of these include pair programming, sim-

ple design, continuous integration, and test-driven
development. We’ll explain each of these briefly, and
then delve into the last one in depth.
Pair programming
XP identified that information exchange between
developers is very important. Pair programming is a
very extreme way of achieving this exchange, but it has
a number of advantages. One advantage, of course, is
continuous knowledge transfer. This knowledge trans-
fer means that other developers are able to fix and
extend code in any module (also known as collective
code ownership). Another advantage of pair program-
ming is sanity checking. While one person is coding,
the other is looking at and checking the code being
produced. They discuss strategies, have fun, and are
more productive than working alone.
Simple design
Another XP practice is to maintain simple designs. This
means only implementing the features we currently
want, and only in the easiest way. This way, we place
strict focus on the functionality requested by our cus-
tomers, and don’t lose ourselves in trying to anticipate
complex future enhancements. Along with this, we
should not try to reinvent the wheel. In the case of PHP
web development, for example, it may be simpler and
result in a better quality end product to use the Smarty
template engine or existing PEAR packages, rather than
trying to roll our own templating system.
Continuous integration
Let’s assume we are using the waterfall model. The

coding begins and proceeds in a more or less uncoor-
dinated manner while developers create their modules.
Shortly before final code freeze they are asked: “Are
you finished? Does your code work?”. “Sure,” they
answer, “I implemented the template engine here, and
there is the database abstraction. Also, the business
logic is complete.” At that time all single modules are
frozen and integrated, resulting in a big bang. The sin-
gle modules may work, but the interaction between
them doesn’t. And this may happen shortly before
release!
The solution for this is continuous integration. We
freeze our code as often as possible, and integrate.
Small releases and pieces are more manageable. The
best case is that the result of each integration cycle is a
runnable version. The worst case is that bugs prevent
the integration. At least we know about them now and
can fix them, rather than finding out about them at the
end of the cycle. Above all, we learn by integrating the
product. It will not be a single event we are afraid of;
it will be routine. We get a good feeling for our appli-
cation, and no big surprises await us at the end of the
project.
Test-driven development
Now that we’ve introduced some of the patterns used
in XP, the remainder of this article will focus on
arguably the most important pattern: test-driven devel-
opment.
As developers code their modules, they test (hopeful-
ly!). Usually, this becomes more debugging than real

testing. Using PHP’s echo or die statements manually
takes a lot of time and is really bug hunting, not test-
ing. Sure, we may use DBG or the Zend Studio
Debugger to lessen the burden, but again this is not
really testing. Another problem with this “echo or die”
type of manual testing is that we often have to add
extra code to our module in order to test it. Thus, you
change the module you want to test.
An even worse case is that testing would be skipped
completely. Now, integrating these non-tested mod-
ules results in that big bang I mentioned earlier. How
can we prevent all of this?
One approach is to apply the “decorator” pattern to
protect our unit (module) code and encase it with the
tests. “Decorator” is a design pattern discussed by
Erich Gamma, et al in the landmark Design Patterns
book. In the decorator pattern, an object (or unit) is
extended with additional functionality. Instead of cod-
ing the new functionality inside the unit, though, we
leave the unit unchanged and add a wrapper around it,
which adds the new functionality. This approach has
the advantage that the underlying unit is left
unchanged, basic, and re-usable. Only the additional
functionality is special for this use case. The decorator
can also add further re-usable modules, such as debug-
ging or logging. In our case, we’ll decorate our unit
with the test functionality, and refer to this functionali-
ty as “unit testing”.
Unit tests are informal functional (black box) tests
normally executed by the developers of code. They are

often quite low-level and test the behavior of special
software components such as classes, modules, func-
tions, and so on. We use unit tests while practicing
test-driven development. Test-driven development
means that we code our unit tests first. No unit code is
written before its test.
Units are as finely-grained as makes sense. We may
write a unit test for a single method, for a whole mod-
ule, or for any other kind of component. The smaller
the component is, the better. Returning to the PHP
templating system example, you might write a set of

×