56

PHP prior to version 5.5 has no finally block - i.e., whereas in most sensible languages, you can do:

try {
   //do something
} catch(Exception ex) {
   //handle an error
} finally {
   //clean up after yourself
}

PHP has no notion of a finally block.

Anyone have experience of solutions to this rather irritating hole in the language?

Mark Amery
  • 143,130
  • 81
  • 406
  • 459
Alistair Evans
  • 36,057
  • 7
  • 42
  • 54

7 Answers7

61

Solution, no. Irritating cumbersome workaround, yes:

$stored_exc = null;
try {
    // Do stuff
} catch (Exception $exc) {
    $stored_exc = $exc;
    // Handle an error
}
// "Finally" here, clean up after yourself
if ($stored_exc) {
    throw($stored_exc);
}

Yucky, but should work.

Please note: PHP 5.5 finally (ahem, sorry) added a finally block: https://wiki.php.net/rfc/finally (and it only took a few years... available in the 5.5 RC almost four years to the date since I posted this answer...)

Mihai Limbășan
  • 64,368
  • 4
  • 48
  • 59
  • 3
    So what you're saying is PHP does have finally... it's spelled "if" – Max Schmeling May 29 '09 at 17:42
  • 18
    What happens if I return within the try{}? Your `if` won't execute. – Rob May 29 '09 at 17:48
  • 19
    That's easy, don't return within the try{}. In case you didn't notice, I specifically pointed out in the very first sentence that it's not a solution but an irritating, cumbersome workaround. The lack of a finally block cannot be worked around completely since providing a finally guarantee requires interpreter support. – Mihai Limbășan May 29 '09 at 17:57
  • Oh, in case it came across wrong - good call in the comment, +1 :) – Mihai Limbășan May 29 '09 at 18:09
  • 1
    I admit I am not an expert try/catch/finally user, but why not just throw the exception inside the catch block? is it because you might run some other code PRIOR TO the throw() as cleanup code? And, if (as Rob says) you have a 'return X;' inside the try block, will other language still run the finally block before returning (if so, that's pretty nice, although when I see a return statement, I make assumption nothing else runs after that? Very interesting stuff to me though. – OneNerd May 29 '09 at 19:34
  • 2
    @OneNerd: Yes, other languages will run the finally block even when returning from inside the catch block. They will run it even if you throw inside the catch block. They will obviously not run it if your program dumps code, is killed, if the oerating system crashes, if the power is cut, etc – Mihai Limbășan May 30 '09 at 13:35
  • You dont need to store the exception into an additional variable. Just `catch (Exception $e) { /* do something */ } if (isset($e)) { /* f finally */ }` – KingCrunch Dec 10 '10 at 12:35
  • @KingCrunch: What I posted was boilerplate code. It's true that you don't need to store it, but simply *ignoring* exceptions is usually a horribly bad idea. I'm rather assuming that one would look at $stored_exc and would do something with that information. – Mihai Limbășan Dec 10 '10 at 13:35
  • You, of course you shouldnt ignore it. I just wanted to mention, that you can omit the additional variable. I voted for your solution anyway ;) – KingCrunch Dec 10 '10 at 14:00
  • That's alright - I shouldn't have assumed... Simply seen too many people subscribe to the "drop exceptions on the floor" school of thought, and since PHP is very lenient towards those practices (as in "it doesn't bomb out when in fact it should do that right after crawling out of the server and beating the responsible coder with a stick")... :) – Mihai Limbășan Dec 10 '10 at 20:39
  • Why don't you put the finalizer code and rethrow within the `catch` block so you don't need `$stored_exc` ? That works for me, at least in PHP 5.3.2. – Joey Adams Aug 13 '12 at 12:25
9

The RAII idiom offers a code-level stand-in for a finally block. Create a class that holds callable(s). In the destuctor, call the callable(s).

class Finally {
    # could instead hold a single block
    public $blocks = array();

