检测 __sleep 中的关闭以防止其序列化

发布于 2024-10-30 15:07:08 字数 1574 浏览 0 评论 0原文

当我尝试序列化一个包含闭包成员的对象时,会抛出异常。 为了避免成员的序列化(包括闭包),我尝试了以下操作:

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

When I'm trying to serialize an object which has members including closures an exception is thrown.
To avoid the serialization of the members including closures I tried the following:

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

Unfortunately this does not work. Is there a better way to detect whether a property is a closure or not.

EDIT: I solved my problem by letting the closure know whether to serialize or not

To do this I am wrapping the closure itself. Here's an example:

/**
 * 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);

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

不如归去 2024-11-06 15:07:08

对我来说效果很好:

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() 函数。

Works fine for me:

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

About checking if value is instance of Closure class (from manual):

Anonymous functions are currently
implemented using the Closure class.
This is an implementation detail and
should not be relied upon.

Therefore I would implement is_closure($value) function as return !is_string($value) && !is_array($value) && is_callable($value) rather than return $value instanceof Closure and hope that some day PHP developers will add native is_closure() function.

自控 2024-11-06 15:07:08

老实说,我认为您正在尝试解决错误的问题。如果你在课堂上睡觉,那么如果你不能序列化所有内容,那么成功的睡眠不是错误的吗?否则,您可能会醒来进入不一致的状态(或者至少是与当前状态不同的状态)。所以我认为你应该将所有内容放入结果数组中,然后让 PHP 告诉你它是否不可序列化。

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

简而言之,我建议只返回所有内容,并让 PHP 处理不可序列化的变量。否则,您需要将其列入白名单(这不切实际)或黑名单(这并不完整)。这两者都不是一个很好的解决方案。只要在异常发生时对其进行处理即可(抛出和捕获异常也不错)。

至于您的确切问题,我将按如下方式实现它:

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

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

Honestly, I think you're trying to solve the wrong problem. If you're sleeping the class, then isn't it wrong to have a successful sleep if you can't serialize everything? Otherwise you can wake up to an inconsistent state (or at least a state that's different than the current one). So I would argue that you should just put everything into the resultant array and then let PHP tell you if it's not serializable.

Otherwise, do you then need to check to see if any stored objects are serialzable? Should you then be checking for Serializable interface or the existence of __sleep? Where do you draw the line? So I would say that you should only not serialize resources and variables that you explicitly know how to recreate in the wakeup function (such as a database connection, or any closures you explicitly know how to recreate). But be careful here, since if you let those closures/resources be changed via the object's API, how can you be sure of a successful wakeup to the prior state.

So in short, I would recommend just returning everything, and letting PHP handle unserializable variables. Otherwise you'd need to either white-list (which isn't going to be practical) or black-list (which isn't going to be complete). And neither is a great solution. Just handle the exception when it comes (throwing and catching exceptions isn't bad).

As far as your exact question, I would implement it as follows:

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

It still relies on the implementation detail of the closure being of a Object type, but I think that's the best we can do at this point. The best solution would be to petition the core to add a is_closure() function which would be implementation independent...

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文