php|architect (August 2005)

This copy is registered to: Rodney Burruss [email protected] 08.2005 DEPARTMENTS FEATURES 6 EDITORIAL New New Med...

19 downloads 6194 Views 4MB Size Report

This content was uploaded by our users and we assume good faith they have the permission to share this book. If you own the copyright to this book and it is wrongfully on our website, we offer a simple DMCA procedure to remove your content from our site. Start by pressing the button below!

Report copyright / DMCA form

’ + ‘
’ + ‘
’ + ‘
’; this.element = document.getElementById(id); this.element.innerHTML = barHTML; this.element.style.height = this.element.parentNode.clientHeight +”px”; var divs = this.element.getElementsByTagName(‘div’); for(var i = 0; i < divs.length; i++) { this[divs[i].className] = divs[i]; this[divs[i].className].worker = this; } this.buttonUp.onclick = function() { this.worker.onScrollUp(); } this.buttonDown.onclick = function() { this.worker.onScrollDown(); } this.gripper.onclick = function() { this.worker.onGripper(); } } ScrollBar.prototype.setHeight = function(height) { this.element.style.height = height; } ScrollBar.prototype.onScrollUp = function() { alert(‘scroll up’); } ScrollBar.prototype.onScrollDown = function() { alert(‘scroll down’); } ScrollBar.prototype.onGripper = function() { alert(‘clicked Gripper’); } ?>

August 2005

FEATURE



PHP Architect



www.phparch.com

A Scrolling Table Moving beyond our basic echo example, let’s walk through an AJAX solution to browsing a large set of data. This is a pretty standard problem for most web sites. In this case, we have a MySQL table with 6000 rows of data about nails. The included source has a data generation script; you can use that to follow along. If we were to build a table in a normal web app, we’d add a pager to move through the data. This works in many cases, but it’s not so nice if other content on the page makes each reload take a second or two. Using AJAX, you can just ask the server for the data you need, and can ignore everything else on the page. Done well, this can be a useful alternative to standard paging. To build a scrolling table, we need to take care of 5 main items: • Create a class that talks to the database • Add and remove data from a table in JavaScript • Create a scrollbar • Load into the table using AJAX • Tie the pieces together Database Interface This is pretty standard PHP code: connect to the database, query it, and return the results. Not much different from other pages on your site. There are, however, a some things to keep in mind. This query is going to be run more often than normal, so if it’s slow, you might need to add a session cache. You need to provide an easy to use API that you’ll call from JavaScript. A stripped down example class that does this is shown in Listing 5. Note the simple API: a method to get the total number of rows, and another to get chunks of row data. A good tip when returning data is to use arrays of Objects instead of deep multi-dimensional arrays. This structure is how the data will be represented in the JavaScript, so you’re in direct control over the translation. I’ve also found it to be a good way of wrapping my head around the process, since if I write test code in PHP for the method, its output will look as close as possible to what I see in JavaScript. JPSpan Proxy Class Our database interface is pretty simple in this example. It has a connect method you wouldn’t want to export to JavaScript, though. The easiest way to control this is to create a wrapper class that is used only when exporting to JavaScript. This can be useful if you want to clean up data before sending it to the client, or perhaps to combine one or two methods from different classes into one API. When writing these classes, I use lowercase method names, your PHP methods are always

19

An Introduction to AJAX and JPSpan

exported to JavaScript in lowercase, so it’s one place that makes sense even if the rest of your classes are “camelCase.” This really improves the usability (from a programming standpoint), since this class is your reference to what you’ll see in your JavaScript proxies. The proxy class for our scrolling table can be seen in Listing 6. Dynamically Updating a Table Before we can make our table scroll, we need to get basics working. A good place to start is with some JavaScript that will add a new row to an existing table. var table = document.getElementById(‘grow’); var newRow = document.createElement(‘tr’); table.appendChild(newRow); var count = table.getElementsByTagName(‘tr’).length; newRow.innerHTML = ‘’+count+’’+count+’’;

To do this we use the JavaScript DOM. It’s a pretty simple process, where we first grab the table were appending to, by using getElementById(), then we create a new row, using createElement() with tr as its input. We finish the row creation process by appending it to the table. Now that we have a new table row, it’s just a matter of giving it a value—the innerHTML property is great for this. You just set it to the HTML you would normally see within a tr tag. If this code seems unfamiliar to you, now is a good time to brush up a bit on the JavaScript DOM. It’s simple and powerful, and you’re not going to be doing much AJAX without it. Adding a Scroll Bar Before our table is done, we’re going to have to remove rows from our table. We’ll also have to add data to it, but before we get to that, let’s build the scroll bar that will be driving those actions. The choice of a scroll bar is an easy one for an element like this—it’s a UI element that users expect. It’s always a good idea to use a standard UI element rather then invent something on your own, since meeting your user’s expectations is an important part of creating something that’s usable. The scroll bar is a slightly frustrating case. The browser is full of them, but since there is no JavaScript API (or HTML markup) for scroll bars, you’re forced to build your own. The job of building something that looks like a scroll bar is pretty simple. We need to grab images for each of the elements, and apply them to divs using CSS. This approach has the added benefit of making your scroll bar easily themeable. So, if you want to make its color scheme match your site, you could. The CSS, HTML, and images for the scrollbar are included in the source archive. To make the scroll bar usable, we need to attach JavaScript actions to it. To use the widget, you

August 2005



PHP Architect



www.phparch.com

FEATURE just create a container div, and then create a new instance with the container’s id as its input. The code for connecting a scrollbar to a table is shown in Listing 7. You’ll notice that there is an extra wrapper table that is used to attach the scrollbar, and while this is ugly HTML, it is the easiest solution to get things to lineup correctly. The JavaScript class that implements the scrollbar is shown in Listing 8. It does a couple of things in its constructor that make it’s use simple: it sets its height to that of its parent container using the clientHeight attribute, and it adds the HTML for the bar, so you don’t have to copy and paste it each time you use it.

“AJAX creates a lot of opportunities for closing the gap between what could happen in the old world of fat clients, and the newer era of rich web applications.” When you use the scroll bar you need to override the onScrollUp and onScrollDown methods, you could also override the onGripper method to allow for drag and drop scrolling, but we’re not going to cover that in this article. Connecting the Scroll Bar Now that we have a table that can grow and a scrollbar with scroll-up and-down events, we can connect them and have a basic scrolling table. To keep things simple, we will create a JavaScript array with data to scroll through. Once that’s working, we’ll add in JPSpan. This code builds on the last listing, so just the new JavaScript is shown in Listing 9. First, we create our test data and then populate 5 rows with it. After this, we create a new ScrollBar and override its event methods to do the actual scrolling on the table. Since JavaScript is a fully dynamic language, you can override class methods at runtime. You just use the function() {} construct, and assign the results to the member you’re overriding. The actual scrolling happens in onScrollDown; it checks if there is more data to scroll to, returning without action, if there isn’t. If there is data, this.currentRow is incremented, and onScrollDown creates a new row, using data from the tableData array. Unlike our earlier “growing table” example, the td elements are adding using 20

FEATURE

An Introduction to AJAX and JPSpan

createElement, since it can be used in a loop where we don’t know the number of columns beforehand. The process is finished by appending our new row to the end of the table and removing one from the top. To finish up our scrolling table, you would also override onScrollUp, whose code would be the same as onScrollDown , except it subtracts from this.currentRow, adds a row at the top, and removes one from the bottom like this: table.insertBefore(row, table.getElementsByTagName(‘tr’).item(0)); table.removeChild(table.getElementsByTagName(‘tr’).it em( this.scrollSize));

Loading in Data Now that we have a working scrolling table, it’s time to update it to use real data. First, we setup a JPSpan server that exports the nail_remote class that we built earlier. This server looks just like the earlier ones, except we

Listing 9 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55

