4

How can I see if an exception is currently in flight, i.e. the stack is unwinding?

In the example below how would you implement isExceptionInFlight()?

<?php

class Destroyer
{
    function __destruct()   {
        if (isExceptionInFlight()) {
            echo 'failure';
        } else {
            echo 'success';
        }
    }
}

function isExceptionInFlight() {
    // ?????
}

function createAndThrow()
{
    $var = new Destroyer;
    throw new \Exception;
}

createAndThrow();

The purpose of this would be to implement D's scope statement, which is available as a library in multiple other languages. This allows you to get rid of nested try-catch blocks, which in turn makes it easier to do transactions with rollbacks correctly.

Addendum1:

I've looked around in the Zend PHP Engine and executor_globals.exception seems to be what I'm looking for (https://github.com/php/php-src/blob/master/Zend/zend_globals.h). However this value is always nullptr when I inspect it during __destruct(). Any idea where I should look next?

Addendum2:

Inspecting executor_globals.opline_before_exception has led to some progress. However it is not reset to nullptr when the exception has been caught.

Addendum3:

I've found the following code (line 135)

/* Make sure that destructors are protected from previously thrown exceptions.
 * For example, if an exception was thrown in a function and when the function's
 * local variable destruction results in a destructor being called.
 */
old_exception = NULL;
if (EG(exception)) {
    if (EG(exception) == object) {
        zend_error_noreturn(E_CORE_ERROR, "Attempt to destruct pending exception");
    } else {
        old_exception = EG(exception);
        EG(exception) = NULL;
    }
}
zend_call_method_with_0_params(&obj, object->ce, &destructor, ZEND_DESTRUCTOR_FUNC_NAME, NULL);
if (old_exception) {
    if (EG(exception)) {
        zend_exception_set_previous(EG(exception), old_exception);
    } else {
        EG(exception) = old_exception;
    }
}

This seems to actively PREVENT me from doing what I want, and explains why executor_globals.exception is always nullptr.

Erik van Velzen
  • 6,211
  • 3
  • 23
  • 23

1 Answers1

1

Although I don't recommend, I have implemented it in the past. My approach was (simply put) like this:

Implement custom Exception class

class MyException extends Exception {
    public static $exceptionThrown = false;

    public function __construct($your parameters) {
         self::$exceptionThrown = true;
    }

}

Now, every exception should be your own exception implementation instead of default Exception.

class Destroyer {
    public function __destruct() {
        if(MyException::exceptionThrown() {
            Database::rollback();
        } else {
            Database::commit();
        }
    }
}
Marco Aurélio Deleu
  • 4,279
  • 4
  • 35
  • 63
  • 1
    What if that exception was caught though? Wouldn't `MyException::exceptionThrown` still be `true` even though it was addressed? – HPierce Oct 18 '15 at 17:10
  • Yes. Like I said, I don't really recommend this approach since there are a lot of frameworks that is a lot more complete. But when using that code, I was more novice and I liked to use exceptions like HTTP failure. When using it, I only had the problem of catching an exception once, so I just added a "MyException::$exceptionThrown = false" inside the catch block. Since I didn't have more troubles similarly, I just let that go. – Marco Aurélio Deleu Oct 18 '15 at 17:13