THETHE
DIVDIVIDEIDE
AN OO LAYERED
APPROACH
TO WEB APPS
REFERENCES IN PHP
HOMO XAPIAN
The Search for
a Better Search...Engine
OBJECT PERSISTENCE IN PHP
CROSCROSSINGSING
Major PHP Contributor
DERICK RETHANS
Gives you an In-Depth Look at
VOLUME IV - ISSUE 6VOLUME IV - ISSUE 6
JUNE 05
FFEEAATTUURREESS
14 CROSSING THE DIVIDE
OBJECT PERSISTENCE IN PHP
Forgetting storage and focusing on functionality
by Theo Spears
23 An OO Layered Approach to Web Apps
You can more confidently develop code
by knowing its place and responsibilities
by Ronel Rumibcay
37 References in PHP:
An In-Depth Look
PHP’s handling of variables explained
by Derick Rethans
47 Homo Xapian:
The Search for a Better Search...Engine
Open-source search technology that
you can integrate directly into your PHP scripts
by Marco Tabini
06.2005
Download this month’s code at:
hhttttpp::////wwwwww..pphhppaarrcchh..ccoomm//ccooddee//
DDEEPPAARRTTMMEENNTTSS
6 EDITORIAL
Political Internals
7 WHAT’S NEW
10 TIPS & TRICKS
CAPTCHA That Form
Dealing with misuse of online forms
by Ben Ramsey
55 TEST PATTERN
The Construction Industry
Dependencies and Object Construction
by Marcus Baker
61 PRODUCT REVIEW
Agata 7 Report Generator
A cross-platform database reporting tool
by Peter B. MacIntyre
67 Exit(0);
Tales from the Script
by Marco Tabini
EEDDIITTOORRIIAALL
T
here’s an interesting discussion taking place, on the PHP-Internals
mailing list, as I type this. A couple of days ago, Andi bravely resur-
rected the PHP 5.1 thread that began many months ago, and it’s
started a flood of discussion.
The Internals list is a strange beast. It can lay nearly dormant for weeks
at a time, and then, overnight it seems, the sleeping giant is awakened
with an onslaught of comments and opinions. It’s a really strange feeling
to wake up and find a list that usually gets 5 or fewer posts overnight,
suddenly dominating my inbox with several dozen loud messages.
The topic du jour, this time around is, once again, GOTO support in
PHP. What seems like a little more than half of the “voters” (not that
many of them actually carry much weight amongst the PHP core devel-
opers) are for GOTO support, while the other bunch, a slightly more con-
servative (some might even say “wise”) bunch are against it.
I, for one, am completely undecided on this issue—I see benefits to
both sides. Yes, GOTO would be nice for certain types of deeply nested
parsing algorithms (I love playing with the PHP tokenizer, for example),
and for other things like code generation (many current code-generating
packages—for other languages—employ liberal use of GOTO-like con-
structs). But the other side of the story is that I know that within months
of a released GOTO implementation, I’ll get stuck debugging a huge
plate of PHP-spaghetti. Some of the code I’ve had to maintain without
GOTO has been knotty enough, thank you.
So, once again, we’re at the crossroads of power and simplicity.
The really interesting part of this thread, for me, though, is the politi-
cal stance that people have taken with this discussion. I’ve got a certain
amount of respect for an equal amount of zealotry. But don’t cross the
line. Fortunately, we haven’t seen much mudslinging, yet. I’ll give it
another few hours—it’s only a matter of time before someone starts
whining about how “[favorite language] has this feature, we must have
it!” and this someone is met with a swift verbal kick from a member of
the PHP Group, reminding him that “PHP is not [favorite language]!”
It should be interesting to see how this all pans out. Fortunately, I’ve
got a fresh beer, and an air-conditioned pub to keep me company while
I write this, and the drama plays out.
As far as this issue goes, I believe it’s the best one since I started edit-
ing, three issues ago. We’ve got a lot of really great content, this month,
especially interesting, for me at least, is the in-depth look at PHP variable
internals, by Derick Rethans. Security Corner is on a mini-hiatus to make
room for the return of Tips & Tricks, which has a new author (whom
you’ll meet when you flip the pages).
Summer is here! Well, at least in the northern hemisphere. Enjoy read-
ing this issue on your patio, or while floating on your pool (unless, of
course, you’re a PDF subscriber; in which case, the pool might not be
such a good idea, unless you’ve printed a copy).
June 2005
●
PHP Architect
●
www.phparch.com
php|architect
Volume IV - Issue 6
June, 2005
Publisher
Marco Tabini
Editorial Team
Arbi Arzoumani
Peter MacIntyre
Eddie Peloke
Graphics & Layout
Aleksandar Ilievski
Managing Editor
Emanuela Corso
News Editor
Leslie Hill
Authors
Marcus Baker, Peter B. MacIntyre,
Ben Ramsey, Derick Rethans,
Theo Spears, Ronel Sumibcay,
Marco Tabini
php|architect (ISSN 1709-7169) is published twelve times a year by
Marco Tabini & Associates, Inc., P.O. Box 54526, 1771 Avenue Road,
Toronto, ON M5M 4N5, Canada.
Although all possible care has been placed in assuring the accuracy of
the contents of this magazine, including all associated source code, list-
ings 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 © 2003-2005 Marco Tabini &
Associates, Inc. — All Rights Reserved
TM
Political
Internals
June 2005
●
PHP Architect
●
www.phparch.com
7
What’s
NEW
? >
PHP-MultiShop 0.7
Php-MultiShop.com releases the latest version of their CMS and eCommerce system,
version 0.7. The website describes php-multishop as:" Php-MultiShop is a CMS & e-
Commerce System, an OpenSource platform to realize a virtual mall that includes vari-
ous shops and contents. The user will have a global vision of the portal, to read the most
interesting content (news, forums, curiosities, suggestions, reviews, cultural or commer-
cial events, fairs, recipes, tourist itineraries,...) and will have the possibility to visit the
shop desired.
Every shop will have all the functions and the personalization of a traditional
e-commerce web-site, as if it were independent from the virtual mall. It will have its own
internet domain and could be administrated in full autonomy by its own administrator.
At the same time, it can be distinct from the mall and other shops thanks to the per-
sonalized graphics, individual style, organization, contents and products, like every shop
in a real market place. Besides, being part of a large place able to attract different
typologies of visitors and consumers, it will be visible and more easily findable, increas-
ing its audience and potential market.
Php-MultiShop is written in PHP, run on Apache webserver and MySQL database serv-
er, and is able to run on any PHP and MySQL environment, including Linux, Solaris, BSD,
Mac OS X, and Microsoft Windows environments.
To realize the portal, the popular CMS PhpNuke is used, and for each shop the
efficient osCommerce e-commerce suite."
Check out Php-MultiShop at php-multishop.com
.
eZ publish 3.6
ez.no announces:" eZ systems is proud
to announce the release of eZ publish
3.6. This release presents yet another
big step forward for eZ publish, with
many improvements throughout the
system.
eZ publish 3.6 is loaded with new
features. The most significant new fea-
tures are:
• Support for database transactions
• Real preview of new content in the
administration interface
• HTML caching of static pages
• Improved support for internal links in
XML fields
• Vastly improved template syntax
• A developer toolbar to clear cache and
enable debug features on the fly"
Visit ez.no
for all the latest informa-
tion or to download.
ZEND Core for IBM Beta
IBM announces the release of the
ZEND Core for IBM Beta. IBM describes
the core as: "a seamless out-of-the-
box, easy to install and supported PHP
development and production environ-
ment. The product includes tight inte-
gration with DB2, the IBM Cloudscape
database server, and native support for
XML and Web Services, while also sup-
porting increased adoption of Service
Oriented Architectures (SOA). It deliv-
ers a rapid development and deploy-
ment foundation for database driven
applications and offers an upgrade
path from the easy-to-use, lightweight
Cloudscape database to the mission
critical DB2, by providing a consistent
API between the two."
Get all of the latest information from
/>software/data/info/zendcore/
MySQL 5.0.6
MySQL 5.0.6 has been released and is ready for download. Some changes in this release
include:
• The GRANT and REVOKE statements now support an object_type clause to be used for
disambiguating whether the grant object is a table, a stored function, or a stored pro-
cedure. Use of this clause requires that you upgrade your grant tables.
• Added a --show-warnings option to mysql to cause warnings to be shown after each
statement if there are any. This option applies to interactive and batch mode. In inter-
active mode, \w and \W may be used to enable and disable warning display.
• SHOW VARIABLES now shows the slave_compresed_protocol, slave_load_tmpdir and
slave_skip_errors system variables.
• If strict SQL mode is enabled, VARCHAR and VARBINARY columns with a length greater
than 65,535 no longer are silently converted to TEXT or BLOB columns. Instead, an
error occurs.
Check out />for more
changes.
WWhhaatt’’ss NNeeww??>>
June 2005
●
PHP Architect
●
www.phparch.com
8
Check out some of the hottest new releases from PEAR.
File_Fstab 2.0.2
File_Fstab is an easy-to-use package which can read & write UNIX fstab files. It presents a pleasant object-oriented inter-
face to the fstab.
Features:
• Supports blockdev, label, and UUID specification of mount device.
• Extendable to parse non-standard fstab formats by defining a new Entry class for that format.
• Easily examine and set mount options for an entry.
• Stable, functional interface.
• Fully documented with PHPDoc.
SOAP 0.9.1
Implementation of SOAP protocol and services
File_Archive 1.3.0
This library is strongly object oriented. It makes it very easy to use, writing simple code, yet the library is very powerfull.
It lets you easily read or generate tar, gz, tgz, bz2, tbz, zip, ar (or deb) archives to files, memory, mail or standard out-
put.
See
for a tutorial
Crypt_Blowfish 1.0.1
This package allows you to perform two-way blowfish on the fly using only PHP. This package does not require the Mcrypt
PHP extension to work.
Looking for a new PHP Extension? Check out some of the lastest offerings from PECL.
big_int 1.0.7
Functions from this package are useful for number theory applications. For example, in two-keys cryptography.
See
/tests/RSA.php in the package for example of simple implementation of RSA-like cryptoalgorithm.
See
project for more complex implementation of RSA-like crypto, which
supports key generating, encrypting/decrypting, generating and validating of digital sign.
The package has many bitset functions, which allow to work with arbitrary length bitsets.
This package is much faster than bundled into PHP BCMath and consists almost all functions, which are implemented in
PHP GMP extension, but it needn't any external libraries.
svn 0.1
Bindings for libsvn.
WinBinder 0.41.154
WinBinder is a new extension that allows PHP programmers to build native Windows applications. It wraps the Windows
API in a lightweight, easy-to-use library so that program creation is quick and straightforward.
intercept 0.3.0
Allows the user to have a user-space function called when the specified function or method is called.
ingres 1.0
This extension supports Computer Associates's Ingres Relational Database.
WWhhaatt’’ss NNeeww??>>
June 2005
●
PHP Architect
●
www.phparch.com
9
Oracle and Zend Partnership
Oracle and Zend Technologies, Inc., the PHP company, and creator of products and services supporting the development, deployment and
management of PHP-based applications, announced that the companies have partnered to produce Zend Core for Oracle™ - a fully test-
ed and supported, free download that will deliver tight integration between Oracle Database and Zend's supported PHP environment,
enabling developers to get up and running in minutes with PHP and Oracle infrastructure.
Scheduled for availability in CQ3, Zend Core for Oracle will deliver reliability, productivity and flexibility to run PHP applications tight-
ly integrated with Oracle Database. Zend will offer support and updates for Zend Core for Oracle, which will be compatible with Zend's
existing products such as Zend Platform and Zend Studio.
For more information visit: />+
The Zend PHP Certification Practice Test Book is now available!
We're happy to announce that, after many months of hard work, the Zend PHP
Certification Practice Test Book, written by John Coggeshall and Marco Tabini, is now
available for sale from our website and most book sellers worldwide!
The book provides 200 questions designed as a learning and practice tool for the
Zend PHP Certification exam. Each question has been written and edited by four
members of the Zend Education Board--the very same group who prepared the
exam. The questions, which cover every topic in the exam, come with a detailed
answer that explains not only the correct choice, but also the question's intention,
pitfalls and the best strategy for tackling similar topics during the exam.
For more information, visit
hhttttpp::////wwwwww..pphhppaarrcchh..ccoomm//cceerrtt//mmoocckk__tteessttiinngg..pphhpp
H
ow can we combat comment
spam or verify that those
using our forms are actually
doing so from our pages and not
some remote script out there? I
don’t pretend to have the definitive
answer, and, in fact, this month’s
Tips & Tricks column doesn’t
attempt to provide a concrete solu-
tion, but I will point out a few erro-
neous practices, show how they
leave forms vulnerable by providing
examples of scripts that can misuse
your forms, and provide a few “best
practices” for securing your forms.
There are several popular meth-
ods out there for protecting Web
forms. Almost all of them, however,
aim to accomplish the same result,
which is to determine the difference
between a human and a computer
(or automated script). Some scripts
embed a token of some sort in the
form and set a cookie or session
variable. Others provide the user
with a CAPTCHA (Completely
Automated Turing test to tell
Computers and Humans Apart)
image of a word or phrase that the
user must enter. Some check the
RReeffeerreerr
header. Still others imple-
ment some variant of each of these
methods.
The problem is that any script can
simulate a valid user (read
“human”) interaction with a form,
and some feel that, as long as the
script is properly simulating a user
session, it’s okay. Yet, if your forms
are set up improperly, these user-
simulating scripts can continually
access your script using the same
session, potentially flooding you
with spam. This month’s Tips &
Tricks examines three popular
methods of “securing” forms and
shows how to keep external scripts
from posting to them.
The Embedded Token Method
The simplest and perhaps most
user-friendly method to “securing”
a Web form is to use what I’m refer-
ring to as the “embedded token”
method.
The embedded token method is
simple because it only requires a
few lines of code to implement, and
it’s user-friendly because it does not
June 2005
●
PHP Architect
●
www.phparch.com
10
TTIIPPSS && TTRRIICCKKSS
CAPTCHA That Form
Before It Gets Away !
by Ben Ramsey
Abuzz with discussions, arguments, and numerous opinions on solutions to
the problem, the PHP community has been focused, lately, on how to pre-
vent weblog comment spam and how to protect one’s forms in general—
be they comment forms, e-mail forms, etc. The topic has graced the pages
of blogs, and threads on the subject have adorned more than one mailing
list. Some say it’s a PHP security problem; others blame the developers. But
one thing is certain: it’s just plain annoying.
require any additional action from
the user to validate their human
identity (there is no word or phrase
to type). It simply relies on the pres-
ence of a user agent (Web browser)
visiting the form. The server either
sets a session variable or asks the
browser to set a cookie that is then
checked against a hidden form field
when the user submits the form.
Listing 1 illustrates a very basic
implementation of this method.
The problem with the embedded
token method in its most basic form
is the assumption that only a Web
browser can set a cookie or make
use of sessions. This could not be
further from the truth, since the
Web server will send a Set-Cookie
header to the user agent, which
doesn’t necessarily have to be a
browser. As long as something can
parse and read HTTP response
headers and send valid HTTP
requests, it is a user agent—even if
it’s a script (PHP or otherwise).
Listing 2 illustrates a script that
uses the PEAR package
HTTP_Request to send and receive
valid HTTP headers, including the
ability to capture the Set-Cookie
header and send it back to the serv-
er in a valid request. For all intents
and purposes, this script is a valid
user, and the form treats it as such.
This script, of course, assumes the
presence of the “token” form field
and assumes that this form field will
never change in any way—the
name will always be “token,” and it
will always exist in its present form.
It uses a regular expression to then
grab the actual token from the form
field to send it back in a subsequent
POST action. Now, this regular
expression could be much more
complex to accommodate for
changing parameters within the
form field so that it is not so limited
to the field that it must find.
However, as I see it, there must be
a constant in order for this type of
simulated form post to work: the
field name. If the field name of the
token is always constant, then this
external post to the form will always
work. If you work out a way to ran-
domize the field name, then you
can block external scripts from
making use of yours. Randomizing
the field name may seem like a
superfluous extra step to block oth-
ers from using your scripts, but it
could save you from unnecessary
spam, flooding, or even being used
as a spam e-mail relay.
The Referrer Check Method
Another common approach to
blocking scripts from using your
forms is to check the
RReeffeerreerr
head-
er using
$$__SSEERRVVEERR[[‘‘HHTTTTPP__RREEFFEERREERR’’]]
.
This is often a suggested method
that many believe will completely
block external scripts from using
your forms. However, just about
every server-side scripting language
has the ability to modify the HTTP
RReeffeerreerr
header—and I’m told even
some proxies will change it, as well.
Let’s take our example in Listing
1. It’s simple to modify the code to
check the
RReeffeerreerr
. Just modify the
iiff
statement checking for the post-
ed “message” field to include a sec-
ond check against the
RReeffeerreerr
header, as shown here:
if (isset($_POST[‘message’]) &&
preg_match(“/^http:\/\/ben-
ramsey.com/”,
June 2005
●
PHP Architect
●
www.phparch.com
TTIIPPSS && TTRRIICCKKSS
11
1 <?php
2 // Embedded Token Method
3 session_start();
4
5 if (isset($_POST[‘message’])) {
6 if ($_POST[‘token’] == $_SESSION[‘token’]) {
7 $message = htmlentities($_POST[‘message’]);
8 echo ‘<h1>’ . $message . ‘</h1>’;
9 }
10 }
11
12 $token = md5(uniqid(rand()));
13 $_SESSION[‘token’] = $token;
14 ?>
15 <form method=”POST”>
16 Message: <input type=”text” name=”message” /><br />
17 <input type=”hidden” name=”token”
18 value=”<?php echo $token; ?>” />
19 <input type=”submit” />
20 </form>
21
Listing 1
1 <?php
2 require_once ‘HTTP/Request.php’;
3
4 $req =& new HTTP_Request(‘listing1-embedded_token.php’);
5
6 /* Simulate a valid user and get a session */
7 $req->setMethod(HTTP_REQUEST_METHOD_GET);
8 $response = $req->sendRequest();
9
10 $regex = ‘/\<input type=\”hidden\” name=\”token\” ‘.
11 ‘value=\”(.*)\” \/\>/’;
12 if (preg_match($regex, $req->getResponseBody(),
13 $matches)) {
14 $token = $matches[1];
15 }
16
17 $cookies = $req->getResponseCookies();
18 foreach ($cookies as $cookie) {
19 if (strcmp($cookie[‘name’], ‘PHPSESSID’) == 0) {
20 $session_id = $cookie[‘value’];
21 }
22 }
23
24 /* POST to the form with the session */
25 $req->
setMethod(HTTP_REQUEST_METHOD_POST);
26 $req->addCookie(‘PHPSESSID’, $session_id);
27 $req->addPostData(‘message’, ‘I simulated a user!’);
28 $req->addPostData(‘token’, $token);
29
30 $response = $req->sendRequest();
31
32 echo $req->getResponseBody();
33 ?>
34
Listing 2
CAPTCHA That Form Before it Gets Away
$_SERVER[‘HTTP_REFERER’])) {
Now, the script will only process the
form if the
RReeffeerreerr
matches any
page from
hhttttpp::////bbeennrraammsseeyy..ccoomm
.
Of course, if the
RReeffeerreerr
were from
hhttttpp::////wwwwww..bbeennrraammsseeyy..ccoomm
, it
would fail, but the regular expres-
sion used here is simple; it can be
made more complex to allow for
other variations of domain names.
Just as the
RReeffeerreerr
check method
is easy to implement, it’s similarly
easy to fake a
RReeffeerreerr
header with
PPEEAARR::::HHTTTTPP__RReeqquueesstt
. Adding the
following line of code to the POST
request in Listing 2 will trick the
form into thinking that the POST
it’s receiving is being sent from
hhttttpp::////bbeennrraammsseeyy..ccoomm
when, in
reality, it could be sent from any-
where on the Web.
$req->addHeader(‘REFERER’,
‘’);
The
RReeffeerreerr
header is not a good
safeguard for your scripts. It’s too
easy to manipulate, and this is not a
fault of PHP—almost every scripting
language can do this.
The CAPTCHA
CAPTCHA’s are quickly becoming a
preferred method of determining
whether a form post is from a valid
user or a script. Their popularity has
also led to great annoyances caused
by unfriendly user experiences due
to the terrible readability of most
CAPTCHA images. Nevertheless,
the CAPTCHA seems here to stay.
For the most part, the CAPTCHA
image is an effective means of
blocking external scripts from using
your forms. However, I have seen
several implementations that leave
much to be desired from the pro-
grammer.
For example, I have seen scripts
that simply embed the actual
CAPTCHA phrase in a hidden field.
In this case, a script such as the one
shown in Listing 2 can easily grab
the phrase and return it in a post to
the form. This form of security does
nothing to hinder external scripts
from using your forms. It merely
gives the appearance of tighter
control while aggravating your real
users who must squint to guess at
the CAPTCHA phrases. Never store
your CAPTCHA phrase in a hidden
field. If you must do so, use
mmdd55(())
and salt to disguise the word or
phrase.
Listing 3 uses
PPEEAARR::::TTeexxtt__CCAAPPTTCCHHAA
to create a simple CAPTCHA test.
Much like the example from Listing
1, it sets the phrase to a session
variable for checking against the
posted user input. Instead of plac-
ing the phrase in a hidden form
field like the token, however, the
user is required to enter the word or
phrase here. Already, the security is
increased because external scripts
cannot request this page and grab
the phrase from the code as shown
in Listing 2. However, not every-
thing is perfect here.
If a malicious user is feeling
rather, well, malicious, he can man-
ually access this form on your site
through a Web browser and grab
the session ID, which is automati-
cally saved to a cookie on his
machine. He can also make note of
the CAPTCHA phrase and then
leave your site without otherwise
touching the form. Now, armed
with a session ID and phrase, he
can use the code in Listing 4 to sim-
ulate a normal user posting to your
form and entering a proper
CAPTCHA phrase. As long as the
session ID remains active on the
server, the CAPTCHA phrase will
work.
This may not seem like a big deal
since it’s a lot of work for someone
to go through simply to flood your
site with posts, but it is an opportu-
nity that you will want to close to
outside scripts, and this is easy to
do. All you must do is unset the ses-
June 2005
●
PHP Architect
●
www.phparch.com
12
TTIIPPSS && TTRRIICCKKSS
1 <?php
2 require_once ‘HTTP/Request.php’;
3
4 $req =& new HTTP_Request(‘listing3-CAPTCHA.php’);
5
6 $req->setMethod(HTTP_REQUEST_METHOD_POST);
7 $req->addCookie(‘PHPSESSID’,’1nkh91unrmh8d6fr4brri4tli1’);
8 $req->addPostData(‘phrase’, ‘traiwrou’);
9
10 $response = $req->sendRequest();
11
12 echo $req->getResponseBody();
13 ?>
14
Listing 4
1 <?php
2 session_start();
3
4 if (isset($_POST[‘phrase’]) && isset($_SESSION[‘phrase’])
5 && strcmp($_POST[‘phrase’],
6 $_SESSION[‘phrase’]) == 0) {
7
8 echo ‘<h1>They match!</h1>’;
9
10 } else {
11
12 require_once ‘Text/CAPTCHA.php’;
13
14 $captcha = Text_CAPTCHA::factory(‘Image’);
15 $captcha->init(200, 80);
16
17 $_SESSION[‘phrase’] = $captcha->getPhrase();
18 ?>
19 <form method=”POST”>
20 <img src=”data:image/png;base64,
21 <?php echo chunk_split(base64_encode(
22 $captcha->getCAPTCHAAsPNG())); ?>
23 “ /><br />
24 <input type=”text” name=”phrase” />
25 <input type=”submit” />
26 </form>
27 <?php
28 }
29 ?>
30
Listing 3
CAPTCHA That Form Before it Gets Away
sion variable after processing the
form.
unset($_SESSION[‘phrase’]);
An external script will now be able
to fool your CAPTCHA exactly once,
but the phrase will no longer be
valid in the session after its use, so
the script cannot continue to post
to your form.
This seems like a no-brainer, but
I’m amazed at how often I see this
simple step left out of code exam-
ples and actual production code.
It’s not a hard thing to do, and it
doesn’t take rocket science, but it’s
an often-overlooked practice.
The Security Question
Throughout this column, I’ve been
referring to these examples as
being “insecure” and giving you
tips on how to “secure” the code.
In reality, these are not true security
concerns. Left unchecked, your
server or database will not be open
to attacks. However, your web site
forms may be open to spamming
and flooding—and you could
potentially be used as an e-mail
relay, depending on how your
forms are set up.
In general—and as a related
aside—you should never use a
“form mail” script that requires a
hidden form field for a
TToo
address.
Even if the script checks the
RReeffeerreerr
header, you are vulnerable as a
spam relay—it’s happened to me.
Instead, always set the
TToo
address
from the server-side and within the
actual PHP code.
Smart Programming
In this column—my debut effort for
Tips & Tricks—I’ve given several
examples of how external scripts
can use your forms even when
you’re sure they can’t. Plus, I’ve
shown you how to use
PPEEAARR::::HHTTTTPP__RReeqquueesstt
to simulate a
valid user and act as a user agent.
I’ve shown more tricks than I have
tips, but in the end, being a smart
programmer is the key. It is my
hope that you’ll take these few tips
and expound upon them as you
program applications. Being a
smart programmer means thinking
through the problem and even con-
sidering how others may abuse
your application. Only then will you
be able to tackle real security prob-
lems head on.
Until next time, be sure to prac-
tice safe coding!
TTIIPPSS && TTRRIICCKKSS
About the Author ?>
To Discuss this article:
/>Ben Ramsey is a Technology Manager for Hands On Network in Atlanta, Georgia. He is an author, Principal member of
the PHP Security Consortium, and Zend Certified Engineer. Ben lives just north of Atlanta with his wife Liz and dog Ashley.
You may contact him at
rraammsseeyy@@pphhpp..nneett
or read his blog at
hhttttpp::////bbeennrraammsseeyy..ccoomm//
.
Award-winning IDE for dynamic languages,
providing a powerful workspace for editing,
debugging and testing your programs. Features
advanced support for Perl, PHP, Python, Tcl and
XSLT, on Linux, Solaris and Windows.
Download your free evalutation at www.ActiveState.com/Komodo30
CAPTCHA That Form Before it Gets Away
P
HP has come a long way from being a little set of
C-based helper code, used to maintain Rasmus
Lerdorf’s résumé. Versions 4 and especially 5 have
added many of the features needed to build enterprise
web applications and make it reasonable to compare
PHP with technologies such as Java Servlets and
ASP.NET. Foremost amongst these additions has been
the improvement of object support. While objects were
possible in version 4, they were little more than syntac-
tic sugar. PHP 5 now has comprehensive support for
objects and increasingly, people are using objects to
build more complicated PHP scripts. However, with
comparable support for objects, PHP begins to face a
problem which has confronted other languages: how
are these objects to be stored? Relational SQL-driven
databases remain the only widespread form of storage
for web applications, but this does not fit smoothly
with object-orientated code.
REQUIREMENTS
PHP 4, 5
OS Any
Other Software PEAR
Code Directory persistence
June 2005
●
PHP Architect
●
www.phparch.com
14
OBJECT PERSISTENCE IN PHP
RESOURCES
DB DATA
OBJECT
hh tt tt pp :: // // pp ee aa rr .. pp hh pp .. nn ee tt // pp aa cc kk aa gg ee //
DDBB__DDaattaaOObbjjeecctt
PROPEL
hhttttpp::////pprrooppeell..pphhppddbb..oorrgg//
EZPDO
hhttttpp::////wwwwww..eezzppddoo..nneett//
ii
Almost any PHP application needs to store some kind of data. While databases provide high performance and
reliability, actually using them when writing object-orientated code can prove tedious. In this article, we’ll look at three
solutions which help you to forget about storage and focus on functionality.
CROSSING
DIVIDE
THE
by Theo Spears
FFEEAATTUURREE
Crossing the Divide: Object Persistence in PHP
June 2005
●
PHP Architect
●
www.phparch.com
15
Persistence frameworks provide a bridge between
object code and the database. To do this they perform
a number of tasks. The most simple of these is transfer-
ring data from the member variables of objects to
tables in the database, and back again. This often
requires marshalling the data to the correct format for
the database (see the discussion on dates, below, for an
example).
A second role performed by persistence frameworks
is managing the relationships between different
objects. When objects contain references to other
objects these should be translated into foreign key
entries, or for many-to-many joins, entries in a join
table. Likewise, when loading objects, references
should be automatically mapped back into objects.
This, in turn, can cause problems with circular refer-
ences or a single object load bringing many other
objects from the database. To overcome this, most per-
sistence frameworks use some form of lazy loading
where related objects are only loaded when they are
accessed.
Lastly, loading an object from the database every
time it is needed is expensive, thus many persistence
frameworks include some form of cache. This means
multiple requests for the same object result in only a
single database query. This can heavily reduce database
load in complicated applications. In Java and ASP.NET
this data is often cached between requests. In PHP,
however—because PHP has no reliable framework for
sharing memory between requests—this caching has to
be performed on each page load. This makes loading
performance more important with PHP than with other
technologies.
There are several well known frameworks for object
persistence with Java or Microsoft .NET, the most pop-
ular probably being Hibernate and NHibernate, respec-
tively. There are also an increasing number of similar
frameworks for PHP that are in various states of comple-
tion and offer varying functionality. Here we will look at
three: DB_DataObject, Propel, and EZPDO. In order to
demonstrate the strengths and weaknesses of each, we
will use each to implement a very simple example that
I have put together, to determine how straightforward
each one makes coding the various parts.
School Manager
In the first example, we will be making a website for a
school to keep track of its students and the classes that
they are taking. While maintaining a relatively simple
object model, this gives scope for testing some of the
more advanced features of the persistence frameworks.
Our website must allow people to do the following:
Add new students and delete or modify existing ones
Display a list of all students and find students with a
given name
View teacher details
View class details
Alter the list of students associated with a class
Of course, any real website would need many more
options than these, but in terms of code, most would
be similar to the ones above, just working with differ-
ent objects.
From this, I have created an object model which you
can see in Figure 1. Notice that Teacher and Student
are derived from a common class—Person. Inheritance
is an important part of object-orientated programming
and the part which is most difficult to translate into
databases. Considering the small size of this project I
chose to do no further high level preparation, the rest
of the design will be decided upon as the code is writ-
ten. For larger projects you should, of course, put more
work into these early stages.
Installation and Configuration
I am choosing to use MySql to provide database func-
tionality, with all three persistence frameworks. It is
installed in the usual way, and a separate database was
created for each framework. As both Propel and EZPDO
provide their own SQL to create the database tables,
and because the tables they generate are incompatible,
using a shared database would be impossible. What
you gain in coding speed with general tools like these,
you sometimes lose in flexibility.
I then installed the three frameworks. Each took some
trial and error to install, initially, but once I had identi-
fied and installed all the requirements, installation was
reasonably straightforward. The simplest was
DB_DataObject, which, as a native member of PEAR,
simply required the following PEAR packages (including
Figure 1
dependencies):
PPEEAARR
,
AArrcchhiivvee__TTaarr
,
CCoonnssoollee__GGeettoopptt
,
XXMMLL__RRPPCC
,
DDBB
,
DDaattee
, and
DDBB__DDaattaaOObbjjeecctt
.
Propel can also be installed via the PEAR installer—
but it must be fetched from a separate repository–by
issuing the following commands:
pear install \
/>pear install \
/>rent.tgz
For development you will also need to run
pear install \
o/pear/phing-current.tgz
pear install \
/>rent.tgz
Propel requires DOM support in your version of PHP.
Most PHP users will already have this installed, but with
some binary distributions (such as Debian) it may be
necessary to install it as an additional module.
EZPDO, by contrast, is installed by downloading a
package and extracting it somewhere in your project
directory. You may wish to rename the folder to remove
the version number so your code does not break when
you upgrade to a newer version. EZPDO also requires
the PHP XML and SPL modules which again can be
compiled in or installed from packages. It also needs
items from PEAR including a few packages that are in
Beta state. To install these, you need to run:
pear install XML_Util Log FSM
pear uninstall XML_Parser
pear -d preferred_state=beta \
install XML_Parser XML_Serializer
Although not strictly related to installation, there is
another hurdle to cross before you are able to use
EZPDO: it has automatic test suites, but these do not
test all database options so, with Pear DB and MySQL,
it is necessary to fix a small bug. You should find a copy
of a patch to fix this in the code archive for this article.
We can now configure each framework for our specif-
ic project. Each of the three packages has its own con-
figuration file. There is no need to memorise the details
for any of the frameworks as all provide examples
which you can copy-paste and then modify to suit your
application. DB_DataObject is, again, the simplest of
the three to configure. It uses the PEAR options so you
must call
PPEEAARR::::ggeettSSttaattiiccPPrrooppeerrttyy
for each property
and set an appropriate value to this static property.
However, the documentation includes a small script to
load all the information from an
iinnii
file. This method of
implementing your configuration is much simpler, but
you might wish to avoid it if you are trying to squeeze
ultimate performance. There are five settings: the data-
base connection, a string your class names must start
with in order to be stored in the database, and three
paths to where the classes and schematic details are
June 2005
●
PHP Architect
●
www.phparch.com
FFEEAATTUURREE
16
1 [DB_DataObject]
2 database = mysql://orm:orm@localhost:/db_dataobject
3 schema_location = /web/orm/db_dataobject/DataObjects
4 class_location = /web/orm/db_dataobject/DataObjects
5 require_prefix = /web/orm/db_dataobject/DataObjects
6 class_prefix = DataObjects_
7
Listing 1
1 class Student extends Person {
2 /**
3 * The school year containing the student
4 * @orm integer
5 */
6 public $year_group;
7 /**
8 * The tutor for this student
9 * @orm has one Teacher
10 */
11 public $tutor;
12 /**
13 * The classes the pupil is part of
14 * @orm has many class
15 */
16 public $classes;
17 }
Listing 2
1 // Get a list of the students in the database
2 $student = DB_DataObject::factory(“student”);
3 $result = $student->find();
4
5 if ( $student->count() == 0 ) {
6 message(“There are no students”);
7 return;
8 }
9
10 // Show a header for the table
11 show_student_list_header();
12
13 while ( $student->fetch() ) {
14
15 // Fetch related classes as well
16 $student->getLinks();
17
18 // Show each student’s details
19 show_student_details ( $student );
20
21 }
22 show_student_list_footer();
23
Listing 3
1 // Get a list of the students in the database
2 $student = DB_DataObject::factory(“student”);
3 $result = $student->find();
4
5 if ( $student->count() == 0 ) {
6 message(“There are no students”);
7 return;
8 }
9
10 // Show a header for the table
11 show_student_list_header();
12
13 while ( $student->fetch() ) {
14
15 // Fetch related classes as well
16 $student->getLinks();
17
18 // Show each student’s details
19 show_student_details ( $student );
20
21 }
22 show_student_list_footer();
23
Listing 4
Crossing the Divide: Object Persistence in PHP
FFEEAATTUURREE
June 2005
●
PHP Architect
●
www.phparch.com
17
stored. A sample configuration file for my computer is
shown in Listing 1.
Propel is configured through an XML file that speci-
fies where to log information and how to connect to
the database. Unlike DB_DataObject and EZPDO,
Propel doesn’t require all classes to be stored in a spe-
cific location; you include the classes directly, so they
can be stored wherever you choose. This is especially
useful for large projects, as classes from different mod-
ules can be stored in different directories.
EZPDO’s configuration is the least flexible of the thee,
and it has the most complicated configuration file.
Configuration must be stored in the project directory,
in a file called
ccoonnffiigg..xxmmll
. The most important ele-
ments are
ssoouurrccee__ddiirrss
,
ddeeffaauulltt__ddssnn
,
ddbb__lliibb
and
aauuttoo__fflluusshh
.
ssoouurrccee__ddiirrss
controls the directory contain-
ing classes to be serialised. Assuming you leave
rreeccuurr--
ssiivvee
set to
ttrruuee
, you can store classes anywhere under
this path, although I did not bother with any subdirec-
tories.
ddeeffaauulltt__ddssnn
and
ddbb__lliibb
control the database
connection. On a unix-like platform, you will probably
want to use
ppeeaarrddbb
as your
ddbb__lliibb
and
ddeeffaauulltt__ddssnn
is
then a standard PEAR database URI.
aauuttoo__fflluusshh
controls
whether all items are automatically stored to the data-
base when your script ends. Although useful, it will
have serious effects on performance, so I recommend
turning it off. You may also wish to modify the logging
options to either turn off logging altogether, or to log
to a database instead of the default file.
The Object Model
Each of the three frameworks has a different way of
specifying the layout of classes and tables. With
DB_DataObject, you create your tables and then either
manually define classes or run a script to automatically
generate them, while with EZPDO you define your
classes and tables are automatically created. Propel
generates both SQL and classes from a separate XML
file that you’ve created.
DB_DataObject fully supports string and numeric
types. It also supports date types, although in a more
awkward way—it allows you to treat them as if they
were strings. Unlike the other frameworks, dates are
not converted into unix timestamps, so dates before
the start of the epoch (January 1
st
, 1970) are support-
ed. If your application has to deal with dates outside of
this range, this may be a critical consideration. Other
data types may or may not work properly. Bearing this
in mind, you are free to create your tables however you
choose. Once this is done, it is simplest to run the
ccrreeaatteeTTaabblleess..pphhpp
script that is included with
DB_DataObject, passing it an
iinnii
file in the same form
as the one used for configuring the runtime. This will
generate classes for of all the tables in your schema. It
will not, however, pull out any details about joins or for-
eign keys. Alternatively you can manually create classes
derived from DB_DataObject and create your own
schema file. Personally, I do not recommend this: it is
exactly the type of boring work persistence frameworks
are meant to save you from.
If you wish to use the join functionality of
DB_DataObject you will need to create a configuration
file specifying the links between tables. This is another
iinnii
file in the same folder as the schema generated by
ggeenneerraatteeTTaabblleess..pphhpp
. Its filename takes on a form like:
<<ddaattaabbaasseennaammee>>..lliinnkkss..iinnii
. In this file, there is a section
for each table and then entries in the form
<<llooccaall ccooll--
uummnn>> == <<rreemmoottee ttaabbllee>>::<<rreemmoottee ccoolluummnn>>
for each rela-
tionship. Look at
ddbb__ddaattaaoobbjjeecctt..lliinnkkss..iinnii
in the
DataObjects directory of the DB_DataObject project for
an example of such a configuration file. DB_DataObject
has no native support for inheritance, although it is
possible to share methods between classes by modify-
ing the generated inheritance hierarchy to give them a
common superclass. It is impossible to search for a
superclass and get all subclasses that match.
As mentioned above, with Propel you instead create
an XML file. This has various nodes for each table and
column, and since it is XML, it is fairly self-document-
ing. The only item of note is that to specify a relation-
ship, you must both define a column in the table for the
foreign key and also separately define the relationship.
Personally, I think XML is a poor format for defining
classes and their relationships, especially as no DTD is
provided to allow editors to auto-complete. I would
suggest that you either generate this XML from anoth-
er format, or use a GUI XML editor, if you wish to use
Propel for anything more than the most simple of
object models.
Propel has a very strange form of inheritance.
Essentially, it uses subtractive rather than additive inher-
itance; the superclass table must specify all fields and
each subclass may choose which of those fields to use.
“P
ersistence frameworks provide
a bridge between object code and the database.
”
Crossing the Divide: Object Persistence in PHP
June 2005
●
PHP Architect
●
www.phparch.com
FFEEAATTUURREE
18
There is no built in way of simply getting objects of a
specific class, you must add code to filter the superclass
entries yourself. Because of this, I chose not to use the
inheritance feature and instead just use separate class-
es. Once this XML file is defined you must run the
pprroo--
ppeell--ggeenn
script, passing it the directory containing your
sscchheemmaa..xxmmll
file. This will, in turn, generate all classes
and an SQL file that will generate the table. This file will
be place under the
bbuuiilldd
directory of your project.
Coming from a C# background, I found EZPDO the
most intuitive. With EZPDO, you write your classes as if
they were not going to be persisted at all. You then add
the custom phpdoc
@@oorrmm
tag to each field that should
be stored in the database, specifying its type. You can
give a name for each table and column, but if you
don’t, EZPDO will automatically choose one. This has
the added side effect of encouraging you to document
your variables.
To specify relationships between classes, you use a
class name as the type, together with information as to
the type and multiplicity of the relationship. EZPDO
supports composition (
ccoommppoosseedd__ooff
) relationships,
where destroying the parent object destroys the chil-
dren and aggregation (
hhaass
) relationships where the
child can exist without the parent. It also allows one-to-
one (
oonnee
) and one-to-many (
mmaannyy
) relationships. For an
example of this, see the
SSttuuddeenntt
class shown in Listing
2. As you simply define classes, inheritance works as
expected, although again there is no way to fetch a
superclass and get results for all matching subclass
instances.
Let’s Write Some Software
It seems to have taken a long time to get to a position
where we are ready to start writing some code, but
hopefully, all of our preparation will prove worthwhile,
by allowing us to write the code more quickly, and with
fewer bugs. Working from the list of requirements, let’s
start by adding a page to list all of the students. For all
three frameworks, the logic is exactly the same: ask the
framework for a list of all students and then iterate
through that list, displaying each entry in a table. With
DB_DataObject, this is a matter of creating an empty
student object, either directly or through the
DDBB__DDaattaaOObbjjeecctt::::ffaaccttoorryy(())
function, and then calling its
ffiinndd(())
method. Using
DDBB__DDaattaaOObbjjeecctt::::ffaaccttoorryy(())
has
the advantage that the required PHP files will automat-
ically be loaded for you. As we have not set any prop-
erties on the object, calling
ffiinndd(())
gets a list of all stu-
dents in the database, regardless of their properties.
See Listing 3 for some simple pseudo-code. Note: as we
want to show details from the student’s tutor, which is
stored in a different table, we have to call
ggeettLLiinnkkss(())
on each student. This loads the tutor into the
__ttuuttoorr
member. We could also have called
aaddddJJooiinn(())
before
finding the entries, to reduce the number of queries,
but this results in a less logical resulting object.
With Propel, we create an empty
CCrriitteerriiaa
object to
match all students, and then call
SSttuuddeennttPPeeeerr::::ddooSSeelleeccttJJooiinnTTeeaacchheerr(())
to directly get all
students and their tutor details from the database.
Note the name is
ddooSSeelleeccttJJooiinnTTeeaacchheerr(())
not
ddooSSeelleeccttJJooiinnTTuuttoorr(())
; it is based on the name of the for-
eign table not the name of the foreign key column in
the local table. We can then call
ggeettTTeeaacchheerr(())
to get
1 // Populate a class object to use for the search
2 $class = DB_DataObject::factory(‘class’);
3 $class->selectAs();
4
5 // We also want the size of each class
6 // so link a student record
7 $class->groupBy(‘class.id’);
8 $student = DB_DataObject::factory(‘link_student_class’);
9 $class->joinAdd(&$student,”LEFT”);
10 $class->selectAdd(“count(link_student_class.student_id) “
11 . “as student_count”);
12
13 $class_count = $class->find();
Listing 6
1 // DB_DataObject
2 $student = DB_DataObject::factory(‘student’);
3 $student->full_name = $_POST[‘frm_full_name’];
4 $student->date_of_birth = $_POST[‘frm_date_of_birth’];
5 $student->year_group = $_POST[‘frm_year_group’];
6 $student->tutor = $_POST[‘frm_tutor’];
7 $student->insert();
8
9 // Propel
10 $student = new Student();
11 $student->setFullName( $_POST[‘frm_full_name’] );
12 $student->setDateOfBirth(
13 strtotime($_POST[‘frm_date_of_birth’]) );
14 $student->setYearGroup( $_POST[‘frm_year_group’] );
15 $student->setTutor( $_POST[‘frm_tutor’] );
16 $student->save();
17
18 // EZPDO
19 $student = epManager::instance()->create(‘Student’);
20 $student->full_name = $_POST[‘frm_full_name’];
21 $student->date_of_birth =
22 strtotime($_POST[‘frm_date_of_birth’]);
23 $student->year_group = $_POST[‘frm_year_group’];
24 $student->tutor = epManager::instance()->get(
25 ‘Teacher’, $_POST[‘frm_tutor’]);
26 $student->tutor->tutees[] = $student;
27 epManager::instance()->commit($student);
Listing 7
1 // Get a list of all the students in the database
2 $student_list = epManager::instance()->getAll(‘Student’);
3
4 // Check there are any
5 if ( $student_list == false ) {
6 message(“There are no students”);
7 return;
8 }
9
10 // Show a header for the table
11 show_student_list_header();
12
13 foreach ( $student_list as $student ) {
14
15 // Show each student’s details
16 show_student_details ( $student );
17 }
18 show_student_list_footer();
Listing 5
Crossing the Divide: Object Persistence in PHP
details of the student’s tutor. See Listing 4 for an exam-
ple of this.
Although doing this with DB_DataObject and Propel
was hardly complicated, EZPDO makes this task the
easiest. Here, it is a matter of getting an instance of the
persistence manager with the
eeppMMaannaaggeerr::::iinnssttaannccee(())
function, and then calling
ggeettAAllll((‘‘SSttuuddeenntt’’))
on the
returned instance. Again, the required files are auto-
matically included. See Listing 5.
There are a few other differences I came across when
working with the list of objects that each framework
returned. While Propel and EZPDO return an array of
objects, DB_DataObject returns an iterators, on which
you call
ffeettcchh(())
,repeatedly. This is slightly less object-
like, but hardly a problem. More annoyingly, in terms
of increased typing, Propel objects use get and set
methods where DB_DataObject and EZPDO use direct
assignment. This also means Propel code requires
slightly more effort to read, for people coming from a
C# background, although Java programmers should be
used to it.
We also want to be able to look up specific students
based on their names. For this, I modified the code that
shows all students, so it could optionally be limited to
showing only students matching a certain criteria. All
three frameworks have a simple method for specifying
criteria when you simply wish to check for equality.
DB_DataObject and EZPDO do this by setting values in
an object and then looking for all objects like it, while
with Propel, you add constraints to the
CCrriitteerriiaa
object.
However, because I want to allow searching on sub-
strings, things are more complicated. This is simplest
with Propel where it is simply a matter of adding a
CCrriitteerriiaa
and specifying that a
LLIIKKEE
match is required.
With DB_DataObject, it is necessary to use the
aaddddWWhheerree(())
method to directly add an SQL
WWHHEERREE
con-
straint to the request. EZPDO requires the most compli-
cated implementation; it uses its own query language
to allow you to constrain the results, thus you must call
the following to get a list of students matching a name:
$student_list = epManager::instance()->find(
“from Student as student where student.full_name
like ?”,
$name_template
);
We can use exactly the same methods to get a list of all
the teachers. In fact, with a little use of polymorphism,
we could use exactly the same code to get the list of
students or the list of teachers, although to keep things
simple in this example, I chose not to. However, we also
want to be able to view details of an individual teacher.
To do this, we must select a single teacher entry from
the database. This is again done in similar ways with all
3 frameworks. With DB_DataObject, you use the
DD BB __ DD aa tt aa OO bb jj ee cc tt :: :: ss tt aa tt ii cc GG ee tt (( $$ cc ll aa ss ss __ nn aa mm ee ,,
$$pprriimmaarryy__kkeeyy))
function, while in Propel you must call
TTeeaacchheerrPPeeeerr::::rreettrriieevveeBByyPPKK(($$pprriimmaarryy__kkeeyy))
. EZPDO
uses the
ggeett(($$ccllaassss__nnaammee,, $$pprriimmaarryy__kkeeyy))
function on
an instance of the persistence manager. In all three
cases, this returns an instance of the relevant object.
As well as showing the teacher’s own properties, I
wanted to show a list of their tutees. To do this with
DB_DataObject, the simplest way was simply to fetch
all of the students from the database, whose tutor field
matched the primary key of the teacher being viewed.
This is effectively querying as if there was no persistence
framework, but simply an object wrapper. It is effective,
but breaks the object-orientated encapsulation I was
hoping for. Propel is far more promising in this regard.
It provides a
TTeeaacchheerr::::ggeettSSttuuddeennttss(())
function that
returns an array of all the teacher’s tutees. By iterating
through this array, we get a list of student objects.
Similarly, EZPDO allows us to access the
ttuutteeeess
proper-
ty as an array of student objects.
Next, let’s get a list of courses. This is slightly more
complicated, as I also want to display how many stu-
dents are enrolled in each course. Here, the abstraction
provided by DB_DataObject was rather inadequate.
The solution I came up with involved manually adding
a join between the course table and the course-student
join table, and manually adding a column to select the
CCOOUUNNTT(())
of rows in the database. Although, arguably
slightly less work that using raw SQL, I still largely had
to think in terms of relational databases. For the code I
used, refer to Listing 6.
Propel again provided a better level of abstraction.
Using the
ggeettSSttuuddeennttCCoouurrsseeRReeffJJooiinnCCoouurrssee(())
function, I
was able to get an array of students which I could
count. As this suggests, many of the function names for
many-to-many joins in Propel are rather cumbersome,
FFEEAATTUURREE
June 2005
●
PHP Architect
●
www.phparch.com
19
“P
ropel is configured through an XML file that specifies where to
log information and how to connect to the database.
”
Crossing the Divide: Object Persistence in PHP
but you can see the list by looking at the generated
code. If you are going to be using a function often, you
can always wrap it to have a friendlier name. With
EZPDO, it is again just a matter of calling
ccoouunntt(())
on
the
ssttuuddeennttss
member.
Moving on, lets now look at how we can create and
store new objects, in this case for our students. In each
case, this is done by creating a new class instance and
setting its properties, then telling the framework to
store it in the database. With DB_DataObject this is
done with the
DDBB__DDaattaaOObbjjeecctt::::ffaaccttoorryy(())
function, as
mentioned above. Once the object is ready to be
added to the database, you call its
iinnsseerrtt(())
method to
store it. Propel is the simplest of the three when it
comes to implementing this. You create the object as
you would any other with the
nneeww
operator and then
call
ssaavvee(())
to store it. EZPDO works slightly differently.
You create the object with the
ccrreeaattee(())
function on the
persistence manager, but it is stored by calling the
ccoomm--
mmiitt(())
method on the manager, rather than the object
itself. This makes for slightly longer code. If you
enabled
aauuttoo__ccoommmmiitt
in the configuration file, this step
is optional.
EZPDO also works slightly differently in terms of
adding relationships. With DB_DataObject and Propel,
all relationships are implicitly bidirectional, so setting
the tutor for a student automatically adds the student
to that teacher’s list of tutees. However, with EZPDO,
links in both directions are discrete, so you must also
modify the teacher to add a reference to the student.
This is the expected behaviour from a purely object-ori-
entated point of view but is far less convenient, and vio-
lates the Don’t Repeat Yourself principle of avoiding data
duplication. For an example, look at Listing 7.
Editing entries is done in almost exactly the same way
as adding new ones. Instead of creating a new record,
you fetch an existing one, as detailed above. You mod-
ify its properties appropriately, and then save it back to
the database. With Propel and EZPDO this is exactly the
same as saving a new object, with DB_DataObject you
must call
uuppddaattee(())
instead of
iinnsseerrtt(())
.
Deleting is done in a similar manner. With
DB_DataObject, you call
ddeelleettee(())
on the relevant
object. You can also create an empty object and use
wwhheerreeAAdddd(())
to delete all objects matching a specific cri-
teria. With Propel, you can delete a specific object or
delete any object matching a provided
CCrriitteerriiaa
class.
With EZPDO, you call
ddeelleettee(())
on the persistence man-
ager to delete an object, however there is no way to
delete all objects matching a certain criteria.
Other considerations
As important as the ease of use of a framework, is the
quality of documentation that is provided for it. In this
respect, all three frameworks do rather well.
DB_DataObject provides the standard PEAR API refer-
ence, along with a user guide that takes you through
each function, with lots of examples. It is worthwhile to
read all of the documentation, if you plan to use
DB_DataObject, as it provides some more efficient
methods of performing certain tasks.
Propel also provides an extensive API reference and
user guide. The user guide walks you through setting
up a simple example and then goes on to cover more
advanced topics. It is probably the best documentation
provided by any of the three frameworks.
EZPDO provides a walk-through tutorial on its web-
site that takes you through setting up a simple project,
which is included with the EZPDO framework down-
load. Its coverage of more advanced areas is adequate.
It may not be as comprehensive as that of Propel,
though this may in part be because it is simpler to use.
All of its functions are fully documented in the phpdoc
scheme, although unfortunately, I could not find a copy
of the generated documentation online, so if you want
it you may have to generate a copy for yourself.
For people building large sites, another important
consideration will be performance. I always question
the value of “microbenchmarks,” but Figure 2 shows
the number of queries each framework needs for vari-
ous operations. The results show similar performance
between DB_DataObject and Propel but a much high-
er query count for EZPDO, especially when modifying
existing data. Clearly, there is room for improvement in
the EZPDO engine, especially where existing data is
modified, which seemed to require an absurd number
of queries. This may, in part, be due to my use of circu-
lar references but this is not that uncommon a pattern.
Hopefully, this is something that will be fixed in later
June 2005
●
PHP Architect
●
www.phparch.com
FFEEAATTUURREE
20
OPERATION NUMBER OF QUERIES
DB
DataObject
Propel EZPDO
LIST STUDENTS 129 2 158
LIST CLASSES 9 9 117
ADD A NEW STUDENT 2 5 46
MODIFY AN
EXISTING STUDENT
2 6 2518
SHOW A TEACHER 3 7 87
SHOW A CLASS 34 19 66
CHANGE MEMBER
OF CLASS
11 51 2138
Figure 2
Crossing the Divide: Object Persistence in PHP
versions as EZPDO did feel noticeably slower to me
than the other two frameworks.
Lastly, a brief note on version support:
DB_DataObject fully supports PHP 4 and 5. Propel pri-
marily supports PHP 5, although work is being done on
a PHP 4 version. EZPDO is firmly PHP 5 only and would
be extremely difficult to port to PHP 4. While I recom-
mend people use PHP 5 and take advantage of the new
features it provides, in some cases this is not feasible
and EZPDO may simply not be an option.
Conclusion
I wish to stress that the three frameworks I looked at
here are by no means all of the options available for
PHP. If there in another framework you like, and think it
is better than the ones presented here, we would love
to have you come and tell us its merits in the discussion
forum. Likewise, if you think there is something crucial
I have failed to mention, let everyone else know about
it.
Restricted to these three, your choice will inevitably
depend on your situation. DB_DataObject is not really
do a full translation between the world of objects and
the world of databases, and is more just a wrapper
around SQL. I chose to include it because of its
ggeettLLiinnkkss(())
and
jjooiinnAAdddd(())
functionality but these do
not really compare with the power of Propel and
EZPDO. It is a good choice if you want a wrapper that
avoids the need to write SQL, but are happy to think in
a relational manner. EZPDO, by contrast, completely
abstracts the database, almost to too great of an
extent, though I found it the most pleasant of the three
alternatives to use. Unfortunately, it has severe perform-
ance problems at the moment. Hopefully these will be
overcome in later versions but until then I cannot real-
ly recommend it for anything beyond the smallest per-
sonal site. This leaves Propel which although not as
complete as EZPDO does a good job of providing an
object-orientated interface to databases. It is more
complete that DB_DataObject and much lighter-
weight than EZPDO so if you can tolerate its XML
method of defining your classes, it is the one I would
recommend.
FFEEAATTUURREE
June 2005
●
PHP Architect
●
www.phparch.com
21
About the Author ?>
To Discuss this article:
/>Theo is a student at a university in the UK, studying Social Sciences. While he claims to pre-
fer C# and ASP.NET, in his spare time he can still, often, be found writing PHP scripts or
giving tips on IRC. He can be contacted at
pphhppgguuyy@@tthheeooss..mmee..uukk
Available Right At Your Desk
All our classes take place
entirely through the Internet
and feature a real, live instructor
that interacts with each student
through voice or real-time
messaging.
What You Get
Your Own Web Sandbox
Our No-hassle Refund Policy
Smaller Classes = Better Learning
Curriculum
The training program closely
follows the certification guide—
as it was built by some of its
very same authors.
Sign-up and Save!
For a limited time, you can
get over
$300 US in savings
just by signing up for our
training program!
New classes start every three weeks!
/>Crossing the Divide: Object Persistence in PHP