web-dev-qa-db-ja.com

PHPのfinallyブロックがないことをどうやって回避できますか?

バージョン5.5より前のPHPには最終的にブロックがありません。つまり、最も賢明な言語では、次のことができます。

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

PHPには、finallyブロックの概念はありません。

言語のこのいらいらさせる穴に対する解決策の経験がある人はいますか?

55
Kazar

解決策はありません。イライラする厄介な回避策、はい:

$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で利用可能です...)

59

[〜#〜] 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で修正)にアクセスできないため、いくつかの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
}
9
outis

これが最終的にブロックされないことに対する私の解決策です。これは、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

2
bobef
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からの戻り値が返されます。
1
Zenexer

これは言語構造なので、簡単な解決策は見つかりません。関数を記述して、tryブロックの例外を再スローする前に、tryブロックの最後の行および最後の行として呼び出すことができます。

良い本では、リソースの解放以外の目的でfinallyブロックを使用しないように反対しています。それをいらいらさせる穴と呼ぶことは、かなり過大な表現です。私を信じて、非常に多くの非常に優れたコードが、最終的にブロックされない言語で書かれています。 :)

最終的にのポイントは、tryブロックが成功したかどうかに関係なく実行することです。

1
Csaba Kétszeri

まだこの質問を追跡している人がいる場合は、PHP wikiの(真新しい) RFCで最終言語機能 を確認してみてください。 。著者はすでに実用的なパッチを持っているようで、提案は他の開発者のフィードバックから利益を得ると確信しています。

0
Tobias Gies

私はあなたに役立つかもしれないよりエレガントなトライ・キャッチ・ファイナル・クラスを書き終えました。いくつかの欠点がありますが、回避できます。

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

0
Ozzy