When Things Go Wrong (and They Will) - From Web Pages to Web Applications - PHP and MySQL: The Missing Manual (2011)

PHP and MySQL: The Missing Manual (2011)

Part 3. From Web Pages to Web Applications

Chapter 7. When Things Go Wrong (and They Will)

So far, you’ve got a growing, functioning set of scripts. You’ve got some web pages that interact with them, CSS to style both your static HTML pages and the HTML that your scripts output, and you can even go in and add some client-side JavaScript validation. Things are looking pretty good.

NOTE

Make that stronger: you should go in and add some client-side JavaScript validation.

But there’s a monster lurking in the deep. While you’ve occasionally added a die or a conditional statement to make sure your queries return a result row, your code still assumes a perfect user. Someone who always types what you expect, never enters a phone number in the email field or spaces in the Facebook URL field; someone who never needs to go back and so never clicks the browser’s Back button at an inopportune time; and never enters her information into the same form twice, furiously clicking “Add my information” instead of waiting on her lousy Internet connection.

Of course, nobody’s that perfect—especially at a computer. The reality with web applications—and in fact any type of software—is that people always find ways to break your best-intended pages and forms and scripts. They supply bad information, leave out required fields, and make a general mess of anything that you’ve not planned on being messy.

NOTE

Once again, client-side JavaScript seems awfully valuable to mention here. You can reduce a lot of this sort of problem by validating your user’s information before it gets sent to your scripts. For a lot more on how to do that, check out JavaScript & jQuery: The Missing Manual by David Sawyer McFarland (O’Reilly).

So what do you do? Well, so far, you’ve done something like Figure 7-1. Until now, this bare-bones error message has been fine. You’re the only one using your system, and you’re just testing things out, making sure your code is right. But it’s a pretty poor way to handling errors in any kind of system that’s going to make it out there in the Wild, Wild West of the Internet.

There’s little that turns a user off more than an error message like this. It’s cryptic, it reveals information about your system that it shouldn’t, and worst of all for your user, it’s ugly! On the Web, looks matter, and consistent looks matter a lot. Your errors should be reported as cleanly as possible, and in a format that’s consistent with the look and feel of the rest of your site.

Figure 7-1. There’s little that turns a user off more than an error message like this. It’s cryptic, it reveals information about your system that it shouldn’t, and worst of all for your user, it’s ugly! On the Web, looks matter, and consistent looks matter a lot. Your errors should be reported as cleanly as possible, and in a format that’s consistent with the look and feel of the rest of your site.

But it gets even worse. Try and visit the show_user.php URL again, and supply an ID of a user you know doesn’t exist. Figure 7-2 shows that what should be an error is swallowed up by your script. You get an “empty” user profile, but it looks like nothing’s wrong, even though the script received an invalid user ID.

So you have a lot of work to do here. First things first, though: what exactly should an error page have on it?

Planning Your Error Pages

When you were creating the page on which you show users, you began with HTML: mocking up a simple page, and then adding PHP as you needed it. There’s no reason to abandon that approach here, as you’re basically trying to do the same thing. You want a nice-looking page for displaying errors, and before you start digging into PHP, get things looking just right.

This “empty user” is actually a very nasty problem, because doesn’t look like there’s a problem. The show_user.php script loads up its HTML regardless of whether a SQL error occurred. Worse, because PHP is happy to simply echo out empty strings for variables without values, this page looks almost normal, except for all the missing information.

Figure 7-2. This “empty user” is actually a very nasty problem, because doesn’t look like there’s a problem. The show_user.php script loads up its HTML regardless of whether a SQL error occurred. Worse, because PHP is happy to simply echo out empty strings for variables without values, this page looks almost normal, except for all the missing information.

So first things first: create a new HTML page, and call it show_error.html. You can begin with the same structure you’ve been using for all your other pages:

<html>

<head>

<link href="../css/phpMM.css" rel="stylesheet" type="text/css" />

</head>

<body>

<div id="header"><h1>PHP & MySQL: The Missing Manual</h1></div>

<div id="example">Error Page</div>

<div id="content">

<h1>Error Page</h1>

<p>Error</p>

</div>

<div id="footer"></div>

</body>

</html>

This code creates an empty shell of an error page (see Figure 7-3), and it’s time to get to work.

No matter how good a designer and coder you are, it’s almost impossible to do good design and good coding simultaneously. By working on a mock-up, and then dealing with code, you can focus on one concern at a time. And once you’ve made that adjustment, it’s almost always easier to mock up at least a general idea of your front-end before digging into code.

Figure 7-3. No matter how good a designer and coder you are, it’s almost impossible to do good design and good coding simultaneously. By working on a mock-up, and then dealing with code, you can focus on one concern at a time. And once you’ve made that adjustment, it’s almost always easier to mock up at least a general idea of your front-end before digging into code.

What Should Users See?

So here’s your first question: what exactly goes on this page that helps your users? To answer this, consider the following two questions:

1. What information does your user need when an error has occurred?

2. In what tone does that information need to be communicated?

Tell Your Users that a Problem has Occurred

The answer to the first question should be pretty obvious. Something has gone wrong; your user needs an explanation. But even in that, there’s nuance. Should you print out an errors that looks like this, the sort of thing MySQL might kick back to one of your scripts?

#1054 - Unknown column 'firstname' in 'field list'

Almost certainly not. Unless your user is a MySQL or PHP programmer, this message isn’t helpful at all. You need to translate that into human language:

We're sorry, we couldn't locate the user's first name.

That’s much more readable, but it may provide too much information, giving the user undue cause for concern: Why can’t they find my first name? Is my record missing? Is my first name in the system? Uh oh, has my record been deleted? What’s going on?!?

NOTE

Does that reaction seem overly dramatic? Not necessarily, especially for users who don’t really trust the Internet with their personal information in the first place.

So maybe that error needs to be just as readable, but a lot less specific:

We're sorry! There's been an error processing your request.

That’s something most people can understand. Things go wrong, and something has here; Your job is simply to communicate that there was a problem, without alarming your user will all the gory details.

Use the Appropriate Tone for Your Error Message

You’ve figured out that, information-wise, your user really just needs to know that a problem has occurred. Details are probably irrelevant, and could even potentially create more worry, rather than less. But what about the tone?

This sounds pretty touchy-feely, and it is. After all, you’re dealing with human users, and that means human emotions. Getting an error message is annoying enough; if your web application errors out, it’s up to you to reduce the stress and frustration as much as possible. Otherwise, people will stop coming back.

It’s not just what you say when a problem occurs, it’s how you say it. A stern, bland error message isn’t as comforting as a colloquial, conversational one. Sometimes you can even add in a little humor. Check out Figure 7-4 for a humorous way to turn a problem into a conversation point. You can almost bet that a user that lands on this page—error or not—is going to come back to the site.

