Идиома RAII предлагает кодовый уровень для блока finally
. Создайте класс, который содержит вызываемые (ие) элементы. В деструкторе вызовите вызываемого (-ых).
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";
приведет к выводу:
Creating global Finally.
Foo::useTry done.
Finally for method run.
A whole bunch more work done by the script.
Global Finally finally run.
$ это
Закрытия PHP 5.3 не могут получить доступ к $this
(исправлено в 5.4), поэтому вам понадобится дополнительная переменная для доступа к элементам экземпляра внутри некоторых блоков finally.
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 заключается в том, что finally-замыкание не может получить доступ к закрытым и защищенным полям объекта. Как и доступ к $this
, эта проблема решена в PHP 5.4. Пока к частным и защищенным свойствам можно получить доступ, используя ссылки, как Артефакто показывает в своем ответе на вопрос по этой самой теме в другом месте на этом сайте.
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
}