    function __construct($block) {
        if (is_callable($block)) {
            $this->blocks = func_get_args();
        } elseif (is_array($block)) {
            $this->blocks = $block;
        } else {
            # TODO: handle type error
        }
    }

    function __destruct() {
        foreach ($this->blocks as $block) {
            if (is_callable($block)) {
                call_user_func($block);
            } else {
                # TODO: handle type error.
            }
        }
    }
}

Coordination

Note that PHP doesn't have block scope for variables, so Finally won't kick in until the function exits or (in global scope) the shutdown sequence. For example, the following:

try {
    echo "Creating global Finally.\n";
    $finally = new Finally(function () {
        echo "Global Finally finally run.\n";
    });
    throw new Exception;
} catch (Exception $exc) {}

class Foo {
    function useTry() {
        try {
            $finally = new Finally(function () {
                echo "Finally for method run.\n"; 
            });
            throw new Exception;
        } catch (Exception $exc) {}
        echo __METHOD__, " done.\n";
    }
}

$foo = new Foo;
$foo->useTry();

echo "A whole bunch more work done by the script.\n";

will result in the output:

Creating global Finally.
Foo::useTry done.
Finally for method run.
A whole bunch more work done by the script.
Global Finally finally run.

$this

PHP 5.3 closures can't access $this (fixed in 5.4), so you'll need an extra variable to access instance members within some finally-blocks.

class Foo {
    function useThis() {
        $self = $this;
        $finally = new Finally(
            # if $self is used by reference, it can be set after creating the closure
            function () use ($self) {
               $self->frob();
            },
            # $this not used in a closure, so no need for $self
            array($this, 'wibble')
        );
        /*...*/
    }

    function frob() {/*...*/}
    function wibble() {/*...*/}
}

Private and Protected Fields

Arguably the biggest problem with this approach in PHP 5.3 is the finally-closure can't access private and protected fields of an object. Like accessing $this, this issue is resolved in PHP 5.4. For now, private and protected properties can be accessed using references, as Artefacto shows in his answer to a question on this very topic elsewhere on this site.

class Foo {
    private $_property='valid';

    public function method() {
        $this->_property = 'invalid';
        $_property =& $this->_property;
        $finally = new Finally(function () use (&$_property) {
                $_property = 'valid';
        });
        /* ... */
    }
    public function reportState() {
        return $this->_property;
    }
}
$f = new Foo;
$f->method();
echo $f->reportState(), "\n";

Private and protected methods can be accessed using reflection. You can actually use the same technique to access non-public properties, but references are simpler and more lightweight. In a comment on the PHP manual page for anonymous functions, Martin Partel gives an example of a FullAccessWrapper class that opens up non-public fields to public access. I won't reproduce it here (see the two previous links for that), but here is how you'd use it:

class Foo {
    private $_property='valid';

    public function method() {
        $this->_property = 'invalid';
        $self = new FullAccessWrapper($this);
        $finally = new Finally(function () use (&$self) {
                $self->_fixState();
        });
        /* ... */
    }
    public function reportState() {
        return $this->_property;
    }
    protected function _fixState() {
        $this->_property = 'valid';
    }
}
$f = new Foo;
$f->method();
echo $f->reportState(), "\n";

try/finally