Going full on with humor might be a little strong for your example site, but you can definitely make sure that you use conversational language. Just getting away from the stern, “Error 1282: An exception has occurred” goes a long way.

This page is worth a few laughs. Unfortunately, it has some problems, too: it assumes that a “404 error” makes sense to a common user. Some geekier folks might get it, but even in your humor and errors, try and be accessible to your users.

Figure 7-4. This page is worth a few laughs. Unfortunately, it has some problems, too: it assumes that a “404 error” makes sense to a common user. Some geekier folks might get it, but even in your humor and errors, try and be accessible to your users.

For example, make just a few conversational improvements to your error page mock-up, and notice how quickly this becomes a little more palatable when the inevitable error does occur:

<html>

<head>

<link href="../css/phpMM.css" rel="stylesheet" type="text/css" />

</head>

<body>

<div id="header"><h1>PHP & MySQL: The Missing Manual</h1></div>

<div id="example">Uh oh... sorry!</div>

<div id="content">

<h1>We're really sorry...</h1>

<p><img src="../images/error.jpg" class="error" />...but something's

gone wrong. Don't worry, though, we've been notified that there's a

problem, and we take these things seriously. In fact, if you want to

contact us to find out more about what's happened, or you have any

concerns, just <a href="mailto:info@yellowtagmedia.com">email us</a>

and we'll be happy to get right back to you.</p>

<p>In the meantime, if you want to go back to the page that caused

the problem, you can do that <a href="javascript:history.go(-1);">by

clicking here.</a> If the same problem occurs, though, you may

want to come back a bit later. We bet we'll have things figured

out by then. Thanks again... we'll see you soon. And again, we're

really sorry for the inconvenience.</p>

</div>

<div id="footer"></div>

</body>

</html>

This message doesn’t say much more than “Yes, we know a problem has occurred, and we’re working on it.” Everything else is about presentation: conversational words, an image to break up the cold page (which at the end of the day still does say, “Hey, sorry, something’s broken”), and a contact link for email and another link to revisit the offending page.

Look at Figure 7-5; this message is a heck of a lot less annoying than the one in Figure 7-3, and took barely any more work to produce.

Effective testing and error pages are what often separate casual and mid-level programmers from high-end consultants and senior-level developers. These seemingly small details keep systems running and users happy, which ultimately keep the lights on and the bills paid. Ignore them at your own risk!

Figure 7-5. Effective testing and error pages are what often separate casual and mid-level programmers from high-end consultants and senior-level developers. These seemingly small details keep systems running and users happy, which ultimately keep the lights on and the bills paid. Ignore them at your own risk!

Know When to Say When

So you’re a capable PHP programmer now, and you may have some clever ideas as to what could go on this error page. You could, say, grab the user’s information from the database and personalize the page. You could set up a table that has error codes and a helpful, easy-to-read error message associated with each error code. Then, when an error occurs, you could look up the error code and print out the corresponding error message from the database.

All these ideas (and anything else you can come up with) would make for a pretty slick error page. But they also require fairly complex programming in and of themselves. There’s a database to connect to, and queries to execute. And every time you write a query, or connect to a database, you introduce the possibility of another error! So where do your users go when your error pages have errors?

As a rule of thumb, you want your error pages to rely on as little programming as possible. They shouldn’t interact with databases, and they shouldn’t be fancy. As nice as that might sound, if your error page can cause an error, you’re in trouble.

POWER USERS’ CLINIC: OVER-PROMISE AT YOUR OWN RISK

Nowhere other than error pages is it easier to over-promise and under-deliver. If you tell a user that you’re looking into their problem, you better really be looking into their problem. If you’re going to supply a contact email, make sure it’s a working email address (yes, lots of times error pages have outdated, defunct contact information), and that the email actually gets to someone who can fix the problem.

If your user thinks you’re dealing with their issue, and he comes back in a few hours only to get the same error, all the clever images and language in the world won’t keep her invested in your site. Furthermore, he’ll be annoyed not just that something went wrong, but that you apparently lied about working on the issue.

If you’re just getting started or have limited resources, you might do well to simply say that you get notified when errors occur and you usually fix problems within 24 or 36 hours, or within some time period to which you can commit. You might also provide an email address for urgent issues…and then actually watch your email! Another option is to pre-format the email with a subject line you look out for, like “URGENT” or “ERROR.” You can even set up a rule in your email program to highlight mails with that subject.

Whatever you do, make sure that your responsiveness matches what your error page promises, or you’re going to have a lot more than a programming problem to deal with.

One more bit of advice as you begin working in large companies: never let the marketing team write error page text without supervision! Not to promote stereotypes, but Marketing’s job is to sell and promote, and in an effort to make your company look good, they could over-sell your capability. Get someone good with words to help you in crafting your error page, but ultimately, you’re probably the person fixing problems; make sure you can back up what your error pages promise.

Finding a Middle Ground for Error Pages with PHP

On one hand, you want your error pages to be dead simple: some text, an image or two, and static content. That way, nothing can go wrong, and your users get some level of reassurance and comfort. On the other hand, the error page in Figure 7-5 is awfully generic. It doesn’t say very much. It would be nice to tell your users something about what actually went wrong, maybe like this:

<html>

<head>

<link href="../css/phpMM.css" rel="stylesheet" type="text/css" />

</head>

<body>

<div id="header"><h1>PHP & MySQL: The Missing Manual</h1></div>

<div id="example">Uh oh... sorry!</div>

<div id="content">

<h1>We're really sorry...</h1>

<p><img src="../images/error.jpg" class="error" />...but something's

gone wrong. <span class="error_message">the username you entered couldn't

be found in our database.</span></p>

<p>Don't worry, though, we've been notified that there's a

problem, and we take these things seriously. In fact, if you want to

contact us to find out more about what's happened, or you have any

concerns, just <a href="mailto:info@yellowtagmedia.com">email us</a>

and we'll be happy to get right back to you.</p>

<p>In the meantime, if you want to go back to the page that caused

the problem, you can do that <a href="javascript:history.go(-1);">by

clicking here.</a> If the same problem occurs, though, you may

want to come back a bit later. We bet we'll have things figured

out by then. Thanks again... we'll see you soon. And again, we're

really sorry for the inconvenience.</p>

</div>

<div id="footer"></div>

</body>

</html>

The result, shown Figure 7-6, does seem to be a good compromise between a generic error page and one that’s so tricked up with user-specific information that it becomes error-prone in itself. So how can you get this personalized error message in place, and still keep the programming minimal?

