Troubleshooters.Com Presents

Troubleshooting Professional Magazine

Volume 4 Issue 4, April 2000
Apache, Apachecon and PHP
Copyright (C) 2000 by Steve Litt. All rights reserved. Materials from guest authors copyrighted by them and licensed for perpetual use to Troubleshooting Professional Magazine. All rights reserved to the copyright holder, except for items specifically marked otherwise (certain free software source code, GNU/GPL, etc.). All material herein provided "As-Is". User assumes all risk and responsibility for any outcome.

[ Troubleshooters.Com | Back Issues ]

Steve Litt is the author of Troubleshooting Techniques of the Successful TechnologistRapid Learning: Secret Weapon of the Successful Technologist, and Samba Unleashed.

CONTENTS

Editors Desk
Part I: Apache

Apachecon Rocks!
Jim and Jeff Talk Webserving
Fun in the Apachecon Exhibit Hall
IBM Are the Good Guys This Time
My Proposed Speaker Preparation Package
Attending Conferences on a Budget
A One Minute Explanation of XML
Part II: PHP Tutorial

Gaining Greatest Benefit from this Tutorial
mod_php: Hello World
PHP Programming
PHP: Maintaining State on a Single Page
PHP: Maintaining State Between Multiple Pages
PHP Data Enabled App Using PostgreSQL
Installing phplib in a Postgres Environment
Linux Log: What's in a License
Letters to the Editor
How to Submit an Article
URLs Mentioned in this Issue

Editors Desk

By Steve Litt
It was too close to miss. Apachecon 2000 was held March 8-10 in Orlando, FL, 15 miles from my home. I got a press pass and went on in. There was much to learn, and many to meet. The sessions and courses were excellent. I came away with a new love of Apache.

This issue of Troubleshooting Professional is in two parts. Part 1 discusses Apachecon and Apache. Part 2 is an introductory PHP tutorial.

Linux possesses two killer apps -- Samba and Apache. Past Troubleshooting Professional Magazine issues have contained extensive Samba coverage. This issue provides some balance. If you're a Troubleshooter, or if you create dynamic web content, this is your magazine. Enjoy!

Steve Litt is the author of the Universal Troubleshooting Process Course. He can be reached at Steve Litt's email address.
Part I: Apache

Apachecon Rocks!

By Steve Litt
You could see it coming. The first signs were the emails from Ken Coar, the conference organizer and planning committee chair. Although these were just informational emails about logistics, registration, how to dress (casual), etc, Ken's enthusiasm shined through. Here's an exerp from his 3/5/2000 email to attendees:
 
A couple of social events have been scheduled, and some special activities in the exhibit hall.  For more information, see <http://ApacheCon.Com/html/special-events.html>.

Only two more days!
-- 
#ken    P-)}
 

"Only two more days!" You can tell Ken was brimming with anticipatory excitement. I went to Apachecon with high expectations. My expectations were exceeded.

For me, the highlight of the first day was Mike Pogue's "The Cathedral Meets the Bazaar" presentation, in which he detailed how his employer, IBM, was employing people to write Open Source software. Mike is a project manager for some of IBM's Open Source work. He detailed advantages of Cathedral (traditional IBM) development and Bazaar development, and how IBM has used the best of both worlds for its development. I left with a clearer understanding of the role of large companies in the Open Source movement. His style and delivery were supurb, so I figured Mike's would probably be the best presentation I'd see at Apachecon. But it wasn't.

The second day I attended Jim Jagielski's "Web Hosting for Fame and Fortune" presentation. It sounded like info on how to make more money on the web. Unexpectedly, it turned out to be an outstandingly complete and concise technical information on setting up a web server, and Jim's clear and understandable delivery. This session is covered in more detail in this month's article entitled Jim and Jeff: Talk Webserving. I gave Mike Pogue him mostly 5s (out of 5), and a few couple 4's on his evaluation sheet. Jim Jagielski got straight 5's. Obviously I didn't expect to see a better presentation. But in fact, the next day I did.

It was called "PHP: Hacker's Paradise" by Nathan Wallace. No perfect score for Nathan -- he spoke too fast and too softly. I had to strain to follow him. But the exceptional content and the subject matter expertise made Nathan's presentation well worth the extra effort.

Within the framework of PHP, Nathan layed out design strategies for todays realities, where you often need to get a complex site up in a week. It rang so true! No 2 month systems analysis, but no shoot from the hip either. Nathan evangelized a methodology of wrapping the most unchanging code in PHP objects, wrapped in a shell of PHP functions and include files, with the quick changing stuff (mainly the appearance) in an outer HTML layer. Nathan showed the code for most of his "base functions", each of which was simple enough to assimilate from 30 seconds of viewing a slide. Nathan discussed the tactics of when and how to use each of these objects and functions in everyday web development life, and how they contributed to ultra rapid development. Nathan's was my favorite Apachecon presentation. I walked out of his presentation determined to learn more about PHP. The URL of Nathan's website and the URL of the presentation slides are in the URL's section of this magazine.

I saw several other excellent presentations, including some very interesting ones on XML. I was personally not enthused with the Birds of a Feather sessions, but some of my friends praised them to the sky. Who's right? I don't know, I guess you'll just have to see for yourself next year.

The exhibit hall was fun and informative. There were Open Source vendors, proprietary software vendors, hardware vendors, book vendors -- all with an Apache focus. Many booths were manned by the technologists actually coding the project. You can read more about the exhibit hall in the article titled Fun in the Apachecon Exhibit Hall.

People Power

As good as Apachecon was, it was the *people* that made it really enjoyable. Certainly the most visible was Ken Coar, who from the attendee's viewpoint was the leader. Ken gave us our information and continually urged us for feedback. He's smart, he knows Apache, and he's a great leader. Ken is a the ASF (Apache Software Foundation) V.P., Apache Conferences, and he's the Apachecon technical chair.

Behind the scenes (from the attendee point of view) was Camelot Communications. They're a New York based company that produces international conferences. This was the first Apachecon they've produced, and it came off smoothly and pleasantly. Camelot recruits the speaking talent, does much of the publicity (although ASF certainly had a hand in the publicity), fills the exhibit halls with exhibitors, and handles all the logistics. If I had to put on a national conference, Camelot Communications would be the first people I'd call.

Besides being technically knowledgeble and excellent speakers, the presenters were personable. The attendees were able to talk extensively to the presenters. Mike Pogue stayed an hour afterward, at first answering questions, but metamorpising into a most interesting technical discussion. The exhibitors were also very personable and interesting.

The attendees were great!d. I was able to have interesting discussions with several friendly and knowledgeable people -- something I didn't expect given my relative unfamiliarity with Apache. Discussions broke out everywhere. Apachecon was wonderfully social -- a great break from the sessions' heavy duty technology.

Culture Shock

In the plenary Wednesday morning, an attendee asked something like "When will Apache achieve world domination?". Ken Coar immediately answered that world domination was the goal of Linux, not Apache. My immediate thought was "Toto, I don't think we're in Kansas anymore".

You see, I come from the Linux and LUG culture where world domination is indeed the goal, especially the overthrow of the evil empire. Apachecon and many of its attendees treated Microsoft as just another vendor, Windows as just another OS. Sure, for technological reasons they preferred to run Apache on UNIX and UNIX clones, but hey, Windows is OK if you can solve the technological problems. Several presenters had slideshows which were obviously Windows hosted. If I tried to give a Samba presentation to LEAP-CF (my LUG), I'd endure endless heckling.

It was refreshing to see such OS objectivity. These guys were there for the technology, and probably to gather more developers for more projects. No time for holy wars -- they leave that to people like me :-).

The bottom line is whether you do Windows, UNIX, Linux, BSD, HPUX, Solaris, or practically any other OS, you'll be comfortable at Apachecon.

Steve Litt is the author of Troubleshooting Techniques of the Successful Technologist. He can be reached at Steve Litt's email address.

Jim and Jeff Talk Webserving

By Steve Litt
Skeptics do better in technology. There's a whole lot of snake oil being sold in our industry, so independent verification of facts is necessary. Independent means neither party got the info from the other, nor did both get it from the same source.

VA Linux exhibitor Jeff Rabin and presenter Jim Jagielski related almost identical facts -- just different enough to emphasize the independence of their views. With Jim an ISP owner and longtime ASF contributor, and Jeff a VA Linux sales person and long time hardware engineer -- they obviously come from very different backgrounds. So when they presented similar technological outlooks, I listened.

Jim Jagielski

Jim presented "Web Hosting for Fame and Fortune". I figured Jim would tell me how to get rich with web hosting. Wrong! He told me (and about 200 other attendees) how to set up a web server for maximum efficiency and economy. As a starting point, he turned our attention to the unbiased Mindcraft survey (funded by Microsoft :) He pointed out that a much better non-Windows web server could have been created.

Jim favors freeBSD on a fast Pentium with at least 128 Mb of ECC RAM. The ECC RAM helps approach 24x7 operation (try that with NT:-). 24x7 also requires a good UPS. Naturally, he recommends a tape backup system for data security.

Jim recommends fast disks, preferrably SCSI. My understanding is that SCSI offloads disk tasks that would otherwise tax the CPU. He recommends either 10 or 100mbps ethernet cards. I would imagine you'd use a 10mbps only if you had a fairly narrow pipe to the Internet, as you'd never want to bottleneck on your LAN. Jim discussed installation, and the use of the Configure and configure programs.

Useful modules

Jim named four modules he felt were useful:
  1. mod_php
  2. mod_macro
  3. mod_vhost_alias.
  4. mod_perl
The mod_vhost_alias module allows a single "template" configuration in httpd.conf to serve many or all websites on the server. Besides this huge administration advantage, mod_vhost_alias also supports the realtime addition of virtual hosts, and has a small footprint. In answer to a question by me, he verified that mod_vhost_alias can be used for some websites and not others. For instance, it could be used for personal websites, while a full setup could be used for corporate sites. This is important because mod_vhost_alias does not allow certain configurations that might be useful to a large corporate site.

Performance

Here's where things got interesting. First and foremost, Jim recommends that the server be used for web service and nothing else. He explains that the Linux or freeBSD servers typically have default configurations optimized to do a tolerable job in many areas, rather than doing one task exceptionally. By removing all non-webserving functionality from the server, it can be tweaked for maximum web service and only maximum web service.

Don't use the webserver for development. And don't compile Apache or other web products there. Compiles are incredibly resource intensive and can slow the server to a crawl. Given the worldwide nature of the web, there's no good time to be slow. Instead, compile on a separate box and copy the resultant binaries.

Increase file descriptors to accommodate the large number of files opened by a web server. File descriptors can be viewed with the fstat command. Likewise, increase the number of mbufs, which can be viewed with netstat.  Bump up MinSpaceServers to 150, MaxSpaceServers to 175, and StartServers up to 25. My hand shot up. Why start with less than the minimum. Jim explained that doing so could have diagnostic value in certain circumstances.

Set MaxRequestsPerChild to 100,000 unless your server suffers from process balooning, which freeBSD and Linux don't.

Other config adjustments he mentioned were process slots, lasterqueue, Maxclients/HARD_SERVER_LIMIT, ThreadsPerChild. Jim cautioned to set AllowOverride None at the top directory so the majority of subdirectories would not be probed. Such probing is time consuming. Finally, he mentioned disabling reverse DNS lookups, as those are slow and their speed isn't even within your control. For those wanting statistics including domains instead of IP addresses, report scripts can be equipped with reverse DNS lookup, but don't put it in all log records.

Last but not least, use two separate drives, each attached to its own SCSI port. Put web pages on one drive and log files on the other.

Security

Jim recommends disabling shell access by specifying a link to /bin/false as the user's shell. He also recommends heavy use of CGI wrappers.

Walking out of the presentation, I knew that if someone called me to set up a high performance web server, I could do it. No, I don't know everything (especially now that I've forgotten much of the presentation in the ensuing 3 weeks), but I'd know what to look at and where to find information. Armed with Jim's info, I headed out to the exhibit hall.

Jeff Rabin

By Steve Litt
Manning the VA Linux booth in the exhibit hall, big as life was my friend Jeff Rabin, . If you're a Linux guy in Central Florida, you're friends with Jeff. He regularly makes the LEAP-CF and ELUG meetings in Orlando, the FLU meetings in Gainsville, and probably several other Central Florida LUG meetings. Although Jeff regularly says he's "in sales", as you get to know him you find he's a technical heavy hitter. I found out why at Apachecon -- before going into sales Jeff was an engineer designing computer hardware. I guess what I'm trying to say is he's a pretty authoritative guy.

So I ask Jeff about VA Linux's servers. He has a rack mount beauty on display. It has a case and power supply that look like they could withstand a direct hit from a 6 mile asteroid. It has five drive bays, each of which can accommodate a drive. Each bay has a hotswap insert/remove mechanism. Of course that feature alone doesn't make them hot-swappable. The hotswap feature also requires the RAID board Jeff has inserted into a slot on the motherboard.

The VA servers have hardware and software suitable for multi-box monitoring. They can all be connected together such that a central box can diagnose most problems.

As mentioned, Jeff is a VA Linux sales employee. He typically doesn't sell single systems -- VA has an excellent self-service website for that. Jeff 's the guy you call when you want to fire up an ISP or web farm, typically over 10 servers -- sometimes hundreds or thousands. He can provide you the servers. If you want, he can also provide you with VA consultants to help set it up. Jeff can get as turnkey as you want.

