Sunday 28 June 2015

PHP 7: three different ways of dealing with much the same error/exception

G'day:
My foray into how PHP "handles" erroring code continues. And continues down the same vein of "how PHP has made things difficult when they really ought to be straight forward".

Here's three variations of much the same code. The difference is how the divisor of the division expression is handled. In the first one it's a hard-coded, inline literal.

<?php
// literal.php

$operator = $_GET['operator'] ?? 'divide';
try {
    if ($operator == 'divide'){
        $result = 1 / 0;
    }else{
        $result = 1 % 0;
    }
} catch (Throwable $t){
    echo "Operator: $operator<br>";
    printf("Type: %s<br>", get_class($t));
    printf("Message: %s<br>", $t->getMessage());
}

(ignore the stuff around the $operator variable... I'll get to that)

This yields:


Fatal error: Division by zero in C:\webroots\shared\php\php.local\www\experiment\compilation\literal\literal.php on line 9


Notice that this occurs despite the fact I'm try/catching it, and catching a Throwable.

Now I have a variation which still uses a literal 0, but assigns it to a variable first:

// literalViaVariable.php

$operator = $_GET['operator'] ?? 'divide';
$divisor = 0;
try {
    if ($operator == 'divide'){
        $result = 1 / $divisor;
    }else{
        $result = 1 % $divisor;
    }
} catch (Throwable $t){
    echo "Operator: $operator<br>";
    printf("Type: %s<br>", get_class($t));
    printf("Message: %s<br>", $t->getMessage());
}

And this gives me:


Warning: Division by zero in C:\webroots\shared\php\php.local\www\experiment\compilation\literal\literalViaVariable.php on line 8


Different way of handling it, and still not catchable because it's only a warning now.

And finally, no literal, just a passed-in parameter (which I pass as 0).

// viaParam.php

$operator = $_GET['operator'] ?? 'divide';
$divisor = $_GET['divisor'] ?? 1;
try {

    if ($operator == 'divide'){
        $result = 1 / $divisor;
    }else{
        $result = 1 % $divisor;
    }
} catch (Throwable $t){
    echo "Operator: $operator<br>";
    printf("Type: %s<br>", get_class($t));
    printf("Message: %s<br>", $t->getMessage());
}

And this gives the same warning as the previous one.

Why's this? Well it seems it's because PHP does a wee logic-error parse when it's compiling the code, and it sees "/ 0" in the first example and goes "nuh-uh", so throws the fatal error. But it can't do this with the other examples because it needs to actual run the code to check those ones. So we get two different behaviours.

You indescribable muppets.

PHP's all compiled "on run" anyhow, so there's no gain to be had having the compiler look at this sort of thing: it'll come out in the wash @ runtime anyhow. So the compiler is doing work for no good reason.

Wanna see something even better? Let's now pay attention to the code around selecting which operator to use. All three examples have some variation of this:

$operator = $_GET['operator'] ?? 'divide';
// [...]
if ($operator == 'divide'){
    $result = 1 / $divisor;
}else{
    $result = 1 % $divisor;
}

I can use the modulus operator instead of the division operator if I so choose. And if I do that, I'd get the same behaviour as with the division operator, right? Oh hohoho, you logical people crack me up.

I do get the same results for the literal version: the fatal error. However with the other two rather than get a Warning, I get an Exception:

Operator: mod
Type: Exception
Message: Division by zero


Boggle. Well: actually this is what I'd expect with all of them. This is the good result.

The way the compilation process picks up logic errors like this is daft to me. Compilation should not be addressing logic, it should be addressing syntax. That's it. Logic stuff should be dealt with at runtime, so the code can deal with it at runtime. Obviously I'm being a plank for hard-coding a division by zero into my code, but the compiler shouldn't care. Maybe on Java or something where the whole app gets compiled, and everything is statically typed it makes sense, but not in a loosely- and dynamically- typed language. And one that is compiled on the fly at runtime.

However I could be wrong. Maybe other similar languages do this sort of crap too. So I emulated this code in CFML and in Ruby.

// literal.cfm

operator = url.operator ?: 'divide';
try {
    if (operator == 'divide'){
        result = 1 / 0;
    }else{
        result = 1 % 0;
    }
} catch (Any e){
    writeOutput("Operator: #operator#<br>");
    writeDump(e);
}


ColdFusion 11 yields this:


And it gives this for all six variations of the code (I'll not bother reproducing the other two CFML files here, but they are on GitHub @ literalViaVariable.cfm and viaParam.cfm. It gives a uniform, runtime, handleable exception. As it bloody should.

However I'm never one to back CFML as being the adjudicator of all things sensible, so I tried Ruby too:

## literal.rb

operator = ARGV[0] || "divide"
begin
    if operator == 'divide' then
        result = 1 / 0
    else
        result = 1 % 0
    end
rescue Exception => e
    puts "Operator: #{operator}"
    puts "Message: #{e.exception}"
    puts "Class: #{e.class}"
end

And its results were:

>ruby literal.rb
Operator: divide
Message: divided by 0
Class: ZeroDivisionError

>ruby literal.rb mod
Operator: mod
Message: divided by 0
Class: ZeroDivisionError

>ruby literalViaVariable.rb
Operator: divide
Message: divided by 0
Class: ZeroDivisionError

>ruby literalViaVariable.rb mod
Operator: mod
Message: divided by 0
Class: ZeroDivisionError

>ruby viaParam.rb divide 0
Operator: divide
Message: divided by 0
Class: ZeroDivisionError

>ruby viaParam.rb mod 0
Operator: mod
Message: divided by 0
Class: ZeroDivisionError

>

IE: all handled the same way, just like CFML does.

So I'm pretty confident that PHP needs a bit of a kick up the arse as far as its error/exception-handling goes. It's still greatly improved in PHP 7 alpha (2), but it's got to change its mindset in how it does things a bit. Fortunately now is precisely the time they can do this, given they're looking at error/exception handling as one of PHP 7's big "features".

If anyone else knows any loosely & dynamically typed runtime-compiled languages that behave like PHP rather than CFML and Ruby which might make PHP's position defensible, pls do let me know.

Bug raised: 69957.

Righto.

--
Adam