You get two benefits from this minor change from the generic error page in Figure 7-5: first, you’re now showing users a message related to the true problem. That bit of personalization gives them hope that you know what’s going on. Second, by attaching a CSS class to this message, you can easily change and update how this message looks.

Figure 7-6. You get two benefits from this minor change from the generic error page in Figure 7-5: first, you’re now showing users a message related to the true problem. That bit of personalization gives them hope that you know what’s going on. Second, by attaching a CSS class to this message, you can easily change and update how this message looks.

Creating a PHP Error Page

Almost everything on the template for this error page is straight HTML. All that’s dynamic—in other words, all that would change from request to request—is the actual error message. So this becomes a relatively easy exercise. As usual, you can start by putting in a variable for the error message, and just assume you’ll come back and actually assign a value to that variable in a bit.

<html>

<head>

<link href="../css/phpMM.css" rel="stylesheet" type="text/css" />

</head>

<body>

<div id="header"><h1>PHP & MySQL: The Missing Manual</h1></div>

<div id="example">Uh oh... sorry!</div>

<div id="content">

<h1>We're really sorry...</h1>

<p><img src="../images/error.jpg" class="error" />

<?php echo $error_message; ?>

<span></p>

<p>Don't worry, though, we've been notified that there's a

problem, and we take these things seriously. In fact, if you want to

contact us to find out more about what's happened, or you have any

concerns, just <a href="mailto:info@yellowtagmedia.com">email us</a>

and we'll be happy to get right back to you.</p>

<p>In the meantime, if you want to go back to the page that caused

the problem, you can do that <a href="javascript:history.go(-1);">by

clicking here.</a> If the same problem occurs, though, you may

want to come back a bit later. We bet we'll have things figured

out by then. Thanks again... we'll see you soon. And again, we're

really sorry for the inconvenience.</p>

</div>

<div id="footer"></div>

</body>

</html>

Save this file as show_error.php. And there’s another wrinkle here: this error page will apply to all your scripts and HTML pages. So don’t save it in a Chapter 7 directory; put it in a scripts/ directory in the root of your site, so it’s easily accessible.

NOTE

If you want to follow along exactly with the book’s structure, save this file in phpMM/scripts/ where phpMM/ is the root directory in this book’s online examples (see page xvii).

Now you need to get the error message. What’s the easiest way to do that? Better yet, what’s the least error-prone way to do that? Well, probably by using request parameters and the $_REQUEST array.

<?php

$error_message = $_REQUEST['error_message'];

?>

<html>

<!-- Existing HTML and PHP -->

</html>

What’s so good about this approach? First, it’s about as basic as PHP programming can be. You’re not using any real calculation, but instead just pulling a value out of an array. Better still, it’s not your own custom array, but one that PHP provides for you, and even fills for you, using information supplied in the request to show_error.php.

Testing out Your Faulty Solution

With all that in mind, try this page out in a browser. Visit your script’s URL and add a request parameter. So you might use something like this in your URL:

http://www.yellowtagmedia.com/phpMM/scripts/

show_error.php?error_message=There's%20been%20a%20problem

%20connecting%20to%20the%20database.

NOTE

This URL should all be on one line in your browser bar. Additionally, many browsers will convert spaces to the web-safe equivalent, that strange %20. That’s a way of telling a browser “insert a space.”

You should see something like Figure 7-7, which is a pretty nice looking error page without a lot of work.

One of the nicest things about any script that uses request parameters and $_REQUEST is that you can easily test these scripts with a little command-line magic. Just name your parameters on the command line, separate the first one from your script with a ?, and then separate multiple request parameters from each other with &.

Figure 7-7. One of the nicest things about any script that uses request parameters and $_REQUEST is that you can easily test these scripts with a little command-line magic. Just name your parameters on the command line, separate the first one from your script with a ?, and then separate multiple request parameters from each other with &.

The simplicity of using request parameters that are just plain text, passed from one page or script to another is the beauty of show_error.php. There’s very little that can go wrong. That’s what you want in an error page: elegance and simplicity.

You do need to make one fix, though: that back slash showing up before a single apostrophe is no good. You can get rid of that with a little regular expression magic. Replace all occurrences of a forward slash with…well, with nothing:

$error_message = preg_replace_all("/\\\\/", '',

$_REQUEST['error_message']);

PHP has an oddity in that you need to actually use four back-slashes to match a single back-slash. So \\\\ matches \, oddly enough. That’s because you’re sort of “fighting” the PHP escape mechanism—which uses a backslash.

Expect the Unexpected

Things are looking good. But once again, you’re assuming that things go just the way you want. In fact, that’s exactly the sort of thinking that leads people to ignore error pages. So if you need to deal with problems to the point that you’re creating an error page, you better believe that problems can also occur when you’re actually on the error page.

Thankfully, you’ve cut down on most of that with simplicity. But what about if there’s not an error_message request parameter? Then you get something like Figure 7-8.

Apparently…well, apparently nothing. This page looks incomplete, which actually makes the user who landed here feel worse about their problem than they did when they first realized something went wrong.

Figure 7-8. Apparently…well, apparently nothing. This page looks incomplete, which actually makes the user who landed here feel worse about their problem than they did when they first realized something went wrong.

FREQUENTLY ASKED QUESTION: WHY IS SHOW_ERROR.PHP STILL IN A SCRIPTS/ DIRECTORY?

In the last chapter, you should have started moving your scripts from nested scripts/ directories into the main parts of your site. So you probably started having web forms like create_user.html right alongside create_user.php and show_user.php. That’s because your HTML pages and your PHP pages are starting to be a lot more alike than they are different.

But here’s show_error.php, still in a scripts/ directory. So what gives? Well, show_error.php really isn’t just another HTML page. It’s something special—something used across your application. In fact, it’s just like database_connection.php, which you should also keep in your main scripts/ directory. These are really utilities, not pages that should live alongside other HTML pages.

Now, there’s another natural question that follows along from this: won’t you eventually end up with a giant mess? PHP files living alongside HTML…and then what? Images next to JavaScript next to CSS? It’s a world gone mad, if you’re not careful.

Ultimately, though, that’s not the idea here. Rather, it’s to move to organizing your files by function. So you might have a directory called users/ with all your user-related files: show_user.php and create_user.php and create_user.html. You might have other similar directories, like groups/ and social/ and the like.

When you begin to organize by function, your organization actually becomes meaningful. It tells you what things do, rather than what they are (CSS or PHP or whatever). In fact, down the line, you might even break things up further, separating code that’s for displaying things from code that interacts with your database. That’s still a bit off in the distance, but for now, be thinking “Function over format.” It’s more important that you have a group of user-related files together than that you have your PHP scripts together.

