检测__sleep中的闭包以防止它们的序列化

时间:2011-04-07 11:13:56

标签: php serialization closures

当我尝试序列化包含闭包的成员的对象时,会抛出异常。 为了避免包括闭包在内的成员的序列化,我尝试了以下方法:

function __sleep(){
    $ref = new ReflectionClass($this);
    $props = $ref->getProperties();

    foreach ($props as $prop){
        $name = $prop->name;
        if (is_callable($this->$name)===false){
            $dream[] = $prop->name;
        }
    }
    return $dream;
}

不幸的是,这不起作用。是否有更好的方法来检测属性是否为闭包。

编辑:我通过让关闭知道是否序列化来解决我的问题

要做到这一点,我正在封闭封闭本身。这是一个例子:

/**
 * Wrapper-class to prevent closure to be serialized.
 */
class WrappedClosure {

    private $closure = NULL;
    protected $reflection = NULL;

    public function __construct($function){
        if ( ! $function instanceOf Closure)
            throw new InvalidArgumentException();
        $this->closure = $function;
        $this->reflection = new ReflectionFunction($function);
    }

    /**
     * When the instance is invoked, redirect invocation to closure.
     */
    public function __invoke(){
        $args = func_get_args();
        return $this->reflection->invokeArgs($args);
    }

    // do nothing on serialization
    public function __sleep(){}

    // do nothing on serialization
    public function __wakeup(){}
}

// Assigning a wrapped closure to a member
$myObject->memberHoldingAClosure = 
    // Wrapping the closure 
    new WrappedClosure(
        function (){
            echo "I'am the inner closure.";
        }
    )
);

// the serialization doesn't throw an exception anymore
serialize($myObject);

2 个答案:

答案 0 :(得分:3)

对我来说很好:

class foo {
    protected $param = 'value';
    protected $closure = null;
    public function __construct() {
        $this->closure = function(){
            return 123;
        };
    }
    public function __sleep() {
        $serializable = array();
        foreach ( $this as $paramName => $paramValue ) {
            if ( !is_string($paramValue) && !is_array($paramValue) && is_callable($paramValue) ) {
                continue;
            }
            $serializable[] = $paramName;
        }
        return $serializable;
    }
}
$foo = new foo();
echo serialize($foo);

关于检查值是Closure类的实例(来自手册):

  

目前是匿名函数   使用Closure类实现。   这是一个实现细节和   不应该依赖

因此,我会将is_closure($value)函数实现为return !is_string($value) && !is_array($value) && is_callable($value)而不是return $value instanceof Closure,并希望有一天PHP开发人员会添加原生is_closure()函数。

答案 1 :(得分:2)

老实说,我认为你正试图解决错误的问题。如果你正在上课,那么如果你不能序列化一切,那么成功睡眠是不是错了?否则,您可以唤醒到一个不一致的状态(或者至少是一个与当前状态不同的状态)。所以我认为你应该把所有东西放到结果数组中然后让PHP告诉你它是否不可序列化。

否则,您是否需要检查是否有任何存储的对象是可串行的?那么你应该检查Serializable接口还是存在__sleep?你在哪里划线?所以我要说你不应该序列化你明确知道如何在唤醒函数中重新创建的资源和变量(例如数据库连接,或者你明确知道如何重新创建的任何闭包)。但是请注意,因为如果你通过对象的API改变那些闭包/资源,你怎么能确定成功唤醒到先前的状态。

简而言之,我建议只返回所有内容,并让PHP处理不可序列化的变量。否则你需要白名单(不可行)或黑名单(不完整)。这两个都不是一个好方案。只需处理异常(抛出和捕获异常并不错)。

就你的确切问题而言,我会按如下方式实施:

function is_closure($callback) {
    $func = function(){};
    return $callback instanceof $func;
}

它仍然依赖于闭包属于Object类型的实现细节,但我认为这是我们现在能做的最好的事情。最好的解决方案是请求核心添加is_closure()函数,该函数将独立于实现......