talideon.com

Blackout Ireland

December 3, 2008 at 5:36PM Custom error handlers + the @-operator in PHP = FAIL

This is definitely up there as one of the most retarded PHP misfeatures I’ve encountered.

One of our systems in work needs to check to see if the host given in an email address can receive mail. For that, we’ve a nice short function that does exactly what RFC5821, and all the other SMTP RFCs say to do to check for this:

function can_host_receive_mail($host) {
    if (checkdnsrr($host, 'MX')) {
        return true;
    }
    if (checkdnsrr($host, 'A') || checkdnsrr($host, 'AAAA')) {
        $h = @fsockopen($host, 25, $_errno, $_errstr, 30);
        if ($h) {
            fclose($h);
            return true;
        }
    }
    return false;
}

Simple, right? Wrong. The key line here is the call to fsockopen(), which uses the @ operator to suppress the warning if the host can’t be contacted on port 25. Only the code is behaving as if the @ operator isn’t there at all.

You see, the particular piece of software where this is causing a problem has a custom error handler that intercepts PHP errors and converts them to exceptions to allow uniform error handling. However, it appears that if you’ve a custom error handler, the @ operator is rendered useless.

This is a big problem, especially with fsockopen() because not being able to connect to a host on a given port is, while a little exceptional, not an error; it’s one of these cases where you want an error code rather than an exception because coping with failure in this case is part of the normal flow of the program. That is why PHP triggers an E_WARNING rather than an E_ERROR, and why I was using the @ operator up above.

This breaks if you’re using a custom error handler, and @ stops suppressing the warning in spite of the fact that the presence of @ before an expression should logically by virtue of its locality override any PHP error (as opposed to exception) handling.

To work around this, I altered it to catch the exception the error handler throws:

function can_host_receive_mail($host) {
    if (checkdnsrr($host, 'MX')) {
        return true;
    }
    if (checkdnsrr($host, 'A') || checkdnsrr($host, 'AAAA')) {
        try {
            $h = fsockopen($host, 25, $_errno, $_errstr, 30);
        } catch (AFK_TrappedErrorException $ex) {
            return false;
        }
        if ($h) {
            fclose($h);
            return true;
        }
    }
    return false;
}

Damn, that sucks.

A custom error handler should not render the @ operator useless; it should allow suppression of the custom error handler too. The @ operator is there for a reason.