バージョン5.5より前のPHPには、finallyブロックがありません。つまり、ほとんどの賢明な言語では、次のことができます。
try {
//do something
} catch(Exception ex) {
//handle an error
} finally {
//clean up after yourself
}
PHPにはfinallyブロックの概念はありません。
言語のこのかなり苛立たしい穴の解決策の経験がある人はいますか?
バージョン5.5より前のPHPには、finallyブロックがありません。つまり、ほとんどの賢明な言語では、次のことができます。
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);
}
うんざりですが、うまくいくはずです。
注意してください: PHP 5.5 は最終的に (エヘム、申し訳ありません)、finally ブロックを追加しました: https://wiki.php.net/rfc/finally (そして、ほんの数年しかかかりませんでした... 5.5 RC で利用できるようになるまで、ほぼ 4 年かかりましたこの回答を投稿してからの日付...)
RAIIイディオムは、finally
ブロックのコード レベルの代用を提供します。callable を保持するクラスを作成します。デスタクタで、callable を呼び出します。
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::use 試してみました。 最後にメソッドを実行します。 スクリプトによって行われたさらに多くの作業。 グローバル 最後に実行します。
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-closure がオブジェクトの private および protected フィールドにアクセスできないことです。へのアクセスと同様$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 マニュアル ページのコメントで、Martin Partel はFullAccessWrapper
非パブリック フィールドをパブリック アクセスに開放するクラスの例を挙げています。ここでは再現しませんが (前の 2 つのリンクを参照してください)、使用方法は次のとおりです。
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
ブロックには少なくとも 1 つの が必要ですcatch
。のみが必要な場合は、非(PHP コードは から派生したもの以外はスローできない)をキャッチするブロックをtry/finally
追加するか、キャッチした例外を再スローします。前者の場合、 「何も捕まえない」という意味の熟語としてのキャッチングをお勧めします。メソッドでは、現在のクラスをキャッチすることは「何もキャッチしない」ことを意味するためにも使用できますが、ファイルを検索するときに使用する方が簡単で見つけやすいです。catch
Exception
Exception
StdClass
StdClass
try {
$finally = new Finally(/*...*/);
/* ... */
} catch (StdClass $exc) {}
try {
$finally = new Finally(/*...*/);
/* ... */
} catch (RuntimeError $exc) {
throw $exc
}
これが、finally ブロックの欠如に対する私の解決策です。これは、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 ハブからダウンロードできます - https://github.com/Perennials/travelsdk-core-php/tree/master/src/sys
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;
}
クロージャを使用して呼び出します。2 番目のパラメーター$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
が返されます。誰かがまだこの質問を追跡している場合は、PHP wikiの最終的な言語機能の(真新しい)RFCをチェックすることに興味があるかもしれません。作者はすでに動作するパッチを持っているようで、提案は他の開発者のフィードバックから利益を得ると確信しています。
あなたに役立つかもしれない、よりエレガントな Try Catch finally クラスを書き終えました。いくつかの欠点がありますが、回避することができます。