02.06
Cutting-Edge Technologies for Web Professionals
Building Ning Apps
PHP + Ning = Your Own Social Application In Minutes
Elements Building a Task Application with PHP 5 and SimpleXML PHP From the Shop Floor Bringing Together People In Need With People Who Care PHP Casts Its Charms Exploring Magic Methods in PHP 5 Best Practices In Enterprise Engagements Nailing Down Your First Big Enterprise Project The Generation Game Code Generation and PHP Peering Into Phorum An Inside Look at Modifying Phorum Code KINKY Language, Data Access and Forms Producing Multilingual Output, Accessing Data and Rendering Output
Table of Contents 29 Building Ning Apps
Ning is a free online playground for building and sharing social apps. Use PHP to write a Ning app and spend your time on what’s interesting about your app, such as sharing photos, trading restaurant reviews, or displaying interactive maps. Ning takes care of everything behind the scenes – hosting, security, tagging and search services, user registration, and all of the other bits that you’d otherwise have to build yourself. Plus, you don’t have to start from scratch. There are lots of apps on Ning for everything from noting movie star sightings to collaboratively reviewing neurobiology papers. You can clone one of these apps to jumpstart your own. by David Sklar
07 Elements
This edition eyes the updated and much improved XML interface provided with PHP 5. The PHP 5 SimpleXML interface delivers – it offers the programmer an easy to use set of methods for creating and parsing XML data. In this article, you’ll see how to use SimpleXML methods to build a simple Task List – one that will allow you to add, edit, delete, and list out Task items. Along the way, you’ll learn how to use SimpleXML to build XML applications. by Thomas Myer
News & Trends 05 The goings on in the PHP world
Column 07 Elements SimpleTask: Building a Task Application with PHP 5 and SimpleXML 18 PHP From the Shop Floor Bringing Together People In Need With People Who Care
Beginners 21 PHP Casts Its Charms Exploring Magic Methods in PHP 5
Cover Story 29 Building Ning Apps PHP + Ning = Your Own Social App In Minutes
18 PHP From the Shop Floor
The second installment of the series focuses on a very real world scenario. What happens when you suddenly face a family crisis such as the death of a loved one or the diagnosis of a deadly disease? Not only do you try to pick up the pieces, but you are also, undoubtedly, inundated with well-meaning Samaritans. Sometimes this outpouring of help can be too much for someone already dealing with a lot of other stuff. What can they do? Simple. Visit the CollaborCare web site, that conveniently uses PHP to coordinate, organize, and connect people who are in need of help with people who are willing to offer that help, thus making lives easier for all involved. by Elizabeth Naramore
21 PHP Casts Its Charms
Learning the magic methods of PHP may not be as exciting as attending Hogwart’ School of Witchcraft and Wizardry. But it is essential if you want to make full use of the object oriented capabilities of PHP. Magic methods were first introduced in PHP 5. Some are methods of convenience that help speed up development. However, it is absolutely essential for the object-oriented programmer to understand magic methods such as __clone. Additionally, the release of PHP 5.1 has introduced a number of new magic methods. The author waves his wand to deconstruct the magic behind these charming new methods in PHP 5. by Peter Lavin
2
International PHP Magazine 01.2006
www.php-mag.net
January 2006 Issue Enterprise 38 Best Practices In Enterprise Engagements Nailing Down Your First Big Enterprise Project
Development 45 The Generation Game Code Generation and PHP
Workshop 54 Peering Into Phorum An Inside Look At Modifying Phorum Code
Spotlight 61 KINKY Language, Data Access and Forms Producing Multilingual Output, Accessing KINKY Data & Rendering KINKY Output
Department 4 Editorial 71 Advertising Index / Preview / Imprint
38 Best Practices In Enterprise Engagements
Congratulations – you’ve landed your first big enterprise client. Now you need to do what you said you’d do, and make everyone happy in the process. This article will show you how to use techniques for visibility and verification to do most things – setting up processes and systems, doing QA and acceptance testing, and requirements gathering. by Thomas Myer
45 The Generation Game
Code generation can provide a useful and efficient approach to solving many problems that crop up during application development. It can cut down the amount of repetitious work you do. It can also help you to avoid the kind of duplication that leads to real maintenance nightmares. This article will give you a vital headstart in implementing code generation solutions that dramatically cut down on your workload and improve your coding life. by Jon Ramsey
54 Peering Into Phorum
Tinkering with open source applications can be quite daunting, especially when there are dozens of directories, files and database tables to contend with. Throw in some Smarty templating and code techniques you’ve never seen before and you’ll soon find yourself lost. This article looks at navigating the complex maze that makes up the Phorum software, and in the process transforms a standard forum into one that allows viewers to see the state of a thread (Answered or Unanswered). So if you’re looking at modifying the Phorum software, need to understand the maze, or are already using a forum in a question-answer type scenario, this article is definitely for you. by Brent Knigge, Boulos Mansour, Andy Cachia
61 KINKY Language, Data Access and Forms
The KINKY application framework now has over three hundred modules, up from just over a hundred in the last article, making it one of the most comprehensive PHP application frameworks around. In this article, the project founder looks at multilingualization, data access, and creating input forms. by Derek W. Keats
www.php-mag.net
International PHP Magazine 01.2006
Editorial
Social Networking with PHP The advent of Web 2.0 has paved the way for the next level of user interaction on the Web, with users being able to move away from installing and maintaining bits and pieces of applications on their personal computers, and graduating to the Web for their computing needs. Ning has taken a great step into this new era by providing a platform for people to build and share social apps. In our Cover Story, David Sklar steps you through the basics of using PHP in the Ning Playground. You’ll learn how to manipulate content in your Ning App, handle uploaded files, use Ning Components that make it easy to do things such as adding Ajax to your app, and more.
showing you how to use techniques for visibility and verification to do most things – setting up processes and systems, doing QA and acceptance testing, and requirements gathering.
In this issue, we have a guest editor for the Elements column, training his eyes on the updated and much improved XML interface provided with PHP 5. The PHP 5 SimpleXML interface delivers – it offers the programmer an easy to use set of methods for creating and parsing XML data. Thomas Myer will show you how to build a task application that does not require the use of a SQL-based database, making it even easier to get it up and running.
Tinkering with open source applications can be quite daunting, especially when there are dozens of directories, files and database tables to contend with. In the Workshop corner, Brent Knigge, Boulos Mansour and Andy Cachia explain how they navigated the complex maze that makes up the Phorum software, and in the process transformed a standard forum into one that allows viewers to see the state of a thread.
More social programs – in the second installment of PHP From the Shop Floor, Elizabeth Naramore talks to the founders of a web site that uses PHP to coordinate, organize, and connect people who are in need of help, such as new parents who are bringing home their newborn(s) or someone who is ill and has no family in the area, with those who are willing to offer that help. Peter Lavin says that learning the magic methods of PHP may not be as exciting as attending Hogwart’s School of Witchcraft and Wizardry, but it is essential if you want to make full use of the object-oriented capabilities of PHP. In the Beginners track, he deconstructs the magic behind these charming methods in PHP 5. Big projects have a number of inner dynamics that make them different from smaller jobs. In the Enterprise track, Thomas Myer nails down the best practices in enterprise engagements,
Code generation is a powerful technique that can help solve many of the problems that crop up during application development. In the Development track, Jon Ramsey explains the commonly used approaches and techniques for code generation, provides a brief survey of the use of code generation within open source tools and applications, and starts on development of a tool you’ll find useful in your day-to-day work.
The Spotlight is back on KINKY. The first part of the series, published in Issue 12.05, introduced the KINKY application framework and created a module called hellokinky, which didn’t include database access or build other objects. Besides, the code violated the multilingualization requirements of KINKY, since it included hard-coded English language text. In this issue, Derek Keats takes the sample application further, looking into multilingualization, data access, and creating input forms. We hope you enjoy this issue of the magazine. Do write in, and let us know what you think.
Indu Britto (
[email protected])
International PHP Magazine 02.2006
News & Trends phpMyVisites 2.1 Stable Roll Out
MySQL Connector/J 5.0.0 Beta Rolled Out
phpMyVisites is a Web traffic analyzer with very detailed reports and advanced graphics. It creates its own logs, and allows access to more complete statistics. phpMyVisites provides information about visitors, page views, visitor follow-ups, countries of origin, software configurations, referrers, and much more. It also includes a powerful administration and configuration tool. phpMyVisites 2.1 stable adds support for Taiwanese, Arabic and Bulgarian, and adds Czech search engines. It updates Slovenian, French, Italian, and Czech language files. http://www.phpmyvisites.net/
MySQL A.B. has posted a beta of MySQL Connector/J 5.0.0, the Type-IV all-Java JDBC driver for MySQL. According to MYSQL‘s Mark Matthews, “Version 5.0.0 is a development release based on code from the stable tree. It is essentially Connector/J 3.1.13 with support for XA back ported from the Connector/J 3.2 branch, as well as some internal refactorings to support JDBC-4.0 features. Connector/J 5.0.0 is suitable for use with any MySQL version including MySQL-4.1, MySQL-5.0 or MySQL-5.1. We expect that Connector/ J 5.0 will soon stabilize, as the majority of the codebase is from Connector/J 3.1 which is a generally available release, with largely non-disruptive new functionality added (namely XA) to the 5.0 codebase (which is also why this is a “beta” release out of the gate).” http://www.mysql.com
PHP 5.1.2 and PHP 4.4.2 Released Following from the PHP 5.1.2 release plan, the PHP development team is proud to announce the release of PHP 5.1.2. The PHP 5.1.2 release combines small feature enhancements with a fair number of bug fixes and addresses three security issues. All PHP 5 users are encouraged to upgrade to this release. Some of the key changes of PHP 5.1.2 include: HTTP Response Splitting has been addressed in ext/session and in the header() function; fixed format string vulnerability in ext/mysqli; fixed possible crosssite scripting problems in certain error conditions; hash & XMLWriter extensions added and enabled by default; upgraded OCI8 extension, and over 85 various bug fixes. The PHP 4.4.2 release addresses a few small security issues, and corrects some regressions that occurred in PHP 4.4.1. This is a bug fix release, which addresses some security problems too. The major points that this release corrects are: prevents header injection by limiting each header to a single line, possible XSS inside error reporting functionality, missing safe_ mode/open_basedir checks into cURL extensionm Apache 2 regression with sub-request handling on non-Linux systems, key() and current() regression related to references, and over 30 bug fixes. http://www.php.net/downloads.php
PHP Exif Library 0.9 Released The PHP Exif Library (PEL) provides an easy-to-use library written in pure PHP that makes it possible to read and write the Exif headers commonly found in JPEG and TIFF photos taken with digital cameras. It runs on any platform that supports PHP5. With PHP Exif Library 0.9 full support for GPS information was added, breaking API compatibility with the previous version, along with JPEG comments, the Gamma tag, and Windows XP-specific title, comment, author, keywords, and subject tags. A non-strict mode for broken images where most errors won’t result in visible exceptions was implemented. A partial Polish translation was added. The API documentation was updated with details about the constraints on format and number of components for each tag. http://pel.sourceforge.net/
The TagMogrifier Library for PHP The purpose of the Tagmogrifier library is to let you perform fast and easy-to-program transformations on XML documents. Instead of having to write your own parser, or directly using the PHP DOM parser, and having to write your own tree walker and such,
International PHP Magazine 02.2006
News & Trends you only need to instantiate a class, add a few match expressions, and assign a function to process every XML element that matches, DOM-style. http://rudd-o.com/projects/tagmogrify/
Apache Market Share Drops Again Apache’s leading share of the Web server market dropped again, according to Netcraft’s (netcraft.com) January 2006 Web server survey. Apache’s share dropped from 69.97 to 67.39 percent. Microsoft, which holds the second largest market share, also dipped, going from 20.92 to 20.29 percent.Meanwhile,Sun lost ground while Zeus One gained. Netcraft’s survey measures Web server software use on all Internet connected computers. The December survey received responses from 74,951,256 sites, higher than November’s 74,353,258. http://www.netcraft.com/
Horde Application Framework 3.1-RC1 The Horde Application Framework is a modular, generalpurpose Web application framework. It provides an extensive array of classes that are targeted at the common problems and tasks involved in developing modern Web applications. Horde Application Framework 3.1-RC1 is a major feature enhancements release that adds expiring and resetting of passwords, detection of “phishing” attempts, and links to all applications’ options in the menu. The latest release also sports improved accessibility, support for right-to-left languages, and import of CSV data. New themes, dynamic table resorting, and support for alternate login screens and redirection on logout have been added. The History library’s performance has been improved. Counting and listing of active sessions and fixed portal blocks have been added. Problem reports can be sent to ticket systems now. Many libraries have been extended and improved. http://www.horde.org/horde/
HessianPHP 1.0.5 RC2 - Major Feature Enhancements HessianPHP is a PHP implementation of the Hessian web services binary protocol. The project includes open source implementations of client and server classes. The
latest release, HessianPHP 1.0.5 RC2, supports both PHP 4 and 5. The internals were extensively refactored. Date handling is now performed via the DateProvider class and a default simple DateTime class was provided. A filter mechanism and several filter examples were included. HttpConnection no longer requires a content-length header to ensure compatibility. Several bugs were fixed. http://www.hessianphp.org
A Shell Wrapped in PHP: PHP Shell 2.1 PHP Shell 2.1 is a shell wrapped in a PHP script. It’s a tool you can use to execute arbiritary shell-commands or browse the filesystem on your remote Web server.This replaces, to a degree, a normal telnet-connection. You can use it for administration and maintenance of your Web site using commands like ps, free, du, df, and more. With the latest version, PHP Shell 2.1, authentication is now handled internally in PHP in an attempt to solve reported login problems. Configuration settings were moved to an ini file, and handling of PHP Safe Mode was improved with better error messages. http://mgeisler.net/php-shell/
Sourdough – Web App Framework for PHP 5 Sourdough is a comprehensive Web application framework for PHP5. It provides developers with realworld solutions for common system components such as user management, session handling, user authentication, exception handling and logging, a template system, and form building and handling. It includes a database abstraction layer with support for MySQL, MS SQL, PostgreSQL, and SQLite. Its extensive feature set can simplify or eliminate many common, and often tedious, programming tasks. Sourdough provides the following distinguishing features – built for PHP5: new object model, fully object-oriented API, contains extensive unit test framework, truly independent (database interactions with Sourdough are truly independent), easy to use (all modules are accessed by a global factory and dependencies are resolved by Sourdough itself). The latest release, Sourdough 0.3.3-alpha, extends support for captcha image generation, offers cleaner session management and more. http://sourdough.phpee.com/
International PHP Magazine 02.2006
Column Elements
Elements Iteration with PHP 5.1’s Standard PHP Libraby Thomas Myer In this instalment of Elements, our regular columnist Douglas Clifton invites SitePoint author Thomas Myer to contribute an article on the updated and much improved XML interface provided with PHP 5. When designing an XML-based application, the programmer is not constrained by an existing DTD or XML schema. In other words, the element can be anything you want it to be – as long as it describes the data being represented in a meaningful (semantic) way! The PHP 5 SimpleXML interface delivers – it offers the programmer an easy to use set of methods for creating and parsing XML data. Read on to learn how to build a task application that does not require the use of an SQL-based database, making it even easier to get it up and running. Introduction If you’re like most folks who have heard about XML, you want to know what all the noise is about. If you’ve worked with XML in any programming language (but especially PHP 4) you know there is lots of room for improvement when it comes to easily working with XML files. Back in the PHP 4 days, you only had two ways to process XML – DOM and SAX. The DOM method was more complicated, but gave you a lot more power. It involved stuffing the XML document you were working with into a node tree, which you could then traverse in much the same way as DOM on an HTML browser. SAX was much easier to deal with, but you had a lot less flexibility – you only got one pass through a document and that was it. Once the document flowed by, you were done; no way to hit reverse or jump around inside the document like you could with DOM. Along came PHP 5 and a brand new way to process XML documents – SimpleXML. As its name implies, it offers a simpler way to parse XML documents. In fact, most developers with PHP experience will find little difference between working with SimpleXML objects and regular arrays.
In this article, you’ll see how to use SimpleXML methods to build a simple Task List – one that will allow you to add, edit, delete, and list out task items. Along the way, you’ll learn how to use SimpleXML to build XML applications. SimpleXML 101 Before we begin working on the Task List Manager, let’s go over a basic example to introduce SimpleXML’s power and simplicity. Let’s say that you had a very simple XML document that described DVDs in your DVD collection. A DVD collection listing usually includes the name of the movie, name of the director, year released, studio, cast of characters and more, but for our purposes, let’s keep it simple. We’ll just list movie name, director’s name, and year the movie was released. Listing 1 shows a snippet from this file (ignore the year attribute for now – we’ll cover it soon). The file has been named mydvd.xml. To process this file with SimpleXML, load the file into a SimpleXML object as shown in Listing 2.
International PHP Magazine 02.2006
Column Elements Listing 1 Star Wars George Lucas ET Steven Spielberg The Great Escape John Sturges
Listing 2 Learn SimpleXML <description>Read article on SimpleXML and apply what I’ve learned. <priority>High <status>In progress 2005-12-15 Buy Christmas Gift for Mom <description>She really enjoys mystery novels <priority>Very High <status>Not Started 2005-12-21
I’ve designed and deployed very complex web sites that are powered by semantically simple (but not simplistic) XML structures. Note that because we’ve taken the time to create a sample file, we can save it in our project repository and start coding the application to work with the file. Notice the use of the timestamp attribute on each task node? We’ll use PHP to generate this information for us – it will allow us to differentiate between different tasks in the list, as its unlikely that we’ll be creating more than one task per second. The next step is to build the index page for the project – the one that lists out all tasks. Building the Index Page The index page will consist of extracting information from tasklist.xml and dumping it into a basic table. The table will also include links that allow to add new tasks or edit/delete/mark complete existing tasks.
Listing 10 <meta http-equiv=”Content-Type” content=”text/html; charset=utf-8”> Task List <style> body,p,li { font-family:arial,verdana,sans-serif; } Task List New Task
Task | Due Date | Priority | Status | |
10
International PHP Magazine 02.2006
Column Elements Listing 11
12
International PHP Magazine 02.2006
Column Elements create, edit, delete or complete. If it’s delete or complete, all we need to do is pass the information along to process.php and exit. If it’s create or edit, invoke writeForm(), with or without passing in $info as an argument.
“http://www.w3.org/TR/html4/loose.dtd’> <meta http-equiv=’Content-Type’ content=’text/html; charset=utf-8’> ”. $_GET[‘action’].” Task
The writeForm() function is a simple process that creates a form for the user to fill in. This form will contain fields for title, status, description, priority, due date, and various hidden fields for timestamp and other information. If we pass in $info, it also pre-populates each field with information from the XML file, such as pre-existing title or status.
<style> body,p,li { font-family:arial,verdana,sans-serif; } label { display:block; }
I’ll go over this step in detail, as understanding this kind of function can make your life easier on other projects as well. First though, let’s take a look at the entire listing (see Listing 13).
input[type=’submit’]{ border:1px solid #ccc; }
We declare the function and indicate that $info is an optional argument by stating $info=’’:
function writeForm($info=’’){
Next comes the form. Notice that with the title and due date, we pause momentarily to check whether the corresponding node in $info has a string length. If so, we drop in a value attribute and the information from $info:
The next step is to build two arrays to contain the available choices for our priority and status drop downs. Using arrays like this can greatly simplify your development, as you can print out lists and check against what is saved in the XML file to indicate state. (For example, if you go to edit a task that has a ‘completed’ status, the system should automatically mark ‘completed’ as the current status without user intervention.):
”.strtoupper($_GET[‘action’]).” TASK
Title title)){
$priority = array(‘low’,’medium’,’high’,’very
$form .= “ value=
high’); $status = array(‘not started’,’in progress’,’completed’,’uncompleted’);
‘”.stripslashes($info[0]->title).”’”; } $form .= “ />
Next, initialize the $form variable with normal HTML header material and introductory copy:
Due Date duedate.”’”; }
Next come the two drop-downs. Note that all we have to do is loop through the respective arrays for priority and status, checking against the appropriate entries in $info to see if we need to mark a particular option as ‘selected’:
Finally, we have the textarea and the submit button. Note the hidden variables for action and timestamp. The timestamp is critical for creating a task, as we will use it to determine uniqueness of the task node. At the very end of the function, we return $form: Description ”;
$form .= “
if (strlen((string)$info[0]->description)){
/>
$form .= “ value=’”.
stripslashes($info[0]->description).”’”;
Priority
}
<select id=’priority’ name=’priority’>”; $form .= “
foreach ($priority as $p){
if ((string)$info[0]->priority == $p){
”; foreach ($status as $s) { if ((string)$info[0]->status == $s) { $ck = ‘selected’; } else { $ck = ‘’; } $form .= “$s\n”; } $form .=”
Fig.2: The Filled-In Form
14
International PHP Magazine 02.2006
Column Elements Listing 14 ‘; switch ($_REQUEST[‘action’]){ case “create”: foreach ($taskList as $key => $task) { if ($_POST[‘ts’] == (string)$task[‘timestamp’]) { //just in case there is another //timestamp in the //file like ours, we can add a second $_POST[‘ts’]++; } $finalXML .= “\n”; $finalXML .= “”. stripslashes($task-> title).”\n”; $finalXML .= “<description>”. stripslashes($task-> description).”\n”; $finalXML .= “”. $task-> duedate.”\n”; $finalXML .= “<priority>”. $task-> priority.”\n”; $finalXML .= “<status>”. $task-> status.”\n”; $finalXML .= “\n”; } $finalXML .= “\n”; $finalXML .= “”. stripslashes($_POST[‘title’]).”\n”; $finalXML .= “<description>”. stripslashes($_POST[‘description’]). “\n”; $finalXML .= “”. $_POST[‘duedate’].”\n”; $finalXML .= “<priority>”. $_POST[‘priority’].”\n”; $finalXML .= “<status>”. $_POST[‘status’].”\n”; $finalXML .= “\n”; break; case “edit”: foreach ($taskList as $key => $task) { if ((string)$task[‘timestamp’] != $_POST[‘ts’]) { $finalXML .= “\n”; $finalXML .= “”. $task-> title.”\n”; $finalXML .= “<description>”. $task-> description.”\n”; $finalXML .= “”. $task-> duedate.”\n”; $finalXML .= “<priority>”. $task-> priority.”\n”; $finalXML .= “<status>”. $task-> status.”\n”; $finalXML .= “\n”; } else { $finalXML .= “\n”; $finalXML .= “”. stripslashes($_POST[‘title’]). “\n”; $finalXML .= “<description>”. stripslashes($_POST[‘description’]) .”\n”; $finalXML .= “”. $_POST[‘duedate’].”\n”; $finalXML .= “<priority>”. $_POST[‘priority’].”\n”; $finalXML .= “<status>”. $_POST[‘status’].”\n”; $finalXML .= “\n”; } } break; case “delete”: foreach ($taskList as $key => $task) { if ((string)$task[‘timestamp’] != $_GET[‘ts’]) { $finalXML .= “\n”; $finalXML .= “”. $task-> title.”\n”; $finalXML .= “<description>”.$task-> description.”\n”; $finalXML .= “”. $task-> duedate.”\n”; $finalXML .= “<priority>”. $task-> priority.”\n”; $finalXML .= “<status>”. $task-> status.”\n”; $finalXML .= “\n”; } } break; case “complete”: foreach ($taskList as $key => $task) { if ((string)$task[‘timestamp’] != $_GET[‘ts’]) { $finalXML .= “\n”; $finalXML .= “”. $task-> title.”\n”; $finalXML .= “<description>”.
15
International PHP Magazine 02.2006
Column Elements Listing 14 (continued) $task->description. “\n”; $finalXML .= “”. $task-> duedate.”\n”; $finalXML .= “<priority>”. $task-> priority.”\n”; $finalXML .= “<status>”. $task-> status.”\n”; $finalXML .= “\n”; } else { $finalXML .= “\n”; $finalXML .= “”. $task-> title.”\n”; $finalXML .= “<description>”. $task->description. “\n”;
value=’”.$_GET[‘action’].”task’/> duedate.”\n”; $finalXML .= “<priority>”. $task-> priority.”\n”; $finalXML .= “<status>completed\n”; $finalXML .= “\n”; } } break; } $finalXML .= ‘’; file_put_contents(‘tasklist.xml’,$finalXML); header(“Location:index.php”); exit; ?>
lead to this file. Here is where our application actually makes changes to the XML file.
value=’”.$_GET[‘action’].”’/>
Note the way I use a concatenated variable and file_put_contents() to replace the file. I’ve done this to show how to manipulate structures without DOM or SAX; besides, SimpleXML doesn’t have very sophisticated editing capabilities.
?>
Summary We’ve covered a lot of territory here detailing the basics for working with SimpleXML. The article also elaborated on how to build a simple Task List with PHP 5 and XML. Hopefully, building this application will give you a head start on creating others.
Have a look at Figure 2 to see what a filled-in form would look like.
Hope you’ve enjoyed this instalment of Elements. We are open for feedback, so your questions and comments are welcome.
”; return $form; }
Let’s move on to the last part of the application – process.php. Building the Process Page The final file in our small application is process.php. Although task.php does a lot of heavy lifting, all roads
Thomas Myer is the founder of Triple Dog Dare Media, an Austin, TX web consultancy that specializes in content management systems. He is the author
16
International PHP Magazine 02.2006
Column Elements of No Nonsense XML Web Development with PHP, ublished by SitePoint. p
About the Columnist: Douglas Clifton is a freelance PHP developer living in the Washingon, DC area. As a long-time advocate of open-source technologies, he has been using PHP, as well as Perl, MySQL, Linux and FreeBSD, since the
early days of the Web. His other interests include carefully designed user interfaces, the adoption and use of open standards, and unrestricted access to all users, regardless of their physical abilities. In his spare time, Doug keeps the database and software for Digital Web Magazine humming, and runs his own Open Source advocacy web site, loadaverageZero.
Questions & Comments • PHP Magazine Forum
17
International PHP Magazine 02.2006
Column PHP From the Shop Floor
PHP From the Shop Floor Bringing Together People In Need With People Who Care by Elizabeth Naramore What happens when you suddenly face a family crisis such as the death of a loved one or the diagnosis of a deadly disease? Not only do you try to pick up the pieces, but you are also, undoubtedly, inundated with well-meaning Samaritans. Sometimes this outpouring of help from friends, neighbours and the community can be too much for someone already dealing with a lot of other stuff, and then what can they do? Simple. Visit the CollaborCare web site, that conveniently uses PHP to coordinate, organize, and connect people who are in need of help with people who are willing to offer that help, thus making lives easier for all involved. Introduction The story behind CollaborCare is simple. Rick MacLean’s wife was diagnosed with cancer last year and the flood of help from friends, family and neighbours was simply too much for them to handle. People were inundating them with food and phone calls and well wishes to the point that it kept them from focusing on the one thing they needed to focus on – dealing with her cancer and her uncertain future. Rick knew there had to be a better way, and he called up his good buddy Dan Erdman to discuss it. Thus CollaborCare [1] was born.
At the time of this writing, they are getting ready to launch the site officially. What Does CollaborCare Do? The basic premise of the site is to act as a central point to coordinate what help is needed with who is able to provide that help. Although it was conceived based on the diagnosis of a disease, its implications are much farther-reaching. Anyone who is in need of help can register with the site, whether it is new parents bringing home their newborn baby (or babies), or someone who is ill and has no family in the area. The service is free to all who register. When you visit the web site, you can register as a Person In Need (PIN) or a Person Who Cares (PWC), or both. As a PIN, you are able to list tasks that you need completed, such as a ride to a doctor’s appointment. These are called ‘Help Requests’. As a PWC, you can sign up to complete Help Requests listed for a particular PIN. The PIN can communicate directly with the PWC via comments attached to each particular task.
Fig.1: The CollaboCare Web Site
As a matter of security, the PIN defines a ‘Help Code’ that they pass on to the PWC, who are required to enter this help code when they are going to commit to completing a task. In this way, exchanges are conducted
18
International PHP Magazine 02.2006
Column PHP From the Shop Floor only by those who have verified they know each other; strangers are not permitted to have access to personal information volunteered by the PIN. The PIN can also assign different levels of access for the PWC. Those who have been assigned the highest levels of access can view a PIN’s detailed personal information, but the rest of the PWC can only view information pertinent to the task at hand. So for instance, if you and your spouse need help with your newborn triplets, you can visit the CollaborCare site and sign up as a PIN. When you register, you can define your own Help Code to verify that any PWC who tries to help you is someone you know and who has the code you’ve supplied. You submit a few ‘Help Requests’ like “Doctor Visit” or “Go to the Grocery Store”. You then notify your friends, family, and others you think would want to help, that you’ve signed up at the CollaborCare site and pass along your Help Code. They can then register and see requests you’ve entered, and decide if they are able to help. Once they’ve decided to “commit” to a task, it is effectively taken off the list viewable by others, and it’s agreed that they will come to your rescue to perform the task on the specified date. So what if you have a family member that isn’t so Internet savvy but could really use some help? The CollaborCare site allows someone to act as that person’s advocate; so you could sign up under their name and coordinate everything for them, on their behalf. The Case for PHP Because the site is free for all to use, cost was of the utmost importance when development began. The PHP/MySQL combination was a logical choice because of its open source nature. Dan and Rick originally enlisted the help of some friends and family members with PHP and web design experience to get the site up and off its feet. Dan also began to get his feet wet by jumping in and learning PHP to help out with the coding of the site. Dan has a background in IT and found PHP incredibly easy to learn and work with. But because he couldn’t “quit his day job”, freelance coder Bill Barnett was brought in to assist with the final touches before they could launch.
“The PHP/MySQL combination was a logical choice for the web site, because of its open source nature. The founders initially enlisted the help of some friends and family members with PHP and web design experience to get the site up and off its feet. Dan also began learning PHP, and found PHP incredibly easy to learn and work with. Later a freelance coder was brought in to clean up, standardize and pull together several different coding styles and snippets of code, and put the final touches on the web site.” The challenge for Bill was in pulling together several different coding styles and snippets of code [resulting from the collaborative effort that was underway before Bill was brought on board]. So Bill cleaned up, standardized, and modified the pre-existing code. As well, Bill was dealing with a web host that was running older versions of PHP and MySQL. Therefore, duplicating the environment for test purposes was a bit tricky. Bill was able to rise above the obstacles presented to him and put the icing on CollaborCare’s cake. The Future of CollaborCare Dan and Rick are excited about the potential in CollaborCare and the massive amounts of people it can help. Dan says, “the real payoff will be when we start to see people really using the site!” Then they know they will have done something special. They have big plans for the site as it launches in the next few weeks, and they have already been contacted by members of the media (myself notwithstanding) who plan on writing about this wonderful tool and what it can mean to those in need. As the site’s usage grows, the functionality will most likely grow as well. They’d like to include facilities
19
International PHP Magazine 02.2006
Column PHP From the Shop Floor like the ability to post announcements about people’s health status to the appropriate group of PWC, or other concerned friends and family members not registered on the site. They have many other plans as well, so you may want to stay tuned and keep an eye on these up-andcomers.
If you have an interesting PHP development project you think would make a great addition to our “PHP From the Shop Floor” series, please don’t hesitate to contact
[email protected].
Elizabeth Naramore has been working in web development since 1997, and PHP specifically since 2002. Although her main focus is in e-commerce, she has also co-authored several PHP related titles. Besides web development, Elizabeth also teaches e-commerce and writes a weekly PHP news column for PHPBuilder.com. She also enjoys being an active part of the mighty OINK-PUG (Ohio, Indiana, Northern Kentucky PHP Users Group). She lives in Cincinnati, Ohio, with her husband and two children.
Resources & References
Questions & Comments • PHP Magazine Forum
[1] CollaborCare
20
International PHP Magazine 02.2006
Beginners PHP Casts Its Charms
PHP Casts Its Charms Exploring Magic Methods in PHP 5 by Peter Lavin Magic methods were introduced as part of the improved object model for PHP 5. These methods are termed magic because they aren’t invoked directly, but as the result of some other action. As of PHP 5.1, these methods are __construct, __destruct, __toString, __autoload, __call, __clone, __get, __set, __sleep, __wakeup, __unset and __isset. Read on to find out everything you need to know about magic methods. Introduction The reasons for providing magic methods are various – some are an absolute requirement while others are “syntactic sugar”. In any case, let’s look at each method in turn. Some methods are relatively straightforward and can be dealt with quickly, while others will require more discussion. Related and complementary methods will be discussed together. Let’s begin with the constructor since it’s probably familiar to most readers. Note: Since this article was written, an additional magic method, __set_state, has been introduced. This method is invoked by a call to the var_dump function. At this point there is minimal documentation and I couldn’t reproduce the behaviour described on the PHP site. For these reasons I have not included a discussion of this method. For more information see: http://php.net/var_export.
“All magic methods begin with a double underscore.” __construct and __destruct In PHP 4, a constructor is the method that bears the name of the class and is invoked whenever a new object is created. In PHP 5 you can still use this style of constructor, but the preferred practice is to define a __construct method. This method is magic because it is indirectly invoked
when the ‘new’ operator is used with a class name. For instance, if we have a class called MyClass that defines a constructor, the statement, $m = new MyClass();, indirectly calls the __construct method of this class. Having a uniform constructor for every class yields benefits when a parent constructor needs to be called. For instance, there is no need to hard-code the name of the parent class or to use the get_parent_class function when invoking a parent constructor from within a derived class. Using parent::__construct();, will suffice. A destructor is the converse of a constructor and gets called when a script ends or a class variable goes out of scope. Destructors are a feature of all OO languages and help ensure that resources are properly disposed. For example, a destructor may ensure that all files are closed. The magic construct method provides a minor convenience in some circumstances, but there is no intrinsic need for it. For example, in Java, constructors bear the name of the class with no serious negative consequences. On the other hand, the __destruct method would seem to be a necessity; and since it is invoked by garbage collection [rather than by any action of the developer], being magic would also seem to be a requirement. PHP does a pretty good job of cleaning up after itself; situations
21
International PHP Magazine 02.2006
Beginners PHP Casts Its Charms where a destructor is strictly necessary aren’t common. However, destructors can ensure that certain actions are taken. Further, they promote good programming habits. __toString Echo a class variable to the screen and you’ll see something like “Object id#3”. The __toString method was introduced to control what happens when a class is displayed – it allows you to define a more meaningful string representation of your class. It is magic by virtue of being invoked in the background whenever a class variable is the object of the print or echo functions. There’s no real need for this method since defining a display method and calling it whenever the need arises would achieve the same effect. However, it is convenient to be able to call print or echo and have a meaningful representation of your class. __get & __set The magic set and get methods are designed for undefined class variables. Let’s see what this means by looking at an example. Suppose we have a class, Person, defined as follows: class Person{ } PHP allows us to do the following: $p = new Person(); $p->name = ‘Fred’; $p->street = ‘36 Springdale Blvd’;
Even though $name and $street have not been declared within the Person class, we can assign them values and, once assigned, we can retrieve them. This is what is meant by undefined class variables. We can create magic set and get methods to handle any undefined class variables by making changes to the Person class, as shown in Listing 1. We add an array to the class and use it to capture any undeclared class variables. With these revisions, assigning a value to an undeclared data member called name, invokes the magic __set method in the background and
Listing 1 Person Class class Person{ private $datamembers = array(); public function __set($variable, $value){ //check value passed in? $this->datamembers[$variable] = $value; } public function __get($variable){ return $this->datamembers[$variable]; } } $p = new Person(); $p->name = “Fred”;
an array element with the key, name, will be assigned a value of “Fred”. In a similar fashion, the __get method will retrieve name. Do We Really Need These Methods? There’s no getting away from the fact that PHP does not prohibit the use of undeclared class variables. The introduction of magic set and get methods seems to be an attempt to improve on this situation without breaking backwards compatibility. It is generally a good idea to make all class variables private and only retrieve or change them through accessor methods. Doing otherwise violates the OO principle of data hiding and leaves class variables exposed to inadvertent changes. While you might argue that using magic set and get methods hides data, the real point of accessor methods is to control how class variables are changed or retrieved. The comment inside the __set method in Listing 1 suggests that such controls could be implemented. But in order to do so, we would need to know the variable names beforehand – a classic catch 22. Why not just use properly declared data members? Undeclared data members also undermine another basic concept of OO programming, namely, inheritance. It’s hard to see how a derived class might inherit undeclared class variables. Regardless of the motives behind the introduction of __ set and __get methods, these methods encourage the use
22
International PHP Magazine 02.2006
Beginners PHP Casts Its Charms Listing 2 Revised Person Class class Person{ protected $datamembers = array(); private $declaredvar = 1; public function __set($variable, $value){ //perhaps check value passed in $this->datamembers[$variable] = $value; } public function __get($variable){ return $this->datamembers[$variable]; } function __isset($name){ return isset($this->datamembers[$name]); } function getDeclaredVariable(){ return $this->declaredvar; } } $p = new Person(); $p->name = ‘Fred’; echo ‘$name:’. isset($p->name). ‘
’;//returns true $temp = $p->getDeclaredVariable(); echo ‘$declaredvar:’. isset($temp). ‘
’;//returns true
of undefined data members and this can easily lead to difficulties when debugging. For instance, if we want to change the value of the name data member of a Person class instance and misspell it, PHP will quietly create another class variable, since setting a non-existent data member produces no error or warning. On the other hand, attempting to use an undefined method produces a fatal error. Consequently, when data members are declared as private and are only available through accessor methods there is no danger of accidentally creating a new, unwanted data member. Using declared data members with accessor methods means fewer debugging problems. One might argue though, that these magic methods make PHP easier to use and this convenience offsets any of the disadvantages. After all, the original and continuing impetus behind PHP is to simplify web development. Allowing undeclared data members in PHP 5 is a necessary evil perhaps, because doing so keeps backwards compatibility with PHP 4. However, creating magic set and get methods doesn’t alleviate the problem but only seems to encourage its continuation.
__isset & __unset PHP 5.1.0 introduces the magic methods __isset and __unset. These methods are called indirectly by the builtin PHP functions, isset and unset. The need for these magic methods is a direct result of having magic set and get methods for undeclared data members. The magic method __isset will be called whenever the function, isset, is used with an undeclared data member. Suppose we wanted to determine whether the name variable of our Person instance in Listing 1 has been set. If we execute the code, isset($p->name); the return value will be FALSE. To check whether an undeclared data member has been set we need to define an __isset method. Let’s revise the code for the Person class and incorporate a magic __isset method. See Listing 2. Calling isset against the undeclared data member, name, will return TRUE because an implicit call is made to the __isset method. Testing whether a declared data member is set will also return TRUE but no call, implicit or otherwise, is made to __isset. We have __isset and __unset methods only because there are magic set and get methods. All in all, it would seem simpler to forget about using undeclared data members and thereby do away with the need for magic set and get methods and their companion __isset and __unset methods. __call The magic method __call is to undeclared methods what __get and __set are to undeclared data members. Not an auspicious sign perhaps, but this method is much more defensible than __set and __get. At first though, it is a little difficult to imagine what an undeclared method is and what use it might have. Here’s one approach. Suppose we wanted to create a MySQLResultSet class. We will doubtless want a method to retrieve the ID of the most recently inserted row. One approach would be to create a wrapper method like this: class MySQLResultSet{ public insertId(){
23
International PHP Magazine 02.2006
Beginners PHP Casts Its Charms return mysql_insert_id(); } }
We could continue in this fashion and write wrapper methods for any existing MySQL functions we wish to use. However, the easier option is to add a __call method such as the following: public function __call($name, $args){ $name = “mysql_”. $name; if(function_exists($name)){ return call_user_func_array($name, $args); } }
On the other hand, this is an easy and natural way to incorporate functions like mysql_stat and mysql_errno that don’t require any arguments, or functions like mysql_escape_string that only require primitive data types as arguments. If properly used, this convenience method seems much more defensible than the __set and __get methods. __autoload The __autoload function is a convenience that allows us to use classes without explicitly having to write code to include them. It’s a bit different from other magic methods because it is not incorporated into a class definition but is simply included in your code like any other procedural function. Normally, to use classes we would include them in the following way:
With a __call method defined we can do away with writing wrapper methods and invoke MySQL functions in the following way:
require_once “MySQLResultSet.php”;
//assume $rs is an instance of the MySQLResultSet class
function __autoload($class) {
This code can be replaced with:
$rs->insert_id();
require_once $class . ‘.php’; }
When we call the insert_id method against a MySQLResultSet object, the method name is passed to __call where “mysql_” is prepended. The mysql_insert_id method is then invoked by call_user_func_array. Not only can you call the mysql_insert_id function, but once __call is defined you can use any MySQL function against a MySQLResultSet instance by using the function name, minus the leading “mysql_ ”, and supplying any required arguments. This magic method does away with the need for writing wrapper methods for existing MySQL functions and allows them to be “inherited”. If you are already familiar with the MySQL function names it also makes for easy use of the class. However, this magic method is not quite as convenient as it might seem at first glance. Functions such as mysql_ fetch_array that require passing a result set resource, even though the class is itself a result set resource, make no sense in an object-oriented context. Why should an object need to pass a copy of itself in order to make a method call?
The __autoload function will be invoked whenever there is an attempt to use a class that has not been explicitly included. The class name will be passed to this magic function and the class will be included by recreating the file name that holds the class definition. Of course, to use __autoload as coded above, the class definition file will have to be found in the ‘include’ path. Using __autoload is especially convenient when your code includes numerous class files. There is no performance penalty to pay; in fact, there may be performance improvements if all classes are not used all of the time. Use of the __autoload function also has the beneficial side-effect of requiring strict naming conventions for files that hold class definitions. In the earlier code snippet, the naming convention for class definition files simply combine the class name and the extension ‘.php’. __sleep & __wakeup These magic methods are invoked by the variable handling functions, serialize and unserialize. They control
24
International PHP Magazine 02.2006
Beginners PHP Casts Its Charms how an object is represented so it can be stored and recreated. The way we store or communicate an integer is fairly trivial, but objects are more complex than primitive data types. Just as the __toString method controls how an object is displayed to the screen, __sleep controls how an object will be stored. It is invoked indirectly whenever a call to the serialize function is made. Clean-up operations such as closing a database connection can be performed within the __sleep method before an object is serialized. Conversely, __wakeup is invoked by unserialize and restores the object to its former state. __clone Like the constructor, a PHP operator, in this case clone, invokes __clone. This is a new operator introduced with PHP 5.To see why it is necessary, we need to take a look at how objects are copied in PHP 4. In PHP 4, objects are copied in exactly the same way that regular variables are copied. To show what I mean let’s reuse the Person class (shown in Listing 1) as shown in Listing 3. If the code is run under PHP 4 the output will be as follows:
Listing 3 Revised Person Class $x = 3; $y = $x; $y = 4; echo $x. ‘
’; echo $y. ‘
’; $obj1 = new Person(); $obj1->name = ‘Waldo’; $obj2 = $obj1; $obj2->name = ‘Tom’; echo $obj1->name. ‘
’; echo $obj2->name;
Where’s Waldo? In PHP 5, the assignment of one object to another creates a reference rather than a copy. This means that $obj2 is not an independent object but another means of referring to $obj1. Any changes to $obj2 will also change $obj1. Using the assignment operator with objects under PHP 5 is equivalent to assigning by reference under PHP 4. In other words, in PHP 5: //PHP 5 $obj2 = $obj1;
3 4
has the same effect as:
Waldo Tom
//PHP 4 $obj2 =& $obj1;
The assignment of $obj1 to $obj2 creates a separate copy of a Person just as the assignment of $x to $y creates a separate integer container. Changing the name attribute of $obj2 does not affect $obj1 in any way, just as changing the value of $y doesn’t affect $x. In PHP 5, the assignment operator behaves differently when it is used with objects. When run under PHP 5, the output of the code in Listing 3 is: 3 4 Tom Tom
For both objects the name attribute is now «Tom».
The same logic applies when an object is passed to a function – not surprising since there is an implicit assignment when passing a variable to a function. Under PHP 4, when objects are passed to functions, the default is to pass them by value, creating a copy in exactly the same way as with any primitive variable. This change is made in PHP 5 because of the inefficiencies associated with passing by value. Why pass by value and use up memory when, in most cases, all that’s required is a reference? In sum, in PHP 5, when an object is passed to a function or when one object is assigned to another object, it is assigned by reference. However, there are some situations where we do want to create a copy of an object and not just another reference to the same object. Hence the need to introduce the clone operator.
25
International PHP Magazine 02.2006
Beginners PHP Casts Its Charms Listing 4
Listing 5
Cloning an Object
An Aggregate Class
$obj2 = $obj1; if ($obj1 === $obj2) { echo ‘After assignment’. $obj2 equals $obj1.
’; } $obj3 = clone $obj1; echo ‘After cloning ‘; if ($obj1 === $obj3) { echo ‘$obj3 equals $obj1.
’; } else { echo ‘$obj3 does not equal $obj1.
’; } $obj3->name = ‘Waldo’; echo ‘Here\’s ‘. $obj1->name. ‘.
’; echo ‘Here\’s ‘. $obj3->name. ‘.
’;
class Player{ private $name; private $position; public function __construct($name){ $this->name = $name; } public function getName(){ return $this->name; } public function setPosition($position){ $this->position = $position; } } class Side{ private $players = array();
NOTE: These changes mean that if you are porting PHP 4 code to a server running PHP 5, you can remove all those ungainly ampersands associated with passing by reference.
private $name;
The clone Operator To understand the clone operator let’s use the Person class again and add a few more lines of code. See Listing 4. Running this code results in the following output:
}
public function __construct($name){ $this->name = $name; public function addPlayer(Player $p){ $this->players[] = $p; } public function getPlayers(){
After assignment $obj2 equals $obj1
return $this->players;
After cloning $obj3 does not equal $obj1.
}
Here’s Tom.
public function getName(){
Here’s Waldo.
return $this->name; }
After $obj1 is assigned to $obj2, the identity test shows they are equal. This is because $obj2 is a reference to $obj1. After $obj1 is cloned to create $obj3, the test for identity produces a negative result. This is confirmed by the output, which shows that changing the name attribute of our newly cloned object does not affect the original object. In PHP 5, cloning an object makes a copy of an object just as the assignment operator does in PHP 4. You may suppose that in our search for Waldo we’ve lost sight of our ultimate goal. Not true. Now that we understand the clone operator, we can make sense of the __clone method. It is invoked in the background when
public function setName($name){ $this->name = $name; } }
an object is cloned and it allows us to fine tune what happens when an object is copied. The need for this magic method is best demonstrated using an aggregate class as an example. Aggregate Objects An aggregate class is any class that includes a data member that is itself an object. Let’s quickly create an aggre-
26
International PHP Magazine 02.2006
Beginners PHP Casts Its Charms Listing 6
$newarray[] = clone $p; }
Cloning an Aggregate Object $rovers = new Side(‘Rovers’); $roy = new Player(‘Roy’); $roy->setPosition(‘striker’); $rovers->addPlayer($roy); $reserves = clone $rovers; $reserves->setName(‘Reserves’); //changes both when __clone undefined $roy->setPosition(‘midfielder’); echo $rovers->getName(). ‘ ‘; print_r($rovers->getPlayers()); echo ‘
’; echo $reserves->getName(). ‘ ‘; print_r($reserves->getPlayers());
gate class called, Side. This class has as a data member, an array of objects called Players. See the class definitions in Listing 5. Let’s create a player, add him to a side and see what happens when we clone an aggregate object (see Listing 6). Outputting the player array shows that it is identical for both instances: Rovers Array ( [0] => Player Object ( [name:private] => Roy [position: private] => midfielder ) ) Reserves Array ( [0] => Player Object ( [name:private] => Roy [position:private] => midfielder ) )
Setting a player’s position, after the clone operation, changes the value of position for the player in both objects. Because player is an object, the default behaviour when making a copy is to make a reference rather than an independent object. For this reason, any change to an existing player affects the player array for both Side instances. This is known as a shallow copy and in most cases doesn’t yield the desired result. The magic clone method was introduced in order to deal with situations such as this. Let’s add a __clone method to the Side class so that each side has a separate array of players. The code to do this is as follows: public function __clone(){ $newarray = array(); foreach ($this->players as $p){
$this->players = $newarray; }
While looping through the array of players each individual player is cloned and added to a new array. Doing this creates a separate array for the cloned side. After adding this __clone method, running the code in Listing 6 now yields this output: Rovers Array ( [0] => Player Object ( [name:private] => Roy [position: private] => midfielder ) ) Reserves Array ( [0] => Player Object ( [name:private] => Roy [position:private] => striker ) )
Changing a player originally added to $rovers has no effect on the player array in the cloned $reserves object. This is the result we want. The magic clone method allows us to define what happens when an object is cloned. Using the terminology of other OO languages, the __clone method is a copy constructor. It’s up to the developer to decide what’s appropriate for any particular class but as a general rule of thumb, a __clone method should always be defined for aggregate classes – for the exact reasons we have shown in our sample code – normally the same object isn’t shared among different instances of a class. (If that is the requirement, a static variable should be used.) In some cases it may make sense to disallow copies altogether. This can be done by implementing code such as: public final function __clone(){ throw new Exception(‘No clones allowed!’); }
Making this method final ensures this behaviour can’t be overridden in derived classes. Further, making it public displays a clear error message when there is an attempt at cloning. Making it private would effectively disallow cloning, but it displays the less explicit message, “Access level must be public”. A Note About Overloading On the www.php.net web site, the __set, __get and __call methods are referred to as overloaded methods.
27
International PHP Magazine 02.2006
Beginners PHP Casts Its Charms Within the context of OO programming, an overloaded method is one that has the same name as another method of the same class but differs in the number or type of arguments – methods with the same name but a different signature. Since PHP is a typeless language and doesn’t really care how many arguments are passed, this kind of overloading is impossible. Languages such as C++ also support something called operator overloads. For example, a programmer can define what the “greater than” operator means when two objects of the same class are compared. Support for such features has been described as “syntactic sugar” because, most of the time, operator overloads are not strictly necessary – writing a method rather than overloading an operator would achieve the same effect. However, operator overloads can be convenient and intuitive. It makes more sense to write:
The __toString method, is like an operator overload because it offers an easy and intuitive way of outputting an object. PHP supports operator overloading for clone and, as we have seen, this is not syntactic sugar but a matter of necessity. The same can be said of the magic sleep and wakeup methods because, as with destructors, the circumstances under which these methods are invoked, aren’t always under the direct control of the developer. All other magic methods are there for convenience, and not out of necessity. I won’t repeat the opinions expressed earlier about __set and __get or press the point. After all, PHP places a high premium on user convenience, aiming to get the job done quickly and easily, and that’s what convenience methods aim to do. Undoubtedly, this is the reason for PHP’s success. If PHP’s aim was language purity, OO or otherwise, it wouldn’t still be with us. Peter Lavin runs a web development firm in Toronto, Canada. This article is adapted from his book, Object Oriented PHP, soon to be published by No Starch Press.
if($obj1 > $obj2)
than: if($obj1->isGreaterThan($obj2))
Schooled in Magic Methods Learning the magic methods of PHP may not be as exciting as attending Hogwart’s School of Witchcraft and Wizardry but it is essential to any object-oriented, PHP programmer. Using the magic construct method means that your code will be compatible with future releases of PHP. Furthermore, it reduces overhead when calling a parent constructor. The magic destruct method ensures that resources are properly disposed of.
Resources & References [1] PHP Manual: Magic Methods [2] PHP Manual: var_export [3] Upgrading to PHP 5, by Adam Trachtenberg, from O’Reilly Media
(ISBN 0-596-00636-5)
Questions & Comments • PHP Magazine Forum
28
International PHP Magazine 02.2006
Cover Story Building Ning Apps
Building Ning Apps Challenge the Status Quo in your Development Practices! by David Sklar Want to build a social web app that helps you and your friends share photos, trade reviews, or collaborate on just about anything? Use Ning to get your app up and running in minutes. The Ning Playground jumpstarts your app development by handling all the nitty-gritty backend details. Take an existing Ning app, add some of your own PHP code to customize it if you like, and your app is live. This article shows you the basics of using PHP in the Ning Playground. After reading this article, you’ll know how to manipulate content in your Ning App, handle uploaded files, use Ning Components that make it easy to do things such as add Ajax to your app, and where to look for even more detailed Ning development info. What is Ning? Ning is a free online playground for building and sharing social apps. There are two ways to get an app of your own – by cloning an existing app or by creating a new one from scratch. When you clone an app, your new app has a copy of just about all of the files in the app you cloned. You can run the cloned app as-is or tweak it so it does what you want. Playground apps are written in PHP, so Ning is a great place to put your PHP skills to work.
them is in the Ning Developer FAQ [1]. It includes many PEAR modules, Smarty, and many extensions, including gd, mbstring, openssl and mcrypt.
Developing your social apps on the Ning Playground has some advantages:
This article focuses on the different things you can do in the Playground and how to do them in PHP. You can apply these techniques to an app that you build from scratch, or to an app you’ve cloned.
• App creation tools, including the ability to view the source code of and clone any running app on the Ning Playground • App management, hosting and security • Example apps you can clone and run in minutes • User registration, user profile and role management • Tagging as a built-in service for every app • Navigation and search Ning’s PHP Setup The Ning Playground runs PHP 5.0.4 and has a large number of extensions and modules loaded. A full list of
You access the files in your app either through Ning’s web-based App Manager or via SFTP [2]. Since you have full control over the filesystem in your app, you can always upload library files or the odd PEAR module that we haven’t installed system-wide.
There are lots of developer resources listed at the end of this article, but here are two links to check out now if you’d like to explore. The Example Apps page lists lots of example apps that you can clone easily to jumpstart your development. The Ning Developer Resources page is a comprehensive list of information and links about developing on Ning. Ning Content Store One of the biggest differences between developing an app in the Ning Playground and developing an app
29
International PHP Magazine 02.2006
Cover Story Building Ning Apps anywhere else is the Ning Content Store. Instead of the traditional relational database, the Ning Content Store provides a flexible way to load and save individual content objects. These content objects can contain whatever data you like. There’s no need to spend time building a complicated schema to describe tables and fields. Just create content and save it.
use the same content object types as another app [or different content object types]. There’s no registry of content object types forcing your data into a particular structure.
Use the XN_Content::create() method to create a content object and use the save() method to save it, like this:
echo $dinner->debugHTML();
A quick way to see what is in a content object is to use the debugHTML() method:
which prints something like: $dinner = XN_Content::create(‘Meal’); $dinner->title = ‘Three-Course Dinner’;
XN_Content:
$dinner->description = “Delicious and economical”;
id [1317]
$dinner->my->firstCourse = ‘salad’;
createdDate [20050609T08:02:26-07:00]
$dinner->my->secondCourse = ‘steak’;
updatedDate [20050609T08:02:26-07:00]
$dinner->my->thirdCourse = ‘ice cream’;
type [Meal]
$dinner->save();
title [Three-Course Dinner] description [Delicious and economical]
Each content object has a type and a bunch of attributes. The type is set when the content object is created. The argument to XN_Content::create() sets the content object’s type. Each attribute has a name (such as ‘firstCourse’), a value (such as ‘steak’), and a type (all of the attributes in the earlier code snippet are strings.)
tagCount [0] contributorName [david] ownerId [14] ownerName [Some Example App] isPrivate [0] my attributes [ [1518] firstCourse : salad : string [1519] secondCourse : steak : string
The title and description attributes are system attributes. Most content objects have values for their title and description. The other system attributes are things like ‘createdDate’ and ‘updatedDate’ that the system sets; you cannot change those values. While the list of system attributes is fixed, a content object can have whatever developer attributes you like. These are the ones referenced by the ‘my’ property. Include whatever you want after my-> to make a developer attribute, such as firstCourse, secondCourse or thirdCourse.
[1520] thirdCourse : ice cream : string ]
So, the create() factory method creates new content objects and the save() instance method saves them. That’s the basics of getting content into the content store. How do you get it out? With XN_Query. Use this class to ask questions of the content store and get back matching content objects. A query uses one or more filters to decide which content to return: $query = XN_Query::create(‘Content’)
There’s no need to declare any rules in advance about what goes into a content object. You can make up types, attribute names, and attribute values on the fly. You can have two content objects of the same type and totally different sets of attributes. Your Ning app can
$query->filter(‘type’,’=’,’Meal’); $meals = $query->execute();
This query has just one filter – any content returned must have type Meal. The filter method generally takes
30
International PHP Magazine 02.2006
Cover Story Building Ning Apps three arguments – the attribute to filter against, the operator to filter with, and the value to use.
You can even tack the execute() on to the end, to execute the query and get the results back in one step:
To just find content of type Meal with title ‘ThreeCourse Dinner’, use:
$meals = XN_Query::create(‘Content’) ->filter(‘owner’) ->filter(type’,’=’,’Meal’)
$query = XN_Query::create(‘Content’)
->execute();
$query->filter(‘type’,’=’,’Meal’); $query->filter(‘title’,’=’,’Three-Course Dinner’);
To find content of any type that has a developer attribute ‘firstCourse’ with value ‘salad’, use:
The execute method returns an array of content objects. Following is some code that prints out an HTML list of information from the returned $meals:
$query = XN_Query::create(‘Content’)
foreach ($meals as $meal) {
$query->filter(‘my->firstCourse’,’=’,’salad’);
echo ‘- ’ . $meal->h(‘title’) . ‘ (‘. $meal->my->h(‘firstCourse’) . ‘)
’; }
Notice that when filtering on developer attributes, the first argument to filter is a string beginning with «my>”. All the previous queries search for content across the entire Ning playground. To restrict queries to just the content in your app (which is usually what you want to do), add this filter: $query->filter(‘owner’);
So, for example, to find all the content of type Meal in your app, do: $query = XN_Query::create(‘Content’)
Regular property accesses (such as $meal->title or $meal->my>firstCourse) return raw attribute values. Raw attribute values are often not what you want, though! To protect against cross-site scripting attacks or generate valid URLs, you need values that have been HTML entity-encoded or URL hex-encoded. That’s where transformers [3] come in. Content object transformers are methods that return a transformed attribute value. The earlier example uses the h() transformer to get entity‑encoded attribute values. This prevents cross-site scripting attacks. There are other transformers available, including urlencode(), which gives you URL hex-encoded values. The transform() method allows you to apply arbitrary transformer methods to content object attributes.
$query->filter(‘owner’); $query->filter(‘type’,’=’,’Meal’);
While most filters expect three arguments, the owner filter allows you a shortcut – if you leave out the operator and the value, the query assumes that you want to match against the current app. The filter method returns the query object itself, so you can chain together method calls, like this:
To change the value of an attribute, or to add a new attribute to a content object, use the familiar object property syntax. The following code adds a new attribute to all Meal content objects in the current app: $meals = XN_Query::create(‘Content’) ->filter(‘owner’) ->filter(type’,’=’,’Meal’) ->execute(); foreach ($meals as $meal) {
$query = XN_Query::create(‘Content’)
$meal->my->delicious = ‘yes’;
->filter(‘owner’)
$meal->save();
->filter(‘type’,’=’,’Meal’);
}
31
International PHP Magazine 02.2006
Cover Story Building Ning Apps The type of an attribute is determined in one of two ways: either from the PHP type of value you set, or by explicitly specifying a type. When you use the $content>my->attribute = $value syntax, the type of the attribute is inferred from the type of $value. If $value is a string, then the attribute type is a string. If $value is an integer or float, the attribute type is number (both integers and floats are attribute type number.)
convenience methods, to get one content object to reference another. This is useful for storing relationships such as a “category” content object referencing individual items it contains, or a “person” content objects referencing another person as a “friend”.
To explicitly set an attribute type, use the set() method and pass the type in as a third argument. This is useful with submitted form data, since PHP always represents them as strings:
$alice = XN_Content::create(‘Person’);
$dinner = XN_Content::create(‘Meal’);
$bob = XN_Content::create(‘Person’);
$dinner->title = $_POST[‘title’];
$bob->title = ‘Bob’;
$dinner->my->set(‘price’, $_POST[‘price’], XN_Attribute::NUMBER);
$bob->my->setContent(‘friend’,$alice);
$dinner->save();
$bob->save();
Because $_POST[‘price’] is a string, the set() method is needed to tell the Content Store that the attribute should be treated as a number. The allowable types are:
Note that before you provide a content object to setContent() (such as $alice in the earlier example) it must have been saved to the content store. Once this relationship between $alice and $bob has been set up, the content() method retrieves the linked content object, like this:
• XN_Attribute::STRING • XN_Attribute::NUMBER • XN_Attribute::DATE • XN_Attribute::UPLOADEDFILE • XN_Attribute::CONTENT
To make one content object reference another, use setContent(), like this:
$alice->title = ‘Alice’; $alice->save();
$people = XN_Query::create(‘Person’)->filter(‘owner’)->filter(‘title’,’= ’,’Bob’); $bob = $people[0]; $alice = $bob->my->content(‘friend’);
The XN_Attribute::STRING and XN_Attribute:: NUMBER types are straightforward. Attributes of XN_Attribute::DATE should have values that are fullyqualified ISO 8601 dates. These are dates such as 200503-10T12:24:14Z (12:24:14 PM on March 3, 2005 - UTC) 2005-03-10T12:24:14+05:00 (12:24:14 PM on March 3, 2005 - 5 hours ahead of UTC), and 2005-0310T12:24:14-03:00 (12:24:14 PM on March 3, 2005 - 3 hours behind UTC). For more information on date formatting see the Ning Developer Documentation.
Unless you tell the Content Store otherwise, the results come back ordered by creation date, newest first. Use the order() method to change that by providing an attribute on which to order and an optional ascending/descending parameter. To get results ordered by title, use: $query = XN_Query::create(‘Content’) ->filter(‘owner’) ->filter(‘type’,’=’,’Meal’) ->order(‘title’) ;
The XN_Attribute::UPLOADEDFILE type is for stuffing uploaded files like images and videos into content objects. We look into this in more detail a little later in the article. The XN_Attribute::CONTENT type is for making links between content objects. You can use this type, or some
To get results ordered by the developer attribute firstCourse, in descending order, use: $query = XN_Query::create(‘Content’) ->filter(‘owner’)
32
International PHP Magazine 02.2006
Cover Story Building Ning Apps ->filter(‘type’,’=’,’Meal’) ->order(‘my->firstCourse’,’desc’) ;
the form and stuff it into the content store is quite simple:
At most, a query returns 100 content objects. Use the getTotalCount() method to find out how many results the query matched (even if it matched more than 100 objects), and the begin() and end() methods, to page through the results. For example, following is some code that uses the start query string variable to move through ten results at a time:
$pic = XN_Content::create(‘Photo’);
$start = isset($_GET[‘start’]) ? intval($_GET[‘start’]) : 0;
Using the type XN_Attribute::UPLOADEDFILE tells the Content Store to save the binary contents of the uploaded file into a developer attribute. The developer attribute name and the form field name don’t have to match, but it can be a good idea to keep them in sync so your code is easier to read.
$end = $start + 10; $query = XN_Query::create(‘Content’)->filter(‘owner’)
->begin($start)->end($end);
$results = $query->execute(); // The getTotalCount() method must be called after execute()
$pic->title = $_POST[‘title’]; $pic->description = $_POST[‘description’]; $pic->my->set(‘photo’, $_POST[‘photo’], XN_Attribute:: UPLOADEDFILE); $pic->save();
$total = $query->getTotalCount(); echo “Displaying $start to $end of $total results.
”; foreach ($results as $result) { // Display something relevant for each content object }
Uploaded Files A file upload from a web form works a little differently in the Ning Playground than elsewhere. Instead of using the $_FILES auto-global array, some $_POST variables and Ning functions let you deal with uploaded files. This organization simplifies the process of storing uploaded files in the c ontent store. Here’s a simple file upload form: Photo Title:
Photo Description:
Photo File :
Notice that just like any file upload form, this form has an enctype=“multipart/form-data” attribute in its tag. The code to take the submission of
• 0: everything went OK and a file was uploaded • 1: the file was bigger than the maximum size allowed by the system (currently 5M) • 2: the file was bigger than the maximum size allowed by the MAX_FILE_SIZE form variable • 3: only part of the file was uploaded • 4: no file was uploaded • 5: something went wrong in the guts of the system when trying to process the uploaded file
33
International PHP Magazine 02.2006
Cover Story Building Ning Apps The Playground provides some special functions for dealing with images and other uploaded files. You can display images, get information about their dimensions, get the raw content of an uploaded file, and generate a URL that points directly at that raw content.
Profile::current(). For example, here’s how to display different things to signed-in and anonymous users:
} ?>
To a signed‑in user, the code displays a form for adding content. To an anonymous user, it displays a message asking the user to sign in. The isLoggedIn() method provides the information to make that decision. If you use isLoggedIn() to conditionally display forms, don’t forget to also check that the user is signed into your form processing code. After a signed‑in user requests the form-displaying page, a number of things could happen (a long time elapsed or the user signed out in a different tab or window, for example) which would make that user anonymous when the form is submitted. To control what a page does, based on whether or not the current user is the app owner, use the isOwner() method. This is handy if you want to display a setup page, content administration page, or otherwise expose information or operations only to the owner of an app. Following is an example of isOwner() in use:
The screenName property of the XN_Profile object, that XN_Profile::current() returns, contains the screen name of the currently signed in user.
Like all the role-related methods discussed in this article, XN_Profile::verifyRole() also accepts an XN_Profile object (instead of a username) to specify a user. This is particularly useful when the XN_Profile object is what you get from XN_Profile::current(). Here’s how to determine whether the current user has a particular role:
} ?>
When Tim is no longer the intern, he should no longer have the Intern role. The XN_Profile::dropRole() method takes a previously-granted role away from a user. The following code revokes the Intern role from user Tim:
Components The system services such as the Content Store and user management make some parts of app development easier. While you can always install your own pre-existing PHP libraries into your app to get things going, we also
35
International PHP Magazine 02.2006
Cover Story Building Ning Apps provide a bunch of components to make common tasks easier. These components do things such as: • • • •
Display maps from Google Maps Insert AJAX-enhanced form elements into your pages Search web sites like Amazon, Flickr, and Yahoo! Let users add comments and ratings to content objects
To give you an idea of what the components are all about, here’s an example using the XNC_Ajax component. It simplifies the process of having elements in a page that update without reloading the entire page. It supports updating links, text boxes, auto-complete fields, checkboxes, and lots of other goodies.
Listing 1 Ajax Form No results yet.
Listing 2 Ajax Backend – ajaxSearch.php
The following example implements live search, in which a page is updated with search results as you type. There are two parts to this example – the form that a user sees and the backend code that actually does the search. Call the file in which the form code does whatever you want, but save the backed code as ‘ajaxSearch.php’ – that’s how the form code references it. See Listings 1 and 2. After you type something into the textbox in the form, some JavaScript behind the scenes sends what you’ve typed to the ajaxSearch.php page in the POST variable searchTerm. The sample code only uses it in an XN_ Query filter, but you can do whatever kind of search you like. This example just scratches the surface of the easy Ajax behaviors you can do with XNC_Ajax. For more information see the Ning Developer Documentation [8]. Conclusion The aspects of developing an app in the Ning Playground explored in this article are just the basics. For more information, check out the listings in the Resources and References section. Remember, although there are some Ning-specific things about the Playground, particularly the content store, you can use all the PHP knowledge (and code) you already have in your apps as well – this makes it easy to generate complicated charts and graphs with GD, parse XML with SimpleXML, or retrieve remote URLs with curl. I look forward to hearing about your apps on our Developer Discussion Board or our developer IRC channel! David Sklar is a software architect at Ning. He is also the author of Learning PHP 5 (O’Reilly), Essential PHP Tools (Apress), and PHP Cookbook (O’Reilly). After discovering PHP as a solution to his web programming needs in 1996, he created the PX, which enables PHP users to exchange programs. Since then, he has continued to rely on PHP for personal and professional projects. When away from the computer, Sklar eats mini-donuts, plays records, and likes to cook. He lives in New York City and has a degree in Computer Science from Yale University.
36
International PHP Magazine 02.2006
Cover Story Building Ning Apps Resources & References [1] Ning Developer FAQ [2] SFTP access [3] Transformers [4] Date formatting [5] Handling Uploaded Files [6] Ning PHP Components [7] Ning PHP Components API Documentation [8] Enhancing Apps with Ajax There’s lots more you can do with Ning than what’s discussed in this article – easy tagging of content, more complicated queries, more components, web services,
as well as anything you can dream up to do in PHP. Some good places to get more information are: [9] Ning Developer Discussion: a message board where developers (as well as Ning staff) talk about new apps, feature ideas, bugs, best practices, and everything else (development-related). [10] Ning Developer Documentation: articles about programming on Ning as well as a detailed API reference of all the Ning classes and components. [11] Ning Developer Resources: our collection of links and pointers to all sorts of information for developers. [12] Ning Tech Blog: posts from the Ning tech staff about new features, cool tricks to try in your app, debugging techniques, and whatever else strikes their fancy. [13] Ning Developer IRC channel: #ning on irc.ning.com:6667. Come and say hi, ask questions, or just lurk to pick up some tips.
37
International PHP Magazine 02.2006
Enterprise Best Practices In Enterprise Engagements
Best Practices In Enterprise Engagements Nailing Down Your First Big Enterprise Project by Thomas Myer Congratulations – you’ve landed your first big enterprise client. Now you need to do what you said you’d do, and make everyone happy in the process. This article will show you how to use techniques for visibility and verification to do most things – setting up processes and systems, doing QA and acceptance testing, and requirements gathering. Listening to the Voice of Reality You’ve worked very hard at your consulting practice. Long hours, lots of coding, working with a wide variety of small and medium-sized clients. Maybe you’ve hired someone to help you do what you’re not particularly fond of – UI design, maybe, or XSLT programming, or QA. You’ve moved out of the cozy spare bedroom in your house and actually rented office space, not much, where you slapped your logo on the door. Then one day, it happens. You meet someone at a networking event who knows someone who knows someone that needs a really sharp development team. You all meet together and you’re given the low-down on the project. 8month project, low to mid-six figures, Fortune 500 client, and the work will start immediately. Are you interested? Of course you’re interested! This is exactly what you were dreaming about. You close your eyes and think about not having to deal with the small fry and their tiny budgets and huge expectations. You can’t wait to sit in a meeting with the big guys, where they send in not one or two but maybe three or four IT specialists along with cadres of marketing folks who are as Web-savvy as they are draped in chic clothing. You put together a proposal, everyone signs off, and now it’s time to get started. Just as you start the work, you hear that little tiny voice in the back of your head.
It’s screaming for you to put the brakes on, telling you that a big client isn’t just a small client with a lot more revenue, that a big project isn’t just a small project with a really far away deadline. You need to listen to that voice, because that voice is right. I know. I didn’t listen, and I got punished for it. First Things First Before you do anything else, take a few moments to consider the big project from the client’s perspective. Do they care about budget? Not as much as you think. Big public companies and government agencies may be conscious of price, but they’ve already identified a problem, one that is probably costing them millions of real dollars (or in lost productivity) so they want the problem solved. What they do care about is open lines of communication and your ability to scale. Let’s face some harsh facts. Most big companies and organizations suffer from bureaucratic paralysis. They may be able to identify problems that need solving or hatch great ideas, but most of these get stuck in endless, mind-numbing meetings and involve more strata of stakeholders than you thought possible. Even simple decisions like who controls the budget or who gets the manpower to address the problem can mean endless meandering through red tape and politics.
38
International PHP Magazine 02.2006
Enterprise Best Practices In Enterprise Engagements So first and foremost, big companies prize vendors who can scale their operations, act decisively and quickly, and keep them up to date on what’s going on. Now, let’s go back to you. You’re probably a technical expert in a programming language, process, or knowledge domain. You’ve been a sole practitioner with a small staff, and you’re probably used to handling all kinds of stuff: payroll, banking, rain making, programming, technical discovery, and training. Time for all that to stop. I know, I know, for many of you, it’s a control or trust issue. I know, because I had them too. You don’t trust anyone to do things like you do, because no one is better than you at this. You want to control what’s going on; otherwise you fear that chaos will follow. I’m not asking you to immediately trust everything and everyone. Distrust or trust all you want, but put processes in place that allow you to verify. As for the control thing, concentrate on controlling the future of your consultancy or company, and again, put processes in place that give you visibility – from there you’ll have as much control as you’ll ever need. So stop focusing on trust; think verify. Stop focusing on control; think visibility. Your First Hire If you don’t already have a project manager in place, then you’d better make that hire now. What to look for in a project manager? Someone who is as anal-retentive as you wish you could be, and who leaves a paper trail behind himself (or herself) that is a mile wide. Someone who will set up meetings, take notes during the meetings, send out minutes about the meetings to all participants. Someone who will be the single point of contact between the client and your group. Someone who will control the client when they want to do an end run directly to your developers. Someone who will never quit making, refining, and collating lists of action items. Do they need to have experience in your particular knowledge domain or a certification in project management? No, but these things help. The most important thing, besides attention to detail, is a fearless communication style, one that
is thorough during the calm periods and calm during the stressful times. You want someone who can communicate easily with a client no matter how ticked off the client is about things, and someone who is not afraid to communicate bad news quickly. Now, let’s pause for review. Have you decided that this person should be you? Lean in close, so I can smack you. Your job is not project management. Your job is being the architect, the rainmaker, the visionary. The client doesn’t need you to be the project manager; they need you to be the expert on your project. Every minute you’re tied up in spreadsheets, GANTT charts, and typing up meeting notes is a minute you’re not looking out for them. Other Hires If you’re a small shop, you will need to hire a lead developer, someone who is the best technical expert you can find who can then own the details surrounding the technical work you do. No, you are not abdicating your role as technical guru, but you need someone who can execute on your ideas and vision. If you’re the one in the technical weeds, you’re going to get mowed down by something big you missed. In many ways, this person will be the technical twin of your project manager. This person will be someone who knows the technology you’re dealing with (PHP, Python, XML, whatever) inside and out, and shows a propensity to learn new and upcoming technologies. This person will lead your efforts in acquiring solid infrastructure and will follow your lead on the processes you set down. Will this person be a coder stuck in his or her cave? Hopefully not. They should combine good communication with technical abilities—after all, you’re asking them to take on a lead role. Yes, they will likely be the person who touches 80% or more of the project, but they have to have a strong head for delegating tasks that they don’t have the time or talent to pursue. If you already have a trusted right hand, then evaluate where this person is weakest. If you’re pursuing a project with lots of XSLT requirements, for example, figure out if their expertise covers this domain. If they don’t have it sewn up, figure out if they can learn it in time or if you need to hire someone on a part- or full-time basis to cover that base, or outsource
39
International PHP Magazine 02.2006
Enterprise Best Practices In Enterprise Engagements it to a contractor. The same approach can be adopted for all other areas, such as QA, documentation, and training. Another bonus to the technical lead: He or she can help you hire folks for other slots in the team, and be that person who can play devil’s advocate when you’ve both been on your feet for 20 hours straight and you’re not that sure if you’re thinking straight. Processes and Infrastructure You’ve got a good start on your team, but now you need to put in the support struts. Do you have adequate processes and infrastructure in place to keep things moving forward? Most small shops don’t have much in the way of process or infrastructure, and sometimes for very good reasons. Some of you reading this piece may be shuddering at the very thought, but don’t fear, I’m not advocating the entrenchment of an inflexible bureaucracy. Remember what I said about verification and visibility? These are your watchwords. The most important pieces of infrastructure you can put in are a development server and a source code repository. You can place the development server on your own network, or you can arrange to host a managed server with a local hosting company. Owning this asset means that you have control over your development environment, and can make the changes you need to make—such as recompiling PHP to support your application’s needs, or adding multiple databases—without waiting for the client’s IT department to get around to it. A good source code repository, such as CVS or Subversion (both of which are open source) can allow your team to grow and give all coders access to source code. This will become more crucial as the project proceeds. A good source code repository also allows you to run reports on who is doing what, and whether different objectives are met on time. Subversion and CVS, for example, both allow branching of code to test different approaches and flagging of releases so you can stage out your delivery of code. Coding Conventions Here’s where we get to the part the makes everyone groan, especially junior developers. What does it matter if your
tabs are set to 2 spaces or 4? Who cares if we put the { on the same (or different) line from the if or else? Why should it matter that your method names and variables are camelcased? It may not seem like a big deal, but using the same style of coding conventions throughout your application, like requiring that all class files be kept in the classes/ folder and be named like page.class.php can be very helpful. If nothing else, it can keep everyone on the same page at 2am. I’m not advocating any particular style—that’s your job. Pick an approach and stick to it. Discovery When you worked for those small clients, you got used to working with minimal or non-existent discovery documents. Sometimes all you needed was a short phone call to gather requirements, which you could implement shortly after the conversation ended. Or you got used to working with a simple bullet list of goals and deliverables, or some scribbled notes from a whiteboarding session. Guess what? Those days are long gone. You have to document everything important. Notice I said everything important, not everything. It’s highly likely that discussions you have over the phone, via email, or during face-to-face meetings might amount to nothing at all, but you have to give the client visibility into what you’re learning and allow them to verify what you gathered. There are untold dozens of systems out there that purport to help you gather requirements from clients, encode those requirements properly (technical specs, Visio diagrams, wireframes, and so on), and get feedback and buy-in. I don’t advocate any particular approach; in fact I’ve tried at least five or six different ways myself, with mixed results. You have to find a system that works for you, your team, and your client. Rest assured that with longer enterprise engagements, you’ll need to have an approach that: • Identifies stakeholders and end-users • Captures requirements from them • Prioritizes those requirements
40
International PHP Magazine 02.2006
Enterprise Best Practices In Enterprise Engagements • Encodes those requirements in such a way that the stakeholders understand them • Verifies those requirements • Communicates those requirements to the developers Yes, this means that you might run through a series of documents during the discovery process. For example, you might have a questionnaire on project goals that gets a 10,000 foot view and identifies stakeholders. You might have another document that helps you capture interviews with those stakeholders, and a template that helps you identify end users of the application and gather critical data from them to fill out your use cases. After this initial work, you might hold a large meeting with key stakeholders to report to them what you‘ve discovered, using a PowerPoint presentation followed by a white boarding session that seeks to prioritize goals and functionality. Here’s where a project manager in the meeting can be key: while you’re working the room and the whiteboard, he or she is busy documenting, asking questions, and reminding stakeholders about budgetary and scheduling constraints. Your final requirements deliverables might be a five-page business case, a separate page flow diagram of your application, and supporting use cases. Each section could have a little place where the stakeholders can initial and date to verify your findings. Finally, you might want to deliver this same set of documents to your developers but then operationalize it by working with your lead developer to create a UML diagram of your application. We also take a note from Agile/ Scrum and create note cards that describe each component of the system and then disseminate these cards to the developers responsible for those components. Of course, every document you create gets placed into your source code repository. Any time any one makes a change to a document, the document’s revision section is updated so that anyone can look into the document and find out what things changed when, who made the changes, and what is the current version of the document.
Doing the Work Depending on your agreement with the client, you could be working in any number of modes. For example, you will likely have milestones that call for delivery of different components at certain dates. Or you may have to convert so many documents to XML by a certain date, or install a database patch by this other date. In my experience, most clients, and this includes the most savvy stakeholders at the largest corporations, don’t know what they want until they’ve had a chance to look at it. No matter how much you document it, wireframe it, or describe it, there will be changes made as soon as they see it. When it happens, don’t be angry or frustrated. They are not trying to get you, nor are they trying to make life intolerable for you or erode your profits. It’s just human nature. What you have to watch out for, though, is that the directives you’re getting are coming from a canonical source. Make sure that you work your process. Involve your project manager. Have him or her create a written description of any modification request, especially if it originates as a call or vague e-mail. Take the time to ask pertinent questions, document it all, and then present it to the chief stakeholders to get final approval. Do you charge for every change that comes down the wire? That I leave to you and your relationship with the client. But if you follow the process of verifying and giving your stakeholders visibility, you won’t get burned as often as you would if you just ran off and complied with every halfbaked request. What happens if you don’t take this approach, get lax, or just think to yourself that this little request can be taken care of quickly? Well, let me tell you a story. At one point in our first enterprise engagement, we had over 40 stakeholders engaged with us. There were so many channels of communication open to us that I decided to “help” my project manager by taking on requests. It started innocently enough, of course, little changes here and there, but then suddenly we found ourselves taking dozens of requests every day, as more and more department stakeholders involved other people who
41
International PHP Magazine 02.2006
Enterprise Best Practices In Enterprise Engagements were seeing the application for the first time. We got a request to do X, which was pretty easy and involved removing Y. Within two hours a manager in another group called to ask why X was in there when they had asked for Y.
Doing the QA Dance On most small projects, your job is pretty easy. You code something up, put it in a development or staging environment, do some testing, show the client, who eventually approves it, and you launch it.
When we explained that X had been requested, we were asked for details. Of course, the request for X had come in the form of a vague email, which I forwarded. The person who wanted Y talked to the manager of the person who wanted X and got X countermanded. So we took it out and put Y back in.
Those days are also over.
By the end of the day, the chief stakeholder called me and wanted to know why Y was in when the original specs called for Z. That was true, I told her, but a month before there had been a change order authorizing the change from Z to Y. I did have that change order as a fax, which I faxed to her. It turned out that the signature on the change form was not hers—it was her assistant’s who had been filling in for her while she traveled to a conference in a nearby city. Apparently, nobody had told her about the change, or she had forgotten (doesn’t make a difference either way). At this point, I came to my senses and requested that we a) stop work, and b) have a stakeholder meeting to sort it all out. I also suggested that I extract myself from the process of playing project manager, and that all stakeholders at the client company should use one channel of communication to speak with us to avoid this kind of chaos. I can’t make this stuff up. One more thing. I prefer to work in an iterative cycle once the requirements are set. Whenever enough components have been pulled together and I have a chance to show them to end users or stakeholders, I do it. Why? Because they’ve paid me a lot of money, and they want to be sure that things are coming together. The more they see progress, the better they’ll feel about the whole thing. (And as you’ll see in the acceptance testing section, showing end users and stakeholders your work allows you to collect feedback early and build goodwill.)
First of all, most enterprise projects will involve hundreds of thousands of lines of code (if not more), and have dozens of components, each with their own subcomponents. You have to have some way to QA all of this without killing yourselves. The first step is to work from accurate requirements documents, use cases, and specifications. The second step is to make sure that you touch base with your developers on a regular basis (we have a standing meeting every morning to go over issues, and it lasts 10-15 minutes most days). With these two in place, you’ll nip a lot of problems in the bud and get visibility over the spectrum of activities happening on the project. If you’re using a source code repository like Subversion, each developer can update their local libraries, work on their pieces independently and check them back into your development server. There developers can perform unit testing—to ensure their code does what it is supposed to do according to specs and use cases. Once they’re sure that their code is working okay, developers can perform integration and regression testing—does the module or component I’m working on play well with others? Did I just change something in a core library that breaks another module? Did I add an unexpected parameter that will break something downstream? Throughout the process, you need a way to communicate and track bugs. Some organizations use spreadsheets, others use home grown utilities. I prefer Bugzilla, because it’s free, puts your bug database on the Web, can be used to report on multiple parameters, and has some very good notification features in it. For
42
International PHP Magazine 02.2006
Enterprise Best Practices In Enterprise Engagements example, you can set it up to notify developer Y if any bug that is tagged as Category A is ever entered—so then all your UI bugs go to your UI developer, and rules engine bugs go to the Java geek down the hall. You can set yourself up as a watcher and can get notified whenever bugs are created, change status, get closed, or other possibilities. (Do yourself a favor and put your project manager on watch lists too!) Best of all, Bugzilla can be exported to other formats, such as CSV, which can then be pulled into a spreadsheet for use by your project manager. Furthermore, each time a bug is created, it has to be entered by someone with a username and password, so you can control who has the ability to issue bugs. Any bug reports you get outside of Bugzilla shouldn’t be ignored, of course. Instead, have your project manager enter them into Bugzilla, even if they seem silly. It’s a trivial thing to go through and close bugs that are repeats or shouldn’t be worked on by your developers. Acceptance Testing Seventy percent of all software projects fail outright. When considering the other 30% of “successful” projects, you find many cases where the organizations involved merely declared victory and shut everything down. But why do projects fail? Missing, miscommunicated, or misunderstood requirements are by far the biggest reasons. Hopefully I’ve given you some tools to help make these problems go away. A close second to all this? Lack of end-user acceptance. Why? Because rarely are front-line troops—the ones who will use your application—ever invited to stakeholder meetings. They never get to give any input. Their managers or outside consultants are the only ones you ever meet. Unfortunately, these people don’t have the kind of visibility actual users of your system have. Ignore the end user at your own peril! Most of the time, the stakeholders you’re dealing with don’t know
you need to talk to end users at all. You’re the expert, not them! Even if they present you with reams of documents in which they’ve covered every conceivable request from end users, request a tour of the office or plant floor. Meet some end users. Take them out to lunch. Talk to them early, see what’s bugging them. Spend an afternoon just hanging out watching what happens. How do their processes get them through the day? Where do their processes get in the way? Where do they put in short cuts to avoid bureaucratic nightmares? Does person A from this department do small favors for person B in finance to expedite work orders? If you ignore the end user at the beginning of the process, if you don’t talk to them upstream, you certainly will be talking to them at the end of the process, when you deliver the final application for acceptance testing and/or training. Guess what? Those stakeholders who didn’t give you access to the end users early on will be asking those end users at the end of the project what they think of the new application, and you’ll be judged by what they say. Fair? No. But you didn’t ask, and they didn’t have that kind of visibility into your expertise. Remember, no one can verify you’re on the right track more than an end-user who is an expert on a particular procedure or workflow. Okay, so you’ve either captured requirements from end users or you haven’t, but now the day has arrived and you’re ready to start acceptance testing. How should you go about doing this? Request that the client provide at least two groups of end-users, roughly equal in size. You’ll use the first group as a kind of focus group. They will get a live demo of the system in a conference room, projected onto a wall. Tell them they have been chosen to give their impressions of the application: what works, what doesn’t, what’s missing, and what’s either wrong or needs modification.
43
International PHP Magazine 02.2006
Enterprise Best Practices In Enterprise Engagements With this group it’s crucial to not get into the weeds, even if somebody in the group wants to see an example of the “workflow scheduler for legal notification” that they first requested six months ago. If you’re lucky, you’ll finish the meeting with several bullet lists you can then show your stakeholders and team members, so that any changes can be prioritized and built into the development schedule. With your second group, sit them down one at a time with the application. Sitting with them one on one helps you capture a precise snapshot of each user with as little bias as possible. Tell each end-user that you’re trying to figure out if the system was built to their satisfaction. Most folks in this situation will blame themselves for being stupid about computers when things go wrong, so tell them ahead of time that they are not being evaluated, the system is. You are there to see how they use the system, what buttons they press, and such. Encourage them to think out loud as they work, even if those thoughts are negative. Present them with a task list (from your use cases) and tell them to start. With any luck, the task list mirrors reality, but if it doesn’t, and they speak up about it (“I wouldn’t do it this way”) make sure that you ask them to elaborate while you take lots of notes. (If you have trouble with this part of the work, hire an outside usability consultant to help with the acceptance testing.) Under no circumstances do you argue with the end user. All you do is sit and take notes as they perform tasks. Don’t give opinions, or value judgments. Try to work through each person quickly—if you take up too much time, you may make them anxious, impatient, or tired, any of which can throw bias on to your data. Once you’re done, collate every note gathered from this second group. Notice any patterns? Usually, if one person has a problem with a certain module, then others will have the same experience. If you have five end users in your study, and only one person had a problem with Module A, then you don’t necessarily have
a problem (unless, of course, that one person was the world’s expert on what Module A is supposed to help her with). If four or five in the group have a problem with Module C, you definitely have a problem, but don’t assume that you have to scrap everything. Look through your notes. The answers should be there, especially if you encouraged folks to think out loud. Maybe they had a problem with Module C because the font was too small, or because three widgets they expected to be grouped together were scattered across the screen. Don’t start working on anything until you show your data to stakeholders. Verify what you’ve found and let them help you prioritize everything. Adding a widget or two to the interface might be quick, but may pale in priority to fixing the massive problems associated with the slow database connection. When should you run acceptance testing? I usually run at least two sets, one scheduled about a third of the way through the project, and the second right before completion. This way I can get as much information as I can early on and a second set of data at the end to confirm what we know. Summary Congratulations! You’ve nailed down your first big project with an enterprise customer. But beware: big projects have a number of inner dynamics that make them very different from the smaller jobs you’ve gotten used to. Hopefully this article has shown you how to scale your operations, using the tools of verification and visibility to keep things running smoothly. Thomas Myer is the founder of Triple Dog Dare Media, an Austin, TX web consultancy that specializes in content management systems. He is the author of No Nonsense XML Web Development with PHP, recently published by Site Point.
Questions & Comments • PHP Magazine Forum
44
International PHP Magazine 02.2006
Development The Generation Game
The Generation Game Code Generation and PHP by Jon Ramsey Code generation can provide a useful and efficient approach to solving many problems that crop up during application development. It can cut down the amount of repetitious work that you do. It can also help you to avoid the kind of duplication that leads to real maintenance nightmares. This article will give you a vital headstart in implementing code generation solutions that dramatically cut down on your workload and improve your coding life. I’ll explain code generation, and commonly used approaches and techniques. I’ll provide a brief survey of the use of code generation within open source tools and applications. Finally, I’ll start on development of a tool that you will find useful in your day-to-day work. Genealogy Code generation is the transformation of some input into a specific and useful output(s). There is generally a focus on producing some kind of code as this desirable output, but beyond that the main point is to save us work – this is always a worthwhile goal. It’s likely you’re already something of an expert; after all, most PHP programmers spend a lot of time generating code – most often (X)HTML. Imagine we’re developing a simple database driven web application. In this situation, we’ll transform data from a couple of different sources (database and templates) into a representation that a web-browser can handle, which, ultimately, a user can understand. Our simple PHP scripts will tie these disparate inputs together and generate a cohesive output for the browser to render. This, ignoring user input and considering only the output HTML code, is an example of active code generation. But Can You Expect a Significant ROI? Yes, you can expect to avoid unnecessary work – repetitive, boring grunt work. Generators also help in cutting down on silly mistakes that one may tend to make. They can significantly speed up development, making business people happy and your life less stressful.
Of course, there are no magic bullets. Like with anything else you’ll need to apply your discretion to determine when code generation will be useful and when it’s just more hassle than it’s worth. The rest of this article will give you some assistance in making those decisions. Generational Differences Code generators come in two main flavours: • Passive code generators use a one-time approach – you run a generator and you get your output. Once it has been generated you can do whatever you want with it; the output has no further relationship with the generator. • Active code generators tend to be run repeatedly over the same input (often as part of a build process). The generated output maintains a relationship with the generator, and it can be recreated at any time by running the generator over the same input again. You Only Have a Single Shot, Make it Count Passive code generators can save you from a lot of mindless typing. This is often a good thing, unless of course you need the practice. Listing 1 holds a simple example of a passive code generator, class-generator.php. It creates a skeleton class file, including copyright and comment blocks, and a match-
45
International PHP Magazine 02.2006
Development The Generation Game Listing 1 #!/usr/bin/env php EOD;
Listing 2 #!/usr/bin/env php
} ?> EOD; /* Write the output files */ $fp = fopen($class_file, ‘wb’); fwrite($fp, $class_template, strlen($class_template)); fclose($fp); $fp = fopen($unit_test_file, ‘wb’); fwrite($fp, $unit_test_template, strlen($unit_test_template)); fclose($fp);
ing unit test file. It runs from the command line, and expects a class name as input. The generator is straightforward. The input class name is used to work out the output filenames and
to populate the basic templates that are subsequently written out to file. Although it doesn’t do much else, it does save us a little time, and the point at which it saves time is an important one – it’s annoying to have to stop what you’re thinking about just to carry out some routine task. When I’ve decided I need a new class I want the minimum disconnect between having the idea and starting on the test cases, so I don’t forget anything. A lot of code generation tasks are simple, with no need for complication. The generator simplicity also demonstrates a useful aspect of passive generators; they don’t have to be perfect. You only need to dedicate enough time to the generator so that it’s useful.
46
International PHP Magazine 02.2006
Development The Generation Game As it stands, the generator does no error checking. But in the simple cases where it’s used it works fine; admittedly, though, it could do with a check to ensure it doesn’t overwrite existing files that we’ve already edited.
An obvious example of this knowledge is a table schema. Within an application you may need some or all of the following representations, or artifacts, all based on one source – the abstract table schema:
Did He Say Code, Or Inode? Listing 2 holds another simple passive generator example, dir-generator.php. This script creates a standard directory structure. Let’s suppose I’ve come up with a new web application framework (PHP-on-Prozac). Now my framework demands the use of a mandated directory structure. I can provide this script with my framework and be safe in the knowledge that my users will have the correct directory setup [as long as they run the script].
• • • • • • • •
I provide a nested array representing this ideal directory structure and the generator uses a simple recursive function to create the directories. (Note that I’ve kept the script a bit light on the error checking to keep the example simple.) We’ve now stepped away from generating code, but the intent remains the same: • Don't repeat yourself (the Pragmatic Programmer's DRY principle) • Remove the opportunity for human error to creep in We should remove duplicate effort in the same way that we would remove duplicate code from your codebase – ruthlessly. It saves work and makes things clearer and easier to understand. A Call To Action Active code generators can take the DRY principle a step further. A major benefit they can provide is to prune back multiple representations of knowledge within your system. Ideally, there will be one authoritative representation of a piece of knowledge within your application. Realistically, we’re always likely to need a few representations of the same knowledge for our application to work as it should, or for that knowledge to be communicated appropriately.
Table creation SQL Dummy data insertion SQL User interface code Validation code (server-side PHP, client-side Javascript) A controller script A model class A data access object class Unit tests
You could hand-code all these separate parts, carefully ensuring that you name everything correctly, test each individual part to make sure that it works, and so on. Alternatively, you could keep the schema in one place only, and get everything else to follow on from that. Thus, when you need to change the schema, your changes can be made in one place and you don’t have to run around to change everything else. This is exactly the kind of situation where active code generation could provide you with a solution. Active Types Jack Herrington, editor-in-chief of the Code Generation Network, breaks down active code generators into a number of types in his book ‘Code Generation in Action’. The book itself is a very thorough survey of code generation techniques, with plenty of detailed examples and code (in Ruby) throughout. His breakdown: • Code Mungers: take parts from an input source and transform them into something useful. • Inline-code expanders: transform source code containing specialised markup into production code ready for use. • Mixed-code generators: modify files containing a mixture of generator code and other source code in place. Generated code remains adjacent to generator code so you can easily validate the output (inline-code expanders write output to a different file). • Partial-class generators: build output base classes from abstract definitions. The base classes are
47
International PHP Magazine 02.2006
Development The Generation Game sub‑classed and extended by application developers to provide custom behaviour. With this approach you’ll need to arrive at your interfaces early on to avoid rewrites of the extension classes. • Tier or layer generators: an extension of partial-class generation; the generator takes responsibility for generation of a whole tier of an n-tier application (for example, the data access layer). • Full-domain language (more widely known as a domain specific language): with this approach some part of the application is represented by a mini-language that is targeted to that domain alone. DSLs can be more expressive within their domain than a generalpurpose language could. So programs written in DSLs can be more concise – they are self‑documenting to some extent, and they allow domain knowledge to be conserved. Code generation can be used to transform DSLs into an implementation language (the alternative is to write a custom interpreter). The generator is a compiler for your domain specific language. Generation Gap Although they can offer a lot, it’s unfortunately not all milk and honey with code generators. You could get caught out if you don’t catch some of the problems in time. The main issue to bear in mind is avoiding the over use of generators. Code generation is a tool that you should be aware of, but it shouldn’t be the only tool at your disposal for fulfilling your application’s requirements.
Don’t use passive code generators for automation of copy-paste programming. If you end up discovering a bug in something that you’ve been using all over the place, you’ll end up with even more work on your hands. Code generation doesn’t remove the necessity to factor out commonalities. With active generators the main problem is that you are increasing the knowledge required to deal with your system. If you work in a team, ensure that every member understands how the generator fits into the overall system and how to use it. This problem can be manifested by developers modifying machine-generated code manually. No matter how obvious it is that generated code shouldn’t be modified directly, other developers may decide they can just add a quick fix to sort out a problem. Of course, the next time you run a build you’ve lost a friend and they’ve lost their code. Education is the only way to avoid this; although you should also ensure that you don’t check generated code into source control – use a build system. Of course, this only applies to active generators. Keep any home-brew generators as simple as possible. Aim for low-hanging fruit rather than attempting to come up with the grand unified generator. Like with every other part of your application, if the generator is at all complex you will need to consider design, implementation, maintenance, support, and training. The last three tasks become even more important if you’re using a third-party tool.
With passive code generators where you extend the output, ensure you understand the generated code. Otherwise you may be programming by coincidence, and your rapid development dreams may turn into a maintenance nightmare.
Generation PHP In the next few sections of this article, we’ll step through a few examples of where generators are used in the real world. For the purposes of this article, I will not feature too much detail on any one particular tool. That said, all of the applications are open source, so you can go straight to the source if you’d like to find out more.
If you’re using a passive generator that you’ve knocked up quickly, try and avoid problems like the ones I mentioned earlier – you don’t want to end up accidentally overwriting code that you’ve previously modified [or worse, that someone else has modified].
Passive Aggressive Ruby-on-Rails comes with some passive code generation tools that have made it across into the new PHP framework – Symfony. In the spirit of “convention over configuration”, these scripts are used for tasks such as
48
International PHP Magazine 02.2006
Development The Generation Game creating a standard directory structure for a new application and creating basic template files for a new application module. They work because things always go in a certain place, so why not make it easier to get moving by removing some mindless busywork? In Symfony, the symfony init-project command is used when you want to begin a new project, and symfony init-module is used to start on a new module. These are definitely one-time operations, so the fit with the benefits and limitations of passive generators is perfect. All Munged Up phpDocumentor is a great example of a code munger. The authoritative knowledge representation in this case is your PHP code itself. phpDocumentor uses this representation to create up-to-date API reference documentation for your application. phpDocumentor scans through the source files, building up its own representation of your classes, methods and functions, along with associated metadata. It uses both the structure of the code and JavaDoc style comments (attributes) to glean the information it requires. It generates output in HTML, Windows CHM, PDF and DocBook formats, demonstrating a powerful aspect of code generation; after parsing your input, it’s fairly straightforward to go to all sorts of different outputs. Data Munging EZPDO, an object relational mapping and persistence tool, also uses a code munging approach to offer its services. It provides a lightweight, persistence framework on top of your simple entity classes. Database tables are automatically created. Further, it deals with entity relationships (1:N, N:1, M:N) in a straightforward way. Once again, your code is the definitive knowledge source. Table names are taken from the class names (this can be overridden), and attributes (again, JavaDoc comment style) on class properties are used to define column types and entity relationships. Although a lot of the functionality is provided through EZPDO’s objects directly, code generation comes into the
mix in one specialized area, and it’s used in an interesting way. When using the tool, persistable objects are created using a factory method of a manager object, rather than by directly instantiating them. When creating the object, the manager checks to ensure that the object implements the interface required by the manager to make the object persistable. If this interface is unavailable [and it will be, unless you have deliberately implemented it], the manager wraps the object with a generated decorator object that implements the persistable interface. The generated decorator object could be kept entirely dynamic [and my code generation angle would be kind of shot], but it is actually stored in a compiled (serialized) form to save on work the next time it’s required. This is an example of code generation being used for optimization. Who’ll Be The Next Inline? PHP is often used as an inline code expander, when it is used to generate (X)HTML output for browsers. Arguably, PHP also qualifies as a domain specific language (with the domain in question being web development), but it is also a general purpose programming language. Both WACT’s templating and Smarty use inline code expansion. As with EZPDO, there is a focus on performance optimization. Both provide custom markup for use within page templates; this markup is transformed into standard PHP/ HTML and written out as a ‘compiled’ template. The compiled templates are then served to web site users. This tactic speeds things up considerably. Rather than dynamically transforming the specialised templating languages on the fly, the compiled templates suffer a onetime-hit when the template is created (or updated) and are then just as fast as any other PHP page. Using custom markup allows template authors to create page templates using syntax that’s simpler than PHP itself. It also provides benefits in terms of security (template authors can’t access the more dangerous PHP functions) and consistency – the generated PHP will always
49
International PHP Magazine 02.2006
Development The Generation Game be the same, given the same input; there is less chance of typos and other errors creeping in. Mixomatosis A great example of the mixed-code generator approach is Ned Batchelder’s Cog (a Python program). A while ago I translated Cog into PHP as Precog. To give you an idea of its usage, and of how a mixed-code generator functions, here is an input file, drop.sql, that contains the following: -- [[[precog -- foreach (array(‘table1’, -- ‘table2’, -- ‘table3’) as $table) { -- echo “DROP TABLE $table;\n”; -- } -- ]]] -- [[[end]]]
Precog will process the file in place. Asa result, the drop. sql file will now contain the following:
After building you can pipe the generated SQL into your database and start using the generated PHP classes within your application. But there’s a trick here – rather than using the generated classes directly, you use (generated) stub subclasses where, if you need to, you can override or add behaviour without losing your customizations when the build is run again. This is exactly how partial class generation works. Along with the other functionality it provides, this makes Propel a full-tier generator for data access/persistence. Domain Thing To Remember Propel also gives us an example of the use of a domain specific language in action. The XML files that you create to represent your table schemas and entity relationships are tied to the domain of object persistence. The functionality of the package [as a whole] relies on the syntax of these files. Although this syntax is much more limited than that of a general purpose programming language like PHP, it is expressive enough to provide an elegant solution for dealing with the problem domain. This is a core aim of domain specific languages – to represent something as simply as is possible.
-- [[[precog -- foreach (array(‘table1’, -- ‘table2’, -- ‘table3’) as $table) { -- echo “DROP TABLE $table;\n”; -- } -- ]]] DROP TABLE table1; DROP TABLE table2; DROP TABLE table3;
Cheap Phrills Carrying on the thread of domain specific languages, let’s get to the application. Phrill is a PHP port of the Python tool Twill. It exposes a domain specific language for testing web applications. That is, it allows you to easily create repeatable tests for your work. The source code is provided in the source code folder accompanying the magazine. (Marcus Baker, creator of Simpletest, suggested both the application and the brilliant name.)
-- [[[end]]]
This is a trivial example, but it should give you an idea of what can be achieved. Top Tier Propel, an object persistence and query toolkit, demonstrates both tier generation and partial class generation. It uses XML representations of table schemas and entity relationships as its knowledge source. This data source is used to generate SQL table definition files, and row and table data access classes in a build step that uses Phing.
Phrill uses the Simpletest browser component as a backend. Simpletest is a testing framework for PHP, similar to PHPUnit. Part of its functionality is provided by a scriptable browser component that allows you to mimic user interactions with your web application, and to test the results of those interactions. The key thing here is that the tests are repeatable without human interaction. You can run your tests whenever you change your codebase; if the tests still pass you can be more confident that you didn’t break anything, without having to manually repeat the different tests that you’d need to run to feel secure.
50
International PHP Magazine 02.2006
Development The Generation Game As a simple example, let’s take a test of the International PHP magazine (IPM) web site. Let’s go to the homepage, check the title we expect to receive, follow the link to the article archive, and ensure that a specific archive article is still available. In Simpletest, this test would be expressed like this:
When this test is run, the Simpletest browser will connect to the IPM web site and follow the rest of its instructions, behaving like a standard human-controlled browser all the way; if any of the assertions are incorrect we’ll be alerted that the tests are failing. Now it’s clear what’s going on in the Simpletest script, so long as you know PHP. Hand this to a client though and they’re liable to run away screaming real quick. The intent behind the test can be represented more clearly if we take away all of the PHP boilerplate and concentrate on the essentials; let’s look at the same thing as represented by Phrill: go http://www.php-mag.net
If we can achieve this, we get to the other half of the problem – convincing the domain experts that it’s worth their while to learn the mini-language that we think is straightforward, and training them in its use. Assuming that I can convince my clients that using Phrill makes sense, they can directly specify how their application should respond to user interaction without me, as a developer, having to mediate. Clients get to specify exactly how their system should behave in unambiguous language. This way, I get to work to a clear specification, and I’ve offloaded the test generation onto them too. I can also benefit more directly though –along with code generation, Phrill also provides a shell mode that can save serious amounts of time in generating test cases for applications where the client is unwilling to do it. I can work out my tests in the shell and export the command history to a file. After editing the history file to remove any extraneous stuff, I can run Phrill on it to create Simpletest test cases without all the typing.
title “Cutting-Edge Technologies” follow “Article Archive...” title “Featured Article” find “SQLite - Part 1”
Now that looks a bit more straightforward, doesn’t it? This input can be run through Phrill to generate the Simpletest code listed earlier. It’s a trivial example, but it points towards some interesting applications. Lords of the Domain Part of the promise of domain specific languages is that domain experts, rather than developers, can become re-
The Phrill Of It All Both the shell and the code generator work on the same input, and the output isn’t really too different – it’s just the manner of interaction that changes. I’ll cover the code generation side of things here. If you’re interested in the shell, please have a look at the source code (a good starting point is the top-level Phrill class, lib/phrill.php). The code generator transforms simple statements such as: go http://www.google.com
51
International PHP Magazine 02.2006
Development The Generation Game into output code that a Simpletest WebTestCase can understand: $this->get(‘http://www.google.com’);
So how does it work? The three core parts of the application are the Lexer that is responsible for tokenising the input, the Parser that is responsible for dealing with those tokens, and the Renderer that is responsible for transforming the tokens into the output code. This is a common separation of concerns for code generation. Analysis Paralysis The Lexer (lib/lexer.php), or rather the LineSplitter (lib/linesplitter.php) object that it uses to split the input commands, is probably the most complicated part of the code. The complication arises because of the need to deal with quoted strings within the input commands. We need to split the command go http://www.google.com into the tokens “go” and “http://www.google.com”. This is straightforward enough to deal with using explode(). However, this approach falls down when we need to split the input find “a string” into the tokens “find” and “a string” rather than “find”, “a”, and “string”. I decided to use a look-ahead approach to deal with the problem. The LineSplitter reads through input lines, character by character, until it comes to a quote. It then looks ahead through the line to see if it can find a matching quote. If it can, the quoted string is pulled out in its entirety and treated as a token. We then carry on reading the rest of the input line, character by character, looking for more tokens. The split string is passed back to the Lexer, which passes it on to the Parser as a command and a variable number of arguments. The Parser (lib/parser.php) is responsible for ensuring that the input conforms to what is expected. If so, it passes on input requests to the Renderer. The main check that the Parser uses is a method_exists() call against the renderer object for the ‘command’ supplied by the Lexer. There are also a few special cases that the Parser deals with, but this is the general pattern. If the method does not exist, an error is triggered.
The Renderer (lib/generatorrenderer.php) has a trivial implementation. The Phrill commands are translated into calls that the Simpletest browser will recognize and write to an output buffer. When we finish reading the input, the buffer is written out, along with some boilerplate code. And there we have it, our simple input syntax transformed to Simpletest output. Command And Conquer A lot of code generators are run via the command line. Whilst PHP can be used for command line scripts, a lot of developers aren’t that familiar with the environment. Here are a few tips: • On Linux/Mac, use a shebang line and make the file executable. This will allow you to run the script directly rather than having to pass it as an argument to the PHP CLI interpreter. • If you're running on a Linux or Mac platform check whether you have the env program at /usr/bin/env. If so, rather than hard-coding the path to your PHP cli binary (/usr/local/bin/php, for example) use env on the shebang to search for the program on the target system: #!/usr/bin/env php
rather than: #!/usr/local/bin/php
If you’re running on Windows, include the shebang line anyway, it won’t have any ill-effect. • Use $_SERVER[‘argc’] and $_SERVER[‘argv’] to get at command line arguments. argc is the argument count. argv is an array containing the argument vector from the commmand line (I just Googled the v). The first element in $_SERVER[‘argv’] will be the name of the script itself (this is useful for usage messages). • If you have many command line switches to deal with, consider using one of the PEAR packages available for dealing with this – Console_Getopt and Console_Getargs are both useful options.
52
International PHP Magazine 02.2006
Development The Generation Game • If the script should always run from a specific location in your application directory structure, but you want to be able to run it from anywhere, use dirname() and the __FILE__ constant to work out where to actually run from, relative to the location of the script. The __FILE__ constant will always contain the location of the running script, so if you have a script under a directory app/bin/, but you always want to run it in a directory app/doc, use: $doc = dirname(dirname(__FILE__)) . ‘doc’; chdir($doc);
Now no matter where you run the script from it will always execute from the app/doc directory. • There's a school of thought that says that Command Line Interface (CLI) programs shouldn't output anything they don't have to. Typically, this means silent operation if everything goes well and error messages if there was a problem. There's another school that says you should make users aware of what you're doing as you do it. Choose which makes most sense in your situation. • Use STDIN to deal with data coming into your script, STDOUT to output notifications, and STDERR to output error messages. All three are easily available to your script as streams (php://stdin, php://stdout, and php://stderr). • If your script has to bomb out, send a non-zero exit code with exit() (for example, exit(1)). If a script successfully executes it will return a zero exit code. Anything other than zero means that there was a problem. Returning a non-zero code allows other scripts that may be running yours (for example, a
build script) to detect that there was a problem. PHP itself honours this convention if you hit a fatal error. Conclusion I hope the article has given you a few ideas on how you can use code generation to make your coding life better. It’s a powerful technique when used in the right place. It can also save your typing fingers and help you avoid RSI. Jon Ramsey is a web-developer living and working in Watford, UK.
Resources & References [1] The Pragmatic Programmer, Andrew Hunt and David Thomas, AddisonWesley [2] The Code Generation Network [3] Code Generation in Action, Jack Herrington, Manning [4] Ruby-on-Rails [5] Symfony [6] phpDocumentor [7] JavaDoc [8] EZPDO [9] WACT [10] Smarty [11] Cog [12] PreCog [13] Propel [14] Phrill [15] Twill [16] Simpletest
Questions & Comments • PHP Magazine Forum
53
International PHP Magazine 02.2006
Workshop Peering Into Phorum
Peering Into Phorum An Inside Look At Modifying Phorum Code by Brent Knigge, Boulos Mansour, Andy Cachia
We used the Phorum application for our computing class at university. Our assignment was to modify the Phorum code to include a thread status, to indicate if the thread was open or closed. We thought the assignment would be a walk in the park, but we quickly learnt that some things were not meant to be easy. In this article, we will introduce you to the installation and functionality of Phorum software, its extensive feature set, and walk you through the various files that were modified to attain our end objective.
Introduction Quite a few forums are set up as a peer-to-peer support group. As a consequence, forum messages can be broken down into two broad categories – questions and answers to those questions. Figure 1 illustrates the standard configuration of the main screen displaying various threads that loosely follows a peer-to-peer support group. The people that use peer-to-peer support groups can also be broken down into two broad categories – those that ask questions and those that answer them. So it is with this type of forum use in mind that we want to alter the main screen to display the status of a thread – that is, has the question been answered, or is it still unanswered. Figure 3 shows our modification to the main screen to support the notion of an answered / unanswered thread. (We used Open and Closed, but you can change that if necesary). We’ll first step through the installation and functionality of Phorum software. The installation is easy, and the features available for the Phorum application are very extensive. The remaining sections are broken down and titled by the various files that we modified. In most cases we have provided a quick summary of what the file is for, and the modification we did (this can be seen in the
highlighted tables). Finally, we wrap up with a conclusion and a couple of links related to the article.
Status We use the definition of status in this article to mean “is the article open or closed (the value is stored in a field called closed)”. There is a field name in the same table called status. However, unless explicitly stated, when we refer to status, we mean “is the article open or closed”, not the field named status.
Phorum Installation The Phorum application can be downloaded from www.phorum.org, the official website for the application. The download comes in a compressed zip file that contains all Phorum files as well as installation instructions. These instructions can be found in the docs directory. They are easy to follow with only one file that needs modification. The file, config.php (it comes with a .sample extension which needs to be dropped) is where you need to specify the database connection, usernames and passwords, and so on. The file is found in the include/db directory, and when you’ve finished making adjustments for the database connection, the entire code can be uploaded to the web server, from where you can proceed to the second stage of installation.
54
International PHP Magazine 02.2006
Workshop Peering Into Phorum Phorum Installation Installation guidelines for Phorum software can be found in the install.txt file under the docs directory.
The second stage of installation requires you to run the admin.php script, where you’ll be asked for an administrator username, password and e‑mail address. The only other interaction you have with the second stage of the installation process is to click a series of continue buttons that inform you of the status of the installation. It’s a straightforward and easy installation, and we’re not going to bore you anymore with the banalities of it here.
Fig. 1: The main screen of the Phorum software (with a couple threads) before any modification has been done
Phorum Functionality Before jumping in and modifying the code, it’s a good idea to play around with the Phorum application first. Get a feel for how it works, and the functionality it offers. We were suitably impressed with the vast array of features that it has to offer. During our exploration of the application we started looking at the tables that the installation process created. In total there were 13 tables, but the table we were interested in the most was phorum_messages. This is the table that stores all the messages in the forum. Examining the table structure, we found two fields that were particularly interesting – status and closed. Keeping in mind that our goal was to modify
the phorum software so the user could set the status of open or closed for a thread, we thought that maybe the application already provided the functionality we were seeking to add. Our immediate goal now was to find out how these two particular fields were used in the application.
phorum_messages table Stores all the forum messages. The closed field is used by Phorum to determine if the thread has been closed, and we’re also going to use this field for the same purposes.
Our preferred method of analysis in this case was to post a few messages, click on a few links and see what effect our actions had on the table, and in particular the status and closed fields. What we discovered was that the application did indeed have the ability to set a thread as open or closed, however only a moderator had those privileges. Did this mean that we could take a short cut with our assignment? Unfortunately no, and as our lecturer pointed out, there were several issues with the short cut approach that probably couldn’t be resolved. • Figure 2 shows the additional features that a moderator has access to. One of the options available is to close the thread; task complete! However other options available include the ability to edit someone else’s post. Hide the message and replies or even delete the message and entire thread. That’s far too much power to bestow upon the general users, and how many people like Scott (who is logged on as a moderator as seen in Figure 2) can we really trust with these sort of privileges? In all likelihood granting these privileges to all users is likely to render the forum ineffective. • The availability of the features for a moderator requires that the person be logged in. It also requires manual intervention (as best we could tell) by the administrator to ‘grant’ the user moderator privileges. Hardly an ideal system if we have to manually alter privileges for each and every member that signed up to the forum. • Finally, the features seen in Figure 2 are only available for existing posts, not for the one you’re currently creating. Thus, if you wanted to close your thread with a final thank-you note (because your question had been
55
International PHP Magazine 02.2006
Workshop Peering Into Phorum answered) then you need to first post your thank-you, then go back into the thread to close it. A two-step process that is hardly viable.
we would need to be able to interpret code, and make sense of the application directory structure if we were to succeed in our task. Examining the directory structure of Phorum software, we discovered that it comprises of .php files for business logic, and .tpl files for presentation. We also discovered that the presentation layer was written using smarty templating; another concept we’d have to try and figure out.
list.tpl Found in the templates/default directory. It is the template file that describes how the main screen of the forum looks (see Figure 1). We added the status column, which can be seen in Figure 3.
Fig. 2: The ‘Close this thread’ option is only available for a moderator
Although the existing application did have some resemblance to what we wanted to achieve, it was clear to us that there would be too many compromises had we simply allowed everyone to log on as a moderator. Besides that, it would have made for a very short assignment anyway.
Examining the list.tpl file we found the HTML code (a snippet is shown below) for the table headers that we see in Figures 1 and 3. Without much of an understanding of Smarty, we found the labelling system rather unique. We interpreted that LANG->Posts and LANG ->StartedBy translated into Posts and Started By on the screen as seen in Figure 1 and Figure 3. The underlying question though is “what is LANG?” and “why use it and not just a normal text label?” (We could replace {LANG->Posts} with Posts and there would be no difference in appearance).
List.tpl file Figure 1 shows the non-modified version of the main Phorum screen. It’s a standard setup that you would expect of any forum software displaying a list of the threads on that forum. The only thing it doesn’t have is a status field to indicate if the thread was still open or not. Our lecturer warned us that we would encounter programming techniques that were not covered in class, or the course text book for that matter. He did say that
Fig. 3: A modified version of the main screen with the status field added
56
International PHP Magazine 02.2006
Workshop Peering Into Phorum
Searching the Phorum directory structure, we found a file called english.php under the include/lang folder. Examining the contents of this file, we discovered a large array that contained ‘translations’ of variable names to text names. In essence, what this means is that no text label that appears on the Phorum screens is hard coded; it all comes from a translation array. So by choosing the default language of English (in the general settings of Phorum’s admin section) we included english.php into our files. Thus the variable LANG->Posts was translated into the text label Posts, which is what we see in Figures 1 and 3. Had we chosen a different language (again the language can be chosen from Phorum’s admin section), that language file would have been loaded and the variable LANG->Posts translated into a text label relevant to that language. It’s all very cool and professional so we decided to adhere to the same principles and add our own entries to the translation array.
ourselves. There is nothing deep and meaningful about bba, it’s just the initial of the first name of the three authors. Figure 3 shows the status field between Started By and Last Post. Following the convention used by Phorum code, we can add the following snippet of HTML code to the section where the table header has been declared (making sure that it is between the Started By and Last Post header labels).
The next bit is to add the actual status of the thread to the table. Scrolling a little past the header labels of the table, we get to the meaty details section. The snippet of code shown below is the table cell section that corresponds to the header labels mentioned earlier: PhorumSmallFont” nowrap=”nowrap”> {ROWS->lastpost}
Naturally the status of the post (where we display either Open or Closed) needs to be displayed in between the linked_author and lastpost. But before we do that, we need to ascertain that the relevant field is retrieved from the phorum_messages table; we’re going to cover that in the next section. mysql.php mysql.php is the script that basically contains all the SQL statements for the forum. All the database interac tions that occur with the 13 tables, created when Phorum was installed, occur in this script. There are three SELECT statements that are used with list.php. They are all in close proximity to each other, and have the following coding convention. $sql = “select $table.author, $table.datestamp, $table.email,
57
International PHP Magazine 02.2006
Workshop Peering Into Phorum … $table.status, $table.subject, $table.thread, …
mysql.php Found in the include/db directory. It contains the SQL queries for the Phorum application. We made modifications to the SELECT statements used by the main screen and added a function call to close the thread if needed.
the translation array in the english.php file). Remember we said that the presentation layer was written with Smarty templating? Well after a bit of trial and error we came up with some Smarty code that displayed our text labels in the status field. That nifty bit of Smarty code is shown below (and placed between the table cells of the linked_author and lastpost in the list.tpl file). {IF ROWS->closed 1}