I asked Jeff about the info I learned at Jim's presentation as it relates to the hardware he's selling. I mentioned Jim's suggesting two drives on two SCSI ports, one for logs and one for web pages. Jeff said that now he understood why everyone was asking him whether it had two SCSI ports, and pointedly said that two drives could not saturate the internal SCSI cable or port. That was the sole point of disagreement. Jeff went on to say that splitting the logs and pages onto separate drives was an excellent performance idea.

I mentioned Jim's suggestion to use lots of memory, and Jeff felt you didn't need lots of memory -- 128Mb is enough according to his field tests. Verification! Jim had defined 128Mb as "a lot", Jeff doesn't think of it as "a lot", but both agree 128 will work well. Jeff mentioned that the money spent to upgrade from 128Mb to 512Mb RAM would yield better results buying faster processors or more boxes.

Jeff went on to explain things that I long since forgot. Journaling filesystems soon to come, server farm configurations, remote phone control of server farms, the fact that for a given big iron webserver, equivalent power can often be purchased for 20 cents on the dollar using VA boxes and Linux.

A hardware guy and an ISP owner told almost identical stories. It rang true.

If I Ever Build an ISP

If called upon to build a major ISP or huge server farm, I know what to do. I'll start by buying all of Jim  Jagielski's books (start writing, Jim :-). I can call up Jeff and order 100 or so rack mount servers, each with a log drive and page drive, each RAID hot swappable drive mirrored. Fast PIII, 128Mb of ECC RAM, tape backup, UPS, 100mbps network card,  the whole farm monitored and constantly scanned for problems, with the ability to restart any machine from 2000 miles away. I'd use Linux because that's what comes on VA boxes. I'd crank up the file handles, mbufs, MinSpaceServers, MaxSpaceServers, StartServers, MaxRequestsPerChild. Configure in mod_php, mod_macro, mod_vhost_alias, mod_perl, phplib and a good DBMS.  CGI wrappers. Shell access disabled by specifying a link to /bin/false as the user's shell. Have the server farm exceed all expectations and be written up in Infoworld.

I guess I don't sound like a skeptic any more, do I?

Steve Litt is the main author of Samba Unleashed. He can be reached at Steve Litt's email address.

Fun in the Apachecon Exhibit Hall

By Steve Litt
Jeff Rabin wasn't the only interesting person in the Exhibit hall. It was brimming with excitement, technology and conversation. The Apache Software Foundation people spread their knowledge and friendly conversation from booth 310. LinuxMall.com sold huge inventories of software at spectacular prices. I waited too long and their stock was mostly depleted, but I managed to grab a $2.00 copy of freeBSD. If you see LinuxMall at a show, buy early!

Naturally O'Reilly and Sun were there. IBM, now a serious Open Source contributor, had a huge presence offering web packages ranging from free, through cheap, to big iron.

Collab.Net had a booth. Things were very busy so there was little time to talk, but from what I understand they put Open Source developers together with corporations needing a project done and willing for that project to be Open Source. From what I understood, this allowed the developers to be paid to write Open Source. Keep up the good work guys!

From booth 207, Lutris evangelized Enhydra, a webdev environment using Java objects on the server side. As they described it, it sounded (at least to me) kind of like Zope, but using Java instead of Python as the underlying language. Like Zope creators Digital Creations, Lutris is primarily a service organization, and Open Source'd the webdev tools they use for development. If your developers are more familiar with Java than Python or PHP, by all means download Enhydra and check it out.

Borders sold a huge inventory of books from booth 306.  My friends and I all salivated over the books, but so many books, so little time. Half way into the conference I decided to quickly learn more Apache in order to get more out of the conference, so I plunked down $49.95 at the Borders booth and walked away with Apache Server Unleashed by Rich Bowen and Ken Coar (the Apachecon Conference Organizer). I read half of it during the night between the second and third days. It paid off in better understanding and ability to participate in discussions. Apache Server Unleashed is an excellent book, logically structured for accelerated learning as well as quick lookup. It's extremely concise, with no fluff, gratuitous listings, or page puffery of any kind. As a busy person, I appreciate that.

The Exhibit hall held too many exhibitors to name them all here, and from what I hear from Camelot Communications there will be many more in the London Apachecon. But exhibitors were only half the story...

There were the discussions. In the isles, in the booths, everywhere. No matter who you are, what your level or interests, you could find great conversation. For me, the conversations got even better when a bunch of my LEAP-CF buddies showed up. What I don't know, they do :-). The last night soft drinks, shrimp, and various other food were passed out in the exhibit area. The last bits of reservedness were replaced by tumultuous technosocial conversation. The exhibit hall was just plain fun.

Steve Litt is the author of Troubleshooting: Tools, Tips and Techniques. He can be reached at Steve Litt's email address.

IBM Are the Good Guys This Time

By Steve Litt
IBM was the badguy when I started my career. I cheered when Bill Gates and Paul Allen gave me a marketable alternative. Can a leopard change its spots?

The 80's are ancient history, "Bill Gates" is now a curseword in my crowd, and today IBM is (drum roll please) a huge Open Source contributor. I hope IBM's Mike Pogue publicizes the notes from his "Cathedral Meets the Bazaar" presentation, as those notes answer many more questions about joint corporate/OpenSource development than I ever could. IBM has Open Source'd an excellent XML parser and they've contributed to PHP, just to name the tip of the iceberg.

IBM was the Premier Corporate Sponsor of Apachecon, and fielded an array of excellent speakers. In addition to Mike Pogue, there was Alfred Spector's excellent  talk entitled "The Web and Technology Fusion". Perhaps more impressive than the solutions he suggested was his repeated warning that if we do not find faster development methodologies through reuse, future programmers will be rationed the way doctors are today.

IBM's Sam Ruby discussed the technical details of some of IBM's Websphere products. It was way over my head, but I could see some of my LEAP-CF buddies nodding and smiling.

Last but not least, Ken Coar himself hails from IBM. These were the IBM speakers I saw, and they were all excellent. And IBM fielded many more speakers.

Although not directly on-topic in this month's Apache theme, IBM has been heavily supporting Linux the past year. They ported DB2 to Linux, and now they've started selling Linux cluster supercomputers. IBM's support is a vital Linux foot in the door to the corporate IT world.

Can a leopard change its spots? Smarter people than myself have argued that question, but I could swear I saw IBM's spots turn jet black and migrate to its head, back and arms, leaving it with a distinctly white stomach and chest.

Steve Litt is the documenter of the Universal Troubleshooting Process. He can be reached at Steve Litt's email address.

My Proposed Speaker Preparation Package

By Steve Litt
The Apachecon 2000 speakers were uniformly authoritative. However, there was quite a bit of variation in their ability to convey the information to the audience. It's not surprising. They've all spent years learning their technological craft, but some speakers may never have had any speaker training or experience, while others may speak publicly on a weekly basis. I have a suggestion which I believe will help inexperienced conference speakers more closely approach the delivery of veteran speakers.

As I suggest this, I'm reminded of the set of guidelines Macmillan Publishing gave me when I wrote 4 chapters in Red Hat Linux 6 Unleashed. They gave me a 33 page document on conventions and common mistakes. I followed the conventions, avoided the mistakes, and my chapters were successful enough to earn me lead authorship of Samba Unleashed.

Every conference should give their speakers a set of speaker guidelines. They needn't be long or voluminous. The guidelines are not meant to *enforce* anything, only to let the speaker know what he or she can do to achieve a good level of audience communication. These guidelines don't help heavy hitter speakers -- heavy hitters already know it. Instead, the guidelines reduce the variation between the inexperienced speakers and the heavy hitters. I believe the following is an excellent starting point:
 
  • Organize to move forward incrementally. Construct the visual material around that organization.
  • Follow your plan! Don't improvise.
  • Rehearse the complete presentation at least once before giving it live.
  • Arrive early enough to test and troubleshoot your audio and video presentation equipment.
  • Bracket every concept between "we will discuss" and "so now you know how to". You never want attendees asking themselves "what is he referring to?" Enforce a mental road map. 
  • Don't say "this", "that", "these", but instead make your meaning clear by naming the object or concept that you mean.
  • Don't say "next", "next is", or "next we have", instead say "that brings us to", "that brings up the topic of", or just flip the page and continue talking (with appropriate concept bracketing, of course).
  • Don't be a perfectionist, and don't feel required to discuss every detail.
  • If possible, use a class volunteer to do the examples in small presentations.
  • Go from the familiar to the unknown.
  • Don't self-depreciate or minimize the importance of your material. Instead say "now watch this!" or "check this out!" when demonstrating a point or example.
  • Don't praddle on at the end of a subject or talk, just end it. 
  • Have content toward the end which you can delete in the event that you run overtime.
  • Don't worry about finishing early, even if there are no questions. Congratulate yourself on giving the audience an extended coffee break. You'll probably be mobbed by attendees asking detailed questions after the end of the presentation.
The preceding list was written by Steve Litt on 4/3/2000, and placed in the public domain 4/3/2000. You may use it as you see fit. As with any public domain material, use at your own risk. The original author (Steve Litt) takes no responsibility whatsoever for the use, misuse, or outcome of the material.

I placed the preceding list in the public domain so you can feel free to use it, add to it, enhance it. It would be great to see it evolve into a meaningful guideline for technical speakers. Please keep me in the loop. I can help.

Steve Litt is the author of Rapid Learning: Secret Weapon of the Successful Technologist. He can be reached at Steve Litt's email address.

Attending Conferences on a Budget

By Steve Litt
One thing that bothers me about most conferences is the pricetag. It's not a problem for those with corporate expense accounts, or even entrapeneurs with few financial responsibilities. But for many of us, including many rank and file Open Source contributors (especially if they support a family), a plane ticket, a rental car, a $1000 conference fee, and three nights at an expensive hotel are not in the cards. That's why most conferences have a two digit priced (or sometimes free) exhibit-only pass. Apachecon's exhibit-only pass was free if pre-registered.

The way I see it, you can drive to any conference within 700 miles. Yes, it's a very long day of driving, but since when is a drive to the airport, a 90 minute wait for boarding, a 2 hour flight (3 if there are stops), a delay at baggage pickup, a session at the car rental counter, and a drive to the hotel an easy day? As long as you're healthy and well rested enough to put in 10-15 hours of drive time, it's well worth it. Be sure to be all packed the night before, get plenty of sleep, and leave either just before or just after rush hour.

But before beginning to plan the drive, start by making sure that the exhibit-only pass meets your needs, technologically and financially. Call or email the conference contacts if necessary.

If the exhibit-only pass meets your needs, find an inexpensive motel in a decent neighborhood near the event, but not so near as to drive up the room rates. Finding this info will probably take10-20 phone calls and some emails, so start early. You'll probably arrive at the motel late, so be sure to get a confirmed reservation and verify that you can check in at any time of night. That takes care of lodging. You can sometimes save even more by staying with a friend, but that might not be a good idea if the friend's lifestyle prevents you from getting enough sleep.

So that's it. The conference costs you 1400 miles of gasoline and auto wear and tear, 3 nights in an inexpensive motel, restaraunt meals, and maybe parking at the event (Apachecon 2000 in Orlando had loads of free parking).

The economy route isn't perfect. You don't get into the classes. You have a little less status than the full participants. The stay in an inexpensive motel might disuade you from bringing your computer (I once had some stuff stolen from a bargain basement Santa Monica, California motel). You need to take a little more time off work than the guy who flies. But it's a way for the small entrapeneur to avoid having to choose between attending a great conference or buying a much-needed computer.

Steve Litt is the author of the Universal Troubleshooting Process Course. He can be reached at Steve Litt's email address.

A One Minute Explanation of XML

By Steve Litt
I attended a couple different XML presentations, the best of which was Pier Paolo Fumagalli's "XML Publishing Fundamentals". I also talked XML with other attendees. This article represents my interpretation of the concensus.

XML takes two forms: Disk and memory. On disk it's the familiar XML file. When in memory, the XML file takes the form of a DOM node tree, or a SAX (Simple Api for XML) event list. A parser converts a XML disk file to DOM or SAX, while a serializer converts a DOM or SAX to an XML disk file. The actual application reads from and writes to the DOM or SAX, as well as maintaining a user interface and writing to printers or other file formats, etc.. Because DOM is a node tree always in memory, it's easiest to manipulate. However, in well hit web apps with a copy of the DOM for each connection, the memory savings of the SAX form make it a life saver. For simplicity the rest of this article assumes DOM, but real-world apps increasingly use SAX. SAX also has the advantage of enabling the app to control XML file reading.
 
------------    ----------    --------------    -------   \0/
| XML file |--->| parser |--->| DOM or SAX |<-->| APP |<-->|
-----^------    ----------    --------------    -------   / \
     ^                               |             |
     |         --------------        |             |
     \----<----| Serializer |<------<--------------/
               --------------

Pier got into specific toolssuch as Cocoon, which I won't reproduce here. He also discussed how the DTD served as a language definition for your document, allowing the sender and recipient to speak the same language, but because that's generally available info it's not included here. Time and bandwidth are gettting tight, and I need to talk about PHP. Read on...

Steve Litt is a contributing author to Linux Unleashed, Fourth Edition, Red Hat Linux 6 Unleashed and Red Hat Linux 7 Unleashed. He can be reached at Steve Litt's email address.
Part II: PHP Tutorial

Gaining Greatest Benefit from this Tutorial

By Steve Litt
This is a PHP tutorial designed to introduce you to PHP and PHP data apps. It does *not* cover installation. I'd therefore recommend using Red Hat 6.1 with all packages installed. This tutorial was done with such a setup. The easiest way is to create a 2Gb partition somewhere and install an all package Red Hat 6.1. It takes about an hour, most of which is filecopy operations not requiring your presense.

