47

So, I have been reading about the exceptions today on the PHP online manual, and realize I have yet to understand the purpose or real necessity of the finally keyword. I have read some posts here, so my question is slightly different.

I understand that we can use finally in this way:

function hi(){
    return 'Hi';
}


try {
    throw new LogicException("Throw logic \n");
} catch (InvalidArgumentException $e) {
    echo $e->getMessage(); 
}

echo hi();

output:

Fatal error:  Uncaught LogicException: Throw Logic in C:\Users\...a.php:167
Stack trace:
#0 {main}
  thrown in C:\Users\...a.php on line 167

So, in this case the function hi(); is not being execute and for a good reason. I understand if exception is not handled php interpreter halts the script. good. So far from what I read, finally enables us to execute the function hi(); even if the exception is not handled (even though I don't know why)

So, this one I understand.

try {
    throw new LogicException("Throw logic \n");
} catch (InvalidArgumentException $e) {
    echo $e->getMessage(); 
}finally{
    echo hi();
}

output:

Hi
Fatal error:  Uncaught LogicException: Throw Logic in C:\Users\...a.php:167
Stack trace:
#0 {main}
  thrown in C:\Users\...a.php on line 167

This on should the exception error as well as the 'hi' message from the function, even those I don't know any usage for this. But what I don't undersand this, even if we catch the LogicException with catch (LogicException $e) and no exceptions were thrown still we would see the function being execute, and we would see the 'hi' message. as in this example

try {
    throw new LogicException("Throw logic \n");
} catch (LogicException $e) {
    echo $e->getMessage(); 
}finally{
    echo hi();
}

outputs

// Throw logic 
// Hi

So, we still see the function hi() executed even though we have no Uncaught exceptions. Why and what is the use for this? I thought the finally block was to be used as a last resort in case the exceptions were not caught, even if that wasn't the case then why is it the use to run it?

Jeff Puckett
  • 37,464
  • 17
  • 118
  • 167
ʎɹnɔɹǝW
  • 811
  • 2
  • 8
  • 14

4 Answers4

99

finally executes every* time

Regardless of errors, exceptions, or even return statements, the finally block of code will run.

*It will not run if the try or catch blocks execute die/exit.

Exception

One example is closing a database connection in a process that might otherwise leave a dangling connection that blocks the database server from accepting new connections.

Consider this pseudo-code:

try {
   $database->execute($sql);
} finally {
   $database->close();
}

Here we will always close the database connection. If it's a normal query, we close connection after success, and the script will continue to execute.

If it's an erroneous query, then we still close after the exception has been thrown, and the uncaught exception will bubble up.

Here's an example with catch doing some logging.

try {
   $database->execute($sql);
} catch (Exception $exception) {
   $logger->error($exception->getMessage(), ['sql' => $sql]);
   throw $exception;
} finally {
   $database->close();
}

This will make it close the connection with or without an exception.

Return

One of the more obscure behaviors is its ability to execute code after a return statement.

Here you can set a variable after the function has returned:

function foo(&$x)
{
    try {
        $x = 'trying';
        return $x;
    } finally {
        $x = 'finally';
    }
}

$bar = 'main';
echo foo($bar) . $bar;

tryingfinally

but an assignment will be what's returned in try:

$bar = foo($bar);
echo $bar . $bar;

tryingtrying

and returning in the finally overrides the return in the try:

function baz()
{
    try {
        return 'trying';
    } finally {
        return 'finally';
    }
}

echo baz();

finally

note this behavior was different in php 5:

finallyfinally
finallyfinally
finally

https://3v4l.org/biO4e

Exceptional Return

You can kinda make it look like throwing 2 exceptions to bubble up at the same time:

try {
    throw new Exception('try');
} finally {
    throw new Exception('finally');
}
Fatal error: Uncaught Exception: try in /in/2AYmF:4
Stack trace:
#0 {main}

Next Exception: finally in /in/2AYmF:6
Stack trace:
#0 {main}
  thrown in /in/2AYmF on line 6

Process exited with code 255.

https://3v4l.org/2AYmF

But you can't really catch the "first" exception that I'm aware of to do anything fun at runtime:

try {
    try {
        throw new Exception('try');
    } finally {
        throw new Exception('finally');
    }
} catch (Exception $exception) {
    echo 'caught ' . $exception->getMessage();
}

caught finally

https://3v4l.org/Jknpm

* Die

If you exit or die then the finally block will not execute.

try {
    echo "trying";
    die;
} finally {
    echo "finally";
}

echo "end";

trying

https://3v4l.org/pc9oc

† Hardware Failure

Finally, you should understand that the finally block will not execute if someone pulls the power plug on your server and although I haven't tested it, I'd expect memory exhaustion to skip it too.

Jeff Puckett
  • 37,464
  • 17
  • 118
  • 167
  • 1
    I understand it now, but the use-cases seem very rare. In your example for instance, you could still close the database before the `throw` statment, and it would've achived the same thing. – ʎɹnɔɹǝW Jan 13 '17 at 08:54
  • 3
    @mvrht: Of course you could close the DB connection before the `throw`statement _without_ using `finally`, but the point is in that case: Firstly, maybe you don't catch _every_ exception but just a certain type, then you don't reach the `close()` call for other exceptions. Secondly, even if you do catch every exception, then you need a duplicate line of code somewhere (at end of `try`block or after `catch` block) to call `close()`. Putting that call in the `finally` block, you have to write it only once and it's executed in every case (if exception occurs or not, if it's catched or not). – dwytrykus Feb 15 '17 at 10:57
  • One thing that wasn't initially clear to me when looking into this: The `finally` block will execute *regardless of whether the exception was caught*. I was wondering what the difference was between having code in a `finally` block and just having code outside the `try` block. Code outside the `try` block would execute when there is no thrown exception, or if the thrown exception is caught, but *not* if there is an uncaught exception. – Kal Zekdor Apr 28 '18 at 04:34
  • @KalZekdor yes I agree that's right. Feel free to suggest an edit to my answer if you can improve it. Thanks :) – Jeff Puckett Apr 28 '18 at 19:12
  • This is not true. > For example, in .NET if an exception is thrown/re-thrown from the catch block, then the finally block will not execute. – JJS Mar 07 '20 at 23:29
  • @JJS it's been many years since I've written C# so maybe things have changed or I am misremembering. Can you provide a running example/proof somewhere? – Jeff Puckett Mar 27 '20 at 16:46
  • @JeffPuckett. I may have misunderstood what you were getting at. Here's an example of exception is re-thrown from the catch block, and the finally block executing, which contradicts what was said. https://gist.github.com/jeremysimmons/067a4d181c4e6f515b4250d10d7fe53e runnable version https://dotnetfiddle.net/l2Av3T – JJS Mar 27 '20 at 17:23
  • @JJS thank you! I stand corrected. I have deleted the note. I wonder if behavior was different in the past or something? TBH, I probably just got it wrong :p – Jeff Puckett Mar 27 '20 at 23:37
  • 1
    If someone is asking, `finally` executes every time [except when there is](https://www.php.net/manual/en/function.exit.php#117146) an `exit()`/`die()` inside the *try* or the *catch* block (the one which gets executed) – Simo Pelle Jun 11 '20 at 21:17
  • 1
    @Foxel nice insight! I will include this in my answer :) – Jeff Puckett Jun 11 '20 at 21:24
  • 1
    You're driving my OCD nuts with having an Asterisk but not the corresponding one to indicate what the note is.. – James Nov 17 '20 at 17:11
  • 2
    @James we must be cut from the same cloth, because that _also_ drives me bananas wherever I see it, so I intentionally left it off to be a jerk. but I realize now the immaturity of my ways and have added two appropriate references ⛪ – Jeff Puckett Nov 19 '20 at 01:34
  • Closing a database connection after each statement is a VERY bad idea. Database connections should always be long living, because destroying and establishing connections is an expensive task. Transactions OTOH should be very short and always COMMITted or ROLLBACKed. – Holger Jakobs Jan 17 '23 at 21:45
