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 Con
Trang 1The WURFL project
Implementing search with Lucene
Integrating a Java search engine
API into your PHP site
Extreme cross-platform WAP development with PHP
A coder's introduction to working with
directory services
The Magazine For PHP Professionals
Agile software development with PHPUnit
Industrial strength MVC
Building a reusable development
framework with Open Source tools
PHP Unit Testing
Use OOP to manage your forms
Implementing object-oriented form
libraries that promote uniformity and
reusability
Trang 2This 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.phparch.com/grant and submit their applications for a grant Our goal is
to provide a financial incentive to those projects that, in our opinion, have the opportunity to revolutionize PHP and its position in the IT world.
In order to be eligible for the Grant Program, a project must be strictly related
to PHP, but not necessarily written in PHP.
For example, a new PHP extension written
in C, or a new program in any language that lends itself to using PHP in new and interesting ways would also be acceptable The only other important restriction is that
the project must be released under either
the LGPL, the GPL or the PHP/Zend license Thus, commercial products are
Program
Trang 4Visit www.zend.com
for evaluation version and ROI calculator
Zend Performance Suite
Reliable Performance Management for PHP
Serve More.
With Less.
Trang 5Graphics & Layout
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: info@phparch.com Editorial: editors@phparch.com Subscriptions: subs@phparch.com Sales & advertising: sales@phparch.com Technical support: support@phparch.com
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
Trang 6counted 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 level, gentle discussion, light on code and long on cold, hard facts you’ll need
high-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!
Trang 7MySQL AB announced this month
that the beta version of the MySQL
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 informationpage for your current PEAR install The format for thepage is similar to that for phpinfo() except usingPEAR colors The output has complete
PEAR Credits (based on the packages youhave installed) and will show if there is anewer version than the one presentlyinstalled (and what it's state is) Eachpackage has an anchor in the formpkg_PackageName - where PackageName is a case-sensitive PEAR package name
Visit PEAR.php.netto download the new package
Mozilla Firebird
Mozilla.org has announced the release of Firebird 0.6.Mozilla Firebird is a redesign of the Mozilla browsercomponent, similar to Galeon, K-Meleon andCamino™, but written using the XUL user interfacelanguage and designed to be cross-platform This lat-est version includes:A New theme, RedesignedPreferences Window, Improved Privacy Options,Improved Bookmarks, Talkback Enabled, AutomaticImage Resizing, Smooth Scrolling, MacOSx Supportand much more For more information, or to down-load, visit Mozilla.org
PHP and Java?
From 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 ourhounds Keep an eye on our website on June 9th for more information!
W
Trang 9This 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 theycan all be printed at once
• Each page of the application will contain thecurrent date
• The user will be able to view a summary ofall the link groups, and will be able to jumpdirectly to that group on the main listing oflinks
• The Admin will be able to add, modify ordelete both link groups and links TheAdmin can change the sequence that bothgroups and links withing groups are present-
ed in the application, ease of use is alsoimportant
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
Trang 10• 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 fewthings The fact that the application is basically a website means that PHP is certainly a leading candidate forimplementation The requirement for transitioning toother resources to maintain the application, and thedesire for a robust and flexible framework, push us inthe direction of implementing an MVC-style applica-tion The fact that “each page” must have a datestamp, and possibly a link to the editing pages, tends
to indicate we should establish some sort of a site dering framework This is often implemented withheaders and footers in templates Finally, with website-independent business logic, strict referential integrity,queries across multiple tables (at least if we model linksand groups in separate tables) and date columns to bemodified on each SQL request, it looks like we havemoved somewhat beyond MySQL’s fast retrieval of sim-ple queries sweet spot, and another RDBMS will berequired
ren-Developing the Application
To review the development of this application, I think
it is appropriate to take a look at the overall ture first, which dovetails into Models Next, reviewinghow the Controller (Phrame) implements applicationflow in this application, and lastly how the views areimplemented in this application
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 sales@chainreactionweb.comwww.chainreactionweb.com
www.chainreactionweb.com/reseller
Nobody
Hosts OSCommerce Better!
We Guarantee It!
Trang 11of 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 othwhatev-er 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 Youshould 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 base abstraction layer In this case, I have implement-
data-ed this project in ADOdb, but you easily could tute PEAR::DB, Eclipse, DBX or whatever else is yourfavorite db abstraction layer (or even code using the
substi-Figure 1
Trang 12native 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 applicationflow is directed bythe Controller,which coinciden-tally is the middle
of our applicationstack This project
is implemented inPhrame, but youcould substituteany of the otherprojects men-tioned in the priorarticle, or roll yourown 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 businesslogic in the database, this is a good starting point forthe section on models There are some preliminaryitems I should cover First of all, the database this wasimplemented on is PostgreSQL 7.3 I created a usernamed 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 haveminimal rights, and will be the user accessing our datafrom PHP Two groups were created, links_admin andlinks_user Groups are the Postgres equivalent of roles,and are a convenient way to assign rights to groups ofusers It is a good database programming habit toalways implement your security through roles
Let’s start with the tables In our application, wewant to track links, and have them organized intogroups In a normalized database design, this impliesthat we need two tables, one for the links and one forthe groups they belong two This first table is for thelink groups
For readers who are not familiar with the Postgressyntax, there are a few nuances to pay attention tohere First of all the link_group_id field is declared astype serial with a constraint of PRIMARY KEY The seri-
al type is a shortcut for creating a sequence in the base, and selecting the next value from the sequence asthe default to populate the field when performing andinsert operation The PRIMARY KEY constraint enforcesthat the field must be unique and not null The next
data-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.sqllink_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 immulti-plement 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 abwith-out corrupting
the data model with our SQL
statements.”
Trang 13item 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 funcconsidera-tion 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 mation regarding link_groups
infor-Here we have now granted SELECT rights tolinks_user, so this view is available to query in our PHPscripts This view also provides some summary infor-mation regarding links associated with each link group
by doing a LEFT JOIN (selecting all link_groups, andlinks where they match) and using aggregate functionslike count() and max()
The requirements we reviewed earlier specified ing fields to capture timestamps for both the creationand the last update times for each row We saw howthe DEFAULT CURRENT_TIMESTAMP constraint could
hav-be used to automatically populate the date_crtd field,but how can you have the database automaticallyupdate the date_last_chngd field where rows areupdated? The answer is to use a database trigger
In Postgres, the creation of a trigger involves twosteps: creating a function, and setting the trigger to usethe function In this case, the trig_upd_dates() functionchanges the value of the date_last_chngd field to bethe current timestamp (the result of the now() func-tion) in the row to be updated The CREATE TRIGGERstatement then implements the function for each rowthat is updated
DROP FUNCTION trig_upd_dates() CASCADE;
CREATE FUNCTION trig_upd_dates() RETURNS TRIGGER
AS ‘BEGIN new.date_last_chngd := now();
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;
Trang 14Now 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 isplpgsql This procedural SQL language is distributedwith 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 (Postgressupports function overloading-multiple functions withthe same name but different input parameters-butdon’t worry, I didn’t use any), in this case, the functionaccepts two integer values as parameters
In the DECLARE section, we can create local variables
we want to use (Postgres is statically typed, so unlikePHP, you must declare a variable and its type prior touse) We can also create more useful names for theinput parameters than “$1” as evidenced by the use ofALIAS By looking at the DECLARE section we can seethat the first integer parameter is the id of the link wewant to change, and the second is the id of the group
we want to change the link to
The rest of the tion is in the statementdelimited by BEGIN andEND The first step inour function is to vali-date the link requested
func-to change actuallyexists We perform thisstep by attempting toselect the row from linkwith the user specified
id into the variablelinkrec The next state-ment checks to see if arecord was found If itwas, we move on withthe next step, otherwise, if you look near the bottom ofthe code where the else branch for that check is, weRAISE EXCEPTION with an error message (quotesescaped, as noted above) By raising an EXCEPTION,the sql statement will result in an error and no result setwill be returned We can use this fact to trap for errors
in the PHP code, and this will be covered later in thearticle
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;
IF linkrec.link_group_fk = ch_group_id THEN
RAISE NOTICE ‘’link % is already in group
IF linkrec.link_ord < max_ord THEN
PERFORM ord_link(ch_link_id, max_ord);
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.”
Trang 15exists, 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 vless-ersion 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 Accesslinks
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 ManipulationFunction/Description Parameters
change the sequence of
an existing link group
Integer - link group id Integer - new order sequence
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
Trang 16we 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 procapproach-essing with[sprintf()], or user ADOdb bind variables The lattermethod is shown in the example code and describedbelow
Next we define the Model class itself
Groups::GetInfo() and Groups::Add() are resentative examples of model methods Each uses aglobal ADOdb connection object This database con-nection is established in the application setup file
rep-(links_setup.php) Groups::GetInfo() nextselects the appropriate SQL statement based on how itwas called, and then executes the SQL and stores theresults in a result set object Next, we check for validexecution 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 datawith a constant SQL statement mentioned above
The Links model is similar to the Groups model Iencourage you to review the code bundles
code/mvc/app/models directory for the full PHPscripts
The concept of the User model is essential to standing the security within this application, so thewhole User.php script is presented in Listing 2
under-This Model class again defines some constants to beused in the class definition The User class has threemethods User::IsAdmin(), User::SetAdmin()
and User::ValidateAdmin() The
User::IsAdmin()method checks for whatever ditions we determine qualify a user as an administrator,and returns a boolean value based on the result of thesechecks In this case, I have implemented logic that says
con-an administrator is con-anyone who:
• is browsing from a particular subnet,
• is browsing from the localhost, or
• has passed a cookie to the application withthe name ‘c_links_admin’ and a particularhash value
The User::ValidateAdmin()method makes use
of the IsAdmin()method to check the current user Ifthe user is not an administrator, then we trigger anerror 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 apassword check If the correct password is passed tothis method, it will drop a cookie with the correct nameand value to pass the IsAdmin()method checks Thismethod, 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
$a_bind = array( $psName , $psDesc );
$o_rs = $go_conn -> Execute ( GROUPS_ADD_SQL ,
Trang 17=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, lishes global variables and defines functions used in theapplication The next ‘if’ statement implements the
estab-“default action” discussed above If no action is rently defined, it is explicitly set to “ShowView” Next
cur-we create our global controller Finally, since cur-we arenow always processing an action, we always call the
ActionController::Process()method
One thing I really liked about adding theShowViewAction was the elimination of all the proce-dural code to determine which view to show Thisaction class is covered in more detail in the section ofthe article dealing with Views in Listing 4
Another important piece of code to review is thisapplication’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 constructorfunction is shown above This class uses the defaultoptions from the MappingManager class
We define three forms for the application The linksform is a pure instance of the ActionForm class, and istherefore 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 ing form data, and both the link editing and groupediting pages have a specific extended ActionFormclass devoted to them
process-The first mapping defined is for the default
“ShowView” No forwards are required because thisaction will terminate in the generation of HTML for theclient anyway
The next mapping shows an example of an actionwith 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”, fies APPL_BASE.’groupedit’ as the forward path.
md5 ( 'links application administrator' ));
define ( 'USER_ADMIN_COOKIE' , 'c_links_admin' );
strlen ( USER_LOCAL_HOST )) || ( array_key_exists ( USER_ADMIN_COOKIE ,
$_COOKIE ) && USER_ADMIN_VAL ==
$_COOKIE [ USER_ADMIN_COOKIE ]) ) {
function ValidateAdmin ( $psMsg = 'You have requested
an action reserved for application administrators Access denied.' )
//set default action if none specified
if (! array_key_exists ( _ACTION , $_REQUEST )) { $_REQUEST [ _ACTION ] = 'ShowView' ;
}
//create Phrame controller
$go_controller = new ActionController (
Trang 18These 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.”
Trang 19To 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 overthese inputs in your action, and use a model classupdate method for each of the different groups posted.The Phrame controller will “load” your form class withthe $_REQUEST array It does this using the
ActionForm::PutAll()method This is the methodoverridden in the GroupForm class above, in which aPhrame ArrayList object is created and stored in theGroupFrom class This ArrayList is created in the
PutAll() method and a Phrame ListIterator isretrieved 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
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 ))
$o_group =& new Groups ;
$o_list = $poActionForm -> GetList ();
while ( $o_list -> HasNext ()) {
$a_vals = $o_list -> Next ();
$o_group -> Update ( $a_vals );
Trang 20extract the next value as $a_valsand 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 thearea that has changed the most from the example pre-sented in the previous article I have done significantrefactoring to several iterations of application, andwhat I am presenting here is what I have arrived at as avery workable solution to integrate Smarty intoPhrame In a nutshell, there is a
ShowViewAction::Perform() method (shown inListing 7) initiated for every page a user will view Thisapplication implements the Factory Pattern to retrieve
a specific subclass of a View base class The samemethod creates a Smarty object, initializes the viewwith both Smarty and the Action’s Form object, checkssecurity, and assigns global values for the application.The View::Render() method is then executed toload view specific values and generate output for theuser
The view Factory Pattern, is implemented prettymuch by the book (“Design Patterns” that is) What wewant 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 );
//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
Trang 21ViewFactory::_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 Thesegive the site the common “look and feel” with theheader.tpl handling the site title and errors, and thefooter.tpl handling the timestamp, navigation andsome 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 Ihave 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 ging routines in such a way that they will not take effect
debug-in production This is done debug-in case I forget to removethe debugging code when I migrate my source to theproduction location; I would not have to re-migrate.The second affect I try to achieve is to have reasonablelooking output to work with (in some of my CSS2absolute positioning layouts, a simple echo statement
in the wrong location can get hidden behind other sions)
divi-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 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
Trang 22The 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
( s t r p o s ( $ _ S E R V E R [ ‘ S C R I P T _ F I L E N A M E ’ ] ,
‘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 environdevelop-ment) 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 applicaloca-tion error box) and the informaloca-tion
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 ity to process and then forward
abil-Future Directions
Where can you go from here in modifying this 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)
applica-You might want to extend the groups data model toinclude an image source for a more graphical flair tothe list In this case, you are altering something prettyfundamental to the application, so you would needmake sure you hit all the blocks in the application stackwhere it is affected: alter the link_group table, addimg_src to the add_group and upd_group plpgsqlfunctions, add the column to the groups views so thePHP database user can query the data, the Groups Addand Update methods to handle processing of the newfield, to the Add and Update actions to process andadd to the groupedit.tpl forms so we pass the value.Lastly, add the img tag to the links.tpl file to display forthe user
This might sound like you are altering a significantportion of the system, but remember that your code isnow well organized into compact function orientedblocks: you need to store the data somewhere, youneed 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 forthe user Each of these tasks now has a location withinyour framework, and you can make a modification likethis, which essentially amounts to a new applicationrequirement, without breaking any of the previouslyimplemented functionality and requirements
What else could be altered? You might want to ate a “link popularity” feature, i.e Measuring the num-ber of times users have followed the links How can this
cre-be accomplished? First of all, you can’t link directly tothe sites, because you would have no way of knowingwhen the user clicks on a link Instead, you would cre-ate a “ViewLink” action, that would bump your countfor the link and then redirect the user to the link
You might add an admin mode that would check forbroken links You might add a “Submit a link” form,giving your end users the capability to add links Thisfeature might further require you to change the datamodel 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 woulddefinitely want separate cache ids for regular andadmin users
You might also consider writing unit tests for your code,especially your model classes and the
Action::Perform()methods you have implemented
Trang 23What 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)
When developing your own applications, I hope theapplication stack diagram from this article, the MVCtechnology figure from “An Introduction to MVC UsingPHP”, and the examples provided in these articles willgive you the tools necessary to design and implementyour own MVC web application Happy Coding!
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 jsweat_php@yahoo.com.
Click HERE To Discuss This Article
http://www.phparch.com/discuss/viewforum.php?f=24
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
Visitphplens.comfor more details Free download
Connect with your database
Trang 24It 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
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 mentations, a pure waterfall model would theoreticallyforbid planning or design once the implementationphase has begun This has led many developers todeem the waterfall approach cumbersome and inert formany projects It is fundamentally inflexible Thismakes late change requests and new features general-
imple-ly very difficult to integrate
The solution
Generally speaking, agile approaches are lightweightprocess 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, ing “Scrum”, “Crystal”, and “Extreme Programming”.Extreme Programming, or XP, was introduced by KentBeck and has definitely received the most attention XP
includ-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
Trang 25es, 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 preventthe integration At least we know about them now andcan fix them, rather than finding out about them at theend of the cycle Above all, we learn by integrating theproduct It will not be a single event we are afraid of;
it will be routine We get a good feeling for our cation, and no big surprises await us at the end of theproject
appli-Test-driven development
Now that we’ve introduced some of the patterns used
in XP, the remainder of this article will focus onarguably the most important pattern: test-driven devel-opment
As developers code their modules, they test ly!) Usually, this becomes more debugging than real
(hopeful-testing Using PHP’s echo or die statements manually
takes a lot of time and is really bug hunting, not ing Sure, we may use DBG or the Zend StudioDebugger to lessen the burden, but again this is not
test-really testing Another problem with this “echo or die”
type of manual testing is that we often have to addextra code to our module in order to test it Thus, youchange the module you want to test
An even worse case is that testing would be skippedcompletely Now, integrating these non-tested mod-ules results in that big bang I mentioned earlier Howcan we prevent all of this?
One approach is to apply the “decorator” pattern toprotect our unit (module) code and encase it with thetests “Decorator” is a design pattern discussed byErich Gamma, et al in the landmark Design Patternsbook In the decorator pattern, an object (or unit) isextended with additional functionality Instead of cod-ing the new functionality inside the unit, though, weleave the unit unchanged and add a wrapper around it,which adds the new functionality This approach hasthe advantage that the underlying unit is leftunchanged, basic, and re-usable Only the additionalfunctionality is special for this use case The decoratorcan also add further re-usable modules, such as debug-ging or logging In our case, we’ll decorate our unitwith the test functionality, and refer to this functionali-
ty as “unit testing”
Unit tests are informal functional (black box) testsnormally executed by the developers of code They areoften quite low-level and test the behavior of specialsoftware components such as classes, modules, func-tions, and so on We use unit tests while practicingtest-driven development Test-driven developmentmeans that we code our unit tests first No unit code iswritten before its test
Units are as finely-grained as makes sense We maywrite a unit test for a single method, for a whole mod-ule, or for any other kind of component The smallerthe component is, the better Returning to the PHPtemplating system example, you might write a set of
Trang 26tests for the template engine This “unit” would likely
be much too functionally broad to properly test A
bet-ter unit granularity might be each page component,
such as headers or footers
What advantages does test-driven development
offer? The first benefit is that we must think about the
module before starting to write its code This ensures
self-reflection about the unit, which is sure to improve
quality
Another benefit of test-driven development is timely
bug discovery Developers very rarely deliver bug-free
code The later code defects are discovered, the more
time it will take to find the bug responsible Fixing
bugs at late stages is often costly in terms of time and
effort If we test during or directly after development,
the code is still fresh in the developer’s mind, and
changes are easy to make
Test-driven development also allows us to spend less
time testing This may sound counter-intuitive, but the
extra effort in the beginning pays out in the long-term
Unit tests are generally automated and repeatable,
which is very different from the traditional “echo or die”
approach Repeating an automated unit test many
times is comfortable and fast Doing this manually
would cause much stress, especially when we are under
time constraints - and we are always under time
con-straints!
Another big advantage of test-driven development is
that we do not need to touch our unit code Although
the unit test is generally highly coupled to the unit, the
actual code of the unit and the unit test are separated
We can feel secure about the fact that we are testing
the actual unit, not a modified testing version
Test-driven development may sound uninteresting
and boring, but developers can actually feel challenged
to write sophisticated unit tests for the modules This
could be a satisfying and stimulating experience in
itself Thinking about the test also improves the
mod-ule’s design
The coding of the unit and its test is an iterative
process This iteration happens because it is hard to
anticipate the whole test environment from the
begin-ning The unit and its test code should not be treated
separately The test code is part of the package If you
are afraid of totally developing the test classes before
writing the module, you may start by developing them
in parallel
The units are integrated once all of the necessary unit
tests are passed No integration can start if one unit
test is not passed If this is enforced in the first and
suc-cessive integrations, it will minimize the number of
bugs found during integration Bugs that appear
dur-ing integration can be harder to track down because it
may be difficult to determine where they originated
Whether you write your unit tests from scratch, or use
a framework, test-driven development is a must
Testing frameworks
Once you are sold on the idea of making unit tests, youshould consider adhering to a standard This is espe-cially true among groups of developers In this case,the usage of a testing framework might make sense.But what is a “framework”? Let me (technically) define
a framework as an object model which can be
extend-ed (normally by inheritance) to suit the custom tion’s needs A testing framework provides guidelinesand best practices in order to write and run testssmoothly Writing tests is easier because the generalsoftware infrastructure is already available.Testing frameworks have the following benefits:
applica-• consistency: within a framework, all unittests generally work the same way
• maintainance: frameworks should be more
or less bug-free and supported
• break-in time: frameworks usually enablenew developers to get up to speed quickly
• automation: frameworks are usually able torun tests automatically
PHPUnit
The framework we’ll use to demonstrate unit testing iscalled PHPUnit, and is part of the PEAR project.PHPUnit is an instance of XUnit, which is a generalframework enabling module authors to write repeat-able tests for their modules Kent Beck, XUnit’s creator,defined a basic approach for unit testing, including fourbasic patterns
The first pattern is the creation of a common “test ture” A test fixture is a configuration for the test Itdoes the setup and teardown of any entities (variables,temporary databases, etc) needed to perform our test.This is like preparing a sandbox for our test to play in,then raking it over again when we’re finished The sec-ond pattern is creating a “test case” A test case stim-ulates a fixture in some predictable way The third pat-tern, the “check”, tests for these predicted results Ourtest cases are aggregated into the fourth pattern, which
fix-is the “test suite” The test suite contains a set of testcases that are all run together
Let’s look more deeply at PHPUnit, and how you canuse it in your applications
Installation
PHPUnit’s source code and documentation can be found at the PEAR website (http://pear.php.net/package-info.php?pacid=38).Let’s assume an Apache and PHP configuration withPHP 4.3 or higher By using PHP 4.3+ we benefit fromthe fact that PEAR is a stable part of the official PHP dis-tribution, and we may also use the PEAR installer
To retrieve and install this package, we browse to thePEAR installer executable (called “pear”) in our file sys-
Trang 27tem (if it is not already part of our PATH) This could
be, for example, under /usr/local/lib/bin beside the PHP
executable If we call the PEAR installer like so:
pear install PHPUnit
we should fetch and install the PHPUnit package (see
Listing 1)
The example
In order to illustrate the features and use of PHPUnit
let’s go over a simple example We have to generate a
complex ASCII file automatically, and we know the
con-tent it must have
A good test for the success of this operation might be
to compare the generated file against a template file or
string This can easily be done using PHPUnit In our
simple case the file (and the template) consists of one
short string
Since we are practicing test-driven development, the
first step is to write the test class Our example test
class is shown in Listing 2 What does the script do?
First, it includes the PHPUnit PEAR package Then it
defines the test class CompareTest, which extends
PHPUnit_TestCase PHPUnit_TestCase is a fundamental
PHPUnit class that provides us with testing
functionali-ty It contains methods for running the test, for
build-ing the result object, and abstract methods for settbuild-ing
up and tearing down the fixture In PHPUnit a
“test case” is a box consisting of tests sharing the same
fixture.
So what does our subclass do? The constructor
(CompareTest()) defines the new PHPUnit test case
The setUp() method does some setup work for ourfixture All test methods should use our fixture If amethod does not use our fixture, it probably doesn’tbelong in this class Our fixture simply reads our tem-plate file into a member variable Our next method iscalled after the test methods are executed Thismethod is called tearDown(), and simply unsets our
fixture variable.
Next, the test methods follow Each method whosename begins with “test” is a test method In our case
we have two methods, testCompare() and
Note: You may need root access on your machine
to install PEAR packages If you receive an error
about PEAR_CONFIG_SYSCONFDIR, simply run the
6 function CompareTest ( $name ) {
7 $this -> PHPUnit_TestCase ( $name );
8 }
9
10 function setUp () {
11 $file = "template.txt" ;
12 $handle = fopen ( $file , "r" );
13 $template = fread ( $handle , filesize ( $file ));
14 $this -> template = trim ( $template );
29 $this -> con = "1234-4321-whatever" ;
30 $this -> assertEquals ( $this -> con ,
installed file /usr/local/lib/php///PHPUnit.php
installed file /usr/local/lib/php/PHPUnit/GUI/HTML.php
installed file /usr/local/lib/php/PHPUnit/GUI/HTML.tpl
installed file /usr/local/lib/php/PHPUnit/GUI/SetupDecorator.php
installed file /usr/local/lib/php/PHPUnit/Assert.php
installed file /usr/local/lib/php/PHPUnit/RepeatedTest.php
installed file /usr/local/lib/php/PHPUnit/TestCase.php
installed file /usr/local/lib/php/PHPUnit/TestDecorator.php
installed file /usr/local/lib/php/PHPUnit/TestFailure.php
installed file /usr/local/lib/php/PHPUnit/TestListener.php
installed file /usr/local/lib/php/PHPUnit/TestResult.php
installed file /usr/local/lib/php/PHPUnit/TestSuite.php
install ok
Listing 1
Trang 28testConcatenate() The first one checks if the
gen-erated file is correct, and the second one verifies a
con-catenation It is important to understand that all test
methods should be independent of the others This
means that we can execute each test method
separate-ly from the others
Let’s look at testCompare() a little closer In the
interests of brevity, $this->generatedsimulates the
output of our complex generation process The
method assertTrue() is an assertion provided by
PHPUnit It simply verifies that a given boolean
condi-tion delivers TRUE Other assercondi-tions provided by
PHPUnit are shown in Listing 3 assertFalse()
checks a given boolean condition to be FALSE
assertNull() ensures a variable is NULL, and
assertNotNull() does the opposite
assertEquals() checks that a variable is equal to
another value assertSame() ensures a variable is
pointing to an expected object assertRegExp()
checks that the value of a given variable is matched by
a regular expression
But what happens with our test now? We need
another script to use the test class Listing 4 shows how
triggering our test might look A PHPUnit_TestSuite
object is created The PHPUnit_TestSuite class is a
con-tainer for grouping different test cases into one logical
unit and providing access to the test results Once the
object is created, we add the test to it explicitly Now
we can run the test While the tests are running, the
results are put into a PHPUnit_TestResult object, which
we display as a string The PHPUnit_TestResult classcontains the basic functionality to store test successesand test failures, and to provide access to these results
We run our test and receive
TestCase comparetest->testCompare() passed
Great!
We also want to check if the concatenation is equal
to our template value We need to add this secondmethod to our test suite Adding the second test man-ually could be done with
$suite->addTest(new
CompareTest(‘testConcatenate’));
Applying several different test methods through thismanual approach could quickly become tedious Let’stry it a different way Take a look at Listing 5 If we cre-ate a PHPUnit_TestSuite instance while passing a testcase class name, PHPUnit will automatically collect alltest methods available and execute them In our casethe name of the test case class is CompareTest Wehave also specifed HTML output rather than string,which makes the output more browser friendly Now,when we call the script, we’ll see this output (displayed
in the browser):
TestCase comparetest->testcompare() passed TestCase comparetest->testconcatenate() failed: expected 1234-4321-whatever, actual 1234-4321whatever
6 $result = PHPUnit :: run ( $suite );
7 echo $result -> toHtml ();
4 $suite = new PHPUnit_TestSuite ();
5 $suite -> addTest (new CompareTest ( 'testCompare' ));
6
7 $result = PHPUnit :: run ( $suite );
8 echo $result -> toString ();
9 ?>
Listing 4
Function Description
assertTrue( $condition, $message) verifies if the boolean $condition is TRUE, otherwise throwing$message
assertFalse ($condition, $message) verifies if the boolean $condition is FALSE
assertNotNull ($object, $message) verifies that the $object is not NULL
assertNull ($object, $message) verifies that the $object is NULL
assertEquals ($expected, $delivered, $message, $delta) verifies that the expected value is equal to the delivered value.
assertSame ($expected, $delivered, $message) verifies if $expected is pointing to $deliverd
assertRegExp ($expected, $delivered, $message) verifies if $delivered matches the regular expression $expected
Listing 3
Trang 29Oops, what is that? We received an error message
concerning our second test method The two strings
are not equal If we look into the code (from Listing 2)
we can see that we added an extra dash The changed
line now looks like this:
$this->con = “1234-4321whatever”;
Okay, let’s try it again We call the test suite testing
both test methods, and we get:
TestCase comparetest->testcompare() passed
TestCase comparetest->testconcatenate() passed
Our test suite finished successfully!
Further considerations
If we decide to refactor our application in the future, we
can easily perform a regression test Refactoring and
regression are also major patterns in the XP world
Refactoring describes changing (and/or extending) the
design of existing code without changing its
function-ality On the outside, the refactored module still has
the same functionality, but inside the code may be
completely reorganized and (hopefully) better
designed
A regression test is a way to ensure that the old
func-tionality of a module or application is still the same, and
is still available without errors after a new version is
released Over time, we can build up a collection of
unit tests which ensures the bug-free functionality of
our software, regardless of the changes that are made
to it
We saw that a group of test cases can be bundled
into a test suite This makes it very easy to set up a
large suite of tests (tens, hundreds, thousands, or
more), and enable a fast regression check This can also
allow us to examine interdependencies between units
very quickly If we change or extend a module and run
a regression test, we may detect a bug in our software
exposed by our changes Perhaps we didn’t expect this
new bug in a seemingly unrelated part of our
applica-tion This allows us to better understand how the
soft-ware works, which is especially valuable when you
did-n’t write it
Conclusion
Both traditional and agile approaches have their
advan-tages On the one hand, a waterfall model offers a
more traditional and structured approach On the
other hand, agile approaches provide flexibility and a
better “time-to-customer” value In this article I’ve
introduced some best practices of XP, and how PHP
development may profit from them Agile processes
exist in every part of the software lifecycle Therefore,
the parts I’ve introduced are only a small sampling of
the plethora of available agile tools
Agile processes provide many patterns which mayhelp improve our development process, but they donot claim to have invented something new They sim-ply identify best practices and try to maximize theirpotential Many of these methods you might alreadyknow under another name or already do implicitly.Agile processes are really just a pool of best practiceswhich can be used and combined where necessary anduseful You do not have to switch the total process to
XP or Scrum, but upon detecting weak points youmight do well to consider using some agile methods Test-driven development is one of those agile meth-ods that fits almost any situation and environment Ifyou do not want to go all out with PEAR PHPUnit orother alternatives, you should still consider developingsome easy test cases yourself My experience is that theuse of these methods ensures the efficient development
of (nearly) bug free applications Who can resist that?
During the last years Michael designed and developed B2C web tions on different platforms Now he is busy with enterprise application development His email is michael@huettermann.net.
applica-Click HERE To Discuss This Article
http://c2.com/cgi/wiki?TestingFramework
http://www.xprogramming.com/testfram.htm
Trang 30Ignoring the counsel of friends, family and loved ones,
and turning a blind eye to the continuous threats of
physical, mental and emotional violence by the
pub-lishers of this fine magazine, I am back to spread more
joy and wisdom
I'm joking of course… I don't have any friends!
Okay, all kidding aside I would like to start this article
off with a proclamation:
SELECT foo FROM bar WHERE foobar LIKE ‘%bla%’
is stupid and useless.
What am I talking about? I'm talking about rolling our
own search engine (which, for me, is always an after
thought - "oh yeah, we need a way to search this
stuff") How many times have you, in an attempt to just
get a "search engine" implemented, hacked up some
variant of the above SQL statement, knowing full well
that it would never satisfy the requirements you have
for a search feature Writing search engine functionality
is not easy, as you are dealing with a lot of ambiguities
and also dealing almost exclusively with human
(free-form) input A lot of care and attention should be taken
when developing search functionality, as the process of
searching textual information is inexact and fraught
with pitfalls such as:
•Determining what is searched
•How to search through large volumes of information
•How to deal with search terms
•How to optimize a search
•How to rank results Just dealing with these core problems can (anddoes… take it from me, I've gone through this) balloonthe scope of a search feature into an all-consumingproject unto itself Now, I don't know of too many pro-fessional developers who are big fans of re-inventingthe wheel, so let me introduce you to Lucene(http://jakarta.apache.org/lucene/docs/index.html)
Hello, Lucene
Lucene is a member of the Apache Jakarta Project.Jakarta is an umbrella project that is host to the ApacheFoundation's Java projects (such as Ant and Tomcat)
By Dave Palmer
Tired of writing a new search engine every time you start
a web project? Lucene, the open-source Java search
engine API, and PHP may be the solution you've been
Trang 31Doug Cutting originally developed Lucene in his spare
time during 1997 and 1998, and is now assisted by a
whole team of volunteers Lucene is an open-source
indexing and search engine API It's written in Java and
boasts a rather rich set of features, including support
for indexing of static files as well as database queries
With Lucene, one can index any type of static file,
assuming that your indexing application is capable of
parsing the file's content
I know you are all scratching your heads and thinking
to yourself "hey, isn't this a magazine about PHP?" Of
course it is, but there's nothing more powerful than a
PHP application supported in the middle tier or
back-end by Java With this article I will show you how you
can implement Lucene with your PHP applications and
provide your end-users with a feature rich search
engine I'll also show you how to do this all in a
frac-tion of the time it would've taken to build something
from scratch
The first thing to establish is that Lucene is NOT an
"out-of-the-box" product (such as Verity) Lucene is an
API This means that the thing you download provides
your "client" application with an interface into Lucene's
internal workings It is up to the developer to actually
implement the two main components required in order
to leverage Lucene's capabilities: the indexer and the
searcher
The indexer is the component that is responsible for
creating the index, or catalog of things that will be
searched When a search is performed, it's never
per-formed against the actual documents or database
objects, but against the index of those things needing
to be searched This not only vastly improves
perform-ance of the search, but it also decouples what is
searched from the actual targets of a search The
index-ing "engine" (as it is commonly known) contains a
series of calls to Lucene's Document object where
"ref-erences" to your data (whether it's a query row or a text
file) are stored A repository on the file system is
creat-ed, representing the index Lucene's Document object
is best thought of as a container It's an object that
rep-resents an "entity" in the Lucene universe Every item
that is indexed "resides" in a Document object
The searcher is the actual search engine or the
"thing" that accepts user input (via a search phrase),
and performs a search on a specified index The search
engine is the front-end component that parses the
search phrase, performs the query, and returns the
results back to the search interface
If you were to be brave (or foolish) enough to
actual-ly try to write the searcher and indexer from scratch,
you would embark on a journey fraught with peril The
functions required to do this work are numerous, and
an amazing amount of testing is required in order to
deploy these components Lucene fills this niche rather
nicely Yes, you should have some experience with
Java, as Lucene's API is written in Java, but don't let that
deter you from getting to know (and love) Lucene.Hopefully with the help of this article you'll just be able
to take my hacking and make it your own!
Before we go any further, let's review the ment and other requirements you'll need to satisfy inorder to use Lucene on your server You'll need PHP 4.2
environ-or greater with Java suppenviron-ort This suppenviron-ort can be either
compiled in (using the with-java=DIR directive
when running 'configure' on Linux), or the php_java.dllenabled on Windows In addition to enabling the JavaPHP extension, you'll also have to make configurationchanges in your php.ini file (configuring Java support
under the Java directive) You will also, of course, need the Lucene JAR file
(http://jakarta.apache.org/builds/jakarta-lucene/release/v1.2).
Lucene comes packaged as Java JAR file which you ply need to download, and store some place on yourlocal file system Once you have the JAR file saved, youneed to add its location to the java.classpath setting inyour php.ini file
sim-Essentially what we will be developing is an indexingand search engine using Lucene by coding two Javaclasses: an Indexer class and a Searcher class The goalhere is to give you, the intrepid reader, the basic knowl-edge of how one may integrate Lucene into any appli-cation that requires a search engine For the sake ofthis example I created a fictional "links" database whereURL's are stored Please see Listing 1 for the SQL cre-ation script
The indexer
The first component we will implement will be theindexer This is the piece that you will use to index thecontent you wish to make available to your searchengine Lucene's indexing API makes this rather simple,but you need to have a basic understanding of Lucene'smethods and the context in which they are used Inour example, the IndexEngine (see Listing 2) is a Javaclass that runs on the command line Use the includedbatch files (for Windows) or shell scripts (for *nix) tocompile the classes, and run the Indexer on the com-mand line
There are several things you need to consider prior
# Host: obione
# Database: PHP_Articles
# Table: 'links'
# CREATE TABLE `links` (
`lid` int(11) NOT NULL auto_increment,
`name` varchar(50) NOT NULL default '',
`url` varchar(50) NOT NULL default '',
`description` varchar(100) NOT NULL default '', PRIMARY KEY (`lid`)
) TYPE=MyISAM;
Listing 1
Trang 3219 public static void main(String[] args) throws Exception {
20 System.out.println("Preparing to index links database ");
27 String sql = "select lid,name,url,description from links";
28 String indexPath = "/path/to/index/file";
29
30 Analyzer analyzer = new StandardAnalyzer();
31 IndexWriter writer = new IndexWriter(indexPath,analyzer,true);
67 String url = "jdbc:mysql://db_host/db_name";
68 String user = "db_username";
69 String pass = "db_password";
70 System.out.println("Preparing connection with URL: " + url);
71 System.out.println("database user: " + user);
72 return DriverManager.getConnection(url, user, pass);
73 }
74 }
Listing 2
Trang 33creating an index Things like what "fields" you want to
include in your index, what "media" you are indexing
(are you needing to index a database query, text files,
PDF's, MS Word documents, etc.) and finally, how do
you want each "field" to be indexed Lucene provides
several ways to index fields you specify For example,
you may decide to index a field, but not actually store
the content with your index in order to control the size
of your index
One of the goals of creating an index is to keep your
index lean and mean and efficient This means not
overburdening it with too much data It's really a
bal-ancing act and not one solution fits all Determining
what should be indexed and the fields you should be
including in your index may be a bit of the old
trial-and-error
For the sake of simplicity, this example will index
con-tent living in a MySQL database This will show you the
power of Lucene, and how you can truly optimize the
querying of your databases through Lucene
Let's take a brief look at how the indexer works It
first creates a JDBC connection to the database, and
executes a simple query:
SELECT lid, name, url, description FROM links
This says I want all links and all columns present in my
result set
With this line:
we create a new ResultSet object In Java parlance a
ResultSet is very much like an array of rows
represent-ing the successful execution of a database query What
we need to do is loop through each element of the
ResultSet and create a new Lucene Document object
The Document object, if you remember, is the primary
object that is used to represent a single item of content
that will be included in the index we are creating To
this Document object I will add the columns in the
ResultSet
It is important to note that Lucene affords the
devel-oper a lot of flexibility in how columns are indexed For
example, you may opt to use Lucene's "UnStored"
method of indexing a column This means that the
col-umn will be included in the index, but its actual
con-tent is not stored in the index This means you can
index large amounts of content without actually storing
any of that content in your index
You'll notice these lines in the ResultSet loop:
This is the important bit in creating an index Both
lines here basically say we want to store the given umn from the database in the index The "Field" inter-face in Lucene has several methods to designate how acolumn will be indexed The method "Text" tells Lucenethat the column is to be indexed and stored in theindex
col-Now, let's say our "description" column is meant tohold a large amount of textual data If this is the case,
we might not want to store that data twice (once in ouractual database where it lives, then a second time inour actual index) With Lucene we don't have to storethe actual contents of our column with the index Wecould have said this:
The last thing we need to do in our ResultSet loop isactually add the Document object we created to what
is called a "writer." The "writer" is the object in Lucenethat generates the index on the file system
One of the stronger benefits of Lucene's indexingcapabilities is that you do not need to destroy or
"purge" the index in order to re-index (Verity, for ple, requires this) Simply run the index engine again,and the index is updated
exam-The Searcher
Now that we have an index generated, it sure would benice to be able to search this index! This is where PHPcomes in, with Java's helping hand If you know me,and I'm sure 100 percent of those reading this don't,you know that I like to decouple any application codefrom the web tier that is not specifically responsible fordisplay In keeping with this multi-tiered approach we'llconstruct a small Java class that will serve as oursearcher object (the thing that queries the index) PHPwill be used to display the search form, and display theresults
Take a look at Listing 3 Once our PHP frontendhands over the keywords from our search interface, the
d add ( Field UnStored ( "description" ,
r getString ( "description" )));
d add ( Field Text ( "name" , rs getString ( "name" )));
d add ( Field UnStored ( "description" ,
Trang 3417 /**
18 * @author Dave Palmer <dave@engineworks.org>
19 */
20 public class SearchEngine {
21 protected IndexSearcher searcher = null;
22 protected Query query = null;
23 protected Hits hits = null;
32 if (index == null || index.equals(""))
33 throw new Exception ("Index cannot be null or empty!");
34 if (matchType == null || matchType.equals(""))
35 throw new Exception ("matchType cannot be null or empty!");
36 if (queryString == null || queryString.equals(""))
37 throw new Exception ("query string cannot be null or empty!");
43 StringBuffer qStr = new StringBuffer();
44 qStr.append("name:\"" + queryString.trim() + "\" "+matchType+" ");
45 qStr.append("url:\"" + queryString.trim() + "\" "+matchType+" ");
55 Hashtable results = new Hashtable();
56 Hashtable metaData = new Hashtable();
57 metaData.put("hits", new Integer(count).toString());
58 metaData.put("query", queryString);
59
60 results.put("meta_data", metaData);
61 Vector rows = new Vector();
62 for (int i = 0; i < count; i++) {
63 Document doc = hits.doc(i);
77 WddxSerializer ws = new WddxSerializer();
78 java.io.StringWriter sw = new java.io.StringWriter();
79 ws.serialize(results, sw);
80 return sw.toString();
81 }
82 }
83 catch (Exception ex){
84 throw new Exception ("SearchEngine.search >> exception: "+ex.toString());
Trang 35first thing we do is build our search string Lucene's
syn-tax is a bit on the complex side, but with that
complex-ity we are provided with a query parser that is very
powerful For the sake of this example we'll just use
something simple and understandable
You'll notice these lines in our Searcher class:
Here we build the query string As part of good Java
programming, we never concatenate strings, and
instead, build a StringBuffer object The idea here is to
specify what columns we want to search, coupled with
our query term (keywords) and the type of match
(AND exact, OR loose) Yes, there are lots of more
complex ways to build a query string, but this is a good
way to get your feet wet The basic syntax is as follows:
[index column name]:"[search phrase]" [match type]
We can append as many columns to this query as we
need in order to broaden our search
Because we are dealing with human readable strings,
and computers obviously aren't human, we need to be
able to translate our human-readable search string into
something a computer can deal with One such
method is to "tokenize" a string Tokens are individual
elements of a string such as a word, a space, or a
char-acter, etc In the land of Lucene, we use things called
"Analyzers" to tokenize our search string
There are several prepackaged analyzers that come
with Lucene These analyzers can be used to tokenize
the query string in different ways in order to satisfy
dif-ferent types of searches For the sake of simplicity I use
the StopAnalyzer The StopAnalyzer is useful for filtering
out "stop" words (words typically not very useful for
searching) The StopAnalyzer also implements the
LetterTokenizer, which tokenizes words on non-letter
characters as well as normalizing text to lower-case
So, back in Listing 3, we have our Analyzer that
breaks up our strings into computer-readable
frag-ments, which is then fed into our Query Parser along
with our actual query string Once we have created
our query parser object, we can execute our search
using this line:
The "Searcher" object has a method called "search"
which accepts a query parser object as a parameter and
returns a "Hits" object The "Hits" object contains therecords found for this query We can use the "length"method on our Hits object to decide if we should pro-ceed on to the next step: building a result object togive back to our PHP client
Assuming we have results to work with, we'll just goright to the interesting bit Because PHP and Java can'treally share complex data types, we need to use WDDXserialization WDDX is a universal XML markup sub-language that enables disparate programming lan-guages to share complex data structures over different platforms - find out more at
http://www.openwddx.org The result object that we'll pass back to PHP containsrows (Vectors) of associative arrays (Hashtables) EachVector holds a search result, with the result's fields inthe associated Hashtable We also create a "meta data"Hashtable that will contain our hit count and the query
we were given
Here's an illustration of what this object may look like:
Search Results Hashtable Meta-data : Hashtable Query: foo bar Hits: 3
Rows : Vector Row 1 : Hashtable
URL: http://foobar.com lid: 100
name: foo bar description: this is foo and bar Score: 0.990000
Row 2 : Hashtable
URL: http://bla.com lid: 101
name: Bla dot com description: this is bla Score: 0.980000
Row 3 : Hashtable
URL: http://more.foo.com lid: 102
name: More Foo description: this is a lot of foo Score: 0.850000
hits = searcher search ( query );
StringBuffer qStr = new StringBuffer ();
qStr append ( "name:\"" + queryString trim () +
Java.”
Trang 36In order to build our result object, we loop through
our Hits object The Hits object is indexed so we can
just pull out a Lucene Document object by using our
loop index Once we have a Lucene Document, we can
then pull out the interesting bits for our result object
As we said before, Lucene uses its Document object to
represent content, whether its content being indexed,
or content being returned from a search Lucene's
Document object, for the sake of oversimplifying it, is
really like an associative array It contains "keys" with
"values." The keys represent the columns you included
in your index, and the value represents that actual tent So, in our index, we created a column in our indexcalled "name" In our Document object we would have
con-a key ncon-ame ccon-alled "ncon-ame" con-and its vcon-alue would be thename of our link
Once our object is complete, we serialize it.Serializing simply means to convert a data structureinto something that can be transported from one plat-form to the next The end result of this serialization will
8 This is a sample front-end for a Lucene search engine implementation
9 This PHP front-end instantiates a Java object, then executes the
10 <code>search()</code> method and returns a WDDX packet which can
11 then be deserialized by PHP and display a search results page
12 <p/>
13
14 <?php
15 if (isset ( $_POST [ "keywords" ])) {
16 $query = $_POST [ "keywords" ];
17 $index = "/path/to/index/file" ;
18 $obj = new Java ( "org.ew.lucene.SearchEngine" );
19 $result = $obj -> search ( $index , "OR" , $query );
29 foreach ( $rows as $row ) {
30 $score = $row [ "score" ];
31 $score = round ( $score * 100 );
32 $lid = $row [ "lid" ];
33 $url = $row [ "url" ];
34 $name = $row [ "name" ];
35 $desc = $row [ "description" ];
45 <form action="index.php" method="POST">
46 <b>Search Links Table:</b>
47 <p/>
48 Keywords: <input type="text" name="keywords" size="30">
49 <input type="submit" value="search">
Trang 37be a WDDX "packet" A WDDX packet is simply an
XML representation of our data structure (an object in
this case) This way, we simply pass a string back to
PHP
Displaying the results
Now that our PHP script has a WDDX packet
contain-ing our search results, we can work on their display
If you look at Listing 4, the code is pretty straight
for-ward We create a new Java object so
that we can actually execute the
search The Java object we create is
an instance of our SearchEngine class
(Listing 3) We then call our "search"
method passing in three parameters:
index, match type, and search
phrase
The "index" parameter contains
the full path to where we store the
index we created earlier using the
IndexEngine, and enables us to pass
in the index we want searched This
makes it handy if we keep several
indices The "match type" contains
either "AND" or "OR" Finally, the "search phrase"
parameter contains the search phrase entered by the
user
You'll notice that when we call our search method:
we are passed back a WDDX packet Before we can use
our search results, we need to deserialize the WDDX
packet into a native PHP data structure, like so:
Now that we have our native PHP structure, we can
display our search results to our user! This is just a
matter of looping over the rows, and displaying each
one
You'll also notice we deal with the "score" column
Lucene's relevance scoring is presented as a floating
point number that can be multiplied by 100 in order to
get a percentage Not a bad way to rank the relevance
of each row
Wrapping up
Well, not too bad, eh? Lucene enables us lazy ers to implement a very powerful search engine and rel-
develop-egate the "SELECT * FROM foo WHERE bar LIKE
'%foobar%'" statement into soon-to-be-forgotten tory
his-Lucene's clean API, powerfulquery parsing, and flexibility meansthat creating a search engine nolonger needs to be a project by itselfand can be easily integrated intojust about any application This arti-cle has really only scratched the sur-face of Lucene's capabilities, butperhaps that's one of Lucene'sstrengths You don't need to knowevery feature, every nook and cran-
ny in order to use it in a real tion I've used other products likeVerity, and have become fed up withthe limited functionality WithLucene there are no limitations and no compromisesmade You index your data the way you want itindexed, and you display the results the way you needthem displayed No more constraints and no moreridiculous licensing entanglements
applica-Lucene web site
http://jakarta.apache.org/lucene/docs/whoweare.html
foreach ( $rows as $row ) {
$score = $row [ "score" ];
$score = round ( $score * 100 );
$lid = $row [ "lid" ];
$url = $row [ "url" ];
$name = $row [ "name" ];
$desc = $row [ "description" ];
$result = $obj -> search ( $index , "OR" , $query );
Dave is a professional geek specializing in java/j2ee, php (naturally), and perl development which is just a cover for his real passion for spend- ing large sums of money on home recording and musical equipment and generally making a nuisance of himself it should also be noted that his / karma is currently "positive" which will surely fall.
Click HERE To Discuss This Article
http://www.phparch.com/discuss/viewforum.php?f=26
“Because PHP and Java can't really share complex data types, we need to use WDDX serializa-
tion.”