обнаружить закрытие в __sleep, чтобы предотвратить их сериализацию - PullRequest
2 голосов
/ 07 апреля 2011

Когда я пытаюсь сериализовать объект, в котором есть члены, включая замыкания, возникает исключение.Чтобы избежать сериализации членов, включая замыкания, я попробовал следующее:

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 ]

3 голосов
/ 07 апреля 2011

Хорошо работает для меня:

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

2 голосов
/ 07 апреля 2011

Честно говоря, я думаю, что вы пытаетесь решить не ту проблему. Если вы спите в классе, разве не плохо спать, если вы не можете все сериализовать? В противном случае вы можете проснуться в несовместимое состояние (или, по крайней мере, в состояние, отличное от текущего). Поэтому я бы сказал, что вы должны просто поместить все в результирующий массив, а затем позволить PHP сообщить вам, если он не сериализуем.

В противном случае, нужно ли проверять, являются ли сохраненные объекты сериализуемыми? Должны ли вы тогда проверять наличие Serializable интерфейса или наличие __sleep? Где вы проводите черту? Поэтому я бы сказал, что вам не следует сериализовать ресурсы и переменные, которые вы явно знаете, как воссоздать в функции пробуждения (например, соединение с базой данных или любые замыкания, которые вы явно знаете, как воссоздать). Но будьте осторожны, поскольку, если вы позволите этим закрытиям / ресурсам изменяться через API объекта, как вы можете быть уверены в успешном пробуждении к предыдущему состоянию.

Короче, я бы порекомендовал просто вернуть все и позволить PHP обрабатывать несериализуемые переменные. В противном случае вам понадобится либо белый список (который не будет практичным), либо черный список (который не будет полным). И ни один не является отличным решением. Просто обработайте исключение, когда оно наступит (бросать и ловить исключения - это неплохо).

Что касается вашего точного вопроса, я бы реализовал его следующим образом:

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

Он по-прежнему опирается на детали реализации замыкания типа Object, но я думаю, что это лучшее, что мы можем сделать на данный момент. Лучшим решением было бы обратиться к ядру с просьбой добавить функцию is_closure(), которая не зависела бы от реализации ...

...