3

Finally should contain any code which needs to be executed regardless of whether there's an exception or not.

Without finally:

try {
   $handle = fopen("file.txt");
   //Do stuff
   fclose($handle);
   return something;
} catch (Exception $e) {
   // Log
   if (isset($handle) && $handle !== false) {
      fclose($handle);
   }     
}

With finally:

try {
   $handle = fopen("file.txt");
   return something;
} catch (Exception $e) {
   // Log
} finally {
   if (isset($handle) && $handle !== false) {
      fclose($handle);
   }     
}

Offers a bit of decluttering in the case that you need to free up a resource after a function has returned.

This becomes even more useful in a case like the following:

 try {
     $handle = fopen("file.txt");
     if (case1) { return result1; }  
     if (case2) { return result2; }
     if (case3) { return result3; }
     if (case4) { return result4; }

 } finally {
     if (isset($handle) && $handle !== false) {
          fclose($handle);
       }    
 }

In this case you can reduce all the required fclose calls before each return to a single fclose call that will be executed right before the method returns but after any other code.

apokryfos
  • 38,771
  • 9
  • 70
  • 114
1
try {
    throw new LogicException("Throw logic \n"); -> LogicException thrown
} catch (InvalidArgumentException $e) { -> LogicException not catched
    echo $e->getMessage(); 
}finally{
    echo hi(); -> code executed. "Hi" printed out
}

LogicException is here -> Fatal error

so in this case:

try {
    throw new LogicException("Throw logic \n"); -> LogicException thrown
} catch (InvalidArgumentException $e) { -> LogicException not catched
    echo $e->getMessage(); 
}finally{
    echo hi(); -> code executed
    die();
}

no fatal error will be raised, because of die statement and the last variation:

try {
    throw new LogicException("Throw logic \n"); -> LogicException thrown
} catch (InvalidArgumentException $e) { -> LogicException not catched
    echo $e->getMessage(); 
} catch (LogicException $e) { -> LogicException catched
    echo $e->getMessage(); 
}finally{
    echo hi(); -> code executed
}
myxaxa
  • 1,391
  • 1
  • 8
  • 7
0

I made a little unit test to show how it is working

    $a = 'a';
    try {
        $a .= 'b';
    } catch (Exception $ex) {
        $a .= 'e';
    } finally {
        $a .= 'f';
    }
    $a .= 'x';

    $this->assertSame('abfx', $a);


    $a = 'a';
    try {
        $a .= 'b';
        throw new Exception();
        $a .= '1';
    } catch (Exception $ex) {
        $a .= 'e';
    } finally {
        $a .= 'f';
    }
    $a .= 'x';

    $this->assertSame('abefx', $a);


    $a = 'a';
    try {
        try {
            $a .= 'b';
            throw new Exception();
            $a .= '1';
        } catch (Exception $ex) {
            $a .= 'e';
            throw $ex;
            $a .= '2';
        } finally {
            $a .= 'f';
        }
        $a .= 'x';
    } catch (Exception $ex) {
        $a .= 'z';
    }

    $this->assertSame('abefz', $a);
Yevgeniy Afanasyev
  • 37,872
  • 26
  • 173
  • 191