Try-Catch-Finally in PHP

2012/08/29 11:16
阅读数 1K

As of PHP 5.3 web developers can use a wonderful feature of the language, the __invoke() magic method. This enables PHP to provide the Closure class and based on it the ability to define anonymous functions. Anonymous function is a powerful language construct, it can come handy in a number of ways. For example, it makes the code easier to manage by letting you define locally visible small functions inside a method or a function. This is useful when you want to sort an array by some special property of the elements in it – you don’t have to define a named function or method in your global scope or in a class, you can just hide the comparison callback inside the method that needs a specially sorted array. But anonymous functions combined with other language constructs, can even be used to emulate some features missing from PHP: finally for instance.

Some say the need for a finally keyword is the result of bad design. Most people would use finally in a similar situation like this to avoid code duplication in the catch block and after the try-catch statement:

class A
{
    public function doSomething()
    {
        $my_resource = open_my_resource();
        try
        {
            $result = use_my_resource($my_resource);
        }
        catch (Exception $e)
        {
            free_my_resource($my_resource);
            throw $e;
        }
        free_my_resource($my_resource);
        return $result;
    }
}

Using finally the above code snippet would look like this:

class A
{
    public function doSomething()
    {
        $my_resource = open_my_resource();
        try
        {
            $result = use_my_resource($my_resource);
        }
        finally
        {
            free_my_resource($my_resource);
        }
        return $result;
    }
}

In this situation rethinking the design can help avoid the need for finally:

class MyResource
{
    public function __construct()
    {
        $this->resource = open_my_resource();
    }

    public function __destruct()
    {
        free_my_resource($this->resource);
    }

    public function use()
    {
        return use_my_resource($this->resource);
    }

    private $resource;
}

class A
{
    public function doSomething()
    {
        $my_resource = new MyResource();
        $result = $my_resource->use();
        return $result;
    }
}


But sometimes life’s not that easy. Sometimes your hands are bound, sometimes it’s not that trivial to circumvent the need for finally. That is the case when a nice hack based on closures can help:

class FinallyEmulator
{
    public function __construct($callable)
    {
        if (!is_callable($callable))
            throw new ErrorException('Ooops, bad callable.');
        $this->callable = $callable;
    }

    public function __destruct()
    {
        $this->invoke();
    }

    public function __invoke()
    {
        $this->invoke();
    }

    private function invoke()
    {
        if ($this->callable)
        {
            $callable = $this->callable;
            $this->callable = NULL;
            call_user_func($callable);
        }
    }

    private $callable;
}/**
 * Note that return value of $callable is totally ignored.
 */
function finally($callable)
{
    return new FinallyEmulator($callable);
} 

Using that class you can transform the second code snippet in this blog post like this:

 

class A
{
    public function doSomething()
    {
        $my_resource = open_my_resource();
        $finally = finally(function () use ($my_resource)
        {
            free_my_resource($my_resource);
        });
        try
        {
            $result = use_my_resource($my_resource);
        }
        catch (Exception $e)
        {
            fprintf(STDERR, "Error: %s\n", $e->getMessage());
            throw $e;
        }
        $finally();
        return $result;
    }
}

Note that this workaround does not handle the return-inside-finally problem at all as you cannot return from the function from the simulated finally block

 

 

展开阅读全文
加载中
点击引领话题📣 发布并加入讨论🔥
0 评论
0 收藏
0
分享
返回顶部
顶部