So store your utility scripts in scripts/ for now. And yes, you could look at renaming scripts/ something like utilities/, if you like. Go ahead, and organize well; when you have 50 or 100 files, you’ll be grateful for the structure.

Now you’re back to instilling confusion, and that’s no good. There’s an easy solution, though: just deal with the situation when there’s no request parameter:

<?php

$error_message = preg_replace_all("/\\\\/", '',

$_REQUEST['error_message']);

if (!isset($error_message)) {

$error_message = "something went wrong, and that's " .

"how you ended up here.";

}

?>

<html>

<!-- Existing HTML and PHP -->

</html>

You haven’t seen isset before, but it makes a lot of sense: if the $error_message variable is set, or has a value, things are fine. If they’re not (that’s what the ! means), then set $error_message to a conversational, albeit generic, message. isset returns true if a variable has been assigned something, and is not null. That’s perfect in this case: even though you assign $error_message the value in $_REQUEST[‘error_message’], that value might be null, so isset does the trick nicely.

Your error page again, without anything on the URL, and you’ll get a nice-looking page once again. Check out Figure 7-9 for what you should expect.

You could probably tweak the style on this page a bit. While the italics worked well for a specific error message, it’s not quite as effective here. If you wanted to get a little more fancy, you could actually set the class on the span within which the error message prints based on whether you’ve got a generic error message, or a specific one.

Figure 7-9. You could probably tweak the style on this page a bit. While the italics worked well for a specific error message, it’s not quite as effective here. If you wanted to get a little more fancy, you could actually set the class on the span within which the error message prints based on whether you’ve got a generic error message, or a specific one.

Welcome to Security and Phishing

And now, welcome to a big fat ugly problem. By using a request parameter to pass information to your scripts, anyone—especially malicious users—can pass information to your script. They can put their own error message in as the value to the error_message request parameter…or they can put something in that’s not an error message at all. And why is that a problem? Keep reading.

Phishing and Subtle Redirection

Putting actual information, like an error message, in a URL is a way of employing a type of Internet vandalism called phishing. Phishing is a technique that supplies to a user what appears to be a trusted URL, and gets that user to an untrusted website. So suppose you get an email with a link to a site that looks like this:

http://yellowtagmedia.com/phpMM/ch07/show_error.php?error_message=%3Ca%20href=%22http://www.syfy.com/beinghuman%22%3EClick%20Here%20To%20Report%20Your%20Error%3C/a%3E

You might just click on this. It’s got lots of gibberish at the end, but you recognize the important part, the host name: yellowtagmedia.com. You’ve been reading PHP & MySQL: The Missing Manual, and all throughout that book, you’ve been seeing yellowtagmedia.com as a domain name. It’s the author’s domain, so you may think this is a perfectly fine site to visit. So you do, and you see something like Figure 7-10.

What’s to be worried about here? It’s a customized error page, just like the one you’ve built for your own machine. It’s got an error message, and you can even apparently click through to report details about your problem. Looks like great customer service…right?

Figure 7-10. What’s to be worried about here? It’s a customized error page, just like the one you’ve built for your own machine. It’s got an error message, and you can even apparently click through to report details about your problem. Looks like great customer service…right?

It’s an error page, just like the one you’ve been creating. And, look, it’s got a link on it. Might as well trust the link, too. It appears on a trusted page. So click the link, and you’d end up on a completely different site—probably one you didn’t expect (see Figure 7-11).

Now, the SyFy channel’s page for Being Human is hardly anything to lose sleep over, although Being Human really is a great show. But suppose that same link took you to a site that asks for your credit card, or that’s full of illicit material that could get you fired when you accidentally land on that site at work, or even just a simple site that asks you to “reconfirm” your username and password: these are potential disasters.

How did you end up here? This is a classic example of phishing. You visit a site you trust (or think you trust), and end up on a site that you don’t trust.

Figure 7-11. How did you end up here? This is a classic example of phishing. You visit a site you trust (or think you trust), and end up on a site that you don’t trust.

A clever and not-so-well-meaning coder could easily use the same CSS that’s used on yellowtagmedia.com to ensure that site looks just like the initial error page, and most users would never know the difference.

The Dangers of Request Parameters

The problem is that any old user can actually type in a request parameter. Look back at the URL that started all of this:

http://yellowtagmedia.com/phpMM/ch07/show_error.php?error_message=%3Ca%20href=%22http://www.syfy.com/beinghuman%22%3EClick%20Here%20To%20Report%20Your%20Error%3C/a%3E

It’s the error_message parameter that creates all the trouble. It allows…well, anything as a value. And when you take away all the escaping, this URL really amounts to this:

http://yellowtagmedia.com/phpMM/ch07/show_error.php?error_message=<a href=“http://www.syfy.com/beinghuman”>Click Here To Report Your Error</a>

So suddenly, a link to a non-trusted site gets dropped right into your trusted page. That’s a big problem, and can create massive headaches for your users.

Unfortunately, fixing this is going to take a lot of PHP wizardry that you just don’t have yet. Fortunately, it’s coming…in about six chapters. So for now, use this approach to error handling, but know that it’s not quite ready for primetime. You’ll need to use something called sessions, detailed in Chapter 13, to avoid ever becoming part of a phishing scam.

NOTE

Your vulnerability to phishing is subtle, but it’s there. It took a clever tech reviewer to reveal the potential problem. But that’s the price of coding on the big bad InterWebs: You must always be aware of what a malicious bored teenager can do to your site if you’re not careful. Thankfully, though, you’re learning everything you need to combat and prevent those attacks. Just hang tight untilChapter 13, where you’ll use sessions to make some small changes that completely shut down any phishing attempts.

Add Debugging to Your Application

You’ve taken care of your users in case of error, but what about taking care of yourself? You need to use your system too, which means you need to figure out what’s going on, not just in your code, but on your front end. But the error pages you’ve put into place actually now shield you from what’s really going on at the script level. Instead of seeing a technically accurate error that’s ugly and unreadable to your users, you get a nice friendly error message. But that’s no help to you!

So are you stuck digging through your code anytime something goes wrong, with no real lead on what happened? That’s really not a good limitation to accept. A better approach would be to figure out a way to show the real errors that occurred, but to do it in a way that only you can see.

Turn on PHP Error Reporting

First, you need a way to report errors when they happen, especially if your program might normally act odd if that behavior went unreported. For example, consider this code fragment:

echo "Hello, {$first_name}\n\n";

$query = "SELECT * FROM users WHERE first_name = {$first_name}";

