バージョン5.5より前のPHPには最終的にブロックがありません。つまり、最も賢明な言語では、次のことができます。
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最終的に(ahem、申し訳ありませんが)最終的にブロックを追加しました: https:/ /wiki.php.net/rfc/finally (そして、わずか数年しかかかりませんでした...この回答を投稿してから現在までほぼ4年で、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で修正)にアクセスできないため、いくつかのfinal-blocks内のインスタンスメンバーにアクセスするには、追加の変数が必要になります。
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がオブジェクトのプライベートフィールドと保護フィールドにアクセスできないことです。$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 anonymous functions のマニュアルページのコメント)で、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
が必要です。 try/finally
のみが必要な場合は、catch
以外のブロックをキャッチするException
ブロックを追加する(PHPコードはException
から派生したものをスローできない)またはre -キャッチされた例外をスローします。前者の場合、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ハブからダウンロードできます- 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
からの戻り値が返されます。これは言語構造なので、簡単な解決策は見つかりません。関数を記述して、tryブロックの例外を再スローする前に、tryブロックの最後の行および最後の行として呼び出すことができます。
良い本では、リソースの解放以外の目的でfinallyブロックを使用しないように反対しています。それをいらいらさせる穴と呼ぶことは、かなり過大な表現です。私を信じて、非常に多くの非常に優れたコードが、最終的にブロックされない言語で書かれています。 :)
最終的にのポイントは、tryブロックが成功したかどうかに関係なく実行することです。
まだこの質問を追跡している人がいる場合は、PHP wikiの(真新しい) RFCで最終言語機能 を確認してみてください。 。著者はすでに実用的なパッチを持っているようで、提案は他の開発者のフィードバックから利益を得ると確信しています。
私はあなたに役立つかもしれないよりエレガントなトライ・キャッチ・ファイナル・クラスを書き終えました。いくつかの欠点がありますが、回避できます。