1
0

提交代码

This commit is contained in:
2020-08-06 14:50:07 +08:00
parent 9d0d5f4be9
commit d7a848c824
11299 changed files with 1321854 additions and 0 deletions

207
vendor/opis/closure/CHANGELOG.md vendored Normal file
View File

@@ -0,0 +1,207 @@
CHANGELOG
---------
### v3.4.1, 2019.10.19
- Fixed a [bug](https://github.com/opis/closure/issues/40) that prevented serialization to work correctly.
### v3.4.0, 2019.09.03
- Added `createClosure` static method in `Opis\Closure\SerializableClosure`.
This method creates a new closure from arbitrary code, emulating `create_function`,
but without using eval
### v3.3.1, 2019.07.10
- Use `sha1` instead of `md5` for hashing file names in `Opis\Closure\ReflectionClosure` class
### v3.3.0, 2019.05.31
- Fixed a bug that prevented signed closures to properly work when the serialized string
contains invalid UTF-8 chars. Starting with this version `json_encode` is no longer used
when signing a closure. Backward compatibility is maintained and all closures that were
previously signed using the old method will continue to work.
### v3.2.0, 2019.05.05
- Since an unsigned closure can be unserialized when no security provider is set,
there is no reason to treat differently a signed closure in the same situation.
Therefore, the `Opis\Closure\SecurityException` exception is no longer thrown when
unserializing a signed closure, if no security provider is set.
### v3.1.6, 2019.02.22
- Fixed a bug that occurred when trying to set properties of classes that were not defined in user-land.
Those properties are now ignored.
### v3.1.5, 2019.01.14
- Improved parser
### v3.1.4, 2019.01.14
- Added support for static methods that are named using PHP keywords or magic constants.
Ex: `A::new()`, `A::use()`, `A::if()`, `A::function()`, `A::__DIR__()`, etc.
- Used `@internal` to mark classes & methods that are for internal use only and
backward compatibility is not guaranteed.
### v3.1.3, 2019.01.07
- Fixed a bug that prevented traits to be correctly resolved when used by an
anonymous class
- Fixed a bug that occurred when `$this` keyword was used inside an anonymous class
### v3.1.2, 2018.12.16
* Fixed a bug regarding comma trail in group-use statements. See [issue 23](https://github.com/opis/closure/issues/23)
### v3.1.1, 2018.10.02
* Fixed a bug where `parent` keyword was treated like a class-name and scope was not added to the
serialized closure
* Fixed a bug where return type was not properly handled for nested closures
* Support for anonymous classes was improved
### v3.1.0, 2018.09.20
* Added `transformUseVariables` and `resolveUseVariables` to
`Opis\Closure\SerializableClosure` class.
* Added `removeSecurityProvider` static method to
`Opis\Closure\SerializableClosure` class.
* Fixed some security related issues where a user was able to unserialize an unsigned
closure, even when a security provider was in use.
### v3.0.12, 2018.02.23
* Bugfix. See [issue 20](https://github.com/opis/closure/issues/20)
### v3.0.11, 2018.01.22
* Bugfix. See [issue 18](https://github.com/opis/closure/issues/18)
### v3.0.10, 2018.01.04
* Improved support for PHP 7.1 & 7.2
### v3.0.9, 2018.01.04
* Fixed a bug where the return type was not properly resolved.
See [issue 17](https://github.com/opis/closure/issues/17)
* Added more tests
### v3.0.8, 2017.12.18
* Fixed a bug. See [issue 16](https://github.com/opis/closure/issues/16)
### v3.0.7, 2017.10.31
* Bugfix: static properties are ignored now, since they are not serializable
### v3.0.6, 2017.10.06
* Fixed a bug introduced by accident in 3.0.5
### v3.0.5, 2017.09.18
* Fixed a bug related to nested references
### v3.0.4, 2017.09.18
* \[*internal*\] Refactored `SerializableClosure::mapPointers` method
* \[*internal*\] Added a new optional argument to `SerializableClosure::unwrapClosures`
* \[*internal*\] Removed `SerializableClosure::getClosurePointer` method
* Fixed various bugs
### v3.0.3, 2017.09.06
* Fixed a bug related to nested object references
* \[*internal*\] `Opis\Closure\ClosureScope` now extends `SplObjectStorage`
* \[*internal*\] The `storage` property was removed from `Opis\Closure\ClosureScope`
* \[*internal*\] The `instances` and `objects` properties were removed from `Opis\Closure\ClosureContext`
### v3.0.2, 2017.08.28
* Fixed a bug where `$this` object was not handled properly inside the
`SerializableClosre::serialize` method.
### v3.0.1, 2017.04.13
* Fixed a bug in 'ignore_next' state
### v3.0.0, 2017.04.07
* Dropped PHP 5.3 support
* Moved source files from `lib` to `src` folder
* Removed second parameter from `Opis\Closure\SerializableClosure::from` method and from constructor
* Removed `Opis\Closure\{SecurityProviderInterface, DefaultSecurityProvider, SecureClosure}` classes
* Refactored how signed closures were handled
* Added `wrapClosures` and `unwrapClosures` static methods to `Opis\Closure\SerializableClosure` class
* Added `Opis\Colosure\serialize` and `Opis\Closure\unserialize` functions
* Improved serialization. You can now serialize arbitrary objects and the library will automatically wrap all closures
### v2.4.0, 2016.12.16
* The parser was refactored and improved
* Refactored `Opis\Closure\SerializableClosure::__invoke` method
* `Opis\Closure\{ISecurityProvider, SecurityProvider}` were added
* `Opis\Closure\{SecurityProviderInterface, DefaultSecurityProvider, SecureClosure}` were deprecated
and they will be removed in the next major version
* `setSecretKey` and `addSecurityProvider` static methods were added to `Opis\Closure\SerializableClosure`
### v2.3.2, 2016.12.15
* Fixed a bug that prevented namespace resolution to be done properly
### v2.3.1, 2016.12.13
* Hotfix. See [PR](https://github.com/opis/closure/pull/7)
### v2.3.0, 2016.11.17
* Added `isBindingRequired` and `isScopeRequired` to the `Opis\Closure\ReflectionClosure` class
* Automatically detects when the scope and/or the bound object of a closure needs to be serialized.
### v2.2.1, 2016.08.20
* Fixed a bug in `Opis\Closure\ReflectionClosure::fetchItems`
### v2.2.0, 2016.07.26
* Fixed CS
* `Opis\Closure\ClosureContext`, `Opis\Closure\ClosureScope`, `Opis\Closure\SelfReference`
and `Opis\Closure\SecurityException` classes were moved into separate files
* Added support for PHP7 syntax
* Fixed some bugs in `Opis\Closure\ReflectionClosure` class
* Improved closure parser
* Added an analyzer for SuperClosure library
### v2.1.0, 2015.09.30
* Added support for the missing `__METHOD__`, `__FUNCTION__` and `__TRAIT__` magic constants
* Added some security related classes and interfaces: `Opis\Closure\SecurityProviderInterface`,
`Opis\Closure\DefaultSecurityProvider`, `Opis\Closure\SecureClosure`, `Opis\Closure\SecurityException`.
* Fiexed a bug in `Opis\Closure\ReflectionClosure::getClasses` method
* Other minor bugfixes
* Added support for static closures
* Added public `isStatic` method to `Opis\Closure\ReflectionClosure` class
### v2.0.1, 2015.09.23
* Removed `branch-alias` property from `composer.json`
* Bugfix. See [issue #6](https://github.com/opis/closure/issues/6)
### v2.0.0, 2015.07.31
* The closure parser was improved
* Class names are now automatically resolved
* Added support for the `#trackme` directive which allows tracking closure's residing source
### v1.3.0, 2014.10.18
* Added autoload file
* Changed README file
### Opis Closure 1.2.2
* Started changelog

20
vendor/opis/closure/LICENSE vendored Normal file
View File

@@ -0,0 +1,20 @@
The MIT License (MIT)
Copyright (c) 2018-2019 Zindex Software
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

9
vendor/opis/closure/NOTICE vendored Normal file
View File

@@ -0,0 +1,9 @@
Opis Closure
Copyright 2018-2019 Zindex Software
This product includes software developed at
Zindex Software (http://zindex.software).
This software was originally developed by Marius Sarca and Sorin Sarca
(Copyright 2014-2018). The copyright info was changed with the permission
of the original authors.

98
vendor/opis/closure/README.md vendored Normal file
View File

@@ -0,0 +1,98 @@
Opis Closure
====================
[![Build Status](https://travis-ci.org/opis/closure.png)](https://travis-ci.org/opis/closure)
[![Latest Stable Version](https://poser.pugx.org/opis/closure/v/stable.png)](https://packagist.org/packages/opis/closure)
[![Latest Unstable Version](https://poser.pugx.org/opis/closure/v/unstable.png)](https://packagist.org/packages/opis/closure)
[![License](https://poser.pugx.org/opis/closure/license.png)](https://packagist.org/packages/opis/closure)
Serializable closures
---------------------
**Opis Closure** is a library that aims to overcome PHP's limitations regarding closure
serialization by providing a wrapper that will make all closures serializable.
**The library's key features:**
- Serialize any closure
- Serialize arbitrary objects
- Doesn't use `eval` for closure serialization or unserialization
- Works with any PHP version that has support for closures
- Supports PHP 7 syntax
- Handles all variables referenced/imported in `use()` and automatically wraps all referenced/imported closures for
proper serialization
- Handles recursive closures
- Handles magic constants like `__FILE__`, `__DIR__`, `__LINE__`, `__NAMESPACE__`, `__CLASS__`,
`__TRAIT__`, `__METHOD__` and `__FUNCTION__`.
- Automatically resolves all class names, function names and constant names used inside the closure
- Track closure's residing source by using the `#trackme` directive
- Simple and very fast parser
- Any error or exception, that might occur when executing an unserialized closure, can be caught and treated properly
- You can serialize/unserialize any closure unlimited times, even those previously unserialized
(this is possible because `eval()` is not used for unserialization)
- Handles static closures
- Supports cryptographically signed closures
- Provides a reflector that can give you information about the serialized closure
- Provides an analyzer for *SuperClosure* library
- Automatically detects when the scope and/or the bound object of a closure needs to be serialized
in order for the closure to work after deserialization
### Documentation
The full documentation for this library can be found [here][documentation].
### License
**Opis Closure** is licensed under the [MIT License (MIT)][license].
### Requirements
* PHP ^5.4 || ^7.0
## Installation
**Opis Closure** is available on [Packagist] and it can be installed from a
command line interface by using [Composer].
```bash
composer require opis/closure
```
Or you could directly reference it into your `composer.json` file as a dependency
```json
{
"require": {
"opis/closure": "^3.4"
}
}
```
### Migrating from 2.x
If your project needs to support PHP 5.3 you can continue using the `2.x` version
of **Opis Closure**. Otherwise, assuming you are not using one of the removed/refactored classes or features(see
[CHANGELOG]), migrating to version `3.x` is simply a matter of updating your `composer.json` file.
### Semantic versioning
**Opis Closure** follows [semantic versioning][SemVer] specifications.
### Arbitrary object serialization
This feature was primarily introduced in order to support serializing an object bound
to a closure and available via `$this`. The implementation is far from being perfect
and it's really hard to make it work flawless. I will try to improve this, but I can
not guarantee anything. So my advice regarding the `Opis\Closure\serialize|unserialize`
functions is to use them with caution.
### SuperClosure support
**Opis Closure** is shipped with an analyzer(`Opis\Closure\Analyzer`) which
aims to provide *Opis Closure*'s parsing precision and speed to [SuperClosure].
[documentation]: https://www.opis.io/closure "Opis Closure"
[license]: http://opensource.org/licenses/MIT "MIT License"
[Packagist]: https://packagist.org/packages/opis/closure "Packagist"
[Composer]: https://getcomposer.org "Composer"
[SuperClosure]: https://github.com/jeremeamia/super_closure "SuperClosure"
[SemVer]: http://semver.org/ "Semantic versioning"
[CHANGELOG]: https://github.com/opis/closure/blob/master/CHANGELOG.md "Changelog"

39
vendor/opis/closure/autoload.php vendored Normal file
View File

@@ -0,0 +1,39 @@
<?php
/* ===========================================================================
* Copyright (c) 2018-2019 Zindex Software
*
* Licensed under the MIT License
* =========================================================================== */
require_once 'functions.php';
spl_autoload_register(function($class){
$class = ltrim($class, '\\');
$dir = __DIR__ . '/src';
$namespace = 'Opis\Closure';
if(strpos($class, $namespace) === 0)
{
$class = substr($class, strlen($namespace));
$path = '';
if(($pos = strripos($class, '\\')) !== FALSE)
{
$path = str_replace('\\', '/', substr($class, 0, $pos)) . '/';
$class = substr($class, $pos + 1);
}
$path .= str_replace('_', '/', $class) . '.php';
$dir .= '/' . $path;
if(file_exists($dir))
{
include $dir;
return true;
}
return false;
}
return false;
});

40
vendor/opis/closure/composer.json vendored Normal file
View File

@@ -0,0 +1,40 @@
{
"name": "opis/closure",
"description": "A library that can be used to serialize closures (anonymous functions) and arbitrary objects.",
"keywords": ["closure", "serialization", "function", "serializable", "serialize", "anonymous functions"],
"homepage": "https://opis.io/closure",
"license": "MIT",
"authors": [
{
"name": "Marius Sarca",
"email": "marius.sarca@gmail.com"
},
{
"name": "Sorin Sarca",
"email": "sarca_sorin@hotmail.com"
}
],
"require": {
"php": "^5.4 || ^7.0"
},
"require-dev": {
"jeremeamia/superclosure": "^2.0",
"phpunit/phpunit": "^4.0 || ^5.0 || ^6.0 || ^7.0"
},
"autoload": {
"psr-4": {
"Opis\\Closure\\": "src/"
},
"files": ["functions.php"]
},
"autoload-dev": {
"psr-4": {
"Opis\\Closure\\Test\\": "tests/"
}
},
"extra": {
"branch-alias": {
"dev-master": "3.3.x-dev"
}
}
}

38
vendor/opis/closure/functions.php vendored Normal file
View File

@@ -0,0 +1,38 @@
<?php
/* ===========================================================================
* Copyright (c) 2018-2019 Zindex Software
*
* Licensed under the MIT License
* =========================================================================== */
namespace Opis\Closure;
/**
* Serialize
*
* @param $data
* @return string
*/
function serialize($data)
{
SerializableClosure::enterContext();
SerializableClosure::wrapClosures($data);
$data = \serialize($data);
SerializableClosure::exitContext();
return $data;
}
/**
* Unserialize
*
* @param $data
* @return mixed
*/
function unserialize($data)
{
SerializableClosure::enterContext();
$data = \unserialize($data);
SerializableClosure::unwrapClosures($data);
SerializableClosure::exitContext();
return $data;
}

59
vendor/opis/closure/src/Analyzer.php vendored Normal file
View File

@@ -0,0 +1,59 @@
<?php
/* ===========================================================================
* Copyright (c) 2018-2019 Zindex Software
*
* Licensed under the MIT License
* =========================================================================== */
namespace Opis\Closure;
use Closure;
use SuperClosure\Analyzer\ClosureAnalyzer;
class Analyzer extends ClosureAnalyzer
{
/**
* Analyzer a given closure.
*
* @param Closure $closure
*
* @return array
*/
public function analyze(Closure $closure)
{
$reflection = new ReflectionClosure($closure);
$scope = $reflection->getClosureScopeClass();
$data = [
'reflection' => $reflection,
'code' => $reflection->getCode(),
'hasThis' => $reflection->isBindingRequired(),
'context' => $reflection->getUseVariables(),
'hasRefs' => false,
'binding' => $reflection->getClosureThis(),
'scope' => $scope ? $scope->getName() : null,
'isStatic' => $reflection->isStatic(),
];
return $data;
}
/**
* @param array $data
* @return mixed
*/
protected function determineCode(array &$data)
{
return null;
}
/**
* @param array $data
* @return mixed
*/
protected function determineContext(array &$data)
{
return null;
}
}

View File

@@ -0,0 +1,34 @@
<?php
/* ===========================================================================
* Copyright (c) 2018-2019 Zindex Software
*
* Licensed under the MIT License
* =========================================================================== */
namespace Opis\Closure;
/**
* Closure context class
* @internal
*/
class ClosureContext
{
/**
* @var ClosureScope Closures scope
*/
public $scope;
/**
* @var integer
*/
public $locks;
/**
* Constructor
*/
public function __construct()
{
$this->scope = new ClosureScope();
$this->locks = 0;
}
}

View File

@@ -0,0 +1,25 @@
<?php
/* ===========================================================================
* Copyright (c) 2018-2019 Zindex Software
*
* Licensed under the MIT License
* =========================================================================== */
namespace Opis\Closure;
/**
* Closure scope class
* @internal
*/
class ClosureScope extends \SplObjectStorage
{
/**
* @var integer Number of serializations in current scope
*/
public $serializations = 0;
/**
* @var integer Number of closures that have to be serialized
*/
public $toserialize = 0;
}

View File

@@ -0,0 +1,94 @@
<?php
/* ===========================================================================
* Copyright (c) 2018-2019 Zindex Software
*
* Licensed under the MIT License
* =========================================================================== */
namespace Opis\Closure;
/**
* @internal
*/
class ClosureStream
{
const STREAM_PROTO = 'closure';
protected static $isRegistered = false;
protected $content;
protected $length;
protected $pointer = 0;
function stream_open($path, $mode, $options, &$opened_path)
{
$this->content = "<?php\nreturn " . substr($path, strlen(static::STREAM_PROTO . '://')) . ";";
$this->length = strlen($this->content);
return true;
}
public function stream_read($count)
{
$value = substr($this->content, $this->pointer, $count);
$this->pointer += $count;
return $value;
}
public function stream_eof()
{
return $this->pointer >= $this->length;
}
public function stream_stat()
{
$stat = stat(__FILE__);
$stat[7] = $stat['size'] = $this->length;
return $stat;
}
public function url_stat($path, $flags)
{
$stat = stat(__FILE__);
$stat[7] = $stat['size'] = $this->length;
return $stat;
}
public function stream_seek($offset, $whence = SEEK_SET)
{
$crt = $this->pointer;
switch ($whence) {
case SEEK_SET:
$this->pointer = $offset;
break;
case SEEK_CUR:
$this->pointer += $offset;
break;
case SEEK_END:
$this->pointer = $this->length + $offset;
break;
}
if ($this->pointer < 0 || $this->pointer >= $this->length) {
$this->pointer = $crt;
return false;
}
return true;
}
public function stream_tell()
{
return $this->pointer;
}
public static function register()
{
if (!static::$isRegistered) {
static::$isRegistered = stream_wrapper_register(static::STREAM_PROTO, __CLASS__);
}
}
}

View File

@@ -0,0 +1,25 @@
<?php
/* ===========================================================================
* Copyright (c) 2018-2019 Zindex Software
*
* Licensed under the MIT License
* =========================================================================== */
namespace Opis\Closure;
interface ISecurityProvider
{
/**
* Sign serialized closure
* @param string $closure
* @return array
*/
public function sign($closure);
/**
* Verify signature
* @param array $data
* @return bool
*/
public function verify(array $data);
}

View File

@@ -0,0 +1,939 @@
<?php
/* ===========================================================================
* Copyright (c) 2018-2019 Zindex Software
*
* Licensed under the MIT License
* =========================================================================== */
namespace Opis\Closure;
use Closure;
use ReflectionFunction;
class ReflectionClosure extends ReflectionFunction
{
protected $code;
protected $tokens;
protected $hashedName;
protected $useVariables;
protected $isStaticClosure;
protected $isScopeRequired;
protected $isBindingRequired;
protected static $files = array();
protected static $classes = array();
protected static $functions = array();
protected static $constants = array();
protected static $structures = array();
/**
* ReflectionClosure constructor.
* @param Closure $closure
* @param string|null $code
* @throws \ReflectionException
*/
public function __construct(Closure $closure, $code = null)
{
$this->code = $code;
parent::__construct($closure);
}
/**
* @return bool
*/
public function isStatic()
{
if ($this->isStaticClosure === null) {
$this->isStaticClosure = strtolower(substr($this->getCode(), 0, 6)) === 'static';
}
return $this->isStaticClosure;
}
/**
* @return string
*/
public function getCode()
{
if($this->code !== null){
return $this->code;
}
$fileName = $this->getFileName();
$line = $this->getStartLine() - 1;
$match = ClosureStream::STREAM_PROTO . '://';
if ($line === 1 && substr($fileName, 0, strlen($match)) === $match) {
return $this->code = substr($fileName, strlen($match));
}
$className = null;
if (null !== $className = $this->getClosureScopeClass()) {
$className = '\\' . trim($className->getName(), '\\');
}
if($php7 = PHP_MAJOR_VERSION === 7){
switch (PHP_MINOR_VERSION){
case 0:
$php7_types = array('string', 'int', 'bool', 'float');
break;
case 1:
$php7_types = array('string', 'int', 'bool', 'float', 'void');
break;
case 2:
default:
$php7_types = array('string', 'int', 'bool', 'float', 'void', 'object');
}
}
$ns = $this->getNamespaceName();
$nsf = $ns == '' ? '' : ($ns[0] == '\\' ? $ns : '\\' . $ns);
$_file = var_export($fileName, true);
$_dir = var_export(dirname($fileName), true);
$_namespace = var_export($ns, true);
$_class = var_export(trim($className, '\\'), true);
$_function = $ns . ($ns == '' ? '' : '\\') . '{closure}';
$_method = ($className == '' ? '' : trim($className, '\\') . '::') . $_function;
$_function = var_export($_function, true);
$_method = var_export($_method, true);
$_trait = null;
$tokens = $this->getTokens();
$state = $lastState = 'start';
$inside_anonymous = false;
$anonymous_mark = 0;
$open = 0;
$code = '';
$id_start = $id_start_ci = $id_name = $context = '';
$classes = $functions = $constants = null;
$use = array();
$lineAdd = 0;
$isUsingScope = false;
$isUsingThisObject = false;
for($i = 0, $l = count($tokens); $i < $l; $i++) {
$token = $tokens[$i];
switch ($state) {
case 'start':
if ($token[0] === T_FUNCTION || $token[0] === T_STATIC) {
$code .= $token[1];
$state = $token[0] === T_FUNCTION ? 'function' : 'static';
}
break;
case 'static':
if ($token[0] === T_WHITESPACE || $token[0] === T_COMMENT || $token[0] === T_FUNCTION) {
$code .= $token[1];
if ($token[0] === T_FUNCTION) {
$state = 'function';
}
} else {
$code = '';
$state = 'start';
}
break;
case 'function':
switch ($token[0]){
case T_STRING:
$code = '';
$state = 'named_function';
break;
case '(':
$code .= '(';
$state = 'closure_args';
break;
default:
$code .= is_array($token) ? $token[1] : $token;
}
break;
case 'named_function':
if($token[0] === T_FUNCTION || $token[0] === T_STATIC){
$code = $token[1];
$state = $token[0] === T_FUNCTION ? 'function' : 'static';
}
break;
case 'closure_args':
switch ($token[0]){
case T_NS_SEPARATOR:
case T_STRING:
$id_start = $token[1];
$id_start_ci = strtolower($id_start);
$id_name = '';
$context = 'args';
$state = 'id_name';
$lastState = 'closure_args';
break;
case T_USE:
$code .= $token[1];
$state = 'use';
break;
case '=':
$code .= $token;
$lastState = 'closure_args';
$state = 'ignore_next';
break;
case ':':
$code .= ':';
$state = 'return';
break;
case '{':
$code .= '{';
$state = 'closure';
$open++;
break;
default:
$code .= is_array($token) ? $token[1] : $token;
}
break;
case 'use':
switch ($token[0]){
case T_VARIABLE:
$use[] = substr($token[1], 1);
$code .= $token[1];
break;
case '{':
$code .= '{';
$state = 'closure';
$open++;
break;
case ':':
$code .= ':';
$state = 'return';
break;
default:
$code .= is_array($token) ? $token[1] : $token;
break;
}
break;
case 'return':
switch ($token[0]){
case T_WHITESPACE:
case T_COMMENT:
case T_DOC_COMMENT:
$code .= $token[1];
break;
case T_NS_SEPARATOR:
case T_STRING:
$id_start = $token[1];
$id_start_ci = strtolower($id_start);
$id_name = '';
$context = 'return_type';
$state = 'id_name';
$lastState = 'return';
break 2;
case '{':
$code .= '{';
$state = 'closure';
$open++;
break;
default:
$code .= is_array($token) ? $token[1] : $token;
break;
}
break;
case 'closure':
switch ($token[0]){
case T_CURLY_OPEN:
case T_DOLLAR_OPEN_CURLY_BRACES:
case T_STRING_VARNAME:
case '{':
$code .= '{';
$open++;
break;
case '}':
$code .= '}';
if(--$open === 0){
break 3;
} elseif ($inside_anonymous) {
$inside_anonymous = !($open === $anonymous_mark);
}
break;
case T_LINE:
$code .= $token[2] - $line + $lineAdd;
break;
case T_FILE:
$code .= $_file;
break;
case T_DIR:
$code .= $_dir;
break;
case T_NS_C:
$code .= $_namespace;
break;
case T_CLASS_C:
$code .= $_class;
break;
case T_FUNC_C:
$code .= $_function;
break;
case T_METHOD_C:
$code .= $_method;
break;
case T_COMMENT:
if (substr($token[1], 0, 8) === '#trackme') {
$timestamp = time();
$code .= '/**' . PHP_EOL;
$code .= '* Date : ' . date(DATE_W3C, $timestamp) . PHP_EOL;
$code .= '* Timestamp : ' . $timestamp . PHP_EOL;
$code .= '* Line : ' . ($line + 1) . PHP_EOL;
$code .= '* File : ' . $_file . PHP_EOL . '*/' . PHP_EOL;
$lineAdd += 5;
} else {
$code .= $token[1];
}
break;
case T_VARIABLE:
if($token[1] == '$this' && !$inside_anonymous){
$isUsingThisObject = true;
}
$code .= $token[1];
break;
case T_STATIC:
$isUsingScope = true;
$code .= $token[1];
break;
case T_NS_SEPARATOR:
case T_STRING:
$id_start = $token[1];
$id_start_ci = strtolower($id_start);
$id_name = '';
$context = 'root';
$state = 'id_name';
$lastState = 'closure';
break 2;
case T_NEW:
$code .= $token[1];
$context = 'new';
$state = 'id_start';
$lastState = 'closure';
break 2;
case T_USE:
$code .= $token[1];
$context = 'use';
$state = 'id_start';
$lastState = 'closure';
break;
case T_INSTANCEOF:
$code .= $token[1];
$context = 'instanceof';
$state = 'id_start';
$lastState = 'closure';
break;
case T_OBJECT_OPERATOR:
case T_DOUBLE_COLON:
$code .= $token[1];
$lastState = 'closure';
$state = 'ignore_next';
break;
case T_FUNCTION:
$code .= $token[1];
$state = 'closure_args';
break;
case T_TRAIT_C:
if ($_trait === null) {
$startLine = $this->getStartLine();
$endLine = $this->getEndLine();
$structures = $this->getStructures();
$_trait = '';
foreach ($structures as &$struct) {
if ($struct['type'] === 'trait' &&
$struct['start'] <= $startLine &&
$struct['end'] >= $endLine
) {
$_trait = ($ns == '' ? '' : $ns . '\\') . $struct['name'];
break;
}
}
$_trait = var_export($_trait, true);
}
$code .= $_trait;
break;
default:
$code .= is_array($token) ? $token[1] : $token;
}
break;
case 'ignore_next':
switch ($token[0]){
case T_WHITESPACE:
case T_COMMENT:
case T_DOC_COMMENT:
$code .= $token[1];
break;
case T_CLASS:
case T_NEW:
case T_STATIC:
case T_VARIABLE:
case T_STRING:
case T_CLASS_C:
case T_FILE:
case T_DIR:
case T_METHOD_C:
case T_FUNC_C:
case T_FUNCTION:
case T_INSTANCEOF:
case T_LINE:
case T_NS_C:
case T_TRAIT_C:
case T_USE:
$code .= $token[1];
$state = $lastState;
break;
default:
$state = $lastState;
$i--;
}
break;
case 'id_start':
switch ($token[0]){
case T_WHITESPACE:
case T_COMMENT:
case T_DOC_COMMENT:
$code .= $token[1];
break;
case T_NS_SEPARATOR:
case T_STRING:
case T_STATIC:
$id_start = $token[1];
$id_start_ci = strtolower($id_start);
$id_name = '';
$state = 'id_name';
break 2;
case T_VARIABLE:
$code .= $token[1];
$state = $lastState;
break;
case T_CLASS:
$code .= $token[1];
$state = 'anonymous';
break;
default:
$i--;//reprocess last
$state = 'id_name';
}
break;
case 'id_name':
switch ($token[0]){
case T_NS_SEPARATOR:
case T_STRING:
$id_name .= $token[1];
break;
case T_WHITESPACE:
case T_COMMENT:
case T_DOC_COMMENT:
$id_name .= $token[1];
break;
case '(':
if($context === 'new' || false !== strpos($id_name, '\\')){
if($id_start !== '\\'){
if ($classes === null) {
$classes = $this->getClasses();
}
if (isset($classes[$id_start_ci])) {
$id_start = $classes[$id_start_ci];
}
if($id_start[0] !== '\\'){
$id_start = $nsf . '\\' . $id_start;
}
}
} else {
if($id_start !== '\\'){
if($functions === null){
$functions = $this->getFunctions();
}
if(isset($functions[$id_start_ci])){
$id_start = $functions[$id_start_ci];
}
}
}
$code .= $id_start . $id_name . '(';
$state = $lastState;
break;
case T_VARIABLE:
case T_DOUBLE_COLON:
if($id_start !== '\\') {
if($id_start_ci === 'self' || $id_start_ci === 'static' || $id_start_ci === 'parent'){
$isUsingScope = true;
} elseif (!($php7 && in_array($id_start_ci, $php7_types))){
if ($classes === null) {
$classes = $this->getClasses();
}
if (isset($classes[$id_start_ci])) {
$id_start = $classes[$id_start_ci];
}
if($id_start[0] !== '\\'){
$id_start = $nsf . '\\' . $id_start;
}
}
}
$code .= $id_start . $id_name . $token[1];
$state = $token[0] === T_DOUBLE_COLON ? 'ignore_next' : $lastState;
break;
default:
if($id_start !== '\\'){
if($context === 'use' ||
$context === 'instanceof' ||
$context === 'args' ||
$context === 'return_type' ||
$context === 'extends'
){
if($id_start_ci === 'self' || $id_start_ci === 'static' || $id_start_ci === 'parent'){
$isUsingScope = true;
} elseif (!($php7 && in_array($id_start_ci, $php7_types))){
if($classes === null){
$classes = $this->getClasses();
}
if(isset($classes[$id_start_ci])){
$id_start = $classes[$id_start_ci];
}
if($id_start[0] !== '\\'){
$id_start = $nsf . '\\' . $id_start;
}
}
} else {
if($constants === null){
$constants = $this->getConstants();
}
if(isset($constants[$id_start])){
$id_start = $constants[$id_start];
}
}
}
$code .= $id_start . $id_name;
$state = $lastState;
$i--;//reprocess last token
}
break;
case 'anonymous':
switch ($token[0]) {
case T_NS_SEPARATOR:
case T_STRING:
$id_start = $token[1];
$id_start_ci = strtolower($id_start);
$id_name = '';
$state = 'id_name';
$context = 'extends';
$lastState = 'anonymous';
break;
case '{':
$state = 'closure';
if (!$inside_anonymous) {
$inside_anonymous = true;
$anonymous_mark = $open;
}
$i--;
break;
default:
$code .= is_array($token) ? $token[1] : $token;
}
break;
}
}
$this->isBindingRequired = $isUsingThisObject;
$this->isScopeRequired = $isUsingScope;
$this->code = $code;
$this->useVariables = empty($use) ? $use : array_intersect_key($this->getStaticVariables(), array_flip($use));
return $this->code;
}
/**
* @return array
*/
public function getUseVariables()
{
if($this->useVariables !== null){
return $this->useVariables;
}
$tokens = $this->getTokens();
$use = array();
$state = 'start';
foreach ($tokens as &$token) {
$is_array = is_array($token);
switch ($state) {
case 'start':
if ($is_array && $token[0] === T_USE) {
$state = 'use';
}
break;
case 'use':
if ($is_array) {
if ($token[0] === T_VARIABLE) {
$use[] = substr($token[1], 1);
}
} elseif ($token == ')') {
break 2;
}
break;
}
}
$this->useVariables = empty($use) ? $use : array_intersect_key($this->getStaticVariables(), array_flip($use));
return $this->useVariables;
}
/**
* return bool
*/
public function isBindingRequired()
{
if($this->isBindingRequired === null){
$this->getCode();
}
return $this->isBindingRequired;
}
/**
* return bool
*/
public function isScopeRequired()
{
if($this->isScopeRequired === null){
$this->getCode();
}
return $this->isScopeRequired;
}
/**
* @return string
*/
protected function getHashedFileName()
{
if ($this->hashedName === null) {
$this->hashedName = sha1($this->getFileName());
}
return $this->hashedName;
}
/**
* @return array
*/
protected function getFileTokens()
{
$key = $this->getHashedFileName();
if (!isset(static::$files[$key])) {
static::$files[$key] = token_get_all(file_get_contents($this->getFileName()));
}
return static::$files[$key];
}
/**
* @return array
*/
protected function getTokens()
{
if ($this->tokens === null) {
$tokens = $this->getFileTokens();
$startLine = $this->getStartLine();
$endLine = $this->getEndLine();
$results = array();
$start = false;
foreach ($tokens as &$token) {
if (!is_array($token)) {
if ($start) {
$results[] = $token;
}
continue;
}
$line = $token[2];
if ($line <= $endLine) {
if ($line >= $startLine) {
$start = true;
$results[] = $token;
}
continue;
}
break;
}
$this->tokens = $results;
}
return $this->tokens;
}
/**
* @return array
*/
protected function getClasses()
{
$key = $this->getHashedFileName();
if (!isset(static::$classes[$key])) {
$this->fetchItems();
}
return static::$classes[$key];
}
/**
* @return array
*/
protected function getFunctions()
{
$key = $this->getHashedFileName();
if (!isset(static::$functions[$key])) {
$this->fetchItems();
}
return static::$functions[$key];
}
/**
* @return array
*/
protected function getConstants()
{
$key = $this->getHashedFileName();
if (!isset(static::$constants[$key])) {
$this->fetchItems();
}
return static::$constants[$key];
}
/**
* @return array
*/
protected function getStructures()
{
$key = $this->getHashedFileName();
if (!isset(static::$structures[$key])) {
$this->fetchItems();
}
return static::$structures[$key];
}
protected function fetchItems()
{
$key = $this->getHashedFileName();
$classes = array();
$functions = array();
$constants = array();
$structures = array();
$tokens = $this->getFileTokens();
$open = 0;
$state = 'start';
$lastState = '';
$prefix = '';
$name = '';
$alias = '';
$isFunc = $isConst = false;
$startLine = $endLine = 0;
$structType = $structName = '';
$structIgnore = false;
foreach ($tokens as $token) {
switch ($state) {
case 'start':
switch ($token[0]) {
case T_CLASS:
case T_INTERFACE:
case T_TRAIT:
$state = 'before_structure';
$startLine = $token[2];
$structType = $token[0] == T_CLASS
? 'class'
: ($token[0] == T_INTERFACE ? 'interface' : 'trait');
break;
case T_USE:
$state = 'use';
$prefix = $name = $alias = '';
$isFunc = $isConst = false;
break;
case T_FUNCTION:
$state = 'structure';
$structIgnore = true;
break;
case T_NEW:
$state = 'new';
break;
case T_OBJECT_OPERATOR:
case T_DOUBLE_COLON:
$state = 'invoke';
break;
}
break;
case 'use':
switch ($token[0]) {
case T_FUNCTION:
$isFunc = true;
break;
case T_CONST:
$isConst = true;
break;
case T_NS_SEPARATOR:
$name .= $token[1];
break;
case T_STRING:
$name .= $token[1];
$alias = $token[1];
break;
case T_AS:
$lastState = 'use';
$state = 'alias';
break;
case '{':
$prefix = $name;
$name = $alias = '';
$state = 'use-group';
break;
case ',':
case ';':
if ($name === '' || $name[0] !== '\\') {
$name = '\\' . $name;
}
if ($alias !== '') {
if ($isFunc) {
$functions[strtolower($alias)] = $name;
} elseif ($isConst) {
$constants[$alias] = $name;
} else {
$classes[strtolower($alias)] = $name;
}
}
$name = $alias = '';
$state = $token === ';' ? 'start' : 'use';
break;
}
break;
case 'use-group':
switch ($token[0]) {
case T_NS_SEPARATOR:
$name .= $token[1];
break;
case T_STRING:
$name .= $token[1];
$alias = $token[1];
break;
case T_AS:
$lastState = 'use-group';
$state = 'alias';
break;
case ',':
case '}':
if ($prefix === '' || $prefix[0] !== '\\') {
$prefix = '\\' . $prefix;
}
if ($alias !== '') {
if ($isFunc) {
$functions[strtolower($alias)] = $prefix . $name;
} elseif ($isConst) {
$constants[$alias] = $prefix . $name;
} else {
$classes[strtolower($alias)] = $prefix . $name;
}
}
$name = $alias = '';
$state = $token === '}' ? 'use' : 'use-group';
break;
}
break;
case 'alias':
if ($token[0] === T_STRING) {
$alias = $token[1];
$state = $lastState;
}
break;
case 'new':
switch ($token[0]) {
case T_WHITESPACE:
case T_COMMENT:
case T_DOC_COMMENT:
break 2;
case T_CLASS:
$state = 'structure';
$structIgnore = true;
break;
default:
$state = 'start';
}
break;
case 'invoke':
switch ($token[0]) {
case T_WHITESPACE:
case T_COMMENT:
case T_DOC_COMMENT:
break 2;
default:
$state = 'start';
}
break;
case 'before_structure':
if ($token[0] == T_STRING) {
$structName = $token[1];
$state = 'structure';
}
break;
case 'structure':
switch ($token[0]) {
case '{':
case T_CURLY_OPEN:
case T_DOLLAR_OPEN_CURLY_BRACES:
case T_STRING_VARNAME:
$open++;
break;
case '}':
if (--$open == 0) {
if(!$structIgnore){
$structures[] = array(
'type' => $structType,
'name' => $structName,
'start' => $startLine,
'end' => $endLine,
);
}
$structIgnore = false;
$state = 'start';
}
break;
default:
if (is_array($token)) {
$endLine = $token[2];
}
}
break;
}
}
static::$classes[$key] = $classes;
static::$functions[$key] = $functions;
static::$constants[$key] = $constants;
static::$structures[$key] = $structures;
}
}

View File

@@ -0,0 +1,18 @@
<?php
/* ===========================================================================
* Copyright (c) 2018-2019 Zindex Software
*
* Licensed under the MIT License
* =========================================================================== */
namespace Opis\Closure;
use Exception;
/**
* Security exception class
*/
class SecurityException extends Exception
{
}

View File

@@ -0,0 +1,42 @@
<?php
/* ===========================================================================
* Copyright (c) 2018-2019 Zindex Software
*
* Licensed under the MIT License
* =========================================================================== */
namespace Opis\Closure;
class SecurityProvider implements ISecurityProvider
{
/** @var string */
protected $secret;
/**
* SecurityProvider constructor.
* @param string $secret
*/
public function __construct($secret)
{
$this->secret = $secret;
}
/**
* @inheritdoc
*/
public function sign($closure)
{
return array(
'closure' => $closure,
'hash' => base64_encode(hash_hmac('sha256', $closure, $this->secret, true)),
);
}
/**
* @inheritdoc
*/
public function verify(array $data)
{
return base64_encode(hash_hmac('sha256', $data['closure'], $this->secret, true)) === $data['hash'];
}
}

View File

@@ -0,0 +1,31 @@
<?php
/* ===========================================================================
* Copyright (c) 2018-2019 Zindex Software
*
* Licensed under the MIT License
* =========================================================================== */
namespace Opis\Closure;
/**
* Helper class used to indicate a reference to an object
* @internal
*/
class SelfReference
{
/**
* @var string An unique hash representing the object
*/
public $hash;
/**
* Constructor
*
* @param string $hash
*/
public function __construct($hash)
{
$this->hash = $hash;
}
}

View File

@@ -0,0 +1,668 @@
<?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());
}
}
}