phpoopexceptiontry-catch-finally

How is the keyword 'finally' meant to be used in PHP?


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:\path\to\filename.php:167
  Stack trace:
      #0 {main}
      thrown in C:\path\to\filename.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(s):

> Hi
> Fatal error: Uncaught LogicException: Throw Logic in C:\path\to\filename.php:167
  Stack trace:
      #0 {main}
      thrown in C:\path\to\filename.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();
}

output(s):

> 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?


Solution

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