PageRenderTime 55ms CodeModel.GetById 27ms RepoModel.GetById 0ms app.codeStats 0ms

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