The results here radically change depending on whether or not there’s actually a value in $first_name. You could get database errors, odd query results, and worse. Now, certainly, you could add some isset calls to avoid problems, but you’ll often forget that sort of error prevention…at least until something goes wrong. What you need isn’t a nice error message, but a report when bad things happen, as they happen. Then you can make the necessary fixes and avoid repeating these errors.

Here’s where PHP offers help, though: you can turn on what’s called error reporting within PHP itself. Typically, you do so through some of the low-level configuration files that PHP uses, but that’s a more difficult a solution than your situation demands.

NOTE

It may actually be more than just difficult. Most ISPs and web hosting companies won’t let you anywhere near the configuration files for the web servers and PHP installations they host. That’s a headache waiting to happen for them and their support staff.

So to see this in action, create a small script called display_error.php, and type this code:

<?php

echo "Hello, {$first_name}\n\n";

$query = "SELECT * FROM users WHERE first_name = {$first_name}";

echo "{$query}\n\n";

?>

Obviously, there’s the problem of $first_name not being defined. And although this script doesn’t actually try and execute the query—which is going to be incomplete—it’s pretty clearly a program where you’d want to know that something bad is going on.

But run this program, and you’ll get this result:

$ php display_error.php

Hello,

SELECT * FROM users WHERE first_name =

Pretty lame, isn’t it? PHP happily runs the program, ignoring the problems. That means you’re not redirected to any error page, at least not until several lines later when you execute this query against your database. But by then, you’re a few lines (or maybe a few hundred lines!) away from the real problem, the missing value in $first_name.

That’s where PHP’s error_reporting function comes in. Add this line into your display_error.php script:

<?php

error_reporting(E_ALL);

echo "Hello, {$first_name}\n\n";

$query = "SELECT * FROM users WHERE first_name = {$first_name}";

echo "{$query}\n\n";

?>

The E_ALL constant is just a level of reporting. E_ALL reports every possible error. You can also use E_ERROR, E_WARNING, E_PARSE, and E_NOTICE, all of which report different things (and let different things pass by silently). You can get the whole scoop on these different levels atwww.php.net/manual/en/function.error-reporting.php. In the simplest case, though, E_ALL absolutely lets you know when something might go wrong.

Now run the script again, and you get an entirely different result:

$ php display_error.php

PHP Notice: Undefined variable: first_name in yellowtagmedia_com/phpMM/ch07/

display_error.php on line 5

Notice: Undefined variable: first_name in yellowtagmedia_com/phpMM/ch07/dis-

play_error.php on line 5

Hello,

PHP Notice: Undefined variable: first_name in yellowtagmedia_com/phpMM/ch07/

display_error.php on line 6

Notice: Undefined variable: first_name in yellowtagmedia_com/phpMM/ch07/dis-

play_error.php on line 6

SELECT * FROM users WHERE first_name =

Suddenly, PHP is hyper-aware of potential problems, and it’s letting you know about it. That’s perfect for getting your application up and running; now you’re going to be bugged about…well…potential bugs. That’s a good thing.

WARNING

Really, this reporting is a big for writing good code, but it’s also a bit annoying. You’re going to constantly get little nudges from PHP about your potential mistakes. Still, that’s a small price to pay for knowing you’ve handled potential problems in your script.

Set Error Reporting Globally

Now you’re left with another subtle issue: you must remember to turn on error reporting. That’s not something you want to have to do in every script, though. And you’re going to have lots of basic procedures like error reporting that you want to apply to all your scripts.

The solution here is probably already apparent to you: you need another script, sort of like database_connection.php, that handles all this common behavior. Then, all your other scripts can make a single call to include that behavior, and it’s taken care of. But you’ve already got a file like this:app_config.php, which database_connection.php uses for common constants like your database’s name and password. That’s actually exactly what you need here.

NOTE

Yes, this does mean that you must still include this one common script, app_config.php, in all your other scripts, so there’s still a level of “Remember to…” happening here. More on that in the box on Remembering (a Little Bit) is Part of Programming.

Go ahead and open up app_config.php in your core scripts/ directory. It should live alongside show_error.php and database_connection.php. Add in the error_reporting directive to turn on error reporting for all your scripts:

<?php

// Database connection constants

// Error reporting

error_reporting(E_ALL);

?>

Now you just need to add an include to all of your scripts:

<?php

require '../scripts/app_config.php';

echo "Hello, {$first_name}\n\n";

$query = "SELECT * FROM users WHERE first_name = {$first_name}";

echo "{$query}\n\n";

?>

NOTE

If you’re following along, you should remove the error_reporting directive from display_error.php (Turn on PHP Error Reporting), since that’s now handled by app_config.php.

With that addition, you’ve now got error reporting in all of your scripts. That’s a pretty helpful upgrade for a single-line addition to each of your scripts.

POWER USERS’ CLINIC: REMEMBERING (A LITTLE BIT) IS PART OF PROGRAMMING

So after all the talk about avoiding having to remember turning on error reporting, it seems like the solution is…remembering to include app_config.php? How does that make things easier?

Unfortunately, as a programmer, you’re always challenged to remember to do certain things. That might be release a certain variable that takes up a lot of system resources, or closing a database connection, or logging out of a system…or including a certain file in every script you write. The key, though, is to minimize the number of things you have to remember.

That’s the point of using app_config.php. By including this single file—one thing to remember—you can include all the common things your script needs. So if you later need to add to app_config.php, all your scripts immediately get those additions. (And you will make some additions to app_config.php really soon.) So include one thing, instead of two or three or ten, whenever possible.

Turn Off Error Reporting When You Go to Production

Now you’ve got error reporting on, and you’re getting a lot more information. But there’s a problem: sometimes what is reported isn’t an error, but the potential for an error. As an example, make sure you have app_config.php included in your show_error.php script:

<?php

require 'app_config.php';

$error_message = preg_replace_all("/\\\\/", '',

$_REQUEST['error_message']);

if (!isset($error_message)) {

$error_message = "something went wrong, and that's how you ended up

here.";

}

?>

<html>

<!-- HTML and PHP -->

</html>

Now visit show_error.php in your browser, and don’t put in anything for the error message. That’s really not a problem for show_error.php, as that’s something for which your code accounts. But look at Figure 7-12, and what may be a surprising output from the script.

Suddenly, this is the worst possible error page: it has its own error! Obviously, you can’t have an error page that doesn’t just report errors, but causes them, but is this really an error? Or just the potential for an error?

Figure 7-12. Suddenly, this is the worst possible error page: it has its own error! Obviously, you can’t have an error page that doesn’t just report errors, but causes them, but is this really an error? Or just the potential for an error?

Even though you handled the situation, it’s still technically a potential problem that you assign $error_message a value ($_REQUEST[‘error_message’]) that may be null.

