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. Наконец, для запуска метода. Целая группа больше работает над сценарием. Глобальный Наконец, наконец, бег.
Закрытие 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
: Исключение будет прерывать стеки вызовов беспрепятственно. Любые другие исключения, запланированные для ретронирования, будут отброшены. $try
будет возвращено. Если кто-то все еще отслеживает этот вопрос, вам может быть интересно проверить (новый) RFC для окончательной языковой функции в PHP-вики. У автора уже есть рабочие исправления, и я уверен, что это предложение принесет пользу от отзывов других разработчиков.
Я только что закончил писать более элегантный класс Try Catch finally, который может вам пригодиться. Есть некоторые недостатки, но их можно обойти.