The tutorial is designed from the ground up to showcase PHP features by removing all irrelavent technology. DNS is eliminated by using IP addresses instead of names. The tutorial assumes 192.168.100.10 for an IP address, so substitute the IP address of the Linux/UNIX/BSD box you're using for Apache. The PHP files go in the server's DocumentRoot directory, or in a phptest subdirectory immediately below it. This eliminates any need for virtual hosting issues, but please remember to make it universally available with this command:

chmod a+rx phptest.
To facilitate differential learning, you'll want your exploration to immediately reveal the effect of any change. The caching inherent in Netscape Navigator and Microsoft Internet Explorer often delay the effect, causing massive confusion. Even clicking the reload button can bring up an old cached copy. This is especially true when the change was in Apache configuration, not in the page itself. There are two solutions to this problem:
  1. Use lynx instead of GUI browsers
  2. Place <META HTTP-EQUIV="pragma" CONTENT="nocache"> between <head> and </head>.
To raise the certainty of your exploration, you may find it best to use both these solutions. Note that #2 disables all caching, so on completion of debugging you'll want to remove it from the release product unless you really want to disable caching (such as on forms containing private materials).

I had intended to include substantial content on objects, phplib, and maybe a shopping cart example. But this magazine is already the second largest Troubleshooting Professional in history, and if it gets hit hard my web provider will be reminding me about incremental bandwidth costs. So I left those subjects for another time. So much technology, so little bandwidth :-)

This tutorial is designed to yield experience in PHP data enabled web pages. It's short on reuse because it's specifically designed so you can build your web app from the ground up. Being introductory, it also doesn't cover real-world issues like transactions with commit and rollback, data checking, indexes, non-meaningful keys (this tutorial uses last name as a key), and the like. The entire purpose of this tutorial is to get you past the point of intimidation, and I think it does that quite well.

Enjoy...

Steve Litt is the documenter of the Universal Troubleshooting Process. He can be reached at Steve Litt's email address.

mod_php: Hello World

By Steve Litt
This PHP content assumes the use of PHP3. PHP4 has just been released, but at this point in time most people have PHP3 installed via their Linux distro. This tutorial uses a Red Hat 6.0 server, installed from a RH6 install CD, with *everything* installed.

The very first step is to make a "Hello World" PHP3 page. Create the following hello.php3 file in the html root directory on an Apache server:
 
<?php phpinfo() ?>

Try for a lucky break by running this command (change the IP address to match your server)

lynx -dump http://192.168.100.10/hello.php3
It probably won't work, either because php and mod_php are not installed properly, or because it's misconfigured. Assuming correct installation, you can configure it by inserting something like the following 3 lines in httpd.conf:
LoadModule    php3_module                modules/libphp3.so
AddModule     mod_php3.c
AddType       application/x-httpd-php3   .php3
In the preceding configuration lines, note that the exact wording is distribution dependent. Note also the likelihood of those commands already being in httpd.conf, but commented out. Simply remove the hash mark to enable them. Often the AddType application/x-httpd-php3 directive is in srm.conf, commented out. If it tells you it can't find modules/libphp3.so, try looking for that file elsewhere on the system. If it doesn't exist, it's likely php isn't correctly installed. Install from source, .rpm, .deb, etc.

A fair question is "how can I be sure it's working?". If it just produces voluminous output, it's working. If it just prints the source (i.e. "<?php phpinfo() ?>) in lynx, it's not working. A great test is the following command:

lynx -dump http://192.168.100.10/hello.php3 | grep PHP
If that command produces output, you've succeeded. Once you have hello.php3 producing php output, you're ready to continue.
Steve Litt is the author of Troubleshooting Techniques of the Successful Technologist. He can be reached at Steve Litt's email address.

PHP Programming

By Steve Litt
My main reason for not using Javascript is how it uglifies html source. I want an actual source file, with actual functions and objects. With normal indentation, and without markup. I want my html to simply call the appropriate functions.

Did somebody say PHP?

This exercise expands on hello.php3 to call functions from an include file. First, create a file called hello.inc, as follows:
 
<?php
function printtitle()
  {
  print "<title>Hello from hello.inc</title>\n";
  }

function printnumbers($start)
  {
  print "<H2>";
  for($temp=0; $temp < 5; $temp++)
    {
    print $start++ . "<br>\n";
    }
  print "</H2>";
  }
?>

The preceding defines functions printtitle() and printnumbers(), which can be called from html. You'll notice that it starts with the PHP escape sequence <?php and ends with ?>. This is necessary because the PHP include() statement in the html automatically sets parsing back to html, so the include file must be explicitly declared to be PHP. The printtitle() function simply prints a title, complete with tags, so if called between <head> and </head>, it will correctly assign a title. Function printnumbers() prints 5 numbers in <h2> style, starting with the number passed in as an argument. Naturally this must be called from the html page's body.

The next step is to enhance hello.php3. It first includes hello.inc. The include request occurs after <head>. As long as the included file does not have a "main routine" producing output, this is fine. Next, it calls printtitle(). Finally, in the body, it calls printnumbers() to print the numbers. The enhanced hello.php3 is shown below:
 
<HTML>
<?php include("./hello.inc") ?>
<HEAD>
<?php printtitle() ?>
<META HTTP-EQUIV="pragma" CONTENT="nocache">
</HEAD>
<BODY>
Begin body<p>
<?php
printnumbers(7);
?>
<p> End body<p>
</BODY>
</HTML>

Now view it with the following command:

lynx http://192.168.100.10/hello.php3
The top part of the screen should look like the following screen representation, with the title on the top line, and the body following. If it does not look similar to the following screen representation, troubleshoot until it does.
 
 
                                                           Hello from hello.inc

   Begin body

7
8
9
10
11

   End body

After getting the preceding output, you've written a PHP "app" with "dynamic content". From here it just gets better. Read on.

Steve Litt is the author of Samba Unleashed. He can be reached at Steve Litt's email address.

PHP: Maintaining State on a Single Page

By Steve Litt
The previous article was nice, but didn't do much that can't be done in pure html. In this article we'll enhance the PHP to set and retain a state, namely, the page's title. The hello.inc file must now contain a variable to hold the title and a function to set it and one to get it. Now the printtitle() function will simply call gettitle(). See the following hello.inc listing:
 
<?php
$gTitle = "Uninitialized";

function settitle($mytitle)
  {
  global $gTitle;
  $gTitle = $mytitle;
  }

function gettitle()
  {
  global $gTitle;
  return $gTitle;
  }

function printtitle()
  {
  print "<title>" . gettitle() . "</title>\n";
  }

function printnumbers($start)
  {
  print "<H2>";
  for($temp=0; $temp < 5; $temp++)
    {
    print $start++ . "<br>\n";
    }
  print "</H2>";
  }
?>

Now modify hello.php3 to first set the title, then print it in the header, and then later use it again in the body. The following is such a hello.php3:
 
<HTML>
<?php
include("./hello.inc");
settitle("I set it myself!");
?>

<HEAD>
<?php printtitle() ?>
<META HTTP-EQUIV="pragma" CONTENT="nocache">
</HEAD>
<BODY>
Begin body<p>
<h3>Let's print the title in the body...<br>
<?php print gettitle() ?></h3><p>
<?php printnumbers(7) ?>
<p> End body<p>

</BODY>
</HTML>          

Run this through the following command:

lynx http://192.168.100.10/hello.php3
The resulting screen should look like the following:
 
                                                               I set it myself!

   Begin body

  Let's print the title in the body...
  I set it myself!

7
8
9
10
11

   End body

Note that the title appears both at the top (the actual page title), and again in the body.

Steve Litt is the author of Rapid Learning: Secret Weapon of the Successful Technologist. He can be reached at Steve Litt's email address.

PHP: Maintaining State Between Multiple Pages

By Steve Litt
The real way to maintain state between multiple pages is to use the sessioning ability of PHP4. But since PHP4 is so new, we'll discuss a "fake" method of "maintaining state" between pages in PHP3. This is not easy because HTTP itself is a stateless protocol. When you leave a page, it's gone. A PHP include file is executed once per page, so variables are reinitialized -- no state maintenance there.

The solution is as old as subroutines -- have the first page "pass" the info to the second. The "pass" occurs at the form component level via $HTTP_POST_VARS;, which is discussed later, or at the URL level, which is discussed now. In this case the URL would look something like this:

http://192.168.100.10/hello2.php3?prevTitle=I+set+it+myself%21%40%21
This URL is printed in the link to the next page, by the final <?php ?> sequence. The trick is to use the urlencode() function, which replaces spaces by plus signs, and replaces certain punctuation with % followed by digits. Upon recieving this URL, the next php3 enabled page decodes the data after the question mark, and stores that string in a  $prevTitle variable. Multiple variable/value pairs can be passed by delimiting them with &. The following is the first page:
 
<HTML>
<?php
include("./hello.inc");
settitle("I set it myself!@!");
?>
<HEAD>
<?php printtitle() ?>
<META HTTP-EQUIV="pragma" CONTENT="nocache">
</HEAD>
<BODY>
Begin body<p>
<h3>Let's print the title in the body...<br>
<?php print gettitle() ?></h3><p>
new: <?php print urlencode(gettitle()) ?></h3><p>
<?php printnumbers(7) ?>
<p>
<?php
print "<a href=\"hello2.php3?prevTitle=";
print urlencode(gettitle());
print "\">Go to next page</a><p>";
print "<p> End body<p>";
?>

</BODY>
</HTML>

The preceding page calls hello2.php3, passing hello.php3's title. The following is hello2.php3:
 
<HTML>
<HEAD>
<title>This is the called page</title>
<META HTTP-EQUIV="pragma" CONTENT="nocache">
</HEAD>
<BODY>
Begin body<p>
<h3>The following is the title of the previous page...<br>
<?php print $prevTitle ?></h3><p>
<p><a href="hello.php3">Go back to hello.php</a><p>
<p> End body<p>

</BODY>
</HTML>

The <?php print $prevTitle ?> prints the previous page's title. While this isn't exactly maintaining state, the effect is the same. Note that the second page does not need to include hello.inc, because the "state" is not kept there.

There are actually many ways to save state, including cookies and PHP4 sessions. Once you've mastered this tutorial, you can investigate those other state-saving techniques.

Steve Litt is the author of Troubleshooting: Tools, Tips and Techniques. He can be reached at Steve Litt's email address.

PHP Data Enabled App Using PostgreSQL

By Steve Litt

Get PostgreSQL Running


Note: Red Hat Linux 6.1 ships with a working PostgreSQL, PHP and Apache, all properly configured to work together, so this tutorial assumes Red Hat. With other distros, you may need to either install PostgreSQL from scratch or work with different directories, etc. I suggest you install Red Hat 6.1 (all packages) on a spare 2Gig partition in order to reproduce these tutorial exercises.

These exercises assume that all PHP pages and associated files are contained in subdirectory phptest located just below the server's html home directory (typically /home/httpd/html).



If you installed Red Hat Linux with PostgreSQL enabled, you should find a /var/lib/pgsql with little or nothing in it, and a /usr/lib/pgsql with several files. While the latter must have been setup by a package or installation, the former can simply be created or recreated. In fact, it may be best to start with /var/lib/pgsql completely empty.

If your Linux installation was done as recommended, you should also find that there's a user postgres. In userconf->Normal->User_accounts->postgres->Base_info, this user looks like this:
 
Login name               postgres______________________
Full name                PostgreSQL Server_____________
group                    postgres_____________________@
Supplementary groups     ______________________________
Home directory(opt)      /var/lib/pgsql________________
Command interpreter(opt) /bin/bash____________________@
User ID(opt)             100___________________________

Group postgres simply has a name and an id in userconf.

If postmaster is being run (you can find out with ps ax | grep postmaster), then it's almost certainly being run from the postgres uid. If uid postgres hasn't been created, you can do it with adduser then passwd then userconf. You can set postmaster to run at bootup by enabling postgreSQL in Red Hat's control panel daemon section, or an eqivalent tool.

Assuming all the above is correct, you're ready to start.

Create the Login Script for Uid postgres

Log in as postgres. If you don't know the password, open up a shell as root and change the password with passwd. Then log in as postgres, and add the following lines to /var/lib/pgsql/.bash_profile:
PGDATA=/var/lib/pgsql
PGLIB=/usr/lib/pgsql
export PGDATA PGLIB
If necessary, create the file /var/lib/pgsql/.bash_profile. When you're done, log out and log back in. These two commands should show what you'd expect from that script:
echo $PGDATA
echo $PGLIB
If they don't show what you'd expect from the .bash_profile file, troubleshoot. Note that every single user who needs access to PostgreSQL must have those three lines in his or her login script, unless those environment variables are set in the system startup scripts. Personally, I prefer to do it in .bash_profile even if the system startup scripts set them.
 

Initialize the DBMS

WARNING: Absolutely, positively do NOT do this as user root. Do it as postgres only!

Do this command:

initdb
If it gripes about 'does not find the file '/usr/lib/psql/local1_template1.bki.source', suspect a bad $PGLIB. Check that the environment variable is spelled correctly, and that it points to the location of local1_template1.bki.source. It will probably be /usr/lib/pgsql. If not, use the locate command to find it. If you need to change your .bash_profile file, be sure to log out and log in as postgres, or at least do the
source .bash_profile
command.

Once the initdb runs, you should find several files in your /var/lib/pgsql directory:

All these files were created by the initdb command.