There are two things you can do here, and both are good ideas. First, you can refactor this code to make sure you never directly access a potentially null value:

<?php

require 'app_config.php';

if (isset($_REQUEST['error_message'])) {

$error_message = preg_replace_all("/\\\\/", '',

$_REQUEST['error_message']);

} else {

$error_message =

"something went wrong, and that's how you ended up here.";

}

?>

<html>

<!-- HTML and PHP -->

</html>

PHP has no problem with you using a null value in isset. In fact, that’s the purpose of isset: to help you avoid using an unexpected null value. So in that sense, the error reporting helped you improve your page. Reload the page, and the error message will be gone, and things should once again look more like Figure 7-9.

But beyond that, there’s a bigger issue: even if this slight change made your code better, there may be times when you need your users to interact with your system before it’s 100 percent perfect. Someone wise once said that “The perfect is the enemy of the good,” and you could extend that to “The perfect web application is the enemy of the good web application.” If you wait until your code is absolutely perfect, you’ll probably never release it.

So what do you do? Well, suppose you could set the “mode” of your application. So you could run in “debug” mode, and errors would print, or you could run in “production” mode, and error reporting wouldn’t be turned on. Then, you could simply run in debug mode until it’s time to go live, and then switch to production mode.

NOTE

You might even take this further: you could copy your code to a server, switch it to run in production mode, and then still run another copy in debug mode for you to work on and improve.

Setting up a debug mode is a breeze; and with app_config.php, you’ve already got a nice central place to configure this sort of thing:

<?php

// Set up debug mode

define("DEBUG_MODE", true);

// Database connection constants

// Error reporting

if (DEBUG_MODE) {

error_reporting(E_ALL);

} else {

// Turn off all error reporting

error_reporting(0);

}

?>

That’s it; now you’ve got the ability to make a single change to DEBUG_MODE, and you get (or don’t get) error reporting across your application.

Moving from require to require_once

If you follow your programming code carefully, you’ll see that database_connection.php has this line at the top:

require 'app_config.php';

So any script that does this…

require '../../scripts/database_connection.php';

…also gets a sort of “automatic” require on app_config.php, also. So if you want to get the setup from app_config.php in a script that already requires database_connection.php, then you technically don’t need to explicitly require app_config.php.

But—and it’s a big but—you’ve now hidden a dependency in your code. So even though you’re not requiring app_config.php explicitly, you’re writing code that assumes that app_config.php has been loaded. So suppose you change a script to not use a database; the natural next step would be to remove the require for database_connection.php. Since your script no longer uses a database, requiring database_connection.php wouldn’t make sense. But with that removal, you also lose app_config.php—a problem that doesn’t show up until you realize that none of your helpful constants and error messages are defined.

For this reason alone, it’s a good idea to be explicit in your requirements. Now, there’s an obvious concern here: You’ll require app_config.php, and then also database_connection.php, which in turn also requires app_config.php. You’re requiring app_config.php twice in database-driven scripts. That turns out to be a problem, because it results in these constants being defined twice, which causes PHP to spit out an error:

// Database connection constants

define("DATABASE_HOST", "db.host.com");

define("DATABASE_USERNAME", "username");

define("DATABASE_PASSWORD", "super.secret.password");

define("DATABASE_NAME", "db-name");

Here’s the error you’d see if you used a require on this file twice:

Notice: Constant DATABASE_HOST already defined in yellowtagmedia_com/phpMM/

scripts/app_config.php on line 4

Notice: Constant DATABASE_USERNAME already defined in yellowtagmedia_com/

phpMM/scripts/app_config.php on line 5

Notice: Constant DATABASE_PASSWORD already defined in yellowtagmedia_com/

phpMM/scripts/app_config.php on line 6

To get around this dilemma, you can use require_once instead of require in all your utility scripts. So in your main script—whichever script your main code lives—use the normal require:

// main script you're writing code

require '../scripts/app_config.php';

Then, in any utility scripts that also need app_config.php, use require_once:

// database_connection.php and any other utility scripts

require_once '../scripts/app_config.php';

require_once checks to see if the specified script has already been included (through include or require), and only include the script if it’s not already been loaded. That ensures that you really only do get app_config.php loaded once.

But there’s yet another problem: sometimes you have one script—like create_user.php—actually call another script—like show_user.php. In this case, you’ve got two scripts that probably both use require, and so you’ll get errors about constants being redefined. Should you rethink and refactor app_config.php? Should you abstract out those constants into another file, or move them into database_connection.php?

Honestly, you can get around all of these issues by using require_once in all your scripts. This way, you ensure that app_config.php never gets loaded more than once. There’s also another side effect: you’re no longer trying to figure out which version of require to use. In fact, as a general rule, you should always use require_once, unless you have a specific need to require something multiple times. Which make sense, since you rarely require something more than once.

NOTE

True, this book could have told you to use require_once from the beginning. But then you’d have no idea why to use that instead of require. By running through this process of actually seeing errors crop out, you can now explain to your coworkers why they too should use require_once in their PHP scripts.

Now You See Me, Now You Don’t

Unfortunately, you’ve done a lot of work, but you still haven’t solved one core problem: you need a way to display more information about an error to you and your programmer buddies, without resorting to terrifying your users. But you’ve laid some groundwork; the app_config.php file you created has a DEBUG_MODE, and that seems to be the key ingredient.

What you need, then, is a way to print out additional error information if you’re in debug mode. And just as with error reporting (through PHP’s own error handling), you can simply turn this option off in production. In the same vein, you could always turn it on for a brief period if you had a problem, and then back off again once you’d used the error reporting to locate and fix any errors that are occurring.

So define a new function—call it debug_print—that only prints information if you’re in debugging mode:

function debug_print($message) {

if (DEBUG_MODE) {

echo $message;

}

}

With this in app_config.php, you’ve now got this function available anywhere in your own code. All it does it selectively print a message; if debugging is turned on, it prints, and if not, $message never sees the light of day.

You’ve just created your first custom function! Nice work. Although there’s lots more to learn about custom functions, notice how easy it is to create your own customized behavior for the rest of your application to use.

Now, you can add some additional information to your show_error.php page:

<?php

require 'app_config.php';

if (isset($_REQUEST['error_message'])) {

$error_message = preg_replace_all("/\\\\/", '',

$_REQUEST['error_message']);

} else {

$error_message = "something went wrong, and that's how you ended up

here.";

}

if (isset($_REQUEST['system_error_message'])) {

$system_error_message = preg_replace("/\\\\/", '',

$_REQUEST['system_error_message']); } else {

$system_error_message = "No system-level error message was reported.";

}

?>

NOTE

