Как ключевое слово «наконец» должно использоваться в PHP?

Итак, сегодня я прочитал об исключениях в онлайн-руководстве по PHP и понял, что мне еще предстоит понять цель или реальную необходимость ключевого слова finally. Я прочитал несколько сообщений здесь, поэтому мой вопрос немного отличается.

Я понимаю, что мы можем использовать finally таким образом:

function hi(){
    return 'Hi';
}


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

echo 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

Итак, в данном случае функция hi(); не выполняется и по уважительной причине. Я понимаю, что если исключение не обрабатывается, интерпретатор php останавливает скрипт. хорошо. Пока что, насколько я читал, наконец-то позволяет нам выполнить функцию hi(); даже если исключение не обрабатывается (хотя я не знаю, почему)

Так вот этот я понимаю.

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

выход:

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

Это должно быть ошибка исключения, а также сообщение «привет» от функции, даже если я не знаю, как это использовать. Но чего я не понимаю, даже если мы перехватим LogicException с помощью catch (LogicException $e) и не будет выдано никаких исключений, мы все равно увидим, что функция выполняется, и мы увидим сообщение «привет». как в этом примере

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

выходы

// Throw logic 
// Hi

Таким образом, мы по-прежнему видим выполнение функции hi(), хотя у нас нет исключений Uncaught. Почему и какая польза от этого? Я думал, что блок finally должен использоваться в качестве последнего средства на случай, если исключения не будут перехвачены, даже если это не так, то зачем его запускать?


person ʎɹnɔɹǝW    schedule 12.01.2017    source источник
comment
Какую версию PHP вы используете? Настоящая функциональность finally не была добавлена ​​до версии PHP 5.5.   -  person Alec Gordon    schedule 12.01.2017
comment
@AlecGordon php 7.0.1   -  person ʎɹnɔɹǝW    schedule 12.01.2017
comment
О, тогда это странно. В соответствии с этим анализом блок finally должен быть напечатан перед необработанным исключение.   -  person Alec Gordon    schedule 12.01.2017
comment
Код в finally выполняется после всего кода в try + catch.   -  person German Lashevich    schedule 12.01.2017


Ответы (4)


finally выполняется каждые* раз

Независимо от ошибок, исключений или даже операторов return блок кода finally будет выполняться.

*Он не запускается, если блоки try или catch выполняют die/exit.

Исключение

Одно из распространенных применений, которое я вижу, — это закрытие соединения с базой данных в долго работающем воркере — вы хотите, чтобы это происходило каждый раз (с исключением или без него), чтобы вы не получили висячее соединение, которое блокирует сервер базы данных от принятия новых соединений. .

Рассмотрим этот псевдокод:

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

Здесь мы всегда будем закрывать соединение с базой данных. Если это обычный запрос, мы закрываем соединение после успеха, и скрипт продолжает выполняться.

Если это ошибочный запрос, то мы все равно закроемся после того, как возникнет исключение, а неперехваченное исключение приведет к остановке скрипта.

Вот пример, когда catch ведет журнал.

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

Это заставит его закрыть соединение с исключением или без него.

Вернуть

Одним из наиболее неясных свойств является его способность выполнять код после оператора return.

Здесь вы можете установить переменную после возврата функции:

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

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

наконец

но присваивание будет тем, что будет возвращено в попытке:

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

Попытка

и возврат в finally отменяет возврат в попытке:

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

echo baz();

наконец

обратите внимание, это поведение было другим в php 5:

наконец-то наконец
наконец-то наконец
наконец-то

https://3v4l.org/biO4e

Исключительное возвращение

Вы можете сделать так, чтобы это выглядело как одновременное появление двух исключений:

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

Но вы не можете поймать первое известное мне исключение, чтобы сделать что-нибудь интересное во время выполнения:

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

наконец-то пойман

https://3v4l.org/Jknpm

* Умереть

Если вы exit или die, то блок finally не будет выполняться.

try {
    echo "trying";

    die;
} catch(Exception $e) {
    echo "caught";
} finally {
    echo "finally";
}

echo "end";

пытающийся

https://3v4l.org/pc9oc

† Аппаратный сбой

Наконец, вы должны понимать, что блок finally не будет выполняться, если кто-то вытащит вилку из розетки на вашем сервере ????, и хотя я не проверял его, я ожидаю, что исчерпание памяти также пропустит его.

