PageRenderTime 20ms CodeModel.GetById 3ms app.highlight 8ms RepoModel.GetById 1ms app.codeStats 1ms

/error-handling.html

https://github.com/tml/php-tutorial
HTML | 577 lines | 519 code | 54 blank | 4 comment | 0 complexity | 731b0a3c65a4f8f641c13c1d912728cf MD5 | raw file
  1---
  2title: Template -- Bugging out
  3---
  4<!doctype html>
  5<html>
  6  <head>
  7    <meta charset="utf-8">
  8    <meta http-equiv="X-UA-Compatible" content="chrome=1">
  9    <title>PHP101 - {{ page.title }}</title>
 10    <link rel="stylesheet" href="stylesheets/styles.css">
 11    <link rel="stylesheet" href="stylesheets/pygment_trac.css">
 12    <script src="https://ajax.googleapis.com/ajax/libs/jquery/1.7.1/jquery.min.js"></script>
 13    <script src="javascripts/main.js"></script>
 14    <!--[if lt IE 9]>
 15      <script src="//html5shiv.googlecode.com/svn/trunk/html5.js"></script>
 16    <![endif]-->
 17    <meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=no">
 18
 19  </head>
 20  <body>
 21
 22      <header>
 23        <h1>PHP101</h1>
 24        <p>For the absolute beginner</p>
 25      </header>
 26
 27      <div id="banner">
 28        <span id="logo"></span>
 29
 30        <a href="https://github.com/ss23/php-tutorial" class="button fork"><strong>View On GitHub</strong></a>
 31      </div><!-- end banner -->
 32
 33    <div class="wrapper">
 34      {% include nav.html %}
 35      <section>
 36      	<h2>Fire-proofing your code</h2>
 37	<p>Even the best developers make mistakes sometimes. That’s why most programming languages
 38	- including PHP – come with built-in capabilities to catch errors and take remedial
 39	action. This action can be as simple as displaying an error message, or as complex as
 40	sending the site administrator an email with a complete stack trace.</p>
 41	<p>To make it easier to do this, PHP comes with a full-featured error handling API that can
 42	be used to trap and resolve errors. In addition to deciding which types of errors a user
 43	sees, you can also replace the built-in error handling mechanism with your own custom (and
 44	usually more creative) functions. If you’re using PHP 5, you get a bonus: a spanking-new
 45	exception model, which lets you wrap your code in Java-like try-catch() blocks
 46	for more efficient error handling.</p>
 47	<p>In this edition of PHP 101, I’m going to discuss all these things, giving you a crash course
 48	in how to add error-handling to your PHP application. Keep reading – this is pretty cool stuff!</p>
 49
 50	<h2>Rogues gallery</h2>
 51	<p>Before we get into the nitty-gritty of how to write an error handler, you need to know a
 52	little theory.</p>
 53	<p>Normally, when a PHP script encounters an error, it displays a message indicating the cause
 54	of the error and may also (depending on how serious the error is) terminate script execution.
 55	Now, while this behaviour is acceptable during the development phase, it cannot continue once
 56	a PHP application has been released to actual users. In “live” situations, it is unprofessional
 57	to display cryptic error messages (which are usually incomprehensible to non-technical users);
 58	it is more professional to intercept these errors and either resolve them (if resolution is
 59	possible), or notify the user with an easily-understood error message (if not).</p>
 60	<p>There are three basic types of runtime errors in PHP:</p>
 61	<ol>
 62		<li>Notices: These are trivial, non-critical errors that PHP encounters while
 63		executing a script – for example, accessing a variable that has not yet been defined.
 64		By default, such errors are not displayed to the user at all – although, as you will see,
 65		you can change this default behaviour.</li>
 66		<li>Warnings: These are more serious errors – for example, attempting to
 67		include() a file which does not exist. By default, these errors are displayed
 68		to the user, but they do not result in script termination.</li>
 69		<li>Fatal errors: These are critical errors – for example, instantiating an object
 70		of a non-existent class, or calling a non-existent function. These errors cause the
 71		immediate termination of the script, and PHP’s default behaviour is to display them to the
 72		user when they take place.</li>
 73	</ol>
 74	<p>It should be noted that a syntax error in a PHP script – for example, a missing brace
 75	or semi-colon – is treated as a fatal error and results in script termination. That’s
 76	why, if you forget a semi-colon at the end of one of your PHP statements, PHP will refuse
 77	to execute your script until you correct the mistake.</p>
 78	<p>PHP errors can be generated by the Zend engine, by PHP built-in functions, or by user-defined
 79	functions. They may occur at startup, at parse-time, at compile-time or at run-time.
 80	Internally, these variations are represented by twelve different error types (as of PHP 5),
 81	and you can read about them at
 82	<a href="http://www.php.net/manual/en/ref.errorfunc.php">http://www.php.net/manual/en/ref.errorfunc.php</a>. Named constants like
 83	E_NOTICE and E_USER_ERROR provide a convenient way to reference
 84	the different error types.</p>
 85	<p>A quick tip here: most of the time, you’ll be worrying about run-time errors
 86	(E_NOTICE, E_WARNING and E_ERROR) and
 87	user-triggered errors (E_USER_NOTICE, E_USER_WARNING and
 88	E_USER_ERROR). During the debug phase, you can use the shortcut
 89	E_ALL type to see all fatal and non-fatal errors generated by your script,
 90	and in PHP 5 you might also want to use the new E_STRICT error type to view
 91	errors that affect the forward compatibility of your code.</p>
 92
 93	<h2>Early warning</h2>
 94	<p>With the theory out of the way, let’s now apply it to some examples. Consider the
 95	following code snippet:</p>
 96	{% highlight php %}<?php
 97// initialize the $string variable
 98$string = 'a string';
 99// explode() a string
