515 lines
14 KiB
PHP
515 lines
14 KiB
PHP
<?php
|
|
|
|
namespace Laravel\SerializableClosure\Serializers;
|
|
|
|
use Closure;
|
|
use DateTimeInterface;
|
|
use Laravel\SerializableClosure\Contracts\Serializable;
|
|
use Laravel\SerializableClosure\SerializableClosure;
|
|
use Laravel\SerializableClosure\Support\ClosureScope;
|
|
use Laravel\SerializableClosure\Support\ClosureStream;
|
|
use Laravel\SerializableClosure\Support\ReflectionClosure;
|
|
use Laravel\SerializableClosure\Support\SelfReference;
|
|
use Laravel\SerializableClosure\UnsignedSerializableClosure;
|
|
use ReflectionObject;
|
|
use UnitEnum;
|
|
|
|
class Native implements Serializable
|
|
{
|
|
/**
|
|
* Transform the use variables before serialization.
|
|
*
|
|
* @var \Closure|null
|
|
*/
|
|
public static $transformUseVariables;
|
|
|
|
/**
|
|
* Resolve the use variables after unserialization.
|
|
*
|
|
* @var \Closure|null
|
|
*/
|
|
public static $resolveUseVariables;
|
|
|
|
/**
|
|
* The closure to be serialized/unserialized.
|
|
*
|
|
* @var \Closure
|
|
*/
|
|
protected $closure;
|
|
|
|
/**
|
|
* The closure's reflection.
|
|
*
|
|
* @var \Laravel\SerializableClosure\Support\ReflectionClosure|null
|
|
*/
|
|
protected $reflector;
|
|
|
|
/**
|
|
* The closure's code.
|
|
*
|
|
* @var array|null
|
|
*/
|
|
protected $code;
|
|
|
|
/**
|
|
* The closure's reference.
|
|
*
|
|
* @var string
|
|
*/
|
|
protected $reference;
|
|
|
|
/**
|
|
* The closure's scope.
|
|
*
|
|
* @var \Laravel\SerializableClosure\Support\ClosureScope|null
|
|
*/
|
|
protected $scope;
|
|
|
|
/**
|
|
* The "key" that marks an array as recursive.
|
|
*/
|
|
const ARRAY_RECURSIVE_KEY = 'LARAVEL_SERIALIZABLE_RECURSIVE_KEY';
|
|
|
|
/**
|
|
* Creates a new serializable closure instance.
|
|
*
|
|
* @param \Closure $closure
|
|
* @return void
|
|
*/
|
|
public function __construct(Closure $closure)
|
|
{
|
|
$this->closure = $closure;
|
|
}
|
|
|
|
/**
|
|
* Resolve the closure with the given arguments.
|
|
*
|
|
* @return mixed
|
|
*/
|
|
public function __invoke()
|
|
{
|
|
return call_user_func_array($this->closure, func_get_args());
|
|
}
|
|
|
|
/**
|
|
* Gets the closure.
|
|
*
|
|
* @return \Closure
|
|
*/
|
|
public function getClosure()
|
|
{
|
|
return $this->closure;
|
|
}
|
|
|
|
/**
|
|
* Get the serializable representation of the closure.
|
|
*
|
|
* @return array
|
|
*/
|
|
public function __serialize()
|
|
{
|
|
if ($this->scope === null) {
|
|
$this->scope = new ClosureScope();
|
|
$this->scope->toSerialize++;
|
|
}
|
|
|
|
$this->scope->serializations++;
|
|
|
|
$scope = $object = null;
|
|
$reflector = $this->getReflector();
|
|
|
|
if ($reflector->isBindingRequired()) {
|
|
$object = $reflector->getClosureThis();
|
|
|
|
static::wrapClosures($object, $this->scope);
|
|
}
|
|
|
|
if ($scope = $reflector->getClosureScopeClass()) {
|
|
$scope = $scope->name;
|
|
}
|
|
|
|
$this->reference = spl_object_hash($this->closure);
|
|
|
|
$this->scope[$this->closure] = $this;
|
|
|
|
$use = $reflector->getUseVariables();
|
|
|
|
if (static::$transformUseVariables) {
|
|
$use = call_user_func(static::$transformUseVariables, $reflector->getUseVariables());
|
|
}
|
|
|
|
$code = $reflector->getCode();
|
|
|
|
$this->mapByReference($use);
|
|
|
|
$data = [
|
|
'use' => $use,
|
|
'function' => $code,
|
|
'scope' => $scope,
|
|
'this' => $object,
|
|
'self' => $this->reference,
|
|
];
|
|
|
|
if (! --$this->scope->serializations && ! --$this->scope->toSerialize) {
|
|
$this->scope = null;
|
|
}
|
|
|
|
return $data;
|
|
}
|
|
|
|
/**
|
|
* Restore the closure after serialization.
|
|
*
|
|
* @param array $data
|
|
* @return void
|
|
*/
|
|
public function __unserialize($data)
|
|
{
|
|
ClosureStream::register();
|
|
|
|
$this->code = $data;
|
|
unset($data);
|
|
|
|
$this->code['objects'] = [];
|
|
|
|
if ($this->code['use']) {
|
|
$this->scope = new ClosureScope();
|
|
|
|
if (static::$resolveUseVariables) {
|
|
$this->code['use'] = call_user_func(static::$resolveUseVariables, $this->code['use']);
|
|
}
|
|
|
|
$this->mapPointers($this->code['use']);
|
|
|
|
extract($this->code['use'], EXTR_OVERWRITE | EXTR_REFS);
|
|
|
|
$this->scope = null;
|
|
}
|
|
|
|
$this->closure = include ClosureStream::STREAM_PROTO.'://'.$this->code['function'];
|
|
|
|
if ($this->code['this'] === $this) {
|
|
$this->code['this'] = null;
|
|
}
|
|
|
|
$this->closure = $this->closure->bindTo($this->code['this'], $this->code['scope']);
|
|
|
|
if (! empty($this->code['objects'])) {
|
|
foreach ($this->code['objects'] as $item) {
|
|
$item['property']->setValue($item['instance'], $item['object']->getClosure());
|
|
}
|
|
}
|
|
|
|
$this->code = $this->code['function'];
|
|
}
|
|
|
|
/**
|
|
* Ensures the given closures are serializable.
|
|
*
|
|
* @param mixed $data
|
|
* @param \Laravel\SerializableClosure\Support\ClosureScope $storage
|
|
* @return void
|
|
*/
|
|
public static function wrapClosures(&$data, $storage)
|
|
{
|
|
if ($data instanceof Closure) {
|
|
$data = new static($data);
|
|
} elseif (is_array($data)) {
|
|
if (isset($data[self::ARRAY_RECURSIVE_KEY])) {
|
|
return;
|
|
}
|
|
|
|
$data[self::ARRAY_RECURSIVE_KEY] = true;
|
|
|
|
foreach ($data as $key => &$value) {
|
|
if ($key === self::ARRAY_RECURSIVE_KEY) {
|
|
continue;
|
|
}
|
|
static::wrapClosures($value, $storage);
|
|
}
|
|
|
|
unset($value);
|
|
unset($data[self::ARRAY_RECURSIVE_KEY]);
|
|
} elseif ($data instanceof \stdClass) {
|
|
if (isset($storage[$data])) {
|
|
$data = $storage[$data];
|
|
|
|
return;
|
|
}
|
|
|
|
$data = $storage[$data] = clone $data;
|
|
|
|
foreach ($data as &$value) {
|
|
static::wrapClosures($value, $storage);
|
|
}
|
|
|
|
unset($value);
|
|
} elseif (is_object($data) && ! $data instanceof static && ! $data instanceof UnitEnum) {
|
|
if (isset($storage[$data])) {
|
|
$data = $storage[$data];
|
|
|
|
return;
|
|
}
|
|
|
|
$instance = $data;
|
|
$reflection = new ReflectionObject($instance);
|
|
|
|
if (! $reflection->isUserDefined()) {
|
|
$storage[$instance] = $data;
|
|
|
|
return;
|
|
}
|
|
|
|
$storage[$instance] = $data = $reflection->newInstanceWithoutConstructor();
|
|
|
|
do {
|
|
if (! $reflection->isUserDefined()) {
|
|
break;
|
|
}
|
|
|
|
foreach ($reflection->getProperties() as $property) {
|
|
if ($property->isStatic() || ! $property->getDeclaringClass()->isUserDefined()) {
|
|
continue;
|
|
}
|
|
|
|
$property->setAccessible(true);
|
|
|
|
if (PHP_VERSION >= 7.4 && ! $property->isInitialized($instance)) {
|
|
continue;
|
|
}
|
|
|
|
$value = $property->getValue($instance);
|
|
|
|
if (is_array($value) || is_object($value)) {
|
|
static::wrapClosures($value, $storage);
|
|
}
|
|
|
|
$property->setValue($data, $value);
|
|
}
|
|
} while ($reflection = $reflection->getParentClass());
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Gets the closure's reflector.
|
|
*
|
|
* @return \Laravel\SerializableClosure\Support\ReflectionClosure
|
|
*/
|
|
public function getReflector()
|
|
{
|
|
if ($this->reflector === null) {
|
|
$this->code = null;
|
|
$this->reflector = new ReflectionClosure($this->closure);
|
|
}
|
|
|
|
return $this->reflector;
|
|
}
|
|
|
|
/**
|
|
* Internal method used to map closure pointers.
|
|
*
|
|
* @param mixed $data
|
|
* @return void
|
|
*/
|
|
protected function mapPointers(&$data)
|
|
{
|
|
$scope = $this->scope;
|
|
|
|
if ($data instanceof static) {
|
|
$data = &$data->closure;
|
|
} elseif (is_array($data)) {
|
|
if (isset($data[self::ARRAY_RECURSIVE_KEY])) {
|
|
return;
|
|
}
|
|
|
|
$data[self::ARRAY_RECURSIVE_KEY] = true;
|
|
|
|
foreach ($data as $key => &$value) {
|
|
if ($key === self::ARRAY_RECURSIVE_KEY) {
|
|
continue;
|
|
} elseif ($value instanceof static) {
|
|
$data[$key] = &$value->closure;
|
|
} elseif ($value instanceof SelfReference && $value->hash === $this->code['self']) {
|
|
$data[$key] = &$this->closure;
|
|
} else {
|
|
$this->mapPointers($value);
|
|
}
|
|
}
|
|
|
|
unset($value);
|
|
unset($data[self::ARRAY_RECURSIVE_KEY]);
|
|
} elseif ($data instanceof \stdClass) {
|
|
if (isset($scope[$data])) {
|
|
return;
|
|
}
|
|
|
|
$scope[$data] = true;
|
|
|
|
foreach ($data as $key => &$value) {
|
|
if ($value instanceof SelfReference && $value->hash === $this->code['self']) {
|
|
$data->{$key} = &$this->closure;
|
|
} elseif (is_array($value) || is_object($value)) {
|
|
$this->mapPointers($value);
|
|
}
|
|
}
|
|
|
|
unset($value);
|
|
} elseif (is_object($data) && ! ($data instanceof Closure)) {
|
|
if (isset($scope[$data])) {
|
|
return;
|
|
}
|
|
|
|
$scope[$data] = true;
|
|
$reflection = new ReflectionObject($data);
|
|
|
|
do {
|
|
if (! $reflection->isUserDefined()) {
|
|
break;
|
|
}
|
|
|
|
foreach ($reflection->getProperties() as $property) {
|
|
if ($property->isStatic() || ! $property->getDeclaringClass()->isUserDefined()) {
|
|
continue;
|
|
}
|
|
|
|
$property->setAccessible(true);
|
|
|
|
if (PHP_VERSION >= 7.4 && ! $property->isInitialized($data)) {
|
|
continue;
|
|
}
|
|
|
|
$item = $property->getValue($data);
|
|
|
|
if ($item instanceof SerializableClosure || $item instanceof UnsignedSerializableClosure || ($item instanceof SelfReference && $item->hash === $this->code['self'])) {
|
|
$this->code['objects'][] = [
|
|
'instance' => $data,
|
|
'property' => $property,
|
|
'object' => $item instanceof SelfReference ? $this : $item,
|
|
];
|
|
} elseif (is_array($item) || is_object($item)) {
|
|
$this->mapPointers($item);
|
|
$property->setValue($data, $item);
|
|
}
|
|
}
|
|
} while ($reflection = $reflection->getParentClass());
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Internal method used to map closures by reference.
|
|
*
|
|
* @param mixed $data
|
|
* @return void
|
|
*/
|
|
protected function mapByReference(&$data)
|
|
{
|
|
if ($data instanceof Closure) {
|
|
if ($data === $this->closure) {
|
|
$data = new SelfReference($this->reference);
|
|
|
|
return;
|
|
}
|
|
|
|
if (isset($this->scope[$data])) {
|
|
$data = $this->scope[$data];
|
|
|
|
return;
|
|
}
|
|
|
|
$instance = new static($data);
|
|
|
|
$instance->scope = $this->scope;
|
|
|
|
$data = $this->scope[$data] = $instance;
|
|
} elseif (is_array($data)) {
|
|
if (isset($data[self::ARRAY_RECURSIVE_KEY])) {
|
|
return;
|
|
}
|
|
|
|
$data[self::ARRAY_RECURSIVE_KEY] = true;
|
|
|
|
foreach ($data as $key => &$value) {
|
|
if ($key === self::ARRAY_RECURSIVE_KEY) {
|
|
continue;
|
|
}
|
|
|
|
$this->mapByReference($value);
|
|
}
|
|
|
|
unset($value);
|
|
unset($data[self::ARRAY_RECURSIVE_KEY]);
|
|
} elseif ($data instanceof \stdClass) {
|
|
if (isset($this->scope[$data])) {
|
|
$data = $this->scope[$data];
|
|
|
|
return;
|
|
}
|
|
|
|
$instance = $data;
|
|
$this->scope[$instance] = $data = clone $data;
|
|
|
|
foreach ($data as &$value) {
|
|
$this->mapByReference($value);
|
|
}
|
|
|
|
unset($value);
|
|
} elseif (is_object($data) && ! $data instanceof SerializableClosure && ! $data instanceof UnsignedSerializableClosure) {
|
|
if (isset($this->scope[$data])) {
|
|
$data = $this->scope[$data];
|
|
|
|
return;
|
|
}
|
|
|
|
$instance = $data;
|
|
|
|
if ($data instanceof DateTimeInterface) {
|
|
$this->scope[$instance] = $data;
|
|
|
|
return;
|
|
}
|
|
|
|
if ($data instanceof UnitEnum) {
|
|
$this->scope[$instance] = $data;
|
|
|
|
return;
|
|
}
|
|
|
|
$reflection = new ReflectionObject($data);
|
|
|
|
if (! $reflection->isUserDefined()) {
|
|
$this->scope[$instance] = $data;
|
|
|
|
return;
|
|
}
|
|
|
|
$this->scope[$instance] = $data = $reflection->newInstanceWithoutConstructor();
|
|
|
|
do {
|
|
if (! $reflection->isUserDefined()) {
|
|
break;
|
|
}
|
|
|
|
foreach ($reflection->getProperties() as $property) {
|
|
if ($property->isStatic() || ! $property->getDeclaringClass()->isUserDefined()) {
|
|
continue;
|
|
}
|
|
|
|
$property->setAccessible(true);
|
|
|
|
if (PHP_VERSION >= 7.4 && ! $property->isInitialized($instance)) {
|
|
continue;
|
|
}
|
|
|
|
$value = $property->getValue($instance);
|
|
|
|
if (is_array($value) || is_object($value)) {
|
|
$this->mapByReference($value);
|
|
}
|
|
|
|
$property->setValue($data, $value);
|
|
}
|
|
} while ($reflection = $reflection->getParentClass());
|
|
}
|
|
}
|
|
}
|