diff options
author | Côme Chilliet <come.chilliet@nextcloud.com> | 2022-06-21 10:57:58 +0300 |
---|---|---|
committer | Côme Chilliet <come.chilliet@nextcloud.com> | 2022-06-21 11:02:41 +0300 |
commit | b5f73e95a36edde3bf36bea8c72204692af1a0b5 (patch) | |
tree | a1a88e1c77181130c10c922e866b4d2d3c38b15a | |
parent | 02403021310540a848eb2385e979fcc0563ee730 (diff) |
Put back opis/closure in requirements
We will need to keep both dependencies for migration purposes.
We can drop opis/closure in a few releases.
Signed-off-by: Côme Chilliet <come.chilliet@nextcloud.com>
-rw-r--r-- | composer.json | 1 | ||||
-rw-r--r-- | composer.lock | 67 | ||||
-rw-r--r-- | composer/autoload_classmap.php | 10 | ||||
-rw-r--r-- | composer/autoload_files.php | 1 | ||||
-rw-r--r-- | composer/autoload_psr4.php | 1 | ||||
-rw-r--r-- | composer/autoload_static.php | 16 | ||||
-rw-r--r-- | composer/installed.json | 68 | ||||
-rw-r--r-- | composer/installed.php | 13 | ||||
-rw-r--r-- | opis/closure/LICENSE | 20 | ||||
-rw-r--r-- | opis/closure/NOTICE | 9 | ||||
-rw-r--r-- | opis/closure/README.md | 92 | ||||
-rw-r--r-- | opis/closure/autoload.php | 39 | ||||
-rw-r--r-- | opis/closure/composer.json | 44 | ||||
-rw-r--r-- | opis/closure/functions.php | 41 | ||||
-rw-r--r-- | opis/closure/src/Analyzer.php | 62 | ||||
-rw-r--r-- | opis/closure/src/ClosureContext.php | 34 | ||||
-rw-r--r-- | opis/closure/src/ClosureScope.php | 25 | ||||
-rw-r--r-- | opis/closure/src/ClosureStream.php | 99 | ||||
-rw-r--r-- | opis/closure/src/ISecurityProvider.php | 25 | ||||
-rw-r--r-- | opis/closure/src/ReflectionClosure.php | 1093 | ||||
-rw-r--r-- | opis/closure/src/SecurityException.php | 18 | ||||
-rw-r--r-- | opis/closure/src/SecurityProvider.php | 42 | ||||
-rw-r--r-- | opis/closure/src/SelfReference.php | 31 | ||||
-rw-r--r-- | opis/closure/src/SerializableClosure.php | 678 |
24 files changed, 2526 insertions, 3 deletions
diff --git a/composer.json b/composer.json index c2fc2444..0099fa05 100644 --- a/composer.json +++ b/composer.json @@ -34,6 +34,7 @@ "microsoft/azure-storage-blob": "^1.5", "nextcloud/lognormalizer": "^1.0", "nikic/php-parser": "^4.2", + "opis/closure": "^3.6", "pear/archive_tar": "^1.4.9", "pear/pear-core-minimal": "^v1.10", "php-ds/php-ds": "^1.3", diff --git a/composer.lock b/composer.lock index 7571fb70..0e643768 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "e0d2361c53dfcb8895529aef14967a7a", + "content-hash": "0ac4f55294fd40eceec3134a252cfc24", "packages": [ { "name": "aws/aws-sdk-php", @@ -2308,6 +2308,71 @@ "time": "2021-05-03T19:11:20+00:00" }, { + "name": "opis/closure", + "version": "3.6.3", + "source": { + "type": "git", + "url": "https://github.com/opis/closure.git", + "reference": "3d81e4309d2a927abbe66df935f4bb60082805ad" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/opis/closure/zipball/3d81e4309d2a927abbe66df935f4bb60082805ad", + "reference": "3d81e4309d2a927abbe66df935f4bb60082805ad", + "shasum": "" + }, + "require": { + "php": "^5.4 || ^7.0 || ^8.0" + }, + "require-dev": { + "jeremeamia/superclosure": "^2.0", + "phpunit/phpunit": "^4.0 || ^5.0 || ^6.0 || ^7.0 || ^8.0 || ^9.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.6.x-dev" + } + }, + "autoload": { + "files": [ + "functions.php" + ], + "psr-4": { + "Opis\\Closure\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Marius Sarca", + "email": "marius.sarca@gmail.com" + }, + { + "name": "Sorin Sarca", + "email": "sarca_sorin@hotmail.com" + } + ], + "description": "A library that can be used to serialize closures (anonymous functions) and arbitrary objects.", + "homepage": "https://opis.io/closure", + "keywords": [ + "anonymous functions", + "closure", + "function", + "serializable", + "serialization", + "serialize" + ], + "support": { + "issues": "https://github.com/opis/closure/issues", + "source": "https://github.com/opis/closure/tree/3.6.3" + }, + "time": "2022-01-27T09:35:39+00:00" + }, + { "name": "pear/archive_tar", "version": "1.4.14", "source": { diff --git a/composer/autoload_classmap.php b/composer/autoload_classmap.php index 7bbafbcb..b5499d8c 100644 --- a/composer/autoload_classmap.php +++ b/composer/autoload_classmap.php @@ -1814,6 +1814,16 @@ return array( 'OpenStack\\ObjectStore\\v1\\Params' => $vendorDir . '/php-opencloud/openstack/src/ObjectStore/v1/Params.php', 'OpenStack\\ObjectStore\\v1\\Service' => $vendorDir . '/php-opencloud/openstack/src/ObjectStore/v1/Service.php', 'OpenStack\\OpenStack' => $vendorDir . '/php-opencloud/openstack/src/OpenStack.php', + 'Opis\\Closure\\Analyzer' => $vendorDir . '/opis/closure/src/Analyzer.php', + 'Opis\\Closure\\ClosureContext' => $vendorDir . '/opis/closure/src/ClosureContext.php', + 'Opis\\Closure\\ClosureScope' => $vendorDir . '/opis/closure/src/ClosureScope.php', + 'Opis\\Closure\\ClosureStream' => $vendorDir . '/opis/closure/src/ClosureStream.php', + 'Opis\\Closure\\ISecurityProvider' => $vendorDir . '/opis/closure/src/ISecurityProvider.php', + 'Opis\\Closure\\ReflectionClosure' => $vendorDir . '/opis/closure/src/ReflectionClosure.php', + 'Opis\\Closure\\SecurityException' => $vendorDir . '/opis/closure/src/SecurityException.php', + 'Opis\\Closure\\SecurityProvider' => $vendorDir . '/opis/closure/src/SecurityProvider.php', + 'Opis\\Closure\\SelfReference' => $vendorDir . '/opis/closure/src/SelfReference.php', + 'Opis\\Closure\\SerializableClosure' => $vendorDir . '/opis/closure/src/SerializableClosure.php', 'PEAR' => $vendorDir . '/pear/pear-core-minimal/src/PEAR.php', 'PEAR_ErrorStack' => $vendorDir . '/pear/pear-core-minimal/src/PEAR/ErrorStack.php', 'PEAR_Exception' => $vendorDir . '/pear/pear_exception/PEAR/Exception.php', diff --git a/composer/autoload_files.php b/composer/autoload_files.php index 1ae107d3..02b06c86 100644 --- a/composer/autoload_files.php +++ b/composer/autoload_files.php @@ -119,6 +119,7 @@ return array( 'fe43ca06499ac37bc2dedd823af71eb5' => $vendorDir . '/thecodingmachine/safe/generated/zip.php', '356736db98a6834f0a886b8d509b0ecd' => $vendorDir . '/thecodingmachine/safe/generated/zlib.php', '8a9dc1de0ca7e01f3e08231539562f61' => $vendorDir . '/aws/aws-sdk-php/src/functions.php', + '538ca81a9a966a6716601ecf48f4eaef' => $vendorDir . '/opis/closure/functions.php', 'decc78cc4436b1292c6c0d151b19445c' => $vendorDir . '/phpseclib/phpseclib/phpseclib/bootstrap.php', '2c102faa651ef8ea5874edb585946bce' => $vendorDir . '/swiftmailer/swiftmailer/lib/swift_required.php', '8825ede83f2f289127722d4e842cf7e8' => $vendorDir . '/symfony/polyfill-intl-grapheme/bootstrap.php', diff --git a/composer/autoload_psr4.php b/composer/autoload_psr4.php index b5e49156..64242249 100644 --- a/composer/autoload_psr4.php +++ b/composer/autoload_psr4.php @@ -54,6 +54,7 @@ return array( 'Psr\\Container\\' => array($vendorDir . '/psr/container/src'), 'PhpParser\\' => array($vendorDir . '/nikic/php-parser/lib/PhpParser'), 'PackageVersions\\' => array($vendorDir . '/composer/package-versions-deprecated/src/PackageVersions'), + 'Opis\\Closure\\' => array($vendorDir . '/opis/closure/src'), 'OpenStack\\' => array($vendorDir . '/php-opencloud/openstack/src'), 'Nextcloud\\LogNormalizer\\' => array($vendorDir . '/nextcloud/lognormalizer/src'), 'MicrosoftAzure\\Storage\\Common\\' => array($vendorDir . '/microsoft/azure-storage-common/src/Common'), diff --git a/composer/autoload_static.php b/composer/autoload_static.php index 511885fc..43ff4254 100644 --- a/composer/autoload_static.php +++ b/composer/autoload_static.php @@ -120,6 +120,7 @@ class ComposerStaticInit2f23f73bc0cc116b4b1eee1521aa8652 'fe43ca06499ac37bc2dedd823af71eb5' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/zip.php', '356736db98a6834f0a886b8d509b0ecd' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/zlib.php', '8a9dc1de0ca7e01f3e08231539562f61' => __DIR__ . '/..' . '/aws/aws-sdk-php/src/functions.php', + '538ca81a9a966a6716601ecf48f4eaef' => __DIR__ . '/..' . '/opis/closure/functions.php', 'decc78cc4436b1292c6c0d151b19445c' => __DIR__ . '/..' . '/phpseclib/phpseclib/phpseclib/bootstrap.php', '2c102faa651ef8ea5874edb585946bce' => __DIR__ . '/..' . '/swiftmailer/swiftmailer/lib/swift_required.php', '8825ede83f2f289127722d4e842cf7e8' => __DIR__ . '/..' . '/symfony/polyfill-intl-grapheme/bootstrap.php', @@ -206,6 +207,7 @@ class ComposerStaticInit2f23f73bc0cc116b4b1eee1521aa8652 ), 'O' => array ( + 'Opis\\Closure\\' => 13, 'OpenStack\\' => 10, ), 'N' => @@ -476,6 +478,10 @@ class ComposerStaticInit2f23f73bc0cc116b4b1eee1521aa8652 array ( 0 => __DIR__ . '/..' . '/composer/package-versions-deprecated/src/PackageVersions', ), + 'Opis\\Closure\\' => + array ( + 0 => __DIR__ . '/..' . '/opis/closure/src', + ), 'OpenStack\\' => array ( 0 => __DIR__ . '/..' . '/php-opencloud/openstack/src', @@ -2444,6 +2450,16 @@ class ComposerStaticInit2f23f73bc0cc116b4b1eee1521aa8652 'OpenStack\\ObjectStore\\v1\\Params' => __DIR__ . '/..' . '/php-opencloud/openstack/src/ObjectStore/v1/Params.php', 'OpenStack\\ObjectStore\\v1\\Service' => __DIR__ . '/..' . '/php-opencloud/openstack/src/ObjectStore/v1/Service.php', 'OpenStack\\OpenStack' => __DIR__ . '/..' . '/php-opencloud/openstack/src/OpenStack.php', + 'Opis\\Closure\\Analyzer' => __DIR__ . '/..' . '/opis/closure/src/Analyzer.php', + 'Opis\\Closure\\ClosureContext' => __DIR__ . '/..' . '/opis/closure/src/ClosureContext.php', + 'Opis\\Closure\\ClosureScope' => __DIR__ . '/..' . '/opis/closure/src/ClosureScope.php', + 'Opis\\Closure\\ClosureStream' => __DIR__ . '/..' . '/opis/closure/src/ClosureStream.php', + 'Opis\\Closure\\ISecurityProvider' => __DIR__ . '/..' . '/opis/closure/src/ISecurityProvider.php', + 'Opis\\Closure\\ReflectionClosure' => __DIR__ . '/..' . '/opis/closure/src/ReflectionClosure.php', + 'Opis\\Closure\\SecurityException' => __DIR__ . '/..' . '/opis/closure/src/SecurityException.php', + 'Opis\\Closure\\SecurityProvider' => __DIR__ . '/..' . '/opis/closure/src/SecurityProvider.php', + 'Opis\\Closure\\SelfReference' => __DIR__ . '/..' . '/opis/closure/src/SelfReference.php', + 'Opis\\Closure\\SerializableClosure' => __DIR__ . '/..' . '/opis/closure/src/SerializableClosure.php', 'PEAR' => __DIR__ . '/..' . '/pear/pear-core-minimal/src/PEAR.php', 'PEAR_ErrorStack' => __DIR__ . '/..' . '/pear/pear-core-minimal/src/PEAR/ErrorStack.php', 'PEAR_Exception' => __DIR__ . '/..' . '/pear/pear_exception/PEAR/Exception.php', diff --git a/composer/installed.json b/composer/installed.json index 4b0b87dc..dd513ba3 100644 --- a/composer/installed.json +++ b/composer/installed.json @@ -2404,6 +2404,74 @@ "install-path": "../nikic/php-parser" }, { + "name": "opis/closure", + "version": "3.6.3", + "version_normalized": "3.6.3.0", + "source": { + "type": "git", + "url": "https://github.com/opis/closure.git", + "reference": "3d81e4309d2a927abbe66df935f4bb60082805ad" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/opis/closure/zipball/3d81e4309d2a927abbe66df935f4bb60082805ad", + "reference": "3d81e4309d2a927abbe66df935f4bb60082805ad", + "shasum": "" + }, + "require": { + "php": "^5.4 || ^7.0 || ^8.0" + }, + "require-dev": { + "jeremeamia/superclosure": "^2.0", + "phpunit/phpunit": "^4.0 || ^5.0 || ^6.0 || ^7.0 || ^8.0 || ^9.0" + }, + "time": "2022-01-27T09:35:39+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.6.x-dev" + } + }, + "installation-source": "dist", + "autoload": { + "files": [ + "functions.php" + ], + "psr-4": { + "Opis\\Closure\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Marius Sarca", + "email": "marius.sarca@gmail.com" + }, + { + "name": "Sorin Sarca", + "email": "sarca_sorin@hotmail.com" + } + ], + "description": "A library that can be used to serialize closures (anonymous functions) and arbitrary objects.", + "homepage": "https://opis.io/closure", + "keywords": [ + "anonymous functions", + "closure", + "function", + "serializable", + "serialization", + "serialize" + ], + "support": { + "issues": "https://github.com/opis/closure/issues", + "source": "https://github.com/opis/closure/tree/3.6.3" + }, + "install-path": "../opis/closure" + }, + { "name": "pear/archive_tar", "version": "1.4.14", "version_normalized": "1.4.14.0", diff --git a/composer/installed.php b/composer/installed.php index 0e9276d4..71d8bbf2 100644 --- a/composer/installed.php +++ b/composer/installed.php @@ -3,7 +3,7 @@ 'name' => 'nextcloud/3rdparty', 'pretty_version' => 'dev-master', 'version' => 'dev-master', - 'reference' => '70e442d437625996457bb36431fc14f288d4640d', + 'reference' => '02403021310540a848eb2385e979fcc0563ee730', 'type' => 'library', 'install_path' => __DIR__ . '/../', 'aliases' => array(), @@ -301,7 +301,7 @@ 'nextcloud/3rdparty' => array( 'pretty_version' => 'dev-master', 'version' => 'dev-master', - 'reference' => '70e442d437625996457bb36431fc14f288d4640d', + 'reference' => '02403021310540a848eb2385e979fcc0563ee730', 'type' => 'library', 'install_path' => __DIR__ . '/../', 'aliases' => array(), @@ -331,6 +331,15 @@ 0 => '1.11.99', ), ), + 'opis/closure' => array( + 'pretty_version' => '3.6.3', + 'version' => '3.6.3.0', + 'reference' => '3d81e4309d2a927abbe66df935f4bb60082805ad', + 'type' => 'library', + 'install_path' => __DIR__ . '/../opis/closure', + 'aliases' => array(), + 'dev_requirement' => false, + ), 'pear/archive_tar' => array( 'pretty_version' => '1.4.14', 'version' => '1.4.14.0', diff --git a/opis/closure/LICENSE b/opis/closure/LICENSE new file mode 100644 index 00000000..9c0a19ba --- /dev/null +++ b/opis/closure/LICENSE @@ -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. diff --git a/opis/closure/NOTICE b/opis/closure/NOTICE new file mode 100644 index 00000000..ae5caa62 --- /dev/null +++ b/opis/closure/NOTICE @@ -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.
\ No newline at end of file diff --git a/opis/closure/README.md b/opis/closure/README.md new file mode 100644 index 00000000..f5f3ad8c --- /dev/null +++ b/opis/closure/README.md @@ -0,0 +1,92 @@ +Opis Closure +==================== +[![Tests](https://github.com/opis/closure/workflows/Tests/badge.svg)](https://github.com/opis/closure/actions) +[![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 || ^8.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.5" + } +} +``` + +### 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 + +We've added this feature in order to be able to support the serialization of a closure's bound object. +The implementation is far from being perfect, and it's really hard to make it work flawless. +We will try to improve this, but we can't guarantee anything. +So our advice regarding the `Opis\Closure\serialize|unserialize` functions is to use them with caution. + + +[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" +[SemVer]: http://semver.org/ "Semantic versioning" +[CHANGELOG]: https://github.com/opis/closure/blob/master/CHANGELOG.md "Changelog" diff --git a/opis/closure/autoload.php b/opis/closure/autoload.php new file mode 100644 index 00000000..2354ea56 --- /dev/null +++ b/opis/closure/autoload.php @@ -0,0 +1,39 @@ +<?php +/* =========================================================================== + * Copyright (c) 2018-2021 Zindex Software + * + * Licensed under the MIT License + * =========================================================================== */ + +require_once __DIR__ . '/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; + +}); diff --git a/opis/closure/composer.json b/opis/closure/composer.json new file mode 100644 index 00000000..5d7d77ac --- /dev/null +++ b/opis/closure/composer.json @@ -0,0 +1,44 @@ +{ + "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 || ^8.0" + }, + "require-dev": { + "jeremeamia/superclosure": "^2.0", + "phpunit/phpunit": "^4.0 || ^5.0 || ^6.0 || ^7.0 || ^8.0 || ^9.0" + }, + "autoload": { + "psr-4": { + "Opis\\Closure\\": "src/" + }, + "files": ["functions.php"] + }, + "autoload-dev": { + "psr-4": { + "Opis\\Closure\\Test\\": "tests/" + } + }, + "extra": { + "branch-alias": { + "dev-master": "3.6.x-dev" + } + }, + "config": { + "preferred-install": "dist", + "sort-packages": true + } +} diff --git a/opis/closure/functions.php b/opis/closure/functions.php new file mode 100644 index 00000000..38dcb91f --- /dev/null +++ b/opis/closure/functions.php @@ -0,0 +1,41 @@ +<?php +/* =========================================================================== + * Copyright (c) 2018-2021 Zindex Software + * + * Licensed under the MIT License + * =========================================================================== */ + +namespace Opis\Closure; + +/** + * Serialize + * + * @param mixed $data + * @return string + */ +function serialize($data) +{ + SerializableClosure::enterContext(); + SerializableClosure::wrapClosures($data); + $data = \serialize($data); + SerializableClosure::exitContext(); + return $data; +} + +/** + * Unserialize + * + * @param string $data + * @param array|null $options + * @return mixed + */ +function unserialize($data, array $options = null) +{ + SerializableClosure::enterContext(); + $data = ($options === null || \PHP_MAJOR_VERSION < 7) + ? \unserialize($data) + : \unserialize($data, $options); + SerializableClosure::unwrapClosures($data); + SerializableClosure::exitContext(); + return $data; +} diff --git a/opis/closure/src/Analyzer.php b/opis/closure/src/Analyzer.php new file mode 100644 index 00000000..9618b1c7 --- /dev/null +++ b/opis/closure/src/Analyzer.php @@ -0,0 +1,62 @@ +<?php +/* =========================================================================== + * Copyright (c) 2018-2021 Zindex Software + * + * Licensed under the MIT License + * =========================================================================== */ + +namespace Opis\Closure; + +use Closure; +use SuperClosure\Analyzer\ClosureAnalyzer; + +/** + * @deprecated We'll remove this class + */ +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; + } + +} diff --git a/opis/closure/src/ClosureContext.php b/opis/closure/src/ClosureContext.php new file mode 100644 index 00000000..d8f13d9d --- /dev/null +++ b/opis/closure/src/ClosureContext.php @@ -0,0 +1,34 @@ +<?php +/* =========================================================================== + * Copyright (c) 2018-2021 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; + } +}
\ No newline at end of file diff --git a/opis/closure/src/ClosureScope.php b/opis/closure/src/ClosureScope.php new file mode 100644 index 00000000..3afd9b55 --- /dev/null +++ b/opis/closure/src/ClosureScope.php @@ -0,0 +1,25 @@ +<?php +/* =========================================================================== + * Copyright (c) 2018-2021 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; +}
\ No newline at end of file diff --git a/opis/closure/src/ClosureStream.php b/opis/closure/src/ClosureStream.php new file mode 100644 index 00000000..d77d97c8 --- /dev/null +++ b/opis/closure/src/ClosureStream.php @@ -0,0 +1,99 @@ +<?php +/* =========================================================================== + * Copyright (c) 2018-2021 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_set_option($option, $arg1, $arg2) + { + return false; + } + + 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__); + } + } + +} diff --git a/opis/closure/src/ISecurityProvider.php b/opis/closure/src/ISecurityProvider.php new file mode 100644 index 00000000..54e2e20a --- /dev/null +++ b/opis/closure/src/ISecurityProvider.php @@ -0,0 +1,25 @@ +<?php +/* =========================================================================== + * Copyright (c) 2018-2021 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); +}
\ No newline at end of file diff --git a/opis/closure/src/ReflectionClosure.php b/opis/closure/src/ReflectionClosure.php new file mode 100644 index 00000000..1f6a8322 --- /dev/null +++ b/opis/closure/src/ReflectionClosure.php @@ -0,0 +1,1093 @@ +<?php +/* =========================================================================== + * Copyright (c) 2018-2021 Zindex Software + * + * Licensed under the MIT License + * =========================================================================== */ + +namespace Opis\Closure; + +defined('T_NAME_QUALIFIED') || define('T_NAME_QUALIFIED', -4); +defined('T_NAME_FULLY_QUALIFIED') || define('T_NAME_FULLY_QUALIFIED', -5); +defined('T_FN') || define('T_FN', -6); + +use Closure; +use ReflectionFunction; + +class ReflectionClosure extends ReflectionFunction +{ + protected $code; + protected $tokens; + protected $hashedName; + protected $useVariables; + protected $isStaticClosure; + protected $isScopeRequired; + protected $isBindingRequired; + protected $isShortClosure; + + 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 This is ignored. Do not use it + * @throws \ReflectionException + */ + public function __construct(Closure $closure, $code = null) + { + parent::__construct($closure); + } + + /** + * @return bool + */ + public function isStatic() + { + if ($this->isStaticClosure === null) { + $this->isStaticClosure = strtolower(substr($this->getCode(), 0, 6)) === 'static'; + } + + return $this->isStaticClosure; + } + + public function isShortClosure() + { + if ($this->isShortClosure === null) { + $code = $this->getCode(); + if ($this->isStatic()) { + $code = substr($code, 6); + } + $this->isShortClosure = strtolower(substr(trim($code), 0, 2)) === 'fn'; + } + + return $this->isShortClosure; + } + + /** + * @return string + */ + public function getCode() + { + if($this->code !== null){ + return $this->code; + } + + $fileName = $this->getFileName(); + $line = $this->getStartLine() - 1; + + $className = null; + + if (null !== $className = $this->getClosureScopeClass()) { + $className = '\\' . trim($className->getName(), '\\'); + } + + $builtin_types = self::getBuiltinTypes(); + $class_keywords = ['self', 'static', 'parent']; + + $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_structure = false; + $isShortClosure = false; + $inside_structure_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'; + } elseif ($token[0] === T_FN) { + $isShortClosure = true; + $code .= $token[1]; + $state = 'closure_args'; + } + 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'; + } + } elseif ($token[0] === T_FN) { + $isShortClosure = true; + $code .= $token[1]; + $state = 'closure_args'; + } 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'; + } elseif ($token[0] === T_FN) { + $isShortClosure = true; + $code .= $token[1]; + $state = 'closure_args'; + } + break; + case 'closure_args': + switch ($token[0]){ + case T_NAME_QUALIFIED: + list($id_start, $id_start_ci, $id_name) = $this->parseNameQualified($token[1]); + $context = 'args'; + $state = 'id_name'; + $lastState = 'closure_args'; + break; + 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 T_DOUBLE_ARROW: + $code .= $token[1]; + if ($isShortClosure) { + $state = 'closure'; + } + 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 T_NAME_QUALIFIED: + list($id_start, $id_start_ci, $id_name) = $this->parseNameQualified($token[1]); + $context = 'return_type'; + $state = 'id_name'; + $lastState = 'return'; + break 2; + case T_DOUBLE_ARROW: + $code .= $token[1]; + if ($isShortClosure) { + $state = 'closure'; + } + break; + 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 '{': + $code .= is_array($token) ? $token[1] : $token; + $open++; + break; + case '}': + $code .= '}'; + if(--$open === 0 && !$isShortClosure){ + break 3; + } elseif ($inside_structure) { + $inside_structure = !($open === $inside_structure_mark); + } + break; + case '(': + case '[': + $code .= $token[0]; + if ($isShortClosure) { + $open++; + } + break; + case ')': + case ']': + if ($isShortClosure) { + if ($open === 0) { + break 3; + } + --$open; + } + $code .= $token[0]; + break; + case ',': + case ';': + if ($isShortClosure && $open === 0) { + break 3; + } + $code .= $token[0]; + 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 .= $inside_structure ? $token[1] : $_class; + break; + case T_FUNC_C: + $code .= $inside_structure ? $token[1] : $_function; + break; + case T_METHOD_C: + $code .= $inside_structure ? $token[1] : $_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_structure){ + $isUsingThisObject = true; + } + $code .= $token[1]; + break; + case T_STATIC: + 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_NAME_QUALIFIED: + list($id_start, $id_start_ci, $id_name) = $this->parseNameQualified($token[1]); + $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: + case T_INSTEADOF: + $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'; + if (!$inside_structure) { + $inside_structure = true; + $inside_structure_mark = $open; + } + 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_NAME_FULLY_QUALIFIED: + 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_NAME_QUALIFIED: + list($id_start, $id_start_ci, $id_name) = $this->parseNameQualified($token[1]); + $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_NAME_QUALIFIED: + case T_NS_SEPARATOR: + case T_STRING: + case T_WHITESPACE: + case T_COMMENT: + case T_DOC_COMMENT: + $id_name .= $token[1]; + break; + case '(': + if ($isShortClosure) { + $open++; + } + if($context === 'new' || false !== strpos($id_name, '\\')){ + if($id_start_ci === 'self' || $id_start_ci === 'static') { + if (!$inside_structure) { + $isUsingScope = true; + } + } elseif ($id_start !== '\\' && !in_array($id_start_ci, $class_keywords)) { + 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]; + } elseif ($nsf !== '\\' && function_exists($nsf . '\\' . $id_start)) { + $id_start = $nsf . '\\' . $id_start; + // Cache it to functions array + $functions[$id_start_ci] = $id_start; + } + } + } + $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 === 'parent'){ + if (!$inside_structure) { + $isUsingScope = true; + } + } elseif ($id_start_ci === 'static') { + if (!$inside_structure) { + $isUsingScope = $token[0] === T_DOUBLE_COLON; + } + } elseif (!(\PHP_MAJOR_VERSION >= 7 && in_array($id_start_ci, $builtin_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 !== '\\' && !defined($id_start)){ + if($constants === null){ + $constants = $this->getConstants(); + } + if(isset($constants[$id_start])){ + $id_start = $constants[$id_start]; + } elseif($context === 'new'){ + if(in_array($id_start_ci, $class_keywords)) { + if (!$inside_structure) { + $isUsingScope = true; + } + } else { + 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; + } + } + } elseif($context === 'use' || + $context === 'instanceof' || + $context === 'args' || + $context === 'return_type' || + $context === 'extends' || + $context === 'root' + ){ + if(in_array($id_start_ci, $class_keywords)){ + if (!$inside_structure && !$id_start_ci === 'static') { + $isUsingScope = true; + } + } elseif (!(\PHP_MAJOR_VERSION >= 7 && in_array($id_start_ci, $builtin_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; + $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_structure) { + $inside_structure = true; + $inside_structure_mark = $open; + } + $i--; + break; + default: + $code .= is_array($token) ? $token[1] : $token; + } + break; + } + } + + if ($isShortClosure) { + $this->useVariables = $this->getStaticVariables(); + } else { + $this->useVariables = empty($use) ? $use : array_intersect_key($this->getStaticVariables(), array_flip($use)); + } + + $this->isShortClosure = $isShortClosure; + $this->isBindingRequired = $isUsingThisObject; + $this->isScopeRequired = $isUsingScope; + $this->code = $code; + + return $this->code; + } + + /** + * @return array + */ + private static function getBuiltinTypes() + { + // PHP 5 + if (\PHP_MAJOR_VERSION === 5) { + return ['array', 'callable']; + } + + // PHP 8 + if (\PHP_MAJOR_VERSION === 8) { + return ['array', 'callable', 'string', 'int', 'bool', 'float', 'iterable', 'void', 'object', 'mixed', 'false', 'null']; + } + + // PHP 7 + switch (\PHP_MINOR_VERSION) { + case 0: + return ['array', 'callable', 'string', 'int', 'bool', 'float']; + case 1: + return ['array', 'callable', 'string', 'int', 'bool', 'float', 'iterable', 'void']; + default: + return ['array', 'callable', 'string', 'int', 'bool', 'float', 'iterable', 'void', 'object']; + } + } + + /** + * @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_NAME_QUALIFIED: + $name .= $token[1]; + $pieces = explode('\\', $token[1]); + $alias = end($pieces); + 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_NAME_QUALIFIED: + $name .= $token[1]; + $pieces = explode('\\', $token[1]); + $alias = end($pieces); + 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: + $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; + } + + private function parseNameQualified($token) + { + $pieces = explode('\\', $token); + + $id_start = array_shift($pieces); + + $id_start_ci = strtolower($id_start); + + $id_name = '\\' . implode('\\', $pieces); + + return [$id_start, $id_start_ci, $id_name]; + } +} diff --git a/opis/closure/src/SecurityException.php b/opis/closure/src/SecurityException.php new file mode 100644 index 00000000..b65f5cb8 --- /dev/null +++ b/opis/closure/src/SecurityException.php @@ -0,0 +1,18 @@ +<?php +/* =========================================================================== + * Copyright (c) 2018-2021 Zindex Software + * + * Licensed under the MIT License + * =========================================================================== */ + +namespace Opis\Closure; + +use Exception; + +/** + * Security exception class + */ +class SecurityException extends Exception +{ + +}
\ No newline at end of file diff --git a/opis/closure/src/SecurityProvider.php b/opis/closure/src/SecurityProvider.php new file mode 100644 index 00000000..19529173 --- /dev/null +++ b/opis/closure/src/SecurityProvider.php @@ -0,0 +1,42 @@ +<?php +/* =========================================================================== + * Copyright (c) 2018-2021 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']; + } +}
\ No newline at end of file diff --git a/opis/closure/src/SelfReference.php b/opis/closure/src/SelfReference.php new file mode 100644 index 00000000..425edf27 --- /dev/null +++ b/opis/closure/src/SelfReference.php @@ -0,0 +1,31 @@ +<?php +/* =========================================================================== + * Copyright (c) 2018-2021 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; + } +}
\ No newline at end of file diff --git a/opis/closure/src/SerializableClosure.php b/opis/closure/src/SerializableClosure.php new file mode 100644 index 00000000..1025ff51 --- /dev/null +++ b/opis/closure/src/SerializableClosure.php @@ -0,0 +1,678 @@ +<?php +/* =========================================================================== + * Copyright (c) 2018-2021 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 = 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; + } + } else { + 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; + } + + $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); + 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()); + } + } + + /** + * 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); + if (PHP_VERSION >= 7.4 && !$property->isInitialized($data)) { + continue; + } + $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); + if (PHP_VERSION >= 7.4 && !$property->isInitialized($data)) { + continue; + } + $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); + 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()); + } + } + +} |