If initdb gripes about PG_VERSION already exists, create an empty /var/lib/pgsql, chown it to postgres.postgres, re-do the .bash_profile, and try again. It should work.

Create a Database

Now do this command:
createdb mydb
If it gripes about your not having rights, make sure you're logged in as postgres, and if not, try again as postgres. Otherwise, read carefully and troubleshoot. Once the create went OK, we can go into interactive sql.

Work with Command Line Interactive SQL

Do this command:
psql mydb
This will run interactive sql against the mydb database. If it gripes, try this:
psql template1
If that works, something went wrong with your createdb statement. if the psql template1 command also fails, read error messages and troubleshoot. Once the psql mydb command succeeds, you'll get the following prompt:
Welcome to the POSTGRESQL interactive sql monitor:
  Please read the file COPYRIGHT for copyright terms of POSTGRESQL


   type \? for help on slash commands
   type \q to quit
   type \g or terminate with semicolon to execute query
 You are currently connected to the database: mydb

mydb=>
That prompt tells you several important things. First, the help command is \?. Second, you can quit out of psql with the \q command. Third, every sql statement must be terminated with a semicolon or a \g. If you forget, the command simply will not run, and the next line will have a hyphen in front of the greater_than, instead of an equal sign. If that happens, you can simply put a semicolon and hit enter to execute the command. However, if you type anythinge else and then put the semicolon, you'll get a syntax error. That's OK, just start over again.

Build a Simple Database

Create a Table

From the mydb=> prompt, type in this SQL command:
create table mytable(lastname char(16), firstname char(12), email char(40));
That creates a table with three columns, lastname, firstname, and email. If you can't create the table, and you're logged in as postgres, it's probably an SQL problem.

View Your New Empty Table

Do this command:
select * from mytable;
You should see this result:
mydb=> select * from mytable;
lastname|firstname|email
--------+---------+-----
(0 rows)

mydb=>

Add Some Data to the Table