person Jeff Puckett    schedule 12.01.2017
comment
Теперь я это понимаю, но варианты использования кажутся очень редкими. Например, в вашем примере вы все еще можете закрыть базу данных перед статусом throw, и это приведет к тому же результату. - person ʎɹnɔɹǝW; 13.01.2017
comment
@mvrht: Конечно, вы могли бы закрыть соединение с БД перед throwstatement без использования finally, но суть в том, что в этом случае: во-первых, возможно, вы не перехватываете каждое исключение но только определенного типа, то вы не дойдете до вызова close() для других исключений. Во-вторых, даже если вы поймаете каждое исключение, вам понадобится дублирующая строка кода где-то (в конце tryблока или после catch блока) для вызова close(). Поместив этот вызов в блок finally, вы должны написать его только один раз, и он выполняется в каждом случае (если возникает исключение или нет, если оно перехвачено или нет). - person dwytrykus; 15.02.2017
comment
Одна вещь, которая изначально не была мне ясна при изучении этого: блок finally будет выполняться независимо от того, было ли перехвачено исключение. Мне было интересно, в чем разница между кодом в блоке finally и кодом вне блока try. Код за пределами блока try будет выполняться, если нет сгенерированного исключения или если сгенерированное исключение перехвачено, но нет, если есть неперехваченное исключение. - person Kal Zekdor; 28.04.2018
comment
@KalZekdor да, я согласен, это правильно. Не стесняйтесь предлагать редактирование моего ответа, если вы можете его улучшить. Спасибо :) - person Jeff Puckett; 28.04.2018
comment
Это неправда. › Например, в .NET, если из блока catch выбрасывается/повторно выбрасывается исключение, блок finally не будет выполняться. - person JJS; 08.03.2020
comment
@JJS Прошло много лет с тех пор, как я написал C #, так что, возможно, что-то изменилось или я неправильно помню. Можете ли вы где-нибудь привести работающий пример/доказательство? - person Jeff Puckett; 27.03.2020
comment
@ДжеффПакетт. Я, возможно, неправильно понял, к чему вы клоните. Вот пример повторного выброса исключения из блока catch и выполнения блока finally, что противоречит сказанному. gist.github.com/jeremysimmons/067a4d181c4e6f515b4250d10d7fe53e исполняемая версия dotnetfiddle.net/l2Av3T" rel="nofollow noreferrer">dotnetfiddle.net/l2Av3T - person JJS; 27.03.2020
comment
@JJS спасибо! Я исправляюсь. Я удалил заметку. Интересно, раньше поведение было другим или что-то в этом роде? ТБХ, я, наверное, просто ошибся :p - person Jeff Puckett; 28.03.2020
comment
Если кто-то спрашивает, finally выполняется каждый раз, за исключением случаев exit()/die() внутри блока try или блока catch (того, который выполняется) - person Simo Pelle; 12.06.2020
comment
@Foxel хорошее понимание! Я включу это в свой ответ :) - person Jeff Puckett; 12.06.2020
comment
Вы сводите меня с ума от ОКР тем, что у вас есть звездочка, но нет соответствующей, чтобы указать, что это за заметка. - person James; 17.11.2020
comment
@Джеймс ???? мы должны быть сделаны из одной ткани, потому что это тоже сводит меня с ума везде, где я это вижу, поэтому я намеренно оставил это как придурок. но теперь я осознаю незрелость своего пути и добавил две подходящие ссылки ⛪ - person Jeff Puckett; 19.11.2020

Наконец, должен содержать любой код, который необходимо выполнить независимо от того, есть исключение или нет.

Без окончательно:

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

С наконец:

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

Предлагает немного расхламления в случае, если вам нужно освободить ресурс после возврата функции.

Это становится еще более полезным в случае, подобном следующему:

 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);
       }    
 }

В этом случае вы можете сократить все необходимые вызовы fclose перед каждым возвратом до одного вызова fclose, который будет выполняться непосредственно перед возвратом метода, но после любого другого кода.

person apokryfos    schedule 12.01.2017

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

так что в этом случае:

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

никакой фатальной ошибки не возникнет из-за оператора die и последней вариации:

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
}
person myxaxa    schedule 12.01.2017

Я сделал небольшой модульный тест, чтобы показать, как он работает.

    $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);
person Yevgeniy Afanasyev    schedule 17.03.2021