The Grumpy Little Book Of Hack A pocket guide for using HHVM and Hack on legacy PHP code bases Chris Hartjes This book is for sale at http://leanpub.com/grumpy-hack This version was published on 2015-03-25 This is a Leanpub book. Leanpub empowers authors and publishers with the Lean Publishing process. Lean Publishing is the act of publishing an in-progress ebook using lightweight tools and many iterations to get reader feedback, pivot until you have the right book and build traction once you do. ©2015 Chris Hartjes Also By Chris Hartjes The Grumpy Programmer’s Guide To Building Testable PHP Applications The Grumpy Programmer’s PHPUnit Cookbook PHPUnit Ricettario Del Programmatore Scontroso The Grumpy Programmer’s Guia para criação de aplicações testáveis em PHP 短気なプログラマのためのPHPUnitクックブック Contents Error Handling . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1 Error Handling One of the more simultaneously interesting and frustrating features of PHP is error handling. You can display them on the screen, you can write them to a log file, you can turn them completely off. I remember a fun interview question I used to ask people back when companies actually let me interview people was to ask them what they would do if presented with a PHP script that, when run in the browser, gives you a blank page. All I was looking for was to see if the person had an idea about web server logs and error levels in PHP. I don’t think it’s a terrible question — not even a “gotcha” type of thing. As web developers, it is inevitable that we spend a lot of time looking at error messages to figure out where things went wrong. However, HHVM takes a very pragmatic approach to error handling — by default it outputs nothing. I figured this out the hard way, fiddling around with various settings in the configuration files. After admitting a temporary setback, I did a search on Google and found this little nugget of a discussion: That Was Unexpected That was… unexpected. All this time I thought I had been messing something up. So, how do we fix this problem? We have to delve into the world of setting error handlers. In the PHP manual, it says this about using set_error_handler() Sets a user function (error_handler) to handle errors in a script. Error Handling 2 This function can be used for defining your own way of handling errors during runtime, for example in applications in which you need to do cleanup of data/files when a critical error happens, or when you need to trigger an error under certain conditions (using trigger_error()). It is important to remember that the standard PHP error handler is completely bypassed for the error types specified by error_types unless the callback function returns FALSE. error_reporting() settings will have no effect and your error handler will be called regardless – however you are still able to read the current value of error_reporting and act appropriately. Of particular note is that this value will be 0 if the statement that caused the error was prepended by the @ error-control operator. Also note that it is your responsibility to die() if necessary. If the error-handler function returns, script execution will continue with the next statement after the one that caused an error. The following error types cannot be handled with a user defined function: E_ERROR, E_PARSE, E_CORE_ERROR, E_CORE_WARNING, E_COMPILE_ERROR, E_COMPILE_WARNING, and most of E_STRICT raised in the file where set_error_handler() is called. If errors occur before the script is executed (e.g. on file uploads) the custom error handler cannot be called since it is not registered at that time. As an aside, one of the thing that makes it difficult to create good REPL (Read-Evaluate-Print-Loop) tools for PHP is that there is a category of fatal errors. Errors that crash the PHP interpreter mean that you can’t easily trap them. REPLs don’t have anything to do with HHVM, but I thought it was an interesting side note. Luckily other people have come up with error handlers that will do the job. Here’s a sample one: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 <?hh set_error_handler(function ($errNum, $msg, $filename, $line) { switch ($errNum) { case E_ERROR: $errorLevel = 'Error'; break; case E_WARNING: $errorLevel = 'Warning'; break; case E_NOTICE: $errorLevel = 'Notice'; break; default: $errorLevel = 'Undefined'; 3 Error Handling 15 16 17 18 19 20 21 22 23 } echo " <br> <b>{$errorLevel}</b>: {$msg} in <b>{$filename}<b> on line <b>{$line}</b> <br>"; }); Now we have the additional problem of needing to have this executed on every single page load. In our current application structure we don’t have a bootstrap sequence because we are using individual pages instead of loading everything via a front controller. What are our options? We could edit every single file to include a bootstrap file. This is probably the best choice, despite the amount of work that I would have to do. This could be even more difficult in applications with a very large number of scripts. The second option is to cheat a little and have a file that is already being loaded everywhere that PHP code is being run and include it there. In our particular application a prime candidate for that would be our db_config.php file. I think instead I will go the safe-but-extra-work route of creating a bootstrap file and refactor all the other files to include it where required. After that all that refactoring, let’s see if we can find an error message to prove that it’s working. Show Hack error message Hrm. That didn’t take long. Off I go to fix the warning, happy with the knowledge that the error handler is working. So, what are some of the other things we could try with an error handler. What if we wanted to add a stack trace for even more verbose error handling? 4 Error Handling 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 <?hh set_error_handler(function ($errNum, $msg, $filename, $line) { switch ($errNum) { case E_ERROR: $errorLevel = 'Error'; break; case E_WARNING: $errorLevel = 'Warning'; break; case E_NOTICE: $errorLevel = 'Notice'; break; default: $errorLevel = 'Undefined'; } echo " <br> <b>{$errorLevel}</b>: {$msg} in <b>{$filename}<b> on line <b>{$line}</b> <br>"; echo "<pre>"; debug_print_backtrace(); echo "</pre>"; }); Now with stack traces! The possible permutations are endless, but the issue I wanted you to remember was that you must create your own error handler for your application if you want to be able to actually display errors on the web site. One thing to keep in mind — this error handler does not check to see if you have configured PHP to actually display errors. You could add some logic inside the error handler that checks for the configuration setting and instead writes the information to a log file if you don’t want to display them. A common practice at the time I wrote this book was for errors to not be displayed when the application is in production, instead writing those errors to a log file for later review. In this case, I am okay with spitting out errors to the screen. How would I change it to write to the log file instead? Error Handling 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 ; php options session.save_handler = files session.save_path = /var/lib/php5 session.gc_maxlifetime = 1440 include_path = ".:/usr/share/hhvm/pear" display_errors = Off error_reporting = E_STRICT log_errors = On error_log = /var/log/hhvm/error.log ; hhvm specific hhvm.log.level = Warning hhvm.log.always_log_unhandled_exceptions = true hhvm.log.runtime_error_reporting_level = 8191 hhvm.mysql.typed_results = false hhvm.dynamic_extension_path = /usr/local/extensions hhvm.dynamic_extensions[pgsql] = pgsql.so Then we change our error handler to build the error message and write it to the log file. 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 <?hh set_error_handler(function ($errNum, $msg, $filename, $line) { switch ($errNum) { case E_ERROR: $errorLevel = 'Error'; break; case E_WARNING: $errorLevel = 'Warning'; break; case E_NOTICE: $errorLevel = 'Notice'; break; default: $errorLevel = 'Undefined'; } $errorDate = date("Y-m-d H:i:s"); $logMsg = "{$errorDate} {$errorLevel}: {$msg} in {$filename}"; $logMsg .= " on line {$line}"; error_log($logMsg); }); So what ends up in the error log? 5 Error Handling 1 6 Notice: Undefined index: max in /var/www/webreg/models/games.php on line 18 I’m sure you can think of some other options to make your error logging useful and letting you quickly diagnose problems with the application.
© Copyright 2024