From the mydb=> prompt, type in this SQL command:
insert into mytable (lastname, firstname, email) values ('Litt', 'Steve', 'Steve Litt's email address');
That creates a row with last name "Litt", first name "Steve" and email "Steve Litt's email address".
If all goes well, you should be able to verify this as follows:
mydb=> select * from mytable;
lastname        |firstname   |email
----------------+------------+----------------------------------------
Litt            |Steve       |Steve Litt's email address
(1 row)

mydb=>

Make the Table Available to All

The following command makes the table available to all postgreSQL users:
grant ALL on mytable to PUBLIC;
If it works correctly, psql prints the word CHANGED. To get more information on the grant and revoke psql commands, type the following commands at the psql prompt:
\h grant
\h revoke
After granting the public rights to the table, you can check the grant/revoke info for the table with psql's \z command, as follows:
 
[postgres@mydesk pgsql]$ psql mydb
Welcome to the POSTGRESQL interactive sql monitor:
  Please read the file COPYRIGHT for copyright terms of POSTGRESQL
[PostgreSQL 6.5.2 on i686-pc-linux-gnu, compiled by gcc egcs-2.91.66]

   type \? for help on slash commands
   type \q to quit
   type \g or terminate with semicolon to execute query
 You are currently connected to the database: mydb

mydb=> grant ALL on mytable TO PUBLIC;
CHANGE
mydb=> \z
Database    = mydb
 +-----------+--------------------------+
 | Relation  | Grant/Revoke Permissions |
 +-----------+--------------------------+
 | mytable   | {"=arwR"}                |
 +-----------+--------------------------+
mydb=> \q
[postgres@mydesk pgsql]$

Note that the \z in the preceding session lists tables and their grants and revokes, and that public is granted arwR. If you see nothing next to mytable, there's something wrong, so Troubleshoot.

Get Out of psql

Just type \q at the mydb=> prompt, and you'll be returned to the Linux command line.

Enable User myuid Access to PostgreSQL

Log into Linux as myuid, and type the following command:
[myuid@linuxhost myuid]$ psql mydb
You'll get the following error message:
 
Connection to database 'mydb' failed.
FATAL 1:  SetUserId: user "myuid" is not in "pg_shadow"
[myuid@linuxhost myuid]$

This is because user myuid hasn't been added to the database. So log in as postgres, and use createuser as shown in this session:
[postgres@mydesk pgsql]$ createuser
Enter name of user to add ---> myuid
Enter user's postgres ID or RETURN to use unix user ID: 501 ->
Is user "myuid" allowed to create databases (y/n) n
Is user "myuid" a superuser? (y/n) n
createuser: myuid was successfully added
Shall I create a database for "myuid" (y/n) n
don't forget to create a database for myuid
[postgres@mydesk pgsql]$
Note that you just hit Enter on the 501 (or whatever the number happens to be). createuser deduced 501 from the Linux myuid uid.

Now log in as myuid and try again:

[myuid@linuxhost myuid]$ psql mydb
You should now get the psql prompt:
Welcome to the POSTGRESQL interactive sql monitor:
  Please read the file COPYRIGHT for copyright terms of POSTGRESQL

   type \? for help on slash commands
   type \q to quit
   type \g or terminate with semicolon to execute query
 You are currently connected to the database: mydb

mydb=>
Now type the following command at the prompt:
select * from mytable;
If all is well, you'll get this response:
 
mydb=> select * from mytable;
lastname        |firstname   |email
----------------+------------+----------------------------------------
Litt            |Steve       |Steve Litt's email address
(1 row)

mydb=>

If you get the following message, it's probably because you forgot to grant rights:
 
mydb=> select * from mytable;
ERROR:  mytable: Permission denied.
mydb=>

If you see the preceding error, log in as user postgres perform the steps in the "Make the Table Available to All" section earlier in this article.

If necessary, devise Troubleshooting tests. Do an insert to table mytable and a select to verify. You should be able to do it. Also try a create table command, and then remove that table with a drop table (careful!). You should be able to do it. Troubleshoot as necessary.

Enable the Apache User Access to PostgreSQL

The web server runs as a low privelege user. On Red Hat 6.1 that user is user nobody. Your mileage may vary. Here's a PHP web page (call it checkuser.php3) to check for your Apache user.
 
<?php print "User is:" . `whoami` . "\n" ?>   

Access it with the following command:

lynx http://192.168.100.10/phptest/checkuser.php3
Once you know the Apache user, use the procedures in the preceding section ("Enable User myuid Access to PostgreSQL") to give PostgreSQL access to the Apache user.

Congratulations

You now have PostgreSQL running, and can prove it with the command line interactive SQL program, psql. Read on...

Verify PHP

Create the following hello.php3 (or .php if you're using PHP4), and place it in an http accessible directory on your http server box:
 
<?php phpinfo() ?>

Assuming you placed it in a directory accessible as www.domain.cxm/test/, pull it up in Lynx:

lynx http://www.domain.cxm/phptest/test.php3
If you get a large page complete with all sorts of data about your system, it works. If not, troubleshoot as recommended earlier in this Troubleshooting Professional issue.

Proof of Concept PHP Data App

The simplest possible PHP<->postgreSQL page is this:
 
<?php
$connection = pg_Connect ("dbname=mydb port=5432 user=myuid");
?>

To view the preceding page, run the following page from *any* user authorized as a PostgreSQL user.

lynx http://192.168.100.10/phptest/test.php3
If, in the preceding test, you got an error message resembling the following then the PostgreSQL PHP module is not installed:
 
Fatal error: Call to unsupported or undefined function pg_connect() in /home/httpd/html/test/test.php3 on line 2

Upon recieving the preceding error message, you have the choice of recompiling php and Apache, or using a Linux distro that's already set up for the job. An all-packages Red Hat 6.1 installation has Apache<->PHP<->postgreSQL functionality working right out of the box. The easiest way to learn is to lay down a Red Hat 6.1 partition and do these exercises there.

If everything's OK, the page prints no error message nor content in lynx. On a Netscape or IE browser it displays the "document contains no data" message. Test on different users to make sure there are no permission problems, and troubleshoot accordingly.

Now test your ability to actually read data with the following test.php3:
 
<?php
$connection = pg_Connect ("dbname=mydb port=5432 user=myuid");
$result = pg_Exec($connection, "SELECT * FROM mytable;");
$row = pg_fetch_row ($result, 0);
print "First column of first row->" . $row[0] . "<-\n";
?>

If all goes well, your lynx screen should look like the following:
 
 
   First column of first row->Litt <-

However, you may run into problems. For instance, if you previously forgot to grant all permissions to PUBLIC for the table, you'll see an error message like the following:
 
   Warning: PostgresSQL query failed: ERROR: mytable: Permission denied.
   in /home/httpd/html/phptest/test.php3 on line 3
   Warning: 0 is not a PostgresSQL link index in
   /home/httpd/html/phptest/test.php3 on line 4

Once again, fix it with the instructions in earlier section "Make the Table Available to All". It's also possible that the Apache run time user (user nobody on default RH6.1 servers) is not set up to access PostgreSQL. The following error message is an example of such a condition:
 
   Warning: Unable to connect to PostgresSQL server: FATAL 1: SetUserId:
   user 'nobody' is not in 'pg_shadow' in
   home/httpd/html/phptest/test.php3 on line 2
   Warning: 0 is not a PostgresSQL link index in
   home/httpd/html/phptest/test.php3 on line 3
   Warning: 0 is not a PostgresSQL result index in
   home/httpd/html/phptest/test.php3 on line 4
   First column of first row-><- User is:nobody 

Upon recieving an error message like the preceding, follow the procedures in the previous section titled "Enable the Apache User Access to PostgreSQL".

Caching and Back Button Disabling

In preparation for data enabled apps, cache disablement and back button disablement are necessary. Disabling cache is necessary so a proxy somewhere doesn't store your filled in form and show it to another user. Back button disablement prevents the user from going backward instead of forward. Nathan Wallace says at http://www.faqts.com/knowledge-base/view.phtml/aid/242/fid/3/lang/en that on error, forms should move the user forward to the form, not tell the user to use the back button.

The following include file, nocache.inc, contains a function to disable cache and a function to disable the back button:
 
<?php

function disable_cache()
  {
  header('<META HTTP-EQUIV="pragma" CONTENT="nocache">');
  header("Cache-control: no-cache");
  header("Expires: " . gmdate("D, d M Y H:i:s", microtime()) . " GMT");
  }

function link_with_disabled_back_button($url, $linktext)
  {
  print "<a href=\"$url\"";
  print "ONCLICK=\"location.replace(this.href);return false;\">";
  print $linktext . "</a>";
  }
?>     

 

WARNING!!!

Be sure not to leave extra text or spaces outside the PHP tags in nocache.inc, or you will see error messages like the following:

Warning: Cannot add more header information - the header was already sent (header information may be added only before any output is generated from the script - check for text or whitespace outside PHP tags, or calls to functions that output text) in ./nocache.inc on line 5

To test nocache.inc, make test_a.php3 and test_b.php3, as follows:
 
test_a.php3
<?php
include ("./nocache.inc");
disable_cache();
print "<center><h1>test_a.php3</h1/</center><p>\n";
link_with_disabled_back_button("test_b.php3", "Click here for test_b.php3");
?> 

 
test_b.php3
<?php
include ("./nocache.inc");
disable_cache();
print "<center><h1>test_b.php3</h1/</center><p>\n";
link_with_disabled_back_button("test_a.php3", "Click here for test_a.php3");
?> 

The previous two pages call each other with the back button disabled. No matter how many times you click the links, the back button is grayed out, or else it points to the page before you began accessing test_a.php3 and test_b.php3. Note that the disabling of the back button is a function of the link calling the page with the disabled back button, not the page itself.

What if you want to disable cache but not the back button? You'd replace the call to link_with_disabled_back_button with a normal link, as shown below:
 
test_a.php3
<?php
include ("./nocache.inc");
disable_cache();
print "<center><h1>test_a.php3</h1/</center><p>\n";
print "<a href=\"test_b.php3\">Click here for test_b.php3</a>";
?> 

 
test_b.php3
<?php
include ("./nocache.inc");
disable_cache();
print "<center><h1>test_b.php3</h1/</center><p>\n";
print "<a href=\"test_a.php3\">Click here for test_a.php3</a>";
?> 

The preceding files would link to each other, enabling the back button. However, the call to disable_cache() would still prevent caching of form data, which in some circumstances produces a "Data Missing" error instead of showing the previous form when clicking the back button. This can be an important security feature in secure commerce.

Data Listing PHP Program

A data-lister is the simplest practical web app. In order to have something to list, insert two more rows in mytable with the following two psql commands:
insert into mytable (lastname, firstname, email) values ('Claus', 'Santa', 'northpole@troubleshooters.com');
insert into mytable (lastname, firstname, email) values ('Bunny', 'Easter', 'springtime@troubleshooters.com');
You can check your results as follows:
 
mydb=> select * from mytable;
lastname        |firstname   |email
----------------+------------+----------------------------------------
Litt            |Steve       |Steve Litt's email address
Claus           |Santa       |northpole@troubleshooters.com
Bunny           |Easter      |springtime@troubleshooters.com
(3 rows)

mydb=>

Now create the following PHP program called helloread.php3:
 
<?php
$connection = pg_Connect ("dbname=mydb port=5432 user=nobody");
if ($connection == 0)
  {
  print "Failed to open database!<br>\n";
  exit(1);
  }

$result = pg_Exec($connection, "SELECT * FROM mytable;");
pg_Close($connection);
for($row=0; $row < pg_NumRows($result); $row = $row + 1)
  {
  print "<p>Row " . $row . "<br>\n";
  for($column=0; $column < pg_NumFields($result); $column++)
    {
    print pg_FieldName($result, $column) . "=";
    print pg_Result ($result, $row, $column) . "<br>\n";
    }
  }
?>

The pg_Exec() command places the retrieved dataset in $result, which is then queried for field name and field contents for each row. Pulling up the URL http://192.168.100.10/phptest/readdata.php3 yields the following results:
 
Row 0
lastname=Litt 
firstname=Steve 
email=Steve Litt's email address 

Row 1
lastname=Claus 
firstname=Santa 
email=northpole@troubleshooters.com 

Row 2
lastname=Bunny 
firstname=Easter 
email=springtime@troubleshooters.com 

Just for fun, try placing order by lastname at the end of the SQL statement in helloread.php, and note that the output becomes sorted by last name. Note also that there are many other ways the same data could have been pulled up, using various row fetching functions. We chose this method for simplicity.

Data Selection PHP Program

Now make a program, called dataselect.php3,  created from and almost identical to helloread.php3. The difference is that it places the data in a drop down list suitable for selection, in preparation for coding an actual web app. It also places most of the contents of helloread.php3's main routine and places them in function fillSelect(), whose job it is to fill the selection with data and display it with Edit, Add and Delete buttons.
 
<?php
function fillSelect()
  {
  $connection = pg_Connect ("dbname=mydb port=5432 user=nobody");
  if ($connection == 0)
    {
    print "Failed to open database!<br>\n";
    exit(1);
    }

  $result = pg_Exec($connection, "SELECT * FROM mytable;");
  pg_Close($connection);
  
  print "<form  action=\"dataupdate.php3\" method=\"POST\">\n";
  print "<p><select name=\"DROWS\" size=\"1\">\n";
  print "<pre>";

  for($row=0; $row < pg_NumRows($result); $row = $row + 1)
    {
    print "  <option>";
    for($column=0; $column < pg_NumFields($result); $column++)
      {
      print pg_Result ($result, $row, $column) . ":::";
      }
    print "<br></option>\n";
    }
  print "</pre>";
  print "</select></p>\n";
  print "&nbsp;&nbsp;\n";
  print "<input type=\"submit\" name=\"BEdit\" value=\"Edit\">\n";
  print "&nbsp;&nbsp;\n";
  print "<input type=\"submit\" name=\"BAdd\" value=\"Add\">\n";
  print "&nbsp;&nbsp;\n";
  print "<input type=\"submit\" name=\"BDelete\" value=\"Delete\">\n";
  print "</form>\n";
  }

header('<META HTTP-EQUIV="pragma" CONTENT="nocache">');
fillSelect();
?>

The preceding program presents a form with a drop-down list of all data, and edit, add and delete buttons calling dataupdate.php3 to perform further user input (adding/changing fields, or confirming delete), and then update the database. Even before writing dataupdate.php3, the preceding dataselect.php3 lists the database content in the drop down list box. However, clicking the buttons produces an error.

To enable the buttons, and in preparation for the rest of the web app, let's make a diagnostic version of dataupdate.php3. This version lists all form variable names and values:
 
<?php
header('<META HTTP-EQUIV="pragma" CONTENT="nocache">');

reset ($HTTP_POST_VARS);
while (list ($key, $val) = each ($HTTP_POST_VARS))
  {
  echo "$key => $val<br>\n";
  }
print "<p><a href=\"dataselect.php3\">Back to Select Screen</a>\n";
?>  

Now experiment. Choose Santa Clause and click the Edit button, and note you see the effect on the dataupdate.php3 page. Then click the Add button and notice that the button name and value changes. Finally, choose Easter Bunny and click the Delete button, and note the results are as expected. If you're doing this from lynx, remember that you press the Enter key to expand a drop down list, and tab to go between form elements. You have now created a data selector.

What dataupdate.php3 must really do is deduce whether it's called as an add, delete or edit, and call the appropriate subroutine. Later in this tutorial code from dataupdate.php3 will be combined with code from dataselect.php3 to create webapp.php3. Therefore, dataupdate.php3 must also work properly when called straight from a URL. To facilitate that we add global variable $fcn to contain the name of the button clicked in the calling form, which will be either BEdit, BAdd, or BDelete. A fourth value,  BNone, signifies that no button was clicked, meaning that the program was accessed directly by URL. Create function setGlobals() to set $fcn, then in the main routine introduce logic to do the right thing given the value of $fcn (which depends on the the button pressed in the selection process). We leave these functions as printing stubs for the time being. The following is the code for dataupdate.php3:
 
<?php
header('<META HTTP-EQUIV="pragma" CONTENT="nocache">');

$fcn = BNone;

function setGlobals()
  {
  global $HTTP_POST_VARS;
  if(sizeof($HTTP_POST_VARS) < 1)
    {
    return;
    }
  global $fcn;
  reset ($HTTP_POST_VARS);
  while (list ($key, $val) = each ($HTTP_POST_VARS)) 
    {
    if(substr($key, 0, 1) == 'B') //Only buttons begin with B
      {
      $fcn=$key;
      }
    }
  }

function doEdit()
  {
  global $HTTP_POST_VARS;
  print "doEdit:" . $HTTP_POST_VARS[DROWS] . "<p>\n";
  }

function doAdd()
  {
  global $HTTP_POST_VARS;
  print "doAdd:" . $HTTP_POST_VARS[DROWS] . "<p>\n";
  }

function doDelete()
  {
  global $HTTP_POST_VARS;
  print "doDelete:" . $HTTP_POST_VARS[DROWS] . "<p>\n";
  }

function doGeneric()
  {
  global $selectionValue;
  print "doGeneric:$selectionValue<p>\n";
  }

setGlobals();

switch ($fcn)
  {
  case "BEdit":
    doEdit();
    break;
  case "BAdd":
    doAdd();
    break;
  case "BDelete":
    doDelete();
    break;
  case "BNone":
    doGeneric();
    break;
  default:
    print "Illegal function identifier encountered:$fcn:<p>\n";
  }

?>

The preceding code indeed displays the correct button name (or BNone if called directly) and the correct selection value.

Making Your Web App Complete

The preceding articles outlined most of the essentials for making the web app. This article puts them all together in a single .php3 file plus a few .inc (include) files.

We'll do this in two steps:

  1. Combine the previously discussed dataselect.php3 fillSelect() function with all code of the previously discussed dataupdate.php3 into a new file called webapp.php3.
  2. Code generic SQL utilites
  3. Code high level application utility functions
  4. BEGIN HIGH LEVEL APP UTILITY FUNCTIONS
  5. Code state specific subroutines (doEdit(), doAdd(), doDelete() and doGeneric())
  6. Code the main routine

Creating the Web App Skeleton

The result looks like the following:
 
<?php

$fcn = BNone;

function fillSelect()
  {
  $result=listInOrder(); //THIS REPLACES FORMER INLINE pg_ CALLS!

  print "<form  action=\"webapp.php3\" method=\"POST\">\n";
  print "<p><select name=\"DROWS\" size=\"1\">\n";
  print "<pre>";

  for($row=0; $row < pg_NumRows($result); $row = $row + 1)
    {
    print "  <option>";
    for($column=0; $column < pg_NumFields($result); $column++)
      {
      print pg_Result ($result, $row, $column) . ":::";
      }
    print "<br></option>\n";
    }
  print "</pre>";
  print "</select></p>\n";
  print "&nbsp;&nbsp;\n";
  print "<input type=\"submit\" name=\"BEdit\" value=\"Edit\">\n";
  print "&nbsp;&nbsp;\n";
  print "<input type=\"submit\" name=\"BAdd\" value=\"Add\">\n";
  print "&nbsp;&nbsp;\n";
  print "<input type=\"submit\" name=\"BDelete\" value=\"Delete\">\n";
  print "</form>\n";
  }

function setGlobals()
  {
  global $HTTP_POST_VARS;
  if(sizeof($HTTP_POST_VARS) < 1)
    {
    return;
    }
  global $fcn;
  reset ($HTTP_POST_VARS);
  while (list ($key, $val) = each ($HTTP_POST_VARS)) 
    {
    if(substr($key, 0, 1) == 'B') //Only buttons begin with B
      {
      $fcn=$key;
      }
    }
  }

function doEdit()
  {
  global $HTTP_POST_VARS;
  print "doEdit:" . $HTTP_POST_VARS["DROWS"] . "<p>\n";
  }

function doAdd()
  {
  global $HTTP_POST_VARS;
  print "doAdd:" . $HTTP_POST_VARS["DROWS"] . "<p>\n";
  }

function doDelete()
  {
  global $HTTP_POST_VARS;
  print "doDelete:" . $HTTP_POST_VARS["DROWS"] . "<p>\n";
  }

function doGeneric()
  {
  fillSelect();
  }

header('<META HTTP-EQUIV="pragma" CONTENT="nocache">'); 
setGlobals();

switch ($fcn)
  {
  case "BEdit":
    doEdit();
    break;
  case "BAdd":
    doAdd();
    break;
  case "BDelete":
    doDelete();
    break;
  case "BNone":
    doGeneric();
    break;
  default:
    print "Illegal function identifier encountered:$fcn:<p>\n";
  }

print "<p><a href=\"webapp.php3\">Back to Select Screen</a><p>\n";
?>

Notice that function fillSelect() now lists webapp.php3 as the form action, and has all the pg_* calls replaced by a single call to listInOrder(), and that doGeneric() calls fillSelect(), so that basically all functinoality of the former dataUpdate.php3 and dataSelect.php3 are incorporated in webapp.php3. Test thoroughly until it works in a satisfactory manner.

Coding the generic SQL functions

If you saw Nathan Wallace's presentation, you know it's critical to place often used utilities in their own objects or functions. The generic SQL functions do just that. The following are the four generic SQL functions:
 
sql($command) Open a connection, carry out the SQL command, close the connection, and return the result
listInOrder() Call sql() to return a lastname ordered record set
lookup($lastname) Call sql() to return a record whose lastname matches the argument
deleteByLastname($lastname) Call sql() do delete the record whose lastname matches the argument

Note that lookup() and deleteByLastname() find *all* records with the specified lastname. For simplicity this tutorial assumes the user will not create multiple records with the same last name. It's easy enough to add a numeric key field and logic preventing dups. Note also that the first three of the preceding 4 functions return a postgreSQL result variable containing all matching data when used as lookups.

The following is the source for the four generic SQL functions:
 
function sql($command)
  {
  $connection = pg_Connect ("dbname=mydb port=5432 user=nobody");
  if ($connection == 0)
    {
    print "Failed to open database!<br>\n";
    return 0; 
    }
  $result = pg_Exec($connection, $command);
  pg_Close($connection);  
  return $result;
  }

function listInOrder()
  {
  return sql("select * from mytable order by lastname;");
  }

function lookup($lastname)
  {
  return sql("SELECT * FROM mytable where lastname='$lastname';");
  }

function deleteByLastname($lastname)
  {
  return sql("DELETE FROM mytable where lastname='$lastname';");
  }

Code High Level Application Utility Functions

These are various utility functions called by the state specific subroutines (doEdit(), doAdd(), doDelete() and doGeneric()), or in the case of setGlobals(), by the main routine. There are three high level utilities:
 
Function Purpose Called from
presentInputForm($lastname,
$firstname, $email, $fcnLetter)
Present form to user and accept all user data input for edits and adds doAdd() and doEdit()
fillSelect() Provide user with list of records and buttons to add, delete and edit the chosen record.  doGeneric()
setGlobals() Set global values upon entry to webapp.php3. Note that entry happens every time a user clicks a form's button. main routine

Function presentInputForm() takes four arguments, the first three being values for the respective columns. The fourth argument is a function letter defining whether the desired operation is add 'A' or edit 'E'. When called from doAdd() the column fields are empty so that the user is presented with blank fields. The call could easily be modified to present some default data on adds. When called from doEdit() the column fields are loaded with the record's current data so that the user needn't retype fields whose value he doesn't change.

Because this function accommodates both Add and Edit, there are several parts of the function that depend on $fcnLetter. First, the function aborts if $fcnLetter is anything besides A or E. On edits it prints the last name as a string rather than a field, to prevent user modification of the primary key. To make the last name available to $HTTP_POST_VARS, on edit this function also defines a hidden field called HILastname to contain the last name and pass it to $HTTP_POST_VARS.

Contrast this with adds, in which an actual editable field contains the last name. Since the field is passed to $HTTP_POST_VARS, no hidden field is necessary.

The next point of Add/Edit dependency is in the formation of the form's buttons, where $fcnLetter is placed between the letter 'B' and the button name string ('Submit' or 'Cancel').

The following is the source for function presentInputForm().
function presentInputForm($lastname, $firstname, $email, $fcnLetter)
  {
  switch ($fcnLetter)
    {
    case "E":
    case "A":
      break;
    default: 
      print "<p><h1>Illegal function letter ($fcnLetter) in presentInputForm()<p>\n";
print "<p><a href=\"webapp.php3\">Back to Select Screen</a></h1><p>\n";
      return;
      break;
    }

  print "<form action=\"webapp.php3\" method=\"POST\">\n";

  print "    <p>Last  Name: ";
  switch ($fcnLetter)
    {
    case "E":                   //don't allow editing of key value
      print "$lastname<br>\n";

      print "<input type=\"hidden\" name=\"HILastname\" value=\"$lastname\">\n";
      break;
    case "A":
      print "<input type=\"text\" name=\"TILastname\" value=\"$lastname\"><br>\n";
      break;
    }

  print "    <p>First Name: ";
  print "<input type=\"text\" name=\"TIFirstname\" value=\"$firstname\"><br>\n";
  print "    <p>     Email: ";
  print "<input type=\"text\" name=\"TIEmail\" value=\"$email\"><br>\n";

  //**** PLACE FUNCTION LETTER IN BUTTON NAMES TO CONVEY STATE ****
  print "    <p><input type=\"submit\" name=\"B" . $fcnLetter;
  print "Submit\" value=\"Submit\">\n";
  print "&nbsp;&nbsp;\n";

  print "<input type=\"submit\" name=\"B" . $fcnLetter;
  print "Cancel\" value=\"Cancel\">\n";
  print "</form>\n";
  }

The next function is function fillSelect(), which is called by doGeneric(). Note that fillSelect() has already been coded in previous tutorial steps, but a modification is needed. Specifically, the pg_ direct database functions are replaced by a call to listInOrder().

Function fillSelect() fills the row selector, populates and sets up the form with its buttons, and allow the user to add, update or delete the chosen record. The listInOrder() function is called to load all rows into $result, after which those rows are displayed in a loop inside the form, followed by the buttons. When the user clicks the button it calls webapp.php3, resetting all state information. The selected row and the choice of add, delete or edit are conveyed to the newly reset page via the form information in $HTTP_POST_VARS. Source for fillSelect() follows:
function fillSelect()
  {
  $result=listInOrder(); //THIS REPLACES FORMER INLINE pg_ CALLS!

  print "<form  action=\"webapp.php3\" method=\"POST\">\n";
  print "<p><select name=\"DROWS\" size=\"1\">\n";
  print "<pre>";

  for($row=0; $row < pg_NumRows($result); $row = $row + 1)
    {
    print "  <option>";
    for($column=0; $column < pg_NumFields($result); $column++)
      {
      print pg_Result ($result, $row, $column) . ":::";
      }
    print "<br></option>\n";
    }
  print "</pre>";
  print "</select></p>\n";
  print "&nbsp;&nbsp;\n";
  print "<input type=\"submit\" name=\"BEdit\" value=\"Edit\">\n";
  print "&nbsp;&nbsp;\n";
  print "<input type=\"submit\" name=\"BAdd\" value=\"Add\">\n";
  print "&nbsp;&nbsp;\n";
  print "<input type=\"submit\" name=\"BDelete\" value=\"Delete\">\n";
  print "</form>\n";
  }

The next function is function setGlobals(), which  is called from the beginning of the main routine to set any global variables. Note that fillSelect() has already been coded in previous tutorial steps. For the purpose of this tutorial the only global set is $fcn, which conveys the user action choice (if any) in the preceding screen. For simplicity, this function assumes all button names begin with 'B', no other elements in $HTTP_POST_VARS begin with 'B', and therefore assumes there will be only one variable beginning with 'B' in $HTTP_POST_VARS. It simply loops through $HTTP_POST_VARS, setting $fcn to any $HTTP_POST_VARS element whose name begins with 'B'. The source for setGlobals() follows:
 
function setGlobals()
  {
  global $HTTP_POST_VARS;
  if(sizeof($HTTP_POST_VARS) < 1)
    {
    return;
    }
  global $fcn;
  reset ($HTTP_POST_VARS);
  while (list ($key, $val) = each ($HTTP_POST_VARS)) 
    {
    if(substr($key, 0, 1) == 'B') //Only buttons begin with B
      {
      $fcn=$key;
      }
    }
  }

Coding State Specific Subroutines

IMPORTANT!! The state specific subroutines are meant to be called upon entry to webapp.php3. If the user clicked the add, edit or delete button on the previous form, the corresponding do routine is called to begin the add, edit or delete process. This is done according to the main routine's $fcn switch. Global $fcn is set by setGlobals() when webapp.php3 is invoked or re-invoked. If there is no button variable in $HTTP_POST_VARS, doGeneric() is called, which displays a record picklist and add, edit and delete buttons. The state specific subroutines are not called by other subroutines, but only by the main routine at page invocation. It is important to remember that in webapp.php3, every time the user clicks a button webapp.php3 is run anew.

The state specific subroutines discussed in this section are as follows:
 
Function Purpose
doEdit() Present an edit form and pass the modified data to the next invocation of webapp.php3.
doAdd() Present an add form and pass the added data to the next invocation of webapp.php3.
doDelete() Present a delete confirmation and pass the key for the deleted record to the next invocation of webapp.php3.
editToDB() Change non-key column values for a specific lastname defined row. The column values and the lastname key are parsed from $HTTP_POST_VARS 
addToDB() Add a new row whose column values are parsed from $HTTP_POST_VARS
deleteFromDB() Delete a row whose lastname value is parsed from $HTTP_POST_VARS 

All 6 functions are state specific functions called from the main routines $fcn switch, and not from other routines. The first three primarily interface with the user, although editToDB() and deleteFromDB() read from the database. The last three interface with the database. Note that stubs for doEdit(), doAdd(), doDelete(), and doGeneric() were coded in previous tutorial steps, but this step gives them a meaningful part in a functioning web app.

Function doEdit() parses $HTTP_POST_VARS for the last name, then looks up the field data. The field data could have been parsed out of $HTTP_POST_VARS, but the data might have been changed between the time the selection form was invoked and the time the edit button was clicked. Finally, it calls presentInputForm() with the field data and function letter 'E' to accept the user changes to the data.
 
function doEdit()
  {
  global $HTTP_POST_VARS;
  if(ereg("^(.*) :::.* :::.* :::", $HTTP_POST_VARS["DROWS"], $regs))
    {
    $lastname = $regs[1];
    }
  else
    {
    print "<p>ERROR: doEdit() recieved no last name key!<p>\n";
    return;
    }
  $result = lookup($lastname);

  presentInputForm(
        pg_Result($result, 0, 0),
        pg_Result($result, 0, 1),
        pg_Result($result, 0, 2),
        "E");
  }
Function doAdd() simply calls presentInputForm() with blank field data and function letter 'A' to accept the user entered data for the new record.
 
function doAdd()
  {
  global $HTTP_POST_VARS;
  presentInputForm("", "", "", "A");
  }

Function doDelete() parses $HTTP_POST_VARS for the last name, then looks up the field data. The field data could have been parsed out of $HTTP_POST_VARS, but the data might have been changed between the time the selection form was invoked and the time the edit button was clicked. It then displays a form with the retrieved data and buttons marked Delete and Cancel. The actual database delete is done in the next invocation of webapp.php3.
 
function doDelete()
  {
  global $HTTP_POST_VARS;
  if(ereg("^(.*) :::.* :::.* :::", $HTTP_POST_VARS["DROWS"], $regs))
    {
    $lastname = $regs[1];
    }
  else
    {
    print "<p>ERROR: doDelete() recieved no last name key!<p>\n";
    return;
    }

  $result=lookup($lastname);
  for($column=0; $column < pg_NumFields($result); $column++)
    {
    print pg_FieldName($result, $column) . "=";
    print pg_Result ($result, 0, $column) . "<br>\n";
    }
  print "<form  action=\"webapp.php3\" method=\"POST\">\n";
  print "<input type=\"hidden\" name=\"HDLastname\" value=\"$lastname\">\n";
     
  print "<input type=\"submit\" name=\"BDDelete\" value=\"Delete\">\n";
  print "&nbsp;&nbsp;\n";
  print "<input type=\"submit\" name=\"BDCancel\" value=\"Cancel\">\n";
  print "&nbsp;&nbsp;\n";
  print "</form>\n";
  }

Function doGeneric() simply calls fillSelect(), which in turn displays a row selector form. The fillSelect() code could have simply been cut and pasted into doGeneric(), but there's always a possibility that doGeneric() will later perform different functions.
 
function doGeneric()
  {
  fillSelect();
  }

The Database Interface Functions

These editToDB(), addToDB(), deleteFromDB() parse $HTTP_POST_VARS for all relevant column data and the record key (lastname in this simple tutorial), build a SQL statement to accomplish their task, then call the previously coded sql() utility to modify the database.

Note that these functions do no user interface nor confirmations. That's all done by the functions that load $HTTP_POST_VARS. Note also that addToDB() does not check for an existing same lastname (duplicate prevention), but such logic could easily be added.

As previously explained, these three functions are used as state specific functions, and are called only from the $fcn switch of the main routine.

The following is the code for these three functions:
 
function editToDB()
  {
  global $HTTP_POST_VARS;
  $statement = "UPDATE mytable ";
  $statement .= "SET lastname='" . chop($HTTP_POST_VARS["HILastname"]);
  $statement .= "',firstname='" . chop($HTTP_POST_VARS["TIFirstname"]);
  $statement .= "',email='" . chop($HTTP_POST_VARS["TIEmail"]);
  $statement .= "' WHERE lastname='" . chop($HTTP_POST_VARS["HILastname"]);
  $statement .= "';";
  sql($statement);
  }

function addToDB()
  {
  global $HTTP_POST_VARS;
  $statement = "INSERT INTO mytable VALUES(";
  $statement .= "'" . $HTTP_POST_VARS["TILastname"] . "', ";
  $statement .= "'" . $HTTP_POST_VARS["TIFirstname"] . "', ";
  $statement .= "'" . $HTTP_POST_VARS["TIEmail"] . "'";
  $statement .= ");";
  sql($statement);
  }

function deleteFromDB()
  {
  global $HTTP_POST_VARS;
  $lastname= $HTTP_POST_VARS["HDLastname"];

  sql("DELETE FROM mytable where lastname='$lastname';");
  }

Coding the Main Routine

The main routine does exactly three things:
  1. Write header information (caching, etc) to the page
  2. Call setGlobals() to set $fcn to the name of the button clicked on the previous page invocation
  3. Deploy a switch statement to call the subroutine appropriate for the button clicked on the previous page invocation.
The code for the main routine follows:
 
header('<META HTTP-EQUIV="pragma" CONTENT="nocache">'); 

setGlobals();

switch ($fcn)
  {
  case "BEdit":
    doEdit();
    break;
  case "BAdd":
    doAdd();
    break;
  case "BDelete":
    doDelete();
    break;
  case "BESubmit":
    editToDB();
    doGeneric();
    break;
  case "BASubmit":
    addToDB();
    doGeneric();
    break;
  case "BDDelete":
    deleteFromDB();
    doGeneric();
    break;
  case "BACancel":
  case "BDCancel":
  case "BECancel":
  case "BNone":
    doGeneric();
    break;
  default:
    print "Illegal function identifier encountered:$fcn:<p>\n";
    break;
  }

Here's the Finished Product

The following code listing is webapp.php3 with complete edit, add and delete capabilities, and with a few comments:
 
<?php

//**** BEGIN GLOBAL VARIABLES ****
$fcn = BNone;
//**** END GLOBAL VARIABLES ****


//**** BEGIN GENERIC SQL FUNCTIONS ****

function sql($command)
  {
  $connection = pg_Connect ("dbname=mydb port=5432 user=nobody");
  if ($connection == 0)
    {
    print "Failed to open database!<br>\n";
    return 0; 
    }
  $result = pg_Exec($connection, $command);
  pg_Close($connection);  
  return $result;
  }

function listInOrder()
  {
  return sql("select * from mytable order by lastname;");
  }

function lookup($lastname)
  {
  return sql("SELECT * FROM mytable where lastname='$lastname';");
  }

function deleteByLastname($lastname)
  {
  return sql("DELETE FROM mytable where lastname='$lastname';");
  }

//**** END GENERIC SQL FUNCTIONS ****


//**** BEGIN HIGH LEVEL APP UTILITY FUNCTIONS ****

function presentInputForm($lastname, $firstname, $email, $fcnLetter)
  {
  switch ($fcnLetter)
    {
    case "E":
    case "A":
      break;
    default: 
      print "<p><h1>Illegal function letter ($fcnLetter) in presentInputForm()<p>\n";
print "<p><a href=\"webapp.php3\">Back to Select Screen</a></h1><p>\n";
      return;
      break;
    }

  print "<form action=\"webapp.php3\" method=\"POST\">\n";

  print "    <p>Last  Name: ";
  switch ($fcnLetter)
    {
    case "E":                   //don't allow editing of key value
      print "$lastname<br>\n";

      print "<input type=\"hidden\" name=\"HILastname\" value=\"$lastname\">\n";
      break;
    case "A":
      print "<input type=\"text\" name=\"TILastname\" value=\"$lastname\"><br>\n";
      break;
    }

  print "    <p>First Name: ";
  print "<input type=\"text\" name=\"TIFirstname\" value=\"$firstname\"><br>\n";
  print "    <p>     Email: ";
  print "<input type=\"text\" name=\"TIEmail\" value=\"$email\"><br>\n";

  //**** PLACE FUNCTION LETTER IN BUTTON NAMES TO CONVEY STATE ****
  print "    <p><input type=\"submit\" name=\"B" . $fcnLetter;
  print "Submit\" value=\"Submit\">\n";
  print "&nbsp;&nbsp;\n";

  print "<input type=\"submit\" name=\"B" . $fcnLetter;
  print "Cancel\" value=\"Cancel\">\n";
  print "</form>\n";

  }

function fillSelect()
  {
  $result=listInOrder(); //THIS REPLACES FORMER INLINE pg_ CALLS!

  print "<form  action=\"webapp.php3\" method=\"POST\">\n";
  print "<p><select name=\"DROWS\" size=\"1\">\n";
  print "<pre>";

  for($row=0; $row < pg_NumRows($result); $row = $row + 1)
    {
    print "  <option>";
    for($column=0; $column < pg_NumFields($result); $column++)
      {
      print pg_Result ($result, $row, $column) . ":::";
      }
    print "<br></option>\n";
    }
  print "</pre>";
  print "</select></p>\n";
  print "&nbsp;&nbsp;\n";
  print "<input type=\"submit\" name=\"BEdit\" value=\"Edit\">\n";
  print "&nbsp;&nbsp;\n";
  print "<input type=\"submit\" name=\"BAdd\" value=\"Add\">\n";
  print "&nbsp;&nbsp;\n";
  print "<input type=\"submit\" name=\"BDelete\" value=\"Delete\">\n";
  print "</form>\n";
  }

function setGlobals()
  {
  global $HTTP_POST_VARS;
  if(sizeof($HTTP_POST_VARS) < 1)
    {
    return;
    }
  global $fcn;
  reset ($HTTP_POST_VARS);
  while (list ($key, $val) = each ($HTTP_POST_VARS)) 
    {
    if(substr($key, 0, 1) == 'B') //Only buttons begin with B
      {
      $fcn=$key;
      }
    }
  }

//**** END HIGH LEVEL APP UTILITY FUNCTIONS ****


//**** BEGIN STATE-SPECIFIC SUBROUTINES ****

function doEdit()
  {
  global $HTTP_POST_VARS;
  if(ereg("^(.*) :::.* :::.* :::", $HTTP_POST_VARS["DROWS"], $regs))
    {
    $lastname = $regs[1];
    }
  else
    {
    print "<p>ERROR: doEdit() recieved no last name key!<p>\n";
    return;
    }
  $result = lookup($lastname);

  presentInputForm(
        pg_Result($result, 0, 0),
        pg_Result($result, 0, 1),
        pg_Result($result, 0, 2),
        "E");
  }

function doAdd()
  {
  global $HTTP_POST_VARS;
  presentInputForm("", "", "", "A");
  }

function doDelete()
  {
  global $HTTP_POST_VARS;
  if(ereg("^(.*) :::.* :::.* :::", $HTTP_POST_VARS["DROWS"], $regs))
    {
    $lastname = $regs[1];
    }
  else
    {
    print "<p>ERROR: doDelete() recieved no last name key!<p>\n";
    return;
    }

  $result=lookup($lastname);
  for($column=0; $column < pg_NumFields($result); $column++)
    {
    print pg_FieldName($result, $column) . "=";
    print pg_Result ($result, 0, $column) . "<br>\n";
    }
  print "<form  action=\"webapp.php3\" method=\"POST\">\n";
  print "<input type=\"hidden\" name=\"HDLastname\" value=\"$lastname\">\n";
     
  print "<input type=\"submit\" name=\"BDDelete\" value=\"Delete\">\n";
  print "&nbsp;&nbsp;\n";
  print "<input type=\"submit\" name=\"BDCancel\" value=\"Cancel\">\n";
  print "&nbsp;&nbsp;\n";
  print "</form>\n";
  }

function doGeneric()
  {
  fillSelect();
  }

function editToDB()
  {
  global $HTTP_POST_VARS;
  $statement = "UPDATE mytable ";
  $statement .= "SET lastname='" . chop($HTTP_POST_VARS["HILastname"]);
  $statement .= "',firstname='" . chop($HTTP_POST_VARS["TIFirstname"]);
  $statement .= "',email='" . chop($HTTP_POST_VARS["TIEmail"]);
  $statement .= "' WHERE lastname='" . chop($HTTP_POST_VARS["HILastname"]);
  $statement .= "';";
  sql($statement);
  }

function addToDB()
  {
  global $HTTP_POST_VARS;
  $statement = "INSERT INTO mytable VALUES(";
  $statement .= "'" . $HTTP_POST_VARS["TILastname"] . "', ";
  $statement .= "'" . $HTTP_POST_VARS["TIFirstname"] . "', ";
  $statement .= "'" . $HTTP_POST_VARS["TIEmail"] . "'";
  $statement .= ");";
  sql($statement);
  }

function deleteFromDB()
  {
  global $HTTP_POST_VARS;
  $lastname= $HTTP_POST_VARS["HDLastname"];

  sql("DELETE FROM mytable where lastname='$lastname';");
  }

//**** END STATE-SPECIFIC SUBROUTINES ****


//**** BEGIN MAIN PROGRAM ****

header('<META HTTP-EQUIV="pragma" CONTENT="nocache">'); 

setGlobals();

switch ($fcn)
  {
  case "BEdit":
    doEdit();
    break;
  case "BAdd":
    doAdd();
    break;
  case "BDelete":
    doDelete();
    break;
  case "BESubmit":
    editToDB();
    doGeneric();
    break;
  case "BASubmit":
    addToDB();
    doGeneric();
    break;
  case "BDDelete":
    deleteFromDB();
    doGeneric();
    break;
  case "BACancel":
  case "BDCancel":
  case "BECancel":
  case "BNone":
    doGeneric();
    break;
  default:
    print "Illegal function identifier encountered:$fcn:<p>\n";
    break;
  }

//**** END MAIN PROGRAM ****
?>

There it is. A web app. Sure, it's a single table app that uses last name as a key. Sure, it has no data integrity features like validation and transactions. Foremost, it's not reusable other than cut and paste. But those features can be added incrementally, and the results cloned for additional tables. The main point of this tutorial is it has gotten you to the point where you understand how PHP works and what you can do with it. The rest is just incremental changes or finding handy PHP tool sets like phplib.

Steve Litt is the author of the Universal Troubleshooting Process course. He can be reached at Steve Litt's email address.

Installing phplib in a Postgres Environment

By Steve Litt
The phplib library encapsulates real-world data functionalities in an OOP library, freeing you from taking the steps in the tutorial for every table in the database. The quick installation instructions are contained in documentation-1.html#ss1.3 in the phplib distro documentation, but those instructions work only with mySQL. The instructions in this article cover postgreSQL implementation.

Start as user root by downloading the distribution (phplib-7.2b.tar.gz at this writing) from the Apache website into user root's home directory, and then use tar -xzvf to convert it to a tree below /root:

/root/
  phplib-7.2b/
    doc/
    pages/
    php/
    stuff/

Copy the Directories

The phplib library is all include files and need needn't be compiled. The installation is done by directory copies and a few other procedures.

The doc/ directory contains phplib documentation, mostly in HTML form. Life is easier if it's accessible via browser, so you can can:

cp -R /root/phplib-7.2b/doc /home/httpd/html/phpdoc
Note that the preceding assumes your server's document root directory is /home/html -- the default on Red Hat installations. Note that the preceding copy operation is not necessary for the proper operation of phplib, but merely makes the documentation easier to access.

Next, copy the php/ directory to a directory *parallel* to your document root directory. Verify that there is no such directory already. If there is, back it up. Then, assuming document root /home/httpd/html, do the following:

cp -R /root/phplib-7.2b/php /home/httpd
The php directory contains all the .inc files containing the various classes and functions comprising phplib. Next you need to install phplib's sample pages. This is done by copying the pages/ directory to the document root:
cp -R /root/phplib-7.2b/pages /home/httpd/html

Modify php.ini

The php.ini file, typically residing in /etc/httpd, determines the startup and configuration of php. Do the following verifications and/or changes in php.ini:
 

Modify local.ini

In local.ini, which is in the php directory (/home/httpd/php in this tutorial), change the DB_Example class as follows:
class DB_Example extends DB_Sql {
  var $Host     = "localhost";
  var $Database = "libtest";
  var $User     = "postgres";
  var $Password = "";
Note that in real life, for security reasons you'd probably use a user with less muscle than postgres, but use of the postgres user yields the highest likelihood of success in this tutorial.

NOTE: Please remember that the $Database is set to "libtest". This is a database you will need to create and load with data in a later step.

Modify prepend.php3

This is what changes your phplib from MySQL enabled to Postgres enabled. Locate two lines that look like this:
require($_PHPLIB["libdir"] . "db_mysql.inc");  /* Change this to match your database. */
#require($_PHPLIB["libdir"] . "db_pgsql.inc");  /* Change this to match your database. */
The preceding has MySQL enabled and Postgres commented out. You want the opposite, so prepend a hash mark to the db_mysql.inc line and delete the leading hash mark from the db_pgsql.inc line.

Create and Load the libtest Database

Remember when you modified local.ini you set $Database = "libtest"? That means database libtest must be created and loaded to complete this exercise. Create libtest by logging in as user postgres and performing the following shell command:
createdb libtest


You need to load the database **FOR POSTGRESQL**, not for MySQL as in the phplib quick install docs. Make sure postmaster is running, make sure you're logged in as user postgres, then go to the stuff directory (/root/phplib-7.2b/stuff in this tutorial), and execute the following shell command:

psql mydb < create_database.pgsql
You will see a bunch of text fly up the screen as the sql statements of create_database.pgsql are executed, creating and loading four tables. You can see the tables with psql's \dt command.

As a final step, make this database available to all, as shown in the following session:
[postgres@mydesk pgsql]$ psql libtest
Welcome to the POSTGRESQL interactive sql monitor:
  Please read the file COPYRIGHT for copyright terms of POSTGRESQL
[PostgreSQL 6.5.2 on i686-pc-linux-gnu, compiled by gcc egcs-2.91.66]

   type \? for help on slash commands
   type \q to quit
   type \g or terminate with semicolon to execute query
 You are currently connected to the database: libtest

libtest=> \z
Database    = libtest
 +-----------------------+--------------------------+
 | Relation              | Grant/Revoke Permissions |
 +-----------------------+--------------------------+
 | active_sessions       |                          |
 | active_sessions_split |                          |
 | auth_user             |                          |
 | auth_user_md5         |                          |
 +-----------------------+--------------------------+
libtest=> grant ALL on active_sessions TO PUBLIC;
libtest=> grant ALL on active_sessions_split TO PUBLIC;
libtest=> grant ALL on auth_user TO PUBLIC;
libtest=> grant ALL on auth_user_md5 TO PUBLIC;
libtest=> \z
Database    = libtest
 +-----------------------+--------------------------+
 | Relation              | Grant/Revoke Permissions |
 +-----------------------+--------------------------+
 | active_sessions       | {"=arwR"}                |
 | active_sessions_split | {"=arwR"}                |
 | auth_user             | {"=arwR"}                |
 | auth_user_md5         | {"=arwR"}                |
 +-----------------------+--------------------------+
libtest=> \q
[postgres@mydesk pgsql]$

WARNING!

The permissions granted in the preceding session are a security risk, and are done only to maximize the ease of this exercise. Be sure to undo them with the proper REVOKE FROM psql commands once phplib with Postgres is installed and tested. Grant only those permissions, to the proper users, necessary to operate an app in a production environment.

Restart Apache

To start with a known state, be sure to restart Apache. On Red Hat boxes, the command is as follows:
/etc/rc.d/init.d/httpd restart

Test Your phplib Configuration

Remember you copied the pages/ directory directly below your server's directory root (/home/httpd/html on default Red Hat systems). Access index.php3 with the following URL:
http://192.168.100.10/pages/index.php3
Click the reload link and note that the Per session Data number increments. If you have cookies the last number will be "remembered" in future sessions -- otherwise future calls to index.php3 will start over at 1.

Click the "Load a more complex example" link, and log in as user kris, password test. These values were loaded by create_database.pgsql. Click the "Logout and delete your authentication information" link, and then reclick the "Load a more complex example" link and deliberately mistype the password, and note that you don't get in. Retry with the correct password, and note you get in.

Using the Admin Pages

Access view_sessions.php3 in the admin subdirectory, and note that you can see, refresh, garbage collect and delete sessions.
http://192.168.100.10/pages/admin/view_sessions.php3
Next, try this URL and create a user:
http://192.168.100.10/pages/admin/new_user.php3
Note this screen also allows you to delete and edit users. Note that the level listbox allows multiple selection via Shift-click and Ctrl-click.

The material in this article's "Test Your phplib Configuration" section is what's supposed to happen. Often it doesn't. So Troubleshoot.

Troubleshoot

It took me two hours the first time I completed the exercises outlined in this article. Much of the problem was due to the MySQL centricity of the phplib docs, but some were due to my misconfigurations, and some were due to unknown causes. At one point I had an error that I finally cured by deleting all rows out of one of the libtest database's tables (I don't remember the error, or which table, and I can't reproduce the problem).

Be sure to read the phplib documentation and this article enough to have a conceptual idea of what configures what. Check that you've coded everything right.

READ EVERY ERROR MESSAGE CAREFULLY AND IN FULL!

Many error messages point you in the right direction. Do they indicate a lack of Postgres or an erroneous install of MySQL? Go after the config options that affect that. Does it indicate a permissions problem? The error messages are your best friend.

Finally, use the Universal Troubleshooting Process detailed at www.troubleshooters.com/tuni.htm. When I started writing this article I probably knew less about phplib than you do, but I was still able to troubleshoot it via the UTP.
 

In Conclusion...

I'd really like to go on and discuss building specific sample apps, and maybe even a shopping cart, with phplib. But I've gotta quit writing and start publishing sooner or later, so this exercise concludes the PHP tutorial.
Steve Litt is an advocate for the Universal Troubleshooting Process. He can be reached at Steve Litt's email address.

Congratulations!

By Steve Litt
That's it. If you completed this tutorial, you've gotten PHP running, explored state, data listing, and finally a add/change/delete data app. Sure, that data app is naively oversimplified, but it has removed any PHP related intimidation you might have felt. This is the stuff being done in corporations now. PHP use has grown an order of magnetude in the past year.

Last but not least, you've learned to install phplib in a Postgres environment, and by doing that you've learned more about phplib than would a person installing it in the "preferred" MySQL environment.

Where to Go From Here

Obviously this tutorial didn't produce a professional grade app. You'll need to add a numeric non-meaningful keys, data validation, data transactions via commit and rollback, PHP large objects. More importantly, you'll need to thoroughly learn a reusable PHP library such as phplib. All these things are matters of incremental learning and reading OPC (Other People's Code). It's not rocket science. If this isn't obvious to you, you might want to read my book, "Rapid Learning: Secret Weapon of the Successful Technologist".

So when your boss or a recruiter asks you whether you know PHP, reply "absolutely!".

Steve Litt is the webmaster of Troubleshooters.Com and the content lead for Troubleshooting Professional Magazine. He can be reached at Steve Litt's email address.

Linux Log:What's in a License

Linux Log is a regular column in Troubleshooting Professional Magazine, authored by Steve Litt. Each month we'll explore a facet of Linux as it relates to that month's theme.
A friend and co-member of my LUG, LEAP-CF, made made me aware that PHP4 has different licensing provisions than PHP3. Both he and I wrote to Zend, initiating a long discussion of licensing. Richard Stallman was eventually included in the thread. I don't know enough about licensing to understand all that was discussed. To make a very long story very short, from the information I've been able to gather, PHP3 sported an Apache license with GPL as a secondary optional license. The GPL option has been removed in PHP4. There were some other changes that are beyond the scope of this article or my ability to describe. All of this got me interested in Open Source licensing.

The GNU GPL is one of the most restrictive licenses with regard to preventing future proprietarization of a product. It specifically allows inline modification of the source code, and redistribution of same. This means if the original author abandons the project or totally botches it, someone else can maintain the code and distribute the software in a modified condition without permission from the original author. In my opinion, this ability enhances the staying power of software -- a definite plus if I depend on the program.

"Free", and "Open"

The words "free" and "open" are pontificated daily. What do they mean?

The vital distinction is this: Free to do what?

Most Open Source people agree with Richard Stallman, who coined the phrase "Free Software", that "free" means free to use, modify, and access source code, now and forever, in any software. However, it could also be construed to mean free to take proprietary and/or use in proprietary software, which to a great extent is the opposite of Stallman's definition. The term must be defined in any discussion.

"Open" could take on the same two meanings. A trip to the "Open Source Definition" at www.opensource.org/osd.html pretty much stresses source availability forever rather than freedom to go proprietary. However, Open Source Definition allows (but doesn't specify) the license can demand patch-only modifications. The way I see it, licenses demanding everyone have the right to be able to make modifications right inside the software (rather than as a patch) are more "free" and "open", because if the present maintainer fails to put out a good product, another person or group can fork the project, making it available to all. With a patch-only license that is problematic, as nobody but the original author has the right to edit and redistribute the original source itself.

In any discussion involving "free" and "open", we need to always define our terms. Is the software intended to be open source to everyone always, or is it intended to be something that can be made proprietary to a greater or lesser degree. Without such a clarification, these words are propaganda. A few times in the past month I heard it said that the QPL, which is a patch-only modification license, is "more open than the GPL". I dispute that statement because I believe that the majority view "open" as "open to perpetual source perusal and in-place modification, and even forking", not "open to distribute in whatever manner you wish" or "redistribute or change it but it still belongs to me". All statements comparing the "openness" of licenses should contain a definition of "open".

The Escrow Function of Open Source

Of Open Source's many advantages, I think it's Escrow function is the most valuable. In some high-priced proprietary software purchases, the software vendor is required to place the source code in third party escrow to ensure that the software customer can continue to use and fix the software if the vendor goes out of business or acts in bad faith. Because Open Source software includes source with the software, it places the escrow in the hands of the customer, which is even more beneficial to the customer. If you are using my UMENU (GPL) software, and I fail to maintain it or make it available, you're not up a creek without a paddle. You can modify and use my source to your needs, and because it's GPL, you can redistribute those modifications.

There are two different escrow functions: 1)Ability to modify code to meet internal needs, and 2)Ability to modify and redistribute as a package (fork) if the initial author/maintainer is doing a poor job. All Open Source software allows the first, but some do not, for practical purposes, allow the second.

UMENU is GPL. If I stopped maintaining UMENU and it became obsolete, absolutely anyone could take my source code, update it, and offer it to the world. UMENU would continue long after I stopped updating it. Contrast this to what would happen if UMENU were QPL. With QPL, all modifications must be made as a patch, and then distributed with the original software. 10 years after I stopped maintaining UMENU, the new "maintainers" would still need to distribute UMENU as my 10 year old source plus one or more extensive patches. Modifications on the modified materials might be patches on top of patches. This is not conducive to easy installation or maintenance.

But UMENU is GPL, so if I drop the ball anyone can pick it up and run with it. The GPL is an excellent choice to provide Escrow Function for project succession.

For Profit Companies and Open Source Licensing

I've heard many say that for-profit companies won't contribute to GPL. Of course, IBM is heavily supporting GNU GPL Linux. According to a 3/26/2000 email from Richard Stallman to zeev@zend.com (I was cc'ed), IBM has contributed to GPL products Emacs and GCC. So I have a little trouble with the concept that "for profit companies will not contribute to GPL projects". They might not like it as well. They might play hardball and say they'll only participate in a non-GPL project. But history proves that *sometimes* for-profit companies *do* contribute to GPL, if they are given no other alternative.

My Personal Views on Licensing

The FSF will hate me for this, but I strongly believe every software author has the moral right to set the terms of use for his or her software, be it proprietary, public domain (no restrictions whatsoever), strict copyleft like GPL, or original author control via patch-only modification distribution provisions. I'd be a hypocrite to say otherwise, since much of my family's support comes from sale of exclusively copyrighted books. To me, as long as the software author does not use tricky licenses, competitor sabotage, UCITA type scams, bans on reverse engineering, or other misleading, monopolistic or fraudulant tactics, I believe he or she is morally and ethically correct in any license he or she chooses.

I *do* have a problem with software authors reducing free distribution guarantees after initial release. I see free distribution guarantees as a promise to be kept. I believe it would be unethical for me to change the license of UMENU from GPL to QPL, regardless of whether or not that would be legal. I thought long and hard before releasing UMENU GPL, because I take GPL seriously enough that if I need to use the code in a proprietary product, I'll spend the extra time to write it from scratch.

The Bottom Line

If you're looking for a conclusion of which license is best, you won't get it here. I can't tell you whether GPL is best (I like it the best, but I know little about licensing). The only bottom line I can give you is this: Look VERY carefully at software licensing provisions of any software you use or contribute to. All Open Source explicitly allow the software source to be given away (free beer), but the licenses vary in protecting ability to modify and distribute modifications inline incorporated in the original product (free speech). Many also require you to insert an advertising sentence that your code includes theirs. If you use 10 such packages it can consume the entire screen.

Every software user on earth should read the Open Source definition and the FSF Free Software and Copyleft definitions. All the URL's are in this issue's URL's section.

Steve Litt is a director on the LEAP-CF executive committee. He can be reached at Steve Litt's email address.

Letters to the Editor

All letters become the property of the publisher (Steve Litt), and may be edited for clarity or brevity. We especially welcome additions, clarifications, corrections or flames from vendors whose products have been reviewed in this magazine. We reserve the right to not publish letters we deem in bad taste (bad language, obscenity, hate, lewd, violence, etc.).
Submit letters to the editor to Steve Litt's email address, and be sure the subject reads "Letter to the Editor". We regret that we cannot return your letter, so please make a copy of it for future reference.

How to Submit an Article

We anticipate two to five articles per issue, with issues coming out monthly. We look for articles that pertain to the Troubleshooting Process, or articles on tools, equipment or systems with a Troubleshooting slant. This can be done as an essay, with humor, with a case study, or some other literary device. A Troubleshooting poem would be nice. Submissions may mention a specific product, but must be useful without the purchase of that product. Content must greatly overpower advertising. Submissions should be between 250 and 2000 words long.

By submitting content, you give Troubleshooters.Com the non-exclusive, perpetual right to publish it on Troubleshooters.Com or any A3B3 website. Other than that, you retain the copyright and sole right to sell or give it away elsewhere. Troubleshooters.Com will acknowledge you as the author and, if you request, will display your copyright notice and/or a "reprinted by permission of author" notice. Obviously, you must be the copyright holder and must be legally able to grant us this perpetual right. We do not currently pay for articles.

Troubleshooters.Com reserves the right to edit any submission for clarity or brevity. Any published article will include a two sentence description of the author, a hypertext link to his or her email, and a phone number if desired. Upon request, we will include a hypertext link, at the end of the magazine issue, to the author's website, providing that website meets the Troubleshooters.Com criteria for links and that the author's website first links to Troubleshooters.Com. Authors: please understand we can't place hyperlinks inside articles. If we did, only the first article would be read, and we can't place every article first.

Submissions should be emailed to Steve Litt's email address, with subject line Article Submission. The first paragraph of your message should read as follows (unless other arrangements are previously made in writing):

I (your name), am submitting this article for possible publication in Troubleshooters.Com. I understand that by submitting this article I am giving the publisher, Steve Litt, perpetual license to publish this article on Troubleshooters.Com or any other A3B3 website. Other than the preceding sentence, I understand that I retain the copyright and full, complete and exclusive right to sell or give away this article. I acknowledge that Steve Litt reserves the right to edit my submission for clarity or brevity. I certify that I wrote this submission and no part of it is owned by, written by or copyrighted by others.
After that paragraph, write the title, text of the article, and a two sentence description of the author.
 

URLs Mentioned in this Issue

  • Speaker URL's
  • Apachecon URL's
  • http://www.apachecon.com: The Apachecon home page. See details of Apachecon 2000 in Orlando, and the upcoming Apachecon in London.
  • http://www.camelot-com.com: Home of Camelot Communications, who produced ApacheCon.
  • Technology URL's
  • http://www.apache.org: Home page of the Apache Software Foundation, the umbrella organization overseeing Apache and Apache module development.
  • http://www.php.net: The PHP project headquarters, including downloads, docs and other services..
  • http://www.cri.ensmp.fr/~coelho/mod_macro/: Home of the mod_macro Apache module.
  • mod_vhost_alias: For many vhosts, real time addition of vhosts, small memory footprint, can be used for some and not others
  • http://perl.apache.org: The mod_perl Apache module headquarters. You can download mod_perl from http://perl.apache.org/dist/ or from http://www.perl.com/CPAN-local/modules/by-module/Apache/ or any CPAN mirror.
  • http://apache.org/docs/mod/mod_vhost_alias.html: Description of mod_vhost_alias.
  • http://phplib.netuse.de: The phplib website, with downloads, samples and docs.
  • postgres
  • http://www.redhat.com: Home of Red Hat Software. After all these years, it's still my favorite server operating system.
  • http://www.faqts.com/knowledge-base/view.phtml/aid/242/fid/3/lang/en: On error, forms should move the user forward to the form, not tell the user to use the back button.

  •  
  • Exhibitor URL's
  • http://www.valinux.com: Website of VA Linux, Jeff Rabin's employer. To buy a small number of systems, click the "systems" link.
  • http://www.linuxmall.com: Home page of LinuxMall.com, who sold huge inventories of software at bargain prices at Apachecon.
  • http://www.ora.com: Home page of O'Reilly of technical book fame.
  • http://www.ibm.com: Home of IBM, our new Open Source ally and provider of many fine speakers to Apachecon.
  • http://www.sun.com: Home of Sun, originators of Java and a major sponsor of Apachecon.
  • http://www.collab.net: Home of Collab.net, the Apachecon exhibitor who put Open Source developers together with corporations needing Open Source development.
  • http://www.lutris.com: Home of Lutris, whose Open Source Enhydra web development environment is getting a lot of attention. You can see Enhydra at http://www.enhydra.org.
  • http://www.borders.com: Home of Borders Bookstore, who sold mucho copies of Apache Server Unleashed at Apachecon.
  • http://www.zend.com: Home of Zend Technologies Ltd., major contributors to PHP and makers of the Zend engine.
  • Free Software and Open Source URL's (licensing)
  • http://www.fsf.org/philosophy/free-sw.html: FSF's definition of Free Software.
  • http://www.fsf.org/copyleft/copyleft.html: FSF's definition of the copyleft concept.
  • http://www.fsf.org/gnu/the-gnu-project.html: A short historical description by Richard Stallman about the first software sharing community, centered around MIT's Artificial Intelligence Lab and other educational institutions in the 1960's and 1970's, and
  • http://www.gnu.org/copyleft/gpl.html: The GNU GPL as an html doc.
  • http://www.gnu.org/copyleft/gpl.txt: The GNU GPL as a text doc. This is what you include in your GPL project distro.
  • http://www.opensource.org: Home of the Open Source writings.
  • http://www.opensource.org/osd.html: The Open Source definition.
  • Miscelleneous URL's
  • http://www.mcp.com: Macmillan's Publishing Corporation, publishers of Apache Server Unleashed, Red Hat 6 Unleashed, Samba Unleashed and many more excellent technical books.
  • http://www.leap-cf.org: Home page of Linux Enthusiasts and Professionals of Central Florida -- my LUG.
  • http://www.troubleshooters.com/bookstore/rl.htm: Web home of Rapid Learning: Secret Weapon of the Successful Technologist.
  • http://www.troubleshooters.com: Steve Litt's primary website.