There are other ways to get this same result, but this one will avoid setting off any errors if you have error_reporting turned on.

Then, down in your HTML, selectively print out this additional information:

<html>

<head>

<link href="../css/phpMM.css" rel="stylesheet" type="text/css" />

</head>

<body>

<div id="header"><h1>PHP & MySQL: The Missing Manual</h1></div>

<div id="example">Uh oh... sorry!</div>

<div id="content">

<h1>We're really sorry...</h1>

<!-- Existing user-friendly error handling and printing -->

<?php

debug_print("<hr />");

debug_print("<p>The following system-level message was received:

<b>{$system_error_message}</b></p>");

?>

</div>

<div id="footer"></div>

</body>

</html>

Finally, then, you can put all this together. You’ve got an error page, you’ve got a means of printing information only if debugging is turned on, and you have app_config.php to tie things all together.

Redirecting on Error

You’ve got a pretty complex mechanism in place to deal with error messages as they crop up, and you’ve even got a way to report errors via PHP (with error_reporting) and a means of printing out errors for your programming benefit (with debug_print). But you’ve not gotten to actually use any of this! It’s definitely time to rectify that situation.

Take a look at one of your simplest page/script combinations: connect.html and connect.php, from Chapter 4.

NOTE

Go ahead and copy these scripts into a new directory so you can make changes to them. You should then change connect.html to submit to connect.php, without using a scripts/ directory, and make sure connect.php lives right alongside connect.html. You should also be sure to require_once app_config.php, and ensure your path to app_config.php reflects the new location of connect.php. That might sound like a lot, but just take a good look through your PHP, and it will all be trivial.

Updating Your Script to Use show_error.php

Here’s the first place you really need to make changes:

<?php

require '../scripts/app_config.php';

mysql_connect(DATABASE_HOST, DATABASE_USERNAME, DATABASE_PASSWORD)

or die("<p>Error connecting to database: " .

mysql_error() . "</p>");

// And so on...

?>

So right now, if mysql_connect fails, the whole script just dies in a ball of flames. Not so great. Now, one way you could fix this would be to do something like this:

if (!mysql_connect(DATABASE_HOST,

DATABASE_USERNAME, DATABASE_PASSWORD)) {

$user_error_message = "there was a problem connecting to the " .

"database that holds the information we need " .

"to get you connected.";

$system_error_message = mysql_error();

header("Location: ../scripts/show_error.php?" .

"error_message={$user_error_message}&" .

"system_error_message={$system_error_message}");

exit();

}

NOTE

This is one of those sections of code that involves long lines ill-suited for print. You certainly don’t need to break up these lines into multiple lines, although you can if you like. The downloadable code uses a single line for defining $user_error_message, as well as for giving a URL to header.

That uses your new error page in conjunction with PHP’s redirect, supplies both a friendly and system-level error, and should work pretty well. For the sake of testing, type in a bad database host, like this:

if (!mysql_connect(DATABASE_HOST, DATABASE_USERNAME, "foo")) {

// handle error

}

Now hit connect.html in your browser, submit the form to connect.php, and you should be rewarded with your error page, as in Figure 7-13.

This is just about all you could ask for in terms of handling errors. You get to see exactly what the user sees, plus you get error reporting at a programming level. Say goodbye to die…show_error.php is a much better solution.

Figure 7-13. This is just about all you could ask for in terms of handling errors. You get to see exactly what the user sees, plus you get error reporting at a programming level. Say goodbye to die…show_error.php is a much better solution.

NOTE

Make sure that you have DEBUG_MODE set to true in app_config.php before you try this out, so you’ll see both the user-friendly and developer-friendly errors.

This is perfect! In terms of seeing errors, you’ve got your users covered. Now, set DEBUG_MODE to false in app_config.php:

// Set up debug mode

define("DEBUG_MODE", false);

Try and hit connect.html and connect.php again, and this time, you should only see the user-facing error (check out Figure 7-14).

What a difference some error-handling work makes. Remember back in the old days when a database error resulted in a blank page with a cryptic error message? This is a pretty massive upgrade, and by now you know: errors are going to happen. But now they happen in style, and that counts for quite a bit with the typical user.

Figure 7-14. What a difference some error-handling work makes. Remember back in the old days when a database error resulted in a blank page with a cryptic error message? This is a pretty massive upgrade, and by now you know: errors are going to happen. But now they happen in style, and that counts for quite a bit with the typical user.

Simplify and Abstract

So are you done? Well, almost. The error printing is great, but take another look at the code in your main script, connect.php:

if (!mysql_connect(DATABASE_HOST,

DATABASE_USERNAME, DATABASE_PASSWORD)) {

$user_error_message = "there was a problem connecting to the " .

"database that holds the information we need " .

"to get you connected.";

$system_error_message = mysql_error();

header("Location: ../scripts/show_error.php?" .

"error_message={$user_error_message}&" .

"system_error_message={$system_error_message}");

exit();

}

That’s a lot of code to handle the problem. In fact, you’ve got a good bit more code dealing with the error than you do dealing with things going right. That’s not always a bad thing, but in this case, it’s just not necessary. Remember how this code originally looked?

mysql_connect(DATABASE_HOST, DATABASE_USERNAME, DATABASE_PASSWORD)

or die("<p>Error connecting to database: " . mysql_error() . "</p>");

That’s pretty darn optimal—a line to do what you want, and then a line if there are problems. Now multiple that by all the different places your code can fail…that’s a lot of error handling code.

So can you get your error handling to be that elegant? It’s worth at try. Look closely at the code again, and notice how regardless of what the error is, parts of this will always be the same:

if (!mysql_connect(DATABASE_HOST,

DATABASE_USERNAME, DATABASE_PASSWORD)) {

$user_error_message = "there was a problem connecting to the " .

"database that holds the information we need " .

"to get you connected.";

$system_error_message = mysql_error();

header("Location: ../scripts/show_error.php?" .

"error_message={$user_error_message}&" .

"system_error_message={$system_error_message}");

exit();

}

So the only thing that ever changes here is the actual error messages. The rest—the variable names, the header call, and the building of the URL—are always the same. So what about creating another function, a lot like debug_print, to handle all this?

Add this function to app_config.php, further expanding your utility script:

<?php

// Set up debug mode

// Database connection constants

// Error reporting

function debug_print($message) {

if (DEBUG_MODE) {

echo $message;

}

}

function ($user_error_message, $system_error_message) {

header("Location: show_error.php?" .

"error_message={$user_error_message}&" .

"system_error_message={$system_error_message}");

exit();

}

?>

This script is really just a variation on what you did with debug_print. You’ve taken something that’s essentially the same code, over and over, and put it into a nice handy, easy-to-reference custom function. The only change is the addition of exit. This ensures that regardless of how the calling script is structured, once the header redirects the browser to your error page, nothing else happens. The error page is shown, and PHP stops whatever else it might have planned to do.

Now, you can simplify connect.php by quite a bit:

if (!mysql_connect(DATABASE_HOST, DATABASE_USERNAME, "foo")) {

handle_error("there was a problem connecting to the database " .

"that holds the information we need to get you connected.",

mysql_error());

}

This is a lot better, especially when you realize that this is easily a single line in a terminal or editor. But you can take this yet further:

mysql_connect(DATABASE_HOST, DATABASE_USERNAME, "foo")

or handle_error("there was a problem connecting to the database " .

"that holds the information we need to get you connect-

ed.",

mysql_error());

Now you’ve dropped the if, and returned to the simple elegance of the or die you used to have… but with a much nicer function: your own handle_error.

redirect is Path-Insensitive

There’s just one problem, and it looks like Figure 7-15.

Sometimes PHP is its own worst enemy. What does this mean? It’s not a typical HTTP error like 404, which tells you a page is missing. It’s just a rather cryptic way to say, “Well, something went wrong, and I betcha have no idea what!”

Figure 7-15. Sometimes PHP is its own worst enemy. What does this mean? It’s not a typical HTTP error like 404, which tells you a page is missing. It’s just a rather cryptic way to say, “Well, something went wrong, and I betcha have no idea what!”

You may see just this when you try out connect.php for yourself. While this reflects that something has gone wrong, it’s sure not the show_error.php page on which you’ve worked so hard. But what is that?

It’s actually a well-known error related to PHP. Most web servers are set to treat any URL request that ends in .php as PHP requests. That’s good, as it means that you don’t have to stash all your PHP scripts in one directory. But it’s bad, because the web server doesn’t actually see if the URL that ends in .php matches an actual file. It just hands the URL over to the PHP program. But if that URL isn’t a pointer to a real file, PHP says, “I don’t have anything to run.” Or, more accurately, it says “no input file specified.”

Yet the question remains: why are you getting this? It has to do with this little bit of code in app_config.php:

function handle_error($user_error_message, $system_error_message) {

header("Location: show_error.php?" .

"error_message={$user_error_message}&" .

"system_error_message={$system_error_message}");

}

In this code, the path to show_error.php is relative to app_config.php. Since app_config.php is in the same directory as show_error.php, there’s nothing before the file name.

But this code is executed from your connect.php script, in (at least in the examples from the book) ch07/. So the path from that location to show_error.php is ../scripts/ show_error.php. And even though the handle_error function is defined in app_config.php, it’s run from the connect.phpscript’s context. The result? You’re looking for show_error.php in the wrong place.

But if you change the path in app_config.php to work with connect.php, and you later have a different script in a different location, you’re going to get this same issue all over again. So how is handle_error very utilitarian anymore?

What you need, once again, is a way to indicate a common property—the root of your site—and then relate the path of show_error.php to that with an absolute path, rather than using a relative path.

UP TO SPEED: RELATIVE AND ABSOLUTE PATHS

A relative path is a path that references a file relative to the current file. This usually means that the path begins with either the file itself, like show_error.php, or a movement back a directory using the .. indicator. So relative paths look like show_error.php or ../scripts/show_error.php. In both cases, your starting point is the current file indicating the path.

An absolute path is a path that is not related to the current file, but instead the root of your site. You can always tell an absolute path because they start with a /, indicating to begin looking for the indicated file at the root, or “base,” of your website. So an absolute path would be something like /scripts/ show_error.php.

You can define your site root in app_config.php with a new constant:

// Site root

define("SITE_ROOT", "/phpMM/");

Now you can use that constant in handle_error. Here’s the final version of app_config.php, with all the new constants and the completed handle_error and debug_print functions:

<?php

// Set up debug mode

define("DEBUG_MODE", false);

// Site root

define("SITE_ROOT", "/phpMM/");

// Database connection constants

define("DATABASE_HOST", "database.host.com");

define("DATABASE_USERNAME", "username");

define("DATABASE_PASSWORD", "super.secret.password");

define("DATABASE_NAME", "database-name");

// Error reporting

if ($debug_mode) {

error_reporting(E_ALL);

} else {

// Turn off all error reporting

error_reporting(0);

}

function debug_print($message) {

if (DEBUG_MODE) {

echo $message;

}

}

function handle_error($user_error_message, $system_error_message) {

header("Location: " . SITE_ROOT . "scripts/show_error.php?" .

"error_message={$user_error_message}&" .

"system_error_message={$system_error_message}");

}

?>

NOTE

You can’t use the curly braces trick to insert constants into a string, so you’ve got to concatenate SITE_ROOT to your URL string in the call to header using the dot (.) operator.

Now, you should finally be able to see show_error.php via an error in connect.php, in all its glory! Check out Figure 7-16 for the result of all this work.

Who said error handling was easy? But now, it’s done, and you and your users get to reap the benefits.

Figure 7-16. Who said error handling was easy? But now, it’s done, and you and your users get to reap the benefits.

To finish up, take a blazing trip through all your scripts, and replace every bit of die and other error handling with calls to handle_error. And don’t forget to update database_connection.php to use handle_error, too:

<?php

require 'app_config.php';

mysql_connect(DATABASE_HOST, DATABASE_USERNAME, DATABASE_PASSWORD)

or handle_error("there was a problem connecting to the database " .

"that holds the information we need to get you connected.",

mysql_error());

mysql_select_db(DATABASE_NAME)

or handle_error("there's a configuration problem with our database.",

mysql_error());

?>

FREQUENTLY ASKED QUESTION: SERIOUSLY? 20+ PAGES ON ERROR HANDLING?

It seems hard to believe, doesn’t it? You’ve not added any real new functionality to your web app. Of course, you’ve learned a bit more about constants, you’ve defined two custom functions, you’ve added a utility class, you’ve gotten a handle on require and require_once, and even added PHP’s internal error reporting to your repertoire.

Still, error handling is usually something that books stick in the last chapter, figuring people won’t mind if it’s near the end and can be ignored. So why spend all this time on something that—hopefully—your users never see? Well, mostly because an application that doesn’t handle errors simply isn’t complete.

And, like it or not, when you’re just starting out programming, or programming in a new language, you’re going to make more mistakes.

Tests and error handling are the best two ways to catch mistakes early and then provide the simplest path toward fixing those mistakes. Now that you have robust error handling, you’ll be surprised how often a big problem is turned into a small problem because you quickly see an error and can track it down…without wading through all your code, hopelessly wondering what really went wrong.