| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668 |
- <?php
- /* ===========================================================================
- * Copyright (c) 2018-2019 Zindex Software
- *
- * Licensed under the MIT License
- * =========================================================================== */
- namespace Opis\Closure;
- use Closure;
- use Serializable;
- use SplObjectStorage;
- use ReflectionObject;
- /**
- * Provides a wrapper for serialization of closures
- */
- class SerializableClosure implements Serializable
- {
- /**
- * @var Closure Wrapped closure
- *
- * @see \Opis\Closure\SerializableClosure::getClosure()
- */
- protected $closure;
- /**
- * @var ReflectionClosure A reflection instance for closure
- *
- * @see \Opis\Closure\SerializableClosure::getReflector()
- */
- protected $reflector;
- /**
- * @var mixed Used at deserialization to hold variables
- *
- * @see \Opis\Closure\SerializableClosure::unserialize()
- * @see \Opis\Closure\SerializableClosure::getReflector()
- */
- protected $code;
- /**
- * @var string Closure's ID
- */
- protected $reference;
- /**
- * @var string Closure scope
- */
- protected $scope;
- /**
- * @var ClosureContext Context of closure, used in serialization
- */
- protected static $context;
- /**
- * @var ISecurityProvider|null
- */
- protected static $securityProvider;
- /** Array recursive constant*/
- const ARRAY_RECURSIVE_KEY = '¯\_(ツ)_/¯';
- /**
- * Constructor
- *
- * @param Closure $closure Closure you want to serialize
- */
- public function __construct(Closure $closure)
- {
- $this->closure = $closure;
- if (static::$context !== null) {
- $this->scope = static::$context->scope;
- $this->scope->toserialize++;
- }
- }
- /**
- * Get the Closure object
- *
- * @return Closure The wrapped closure
- */
- public function getClosure()
- {
- return $this->closure;
- }
- /**
- * Get the reflector for closure
- *
- * @return ReflectionClosure
- */
- public function getReflector()
- {
- if ($this->reflector === null) {
- $this->reflector = new ReflectionClosure($this->closure, $this->code);
- $this->code = null;
- }
- return $this->reflector;
- }
- /**
- * Implementation of magic method __invoke()
- */
- public function __invoke()
- {
- return call_user_func_array($this->closure, func_get_args());
- }
- /**
- * Implementation of Serializable::serialize()
- *
- * @return string The serialized closure
- */
- 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;
- }
- } elseif($reflector->isScopeRequired()) {
- if($scope = $reflector->getClosureScopeClass()){
- $scope = $scope->name;
- }
- }
- $this->reference = spl_object_hash($this->closure);
- $this->scope[$this->closure] = $this;
- $use = $this->transformUseVariables($reflector->getUseVariables());
- $code = $reflector->getCode();
- $this->mapByReference($use);
- $ret = \serialize(array(
- 'use' => $use,
- 'function' => $code,
- 'scope' => $scope,
- 'this' => $object,
- 'self' => $this->reference,
- ));
- if (static::$securityProvider !== null) {
- $data = static::$securityProvider->sign($ret);
- $ret = '@' . $data['hash'] . '.' . $data['closure'];
- }
- if (!--$this->scope->serializations && !--$this->scope->toserialize) {
- $this->scope = null;
- }
- return $ret;
- }
- /**
- * Transform the use variables before serialization.
- *
- * @param array $data The Closure's use variables
- * @return array
- */
- protected function transformUseVariables($data)
- {
- return $data;
- }
- /**
- * Implementation of Serializable::unserialize()
- *
- * @param string $data Serialized data
- * @throws SecurityException
- */
- public function unserialize($data)
- {
- ClosureStream::register();
- if (static::$securityProvider !== null) {
- if ($data[0] !== '@') {
- throw new SecurityException("The serialized closure is not signed. ".
- "Make sure you use a security provider for both serialization and unserialization.");
- }
- if ($data[1] !== '{') {
- $separator = strpos($data, '.');
- if ($separator === false) {
- throw new SecurityException('Invalid signed closure');
- }
- $hash = substr($data, 1, $separator - 1);
- $closure = substr($data, $separator + 1);
- $data = ['hash' => $hash, 'closure' => $closure];
- unset($hash, $closure);
- } else {
- $data = json_decode(substr($data, 1), true);
- }
- if (!is_array($data) || !static::$securityProvider->verify($data)) {
- throw new SecurityException("Your serialized closure might have been modified and it's unsafe to be unserialized. " .
- "Make sure you use the same security provider, with the same settings, " .
- "both for serialization and unserialization.");
- }
- $data = $data['closure'];
- } elseif ($data[0] === '@') {
- if ($data[1] !== '{') {
- $separator = strpos($data, '.');
- if ($separator === false) {
- throw new SecurityException('Invalid signed closure');
- }
- $hash = substr($data, 1, $separator - 1);
- $closure = substr($data, $separator + 1);
- $data = ['hash' => $hash, 'closure' => $closure];
- unset($hash, $closure);
- } else {
- $data = json_decode(substr($data, 1), true);
- }
- if (!is_array($data) || !isset($data['closure']) || !isset($data['hash'])) {
- throw new SecurityException('Invalid signed closure');
- }
- $data = $data['closure'];
- }
- $this->code = \unserialize($data);
- // unset data
- unset($data);
- $this->code['objects'] = array();
- if ($this->code['use']) {
- $this->scope = new ClosureScope();
- $this->code['use'] = $this->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;
- }
- if ($this->code['scope'] !== null || $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'];
- }
- /**
- * Resolve the use variables after unserialization.
- *
- * @param array $data The Closure's transformed use variables
- * @return array
- */
- protected function resolveUseVariables($data)
- {
- return $data;
- }
- /**
- * Wraps a closure and sets the serialization context (if any)
- *
- * @param Closure $closure Closure to be wrapped
- *
- * @return self The wrapped closure
- */
- public static function from(Closure $closure)
- {
- if (static::$context === null) {
- $instance = new static($closure);
- } elseif (isset(static::$context->scope[$closure])) {
- $instance = static::$context->scope[$closure];
- } else {
- $instance = new static($closure);
- static::$context->scope[$closure] = $instance;
- }
- return $instance;
- }
- /**
- * Increments the context lock counter or creates a new context if none exist
- */
- public static function enterContext()
- {
- if (static::$context === null) {
- static::$context = new ClosureContext();
- }
- static::$context->locks++;
- }
- /**
- * Decrements the context lock counter and destroy the context when it reaches to 0
- */
- public static function exitContext()
- {
- if (static::$context !== null && !--static::$context->locks) {
- static::$context = null;
- }
- }
- /**
- * @param string $secret
- */
- public static function setSecretKey($secret)
- {
- if(static::$securityProvider === null){
- static::$securityProvider = new SecurityProvider($secret);
- }
- }
- /**
- * @param ISecurityProvider $securityProvider
- */
- public static function addSecurityProvider(ISecurityProvider $securityProvider)
- {
- static::$securityProvider = $securityProvider;
- }
- /**
- * Remove security provider
- */
- public static function removeSecurityProvider()
- {
- static::$securityProvider = null;
- }
- /**
- * @return null|ISecurityProvider
- */
- public static function getSecurityProvider()
- {
- return static::$securityProvider;
- }
- /**
- * Wrap closures
- *
- * @internal
- * @param $data
- * @param ClosureScope|SplObjectStorage|null $storage
- */
- public static function wrapClosures(&$data, SplObjectStorage $storage = null)
- {
- if($storage === null){
- $storage = static::$context->scope;
- }
- if($data instanceof Closure){
- $data = static::from($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){
- 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);
- $value = $property->getValue($instance);
- if(is_array($value) || is_object($value)){
- static::wrapClosures($value, $storage);
- }
- $property->setValue($data, $value);
- };
- } while($reflection = $reflection->getParentClass());
- }
- }
- /**
- * Unwrap closures
- *
- * @internal
- * @param $data
- * @param SplObjectStorage|null $storage
- */
- public static function unwrapClosures(&$data, SplObjectStorage $storage = null)
- {
- if($storage === null){
- $storage = static::$context->scope;
- }
- if($data instanceof static){
- $data = $data->getClosure();
- } 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::unwrapClosures($value, $storage);
- }
- unset($data[self::ARRAY_RECURSIVE_KEY]);
- }elseif ($data instanceof \stdClass){
- if(isset($storage[$data])){
- return;
- }
- $storage[$data] = true;
- foreach ($data as &$property){
- static::unwrapClosures($property, $storage);
- }
- } elseif (is_object($data) && !($data instanceof Closure)){
- if(isset($storage[$data])){
- return;
- }
- $storage[$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);
- $value = $property->getValue($data);
- if(is_array($value) || is_object($value)){
- static::unwrapClosures($value, $storage);
- $property->setValue($data, $value);
- }
- };
- } while($reflection = $reflection->getParentClass());
- }
- }
- /**
- * Creates a new closure from arbitrary code,
- * emulating create_function, but without using eval
- *
- * @param string$args
- * @param string $code
- * @return Closure
- */
- public static function createClosure($args, $code)
- {
- ClosureStream::register();
- return include(ClosureStream::STREAM_PROTO . '://function(' . $args. '){' . $code . '};');
- }
- /**
- * Internal method used to map closure pointers
- * @internal
- * @param $data
- */
- 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);
- $item = $property->getValue($data);
- if ($item instanceof SerializableClosure || ($item instanceof SelfReference && $item->hash === $this->code['self'])) {
- $this->code['objects'][] = array(
- '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
- *
- * @internal
- * @param mixed &$data
- */
- 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);
- if (static::$context !== null) {
- static::$context->scope->toserialize--;
- } else {
- $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){
- if(isset($this->scope[$data])){
- $data = $this->scope[$data];
- return;
- }
- $instance = $data;
- $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);
- $value = $property->getValue($instance);
- if(is_array($value) || is_object($value)){
- $this->mapByReference($value);
- }
- $property->setValue($data, $value);
- }
- } while($reflection = $reflection->getParentClass());
- }
- }
- }
|