Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can I get around the lack of a finally block in PHP?

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?

like image 741
Alistair Evans Avatar asked May 29 '09 17:05

Alistair Evans


People also ask

How do I skip finally block execution?

You cannot skip the execution of the final block. Still if you want to do it forcefully when an exception occurred, the only way is to call the System. exit(0) method, at the end of the catch block which is just before the finally block.

How do you handle errors in finally block?

The "finally" block execution stops at the point where the exception is thrown. Irrespective of whether there is an exception or not "finally" block is guaranteed to execute. Then the original exception that occurred in the try block is lost.

Can we have empty finally block?

As such, an empty finally block is most probably the sign of potential "resource leaks" that will jeopardize the application's stability. Add code to the finally block, especially the release of resources used in the try block, if any.

Is it necessary to give finally block?

But finally is useful for more than just exception handling — it allows the programmer to avoid having cleanup code accidentally bypassed by a return , continue , or break . Putting cleanup code in a finally block is always a good practice, even when no exceptions are anticipated.


2 Answers

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...)

like image 136
Mihai Limbășan Avatar answered Oct 23 '22 03:10

Mihai Limbășan


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 } 
like image 25
outis Avatar answered Oct 23 '22 04:10

outis