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.
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.
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.
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?
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.
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.
So here’s your first question: what exactly goes on this page that helps your users? To answer this, consider the following two questions:
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?!?
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.
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.
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:[email protected]">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.
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.
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'sgone 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:[email protected]">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?
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:[email protected]">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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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 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.
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 until Chapter 13, where you’ll use sessions to make some small changes that completely shut down any phishing attempts.
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.
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} "; $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.
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} "; $query = "SELECT * FROM users WHERE first_name = {$first_name}"; echo "{$query} "; ?>
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}
";
$query = "SELECT * FROM users WHERE first_name = {$first_name}";
echo "{$query}
";
?>
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 at www.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.
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.
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.
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: <?phprequire '../scripts/app_config.php';
echo "Hello, {$first_name} "; $query = "SELECT * FROM users WHERE first_name = {$first_name}"; echo "{$query} "; ?>
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.
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.
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.
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.
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.
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.
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.";
}
?>
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.
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.
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.
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(); }
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.
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).
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.
There’s just one problem, and it looks like Figure 7-15.
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.php script’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.
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}"); } ?>
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.
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()); ?>
18.224.54.13