tableData.length) { return; // no data to scroll too bail } this.currentRow += 1; // add our new row var index = this.currentRow + this.scrollSize -1; var row = document.createElement(‘tr’); for(var key in tableData[index]) { var cell = document.createElement(‘td’); cell.appendChild(document.createTextNode( tableData[index][key])); row.appendChild(cell); } table.appendChild(row); // remove the first row table.removeChild( table.getElementsByTagName(‘tr’).item(0)); } ?>

August 2005



PHP Architect



www.phparch.com

set up the object before registering it. $remote = new nail_remote(); $remote->dataObject = new NailData(); $S->addHandler($remote)

Our HTML page builds on the earlier steps as well; in the page header, we include our scrollbar widget class and the JPSpan generated client. Then, we set up two functions that talk to the server, getDataSync(start,rows) and getNumRowsSync(). As you can guess from the names, we’re not doing asynchronous JavaScript calls, as performing synchronous requests has a couple advantages in this case: the functions are simpler because no callbacks are required, and they meet our use case better, since we don’t want to move on until we have those pieces of data. The remote data functions are shown below. The Sync() call on our proxy objects takes it out of the default async operation. var remote = new nail_remote(); remote.Sync(); function getDataSync(start,rows) { return remote.data(start,rows); } function getNumRowsSync() { return remote.num_rows(); }

After the HTML is added to the page, we do a getDataSync call, and populate the table. You could actually do this in the PHP code that generates your HTML page, and omit the extra AJAX call, but for now, we’re keeping the extra complexity to a minimum. The rest of the table and scrollbar setup procedure is also the same, until we get to the event functions. The one exception is that we use getNumRowsSync() to get the max rows in our table. onScrollDown() now does a getDataSync() call if the index that it’s looking for in tableData isn’t set. if (!tableData[index]) { var d = getDataSync(index,5); for(var i in d) { tableData[i] = d[i]; } }

onScrollUp() does a similar check, but also subtracts 5 from index since it wants higher rows. Of course, that check won’t ever take place in this particular page, since there is no place to start but the first row, and you can’t scroll up unless you’ve already scrolled down. We now have a basic working scrolling table, but it is a little slow since we get a fetch delay every 5 rows. In some cases, just adding a loading icon while this is happening might be enough, or fetching more rows at once, but generally you’ll want to preload data. Preloading data This type of data preloading is pretty easy, since we don’t need to develop a complicated algorithm. We

21

FEATURE

An Introduction to AJAX and JPSpan

can assume that if the user starts scrolling down, they will want to continue scrolling down. We don’t want to waste bandwidth so we’re not going to preload all 6000 rows, but staying 5 rows in front of the direction we’re scrolling is a good start. If you put a scrolling table into production, you’ll want to gather data on peoples’ usage patterns. This might tell you that people generally scroll 20 rows down so you should make that your default preload. Adding data preloading to our last example requires updating onScrollDown and onScrollUp, as well as adding an async variant to our getData() function. The getData() function uses an object pool to get its proxy object and then does a get_data() call on it. Its callback loads the result onto the tableData object. tableData is an object now that we’re using it as a hash. We’re doing this to limit our JavaScript memory usage. This didn’t matter before, since we were always starting at row 0, but this final page adds a GET variable to start at any row. If we start at row 4000 and we’re using an array, the JavaScript engine will have to fill in those missing entries. The updated logic for onScrollUp and onScrollDown is a call to the matching prefetchAsNeeded when they have the data they currently need. Otherwise, they do the same sync call so that they don’t have to render a row without data. The prefetchAsNeededUp or prefetchAsNeededDown functions look at all the rows ahead in the pre-fetch interval and do an async getData() call if any of them are empty. The final code of the HTML file which contains all the JavaScript that drives the scrolling table is shown in Listing 10. The Finished Scrolling Table So, now we have a scrolling table. Its final features are: scrolling based on clicks on a scrollbar, preloading data, and on-demand loading of data. It’s a pretty good table that can be expanded to any number of columns just by changing its data source, but it does have a couple of areas were it could use some improvements. First, there should be timers on the onScroll actions, so that clicking and holding will scroll through the data, you might also want to add some acceleration to this. When getDataSync has been called, and we’re waiting for its response, it would be a good idea to show some sort of indication that it’s loading, since the row can’t be added until we get data back. The functions for loading data could also be encapsulated into a JavaScript class so that it’s easier to add to any page. You would also want to attach the gripper bar in the center of the scroll bar, so you could quickly scroll through the data. Debugging Tips As you’re experimenting with AJAX, you’ll find one of

August 2005



PHP Architect



www.phparch.com

Listing 10 1 2 3 4 Scroll Bar 5 6 <script type=”text/javascript” src=”scrollbar.js”> 7 8 9 <script type=’text/javascript’ src=’server.php?client’> 10 <script type=’text/javascript’ src=’objectPool.js’> 11 <script type=’text/javascript’> 12 var remote = new nail_remote(); 13 remote.Sync(); 14 // our get data function to call in onScroll methods 15 function getDataSync(start,rows) { 16 ret = remote.data(start,rows); 17 document.getElementById(‘trace’).innerHTML += “
  • Sync Fetch Rows: “ + start + “ - “+rows+”
  • ”; 18 return ret; 19 } 20 21 function getNumRowsSync() { 22 ret = remote.num_rows(); 23 return ret; 24 } 25 26 // our get data function to call in onScroll Up methods 27 function getData(start,rows) { 28 var r = remotePool.getObject(); 29 30 document.getElementById(‘trace’).innerHTML += “
  • Async Call data(“+start+”,”+rows+”)
  • ”; 31 r.data(start,rows); 32 } 33 // callback class 34 35 function NailCallback() {} 36 NailCallback.prototype.data = function(result) { 37 var start = false; 38 var rows = 0; 39 for(var key in result) { 40 if (!start) { 41 start = key; 42 } 43 if (!tableData[key]) { 44 tableData[key] = result[key]; 45 rows++; 46 } 47 } 48 document.getElementById(‘trace’).innerHTML += “
  • ASync Fetch Rows: “ + start + “ - “+rows+”
  • ”; 49 remotePool.returnObject(this.parent.poolId); 50 } 51 52 // jpspan remote proxy class pool 53 var remotePool = new objectPool(); 54 remotePool.createObject = function() { 55 var callback = new NailCallback(); 56 var remote = new nail_remote(callback); 57 callback.parent = remote; 58 return remote; 59 } 60 61 var lastPrefetch = false; 62 63 // decide if we should try to prefetch some data 64 function prefetchDownAsNeeded(currentIndex) { 65 var testUntil = currentIndex + 5; 66 67 var startPrefetch = false; 68 for(var i = currentIndex; i < testUntil; i++) { 69 if (!tableData[i]) { 70 startPrefetch = i; 71 break; 72 } 73 } 74 75 if (startPrefetch && startPrefetch != lastPrefetch) { 76 lastPrefetch = startPrefetch; 77 getData(startPrefetch,10); 78 } 79 } 80 81 function prefetchUpAsNeeded(currentIndex) { 82 var testUntil = currentIndex - 5; 83 84 var startPrefetch = false; 85 for(var i = currentIndex; i > testUntil; i—) {

    22

    FEATURE

    An Introduction to AJAX and JPSpan

    Listing 10 (cont’d)

    Listing 10 (cont’d)

    86 if (!tableData[i]) { 87 startPrefetch = i-10; 88 break; 89 } 90 } 91 if (startPrefetch < 0) { 92 93 startPrefetch = 0; 94 } 95 96 if (startPrefetch && startPrefetch != lastPrefetch) { 97 lastPrefetch = startPrefetch; 98 getData(startPrefetch,10); 99 } 100 } 101 102 103 104 105 106 107 108 109 121 124 125
    110 111 112 113 114 115 116 117 118 119
    Nail IdLengthDiameter
    120
    122
    123
    126
      127
    128 <script type=”text/javascript”> 129 // load in some starting data 139 140 var tableData = new Object(); 141 var d = getDataSync(start,5); 142 143 for(var i = start; i < (start+5); i++) { 144 tableData[i] = d[i]; 145 } 146 147 // render 5 rows of data 148 var table = document.getElementById(‘table’); 149 for(var i = start; i < (start+5); i++) { 150 var row = document.createElement(‘tr’); 151 for(var key in tableData[i]) { 152 var cell = document.createElement(‘td’); 153 cell.appendChild(document.createTextNode(tableData[i][key])); 154 row.appendChild(cell); 155 } 156 table.appendChild(row); 157 } 158 // add our scroll bar 159 160 var bar = new ScrollBar(‘bar’); 161 bar.currentRow = 0; 162 bar.scrollSize = 5; 163 bar.numRows = getNumRowsSync(); 164 bar.currentRow = start; 165 // override the scrollbars onScrollDown method 166 167 // use currentRow and scrollSize to see if we have more data to scroll 168 // Scrolling means remove a row from the top add one to the bottom 169 bar.onScrollDown = function() { 170 // check if we should scroll 171 if ( (this.currentRow + 1 + this.scrollSize) > this.numRows) {

    August 2005



    PHP Architect



    www.phparch.com

    172 return; // no data to scroll too bail 173 } 174 this.currentRow += 1; 175 176 177 // add our new row 178 var index = this.currentRow + this.scrollSize -1; 179 if (!tableData[index]) { 180 181 var d = getDataSync(index,5); 182 for(var i in d) { 183 184 tableData[i] = d[i]; 185 } 186 } 187 else { 188 prefetchDownAsNeeded(index); 189 } 190 var row = document.createElement(‘tr’); 191 for(var key in tableData[index]) { 192 var cell = document.createElement(‘td’); 193 cell.appendChild(document.createTextNode(tableData[index][key])); 194 row.appendChild(cell); 195 } 196 table.appendChild(row); 197 198 // remove the first row 199 table.removeChild(table.getElementsByTagName(‘tr’).item(0)); 200 } 201 202 // override the scrollbars onScrollUp method 203 // same as onScrollDown only in the other direction 204 bar.onScrollUp = function() { 205 // check if we should scroll 206 if ( this.currentRow == 0) { 207 return; // no data to scroll too bail 208 } 209 210 this.currentRow -= 1; 211 212 // add our new row 213 var index = this.currentRow; 214 215 // to hit this get you need to pass in a start value ?start=100 and then scroll up 216 if (!tableData[index]) { 217 var start = index - 4; 218 var d = getDataSync(start,5); 219 220 for(var i in d) { 221 tableData[i] = d[i]; 222 } 223 } 224 else { 225 prefetchUpAsNeeded(index); 226 } 227 228 var row = document.createElement(‘tr’); 229 for(var key in tableData[index]) { 230 var cell = document.createElement(‘td’); 231 cell.appendChild(document.createTextNode(tableData[index][key])); 232 row.appendChild(cell); 233 } 234 235 table.insertBefore(row,table.getElementsByTagName(‘tr’).item(0)); 236 237 // remove the last row 238 table.removeChild(table.getElementsByTagName(‘tr’).item(this.scroll Size)); 239 } 240 241 242 243 244 245

    23

    FEATURE

    An Introduction to AJAX and JPSpan

    the biggest changes is your debugging approach. Instead of only having PHP code to worry about, you now you have JavaScript and the AJAX communication between the two. These problems aren’t insurmountable, though. First, always test each piece separately. When you’re working in JavaScript make sure you create a debugging function. The easiest thing to do is create your own print_r() equivalent, like this: function print_r(input) { var ret; for(var i in input) { ret += “[“+i+”] = “+input[i]+”\n”; } alert(ret); }

    JPSpan also offers logging capabilities through its observer functionality; you can use this to log errors (and success) of your JPSpan calls. The default server setup also passes PHP errors to your browser as JavaScript alerts. You might also notice that you’re seeing alerts from JavaScript errors. This happens because JPSpan is catching them and creating alerts as well. Finally, I would recommend developing on Firefox and then testing in IE. Firefox’s built-in development tools are much better than anything available for IE, plus it offers tons of great extensions.

    Adding AJAX to your Site AJAX offers lots of new capabilities, and as long as you keep usability at the center of its use, you’ll see good results. Your goal shouldn’t be to implement a new technology, it should be to use the technology to create a better user experience for your web applications. I like to think of it in terms of metrics: after adding AJAX, user-registration takes 30 seconds and 1 page load, compared to 2 minutes and 6 loads, before. As long as my metrics get better when I use AJAX, I know I’m improving the user experience of my application. If they don’t, then I know I’m just messing around with some new technology. AJAX offers great new capabilities, but like anything else, it’s only useful when used wisely. About the Author

    ?>

    Joshua Eichorn has been creating websites with PHP for seven years. He is the creator of phpDocumentor, an award winning and extremely popular documentation tool for PHP. He has a Bachelor of Science in Computer Information Systems from Arizona State University. He is currently the Senior Architect for Uversa Inc., creating unique solutions for customers, including adding AJAX to Uversa applications before the term was coined. He is also a prolific blogger and PHP community member covering new developments in the PHP and AJAX worlds. He currently lives in Phoenix, Arizona.

    To Discuss this article: http://forums.phparch.com/241

    Available Right At Your Desk All our classes take place entirely through the Internet and feature a real, live instructor that interacts with each student through voice or real-time messaging.

    What You Get Your Own Web Sandbox Our No-hassle Refund Policy Smaller Classes = Better Learning

    Curriculum The training program closely follows the certification guide— as it was built by some of its very same authors.

    Sign-up and Save! For a limited time, you can get over $300 US in savings just by signing up for our training program! New classes start every three weeks!

    http://www.phparch.com/cert

    August 2005



    PHP Architect



    www.phparch.com

    24

    FEATURE

    PHP at Home by Ron Goff

    Combing home automation and PHP can give you amazing everyday control over your home and its electronic appliances. Intro The car slowly made its way down the dark dirt road, the moon was nowhere in sight. Nothing but black was in the eyes of the three passengers of the automobile. Suddenly the car stopped in front of an object, a house, barely visible from the headlights of the car piercing the dust in the air. “Well this is it,” the driver calmly said. “My house”. “It’s kind of dark out here don’t you think.” Said a nervous voice from the back seat. “Ah, let me hit the lights.” The driver said as he grabbed his small metallic looking cell phone. “Yeah, you’re going to need that if you’re going out there. We’re in the middle of nowhere and who knows if someone’s waiting to get you from behind a tree or something.” The backseat passenger said with much apprehension in his voice. The driver calmly opened his cell phone, and sent a text message, and said “One minute”.

    August 2005



    PHP Architect



    www.phparch.com

    A short time later lights started to slowly brightened on to reveal the beautiful house in the middle of the dark air and everyone in the car enjoyed a safe, relaxed walk into the home. This is what home automation can do, and not only just home automation, but a home powered by PHP. Home automation can do many things freeing time, making you feel more secure and if nothing else just being really fun to program and impress others. When I started tinkering around with the idea of using PHP to control my house, I really wanted to take the programming language that I use everyday at work and control something other than web applications

    REQUIREMENTS PHP

    4.2+

    OS

    RedHat 9.0

    Other Software

    Flipit

    Code Directory

    phphome

    25

    FEATURE

    PHP at Home

    with it. In fact one of my main inspirations to start my experiments came from dinner. I was sitting at a well known chain restaurant—I’m not going to mention names—in the evening ready to enjoy a steak dinner with my wife. All of sudden the lights began to dim to a very romantic level. Now at first, I thought there was a power outage of some sort, but I then realized all the lights in the restaurant were setting the mood, I was impressed. I soon started to research how this same effect could be achieved in my own home, of course utilizing PHP. The system in the story or the system in the restaurant is not anything out of the realm of what can be done with a few home automation devices and PHP. In fact later in this article we will see how to build this exact system using a RedHat or another flavor of Linux box, your choice, and a couple X10 devices. So if your up for a challenge and want PHP to control more aspects of your life, I know I do, let’s get into controlling your home with PHP. What you Need to Start To get started you will have to gather up some equipment. First you will need a computer running Linux, your choice, I run RedHat 9.0 on my personal home setup. Next you will need to order what’s called a Firecracker (cm17a) produced by X10. This is a little device that fits into the serial port on you computer allowing it to send signals to other home automation devices using the X10 standard. You will also need to pick up a couple lamp modules, which you can purchase from X10 or even electronic stores like RadioShack. The best and least expensive way is to buy a package with all theses pieces in it. I’ll give you more details about this package later on in the article. Next you’ll need to download the latest version of Flipit. This program allows you to send the signals out of the Firecracker(cm17a) from the command prompt. Once you’ve gathered all these essential items, you’re on your way to automating your house with PHP. FireCracker(cm17a) The FireCracker as described on the x10.com website, “Is no bigger than a fig newton but is lighter and more powerful.” This little device as I described earlier allows you to send commands to other X10 modules to turn on and off lights, dim or brighten and even control other appliances other than lights, like a coffee pot or sprinkler system. Finding the FireCracker was very easy enough, although I couldn’t find it in an actual store. There were hundreds of places that I found online. It actually comes bundled with other pieces, that you will need, called the “FireCracker Home Control Kit” from X10. This kit includes the FireCracker (cm17a), a lamp modAugust 2005



    PHP Architect



    www.phparch.com

    ule, wireless receiver to receive the signals from the FireCracker and a remote control—remote control we don’t need no stinkin’ remote control we have PHP. Prices range from $40 to $17, $17 is the price I paid on Ebay, brand new and never removed from the box I might add. How does the X10 System Work The X10 system works by sending signals throughout the actual power lines of the house. The modules that are setup do not have to receive signals from the FireCracker directly. Basically a command is given though the FireCracker or a remote control. Then the wireless receiver, that usually can control a light as well, picks up the signal and sends the signal throughout the rest of the house via the power lines. Once the signal is received at the appropriate module, let’s say it’s controlling a light and the signal is to turn it on, the lamp will automatically turn on and the transmission is complete. X10 itself, which I didn’t know before I started working on this project, is considered to be the founder of home automation and the company itself was started way back 1978. I had know idea that home automation had been around that long. Installing Flipit Flipit is a great program written by Matt Armstrong that allows you to control the FireCracker via the command line. The software that X10 provides will not work with Linux, that is why we have to use programs like Flipit. With Flipit you will be able to turn on and off an electrical device or even dim or brighten a lamp, even if it wasn’t meant to be dimmed. There are other programs that work similar to Flipit such as BottleRocket, but I liked how the program Flipit worked and its ease of use. The first step to installing Flipit is downloading it from http://www.lickey.com/flipit/, the latest version as of this article is 0.3.6. I first downloaded 0.3.5 and found that it would not brighten the lamps correctly on my RedHat 9 box but as soon as I downloaded and installed 0.3.6 everything worked fine. Next you will have to untar the package. Then the fun begins. It’s really not too difficult and the “readme” and “install” documentation walks you through the whole process, but there are several steps to installing it on to your system. This is what I did for my system. At the command prompt, and in the directory where the package was untarred I typed “./configure”. After that was completed I typed “make”. Then “make install”. That was it. After you finish getting Flipit installed you’re ready to test communication to the FireCracker that you will 27

    FEATURE

    PHP at Home

    place into a free serial port. The FireCracker is designed to not take up the use of the serial port, it’s built so that you can plug in another serial device. This can interfere with a modem placed on the FireCracker. You will also need to plug in the wireless receiver and lamp module into power outlets, make sure you plug a lamp into the lamp module, otherwise you won’t be able to tell if it’s working. This may be obvious but you never know. You will also need to give these modules specific id’s. When you first take them out of the box they are set to the exact same id and if used this way, it will not function correctly. The way you set the id of each module is to move the small dials that are located on the front of them to differing letter number combinations. The wireless receiver only contains one dial with letters around it. When you set the module to one

    “With Flipit you will be able to turn on and off an electrical device or dim or brighten a lamp, even if it wasn’t meant to be dimmed.” of the letters it will be set to the letter and device number 1, so if you set to the “a” position, it will be device “a1” or if set to “c” it will be “c1”. Now to the lamp module, which by default is set to “a1” will have to be adjusted if the wireless receiver is set to “a1”. Turn the number dial on the lamp module to another number other than 1, for this example move the dial to 2. This will make the wireless receiver “a1” and lamp module “a2”. To test the complete installation you will run this command at the command line. Flipit –t /dev/ttys0 flip a2 on.

    What this command does, is it tries to open the device /dev/ttys0 which is usually the first serial port and sends a signal. If this is the correct serial port that your FireCracker is in then your lamp will turn on and you will probably get an excited feeling running through your body. If it didn’t turn on, then you may need to try a different device file, maybe /dev/ttys1 or something similar. Once you have established which serial port that the Firecracker is attached to you will need to edit the flipit.conf to point to the right device file. By default the filipit.conf file is located at /usr/local/etc/flipit.conf and is set to “/dev/ttys0”.

    August 2005



    PHP Architect



    www.phparch.com

    This is the first but big step to getting your house powered by PHP. A Test Program Now onto to the PHP. With my system, currently sitting in my garage, I knew I wasn’t going to connect to the Internet for a while so I wanted to use the command line option for PHP. This works well because you will need root access to control the serial port via Flipit. This test program will run a series of commands through the FireCracker to make sure it functions correctly with the wireless receiver and lamp module. We will use the [exec()] function in PHP to run Flipit. The program will first turn off the lamp module wait five seconds and then turn the lamp module on, this makes sure we have on and off capabilities. Next the program will wait five seconds then dim the lamp module five steps. It will wait five more seconds and brighten the lamp module five steps. Then finally wait five more seconds and turn off the lamp module again (Listing Test 1). By going to the directory where this PHP file was created and then typing “PHP test.php” at the prompt, the program will run and you should see the light start reacting. You should have also noticed that we did not include the “-t /dev/ttys0” option in the Flipit statement. This is because the “flipit.conf” file controls which port to use if not specified. So if everything worked correctly you should see your light turn off, then on, dim , brighten and then finally turn off. Congratulations you now have your first light powered by PHP.

    Listing Test 1 1 2 3 4 5 6 7 8 9 10 11 12



    Listing Test 2 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17


    “8:00”){ a2 off”); a3 off”); a4 off”); b1 off”);

    if($check_time == exec(“flipit flip exec(“flipit flip exec(“flipit flip exec(“flipit flip } ?>

    “17:00”){ a2 on”); a3 on”); a4 on”); b1 on”);

    FEATURE

    PHP at Home

    Creating a Timer Now to a much more useful program, an automatic timer. Sure you can buy a timer from any hardware store to control your house lights, but you can’t fully control the timer or set variables based on day, week, month or even year. But with a timer system powered by PHP you can have a robust timing system. Let’s start with a basic timer. We will use “crontab” to schedule our timer. We will create a program that will turn on our lights at 5:00 p.m. and turn them off at Listing 1 1 2 3 4 5 6 7 8 9



    Listing 2 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17



    Listing 3 1 = “17:00” OR ($check_time >= “00:00” AND $check_time < “06:00”)){ 8 9 $number_pick = array_rand($number); 10 11 $number_full = $number[$number_pick]; 12 13 14 if($number_full == “5”){ 15 exec(“flipit flip a2 on”); 16 17 } 18 } 19 20 if($check_time >= “06:00” AND $check_time < “17:00”){ 21 22 $number_pick = array_rand($number); 23 24 $number_full = $number[$number_pick]; 25 26 27 if($number_full == “6”){ 28 exec(“flipit flip a2 off”); 29 30 } 31 } 32 ?> 33

    8:00 a.m., a simple method but it’s a start. (Listing 1) Next we will set a crontab line in the “/etc/crontab” file to run our program every minute. 1-59 * * * timer1.php

    * php

    Now everytime 8:00 a.m. rolls around the light plugged into lamp module “a2” will turn off and at 5:00 p.m. will turn off. You can also add more lamp modules like so (Listing Test 2). Simple but again this is just a start. One of the new features in PHP5 is the ability to get the time of the sunset and sunrise. Using these times will give a much more automated and usefulness to your timer. You wouldn’t have to adjust your timer every time the seasons change or have lights on when it’s still very bright outside. Look at the documentation for [date_sunset()] and [date_sunrise()] for setting purposes. Look at this timer example (Listing 2) for PHP 5. Again you will want to set up your crontab to run every minute. Once this is going you will enjoy the lights being on at the right time. You could also have the lights start dimming or brightening a half hour before the sunrise or sunset and fully shut off or on at the time of sunset or sunrise. An interesting idea that will make your house feel like it’s really reacting to the outside environment. This is also a more conservative approach, by turning the lights on only when you need them, will conserve energy and stop any slight waste. The Organic Vacation Timer I am sure most of you have seen the movie “Home Alone”, one of my all time favorites. There is a scene in that movie where the two robbers are sitting in their van at night watching the lights on the houses come on as the robber, played by Joe Pesci, points to each house. This shows the main problem with standard light timers, they go on at the same time everyday. No one turns on their lights at the same time everyday. There are always slight variables and reasons why we turn our lights on at different times. Anytime they come on at the same time everyday it can make people wonder if you’re really there. So how can we combat the static timer? Well by creating a more organic or random type timer. This is done by allowing the system to randomly turn on and off lights throughout the evening. This program will start turning on or off lights during the night. Some lights will have higher chances of staying on, while others like a bathroom light will turn off and on more frequently. We will first designate our timer areas in the house. Let’s say our porch light, device id “a2” will need to come on anytime after 5:00 p.m. and turn off anytime after 6:00 a.m. And our kitchen light, “a3”, will come

    29

    FEATURE

    PHP at Home

    on anytime after 6:15 p.m. and turn off anytime after 10:00 p.m.. Lastly we will start turning on and off the bathroom light, “a4”, between 5:00 p.m. and 8:00 a.m.. Setting these and other lights will give the house a more lived in feeling and allow you to rest a littler easier while away on vacation. First we’ll start off with the porch light. We want it to come on anytime after 5:00 p.m. so will have the system will start pulling random numbers from an array after 5:00 p.m. If the number is equal to the number we specified in the program, the light will be turned on. We will set this up also for the shutting down of the light. The more numbers in the array the less likely the light will be turned on or off. Here is the porch light example (Listing 3). Next (Listing 4) we will move on to the kitchen using the same code structure. Finally the bathroom (Listing 5). The code is slightly different than the first two and will have a higher probability of turning on and off. We want the bathroom, which is normally a room that is used throughout the evening, and its light turned on and off to really look like it’s being used. Now you have a much more organic and more realistic timer. Once implemented you will see that your house will start coming alive. The porch light will probably be the first to turn on and then the kitchen but the bathroom light will start tuning on and off like it was in normal use throughout the entire evening. Now that is a much more robust timer and I seriously doubt you

    Now a Little Fun By powering your house with PHP we have opened many doors to useful applications but on occasion you may want to use this new found power for pure entertainment. Let’s take a break from the everyday applications this technology applies to and discover one of the more useful applications, scaring your friends and family. This works great if your house is over 40 years old or if you live in an older apartment. You will first want to develop a story, something about a cranky old ghost coming and disrupting your electricity in a room and then set up a series of light dims and brightens for a specific time say 11:30 p.m., I know spooky already. Now invite your more gullible friends over for tea or whatever you may drink at that hour and watch as the fun begins. Since you set up a series of light dims and brightens you could tell your friends and family that when the light dims the cranky old ghost is saying yes or saying no, great fun and good times provided by PHP. Sorry, for the aside, let’s get back to business. Control via Text Message Now that we have covered the basics, let’s move on to another application. Like I mentioned at the start of this article we are going to see how to create a similar proListing 5

    Listing 4 1 = “17:00” OR ($check_time >= “00:00” AND $check_time < “06:00”)){ 8 9 $number_pick = array_rand($number); 10 $number_full = $number[$number_pick]; 11 12 if($number_full == “5” OR $number_full == “1” OR $number_full == “9”){ 13 exec(“flipit flip a2 on”); 14 } 15 } 16 17 if($check_time >= “17:00” OR ($check_time >= “00:00” AND $check_time < “06:00”)){ 18 19 20 $number_pick = array_rand($number); 21 $number_full = $number[$number_pick]; 22 23 if($number_full == “6”){ 24 exec(“flipit flip a2 off”); 25 } 26 } 27 28 if($check_time >= “06:00” AND $check_time < “17:00”){ 29 30 exec(“flipit flip a2 off”); 31 32 33 } 34 ?>

    August 2005

    could ever find a timer like this.



    PHP Architect



    www.phparch.com

    1 = “17:00” OR ($check_time >= “00:00” AND $check_time < “06:00”)){ 8 9 $number_pick = array_rand($number); 10 $number_full = $number[$number_pick]; 11 12 if($number_full == “5” OR $number_full == “1” OR $number_full == “9”){ 13 exec(“flipit flip a2 on”); 14 15 } 16 } 17 18 if($check_time >= “17:00” OR ($check_time >= “00:00” AND $check_time < “06:00”)){ 19 20 $number_pick = array_rand($number); 21 $number_full = $number[$number_pick]; 22 23 if($number_full == “3” OR $number_full == “7” OR $number_full == “10”){ 24 exec(“flipit flip a2 off”); 25 26 } 27 } 28 29 if($check_time >= “06:00” AND $check_time < “17:00”){ 30 31 exec(“flipit flip a2 off”); 32 33 34 } 35 ?>

    30

    FEATURE

    PHP at Home

    gram like the one in the short story so with out further delay let’s get into controlling your house over the Internet.

    “Home automation can do many things like freeing time or making you feel more secure.” Pretty much all cell phones and other personal devices like a PDA can now send text messages to a specified email address, nothing new there. Since these messages can be sent to any email address we can use these text messages to send commands to our house. You will first need to have Internet connectivity to your computer and be able to retrieve email via imap through PHP. You will need an email account with a server that allows an imap connection of course. If you need more information on imap itself there are many resources available on the Internet and full descriptions of the imap functions within PHP on php.net. On to the first step, this is the basic connecting command when connecting to an imap server through PHP (Listing 6).

    We will now start inspecting the body of the email. From my experience most cell phone text messages usually send as the body of the email. So we will be checking just the body (Listing 7) for specific commands. If certain keywords are present then we will tell the specific lights what to do (Listing 8). You will notice that if the program sees the “all on” key word, then every light that can be turned on will, this matches the story example. For added security you can always check the from email address and only allow certain email address to control the house. How frustrating would it be if a few spam emails started to turn on and off your lights? This is a fairly simple example and is much like the one from the story at the first of the article. Controlling the house by text messages and email means a simpler server setup. Since we only need to access email we don’t have to worry about installing a web server or getting a static ip address and domain name. There is much that can be done with this type of program and it can be as complicated and robust as you would like. Having the server connected to the Internet opens the doors for many more applications for controlling your house appliances. For example you could create a web page that would allow you to choose from a list which lights and appliances to turn on and off while away. Other Resources Here are some other resources and products that can Listing 8 (cont’d)

    Listing 6 1 ”; 9 10 for ($i = 1; $i <= imap_num_msg($mbox); $i++) 11 { 12 13 $header = imap_headerinfo($mbox, $i, 80, 80); 14

    Listing 7 1 2 3 4 5 6 7

    from; $from1 = $from[0]->mailbox; $from2 = $from[0]->host; $subject = $subject[$i] = $header->fetchsubject; ?>

    Listing 8 1 2 3 4 5 6


    August 2005



    PHP Architect



    www.phparch.com

    7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44

    if($a1_on == 1){ exec(“flipit flip a1 on”); } if($a1_off == 1){ exec(“flipit flip a1 off”); } if($all_on == 1){ exec(“flipit flip a1 on a2 on”); } if($all_off == 1){ exec(“flipit flip a1 off a2 off”); }

    imap_delete($mbox,

    $i);

    }

    imap_expunge($mbox); imap_close($mbox); ?>

    31

    FEATURE

    PHP at Home

    be used the FireCracker for further home automation. • X10.com – Offical x10 website. Here you will find a plethora of products that can be used to fully automate your house. It’s a little difficult to navigate, but is a great resource for X10 products. • Smarthome.com – Nice clean site that contains many of the same products X10 carries and also some from other vendors. • Radioshack.com- The nice thing about this site is that a lot of their stores carry the same products. So if you find it online you will probably find in store as well. Now let’s look at a few of the other products that X10 has to offer. We already have discussed their lamp module and they have several other products that will help in the whole home automation process. • Universal Module (UM506)- This module can be used for many things. It will basically allow you to control alternative appliances like garage door openers and sprinkler systems. By utilizing the FireCracker you can set up your own sprinkler system timer or even open and close a garage door, although I don’t know how useful opening up a garage

    August 2005



    PHP Architect



    www.phparch.com

    door would be. • Chime Module (sc546a) – This module will chime when sent an “on” signal. This can be used to let you know when something has been turned on or even chime at every hour like a clock. • 3 Pin Grounded Appliance Module (AM466) – You can use this module to properly turn off/ and on grounded appliances. • High Voltage Module (HD243) – This can turn on and off high voltage (220 v) appliances such as water heaters. • Socket Replacement (SR227) – You can use this to actually hard wire an X10 module into your home. This will replace the entire wall socket and give the house and the X10 module a more integrated and built-in look. • Socket Rocket (LM15A) and Screw In Socket Module (SI575) – Both of these modules can actually screw into a lamp socket and then the light bulb into the module and will allow you to turn on and off a light. However, the socket rocket doesn’t allow for the dimming of lights. So as you can see there are many products that can

    32

    FEATURE

    PHP at Home

    be used in your home automation setup. You are not confined to just a light but could essentially control just about every appliance. There are other products available but I just wanted to show a few to get the mental juices and ideas flowing for further automation. Outro This is just a small glimpse into the world of home automation. The programs we have discussed are simple, and there are so many more facets to explore and places this kind of technology can be applied. So besides being useful for a home this type of automation can be applied in so many places. One place would obviously be a restaurant. Using the new sunset sunrise functions in PHP 5, a great effect can be achieved Lights could slowly come on after sunset or start coming on a few minuets before. You could even setting up a system to control the lights of an outdoor basketball court or baseball field. Powering appliances and lights with PHP gives you so much control and can actually help in being more energy efficient by cutting down on lights coming on to early or shutting off to late. A house powered by PHP can really be a useful thing. It is really cool to see something other than web applications get powered by PHP. I used to think that home automation was expensive

    and was out of the realm of the average Joe but thanks to a few inexpensive pieces of hardware and PHP you can now have your own automated house. If you will be braving the integration of your home and PHP good luck and have fun.

    About the Author

    ?>

    Ron is the technical director/senior programmer for Conveyor Group, a Southern-California based web development firm. His responsibilities include technology development, programming, IT and network management, strategic research, server systems management (webmaster), and website projects leader.

    To Discuss this article: http://forums.phparch.com/244

    Award-winning IDE for dynamic languages, providing a powerful workspace for editing, debugging and testing your programs. Features advanced support for Perl, PHP, Python, Tcl and XSLT, on Linux, Solaris and Windows.

    Download your free evalutation at www.ActiveState.com/Komodo30

    NEXCESS.NET Internet Solutions 304 1/2 S. State St. Ann Arbor, MI 48104-2445

    http://nexcess.net

    PHP / MySQL SPECIALISTS! Simple, Affordable, Reliable PHP / MySQL Web Hosting Solutions P O P U L A R S H A R E D H O S T I N G PAC K A G E S

    MINI-ME

    $

    6 95

    SMALL BIZ $ 2195/mo

    /mo

    500 MB Storage 15 GB Transfer 50 E-Mail Accounts 25 Subdomains 25 MySQL Databases PHP5 / MySQL 4.1.X SITEWORX control panel

    2000 MB Storage 50 GB Transfer 200 E-Mail Accounts 75 Subdomains 75 MySQL Databases PHP5 / MySQL 4.1.X SITEWORX control panel

    16 95

    /mo

    900 MB Storage 30 GB Transfer Unlimited MySQL Databases Host 30 Domains PHP5 / MYSQL 4.1.X NODEWORX Reseller Access

    NEXRESELL 2 $

    We'll install any PHP extension you need! Just ask :) PHP4 & MySQL 3.x/4.0.x options also available

    59 95

    /mo

    7500 MB Storage 100 GB Transfer Unlimited MySQL Databases Host Unlimited Domains PHP5 / MySQL 4.1.X NODEWORX Reseller Access

    : CONTROL

    php 5 4.1.x

    POPULAR RESELLER HOSTING PACKAGES NEXRESELL 1 $

    NEW! PHP 5 & MYSQL 4.1.X

    PA N E L

    All of our servers run our in-house developed PHP/MySQL server control panel: INTERWORX-CP INTERWORX-CP features include: - Rigorous spam / virus filtering - Detailed website usage stats (including realtime metrics) - Superb file management; WYSIWYG HTML editor

    INTERWORX-CP is also available for your dedicated server. Just visit http://interworx.info for more information and to place your order.

    WHY NEXCESS.NET? WE ARE PHP/MYSQL DEVELOPERS LIKE YOU AND UNDERSTAND YOUR SUPPORT NEEDS!

    php 4 3.x/4.0.x

    128 BIT SSL CERTIFICATES AS LOW AS $39.95 / YEAR DOMAIN NAME REGISTRATION FROM $10.00 / YEAR GENEROUS AFFILIATE PROGRAM

    UP TO 100% PAYBACK PER REFERRAL

    30 DAY MONEY BACK GUARANTEE

    FREE DOMAIN NAME WITH ANY ANNUAL SIGNUP

    ORDER TODAY AND GET 10% OFF ANY WEB HOSTING PACKAGE VISIT HTTP://NEXCESS.NET/PHPARCH FOR DETAILS

    Dedicated & Managed Dedicated server solutions also available Serving the web since Y2K

    FEATURE

    FEATURE

    May I See Your License, Please? by Alasdair Stewart

    Last year, the Business Software Alliance reported that 35% of all software installed was pirated. In a world where code-theft is commonplace, and crackers are waiting with itchy palms to make your script accessible to the masses for zilch, we will show you how to keep the doors of script piracy locked, bolted and chained shut, for less than you might have initially thought.

    Y

    ou’ve decided you want to sell your PHP script, but you have to acknowledge that you’ll need to think a few things over first. What are the license terms going to be for your product, and how are you going to enforce those terms? Many PHP developers—from personal experience— get struck down by a lightning bolt of inspiration and hurry away to code the next life-changing masterpiece, slap a price on it and leave it to fend for itself; without really taking into account the options available to help reduce piracy. The beauty of deciding on how you are going to license and sell your product is that you can make it as restrictive or flexible as you want, offering different levels of access for different tiered levels of pricing. Many of you might go out and see something like the Zend SafeGuard Suite to enforce your licensing terms at around $5000 for a perpetual license, and simply forget the idea altogether, due to the price. The truth is that there are ways of enforcing licensing starting from as little as $5. Choosing the correct licensing model for your script is crucial if you want it to be a success, so you don’t want to overlook it and push it to the bottom of your things-to-do list. For the purpose of this article, let’s create of a fiction-

    August 2005



    PHP Architect



    www.phparch.com

    REQUIREMENTS PHP

    4.3.0+

    OS

    Windows, Linux, MacOS or BSD

    Code Directory

    lincensing

    RESOURCES URL

    http://www.ioncube.com/

    URL

    http://www.zend.com/

    URL

    http://www.sourceguardian.com/

    URL

    http://www.phpaudit.com/

    i

    al content management system (CMS) called “MyCMS”. MyCMS has all the usual features that a CMS should have, such as the ability to manage content across multiple websites, have multiple editors and content creators, and manage a near unlimited number of articles, reviews, news and other miscellaneous content. Originally, the fictional developer was going to sell it for $250 and let the user use it how and where they wanted. Since the majority of big companies don’t do that, why should the little guys? As a side note, all pricing figures in this article are in United States Dollars. If you are purchasing the products from within the European Union, you may also be liable to pay sales tax on the prices, so don’t forget to take this into account!

    37

    May I See Your License, Please?

    The Licensing Options If you are developing PHP for a big company, choosing how the script is licensed and sold will probably fall to some little known staff member on the other side of the office. But if you are the sole developer, or working in a small team, you get to make critical key decisions and it’s important you make the right one. Making the right choice on how you license your creation affects your revenue—and ultimately your profits. A very common form of licensing for a PHP-based product is the ‘per domain’ option. Simply put, this is where the user is granted a license to use the script on a single domain name. So in the case of our fictional MyCMS product, for the $250 the user paid for the script, they’d be granted a license to use it an unlimited number of times on one domain. This is probably one of the easiest licensing terms to enforce, which might explain why it’s also one of the most popular. An extension of the ‘per domain’ option is the ‘per installation’ option. Rather than permitting users to use the script an unlimited times on one domain name, the user is granted a license to install one copy of the script, on one domain. Again, this is also fairly easy to enforce and is also very popular, and can be seen in established forum scripts such as vBulletin. One level up from ‘per domain’ licensing is to offer ‘per IP’ or ‘per server’ licensing; which in the case of ‘MyCMS’ might be a very appropriate option, allowing the end user to install the system across multiple sites on the one server, and take advantage of its ability to manage multiple websites. You might now be thinking “That’s very easy. Nothing hard about that.” But, take a step back and think about some of the other options available. As I said at the beginning of the article, the fictional MyCMS system supports multiple staff editors, so why not develop a pricing and licensing structure based around this? A quick example might be that having one editor in MyCMS costs $100, up to five editors costs $250, 15 editors costs $500, and unlimited editors costs $1000. The advantage of a system like this is that your script becomes relevant to a much bigger market, due to the much wider price range. MyCMS would be priced at a level suitable for an individual wanting to create a personal homepage, all the way up to a larger business who would want to create a fully interactive website. As the business grows, they might want to have more than five editors in the system, and so go and purchase the 15 editor edition. With other licensing schemes such as ‘per domain’, the end-user would only purchase another license if they wanted to use it on another website, whereas licensing MyCMS per editor brings in the greater possibility of further revenue later on from existing clients. A similar scheme could be applied to the amount of content that is stored in MyCMS, and again it would be quite easy to develop a pricing model August 2005



    PHP Architect



    www.phparch.com

    FEATURE based around this. To look at it from another angle, if you have developed a PHP billing/invoicing system, there are plenty of ways to license and sell it. Like MyCMS, you could license it based on the number of staff that need access to the billing/invoicing system. You could also develop a licensing and pricing structure based on the number of clients and/or customers that are in the system, such as up to 100 customers for $500, up to 500 customers for $1000, and unlimited customers for $2500. Another interesting and potentially unique method of licensing a billing/invoicing system could be to license it based on the amount of revenue that is going through the system. For example, you might allow the business using the billing/invoicing system to process up to $100,000 of transactions per year for $1000, $250,000 worth of transactions for $5000, and up to $1,000,000 worth of transactions for $10,000. Again, these methods of licensing and selling your product are tiered, and could create potential future revenue for you. You could also “mix and match” different licensing terms to create a flexible licensing system for your endusers. With a script like MyCMS, you might create a “one domain, one editor” license, and then a “one server, fifteen editors” license at a higher price. This creates further revenue possibilities, and allows a greater number of pricing tiers. The downside to a mix and match system like this is that it can make it complicated for users wanting to buy your product, and it might not be entirely obvious what license they need to purchase—which can easily turn from a multitude of apparently flexible options into lost sales. The licensing terms you set and how you sell your product depend on various factors that are unique to your business, your product and your circumstances. Factors that’ll affect how you choose to sell your script might include: who your script is targeted at; how much money you want to make, and how popular you think it might become. If you are targeting your script at small webmasters, then you might prefer to sell your script on a “per domain” basis, whereas if you are targeting large businesses, you might want to go with a “per editor” or “per user” style approach. Ultimately, the choice is yours, and hopefully this has opened your eyes to the wide variety of ways you can license your script. Enforcing the EULA Once you’ve decided how you will sell your product and the license terms that accompany the sale, it’s time to turn your attention to how you will actually enforce your EULA (End User License Agreement). It’d be pretty pointless to tell your users “you can only use it on one domain”, and then not have any mechanisms in your script to enforce it—or would it?

    38

    FEATURE

    May I See Your License, Please?

    A great example where leaving the source code viewable to users has had positive results is the popular vBulletin forum system, which is used on sites from small communities all the way up to huge corporate sites. One of the attractions of vBulletin is that it’s distributed with “viewable source”, which has allowed a thriving community to emerge who contribute hacks, patches and other useful add-ons to the thousands of vBulletin users. The downside to this is that it’s allowed software pirates to simply remove the licensing code from vBulletin, which has resulted in vBulletin being widely pirated across the internet. Without preventing the user from removing any licensing code from your script, any user with even basic PHP knowledge could probably remove it, defeating the purpose of having it there in the first place. All it takes is one user to get your script, remove any licensing code, distribute it, and you’ve instantly lost sales. So, what’s the solution? Encoding Your Script If you can stop users from being able to alter or remove the PHP code from your script that handles your products licensing, then it will make it much harder—if not nearly impossible—for your script to be pirated. To do this, you’ll need to invest in an encoder and encode your PHP scripts. What do I mean when I say “encode your PHP scripts?” Quite simply, an encoder turns the “Hello World” example in Listing 1 into an unreadable and unrecognizable file. Most of the products also apply numerous layers of encryption, and you can see the ‘Hello World’ encoded file produced by the ionCube encoder in Listing 2. The products listed here all byte-encode PHP scripts, but also apply varying layers of encryption and obfuscation. The result is a PHP file which can’t be edited or returned to the original file—as well as preventing the removal of any licensing code in your product, it also prevents competitors or casual users from seeing how your script works and then copying the code into their own products. While there are other solutions available, ionCube, Zend and SourceGuardian make the most popular tools for encoding PHP, which start from as little as $5. All of the products mentioned here also require some form of “loaders” or other additional software to run the encoded files. Beware of any PHP encoders or obfuscators which don’t require any loaders or extra software to run encoded files—these are usually insecure as they simply use existing php functions to decode the scripts, making it very easy to return files to their original state. The ionCube Encoder One of the unique advantages of the ionCube encoder is that you can either buy the ionCube software and encode files on your own machine, or use the online August 2005



    PHP Architect



    www.phparch.com

    encoder on a “pay as you go” basis. The online ionCube encoder can encode files from just $0.50, but requires a minimum deposit of $5. The advantage with the online ionCube encoder is that if you are only encoding a few files, or encoding files a few times a year, then it can work out to be a very cost effective solution for encoding your files cheaply. The only downside at the moment with the online ionCube encoder is that it doesn’t support PHP 5, and so if you intend on encoding PHP 5 code then you should look at the ionCube encoder software or one of the other products mentioned in this article. The ionCube encoding software is available in three editions: Entry, Pro and Cerberus. All three editions encode PHP source (including PHP 5), and the software can be purchased for Windows, Linux or FreeBSD. The Entry edition of the ionCube encoder costs $199 and only encodes PHP. The Pro version of the ionCube encoder costs $269, and as well as encoding PHP scripts, it can also restrict where and when the encoded PHP files can be run. When encoding the PHP scripts, you can specify an expiry date after which the files will stop working. This is ideal for providing a free 30 day trial that users can try out before purchasing the full version of your software. Another useful feature in

    Listing 1 1 2 3 4 5 6 7 8



    Listing 2 1 ’.__FILE__.’ has been encoded with the ionCube PHP Encoder and requires the free ‘.basename($__ln).’ ionCube PHP Loader to be installed.’); 4 ?> 5 0y4hY9w2H1DhLBqaYJhHC7RlUQWmddxhK0BvZv13mU3xsasdc2hM6eUWE3S4Qi61RcI +GPzBGslb 6 qc+GLdQhKlgklTn4TgoFSBh7wPekS5LRoGinNYGJrwzSQTxM0JImLcmkR8ty0C25plv qOY9A5MDL 7 dMIxsqfpEkRE2eGoEKT88rYXeNpnuuQQt3CWmzPJ6JCUaKJv9eQoqlY4baXHaR6BQK+ BXRL+kv18 8 BDpkvEtMX+09alnBmlkaN2UuNG== 9 10

    39

    May I See Your License, Please?

    the Pro version is the ability to restrict encoded files to only run on certain domain names and/or IP addresses, which can form part of your licensing if you choose to sell your script on a ‘per IP’ or ‘per domain’ basis. The Cerberus version of the ionCube encoder costs $348, and has all the features that the Entry and Pro versions have, but also has the ability to restrict encoded files to only run on certain MAC addresses. For those that don’t know, a MAC address is a unique 48-bit key embedded in every Ethernet networking device, which makes this feature very useful if you plan on selling your script on a ‘per server’ basis. An advantage that the ionCube encoder software has over other encoders such as the Zend encoder is that it

    FEATURE Encoder which has a similar feature set to the ionCube Entry encoder, and the second product is the Zend SafeGuard Suite which adds a number of options for licensing your scripts. The Zend encoder is priced quite a bit higher than the ionCube encoder, and starts at $960 for a license to use the encoder for a year. A perpetual (lifetime) license costs $2400 which doesn’t include any support or upgrades; and a perpetual license including one year of support and product updates costs $2880. Before the price puts you off, its worthwhile noting that if your company has less then $250,000 revenue per year, then you qualify for Zend’s Small business program. This allows you to get a one year license to use the

    “Another interesting method of licensing could be to license it based on the amount of revenue that is going through the system.” doesn’t require an annual fee for continued support or upgrades to the product. This lowers the total cost of ownership, and so for budget conscious buyers it’s a major advantage. One of the reasons the ionCube encoder is so popular is that to run ionCube encoded files most servers don’t need to have any changes made or anything extra installed. This is a particular advantage, as it allows most end-users without root access to servers to still run your encoded script. To run ionCube encoded files, most users simply need to upload the “ionCube loaders” to their web space along with your ionCube encoded script. These loaders are then accessed along with the ionCube encoded PHP script, and this allows the encoded files to run. If the “run-time loading” feature doesn’t work on a server, then a simple line added to the php.ini will normally solve the problem. A unique feature offered by the ionCube encoder is the ability to “ASCII” encoded files. All of the other encoders mentioned here only produce binary encoded files, and so these need to be uploaded via FTP in binary mode by users. This used to be a huge support headache for companies; as often users didn’t bother to upload the files in binary, which corrupted the file and so the script was unusable. Since most FTP clients view PHP files as text files, they default to uploading them in ASCII, and so if you take advantage of the ASCII encoding option of the ionCube encoder it might save you quite a bit of time in terms of supporting users who didn’t follow your instructions. The Zend Option Zend produces two products that can assist you in enforcing your EULA. The first product is the Zend

    August 2005



    PHP Architect



    www.phparch.com

    Zend encoder, and Zend’s popular PHP development environment—Zend Studio—for only $395. This is considerably less than $960, and so if you qualify, it’s definitely the better option. In terms of features, the Zend encoder can encode both PHP 4 and PHP 5 scripts and also includes the ability to set an expiry date for the encoded scripts, much like the Pro edition of the ionCube encoder. Unlike the ionCube encoder and SourceGuardian, before a user can run Zend encoded scripts, the Zend Optimiser – a free download from Zend.com – must be installed on the end-users server. While the Zend Optimiser is installed on a large number of servers worldwide, it can be an added inconvenience to users if they need to install it, or get their web host to install it for them. There is also the possibility that the users host might not want to install it, and if that’s the case then the user won’t be able to run your product. While cases like this are rare from my experience, it is still something to consider when choosing what product or products to go with. My own personal experience with the Zend encoder is that the files it produces are generally smaller than those encoded by the ionCube encoder and the original product source code. A copy of one of my products’ source is 3.68MB, the ionCube encoded version is 4.50MB and the Zend encoded version is 3.39MB. It’s also worth noting, that once you add the ionCube loaders, then the size of the ionCube version will also increase in size, depending on what loaders you decide to add. While the size of your script will vary depending on its complexity, it is something that you might wish to take into consideration when choosing which encoder or encoders to buy. The second product produced by Zend is the Zend 41

    May I See Your License, Please?

    SafeGuard Suite. The Zend SafeGuard Suite contains all of the features of the Zend encoder, but also contains a number of advanced and unique features for licensing your encoded scripts. Like the Zend Encoder, it is expensive, with a one year license costing $2920. A perpetual license costs $4450 excluding any support or upgrades, and a perpetual license including one year of support and product updates is $5340. Also worth noting at this point, is that if you qualify under the Zend Small business program, you can upgrade your Zend Encoder license to a one year license of the Zend SafeGuard Suite for $599, which is again a considerable savings. The licensing features in the Zend SafeGuard Suite focus around license files which contain information on where and when an encoded script can be run. This is different from the ionCube encoder, as these details are actually encoded into the files. A unique feature found in the Zend SafeGuard Suite is the ability to limit the number of concurrent users accessing the encoded script. To bring this back to MyCMS, this could be particularly useful for licensing it, as MyCMS could then be sold on the number of active editors logged into the system; for example: one logged in editor could cost $250, five editors $1000, and fifteen active editors $2500. Like the ionCube Pro encoder, the Zend SafeGuard Suite through its license files can also restrict encoded files to only run on certain IP addresses, but can also lock encoded files to a computer’s unique “Zend ID.” The advantage of using license files is that you only need to generate one set of encoded files, and then individual license files for all of your customers. All in all, if you are looking for an encoding and licensing solution in one, then the Zend SafeGuard Suite is a serious contender if you can afford it. SourceGuardian Unlike the ionCube and Zend encoder, SourceGuardian doesn’t have multiple tiers of pricing, and costs a straightforward $250 which includes support and minor product updates. It has a feature set that rivals the ionCube encoder and the Zend SafeGuard Suite, but for some reason, my own experience with encoded products is that it doesn’t seem to be as popular as either Zend or ionCube. Like ionCube, SourceGuardian encoded files can normally be run without anything being installed or reconfigured through their “ixed” loaders, and again, if that isn’t possible, a single line added to php.ini usually solves any problems. SourceGuardian can encode files for both PHP 4.3 and PHP 5. Like ionCube, it also has a number of options for controlling encoded PHP files, and can encode files with an expiry date, and also lock them to certain IP addresses, domain names and MAC addresses. Where SourceGuardian differs from ionCube is that it also provides the ability to generate external “license

    August 2005



    PHP Architect



    www.phparch.com

    FEATURE files” similar to those generated by the Zend SafeGuard Suite, and so you have the option to either embed the information into the encoded PHP files or include it in an external license file. If you’d previously looked at SourceGuardian version 2 and decided it wasn’t to your liking, then their latest version (v4.2) is hugely improved and well worth a look. SourceGuardian is certainly a very flexible product for encoding your PHP files, and includes a wide variety of licensing features at an extremely attractive price. PHPAudit Unlike the other systems mentioned here, PHPAudit isn’t an encoder but is instead a dedicated licensing and distribution system. It costs $125 for a perpetual license including one year of support and product updates, and can also be purchased bundled with the ionCube encoder from only $250. PHPAudit is written in PHP and runs on your Windows, Linux or FreeBSD web server, providing a completely automated licensing system. It provides a variety of options for licensing your scripts, including per installation, per domain, per IP, and per server (based off the MAC address), or a combination of these together depending on what you need. The main difference with PHPAudit is that it focuses on a “call home” style licensing system, where your product “calls home” to your server to verify the user’s license. However, it also offers ‘local keys’ which are similar to the license files provided by SourceGuardian and the Zend SafeGuard Suite for those cases where a “call home” style system just wouldn’t cut it. For the widest possible compatibility, PHPAudit can remotely verify licenses using sockets, cURL and file_get_contents(), so there shouldn’t be any problems with finding a method that works for you and your product. One of the problems with traditional “call home” systems is what happens when your server goes down or if the user firewalls it out, but PHPAudit handles this with ease as it allows you to fallback on the “local key” if it can’t connect to your server to verify the license remotely. Once you’ve setup your product in PHPAudit and decided how you are going to license it, you are provided with “integration code” which you then insert into your script and encode with your chosen encoder(s). The “integration code” contains everything necessary for your script to “call home” to PHPAudit and/or check the “local key”, to verify that the user’s installation is valid. It’s a very simple process and should only take you around five minutes to implement. The real advantage with PHPAudit is that it’s a completely automated system, and can be setup to require no intervention from you at all. The main part of this automation is the “order profile,” which contains a shopping cart style ordering system. Users are able to

    42

    FEATURE

    May I See Your License, Please?

    select your product(s); pay using one of the integrated payment processors which includes Paypal, Authorize.net, Stormpay, 2checkout and Worldpay; and once they’ve paid they are instantly issued a license key which will allow them to use your product. The second part of the “order profile” is the client area, which provides a location for users to login, view their past orders and licenses, and download your product. Once the user has downloaded your product and entered their license key, PHPAudit locks in the details of where they are using it, and the user is then prevented from installing it anywhere that would breach your licensing setup. Unlike the ionCube encoder, SourceGuardian and the Zend SafeGuard Suite, with PHPAudit you don’t need to manually generate any license files or encode your script for the user, as it handles everything for you. Another interesting option with PHPAudit is that if you have the ionCube encoder installed on the same server as PHPAudit, you can do all of your ionCube encoding from inside PHPAudit and have the finished distribution added ready for download by your customers. PHPAudit would make a great companion to any encoder to provide a robust and flexible licensing system, and the bundle including the ionCube Entry encoder with PHPAudit at $250 is great value for money.

    of your product prior to purchasing it. The ionCube Pro Encoder, Zend Encoder, SourceGuardian and PHPAudit are all capable of enforcing a time limit on how long a user can run your product, and so you can provide your users with a trial without worrying about them using it past the expiry date. When encoding scripts, it’s also crucial to get a balance between encoding enough of your script to protect your functions and license it, but also leave enough of it open so that users can customise it to their needs. With this, you might also want to consider having a ‘standard’ version of your script with a large part of it encoded, and also a ‘developer’ version at a much higher price, which has more of the source available so that users can customise it more to their needs. On a final note, recent information from the Business Software Alliance shows that 35% of the software installed on computers in 2004 was pirated. Don’t become a statistic – take action and protect your product now.

    About the Author

    ?>

    Alasdair Stewart currently lives and works in Scotland, and first became interested in PHP in early 2002. After a brief stint running a web hosting company, he sold it to focus on PHP, in 2004. He currently works for US based SolidPHP, Inc. who develop and sell a number of PHP based products. He can be reached at [email protected]

    Some Final Thoughts While this article has focused on using the encoders and licensing systems to protect your code from piracy, another great use of them is to provide users with a trial

    August 2005



    PHP Architect



    www.phparch.com

    To Discuss this article: http://forums.phparch.com/242

    43

    FEATURE

    Release Your Next Project as a

    PEAR 1.4.0 Package by Clay Loveless

    REQUIREMENTS With the release of a stable PEAR 1.4.0 installer on the horizon, now is a good time to get familiar with the new features provided by PEAR 1.4.0 that can make distribution of your open source and proprietary libraries and applications easier than ever before.

    T

    he upcoming release of PEAR 1.4.0 is an exciting milestone not only for PHP developers already familiar with PEAR, but for any PHP developer who is responsible for distributing applications on a large or small scale. That’s right, applications. While PEAR is an acronym for PHP Extension and Application Repository, the PEAR installer has never been particularly well suited for installing or maintaining installations of full-blown applications—until now. With the new features of PEAR 1.4.0, distributing full applications complete with dependency checking, customized environment modifications upon upgrades, and more are possible with just a little bit of extra work. As you’ll see, the benefits of taking advantage of the new PEAR 1.4.0 distribution options far outweigh the extra work required.

    What’s New in PEAR 1.4.0 There are several major new features in PEAR 1.4.0, the largest of which is support for channels. A PEAR channel is essentially a server that behaves just like the pear.php.net package distribution server, but can be served from another domain. That means that anyone may set up a PEAR channel server and serve PEAR-compatible packages. All an end-user needs to do is make their PEAR installation aware of the additional channels, and install from those channels, specifically. In order to do this, let’s get ready by updating your PEAR installation to the latest PEAR alpha that supports

    August 2005



    PHP Architect



    www.phparch.com

    PHP

    4.2 or greater

    OS

    Any

    Code Directory

    pearpackage

    RESOURCES

    i

    URL http://pear.php.net/package/PEAR URL

    http://pear.php.net/package/PEAR_PackageFileMan ager

    URL http://www.phpmyadmin.net/ URL http://www.pearified.com/ http://www.schlitt.info/applications/blog/index

    URL .php?/archives/308-Set-up-your-own-PEAR-chan nel.html http://greg.chiaraquartet.net/archives/31-

    URL Using-PEAR-1.4.0-to-install-PEAR-packages-on-aremote-host.html

    URL

    http://pear.php.net/manual/en/guide.migrating.p ostinstall.php

    channels. Then we’ll upgrade your PEAR installation, and add an example channel. $ $ $ $

    pear pear pear pear

    config-set preferred_state alpha upgrade PEAR channel-discover pearified.com list -c pearified

    What we should have now is an upgraded version of PEAR (1.4.0a12 at this writing), and a PEAR installation that is aware of two PEAR channels: the default PEAR channel at pear.php.net, and the new “Pearified” channel at pearified.com. Now, packages can be installed

    44

    Release Your Next Project as a PEAR 1.4.0 Package

    and maintained, including full dependency resolution, from both locations. The next most significant feature of PEAR 1.4.0 is support for post-install scripts. A post-install script is run when called, like this: $ pear run-scripts Example_Package

    If Example_Package has defined post-install scripts, the PEAR installer will alert you following successful installation of the package. Scripts are never automatically executed for security reasons—you must explicitly run them using pear run-scripts as illustrated above. A post-install script can perform just about any function you can imagine, provided that the user running the script has the proper permissions. Examples of good post-install script ideas are setting up default users of a permission system, installing or upgrading database schemas, or perhaps setting up a directory structure required by an application, complete with necessary permissions on those directories. While there are several other enhancements in PEAR 1.4.0, the last one we’ll discuss in this article is the very handy capability to manage remote PEAR installations over FTP. Frequently when doing contract work, I find that I’d like to use PEAR for an application, but since the client’s web hosting account does not offer shell access, I’m forced to either use the web browser front-end to PEAR, or maintain the PEAR installation manually over FTP. With PEAR 1.4.0, it is now possible to specify a remote configuration file, which stores relevant install path information for the remote server, and then use the command-line PEAR command to synchronize your local PEAR installation with the remote PEAR installation. For more details on all of PEAR 1.4.0’s new features, review the PEAR documentation at http://pear.php.net/manual/en/guide-migrating.php . PEAR 1.4.0’s primary developer, Greg Beaver, also maintains a weblog with several entries that discuss PEAR 1.4.0’s architecture in detail. Visit Greg’s blog at http://greg.chiaraquartet.net/categories/3-PEAR . Why PEAR packaging makes sense now PHP is maturing quickly, and as it does, more and more packages that handle core application functionality (such as database abstraction, form validation and unit testing) are maturing as well. Naturally, it follows that more and more of the applications you build for internal or external distribution will depend on at least one of these supporting packages. (You are looking for ways not to re-invent the wheel, aren’t you?) Whenever an application becomes dependent upon a third-party library, the question then becomes how to stay on top of updates to the external library along with maintaining the application that depends on it. For a long time, the “solution” to this for many applications

    August 2005



    PHP Architect



    www.phparch.com

    FEATURE was simply to bundle a copy of the dependency with the application. Unfortunately, the “bundle-the-dependency” solution does not work well in the event of critical updates to the dependency. For example, many applications depend on PEAR’s XML-RPC package, versions up to and including 1.3 of which was discovered to contain some rather serious potential security holes on June 29, 2005. (See http://secunia.com/advisories/15852/ for details.) Following the discovery of these security holes, a scramble ensued by maintainers of applications dependent upon the PEAR::XML_RPC package to release updates containing a new bundled version of the patched package, PEAR::XML_RPC 1.3.1. Since this initial scramble, PEAR::XML-RPC has had two more updates, but none of the applications that I use which depend on PEAR::XML-RPC have released updates to reflect the updated dependency—the current release as of this writing is 1.3.3, which contains two additional security and performance updates since version 1.3.1. The answer? Convert your applications to PEAR installer-managed packages, and encourage the maintainers of the applications you use to do the same. With PEAR 1.4.0 managed packages, an application’s dependencies can be updated as needed for security fixes and performance enhancements without the need to wait for an update release of the entire package in hopes that it will contain updated bundled dependencies as well. A common counter-argument to relying on non-bundled dependencies is “What if a release for a dependency breaks backwards-compatibility?” If you or an application maintainer is particularly concerned about that possibility, it is trivial to specify limits on dependency versions in the definition of an application’s package. (We’ll explore how to do that a bit later.) Given that PEAR packages are not supposed to break backwards compatibility except on major version number increases, applications distributed as PEAR packages could avoid backwards-compatibility breakage concerns by specifying, for example, that any PEAR::XML_RPC package less than version 2.0 be acceptable for use by an application. Given the increased usage of external library dependencies in PHP applications, it just makes sense to implement a structured package management system into your build-release protocol. PEAR 1.4.0 provides an excellent system for that purpose—and given that PEAR is installed by default in most PHP installations, soon PEAR 1.4.0 will make its way into a majority of PHP environments. Candidates for PEAR package management What type of application or set of PHP scripts makes sense as a PEAR package? Thanks to PEAR 1.4.0, just

    45

    FEATURE

    Release Your Next Project as a PEAR 1.4.0 Package

    about any type. Let’s take a look at a short list of various scenarios where PEAR packaging would be a good idea. Library distribution—This is how PEAR itself got started, so it makes sense that packaging a code library intended for re-use would be a good candidate for PEAR packaging. “Off-the-shelf” application distribution—Whether an application is fully object-oriented or not, PEAR packaging makes sense primarily due to its intelligent handling of dependencies. Custom closed-source applications—Even if the target

    PEAR installer. All that’s required to do this is building your package in such a way that the installer knows what to do with each file in the distribution. Where PEAR 1.4.0 Installs Stuff As soon as we start thinking about installing all kinds of applications, packages, and any other files we might consider including in a distributed application, the first thing you’ll probably worry about is what file will get installed where. By default, PEAR 1.4.0 (and older versions as well) installs files in a set of pre-defined locations.

    “Anyone may set up a PEAR channel server and serve PEAR-compatible packages.” installation is only a specific client’s server, PEAR packaging can offer a tremendous advantage. Beyond the dependency management that is likely to be beneficial in this type of application, the code may also need to be deployed on a server that not everyone has access to. For example, consider that you’re a consultant that is building an application for a client, yet due to security reasons you may not have access to install the application on the production server where it will ultimately reside. You can reduce the back-and-forth communication along the lines of “Oh yeah, make sure you have version 2.x of Package XYZ installed” by managing the distribution of your work with a PEAR package. All the person with clearance to install the application on the production server would need to do is: $ pear upgrade -o consultant_channel/Custom_Application

    in order to get their installation upgraded with whatever dependencies are required. In other words, you can control the installation environment much more with a PEAR-managed package, without having to bother the client with the details. You may be wondering if there’s a typo in this article—did I really just say that object-oriented code is not a requirement for a PEAR package? It’s true that packages that are submitted to PEAR itself are generally required to have an OOP structure, however, that requirement does not extend to a package you may create to be compatible with the PEAR installer. The PEAR installer itself doesn’t care whether the files in your package are OOP-based, procedural or a combination of the two. You can also install graphics, JavaScript files, and whatever else you need to with the

    August 2005



    PHP Architect



    www.phparch.com

    Every PEAR installation has a default base working directory, known conveniently as the “PEAR Directory”. The location of the PEAR Directory varies from platform to platform—though it is often in /usr/lib/php (the default) or /usr/share/php. To find out what the PEAR Directory is set to in your PEAR installation, just use: $ pear config-show

    Check the output of the above command for “PEAR Directory”, which is also known by its variable name of php_dir. Within the PEAR Directory, there are a series of subdirectories: • data_dir: The data directory, where any data files that are associated with a package are installed. • doc_dir: The documentation directory, where documentation that accompanies a package is installed. • test_dir: The tests directory, where package regression tests are installed. With these install directories in mind, and assuming you need to package an application with “front end” capabilities (in other words, portions of your packaged application need to be directly accessible by a web browser), you can proceed with configuring your package to be installed three different ways: the “Change PEAR Config Each Time” method, the “PEAR Default + Aliasing” method, or the “PEAR Custom Roles” method. We’ll explore each below.

    46

    Release Your Next Project as a PEAR 1.4.0 Package

    The “Change PEAR Config Each Time” Method At first glance, it may make sense to just change PEAR’s configuration based on the needs of your packaged application. Let’s say that everything in your packaged application needs to be directly accessible via a web browser, so all files in the package need to be installed relative to the web site’s document root (such as /path/to/htdocs or /path/to/public_html). Easy enough—just run: $ pear config-set php_dir /path/to/htdocs $ pear install -o consultant_channel/Custom_Application $ pear config-set php_dir /usr/lib/php

    Right? Wrong. Choosing this method of installing PEAR-packaged applications is bound to get you into trouble eventually. You may forget to set the configuration back to what it was, or using the -a flag during installation, which automatically installs any necessary dependencies, you may wind up with your dependencies installed in all kinds of strange and unexpected places. In other words—don’t use this method, even if it seems to make sense to do so. The “PEAR Default + Aliasing” Method Using this method, a package would be configured to use the default PEAR Directory structure. Our Custom_Application package might be installed in php_dir]/Consultant/Custom_Application . Once installed there, the installation location could be aliased by creating a symbolic link or Alias by using Apache’s directive. (See http://httpd.apache.org/docs/urlmapping.html for details.) The primary benefit to this method is that if the application being distributed can potentially have multiple installations on the same server, all installations that are symbolically linked or aliased to the location within the PEAR Default directory structure can be upgraded in a single operation. This behavior may be a positive or a negative, depending on your point of view and/or particular situation. The “PEAR Custom Roles” Method Beginning with PEAR 1.4.0, a custom file role may be defined by a package configuration file that dictates a special location where certain files should be installed. Prior to PEAR 1.4.0, package file roles were limited to a pre-defined list of roles: • php: The default and most common role, this type of file is just a PHP file within a package. • ext: Files of this role type should provide a binary extension to PHP.

    August 2005



    PHP Architect



    www.phparch.com

    FEATURE • src: Files of this type are considered source files used for compiling PECL extensions. • test: Regression tests for a package should be given the role test, and will be automatically installed in the PEAR Directory’s test_dir directory. • data: Considered to be supplementary to the package itself, data role files are for documentation, database schemas, examples of package usage, etc. • script: Command-line scripts for use with a package should be assigned the role of script. (Tip: the pear command-line tool is defined as a script role type in the package configuration for PEAR itself.) PEAR 1.4.0 allows package maintainers to expand upon this list of roles. By defining a custom role for a file, you are able to determine where files of that type are installed by default. Plus, with custom roles you are also able to extend PEAR’s configuration commands (cconfig-set/config-show/config-get) to allow for further customization of how particular roles are handled. Since we are exploring the installation of an application that requires files that are directly accessible with a browser, a helpful file role would be a role that automatically sets the installation path for that type of file to a web site’s $DOCUMENT_ROOT. The details of how to create such a role are outside the scope of this article— however, you can find a detailed explanation of creating custom file roles for PEAR packages in the PEAR online documentation. Meanwhile, a custom role called “Web” is included with the files related to this article. (See Role_Web-1.0.0.tgz in the code archive.) With the custom Web role installed, PEAR has been extended to recognize a new configuration directive called web_dir. Using this directive, we are able to issue this command: $ pear config-set web_dir /var/www/htdocs

    Following that command, any file with a role of web will have the value of web_dir prepended to its installation path. Putting Together a Real Package Now that we’ve covered the basic issues of how to install an application with “front end” requirements as a PEAR package in a hypothetical sense, let’s apply these theories to a real-live application. Rather than build a sample application from scratch, we’ll focus applying these theories to an existing application that was built without any consideration for the default PEAR directory structure, or any other PEAR concepts for that matter. In doing so, I hope to illustrate how you can make any of your existing projects PEAR-

    47

    FEATURE

    Release Your Next Project as a PEAR 1.4.0 Package

    installable—as well as any future projects you may develop. PEARifying phpMyAdmin If you’ve been using PHP for any length of time at all, you’re no doubt familiar with the venerable phpMyAdmin ( http://www.phpmyadmin.net/). This popular and robust application has been around for many years, and is used by a very large number of PHP users ranging from novice to expert level. It also happens to be about as far away from the traditional notion of a PEAR package as we can get—making it an excellent guinea pig for learning how turn any PHP application into a PEAR-installable application. Start off with downloading the latest release of phpMyAdmin (as of this writing, the latest version is 2.6.3-pl1). A glance through the directory structure of the standard distribution indicates that phpMyAdmin is intended to be fully accessible with a browser. In other words, the authors intend for the entire distribution to be located at http://www.example.com/path/to/phpMyAdmin_Distrib ution/. Based on this structure, we know that we need

    to use one of the two acceptable methods described above for installing a package that requires directlyaccessible files. The only other significant concern with a PEARified phpMyAdmin is to be sensitive regarding the application’s configuration file. phpMyAdmin stores its configuration in a file called config.inc.php, which is located in the root directory of the distribution. The concern with this layout is that any upgrades handled by the PEAR installer will overwrite files with the same name in a directory structure by default; meaning that any future upgrade would overwrite a customized config.inc.php file with the distributed version of that file, and we will lose all our local customizations. To avoid this problem, we need to change the way our PEAR packaged distribution of phpMyAdmin distributes config.inc.php by renaming the file in the distribution to config.inc.php.dist and instructing users to make a copy of that file to config.inc.php in order to set up their installation. Finally, since we know that phpMyAdmin releases updates on a fairly regular basis, we need to be prepared to update our PEAR packaged version quickly to reflect new releases by the phpMyAdmin team. To do that, we’ll take advantage of the powerful PEAR_PackageFileManager package. PackageFileManager Crash Course Throughout this article, I’ve made several references to “configuring your package.” A PEAR packaged application or library carries with it a file called package.xml (and possibly a file called package2.xml for packages

    August 2005



    PHP Architect



    www.phparch.com

    designed to be compatible with PEAR 1.3.x and PEAR 1.4.x). The package.xml file is the configuration file that tells the PEAR installer how to handle your packaged application. A wide range of configuration options are available, but the primary behaviours we’ll want to keep in mind are how to configure dependencies, how to describe where our packaged applications comes from and what it does, and how to deal with installing individual files. Greg Beaver’s PEAR_PackageFileManager can be a somewhat daunting tool—to the point where many PEAR developers opt to manage their package.xml files manually. However, there is nothing to be afraid of regarding PackageFileManager. A little patience in conListing 1 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66

    ‘doc’, ‘ChangeLog’ => ‘doc’, ‘CREDITS’ => ‘doc’, ‘INSTALL’ => ‘doc’, ‘LICENSE’ => ‘doc’, ‘README’ => ‘doc’, ‘RELEASE-DATE-2.6.3-pl1’=> ‘data’, ‘TODO’ => ‘doc’ ); $options[‘dir_roles’] = array(‘scripts’ => ‘data’); // Set up our custom web role with a wildcard $options[‘roles’] = array(‘*’ => ‘web’); $pkg->setOptions($options); // Set basic application information $pkg->setPackage(‘phpMyAdmin’); $pkg->setSummary(‘MySQL Database Administration Tool’); $pkg->setDescription(‘phpMyAdmin is a tool written in PHP intended to handle the administration of MySQL over the Web. Currently it can create and drop databases, create/drop/alter tables, delete/edit/add fields, execute any SQL statement, manage keys on fields, manage privileges, export data into various formats, and is available in 50 languages. ** PLEASE NOTE: When installing for the first time, copy config.inc.php.dist to config.inc.php, then make changes to that copy.’); $pkg->setLicense(‘GPL’, ‘http://www.phpmyadmin.net/documentation/LICENSE’); // 2.6.3-pl1 becomes 2.6.3pl1

    48

    Release Your Next Project as a PEAR 1.4.0 Package

    figuring a package construction script that uses PackageFileManager pays off with huge dividends later when you need to make a quick release of your package and don’t want be bothered with recalling the specifics of the complex package.xml structure each time you need to make a change. Since we are building a package that leverages PEAR 1.4.0’s capabilities, we will not concern ourselves with a PEAR 1.3.x compatible package in this article—we’ll stay focused on PEAR 1.4.0’s package.xml format, known as package.xml 2.0. Also, since PEAR 1.4.0 is still in an alpha state at this writing, PEAR_PackageFileManager’s support of PEAR 1.4.0 compatible package.xml files is also in an alpha state. We will Listing 1 (cont’d) 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130

    $pkg->setReleaseVersion(‘2.6.3pl1’); $pkg->setAPIVersion(‘2.6.3pl1’); // Stability settings - dev, alpha, beta or stable $pkg->setReleaseStability(‘stable’); $pkg->setAPIStability(‘stable’); // A ‘php’ Package type is a PEAR-style package $pkg->setPackageType(‘php’);

    FEATURE build as much of our package.xml as we can with PackageFileManager, and finish up with a few manual edits before doing the actual packaging. As PackageFileManager’s support for the new package.xml 2.0 format matures, you should eventually be able to eliminate the need for any manual editing of your package.xml configuration files. For full documentation regarding the package.xml 2.0 format, please see http://pear.php.net/manual/en/guide.developers.pa ckage2.php.

    With that overview under your belt, please refer to Listing 1 for a fully commented, step-by-step walkthrough of how we’ll use PackageFileManager to build the package.xml file for our PEAR-installable phpMyAdmin distribution. Please note that you will need the latest alpha version of PEAR_PackageFileManager in order to run the packager script in Listing 1. $ pear config-set preferred_state alpha $ pear install -o PEAR_PackageFileManager

    With the latest alpha PEAR_PackageFileManager (PPPFM from here on) installed, we’re ready to begin packaging a PEAR 1.4.0-ready “front-end” web application.

    // Require at least PHP 4.1.0, as required by phpMyAdmin $pkg->setPhpDep(‘4.1.0’); // Since this is a PEAR 1.4 package, after all ... $pkg->setPearinstallerDep(‘1.4.0a12’); // Set the channel - you’d use your own channel server // information here. $pkg->setChannel(‘pearified.com’); // Add the package maintainer, who may not necessarily // be a package developer. I’m the lead package maintainer // in this case. $pkg->addMaintainer( ‘lead’,’clay’,’Clay Loveless’,’[email protected]’); // Make sure we handle the config.inc.php to // config.inc.php.dist renaming consistently. $pkg->addInstallAs(‘config.inc.php’, ‘config.inc.php.dist’); // Add a release, and notes about this release. $pkg->addRelease(); $pkg->setNotes(‘Initial PEAR-packaged release.’); // Generate the file list $pkg->generateContents(); // // // if

    Debug the package file by default, and create the actual package.xml file if this script is called with a ‘make’ argument. (isset($argv[1]) && $argv[1] == ‘make’) { $pkg->writePackageFile(); // Add the ‘usesrole’ section for our custom file role. // PPFM1.6.0a1 does not have a method for automating this. $pkgxml = file_get_contents( $options[‘packagedirectory’].’package.xml’); $usesrole = “ <usesrole>\n”; $usesrole .= “ web\n”; $usesrole .= “ <package>Role_Web\n”; $usesrole .= “ pearified.com\n”; $usesrole .= “ ”; $pkgxml = str_replace(“”, “\n$usesrole”, $pkgxml); $fp = fopen($options[‘packagedirectory’].’package.xml’, ‘w’); fwrite($fp, $pkgxml); fclose($fp);

    } else { $pkg->debugPackageFile(); } ?>

    August 2005



    PHP Architect



    www.phparch.com

    Packaging phpMyAdmin: Step by Step Before we begin, make sure you’ve got a working directory that contains a PHP script, which we’ll call “ppackager.php,” that knows about your include_path (so that it can find PPFM packages). In that same working directory, download and expand a copy of phpMyAdmin 2.6.3-pl1. As we discussed above, we need to choose a method for handling the installation of the “front end” web files. (The bulk of a phpMyAdmin installation should be considered front end files, due to the way the application is structured.) I recommend using the “Custom File Role” approach, which requires an explicit installation for each website it will be used for. There are a few good reasons for this approach with phpMyAdmin: Configuration file: phpMyAdmin requires a configuration file that lives within the phpMyAdmin directory. If we chose a symbolic link method, we would create a symbolic link to the php_dir]/phpMyAdmin directory as a whole, which means we would also need to modify files within the phpMyAdmin distribution to allow for the inclusion of config files from a location other than within the phpMyAdmin directory. Maintaining a package is enough work as it is—we don’t want to get into maintaining a package of a patched version the application if we can avoid it. Selective upgrades: by explicitly installing phpMyAdmin on each site that needs it, you are able to selectively perform upgrades as new releases are available. In the event of an upgrade that does not go well for some reason, you wind up only affecting a single

    49

    FEATURE

    Release Your Next Project as a PEAR 1.4.0 Package

    site’s phpMyAdmin functionality, limiting any potential disruption. True, this method means slightly more busywork if you need to upgrade multiple installations on one machine, but the peace of mind factor often wins in this scenario. Since we have chosen the “Custom File Role” method for handling the front end files, we need to install a custom file role that suits our needs as discussed in the “PEAR Custom Roles” section above. Please make sure you have installed the Role_Web package, which provides the web custom file role, before continuing. Now we’re ready to go. Lines 14-19 of Listing 1 set the stage by including the package.xml 2.0-aware PPFM, instantiating it, and setting an empty options array. In lines 21-27, we establish that we’re going to be installing phpMyAdmin in a directory called pma, and that we’re building the package from our downloaded copy of phpMyAdmin 2.6.3-pl1. In line 28, we tell PPFM how to read our package directory. We’re using the default “file” method, but if your package directory is actually a working copy checked out from a CVS or Subversion repository, you may use the “Cvs” or “Svn” methods, respectively. Other supported methods include “Perforce,” “XMLOutput,” and “SimpleGenerator.” PEAR-compatible packages generally install documentation and general data files in the respective doc_dir and data_dir locations, inside a subfolder with the same name as the package itself. In lines 31-40, we specifically declare some documentation and informational files to be filed according to this standard. We don’t specify phpMyAdmin’s documentation.html , docs.css or translators.html file, since moving those outside of the main application installation directory would break the functionality of the application. Similarly, on line 41, we specify the entire scripts directory within the standard phpMyAdmin distribution as a data directory, since it contains a variety of files (some scripts, some schema files, etc.) which are not essential for phpMyAdmin to function. The “web application” magic begins on line 44, as we override PPFM’s default file roles to set everything we haven’t already specified as files of the “wweb” role type. That will result in everything else being installed in the location specified by our new, custom web_dir PEAR configuration value. Note that you won’t want to override the defaults in every scenario—but due to the architecture of the phpMyAdmin application, it makes sense to do it in this case. That completes our options setup, so we set the options in line 45. Lines 48-64 define some basic information about phpMyAdmin, lifted nearly verbatim from descriptions on http://phpmyadmin.net. We make a slight adjustment to the official release version number on lines 67 and 68, due to the fact that PEAR does not consider

    August 2005



    PHP Architect



    www.phparch.com

    version numbers with hyphens to be valid. This version of phpMyAdmin is considered stable, so we set the package to the corresponding state in lines 71 and 72. We’re building a PEAR-style package (as opposed to a PECL extension source package or a binary extension distribution), so we set the package type to php in line 75. In lines 78 and 81, we set a few basic dependencies. Since phpMyAdmin does not currently have any external dependencies, let’s digress for a moment and illustrate how we would declare a dependency on the PEAR::DB package if that were an actual requirement of phpMyAdmin. $pkg->addPackageDepWithChannel( ‘DB’, // package name ‘pear.php.net’, // package channel ‘1.6.0’, // minimum version false, // no maximum version ‘1.7.6’ // recommended version );

    But, since we don’t need that dependency for this package, just keep this in mind when you build you own application packages. On lines 85 and 90, we set a little personal information. I have nothing to do with the phpMyAdmin package formally, but every package needs a maintainer and a channel server. So, we set those values here—your values for your own packages will most certainly be different! Line 95 contains an important trick to keep future upgrades to this package from stepping on previous installations. As we discussed above, every phpMyAdmin installation needs a config.inc.php file at the top level of the phpMyAdmin installation directory. To avoid overwriting a customized config.inc.php file in the future, line 95 automatically renames config.inc.php to config.inc.php.dist at install-time. Lines 98-102 complete a few formalities, and we’re then ready to test the package file. If your packager.php script is called with an argument of “make”, lines 108-125 will write out the package file and manually add details relating to our custom file role. Otherwise, the script will display debug output relating to our package file on line 128. Run the script like so: $ php -f packager.php

    If there are no errors, you can write out the package file. $ php -f packager.php make

    You will wind up with a file called package.xml inside the phpMyAdmin distribution directory. Create the package file itself by moving to the phpMyAdmin directory and running the package command.

    50

    FEATURE

    Release Your Next Project as a PEAR 1.4.0 Package

    $ cd phpMyAdmin-2.6.3-pl1 $ pear package

    Presto! Now you’ve got a PEAR-installable distribution of phpMyAdmin. Further Reading Now that you’re sold on the benefits and ease of distributing applications via PEAR 1.4.0 packages, check out these resources for excellent documentation on configuring PEAR post-install scripts, setting up remote PEAR installations over FTP, and building your own PEAR channel server. PEAR post-install script documentation: http://pear.php.net/manual/en/guide.migrating.pos tinstall.php http://pear.php.net/manual/en/guide.developers.p ackage2.tasks.php#guide.developers.package2.tasks .postinstallscript

    Remote

    PEAR

    installation

    over

    FTP:

    http://greg.chiaraquartet.net/archives/31-UsingPEAR-1.4.0-to-install-PEAR-packages-on-a-remotehost.html

    Setting

    up

    a

    PEAR

    channel

    distribution into a PEAR-compatible distribution— essentially replacing the need for you to understand the ins and outs of PEAR_PackageFileManager. It’s a Good Thing™ There is no question that changing the way you distribute your code can feel like a bother, and may not seem like it is worth the effort. However, the flexibility offered by the PEAR 1.4.0 installer can result in smaller distributions and fewer installation headaches for your end users. A mature application packaging system is often a sign of a mature platform or language. As PHP continues to mature into an enterprise-class development platform, it is only fitting that the PEAR installer is maturing along with it. Once the PHP development community fully embraces library and application distribution through via PEAR packaging, we will all benefit from easily installable and reusable application components.

    server:

    http://www.schlitt.info/applications/blog/index.p hp?/archives/308-Set-up-your-own-PEARchannel.html

    Start distributing now If you’re not interested in getting your hands dirty setting up your own PEAR channel server, Pearified.com offers free hosting of open source PEAR 1.4.0-compatible packages, as well as commercial hosting solutions for closed-source and private packages. The service also offers a helpful wizard for converting your non-PEAR

    Have you had your PHP today?

    About the Author

    ?>

    Clay Loveless has been developing web applications with PHP since 1997’s PHP/FI 2.0b6. A New York University-trained actor, Clay now works from California as an independent internet solutions consultant under the name Killersoft (http://www.killersoft.com). He is also actively involved in maintaining Pearified.com. Clay is a husband, father of a very cool one year old son, and a Zend Certified Engineer. Reach Clay via [email protected] .

    To Discuss this article: http://forums.phparch.com/243

    http://www.phparch.com

    NEW !

    ce Lower Pri NEW COMBO NOW AVAILABLE: PDF + PRINT

    The Magazine For PHP Professionals

    August 2005



    PHP Architect



    www.phparch.com

    51

    TEST PATTERN

    Other People’s Code by Marcus Baker

    We shouldn’t be writing code anymore. By now everything should already have been written and we should just be stitching together libraries and prewritten components. We should be, but we often don’t. Are we blindly following a “not invented here” philosophy and needlessly reinventing wheels? Or are we right to be nervous?

    W

    e’re covering a rather unpleasant subject this month: other peoples’ code in your application. If you are a library writer, that statement was probably not very encouraging. After all, your library was written to make everybody’s life easier. The intentions were good, and to reject all that hard work from the angle of nothing more than personal prejudice is, well, rather hurtful. Anyway, expressing distaste for code “reuse” is irrational isn’t it? A stick in the mud “not invented here” attitude? My opening statement is emotional, but is it any less rational than chanting “reuse” as a mantra? Is “reuse” always best? It seems to me this issue is usually debated in emotional terms and this is a shame twice over. Firstly, the decision to import a library is a crucial one in the day to day building of an application, but more tragically, because the issues are already well understood in the literature. I am going to carry out a little survey of some of the problems and their solutions. Hopefully we can dispel a

    August 2005



    PHP Architect



    www.phparch.com

    vague, but actually quite justified fear, by replacing luck with a carefully weighed design decision. Fear of the Unknown The problem with a library is that you cannot just drop it into your code. It will create work. In fact, it causes work even before you find it. Does it do everything you need? Does it do everything you need in the near future? If it doesn’t, how easy is it to extend? Can you understand how it works? Can you colleagues? Does it even work at all, or does it have bugs? If it has bugs, how will you get them fixed? How often does a new version come out and how will you manage updates? You have documentation to read, e-mails to send, APIs to pore over and code to review. A lot of these problems apply to your own code as well, but when discovered can be easily fixed. You understand your own code and so there is no barrier to refactoring. When importing a library you have to get to grips with someone else’s way of doing things. You

    52

    TEST PATTERN

    Other People’s Code

    then have to learn the little details, and after that, you finally have to fix the problems. Additionally, you cannot change the interface, for if you do, you won’t be able to re-import a later version. It’s a bit like stirring soup with bricks in it. You would like to stir the code around, but the there are fixed lumps that have to be moved around whole. So, how do we deal with all of these problems? Well our first strategy is easy. Just follow your fears... Going Your Own Way If you are going to refactor a piece of code repeatedly, don’t use a library. This is most likely to happen when the module is critical, especially if there is business value tied up with it. This is easiest to explain with an example. Suppose you are maintaining a web site and need to send occasional uptime e-mails. You would be crazy to waste time coding the mail functions yourself. This is a small part of the system and the messages can be simple plain text. Just use the phpmailer library from

    directly into your version control system as if it was your own, pasting the entire library into your project. The effect is the same as cutting and pasting straight into your editor. It’s a one time operation and from now on, you will be treating the code as your own. This is an intermediate step to going your own way. Rather than code from scratch, you bring in a library as a starting point. The downside of this operation is that for a long time you will have a big lump of code in your system that you don’t understand. You also have to weigh the extra refactoring work against the short term advantage you gain with this approach. The refactoring work can be reduced if the library ships with regression tests, or is small. If not you will have to wrap tests around it as a first step. The “cut’n’paste” option is not very sophisticated. I include it here, because projects often end up using it by accident when they start to modify library code without thinking. It does work well with very small libraries.

    “The decision to import a library is a crucial one in the day to day building of an application.” Sourceforge, or even PHP’s own mail() function. Suppose now that you are writing the core of an Ecard service. Customers are paying for high quality emails with all sorts of attachments and rich formatting. The E-card business will likely want to record the delivery results when sending these mails. Perhaps delivery failures are diagnosed with trackback images or by parsing the SMTP conversation. A simple library will no longer cut it. Even if you find one that does everything you want, you will be adding new features as fast as your development team will allow. You don’t want to wait for external authors to respond to your support requests. Pretty soon, you will have the most featurerich mailer in the business, making all other mail options out there obsolete. If you don’t, then you aren’t doing a good job competing. This leads to what Eric Evans (in Domain Driven Design, Addison Wesley) calls the “SegregatedCore” pattern. You identify the critical features of the business and then keep complete control of that code. Development expenditure here is well spent, so spend it. Cut’n’Paste What I mean by “cut’n’paste” is importing the library

    August 2005



    PHP Architect



    www.phparch.com

    Library Import If you finally decide to bite the bullet and use a library, the first problem is how to bring a foreign object into your version control. This is an essential step. You need to keep your application in sync with the library. Tools like RPM, apt-get or PEAR channels can download dependent packages automatically, but I don’t advise this for mission critical applications. Using an automatic tool that can be very convenient on a development box, but can turn into a nightmare when you are trying to debug subtle version conflicts on a production server. Keep the library in the application code repository if you have any suspicions about backward compatibility. These days I do this by habit. One simple solution is to import the release tarball as a binary file. This can cause management hassle, because you have to make sure the tarball is unpacked before use, but this issue is trivial. The library can only be extended without modifying its core code, say by subclassing or by passing in Strategy/Policy objects. A good library should behave like this anyway of course. If it doesn’t, one problem is the application of patches. If you find a bug and have to work on the code, you will want your own version checked back into the repository. The more sophisticated plan involves unpacking the

    53

    TEST PATTERN

    Other People’s Code

    source and then using the advanced features of your version control system. If you are using CVS, this power is buried in the import command. Usually, the only time most developers touch this command is when first creating a project, because it takes the contents of a directory and creates a first version of every file in the repository. It turns out that you can use this command from within your development tree, although it requires special care. Say you are importing a project called “greatlib,” created by “marcus”. You have already unpacked the source into a folder called “download/greatlib” and the convention in your team is to have all external libraries in a repository folder called “external”. Here is the CVS command to be issued from the “download/greatlib” directory... cvs -d:ext:[email protected]:/var/cvs import \ -ko external/greatlib marcus version_1_0

    I doubt you have memorized the CVS manual, so here’s a description of the pieces in order. The -d flag is the location of your repository, so your parameter will look very different from mine. The -ko argument tells CVS not to do the dollar substitutions. Otherwise any $Id$ tags will have your name inserted rather than the original author, and you will get the blame for anything that’s wrong. The next parameter injects the new code into the repository at “external/greatlib”. You will see this new directory on the next check-out. The parameter after that is the vendor tag—that’s me—and finally there is the release tag under which you check it in. These tags are the key to our strategy, as we can always get back to this point. Meanwhile, we probably have some fixing to do. One irritation with CVS is that any binary files, such as images, will have been checked in as text. For each one of these you need to issue the following command… cvs admin -kb binary_file

    ...where binary_file is the file to modify. Do this before you update your sandbox or CVS will make you will suffer, horribly. After all of this has been done, it’s plain sailing. Say you find a bug and you dutifully fix it and then send the library author a patch. The author eventually gets around to applying your patch, fixes a few other problems on top and then issues another release, version 1.1. What do we do now? Simple. We repeat the import command, but change the release tag from version_1_0 to version_1_1. CVS will then complain of conflicts. The import command does something very clever. It actually imports all of these releases into a separate branch. In our case, it’s called “marcus”, but in the CVS documentation, it’s nicknamed the vendor branch. As

    August 2005



    PHP Architect



    www.phparch.com

    we want to ditch our patched version in the main branch in favour of the new improved one, we just tell CVS to switch lanes. We go to our sandbox and enter... cd exernal/greatlib cvs admin -bmarcus .

    ...and our patched version appears to disappear on the next sandbox update. There are lots of other tricks with CVS import, such as the ability to manage customization of a library and to merge those changes with successive releases. It’s worth reading that part of the manual for whatever version control software you use, as these facilities are strategic. The Ten Foot Pole Just because the version control can cope with a foreign object, it doesn’t mean that we can. Changes to the library interface, either through our own fixes or through later releases, will affect our application. Actually, they will drive us nuts. Even if the library doesn’t change, we may want to swap it for another one when we need more features. This is known as a package dependency (Figure 1). The dependency is shown with the arrow in UML package diagrams. If you only ever learn one piece of UML, make it this arrow. It speaks volumes. Counter-intuitively, libraries change more often than application code. The core code of your system has probably come about through lengthy discussions with the project stakeholders, a requirements gathering stage and steady evolution of the business rules. That costs money and it’s work that you won’t want to repeat. By contrast, libraries are discarded or replaced not only on the merest whim of the application, but also for technical reasons. Having applications depend on libraries means that your cheap unstable code is forcing rewrites in your expensive stable code. The situation is even worse than it first appears (Figure 2). Rather than avoid touching such libraries with a ten foot pole, there is a way of isolating your application. Usually you only want a subset of a library’s services. If you can define the functionality you need as an abstract interface, then that interface can become a kind of stable contract between the two parties. The interface does not have to be formal or elaborate, just a few method signatures. It helps to hide class names behind factory methods, though, in case you decide to switch libraries altogether. Otherwise, you will have to edit a lot of new statements. The application code is written to fit the interface, and if the underlying library is altered, some adapter code is placed in between to maintain consistency. The application sees only the interface, but so does the adapter (Figure 3). Notice that one of those magic arrows has now changed direction. For this reason, this trick is known as the “Dependency Inversion” pattern.

    54

    TEST PATTERN

    Other People’s Code

    Figure 1

    Figure 2

    Figure 3

    Now the library/adapter follows the application/interface and not the other way around. All of this may sound like a lot of extra work, but in reality, the adapter code is very small and can be skipped, initially, if the contract matches the library. If you aren’t using PHP 5, then the adapter and interface will be the same thing anyway. The DependencyInversion trick is also very common, even if it’s not well understood. If you are using a database connection library such as ADODB or PEAR::DB then you are already coding to an interface, or at least an abstract class. That’s why it’s relatively easy to switch databases as long as you use only a common set of SQL commands. There is a lot of literature on DependencyInversion and it’s main exponent is Robert Martin (see Agile Software Development, Prentice Hall). Tips for a Library Author There is no market for a super-duper, do-everything module. If part of an application is important, then the development team will write it themselves. Libraries live on the periphery of an application. On the other hand, if your library is small then it won’t be worth anyone’s effort to manage it as a dependency. For small components, just go for clear code and ship regression tests.

    August 2005



    PHP Architect



    www.phparch.com

    This allows your target audience to simply paste the code straight into their application or import it as a tarball. The most useful libraries fall in the middle ground between these two extremes. A library that is easy to manage will have a narrow stable interface that is fairly abstract. This makes it easy to wrap and easy to understand. It also makes it easy to explain. The interface does not have to be overly simplistic; it could be a single class. After all, it will probably be wrapped in an adapter, anyway. Having small groups of cooperating classes will usually be better than a facade with lots of options. As a bonus, such libraries are easier to extend. Your clients don’t want to actually look at your implementation except as a last resort. This means that good upfront documentation explaining the capabilities is vital. If your only documentation is a PHPDoc API then you are sending the wrong message. Timely response to e-mails will usually equate to rapid bug fixes, and so gives confidence. It is also important to have a clear transition between versions, keeping backward compatibility for a while if possible. Importing a release is tricky enough, without having to edit broken dependencies at the same time. In the end, using other peoples’ hard work is nice. Both the users and authors benefit, not just immediately, but also for being connected to a growing community. Libraries can hide a mysterious technical task, such as storage or communication, behind a few simple lines of code and can reduce the tedious to the trivial. Other peoples’ code can be a wonderful thing. It shouldn’t be anything to worry about.

    About the Author

    ?>

    Marcus Baker works at Wordtracker (www.wordtracker.com) as Head of Technical, where his responsibilities include the development of applications for mining Internet search engine data. His previous work includes telephony and robotics. Marcus is the lead developer of the SimpleTest project, which is available on Sourceforge. He's also a big fan of eXtreme programming, which he has been practising for about two years.

    To Discuss this article:

    http://forums.phparch.com/245 55

    CLASSROOMS VIRTUAL

    Online Training Courses from php|architect Zend PHP Essentials Our introductory PHP course, Zend PHP Essentials, was developed for us and Zend Technologies by PHP expert Chris Shiflett, co-founder of the PHP Security Consortium. This 19-hour course provides a thorough introduction to PHP development, with particular care to "doing things right" by covering security, performance and the best development techniques. Rather than cramming as much theory as possible, PHP Essentials provides a thoroughly practical approach to learning PHP—thus ensuring that each student will be able to write good PHP code in a real-world setting by the end of the course. Zend PHP Certification Training

    Zend Professional PHP Development

    If you want to become a Zend Certified Engineer, this course is the best preparation tool that you'll ever find! Designed by some of the same Subject Matter Experts who also helped write the exam itself, this course covers every single topic that is part of the exam. The Zend PHP Certification Training (course) provides a complete overview of the exam, and doubles as an excellent refresher course in PHP for any developer.

    This is our advanced course for the professional PHP developer. This course picks up from where PHP Essentials ends and provides a thorough, in-depth analysis of advanced features found in both PHP 4 and PHP 5, including object-oriented programming and design patterns, XML development, regular expressions, encryption, e-mail manipulation, performance management and advanced databases.

    Course

    Description

    Start Dates

    Zend PHP Essentials

    • Covers PHP 4 and PHP 5 • Provides a thorough practical Every month introduction to PHP • Covers security and performance

    7 Sessions 19 Hours 3 Weeks

    YES

    -

    $769.99 US ($999.99 CAD)

    Zend PHP Certification Training

    • Covers every topic in the exam • Provides an excellent refresher course for PHP at all levels

    Every month

    7 Sessions 19 Hours 3 Weeks

    YES

    Zend PHP Essentials

    $644.99 US ($838.99 CAD)

    Every month

    7 Sessions 19 Hours 3 Weeks

    YES

    Zend PHP Essentials

    $769.99 US ($999.99 CAD)

    • Covers advanced PHP 4 and PHP 5 topics

    Zend Professional • Perfect for going "beyond the PHP Development basics" and learning the true

    Duration

    Tutoring Prerequisites

    Cost

    power of PHP

    • All our courses are delivered entirely online using an innovative system that combines the convenience of the Internet with the unique experience of being in a real classroom. • All sessions take place in real time, and the students can interact directly with the instructor as if they were in a real classroom either via voice or text messaging. • In most cases, our system requires no software installation and works with the majority of operating systems and browsers, including Windows, Mac OS and Linux, as well as Internet Explorer, Firefox and Safari. • All courses include a generous amount of homework and in-class exercises to ensure that the students assimilate each topics thoroughly. • Tutoring is available (via e-mail) throughout the duration of the entire course. • Each class includes a complete set of recordings that the students can peruse at their leisure.

    For more information, visit our website at http://www.phparch.com/phptraining or call us toll-free at (877) 630-6202 (416-630-6202 outside Canada and the U.S.)

    PRODUCT REVIEW

    PHP Runner 2.0

    Good Prototyping on the Cheap by Peter B. MacIntyre

    We shouldn’t be writing code anymore. By now everything should already have been written and we should just be stitching together libraries and prewritten components. We should be, but we often don’t. Are we blindly following a “not invented here” philosophy and needlessly reinventing wheels? Or are we right to be nervous?

    W

    ell it has finally happened! I have found the solution to fast prototyping in the MySQL / PHP world. Now there may be others out there, but this is the first one I have seen that is both practical and functional. I am talking about FAST prototyping based on an existing database structure. PHPRunner is that product. This month I am reviewing a product that will actually become a regular addition to my “tool belt”. As is my tradition, I always try to give the developers of the product I am reviewing the first word, or at least a very early word. They have this to say about their product: PHPRunner builds visually appealing web interface for any local or remote MySQL database. Your web site visitors will be able to easily search, add, edit, delete and export data in MySQL database. Advanced

    August 2005



    PHP Architect



    security options allow to build password-protected members only Web sites easily. PHPRunner is simple to learn so you can build your first project in just fifteen minutes [SIC]. Let’s Get Started This product is almost entirely “wizard” based. The design and layout of the interface is quite easy to navigate, and the installation (also a wizard style interface) is “as smooth

    as a trout’s ear”, to use a local colloquialism. Figure 1 shows the initial look to the application once it is installed. Notice that there are 2 buttons on this screen that help the user to get started: Live Demo and Tutorial. Both of these helpful avenues were quite good, but I won’t spend page space here reviewing them. Now, keep in mind that this tool is primarily used for quick prototyping, based on an existing database–at least that is where I see its

    PRODUCT INFORMATION PHP

    5+

    OS

    Windows

    Product Version

    2.0

    Price

    $199.00

    Web Address

    http://www.xlinesoft.com/

    www.phparch.com

    57

    PRODUCT REVIEW

    PHP Runner 2.0

    reviews. This is just a one-to-one relationship so it is not too complex.

    Figure 1

    First Looks Now, let’s move further into the wizard. After you digest the opening screen, you will be taken on the journey of creating a connection to an assumed MySQL database. Since this is the main point behind this tool, it won’t be of any real benefit to follow this through without a database. You will see on the second page of this wizard that it has the ability to connect to other data sources like MS-Access, SQL Server, and PostgreSQL. We will focus on the MySQL connection type here. On the third screen, you are asked for your connection information, and upon offering the correct credentials, you are shown a list of existing databases on the server. This is shown in Figure 2. Note that as you move along there is this concept that you are building a “project”, and you can therefore save any of your selections along the way for later use or alteration. This is quite handy since there are 12 steps to this creation wizard, in its most basic form. Figure 3 shows that I have selected the two tables from the connected database and that I am currently working on the “rreviews” table. There are many options here to set so be sure that you understand the ones that you play with. Most important, here, is that you can make a menu selection based on this table name. You will see this in action later on. Also, you can create a parent-child relationship here if you want to. I have made this type of relationship connection between the reviews and books tables.

    Figure 2

    Figure 3

    highest value. This is best served, naturally, if the database being used is well normalized. In my testing, I used a database structure of only 2 tables, but they were related by a primary/foreign key linkage. The tables are based on a book review website that I maintain and I used the tables of books and their respective

    August 2005



    PHP Architect



    www.phparch.com

    Moving on Now that the basic connections are established, I am skipping over the next few pages in the wizard, as they are quite self-explanatory. You will be able to select which fields should be included on the report screens, you can fine-tune the generated SQL, and you can select which table fields will be available on the generated search screens. Page 8 in this wizard is the next highlight. Here, you can manage the way in which the data will be presented to the end user, which column labels will be used, and in which format the data will be managed, on the generated add and edit screens. Figures 4 and 5 respectively show the data formatting management grid and the details that surround the changing of a date column (as an example). Figure 6 shows the last page that I will cover in this review. It allows the designer to create a simple login page for the web site that is being built. The neat thing here is that you can either hard code a simple username and password combination, or you can direct it to use

    58

    PRODUCT REVIEW

    PHP Runner 2.0

    Figure 4

    Figure 5

    Figure 6

    existing fields in an existing table. I have chosen the latter here as I had already designed a username and password requirement for my database. The last 2 pages in the design wizard are used for laying out any basic look and feel formatting for the generated report pages, and then giving the designer a page with which to direct the generation of all the code that is to be created. A side note here: I have been researching the PEAR::DB extension and some of its PEAR relatives, lately, and a two of them that interested me a great deal were the DB_Dataobject_Formbuilder and DB_Dataobject sub-libraries, which according to their documentation should be able to do something similar to PHPRunner. This makes me wonder if this product is built on that PEAR technology, or if it is a unique creation on its own. In the case of the former, the creators of PHPRunner have taken the PEAR technology and made it sing.

    Figure 7

    August 2005



    PHP Architect



    www.phparch.com

    59

    PRODUCT REVIEW

    PHP Runner 2.0

    Figure 8

    The Grand Finale Having said all this, it is time to see what this product can really produce. The following screens are all taken from the design that I had engineered in the 12 pages of the PHPRunner wizard. Figure 7 shows the generated login page, Figure 8 shows the basic look of the main generated page, and Figure 9 shows one of the generated data editing pages. The supporting web site will give you some additional tips and tricks that you can add to your generated site. SuperTop and SuperBottom, for example, are ways to add your own banner and footers to the generated pages. This can give a simple prototype a really nice look, which is great for perspective clients. There is also a web forum at the product’s site, where you can ask questions from other users and get support from the staff. Summary PHPRunner is the answer to quick PHP development prototyping. I mentioned in my introduction that this would become part of my regimen for PHP development. Without doubt, it will be used on my very next project. It is not that often that I get this excited about an add-on tool for PHP, but the practical application and use of this tool will be widely recognized before too long, in my opinion. There is some room for improvement, but not much (I would like to

    August 2005



    PHP Architect



    Figure 9

    see their list of improvements for version 3, as that would be quite interesting, indeed) and with the price being what it is, these guys

    should become quite wealthy in the

    www.phparch.com

    near future. I give this product 4.5 out of 5 stars. About the Author

    ?>

    Peter MacIntyre lives and works in Prince Edward Island, Canada. He has been and editor with php|architect since September 2003. Peter’s web site is at http://paladin-bs.com

    60

    SECURITY CORNER

    Shared Hosting by Peter B. MacIntyre

    Welcome to another edition of Security Corner. This month, I have chosen a topic that is a concern for many PHP developers: shared hosting. Through my involvement with the PHPCommunity.org project, my contributions to mailing lists, and my frequent browsing of PHP blogs and news sites, I have seen this topic brought up in various incarnations. Some people are concerned about hiding their database access credentials, some are concerned about safe_mode being enabled or disabled, and others

    Shared Hosting Since the advent of HTTP/1.1 and the required Host header, shared hosting has become very popular. Prior to HTTP/1.1, there was no direct way for a Web client to identify the domain from which it wanted content. The browser simply used to determine the IP address associated with the domain entered by the user, and sent its request there. An HTTP 1.0 request looks something like the following, at a minimum: GET /path/to/index.php HTTP/1.0

    Notice that the URL presented in the request does not include the domain name. This is because this is unnecessary information under the assumption that only one domain is served by the particular Web server (and that domains have a one-to-one

    August 2005



    PHP Architect



    just want to know what they should be concerned about, if anything. As a result, I have decided to address these concerns in as much detail as possible, so that you will have a better understanding and appreciation of shared hosting. After reading this article, you may decide that there is nothing for you to be concerned about, or you may be terrified. Regardless, I hope to at least provide you with clarity.

    relationship with IP addresses). With HTTP/1.1, Host becomes a required header, so this request, at a minimum, must be expressed as follows: GET /path/to/index.php HTTP/1.1 Host: www.example.org

    With this format, a single Web server (with a single IP address) can serve an arbitrary number of domains, because the client must identify the domain from which it intends to be requesting content. As a direct result, a hosting company can host many domains on a single server, and it is not necessary to have a separate public IP for each domain. This yields much more inexpensive hosting and has spurred a tremendous growth in the Web itself. Of course, this has been a driving force behind early PHP adoption as well. The downside to shared hosting is

    www.phparch.com

    that it incurs some security risks that do not exist in a dedicated server environment. Some of these risks are mitigated by PHP's safe_mode directive, but a solid understanding of the risks is necessary to appreciate what safe_mode does (and what it doesn't). Because of this, I will begin by introducing some of the unique risks associated with shared hosting.

    Filesystem Security A true multi-user operating system, such as Linux, is built upon a fundamentally secure approach to user permissions. When you create a file, you specify a set of permissions for that file, either explicitly or implicitly by virtue of the fact that you are creating that file within a specific context. This is achieved by assigning each file both user and group owner-

    62

    SECURITY CORNER

    Shared Hosting

    ship as well as a set of privileges for three groups of people: 1. The user who owns the file 2. All users in the group 3. All users on the server These categories of people are referenced as user, group, and other, respectively. The privileges that you can assign each category of user include read, write, and execute (there are some other details, but they are irrelevant to the present discussion). To illustrate this further, consider the following file listing: -rw-r--r-1 chris 12:34 myfile

    shiflett

    4321 May

    21

    Listing 1

    This file, myfile, is owned by the user chris and the group shiflett. The permissions are identified as -rw-r-r--, and this can be broken into the leading hyphen (indicating a normal file, as opposed to, say, a directory), and then three groups of permissions: execute) 1. rw- (read, write, no execute) 2. r-- (read, no write, no execute) 3. r-- (read, no write, no These three sets of permissions correspond directly to the three groups of users: user (chris), group (shiflett), and other. Linux users are probably familiar with these permissions and how to change them with commands such as chown and chmod . For a more thorough explanation of filesystem security, see http://www.linuxsecurity.com/ docs/LDP/Security-HOWTO/file-security.html . As a user on a shared host, it is unlikely that you will have read access to many files outside of your own home directory. You certainly shouldn't be able to browse the home directory or document root of other users. However, with a simple PHP script, this can be possible.

    Browsing with PHP For this discussion, we'll assume that the Web server is Apache and that it is running as the user nobody. As a result, in order for Apache to be able to serve your Web content, that content must be readable by the user nobody. This includes images, HTML files, and PHP scripts. Thus, if someone could gain the same privileges as nobody on the server, they would at least have access to everyone's Web content, even if precautions are taken to prevent access to any other user. Whenever Apache executes your PHP scripts, it of course does so as the user nobody. Combine this with PHP's rich set of filesystem functions (http://www.php.net/filesystem), and you should begin to realize the risk. To make the risk clearer, I have written a very simplistic filesystem browser in PHP (See Listing 1). This script outputs the current setting for the safe_mode directive (for informational purposes) and allows you to

    August 2005



    PHP Architect

    browse the local filesystem. This is an example of the type of script an attacker might write, although several enhancements would likely be added to make malicious actions more convenient. One of the first places an attacker might want to glance is at /etc/passwd. This is achieved by either browsing there from the root directory (where the script begins) or visiting the URL directly (by calling the script with ?file=/etc/passwd). This gives an attacker a list of users and their home directories. Another file of interest might be httpd.conf. Assuming each user's home directory has a directory called public_html for their respective document roots,



    www.phparch.com

    1 \n”; 3 4 if (ini_get(‘safe_mode’)) 5 { 6 echo “[safe_mode enabled]\n\n”; 7 } 8 else 9 { 10 echo “[safe_mode disabled]\n\n”; 11 } 12 13 if (isset($_GET[‘dir’])) 14 { 15 ls($_GET[‘dir’]); 16 } 17 elseif (isset($_GET[‘file’])) 18 { 19 cat($_GET[‘file’]); 20 } 21 else 22 { 23 ls(‘/’); 24 } 25 26 echo “\n”; 27 28 function ls($dir) 29 { 30 $handle = dir($dir); 31 while ($filename = $handle->read()) 32 { 33 $size = filesize(“$dir$filename”); 34 35 if (is_dir(“$dir$filename”)) 36 { 37 if (is_readable(“$dir$filename”)) 38 { 39 $line = str_pad($size, 15); 40 $line .= “$filename/”; 41 } 42 else 43 { 44 $line = str_pad($size, 15); 45 $line .= “$filename/”; 46 } 47 } 48 else 49 { 50 if (is_readable(“$dir$filename”)) 51 { 52 $line = str_pad($size, 15); 53 $line .= “$filename”; 54 } 55 else 56 { 57 $line = str_pad($size, 15); 58 $line .= $filename; 59 } 60 } 61 62 echo “$line\n”; 63 } 64 $handle->close(); 65 66 return true; 67 } 68 69 function cat($file) 70 { 71 ob_start(); 72 readfile($file); 73 $contents = ob_get_contents(); 74 ob_clean(); 75 echo htmlentities($contents); 76 77 return true; 78 } 79 ?>

    SECURITY CORNER

    Shared Hosting

    an attacker can browse another user's Web content by calling the script with ?dir=/home/victim/public_html/. A security-conscious user will most likely keep sensitive configuration files and the like somewhere outside of document root. For example, perhaps the database username and password are stored in a file called db.inc and included with code similar to the following: include('../inc/db.inc');

    This seems wise, but unfortunately an attacker can still view this file by calling the browse.php script with ?file=/home/victim/inc/db.inc. Why does this necessarily work? For the include() call to be successful, Apache must have read access to the file. Thus, this script must also have access. In addition, because the user's login credentials are often the same as the database access credentials, this technique will likely allow an attacker to compromise any account on the server (and launch additional attacks from compromised accounts). There is also the potential for an attacker to use this same script to gain access to anyone's session data. By just browsing the /tmp directory (?dir=/tmp/), it is possible to read any session that is stored there. With a few enhancements to the script, it could be even easier to view and/or modify session data from these files. An attacker could visit your application and then modify the associated session to grant administrator access, forge profile information, or anything of the like. And, because the attacker can browse the source to your applications, this doesn't even require guesswork. The attacker knows exactly what session variables your applications use. Of course, it is much safer to store session data in your own database, but we have just seen how an attacker can gain access to that as well. Luckily, safe_mode helps prevent these attacks.

    The safe_mode Directive The safe_mode directive is specifically designed to try to mitigate some of these shared hosting concerns. If you practice running the script from Listing 1 on your own server, you can experiment with enabling safe_mode and observing how much less effective the script becomes. When safe_mode is enabled, PHP checks to see whether the owner of the script being executed matches that of the file being opened. Thus, a PHP script owned by you cannot open files that are not owned by you. Your PHP scripts are actually more restricted than you are from the shell when safe_mode is enabled, because you likely have read access to files not specifically owned by you. This strict checking can be relaxed somewhat by enabling the safe_mode_gid directive, which relaxes the checking to the group instead of the user. Because safe_mode can cause problems for users who have a legitimate reason to access files owned by another user, there are a few other directives that allow even more flexibility. The safe_mode_include_dir directive can specify one or more directories from which users can include() files, regardless

    August 2005



    PHP Architect



    www.phparch.com

    of

    ownership.

    I

    encourage

    you

    to

    read

    http://www.php.net/features.safe-mode for more informa-

    tion. A similar PHP directive is open_basedir. This directive allows you to restrict all PHP scripts to only be able to open files within the directories specified by this directive, regardless of whether safe_mode is enabled.

    Bypassing safe_mode Is there a known flaw in safe_mode that allows people to bypass it? Not to my knowledge, but keep in mind that safe_mode only protects against people using PHP to gain access to otherwise restricted data. safe_mode does nothing to protect you against someone on your shared server who writes a similar program in another language. In fact, the manual states: "It is architecturally incorrect to try to solve this problem at the PHP level, but since the alternatives at the web server and OS levels aren't very realistic, many people, especially ISP's, use safe mode for now." Consider the following CGI script written in Bash: #!/bin/bash echo "Content-Type: text/plain" echo "" cat /etc/passwd

    This will output the contents of /etc/passwd as long as Apache can read that file. So, we're back to the same dilemma. While the attacker can't use the script in Listing 1 to browse the filesystem when safe_mode is enabled, this doesn't prevent the possibility of similar scripts written in other languages.

    What Can You Do? You probably knew that a shared host was less secure than a dedicated one long before this article. Luckily, there are some solutions to a few of the problems I have presented, but not all. There are basically two main steps that you want to take on a shared host: 1. Keep all sensitive data, such as session data, stored in the database. 2. Keep your database access credentials safe. The question is: how do you achieve the second goal? If another user can potentially have access to any file that we make available to Apache, it seems that there is nowhere to hide the database access credentials. My favorite solution to this problem is one that is described in the PHP Cookbook by David Sklar and Adam Trachtenberg. The approach is to use environment variables to store sensitive data (such as your database access credentials). With Apache, you can use the SetEnv directive for this: SetEnv DB_USER "myuser" SetEnv DB_PASS "mypass"

    Set as many environment variables as you need using

    64

    SECURITY CORNER

    Shared Hosting

    this syntax, and save this in a separate file that is not readable by Apache (so that it cannot be read using the techniques described earlier). In httpd.conf, you can include this file as follows: Include "/path/to/secret-stuff"

    Of course, you want to keep these include statements within each user's VirtualHost block, otherwise all users could access the same data. Because Apache is typically started as root, it is able to include this file while it is reading its configuration. Once it is running as the user nobody, it can no longer access this file, so other users cannot access this information with clever scripts. Once these environment variables are set, you can access them in the $_ENV array. For example: mysql_connect('localhost', $_ENV['DB_USER'], $_ENV['DB_PASS']);

    Because this information is stored in $_ENV, you need to take care that this array is not output in any of your scripts. In addition, a call to phpinfo() reveals all environment variables, so you should ensure that you have no public scripts that execute this function.

    August 2005



    PHP Architect



    www.phparch.com

    Until Next Time... Hopefully, you now understand some of the risks involved with shared hosting and can take some steps to mitigate them. While safe_mode is a nice feature, there is only so much help it can provide in this regard. It should be clear that these risks are actually independent of PHP, and this is why other steps are necessary. As always, I'd love to hear about your own solutions to these problems. Until next month, be safe.

    About the Author

    ?>

    Chris Shiflett is an internationally recognized expert in the field of PHP security and the founder and President of Brain Bulb, a PHP consultancy that offers a variety of services to clients around the world. Chris is a leader in the PHP industry, and his involvement includes being the founder of the PHP Security Consortium, the founder of PHPCommunity.org, a member of the Zend PHP Advisory Board, and an author of the Zend PHP Certification. A prolific writer, Chris has regular columns in both PHP Magazine and php|architect. He is also the author of the HTTP Developer's Handbook (Sams) as well as the highly anticipated PHP Security (O'Reilly). You can contact him at [email protected] or visit his web site at http://shiflett.org/.

    To Discuss this article: http://forums.phparch.com/246

    65

    You’ll never know what we’ll come up with next For existing subscribers

    NEW

    Upgrade to the Print edition and save!

    LOWER PRICE! Login to your account for more details.

    php|architect

    Visit: http://www.phparch.com/print for more information or to subscribe online.

    The Magazine For PHP Professionals

    Address: _________________________________________ City: _____________________________________________ State/Province: ____________________________________

    E!

    WE

    RP

    RIC

    W

    *US Pricing is approximate and for illustration purposes only.

    NE

    Name: ____________________________________________

    Your charge will appear under the name "Marco Tabini & Associates, Inc." Please allow up to 4 to 6 weeks for your subscription to be established and your first issue to be mailed to you.

    Choose a Subscription type:

    Canada/USA International Air Combo edition add-on (print + PDF edition)

    LO

    php|architect Subscription Dept. P.O. Box 54526 1771 Avenue Road Toronto, ON M5M 4N5 Canada

    $ 77.99 CAD $105.19 CAD $ 14.00 CAD

    ($59.99 US*) ($80.89 US*) ($10.00 US)

    ZIP/Postal Code: ___________________________________ Country: ___________________________________________ Payment type: VISA Mastercard

    American Express

    Credit Card Number:________________________________ Expiration Date: _____________________________________

    Signature:

    Date:

    *By signing this order form, you agree that we will charge your account in Canadian dollars for the “CAD” amounts indicated above. Because of fluctuations in the exchange rates, the actual amount charged in your currency on your credit card statement may vary slightly.

    E-mail address: ______________________________________ Phone Number: ____________________________________

    To subscribe via snail mail - please detach/copy this form, fill it out and mail to the address above or fax to +1-416-630-5057

    exit(0);

    Home is Where the Index is by Marco Tabini

    T

    hose of you who read the June issue of php|a know that, for the last couple of months, I’ve had a new toy to play with called Xapian. For those of you who haven’t had the pleasure of reading my article on this topic (shame on you!), Xapian is a search engine library capable of indexing full-text documents and allowing for search operations on them. Best of all, it’s open source, licensed under the GPL and well supported by a community of nice people which, surprisingly, have no ego issues whatsoever. When I first came into contact with this wonderful library, I figured it might just be what we needed for the search operations that (at least in theory) should take place on our website. Right now, these are all handled using different methods: the article search and news search run through the full-text indexing engine provided by our MySQL database, while the discussion board uses the built-in search func-

    August 2005



    PHP Architect



    tionality provided by FUDForum, the software that we use to power our forums. The effectiveness of these methods varies considerably, but is generally far short than what a proper search engine—like, say Google or Yahoo!—can cook up when it comes to providing a powerful search environment. Now, I know what you’re thinking—he can’t possibly be comparing something like the full-text indexing capabilities of a relational database, or even an open-source search engine library, to the custom software developed by two of the largest pools of brainpower on the planet. True, I can’t— but not because the search software itself is necessarily better or worse on either side of the equation. But I’m getting ahead of myself. Let me go back to about the middle of May, when I started playing around with Xapian. The first thing that I did was rewrite the bindings that allow me to access the library directly from a PHP script. The

    www.phparch.com

    bindings that come with the library by default have several drawbacks—mostly the fact that they are automatically generated and tend to produce a huge extension (at least on my system, it was well over 2MB, which seems hardly practical in a production environment) and that they don’t work on PHP 5. Given that I was looking exactly for an excuse to use PHP 5, I figured I’d write my own bindings, which is neither difficult nor time-consuming, particularly since all that I needed to do was create an interface to a library—a task that involves little in the way of actual programming skills (and, therefore, perfect for me). I figured that, by the time I would have had the bindings done, writing the search engine itself would just be a matter of indexing the data and then running queries against the database. As it often happens, reality came around and slapped me in the face so hard that, a month and a half later, I was bare-

    67

    EXIT(0);

    Home is Where the Index is

    ly able to get a decent set of results back—and I was far away from actually having a working search engine. You see, the difficulty was not in making the database itself index the data and return results—it was in organizing the application in a meaningful way. Although the functionality provided by Xapian is certainly not at the same level as the search functionality provided by Google’s software, I think that the former could easily give the latter a run for its money. Especially given the vast disparity in the number of resources that went into creating it, compared to those that went into creating the Big G. The problem, however, is not in the database—it’s in how it’s used. One of the first problems I faced was very simple, and yet daunting: what do I index? I couldn’t just go about indexing everything that was on our website, or the search results would have been meaningless. I eventually settled on simply indexing certain content, creating different databases depending on the nature of the information I was dealing with. This would give me the side benefit of easily allowing users to search specific areas of interest (say, our articles or our news, for example), as well as performing a combined query that would span multiple databases as needed. This seemed to work quite well—at least until I started wondering whether I was performing search operations in the right way. I had started by using the query parser that comes with Xapian: I would simply let it parse and interpret the search query, and then run it against the database. The problem with this approach was that the query parser had been written as an allpurpose tool, but I wasn’t writing an all-purpose search engine. Therefore, I needed to write my own query parser that would take into account things like function names, PHP keywords, and so on. At this point, I started showing what I had to a few friends. Surprisingly (at least to me) some of them liked it enough to start making suggestions on things to add. I figured that I might as well play around a bit more, and started writing interfaces to index and search additional sources of data, such as the PHP mailing lists,

    August 2005



    PHP Architect



    www.phparch.com

    many PHP-related blogs, PHP jobs, and so forth. The result—still very much a work in progress—now powers the search options provided by php|architect and can be accessed independently through http://beeblex.com (don’t ask why I picked that name—I don’t know). One of the things that I wanted to do was to make the search engine as easy to use as possible. By this I don’t mean that I wanted to lower the level of technical knowledge required to access the functionality provided by the site—if anything, I wanted to raise it so that “power users” would be able to get the most out of the website with minimum efforts. After all, Beeblex is a search engine for PHP developers, so it makes sense to have features like special prefixes for limiting the search results to specific databases, full RSS support to allow for the monitoring of results from outside the search engine’s GUI, and so on. We even have a Firefox search bar, with a feature we call “the Google passthrough” that allows you to bypass our search engine and feed your query directly to Google—this allows you to keep Beeblex as the default search engine in your browser, while still taking advantage of Google for more general searches. Believe it or not, this is just the tip of the iceberg—I have a feature list on my to-do schedule that keeps growing, and there is no end in sight, not only in terms of adding more sources of information, but also (and especially) in terms of improving the quality of the search experience by allowing the user a higher and higher degree of control. The search engine database itself has become almost an externality—an indispensable one, to be sure, for it would be impossible to run a search engine without it working well and providing a powerful search environment. While it represents the core of the application, however, it is not what tells it apart (for better or worse) from the rest. This is a very practical example of what everyone refers to as the commoditization of software. It has nothing to do with competition lowering prices—it has everything to do with the raising of the minimum bar at which software is considered a commodity as opposed to a product. In our case, Xapian is the commodity—the base that provides the minimum functionality required for us to build a product. Without Xapian (or a library like it), it would be impossible for us to create a search engine without investing a significant amount of resources that, as a small company, we simply cannot afford. It’s this commoditization process that allows software to progress these days—and that’s a process that is too important to be left in the hands of private interests (be they corporate or governmental). That’s why free speech is more important that free beer: because while the latter fuels our dinners, the former fuels our ability to innovate.

    68

    Can’t stop thinking about PHP? Write for us! Visit us at http://www.phparch.com/writeforus.php

    Recommend Documents

    ALL CHANGE: TURKEY ASEAN NATIONS FIRMS WAKE UP TO RECONSIDERS ITS BALANCE COMPETITION BENEFITS OF TAILOROPTIONS AND COO...

    TeAM YYePG AUGUST 2005 Digitally signed by TeAM YYePG DN: cn=TeAM YYePG, c=US, o=TeAM YYePG, ou=TeAM YYePG, email=yyep...

    › YOUR PERFECT RIDE The Tech-Lover’s Guide to the Best New Cars TeAM YYePG › › HOT TV PROJECTORS 48 AND OTHER GR...

    GOOGLE’S TALENT GRAB (P. 28) l COOL TOOLS FOR COLLEGE (P. 74) AUGUST 8, 2005 www.businessweek.com THE STATE OF SURVE...

    Go Tuesday September 13th 2005 About | My account | Log out | Help Germany's surprising economy Aug 20th 2005 Aug 1...

    SEARCH RESEARCH TOOLS Economist.com Choose a research tool... Subscribe advanced search » Sunday December 11th 200...

    Go Tuesday September 13th 2005 About | My account | Log out | Help Goodbye to Gaza Aug 6th Aug 13th 2005 Jul 30th...

    requirements Editor: Suzanne Robertson ■ The Atlantic Systems Guild ■ [email protected] Are We Afraid of the...

    from the editor Editor in Chief: Steve McConnell ■ Construx Software ■ [email protected] The Business of Softwa...