100// this will generate a warning or E_WARNING because the number of arguments to explode() is incorrect
101explode($string);
102?>{% endhighlight %}
103	<p>If you run this script, you’ll get a non-fatal error (E_WARNING), which
104	means that if you had statements following the call to explode(), they
105	would still get executed. Try it for yourself and see!</p>
106	<p>To generate a fatal error, you need to put in a bit more work. Take a look at this:</p>
107	{% highlight php %}<?php
108// call a non-existent function
109// this will generate a fatal error (E_ERROR)
110callMeJoe();
111?>{% endhighlight %}
112	<p>Here, the call to a non-existent function trips all of PHP’s alarm wires and generates a
113	fatal error, which immediately stops script execution.</p>
114	<p>Now, here’s the interesting bit. You can control which errors are displayed to the user,
115	by using a built-in PHP function called error_reporting(). This
116	function accepts a named constant, and tells the script to report only errors that
117	match that type. To see this in action, consider the following rewrite of one of the
118	earlier scripts to “hide” non-fatal errors:</p>
119	{% highlight php %}<?php
120// report only fatal errors
121error_reporting(E_ERROR);
122// initialize the $string variable
123$string = 'string';
124// attempt to explode() a string
125// this will not generate a warning because only fatal errors are reported
126explode($string);
127?>{% endhighlight %}
128	<p>In this case, when the script executes, no warning will be generated even though the call
129	to explode() contains one less argument than it should.</p>
130	<p>You can use a similar technique to turn off the display of fatal errors:</p>
131	{% highlight php %}<?php
132// report no fatal errors
133error_reporting(~E_ERROR);
134// call a non-existent function
135callMeJoe();
136?>{% endhighlight %}
137	<p>Keep in mind, though, that just because the error isn’t being reported doesn’t mean it
138	isn’t occurring. Although the script above will not display a visible error message, script
139	execution will still stop at the point of error and statements subsequent to that point will
140	not be executed. error_reporting() gives you control over which errors are
141	displayed; it doesn’t prevent the errors themselves.</p>
142	<p>Note that there are further settings within php.ini that should be used on production
143	sites. You can (and should) turn off display_errors, stipulate an
144	error_log file and switch on log_errors.</p>
145	<p>Note also that the approach used above to hide error messages, although extremely simple, is
146	not recommended for general use. It is poor programming practice to trap all errors,
147	regardless of type, and ignore them; it is far better – and more professional – to anticipate
148	the likely errors ahead of time, and write defensive code that watches for them and handles
149	them appropriately. This will prevent your users from finding themselves staring at an
150	unexplained blank page when something goes wrong.</p>
151
152	<h2>Rolling your own</h2>
153	<p>With this in mind, let’s talk a little bit about changing the way errors are handled.
154	Consider a typical PHP error message: it lists the error type, a descriptive message,
155	and the name of the script that generated the error. Most of the time, this is more
156	than sufficient… but what if your boss is a demanding customer, and insists that
157	there must be a “better way”?</p>
158	<p>Well, there is. It’s a little function called set_error_handler(), and it
159	allows you to divert all PHP errors to a custom function that you’ve defined, instead
160	of sending them to the default handler. This custom function must be capable of
161	accepting a minimum of two mandatory arguments (the error type and corresponding
162	descriptive message) and up to three additional arguments (the file name and line
163	number where the error occurred and a dump of the variable space at the time of error).</p>
164	<p>The following example might make this clearer:</p>
165	{% highlight php %}<?php
166// define a custom error handler
167set_error_handler('oops');
168// initialize the $string variable
169$string = 'a string';
170// explode() a string
171// this will generate a warning because the number of arguments to explode() is incorrect
172// the error will be caught by the custom error handler
173explode($string);
174// custom error handler
175function oops($type, $msg, $file, $line, $context) {
176	echo "<h1>Error!</h1>";
177	echo "An error occurred while executing this script. Please contact the <a href=mailto:webmaster@somedomain.com>webmaster</a> to report this error.";
178	echo "<p />";
179	echo "Here is the information provided by the script:";
180	echo "<hr><pre>";
181	echo "Error code: $type<br />";
182	echo "Error message: $msg<br />";
183	echo "Script name and line number of error: $file:$line<br />";
184	$variable_state = array_pop($context);
185	echo "Variable state when error occurred: ";
186	print_r($variable_state);
187	echo "</pre><hr>";
188}
189?>{% endhighlight %}
190	<p>The set_error_handler() function tells the script that all errors are to
191	be routed to my user-defined oops() function. This function is set up to
192	accept five arguments: error type, message, file name, line number, and an array
193	containing a lot of information about the context that the error occurred in (including
194	server and platform, as well as script information). The final element of the context
195	array contains the current value of the guilty variable. These arguments are then used
196	to create an error page that is friendlier and more informative than PHP’s standard
197	one-line error message.</p>
198	<p>You can use this custom error handler to alter the error message the user sees, on the
199	basis of the error type. Take a look at the next example, which demonstrates this technique:</p>
200	{% highlight php %}<?php
201// define a custom error handler
202set_error_handler('oops');
203// initialize $string variable
204$string = 'a string';
205// this will generate a warning
206explode($string);
207// custom error handler
208function oops($type, $msg, $file, $line, $context) {
209	switch ($type) {
210		// notices
211		case E_NOTICE:
212			// do nothing
213			break;
214		// warnings
215		case E_WARNING:
216			// report error
217			print "Non-fatal error on line $line of $file: $msg <br />";
218			break;
219		// other
220		default:
221			print "Error of type $type on line $line of $file: $msg <br />";
222			break;
223	}
224}
225?>{% endhighlight %}
226	<p>Note that certain error types can’t be handled in this way. For example, a fatal
227	E_ERROR will prevent the PHP script from continuing, therefore it can
228	never reach a user-created error-handling mechanism. See 
229	<a href="http://www.php.net/set-error-handler">http://www.php.net/set-error-handler</a> for more information on this.</p>
230
231	<h2>Pulling the trigger</h2>
232	<p>So far we've been talking about handling errors generated by PHP itself, but why stop there? PHP allows you to use its built-in error handling system to raise your own custom errors as well.</p>
233	<p>This is accomplished via a function named trigger_error(), which allows you to raise any of the three error types reserved for users: E_USER_NOTICE, E_USER_WARNING and E_USER_ERROR. When these errors are triggered, PHP's built-in handler will automatically wake up to handle them.</p>
234	{% highlight php %}<?php
235// function to test a number
236// generates E_USER_WARNING if number is a float
237// generates E_USER_ERROR is number is negative
238function testNumber($num) {
239    // float
240    // trigger a warning
241    if (is_float($num)) {
242        trigger_error("Number $num is not an integer", E_USER_WARNING);
243    }
244
245    // negative
246    // trigger a fatal error
247    if ($num < 0) {
248        trigger_error("Number $num is negative", E_USER_ERROR);
249    }
250}
251
252// test the function with different values
253testNumber(100);
254testNumber(5.6);
255testNumber(-8);
256?>{% endhighlight %}
257	<p>If you'd like to have a custom error handler to handle your custom errors... well, you're just hard to please, aren't you? Take a look at this next example, which rewrites the previous script to use a user-defined error handler:</p>
258	{% highlight php %}<?php
259<?php
260// function to test a number
261// generates E_USER_WARNING if number is a float
262// generates E_USER_ERROR is number is negative
263function testNumber($num) {
264    // float
265    // trigger a warning
266    if (is_float($num)) {
267        trigger_error("Number $num is not an integer", E_USER_WARNING);
268    }
269
270    // negative
271    // trigger a fatal error
272    if ($num < 0) {
273        trigger_error("Number $num is negative", E_USER_ERROR);
274    }
275}
276
277// custom error handler
278function myErrorHandler($type, $msg, $file, $line, $context) {
279    switch ($type) {
280
281        // warnings
282        case E_USER_WARNING:
283            // report error
284            print "Non-fatal error on line $line of $file: $msg <br />";
285            break;
286
287        // fatals
288        case E_USER_ERROR:
289            // report error and die()
290            die("Fatal error on line $line of $file: $msg <br />");
291            break;
292
293        // notices
294        default:
295            // do nothing
296            break;
297    }
298}
299
300// set the name of the custom handler
301set_error_handler('myErrorHandler');
302
303// test the function with different values
304testNumber(100);
305testNumber(5.6);
306testNumber(-8);
307?>{% endhighlight %}
308	<p>Note that it is the responsibility of the custom handler to die() in the event of user-generated fatal errors - PHP will not do this automatically.</p>
309	<p>You can use the same method to deal with exceptions too. Scroll on down, and let me show you how.</p>
310
311	<h2>Catching up</h2>
312	<p>If you're using PHP 5.x, you also have an alternative to the techniques discussed so far in the new Exception model (exception is Geek for error). Exceptions are new to PHP (although they've been in languages like Java and Python for ages) and they're stirring up a great deal of excitement.</p>
313	<p>In the exception-based approach, program code is wrapped in a try() block, and exceptions generated by it are "caught" and resolved by a catch() block. Multiple catch() blocks are possible, each one dealing with a different error type; this allows developers to trap different types of errors and execute appropriate exception-handling.</p>
314	<p>Here's what a typical try-catch() block looks like:</p>
315	{% highlight php %}<?php
316try {
317    execute this block
318}
319catch (exception type 1) {
320    execute this block to resolve exception type 1
321}
322catch (exception type 2) {
323    execute this block to resolve exception type 2
324}
325... and so on ...
326?>{% endhighlight %}
327	<p>When PHP encounters code wrapped within a try-catch() block, it first attempts to execute the code within the try() block. If this code is processed without any exceptions being generated, control transfers to the lines following the try-catch() block. However, if an exception is generated while running the code within the try() block, PHP stops execution of the block at that point and begins checking each catch() block to see if there is a handler for the exception. If a handler is found, the code within the appropriate catch() block is executed; if not, a fatal error is generated. It is even possible to handle that fatal error in a nice way using exceptions.</p>
328	<p>The exceptions themselves are generated via PHP's throw statement. The throw statement needs to be passed a descriptive message, and an optional error code. When the exception is raised, this description and code will be made available to the exception handler.</p>
329	<p>Wanna see how this works? Here's an example:</p>
330	{% highlight php %}<?php
331// PHP 5
332
333error_reporting(0);
334
335// try this code
336try {
337    $file = 'somefile.txt';
338
339    // open file
340    if (!$fh = fopen($file, 'r')) {
341        throw new Exception('Could not open file!');
342    }
343
344    // read file contents
345    if (!$data = fread($fh, filesize($file))) {
346        throw new Exception('Could not read file!');
347    }
348
349    // close file
350    fclose($fh);
351
352    // print file contents
353    echo $data;
354}
355
356// catch errors if any
357catch (Exception $e) {
358    print 'Something bad just happened...';
359}
360?>{% endhighlight %}
361	<p>If the file doesn't exist or is unreadable, the throw statement will generate an exception (basically, an instance of PHP's built-in Exception object) and pass it a message describing the error. When such an exception is generated, control passes to the first catch() block. If the catch() block can handle the exception type, the code within the catch() block is executed. If the first catch() block cannot handle the generated exception, control passes to the next one.</p>
362	<p>Don't worry too much about "exception types" at this point - all will be explained shortly. For the moment, all you need to know is that the generic catch() block above will catch all exceptions, regardless of type.</p>
363	<p>Now, there's one problem with the previous listing. Although the catch() block will trap the exception and print a message, it can't display the descriptive message sent by the throw statement with the exception. To access this message, as well as a couple of other interesting pieces of information, it is necessary to use some of the Exception object's built-in methods. Take a look at this revision of the previous script, which illustrates:</p>
364	{% highlight php %}<?php
365<?php
366// PHP 5
367
368error_reporting(0);
369
370// try this code
371try {
372    $file = 'somefile.txt';
373
374    // open file
375    if (!$fh = fopen($file, 'r')) {
376        throw new Exception('Could not open file!', 12);
377    }
378
379    // read file contents
380    if (!$data = fread($fh, filesize($file))) {
381        throw new Exception('Could not read file!', 9);
382    }
383
384    // close file
385    fclose($fh);
386
387    // print file contents
388    echo $data;
389}
390// catch errors if any
391catch (Exception $e) {
392    print '<h2>Exception</h2>';
393    print 'Error message: ' . $e->getMessage() . '<br />';
394    print 'Error code: ' . $e->getCode() . '<br />';
395    print 'File and line: ' . $e->getFile() . '(' . $e->getLine() . ')<br />';
396    print 'Trace: ' . $e->getTraceAsString() . '<br />';
397}
398?>{% endhighlight %}
399	<p>When you run this script, you'll see that the message generated by the exception handler contains:</p>
400	<ul>
401		<li>the descriptive data sent by throw,</li>
402		<li>an error code (also sent by throw),</li>
403		<li>the file name and line number where the exception occurred, and</li>
404		<li>a stack trace indicating the exception's progress through the class hierarchy, if there is one.</li>
405	</ul>
406	<p>This data is generated by calling the Exception object's getMessage(), getCode(), getFile(), getLine() and getTraceAsString() methods respectively inside the catch() block.</p>
407
408	<h2>Adding some class</h2>
409	<p>You can handle different exceptions in different ways, by sub-classing the generic Exception object and using more than one catch() block. The following example is a simple illustration of this:</p>
410	{% highlight php %}<?php
411<?php
412// PHP 5
413
414// sub-class the Exception class
415class NegativeNumException extends Exception {}
416class OutOfRangeException extends Exception {}
417class FloatException extends Exception {}
418
419// function to test a number
420function testNumber($num) {
421    // float
422    // trigger an exception
423    if (is_float($num)) {
424        throw new FloatException($num);
425    }
426
427    // negative
428    // trigger an exception
429    if ($num < 0) {
430        throw new NegativeNumException($num);
431    }
432
433    // out of range
434    // trigger an exception
435    if ($num > 1000 || $num < 100) {
436        throw new OutOfRangeException($num);
437    }
438}
439
440// try this code
441try {
442    testNumber(-19);
443}
444
445// catch errors, if any
446catch (NegativeNumException $e) {
447    print 'A negative number was provided ('.$e->getMessage().'). Please provide a positive integer between 100 and 1000.<br />';
448}
449catch (OutOfRangeException $e) {
450    print 'The number provided is out of range ('.$e->getMessage().'). Please provide a positive integer between 100 and 1000.<br />';
451}
452catch (FloatException $e) {
453    print 'The number provided is not an integer ('.$e->getMessage().'). Please provide a positive integer between 100 and 1000.<br />';
454}
455catch (Exception $e) {
456    print 'Error message: ' . $e->getMessage() . '<br />';
457    print 'Error code: ' . $e->getCode() . '<br />';
458    print 'File and line: ' . $e->getFile() . '(' . $e->getLine() . ')<br />';
459    print 'Trace: ' . $e->getTraceAsString() . '<br />';
460}
461?>{% endhighlight %}
462	<p>In this case, I've created three new Exception sub-classes from the base object, one for each possible error. Next, I've set up catch() blocks for each exception type, and written exception-handling code that is specific to each type. Depending on which exception occurs (you can generate different ones by sending the testNumber() function different values), the appropriate catch() block will be invoked and a different error message will be printed.</p>
463	<p>Note that because PHP will always use the first catch() block that matches the exception type, and because the generic Exception class matches all exceptions, the catch() blocks must be arranged in the order of most specific first. This has been done in the example above, where the generic catch() block appears last on the list.</p>
464	<p>Here's another example, this one illustrating a more useful application - using the exception model in a user authentication class to provide easy-to-understand error handling (I'll assume here that the passwords in the password file are encrypted with MD5, and use a 12-character salt beginning with $1$). Take a look:</p>
465	{% highlight php %}<?php
466<?php
467// PHP 5
468// class definition
469class userAuth {
470    // define properties
471    private $username;
472    private $passwd;
473    private $passwdFile;
474    private $userFound;
475
476    // constructor
477    // must be passed username and non-encrypted password
478    public function __construct($username, $password) {
479            $this->username = $username;
480            $this->passwd = $password;
481    }
482
483    // set .htaccess-style file to check for passwords
484    public function setPasswdFile($file) {
485        $this->passwdFile = $file;
486    }
487
488    // perform password verification
489    public function authenticateUser() {
490        // check that the file exists
491        if (!file_exists($this->passwdFile)) {
492            throw new FileException("Password file cannot be found: " . $this->passwdFile);
493        }
494
495        // check that the file is readable
496        if (!is_readable($this->passwdFile)) {
497            throw new FileException("Unable to read password file: ". $this->passwdFile);
498        }
499
500        // read file
501        $data = file($this->passwdFile);
502    
503        // define flag
504        $this->userFound = 0;
505        
506        // iterate through file
507        foreach ($data as $line) {
508            $arr = explode(":", $line);
509            // if username matches, test password
510            if ($arr[0] == $this->username) {
511                $this->userFound = 1;
512                // encrypt the supplied password and 
513                // match it against value in password file
514                if ($arr[1] == crypt($this->passwd, $arr[1])) {
515                    echo "User was authenticated";
516                    // do some other stuff
517                }
518                // otherwise return exception
519                else {
520                    throw new AuthException("Incorrect password");
521                    break;
522                }
523            }
524        }
525        
526        // could not find a username match
527        // return exception
528        if ($this->userFound == 0) {
529                throw new AuthException("No such user");
530        }
531    }
532// end class definition
533}
534
535// subclass exceptions
536class FileException extends Exception {};
537class AuthException extends Exception {};
538
539// try the code
540try {
541    // create instance
542    $ua = new userAuth("joe", "secret");
543
544    // set password file
545    $ua->setPasswdFile("password.txt");
546
547    // perform authentication
548    $ua->authenticateUser();
549
550}
551// catch authentication failures, if any
552catch (FileException $e) {
553    // print file errors
554    print "A file error occurred. ".$e->getMessage();
555}
556catch (AuthException $e) {
557    // an authentication error occurred
558    print "An authentication error occurred. ".$e->getMessage();
559    // more normally, redirect to new page on auth errors, e.g.
560    // header ('Location: login_fail.php');
561}
562catch (Exception $e) {
563    print "An unknown error occurred";
564}
565?>{% endhighlight %}
566	<p>Here, depending on the type of error, either a FileException() or an AuthException() will be generated - and handled by the corresponding catch() block. Notice how easy the exception handling framework is to read and extend. It's precisely this ease of use and extensibility that helps the new PHP 5 model score over the earlier, more primitive techniques of handling application errors.</p>
567	<p>That's about it for the moment. Come back soon, for more PHP 101!</p>
568
569      </section>
570      <footer>
571        <p><small>Hosted on GitHub Pages &mdash; Theme by <a href="http://twitter.com/#!/michigangraham">mattgraham</a></small></p>
572      </footer>
573    </div>
574    <!--[if !IE]><script>fixScale(document);</script><!--<![endif]-->
575    
576  </body>
577</html>