1. Trang chủ
  2. » Công Nghệ Thông Tin

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

74 424 0

Đang tải... (xem toàn văn)

Tài liệu hạn chế xem trước, để xem đầy đủ mời bạn chọn Tải xuống

THÔNG TIN TÀI LIỆU

Thông tin cơ bản

Tiêu đề Agile Software Development With PHPUnit
Trường học PHP Architect
Chuyên ngành Software Development
Thể loại Báo cáo
Năm xuất bản 2003
Định dạng
Số trang 74
Dung lượng 1,79 MB

Các công cụ chuyển đổi và chỉnh sửa cho tài liệu này

Nội dung

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 1

The 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 2

This is the LAST month to submit your project for approval.

The deadline is June 18th! Hurry and submit your proposal.

As PHP’s importance grows on the IT

scene—something that is happening every

day—it’s clear that its true capabilities go

well beyond what it’s being used for

today The PHP platform itself has a lot of

potential as a general-purpose language,

and not just a scripting tool; just its basic

extensions, even discounting repositories

like PEAR and PECL, provide a

high-quality array of functionality that most of

its commercial competitors can’t afford

without expensive external components.

At php|a, we’ve always felt that our

mis-sion is not limited to try our best to

pro-vide the PHP community with a

publica-tion of the highest possible quality We

think that our role is also that of

reinvest-ing in the community that we serve in a

way that leads to tangible results.

To that end, this month we’re launching

the php|architect Grant Program, a new

initiative that will see us award two

$1,000 (US) grants to PHP-related

proj-ects at the end of June.

Participating to the program is easy We

invite all the leaders of PHP projects to register with our website at

http://www.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 4

Visit www.zend.com

for evaluation version and ROI calculator

Zend Performance Suite

Reliable Performance Management for PHP

Serve More.

With Less.

Trang 5

Graphics & 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 6

counted 16 books devoted solely to the

use of PHP with MySQL as a data source!

Here is the very real truth: MySQL is

NOT the only data source PHP is

capa-ble of working with

There are scores of developers who

need to know this Pass it on Quite

hon-estly, I’m tired of downloading

applica-tions from Freshmeat and elsewhere

which require MySQL, only to find that

they also contain PHP code to implement

some feature that should be offloaded to

(or better, built into) the database! This

extra code introduces bugs, makes it

more difficult to maintain, and inevitably

slows down the application, and in the

process makes PHP look unnecessarily

slow and bulky

Note that this is not a rant targeted at

people who are using MySQL because

they have properly evaluated their needs

and found it to be the best tool for the

job Nor is it aimed at newbies using

MySQL as an introduction to the world

of data-driven development I’m simply

trying to enlighten some poor souls who

might think that MySQL is the only

choice they have when it comes to using

PHP for their development needs.

PHP has native support for Sybase,

Oracle, DB2, Informix, MS SQL Server,

and other databases (yes, there are other

databases) Aside from databases, PHP

has native support for alternative sources

of data such as SNMP agents and LDAP

directories In short, PHP is a very

capa-ble development platform without the

help of MySQL

In this month’s issue of php|architect,

you can capture a glimpse of a couple of

these different data sources in action As promised, I’ve written a coder’s overview

of using PHP with LDAP It’s a very 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 7

MySQL 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 9

This article assumes that you have either read the

afore-mentioned “An Introduction to MVC Using PHP” article,

or that you are already somewhat familiar with the

MVC pattern, OO programming in PHP, and have at

least looked at the Phrame examples The previous

article highlighted proper use of the MVC pattern, with

business logic in the Model classes, presentation logic

in the View classes, and application flow directed by the

Controller classes (ActionController, Action,

ActionForms and ActionForwards in Phrame) Where

the previous article only stored data in the session, this

article steps it up a notch towards the “real world” by

making extensive use of a database

The Application

To give this article a little more “real world” flavor, I

would like to start with a hypothetical set of

require-ments for the application The application is a

manage-ment system for hyperlinks The people who

commis-sioned the application have identified three key sets of

requirements: users, administrators and infrastructure

User:

• The user will access this application as a web

site

• The list of links will be organized into groups

• The main link list will contain all of the links

in the application on a single page, so 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 11

of tricks: Phrame for MVC, ADOdb for database

abstraction and Smarty for templating

The second decision is what the persistent data store

for this application will be I don’t think there would be

much room for disagreement in saying a database is

the most appropriate technology here The project

requirements indicate that referential integrity, triggers,

views and stored procedures will be needed in the

data-base There are a variety of databases that have these

capabilities; Oracle, Microsoft SQL Server, Sybase and

SAPdb, to name just a few To make this article more

accessible to readers, I am going to use the most

pop-ular open source database that supports these features:

PostgreSQL

There is an interesting requirement to have

applica-tions other than this PHP web application be a possible

