program story

PHP에서 finally 블록의 부족을 어떻게 해결할 수 있습니까?

inputbox 2020. 12. 31. 08:14
반응형

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블록을. 콜 러블을 보유하는 클래스를 만듭니다. destuctor에서 callable (s)을 호출합니다.

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에서 수정 됨) 일부 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에서이 접근 방식의 가장 큰 문제는 최종적으로 폐쇄가 개체의 개인 및 보호 필드에 액세스 할 수 없다는 것입니다. 에 액세스하는 $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공개 액세스를 위해 비공개 필드를 여는 클래스 의 예를 제공합니다 . 여기서 재현하지는 않겠지 만 (이전 두 링크 참조), 사용 방법은 다음과 같습니다.

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. 원하는 경우에만 비- (PHP 코드는에서 파생되지 않은 것을 던질 수 없음) 를 catch try/finally하는 catch블록을 추가 하거나 catch 된 예외를 다시 throw합니다. 전자의 경우 "아무것도 잡지 않는다"는 의미의 관용구로 잡는 것이 좋습니다 . 메소드에서 현재 클래스를 포착하는 것은 "아무것도 포착하지 않음"을 의미 할 수도 있지만 파일을 검색 할 때 사용하는 것이 더 간단하고 찾기 쉽습니다.ExceptionExceptionStdClassStdClass

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
    }
);

You can download the solution with documentation and examples from git hub - https://github.com/Perennials/travelsdk-core-php/tree/master/src/sys


As this is a language construct, you won't find an easy solution for this. You can write a function and call it as the last line of your try block and last line before rethrowing the excepion in the try block.

Good books argues against using finally blocks for any other than freeing resource as you can not be sure it will execute if something nasty happens. Calling it an irritating hole is quite an overstatement. Believe me, a hell lot of exceptionally good code is written in languages without finally block. :)

The point of finally is to execute no matter if the try block was successfull or not.


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;
}

Call using closures. Second parameter, $catch, is optional. Examples:

_try(function ()
{
    // try
}, function ($ex)
{
    // catch ($ex)
}, function ()
{
    // finally
});

_try(function ()
{
    // try
}, function ()
{
    // finally
});

Properly handles exceptions everywhere:

  • $try: Exception will be passed to $catch. $catch will run first, then $finally. If there is no $catch, exception will be rethrown after running $finally.
  • $catch: $finally will execute immediately. Exception will be rethrown after $finally completes.
  • $finally: Exception will break down the call stack unimpeded. Any other exceptions scheduled for rethrow will be discarded.
  • None: Return value from $try will be returned.

If anyone is still keeping track of this question, you might be interested in checking out the (brand new) RFC for a finally language feature in the PHP wiki. The author already seems to have working patches, and I'm sure the proposal would benefit from other developers' feedback.


I just finished writing a more elegant Try Catch Finally class which may be of use to you. There are some drawbacks but they can be worked around.

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

ReferenceURL : https://stackoverflow.com/questions/927214/how-can-i-get-around-the-lack-of-a-finally-block-in-php

반응형