Sharing Code - Handling website errors

I just dealt with an error that was discussed in this forum post: Login-form-problems-making-me-go-bald!!! and it got me to thinking about error handling. So I’ve taken time this morning to write up some code to handle errors a little more gracefully, and with feedback via email to a specified recipient.

I’m open to all critiques of this method and my code to help improve it, but thought it may be beneficial to some and I’d like to try to give back to at least a few people here with time I’ve put out to maybe make someone’s project go a little easier.

I am posting this under a Creative Commons Attribution-ShareAlike license click the link to read the details. If you use the code, edited or unedited, I’d love to hear about it: <snip>e-mail address removed</snip>

This code is stored in an include file that you would call on every page of your website in the first line:
Mine is called: init.inc.php


<?php
// This header MUST be included in all variations/uses
// Script written by: Greg Hicks
// Date written: 9/5/2012
// Author email: greg@tekamba.com
// Licensed using CC Attribution-ShareAlike licensing
// Details can be found here: http://creativecommons.org/licenses/by-sa/3.0/

// If no session exists start one
if (session_id() == '')
{
  session_start();
}

// #####################################
// # File variables - modify as needed #
// #####################################

// Do you want error reporting on or off?
// 0:disable - 1:enable
$seterrorrpt = "1";

// Database connection - variables
$dbserv = "localhost"; // modify if on separate server
$dbtouse = "SET DATABASE";
$dbuser = "SET DB USER";
$dbpass = "SET DB PASSWORD";
$dbtable = "SET DB TABLE";
$dblink = ""; // Variable for script db access

// Set a friendly name for this website
// Keep it fairly brief
$sitename = "FRIENDLY NAME";
$domainname = "YOURDOMAIN.com";

// Who to notify in case of db error
$contactname = "SET YOUR NAME";
$contactbiz = "SET BUSINESS NAME"; // OPTIONAL
$contactphone = "SET PHONE";
$contactemail = "SET EMAIL";

// Base directory of website
// Comment/uncomment whichever method is desired
//$rootdir = "http://www.YOURDOMAIN.com";
$rootdir = $_SERVER['SERVER_NAME'];

// #############################################
// # BEGIN CODE - USE CAUTION EDITING FILE BELOW THIS LINE!!! #
// ############################################

// Error reporting - comment out for deployment
if ($seterrorrpt == "1")
{
  ini_set('display_errors', 1);
  ini_set('log_errors', 1);
  ini_set('error_log', dirname(__FILE__) . '/error_log.txt');
  error_reporting(E_ALL);
}

// Make connection to database
$dblink = mysqli_connect($dbserv, $dbuser, $dbpass);
if (!$dblink)
{
  $errornotice = 'Unable to connect to the database server.';
  include ('includes/report-error.php');
  exit();
}

if (!mysqli_set_charset($dblink, 'utf8'))
{
  $errornotice .= 'Unable to set database connection encoding.';
  include $rootdir . '/includes/report-error.php';
  exit();
}

if (!mysqli_select_db($dblink, $dbtouse))
{
  $errornotice .= 'Unable to locate the database.';
  include $rootdir . '/includes/report-error.php';
  exit();
}

?>

Here is the report-error.php file used in previous script stored in an “includes” directory of main site


<?php
// This header MUST be included in all variations/uses
// Script written by: Greg Hicks
// Date written: 9/5/2012
// Author email: greg@tekamba.com
// Licensed using CC Attribution-ShareAlike licensing
// Details can be found here: http://creativecommons.org/licenses/by-sa/3.0/

// Hash the password for basic security
$dbpass = md5($dbpass);
// Set default timezone for admin (adjust from server timezone)
// Use ONLY if your server is in a different timezone and your not sure what to edit in .htaccess
// I ended up using the .htaccess method myself
// For list of US zones: http://www.php.net/manual/en/timezones.america.php
//date_default_timezone_set('America/Phoenix');
// Capture the time error occurred
$errortime = date('M d, Y h:i A');
// Set subject for emailed report
$errorsubject = $domainname . " ERROR: diagnostic info";

// Get the error thrown
$phpgeterror = error_get_last();
$phperror = "File " . $phpgeterror['file'] . " caused error " . $phpgeterror['message'] . " at line " . $phpgeterror['line'];

// Which script file errored
$badscript = $_SERVER['SCRIPT_FILENAME'];

// Gather some basic visitor info
$visitorip = $_SERVER['REMOTE_ADDR'];
$visitorbrowser = $_SERVER['HTTP_USER_AGENT'];

// Error message to display to user
// Used when error reporting is disabled
$errormsg_basic = <<<EOF
The website at $domainname has errored.<br />
Please notify the Admin that you received this error.<br />

<b>Site that error occurred on:</b> $badscript<br />
<b>Error:</b> $phperror<br />
<b>Handled Error:</b> $errornotice<br />
<br />
  ---------------------------------<br />
  <b>Admin Name:</b> $contactname<br />
  <b>Business Name:</b> $contactbiz<br />
  <b>Admin Email:</b> $contactemail<br />
  <b>Admin Phone:</b> $contactphone<br />
  ---------------------------------<br />
  <b>Reported Base Directory:</b> $rootdir<br />
EOF;

// Error message to display to user
// Used when error reporting is enabled
// and for the email sent to admin
$errormsg_verbose = <<<EOF
The website at $domainname has errored. Below is the diagnostic information.
Please notify the Admin that you received this error.

