This copy is registered to: Rodney Burruss
[email protected]
TM
FEATURES 9
Total Eclipse of PHP Development
CONTENTS
COLUMNS 5
EDITORIAL
7
php|news
Banish your text editor to the dark ages! by Alexander J. Tarachanowicz II
45 TEST PATTERN
Dependency Injection
18 Long Live the Code!
A simple technique that improves the reusability of your code by JEFF MOORE
Refactoring towards OOP by STEFAN PRIEBSCH
50 SECURITY CORNER
All Your Session Are Still Belong to Us!
27 PHP Clustering on Linux Part 2
by ILIA ALSHANETSKY
58 PRODUCT REVIEW
by JOSEPH H. KOUYOUMJIAN
PHP Protector:
The Castle for your Code? by PETER B. MACINTYRE
37 PHP and XForms Building next generation HTML forms by RUBÉN MARTÍNEZ ÁVILA
64 EXIT (0);
Are We Losing LAMP? by MARCO TABINI
Download this month’s code at: http://www.phparch.com/code/
WRITE FOR US!
If you want to bring a php-related topic to the attention of the professional php community, whether it is personal research, company software, or anything else, why not write an article for php|architect? If you would like to contribute, contact us and one of our editors will be happy to help you hone your idea and turn it into a beautiful article for our magazine. Visit www.phparch.com/writeforus.php or contact our editorial team at
[email protected] and get started!
NEXCESS.NET Internet Solutions 304 1/2 S. State St. Ann Arbor, MI 48104-2445
http://nexcess.net
PHP / MySQL SPECIALISTS! Simple, Affordable, Reliable PHP / MySQL Web Hosting Solutions P O P U L A R S H A R E D H O S T I N G PAC K A G E S
MINI-ME
$
6 95
SMALL BIZ $ 2195/mo
/mo
500 MB Storage 15 GB Transfer 50 E-Mail Accounts 25 Subdomains 25 MySQL Databases PHP5 / MySQL 4.1.X SITEWORX control panel
2000 MB Storage 50 GB Transfer 200 E-Mail Accounts 75 Subdomains 75 MySQL Databases PHP5 / MySQL 4.1.X SITEWORX control panel
P O P UL AR RES ELLE R H OS TIN G PAC KA G ES NEXRESELL 1 $16 95/mo 900 MB Storage 30 GB Transfer Unlimited MySQL Databases Host 30 Domains PHP5 / MYSQL 4.1.X NODEWORX Reseller Access
NEXRESELL 2 $ 59 95/mo 7500 MB Storage 100 GB Transfer Unlimited MySQL Databases Host Unlimited Domains PHP5 / MySQL 4.1.X NODEWORX Reseller Access
: CONTROL
PA N E L
All of our servers run our in-house developed PHP/MySQL server control panel: INTERWORX-CP INTERWORX-CP features include: - Rigorous spam / virus filtering - Detailed website usage stats (including realtime metrics) - Superb file management; WYSIWYG HTML editor
INTERWORX-CP is also available for your dedicated server. Just visit http://interworx.info for more information and to place your order.
WHY NEXCESS.NET? WE ARE PHP/MYSQL DEVELOPERS LIKE YOU AND UNDERSTAND YOUR SUPPORT NEEDS!
NEW! PHP 5 & MYSQL 4.1.X
php 5
4.1.x
We'll install any PHP extension you need! Just ask :) PHP4 & MySQL 3.x/4.0.x options also available
php 4
3.x/4.0.x
128 BIT SSL CERTIFICATES AS LOW AS $39.95 / YEAR DOMAIN NAME REGISTRATION FROM $10.00 / YEAR GENEROUS AFFILIATE PROGRAM
UP TO 100% PAYBACK PER REFERRAL
30 DAY MONEY BACK GUARANTEE
FREE DOMAIN NAME WITH ANY ANNUAL SIGNUP
ORDER TODAY AND GET 10% OFF ANY WEB HOSTING PACKAGE VISIT HTTP://NEXCESS.NET/PHPARCH FOR DETAILS
Dedic ate d & Managed Ded i ca te d se rv e r sol u t i on s a l so av a i l ab le Serving the web since Y2K
EDITORIAL Volume 5 - Issue 6
All the Latest ... Olds
P
art of my job (which, incidentally, has also been one of my hobbies) is keeping on top of technical news and developments. I read a number of technology sites as part of my daily routine. I’m a 5-digit Slashdot member (I registered somewhere between the 10,000th and 99,999th member— current registrations are about to cross over into 7-digit territory; I’ve been reading /. for around 8 years, now). For the past year or so, I’ve mostly given up my obsessive Slashdot-reloading addiction in favour of Digg, although I do still read Slashdot occasionally. I’m not alone, here—according traffic monitoring services like Alexa, Digg’s traffic now regularly exceeds that of Slashdot. Slashdot’s once-near-monopoly (ironic considering part of the site’s popularity was due to campaigning against Microsoft’s monopoly) on tech news has fallen to new-generation Web sites. Also like many others, I’m accustomed to reading my news with an RSS aggregator (an application that parses news feeds from many sources and displays them in a common manner). Together, these factors have reminded me of something interesting: the technology world is extremely incestuous. Take today’s news for example. This morning, I read about how to “Make your own air conditioner” with a cooler full of ice, a fan and some copper tubing on the Make Magazine weblog. An hour or two later, this same story (with a slightly different write up) appeared on Digg. After lunch, I noticed it on Gizmodo (another gadget-filled blog). Allegedly article originally appeared on Lifehacker—a blog that I don’t actually read… directly. This cross-site aggregation isn’t abnormal; it happens on a daily basis. This type of thing isn’t limited to General Technology, either. We’re guilty of it in the PHP world. A few months back, I found a PHP article that was originally posted on a weblog, then linked from another, reported on at a PHP news site and the report was then aggregated to a 4th (or 5th? I lost count) site. If you happened to be unlucky enough join only at the end of the chain, you would either be forced to endure a half dozen or so clickthroughs, or you would give up before ever seeing the article. So, how can we help? Well, as you know, php|architect is an excellent source of original content, and there are some sources on the Web, but we thought: how better to foster new php|a authors, while contributing to the community (and as a result, increasing traffic to our sites) than with a repository of original PHP content on the Web? Thus was born A/R/T, The php|architect Article Repository (http://hades.phparch.com/artemis/main/), one of our newest projects. Stop by, subscribe to the feed, and if you’re so inclined, send an article proposal! We’re excited about it, and we hope you are too.
Publisher Marco Tabini Editor-in-Chief Sean Coates Editorial Team Arbi Arzoumani Steph Fox Peter MacIntyre Eddie Peloke Graphics & Layout Aleksandar Ilievski Managing Editor Emanuela Corso News Editor Leslie Hill
[email protected] Authors Rubén Martínez Ávila, Ilia Alshanetsky, Joseph H. Kouyoumjian, Peter B. MacIntyre, Jeff Moore, Stefan Priebsch, Tobias Schlitt, Marco Tabini, Alexander J Tarachanowicz II php|architect (ISSN 1709-7169) is published twelve times a year by Marco Tabini & Associates, Inc., P.O. Box 54526, 1771 Avenue Road, Toronto, ON M5M 4N5, Canada. Although all possible care has been placed in assuring the accuracy of the contents of this magazine, including all associated source code, listings and figures, the publisher assumes no responsibilities with regards of use of the information contained herein or in all associated material. php|architect, php|a, the php|architect logo, Marco Tabini & Associates, Inc. and the Mta Logo are trademarks of Marco Tabini & Associates, Inc.
Contact Information: General mailbox:
[email protected] Editorial:
[email protected] Sales & advertising:
[email protected] Printed in Canada Copyright © 2003-2006 Marco Tabini & Associates, Inc. All Rights Reserved
5 • php|architect • Volume 5 Issue 6
news •
Taco HTML Edit 1.7.2 What is it? The Taco Software homepage describes it as “Taco HTML Edit is free software for Mac OS X. It is designed to simplify the process of creating attractive web sites that render correctly in various browsers. Taco HTML Edit includes tag wizards, which generate HTML markup for you. Taco HTML Edit also helps find errors in your HTML markup, and it can even check spelling in your documents. For those people who use PHP scripts in their development, Taco HTML Edit includes tools for PHP management.” Work on a Mac and want to give it a try? Get all the info from http://tacosw.com/htmledit/.
New PDFlib Book Released
eZ Components 1.1
We are proud to announce the release of our latest book in the “Nanobooks” series called Beginning PDF Programming with PHP and PDFlib. Authored by Ron Goff, this book provides a thorough introduction to the great capabilities provided by the PDFlib library for the creation and manipulation of PDF files. The book features a foreword by Thomas Merz, the original author of PDFlib and founder of PDFlib GmbH, and tackles topic like PDF file creation, fonts, text, shapes and much more, including PDFlib’s Block Tool, which allows for the manipulation of existing PDF documents. For more information, http://www.phparch.com/pppp
visit
eZ Systems has announced the latest release of eZ Components version 1.1. They announce:”The eZ components 1.1 have been released. Major new features include the new Template component, mail parsing functionality for the Mail component, and a package for manipulating database structures. During the past five months, we have been working on numerous additions to the eZ components. Besides the occasional bugfixing, we worked hard on the new components to provide even more functionality. Major highlights in this new release include: •
•
Template: A compiling template engine with functionality for output escaping. The generated PHP code is optimized for speed. Mail: New classes for parsing e-mail in the most common formats with support for MIME messages and attachments as well.
DatabaseSchema: A package for manipulating and comparing database structures, with a backend that integrates with PersistentObject to automatically generate definition files for that component.”
For more information or to download, http://ez.no/community/news/ visit
ez_components_1_1_released
cURL 7.5.14 Need to grok some URLs? Check out the latest version of curl. Never heard of it? curl is: ”a command line tool for transferring files with URL syntax, supporting FTP, FTPS, TFTP, HTTP, HTTPS, TELNET, DICT, FILE and LDAP. curl supports SSL certificates, HTTP POST, HTTP PUT, FTP uploading, HTTP form based upload, proxies, cookies, user+password authentication (Basic, Digest, NTLM, Negotiate, kerberos...), file transfer resume, proxy tunneling and a busload of other useful tricks.” Changes in this release include: • NTLM2 session response support • CURLOPT_COOKIELIST set to “SESS” clears all session cookies • CURLINFO_LASTSOCKET returned sockets are now checked more before returned • curl-config got a --checkfor option to compare version numbers • line end conversions for FTP ASCII transfers • curl_multi_socket() API added (still mostly untested) • conversion callback options for EBCDIC ASCII conversions CURLINFO_FTP_ENTRY_ • added PATH • less blocking for the multi interface connect (Open)SSL during
Volume 5 Issue 6 • php|architect •7
• Grab
negotiation Several bug fixes. the
latest
version
from
http://curl.haxx.se/
Check out the hottest new releases from PEAR.
Looking for a new PHP extension? Check out some of the latest offerings from PECL.
PHP_Shell 0.3.0
WinBinder 0.46.0
An interactive PHP Shell with tab-completion, inline help and handling of FATAL errors.
The PEAR Numbers_Words package provides methods for spelling numerals in words.
WinBinder is a new extension that allows PHP programmers to build native Window applications. It wraps the Windows API in a lightweight, easy-to-use library, so that program creation is quick and straightforward.
PHP_CompatInfo 1.0.1
pecl_http 1.0.0
Numbers_Words 0.15.0
ATK 5.6.0 Ivo Jansch announces a new release of the Achievo Tool Kit. On his blog he writes: “We’ve just released ATK 5.6.0, the next stable version of the Achievo ATK framework. This release contains over 140 changes and improvements. Some highlights of the changes since ATK 5.5: • 5 new attributes, from a simple attribute for manipulating URLS to a flexible calculator that can add columns containing arbitrary calculations of other attributes, and a generic attribute that can turn any other attribute into an internationalised data field. • A new ‘shuttle’ relation for easy selection of records in many-tomany relationships. • 2 new themes, ‘macoslike’ and ‘t3skin’. The latter creates a Typo3 4.0 look for ATK, useful for integrating ATK applications into a Typo3 environment. • An AJAX based generic data structure search functionality. • An atkMessageQueue for relaying messages to the user. • Many API enhancements (new methods, new features and new flags) And many more fixes and improvements.” Get all the latest info here: ht t p : / / w w w. a c h ie v o . o rg / b l o g / a rc h i v e s / 41-ATK-5.6.0-released.html
Find out the minimum version and the extensions required for a piece of code to run.
PHPUnit2 3.0.0alpha10
PHPUnit is a regression testing framework used by the developer who implements unit tests in PHP. This is the version to be used with PHP 5.
Validate_Finance 0.5.3 Validation class for Finance.
Pager 2.4.2
Pager takes an array of data as input and pages it according to various parameters. It also builds links within a specified range, and allows complete customization of the output (it even works with front controllers and mod_rewrite). Two operating modes available: “Jumping” and “Sliding” window style.
PHP_Beautifier 0.1.10
This program reformats and beautifies PHP 4 and PHP 5 source code files automatically. The program is Open Source and distributed under the terms of PHP Licence. It is written in PHP 5 and has a command line tool.
This HTTP extension aims to provide a convenient and powerful set of functionality for one of PHPs major applications. It eases handling of HTTP urls, dates, redirects, headers and messages, provides means for negotiation of clients preferred language and charset, as well as a convenient way to send any arbitrary data with caching and resuming capabilities. It provides powerful request functionality, if built with CURL support. Parallel requests are available for PHP 5 and greater.
runkit 0.9
Replace, rename, and remove user defined functions and classes. Define customized superglobal variables for general purpose use. Execute code in restricted environment (sandboxing).
rar 0.3.0
Rar is a powerful and effective archiver, which was created by Eugene Roshal and became rather popular quite quickly. This extension allows you to read Rar archives.
SPL_Types 0.2
SPL Types is a collection of special typehandling classes.
PEAR_ PackageFileManager 1.6.0b2
PEAR_PackageFileManager takes an existing package.xml file and updates it with a new filelist and changelog.
Volume 5 Issue 6 • php|architect •8
FEATURE
Total Eclipse of PHP Development
T O TA L OF PHP DEVELOPMENT Still stuck in the Dark Ages of the Web and using a text editor as your primary development tool? What’s stopping you from taking that leap of faith and casting your trusty text editor aside for a full-scale IDE? Don’t feel like dishing out big bucks or paying for upgrades? Luckily for you there is an open source alternative— PHPeclipse.
SOFTWARE: eclipse 3.1.x: http://download.eclipse.org/eclipse/downloads/ J2SE5: http://java.sun.com/j2se/1.5.0/index.jsp DBG PHP Debugger: http://dd.cron.ru/dbg/downloads.php MySQL JDBC Driver: http://dev.mysql.com/downloads/connector/j/
LINKS: PHPeclipse Developers Guide:
http://plog4u.org/index.php/Developing_PHPEclipse
Seagull Subversion Repository:
http://svn.seagullproject.org/svn/seagull/
Seagull Wiki: http://trac.seagullproject.org/ TO DISCUSS THIS ARTICLE VISIT: http://forum.phparch.com/308
T
his article will cover the various aspects and features available for Eclipse, and how to properly use it for PHP Development. I will illustrate how using an IDE can increase your productivity and save both your own and your clients’ time and money, resulting in a less stressful work environment and essentially creating a coders’ utopia. Although we will be discussing other plugins as well, the PHPeclipse plugin will be the main focus of this article. The PHPeclipse project originally began in July 2002 with a small handful of developers, and over the past 4 years has grown significantly to accommodate 31 registered developers on SourceForge. PHPeclipse is a plugin for the Eclipse Integrated Development Environment (IDE), which was itself released into open source in November 2001 by IBM. This resulted in the formation of the Eclipse Consortium, founded by industry leaders: IBM, Borland, Red Hat, SuSE, MERANT, QNX, Rational Software, TogetherSoft and Webgain. In February of 2004, it was reorganized as a not-for-profit corporation, known today as the Eclipse Foundation. Eclipse is both an extensible development platform and a language-neutral IDE. It includes many of the standard features that are found in commercial IDEs today, but at less than a fraction of the cost—it’s free! That alone makes Eclipse an enticing prospect to both the aspiring Web developer and the seasoned professional. Eclipse comes with all the necessary tools for Java development as standard, which is where PHPeclipse comes into play. PHPeclipse provides a PHP-specific implementation of the standard features found in the Eclipse IDE. Volume 5 Issue 6 • php|architect • 9
Total Eclipse of PHP Development
Eclipse is both an extensible development platform and a language-neutral IDE. Installing Eclipse Eclipse has to be one of the easiest programs I have ever had pleasure of installing, with the most painful part being the time it takes to download—it weighs in at roughly 100 MB. The ease of installation is largely due to the fact that Eclipse is a Java-based application. Therefore its only prerequisite is the Java Runtime Environment (JRE), which in itself allows Eclipse to be platform independent. I will outline the necessary steps to install Eclipse on various operating systems below; you can begin the installation process by downloading the latest available version of Eclipse and referring to the specific details for your operating system. Note: The Eclipse download page will automatically detect your operating system and display a link to the appropriate file. If you are using a machine that has an operating system other than the one you are planning to install Eclipse on, click on the Other downloads for 3.x.x link.
disable this feature, remove write permission from the features and plugins directories for the group users. •
mkdir /home/username/workspace
•
•
Extract
the
downloaded
file
a
shortcut
with
the
command
Mac OS X •
Upon completion of the download, you will be prompted with a message stating that the downloaded file may contain an application. If you choose to ignore this warning, the downloaded file should automatically begin to self-extract. Otherwise, find the file named eclipse-SDK-3.x.x-macosx-carbon.tar.gz and double-click on it to extract the contents.
•
Next,
find
the
extracted
file and double-click on it to extract Eclipse to your desktop. eclipse-SDK-3.x.x-macosx-carbon.tar
•
You should now have an eclipse directory on your desktop. Next, double-click on the Hard Drive icon on your desktop and then doubleclick on the Application folder. Then drag the eclipse folder from your desktop to the Application folder.
•
Create
eclipse-SDK-3.x.x-linux-gtk.tar.gz to the /opt directory. If you do not have root access,
you can install Eclipse in your home directory. tar -xvzf eclipse-SDK-3.x.x-linux-gtk.tar.gz /opt
•
Create
/opt/eclipse/bin/eclipse -data /home/ username/workspace. There is an Eclipse icon named icon.xpm available for use in the /opt/eclipse directory.
Linux Many Linux distributors provide an Eclipse package in their software repository to maintain system integrity. Please check your distribution’s software repository before proceeding and verify whether Eclipse version 3.1.x is available. If you install Eclipse from a repository, the only required step below is the creation of the workspace directory in your home directory.
Now, create a workspace directory in your home directory. This directory will be used to store your Eclipse projects.
If you installed Eclipse in /opt, you will next need to grant all users permission to access it there.
a workspace directory in the /Users/ directory. This directory will be used to store your Eclipse projects.
Windows chown -R root:users /opt/eclipse/eclipse
WARNING: By default, users will be able to install features and plugins in the /opt/eclipse directory. To
•
Uncompress
the
downloaded
file
eclipse-SDK-3.x.x.win32.zip to the directory C:\Program Files.
Volume 5 Issue 6 • php|architect • 10
Total Eclipse of PHP Development •
Create a new directory named workspace in which to store your Eclipse projects.
•
Create
a
shortcut
with
the
command
C:\Program Files\eclipse\eclipse.exe -data
Finally, for all platforms, you will need to configure your Web server so that the workspace directory is accessible from your browser. If you are running Apache, simply add a directory alias to your httpd.conf. If you are using a remote Web server, you should create a share and mount the directory locally as your project workspace. Now, fire up Eclipse, and assuming it has installed correctly you should see the startup splash screen.
for features to install, select Search for new features to install and click Next; you will be presented with the Feature Install dialog. We will now install the following features from a remote site: the Eclipse Web Tools Platform (WTP), Subclipse (the Subversion plugin), and of course PHPeclipse. Since the process is almost identical for the other two installation methods, I will only outline the steps necessary to install a feature from a remote site here. See Figure 1 for a list of Eclipse plugins and their URLs. •
Start by clicking on New Remote Site, and you will be prompted with a dialog asking you to enter a Name and URL.
•
Enter the name of the feature (i.e. Web Tools, PHPeclipse, or Subeclipse) you are installing and the proper URL (referring to Figure 1). Confirm by clicking on OK.
•
Repeat the above two steps for each plugin you wish to install.
•
When you have finished adding features from remote sites, check the box next to the Eclipse. org update site. The Install dialog should now look similar to Figure 2.
•
Finally, click on Finish and you will be prompted with another dialog asking you to select a mirror for Eclipse and the Web Tools feature. Select a mirror close to your geographical location and click OK.
•
Eclipse will now populate the search result list.
Installing Features The integrated Update Manager allows you to install, upgrade and manage every aspect of Eclipse’s features from one central location. It also lets you save and restore feature configurations, enable and disable features, and schedule automatic updates. There are three methods available for installing features: Remote Site, which can install features from a remote location (URL); Local Site, which installs features locally (CDROM); and Archived Site, which can install features from a .jar or .zip file. To access the Update Manager, you can simply click Help->Software Updates and choose either one of Find and Install and Manage Configuration. Since we are about to install the features necessary for PHP development, select Find and Install. When you are prompted with a dialog asking you to Choose the way you want to search FIGURE 1 REMOTE SITES FEATURE Eclipse Web Tools PHPEclipse – PHP Developemnt Subclipse – Subversion Plugin
URL http://download.eclipse.org/webtools/updates/ http://phpeclipse.sourceforge.net/update/releases/ http://subclipse.tigris.org/update_1.0.x/
ARCHIVED SITES FEATURES HtmlTidy – Html Tidy SimpleTest – Unit Testing GraySky Logwatcher – Log monitor Solex – Web Application Testing
URL http://sourceforge.net/projects/eclipsetidy/ http://sourceforge.net/projects/simpletest/ http://www.easyeclipse.org/site/plugins/graysky-logwatcher.html http://www.easyeclipse.org/site/plugins/solex.html
ECLIPSE RESOURCES NAME EasyEclipse Eclipse Plugin Central EclipsePlugins
URL http://www.easyeclipse.org/site/home/ http://www.eclipseplugincentral.com/ http://eclipse-plugins.2y.net/eclipse/ Volume 5 Issue 6 • php|architect • 11
Total Eclipse of PHP Development When this is complete, you will be prompted with a dialog asking you to Select features to install from the search result list. Check the boxes next to the entries for Eclipse Web Tools, PHPeclipse and Subclipse. NOTE: If you wish to install FTP and WebDAV support, uncheck the box to the left of Show the latest version of a feature only. Then expand both the Eclipse.org update site and 3.1.x entry. Now check the box for the Eclipse FTP and WebDAV support entry. •
For simplicity’s sake, I will not go into detail, however, you can greatly reduce the download time by unchecking all of the SDK entries under the Eclipse Web Tools entry except the Web Standard Tools (WST) SDK. Then click on Next.
•
You will now be prompted with a dialog asking you to agree to each feature’s license. After reading the license, select the option I accept the terms in the license agreement and click Next.
•
After this, you will be prompted with a dialog containing the selected features to install. If you do not want to install the selected features system-wide, or if you’ve disabled the ability for users to install features, click on Change Location and select a new location. Otherwise, click on Finish.
•
Eclipse will now download the requested items. When the download is complete, you will be prompted with a Feature Verification dialog warning that you are about to install an unsigned feature. Click on Install All, and Eclipse will verify each feature’s package and begin the installation.
•
Eclipse will now prompt you with a dialog recommending you restart the workbench for the changes to take effect. Click Yes.
Overview of Eclipse Workbench Eclipse should have now restarted. Go ahead and close the Welcome tab. Eclipse uses something called perspectives, which contain multiple views and may contain other perspectives. Perspectives are designed to help you quickly accomplish the current task at hand. For example, the Debug perspective is designed specifically for debugging your application, and the PHP perspective for editing PHP source code files, while the SVN Repository Exploring perspective is for administrating and viewing
the contents of a subversion repository. You will switch perspectives frequently throughout the course of your day. Let’s add a couple of perspectives. Click on the icon in the upper right-hand corner of a window with a plus sign, which is circled in Figure 3, and select Other... from the drop down menu. This is known as the Open Perspective icon, and the tool bar it is located in is the Perspective tool bar. Select PHP when you are prompted with a dialog asking you to choose a perspective, and click OK. Repeat this process to open the following perspectives: Debug, SVN Repository Exploring and Team Synchronization. I personally find the text labels a tad irritating, so we’ll make those vanish next. Right-click on any of the icons to the right of the Open Perspective icon and select Show Text from the drop down menu. This will remove the check mark, and those pesky labels should now be gone. (You can always re-enable text labels using the same process.) Next, click on the PHP icon in the Perspective tool bar. Doing this will switch you to the PHP perspective. Before we proceed, let me explain the functionality of a couple of the different views. The Outline view provides an overview of the classes, variables and functions for the current file. However, the Outline view is not specific to PHP, and if you have an HTML or XML file open it will display a tree containing all of that file’s elements. This comes in very handy when you need to make sure that elements are nested correctly. The Navigation view allows you to open files from a project and perform various actions on a file by right-clicking on it. The Console view is used to provide you with a variety of information. For example, when you are working with a Subversion repository, the Console view will display the files that are being downloaded or the files being uploaded. You will soon learn that Eclipse is extremely dragand-drop friendly, with almost every aspect of the workbench being drag-able. The animated effect of the windows minimizing to the lower left hand corner should have given you a hint as to where the Fast View tool bar is located. Personally, I prefer to move it below the Perspectives tool bar, keeping everything close together. You can do this by moving your mouse over the five dots and then dragging the Fast View tool bar below the Perspectives tool bar. You should see an arrow pointing to the left; when you do, simply release the mouse button. Next, click the minimize icon in the upper right corner of the Problems view. You now have the maximum amount of area to work in.
Configuring Eclipse There are a few things that need to be configured Volume 5 Issue 6 • php|architect • 12
Total Eclipse of PHP Development to transform Eclipse into a more usable state. You can find the workbench preferences by clicking on Window->Preferences. Each section below details the change necessary.
Team • •
General • • •
•
• •
•
Select Compare/Patch and enable Show additional compare information in the status line. Expand Editors, select Text Editors and enable Show print margin and Show line numbers. Expand Text Editors, select Accessibility and enable Use characters to show changes on line number bar. Expand the Quick Diff section and enable Show differences in overview ruler. You might also want to modify Use this reference source to your liking. Select Spelling and click Enable spell checking. Select the Keys entry from the General section. Change the entry for Previous perspective and Next perspective to Ctrl+Shift+Page Down and Ctrl+Shift+Page Up respectively. Choose Perspectives and set PHP as the default.
PHPeclipse • •
•
•
• • •
You can change your embedded browser under the Browser Configurations entry. Personally I disable Refresh and Show PHP browser view when FIGURE 2 opening editor under Browser Preview Defaults. Select the PHP entry and enable Show line numbers and Show print margin. Under the Typing tab, enable Insert spaces for tabs, Wrap comments and PHPDoc at print margin and Remove trailing spaces on editor save. You will need to add an interpreter under Installed Interpreters. Enable Spell-check comments under the Spelling section. If you are running your server applications locally, modify each section for your systems setup under PHP External Tools. Otherwise, modify Project Defaults by changing Localhost to the URL of your remote server.
You can increase the compression for CVS under the Connection entry. Enable Select unversioned resources on commit and Fetch affected paths on demand under SVN.
NOTE: If you are using Linux or Mac OS X, you will be displayed an error message when selecting SVN if you do not have JavaHL installed. If you plan to use SVN daily, I recommend using JavaHL over the 100% Java SVN implementation. Instructions for installing JavaHL can be found in the Subclipse FAQ (http://subclipse.tigris.org/faq.html #get-javahl).
Validation •
Enable HTML Syntax under The selected validators will run when validation is preformed.
Web and XML •
Expand the CSS, HTML and XML Files section and, under the Source entry, modify the settings to use 4 spaces instead of tabs.
When finished, click OK and you should be back in the PHP Perspective.
Getting Connected The Web Standard Tools (WST) provides the ability to
Volume 5 Issue 6 • php|architect • 13
Total Eclipse of PHP Development manage every aspect of your RDBMS. FIGURE 3 You can create SQL scrapbooks, generate DDL, import and extract data–-and it even comes with a complete overview of your RDBMS. The overview displays dependencies, stored procedures, tables, user-defined functions and views for each available schema. You can also view columns, constraints, dependencies, indexes and triggers for each table. The WST also provides an SQL Editor and a means to modify the contents of your RDBMS. Since most PHP developers use MySQL as their RDBMS, this will be the type of connection we will be configuring. You will need to download the MySQL JDBC driver before we can begin. Personally, I
connection test is successful and you do not need to add any additional RDBMS, click Finish. NOTE: If your RDBMS is not supported, simply install the QuantumDB Eclipse plugin (see Figure 1) which provides JDBC access for a variety of vendors. Now, right-click on the entry for your MySQL database, selecting Reconnect. If you like, you can create a SQL Scrapbook by right-clicking on the database name and selecting Open SQL Scrapbook. You will need to enter a filename and select a project in which to store the scrapbook. To edit a table’s contents, drill down to the Tables entry and
Eclipse is extremely drag and drop friendly–almost every aspect of the workbench is dragable. uncompress the driver to a directory named drivers within the eclipse directory, because I do not use any other applications that require it. However, you might find it more useful to place the driver in a system-wide accessible directory. Let’s start by opening the Database Explorer and Data Output views. To do this, open the Show View dialog by selecting Window->Show View->Other... from the menu. You can find the previously mentioned views under the Data entry. Next, in the Database Explorer view right-click on the entry labeled Connections and select New Connection. Expand MySQL and select the proper version. Under User Information, enter the username and password for your account. Then enter a database name and browse to the right of Class location to select the MySQL JDBC driver. Enter com.mysql.jdbc.Driver as the JDBC driver class and jdbc:mysql://host:3306/database for the Connection URL, modifying it with the proper host address and database name. Once your details are entered, click on the Test Connection button. Eclipse will now test the connection to ensure that it works. If the
right-click on any table selecting Data->Edit. This will open a new view, allowing you to edit the contents of your database or insert a new row. If you need to modify a table’s schema or execute a custom query, you can do so by right-clicking on a table and selecting Open->With SQL Editor. When you have completed your modifications to the contents of the SQL Editor, you can execute the query simply by right-clicking in the SQL Editor view and selecting Run SQL.
Installing DBG We’re on the home stretch of installing and configuring our environment, and in just a few moments we’ll get down and dirty by debugging some code. First we need to install the DBG PHP Debugger. Before I go into the details of that, a few words about the Xdebug PHP Debugger are in order. Some of you out there might be saying, “Hey, what about Xdebug?” Well don’t worry, Xdebuggers, you’re not left out in the rain. There is currently support for it, but the bad news is it requires a custom build of the Volume 5 Issue 6 • php|architect • 14
Total Eclipse of PHP Development PHPeclipse plugin. This is beyond the scope of this article, so I will not be covering the details. Xdebug support will, however, soon become standard in PHPeclipse–mainly due to the fact that the Xdebug support for PHPeclipse includes the ability to use Eclipse’s built-in Profiler. If you’re adventurous and decide to make a custom build
see Zend Engine v2.0.3, Copyright (c) 1998-2004 Zend Technologies with DBG v2.x.x, (C) 2000,2005, by Dmitri Dmitrienko in the copyright section, and DBG
should have its own section below displaying its configuration settings.
There is currently support for Xdebug, but it requires a custom build of the PHPeclipse plugin. of PHPeclipse before this becomes part of the standard installation, the instructions for doing so can be found in the PHPeclipse Developers Guide. However, at the time of writing, the Profiler feature was not ready for general consumption—you have been warned. To start, simply download the latest version of the DBG PHP Debugger and extract the contents to a temporary directory. Copy the correct file for your PHP version to the modules directory defined in your php.ini file and rename it without the PHP version. If you do not know the module’s path, simply create a file with the code snippet below, placing it in your web server’s web root and then load it in a web browser.
If you are on a *nix based system you will need to change the file permissions to allow the web server to have read and execute access. Next, add the entry zend_extension=debug.so (or zend_extension_ts=php_dbg.dll if using Windows) and the configuration options below to your php.ini. [debugger] debugger.enabled=on debugger.profiler_enabled=on debugger.hosts_allow=Enter the host names or IP allowed to start debug sessions separated by a space. debugger.hosts_deny=ALL debugger.ports=7869, 10000/16
Now, restart your web server and check phpinfo() to verify that the DBG module installed properly. You should
Debugging Now it’s time to go beyond configuring and installing software and have a bit of fun. To get started, we’re going to need some code to debug. Instead of providing a code snippet, let’s check out a project from a Subversion repository. Using a complete application will help illustrate the different techniques used in debugging. The application that we will be looking at is the Seagull PHP Framework. We will begin by switching to the SVN Repository Exploring perspective. Right-click in the SVN Repository view and select New->Repository Location.... Next enter the URL for the Seagull SVN Repository and click Finish. Now, expand the entry for the Seagull SVN repository and then the tags entry below it. Find the latest version and right-click on it, selecting Checkout. When prompted to select the method to use for checking out Seagull, choose Check out as a project configured using the New Project Wizard and click Finish. Expand the entry for PHP, select PHP Project and click Next. Then enter seagull as the name of the project and click Finish. Eclipse will now begin downloading Seagull, and upon completion you will be automatically switched back to the PHP perspective. Next, in the PHP Browser view, enter the URL to access the www directory of the seagull project. This may vary depending on your web server’s configuration, but should look similar to http://localhost/workspace/seagull/www/. Fill in the necessary information in the Installer, and upon completion it will properly generate the database and install Seagull. (The Seagull Wiki has a complete installation guide and troubleshooting techniques if needed.) You will need to do a little preparatory work to get the Debug perspective set up before we use it. Volume 5 Issue 6 • php|architect • 15
Total Eclipse of PHP Development Switch to the Debug perspective, open the Navigation and PHP Browser views and add them to the Fast View tool bar. Now we’ll configure Eclipse to properly debug Seagull. Currently you need to create a configuration for each project you plan to debug, but there are plans to automate this in the future. •
Start by clicking on Run->Debug in the menu at the top of the workbench.
•
Select PHP DBG Script from the Configuration section and click New.
•
Name the configuration seagull. Select seagull as the project and www/index.php as the file.
•
Switch to the PHP Environment tab and, if not already selected, choose an interpreter.
•
Choose the Remote Debug tab within the PHP Environment tab and enable Remote Debug. You will need to enable this even if you are running your web server locally, so that you can interactively debug your application.
•
If you are running under Windows locally and using a remote Linux server, you will need to enable Cross Platform Debugging.
•
Enter the path of your server's web root for the Remote Sourcepath. If you are running your web server locally, simply enter your workspace path; otherwise enter your remote server’s web root path.
•
If you will be debugging a project frequently,
FIGURE 4
switch to the Common tab and enable Debug under Display in favorites menu. •
Finally, click Debug. Your settings will be automatically saved, and Eclipse will launch a DBG Client in the Debug perspective.
Your Debug perspective should look like Figure 4 with an entry labeled seagull [PHP DBG Script]. Next, we will need to set a couple of breakpoints to pause execution, which will allow us to examine Seagull in real time. Open the Navigator view and then open the files www/index.php and lib/SGL/FrontController.php. In index.php, doubleclick to the left of line number 17—this will set a breakpoint on this line. Do the same on lines 77 and 94 of FrontController.php. You should notice a dot on each line where a breakpoint is set. Right-click on seagull [PHP DBG Script] in the Debug view and select Terminate and Relaunch. Eclipse will now re-launch the DBG client. If you open the PHP Browser view, Eclipse should have loaded the URL you specified in Project Defaults of your preferences—appending DBGSESSID. To navigate your code effectively, you can use the following shortcuts: F5 to step in, F6 to step over, F7 to step return and F8 to resume. Press F5 and step inside the block of code for the if() statement and do so again to step inside the file that is being required— FrontController.php. Now line 42 should be highlighted. Since stepping in every file would quickly become very time consuming, you can use F6 to step over a statement. However, you should note that if you attempt to step over a statement and there is a breakpoint set inside that statement, execution will halt at this breakpoint. Next, step through FrontController.php by pressing F6. You might have noticed that there are breakpoints set on lines 77 and 94, but the debugger did not halt at them. This is because the debugger is only parsing the file and not executing the code of the included or required file. Before you get to the bottom of FrontController.php, press F7 which will bring you back to index.php. If you have not already done so, switch now to the Variables view in the upper right hand corner. You will notice that this contains all the variables that are currently initialized. If you rightclick on a variable, you can perform
Volume 5 Issue 6 • php|architect • 16
Total Eclipse of PHP Development some useful functions. The main two we’re interested in here are Change Value and Watch. The former, as you probably guessed, allows you to change a variable’s value during run-time. The latter allows you to add a Watch Expression to the variable. This is extremely useful when you want to keep an eye on the value of a variable as you step through your code. In the Variables view, right-click on the $pearTest variable and select Watch. This will add a Watch Expression and also open the Watch view. Besides being able to watch the value of a variable, you can also
Unit Testing Of the various PHP test suites, two are available for Eclipse—PHPUnit and SimpleTest. Support for both suites is provided by the SimpleTest Eclipse plugin. Start by downloading the SimpleTest plugin from SourceForge, and install it as a New Archived Site in Eclipse. You will also need to install either PHPUnit2 or SimpleTest using PEAR. PHPUnit2 is available from the PEAR Channel Server, and SimpleTest is available from the Seagull PEAR Channel Server. In Eclipse’s Preferences, you will need
To navigate your code effectively, you can use the built-in shortcuts. add any Boolean expression—and this may also contain native PHP functions. Next, right-click on the $pearTest in the Watch view and select Edit Watch Expression. Modify the Watch Expression entering $pearTest == ‘@PHP-DIR@’ and click OK. Notice how Eclipse evaluates the Watch Expression and now displays TRUE at the end of the expression. This expression will be reevaluated every time you send an instruction (step in, step over, etc) to the debugger. Since we are on the subject of expressions, another useful feature that the debugger provides is the ability to add a breakpoint condition. A breakpoint condition is exactly that—a condition that is evaluated prior to execution being halted. These come in handy when you want the debugger to halt only if the condition is met. Switch to the Breakpoint view, right-click on the breakpoint entry for line 77 of FrontController.php and select Properties. In the dialog enter the expressions count(get_class_methods($req))) == 1, click Enable Expression and then on OK. You just added a breakpoint condition that will only halt the debugger if the $req object contains exactly one method. You may have also noticed that there is a skip count parameter—a real life-saver when you’re evaluating a loop and the bug only occurs after 102 executions. To demonstrate the breakpoint condition, we will use the resume function of the debugger. This is used when you want to jump from one breakpoint to the next; do so now by pressing F8, and notice how the debugger stops at line 94 instead of line 77.
to configure the proper paths for the SimpleTest plugin. Once configured, running a test is as simple as opening the Results View under the entry for your test suite, right-clicking on a file and selecting either SimpleTest or PHPUnit2 from the Run as menu entry.
Conclusion Hopefully I have clearly illustrated the benefits of using an Integrated Development Environment, and this is the first day of your whole new approach to Web development. I’d like to take a minute to thank everyone involved in Eclipse development for creating such a wonderful product. More specifically, I’d like to thank the PHPeclipse and EasyEclipse development teams for answering my many questions.
ALEXANDER J. TARACHANOWICZ II is the Chief Technology Officer at Local Hype and a core developer of the Seagull PHP Framework. He works in a variety of fields, ranging from software design and development to arts marketing and promotion. You may contact him at
[email protected], or step inside his mind and read his blog at http://aj.tarachanowicz.com.
Volume 5 Issue 6 • php|architect • 17
FEATURE
Long Live the Code!
Long Live the Code! Refactoring Towards OOP
Many widely used web applications such as bulletin boards and wikis, and of course a lot of Web sites, are written in PHP. This huge codebase was mostly created for PHP 4, and thus does not make much use of object-oriented concepts. Yet, object-oriented code is easier to maintain and extend. Does PHP 5 mean we will have to rewrite all that code from scratch? In this article, you will learn how to introduce object-oriented programming concepts while refactoring existing procedural code. by STEFAN PRIEBSCH
T
he term “refactoring” refers to the modification of existing software to make it more humanreadable, maintainable and extendable. Refactoring does not mean the introduction of new features into existing software. You are either refactoring or you are developing. If you mix up the two, you will usually end up with buggy and unusable code—so don’t do it! In practice, you will often find yourself refactoring a piece of code shortly before you add a new feature. Quite often, it is not clear how to add a feature or how to extend code in a certain way, but by refactoring the code first, it suddenly and magically becomes obvious. You should also refactor code if you are looking for an error and you do not immediately understand the code. The chances are good that you or your colleagues will not understand the code when you need to read it the next time either, so go ahead and refactor it until the code communicates its purpose more clearly. Everyone will benefit from this. In this article, I will explain refactoring as a means of introducing object-oriented concepts into existing procedural PHP applications. Doing this will not only make
PHP: 5 CODE DIRECTORY: refactoring TO DISCUSS THIS ARTICLE VISIT: http://forum.phparch.com/309
the existing code more maintainable and extensible, but will also help you to create cleaner code in the future. Since we change existing software when we refactor, we need to find a way of making sure that everything still works as expected after our intervention. This is where testing comes into play. In an ideal world, you should have a suite of automated tests that covers your code and tests every feature. Automated tests basically compare a calculated result to a pre-calculated expected result. If both match, the test passes, if they do not match, the test fails. When we refactor code, we run tests first to make sure everything works before we change it. Then we take one refactoring step, and then rerun the tests. If all tests still pass, we can be reasonably Volume 5 Issue 6 • php|architect • 18
Long Live the Code! sure that our software still works. If any of the tests fail, we have introduced a change in behaviour while refactoring. At this point, we can choose to either discard the refactoring step, or to try and fix the bug we have introduced.
Legacy Code and Automated Tests In reality, and particularly when it comes to legacy PHP code, automated tests simply do not exist. Usable test frameworks such as PHPUnit 2 (http://pear.php.net/package/PHPUnit/) or SimpleTest (http://www.lastcraft.com/simple_test.php) were not available before 2002 or 2003, so we cannot even blame the developers of the legacy code for not having creating automated tests. Furthermore, the original idea behind PHP was to embed some program code directly into an HTML page. This is very efficient for adding some functionality to otherwise static HTML pages or for creating small applications. From the perspective of testing, however, embedding PHP directly into HTML is the worst possible thing to do, since it means that presentation, business logic and data access are not separated. The only way to test PHP code in that situation is by manually clicking through HTML pages, or by creating some software that sends HTTP requests and parses the resulting HTML pages. During the last few years, PHP has shifted from being a scripting language to being a full-blown development language. The large companies using PHP and the complex applications that are being created using the language, prove that PHP is also a viable environment for enterprise applications. The object-oriented features in PHP 5, particularly, make it easier to separate concerns in PHP software. This makes it easier to write automated unit tests that allow us to quickly check whether or not our software works as expected. I would recommend that you start to refactor your procedural code in small steps, and add automated tests as you move along. Every test you create adds a little more reliability and makes any future work on that application easier. When you have reached a certain level of “code testability”—that is, when your code has been broken down into small pieces and each piece of code does exactly one thing—it becomes very easy to add automated tests. So, do not waste too much time on creating automated tests for your existing procedural PHP code, but start refactoring until the way to create automated tests becomes easy and obvious. You might then be tempted not to create any tests, as it seems that all the small pieces of code work as expected anyway. It is very important, however, to keep creating them, as you are bound to have to change the existing code again
at some point. Any future changes will be much easier to perform when you have existing automated tests. You will know for certain that your change did not break anything, and this is a very good feeling. It makes you sleep better after you have delivered your work. When writing automated tests, always test the interfaces rather than the implementation. You should not focus on the internals of the software, and you should LISTING 1 1
LISTING 2 1
getPriceOfNavigation() 5; getPriceOfExtras($aAdditional) 2.5 * sizeof($aAdditional); getDiscount($aPrice) 0.9 * $aPrice;
LISTING 3 1
Refactoring Towards ObjectOrientation
Volume 5 Issue 6 • php|architect • 20
Long Live the Code! code easier to test. It also makes the code more reusable: we can use the business logic even if we do not read the data from a database, but from a flat file or by XML from a remote web service. We can reuse the code that creates the HTML table for other data as well. In addition, our code has become easier to maintain; we can change the business logic without touching either the data access code or the presentation code. As a rule of thumb, separation of concerns means having no HTML code anywhere near data access code or business logic. It can actually be quite difficult to separate concerns when developing software, especially if you have a strong scripting background—but the more refactoring you do, the easier it will become for you to separate different concerns in your software from inception.
Cars for Rent The “separation of concerns” principle also helps you to structure your code better on a smaller level. Imagine, for example, a piece of software that calculates the price of a rental car. Based on the chosen model and the number of days to rent the car, additional fees might apply for insurance, satellite navigation, or any additional equipment. In addition, a corporate discount might apply. When creating a software that performs this calculation, you would probably create one function, calculate_price(), that receives all necessary input as parameters and returns the calculated price. Listing 1 shows an implementation of this function. In our somewhat simplified example, we make the assumption that each piece of additional equipment costs $2.50 per day. This example may not make much sense in the real world, but it has good refactoring potential. Now let’s assume the price calculation needs to be modified because all rental cars can optionally be equipped with winter tires. Winter tires come at an extra cost, so we will have to modify the calculate_price() function. To make this modification easier, we refactor the function first. Let us separate the concerns and break them down into different functions. calculate_price() then merely delegates the calculations to specific calculation functions and aggregates the results. This version is shown in Listing 2. The refactored implementation has various advantages over the previous version. For a start, the main calculation workflow is much more visible now. A glance at the function tells you that insurance, satellite navigation, and extras are added to the base price of the selected model, whereas the discount is deducted. This is much easier to modify; if we need to change certain aspects of our price calculation, we only need change
LISTING 4 1
LISTING 5 1
LISTING 6 1
Volume 5 Issue 6 • php|architect • 22
Long Live the Code! LISTING 7
LISTING 7 (CONT’D)
1
LISTING 8 1
LISTING 9 1
Volume 5 Issue 6 • php|architect • 23
Long Live the Code! one function. For example, we could add a new model just by modifying the function getPriceOfModel(). This function could read a list of models and prices from a database instead of having them hard-coded in a switch statement. We have encapsulated the price calculation in a function; this is a big step towards introducing objectorientation. As we all know, encapsulation is one of the major principles of object-orientation. Now that we have refactored our code, it has become much easier to add the winter tire feature. The result is shown in Listing 3. We have introduced a function getPriceOfWinterTires(). If there happened to be an introductory price for winter tires, all we would have to do would be change this one function. No need to touch any other code. While this example is pretty basic, it does at least show how separating concerns and encapsulation make code more readable and maintainable. Now, imagine a really complex application with thousands of lines of code. Faced with that, you would be grateful if you only needed to modify one function and could immediately check that everything works as expected. It is worth mentioning that adding the getPriceOfWinterTires() function is not, in itself, refactoring, since we are adding something new. Keep in mind that we refactored our code first, and then added the new function. The essence of object orientation is that program code is broken down into small pieces, allowing functionality to be extended without modifying the original code. This is often done by subclassing. If you want to modify a certain method, create a subclass of an object to contain that modification. You do not have to touch the original method code. This, however, will only work well if each method has one clear and defined purpose. This again is another reason to separate concerns as much as possible. Object-oriented code allows us to integrate code and data into objects. It is easier to pass around a single object than it is to pass around a long parameter list. The calculate_price()function has a long parameter list which we had to modify when we added the winter tire option earlier. It is not a good idea to change function signatures as we did, because we will have to change all of the client code using that function. Also, the change we made is not backward compatible; older client code using the function will pass the wrong parameters to it, so a wrong result will be returned.
Rental Refactoring Time to refactor. Instead of passing around a long parameter list, we will introduce an object that “knows” all of the required parameters, and pass this object around. If the input comes from a web form, this object
might be named Input and contain all input parameters passed to the script. Since each function knows the arguments it needs, it can ask the Input object for them. If new arguments are introduced, we only need to modify the Input object. This way, if a function needs additional parameters, we do not need to change the function signature, but simply add a line that requests the required parameter from the Input object. The Input object is shown in Listing 4. All input variables are passed to the object constructor as an associative array. In an application we could, for example, just pass $_REQUEST to the Input constructor. For testing, we can create an array containing the desired input variables. In short, by using an Input object, we can make our software independent of the actual GET and POST parameters it would normally use. This makes testing much easier. Our Input object has one generic accessor method that requires the name of an input variable as parameter. We could also create an input object with a named accessor for each input variable (such as getModel(), getDays() etc.). This might be more secure, since it prevents us from reading input variables that do not exist—on the other hand, it requires us to modify the Input object whenever a new input variable is introduced. It is important to note that the Input object is being passed around rather than being declared a global variable. Many programmers that have a strong procedural programming background tend to use global variables instead of passing around objects. By passing around objects you can reduce the amount of data being passed, since an object only needs to know where to ask for what it does not know. For example, an object performing calculations does not need to “know” the values of any input variables; it can ask the Input object for them. To make sure that the object passed to calculate_price() actually is of the class Input, we use a type hint. The type hint checks the class of the object passed in at runtime. If we passed in another object, a runtime error would occur. This is important because we need to be sure that the object actually has a get() method. If we pass in an object that does not support a get() method, the $aInput->get(‘Model’) statement in line 18 would cause a runtime error, because you can obviously not call a method that does not exist. Try to use type hints as much as possible; they will help you find errors quickly. We could also pass a subclass of Input to our function, because any subclass of Input also contains all the features of Input. This is a very important feature of object-oriented programming called polymorphism. Volume 5 Issue 6 • php|architect • 24
Long Live the Code! In practice, this means that we could create a class TestInput, for example, which has additional features, or only contains specific default input variables. Now we will refactor getPriceOfModel() to become a Model object. This is an example of a common refactoring pattern—namely, replacing conditions by polymorphism—achieved by subclassing. As Listing 5 shows, our basic Model object has the default price of $35. Note that $price is a protected member and there is no setter method for it; you would not want somebody to
probably become more and more complex with each refactoring step. This is not what we want. We therefore wrap calculate_price() into a Process object that encapsulates one leasing process (see Listing 6). In our example, we just look at the price calculation, but you could imagine a Process also holding information about the renter, payment, and the status of the car. But even when we only focus on the price calculation, the benefit of our refactoring effort becomes obvious. We do not need to pass an extensive parameter list to
When writing automated tests, always test the interfaces rather than the implementation. change the price of a given model at runtime. We change the signature of the calculate_price() function and pass a Model object to it. As described above, all subclasses of Model (namely Ferrari, Humvee and Buick) will also pass the type hint check. The type hint ensures that the object referenced by $aModel in line 5 supports the getPrice() method. Note that PHP currently does not support type hints for optional parameters, although this is planned for PHP 6. For now, if you declare a parameter as optional, you need to manually code a check for the correct class. The Model class and its subclasses in Listing 5 are also a great example for making modifications without touching on existing code; for example, let’s assume that for some reason the price of a Humvee depends on a calculation. We could simply create a method getPrice() in the Humvee class that performs the required calculation instead of returning the fixed value $price. This is one of the advantages of using accessor methods rather than direct access to members. One could argue that the Model object should also be kept inside an Input object. This would certainly be possible, but I would rather create a function or method that creates the Model object from whatever input, then passes both to calculate_price(). This is because Input currently holds only input variables. Since object references are not usually part of the GET or POST input, adding a Model reference to Input would be mixing two separate concerns. If we continued on to refactor functions to objects, the signature of the calculate_price() function would
a function, but can set various members one by one, and call calculatePrice() when we are ready to perform the price calculation. This works because our object has data encapsulated. It can remember things, whereas a function only has access to the parameters passed to it and to any global variables. (Global variables, by the way, are evil. They make code almost impossible to test, and it is far too easy to change things you are not supposed to change—probably causing the application to break.) With our changes to price calculation, we introduced delegation, which is another important object-oriented principle. Just like in real life, you don’t have to be able to do everything yourself, but it’s useful to know somebody who can. Note that a statement like $this->model->getPrice() was not allowed in PHP 4, where you had to store $this->model to a temporary variable and then invoke the method. In PHP 5, this syntax is fine. You may have noticed that in calculatePrice(), no check is performed to see whether $model is set. It’s still possible to call the method without setting $model first, which currently results in a runtime error because you cannot invoke a method on a NULL value. This brings us neatly to the subject of error handling.
Exceptionally... In PHP 5, we can use exceptions to handle non-fatal errors at runtime. Listing 7 shows how an exception is thrown when Model was not set before calling the getPrice() method. We define our own CalculationException, which Volume 5 Issue 6 • php|architect • 25
Long Live the Code! inherits from the built-in Exception class that PHP 5 has. The CalculationException does not offer any additional functionality, but we can use the class to distinguish between different types of exceptions. Since exceptions are objects, we can use type hints when we pass them around—and we can check whether an exception has a certain class using the PHP 5 type operator, instanceof. Exceptions allow us to handle errors much more efficiently than the error handling mechanisms in PHP 4 ever did. That is because an exception can store environmental information about an error—exactly the kind of environmental information required to handle an error efficiently. Most of the time errors are not handled where they occur, or can be handled much more efficiently somewhere else. Let’s look at a different example: imagine our car rental application reads the availability of cars from a database. Listing 8 shows a simplified exemplary implementation, which, again, does not make too much sense in real life, but serves the purpose of illustrating the point. The application has different layers. A data access layer handles the database access and returns an array containing a list of those dates when the given model is available at a specific office. (Each office uses its own database.) The business logic then checks whether that model is available on the given date. The code uses conventional error handling; if an error occurs, we return false instead of an array. This kind of error handling has the disadvantage that we must mix up error checks with production code in the client code. Also, we cannot pass back any information about the error that occurred. Was the database not available? Were we not allowed to read from the table? Or was there an error in the SQL statement? Now, let’s assume that the database suddenly goes offline. The next time a user tries to check when his desired car will be available, an error occurs. From the viewpoint of the data access layer, this is a fatal error, since without a database connection, nothing can be read from the database. From the viewpoint of the business logic or user interface, the error is not so severe. We do not want to kill the application; we can fall back on a backup database. If the backup database does not work (for example because the whole network is down), we can still ask the user whether he would like to queue his request so that it can be processed when the database becomes available again. So, what happens when we refactor the code to use exceptions? In Listing 9, the data access layer has been refactored in this way. Instead of passing back FALSE, we throw an exception, and this also allows us pass information about the reason for the error. We
use two exception subclasses, DatabaseException and DatabaseQueryException. Using our two exception types, we can handle different kinds of errors that occurred inside the data access layer, within the business logic layer. Notice that a query exception is handled differently from a database exception; we assume that if the SQL query is faulty, falling back to the backup database will not improve the situation. This level of error handling would not have been possible with PHP 4. The more complex your applications get, the more you will benefit from using exceptions. Exceptions elegantly solve the problem that errors must be handled in another layer than the one in which they occur. In procedural code, we would have had to violate the “separation of concerns” principle to achieve sensible error handling.
Conclusion As we have seen, it is not too difficult to make a transition from procedural to object-orientated code through refactoring. While in practice there are situations where you should re-implement from scratch, there is still a lot to gain from knowing how to refactor. By improving the design step by step, you gain a lot of experience on which classes and objects to use, and how to make them interact. That is because you are looking at code that already does what it is supposed to, which allows you to put your main focus on the present functionality rather than wasting time on parts of the application that you feel are important, but later turn out to be obsolete. If you are interested in more background information about refactoring, you should read the book “Refactoring” by Martin Fowler. This great book is pretty much the definitive work on refactoring, and although the code examples are in Java, it is a good read for PHP programmers as well. In the next installment of this series, I will focus on refactoring object-oriented code. Since object-oriented code, by its nature, is already easier to maintain, you will see that there can be great benefits in even small refactoring steps. Until next time, happy refactoring!
STEFAN PRIEBSCH is a consultant, trainer, speaker and author. He holds a university degree in computer science and is the founder and CEO of e-novative GmbH, a provider of PHP-based e-business software, solutions and services. He got infected with PHP 4.0b3 and has been working with PHP ever since. Stefan is currently working on his first book, “Enterprise PHP Tools”. You can reach him at
[email protected].
Volume 5 Issue 6 • php|architect • 26
FEATURE
PHP Clustering on Linux - Part 2
Now you can build your own highly available, scalable platform for running mission-critical
PHP Clustering on Linux PHP applications on commonly available commodity hardware, using proven open source software. With no software license fees, you can add as many servers as you like to increase performance. This frees you from the need to buy the latest and fastest hardware. This arti-cle is the second of a threepart series showing you exactly how to build and configure just such a platform.
PART 2 PHP: 4 O/S: CentOS 4.2 or RedHat Enterprise Linux 4 OTHER: Linux Virtual Server, Heartbeat, Apache, JMETER, osCommerce LINKS: www.centos.org www.linuxvirtualserver.org www.linux-ha.org www.apache.org www.oscommerce.com TO DISCUSS THIS ARTICLE VISIT: http://forum.phparch.com/310
by JOSEPH H. KOUYOUMJIAN
L
ast month, we covered the absolute basics of setting up a Linux cluster to serve your PHP applications in a highly-available, highperformance environment. We covered some terminology (Directors, for example), and the first round of networking. This time, we’ll dive right back into our project by looking at Heartbeat, the utility that will keep your nodes in constant check, which in turn keeps your cluster running.
Configuring Heartbeat We will have a pair of Directors, one primary and one secondary. Whichever Director is handling traffic for the cluster will answer requests for the cluster’s VIP, which in our case is 192.168.1.100. The VIP is a resource that Heartbeat will control. Heartbeat will make sure that
only one Director has the IP at a time. When Heartbeat is started on both the primary and secondary Directors, and a communication link is established, it will create an aliased IP address on the active Director’s NIC and announce to the network that the active Director is handling traffic for that IP (through ARP). In the event of a failure of the active Director, the secondary Director will automatically take over the IP address and send an ARP broadcast to all devices on the subnet, effectively taking over the IP address. To get Heartbeat running, download the source tar ball from www.linux-ha.org. Our example uses version 2.0.2. Heartbeat requires Libnet, which can be downloaded from http://www.packetfactory.net/libnet. Our example uses Libnet 1.1.2.1. Follow these steps to compile and install Libnet and Heartbeat. You’ll need to install Libnet first: Volume 5 Issue 6 • php|architect • 27
PHP Clustering on Linux - Part 2
In the event of a failure of the active Director, the secondary Director will automatically take over. 1. tar -xvzf libnet.tar.gz 2. cd libnet 3. make 4. make install 5. cd .. 6. tar -xvzf heartbeat-2.0.2.tar.gz 7. cd heartbeat-2.0.2 8. groupadd haclient 9. useradd hacluster -g haclilent 10. ./ConfigureMe configure 11. make 12. make install The Heartbeat configuration files are located in the /etc/ha.d directory. The main configuration file is ha.cf. In our case, the ha.cf file is the same on both Directors. Here is the ha.cf file for our Web cluster Directors: bcast eth1 udpport 694 keepalive 2 warntime 10 deadtime 30 initdead 120 autofailback off node 192.168.1.3 node 192.168.1.4
The bcast directive tells Heartbeat to send its heartbeat signal to the other node on the device eth1. For our example, the Directors are configured with two NICs: one for network traffic (eth0) and the other exclusively for Heartbeat (eth1). Putting the heartbeats on their own network keeps them from taking up bandwidth on the cluster network. It also prevents traffic on the client network from slowing down the heartbeats as they travel between Directors. The heartbeats can be broadcast on more than one interface, which is what you would want
for a production environment. Heartbeat will not initiate a fail-over unless all the heartbeats stop. In the case of the example, a failure of just eth1 would cause a fail-over, even though the server is still running fine. You can use an Ethernet crossover-cable to connect the two Directors’ eth1 NICs directly to each other, without using a hub or switch. You can use a hub or switch if you want to see the heartbeats on the network. The udpport directive tells heartbeat which port to use for the heartbeats. The keepalive time is the frequency of the heartbeats, in seconds. The warntime directive specifies how long a heartbeat can be missing before a warning is issued. The deadtime directive specifies how much time must pass before a node is declared to be failed. When this time is exceeded, a fail-over will be initiated. The initdead directive is just like deadtime, except that it only applies when Heartbeat is first started. The last two lines represent the two nodes that make up the pair of Directors. In our configuration file, autofailback is set to off. This means that when the secondary Director reboots the primary by power-cycling it with the fence device, and the primary successfully restarts, it will not reacquire the controlled resources from the secondary without human intervention (see the commands described below). Setting autofailback to on opens the possibility that an intermittent failure of the primary Director (which caused it to shut down), might start a loop where the primary Director constantly starts up, takes over resources, fails and is restarted by the secondary, only to fail again. If such a loop condition develops, it can only be stopped by human intervention. This is not to say that autofailback is bad, just that you should be aware that this possibility exists. In most cases, you should probably attempt to find the reason that a machine failed before putting it back into service. The next file we need to worry about is the authkeys file. authkeys is used by Heartbeat to authenticate members of clusters. Since we are using a dedicated network between our two Directors, we can instruct Heartbeat to sign outgoing packets using a simple CRC instead of a secret key. The /etc/ha.d/authkeys file looks Volume 5 Issue 6 • php|architect • 28
PHP Clustering on Linux - Part 2 like this: auth 1 1 crc
Finally, Heartbeat needs the haresources file. haresources tells Heartbeat about the resources which are to be set up and failed-over between the cluster nodes. It is critical that this file be the same on both machines; Heartbeat will not work correctly otherwise. Here is the /etc/ha.d/haresources file for our Web cluster: 192.168.1.3 IPaddr::192.168.1.100
This says that the primary node of the cluster is 192.168.1.3. In our case, the resource to be controlled is an IP address, 192.168.1.100, which is the VIP of our LVS. IPaddr is a script which is located in /etc/ha.d/resources.d. You can create your own scripts to control any kind of resource you want. You can also add additional resources to the line, separated by spaces, and Heartbeat will diligently call those scripts as well. The scripts are called with whatever follows :: as an argument. Heartbeat also adds start and stop as appropriate during a fail-over/takeover. Notice that only the primary Director’s IP address is
listed. This must be the case on both Directors. Do not put the secondary Director’s IP address in this file. There are no logging directives in our ha.cf file, so all log messages will be written to /var/log/messages. The best way to see what is happening is to open two terminal sessions for each Director. Issue the command tail -f /var/log/messages in one session and the cluster commands in the other. tail -f will cause the most recent entries in the log file to be display continuously until you close the session. This way you can see exactly what is happening on both machines. Make sure that ha.cf, authkeys and haresources are present on both Directors in /etc/ha.d. To start Heartbeat running, go to 192.168.1.3 and issue the command service heartbeat start. You should see a log entry with many lines, including the following: Feb 13 16:23:37 opal heartbeat: [6942]: info: Configuration validated. Starting heartbeat 2.0.2 Feb 13 16:23:37 opal heartbeat: [6943]: info: heartbeat: version 2.0.2 ... Feb 13 16:23:38 opal heartbeat: [6943]: info: Link opal.int. strongarch.com:eth1 up.
opal, here is the hostname of the primary Director. At
Volume 5 Issue 6 • php|architect • 29
PHP Clustering on Linux - Part 2 this point, Heartbeat is waiting for communications to be established with its twin on eth1. If heartbeats are not received from 192.168.1.4 within the specified initdeadtime (120 seconds), Heartbeat will assume the other machine is down and will attempt to reset it before
to power-cycle the other, and only one will win. This will keep the cluster running without interruption. Configuring a STONITH device is simple. For test purposes you can run without a real STONITH device, but you will want one in a production environment. There are
Heartbeat will not initiate a fail-over unless all the heartbeats stop. assuming control of the resources. (More on this later.) Now go to the 192.168.1.4 window and start Heartbeat. In the log for 192.168.1.3, you should see these lines at some point: Feb 13 17:15:27 opal ResourceManager[7525]: [7569]: info: Running /etc/ha.d/resource.d/IPaddr 192.168.0.252 start Feb 13 17:15:27 opal IPaddr[7571]: [7620]: info: /sbin/ifconfig eth0:0 192.168.1.100 netmask 255.255.255.0
broadcast
192.168.1.255 Feb 13 17:15:27 opal IPaddr[7571]: [7625]: info: Sending Gratuitous Arp for 192.168.1.100 on eth0:0 [eth0]
This indicates that 192.168.1.3 now controls the IP address of the VIP. You can confirm this by issuing an ifconfig (or ip addr list dev eth0) command to list the IP address on the machine. Only the primary will have the VIP.
Fencing In a high-availability cluster, it is desirable to make sure that a failed node which appears to have crashed does not come back to life and started responding to requests after another node has taken over its resources. We also want to avoid the “split-brain” condition where two healthy nodes each decide to take over the controlled resources (which would be the case when all heartbeat paths failed on otherwise healthy nodes). In our case, each Director needs the ability to “fence off” the other in case of a fail-over. This is done by connecting each of the Directors to a special power switch that the other can command to be shut off or recycled. In Heartbeat parlance this is known as a STONITH device—an acronym for “Shoot the Other Node in the Head.” As the documentation says, it is brutal but it is simple. It is also essential to the operation of the cluster. In the case of a split-brain, each Director will attempt
several supported devices to choose from, and there is an interface which you can use to write your own. There is even a meatware device, which pauses for a human being to power cycle a dead node. To see a list of all the available devices, enter the command stonith -h. For this example, I used an IPS400 from Western Telematic, Inc (www.wti.com). This switch has four addressable power outlets, in case you have two power supplies in each server. All you have to is configure the names of the outlets to be the names of the attached hosts, and STONITH will do the rest. If you want more than one outlet to cycle, use the same name more than once. The switch works over the LAN, so it can be reached from anywhere on the network, and it includes a Web-based interface. We have configured it to be 192.168.1.5 (and .2.5 on the MySQL network). The switch costs under $500, which is cheap insurance to make sure your application keeps running. Add the following line to the ha.conf file on each Director (with the appropriate address for the switch and password). Heartbeat will pass the host name to reset to the switch. stonith_host * wti_nps 192.168.1.5 switchpassword
NOTE: the IPS-400 limits the names of the outlets to 8 characters. If your host names are more than 8 characters long, STONITH will send the entire name to the switch and it will not work. If you intend to use this particular piece of hardware, make sure you install your Directors with host names of 8 characters or less, and check with any other vendor for similar restrictions.
Setting the LVS Routing Table Now that we have our Directors able to monitor each other and control the VIP of the LVS, we need some way to see which Realservers are available and add them to Volume 5 Issue 6 • php|architect • 30
PHP Clustering on Linux - Part 2 the LVS table using ipvsadm. This is the job of ldirectord, which is installed as part of Heartbeat and runs on the Directors. ldirectord is a sophisticated program that uses a configuration file to build the routing table and monitor the health of the Realservers. As its name implies, it runs in the background. If a Realserver becomes unresponsive to a health check, ldirectord will take that server out of rotation.
Health Checks on the Web Servers ldirectord has the ability to monitor many kinds
of services on the Realservers. In our case we will be monitoring HTTP. Here is the ldirectord configuration file for our Web cluster. It should be created in the directory /etc/ha.d/conf and named www. negotiatetimeout = 10 checkinterval = 10 autoreload = yes
page to return a string that matches a regular expression defined in the receive directive. Note that, as we have it configured here, this page does not return any HTML elements, only the literal string healthy. By using a PHP page, we can make our health check as sophisticated or as simple as we want. All we have to do is output anything other than healthy, and ldirectord will take the server off-line. Next comes a list of all the Realservers in our cluster. gate means the servers are running LVS-DR, and the number 1 is the weight of the server. The weight is used in determining scheduling priority, according to the scheduling algorithm selected. See the ldirecord man page for details. To test the configuration, you can run ldirectord from the command line, independently of Heartbeat. Enter the command ldirecord www start and then check the routing table with ipvsadm. You should see something like this:
virtual = 192.168.1.100:80 protocol = tcp
IP Virtual Server version 1.2.0 (size=4096)
scheduler = rr
Prot LocalAddress:Port Scheduler Flags
checktype = negotiate
-> RemoteAddress:Port
service = http
InActConn
checkport = 80
TCP 192.168.1.100:http rr
Forward Weight ActiveConn
request = “check.php”
-> 192.168.1.6:http
Route
0
0
0
receive = “^healthy$”
-> 192.168.1.7:http
Route
0
0
0
real = 192.168.1.6:80 gate 1
-> 192.168.1.8:http
Route
0
0
0
real = 192.168.1.7:80 gate 1
-> 192.168.1.9:http
Route
0
0
0
real = 192.168.1.8:80 gate 1 real = 192.168.1.9:80 gate 1
The negotiatetimeout directive tells ldirectord how long to wait (in seconds) for the server to respond to a health check. checkinterval governs how long (in seconds) between checks. The autoreload directive tells ldirectord to reload the table whenever the configuration file is changed, and the virtual directive defines a virtual service. An LVS Director can handle more than one virtual service at a time. A service runs on a given port at an IP address. The rest of the directives apply to the virtual service. The protocol directive specifies the network protocol to use. The scheduler can be one of seven values. Here we are using round-robin, which cycles the requests through all the servers in turn. checktype tells ldirectord what kind of health check to perform. We want it to request a Web page, so it is set to negotiate, and the service directive is http. It will check on port 80 (but you could set it up to check on any port), and it will do so with a request for the page check.php from the server root. It expects that
ldirectord has built the LVS table to our specifications. Now enter ldirectord www stop and check again with ipvsadm. The table should now be blank. Edit the haresources file on each of the directors giving them control of the ldirectord resource. Make sure /etc/ha.d/confg/www exists on both machines. Haresources should read: 192.168.1.3 Ipaddr::192.168.1.100 ldirectord::www
Now start Heartbeat on both Directors and check the logs. Check the routing table with ipvsadm on the primary. You should see the display above. Now stop the Heartbeat service with service heartbeat shutdown on the primary Director. The primary Director should call ldirectord www stop and release the VIP. The secondary Director should assume the VIP and call ldirectord www start. Congratulations, you now have a working Web cluster. There are two commands you can use to gracefully initiate a fail-over. The /usr/lib/heartbeat/hb_standby command can be run on the current active Director, and /usr/lib/heartbeat/hb_takeover Volume 5 Issue 6 • php|architect • 31
PHP Clustering on Linux - Part 2 on the current standby Director. Both commands have the same effect. Neither will cause Heartbeat to initiate a power-cycle using STONITH. To simulate the failure of whichever Director is currently active, disconnect it from the Heartbeat network. Once disconnected, immediately shutdown the Heartbeat service with the command service heartbeat stop. After the timeouts expire, you should see Heartbeat on the then standby Director power-cycle the active Director using STONITH, and acquire the controlled resources. To restore service, reconnect the Heartbeat network and restart the Heartbeat service on the primary Director. Issue the hb_standby command on the backup Director, and the primary will take over. Obviously, do not plug the Directors into the STONITH device for this test!
node writes its data to disk periodically, so nothing is lost in the event of a crash or a shutdown, but the data must be held in memory to be used. The nodes exchange data with each other via the network as changes are made, keeping the cluster in sync. The “share-nothing” aspect of the architecture has the significant advantage that individual components can fail without stopping the cluster from operating. The data nodes can run on non-specialized, commodity hardware interconnected by a TCP/IP network. The current in-memory design means that the maximum size of the database is limited by the amount of RAM in the storage nodes. However, it is possible to have a database that is too large to fit on any one node. The formula for determining the amount of RAM
Connect each of the Directors to a special power switch, known as a STONITH device—an acronym for “Shoot the Other Node in the Head.” Setting up the MySQL Cluster The database cluster actually consists of two clusters. The MySQL SQL Node cluster (on the 192.168.2.0 network) is an LVS controlled, load-balanced cluster just like our Web server cluster. The Web servers will connect to the SQL Nodes on the SQL Node VIP to run SQL queries; LVS will load-balance. The SQL Nodes will in turn connect to a cluster of Data Nodes for data access (on the 192.168.3.0 network). The data storage clustering technology comes as a part of MySQL and is not load-balanced by LVS. MySQL Cluster has its own internal methods of loadbalancing and failure detection. MySQL has the ability to use several different storage engines along with its SQL processing technology. Clustering is handled by the NDB Cluster storage engine, which comes as a part of the MySQL-Max variant of MySQL. The current version of NDB Cluster (at the time of writing, 5.0.18) uses a memory-resident, share-nothing, architecture. The storage cluster divides pieces of the database over a number of independent data storage nodes. No single node will hold the entire database, but each node holds its entire portion of the database in its local memory while the cluster is running (the next point release of MySQL—5.1—will include disk-based data tables which should alleviate this limitation). Each
needed in each storage node is the size of the database required (including indexes), multiplied by the number of replicas of that database (minimum of 2 for redundancy), multiplied by 1.1 for overhead, divided by the number of nodes available. To store a 6 GB database on 4 nodes with 2 replicas, we would need about 3.8 GB of RAM (including 512MB for Linux). If we had 8 nodes available, we would only need about 2 GB of RAM per node. With the upcoming MySQL 5.1, the NDB Cluster engine is slated to be upgraded to allow disk-based data tables (indexes will still be held in RAM). This reduces the amount of RAM needed by the data nodes dramatically. MySQL is under heavy development and there is lots of activity around the clustering technology; version 5.1 is currently in the alpha release stage and does include the upgrade. It probably will not be very long before it is released for production. The MySQL Server cluster setup is the same as the Web server cluster, except that MySQL runs on port 3306 (as opposed to port 80). At this point you should prepare the SQL node cluster by doing the following: 1. Install CentOS on all the machines and fully patch them from the CentOS mirrors.
Volume 5 Issue 6 • php|architect • 32
PHP Clustering on Linux - Part 2 2. Set up arptables_jf on each of the Realservers. 3. Set up Heartbeat on the two SQL node cluster Directors to control the MySQL VIP (192.168.2.100), making the appropriate changes to the configuration files. 4. Configure ldirectord on the Directors to build the LVS table for the MySQL service which runs on port 3306 (not port 80 like the Web cluster).
2) On the SQL Nodes only (192.168.2.5 - .8), do all the above in #1 and then: a. cp support-files/mysql.server /etc/rc.d/init.d b. chmod +x /etc/rc.d/init.d/mysql.server c. chkconfig –add mysql.server 3) Only on the Management Nodes, copy the install file into any temporary directory (like /var/tmp) and execute these commands:
5. Configure the STONITH fence device.
a. tar -xvzf mysql-max-5.0.18-linux-i686.tar.gz
6. Make sure Heartbeat properly transfers the VIP, calls ldirectord to configure the LVS table and uses STONITH to power-cycle a failed node.
b. cp mysql-max-5.0.18-linux-i686/bin/ndb_ mgm* /usr/local/bin
When these steps are complete, you can install MySQL. The full MySQL binaries will need to be installed on each of the SQL Nodes and also on each of the Data Nodes. The SQL Management Nodes only need two files from the install tar ball—they do not need the full binaries. The SQL Nodes will actually need to run the MySQL service for the cluster to operate, while the Data Nodes will not, but they will nonetheless need everything installed. On each Realserver in the SQL Node cluster download and install MySQL Max 5.0.18. You need the Max version because it includes the NDB Cluster engine. To avoid problems, use the statically linked Linux version (mysql-max-5.0.18-linux-i686.tar.gz); the download is about 40 megabytes, but it is guaranteed to work. We want our MySQL installation to be in /usr/local/mysql. The easiest way to do this is to download the file from www.mysql.com and execute the following commands: 1) On the SQL Nodes and the Data Nodes (but not the management nodes): a. tar -xvzf mysql-max-5.0.18-linux-i686.tar.gz -C /usr/local b. cd /usr/local c. mv mysql-max-5.0.18-linux-i686 mysql d. groupadd mysql
c. chmod +x /usr/local/bin/ndb_mgm* d. rm -rf mysql-max-5.0.18-linux-i686 We need to set up the MySQL configuration file, which resides on both the SQL Nodes and the Data Nodes in /etc/my.cnf. This file will be the same for all 8 machines. [MYSQLD] ndbcluster ndb-connectstring=”192.168.3.3,192.168.3.4” old_passwords=1 interactive_timeout=900 wait_timeout=900 max_connections=100
[MYSQL_CLUSTER] ndb-connectstring=”192.168.3.3,192.168.3.4”
The configuration file /var/lib/mysql-cluster/config.ini goes only on the management nodes. It is critical that it be exactly the same on both: [NDBD DEFAULT] NoOfReplicas=2 DataMemory=200M IndexMemory=100M
e. useradd -g mysql mysql f. cd mysql g. scripts/mysql_install_db –user=mysql #(note – is two “-”)
[NDB_MGMD] id=1 hostname=192.168.3.3 datadir=/var/lib/mysql-cluster
h. chown -R root . i.
chown -R mysql data
j.
chgrp -R mysql
[NDB_MGMD] id=2 hostname=192.168.3.4 datadir=/var/lib/mysql-cluster
Volume 5 Issue 6 • php|architect • 35
PHP Clustering on Linux - Part 2 [MYSQLD] [NDBD]
id=8
id=3
hostname=192.168.2.7
hostname=192.168.3.6 datadir=/usr/local/mysql/data
[MYSQLD] id=9
[NDBD]
hostname=192.168.2.8
id=4 hostname=192.168.3.7
[MYSQLD]
datadir=/usr/local/mysql/data
id=10 hostname=192.168.2.9
[NDBD] id=5
We have now configured an untuned MySQL cluster.
hostname=192.168.3.8 datadir=/usr/local/mysql/data
[NDBD] id=6 hostname=192.168.2.9 datadir=/usr/local/mysql/data
# SQL node options: [MYSQLD] id=7 hostname=192.168.2.6
Next Time… Now that we have our MySQL cluster in place, we’re pretty much there. Next month, we’ll continue by setting up the Web and database clusters to work together, before installing osCommerce across our network. Finally, we will load test our Web, database and ecommerce solution. JOSEPH KOUYOUMJIAN has built a computer sales and repair
company with a heavy emphasis on using computer automation in the business. As a software engineer, he has built systems using both proprietary and open source software. He is currently president of StrongArch, Inc., an open source and IT consulting firm. He can be reached at
[email protected].
Available Right At Your Desk
All our classes take place entirely through the Internet and feature a real, live instructor that interacts with each student through voice or real-time messaging.
What You Get
Your Own Web Sandbox Our No-hassle Refund Policy Smaller Classes = Better Learning
Curriculum
The training program closely follows the certification guide— as it was built by some of its very same authors.
Sign-up and Save!
For a limited time, you can get over $300 US in savings just by signing up for our training program! New classes start every three weeks!
http://www.phparch.com/cert
Volume 5 Issue 6 • php|architect • 36
FEATURE
PHP & XForms
PHP & XForms The next generation of HTML forms represents new ways of thinking and creating on the web. Are you ready to wake up and smell the coffee? by RUBÉ N MARTÍNEZ ÁVILA
O
ver the last few years, the Internet has indisputably become an important tool for companies and individuals alike. Companies use it to sell their goods and maintain customer relations; students use it find the information they need with a simple query; and people from all walks of life use it communicate with others all around the world in real time. Thanks to the sheer range of modern Internet usage, technologies dating from the early days of the Web are now recognized as having many limitations. Several new technologies have emerged in response to that recognition, and one technology in particular that is gaining in importance is XML. XML (eXtensible Markup Language) is a universally accepted W3C specification, and represents a new and exciting technology that can be used online. XML is a markup language that allows documents to contain structured data in a very simple form. Sharing a common structure allows we can data to be passed between computer systems in heterogeneous environments. This means that it’s irrelevant which platform our system, the system we want to communicate with, uses. Thanks to the XML standard, we can share information, in that structured form, between any two servers, between any server and any client, or between several very different office applications. XForms, another W3C standard, was created for many of the same reasons that XML was needed. XForms is an up-and-coming technology that helps solve many of the limitations apparent in standard HTML forms. Here are some of those perceived limitations: •
In HTML, you cannot separate data from presentation
•
Data validation requires third-party scripting (not HTML)
PHP: 4.3+5 OTHER: FormFaces CODE DIRECTORY: xforms LINKS: www.w3.org www.formfaces.com freshmeat.net/projects/formfaces www.w3.org/MarkUp/Forms TO DISCUSS THIS ARTICLE VISIT: http://forum.phparch.com/311 •
Responding to user events requires third-party scripting
•
HTML forms are device-dependant: mobile phones, tablet PCs and hand-held computers can’t use them without changes
•
The HTML form standard supports only a single form per page
•
Submitted data is artificially limited to direct key/value pairs and file uploads
With the introduction of XForms, all the issues listed above are said to have been resolved. Does that whet your curiosity? It did mine, too. So let’s take a look at how XForms work!
The X Factor Since XForms is an XML-based standard, it follows the basic rules of XML. Adherence to this base structure enables XForms to support user events and validation, Volume 5 Issue 6 • php|architect • 37
PHP & XForms to submit data in XML format, to have complete device independence, and to allow multiple forms per document. Suppose we wanted to make a very simple log-in page—say, just a title and a pair of input boxes for the username and password. In HTML, the code for doing this will look something like the document in Listing 1. It consists of the document title, a named form, an action and method for submission, labels and text input boxes for the username and password, and of course a submit button. A simple page, but in XML terms it’s already problematic because there’s no way to know, through the code alone, that the strings Log in: and Password: are in any way related to the input text boxes. Another point is that the data is sent out as invalidated key/value pairs. The PHP script responsible for processing our form will need to parse the resulting $_POST array twice, once to validate the data and once to actually process it. This is not too elegant in Web applications; we need more efficient methods for manipulating form data. In Listing 2, you can see the same simple form coded using the XForms standard. You might notice that in the HTML element there is an entirely new section, . This is where the form data is stored, within the tags and described here as . (I’ll return to that in a moment.) Also within the tags, you can see the element ; the action and method belong here. Note that the elements in our XForms example have the namespace prefix xf. You can use any prefix you like here; it simply means that this is an XForms element, and will be processed according to XForms language rules. You determine the prefix used by declaring your chosen namespace for XForms in the opening tag at the head of the document. Let’s zoom in on that for a moment. The tag contains the mysterious looking xmlns=”http://www.w3.org/1999/xhtml”. The xmlns part is an abbreviation of XML NameSpace, and the URL refers to the language specification used by XHTML documents. We don’t need to give that specification a name, because we’re using XHTML as the default “language” in our document. We could name them and have XForms as the default; if you look at the existing documentation on the official W3C site, this is the route taken there, and all their HTML tags are prefixed accordingly. In XHTML 2.0, we are told, namespacing won’t be necessary for XForms any more—but for now, it is. The XHTML declaration is followed by declarations of xmlns:xf and xmlns:ev; again, the xmlns part refers to the namespace itself, and the part immediately after it is where we declare our chosen namespace prefixes for XForms
xmlns:xf=”http://www.w3.org/2002/xforms” and XML Events xmlns:ev=”http://www.w3.org/2001/xml-events”,
respectively. We complete our header tag with an XML language declaration, xml:lang=”en”—in this case, English. Back to the we noticed earlier. —which could be called anything—is simply an envelope element that will surround the form’s XML output: myname mypass
It needs to escape all those namespace definitions, because neither nor the elements it contains are part of any of the XML languages—XHTML, XForms or XML Events—that we’ve just told the XML processor we’re going to use. At this stage, we have merely described what our XForms will do (the action and method on submitting our form); we haven’t added any controls yet. The place for these—as with HTML forms—is in the document body. This is where the form elements are declared; text boxes, buttons, select controls, password boxes and so on. Going back to Listing 2, we can see that there are three controls in our form: , , and . Notice that the syntax for our input text is namespaced—. Similarly, the syntax for our password input becomes , and the syntax for our submit button is now . The elements between the tags—and in this case between the element—match the ref attribute defined in the respective controls, and represent the incoming data. Notice the way that, in XForms, the caption or label in every control is bound directly to the control itself rather than being an entirely separate entity. Finally, you may have spotted that there is no tag in the XForms version. Our input controls could be anywhere in the XHTML page. Now that you have seen how a simple page is set up, let’s take a tour around the main controls supported by XForms and compare them with the ones we know in HTML.
Common Form Controls The most common controls used in XForms are: input text boxes, text areas, radio buttons, select fields, password boxes, file selections, buttons, reset buttons, and two entirely new controls named output and range. We’ve already seen how the input text control is set Volume 5 Issue 6 • php|architect • 38
PHP & XForms up. Text area controls are declared in a similar way: Opinion:
“Now, hold on a moment,” you might be thinking. Where are the rows and cols attributes? They’ve gone completely. You need to define the size of your controls in a CSS stylesheet: textarea[ref=”opinion”] { font-family: sans-serif; height: 20em; width: 80em }
This way, you can describe the appearance of your controls in any of the ways CSS allows—and your form just became portable with a one-line stylesheet include change. When it comes to radio buttons, XForms syntax departs completely from traditional HTML. As you’re aware, radio buttons allow the user to select a single item from a list of available options. Going to Listing 3, you’ll see this meaning clearly represented in the syntax . Each option is represented by a tag, and in every tag is the label and the value of each option. However, the input type isn’t described anywhere; whether the listing is displayed as the radio buttons you thought you were creating, as a dropdown menu, or as any other representation of single selection, is left largely to the device that interprets it. Checkboxes have similar functionality, so let’s take a look at the way they are coded in Listing 4. The multiple selection control appears to be very similar to the single selection control, in that it has a list of items with individual labels and values. The main difference is that the multiple selection control is declared with the tag, which comes with a new-to-us attribute called . This gives us some element of control over the display, and can be one of full, compact or minimal. The effect of those display options is device dependent; but choosing full should display a checkbox control as you would expect to see it in an HTML form, assuming you’re using a browser to view it. Similarly, adding the hint appearance=”full” to the tag in Listing 3 gives us the missing radio buttons under Firefox. We already saw in Listing 2, so we’ll go straight from here to file selects. These are a two-part declaration, thanks to the change in submission method. In the element we need to write:
LISTING 1 1 2 3 Login page 4 5 6 7 8
Login:
9
Password:
10
11 12 13
LISTING 2 1 4 5 6 7 Login page 8 9 10 11 12 13 14 15 16 17 18 19 20 21
22 23 Login: 24 25
26 27
28 29 Password 30 31
32 33
34 35 Submit 36 37
38 39 40
LISTING 3 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
Do you accept? Yes No Do you accept? Yes Y No N
LISTING 4 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
Which are your favorite pets?
Dog Cat Tortoise Fish
Which are your favorite pets? DogD CatC TortoiseT FishF
Volume 5 Issue 6 • php|architect • 39
PHP & XForms
When it comes to radio buttons, XForms syntax departs completely from traditional HTML.
before we can declare our control:
reset buttons actually do more harm than good, so they deliberately made them harder to code:
File:
but more on this later. In XForms, buttons can be used to trigger all kinds of tasks, such as calculating values or opening a new window. This explains the syntax we use to declare a button: Click me! You clicked me!
Clear all fields
You’ll have spotted that here is a child element of rather than a control in its own right. You may also have spotted your second ev:event. Don’t worry, we’ll get to XML Events soon enough! Finally there are the new controls, range and output. The range control, simply enough, specifies a range of values. It has three attributes—start, end and stepwhich, in combination, do something very similar to a for loop in PHP:
Button controls need to be associated with XML Events as part of their declaration; this is fairly complex stuff, so we’ll leave it at that for now and come back to it later. There are only two kinds of buttons that have predefined actions. As you saw in Listing 2, we have to use for submit buttons, much the same as we’re used to doing in HTML. However in XForms, this control has a special attribute named submission. Since XForms gives us the ability to have multiple forms in a single document, the submission attribute serves as a means of distinguishing between them. You can write as many forms as you want, simply by adding a new into the declaration at the head of the document for each form. In each XForms model you create, you need to specify a tag containing the action, the method, and the reference for the specific submit button represented by that model. So in the button, the submission attribute corresponds to in . The other predefined button is the control, which—just as in HTML—is used to clear the fields of a form. The W3C XForms Working Group decided that
Again, it’s important to stress that the way this is displayed is device dependent. It might be a slider, or it might be a spin button... or even a dropdown menu. The output control is the equivalent of echo $var in PHP; it’s a dynamic label used to display variable data. This data would usually come from input fed to other form controls, and—given that XForms doesn’t need to call on the server in order to react to what is being typed—can be used to give the user a running status report on his or her input throughout the transaction. Total volume:
Now that the introductions are over, you should probably know a couple of small but important details regarding submitted, hidden and default values in XForms. To begin with, input hidden controls are not available. If you want to add the equivalent of an HTML form hidden value, you simply add it to the section. You don’t need to add the declaration in the document body. After all, why should you add a control if the user won’t even see it? Volume 5 Issue 6 • php|architect • 40
PHP & XForms Initial values for form controls are defined in the same place. Let’s say we want admin to be the default value for the username input text in our original log-in form. While we’re at it, we might also want to add a hidden value containing the address of the server we want to log on to: admin http://127.0.0.1
When the form is displayed, the value admin will now appear in the username input text box. And with all our basic controls out of the way, this is a good moment to look at the changes affecting submitted data and methods.
Submitting Data There are more ways of submitting data in XForms than in HTML: get, urlencoded-post, form-data-post, post and put. The get method works exactly as you’d expect from your experience with HTML. Values passed via get will appear in the URL of the browser and, once they reach the processing script, can be picked up in the $_GET array there. Everything else is a little different. The post and put methods in XForms actually post the form data as XML. If you want to update or add to something that already exists, you should use post; if you want to create or replace a file, either locally or in a directory on a server that you have the appropriate write permissions for, you should use put. If you’re looking for an equivalent of the HTML way to populate the $_POST array in your PHP script, you’ll need to choose urlencoded-post. In HTML you’d need to have the enctype attribute sent as part of the HTTP header; XForms does this for you, and makes the behaviour of the method explicit through the name. Finally, the form-data-post method is the equivalent of the HTML file upload syntax:
as we saw when we looked at . It may not look as though it’s saving you very much typing at present— but remember: you can have multiple submissions through a single page with XForms. The submission methods are all declared together in the structure in the document head, directly below the list of data parameters in .
LISTING 5 1 4 5 6 7 Login page 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
24 25 Login: 26 27
28 29
30 31 Password 32 33
34 35
36 37 Submit 38 39
40 41 42
LISTING 6 1 4 5 6 7 Login page 8 9 10 11 12 13 14 15 16 17 XForms is ready! 18 19 20 21 22 23 24 25
26 27 Login: 28 29 The username value has changed 30 31 32
33 34
35 36 Password 37 38 The password value has changed 39 40 41
42 43
44 45 Submit 46 47
48 49 50
Volume 5 Issue 6 • php|architect • 41
PHP & XForms
Controlling the Controls When it comes to setting up the controls, you’ll find some interesting properties in XForms. You can directly establish whether a value is required, or whether it needs to be a particular data type, or whether the control should be disabled or read-only—and that’s just the start. You’ll save a lot of time that would have been spent in coding JavaScript to do these tasks for HTML forms. Going back to our little login form, we’d better make the username and password required values. Take a look at Listing 5, and you’ll see that there needs to be a new element between the parameter lists and the details. We need to add before we can create any rules or restrictions for the input data. The nodeset attribute references the data element we’re concerned with, and the constraint does exactly what it says on the box—it constrains that parameter according to the rules defined in it. For both the username and password, it would be wise to refuse an empty string. There are at least two ways to do this, using bind properties, and both are represented in our form. Take another look at the constraint property used in the password field: constraint=”. != ‘’”. That single dot is a little like the one that represents the current directory in a file path. It means you can use one element as a stepping stone to access another; it’s XPath syntax. From the declaration, you can use ../ to reference any parameter listed in the data section, and you can use its submitted value as part of a constraint rule - or as part of a read-only calculation:
readonly is an attribute we usually have
control over, although not in calculated data. We could easily restrict our user to a single password attempt by replacing the constraint property with readonly:
although it’s difficult to imagine why we might want to do that. We could also use bind properties to make the password field only available when username is filled:
Depending on the device you’re using, the password field in that case will probably be greyed out. Using the XForms implementation, you could reliably make the field disappear altogether—but that would mean writing the username and password controls as separate s
(fieldsets in HTML), which seems a little extreme for our humble form. Finally, it’s possible to restrict our input to an XML Schema data type:
This is more useful than it appears; we can restrict our own user-defined data types to a given regex pattern using XML Schema, which we will discuss later. As an end note to this section, it’s probably useful to know that if you’re going to use the comparison operators < and > anywhere in XForms, they’ll need to be written as HTML entities (< and >)-otherwise they won’t get through the XML validation.
XML Events The ability to capture user events makes our applications more powerful. Every event is associated with an object, as you saw when we briefly looked at buttons and resets. We use that association to capture the user event we want to act upon. You may have noticed back there that onClick has mutated to DOMActivate. The XForms Working Group recommends this over the familiar JavaScript onClick, on the grounds that DOMActivate is more portable— there are more ways to say OK than by clicking alone. However, DOMActivate isn’t specific to XForms; it’s a part of the Dom Level 2 Event Model. xforms-value-changed, on the other hand, is specific to XForms, and can be used to make any control into a trigger. We don’t have any buttons in our little log-in form, so let’s try using xforms-value-changed to call up some popup windows. Take a look now at Listing 6. Inside the username and password controls there is a new element, , simply containing message text. It has two new attributes for us, level and ev:event. The former determines the way in which the message will be displayed—depending on the device, naturally— and the latter refers to the event that will trigger the display. Our xf:submit button can’t do this directly, because pressing that button obviously reloads the form. If we want it to appear to do anything other than submit, we need to have the form itself trigger the event—and we do this by using the xforms-ready event in a xf:message tag within the model declaration. That’s all there is to capturing events. Here are a few more for you to play around with: xforms-submit-done, xforms-submit-error, xforms-readonly, xforms-invalid, xforms-valid, DOMFocusIn and DOMFocusOUT. There are several more available—most of them will already be Volume 5 Issue 6 • php|architect • 42
PHP & XForms familiar to you through JavaScript—and you can use them to trigger JavaScript functions, in much the same way as HTML forms can call JavaScript functions during submission. The main difference here is that any element of the form can easily do so in response to just about any user event.
Running XForms When the first draft of the XForms recommendation was published back in 2003, no browser was capable of supporting it. Since then, interest in the technology has increased, and several companies have developed plug-ins for it. However, much of the XForms support available currently is browser-specific and takes the form of client-side plug-ins. That’s not very useful for web developers; we need a cross-browser, opensource solution that can be run from the server side. Meet FormFaces (www.formfaces.com), a dual-licensed implementation written in pure JavaScript. All you need to do is download it, unzip it and put formfaces.js and the Source directory somewhere your web pages can easily reach them. Add the line:
to the declaration in any XForms-enabled page, and you’re ready to go. Let’s begin to test some code. Save our login file as .php, .html or whatever you prefer in your web root directory, add the formfaces.js link to it, and fire up your browser. It is important to say that, although FormFaces is a good implementation of XForms and is fully functional with our applications, it’s not complete—the full XForms specification isn’t available yet. Still, FormFaces has implemented most of what is there, and is already a very useful tool that allows us to create functional XForms code in our current projects.
Where is PHP? We still haven’t examined the role of PHP in all this; it’s probably time we did. You saw that one difference in XForms is that form data can be submitted in XML format. Here is one way PHP will work for us. We are going to receive the form data in XML format, and we’re going to validate it using DOM. With HTML forms, we need to write a lot of code in JavaScript to validate the data on the client side, and once we receive that data we have to validate it again with PHP in the server side, which implies writing code one more time. The good news about receiving the data
as XML is that we can share the same XML schema file for validation at both stages, and that doing so requires very little code. Let’s try using XML as the submitted data format to understand how that part of the process works. Change the submission declaration in the log-in example to read:
To receive PUT data in PHP, we have to consult the PHP INPUT stream: $xml_content = file_get_contents(“php://input”);
We now have the data stored in $xml_content. If you want to check that, just try print_r($xml_content);. Now that we’ve actually received the XML data, the next step is to check its validity, and for that—PHP’s new filtering abilities notwithstanding—we will use XML Schema. XML Schema is an XML language used for writing constraints or business rules. It makes it possible to declare the type or length that the data has to be, and the structure the data should have. See Listing 7 for an example of an XML Schema file. You’ll see that it’s a simple XML structure, defined using the namespace xsd and containing the precise structure we expect our incoming data to have. Notice that, as with XForms, each element within an XML Schema document needs to be namespaced. Notice also that our element is a complex type. That means we’re defining a structure for a new type, one that contains other elements. (Simple types are used for common data types such as string, integer, boolean or date.) We go on to define the sequence of those elements by declaring our data elements within the tag. So far, so good, but XML Schema can do a lot more for us than define our data structure. We can get fussy about the format of the data itself. Take a look at Listing 8, where our schema has been extended to do just that. It will now turn down any attempts to submit a username shorter than 6 or longer than 12 characters, and it will also insist that the password—again length-limited— contains at least one digit. Save this file as schema.xsd, and we can use it to validate our data at both ends of the form submission process. To add type-checking to our form submission process, all we need to do now is link this file to the model declaration like so:
Volume 5 Issue 6 • php|architect • 43
PHP & XForms
and it will work in most cases. However, it’s worth bearing in mind that not every device that will be capable of supporting XForms will also have support for XML Schema; it’s possible that PHP will carry the entire responsibility for validating our XForms data. Luckily, the DOM extension shipped with PHP 5 has a built-in method for validating an XML document using an XML Schema and letting us know the outcome. Here is the entire validation process: $dom = new DOMDocument; //create a new object $dom->loadXML($xml_content); //load our XML data into it if (!$dom->schemaValidate(‘schema.xsd’)) { //the data is not valid } else { //the data is valid }
What’s the next step? Once we know whether the data is valid or not, we need to decide what to do in either case. If it’s going to be stored in a database, we’ll need to either be certain that our DBMS supports native XML, or else extract the individual data elements using either DOM or SimpleXML so that we can populate the database fields in the traditional way. Of course, we still have the option of using the good old-fashioned $_POST array—but where there are rather more than two elements of data, I think you’ll see the benefits of transmitting XML. One feature of FormFaces is that, when you click on the button, the browser doesn’t redirect you to another location. AJAX applications have become very popular nowadays, thanks to this characteristic; it makes the Web experience more like working with desktop applications. We should take advantage of this feature, but we also need to let the user know whether their input has been accepted or not. Ironically, that’s one thing that’s easier to do through XForms validation than it is through PHP validation.
LISTING 7 1 2 3 4 5 6 7 8 9 10 11 12
LISTING 8 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
to increase productivity. PHP 5 has been quick to adapt to this trend, and already offers strong support for XML technologies in the shape of the DOM, SimpleXML and SOAP extensions that now ship with the PHP distribution. We have the potential to make our PHP applications more powerful and easier to manage by utilizing that support in combination with XForms, XML Schema and the native XML support in the DBMS.
Conclusion These days, a lot of tools and applications have native support for XML data, and this is true of several database management systems. With Microsoft SQL Server, for example, you can make queries and get the results in XML format with an associated XML Schema. Ingres and MySQL 5.1 already have native support for XML, and the PostgreSQL development teams are starting to look into providing such support. The growth of XML-based technologies in general gives software developers of all kinds the opportunity
RUBÉN MARTÍNEZ ÁVILA is a web developer and an instructor specializing in Open Source Technologies at AQUA Interactive. He is interested in Software Engineering, Database Technologies, and Sciences. You can contact him at
[email protected].
Volume 5 Issue 6 • php|architect • 44
TEST PATTERN: Dependency Injection
TEST PATTERN
Dependency Injection Dependency injection is a simple pattern and philosophy that improves the reusability, testability and maintainability of code. This month, we’ll look at the basics of this technique.
by JEFF MOORE
I
n the time that I have been programming, one of the dreams that I see continuously renewed—but that never quite lives up to its hype—is the idea of software components, software ICs, software factories, or whatever the buzzword is today. (Service Oriented Architecture?) Somehow, in each iteration, programming is going to become mostly about wiring together a variety of pre-written components. Code gets reused, productivity goes up, and programming costs go down. World peace ensues. Code reuse is the holy grail of programming productivity, isn’t it? After all, we stand on the backs of giants when we write the simplest . This code invokes PHP, Apache, Linux and scores of independently written libraries that come together to perform this simple task. Imagine being transported back to the 1950s and having to write a basic CRUD application.
TO DISCUSS THIS ARTICLE VISIT: http://forum.phparch.com/312 Without the modern tool chain, how productive could you be? At least expectations would be low. On the other hand, there are some drawbacks to reuse. Reuse proponents like to chide people about the not invented here syndrome, but there are some sound reasons for avoiding other people’s code. When you reuse someone else’s code, you take on their dependencies. What happens if the code owner goes in a different direction or fails to update the code for new technologies? When Windows came out, many leaders in the DOS applications market fell because their tool and library vendors did not migrate to Windows fast enough. The more vendors you
Volume 5 Issue 6 • php|architect • 45
TEST PATTERN: Dependency Injection
depend on, the more risk you assume. Bloat is another common reason given to avoid reuse. Why should I accept the overhead and bugs for features I’m not even going to use? This is a legitimate concern, although programmers tend to underestimate the effort and risk involved with implementing something from scratch.
a few month ago. But, dependencies present specific problems for reusable code. Trying to reuse tightly coupled code is like trying to extract a single lure from a poorly organized tackle box. The hooks snag other lures, lines, your clothes, and anything else that might be nearby. Trying to use poorly organized code in another context is the same way. The dependencies snag all sorts of things
Trying to reuse tightly coupled code is like trying to extract a single lure from a poorly organized tackle box. Another problem is just finding quality code that can be reused. There is a lot of useless or inappropriate code out there. Who has time to weed through it all? Only a very few packages can gain the reputation for quality and usability that earns them widespread use. For the rest, we just re-invent the wheel. One more problem is that packages intended for reuse often don’t do exactly what we need and may not be extensible or configurable. They can be poorly documented and poorly understood. Perhaps the learning curve is not worth the benefits? Let’s switch from the consumer side to the producer side in the reuse equation. Producing reusable code is hard: maybe an order of magnitude harder than creating single situational code. Trying to create a high quality package that solves a problem that’s simple, configurable, extensible, flexible, well documented and well supported is not easy. There are many factors requiring attention. About a year ago, I started experimenting with a new technique that makes the task of creating reusable code easier. That technique is called dependency injection.
Puppy Dog Tails If reuse represents the sugar and spice of programming productivity, dependencies are the snakes and snails. I talked about the evils of dependencies, A.K.A. coupling,
that you would rather not have. If somebody handed you a snarled mess of lines and lures, would you want to put that in your own tackle box? So what are these hooks and snags in PHP? For our purposes, they are objects, classes, interfaces, and global variables. Functions and files may form dependencies, but they do not lend themselves to this discussion. These are the primary dependencies that a PHP class can have. All of an object’s dependencies form an environment in which the object lives. The more complicated the web of dependencies, the more difficult it is to place that code into a different environment—and that’s fine. Not all code has to be reusable. Actually, writing code that lives inside a rich environment can be very productive. Many successful software applications offer plugin APIs and become platforms that support rich ecosystems. As you might imagine, PHP’s built in functions, classes, and interfaces form an environment for your PHP code. These dependencies are generally considered neutral. They are always there and most PHP programmers can be expected to be familiar with them. However, there is always a learning curve associated with programming inside any environment and PHP is no exception.
Canary in a Coal Mine Even for code that is not targeted for reuse, there is one
Volume 5 Issue 6 • php|architect • 46
TEST PATTERN: Dependency Injection area where isolating the code from its environment is a distinct advantage: unit testing. Unit testing is our canary in a coal mine for dependency problems. The more
Let’s look at an example of a typical class dependency in PHP. Let’s start with an imaginary FeedFetcher class. The job of this class is to fetch an RSS feed, given an URL.
The more complicated the web of dependencies, the more difficult it is to place that code into a different environment complicated the environment that an object lives in, the more difficult it is to create tests for it. Actually, I find that unit testing provides advanced warning for all sorts of problems. When code is hard to test, it also turns out to be hard to understand and hard to maintain. I’m focusing on reuse in this column, but you can probably substitute maintainability just as easily. Code with fewer dependencies is easier to maintain.
So how do we go about untangling our dependencies?
As you might imagine, the job of fetching the feed could become a performance bottleneck. It’s not unreasonable to want to cache the fetching of the raw XML. To do this, we might want to introduce a FeedCache class. In a common PHP implementation, the FeedFetcher class might create an instance of FeedCache with code that looks something like $cache = new FeedCache(). This represents a hard dependency on a concrete class. One problem with this is that our FeedCache class is very likely to need some configuration information. It may need a maximum age, or a cache directory, or perhaps
LISTING 1
LISTING 2
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
Untangling dependencies
public function __construct() { $this->cache = new FeedCache::getCache( CACHE_DRIVER, CACHE_AGE, CACHE_DIR); } } $fetcher = new FeedFetcher(); ?>
Volume 5 Issue 6 • php|architect • 47
TEST PATTERN: Dependency Injection some error handling configuration. How can a client of the FeedFetcher get this configuration information into its FeedCache object? One common way to do this would be to create a set of defined constants for this configuration information. We might create a CACHE_DIR constant or a CACHE_AGE constant, or even a CACHE_DEBUG constant that tells our FeedCache class how to operate. The problem is that these are little better than global variables, and they are unchangeable global variables at that. What if you need two different caches for different sets of feeds? Sorry, there can be only one CACHE_DIR. A FeedCache that depends on defined constants for configuration isn’t very reusable at all. What if you are testing the FeedFetcher and you don’t actually want to have the cache involved at all? Since FeedFetcher is constructing the FeedCache directly, there
Now, we can say that we are pushing the dependencies into the object from the outside. This is where the term dependency injection gets its name. We inject the dependencies into the object through its public interface. Pretty Simple, huh? What have we gained? Since both FeedCache and FeedFetcher are constructed by the same unnamed code fragment, that code is free to configure FeedCache in whatever way is necessary before passing it to FeedFetcher’s constructor. There are no global variables. The interface to FeedFetcher retains clarity of purpose. Multiple FeedCaches can be used. FeedFetcher becomes easier to test because we can pass in a mock FeedCache, simplifying the setup in the FeedFetcher tests. Additionally, FeedFetcher is a bit more self-documenting. Instead of having its dependency on FeedCache hidden internally, it declares its dependency on FeedCache explicitly on its public interface in the
When code is hard to test, it also turns out to be hard to understand and hard to maintain. is no way to have it use a mock version of FeedCache for testing purposes. Additionally, the unit test is required to set up correct values for the configuration constants, making both of these classes hard to test. Another way would be to add caching configuration options to the interface of FeedFetcher. FeedFetcher could then pass these options on to its FeedCache instance. This does eliminate the globals for configuration. One could have multiple caches. Unfortunately, this approach doesn’t much improve the reusability or testability of either class, but it does bloat the interface for FeedFetcher, contaminating its singularity of purpose. So, how can we offer configurability for FeedCache without resorting to globals and without contaminating the interface of FeedFetcher? The solution lies in breaking the direct concrete dependency formed by constructing FeedCache with new FeedCache() inside FeedFetcher. With this kind of dependency, FeedFetcher can be considered to be pulling its own dependencies from the inside. To break this, we move the new FeedCache() from inside the FeedFetcher class to outside. Since we will still need the FeedCache, we add it as a parameter to the constructor of FeedFetcher: public function __construct(FeedCache $cache).
__construct() parameter. There is one more factor to
consider.
It depends on the interface In our example, it’s reasonable that we might have more than one kind of cache. We might want our FeedCache to use the file system for a backing store, or perhaps shared memory or a database. A common PHP pattern to solve this problem is to load different back end drivers for different data stores. We might create a FileCache, a SharedMemoryCache, and a DatabaseCache. In our original FeedFetcher example—the one where we create our FeedCache with new FeedCache()—we need to be able to tell FeedCache which backend driver to use. This becomes part of the configuration for FeedCache. A typical solution might be to associate a type code with each driver. Then FeedCache becomes a factory that translates the type code into the correct class and its corresponding file name. For example, the factory might translate file into FileCache using some code like $classname = ucfirst($type) . ‘Cache’. Then, the type code can be passed to the constructor of FeedCache as in new FeedCache(‘file’), or a static factory method can be used, such as Cache::getCache(‘file’). Volume 5 Issue 6 • php|architect • 48
TEST PATTERN: Dependency Injection The problem with this is that eschews one of our primary benefits of object-oriented code. It implements a proprietary dispatch mechanism, when we already have a polymorphic dispatch mechanism available from the PHP object model. There is nothing special about the type code file in this example. Since our dependency injection implementation deals with an object instance, why bother with a type code at all? The code that constructs the cache can construct the correct cache instance directly with new FileCache() instead of using a
for, such as maintainability and understandability. Listing 1 contains the skeleton of a FeedCache implementation that uses a type code and defined constants. Listing 2 is the refactored version using dependency injection and interfaces.
Containers There are many benefits to dependency injection. We have come closer to our dream of actually being able to wire together independently written objects. Our objects
The job of the container is to make it easier to construct objects that rely on dependency injection. factory. Then, of course, it would pass that object to the constructor of FeedFetcher. Since we now have many cache implementations, we can extract the common interface. Let’s simply call that Cache, as opposed to the concrete class FeedCache that we have been using. FeedFetcher would be changed to depend on the abstract interface Cache instead of the concrete class FeedCache. We can then pass any implementation of Cache into FeedFetcher. This organization has the advantage of making FeedFetcher even more independent of the particular cache implementation it uses. Actually, an adapter could be written for the Cache interface that FeedFetcher depends on, that adapts almost any cache implementation into one that FeedFetcher could use. That makes FeedFetcher with an interface dependency usable in more environments than the FeedFetcher with a class dependency. Additionally, we’ve made caching into a nearly independent subsystem that could also be used in other contexts. With dependency injection and interfaces, we’ve improved the reusability and flexibility of our code. FeedFetcher is now easier to test because we can more easily create a low configuration mock cache to use while testing it. The cache system is also easier to test because we no longer have the indirect proprietary dispatch mechanism. With dependency injection and interfaces, we’ve improved the testability of our code. Hopefully, we’ve also improved the factors that testing is a proxy
are simple, easy to test, independent, and pluggable. Our component system is non-invasive and we avoid lock in. We have a greater consistency in configuration management and our dependencies are better documented. There are some drawbacks. Because we push our dependencies into the construction interface of the object, the objects can become more complicated to construct. As you read the dependency injection literature, you might run across the concept of a dependency injection container. The job of this container is to make it easier to construct objects that rely on dependency injection. However, the objects being constructed are not in any way dependent on the container being used. I wanted to make that clear by presenting dependency injection here without using a container. The base concept of dependency injection is really quite simple. We don’t need a container to gain the benefits of dependency injection.
JEFF MOORE learned to program in the 80s, worked on ERP systems in the 90s and is devoting this decade to PHP. Jeff does freelance programming, works on the open source framework WACT and occasionally posts to his blog at http://www.procata.com/blog.
Volume 5 Issue 6 • php|architect • 49
SECURITY CORNER: All Your Session Are Still Belong to Us!
SECURITY CORNER
All Your Session Are Still Belong to Us! A person’s identity is a precious thing; it defines who we are and ultimately makes each person unique. In the offline world, the identity of a person is often represented by various documents ranging from social security numbers, passports, credit cards and so on. For the most part, people try to keep this information secret to prevent outsiders from taking over their identity. When it comes to the online world, the situation is much simpler: the “identity” of a person is often nothing more than a short stream of bytes passed via URLs or Cookies, known as sessions. Last month, we covered the basics of sessions and a few ways to protect your users’ data. This month, we’ll dive deeper into the dangers of session management. by ILIA ALSHANETSKY
L
ast month, we looked at the problems with cookie-based sessions. Many developers, fearing cookies, turn to URL-based sessions as an alternate means of ensuring security. While using this method avoids some of the issues we covered, URLs have their own share of weaknesses. The most prevalent is the exposure of the session id in every single link, which makes session id theft a trivial task—all that a malicious site has to do is
TO DISCUSS THIS ARTICLE VISIT: http://forum.phparch.com/313 analyze the HTTP Referrer. When most browsers make a request, they conveniently pass the URL of the page that the user previously visited via the Referer (no this is not a typo) header. The value contains not only the URL, but also all of the query parameters—which includes Volume 5 Issue 6 • php|architect • 50
Anytime
Anytime
Anytime
SECURITY CORNER: All Your Session Are Still Belong to Us! the session identifier, if applicable. This means that all a malicious site operator must do to get a hold of a user’s session id, he must simply embed an image onto the page. As soon as the page is loaded the browser will automatically download the image, making a request to the hacker’s server and happily transmitting the session
doing so may break some sites that rely on it. So, what can we do on the server side to prevent this form of hacker? The simplest solution involves a technique that I like to refer to as “session cycling.” This process involves changing the session ID on every each request, meaning that even if the hacker obtains
The referer contains not only the URL, but also all of the query parameters. id through the referrer. The requested page can be a perfectly valid image to hide detection and the user won’t even suspect that their identity has been compromised. In the absence of images, the hacker simply needs to create a link to his own site and put a sufficiently interesting caption that would make some users click on it. With the session id in hand, it’s trivial to become the compromised user.
the user’s session key, the id they got a hold has become invalid before they’re able to use it. The reason for is that the value found in the referrer comes from the current page, which displays a session id that is one request to old, as a new id was generated when the page was loaded. The only way the active session id can be stolen is if a hacker manages to do cross-site scripting on the page and use JavaScript to fetch the valid id from a link on the page.
if (isset($_SERVER[‘HTTP_REFERER’])) { $url_info = parse_url($_SERVER[‘HTTP_REFERER’]);
session_start();
if (isset($url_info[‘query’])) {
if (version_compare(“5.1.0”, phpversion(), “>”)) {
parse_str($url_info[‘query’], $params);
session_regenerate_id(TRUE);
if (!empty($params[‘PHPSESSID’])) {
} else {
file_put_contents(“session_log”,
unlink(ini_get(“session.save_path”).”/sess_”.session_id());
“{$url_info[‘host’]} >> {($params[‘PHPSESSID’]}\n”, FILE_APPEND);
session_regenerate_id(); }
} } } readfile(“./daily_funny.jpg”);
The above example is a little script designed to analyze referrer information and log the domain and session id when they are available. The script determines if there is a referrer value available and if it exists, breaks it down into path components. It then examines the URL query to determine if PHPSESSID (common name of PHP sessions) is available. If it is, its value and the domain are logged. At the end of the script, the desired image (this could be any content) is displayed. To the victim, the fact that this particular process occurred is entirely invisible, making detection impossible. The only thing a user can do is disable the sending of the referrer—a feature that many modern browsers support—however,
The above snippet demonstrates the implementation of session cycling, using PHP’s session extension. If PHP 5.1 or higher is used, the session_generate_id() function is used to create a new, random session id and move all of the data from the old session into the new one. The TRUE parameter indicates that the old session should be removed. This bit of functionality was unavailable in earlier versions, so we need to use a bit of a hack to work around this limitation. The hack involves fetching the session storage location, and using unlink() to remove the session file. Needless to say, for this hack to work, the storage directory needs to be found inside the session.save_path INI setting, and sessions have to be file-based. If you are using an alternate or user-based handler, the session removal process will need to be performed in a different fashion. Volume 5 Issue 6 • php|architect • 52
SECURITY CORNER: All Your Session Are Still Belong to Us! While on the topic of the PHP’s session extension and data storage, another possible security fault should be mentioned. By default, the PHP session handler uses file based session storage, meaning that for each session, a new file is created. Unless otherwise specified, the session file is created inside the system temporary directory, usually /tmp on *NIX based system and c:/Temp or c:/windows/temp on Windows. The temporary directory is that is normally accessible to any user on the system, and since the session file name contains the session id (e.g. sess_ffdcd8be3614fd7fabfbdb0ba1511ce7), stealing the session id becomes trivial to any local user. A simple PHP script can do the job of listing all of the sessions and even decoding their contents. foreach(glob(“/tmp/sess_*”, as $ses)) { $data = unserialize(file_get_contents($v)); }
The 3-line script above will fetch all active session files from the temporary directory, iterate through the resulting array and decode each session’s contents. All of this means that any local user can not only fetch the session id, but they can also read the session’s contents and in many cases even modify what’s in it. The latter is possible due to the fact that—in most instances—PHP runs under the Web server user and all files created by it, such as session files, are marked as belonging to this user. To modify a session’s data, all that a local user needs to do is to write a little PHP script and then access it through their browser. $sess_data = unserialize(file_get_contents(“sess_...”)); $sess_data[‘admin’] = 1; file_put_contents(“sess_...”, serialize($sess_data);
The above snippet does exactly this; it opens a session file, decodes the serialized data found within and adjusts the value of the “admin” flag used to determine if the user is an administrator or not, in a hypothetical application. The script then serializes the data and writes it back to session file, overwriting the original. At this point, a user to whom the session belongs, acquires administrator level access to the site and/or application. Some may say that this attack would not be possible had the open_basedir and/or safe_mode PHP configuration directives been enabled. However, it is important to remember that while such options exist for PHP, they do not exist for Perl, Python and any number of other programming languages that may be available to the users of the system. So, relying on these directives to restrict access to the files is a not foolproof. Another
common solution is to modify the session.save_path INI setting, instructing the extension to store the sessions inside an alternate directory. Doing so may make it even more difficult to locate the session files, but ultimately it is still possible to do so without too much effort. On many systems, it may be as simple as running “locate sess_”; the locate command is a common *NIX file indexer that builds a search database of all the files on the system. Since it runs as the root user, it has access to everything and if enabled, can be used to easily locate session files, anywhere on the system. So, what is the solution? The only safe solution is to change the location of where the session data is being stored to a more secure mechanism, such as a database. There are native database drivers, such as session_pgsql that can be found in the PECL repository, or you can simply make your own from within PHP. Since access to the database requires correct login credentials, local users will not have the ability to access the session data without determining the correct access credentials to the database. To store sessions inside the database, we first need to create a table where the session data will be stored. CREATE TABLE sess_data ( id CHAR(32) NOT NULL PRIMARY KEY, data TEXT, expires TIMESTAMP, INDEX(expires) );
The id field will be used to store the session id and will be the main access field. As such, it is designated as a primary key. This designation also makes sure that each session id is unique, as a primary key disallows duplicate values. Then, we have the data column to store the actual session data and an expires column to store the date on which the session will expire. The “expires” column is indexed to accelerate garbage collection, which can take quite some time on a large site, without this index in place. function open($save_path, $sess_name) { define(‘sess_con’, mysql_connect(“host”,”login”,”passwd”)); return (sess_con && mysql_select_db($save_path, sess_con)); }
The open handler is the first event we need to address; it happens during session initialization. The open() function is passed two parameters: the session’s save path as defined by the session.save_path INI setting, and the name of the session. For our purposes, only the Volume 5 Issue 6 • php|architect • 53
SECURITY CORNER: All Your Session Are Still Belong to Us! $save_path parameter is necessary, as it will be used
$id = mysql_real_escape_string($id, sess_con);
to store the name of the database where the sessions are to be put. The handler itself is really quite simple; it opens a MySQL connection, stores the connection resource inside a constant, and then selects the correct database. If connection creation was successful and the database could be selected, TRUE is returned, which tells the session extension that all is well and that it can proceed.
mysql_query(“DELETE FROM sess_data WHERE id=’{$id}’”);
function write($id, $data) {
return TRUE; }
In some instances, a session needs to be removed, for example when a user logs out. This is where the delete handler comes into play. The job of the handler is to take the session id and then remove the session associated with that id. In our case, this can be accomplished with a simple delete query.
$id = mysql_real_escape_string($id, sess_con); $data = mysql_real_escape_string($data, sess_con); return (bool) mysql_query(“INSERT INTO sess_data (id,data)
function gc($expiry) { mysql_query(“DELETE FROM sess_data WHERE expires+{$expiry}
VALUES(‘{$id}’,’{$data}’) ON DUPLICATE
< NOW()”);
KEY UPDATE data=VALUES(data)”, sess_con);
return TRUE;
); }
}
The write handler takes two parameters: the session id and the data to be placed into the session. It is used for both updating existing sessions and creating new ones. The simplest way to handle the needed functionality is to use an INSERT query with an ON DUPLICATE KEY handler that will trigger when the uniqueness constraint on the id column is being violated. In this instance, rather then inserting a new record, the value of the data column in the original record will be updated. We don’t need to worry about the expires field as it will be automatically populated with the current date by MySQL.
PHP cannot rely on users setting up cron jobs (or equivalent) to remove expired sessions, so every once in a while, at the end of request, it performs garbage collection. The purpose of this process is to remove any expired sessions. The garbage collection handler is a way for us to provide this functionality. The function takes one parameter: the maximum age of the session is seconds. We combine this value with the time of the last modification for the session, and if the sum is less than the current time, the session is removed. session_set_save_handler(“open”, “close”, “read”,
function read($id) { $id = mysql_real_escape_string($id, sess_con); $res = mysql_fetch_row(mysql_query(“SELECT data FROM sess_data WHERE id=’{$id}’”,sess_con)); return $res ? $res[0] : ‘’; }
The read handler is even simpler; all it does is take a session id and try to return the session data based on the given value. function close() { mysql_close(sess_conn); return TRUE; }
The close handler is executed once all of the session work has been done, and in our case, it is ideal for the purpose of closing the established MySQL connection. function delete($id) {
“write”, “delete”, “gc”);
The final step in registering a custom, MySQL session handler is to register the handler functions via the session_set_save_handler() function. At this point, call session_start() and session work can begin. The end result of these few lines of code is a much more secure session storage mechanism that is much more difficult to compromise.
Session Fixation The attacks against sessions we’ve seen so far have all been based on various techniques designed to somehow obtain the session id, but there is another approach to session hacking, called Session Fixation. This approach is much simpler to execute because it does not require the hacker to predict the session id; instead, it gives them the ability to set a specific user’s session id to a known value. The attack relies of a “feature” of many session systems, including that of PHP’s session extension, which says that if a user Volume 5 Issue 6 • php|architect • 54
SECURITY CORNER: All Your Session Are Still Belong to Us! specifies a session id, which is not found on the server, the specified value will be used for the session id. For example if you were to go to any site using PHP’s session extension where URL sessions are enabled and to pass PHPSESSID=ABC123 via the query string, your session id would now become ABC123. This bit of functionality can be explained by the
The other approach is similar, but slightly different. Rather the having the check at the time of first use, every time the user’s privileges change, their session id is regenerated. For example if the user comes to the site with a fake session, they continue to use it up until they login. Once they login, the session for the logged-in account is created on the server and assigned to the user,
Session Fixation does not require the hacker to predict the session ID. fact that a new session id is generated only if there is no existing value. By specifying our own session id, we make PHP skip new session creation and subsequently use the provided value. So how can this be abused? Well, a hacker simply needs to convince the user to visit a site where this behaviour occurs, and indicate a supposed session id via a URL. If and when the user logs in, rather then having a random and unpredictable session id, their id is actually the one hard coded by the hacker. To perpetrate the attack, the hacker simply needs to convince the user to click the link leading to a site they trust and eventually login. In many instances this attack is coupled with spam going to millions of users inviting them to visit their bank, popular e-commerce site, etc. A small percentage of users will always fall for it and that’s plenty for the hacker. Fortunately for us, defending against this attack is fairly trivial, all we need to do is either reject session ids that have either expired or have not been created by us. This can be done in the following manner:
which means that the old, fake session id cannot be used for anything except to browse the site anonymously.
Wrapping Up This concludes our brief tour through the common attacks against sessions in PHP and the review of possible defense techniques. For the most part PHP’s session extension was used, however it is important to remember that even a custom, user-land session handler is likely to suffer from similar problems and requires the same defense mechanisms. After all, the last thing any site operator wants to have happen is to have their users’ identities compromised by third parties.
session_start(); if (empty($_SESSION[‘valid’])) { session_regenerate_id(); $_SESSION[‘valid’] = 1; }
ILIA ALSHANETSKY is the principal of Advanced Internet Designs Once the session is started, we check for the presence of the “valid” key, if it is absent, we regenerate the session id and initialize the validation key. With this little check in place we know that only session ids created on the server are valid. All other sessions will not have a valid key, which stored on the server and cannot be set externally and therefore will be rejected.
Inc., company specializing in security auditing, performance analysis and application development. He is an active member of the PHP’s quality assurance team with hundreds of bug fixes to his name as well as a sizeable number of performance tweaks and features. Ilia is a regular speaker at-PHP related conferences worldwide, the author of php|architect’s Guide to PHP Security as well as many magazine publications. He also maintains an active blog at http://ilia.ws, which is filled tips and tricks on how to get the most out of PHP.
Volume 5 Issue 6 • php|architect • 55
SECURITY CORNER: All Your Session Are Still Belong to Us!
Volume 5 Issue 6 • php|architect • 56
PRODUCT REVIEW: PHP Protector 3.8
PRODUCT REVIEW
PHP Protector
The Castle for Your Code?
by PETER B. MacINTYRE
T
his month, I will be taking a look at PHP Protector from ByteRun Software. This tool promises to protect your code by encrypting it, and also allow you to distribute your code through installation scripts. This all sounds great, but let’s see if this is the case. Here, as per my norm, is the first word from the product creator: All scripts protected by ByteRun Protector for PHP are converted to bytecode and encrypted. These scripts contain no source code, but they are still executable and cross-platform compatible. This time-proved technique is 100% secure. You can create trialware scripts (that will expire), limit script usage (by means of domain lock) and add customized copyright information.
PHP: 4+ & 5+ PRODUCT VERSION: 3.8 O/S: Any PRICING: $79 US (lite edition) $299 US (professional edition) LINK: http://www.byterun.com/php-protector.php
ByteRun Protector for PHP makes project management easy. The visual interface was developed to meet the needs of PHP developers. All common tasks can be performed with a single mouse click! Volume 5 Issue 6 • php|architect • 58
PRODUCT REVIEW: PHP Protector 3.8
FIGURE 1 So let’s see if we can prove these claims. Please keep your hands and arms inside the vehicle at all times; here we go…
Getting Started The download and installation were very straightforward, and as you would hope, quite secure. After running a 5-screen installation wizard, I was presented with my request for the secure access code in order to proceed. Once this was authenticated, I was presented with a start up screen (Figure 1) that offered me a choice of working with one of two examples, or working on my own projects. I started with the sample project to see what that was like. As you can see from Figure 2, the basic interface screen is tab-based and seems well laid out. All of the project options are there for you to explore and to fiddle with. The tabs across the main screen are: Project Summary, File Selection, File Inspector, PHP Options, Execution Options, Copyright Options, and Destination. FIGURE 2
Volume 5 Issue 6 • php|architect • 59
PRODUCT REVIEW: PHP Protector 3.8 FIGURE 3
the example Web site running on my local test server. However, this is just the tip of the proverbial iceberg. I was thinking “how could this be so well done?” There would have to be an ace up their sleeve. So, I checked out the source code for the index.php file in its natural state and in its encrypted state. Figure 7 shows these two files. The encryption looked great to me; certainly I would not be able to reverse engineer it. But I was curious as to how the higher level of encryption would look. So, I went back to the options in the Protector and increased the level of protection to STRONG. When I did this and re-served FIGURE 4
I will explore a few of these option screens a little later in the review. Since I was offered an example application, I simply clicked on the “Start Express Wizard” compilation button, and started the encoding process. Figure 3 shows the first screen of the wizard that gives you a summary of what you are about to encode. What intrigued me on this screen was the “Options” button at the bottom, which opens the dialog shown in Figure 4. This is a very nice feature and it is quite powerful, as well. How often have you wanted to give someone a fully operational application for them to try out and then pay for, but they delay and delay on the payment until you have to threaten a law suit? Well, I seem to have wondered a bit there, but you can see that this is indeed a valuable feature to have: the ability to control if and when your code stops working, as well as what URL it will only work on. That’s POWER! After running the wizard to its completion (I did not fiddle with any of the default options), I was presented with this summary screen (Figure 5). The summary tells me where the encrypted files have been stored and what level of encryption strength was used. This also gives a little advice on possibly increasing the level of encryption to prevent source code extraction. It’s all good so far.
FIGURE 5
Digging Deeper So, I was able to encode a sample application, but how do I know that it worked? The next thing I did was copy the encoded files to my server, running on localhost, to see what they looked like when served. Figure 6 shows
Volume 5 Issue 6 • php|architect • 60
PRODUCT REVIEW: PHP Protector 3.8 the index.php page, I was given a different page telling me to download and install some additional files that were required for this level of protection. After fiddling with my php.ini file, and re-starting my server a few times, the better encrypted site was provided. The encryption code for this page is about twice the size (Figure 7). I also had to perform the encryption a little differently by using the project>protect menu item rather than the wizard, as the wizard does not seem to pick up the fact that I increased the level of encryption. I made these changes on the PHP Options tab (shown in Figure 8) where there were some handy controls. You can also see here that what is called the loader is really the key to having these encrypted files reversed when it is time to serve them. That is the magic that they are using. But that is not all that this product can do. It can also create an encrypted and password protected install.php file for you. This process is shown in Figures 9 and 10. Figure 9 is the setup screen for the install file, and Figure 10 is the PHP file running
the installation process and requesting the password. This is another great feature of this product! To have this feature alone at hand to the PHP developer of commercial software is well worth the price.
Summary I was a little dubious of this product when I first took a look at it, as I was wondering just how this encryption thing would work and what kind of voodoo it might do to my machinery. The company’s web site is a little sparse and FIGURE 6
FIGURE 7
Volume 5 Issue 6 • php|architect • 61
PRODUCT REVIEW: PHP Protector 3.8
FIGURE 8
FIGURE 9
to the point, but functional. After playing with PHP Protector and using it to actually run installs of encrypted files, I was totally sold on its worth. The only draw back that I see in this product is that you cannot actually directly edit the PHP source files, but to be fair PHP Protector does not claim to be a PHP IDE. The online help was well done and the e-mail support was great and responsive—I had to send a few e-mails for some direction on the products use.
Dynamic Web Pages www.dynamicwebpages.de sex could not be better |
PETER MACINTYRE lives and works in Prince Edward Island,
dynamic web pages - german php.node
http://www.paladin-bs.com.
news . scripts . tutorials . downloads . books . installation hints
Canada. He has been and editor with php|architect since September 2003. Peter is a Zend Certified Engineer. Peter’s web site is at
Volume 5 Issue 6 • php|architect • 62
///exit(0); ////// Are We Losing LAMP? by M ARCO TABINI
I
n a recent post on a blog that was reported in our news[1], author Cliff Wells claims that the light has gone out on LAMP. I have addressed enough of these articles in the past—as have many others who know a lot more about technology than I do—and I am not really going to address another one here. In fact, I am only going to use it for the most human of human form of expression: imitation. More precisely, I am going to “borrow” the title of our news item and shuffle it around a bit. The question that I have been asking myself lately is not whether LAMP is lost, but whether we, the community, are losing control over it. I’m sure you don’t need to hear me say—again—that the PHP market has changed dramatically over the last year or so. In fact, I’m willing to bet that many of you are thinking something along the lines of “duh!” right this very moment. To be perfectly honest, it bothers me a bit that so many seem to think that companies have affected the market, because, in my opinion, that’s just impossible. Anyone who deludes himself into thinking that a large market can be affected at any level by the action of a small number of entities—regardless of how big they are—has been reading far too many books on economics (or, perhaps, far too few). In reality, the corporate attitude towards PHP is the result of a shift in the PHP market itself—because there are more “enterprise-level” users in the PHP space, so called “enterprise” software providers are falling all over each other to ensure that their applications are compatible with PHP. In addition, many of these companies are contributing to the community by sponsoring—and in some cases hiring outright—many prominent PHP folks for their work on (or on behalf of) the language. Now, there is absolutely nothing wrong with this— in fact, it’s great to see so many smart people working directly on the core to not only improve PHP as it exists today, but also to introduce the exciting new features that will become part of it in the not-so-distant future. The problem with this reality, however, is one of strategic direction. We have been lucky, so far, that both the companies that have expressed an interest in PHP and the folks who have been doing the work have done so with complete respect for the community and in the spirit of a strong sense of ethics. As much as I sometimes
hear people complain about the fact that large companies are trying to influence the PHP development process, I firmly believe that we are better off today, thanks—at least in part—to their contributions. What we often fail to remember, however, is that companies are made of people and, while companies stay the same, people come and go, and company goals change. Therefore, while the interests of these companies are somewhat aligned today, they will, sooner or later, diverge and bring about a very dangerous Balkanization process that may cause a severe split in the language. The PHP community is particularly vulnerable to this process by virtue of the very things that make PHP so powerful and easy to use: the low barrier of entry to PHP programming means that the “PHP pyramid” has an abnormally wide base of users who can’t contribute to the evolution of the language—a job that ends up in the end of a relatively small group of people who sit inside a very narrow tip. How do we prevent this from ever becoming an issue? The obvious answer is to prevent corporations from gaining a foothold inside the PHP world. That, however, is neither possible nor particularly smart. Many of the “cool” features currently under development are the result of work that is, at least in part, sponsored by some of those very players—plus, as I mentioned, their contributions so far have been mostly positive and helpful, so it would be silly to try and cut them out of the picture. A slightly less obvious—and much more challenging— solution is to widen the tip of the pyramid by increasing the number of people capable of contributing to the evolution of PHP. This is easier said than done, however, as core development is still something of a black art that takes time (and lots of patience) to master. A third solution—by the far the most fetched of the three—consists of assigning the development of PHP to a foundation, like so many other open-source projects have done in the recent past. However, even this approach has potential for some severe drawbacks, as some well-publicized fiascos (the whole Mambo/Joomla affair comes to mind, as it took place right in our PHPpowered backyard) have shown. [1] http://blog.develix.com/frog/user/cliff/article/2006-06-04/9 Volume 5 Issue 6 • php|architect • 64