Copyright © 2009 O'Reilly Media, Inc.
O'Reilly Media
This book is dedicated to my wife, Lauren.
You have supported me in everything I have done and said, and sometimes, in spite of it.
I love you.
The following people contributed hacks to this book:
Ross ShannonRoss Shannon is a student from Dublin, Ireland, currently studying for a Ph.D. in computer science at University College Dublin.
Ross is a part-time web designer with a great interest in web technologies. He is the webmaster of a web design tutorial site, HTMLSource, which you can find at http://www.yourhtmlsource.com/.
Matthew TerenzioPHP has earned its place as one of the premiere web scripting languages and is used in everything from small utility scripts to object-oriented enterprise applications. This book covers that entire spectrum, offering hacks focusing on everything from HTML and Ajax to code generation and database-driven message queuing.
You can read this book from cover to cover if you like, but each hack stands on its own, so feel free to browse and jump to the different sections that interest you most. If there's a prerequisite you need to know about, a cross-reference will guide you to the right hack.
The book is divided into several chapters, organized by subject:
Chapter 1, Installation and BasicsThis chapter walks you through the basics of installing PHP and MySQL, as well as using the excellent PEAR library.
Chapter 2, Web DesignIn this chapter, we cover how to use HTML tricks in conjunction with PHP to jazz up your interface.
Chapter 3, DHTMLIn this chapter, we use the powerful combination of HTML, CSS, and JavaScript known as Dynamic HTML (DHTML) in conjunction with PHP to show just what you can do in a web browser.
Chapter 4, GraphicsThis chapter shows a wide variety of methods that you can use to display data in a graphical form.
Chapter 5, Databases and XMLDatabases are critical to PHP applications. In this chapter, we show you how to make flexible database objects and even to build your database layer automatically using code generation.
Chapter 6, Application DesignIn this chapter, we take the coverage up a notch and discuss techniques that you can use to develop applications quickly and reliably.
Chapter 7, PatternsC++, C#, and Java programmers have used design patterns for years. Can you use them in PHP as well? You betcha. This chapter shows how to use several of the design patterns from the original Design Patterns book (Addison Wesley) to make better PHP applications.
Chapter 8, TestingDo you stay awake at night thinking about whether your PHP application is still running? This chapter covers testing techniques that will find bugs for you and continuously monitor the operation of your site.
Chapter 9, Alternative UIsIn this chapter, we show the use of different user interfaces to work with your PHP code. You can run PHP applications on the desktop, from your cell phone, and from your instant messaging application.
Chapter 10, Fun StuffIn this chapter, we let it all hang out and use the fun stuff on the Web to monitor multiplayer games, use Google Maps in our applications, and much more.
I often see several problems with PHP applications, and this book helps address a number of these:
Bad database designMost PHP applications work with a relational database, usually MySQL. Database design is not something that comes easily to most engineers trained in traditional programming languages. The first step in cleaning up an application is to make sure the database design is good [Hack #34] .
Poor database useThe following is a list of the typographical conventions used in this book:
ItalicsUsed to indicate URLs, filenames and extensions, and directory/folder names. For example, a path in the filesystem would appear as /Developer/Applications.
Constant widthUsed to show code examples, the contents of files, and console output, as well as the names of variables, commands, and other code excerpts.
To explore Hacks books online or to contribute a hack for future titles, visit:
http://hacks.oreilly.comWhen you see a Safari® Enabled icon on the cover of your favorite technology book, that means the book is available online through the O'Reilly Network Safari Bookshelf.
Safari offers a solution that's better than e-books. It's a virtual library that lets you easily search thousands of top tech books, cut and paste code samples, download chapters, and find quick answers when you need the most accurate, current information. Try it for free at http://safari.oreilly.com.
Before you start hacking PHP, you have to either install PHP or get an account on a machine that has PHP already installed. This chapter covers the basics of installing PHP, as well as installing the critical second component, the MySQL database engine, that is so commonly used to provide data that drives PHP applications. The chapter also covers installing PEAR open source modules, which you can use for free in your own PHP applications.
Install the PHP language on Windows, Mac OS X, and Linux, and for both Apache and Internet Information Server.
Installing PHP is the first step in using this book, and on most operating systems, it's a very easy thing to do. PHP installation starts with going to the PHP web site (http://www.php.net/) and downloading either the source code or the binaries, along with documentation.
On Windows, you need to start your PHP installation by downloading the PHP binaries for PHP Version 5. Use the .msi installer to make it easy on yourself, and specify the installation directory as c:\php5. With your PHP installation in place, you can run the PHP interpreter from a Windows DOS prompt:
C:\>php -v PHP 5.0.4 (cli) (built: Mar 31 2005 02:45:00) Copyright © 1997-2004 The PHP Group Zend Engine v2.0.4-dev, Copyright (c) 1998-2004 Zend TechnologiesIf the php executable is not found, you need to add c:\php5\bin to your path. Use the Advanced tab of the system control panel, and click on the Environment Variables button. From there, edit the Path variable, adding c:\php5\bin to whatever path you already have in place.
You will need to close any open command prompt windows and then open a new command prompt window to ensure that these changes take effect.
Command-line access to PHP is great, but you really want to have PHP installed in and integrated with your web server. On Windows, you have two options for this integration. The first is to install the Apache Web Server and configure it for PHP; the second is to install the Internet Information Services (IIS) web server and to install PHP into that environment.
In either case, you need to copy the php.ini file to your Windows directory, c:\windows. Edit the c:\windows\php.ini file and change the extension_dir line to read as follows:
extension_dir = "c:\php5\ext"Further, uncomment lines such as this one:
extension=php_mysql.dllThis line enables access to the MySQL database.
You might want to uncomment several other libraries in this file to enable access to other libraries; see the PHP documentation for more on specific libraries.
Now go back to the PHP site (http://www.php.net/) and download the collection of PECL modules. Save these DLL files into the c:\php5\ext directory (the same directory you just referenced in php.ini). These extensions are required if you want access to SQL databases or if you want to use graphics functions (you will want to use both of these at some point).
Go to the Apache web site (http://www.apache.org/) and download Version 1.3 of Apache, which is precompiled for Windows. This comes as an MSI installer, and that's the easiest way to install Apache. Once you've got Apache installed, the next step is to fix the http.conf file in the Apache conf directory (c:\Program Files\Apache Group\Apache\conf if you installed Apache in the default location).
Add the following lines to the end of the httpd.conf file:
LoadModule php5_module "c:/php5/php5apache.dll" AddModule mod_php5.c AddType application/x-httpd-php .php AddType application/x-httpd-php-source .phpsNext, start the Apache server by running apache.exe:
C:\Program Files\Apache Group\Apache> apache Apache/1.3.33 (Win32) PHP/5.0.4 running…The documents directory for this installation is htdocs (making the complete path c:\Program Files\Apache Group\Apache\htdocs). To test it, create a test.php file in the htdocs directory and put this code in the file:
<?php phpinfo(); ?>Use your web browser to surf to the page; you should see something like Figure 1-1.
From here, you can use the code from all of the hacks in this book.
After installing PHP to the c:\php5 directory, you can integrate PHP into IIS through php5isapi.dll. Start by launching the IIS control panel. Then create a new virtual directory as shown in Figure 1-2.
Make sure to set the Execute permission correctly (detailed in Figure 1-3).
Next, right-click on the virtual directory and select Properties. Then, in the Properties dialog, click on the Configuration button. This will bring up the Application Mappings dialog, where you can associate the .php extension with php5isapi.dll. This dialog is shown in Figure 1-4.
Click on the Add button to create a new mapping, and set the executable to c:\php5\php5isapi.dll.
If you use the Browse button when creating a new mapping, you will need to change the file type to the DLL setting so that you can see the file.
Set the extension to .php
Access the vast PEAR source code repository to find cool functionality to add to your PHP applications.
The PEAR library is a set of user-contributed PHP modules that are structured in a common way so that they can be downloaded, installed, and versioned consistently. PEAR is so fundamental to PHP that it now comes as a standard part of the PHP installation.
To find out what is available in the PEAR library, surf on over to the PEAR site (http://pear.php.net/). There you can find the list of modules or search by module name. When you find a module you want to install, simply run the pear program on your command line.
On Windows, the invocation looks like this:
C:\> pear installDB downloading DB-1.7.6.tgz … Starting to download DB-1.7.6.tgz (124,807 bytes) ............................done: 124,807 bytes install ok: DB 1.7.6In this case, I am installing the PEAR module named DB [Hack #35] , an object-oriented database wrapper that is used extensively in this book.
On Windows, you might need to make sure that the pear.bat batch file, located in the bin directory of your PHP installation directory, is on the path. In addition, the directory where the PEAR modules are installed is often not created by default. In that case, you need to use Windows Explorer or the command line to create the PEAR directory. If you installed PHP in c:\php5, the PEAR directory is c:\php5\pear. You might also need to add this directory to the modules path in the c:\windows\php.ini file.
On Unix systems, including Mac OS X, running the pear program is just as easy:
This chapter provides user interface hacks that you can perform with HTML using PHP. The hacks cover building tabs and boxes to clean up your web interface, building interface elements like breadcrumb trails, and building lightweight HTML graphs. The chapter even includes a hack that shows you how to send HTML email to your customers.
Use CSS to allow your user to select how your web application should look.
Have you ever run across a user who just has to have every blog he reads appear in his own personal color scheme? Are you that kind of user? Thankfully, supporting these users is far easier with CSS support in modern browsers.
CSS defines the fonts, colors, sizes, and even positions of elements of a page independent of the HTML code for that page. You can change the look of a single HTML page drastically simply by redefining its CSS stylesheet. This hack shows how to provide user-selectable CSS and offers some advice on creating customizable interfaces.
Start out by saving the code in Example 2-1 as index.php.
Example 2-1. Simple index page that sets the stage for customizable CSS
<html> <head> <?php $style = "default"; if ( $_GET["style"] ) $style = $_GET["style"]; $files = array(); $dh = opendir( "styles" ); while( $file = @readdir( $dh ) ) { if( preg_match( "/[.]css$/", $file ) ) { $file = preg_replace( "/[.]css$/", "", $file ); $files []= $file; } } ?> <style type="text/css" media="all">@import url(styles/<?php echo($style); ?>. css);</style> </head> <body> <table width="800"> <tr> <td width="200" class="menu" valign="top"> <div class="menu-active"><a href="home.php">Home</a></div> <div class="menu-inactive"><a href="faq.php">FAQ</a></div> <div class="menu-inactive"><a href="contact.php">Contact</a></div> </td> <td width="600" valign="top"> <table class="box"> <tr> <td class="box-title"> Important information </td> </tr> <tr> <td class="box-content"> Lots of information about important events and stuff. </td> </tr> </table> </td> </tr> </table> <form> Style: <select name="style"> <?php foreach( $files as $file ) { ?> <option value="<?php echo($file); ?>" <?php echo( $file == $style ? "selected" : "" ); ?> ><?php echo($file); ?></option> <?php } ?> </select> <input type="submit" value="Select" /> </form> </body> </html>Use a breadcrumb trail to tell your users where they are on your site.
A breadcrumb trail is a list of links at the top of a page that indicates where a person is in the site's organizational hierarchy.
A breadcrumb trail is not, as the term might suggest, the set of pages that a person navigated through to get to his destination. The user already has the Back button for retracing his steps.
A breadcrumb trail allows a user to navigate back up the hierarchy a little to where he would find more relevant information. For example, the trail might be: Home | Platforms | Portables | PSP. The user could easily navigate back to the Portables page, which lists all of the portable game consoles, the Platforms page, which lists the different gaming platforms, or the home page, all with a single click.
Figure 2-3 shows the breadcrumb trail that Yahoo! uses in its directory to show where you are. In this case, I'm in the Buddy section of the Comedy category, within the Titles area of Movies and Films. As a user, I can go back to any level of organization that suits my interests.
Figure 2-3. The Yahoo! directory breadcrumb trail
By convention, the last item on the list is the current page and is not given a link. The first item on the list is the home page of the site.
Use HTML and simple graphics to create attractive boxes for your web pages.
Sometimes it's useful to put your page content into boxes to make it easier for users to navigate your site. You can draw attention to a particular piece of content, create newspaper-like interfaces, or just go with a little cubism to impress your artsy friends. The scripts in this hack make it easy to draw boxes around any content you like.
Save the code in Example 2-5 as box1test.php.
Example 2-5. A test page for boxing up content
<html> <head> <? include( "box1.php" ); add_box_styles(); ?> </head> <body> <div style="width:200px;"> <? start_box( "News" ); ?> Today's news is that there is no news. Which is probably a good thing since the news can be fairly distressing at times.<br/><br/> <a href="morenews.html">more…</a> <? end_box(); ?> </div> </body> </html>For the PHP portion of the mini-application, save the code in Example 2-6 as box1.php.
Example 2-6. Adding a little PHP and CSS
<? function add_box_styles() { ?> <style type="text/css"> .box { font-family: arial, verdana, sans-serif; font-size: x-small; background: #ccc; } .box-title { font-size: small; font-weight: bold; color: white; background: #777; padding: 5px; text-align: center; } .box-content { background: white; padding: 5px; } </style> <? } function start_box( $name ) { ?> <table class="box" cellspacing="2" cellpadding="0"> <tr><td class="box-title"><? print( $name ) ?></td></tr> <tr><td class="box-content"> <? } function end_box() { ?> </td></tr></table> <? } ?>Use HTML and CSS to create a tabbed interface for your web application.
Sometimes there is just too much data to put onto one web page. An easy way to break up a site (or even a content-heavy page) is to display it using tabs, where the data is broken up into subelements, each correlating to a named tab. Lucky for us, tabs are a piece of cake with PHP.
Save the code in Example 2-9 as index.php.
Example 2-9. Using the tabs library to show a tabbed interface
<?php require_once("tabs.php"); ?> <html> <head> <?php tabs_header(); ?> </head> <body> <div style="width:600px;"> <?php tabs_start(); ?> <?php tab( "Tab one" ); ?> This is the first tab. <?php tab( "Tab two" ); ?> This is the second tab. <?php tabs_end(); ?> </div> </body> </html>Next, code up a nice PHP and CSS library. Save the code in Example 2-10 as tabs.php.
Example 2-10. Using PHP and some CSS to create user-friendly tabs
Use PHP's XSL support to enable your customers to design their own pages.
Amazon provides an interesting service to its corporate customers. The customer can skin an Amazon page by providing an XSL stylesheet that formats the XML data about the products, prices, and related data. This means that if you're a corporate customer, you can add your own links and graphics, and even customize the look and feel of Amazon.com to give purchasing pages an integrated look.
This hack does the same with PHP's XSL engine (and no corporate membership is required!). Figure 2-10 shows the flow of XSL processing in this hack (and with XSL in general). The processor takes two inputs. In this case, the input.xml file contains the data for the page, and the format.xsl file contains the formatting for the page, along with specifications for where the data is to be placed. The XSL processor then takes these two inputs and emits XML, HTML, or text.
Figure 2-10. The XSL processing flow
Use HTML to create simple graphs for your data.
It seems as though every site you go to these days requires QuickTime or Flash so that you can see fancy images and graphs. For simple bar graphs, though, you don't need fancy image rendering or Flash movies. You can use this hack to create bar graphs with just a few HTML tables and some PHP. The result looks just as cool as those other Flash-heavy sites but doesn't require any extra plug-ins or downloads.
Save the code in Example 2-14 as htmlgraph.php.
Example 2-14. Drawing some simple bar graphs
Use PHP image support to set the height and width attributes of your images properly.
All of the modern browsers start showing web pages as quickly as possible so that web surfers feel they are getting fast(er) response times. This means browsers will start showing a page well before any images or other accompanying resources are downloaded. Because the browser hasn't downloaded the image before rendering the page, it doesn't know how big the image should be, unless you specify height and width attributes on the img tag.
If you don't specify the width and height of images, though, the page will jerk around as it's being downloaded. The browser will guess at the size of the image (usually picking 10 pixels by 10 pixels), but then find out after the image is downloaded that the actual size is much larger. Thus, the browser will need to lay out the page again to adjust for the new size.
This hack builds img tags with the proper width and height attributes by using the getimagesize function to retrieve the actual width and height of the image.
Use multipart email messages to send email content in both plain text and HTML format.
Email is another interface to your web application. Ideally, you want that interface (and any other, be it a phone or a handheld device) to be as full-featured as the one you provide through a web server. While this isn't always possible, it's a good goal to keep in mind, and it will help push you to create better user interfaces.
This hack describes how to send email using a multipart construction, where one part contains a plain-text version of the email and the other part is HTML. If your customers have HTML email turned off, they will still get a nice email, even if they don't get all of the HTML markup.
Figure 2-13 shows some different forms of mail messages. On the lefthand side is the simplest form of mail, the text message. At the top of the email is the header, which defines the subject, whom the mail is from, whom the mail is going to, and so on. These are followed by a carriage return, and finally, the message text.
Figure 2-13. Different mail forms
The emails in the middle and on the right in Figure 2-13 are multipart messages. The header remains much the same as with the text message, with the exception that some information about the multiple parts is included. The text is then placed within a part, and the HTML is placed in another part.
This chapter covers using Dynamic HTML (DHTML) in your web applications. DHTML is a term used to define the powerful combination of HTML, CSS, and JavaScript. The hacks in this chapter use DHTML to create all types of interactive interfaces for your web applications, from spreadsheets to dynamic graphs, pop ups, slideshows, vector graphics, and more.
Use the ActiveWidgets spreadsheet library to put an interactive JavaScript data control on your page.
Let's face it: some data—particularly financial and statistical data—just looks better when it's presented as a spreadsheet. Unfortunately, HTML does a poor job of giving you an interactive spreadsheet-style feel, especially when it comes to scrolling around, sorting, or any of the truly interactive user experience elements of a spreadsheet.
This hack uses the ActiveWidgets (http://activewidgets.com/) grid control to create a spreadsheet-style interface on a web page.
Save the code in Example 3-1 as index.php.
Example 3-1. A script that provides state-specific data in a spreadsheet format
Use the overLIB library to pop up hints for words on your web page using JavaScript and PHP.
With the overLIB JavaScript library (http://www.bosrup.com/web/overlib/), you can have handy pop-up labels that appear above text on your page. This hack makes it a little easier to create these links by providing a PHP wrapper function to invoke the library.
Save the code shown in Example 3-2 as index.php.
Example 3-2. A wrapper function that simplifies overLIB use, courtesy of PHP
Use JavaScript, DHTML, and PHP to create and use drag-and-drop lists.
Creating an interface that allows the user to prioritize a list has always been a problem when working with HTML. With PHP, though, this is no longer the case. This hack uses an open source drag-and-drop library from ToolMan (http://tool-man.org/) to create drag-and-drop lists.
Enter the code shown in Example 3-3 and save it as index.html.
Example 3-3. Building a drag-and-drop list with HTML and CSS
Using DHTML, you can build graphs that change without requiring even a page refresh. The result? Your users can play with data in real time.
Something is fundamentally unsatisfying about the way the Web works. You click on a link, the page disappears, and that lovely spinning ball or ticking clock grinds by as a new page appears section by section, (hopefully) with the information you want. This certainly is not the interactivity we're all used to from our client-side applications.
But, thank goodness, you can make an application that works without a page refresh. This hack shows you how to make an interactive scatter plot using a few graphics, some PHP, and a whole slew of JavaScript.
The index file, index.php, is shown in Example 3-5.
Example 3-5. JavaScript, the real workhorse in this hack
Use spinners to divide your page content into sections, each of which you can show or hide individually.
Sometimes there is just too much great content to have visible on a single page at one time. One approach is to use tabs [Hack #6] , and another is to section the content with spinners that allow the user to open up specific sections of content. This hack shows how to create sections on your page with spinners that open and show sections of the content interactively.
The code for index.php is shown in Example 3-6.
Example 3-6. PHP allowing for user selection of a specific spinner
Use DHTML to position sticky drop-down windows relative to keywords in your HTML.
Attaching a drop-down sticky to a word or phrase in your document is an easy way to add valuable information close to the word, without obscuring it. That way, the user can click on the word and get more contextual information, all without scrolling or lots of mouse movement.
Save the code in Example 3-7 as index.php.
Example 3-7. PHP and JavaScript cooperate to make drop-down stickies work
Use PHP to build a navigation menu widget that works consistently across your site.
Writing the navigation menu for your site can be a pain. You don't want to write the same code over and over on every page. Ideally, you would have a PHP menu function that would render the menu with the current page highlighted. This hack gives you that simple menu function (for the low cost of this book, no less!).
Save the code in Example 3-8, which demonstrates the use of menu.php as index.php.
Example 3-8. Using the menu library
Use PHP to obscure the names of your JavaScript functions, hiding all of your clever code.
Sometimes it's useful to obscure some of your JavaScript code to hide intellectual property. It is impossible to twist the code so completely that users cannot unravel it, but it is possible to do some obfuscation to fend off the casual observer. This hack starts you down the road of JavaScript obfuscation by automatically renaming JavaScript function calls. With this code, you can write JavaScript in the clear on the server, and then have it obfuscated on the way out to the browser.
Save the sample in Example 3-10 as index.php.
Example 3-10. The script using the obscure.php library
Use a combination of PHP and DHTML to build the bit clock that you can buy on ThinkGeek.com.
A coworker of mine recently bought one of the "bit clocks" from ThinkGeek (http://www.thinkgeek.com/). The clock has three sections, each with eight lights. The sections represent seconds, minutes, and hours, and the individual lights represent the bits in each value. Though neither he nor I could actually read the clock (!!), we both agreed that it was very cool. Along the same lines, here's a hard-to-read clock in PHP, also super-cool to show off.
clock.php does the work; it's shown in Example 3-12.
Example 3-12. Setting up an array of bit masks, and beyond
Use JSON to make Ajax easier to implement.
The combination of DHTML, CSS, and XML SOAP or REST requests from the browser is known as Ajax. Ajax is a great way to create dynamic web interfaces without ever requiring a page fetch. Largely centered on the JavaScript in a page requesting HTTP transfers, Ajax-enabled applications often use DHTML to change the HTML of a page, creating dynamic applications that feel like client-side apps. Typically, JavaScript contacts a PHP or Perl script on a server and then interprets the returned text, which is most often XML.
Parsing the XML isn't rocket science, but it isn't a walk in the park, either. That's why developers created the JSON (http://json.org/) library, which makes it easy for JavaScript to interpret the response from the server. This hack uses the JSON PEAR module to format data coming out of the server, and shows how that JSON code is consumed in the browser.
Save the code shown in Example 3-13 as getdata.php.
Example 3-13. PHP responding to an Ajax request
Use the PHP graphics library and DHTML to make a slideshow in your browser.
Family pictures are the classic icebreaker among parents. Who doesn't like to look at pictures of their own kids? (And who isn't bored silly looking at pictures of someone else's kids?) But isn't a set of wallet photos just passé in this modern wired world? How about an online slideshow?
This hack shows how to use a combination of PHP 5, the PHP image library, and some DHTML using JavaScript to create in your browser a slideshow of your favorite images (kids or otherwise).
Save the code in Example 3-15 as index.php.
Example 3-15. PHP script handling image display
<?php $dh = new DirectoryIterator( "pics" ); $files = array(); foreach( $dh as $file ) { if ( preg_match( "/[.]jpg$/", $file ) ) $files []= "$file"; } ?> <html> <head> <title>Slideshow</title> <style> body { background: black; } #thumbnails { height: 140px; width: 100%; overflow: auto; } #pic { text-align: center; height: 400px; padding: 20px; } </style> <script> varUse JavaScript to render line graphics without plug-ins.
You would think that with its inception as a language for writing scientific papers, HTML would support vector graphics for charting. But alas, it doesn't, and several plug-ins have been developed to enhance the graphics capabilities of HTML browsers. Notable among these are Flash and Scalable Vector Graphics (SVG). Both of these seem like heavyweight solutions for simple graphs, though, so what is a poor DHTML programmer to do?
One option is to use Walter Zorn's JavaScript Vectorgraphics Library (http://www.walterzorn.com/). This single JavaScript file can help you put vector graphics anywhere you want on your page, and even allows you to alter those graphics in real time in the browser. The script creates thousands of small <div> tags, one for each pixel of a graph. It's not a subtle approach, but it does the job, and for small graphs it's fast enough to be usable.
Use HSB and DHTML to create a PHP color picker.
People like to be able to pick the colors of their site, or the colors that are applied to their data in a web application. This hack demonstrates a DHTML color picker that allows people to select a color from a grid of HSB color values.
Figure 3-18. A simple geometric shape created with the graphics library
Example 3-18 is the code for index.php.
Example 3-18. Handling conversion between HSB and RGB values
Use the font size of links to express the importance of certain terms.
Flikr (http://flikr.com/) is a site that allows users to upload images, and then tag those images with single-word terms. You can come back later and search the Flikr database using those terms, and you can see a link graph [Hack #91] that shows the most-used terms in a font larger than that used for the less frequently used terms.
In this hack, I show you how to create a link graph by analyzing an article from the Cable News Network web site (http://cnn.com/) for keywords. The words are counted, and the font size of each word is scaled relative to the number of counts.
Save the code in Example 3-19 as linkgraph.php.
Example 3-19. Link graph code
Use PHP's date functions to create an interactive HTML calendar.
A calendar is often the best way to represent event data. It's something that your users are familiar with, and it is a great way to show lots of information in a small space. Pop-up calendar controls are available on the Web, and open source event calendar systems that include all the event management functionality you need are also available, but, at least when I looked, no simple calendar display controls that could show events in a calendar format were available. This hack creates a simple HTML calendar with controls for navigating back and forth through the months of the year.
Save the code in Example 3-20 as cal.php.
Example 3-20. Making calendaring work using PHP
<html> <head> <style type="text/css"> .calendar { font-family: arial, verdana, sans serif; } .Use a combination of small images, PHP, and JavaScript to allow users to use their mouse to scroll around an image that is much larger than their screen.
When Google Maps (http://maps.google.com/) was first introduced, the interactivity of the map blew people's socks off. On older mapping sites, you were presented with a map that had eight arrows positioned around it. When you clicked on an arrow, the page would be refreshed, and the map scrolled by one map unit in the direction you requested. Because of that page refresh, though, you had to reorient yourself to the new location.
Google Maps changed all that. With Google Maps [Hack #95] , you simply click on the map and then drag it in whichever direction you want to go. The page never refreshes (although it occasionally redraws), and you never lose track of where you were.
I was so impressed with these maps that I decided to make my own version of the Google Maps code using PHP, some JavaScript, and the very large image created in "Split One Image into Multiple Images" [Hack #30] .
Figure 3-23 shows the system's conceptual design. On the lefthand side of the illustration is the page, and on the righthand side is a set of images that contain the sliced-up larger image. The map moves within a view rectangle by repositioning the images within the view area. These images are drawn from the bank of images on the righthand side (all of the images are available instantly, without requiring a refresh).
Figure 3-23. The image scroller design
The code you want is shown in Example 3-21; save it as index.php.
Example 3-21. Mimicking Google with a little PHP wizardry
It's easy to think of PHP as nothing more than a scripting language for HTML. But PHP is far more than that, with support for databases, graphing, image manipulation, and a lot more. This chapter details hacks for building beautiful graphics with bitmaps, vector graphics, and even Dynamic HTML (DHTML). You'll even see how you can take photos from your iPhoto library and export them into HTML—all with that "HTML scripting language," PHP.
Use the GD graphics API in PHP to create thumbnails of your images.
This simple hack takes a set of JPEG images in a directory named pics and creates thumbnails of them in a directory named thumbs. It also creates a file in the same directory as the script called index.html, which contains all of the thumbnails, as well as links to the original images.
Save the code in Example 4-1 as mkthumbs.php.
Example 4-1. A script for handling thumbnail creation
<?php $dir = opendir( "pics" ); $pics = array(); while( $fname = readdir( $dir ) ) { if ( preg_match( "/[.]jpg$/", $fname ) ) $pics []= $fname; } closedir( $dir ); foreach( $pics as $fname ) { $im = imagecreatefromjpeg( "pics/$fname" ); $ox = imagesx( $im ); $oy = imagesy( $im ); $nx = 100; $ny = floor( $oy * ( 100 / $ox ) ); $nm = imagecreatetruecolor( $nx, $ny ); imagecopyresampled( $nm, $im, 0, 0, 0, 0, $nx, $ny, $ox, $oy ); print "Creating thumb for $fname\n"; imagejpeg( $nm, "thumbs/$fname" ); } print "Creating index.html\n"; ob_start(); ?> <html> <head><title>Use the SVG XML standard to create scalable graphics that render beautifully.
Adobe's Scalable Vector Graphics (SVG) XML standard provides a whole new level of graphics functionality to PHP web applications. In this hack, I'll use a web page and a simple PHP script to create a scalable vector graphic.
It's important to note that before you can view an SVG image you must have an SVG viewer plug-in installed in your browser. Adobe hosts plug-in viewers on its web site, http://www.adobe.com/svg/main.html. SVG is an open standard, which Adobe strongly supports. The SVG.org site (http://svg.org/) is an open community supporting the standard across multiple browsers and now even cell phones.
Figure 4-2 demonstrates how the SVG plug-in interacts with the circle_svg. php script, which generates the SVG. The SVG object embedded on the page requests the XML from the script, and the script then returns the XML with the SVG plug-in plots.
Figure 4-2. The SVG plug-in requesting SVG from the circle_svg.php script
Use the object-oriented features of PHP to simplify your graphics using layering, object-oriented drawing, and viewport scaling.
PHP's support for graphics is great. But when you try to build a complex visualization, PHP becomes difficult to use for several reasons. First, the drawing order is really important. Things that you draw first will be covered by the stuff that you draw later (and there's no good way to get around that limitation). This means that you have to sequence your code based on the drawing order, even when it's difficult to do so programmatically.
Another problem is scaling. To draw into an image, you have to know how big the image is and how to scale your drawing. That means passing around a lot of information about the drawing context. Add that to the layering issues, and PHP image code becomes a real mess.
Lucky for all of us hackers who aren't graphics pros, all of these problems have been solved via graphics libraries like PHP's GD library. In this hack, I build a simple object API for graphics that manages drawing order through z buffering, and handles scaling by creating a viewport.
Save the code in Example 4-4 as layers.php.
Example 4-4. Defining several classes used to layer graphics
<?php class GraphicSpace { var $image; var $colors; var $xoffset; var $yoffset; var $xscale; var $yscale; functionUse PHP's graphics engine to break a single large image into multiple small images.
Sometimes it's handy to have a group of smaller images that make up a single image rather than the one small image. An example is "Create the Google Maps Scrolling Effect" [Hack #26] , which scrolls many smaller images around the screen seamlessly, creating the effect of moving around one large image. To accomplish that trick, though, you might have to break up a large image first; this hack does just that.
Save the code in Example 4-5 as imgsplit.php.
Example 4-5. Breaking up images
<?php $width = 100; $height = 100; $source = @imagecreatefromjpeg( "source.jpg" ); $source_width = imagesx( $source ); $source_height = imagesy( $source ); for( $col = 0; $col < $source_width / $width; $col++) { for( $row = 0; $row < $source_height / $height; $row++) { $fn = sprintf( "img%02d_%02d.jpg", $col, $row ); echo( "$fn\n" ); $im = @imagecreatetruecolor( $width, $height ); imagecopyresized( $im, $source, 0, 0, $col * $width, $row * $height, $width, $height, $width, $height ); imagejpeg( $im, $fn ); imagedestroy( $im ); } } ?>This is the inverse of the code in Example 4-6 in the upcoming "Hacking the Hack" section of this hack. It creates—from a single big image—lots of smaller images and puts them in a grid. It's a useful match for the Google Maps scrolling effect hack in "Create the Google Maps Scrolling Effect" [Hack #26] .
The constants at the top of the file define how big the output images should be. The script reads in the source image and figures out how big it is; then it uses a set of nested for
Use PHP's image toolkit to create dynamic graphs from your data.
PHP has excellent dynamic imaging capabilities. You can use these to overlay images [Hack #32] , or to create whole new images on the fly. This hack uses the image toolkit to do some simple scientific graphing of sine waves (proving that PHP is great for math as well as for imaging).
Save the code in Example 4-7 as graph.php.
Example 4-7. Graphing a mathematical function
<? $width = 400; $height = 300; $data = array(); for( $i = 0; $i < 500; $i++ ) { $data []= sin( deg2rad( ( $i / 500 ) * 360 ) ); } $xstart = $width/10; $ystart = $height - ($height/10); $image = imagecreate($width, $height); $back = imagecolorallocate($image, 255, 255, 255); $border = imagecolorallocate($image, 64, 64, 64); imageline( $image, $xstart, 0, $xstart, $ystart, $border );imageline( $image, $xstart, $ystart, $width, $ystart, $border );Using PHP's graphics capabilities to build a single image from several source images.
One common graphics scenario is to put some overlay images at specific data-driven locations, stacking those overlays on top of another base graphic. This hack starts with the map in Figure 4-10 as the base image.
Figure 4-10. The map graphic
Then it places the star graphic in Figure 4-11 onto the map, over the city of San Francisco, as it might appear if you were looking up a location by city or Zip code.
Figure 4-11. The star graphic
Save the (rather simple) code in Example 4-8 as graphic.php.
Example 4-8. PHP making overlaying graphics almost trivial
<?php $map =imagecreatefrompng("map.png"); $star = imagecreatefromgif("star.gif");Use PHP's XML capabilities to parse through iPhoto's picture database.
Apple is a company known for producing innovative and easy-to-use products. Following on that line, it recently released the iLife suite (http://www.apple.com/ilife/), which makes it easy to produce and organize rich media. I was a bit dismayed by my options for sharing my photos from iPhoto, though. In particular, after having imported my digital photos from my camera and organizing them using iPhoto, I wanted to show off these pictures to family and friends. I didn't want to sign up for hosting, open an account with a photo printing service, wait for hundreds of files to upload somewhere, export photos to a smaller size, or reorganize all of my images in some other program after having already done the work in iPhoto. I wanted them available to everybody—right now—and I didn't want to have to lift a finger to make it so. I'd already done plenty of work by taking the actual photos, not to mention organizing and captioning them!
This is what got me working on myPhoto (http://agent0068.dyndns.org/~mike/projects/myPhoto). One Mac OS X feature that most users often do not notice is the built-in web server; Mac OS X includes both Apache and PHP, and both are itching to be enabled. When you combine this and a broadband connection with all of the information readily available in iPhoto, sharing photos becomes (as it should be) a snap.
If your PHP project requires a photo gallery component, it might be tempting to place the burden on users to upload, caption, and organize all of their photos into your system. However, if users have already done the work in iPhoto, do the rest for them! Armed with a simple XML parser, it's possible to extract all of the meaningful data from iPhoto and reformat it into a simpler format that's more appropriate and convenient for use with PHP.
The first logical step is to get up close and personal with iPhoto so that you know what data is easily available.
I am basing this discussion on iPhoto Version 5.x, the most current version of iPhoto available as of this writing. With a few small tweaks here or there, though, it's trivial to apply these same concepts to other versions of iPhoto—something I've been doing since iPhoto 2.0.
Figure 4-14 shows a small selection from my iPhoto album.
Figure 4-14. iPhoto showing pictures from my wedding
A quick look in ~/Pictures/iPhoto Library/ shows almost everything we could ever need from iPhoto:
Directories broken down by dateFor instance, ~/Pictures/iPhoto Library/2005/07/02/ contains photos from July 2, 2005. The image files in this directory are the actual full-size photos, but they contain all of the edits the user made from within iPhoto (i.e., rotations, color corrections, etc.). It also contains two other subdirectories: Thumbs, which contains 240 x 180 thumbnails corresponding to each image, and Originals, which contains the original, unmodified versions of the images (only if the user has performed any edits in iPhoto). Furthermore, in nearly all cases, these photos are in JPEG format, which is perfect for the Web.
One notable exception: if the user takes photos in RAW format (available on higher-end cameras), the Originals directory contains the RAW files and all other images are JPEG representations.
AlbumData.xmlThis XML document contains all of the really interesting (and uninteresting) data surrounding these photos: file paths for a given photo, captions, ratings, modification dates, etc. This file also contains information about groups of photos—also called albums—as well as user-defined keywords. Some version information and meta-information is included as well, but that's not terribly helpful.
So now we need to make some sense of that AlbumData.xml file. First off, it's not just any XML file; it's an Apple Property List. This means that a limited set of XML tags is being used to represent common programmatic data structures like strings, integers, arrays, and dictionaries (also known as associative arrays in some languages). Therefore, for the interesting structures within this file, we should look at some sample content, since the XML tags themselves aren't terribly descriptive. Rather, the tagged content is where the meaty structure is. I've cut some pieces out for the sake of brevity, but the more important parts of the file are here.
The beginning of the file looks something like this—not terribly interesting:
<?xml version="1.0" encoding="UTF-8"?> <plist version="1.0"> <dict> <key>Application Version</key> <string>5.0.4 (263)</string> <key>Archive Path</key> <string>/Users/mike/Sites/myPhoto/iPhoto Library</string>But further down is a listing of all the photos in the dictionary keyed by unique identifiers for each photo. In the following example, you can see that we're looking at an individual photo with a unique ID of 5. Furthermore, it's an image (rather than, say, a video) which has a caption of "No more pictures, please" as well as an optional keyword associated with it (the keyword's unique keyword ID is 2):
<key>Master Image List</key> <dict> <key>5</key> <dict> <key>MediaType</key> <string>Image</string> <key>Caption</key> <string>No more pictures, please</string> <key>Aspect Ratio</key> <real>0.750000</real> <key>Rating</key> <integer>0</integer> <key>DateAsTimerInterval</key> <real>62050875.000000</real> <key>ImagePath</key> <string>/Users/mike/Sites/myPhoto/At the core of most PHP applications is the database, and in most cases that database is MySQL. This chapter has a variety of hacks to help you develop code for database access and for your work with XML. In particular, you should check out the dynamic database object hack, which provides a single class that will talk to any database. Add to that the code generation hacks, which will help automate your database access code from an XML representation of the database schema, and PHP and databases are going to be a piece of cake!
Most PHP applications use an SQL database. Here are some hints to help you avoid common problems.
PHP applications usually use MySQL databases for the back end. I've worked on a bunch of my own applications, as well as with open source application databases and some commercial ones. In my travels, I have seen a few common problems appear repeatedly; here are a few of those problems, along with easy solutions.
To find a unique record in a database table, you need a primary key. This is usually a unique, nonrepeating integer that starts at 1. All databases have the ability to handle this for you, but it seems that some engineers aren't aware of it.
Take the simple schema in Example 5-1. You have an author table with an id and a name.
Example 5-1. SQL without a primary key
DROP TABLE IF EXISTS author; CREATE TABLE author ( id INT, name TEXT );But who ensures that the ID is unique? Often the PHP code that uses a table like this will first do a SELECT to find the maximum value of the ID field, and then create a new record with that value plus 1. But that takes an extra SQL statement and assumes the PHP developer remembers to take this step. It's much better to let the database handle this (rather routine) task.
A much better version of the schema from Example 5-1 is shown in Example 5-2.
Example 5-2. Adding an auto-incrementing ID field
DROP TABLE IF EXISTS author; CREATE TABLE author ( id INT NOT NULL AUTO_INCREMENT, name TEXT, PRIMARY KEY( id ) );Now the ID field is specified as an auto-incrementing integer that cannot be null. It's also identified as the primary key.
To insert a record into this type of table, follow this recipe:
INSERT INTO author VALUES ( 0, "Brad Phillips" );MySQL replaces the 0 value for the ID with an auto-incrementing value. To find out the value of the ID from the most recent insert, use this SELECT statement:
SELECT LAST_INSERT_ID();This version of the table is also faster than the first version because the primary key specification creates an index that speeds up the look-up process.
If you find that the code that inserts records into the database is first doing a SELECT to find the largest ID value, and then running INSERT
Learn how to use PEAR's DB module to create bulletproof database access for your web applications.
I've read a number of books on PHP over the years, and almost all of them make the same mistakes when it comes to database access. Applications that use SQL improperly are susceptible to SQL injection attacks, which can literally hand your entire database (and its contents) over to hackers. What's even worse is that the proper way to do database access is actually easier than the improper way.
To illustrate, Example 5-7 shows proper SQL command construction.
Example 5-7. Proper SQL command construction
Use the new object-oriented features of PHP 5 to create classes that wrap access to any database table.
PHP 5 represents a substantial upgrade in terms of object-oriented support in the PHP language. Along with a number of upgrades in performance, PHP 5 has a major upgrade in the ability to create dynamic classes. These are classes where the methods and attributes change from object to object. This can be very handy in building database applications.
Usually, there is one PHP class for each table in the database. For example, if you have tables named books, authors, and publishers, you would have PHP classes named Book, Author, and Publisher. Each PHP class has methods to get and set the values in a record in the corresponding table.
On the one hand, this is a very clean and easy-to-understand model. On the other hand, it's a lot of work to maintain these classes (and that's just for three tables!). Is it possible to write a single class that will wrap any table in the database? Yes. With PHP 5's support for _ _call, _ _get, and _ _set methods, it is.
To understand why _ _call, _ _get, and _ _set are important you need to understand how methods on objects get called. When you invoke a method on an object, the interpreter first looks at the class to see whether the method exists. If the method does exist, it's called; if it doesn't, the base class of the class is inspected; if that fails, the base class of the base class is examined, and so on, up the chain of classes.
In PHP 5, when the method lookup fails, the _ _call method is invoked, if it exists. This method has two arguments: the name of the method and the array of arguments for that method. If you implement the _ _call method and return a real value, PHP 5 is satisfied that it has found a method and that the method invocation worked.
The _ _get and _ _set methods correspond to the getting and setting of instance variables on the object. The _ _get method has a single parameter, the name of the instance variable. The _ _set method has two parameters, the name of the instance variable and the new value.
That means that you can effectively create new methods and instance variables on your objects on the fly. And that means that you can have a class that loads a record from a database table and has dynamic methods and instance variables that make it look like an object built just for that record.
Figure 5-3 shows how these dynamic methods and fields work. The code calls the class for either a method or a field. Then the object indicates that there is no such field. PHP calls to get the field value or method value, and then—if given a valid response—returns that value to the calling code as though the field or method were there.
Automatically generate the code to create, read, update, and delete (CRUD) records from your database tables.
This book presents several hacks that will help you speed up your database development by generating the required PHP and SQL code. In this hack, I show you how to build a generator that will create PHP 4 (or 5) classes that wrap database records. With these classes, you will be able to create, read, update, and delete individual records on any table, without spending lots of time writing the database code yourself.
Figure 5-4 shows the flow from the schema file into the generator, which in turn creates the output PHP code. I've rendered the output code as dashes because it's temporary and should never be altered manually.
Figure 5-4. The flow through the generator
Save the XML representing a database schema (shown in Example 5-14) as schema.xml.
Example 5-14. An XML document that maps to the database schema
<schema> <table name="book"> <field name="id" type="int" primary-key="true" /> <field name="title" type="text" /> <field name="publisher_id" type="int" /> <field name="author_id" type="int" /> </table> <table name="publisher"> <field name="id" type="int" primary-key="true" /> <field name="name" type="text" /> </table> <table name="author"> <field name="id" type="int" primary-key="true" /> <field name="name" type="text" /> </table> </schema>Example 5-15 shows the generation code; I saved this script as gen.php.
Example 5-15. PHP that handles database code generation
<?php $tables = array(); function start_element( $parser, $name, $attribs ) { global $tables; if ( $name == "TABLE" ) { $table = array(); $fields = array(); $table['name'] = $attribs['NAME']; $table['fields'] = array(); $tables []= $table; } if ( $name == "FIELD" ) { $field = array(); $field['name'] = $attribs['NAME']; $field['type'] = $attribs['TYPE']; $field['pk'] = ( $attribs['PRIMARY-KEY'] == "true" ) ? 1 : 0; $tables[count($tables)-1]['fields'] []= $field; } } function end_element( $parser, $name ) { } $parser =xml_parser_Use regular-expression hacks to read XML without paying the expense of firing up the XML parser functions.
You can read XML with PHP using very few PHP libraries. For example, XML support is actually an extension that might or might not be installed on the server your code is running on. To avoid reliance on an optional extension, it's sometimes easier and more portable to extract data from XML with a few regular expressions than it is to fire up the XML parser.
Save the XML in Example 5-20 as books.xml.
Example 5-20. Some simple XML code, serving as a demonstration
<books> <book name="Pragmatic Programmer" /> <book name="Code Generation in Action" /> <book id="8951234" name="Podcasting Hacks" /> </books>Now save the code in Example 5-21 as bookread.php.
Example 5-21. A simple script that uses regular expressions to read XML
Use PHP to read the schema from your database and export it as XML for documentation or code generation.
It can be handy to have a dump of the current database schema for several reasons. First, you can use it to generate PHP for database access [Hack #37] . You can also use it to compare two versions of a schema to build a migration script for software upgrades.
schema.php is shown in Example 5-23.
Example 5-23. Script that extracts XML for a database schema representation
<?php $dbuser = "root"; $dbpassword = "password"; $dbserver = "localhost"; $dbname = "wordpressXSLT can read XML data from URLs directly. This hack is a quick script that exposes your entire database to XSLT from a URL.
XSLT is a great language for reporting or data conversion. It can even read data directly from URLs. However, without extensions, it can't natively access a database. This hack opens up your entire database through your web server (a cool, albeit questionable, idea). It exports queries that are specified on the URL as XML.
Save the script in Example 5-24 as query.php.
Example 5-24. An insecure script
<?php $dbuser = "rootUse PHP to create SQL scripts automatically from a database schema represented as XML.
One of the most common problems with writing database code is the PHP code getting out of sync with the database's structure, or vice versa. Generally we use a .sql script to preload the database with the tables and data required to run the application. But this SQL script can be a pain to maintain, especially when you need to update the PHP that references the SQL tables at the same time.
This hack presents some simple scripts to build SQL and PHP automatically from an XML description of the database. This will ensure that the SQL and the PHP are kept in sync. It also means that if you change database servers or versions of PHP, you can still use the same schema.xml file. All you need to do is change the generator to emit code for a different type of server or PHP version.
Figure 5-5 shows the program flow with the schema.xml file being used as input to the generator code—written in PHP—that creates the MySQL file, which will in turn create the database.
Figure 5-5. The generator creating SQL from a schema XML file
Use PHP to build code for database access directly from an XML description of the schema.
Building database access classes for SQL tables can require a lot of annoying, error-prone grunt work. In this hack, I use an XML file that describes a database schema and a code generator written in PHP to create the PHP classes automatically.
I used the same schema.xml file that I use in this hack, to generate the corresponding SQL [Hack #41] .
Figure 5-6 illustrates how the abstract schema XML is taken as input by the generator. The generator in turn creates the PHP classes in the mydb.php file.
This output file is temporary and you should never edit it directly.
Figure 5-6. The flow of the PHP SQL Select generator
schema.xml, representing the database, is shown in Example 5-27.
Example 5-27. XML representing a database schema
<schema> <table name="book"> <field name="id" type="int" primary-key="true" /> <field name="title" type="text" /> <field name="publisher_id" type="int" /> <field name="author_id" type="int" /> </table> <table name="publisher"> <field name="id" type="int" primary-key="true" /> <field name="name" type="text" /> </table> <table name="author"> <field name="id" type="int" primary-key="true" /> <field name="name" type="text" /> </table> </schema>Use PHP to create PHP data arrays from comma-separated value (CSV) datafiles.
Every once in a while, I have a static list of values that I don't want to put into a database, but that I do want to use in my PHP application. That static data can come from a variety of sources, but often it's in a spreadsheet. This handy hack converts any CSV data (one of the easiest formats to pull from a spreadsheet) to PHP code that I can then copy and paste into my PHP page.
Figure 5-7. The publisher table as shown in the browser
Save the code in Example 5-31 as index.php.
Example 5-31. Page that sets up some comma-separated values to convert
<html> <body> <form method="post" action="commaconv.php" /> <table> <tr><td>Use regular expressions to scrape data from sources like Metacritic.
What do you do when you want the data from a site, but the site won't let you export that data in a predictable format (like XML [Hack #38] or CSV [Hack #43] )? One popular option is to perform what's called a screen scrape on the HTML to extract the data. Screen scraping starts with downloading the contents of the page containing the data into either a string in memory or a file. Regular expressions are then used to extract the relevant data from the string or file.
You can scrape almost any web site for data; for the example in this hack, I chose the Metacritic DVD review page (http://www.metacritic.com/video/).
Figure 5-9. The resulting generated PHP
Metacritic is a site where movies, music, and video games are given a review score based on a selection of reviews. Figure 5-10 shows the Metacritic page that I scraped for this hack. On the lefthand side of the window is a list of movies ordered by name, along with their review scores.
I can tell from the size of the page that I want only a small portion of the HTML. I use View Source to see what the code looks like, and indeed there is a section for these scores well defined by a div tag that contains what I'm looking for:
</TR> </TABLE><DIV ID="sortbyname1"> <P CLASS="listing"> <SPAN CLASS="yellow">51</SPAN> <A HREF="/video/titles/800bullets">800 Bullets</A><BR> <SPAN CLASS="yellow">58</SPAN> <A HREF="/video/titles/actsofworship">Acts of Worship</A><BR> <SPAN CLASS="green">81</SPAN> <A HREF="/video/titles/badeducation"><B>Bad Education</B></A><IMG SRC="/_images/scores/star.gif" WIDTH="11" HEIGHT="11" ALIGN="absmiddle"><BR> …The first step will be to extract just this div tag. Then we need to use another regular expression to pick out each movie entry from text within the div tag. Notice that each movie listing starts with a span
Using the XML from Excel 2003, you can read data directly from spreadsheets that customers upload to your site.
Your customers' data can come from many different sources. Making it easy for them to get their data into your system can mean the difference between getting their business and having them go somewhere else for their data needs (and taking their money with them). Supporting data import from common data sources such as Excel can be a very compelling feature for customers.
This hack shows you how to save Excel spreadsheets in the new XML format supported by Excel and Microsoft Office 2003 and how to read that format and display the data back to the user. Figure 5-12 illustrates the flow between the browser (shown here as the computer) and the import system. The first page is index.php, which presents the Browse button. The user then selects an Excel XML file, which is submitted to the import.php page; that page returns an HTML rendering of the data in the file.
Figure 5-12. The flow of the Excel XML import
index.php (shown in Example 5-34) is responsible for getting the Excel data into your PHP scripts.
Example 5-34. The PHP for getting Excel data into your scripts
<html> <body> <form enctype="multipart/form-data" action="import.php" method="post"> Excel XML file: <input type="hidden" name="MAX_FILE_SIZE" value="2000000" /> <input type="file" name="file" /><br/> <input type="submit" value="Upload" /> </form> </body> </html>Use Excel 2003's XML capability to load your SQL database from an Excel spreadsheet.
More than a few times, I have had an Excel spreadsheet full of data that I needed to load into my database. Before Office 2003, I had to export each sheet as a CSV, and then use a custom loader to insert the records into the database. With Excel 2003's ability to save spreadsheets, macros, and even formatting as XML, that custom loader can go the way of eight-track tapes. The script in this hack turns Excel XML data into SQL that you can feed to your database. Figure 5-17 shows how the Excel-generated XML, taken as input to the gen.php script, is converted to SQL, which is then fed into the database.
Figure 5-17. The flow between the Excel XML and the database
Save the code in Example 5-36 as gen.php.
Example 5-36. The code to generate SQL from an Excel XML file
<?php $tables = array(); $indata = 0; function encode( $text ) { $text = preg_replace( "/'/", "''", $text ); return "'".$text."'"; } function start_element( $parser, $name, $attribs ) { global $tables, $indata; if ( $name == "WORKSHEET" ) { $tables []= array( 'name' => $attribs['SS:NAME'], 'data' => array() ); } if ( $name == "ROW" ) { $tables[count($tables)-1]['data'] []= array(); } if ( $name == "DATA" ) { $indata = 1; } } function text( $parser, $text ) { global $tables, $indata; if ( $indata ) { $data =& $tables[count($tables)-1]['data']; $data[count($data)-1] []= $text; } } function end_element( $parser, $name ) { global $indata; if ( $name == "DATA" ) $indata = 0; } $parser =Search the text in Microsoft Word documents by parsing WordML files.
A lot of valuable data is locked up in Microsoft Word documents. In particular, documents such as resumes are particularly tempting for data-mining applications. Job boards need code that parses Word documents and finds keywords or phrases to categorize the job candidates. This hack demonstrates how to search Word documents saved as WordML for text strings.
Save the code shown in Example 5-37 as index.php.
Example 5-37. HTML that handles data uploads
<html> <body> <form enctype="multipart/form-data" action="search.php" method="post"> WordML file: <input type="hidden" name="MAX_FILE_SIZE" value="2000000" /> <input type="file" name="file" /><br/> <input type="submit" value="Upload" /> </form> </body> </html>Save the code in Example 5-38 as search.php
Use PHP to generate Rich Text Format (RTF) documents dynamically.
Rich Text Format (RTF) is a text format used by word processors, notably Microsoft Word, and some text editors and viewers to store highly styled documents. If you want to generate documents dynamically with all of the features of a word processor, RTF gives you an opportunity to do that.
Start with a word processing document in an editor such as Microsoft Word. Figure 5-21 shows the document used in this hack.
Figure 5-21. The original Microsoft Word document
The sections of the document with the %% markers around them are where I want the dynamic data to go. I could have picked any special characters, but %% has the advantage of being pretty distinct and unusual; further, the percent signs aren't encoded in RTF.
With the document in hand, use the Save As command to save the file as RTF. Then, using that RTF file as a template, you can start writing the PHP code that will generate the RTF.
Save the file shown in Example 5-39 as rtf.php.
Example 5-39. Using escape sequences to represent RTF
Use the new XML format supported by Microsoft Office 2004 to generate spreadsheets dynamically.
Word processing documents aren't the only things you might want to generate dynamically [Hack #48] . You also can create spreadsheets dynamically. With the new XML features of Microsoft Office 2004, we can build spreadsheets by simply using XML.
Start by creating a document in Excel, as shown in Figure 5-24.
Use the Save As command to save the spreadsheet in XML format. Then use the XML that's exported by Excel as the basis of your PHP file.
Save the code in Example 5-40 as spreadsheet.php.
Figure 5-24. The simple Excel spreadsheet that I use as a template
Example 5-40. Using state data to represent a spreadsheet
<? header( "content-type: text/Use a MySQL table to create a simple message queue for delayed notifications.
Background processing is always a problem in web applications. Users like to get snappy responses from their web pages, but sometimes processing can take a while. A classic example is a web notification mail-out. The user initiates some process that requires mailing a notice to 100 people, but mailing to 100 people takes a while. Making the server wait for all the mail to go out isn't a good idea. It looks like the application is hung or about to crash. Ideally we could return a page to the user, and then in the background, send out the 100 messages. But how do we do that?
One method is to create a simple database-driven message queue. The web page puts some data into the queue, which is executed by another process later (usually fired off by the cron task-scheduling system). The message queue in this system takes two parameters: the function to be run and the arguments to the function. Therefore, you can delay almost any processing you want.
Figure 5-27 shows the simple schema for the message queue. There are really only two fields: func, which holds the name of the function, and args, which holds the XML version of the arguments.
Figure 5-27. The schema for the queue
Figure 5-28 shows the status of the queue table. It starts empty, with no messages. Then a couple of messages are added. Messages are removed as they are processed.
Sitting on top of the database and below the HTML is application logic. This chapter concentrates on hacks that will add stability and flexibility to your application logic. Topics covered include security and roles, password management, login and session management, and e-commerce.
Use dynamic loading to allow users to write snap-in modules for your application.
Most of the really popular PHP open source applications have an extension mechanism that allows for PHP coders to write small fragments of code that are dynamically loaded into the application. This hack demonstrates an XML-based drawing script that you can extend simply by placing new PHP classes into a modules directory; of course, the point is not as much the drawing code as the way you can extend it easily.
Save the code in Example 6-1 as modhost.php.
Example 6-1. The code that handles a modular PHP architecture
<?php class DrawingEnvironment { private $img = null; private $x = null; private $y = null; private $colors = array(); public function __construct( $x, $y ) { $this->img = imagecreatetruecolor( $x, $y ); $this->addColor( 'white', 255, 255, 255 ); $this->addColor( 'black', 0, 0, 0 ); $this->addColor( 'red', 255, 0, 0 ); $this->addColor( 'green', 0, 255, 0 ); $this->addColor( 'blue', 0, 0, 255 ); imagefilledrectangle( $this->image(), 0, 0, $x, $y, $this->color( 'white' ) ); } public function image() { return $this->img; } public function size_x() { return $this->x; } public function size_y() { return $this->y; } public function color( $c ) { return $this->colors[$c]; } public function save( $file ) { imagepng( $this->img, $file ); } protected function addColor( $name, $r, $g, $b ) { $col = imagecolorallocate($this->img, $r, $g, $b); $this->colors[ $name ] = $col; } } interfaceMake it easier for your customers to enter styled text into your application by supporting the Wiki syntax.
A new form of content management system for the Web, Wikis are a collection of pages, each titled with a WikiWord, which is a set of two or more capitalized words joined together without spaces. The ease with which you can install and update Wikis has made them extremely popular both on intranets and on the Internet. Perhaps the most famous Wiki is Wikipedia (http://www.wikipedia.org/). This is an encyclopedia on the Web that anyone can contribute content to by using just their web browser.
Another reason wikis are so popular is that formatting a Wiki page is a lot easier than writing the equivalent HTML code. For example, you specify a paragraph break by just typing two returns—there is no need to add p tags. In fact, most of the time tags aren't used at all. For example, you create a bulleted list by putting an asterisk at the start of each line; this is far easier than using the equivalent ul and li tags. This hack demonstrates using the wiki-formatting PEAR module in a PHP application.
Use the Iterator interface in PHP 5 to turn any object into an array.
If you have ever used the DOM interface to read or write XML in PHP, you're already familiar with the DOMNodeList interface. Many methods in the DOM return an array of nodes. That array is implemented by the DOMNodeList object. To read the node list, you have to write code like this:
$dl = $doc->getElementsByTagName( "foo" ); for( $i = 0; $i < $dl->length; $i++ ) { $n = $dl->item( $i ); … }That's kind of unfortunate, isn't it, since PHP has that beautiful foreach operator that gives access to arrays with almost no potential for messing things up. Wouldn't it be great if the interface to DOM looked more like this?
foreach($doc->getElementsByTagName( "foo" ) as $n ) { … }That is a lot cleaner and far less error prone.
Thanks to the additions in PHP 5, we can now allow foreach to work on any object, simply by having that class implement the Iterator interface. In this hack, I'll show how to implement an Observer pattern [Hack #67] using the Iterator interface.
Save the code in Example 6-5 as iterator.php.
Use the XML DOM to create XML without errors.
Creating XML from your PHP web application is easy to get wrong. You can screw up the encoding so that special characters are not formatted properly, and you can miss start or end tags. Both of these problems, which are common in even simple PHP applications, will result in invalid XML and will keep the XML from being read properly by other XML consumers. Almost all of the problems result from working with XML as streams of characters instead of using an XML API such as DOM.
This hack will show you how to create XML DOMs in memory and then export them as text. This method of creating XML avoids all of these encoding and formatting issues, so your XML will be well-formed every time.
Figure 6-6 shows the in-memory XML tree that we will create in this hack. Each element is an object. The base of the system is DOMDocument, which points to the root node of the tree. From there, each DOMElement node can contain one or more child nodes and attribute nodes.
Use a transaction table in your database to fix the classic double submit problem.
I have a couple of pet peeves when it comes to bad web application design. One of the biggest is the wealth of bad code written to fix "double submits." How often have you seen an e-commerce site that implores you, "Do not hit the submit button twice"?
This class problem results when a browser posts the contents of a web form to the server twice. However, if the user hits "submit" twice, this is exactly what the browser should do; it's the server that needs to determine whether this is an error.
Figure 6-8 shows the double submit problem graphically. The browser sends two requests because the user clicks twice. The first submit is accepted, and before the HTML is returned, the second submit goes out. Then the first response comes in, followed by the second response.
Figure 6-9 illustrates a fix to the double submit problem; the first request stores a unique ID in the page being processed. That way, when the second request comes in with the same ID, the redundant transaction is denied.
Figure 6-8. The double submit problem sequence diagram
Figure 6-9. The double submit solution requires denying the second request
Save the code in Example 6-7 as db.sql.
Example 6-7. The database code for the transaction checker
DROP TABLE IF EXISTS transcheck; CREATE TABLE transcheck ( transid TEXT, posted TIMESTAMP );Use a PHP reporting engine that takes an XML definition file and creates a custom report.
Reporting engines allow end users to customize the reports generated in their applications. This is extremely valuable in enterprise applications because these systems rarely are exactly what the customer wants. The ability to tweak the reports, notifications, or other front-facing features is critical for a satisfying user experience.
A reporting engine gives the user a declarative method for specifying a report. The host page sets up the query, gets the data, and then runs the report engine to format the data. Some reporting engines, like RLIB (http://rlib.sf.net/), can export not only to HTML, but also to PDF, XML, and other formats. In this hack, I use the PHPReports system (http://phpreports.sf.net/) to implement a simple book report.
Save the code in Example 6-11
Sturdy login systems are required for any complex multi-user web application.
With any multi-user web application, you are going to need a user authentication system. You can use Apache's authentication mechanism, which pops up a dialog with a username and password when pages are accessed, but that means integrating your application and database with that authentication mechanism. And, unfortunately, it means that you don't have control over the login dialog; you can't include an "I've forgotten my password" option or a contact link.
Figure 6-14 shows the page flow of the login system. The user starts at index.php, the login page. From there, login.php verifies the login credentials the user provides.
Figure 6-14. The page flow of the login system
If login.php approves the credentials, the user receives a session and is sent to welcome.php. At welcome.php, the user can click on the logout link, which takes him back to the logout.php script, removes his session, and then finally sends him to the original index.php page. If the user types the welcome.php URL directly into his browser's location field without logging in, the welcome.php page will detect that and will send the sneaky user back to the index.php login page.
Save the code in Example 6-14 as users.sql.
Example 6-14. The database definition for the users
DROP TABLE IF EXISTS users; CREATE TABLE users ( id MEDIUMINT NOT NULL AUTO_INCREMENT, name TEXT, password TEXT, PRIMARY KEY( id ) ); INSERT INTO users VALUES ( 0, 'jack', MD5( 'toronto' ) ); INSERT INTO users VALUES ( 0, 'megan', MD5( 'seattle' ) );Save the code in Example 6-15 as index.php.
Use security roles to provide varying levels of access to your web application.
Not all users who approach a system have the same rights within that system. For example, some users can add and remove users, some can post, some can only read messages, and some can do a combination of all of these.
A proper role-based system not only restricts access to parts of the system, but also reduces the complexity of pages for users with restricted rights. The user should not be able to see links that she cannot use, and she should have links to the tasks appropriate for her. This hack demonstrates a fairly straightforward role-based security system.
Figure 6-18 shows the page flow among the different pages in the hack. The user starts on the index.php page, which has the login. That page submits to the login.php page, which checks the login information. If the login credentials are accepted, the user is logged in and is forwarded on to the welcome.php page. If the login credentials aren't accepted, the user is sent back to the index.php page. From the welcome.php page, the user can do one of two things. She can log out by clicking a link to the logout.php page, which dumps her session and sends her back to index.php, or she can try to go to the manage.php page directly; that page checks her credentials and sends her back to the welcome.php page if she doesn't have proper credentials.
Figure 6-18. The page flow in this hack
The welcome.php page also checks to see whether the user's session is valid. If the session is not valid, the user is sent back to the index.php page to log in. In other words, people can't bypass the login to get into the site simply by typing in a direct URL.
Save the code in Example 6-19 as dblib.php.
Example 6-19. The database library for the roles system
<?php require_once( "DB.php" ); $dsn = 'mysql://root:password@localhost/roles'; $db =& DB::Connect( $dsn, array() ); if (PEAR::isError($db)) { die($db->getMessage()); } function get_db() { global $db; return $db; } function get_role_id( $role ) { global $db; $res = $db->query( "SELECT id FROM roles WHERE name=?", array( $role ) ); if ( $res != null ) { $res->fetchInto( $row ); return $row[0]; } return null; } function has_role( $user, $role ) { global $db; $role_id = get_role_id( $role ); $res = $db->query( "SELECT user_id FROM user_role WHERE user_id=? AND role_ d=?", array( $user, $role_id ) ); if ( $res != null ) { $res->fetchInto( $row ); return ( (int)$row[0] == (int)$user ) ? true : false; } return false; } ?>Save the code in Example 6-20 as index.php.
Example 6-20. The login page
<html> <head><title>Login</title></head> <body> <?php if ( $_GET['bad'] == 1 ) { ?> <font color="red">Bad login or password, please try again<br/></font> <?php } ?> <form action="login.php" method="post"> <table width="300" border="0" cellspacing="0" cellpadding="2"> <tr><td>User name:</td><td><input type="text" name="user" /></td></tr> <tr><td>Password:</td><td><input type="password" name="password" /></td></tr> <tr><td colspan="2"><center><input type="submit" value="Login" /></center></td></ tr> </table> </form> </body> </html>Save the code in Example 6-21 as login.php.
Use a migration script to turn your plain-text passwords into MD5-encrypted passwords.
From years of consulting work, I can tell you that although people say their web applications have encrypted passwords, they often do not. Realistically, though, encrypting passwords is just not that difficult to do. Even worse, any site that can send you the exact text of your password when you click the "I forgot my password" link stores a copy of your password in clear text somewhere. Needless to say, this isn't a good thing.
So why are encrypted passwords so important? First, because anyone who gets access to the database through a security hole can get access to the entire system. Second, most people talk about using different passwords on different accounts, but end up using the same, or similar, passwords, simply because it's easier. Getting a password on one machine can mean having access to other, possibly more important accounts. This hack describes how to migrate a table of users and passwords from plain text to MD5 encryptions.
Save the code in Example 6-27 as schema.sql.
Example 6-27. The original schema file
DROP TABLE IF EXISTS users; CREATE TABLE users ( id MEDIUMINT NOT NULL AUTO_INCREMENT, name TEXT, pass TEXT, PRIMARY KEY( id ) );Save the code in Example 6-28 as users.sql.
Example 6-28. The original nonencoded passwords
INSERT INTO users VALUES ( 0, "jack", "toronto" ); INSERT INTO users VALUES ( 0, "megan", "omaha" );Use Apache's mod_rewrite module to create URLs that are easy to understand and use.
The Apache server's mod_rewrite module gives you the ability to redirect one URL to another transparently, all without the user's knowledge. This opens up all sorts of possibilities, from simply redirecting old URLs to new addresses, to cleaning up the "dirty" URLs (filled with extra parameters and data your application will never use) coming from a poor publishing system—turning them into URLs that are friendlier to both readers and search engines.
Readable URLs are nice. A well-designed web site will have a logical filesystem layout with smart folder names and filenames and as many implementation details left out as possible. In the better-designed sites, readers can even guess at filenames with a high level of success.
However, sometimes the best possible design still can't stop your site's URLs from being nigh impossible to use. For instance, you might be using a content management system that serves out URLs that look something like http://www.site.com/viewcatalog.php?category=hats&prodID=53.
This is a horrible URL, but it and its brethren are becoming increasingly prevalent in these days of dynamically generated pages. There are a number of problems with a URL of this kind:
It exposes the underlying technology of the web site (in this case, PHP). This can give potential hackers clues as to what type of data they should send, along with the query string needed, to performa front-door attack on the site. You shouldn't give away information like this if you can help it.
Even if you're not overly concerned with the security of your site, the technology you're using is at best irrelevant—and at worst a source of confusion—to your readers, so you should hide it from them if at all possible.
If at some point in the future, you decide to change the language that your site is based on (to ASP, for instance), all of your old URLs will stop working. This is a pretty serious problem, as anyone who has tackled a full-on site rewrite will attest.
The URL is littered with awkward punctuation, such as the question mark and ampersand. Those & characters, in particular, are problematic; if another webmaster links to this page using that URL, the unescaped ampersands will invalidate his XHTML.
Some search engines won't index pages that they think are generated dynamically. Because of the danger of finding infinite pages by changing the query string of a URL, many search engine spiders are designed to avoid adding pages like this to their index.
Luckily, using rewriting, it's easy to clean up this URL to something far more manageable. For example, you can map the URL to http://www.site.com/catalog/hats/53/.
Much better, isn't it? This URL is more logical, readable, and memorable, and will be picked up by all search engines. The faux directories are short and descriptive. As an added benefit, it looks more permanent.
To use mod_rewrite, you supply it with the URLs you want the server to match (these are the dirty URLs mentioned earlier) and the real URLs that these will be redirected to. The URLs to be matched can be normal file addresses, which will match one file, or they can be regular expressions, which can match many files at the same time.
Add the ability for your site to serve up ads on a random basis between link clicks.
Content sites, like the IGN gaming site (http://www.ign.com/) have revenues based on serving ads. If you click on an article link, you might get the article, or you might get an ad page. The ad page has both the ad and a link to the requested article (so that you can manually go to the requested page). The ad page will also automatically forward you to your article if you let it sit for a few seconds.
I have to admit that I thought twice about writing and including this hack, because I don't like this behavior all that much. But I figured I would let you decide for yourself. It's sort of like the Anarchist's Cookbook; just because there is a book on how to make a bomb doesn't mean you have to make one for yourself.
The illustration in Figure 6-27 shows the relationship among the pages in the ad redirector system. All of the links on the index.php page go to the redir.php redirector page. Based on a random value, the redir.php page decides whether you will stay there and watch an ad or be sent to the originally requested article.
Save the code in Example 6-32 as index.php.
Figure 6-27. The ad redirector page flow
Use PayPal to add a Buy Now button to your PHP web application.
PayPal's Buy Now buttons are ideal for e-commerce impulse sales. They are easy for the customer to use, and there's no lengthy checkout procedure. One click and you're on PayPal's secured site.
These buttons are also very easy to install; because of their simplicity, though, web applications often don't put in the extra work required to track and secure sales. This hack shows how to track and secure purchases made using Buy Now buttons. I will take you through the steps of creating a Buy Now button, modifying it to create a database record of the purchase, and I'll show how to secure the purchase using Instant Payment Notification (IPN).
This hack requires PHP 5 and MySQL Version 4.1.3 or higher, along with the mysqli extension.
You don't need to worry about manually creating the HTML form for a Buy Now button. Just go to your PayPal account, choose Merchant Tools, and click on the Buy Now Buttons link. Within seconds, you'll have the HTML form ready to embed into your web site. The Buy Now button looks like Figure 6-31.
Figure 6-31. The Buy Now button
As it stands right now, this form has a couple of shortcomings. It's not geared toward tracking purchases in a database, and like any form on the Internet, it is subject to hijacking. A local copy of the form can be made, and values can be changed, all in an effort to sabotage your site (and your customers); even worse, if there is no way of crosschecking the values from this form against a correct set of values, an order might easily be processed at discounted prices.
The buynow.html file in Example 6-37 (shown shortly) contains the HTML for the Buy Now button. It differs in one major respect from the code generated by PayPal: the action attribute of the form tag doesn't point to the PayPal site, but to another script, presubmit.php (shown in Example 6-39). This intermediary script allows information to be added to a database before submitting purchases to PayPal.
Briefly stated, the code in presubmit.php inserts a record into a tblorders table and into a related tblorderitems table. The order ID is then retrieved and added to the query string constructed from the values posted to this page. Finally, this query string is forwarded to the PayPal site, as shown in Figure 6-32.
Figure 6-32. The PayPal checkout page
Creating a database entry and passing the order number along to PayPal will assist in tracking and securing the purchase.
Since you're accessing the database using the relatively new object-oriented (OO) interface to the mysqli extension, a few comments are in order. Even if you have no experience in OO programming, it is easy to understand the code that inserts a record into the orders table. Since this table has an auto_increment field, it's necessary to retrieve the order ID after insertion, so as to identify the order later.
The way you insert a record into the tblorderitems table is not as straightforward. To create a record in this table, the code uses a prepared statement; the mysqli extension supports this MySQL 4.1 capability. Prepared statements are commonly used to insert multiple records into a database and are much more efficient than a series of individual SQL statements. However, the code here uses a prepared statement because data passed to a prepared statement does not have to be escaped first. Prepared statements automatically escape data (a nice convenience feature taken advantage of here).
Securing a payment means ensuring that the payment is made to the correct account in the correct amount and is not a duplicate of an earlier transaction. IPN allows us to do this programmatically by identifying a URL that will receive notification of payment.
When a purchase is made at the site, the sequence of events is as follows:
Clicking the Buy Now button invokes the script, creating a database record, and then forwards the buyer to the PayPal site.
Use the Net-Geo PEAR module to tell you where in the world the people surfing to your site are coming from.
Have you ever wondered where the guests to your site are coming from? It turns out that finding out is not as tough as you might think. First, the IP address of every incoming request is provided to you through the Apache request handler. Second, a simple PEAR library is available that will turn an IP address into a physical location. This hack will show you how it all works.
Save the code in Example 6-41 as
Teach your PHP application to read the vCard standard for the storage of contact information.
All too often, we ask web application users to recode data in files that they already have, when a flexible application could simply read the user's (already-existing) files directly. An example of this is the vCard file, a universal mechanism for storing contact information, particularly information like email and physical addresses. Every reasonable email and address book program can import and export the vCard (.vcf ) file format.
This hack shows you how to read this format using the Contact_Vcard_ Parse PEAR module [Hack #2] .
Save the code in Example 6-42 as index.php.
Example 6-42. A simple file upload page
Use a simple vCard template to create vCards dynamically from your application's data.
If you have a database of contacts on your web site, it's handy to put up a link to the email address of each contact, but wouldn't it be great to get all the information in one handy format? Well, it turns out that the vCard contact formula (VCF) is perfect for this task. And, you can create vCards automatically with PHP (as well as by reading the VCF format [Hack #64] ).
Save the code in Example 6-44 as vcard.php.
Example 6-44. A vCard writer
Use cookies and sessions to create a simple shopping cart application.
This hack demonstrates a simple shopping cart application using PHP and session variables.
Figure 6-38 shows the relationship among the pages in the shopping cart application. The user starts on the index.php page and can traverse freely between there and the checkout.php page (which shows his shopping cart). On the index.php page, he can add items to the cart by clicking on the Add button, which submits the information to the add.php page and sends him back to the index.php page. From The checkout.php page, he can remove items, a process that follows the same routine as the add logic but uses the delete.php page and returns him to the checkout.php page.
Save the code in Example 6-45 as shopcart.sql.
Figure 6-38. The shopping cart page flow
Example 6-45. The shopping cart schema
DROP TABLE IF EXISTS product; CREATE TABLE product ( id MEDIUMINT NOT NULL AUTO_INCREMENT, name TEXT, price FLOAT, PRIMARY KEY( id ) ); INSERT INTO product VALUES ( 0, "Code Generation in Action", 49.99 ); INSERT INTO product VALUES ( 0, "Podcasting Hacks", 29.99 ); INSERT INTO product VALUES ( 0, "PHP Hacks", 29.99 );Save the code in Example 6-46 as dblib.php.
Example 6-46. The database library
<?php require_once( "DB.php" ); $dsn = 'mysql://root:password@localhost/shopcart'; $db =& DB::Connect( $dsn, array() ); if (PEAR::isError($db)) { die($db->getMessage()); } function get_db() { global $db; return $db; } function get_products() { global $db; $res = $db->query( "SELECT * FROM product", array() ); $out = array(); if ( $res != null ) while( $res->fetchInto( $row, DB_FETCHMODE_ASSOC ) ) { $out []= $row; } return $out; } function product_info( $id ) { global $db; $res = $db->query( "SELECT * FROM product WHERE id=?", array( $id ) ); if ( $res != null ) { $res->fetchInto( $row, DB_FETCHMODE_ASSOC ); return $row; } return null; } ?>Save the code in Example 6-47 as index.php.
Example 6-47. The product page
In 1994, Erich Gamma, Richard Helm, Ralph Johnson, and John Vlissides published Design Patterns
Use the Observer pattern to loosely couple your objects.
Loose coupling is critical to any large-scale project, but few people actually understand what the term really means. Have you ever made a small change in a project, and it seems that as a result, almost everything else has to change as well? This occurs all too often because of tight coupling among the modules in the program. Each module relies on the exact state or function of several other modules. When one fails, they all fail. When one changes, they all must change.
The Observer pattern loosens up the bonds among objects by providing a simpler intra-object contract. An object allows itself to be observed by providing a mechanism where objects can register with it. When the observed object changes, it notifies the observing objects through a notification object. The observed object does not care how or why it is being observed, nor does it even know what types of objects are observing it. Further, the observers usually don't care why or how the object is changing; all they are looking for is a change.
A classic example is the code in a dialog observing the state of a checkbox. The checkbox doesn't care if it's being observed by one object or a thousand objects. It simply sends out a message when its state changes. In the same way, the dialog doesn't care how the checkbox is implemented; it only cares about the box's state and about being notified when that state changes.
In this hack, I'll demonstrate the Observer pattern by setting up an observable customer list. This object represents a database table of customers. The CustomerList object will send out notifications when new customers are added. The object uses a SubscriptionList object to implement its observability. The listeners object is an instance of SubscriptionList
Use an Abstract Factory pattern to control what type of object is created.
The Abstract Factory pattern is the vending machine of design patterns. You ask it for what you want, and it vends you an object based on your criteria. The value is that you can change what types of objects are created throughout the system by altering just the factory.
The super-simple factory in this example creates Record objects, where each record has an ID, a first name, and a last name. The relationship between these classes is shown in Figure 7-2.
Figure 7-2. The Record and the RecordFactory classes
Factory objects often create more than one type of object. To keep this example simple, though, I've limited the factory to creating only a single object type.
There is no way to strictly enforce that only the factory can create objects of a particular type in PHP. But if you use a factory often enough, engineers copying and pasting your code will end up using the factory; it will quickly become the de facto way of creating the different types of objects.
Use the Factory Method pattern when creating objects to allow derived classes to alter what types of objects are created.
Closely related to the Abstract Factory pattern is the Factory Method pattern. This one is fairly commonsense. If you have a class that is creating a lot of objects, you can use protected methods to encapsulate the object creation. That way, a derived class can just override the protected method(s) in the factory to create a different type of object.
In this case, the RecordReader class, instead of using a Factory class, uses a method called newRecord() to create a new Record object. That way, a class that derives from RecordReader can change the type of Record objects created by overriding the newRecord() method. This is shown graphically in Figure 7-3.
Use the Builder pattern to abstract code that performs a routine construction task, such as composing HTML or text for an email.
I've always found that code that constructs something is some of the most elegant code in a system. I suppose that's just because I spent a year writing a book on code generation, which is all about constructing code.
By the way, the book is Code Generation in Action (Manning). It_s still available and makes an excellent holiday gift for friends and family.
An example of construction code is the code that reads an XML document off the disk and constructs an in-memory representation of that structure. Another is a module of code that constructs an email message to tell a customer she is behind on her payments.
It's the late-payment mail that I want to focus on in this hack; but I'm going to do it with a twist. I'm going to use the Builder pattern so that the same code that creates a message in HTML can also create a message in XHTML or text format.
I'm going to have the code that writes the past-due notice use a builder instead of just creating the string directly. This builder object will have a set of methods, as shown in Figure 7-4. The startBody() and endBody() methods wrap the creation of the message. The addText() method adds some text, and the addBreak() method adds a line break.
Use the Strategy pattern to abstract the code that traverses structures from the code that operates on those structures.
You use the Strategy pattern to abstract the processing of an object, allowing how an object is processed to be separated from where the object is located.
In this hack, I'll use a car chooser as an example. This code will recommend a car based on some search criteria. In this case, I will provide my specs for an ideal car and let the code pick the car that most closely matches my dream specs. The value of the Strategy pattern is that I can alter the car comparison code independently of the car selection code.
The UML for this hack is shown in Figure 7-5. The CarChooser object uses a CarWeighter object to compare each Car to the ideal model. Then the best Car match is returned to the client.
Use an adapter class to transfer data between two modules when you don't want to change the API of either module.
Sometimes you have to get data from two objects, each of which uses a different data format. Changing one or both objects just isn't an option because you'll have to make all sorts of other changes in the rest of your code. One solution to this problem is to use an adapter class. An adapter is a class that understands both sides of the data-transfer fence and adapts one object to talk to another.
The adapter demonstrated in this hack adapts data from a mock database object into data usable by a text-graphing engine.
Figure 7-6 shows the RecordGraphAdapter sitting between the TextGraph on the left and the RecordList on the right. The TextGraph object very nicely specifies the format it expects for data using an abstract class called TextGraphDataSource. The RecordList is a container class that has a list of Records, where each Record contains a name, age, and salary.
Figure 7-6. The adapter sitting between the graph and the data
Use a Bridge pattern to hide the implementation on an object, or to change the implementation based on the environment.
In one of my jobs, working for a company that shipped a very large cross-platform C++ application, we used the heck out of the Bridge pattern. The Bridge pattern allows you to hide a portion of the implementation of a class in another class, either because you want to hide the code from other implementers or because the implementation of a portion of the code is platform specific.
I've taken the platform-specific approach in this hack to show the benefits of a bridge. Figure 7-7 shows the relationship between two classes, TableCreator and TableCreatorImp. The role of the table creator is to create tables in the target database. The implementation class, TableCreatorImp, is defined in another file, which is included from a database-specific directory.
This flexibility allows for one version of the code specific to Oracle and another for MySQL (or any other database). This is handy, as each database has a different syntax for creating tables.
Use the Chain of Responsibility pattern to create a Plug and Play (PnP) processing framework in your code.
Watching football with programmers is fun. Even in the fourth quarter, when the game is 33 to 7 with 1:30 to go, you still get a range of options when you ask who is going to win the game. That's because programmers are trained to think of every potential situation, no matter how unlikely (and generally ludicrous). And I've found that most programmers, including myself, hate closing the door on any question. It's always better to write code that handles 100 possible scenarios, even when your manager swears up and down there will only ever be one.
That's why I think the Chain of Responsibility pattern is so appealing. Imagine walking into a room full of people with a box of donuts. You flip open the box and pull out a jelly donut. One by one you ask people, "You want a jelly donut?" until you find someone that does. Then you continue with your varied box of donut flavors until the box is empty.
That's the Chain of Responsibility pattern; each person in the room registers himself in advance with you, the donut vendor. Then, as new donuts come in, you see who wants them by looking through your list of registered people. The advantage is that you, as the vendor, don't care about how many people want donuts; you don't even care what they do with the donuts. You just manage the registry and the vending.
In this hack, I'll write some code that turns URLs into donuts. Actually, it just vends URLs to a bunch of handlers that will potentially remap the URL. If nobody handles the URL, it will just fall through and be ignored.
Figure 7-8 shows how this is going to work. URLMapper
Use the Composite pattern to break megaclasses into small, manageable classes.
I have a curious reaction when I hear news about enormous databases storing everything anyone would ever want to know about me. While most people are immediately concerned about the privacy implications, I think about how poorly designed that system almost certainly is. I'm almost positive there's one awful mega-object in there called Person, probably with 4,000 fields and 8,000 methods.
How do I know that? Because I've seen and maintained objects like that before! What that kind of class really needs is the Composite pattern. The Composite pattern would retain the Person class, but would have groups of those 4,000 fields lumped into contained child objects; the Person object would really comprise 100 or so objects, each containing other small objects (which might contain even smaller objects, and so on).
Now, I'm not saying I've seen classes this bad in PHP; but I have seen classes with upward of 100 fields, simply because the objects represent a set of tables that have that many fields related to a single entry. This hack demonstrates how to take a Customer
Use the Façade pattern to simplify the API that you present to other programmers.
This is one of the patterns I really wish more programmers would use, and not just because of that fancy squiggle under the c in façade. It's because creating a façade means that some other programmer has thought of me and made sure I have just the information I need (and nothing else that I can screw up).
Take a sample logging API, such as the one shown in Figure 7-10.
Figure 7-10. The logging API with a simple façade
This API can log to XML, text, or both. Wow. As a peer programmer, I'm impressed with the skill of the coding. There seem to be methods for everything; starting a message, adding text, cleaning up…even handling XML and text easily.
But what I really want to know is which method to use and when to use it. That's precisely what a façade does: it ensures that I use the API correctly. The façade in this example is the list of three functions with the line through it on the lefthand side. That line is a sort of theoretical bar that says, "I'll handle the stuff to the right; just call my methods, and I'll take care of the rest."
Use the Singleton pattern to create objects that exist as a solitary object in the system.
Of all of the patterns detailed in the Gang of Four's Design Patterns, none seems to have been used as often as the Singleton pattern, probably in part because it's so easy to implement. Besides, who can beat coding up a singleton and saying, "There can be only one!" Or maybe that's just a Highlander thing.
A singleton is an object that can have only one instance at any given time in the system. A great example of a potential singleton is a database handle. For each instance of the PHP interpreter, there should be only one database handle. This hack implements just such a setup, a singleton version of a database handle.
Figure 7-11 shows the UML for the database handle singleton (I told you it was simple!).
Figure 7-11. The database singleton
Use the Visitor pattern to separate data traversal from data handling.
Early in my career as a programmer, I did a lot of scientific programming with data acquisition systems. These were systems that recorded data at a sampling interval of 3 microseconds—in other words, 333,333 samples per second. That came out to 38 megabytes for every minute of information! For long recording sessions, a file could easily get into gigabytes. Needless to say, we had problems recording and storing that much information without any hiccups.
Another problem had to do with analyzing this data. How do you analyze a multigigabyte file when the machine doing the work has only 128 MB of memory? The answer is to chunk the data. Chunking means reading in the file by sections, swapping out the data you don't need and swapping in what you do need.
That said, you'd think that these scientific algorithms were tough enough without worrying about swapping in and out big chunks of data. To solve these problems—and do it elegantly—we used the Visitor pattern. One object would handle getting the data in and out of memory, and another object would handle processing the data when it was in memory.
Figure 7-12 shows a RecordList object that contains a list of Records. It has an iterate() function that, when given another function, calls the passed-in function on each record.
With this approach, the data processing function—passed in to iterate()—doesn't have to understand how records are managed in memory. All the function has to do is handle the data it's given.
Application testing—and squeezing that testing into what are almost always strenuous development cycles—has been a hot topic in recent years. This chapter starts with unit tests, which test an application's individual functions and classes. Then we'll move on and cover automatic generation of unit tests, as well as the testing of an application through alternate means. You'll code up robots that make HTTP requests to a server and tests that use Internet Explorer automation for checking a web interface for problems.
Use PHPUnit and PHPUnit2 to test your code continuously.
PHP is quietly becoming a force in enterprise application development. With larger applications come more complexity and requirements for testing in an attempt to make sure that the application is stable. It's a truism that stable applications are built by creating a multitiered structure, with each layer well tested on its own. For example, you might have a self-contained database layer (that is tested) that sits underneath a business logic layer (that is tested), which in turn is used by the user interface layer (that is also tested). Starting to get the idea?
To test each layer, it has become common practice to create unit tests. These are tests that are run after any code change and before any code is checked in (you are using version control on your enterprise projects, aren't you?). In fact, it's common to insist that all unit tests must
Use PHP to build your unit tests from code comments.
Unit tests [Hack #79] are so critical to the development of a stable application that it's worth going to some effort to create them. However, there's some nice middle ground between writing unit tests completely by hand (including the routine portion of those tests that is the same, over and over again), and automating test creation. This hack shows how to use a script to generate unit test code from comments embedded in your PHP code. The comments are test specific, but this does cut down on the redundant code you have to type in.
Save the code in Example 8-3 as Add.php. Note that several tests are laid out using the == and != operators all in the PHP script comments.
Example 8-3. The code from which to build unit tests
<?php // UNIT_TEST_START // ( 1, 2 ) == 3 // ( 1, -1 ) == 0 // ( 1, 1 ) != 3 // ( 1, -1 ) != 1 // UNIT_TEST_END function add( $a, $b ) { return $a + $b; } // UNIT_TEST_START // ( 1, 2 ) == -1 // ( 1, -1 ) == 2 // ( 1, 1 ) != 1 // ( 1, -1 ) != 1 // UNIT_TEST_END function minus( $a, $b ) { return $a - $b; } ?>Save the code in Example 8-4 as GenUnit.php. This script handles generation of tests from another script's comments.
Use output buffering to analyze the current page and CURL to check the links on a page to make sure they point to existing pages.
Broken links are the bane of web administrators; what's worse, a link that works today might not work next week, due to the ever-evolving nature of the Web. To help fix this pesky problem, the script in this hack captures blocks of HTML and checks the links for that section of markup. Then it provides a handy report noting any bad links, allowing you to easily find and repair problems.
Save the code in Example 8-5 as index.php. All this page does is present a link that works and one that doesn't.
Example 8-5. The host page for the link checker
<?php require_once( "checklinks.php" ); ?> <html> <body> <?php checklinks_start() ?> <div style="width: 800px" /> <a href="http://www.cnn.com">CNN</a><br/>Use the Internet Explorer automation interface to test your application through the UI.
You can test the back end of an application by using unit tests for the database and business logic code. You can test a portion of the frontend of your application by using robots [Hack #83] that request pages from the server and submit data. But how do you actually test what users would do? How do you simulate the buttons pushed and the boxes filled in by a typical user?
Particularly with JavaScript-heavy applications, you need something that will actually click on buttons to ensure that your application does what it should.
On Windows, you can use Internet Explorer COM objects to tell the IE browser to navigate to your site and even to hit the appropriate buttons. Figure 8-2 illustrates the relationships among the different PHP files used in this hack. test.php and print.php are standard PHP web pages. The testagent.php script is run on the command line and drives the browser to visit these pages, fill in the forms, and check the results.
Figure 8-2. The test agent driving the browser
Save the code in Example 8-7 as test.php. This simple web page is used for testing the agent.
Example 8-7. The first page of the test code
Use the HTTP_Client PEAR module to test your PHP application through the Web.
How do you know that your application is running properly? It's a lot like that little light in your refrigerator; if you can't see it, can you really be sure it's off when you close the door? One way to keep an eye on your application is to build a robot that tests your site. You can run this robot periodically, ensuring the server is always responding properly (and notifying you when it's not).
This hack shows how to use the HTTP_Client PEAR module [Hack #2] to test the shopping cart application [Hack #66] . Figure 8-5 illustrates the robot.php script driving the shopping cart application, all through requests to the web server. The robot checks the contents of each return page in the application, making sure that the process for adding and removing items from the shopping cart works properly.
Figure 8-5. The robot testing the shopping cart application through the web server
Save the code in Example 8-10 as robot.php.
Example 8-10. The test robot
<?php require_once 'HTTP/Client.php'; function check_html( $testname, $client, $values ) { $resp = $client->currentResponse(); $body = $resp['body']; preg_match( "/\<\!\-\- CART \: (.*?) \-\-\>/", $body, $found ); print "$testname: "; print ( $found[1] == join(",", $values ) ) ? "passed" : "failed"; print "\n"; } $client = newUse the HTTP_Client PEAR module to create a spider that walks all of the pages on your web site.
This hack demonstrates using PHP to write a spider for checking out the pages on your site. This is ideal for testing purposes and makes it simple to ensure that all of the PHP and HTML on your site still responds properly after an update.
Save the code in Example 8-12 as spider.php.
Example 8-12. A simple spider
<?php require_once 'HTTP/Client.php'; require_once 'HTTP/Request/Listener.php'; $baseurl = "http://localhost/phphacks/spider/test/index.html"; $pages = array(); add_urls( $baseurl ); while( ( $page = next_page() ) != null ) { add_urls( $page ); } function next_page() { global $pages; foreach( array_keys( $pages ) as $page ) { if ( $pages[ $page ] == null ) return $page; } return null; } function add_urls( $page ) { global $pages; $start = microtime(); $urls = get_urls( $page ); $resptime = microtime() - $start; print "$page…\n";$pages[ $page ] = array( 'resptime' => floor( $resptime * 1000 ), 'url' => $page ); foreach( $urls as $url ) { if ( !array_key_exists( $url, $pages ) ) $pages[ $url ] = null; } } function get_urls( $page ) { $base = preg_replace( "/\/([^\/]*?)$/", "/", $page ); $client = newUse PHPDoc comments to document your code, and use the phpDocumentor to build your documentation from code comments.
JavaDoc is the commenting standard for Java, and it is used to generate documentation for Java classes automatically. This comment-to-documentation idea was so popular that now almost every language has a comment markup that can be used to automatically generate documentation.
For PHP, there's PHPDoc; it makes writing programmers' documentation for your classes much easier for everyone involved. phpDocumentor (http://www.phpdoc.org/) is an open source tool that parses PHP code, extracts the PHPDoc documentation, and generates HTML from the source, all with a variety of different styles. Figure 8-7 illustrates how PHPDoc takes PHP files as input—in this case, Author.php—and creates a set of HTML files as a documentation package.
Figure 8-7. The PHPDoc workflow
Save the code in Example 8-17 as Author.php.
HTML and Dynamic HTML (DHTML) aren't the only user interface technologies that you can take advantage of with PHP. This chapter presents a set of hacks that will get your PHP code producing UIs on a computer desktop—whether it is Linux, Macintosh, Windows, or even PlayStation Portable (PSP). You'll also learn how to communicate with your application using instant messaging.
Use MapServer and PHP to build dynamic maps within your web application.
There has been a recent surge in the popularity of digital mapping. Part of this has been fueled by access to open source mapping tools and free geospatial data; it also hasn't hurt to see killer applications like Google Maps and MapQuest come on the scene. These are popular incarnations of some exciting web mapping technology that you too can use. With a few pieces of mapping data, a mapping programming library, and a PHP script, almost anyone can create custom and interactive maps.
Several open source mapping tools are available, from desktop applications to web-enabled mapping services. One of these is the University of Minnesota MapServer (http://ms.gis.umn.edu/). With a large user base, active community, and dedicated developers, it is a powerful product for publishing maps over the Web.
MapServer is actively used as the back end to many PHP web page frontends. For example, the Chameleon (http://maptools.org/) and Mapbender (http://mapbender.org/) products both use PHP extensively. Also available is a powerful implementation of Ajax-based web mapping called ka-Map.
For a tutorial on using ka-Map, see http://www.xml.com/pub/a/2005/08/10/ka-map.html.
All of these tools allow PHP programmers to handle maps and mapping data through PHP. MapServer sits on the back end cranking out map images, and PHP controls interaction and brokers requests.
MapServer is commonly used as a CGI application coordinating with a web server. To have ultimate power and flexibility, you can use the MapServer API with one of many programming languages, including PHP, Perl, Python, Ruby, Java, and C#. MapScript provides methods for interacting with mapping data, cartographic styling of map output, and the creation of final map images.
MapServer's core configuration is handled through a text-based runtime configuration file. Referred to as a map file (pretty clever, huh?), this file is the core of most MapServer-based applications. The CGI program, or custom MapScript application, reads this configuration file, accesses data, draws a map, and returns a graphic ready for online viewing. The resulting map and graphic can even be run as a standalone command-line program. The examples in this hack use PHP MapScript from the command line. You can take these examples and modify them to suit your particular environment.
The map file has a simple hierarchical object structure, with objects inheriting settings from their parents. This hack uses a very simple map file, intended for use with PHP MapScript. You'll also see how to create a map without having a map file, developing the configuration data totally in memory and rendering the map all within a PHP script.
Before you can start using PHP MapScript, you need to download and set up the MapScript tools. The main PHP MapScript site, http://maptools.org/php_mapscript/, has download instructions, as well as an API reference document. There are several other easy ways to get binary distributions of PHP and MapScript preconfigured to work together:
For Windows, you can use the MapServer for Windows (MS4W) distribution, which is available at http://maptools.org/ms4w/. It comes packaged as a base zip file containing all the MapServer basics, as well as PHP MapScript.
For Linux, you can use the FGS Linux installer at http://maptools.org/fgs/. It includes an installation shell script that you can use to help automate the install. FGS runs as a separate set of libraries, applications, and even a web server, making it easy to get started without having to compile a bunch of external dependencies.
For other operating systems, you might need to compile your own MapServer from source. When compiling, you can usually set an option to create the PHP MapScript libraries as well.
The following hacks show how to use map files, manipulate those files, and create maps from scratch, all within a simple PHP script.
Before you can start creating a map, you will need some basic map information. For these hacks, only one file is required: an image created from global satellite imagery. These images are in a GeoTIFF file format; Figure 9-1 shows an example of the global cloud image data set.
You can download the sample cloud image dataset from http://examples.oreilly.com/phphks.
The TIFF image format might already be familiar to you, but GeoTIFF extends it further, allowing the image to have geographic coordinate information embedded in the image. This makes it possible to use a mapping datafile when you might want to look at a particular geographic area (say, a certain latitude and longitude); GeoTIFFs can quickly make the translation between pixel rows and columns and geographic X and Y coordinates.
Use GTk to build cross-platform GUIs for your PHP code.
Why limit yourself to web markup, or even tag-based devices? Why not take over the desktop itself with your PHP code? With the GTk toolkit you can do that, and it's really easy. This hack shows how to use GTk to develop a simple regular-expression test application.
Save the code in Example 9-6 as retest.php.
Example 9-6. A GTk regular expression tester
<?php if( !extension_loaded('gtk')) { dl( 'php_Use PHP and Jabber to send RSS feeds to your instant messaging application.
Instant messaging is ubiquitous. Some studies have shown that younger Internet users rely more on IM than on email. Unfortunately, because of the proprietary nature of the most popular IM systems and the stateless connections of HTTP, IM hasn't been easily integrated into PHP applications.
The Jabber open source protocol, developed by Jeremie Miller in 1998 (and now called the Extensible Messaging and Presence Protocol [XMPP]), is a native XML streaming protocol and IETF-approved Internet standard for presence and messaging technologies. Important to us, though, is that XMPP allows for PHP scripts to access IM applications. This hack creates a command-line PHP Jabber client that uses the freely available class.jabber.php as a bridge to the XMPP protocol.
Another popular XML protocol called RSS allows a site to syndicate its content as a feed. Newsreaders and web pages poll a feed URL periodically, looking for new content items. The Jabber client we create will poll some existing weather RSS feeds for a new weather alert and send that alert off as an instant message.
Save the code in Example 9-7 as client.php.
Example 9-7. A Jabber client example
<?php /* CONFIG VARIABLES */ // jabber server you are registed at $SERVER ='yourserver'; //username and password for your special account $USERNAME = 'yourusername'; $PASSWORD = 'yourpassword'; // jabber id for your personal account $PERSONAL = 'username@yourserver'; //rss url for the alerts you want $NOAA = 'http://www.nws.noaa.gov/alerts/ct.rss'; /* END CONFIG */ function send($to, $msg) { global $JABBER; $JABBER->SendMessage("$to","normal", NULL, array("body" => htmlspecialchars($msg)),$payload); } //overrides jabber.class.php handler function Handler_message_normal($message) { global $JABBER; $from = $JABBER->GetInfoFromMessageFrom($message); $body = $JABBER->GetInfoFromMessageBody($message); if (substr ($body ,0,3) == SMS) { $bodyparts = explode(":", $body); $zip = $bodyparts[1]; weatherize($from, $zip); } } function Handler_message_chat($message) { Handler_message_normal($message); } //RSS functions adapted from PHP RSS Reader v1.1 By Richard James Kendall function startElement($parser, $name, $attrs) { global $rss_channel, $currently_writing, $main; switch($name) { case "RSS": case "RDF:RDF": case "ITEMS": $currently_writing = ""; break; case "CHANNEL": $main = "CHANNEL"; break; case "IMAGE": $main = "IMAGE"; $rss_channel["IMAGE"] = array(); break; case "ITEM": $main = "ITEMS"; break; default: $currently_writing = $name; break; } } function endElement($parser, $name) { global $rss_channel, $currently_writing, $item_counter; $currently_writing = ""; if ($name == "ITEM") { $item_counter++; } } function characterData($parser, $data) { global $Use Net_SmartIRC to have a conversation with your web application through your web server.
Sometimes a web page is not the most convenient way to talk with an application. A lot of people are using instant messaging and chat systems (like IRC) to converse with each other. So why not allow them to have a conversation with your web application?
Figure 9-8 illustrates the user and a bot having a conversation through an IRC server. The bot is run on the command line as a standalone PHP process. The Net_SmartIRC PEAR module [Hack #2] allows your web application to log into an IRC server and respond to commands.
Save the code in Example 9-8 as ircbot.php.
Use PHP and the web browser in the PSP's 2.0 system software to read RSS feeds.
The Sony PlayStation Portable (PSP) was a great device even before it got the killer addition of a built-in web browser. But now, with this addition, the game-playing, web-surfing, hip-pocket-size device is simply indispensable.
This hack creates a specially formatted RSS feed data page that displays nicely on a single page of the PSP screen.
Save the code in Example 9-9 as index.php.
Example 9-9. An HTML RSS reader formatted for PSP
Use Google's Web Services API and a Flikr-style link graph to search Google.
Google is a great search engine, but sometimes I find myself looking at the page snippets more than I do the pages themselves. This hack takes the snippets and looks for repeating words around the search term. It's a fascinating way to get more insight into a search phrase.
Save the code in Example 9-10 as index.php.
Example 9-10. A DHTML link graph that uses Google as a data source
<?php require_once("Services/Google.php"); $ignore = array( 'the','for','and','with','the','new','are','but','its','that','was', 'your', 'yours', 'also', 'all', 'use', 'could', 'would', 'should', 'when', 'they', 'far', 'one', 'two', 'three', 'you', 'most', 'how', 'these', 'there', 'now', 'our', 'from', 'only', 'here', 'will' ); $ignorehash = array(); foreach( $ignore as $word ) { $ignorehash[ $word ] = 1; } $term = "Code Generation"; if( array_key_exists( 'term', $_GET ) ) $term = $_GET['term']; $key = "GOOGLE_KEYUse Amazon.com's Web Services API to create a new search mechanism for books.
Amazon.com was one of the first major dotcom companies to embrace web services fully. Its API is both extensive and easy to use. For this hack, I'll use just the book search portion of the API to create a search mechanism that shows the results from two searches simultaneously in two columns. Any books that show up in both searches are placed at the top of the page across the two columns. Theoretically, this should show you the highly ranked books that cover both of the topics you are interested in.
Save the code in Example 9-11 as index.php.
Example 9-11. An HTML interface that compares two Amazon book searches
<?php require_once 'PEAR.php'; require_once 'Services/Amazon.php'; $devtoken = "XXXXXXX"; $userid = "USERID"; $amazon = &Use PHP to send SMS messages to a cell phone from Jabber messages from your instant messenger client.
One of the most useful features of instant messaging is the availability of user presence (knowing whether a user is online). Of course, if a user is unavailable and you really want to get a message to him, it would probably be really cool if you could send the message to his mobile phone.
While many of the latest mobile phones are beginning to support instant messaging, nearly all of them support SMS text messaging. It would be great to send an instant message to a user's cell phone, and this hack shows how to do just that, by using an email-to-SMS gateway (something almost all major phone providers already have in place).
Save the code in Example 9-12 as smsclient.php.
Example 9-12. An SMS client in PHP
<?php /* CONFIG VARIABLES */ // jabber server you are registed at // jabber server you are registed at $SERVER = 'yourserver'; //username and password for your special account $USERNAME = 'yourusername'; $PASSWORD = 'yourpassword'; // jabber id for your personal account $PERSONAL = 'username@yourserverUse Ming to create dynamic Flash movies from PHP.
Have you ever wanted to make a web graphic have a little more pizzazz or zing? We all have at some point. One way to do this is to use the Macromedia Flash format (also known as the SWF format). But how do you do that with just open source tools? Well, there is a PHP module called Ming that saves the day (ironic, isn't it? Ming saves Flash?). It allows you to generate full-blown Flash files on the fly. This hack will show you how to pull that off with a Flash application that dynamically generates charts.
Save the code in Example 9-13 as data.php.
Example 9-13. Some XML to be rendered by Flash
<?php header("Expires: Mon, 26 Jul 1997 05:00:00 GMT"); // Date in the past header("Last-Modified: " . gmdate("D, d M Y H:i:s") . " GMT"); // always modified header("Cache-Control: no-store, no-cache, must-revalidate"); // HTTP/1.1 header("Cache-Control: post-check=0, pre-check=0", false); header("Pragma: no-cache"); // HTTP/1.0 header('Content-type: application/xml'); echo("<?xml version=\"1.0\" ?>\n"); ?> <GRAPH TYPE="BAR"> <TITLE>Revenues 2005</TITLE> <YAXIS>Dollars <RANGE MIN="0" MAX="50000" /> </YAXIS> <XAXIS>Period </XAXIS> <DATA> <?php $colors = array( "0xFF0000", "0xFFFF00", "0xFF00FF", "0x00FFFF", "0x00FF00" ); srand((double)microtime()*1000000); for ($i = 1; $i < 7; $i++) { $clr = $colors[ ($i - 1) % count($colors) ] ; $val = rand(10000,45000); echo("<D$i>$val<COLOR C=\"$clr\" /></D$i>\n"); } /* <D1>20000<COLOR C="0xFF0000" /></D1> <D2>25000<COLOR C="0xFFFF00" /></D2> <D3>27000<COLOR C="0xFF00FF" /></D3> <D4>42000<COLOR C="0x00FFFF" /></D4> <D5>48000<COLOR C="0x00FF00" /></D5> */ ?> </DATA> </GRAPH>graph.php, shown in Example 9-14, is the actual PHP script that does the work (with lots of help from Ming).
Example 9-14. Ming and PHP to the rescue
PHP coding isn't always about writing accounting applications. This chapter contains hacks that cover the fun side of PHP. From creating your own Google maps, to building an MP3 server, to uploading Wikipedia to your PlayStation Portable (PSP), this chapter's all about the frivolous side of PHP.
Use the Google Maps API to embed dynamic maps into your application with custom markup, overlays, and interactivity.
In what little spare time I have, I love to hike around my neighborhood in Fremont, California. Thankfully, some of the best hiking in the Bay Area is just a walk away. In particular, the hike up Mission Peak is tremendous, both for its scenic vista and for the great workout.
To illustrate my hikes up Mission Peak, I've always used a wiki page with a bunch of images. But I always wanted something more interactive—and with the advent of Google Maps and its extensible API, I was able to use a combination of PHP and JavaScript to detail my hike up the mountain, using satellite imagery and interactive markers (how's that for technology and nature converging!).
Start by saving the code in Example 10-1 as index.php.
Example 10-1. Setting up latitude and longitude for Google mapping tasks
<?php $images = array( array( 'lat' => -121.9033, 'lon' => 37.5029, 'img' => "mp0.jpg" ), array( 'lat' => -121.8949, 'lon' => 37.5050, 'img' => "mp1.jpg" ), array( 'lat' => -121.8889, 'lon' => 37.5060, 'img' => "mp2.jpg" ), array( 'lat' => -121.8855, 'lon' => 37.5076, 'img' => "mp3.jpg" ), array( 'lat' => -121.8835, 'lon' => 37.5115, 'img' => "mp4.jpg" ), array( 'lat' => -121.8805, 'lon' => 37.5120, 'img' => "mp5.jpg" ) ); ?> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/ xhtml1/DTD/xhtml1-strict.dtd"> <html xmlns="http://www.w3.org/1999/xhtml"> <head> <title>Simple Google Maps Page</title> <script src="http://maps.google.com/maps?file=api&v=1&key=<mapskey>Use the XML Simple Playlist Format (XSPF) to create playlists from PHP.
Creating an MP3-playing application for you and your friends is easy with PHP. A new standard for playlists called XSPF (http://www.xspf.org/) is available and can be used by a Flash MP3 player movie called Music Player (http://musicplayer.sf.net). And, of course, PHP will work with all of this.
Figure 10-6 illustrates how the Flash movie requests the XSPF playlist XML from the playlist.php script.
Figure 10-6. The Flash movie requesting the playlist from the PHP script
Save the code in Example 10-2 as index.html.
Example 10-2. Beginning the process of setting up an MP3 player
Allow users to upload and download media files from your application.
Sometimes the customers of your site will want to trade more than just text. They will want to trade media files, images, and who knows what else. The legality of such things aside, this hack will walk you through building a simple media upload/download center for your site.
Figure 10-9 shows the page flow of the upload/download center. The user starts at index.php, where he uploads a file through upload.php. The upload.php script forwards himto the dir.php page, which then shows the files that are available as downloads. Clicking on any of the files will download them through the download.php script.
Figure 10-9. The page flow of the media upload/download center
Save the code in Example 10-4 as media.sql. It's a simple little SQL statement to create a new table.
Example 10-4. Creating the media table for file uploads
DROP TABLE IF EXISTS media; CREATE TABLE media ( id MEDIUMINT NOT NULL AUTO_INCREMENT, filename TEXT, mime_type TEXT, PRIMARY KEY( id ) );Save the HTML in Example 10-5 as index.php.
Example 10-5. A form allowing for an upload
<html> <body> <form enctype="multipart/form-data" action="upload.php" method="post"> <input type="hidden" name="MAX_FILE_SIZE" value="2000000" /> <input type="file" name="file" /> <input type="submit" value="Upload" /> </form> </body> </html>Use the Net_GameServerQuery PEAR module to check up on your network game, all using PHP.
There is a PEAR module [Hack #2] for almost anything, it seems! Proving that to be true, this hack uses the Net_GameServerQuery module to check up on a Half Life server, just to see how many people are playing (and to show off PEAR and yet another cool module).
Save the code in Example 10-9 as index.php.
Example 10-9. Checking the Half Life server as an automated task
<?php require( 'Net/GameServerQuery.php' ); $protocol = 'halflife'; $ip = '66.159.222.15'; $gsq = newUse MySQL and PHP to build a dictionary from Wikipedia that fits in your hip pocket.
Wikipedia (http://www.wikipedia.org) is probably the single most informative site on the Internet. It's a user-contributed encyclopedia and dictionary. What's even better is that you can download the entire contents of Wikipedia and use it for your own purposes.
In my case, I wanted the Wikipedia dictionary on my PSP. Being a PHP hacker, of course I had to use PHP and MySQL; I created a set of static pages from Wikipedia and then downloaded those pages to my PSP memory stick. It's not dynamic, but it still impresses my buddies when I can look up grok on my PSP.
Figure 10-15 shows the basic flow of the processing in this hack. The Wiktionary contents are loaded into the MySQL database [Hack #1] . An elaborate dict.php script takes the contents of the database and creates a set of specially formatted HTML pages appropriate to the PSP.
Figure 10-15. The processing flow of the PSP dictionary creator
Save the code in Example 10-10 as dict.php.
Example 10-10. Downloading the current Wikipedia to create static HTML
<?php require_once( "DB.php" ); require_once( "Text/Wiki.php" ); $g_wiki = new Text_Wiki(); $g_wiki->enableRule('html'); $g_wiki->enableRule('list');function wikiToHTML( $text ) { global $g_wiki; $text = preg_replace( "/\=\=\=\s* Pronunciation.*?\n\=\=\=/is", "\n===", $text ); $text = preg_replace( "/\=\=\=\=\=\s*(.*?)\s*\=\=\=\=\=/", "\n+++++ ", $text ); $text = preg_replace( "/\=\=\=\=\s*(.*?)\s*\=\=\=\=/", "++++ ", $text ); $text = preg_replace( "/\=\=\=\s*(.*?)\s*\=\=\=/", "+++ ", $text ); $text = preg_replace( "/\=\=\s*(.*?)\s*\=\=/", "++ ", $text ); $text = preg_replace( "/\=\s*(.*?)\s*\=/", "++ ", $text ); $text = preg_replace( "/\[\[image:.*?\]\]/i", "", $text ); $text = preg_replace( "/\[\[it:.*?\]\]/i", "", $text ); $text = preg_replace( "/\[\[.*?\|(.*?)\]\]/", "", $text ); $text = preg_replace( "/\[\[(.*?)\]\]/", "", $text ); $text = preg_replace( "/\[(.*?)\]/", "", $text ); $text = preg_replace( "/\n\#([^#])/", "\n# ", $text ); $text = preg_replace( "/\n\*([^*])/", "\n* ", $text ); $text = preg_replace( "/\<\!\-\-.*?\-\-\>/mi", "", $text ); $text = preg_replace( "/\n\|.*?\|\s*\n/", "", $text ); $text = preg_replace( "/\n\{\|.*\n/", "", $text ); $text = preg_replace( "/\n\|\}.*\n/", "", $text ); $text = preg_replace( "/\n\|\}\n/", "", $text ); $text = preg_replace( "/\{\{.*?\}\}/", "", $text ); $text = preg_replace( "/\|\}/", "", $text ); $text = preg_replace( "/\|.*?\|/", "", $text ); $text = preg_replace( "/\'\'\'\'\'\'/", "'''\n'''", $text );Use Weather.com's web service to build a weather showdown page that will help you decide where you want to be this week (and what to pack).
The Services_Weather PEAR module [Hack #2] makes it easy to integrate weather forecast information into your web application. This hack uses the information from the Weather.comweb service through the Services_ Weather PEAR module to find the weekly temperature range in two different cities. It then compares those temperatures against a desired value and tells you which city is closer to your desired climate.
Save the code in Example 10-11 as index.php.
Example 10-11. PHP checking the weather forecast
<?php require_once( "Services/Weather.php" ); $weather = newOur look is the result of reader comments, our own experimentation, and feedback from distribution channels. Distinctive covers complement our distinctive approach to technical topics, breathing personality and life into potentially dry subjects.
The image on the cover of PHP Hacks