The Grumpy Little Book Of Hack

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.