Thanks for your input.

I did not try-catch because it doesn't really work, you either
duplicate rollback code or get excessive nesting. Real code will
obviously be more complicated than the examples.
Also in both cases the handlers are many lines apart from the actual
transaction call.

Duplicate rollback code:

    try {
        transactionOne();
    }
    catch (\Throwable $e) {
       rollbackTransactionOne();
       throw $e;
    }

    try {
        transactionTwo();
    }
    catch (\Throwable $e) {
        rollbackTransactionOne();
        rollbackTransactionTwo();
        throw $e;
    }
    finally {
        cleanupTransactionTwo();
    }

    try {
       transactionThree();
    }
    catch (\Throwable $e) {
        rollbackTransactionOne();
        rollbackTransactionTwo();
        rollbackTransactionThree();
        throw $e;
    }

    logTransactionOne();
    logTransactionTwo();
    logTransactionThree();

Excessive nesting:

    try {
        transactionOne()
        try {
            transactionTwo();
            try {
                transactionThree();
            }
            catch (\Throwable $e) {
                rollbackTransactionThree()
                throw $e;
            }
        }
        catch (\Throwable $e) {
            rollbackTransactionTwo();
            throw $e;
        }
        finally {
            cleanupTransactionTwo();
        }
    }
    catch (\Throwable $e) {
        rollbackTransactionOne();
        throw $e;
    }

But you did help me very well with your second example because I can
put a single try-catch over the entire thing and indeed do the
optional callbacks at the end:

    try {
        $scope = new ScopeGuard;

        $scope->onSucces(function() { logTransactionOne(); });
        $scope->onFailure(function() { rollbackTransationOne(); });
        doTransactionOne();

        $scope->onSuccess(function() { logTransactionTwo(); });
        $scope->onFailure(function() { rollbackTransactionTwo(); });
        $scope->onExit(function() { cleanupTransactionTwo(); });
        doTransactionTwo();
    }
    catch (\Throwable $e) {
        $scope->callFailureHandlers();
        throw $e;
    }
    finally {
        $scope->callExitHandlers();
    }

    $scope->callSuccessHandlers();

This suffices as far as duplicate code and nesting go. But the last
few lines are always the same, and this is a common pattern. Perhaps
this can be encapsulated somehow (IoC)? Destructors seem like a good
fit but it seems the engine does not have the necessary hook.

Maybe we can let the ScopeGuard object itself execute the code? Like this:

class ScopeGuard
{
    public static function execute(closure $code) {
        try {
            $scope = new self;
            $result = $code($scope);
            $scope->executeSuccessHandlers();
        }
        catch(\Throwable $e) {
            $scope->executeFailureHandlers();
            throw $e;
        }
        finally {
            $scope->executeExitHandlers();
        }
        return $result;
    }
    public function onFailure($callable) {}
    public function onSuccess($callable) {}
    public function onExit($callable) {}
}

ScopeGuard::execute(function(ScopeGuard $scope) {
    /* same linear transaction code here as above, but without try-catch */
});

Not sure on this one. It restricts you to use a single ScopeGuard at a
time. I might make a library for it though.

2015-10-28 12:24 GMT+01:00 Leigh <lei...@gmail.com>:
>
>
> Why don't you catch the exception? (You can always re-throw it if you want
> to do something when there is an exception, but don't want to handle it
> yourself)
>
> try {
>     doTransactionOne();
>     logTransactionOne();
> }
> catch (\Throwable $e) {
>     rollbackTransationOne();
>     throw $e;
> }
>
> Or if you really want to use your scope thingy.
>
> try {
>     doTransactionOne();
> }
> catch (\Throwable $e) {
>     $scope->abortedDueToException();
>     throw $e;
> }
>

-- 
PHP Internals - PHP Runtime Development Mailing List
To unsubscribe, visit: http://www.php.net/unsub.php

Reply via email to