A Note About Trying To Use Exceptions To Handle Errors From MySQLi In PHP

PHP's attempts to take collections of coding features and functions and present them as a more streamlined and uniform feature is often as confising as it is admirable. PHP arrays are a good example of this. Someone that has little or no exprience with other languages could be forgiven for thinking that they're fairly trivial structures; yet they seem weirdly flexible. You can use both integers and strings as array indices/keys even within the same array. A single array can hold differing types too. Integers, Strings, Booleans, Objects, other Arrays and pretty-much anything else you can lay your hands on can all be stored in the same array. This is far more flexible than an array in something like Java where you not only have to say how many items the array can hold when you create it, but also what type of item it will hold. So if you declare an array that can hold Fish and you try and stick a Chip in it, Java's gonna barf.

The difference between arrays in Java and PHP is, of course, not that PHP is just magic; PHP simply has a much more complex structure that it simplycalls an array. In reality an carray in PHP is more like a mash-up of array buckets, pointers, hash maps, unicorns and rainbows (so maybe there's a little bit of magic in there). All of this, however, is tucked away and papered-over somewhat so that the beginner gets as simplistic a view of an "array" as possible. That is until they try to take a shortcut by trying to programmatically obtain the Nth key of a mixed index array.

So it is with PHP's MySQLi module. A large module with many features and functions with complexity heaped upon complexity. Aside from the fact that the old MySQL is still right there alongside MySQLi (although deprecated), there's also the fact that it has a dual paradigm implementation. You have the Object Oriented API and—despite the implication of PHP moving twards object orientation with their "look at our shiny new OO yumminess" at PHP5's launch—a comparatively unkempt procedural API that has subtle differences to it's object oriented brother (beyond the very obvious ones of course).

On top of that there's also the matter of deciding how to detect and deal with errors. I kept thinking that there's just no point bothering with Exceptions in PHP because with PHP you're not trying to maintain a state of execution like you would in Java. In PHP you want your script to just execute quickly and then exit. There's little point bothering about why something went wrong because it's not usually the sort of thing the user could do anything about anyway. In Java on the other hand, they're very handy 'cause you can catch an exception, see what it is and do handle it in a way that allows the program to keep running. When I finally got around to giving this place an overhaul I decided to start moving towards using exceptions more. The main motivation behind the decision was simply to drastically reduce the ammount of code that simply checks for errors. The other great advantage of using exceptions is that you can more-or-less do away with a lot of error checking code and have most/all of your error handling code in one place; which makes it much more readable and easier to maintain. A significant drawback, however, is that exceptions seem more like an ill-suited afterthought. When trying to use them with MySQLi, you not only have to tell PHP that you want MySQLi to throw them, but also what should cause one to be thrown.

This non-uniform approach to exceptions seems to cause an unexpected problem to occur when trying to use them with MySQLi. According to the second note in the notes section of the PHP manual's page on the MySQLi class constructor OO syntax only: If a connection fails an object is still returned.. I don't know if this is true if you don't activate the throwing of exceptions or not (probably would have been sensible to check that before I started writing this in retrosect), but it certainly is not true if exceptions are enabled in my version of PHP (5.6.1). While I'm making pages I tend to check the error handling of some sections by deliberately breaking them temporarily. When I did this with the PHP shown below, it didn't respond with the errors I was expecting. The following is a snippet of PHP I put together where the password is intentionally wrong so that an exception indicating an inability to connect to the database would be trown, caught and then some clean up should occur.

<?php
$driver = new mysqli_driver();
$driver->report_mode = MYSQLI_REPORT_ERROR | MYSQLI_REPORT_STRICT;
try {
    $db = new mysqli('localhost', 'root', 'rong', 'db-name');
} catch (mysqli_sql_exception $e) {
    echo 'Caught exception. Message was ' . $e->getMessage();
} finally {
    $db->kill($db->thread_id);
    $db->close();
}
?>

Running this code will indeed cause an exception to be thrown and caught, but then something unexpected will happen. After PHP has echoed the error message from the catch block as expected, it'll spit out an undefined variable warning and then it'll cough up a fatal error and die. The warning is that $db is undefined (despite the manual page saying it'll be an unusable instance of mysqli), and the fatal error is caused by calling a member function (kill()) on null. When I first had this happen it was mixed in with a lot of other code so I wasn't really sure why PHP was saying $db was undefined. My initial thought was that it must be something in my catch block that was causing it, but there wasn't so much as a reference to $db in it. I was trying all kinds of stuff until I made this tiny little test case and realised that the problem is with the documentation, not the code. It turns out that an instance of mysqli is not returned if the constructor fails, which throws a bit of a spanner in the works of the idea for having the clean up code in the finally block 'cause you can't guarantee that the object will be there to clean up. Since the fatal error complains about a member funtion being called on null then the obvious solution is to just bung the cleanup code in in if block that tests for !is_null($db) right? Alas not. The fatal error my complain of a null value being used as an object, but the notice that came first has it right. The variable is undefined: not null. Although the difference is subtle, you need to test for isset($db) instead. Testing for !is_null($db) will just cause PHP to spit out another undefined variable notice because the variable is undefined: not NULL or containing some other value/object reference.

Categories