/usr/share/php/SuperClosure/SerializableClosure.php is in php-superclosure 2.2.0-1build1.
This file is owned by root:root, with mode 0o644.
The actual contents of the file can be viewed below.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 | <?php namespace SuperClosure;
use Closure;
use SuperClosure\Exception\ClosureUnserializationException;
/**
* This class acts as a wrapper for a closure, and allows it to be serialized.
*
* With the combined power of the Reflection API, code parsing, and the infamous
* `eval()` function, you can serialize a closure, unserialize it somewhere
* else (even a different PHP process), and execute it.
*/
class SerializableClosure implements \Serializable
{
/**
* The closure being wrapped for serialization.
*
* @var Closure
*/
private $closure;
/**
* The serializer doing the serialization work.
*
* @var SerializerInterface
*/
private $serializer;
/**
* The data from unserialization.
*
* @var array
*/
private $data;
/**
* Create a new serializable closure instance.
*
* @param Closure $closure
* @param SerializerInterface|null $serializer
*/
public function __construct(
\Closure $closure,
SerializerInterface $serializer = null
) {
$this->closure = $closure;
$this->serializer = $serializer ?: new Serializer;
}
/**
* Return the original closure object.
*
* @return Closure
*/
public function getClosure()
{
return $this->closure;
}
/**
* Delegates the closure invocation to the actual closure object.
*
* Important Notes:
*
* - `ReflectionFunction::invokeArgs()` should not be used here, because it
* does not work with closure bindings.
* - Args passed-by-reference lose their references when proxied through
* `__invoke()`. This is an unfortunate, but understandable, limitation
* of PHP that will probably never change.
*
* @return mixed
*/
public function __invoke()
{
return call_user_func_array($this->closure, func_get_args());
}
/**
* Clones the SerializableClosure with a new bound object and class scope.
*
* The method is essentially a wrapped proxy to the Closure::bindTo method.
*
* @param mixed $newthis The object to which the closure should be bound,
* or NULL for the closure to be unbound.
* @param mixed $newscope The class scope to which the closure is to be
* associated, or 'static' to keep the current one.
* If an object is given, the type of the object will
* be used instead. This determines the visibility of
* protected and private methods of the bound object.
*
* @return SerializableClosure
* @link http://www.php.net/manual/en/closure.bindto.php
*/
public function bindTo($newthis, $newscope = 'static')
{
return new self(
$this->closure->bindTo($newthis, $newscope),
$this->serializer
);
}
/**
* Serializes the code, context, and binding of the closure.
*
* @return string|null
* @link http://php.net/manual/en/serializable.serialize.php
*/
public function serialize()
{
try {
$this->data = $this->data ?: $this->serializer->getData($this->closure, true);
return serialize($this->data);
} catch (\Exception $e) {
trigger_error(
'Serialization of closure failed: ' . $e->getMessage(),
E_USER_NOTICE
);
// Note: The serialize() method of Serializable must return a string
// or null and cannot throw exceptions.
return null;
}
}
/**
* Unserializes the closure.
*
* Unserializes the closure's data and recreates the closure using a
* simulation of its original context. The used variables (context) are
* extracted into a fresh scope prior to redefining the closure. The
* closure is also rebound to its former object and scope.
*
* @param string $serialized
*
* @throws ClosureUnserializationException
* @link http://php.net/manual/en/serializable.unserialize.php
*/
public function unserialize($serialized)
{
// Unserialize the closure data and reconstruct the closure object.
$this->data = unserialize($serialized);
$this->closure = __reconstruct_closure($this->data);
// Throw an exception if the closure could not be reconstructed.
if (!$this->closure instanceof Closure) {
throw new ClosureUnserializationException(
'The closure is corrupted and cannot be unserialized.'
);
}
// Rebind the closure to its former binding and scope.
if ($this->data['binding'] || $this->data['isStatic']) {
$this->closure = $this->closure->bindTo(
$this->data['binding'],
$this->data['scope']
);
}
}
/**
* Returns closure data for `var_dump()`.
*
* @return array
*/
public function __debugInfo()
{
return $this->data ?: $this->serializer->getData($this->closure, true);
}
}
/**
* Reconstruct a closure.
*
* HERE BE DRAGONS!
*
* The infamous `eval()` is used in this method, along with the error
* suppression operator, and variable variables (i.e., double dollar signs) to
* perform the unserialization logic. I'm sorry, world!
*
* This is also done inside a plain function instead of a method so that the
* binding and scope of the closure are null.
*
* @param array $__data Unserialized closure data.
*
* @return Closure|null
* @internal
*/
function __reconstruct_closure(array $__data)
{
// Simulate the original context the closure was created in.
foreach ($__data['context'] as $__var_name => &$__value) {
if ($__value instanceof SerializableClosure) {
// Unbox any SerializableClosures in the context.
$__value = $__value->getClosure();
} elseif ($__value === Serializer::RECURSION) {
// Track recursive references (there should only be one).
$__recursive_reference = $__var_name;
}
// Import the variable into this scope.
${$__var_name} = $__value;
}
// Evaluate the code to recreate the closure.
try {
if (isset($__recursive_reference)) {
// Special handling for recursive closures.
@eval("\${$__recursive_reference} = {$__data['code']};");
$__closure = ${$__recursive_reference};
} else {
@eval("\$__closure = {$__data['code']};");
}
} catch (\ParseError $e) {
// Discard the parse error.
}
return isset($__closure) ? $__closure : null;
}
|