try blocks require at least one catch. If you only want try/finally, add a catch block that catches a non-Exception (PHP code can't throw anything not derived from Exception) or re-throw the caught exception. In the former case, I suggest catching StdClass as an idiom meaning "don't catch anything". In methods, catching the current class could also be used to mean "don't catch anything", but using StdClass is simpler and easier to find when searching files.

try {
   $finally = new Finally(/*...*/);
   /* ... */
} catch (StdClass $exc) {}

try {
   $finally = new Finally(/*...*/);
   /* ... */
} catch (RuntimeError $exc) {
    throw $exc
}
Community
  • 1
  • 1
outis
  • 75,655
  • 22
  • 151
  • 221
  • 1
    I actually rather like this, mostly because it still works even if an exception is thrown that you weren't anticipating, the function will still exit and destroy the finally object. To get around the whole "not running until the function exits" thing, you could use `unset $finally` after the exception which will call the destructor early. – Alistair Evans Feb 12 '12 at 23:44
  • +1, me likey. Also, we're using PHP 5.2.8 here for some reason, so I have to add: If anonymous functions aren't available, you can define the finally-function _before_ the Finally object is created, then simply pass in the name: `function fooFinally() {}; try { $finally = new Finally(fooFinally); ....` – Izkata Mar 21 '12 at 20:26
  • Oh, nice: These "finally" statements will run even in the event of a fatal error. (Tested by attempting `throw new StdClass;`) – Izkata Mar 21 '12 at 20:34
  • @Izkata: note that barewords (i.e. [undefined constants](http://php.net/manual/en/language.constants.syntax.php)) are discouraged, as evidenced by the fact they produce an error of level `E_NOTICE`. When passing a callback by name, use a string: `new Finally('fooFinally')`. – outis Mar 23 '12 at 03:29
2

Here is my solution to the lack of finally block. It not only provides a work around for the finally block, it also extends the try/catch to catch PHP errors (and fatal errors too). My solution looks like this (PHP 5.3):

_try(
    //some piece of code that will be our try block
    function() {
        //this code is expected to throw exception or produce php error
    },

    //some (optional) piece of code that will be our catch block
    function($exception) {
        //the exception will be caught here
        //php errors too will come here as ErrorException
    },

    //some (optional) piece of code that will be our finally block
    function() {
        //this code will execute after the catch block and even after fatal errors
    }
);

You can download the solution with documentation and examples from git hub - https://github.com/Perennials/travelsdk-core-php/tree/master/src/sys

bobef
  • 990
  • 1
  • 9
  • 14
1
function _try(callable $try, callable $catch, callable $finally = null)
{
    if (is_null($finally))
    {
        $finally = $catch;
        $catch = null;
    }

    try
    {
        $return = $try();
    }
    catch (Exception $rethrow)
    {
        if (isset($catch))
        {
            try
            {
                $catch($rethrow);
                $rethrow = null;
            }
            catch (Exception $rethrow) { }
        }
    }

    $finally();

    if (isset($rethrow))
    {
        throw $rethrow;
    }
    return $return;
}

Call using closures. Second parameter, $catch, is optional. Examples:

_try(function ()
{
    // try
}, function ($ex)
{
    // catch ($ex)
}, function ()
{
    // finally
});

_try(function ()
{
    // try
}, function ()
{
    // finally
});

Properly handles exceptions everywhere:

  • $try: Exception will be passed to $catch. $catch will run first, then $finally. If there is no $catch, exception will be rethrown after running $finally.
  • $catch: $finally will execute immediately. Exception will be rethrown after $finally completes.
  • $finally: Exception will break down the call stack unimpeded. Any other exceptions scheduled for rethrow will be discarded.
  • None: Return value from $try will be returned.
Zenexer
  • 18,788
  • 9
  • 71
  • 77
1

As this is a language construct, you won't find an easy solution for this. You can write a function and call it as the last line of your try block and last line before rethrowing the excepion in the try block.

Good books argues against using finally blocks for any other than freeing resource as you can not be sure it will execute if something nasty happens. Calling it an irritating hole is quite an overstatement. Believe me, a hell lot of exceptionally good code is written in languages without finally block. :)

The point of finally is to execute no matter if the try block was successfull or not.

Csaba Kétszeri
  • 674
  • 3
  • 6
0

If anyone is still keeping track of this question, you might be interested in checking out the (brand new) RFC for a finally language feature in the PHP wiki. The author already seems to have working patches, and I'm sure the proposal would benefit from other developers' feedback.

Tobias Gies
  • 724
  • 1
  • 6
  • 15
0

I just finished writing a more elegant Try Catch Finally class which may be of use to you. There are some drawbacks but they can be worked around.

https://gist.github.com/Zeronights/5518445

Ozzy
  • 10,285
  • 26
  • 94
  • 138