source and/or consumer of these links This implies

that if we coded the business logic for this application

in PHP, it would have to be reimplemented in

whatev-er the 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 12

native PHP db calls, eliminating the abstraction layer

benefits of long term portability, simplified calling

con-ventions and overall flexibility) The green bar also

denotes the shift in implementing technology from the

database to our scripting language of PHP

Your Model classes make use of the database

Remember from the previous article that only Model

classes should access your persistent data store The

Model classes are also where you can implement data

validation, error handling, and other rules of your

busi-ness logic

The 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 13

item of interest is the date_crtd field with a constraint

of DEFAULT CURRENT_TIMESTAMP This constraint

means that any time a record is inserted, and this field’s

value is not specified, it will instead be created with the

current data and time The last two GRANT statements

designate our security What is most interesting here is

that which is conspicuous by it’s absence: the links_user

group has no rights at all - not even SELECT - to the

link_group table This fact is an important

considera-tion to remember as we address 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 14

Now that we have seen how to view data, and how

the database itself tracks some of our data

require-ments, the question still exists-how do we modify the

data without rights to the table? The answer is to use

functions, and in particular, to take advantage of the

ability to define security for a function that executes as

the person who created the function, rather than the

user of the function We can walk through one

exam-ple of a function that modifies data in the link table:

This one is going to take some explanation, so here

we go! First of all, near the bottom we see LANGUAGE

‘plpgsql’, so the language this function is written in 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 15

exists, the next thing we check is if we are asking to

change the group to an identical value If so, we RAISE

NOTICE, that we were asked to essentially do nothing,

and RETURN 0 Because we raised a NOTICE, a result

set will still be returned as a result of this function call

Assuming the link group we are changing to is not

the same as the existing link’s group, the next step is to

validate that the requested link group we want to

change to exists This is performed in a similar fashion

to checking for the existence of the link If we do not

find the link, it is time to RAISE EXCEPTION again

Since that all checks out, we are almost ready to

update But first, we need to see if this link is the last

link in it’s group If not, we move it to the end (using

another function we have already defined - ord_link)

This is done so that the sequence of links within a

group does not get a gap in it We will also determine

the end position in the new group, so we can position

the link there Then we perform the actual UPDATE

statement, and RETURN 1, indicating success

This may all look like a lot of work - is it really worth

it? Remember this is all part of the requirements for

data integrity in our project Consider this from the

perspective of coding in a PHP script Would you want

to code and run all of the above logic in PHP, or simply

execute SELECT chgrp_link(1,2)?

The last line is SECURITY DEFINER, which specifies the

function should run with the security of linkdbo, rather

than the user executing the function This is what

allows us to log in through PHP as link_user, have no

access to the base tables, and yet still modify the data

The rest of our database API is similarly defined with

functions, and is summarized in Table 1

You can review the scripts in code/mvc/sql for the

implementation of all of the tables, triggers, views,

functions and sample data used in this application

With all that database work out of the way, we can

finally get back to the subject we all know and love:

PHP! Let’s move up the application stack a block or two

and dig into the Model classes, which will actually be

accessing the database code we just developed

The classes that access the database break down

pret-ty easily in this application to two classes-Links and

Groups In addition, we will want to model the user of

our system, primarily for security (to determine if this

particular user is an application administrator) Lastly,

NOTE: This feature was added in the 7.3 release

of PostgreSQL The function should run on a

less-er 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 16

we will have a model for Errors, similar to the previous

article An excerpt from Groups.php can be found in

Listing 1

I like to create constants for the SQL statements I

intend to use in the class To avoid potential name

space conflicts, I generally prefix the constants with the

name of the class (or an abbreviation of the class name

if it is long) The heredoc syntax is used for additional

clarity on the the multi-line SQL statements One item

to consider is how to deal with values that change at

runtime (substituting values like dates or ID fields into

the statement) How can you make a constant flexible

enough to handle this? There are two easy

approach-es I have used: format the constant for 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 18

These are used in the action based on success or failure

of the login action On success, you would forward to

“edit” and allow the administrator to edit the

applica-tion, otherwise, you should just forward the user to the

index page with an error message indicating the failed

login attempt Listing 5 is the actual code for the

LoginAction::Perform() method that executes

what I just described

The rest of the mappings defined are fairly typical of

what I see in most applications developed with this

methodology-Actions are associated with a specific

view in the application, and they generally have just a

single forward that returns the user to the view that

originated the action As a matter of style, I tend to

group all of the mappings associated with a single view

together, as shown for both the group editing and the

link editing actions

The last subject to be covered in relation to the

Controller is the customized form classes The

sim-plest way to make an editing page is to have it perform

“record at a time”, i.e you might go to the

“edit-group” view and pass a parameter of group_id=1 The

“editgroup” view would have all the fields you can