Site that error occurred on: $badscript
Error: $phperror
Handled Error: $errornotice

User Info:
  IP of Visitor: $visitorip
  Browser: $visitorbrowser
  Time of error: $errortime

Variables:
  Error Reporting: $seterrorrpt
  Database Server: $dbserv
  Database: $dbtouse
  Database User: $dbuser
  Database Password: $dbpass
  Database Table: $dbtable
  ---------------------------------
  Admin Name: $contactname
  Business Name: $contactbiz
  Admin Email: $contactemail
  Admin Phone: $contactphone
  ---------------------------------
  Reported Base Directory: $rootdir

NOTE: This information provided for troubleshooting purposes only!
Please reference this info when contacting admin for support.
EOF;

// Send an error report to the admin
if (mail($contactemail, $errorsubject, $errormsg_verbose))
{
  $emailsuccess = "The administrator has been notified,";
} else {
  $emailsuccess = "The administrator has NOT been notified,";
}
?>

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <title><?php echo $sitename; ?> Error Reported</title>
</head>
<style>
  body {
    background-color: #c0c0c0;
  }

  .mainbody {
    background-color: #f0f0f0;
    width: 960px;
    border: 2px solid #ff0000;
    color: #000000;
    font-size: 14pt;
    padding: 3px;
    margin: auto;
  }
</style>
<body>
  <div class="mainbody">
    <h1><?php echo $sitename; ?> Reported an Error</h1>
    <h3><?php echo $emailsuccess; ?> you are welcome to send them this info yourself as well.</h3>
    <?php
      echo $errormsg_basic;
    ?>
    <h4>
      Return to Home Page of Website: <a href="http://<?php echo $rootdir; ?>"><?php echo $sitename; ?></a>
      <br />
      <a href='javascript:history.back()>RETURN TO PREVIOUS PAGE</a>
    </h4>
  </div>
</body>
</html>

And finally, here is a junky little test script to ensure things are working, just set one of the variables in the init.inc.php file to make it error.


<?php
  include('includes/inc_initialize.inc.php');
?>
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <title>No Error</title>
  <link rel="stylesheet" href="style.css">
  <script src="script.js"></script>
</head>
<body>
If you see this page no error was thrown.
</body>
</html>

Learn how to use exceptions to throw errors.

Not very reusable or useful. Every different type of error will have differentiating meta data associated with to debug the issue. Hard coding everything like you have done pretty much makes the script only useful for your specific purpose. Unless of course one would to follow the same absurd declaration of polluting the global namespace and variable names. I guess it all depends on what your trying to do. I always think of building things in terms of reusable components myself. Though OOP lends itself more toward that than procedural though it can be done.

My impression of this though it that is a lot of spaghetti to handle something so simple. All you’re really doing is handling database connection issues. Keep it simple stupid unless you’re going to write something to handle error on a more generic level, not just database issues.

Also you would need to consider different level of “errors”. There are PHP errors, exceptions and those specific to an applications requirements for a task. Those are the three I can think of right off hand. Each one of those could probably than be broken up further and further only limited by how granular you like to get.

Thank you oddz, much better feedback than just a single line that does not appear to have been meant to be very helpful at all. And your response is one of the reasons I posted this code, so I can learn based off my own way of coding/scripting. I learn much better by solving my own problems and the way I handled them.

I will do some research on the points you bring up and either clean up, or dispose of, this code.

One thing I did like about this was the fact that it doesn’t just display output to the user, but sends off a notice to the admin. I realize that too, could be handled with much less code, but I was making an attempt to make it of benefit to both the site visitor and the admin. As I said though, I will rethink what I have written and move forward and learn from it.

Thank you for your feedback.

Greg

That “single line” did contain enough information to google to find what you’d need to improve. I figured you’d google it :stuck_out_tongue:

Ok, so with exceptions, you could do something like this:

Let’s say you have a process that kicks off inside some class somewhere:

class Process{

const ERROR_MESSAGE_USER_CHECK_FAILED = "User check failed";

const ERROR_CODE_USER_CHECK_FAILED = 0;

public function doSomething(){
    $this->_step1();
    $this->_step2();
}

private function _step1(){
 //pretend this code executes successfully
}

private function _step2(){
 //something has gone wrong here
throw new Exception(self::ERROR_MESSAGE_USER_CHECK_FAILED, self::ERROR_CODE_USER_CHECK_FAILED); 
}


}

In your calling code, you can do this:



$process = new Process();
try{
  $process->doSomething();
}catch(Exception $e){
  $e->getMessage(); //if step two in the process has gone wrong, the error message will be: "User check failed"
 //so now you could do this if you wanted:
 mail('me@test.com', "Error", $e->getMessage());
}


Using this kind of technique means you can fire error messages off from within a process and can handle the errors gracefully, however you want.

You could even extend the Exception class in php and get your new class to automatically do logging in the database, or to email automatically and things like that. Not too hard to do.

Is that better?

Yes, and I did Google it (as I always do) and came up with over 9,000,000 results. My only point was in forums, when I reply to people and suggest an alternative, I also typically try to include a small example to get their head into the topic just a bit, then that gives a bit more detail to help them with their Googling of the answer for them to learn more about it. What you have provided in this new reply gives me a much better idea of where to start heading off to learn how to do my error handling using exceptions.

Off to the Googles!

Greg

Use exceptions with care, here is an old discussion featuring me asking the stupid questions about exceptions – from about post #10 onwards there are some interesting and important points being made. I could never put it better than those guys.