opis / closure

Serialize closures (anonymous functions)
https://opis.io/closure
MIT License
2.5k stars 81 forks source link

Should closures with `$this` keyword be wrapped in an additional closure? #117

Open nmeri17 opened 2 years ago

nmeri17 commented 2 years ago

The examples in the docs referring to $this all use classes, but I just want to serialize a closure, not the entire class. I'm passing the following closure to the serializer

function () use ($outerVar) {

    return $outerVar . "/" . $this->myProperty;
}

And it throws the error "PDOException: You cannot serialize or unserialize PDO instances". I don't have a PDO instance anywhere close to where this closure is sent down to serializer. I realized other closures work without the $this keyword, combined with clues from reading other issues, I wrapped closure in additional closure

function () use ($outerVar) {

    return function () use ($outerVar) {

        return $outerVar . "/" . $this->myProperty;
    };
}

And error disappeared during serialization, but issue still stands. See next paragraph. I'm able to serialize and unserialize, but this doesn't seem to be intended use since I can't bind $this to context where closure is unserialized at. When I unserialize like so:

public function evaluateClosure (callable $receivedClosure) {
    $callable = unserialize($receivedClosure)->getClosure();

    $callable->bindTo($this, $this); // so dev can have access to `myProperty`
    // also tried ->bindTo(null)

    var_dump($callable()); // returns the outer closure

Then, going further to call $callable()() results in "Error: Using $this when not in object context". Is this a serialization problem or a binding one?

nmeri17 commented 2 years ago

Update: My bad. I just realised bindTo doesn't modify original closure but returns a new instance. I should have assigned the result of $callable->bindTo($this, $this);

You can close issue after confirming closure should be doubly wrapped

sorinsarca commented 2 years ago

If your $this is not serializable then it has nothing to do with closure serialization. To avoid $this serialization consider making the closure static and possibly refactor the code. You may may need to wrap the closure if you want to have an unserializable $this and still serialize the closure

$wrapper = static function ($self) use ($outerVar) {
  return static function() use ($self, $outerVar) {
    return $outerVar . "./" . $self->myProperty;
  };
};
public function evaluateClosure (callable $receivedClosure) {
    $callable = unserialize($receivedClosure)->getClosure();

    return $callable($this); // set $self to $this in inner closure and return it
}

Anyway, this may work for simple cases, but wrapping a lot of closures makes the code hard to follow. The best option is to make $this serializable, there are lot of libs you can use instead of raw PDO objects.