modify on the record available as inputs in a form, and

you would typically have a hidden input with the

group_id of the record being edited Under this style

of application, the user would have to go back to a

list-ing of groups and select another group to edit to make

multiple changes

function LinksMap ()

{

$this -> _SetOptions ();

$this -> _AddForm ( 'links' , 'ActionForm' );

$this -> _AddForm ( 'updgroup' , 'GroupForm' );

$this -> _AddForm ( 'updlinks' , 'LinkForm' );

//default action to show views

// no forwards are required becuase this action displays HTML pages

$this -> _AddMapping ( 'ShowView' , 'ShowViewAction' , APPL_ACTN , 'links' );

//admin login action

$this -> _AddMapping ( 'AdminLogin' , 'LoginAction' , APPL_BASE 'index' , 'links' );

$this -> _AddForward ( 'AdminLogin' , 'index' );

$this -> _AddForward ( 'AdminLogin' , 'edit' , APPL_BASE 'groupedit' );

//group edit actions

$this -> _AddMapping ( 'AddGroup' , 'AddGroupAction' , APPL_BASE 'groupedit' , 'links' );

$this -> _AddForward ( 'AddGroup' , 'edit' );

$this -> _AddMapping ( 'UpdGroup' , 'UpdGroupAction' , APPL_BASE 'groupedit' , 'updgroup' );

$this -> _AddForward ( 'UpdGroup' , 'edit' );

$this -> _AddMapping ( 'OrdGroup' , 'OrdGroupAction' , APPL_BASE 'groupedit' , 'links' );

$this -> _AddForward ( 'OrdGroup' , 'edit' );

$this -> _AddMapping ( 'DelGroup' , 'DelGroupAction' , APPL_BASE 'groupedit' , 'links' );

$this -> _AddForward ( 'DelGroup' , 'edit' );

//link edit actions

$this -> _AddMapping ( 'AddLink' , 'AddLinkAction' , APPL_BASE 'linkedit' , 'links' );

$this -> _AddForward ( 'AddLink' , 'edit' );

$this -> _AddMapping ( 'UpdLink' , 'UpdLinkAction' , APPL_BASE 'linkedit' , 'updlinks' );

$this -> _AddForward ( 'UpdLink' , 'edit' );

$this -> _AddMapping ( 'OrdLink' , 'OrdLinkAction' , APPL_BASE 'linkedit' , 'links' );

$this -> _AddForward ( 'OrdLink' , 'edit' );

$this -> _AddMapping ( 'DelLink' , 'DelLinkAction' , APPL_BASE 'linkedit' , 'links' );

$this -> _AddForward ( 'DelLink' , 'edit' );

}

Listing 4: Action class dealing with views.

function & Perform (& $poActionMapping ,

& $poActionForm ) {

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

if ( User :: SetAdmin ( $s_password )) { $o_action_forward =&

$poActionMapping -> Get ( 'edit' );

} else { $o_action_forward =&

$poActionMapping -> Get ( 'index' );

} return $o_action_forward ; }

Listing 5: Login action.

“Each of these tasks now has a location within your framework, and you can make a modification like this, which essentially amount to a new application requirement, without breaking any of the previously implement-

ed functionality and

require-ments.”

Trang 19

To make things easier for the user, you can

imple-ment “table at a time” editing, which

is what is shown with the GroupForm

(code/app/GroupForm.php) and UpdGroupAction

(code/app/UpdGroupAction.php) classes in

Listing 6

Instead of a single hidden input for group_id, you will

instead make a hidden input array that is populated

with all of the group_id’s for the table as you iterate

over them in the edit view Instead of having an input

like <input type=”text” name=”group_name”

value=”first old group name”>you will instead

code the group_id into the name for all of

the input fields: <input type=”text”

name=”group_name1” value=”first old group

name”> All other inputs will be named similarly

You will want to create an easy way to iterate 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 20

extract 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 21

ViewFactory::_GetViewClass() method This

method takes a single argument the requested view

-and must return a valid view subclass name The

easi-est way to implement this is a case statement, with a

default to your “index” or “main” view The only other

assumption made by the View Factory is that the

sub-class is defined in a file in the views subdirectory, with

the class name and the php extension The

_GetViewClass() method for LinksViewFactory is

shown in Listing 8

How do the view subclasses work? The

View::Init() method take the Smarty object and

the ActionForm object, both by reference, and assigns

them to class vars This is important, especially in the

case of Smarty, because assignments made to the

Smarty object after initialization are still present in the

$this->_moTpl var when used later in the

View::Render() method The Render() method

calls a Prepare() method (where each subclass will

assign view specific data), then handles errors, and

dis-plays the subclasses Smarty template

There are only two things to do for each subclass of

View to make another view for your application: assign

the template to the $_msTemplate var, and

