Intereting Posts

Как я могу обойти отсутствие блока finally в PHP?

PHP до версии 5.5 не имеет окончательного блока – то есть, в то время как на самых разумных языках вы можете:

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

PHP не имеет понятия о блоке finally.

У кого-нибудь есть опыт решения этой довольно раздражающей дыры в языке?

Решение, №. Раздражающее громоздкое обходное решение, да:

 $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, но должен работать.

Обратите внимание : PHP 5.5 наконец (гм, извините) добавил блок finally: https://wiki.php.net/rfc/finally (и это заняло всего несколько лет … доступно в 5.5 RC почти четыре года для дата с тех пор, как я опубликовал этот ответ …)

Идиома RAII предлагает уровень кода для блока finally . Создайте класс, который содержит вызываемые вызовы. В destuctor вызовите вызываемый (ы).

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

координация

Обратите внимание, что PHP не имеет области блока для переменных, поэтому, Finally , не будет выполняться до тех пор, пока функция не выйдет или (в глобальной области) последовательность выключения. Например, следующее:

 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"; 

приведет к выводу:

 Создание глобального Наконец.
 Foo :: useTry done.
 Наконец, для запуска метода.
 Целая группа больше работает над сценарием.
 Глобальный Наконец, наконец, бег.

$ this-

Закрытие PHP 5.3 не может получить доступ к $this (зафиксировано в 5.4), поэтому вам понадобится дополнительная переменная для доступа к членам экземпляра в некоторых окончательных блоках.

 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() {/*...*/} } 

Частные и защищенные поля

Вероятно, самая большая проблема с этим подходом в PHP 5.3 – это, наконец, закрытие не может получить доступ к закрытым и защищенным полям объекта. Подобно доступу к $this , эта проблема разрешена в PHP 5.4. Пока что частные и защищенные свойства можно получить с помощью ссылок, как показывает Artefacto в ответе на вопрос по этой теме в другом месте на этом сайте.

 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"; 

Частные и защищенные методы могут быть доступны с помощью отражения. Вы можете использовать тот же метод для доступа к непубличным свойствам, но ссылки более простые и легкие. В комментарии на странице руководства PHP для анонимных функций Мартин Пардель приводит пример класса FullAccessWrapper который открывает открытые поля для открытого доступа. Я не буду воспроизводить его здесь (см. Две предыдущие ссылки для этого), но вот как вы его используете:

 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 блоки требуют по крайней мере одного catch . Если вам нужен только try/finally , добавьте блок catch который поймает Exception (код PHP не может выбросить что-либо, не полученное из Exception ), или повторно выбросить исключенное пойманное исключение. В первом случае я предлагаю поймать StdClass как идиому, означающую «ничего не поймать». В методах уловка текущего класса также может использоваться для обозначения «ничего не поймать», но использование StdClass проще и легче найти при поиске файлов.

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

Вот мое решение проблемы отсутствия блока. Он не только обеспечивает работу для блока finally, но и расширяет возможности try / catch, чтобы уловить ошибки PHP (и фатальные ошибки тоже). Мое решение выглядит так (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 } ); 

Вы можете загрузить решение с документацией и примерами из git hub – https://github.com/Perennials/travelsdk-core-php/tree/master/src/sys

Поскольку это языковая конструкция, вы не найдете для этого простого решения. Вы можете написать функцию и называть ее последней строкой блока try и последней строкой, прежде чем реконструировать excepion в блоке try.

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

Точка, наконец, должна выполняться независимо от того, был ли блок try успешным или нет.

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

Позвоните с помощью закрытий. Второй параметр $catch является обязательным. Примеры:

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

Правильно обрабатывает исключения везде:

  • $try : Исключение будет передано в $catch . Сначала будет запускаться $catch , а затем $finally . Если нет $catch , исключение будет восстановлено после запуска $finally .
  • $catch : $finally будет выполняться немедленно. Исключение будет восстановлено после завершения $finally .
  • $finally : Исключение будет прерывать стеки вызовов беспрепятственно. Любые другие исключения, запланированные для ретронирования, будут отброшены.
  • None : Возвращаемое значение из $try будет возвращено.

Если кто-то все еще отслеживает этот вопрос, вам может быть интересно проверить (новый) RFC для окончательной языковой функции в PHP-вики. У автора уже есть рабочие исправления, и я уверен, что это предложение принесет пользу от отзывов других разработчиков.

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

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