开发者

detect closure in __sleep to prevent their serialization

开发者 https://www.devze.com 2023-02-22 13:48 出处:网络
When I\'m trying to serialize an object which has members including closures an exception is thrown.

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 f开发者_StackOverflowollowing:

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


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.


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...

0

精彩评论

暂无评论...
验证码 换一张
取 消

关注公众号