imple-ment a Prepare()method Listing 9 is a sample view

class for the groupedit view

You might want to take a look at how the templates

in this application are organized Each view-specific

template calls {include file=”header.tpl”} as the first statement and {include

file=”footer.tpl”}as the final statement 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 22

The easiest way I have found to achieve these results

is to dynamically determine at runtime if we should be

in debug mode In the links_setup.php script, the

glob-al variable $gb_debug is can be set to

( 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 23

What I have tried to present is the foundation for an

enterprise strength PHP application architecture

Building on the strengths of the MVC design pattern by

implementing Phrame, we have fortified this with a

good database design, database abstraction in the PHP

Model classes, and implemented Views using Smarty

templates Once familiar with this kind of application

architecture, you can deploy effective web applications

by writing rock-solid Model classes, Action::Process(),

View::Prepare() and Smarty templates Deploying MVC

based PHP applications addresses many common

func-tional requirements: robust, flexible, maintainable,

secure

These two articles and the example code provided

have been a whirlwind tour of PHP features, some

cov-ered in depth and others just mentioned or touched on

briefly (or even assumed) Here is a selection of some

of the PHP features, functions and concepts we have

applied in this article and example:

• the MVC design pattern

• practicing separation of business logic,

appli-cation flow and presentation logic

• the Phrame PHP implementation of the

Jakarta Struts MVC controller

• Object Oriented programming in PHP

• creating abstract base classes

• using static methods of classes

• using the PostgreSQL database

• coding in plpgsql, a procedural SQL

lan-guage

• using a database abstraction layer (ADOdb)

• practicing good security habits

• using templates to separate presentation

logic (Smarty)

• writing custom Smarty variable modifiers

• using PHP’s session to store data

• using cookies to store data

• applying the Factory design pattern(ViewFactory)

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 24

It is unfortunate that so many software projects are not

successful There can be many different reasons for this

and, of course, some circumstances cannot be

prevent-ed By relying on the experiences of other people,

however, many common problems in the software

development process can be mitigated

Agile software processes are “best practices” that

have been identified through experience In this article

I want to introduce the agile approach and its benefits

for PHP developers I focus primarily on patterns and

examples from extreme programming, which is one of

the most prevalent agile methods With a little

back-ground, we’ll set out to discuss unit testing in detail

We’ll look at what unit testing is, what the advantages

are, and how to implement unit testing in the PHP

world

The problem

Process models are used to manage software

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 25

es, and iterative development Over several iterative

cycles more and more features are added to the

prod-uct, but even the first iteration contains real

functional-ity Customers are able to run through mini acceptance

tests, and can offer feedback very early on This

incre-mental release cycle prevents misunderstandings, and

keeps projects on the right track

There are a number of best practices that XP

pro-motes Some of these include pair programming,

sim-ple design, continuous integration, and test-driven

development We’ll explain each of these briefly, and

then delve into the last one in depth

Pair programming

XP identified that information exchange between

developers is very important Pair programming is a

very extreme way of achieving this exchange, but it has

a number of advantages One advantage, of course, is

continuous knowledge transfer This knowledge

trans-fer means that other developers are able to fix and

extend code in any module (also known as collective

code ownership) Another advantage of pair

program-ming is sanity checking While one person is coding,

the other is looking at and checking the code being

produced They discuss strategies, have fun, and are

more productive than working alone

Simple design

Another XP practice is to maintain simple designs This

means only implementing the features we currently

want, and only in the easiest way This way, we place

strict focus on the functionality requested by our

cus-tomers, and don’t lose ourselves in trying to anticipate

complex future enhancements Along with this, we

should not try to reinvent the wheel In the case of PHP

web development, for example, it may be simpler and

result in a better quality end product to use the Smarty

template engine or existing PEAR packages, rather than

trying to roll our own templating system

Continuous integration

Let’s assume we are using the waterfall model The

coding begins and proceeds in a more or less

uncoor-dinated manner while developers create their modules

Shortly before final code freeze they are asked: “Are

you finished? Does your code work?” “Sure,” they

answer, “I implemented the template engine here, and

there is the database abstraction Also, the business

logic is complete.” At that time all single modules are

frozen and integrated, resulting in a big bang The

sin-gle modules may work, but the interaction between

them doesn’t And this may happen shortly before

release!

The solution for this is continuous integration We

freeze our code as often as possible, and integrate

Small releases and pieces are more manageable The

best case is that the result of each integration cycle is a

runnable version The worst case is that bugs 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 26

tests 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 27

tem (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 28

testConcatenate() 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 29

Oops, 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 30

Ignoring 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 31

Doug 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 32

19 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 33

creating 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 34

17 /**

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 35

first 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 36

In 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 37

be 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.”

Ngày đăng: 21/12/2013, 12:15

TỪ KHÓA LIÊN QUAN

w