diff --git a/composer.json b/composer.json new file mode 100644 index 0000000..d37275a --- /dev/null +++ b/composer.json @@ -0,0 +1,5 @@ +{ + "require": { + "phpmyadmin/motranslator": "^5.3" + } +} diff --git a/composer.lock b/composer.lock new file mode 100644 index 0000000..65da850 --- /dev/null +++ b/composer.lock @@ -0,0 +1,692 @@ +{ + "_readme": [ + "This file locks the dependencies of your project to a known state", + "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", + "This file is @generated automatically" + ], + "content-hash": "b102e899e31d62f4d40282ff04a339d9", + "packages": [ + { + "name": "phpmyadmin/motranslator", + "version": "5.3.1", + "source": { + "type": "git", + "url": "https://github.com/phpmyadmin/motranslator.git", + "reference": "d03b4d9c608e7265091bf6decc05323d16c7c047" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpmyadmin/motranslator/zipball/d03b4d9c608e7265091bf6decc05323d16c7c047", + "reference": "d03b4d9c608e7265091bf6decc05323d16c7c047", + "shasum": "" + }, + "require": { + "php": "^7.1 || ^8.0", + "symfony/expression-language": "^4.0 || ^5.0 || ^6.0" + }, + "require-dev": { + "phpmyadmin/coding-standard": "^3.0.0", + "phpstan/phpstan": "^1.4.6", + "phpunit/phpunit": "^7.4 || ^8 || ^9" + }, + "suggest": { + "ext-apcu": "Needed for ACPu-backed translation cache" + }, + "type": "library", + "autoload": { + "psr-4": { + "PhpMyAdmin\\MoTranslator\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "GPL-2.0-or-later" + ], + "authors": [ + { + "name": "The phpMyAdmin Team", + "email": "developers@phpmyadmin.net", + "homepage": "https://www.phpmyadmin.net/team/" + } + ], + "description": "Translation API for PHP using Gettext MO files", + "homepage": "https://github.com/phpmyadmin/motranslator", + "keywords": [ + "gettext", + "i18n", + "mo", + "translator" + ], + "support": { + "issues": "https://github.com/phpmyadmin/motranslator/issues", + "source": "https://github.com/phpmyadmin/motranslator" + }, + "time": "2023-08-23T18:32:07+00:00" + }, + { + "name": "psr/cache", + "version": "3.0.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/cache.git", + "reference": "aa5030cfa5405eccfdcb1083ce040c2cb8d253bf" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/cache/zipball/aa5030cfa5405eccfdcb1083ce040c2cb8d253bf", + "reference": "aa5030cfa5405eccfdcb1083ce040c2cb8d253bf", + "shasum": "" + }, + "require": { + "php": ">=8.0.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Cache\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interface for caching libraries", + "keywords": [ + "cache", + "psr", + "psr-6" + ], + "support": { + "source": "https://github.com/php-fig/cache/tree/3.0.0" + }, + "time": "2021-02-03T23:26:27+00:00" + }, + { + "name": "psr/container", + "version": "2.0.2", + "source": { + "type": "git", + "url": "https://github.com/php-fig/container.git", + "reference": "c71ecc56dfe541dbd90c5360474fbc405f8d5963" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/container/zipball/c71ecc56dfe541dbd90c5360474fbc405f8d5963", + "reference": "c71ecc56dfe541dbd90c5360474fbc405f8d5963", + "shasum": "" + }, + "require": { + "php": ">=7.4.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Container\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common Container Interface (PHP FIG PSR-11)", + "homepage": "https://github.com/php-fig/container", + "keywords": [ + "PSR-11", + "container", + "container-interface", + "container-interop", + "psr" + ], + "support": { + "issues": "https://github.com/php-fig/container/issues", + "source": "https://github.com/php-fig/container/tree/2.0.2" + }, + "time": "2021-11-05T16:47:00+00:00" + }, + { + "name": "psr/log", + "version": "3.0.2", + "source": { + "type": "git", + "url": "https://github.com/php-fig/log.git", + "reference": "f16e1d5863e37f8d8c2a01719f5b34baa2b714d3" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/log/zipball/f16e1d5863e37f8d8c2a01719f5b34baa2b714d3", + "reference": "f16e1d5863e37f8d8c2a01719f5b34baa2b714d3", + "shasum": "" + }, + "require": { + "php": ">=8.0.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Log\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interface for logging libraries", + "homepage": "https://github.com/php-fig/log", + "keywords": [ + "log", + "psr", + "psr-3" + ], + "support": { + "source": "https://github.com/php-fig/log/tree/3.0.2" + }, + "time": "2024-09-11T13:17:53+00:00" + }, + { + "name": "symfony/cache", + "version": "v7.2.1", + "source": { + "type": "git", + "url": "https://github.com/symfony/cache.git", + "reference": "e7e983596b744c4539f31e79b0350a6cf5878a20" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/cache/zipball/e7e983596b744c4539f31e79b0350a6cf5878a20", + "reference": "e7e983596b744c4539f31e79b0350a6cf5878a20", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "psr/cache": "^2.0|^3.0", + "psr/log": "^1.1|^2|^3", + "symfony/cache-contracts": "^2.5|^3", + "symfony/deprecation-contracts": "^2.5|^3.0", + "symfony/service-contracts": "^2.5|^3", + "symfony/var-exporter": "^6.4|^7.0" + }, + "conflict": { + "doctrine/dbal": "<3.6", + "symfony/dependency-injection": "<6.4", + "symfony/http-kernel": "<6.4", + "symfony/var-dumper": "<6.4" + }, + "provide": { + "psr/cache-implementation": "2.0|3.0", + "psr/simple-cache-implementation": "1.0|2.0|3.0", + "symfony/cache-implementation": "1.1|2.0|3.0" + }, + "require-dev": { + "cache/integration-tests": "dev-master", + "doctrine/dbal": "^3.6|^4", + "predis/predis": "^1.1|^2.0", + "psr/simple-cache": "^1.0|^2.0|^3.0", + "symfony/clock": "^6.4|^7.0", + "symfony/config": "^6.4|^7.0", + "symfony/dependency-injection": "^6.4|^7.0", + "symfony/filesystem": "^6.4|^7.0", + "symfony/http-kernel": "^6.4|^7.0", + "symfony/messenger": "^6.4|^7.0", + "symfony/var-dumper": "^6.4|^7.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Cache\\": "" + }, + "classmap": [ + "Traits/ValueWrapper.php" + ], + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides extended PSR-6, PSR-16 (and tags) implementations", + "homepage": "https://symfony.com", + "keywords": [ + "caching", + "psr6" + ], + "support": { + "source": "https://github.com/symfony/cache/tree/v7.2.1" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-12-07T08:08:50+00:00" + }, + { + "name": "symfony/cache-contracts", + "version": "v3.5.1", + "source": { + "type": "git", + "url": "https://github.com/symfony/cache-contracts.git", + "reference": "15a4f8e5cd3bce9aeafc882b1acab39ec8de2c1b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/cache-contracts/zipball/15a4f8e5cd3bce9aeafc882b1acab39ec8de2c1b", + "reference": "15a4f8e5cd3bce9aeafc882b1acab39ec8de2c1b", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "psr/cache": "^3.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "3.5-dev" + }, + "thanks": { + "name": "symfony/contracts", + "url": "https://github.com/symfony/contracts" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Contracts\\Cache\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Generic abstractions related to caching", + "homepage": "https://symfony.com", + "keywords": [ + "abstractions", + "contracts", + "decoupling", + "interfaces", + "interoperability", + "standards" + ], + "support": { + "source": "https://github.com/symfony/cache-contracts/tree/v3.5.1" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-09-25T14:20:29+00:00" + }, + { + "name": "symfony/deprecation-contracts", + "version": "v3.5.1", + "source": { + "type": "git", + "url": "https://github.com/symfony/deprecation-contracts.git", + "reference": "74c71c939a79f7d5bf3c1ce9f5ea37ba0114c6f6" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/74c71c939a79f7d5bf3c1ce9f5ea37ba0114c6f6", + "reference": "74c71c939a79f7d5bf3c1ce9f5ea37ba0114c6f6", + "shasum": "" + }, + "require": { + "php": ">=8.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "3.5-dev" + }, + "thanks": { + "name": "symfony/contracts", + "url": "https://github.com/symfony/contracts" + } + }, + "autoload": { + "files": [ + "function.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "A generic function and convention to trigger deprecation notices", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/deprecation-contracts/tree/v3.5.1" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-09-25T14:20:29+00:00" + }, + { + "name": "symfony/expression-language", + "version": "v6.4.13", + "source": { + "type": "git", + "url": "https://github.com/symfony/expression-language.git", + "reference": "3524904fb026356a5230cd197f9a4e6a61e0e7df" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/expression-language/zipball/3524904fb026356a5230cd197f9a4e6a61e0e7df", + "reference": "3524904fb026356a5230cd197f9a4e6a61e0e7df", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "symfony/cache": "^5.4|^6.0|^7.0", + "symfony/deprecation-contracts": "^2.5|^3", + "symfony/service-contracts": "^2.5|^3" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\ExpressionLanguage\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides an engine that can compile and evaluate expressions", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/expression-language/tree/v6.4.13" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-10-09T08:40:40+00:00" + }, + { + "name": "symfony/service-contracts", + "version": "v3.5.1", + "source": { + "type": "git", + "url": "https://github.com/symfony/service-contracts.git", + "reference": "e53260aabf78fb3d63f8d79d69ece59f80d5eda0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/service-contracts/zipball/e53260aabf78fb3d63f8d79d69ece59f80d5eda0", + "reference": "e53260aabf78fb3d63f8d79d69ece59f80d5eda0", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "psr/container": "^1.1|^2.0", + "symfony/deprecation-contracts": "^2.5|^3" + }, + "conflict": { + "ext-psr": "<1.1|>=2" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "3.5-dev" + }, + "thanks": { + "name": "symfony/contracts", + "url": "https://github.com/symfony/contracts" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Contracts\\Service\\": "" + }, + "exclude-from-classmap": [ + "/Test/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Generic abstractions related to writing services", + "homepage": "https://symfony.com", + "keywords": [ + "abstractions", + "contracts", + "decoupling", + "interfaces", + "interoperability", + "standards" + ], + "support": { + "source": "https://github.com/symfony/service-contracts/tree/v3.5.1" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-09-25T14:20:29+00:00" + }, + { + "name": "symfony/var-exporter", + "version": "v7.2.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/var-exporter.git", + "reference": "1a6a89f95a46af0f142874c9d650a6358d13070d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/var-exporter/zipball/1a6a89f95a46af0f142874c9d650a6358d13070d", + "reference": "1a6a89f95a46af0f142874c9d650a6358d13070d", + "shasum": "" + }, + "require": { + "php": ">=8.2" + }, + "require-dev": { + "symfony/property-access": "^6.4|^7.0", + "symfony/serializer": "^6.4|^7.0", + "symfony/var-dumper": "^6.4|^7.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\VarExporter\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Allows exporting any serializable PHP data structure to plain PHP code", + "homepage": "https://symfony.com", + "keywords": [ + "clone", + "construct", + "export", + "hydrate", + "instantiate", + "lazy-loading", + "proxy", + "serialize" + ], + "support": { + "source": "https://github.com/symfony/var-exporter/tree/v7.2.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-10-18T07:58:17+00:00" + } + ], + "packages-dev": [], + "aliases": [], + "minimum-stability": "stable", + "stability-flags": {}, + "prefer-stable": false, + "prefer-lowest": false, + "platform": {}, + "platform-dev": {}, + "plugin-api-version": "2.6.0" +} diff --git a/translator.php b/translator.php new file mode 100644 index 0000000..0bf721e --- /dev/null +++ b/translator.php @@ -0,0 +1,39 @@ +setlocale('fr_FR.utf8'); + +// Set default text domain +$loader->textdomain($domain); + +// Set path where to look for a domain +$loader->bindtextdomain($domain, __DIR__ . '/Locale/'); + +// Get translator +$translator = $loader->getTranslator(); + + + +// Now you can use Translator API (see below) + +echo $translator->gettext('String'); + +echo $translator->gettext('Aperture Priority'); + +//echo $translator->getTranslations(); +/* +echo $translator->setTranslations([ + 'String' => 'ChaĆ®ne', + 'Number' => 'Nombre', +]); + +echo $translator->gettext('String'); +*/ +?> diff --git a/translator_gettext.php b/translator_gettext.php new file mode 100644 index 0000000..cfffc72 --- /dev/null +++ b/translator_gettext.php @@ -0,0 +1,19 @@ + diff --git a/vendor/autoload.php b/vendor/autoload.php new file mode 100644 index 0000000..67a1231 --- /dev/null +++ b/vendor/autoload.php @@ -0,0 +1,25 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Autoload; + +/** + * ClassLoader implements a PSR-0, PSR-4 and classmap class loader. + * + * $loader = new \Composer\Autoload\ClassLoader(); + * + * // register classes with namespaces + * $loader->add('Symfony\Component', __DIR__.'/component'); + * $loader->add('Symfony', __DIR__.'/framework'); + * + * // activate the autoloader + * $loader->register(); + * + * // to enable searching the include path (eg. for PEAR packages) + * $loader->setUseIncludePath(true); + * + * In this example, if you try to use a class in the Symfony\Component + * namespace or one of its children (Symfony\Component\Console for instance), + * the autoloader will first look for the class under the component/ + * directory, and it will then fallback to the framework/ directory if not + * found before giving up. + * + * This class is loosely based on the Symfony UniversalClassLoader. + * + * @author Fabien Potencier + * @author Jordi Boggiano + * @see https://www.php-fig.org/psr/psr-0/ + * @see https://www.php-fig.org/psr/psr-4/ + */ +class ClassLoader +{ + /** @var \Closure(string):void */ + private static $includeFile; + + /** @var string|null */ + private $vendorDir; + + // PSR-4 + /** + * @var array> + */ + private $prefixLengthsPsr4 = array(); + /** + * @var array> + */ + private $prefixDirsPsr4 = array(); + /** + * @var list + */ + private $fallbackDirsPsr4 = array(); + + // PSR-0 + /** + * List of PSR-0 prefixes + * + * Structured as array('F (first letter)' => array('Foo\Bar (full prefix)' => array('path', 'path2'))) + * + * @var array>> + */ + private $prefixesPsr0 = array(); + /** + * @var list + */ + private $fallbackDirsPsr0 = array(); + + /** @var bool */ + private $useIncludePath = false; + + /** + * @var array + */ + private $classMap = array(); + + /** @var bool */ + private $classMapAuthoritative = false; + + /** + * @var array + */ + private $missingClasses = array(); + + /** @var string|null */ + private $apcuPrefix; + + /** + * @var array + */ + private static $registeredLoaders = array(); + + /** + * @param string|null $vendorDir + */ + public function __construct($vendorDir = null) + { + $this->vendorDir = $vendorDir; + self::initializeIncludeClosure(); + } + + /** + * @return array> + */ + public function getPrefixes() + { + if (!empty($this->prefixesPsr0)) { + return call_user_func_array('array_merge', array_values($this->prefixesPsr0)); + } + + return array(); + } + + /** + * @return array> + */ + public function getPrefixesPsr4() + { + return $this->prefixDirsPsr4; + } + + /** + * @return list + */ + public function getFallbackDirs() + { + return $this->fallbackDirsPsr0; + } + + /** + * @return list + */ + public function getFallbackDirsPsr4() + { + return $this->fallbackDirsPsr4; + } + + /** + * @return array Array of classname => path + */ + public function getClassMap() + { + return $this->classMap; + } + + /** + * @param array $classMap Class to filename map + * + * @return void + */ + public function addClassMap(array $classMap) + { + if ($this->classMap) { + $this->classMap = array_merge($this->classMap, $classMap); + } else { + $this->classMap = $classMap; + } + } + + /** + * Registers a set of PSR-0 directories for a given prefix, either + * appending or prepending to the ones previously set for this prefix. + * + * @param string $prefix The prefix + * @param list|string $paths The PSR-0 root directories + * @param bool $prepend Whether to prepend the directories + * + * @return void + */ + public function add($prefix, $paths, $prepend = false) + { + $paths = (array) $paths; + if (!$prefix) { + if ($prepend) { + $this->fallbackDirsPsr0 = array_merge( + $paths, + $this->fallbackDirsPsr0 + ); + } else { + $this->fallbackDirsPsr0 = array_merge( + $this->fallbackDirsPsr0, + $paths + ); + } + + return; + } + + $first = $prefix[0]; + if (!isset($this->prefixesPsr0[$first][$prefix])) { + $this->prefixesPsr0[$first][$prefix] = $paths; + + return; + } + if ($prepend) { + $this->prefixesPsr0[$first][$prefix] = array_merge( + $paths, + $this->prefixesPsr0[$first][$prefix] + ); + } else { + $this->prefixesPsr0[$first][$prefix] = array_merge( + $this->prefixesPsr0[$first][$prefix], + $paths + ); + } + } + + /** + * Registers a set of PSR-4 directories for a given namespace, either + * appending or prepending to the ones previously set for this namespace. + * + * @param string $prefix The prefix/namespace, with trailing '\\' + * @param list|string $paths The PSR-4 base directories + * @param bool $prepend Whether to prepend the directories + * + * @throws \InvalidArgumentException + * + * @return void + */ + public function addPsr4($prefix, $paths, $prepend = false) + { + $paths = (array) $paths; + if (!$prefix) { + // Register directories for the root namespace. + if ($prepend) { + $this->fallbackDirsPsr4 = array_merge( + $paths, + $this->fallbackDirsPsr4 + ); + } else { + $this->fallbackDirsPsr4 = array_merge( + $this->fallbackDirsPsr4, + $paths + ); + } + } elseif (!isset($this->prefixDirsPsr4[$prefix])) { + // Register directories for a new namespace. + $length = strlen($prefix); + if ('\\' !== $prefix[$length - 1]) { + throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator."); + } + $this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length; + $this->prefixDirsPsr4[$prefix] = $paths; + } elseif ($prepend) { + // Prepend directories for an already registered namespace. + $this->prefixDirsPsr4[$prefix] = array_merge( + $paths, + $this->prefixDirsPsr4[$prefix] + ); + } else { + // Append directories for an already registered namespace. + $this->prefixDirsPsr4[$prefix] = array_merge( + $this->prefixDirsPsr4[$prefix], + $paths + ); + } + } + + /** + * Registers a set of PSR-0 directories for a given prefix, + * replacing any others previously set for this prefix. + * + * @param string $prefix The prefix + * @param list|string $paths The PSR-0 base directories + * + * @return void + */ + public function set($prefix, $paths) + { + if (!$prefix) { + $this->fallbackDirsPsr0 = (array) $paths; + } else { + $this->prefixesPsr0[$prefix[0]][$prefix] = (array) $paths; + } + } + + /** + * Registers a set of PSR-4 directories for a given namespace, + * replacing any others previously set for this namespace. + * + * @param string $prefix The prefix/namespace, with trailing '\\' + * @param list|string $paths The PSR-4 base directories + * + * @throws \InvalidArgumentException + * + * @return void + */ + public function setPsr4($prefix, $paths) + { + if (!$prefix) { + $this->fallbackDirsPsr4 = (array) $paths; + } else { + $length = strlen($prefix); + if ('\\' !== $prefix[$length - 1]) { + throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator."); + } + $this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length; + $this->prefixDirsPsr4[$prefix] = (array) $paths; + } + } + + /** + * Turns on searching the include path for class files. + * + * @param bool $useIncludePath + * + * @return void + */ + public function setUseIncludePath($useIncludePath) + { + $this->useIncludePath = $useIncludePath; + } + + /** + * Can be used to check if the autoloader uses the include path to check + * for classes. + * + * @return bool + */ + public function getUseIncludePath() + { + return $this->useIncludePath; + } + + /** + * Turns off searching the prefix and fallback directories for classes + * that have not been registered with the class map. + * + * @param bool $classMapAuthoritative + * + * @return void + */ + public function setClassMapAuthoritative($classMapAuthoritative) + { + $this->classMapAuthoritative = $classMapAuthoritative; + } + + /** + * Should class lookup fail if not found in the current class map? + * + * @return bool + */ + public function isClassMapAuthoritative() + { + return $this->classMapAuthoritative; + } + + /** + * APCu prefix to use to cache found/not-found classes, if the extension is enabled. + * + * @param string|null $apcuPrefix + * + * @return void + */ + public function setApcuPrefix($apcuPrefix) + { + $this->apcuPrefix = function_exists('apcu_fetch') && filter_var(ini_get('apc.enabled'), FILTER_VALIDATE_BOOLEAN) ? $apcuPrefix : null; + } + + /** + * The APCu prefix in use, or null if APCu caching is not enabled. + * + * @return string|null + */ + public function getApcuPrefix() + { + return $this->apcuPrefix; + } + + /** + * Registers this instance as an autoloader. + * + * @param bool $prepend Whether to prepend the autoloader or not + * + * @return void + */ + public function register($prepend = false) + { + spl_autoload_register(array($this, 'loadClass'), true, $prepend); + + if (null === $this->vendorDir) { + return; + } + + if ($prepend) { + self::$registeredLoaders = array($this->vendorDir => $this) + self::$registeredLoaders; + } else { + unset(self::$registeredLoaders[$this->vendorDir]); + self::$registeredLoaders[$this->vendorDir] = $this; + } + } + + /** + * Unregisters this instance as an autoloader. + * + * @return void + */ + public function unregister() + { + spl_autoload_unregister(array($this, 'loadClass')); + + if (null !== $this->vendorDir) { + unset(self::$registeredLoaders[$this->vendorDir]); + } + } + + /** + * Loads the given class or interface. + * + * @param string $class The name of the class + * @return true|null True if loaded, null otherwise + */ + public function loadClass($class) + { + if ($file = $this->findFile($class)) { + $includeFile = self::$includeFile; + $includeFile($file); + + return true; + } + + return null; + } + + /** + * Finds the path to the file where the class is defined. + * + * @param string $class The name of the class + * + * @return string|false The path if found, false otherwise + */ + public function findFile($class) + { + // class map lookup + if (isset($this->classMap[$class])) { + return $this->classMap[$class]; + } + if ($this->classMapAuthoritative || isset($this->missingClasses[$class])) { + return false; + } + if (null !== $this->apcuPrefix) { + $file = apcu_fetch($this->apcuPrefix.$class, $hit); + if ($hit) { + return $file; + } + } + + $file = $this->findFileWithExtension($class, '.php'); + + // Search for Hack files if we are running on HHVM + if (false === $file && defined('HHVM_VERSION')) { + $file = $this->findFileWithExtension($class, '.hh'); + } + + if (null !== $this->apcuPrefix) { + apcu_add($this->apcuPrefix.$class, $file); + } + + if (false === $file) { + // Remember that this class does not exist. + $this->missingClasses[$class] = true; + } + + return $file; + } + + /** + * Returns the currently registered loaders keyed by their corresponding vendor directories. + * + * @return array + */ + public static function getRegisteredLoaders() + { + return self::$registeredLoaders; + } + + /** + * @param string $class + * @param string $ext + * @return string|false + */ + private function findFileWithExtension($class, $ext) + { + // PSR-4 lookup + $logicalPathPsr4 = strtr($class, '\\', DIRECTORY_SEPARATOR) . $ext; + + $first = $class[0]; + if (isset($this->prefixLengthsPsr4[$first])) { + $subPath = $class; + while (false !== $lastPos = strrpos($subPath, '\\')) { + $subPath = substr($subPath, 0, $lastPos); + $search = $subPath . '\\'; + if (isset($this->prefixDirsPsr4[$search])) { + $pathEnd = DIRECTORY_SEPARATOR . substr($logicalPathPsr4, $lastPos + 1); + foreach ($this->prefixDirsPsr4[$search] as $dir) { + if (file_exists($file = $dir . $pathEnd)) { + return $file; + } + } + } + } + } + + // PSR-4 fallback dirs + foreach ($this->fallbackDirsPsr4 as $dir) { + if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr4)) { + return $file; + } + } + + // PSR-0 lookup + if (false !== $pos = strrpos($class, '\\')) { + // namespaced class name + $logicalPathPsr0 = substr($logicalPathPsr4, 0, $pos + 1) + . strtr(substr($logicalPathPsr4, $pos + 1), '_', DIRECTORY_SEPARATOR); + } else { + // PEAR-like class name + $logicalPathPsr0 = strtr($class, '_', DIRECTORY_SEPARATOR) . $ext; + } + + if (isset($this->prefixesPsr0[$first])) { + foreach ($this->prefixesPsr0[$first] as $prefix => $dirs) { + if (0 === strpos($class, $prefix)) { + foreach ($dirs as $dir) { + if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) { + return $file; + } + } + } + } + } + + // PSR-0 fallback dirs + foreach ($this->fallbackDirsPsr0 as $dir) { + if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) { + return $file; + } + } + + // PSR-0 include paths. + if ($this->useIncludePath && $file = stream_resolve_include_path($logicalPathPsr0)) { + return $file; + } + + return false; + } + + /** + * @return void + */ + private static function initializeIncludeClosure() + { + if (self::$includeFile !== null) { + return; + } + + /** + * Scope isolated include. + * + * Prevents access to $this/self from included files. + * + * @param string $file + * @return void + */ + self::$includeFile = \Closure::bind(static function($file) { + include $file; + }, null, null); + } +} diff --git a/vendor/composer/InstalledVersions.php b/vendor/composer/InstalledVersions.php new file mode 100644 index 0000000..07b32ed --- /dev/null +++ b/vendor/composer/InstalledVersions.php @@ -0,0 +1,362 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer; + +use Composer\Autoload\ClassLoader; +use Composer\Semver\VersionParser; + +/** + * This class is copied in every Composer installed project and available to all + * + * See also https://getcomposer.org/doc/07-runtime.md#installed-versions + * + * To require its presence, you can require `composer-runtime-api ^2.0` + * + * @final + */ +class InstalledVersions +{ + /** + * @var mixed[]|null + * @psalm-var array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array}|array{}|null + */ + private static $installed; + + /** + * @var bool|null + */ + private static $canGetVendors; + + /** + * @var array[] + * @psalm-var array}> + */ + private static $installedByVendor = array(); + + /** + * Returns a list of all package names which are present, either by being installed, replaced or provided + * + * @return string[] + * @psalm-return list + */ + public static function getInstalledPackages() + { + $packages = array(); + foreach (self::getInstalled() as $installed) { + $packages[] = array_keys($installed['versions']); + } + + if (1 === \count($packages)) { + return $packages[0]; + } + + return array_keys(array_flip(\call_user_func_array('array_merge', $packages))); + } + + /** + * Returns a list of all package names with a specific type e.g. 'library' + * + * @param string $type + * @return string[] + * @psalm-return list + */ + public static function getInstalledPackagesByType($type) + { + $packagesByType = array(); + + foreach (self::getInstalled() as $installed) { + foreach ($installed['versions'] as $name => $package) { + if (isset($package['type']) && $package['type'] === $type) { + $packagesByType[] = $name; + } + } + } + + return $packagesByType; + } + + /** + * Checks whether the given package is installed + * + * This also returns true if the package name is provided or replaced by another package + * + * @param string $packageName + * @param bool $includeDevRequirements + * @return bool + */ + public static function isInstalled($packageName, $includeDevRequirements = true) + { + foreach (self::getInstalled() as $installed) { + if (isset($installed['versions'][$packageName])) { + return $includeDevRequirements || !isset($installed['versions'][$packageName]['dev_requirement']) || $installed['versions'][$packageName]['dev_requirement'] === false; + } + } + + return false; + } + + /** + * Checks whether the given package satisfies a version constraint + * + * e.g. If you want to know whether version 2.3+ of package foo/bar is installed, you would call: + * + * Composer\InstalledVersions::satisfies(new VersionParser, 'foo/bar', '^2.3') + * + * @param VersionParser $parser Install composer/semver to have access to this class and functionality + * @param string $packageName + * @param string|null $constraint A version constraint to check for, if you pass one you have to make sure composer/semver is required by your package + * @return bool + */ + public static function satisfies(VersionParser $parser, $packageName, $constraint) + { + $constraint = $parser->parseConstraints((string) $constraint); + $provided = $parser->parseConstraints(self::getVersionRanges($packageName)); + + return $provided->matches($constraint); + } + + /** + * Returns a version constraint representing all the range(s) which are installed for a given package + * + * It is easier to use this via isInstalled() with the $constraint argument if you need to check + * whether a given version of a package is installed, and not just whether it exists + * + * @param string $packageName + * @return string Version constraint usable with composer/semver + */ + public static function getVersionRanges($packageName) + { + foreach (self::getInstalled() as $installed) { + if (!isset($installed['versions'][$packageName])) { + continue; + } + + $ranges = array(); + if (isset($installed['versions'][$packageName]['pretty_version'])) { + $ranges[] = $installed['versions'][$packageName]['pretty_version']; + } + if (array_key_exists('aliases', $installed['versions'][$packageName])) { + $ranges = array_merge($ranges, $installed['versions'][$packageName]['aliases']); + } + if (array_key_exists('replaced', $installed['versions'][$packageName])) { + $ranges = array_merge($ranges, $installed['versions'][$packageName]['replaced']); + } + if (array_key_exists('provided', $installed['versions'][$packageName])) { + $ranges = array_merge($ranges, $installed['versions'][$packageName]['provided']); + } + + return implode(' || ', $ranges); + } + + throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed'); + } + + /** + * @param string $packageName + * @return string|null If the package is being replaced or provided but is not really installed, null will be returned as version, use satisfies or getVersionRanges if you need to know if a given version is present + */ + public static function getVersion($packageName) + { + foreach (self::getInstalled() as $installed) { + if (!isset($installed['versions'][$packageName])) { + continue; + } + + if (!isset($installed['versions'][$packageName]['version'])) { + return null; + } + + return $installed['versions'][$packageName]['version']; + } + + throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed'); + } + + /** + * @param string $packageName + * @return string|null If the package is being replaced or provided but is not really installed, null will be returned as version, use satisfies or getVersionRanges if you need to know if a given version is present + */ + public static function getPrettyVersion($packageName) + { + foreach (self::getInstalled() as $installed) { + if (!isset($installed['versions'][$packageName])) { + continue; + } + + if (!isset($installed['versions'][$packageName]['pretty_version'])) { + return null; + } + + return $installed['versions'][$packageName]['pretty_version']; + } + + throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed'); + } + + /** + * @param string $packageName + * @return string|null If the package is being replaced or provided but is not really installed, null will be returned as reference + */ + public static function getReference($packageName) + { + foreach (self::getInstalled() as $installed) { + if (!isset($installed['versions'][$packageName])) { + continue; + } + + if (!isset($installed['versions'][$packageName]['reference'])) { + return null; + } + + return $installed['versions'][$packageName]['reference']; + } + + throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed'); + } + + /** + * @param string $packageName + * @return string|null If the package is being replaced or provided but is not really installed, null will be returned as install path. Packages of type metapackages also have a null install path. + */ + public static function getInstallPath($packageName) + { + foreach (self::getInstalled() as $installed) { + if (!isset($installed['versions'][$packageName])) { + continue; + } + + return isset($installed['versions'][$packageName]['install_path']) ? $installed['versions'][$packageName]['install_path'] : null; + } + + throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed'); + } + + /** + * @return array + * @psalm-return array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool} + */ + public static function getRootPackage() + { + $installed = self::getInstalled(); + + return $installed[0]['root']; + } + + /** + * Returns the raw installed.php data for custom implementations + * + * @deprecated Use getAllRawData() instead which returns all datasets for all autoloaders present in the process. getRawData only returns the first dataset loaded, which may not be what you expect. + * @return array[] + * @psalm-return array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array} + */ + public static function getRawData() + { + @trigger_error('getRawData only returns the first dataset loaded, which may not be what you expect. Use getAllRawData() instead which returns all datasets for all autoloaders present in the process.', E_USER_DEPRECATED); + + if (null === self::$installed) { + // only require the installed.php file if this file is loaded from its dumped location, + // and not from its source location in the composer/composer package, see https://github.com/composer/composer/issues/9937 + if (substr(__DIR__, -8, 1) !== 'C') { + self::$installed = include __DIR__ . '/installed.php'; + } else { + self::$installed = array(); + } + } + + return self::$installed; + } + + /** + * Returns the raw data of all installed.php which are currently loaded for custom implementations + * + * @return array[] + * @psalm-return list}> + */ + public static function getAllRawData() + { + return self::getInstalled(); + } + + /** + * Lets you reload the static array from another file + * + * This is only useful for complex integrations in which a project needs to use + * this class but then also needs to execute another project's autoloader in process, + * and wants to ensure both projects have access to their version of installed.php. + * + * A typical case would be PHPUnit, where it would need to make sure it reads all + * the data it needs from this class, then call reload() with + * `require $CWD/vendor/composer/installed.php` (or similar) as input to make sure + * the project in which it runs can then also use this class safely, without + * interference between PHPUnit's dependencies and the project's dependencies. + * + * @param array[] $data A vendor/composer/installed.php data set + * @return void + * + * @psalm-param array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array} $data + */ + public static function reload($data) + { + self::$installed = $data; + self::$installedByVendor = array(); + } + + /** + * @return array[] + * @psalm-return list}> + */ + private static function getInstalled() + { + if (null === self::$canGetVendors) { + self::$canGetVendors = method_exists('Composer\Autoload\ClassLoader', 'getRegisteredLoaders'); + } + + $installed = array(); + $copiedLocalDir = false; + + if (self::$canGetVendors) { + foreach (ClassLoader::getRegisteredLoaders() as $vendorDir => $loader) { + if (isset(self::$installedByVendor[$vendorDir])) { + $installed[] = self::$installedByVendor[$vendorDir]; + } elseif (is_file($vendorDir.'/composer/installed.php')) { + /** @var array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array} $required */ + $required = require $vendorDir.'/composer/installed.php'; + self::$installedByVendor[$vendorDir] = $required; + $installed[] = $required; + if (strtr($vendorDir.'/composer', '\\', '/') === strtr(__DIR__, '\\', '/')) { + self::$installed = $required; + $copiedLocalDir = true; + } + } + } + } + + if (null === self::$installed) { + // only require the installed.php file if this file is loaded from its dumped location, + // and not from its source location in the composer/composer package, see https://github.com/composer/composer/issues/9937 + if (substr(__DIR__, -8, 1) !== 'C') { + /** @var array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array} $required */ + $required = require __DIR__ . '/installed.php'; + self::$installed = $required; + } else { + self::$installed = array(); + } + } + + if (self::$installed !== array() && !$copiedLocalDir) { + $installed[] = self::$installed; + } + + return $installed; + } +} diff --git a/vendor/composer/LICENSE b/vendor/composer/LICENSE new file mode 100644 index 0000000..f27399a --- /dev/null +++ b/vendor/composer/LICENSE @@ -0,0 +1,21 @@ + +Copyright (c) Nils Adermann, Jordi Boggiano + +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/vendor/composer/autoload_classmap.php b/vendor/composer/autoload_classmap.php new file mode 100644 index 0000000..322ba01 --- /dev/null +++ b/vendor/composer/autoload_classmap.php @@ -0,0 +1,11 @@ + $vendorDir . '/composer/InstalledVersions.php', + '©' => $vendorDir . '/symfony/cache/Traits/ValueWrapper.php', +); diff --git a/vendor/composer/autoload_files.php b/vendor/composer/autoload_files.php new file mode 100644 index 0000000..be8e1c3 --- /dev/null +++ b/vendor/composer/autoload_files.php @@ -0,0 +1,10 @@ + $vendorDir . '/symfony/deprecation-contracts/function.php', +); diff --git a/vendor/composer/autoload_namespaces.php b/vendor/composer/autoload_namespaces.php new file mode 100644 index 0000000..15a2ff3 --- /dev/null +++ b/vendor/composer/autoload_namespaces.php @@ -0,0 +1,9 @@ + array($vendorDir . '/symfony/service-contracts'), + 'Symfony\\Contracts\\Cache\\' => array($vendorDir . '/symfony/cache-contracts'), + 'Symfony\\Component\\VarExporter\\' => array($vendorDir . '/symfony/var-exporter'), + 'Symfony\\Component\\ExpressionLanguage\\' => array($vendorDir . '/symfony/expression-language'), + 'Symfony\\Component\\Cache\\' => array($vendorDir . '/symfony/cache'), + 'Psr\\Log\\' => array($vendorDir . '/psr/log/src'), + 'Psr\\Container\\' => array($vendorDir . '/psr/container/src'), + 'Psr\\Cache\\' => array($vendorDir . '/psr/cache/src'), + 'PhpMyAdmin\\MoTranslator\\' => array($vendorDir . '/phpmyadmin/motranslator/src'), +); diff --git a/vendor/composer/autoload_real.php b/vendor/composer/autoload_real.php new file mode 100644 index 0000000..c17a605 --- /dev/null +++ b/vendor/composer/autoload_real.php @@ -0,0 +1,50 @@ +register(true); + + $filesToLoad = \Composer\Autoload\ComposerStaticInitf0f23926796d6825a9caf18ca123a270::$files; + $requireFile = \Closure::bind(static function ($fileIdentifier, $file) { + if (empty($GLOBALS['__composer_autoload_files'][$fileIdentifier])) { + $GLOBALS['__composer_autoload_files'][$fileIdentifier] = true; + + require $file; + } + }, null, null); + foreach ($filesToLoad as $fileIdentifier => $file) { + $requireFile($fileIdentifier, $file); + } + + return $loader; + } +} diff --git a/vendor/composer/autoload_static.php b/vendor/composer/autoload_static.php new file mode 100644 index 0000000..cb74c64 --- /dev/null +++ b/vendor/composer/autoload_static.php @@ -0,0 +1,84 @@ + __DIR__ . '/..' . '/symfony/deprecation-contracts/function.php', + ); + + public static $prefixLengthsPsr4 = array ( + 'S' => + array ( + 'Symfony\\Contracts\\Service\\' => 26, + 'Symfony\\Contracts\\Cache\\' => 24, + 'Symfony\\Component\\VarExporter\\' => 30, + 'Symfony\\Component\\ExpressionLanguage\\' => 37, + 'Symfony\\Component\\Cache\\' => 24, + ), + 'P' => + array ( + 'Psr\\Log\\' => 8, + 'Psr\\Container\\' => 14, + 'Psr\\Cache\\' => 10, + 'PhpMyAdmin\\MoTranslator\\' => 24, + ), + ); + + public static $prefixDirsPsr4 = array ( + 'Symfony\\Contracts\\Service\\' => + array ( + 0 => __DIR__ . '/..' . '/symfony/service-contracts', + ), + 'Symfony\\Contracts\\Cache\\' => + array ( + 0 => __DIR__ . '/..' . '/symfony/cache-contracts', + ), + 'Symfony\\Component\\VarExporter\\' => + array ( + 0 => __DIR__ . '/..' . '/symfony/var-exporter', + ), + 'Symfony\\Component\\ExpressionLanguage\\' => + array ( + 0 => __DIR__ . '/..' . '/symfony/expression-language', + ), + 'Symfony\\Component\\Cache\\' => + array ( + 0 => __DIR__ . '/..' . '/symfony/cache', + ), + 'Psr\\Log\\' => + array ( + 0 => __DIR__ . '/..' . '/psr/log/src', + ), + 'Psr\\Container\\' => + array ( + 0 => __DIR__ . '/..' . '/psr/container/src', + ), + 'Psr\\Cache\\' => + array ( + 0 => __DIR__ . '/..' . '/psr/cache/src', + ), + 'PhpMyAdmin\\MoTranslator\\' => + array ( + 0 => __DIR__ . '/..' . '/phpmyadmin/motranslator/src', + ), + ); + + public static $classMap = array ( + 'Composer\\InstalledVersions' => __DIR__ . '/..' . '/composer/InstalledVersions.php', + '©' => __DIR__ . '/..' . '/symfony/cache/Traits/ValueWrapper.php', + ); + + public static function getInitializer(ClassLoader $loader) + { + return \Closure::bind(function () use ($loader) { + $loader->prefixLengthsPsr4 = ComposerStaticInitf0f23926796d6825a9caf18ca123a270::$prefixLengthsPsr4; + $loader->prefixDirsPsr4 = ComposerStaticInitf0f23926796d6825a9caf18ca123a270::$prefixDirsPsr4; + $loader->classMap = ComposerStaticInitf0f23926796d6825a9caf18ca123a270::$classMap; + + }, null, ClassLoader::class); + } +} diff --git a/vendor/composer/installed.json b/vendor/composer/installed.json new file mode 100644 index 0000000..e309fbb --- /dev/null +++ b/vendor/composer/installed.json @@ -0,0 +1,709 @@ +{ + "packages": [ + { + "name": "phpmyadmin/motranslator", + "version": "5.3.1", + "version_normalized": "5.3.1.0", + "source": { + "type": "git", + "url": "https://github.com/phpmyadmin/motranslator.git", + "reference": "d03b4d9c608e7265091bf6decc05323d16c7c047" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpmyadmin/motranslator/zipball/d03b4d9c608e7265091bf6decc05323d16c7c047", + "reference": "d03b4d9c608e7265091bf6decc05323d16c7c047", + "shasum": "" + }, + "require": { + "php": "^7.1 || ^8.0", + "symfony/expression-language": "^4.0 || ^5.0 || ^6.0" + }, + "require-dev": { + "phpmyadmin/coding-standard": "^3.0.0", + "phpstan/phpstan": "^1.4.6", + "phpunit/phpunit": "^7.4 || ^8 || ^9" + }, + "suggest": { + "ext-apcu": "Needed for ACPu-backed translation cache" + }, + "time": "2023-08-23T18:32:07+00:00", + "type": "library", + "installation-source": "dist", + "autoload": { + "psr-4": { + "PhpMyAdmin\\MoTranslator\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "GPL-2.0-or-later" + ], + "authors": [ + { + "name": "The phpMyAdmin Team", + "email": "developers@phpmyadmin.net", + "homepage": "https://www.phpmyadmin.net/team/" + } + ], + "description": "Translation API for PHP using Gettext MO files", + "homepage": "https://github.com/phpmyadmin/motranslator", + "keywords": [ + "gettext", + "i18n", + "mo", + "translator" + ], + "support": { + "issues": "https://github.com/phpmyadmin/motranslator/issues", + "source": "https://github.com/phpmyadmin/motranslator" + }, + "install-path": "../phpmyadmin/motranslator" + }, + { + "name": "psr/cache", + "version": "3.0.0", + "version_normalized": "3.0.0.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/cache.git", + "reference": "aa5030cfa5405eccfdcb1083ce040c2cb8d253bf" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/cache/zipball/aa5030cfa5405eccfdcb1083ce040c2cb8d253bf", + "reference": "aa5030cfa5405eccfdcb1083ce040c2cb8d253bf", + "shasum": "" + }, + "require": { + "php": ">=8.0.0" + }, + "time": "2021-02-03T23:26:27+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "Psr\\Cache\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interface for caching libraries", + "keywords": [ + "cache", + "psr", + "psr-6" + ], + "support": { + "source": "https://github.com/php-fig/cache/tree/3.0.0" + }, + "install-path": "../psr/cache" + }, + { + "name": "psr/container", + "version": "2.0.2", + "version_normalized": "2.0.2.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/container.git", + "reference": "c71ecc56dfe541dbd90c5360474fbc405f8d5963" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/container/zipball/c71ecc56dfe541dbd90c5360474fbc405f8d5963", + "reference": "c71ecc56dfe541dbd90c5360474fbc405f8d5963", + "shasum": "" + }, + "require": { + "php": ">=7.4.0" + }, + "time": "2021-11-05T16:47:00+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "Psr\\Container\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common Container Interface (PHP FIG PSR-11)", + "homepage": "https://github.com/php-fig/container", + "keywords": [ + "PSR-11", + "container", + "container-interface", + "container-interop", + "psr" + ], + "support": { + "issues": "https://github.com/php-fig/container/issues", + "source": "https://github.com/php-fig/container/tree/2.0.2" + }, + "install-path": "../psr/container" + }, + { + "name": "psr/log", + "version": "3.0.2", + "version_normalized": "3.0.2.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/log.git", + "reference": "f16e1d5863e37f8d8c2a01719f5b34baa2b714d3" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/log/zipball/f16e1d5863e37f8d8c2a01719f5b34baa2b714d3", + "reference": "f16e1d5863e37f8d8c2a01719f5b34baa2b714d3", + "shasum": "" + }, + "require": { + "php": ">=8.0.0" + }, + "time": "2024-09-11T13:17:53+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.x-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "Psr\\Log\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interface for logging libraries", + "homepage": "https://github.com/php-fig/log", + "keywords": [ + "log", + "psr", + "psr-3" + ], + "support": { + "source": "https://github.com/php-fig/log/tree/3.0.2" + }, + "install-path": "../psr/log" + }, + { + "name": "symfony/cache", + "version": "v7.2.1", + "version_normalized": "7.2.1.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/cache.git", + "reference": "e7e983596b744c4539f31e79b0350a6cf5878a20" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/cache/zipball/e7e983596b744c4539f31e79b0350a6cf5878a20", + "reference": "e7e983596b744c4539f31e79b0350a6cf5878a20", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "psr/cache": "^2.0|^3.0", + "psr/log": "^1.1|^2|^3", + "symfony/cache-contracts": "^2.5|^3", + "symfony/deprecation-contracts": "^2.5|^3.0", + "symfony/service-contracts": "^2.5|^3", + "symfony/var-exporter": "^6.4|^7.0" + }, + "conflict": { + "doctrine/dbal": "<3.6", + "symfony/dependency-injection": "<6.4", + "symfony/http-kernel": "<6.4", + "symfony/var-dumper": "<6.4" + }, + "provide": { + "psr/cache-implementation": "2.0|3.0", + "psr/simple-cache-implementation": "1.0|2.0|3.0", + "symfony/cache-implementation": "1.1|2.0|3.0" + }, + "require-dev": { + "cache/integration-tests": "dev-master", + "doctrine/dbal": "^3.6|^4", + "predis/predis": "^1.1|^2.0", + "psr/simple-cache": "^1.0|^2.0|^3.0", + "symfony/clock": "^6.4|^7.0", + "symfony/config": "^6.4|^7.0", + "symfony/dependency-injection": "^6.4|^7.0", + "symfony/filesystem": "^6.4|^7.0", + "symfony/http-kernel": "^6.4|^7.0", + "symfony/messenger": "^6.4|^7.0", + "symfony/var-dumper": "^6.4|^7.0" + }, + "time": "2024-12-07T08:08:50+00:00", + "type": "library", + "installation-source": "dist", + "autoload": { + "psr-4": { + "Symfony\\Component\\Cache\\": "" + }, + "classmap": [ + "Traits/ValueWrapper.php" + ], + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides extended PSR-6, PSR-16 (and tags) implementations", + "homepage": "https://symfony.com", + "keywords": [ + "caching", + "psr6" + ], + "support": { + "source": "https://github.com/symfony/cache/tree/v7.2.1" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "install-path": "../symfony/cache" + }, + { + "name": "symfony/cache-contracts", + "version": "v3.5.1", + "version_normalized": "3.5.1.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/cache-contracts.git", + "reference": "15a4f8e5cd3bce9aeafc882b1acab39ec8de2c1b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/cache-contracts/zipball/15a4f8e5cd3bce9aeafc882b1acab39ec8de2c1b", + "reference": "15a4f8e5cd3bce9aeafc882b1acab39ec8de2c1b", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "psr/cache": "^3.0" + }, + "time": "2024-09-25T14:20:29+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "3.5-dev" + }, + "thanks": { + "name": "symfony/contracts", + "url": "https://github.com/symfony/contracts" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "Symfony\\Contracts\\Cache\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Generic abstractions related to caching", + "homepage": "https://symfony.com", + "keywords": [ + "abstractions", + "contracts", + "decoupling", + "interfaces", + "interoperability", + "standards" + ], + "support": { + "source": "https://github.com/symfony/cache-contracts/tree/v3.5.1" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "install-path": "../symfony/cache-contracts" + }, + { + "name": "symfony/deprecation-contracts", + "version": "v3.5.1", + "version_normalized": "3.5.1.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/deprecation-contracts.git", + "reference": "74c71c939a79f7d5bf3c1ce9f5ea37ba0114c6f6" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/74c71c939a79f7d5bf3c1ce9f5ea37ba0114c6f6", + "reference": "74c71c939a79f7d5bf3c1ce9f5ea37ba0114c6f6", + "shasum": "" + }, + "require": { + "php": ">=8.1" + }, + "time": "2024-09-25T14:20:29+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "3.5-dev" + }, + "thanks": { + "name": "symfony/contracts", + "url": "https://github.com/symfony/contracts" + } + }, + "installation-source": "dist", + "autoload": { + "files": [ + "function.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "A generic function and convention to trigger deprecation notices", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/deprecation-contracts/tree/v3.5.1" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "install-path": "../symfony/deprecation-contracts" + }, + { + "name": "symfony/expression-language", + "version": "v6.4.13", + "version_normalized": "6.4.13.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/expression-language.git", + "reference": "3524904fb026356a5230cd197f9a4e6a61e0e7df" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/expression-language/zipball/3524904fb026356a5230cd197f9a4e6a61e0e7df", + "reference": "3524904fb026356a5230cd197f9a4e6a61e0e7df", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "symfony/cache": "^5.4|^6.0|^7.0", + "symfony/deprecation-contracts": "^2.5|^3", + "symfony/service-contracts": "^2.5|^3" + }, + "time": "2024-10-09T08:40:40+00:00", + "type": "library", + "installation-source": "dist", + "autoload": { + "psr-4": { + "Symfony\\Component\\ExpressionLanguage\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides an engine that can compile and evaluate expressions", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/expression-language/tree/v6.4.13" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "install-path": "../symfony/expression-language" + }, + { + "name": "symfony/service-contracts", + "version": "v3.5.1", + "version_normalized": "3.5.1.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/service-contracts.git", + "reference": "e53260aabf78fb3d63f8d79d69ece59f80d5eda0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/service-contracts/zipball/e53260aabf78fb3d63f8d79d69ece59f80d5eda0", + "reference": "e53260aabf78fb3d63f8d79d69ece59f80d5eda0", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "psr/container": "^1.1|^2.0", + "symfony/deprecation-contracts": "^2.5|^3" + }, + "conflict": { + "ext-psr": "<1.1|>=2" + }, + "time": "2024-09-25T14:20:29+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "3.5-dev" + }, + "thanks": { + "name": "symfony/contracts", + "url": "https://github.com/symfony/contracts" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "Symfony\\Contracts\\Service\\": "" + }, + "exclude-from-classmap": [ + "/Test/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Generic abstractions related to writing services", + "homepage": "https://symfony.com", + "keywords": [ + "abstractions", + "contracts", + "decoupling", + "interfaces", + "interoperability", + "standards" + ], + "support": { + "source": "https://github.com/symfony/service-contracts/tree/v3.5.1" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "install-path": "../symfony/service-contracts" + }, + { + "name": "symfony/var-exporter", + "version": "v7.2.0", + "version_normalized": "7.2.0.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/var-exporter.git", + "reference": "1a6a89f95a46af0f142874c9d650a6358d13070d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/var-exporter/zipball/1a6a89f95a46af0f142874c9d650a6358d13070d", + "reference": "1a6a89f95a46af0f142874c9d650a6358d13070d", + "shasum": "" + }, + "require": { + "php": ">=8.2" + }, + "require-dev": { + "symfony/property-access": "^6.4|^7.0", + "symfony/serializer": "^6.4|^7.0", + "symfony/var-dumper": "^6.4|^7.0" + }, + "time": "2024-10-18T07:58:17+00:00", + "type": "library", + "installation-source": "dist", + "autoload": { + "psr-4": { + "Symfony\\Component\\VarExporter\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Allows exporting any serializable PHP data structure to plain PHP code", + "homepage": "https://symfony.com", + "keywords": [ + "clone", + "construct", + "export", + "hydrate", + "instantiate", + "lazy-loading", + "proxy", + "serialize" + ], + "support": { + "source": "https://github.com/symfony/var-exporter/tree/v7.2.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "install-path": "../symfony/var-exporter" + } + ], + "dev": true, + "dev-package-names": [] +} diff --git a/vendor/composer/installed.php b/vendor/composer/installed.php new file mode 100644 index 0000000..d3fbad4 --- /dev/null +++ b/vendor/composer/installed.php @@ -0,0 +1,131 @@ + array( + 'name' => '__root__', + 'pretty_version' => 'dev-master', + 'version' => 'dev-master', + 'reference' => '4c22f27eb63b45502b3a4a42004e6d40d7da55a3', + 'type' => 'library', + 'install_path' => __DIR__ . '/../../', + 'aliases' => array(), + 'dev' => true, + ), + 'versions' => array( + '__root__' => array( + 'pretty_version' => 'dev-master', + 'version' => 'dev-master', + 'reference' => '4c22f27eb63b45502b3a4a42004e6d40d7da55a3', + 'type' => 'library', + 'install_path' => __DIR__ . '/../../', + 'aliases' => array(), + 'dev_requirement' => false, + ), + 'phpmyadmin/motranslator' => array( + 'pretty_version' => '5.3.1', + 'version' => '5.3.1.0', + 'reference' => 'd03b4d9c608e7265091bf6decc05323d16c7c047', + 'type' => 'library', + 'install_path' => __DIR__ . '/../phpmyadmin/motranslator', + 'aliases' => array(), + 'dev_requirement' => false, + ), + 'psr/cache' => array( + 'pretty_version' => '3.0.0', + 'version' => '3.0.0.0', + 'reference' => 'aa5030cfa5405eccfdcb1083ce040c2cb8d253bf', + 'type' => 'library', + 'install_path' => __DIR__ . '/../psr/cache', + 'aliases' => array(), + 'dev_requirement' => false, + ), + 'psr/cache-implementation' => array( + 'dev_requirement' => false, + 'provided' => array( + 0 => '2.0|3.0', + ), + ), + 'psr/container' => array( + 'pretty_version' => '2.0.2', + 'version' => '2.0.2.0', + 'reference' => 'c71ecc56dfe541dbd90c5360474fbc405f8d5963', + 'type' => 'library', + 'install_path' => __DIR__ . '/../psr/container', + 'aliases' => array(), + 'dev_requirement' => false, + ), + 'psr/log' => array( + 'pretty_version' => '3.0.2', + 'version' => '3.0.2.0', + 'reference' => 'f16e1d5863e37f8d8c2a01719f5b34baa2b714d3', + 'type' => 'library', + 'install_path' => __DIR__ . '/../psr/log', + 'aliases' => array(), + 'dev_requirement' => false, + ), + 'psr/simple-cache-implementation' => array( + 'dev_requirement' => false, + 'provided' => array( + 0 => '1.0|2.0|3.0', + ), + ), + 'symfony/cache' => array( + 'pretty_version' => 'v7.2.1', + 'version' => '7.2.1.0', + 'reference' => 'e7e983596b744c4539f31e79b0350a6cf5878a20', + 'type' => 'library', + 'install_path' => __DIR__ . '/../symfony/cache', + 'aliases' => array(), + 'dev_requirement' => false, + ), + 'symfony/cache-contracts' => array( + 'pretty_version' => 'v3.5.1', + 'version' => '3.5.1.0', + 'reference' => '15a4f8e5cd3bce9aeafc882b1acab39ec8de2c1b', + 'type' => 'library', + 'install_path' => __DIR__ . '/../symfony/cache-contracts', + 'aliases' => array(), + 'dev_requirement' => false, + ), + 'symfony/cache-implementation' => array( + 'dev_requirement' => false, + 'provided' => array( + 0 => '1.1|2.0|3.0', + ), + ), + 'symfony/deprecation-contracts' => array( + 'pretty_version' => 'v3.5.1', + 'version' => '3.5.1.0', + 'reference' => '74c71c939a79f7d5bf3c1ce9f5ea37ba0114c6f6', + 'type' => 'library', + 'install_path' => __DIR__ . '/../symfony/deprecation-contracts', + 'aliases' => array(), + 'dev_requirement' => false, + ), + 'symfony/expression-language' => array( + 'pretty_version' => 'v6.4.13', + 'version' => '6.4.13.0', + 'reference' => '3524904fb026356a5230cd197f9a4e6a61e0e7df', + 'type' => 'library', + 'install_path' => __DIR__ . '/../symfony/expression-language', + 'aliases' => array(), + 'dev_requirement' => false, + ), + 'symfony/service-contracts' => array( + 'pretty_version' => 'v3.5.1', + 'version' => '3.5.1.0', + 'reference' => 'e53260aabf78fb3d63f8d79d69ece59f80d5eda0', + 'type' => 'library', + 'install_path' => __DIR__ . '/../symfony/service-contracts', + 'aliases' => array(), + 'dev_requirement' => false, + ), + 'symfony/var-exporter' => array( + 'pretty_version' => 'v7.2.0', + 'version' => '7.2.0.0', + 'reference' => '1a6a89f95a46af0f142874c9d650a6358d13070d', + 'type' => 'library', + 'install_path' => __DIR__ . '/../symfony/var-exporter', + 'aliases' => array(), + 'dev_requirement' => false, + ), + ), +); diff --git a/vendor/composer/platform_check.php b/vendor/composer/platform_check.php new file mode 100644 index 0000000..d32d90c --- /dev/null +++ b/vendor/composer/platform_check.php @@ -0,0 +1,26 @@ += 80200)) { + $issues[] = 'Your Composer dependencies require a PHP version ">= 8.2.0". You are running ' . PHP_VERSION . '.'; +} + +if ($issues) { + if (!headers_sent()) { + header('HTTP/1.1 500 Internal Server Error'); + } + if (!ini_get('display_errors')) { + if (PHP_SAPI === 'cli' || PHP_SAPI === 'phpdbg') { + fwrite(STDERR, 'Composer detected issues in your platform:' . PHP_EOL.PHP_EOL . implode(PHP_EOL, $issues) . PHP_EOL.PHP_EOL); + } elseif (!headers_sent()) { + echo 'Composer detected issues in your platform:' . PHP_EOL.PHP_EOL . str_replace('You are running '.PHP_VERSION.'.', '', implode(PHP_EOL, $issues)) . PHP_EOL.PHP_EOL; + } + } + trigger_error( + 'Composer detected issues in your platform: ' . implode(' ', $issues), + E_USER_ERROR + ); +} diff --git a/vendor/phpmyadmin/motranslator/CHANGELOG.md b/vendor/phpmyadmin/motranslator/CHANGELOG.md new file mode 100644 index 0000000..f5b5c34 --- /dev/null +++ b/vendor/phpmyadmin/motranslator/CHANGELOG.md @@ -0,0 +1,104 @@ +# Change Log + +## [5.3.1] - 2023-08-23 + +* Add function guards to the global functions (#44) + +## [5.3.0] - 2022-04-26 + +* Add support for Symfony 6 +* Split out `.mo` parsing to separate `MoParser` class +* Added `CacheInterface` so alternate cache implementations are pluggable +* Added `ApcuCache` implementation to leverage shared in-memory translation cache + +## [5.2.0] - 2021-02-05 + +* Fix "Translator::selectString() must be of the type integer, boolean returned" (#37) +* Fix "TypeError: StringReader::readintarray() ($count) must be of type int, float given" failing tests on ARM (#36) +* Add support for getting and setting all translations (#30) + +## [5.1.0] - 2020-11-15 + +* Allow PHPUnit 9 (#35) +* Fix some typos +* Sync config files +* Allow PHP 8.0 + +## [5.0.0] - 2020-02-28 + +* Drop support for PHP 5.3, PHP 5.4, PHP 5.5, PHP 5.6, PHP 7.0 and HHVM +* Enabled strict mode on PHP files +* Add support for Symfony 5 (#34) +* Add support for phpunit 8 +* Rename CHANGES.md to CHANGELOG.md and follow a standard format + +## [4.0] - 2018-02-12 + +* The library no longer changes system locales. + +## [3.4] - 2017-12-15 + +* Added Translator::setTranslation method. + +## [3.3] - 2017-06-01 + +* Add support for switching locales for Loader instance. + +## [3.2] - 2017-05-23 + +* Various fixes when handling corrupted mo files. + +## [3.1] - 2017-05-15 + +* Documentation improvements. + +## [3.0] - 2017-01-23 + +* All classes moved to the PhpMyAdmin namespace. + +## [2.2] - 2017-01-07 + +* Coding style cleanup. +* Avoid installing tests using composer. + +## [2.1] - 2016-12-21 + +* Various code cleanups. +* Added support for PHP 5.3. + +## [2.0] - 2016-10-13 + +* Consistently use camelCase in API. +* No more relies on using eval(). +* Depends on symfony/expression-language for calculations. + +## [1.2] - 2016-09-22 + +* Stricter validation of plural expression. + +## [1.1] - 2016-08-29 + +* Improved handling of corrupted mo files. +* Minor performance improvements. +* Stricter validation of plural expression. + +## [1.0] - 2016-04-27 + +* Documentation improvements. +* Testsuite improvements. + +## [0.4] - 2016-03-02 + +* Fixed test failures with hhvm due to broken putenv. + +## [0.3] - 2016-03-01 + +* Added Loader::detectlocale method. + +## [0.2] - 2016-02-24 + +* Marked PHP 5.4 and 5.5 as supported. + +## [0.1] - 2016-02-23 + +* Initial release. diff --git a/vendor/phpmyadmin/motranslator/CODE_OF_CONDUCT.md b/vendor/phpmyadmin/motranslator/CODE_OF_CONDUCT.md new file mode 100644 index 0000000..4c40291 --- /dev/null +++ b/vendor/phpmyadmin/motranslator/CODE_OF_CONDUCT.md @@ -0,0 +1,76 @@ +# Contributor Covenant Code of Conduct + +## Our Pledge + +In the interest of fostering an open and welcoming environment, we as +contributors and maintainers pledge to making participation in our project and +our community a harassment-free experience for everyone, regardless of age, body +size, disability, ethnicity, sex characteristics, gender identity and expression, +level of experience, education, socio-economic status, nationality, personal +appearance, race, religion, or sexual identity and orientation. + +## Our Standards + +Examples of behavior that contributes to creating a positive environment +include: + +* Using welcoming and inclusive language +* Being respectful of differing viewpoints and experiences +* Gracefully accepting constructive criticism +* Focusing on what is best for the community +* Showing empathy towards other community members + +Examples of unacceptable behavior by participants include: + +* The use of sexualized language or imagery and unwelcome sexual attention or + advances +* Trolling, insulting/derogatory comments, and personal or political attacks +* Public or private harassment +* Publishing others' private information, such as a physical or electronic + address, without explicit permission +* Other conduct which could reasonably be considered inappropriate in a + professional setting + +## Our Responsibilities + +Project maintainers are responsible for clarifying the standards of acceptable +behavior and are expected to take appropriate and fair corrective action in +response to any instances of unacceptable behavior. + +Project maintainers have the right and responsibility to remove, edit, or +reject comments, commits, code, wiki edits, issues, and other contributions +that are not aligned to this Code of Conduct, or to ban temporarily or +permanently any contributor for other behaviors that they deem inappropriate, +threatening, offensive, or harmful. + +## Scope + +This Code of Conduct applies both within project spaces and in public spaces +when an individual is representing the project or its community. Examples of +representing a project or community include using an official project e-mail +address, posting via an official social media account, or acting as an appointed +representative at an online or offline event. Representation of a project may be +further defined and clarified by project maintainers. + +## Enforcement + +Instances of abusive, harassing, or otherwise unacceptable behavior may be +reported by contacting the project team at info@phpmyadmin.net. All +complaints will be reviewed and investigated and will result in a response that +is deemed necessary and appropriate to the circumstances. The project team is +obligated to maintain confidentiality with regard to the reporter of an incident. +Further details of specific enforcement policies may be posted separately. + +Project maintainers who do not follow or enforce the Code of Conduct in good +faith may face temporary or permanent repercussions as determined by other +members of the project's leadership. + +## Attribution + +This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, +available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html + +[homepage]: https://www.contributor-covenant.org + +For answers to common questions about this code of conduct, see +https://www.contributor-covenant.org/faq diff --git a/vendor/phpmyadmin/motranslator/CONTRIBUTING.md b/vendor/phpmyadmin/motranslator/CONTRIBUTING.md new file mode 100644 index 0000000..bbd7ffb --- /dev/null +++ b/vendor/phpmyadmin/motranslator/CONTRIBUTING.md @@ -0,0 +1,42 @@ +# Contributing to motranslator + +## Reporting issues + +Our issue tracker is hosted at GitHub: + +https://github.com/phpmyadmin/motranslator/issues + +Please search for existing issues before reporting new ones. + +## Working with Git checkout + +The dependencies are managed by Composer, to get them all installed (or update +on consequent runs) do: + +``` +composer update +``` + +## Submitting patches + +Please submit your patches using GitHub pull requests, this allows us to review +them and to run automated tests on the code. + +## Coding standards + +We do follow PSR-1 and PSR-2 coding standards. + +You can use phpcbf to fix the code to match our expectations: + +``` +composer run phpcbf +``` + +## Testsuite + +Our code comes with quite comprehensive testsuite, it is automatically executed +on every commit and pull request, you can also run it locally: + +``` +composer run phpunit +``` diff --git a/vendor/phpmyadmin/motranslator/LICENSE b/vendor/phpmyadmin/motranslator/LICENSE new file mode 100644 index 0000000..23cb790 --- /dev/null +++ b/vendor/phpmyadmin/motranslator/LICENSE @@ -0,0 +1,339 @@ + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Lesser General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + {description} + Copyright (C) {year} {fullname} + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) year name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + {signature of Ty Coon}, 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. diff --git a/vendor/phpmyadmin/motranslator/PERFORMANCE.md b/vendor/phpmyadmin/motranslator/PERFORMANCE.md new file mode 100644 index 0000000..cd0f3e1 --- /dev/null +++ b/vendor/phpmyadmin/motranslator/PERFORMANCE.md @@ -0,0 +1,29 @@ +# Performance + +This library was tweaked for best performance for single use - translating +application with many strings using mo file. Current benchmarks show it's about +four times faster than original php-gettext. + +There are two benchmark scripts in the code: + +* ``benchmark-context.php`` - benchmarks context usage +* ``benchmark-plural.php`` - benchmarks plural evaluation +* ``benchmark.php`` - benchmarks file parsing +* ``benchmark-apcu.php`` - benchmarks file parsing with APCu cache enabled + +## Performance measurements + +The performance improvements based on individual changes in the code: + +| Stage | Seconds | +| -------------- | --------------- | +| Original code | 4.7929680347443 | +| Remove nocache | 4.6308250427246 | +| Direct endian | 4.5883052349091 | +| Remove attribs | 4.5297479629517 | +| String reader | 1.8148958683014 | +| No offset | 1.2436759471893 | +| Less attribs | 1.1722540855408 | +| Remove shift | 1.0970499515533 | +| Magic order | 1.0868430137634 | + diff --git a/vendor/phpmyadmin/motranslator/README.md b/vendor/phpmyadmin/motranslator/README.md new file mode 100644 index 0000000..1aea481 --- /dev/null +++ b/vendor/phpmyadmin/motranslator/README.md @@ -0,0 +1,242 @@ +# motranslator + +Translation API for PHP using Gettext MO files. + +![Test-suite](https://github.com/phpmyadmin/motranslator/workflows/Run%20tests/badge.svg?branch=master) +[![codecov.io](https://codecov.io/github/phpmyadmin/motranslator/coverage.svg?branch=master)](https://codecov.io/github/phpmyadmin/motranslator?branch=master) +[![Scrutinizer Code Quality](https://scrutinizer-ci.com/g/phpmyadmin/motranslator/badges/quality-score.png?b=master)](https://scrutinizer-ci.com/g/phpmyadmin/motranslator/?branch=master) +[![Packagist](https://img.shields.io/packagist/dt/phpmyadmin/motranslator.svg)](https://packagist.org/packages/phpmyadmin/motranslator) + +## Features + +* All strings are stored in memory for fast lookup +* Fast loading of MO files +* Low level API for reading MO files +* Emulation of Gettext API +* No use of `eval()` for plural equation + +## Limitations + +* Default `InMemoryCache` not suitable for huge MO files which you don't want to store in memory +* Input and output encoding has to match (preferably UTF-8) + +## Installation + +Please use [Composer][1] to install: + +```sh +composer require phpmyadmin/motranslator +``` + +## Documentation + +The API documentation is available at . + +## Object API usage + +```php +// Create loader object +$loader = new PhpMyAdmin\MoTranslator\Loader(); + +// Set locale +$loader->setlocale('cs'); + +// Set default text domain +$loader->textdomain('domain'); + +// Set path where to look for a domain +$loader->bindtextdomain('domain', __DIR__ . '/data/locale/'); + +// Get translator +$translator = $loader->getTranslator(); + +// Now you can use Translator API (see below) +``` + +## Low level API usage + +```php +// Directly load the mo file +// You can use null to not load a file and the use a setter to set the translations +$cache = new PhpMyAdmin\MoTranslator\Cache\InMemoryCache(new PhpMyAdmin\MoTranslator\MoParser('./path/to/file.mo')); +$translator = new PhpMyAdmin\MoTranslator\Translator($cache); + +// Now you can use Translator API (see below) +``` + +## Translator API usage + +```php +// Translate string +echo $translator->gettext('String'); + +// Translate plural string +echo $translator->ngettext('String', 'Plural string', $count); + +// Translate string with context +echo $translator->pgettext('Context', 'String'); + +// Translate plural string with context +echo $translator->npgettext('Context', 'String', 'Plural string', $count); + +// Get the translations +echo $translator->getTranslations(); + +// All getters and setters below are more to be used if you are using a manual loading mode +// Example: $translator = new PhpMyAdmin\MoTranslator\Translator(null); + +// Set a translation +echo $translator->setTranslation('Test', 'Translation for "Test" key'); + +// Set translations +echo $translator->setTranslations([ + 'Test' => 'Translation for "Test" key', + 'Test 2' => 'Translation for "Test 2" key', +]); + +// Use the translation +echo $translator->gettext('Test 2'); // -> Translation for "Test 2" key +``` + +## Gettext compatibility usage + +```php +// Load compatibility layer +PhpMyAdmin\MoTranslator\Loader::loadFunctions(); + +// Configure +_setlocale(LC_MESSAGES, 'cs'); +_textdomain('phpmyadmin'); +_bindtextdomain('phpmyadmin', __DIR__ . '/data/locale/'); +_bind_textdomain_codeset('phpmyadmin', 'UTF-8'); + +// Use functions +echo _gettext('Type'); +echo __('Type'); + +// It also support other Gettext functions +_dnpgettext($domain, $msgctxt, $msgid, $msgidPlural, $number); +_dngettext($domain, $msgid, $msgidPlural, $number); +_npgettext($msgctxt, $msgid, $msgidPlural, $number); +_ngettext($msgid, $msgidPlural, $number); +_dpgettext($domain, $msgctxt, $msgid); +_dgettext($domain, $msgid); +_pgettext($msgctxt, $msgid); +``` + +## Using APCu-backed cache + +If you have the [APCu][5] extension installed you can use it for storing the translation cache. The `.mo` file +will then only be loaded once and all processes will share the same cache, reducing memory usage and resulting in +performance comparable to the native `gettext` extension. + +If you are using `Loader`, pass it an `ApcuCacheFactory` _before_ getting the translator instance: + +```php +PhpMyAdmin\MoTranslator\Loader::setCacheFactory( + new PhpMyAdmin\MoTranslator\Cache\AcpuCacheFactory() +); +$loader = new PhpMyAdmin\MoTranslator\Loader(); + +// Proceed as before +``` + +If you are using the low level API, instantiate the `ApcuCache` directly: + +```php +$cache = new PhpMyAdmin\MoTranslator\Cache\ApcuCache( + new PhpMyAdmin\MoTranslator\MoParser('./path/to/file.mo'), + 'de_DE', // the locale + 'phpmyadmin' // the domain +); +$translator = new PhpMyAdmin\MoTranslator\Translator($cache); + +// Proceed as before +``` + +By default, APCu will cache the translations until next server restart and prefix the cache entries with `mo_` to +avoid clashes with other cache entries. You can control this behaviour by passing `$ttl` and `$prefix` arguments, either +to the `ApcuCacheFactory` or when instantiating `ApcuCache`: + +```php +PhpMyAdmin\MoTranslator\Loader::setCacheFactory( + new PhpMyAdmin\MoTranslator\Cache\AcpuCacheFactory( + 3600, // cache for 1 hour + true, // reload on cache miss + 'custom_' // custom prefix for cache entries + ) +); +$loader = new PhpMyAdmin\MoTranslator\Loader(); + +// or... + +$cache = new PhpMyAdmin\MoTranslator\Cache\ApcuCache( + new PhpMyAdmin\MoTranslator\MoParser('./path/to/file.mo'), + 'de_DE', + 'phpmyadmin', + 3600, // cache for 1 hour + true, // reload on cache miss + 'custom_' // custom prefix for cache entries +); +$translator = new PhpMyAdmin\MoTranslator\Translator($cache); +``` + +If you receive updated translation files you can load them without restarting the server using the low-level API: + +```php +$parser = new PhpMyAdmin\MoTranslator\MoParser('./path/to/file.mo'); +$cache = new PhpMyAdmin\MoTranslator\Cache\ApcuCache($parser, 'de_DE', 'phpmyadmin'); +$parser->parseIntoCache($cache); +``` + +You should ensure APCu has enough memory to store all your translations, along with any other entries you use it +for. If an entry is evicted from cache, the `.mo` file will be re-parsed, impacting performance. See the +`apc.shm_size` and `apc.shm_segments` [documentation][6] and monitor cache usage when first rolling out. + +If your `.mo` files are missing lots of translations, the first time a missing entry is requested the `.mo` file +will be re-parsed. Again, this will impact performance until all the missing entries are hit once. You can turn off this +behaviour by setting the `$reloadOnMiss` argument to `false`. If you do this it is _critical_ that APCu has enough +memory, or users will see untranslated text when entries are evicted. + +## History + +This library is based on [php-gettext][2]. It adds some performance +improvements and ability to install using [Composer][1]. + +## Motivation + +Motivation for this library includes: + +* The [php-gettext][2] library is not maintained anymore +* It doesn't work with recent PHP version (phpMyAdmin has patched version) +* It relies on `eval()` function for plural equations what can have severe security implications, see [CVE-2016-6175][4] +* It's not possible to install it using [Composer][1] +* There was place for performance improvements in the library + +### Why not to use native gettext in PHP? + +We've tried that, but it's not a viable solution: + +* You can not use locales not known to system, what is something you can not + control from web application. This gets even more tricky with minimalist + virtualisation containers. +* Changing the MO file usually leads to PHP segmentation fault. It (or rather + Gettext library) caches headers of MO file and if it's content is changed + (for example new version is uploaded to server) it tries to access new data + with old references. This is bug known for ages: + https://bugs.php.net/bug.php?id=45943 + +### Why use Gettext and not JSON, YAML or whatever? + +We want translators to be able to use their favorite tools and we want us to be +able to use wide range of tools available with Gettext as well such as +[web based translation using Weblate][3]. Using custom format usually adds +another barrier for translators and we want to make it easy for them to +contribute. + +[1]:https://getcomposer.org/ +[2]:https://launchpad.net/php-gettext +[3]:https://weblate.org/ +[4]: https://www.cve.org/CVERecord?id=CVE-2016-6175 +[5]:https://www.php.net/manual/en/book.apcu.php +[6]:https://www.php.net/manual/en/apcu.configuration.php diff --git a/vendor/phpmyadmin/motranslator/benchmark-apcu.php b/vendor/phpmyadmin/motranslator/benchmark-apcu.php new file mode 100644 index 0000000..90ef494 --- /dev/null +++ b/vendor/phpmyadmin/motranslator/benchmark-apcu.php @@ -0,0 +1,29 @@ + './tests/data/big.mo', + 'little' => './tests/data/little.mo', +]; + +$start = microtime(true); + +for ($i = 0; $i < 2000; ++$i) { + foreach ($files as $domain => $filename) { + $translator = new PhpMyAdmin\MoTranslator\Translator( + new PhpMyAdmin\MoTranslator\Cache\ApcuCache( + new PhpMyAdmin\MoTranslator\MoParser($filename), 'foo', $domain + ) + ); + $translator->gettext('Column'); + } +} + +$end = microtime(true); + +$diff = $end - $start; + +echo 'Execution took ' . $diff . ' seconds' . "\n"; diff --git a/vendor/phpmyadmin/motranslator/composer.json b/vendor/phpmyadmin/motranslator/composer.json new file mode 100644 index 0000000..2536f58 --- /dev/null +++ b/vendor/phpmyadmin/motranslator/composer.json @@ -0,0 +1,56 @@ +{ + "name": "phpmyadmin/motranslator", + "description": "Translation API for PHP using Gettext MO files", + "license": "GPL-2.0-or-later", + "keywords": ["gettext", "mo", "translator", "i18n"], + "homepage": "https://github.com/phpmyadmin/motranslator", + "authors": [ + { + "name": "The phpMyAdmin Team", + "email": "developers@phpmyadmin.net", + "homepage": "https://www.phpmyadmin.net/team/" + } + ], + "support": { + "issues": "https://github.com/phpmyadmin/motranslator/issues", + "source": "https://github.com/phpmyadmin/motranslator" + }, + "scripts": { + "phpcbf": "@php phpcbf", + "phpcs": "@php phpcs", + "phpstan": "@php phpstan", + "phpunit": "@php phpunit", + "test": [ + "@phpcs", + "@phpstan", + "@phpunit" + ] + }, + "require": { + "php": "^7.1 || ^8.0", + "symfony/expression-language": "^4.0 || ^5.0 || ^6.0" + }, + "require-dev": { + "phpunit/phpunit": "^7.4 || ^8 || ^9", + "phpmyadmin/coding-standard": "^3.0.0", + "phpstan/phpstan": "^1.4.6" + }, + "autoload": { + "psr-4": { + "PhpMyAdmin\\MoTranslator\\": "src" + } + }, + "autoload-dev": { + "psr-4": { + "PhpMyAdmin\\MoTranslator\\Tests\\": "tests" + } + }, + "config": { + "allow-plugins": { + "dealerdirect/phpcodesniffer-composer-installer": true + } + }, + "suggest": { + "ext-apcu": "Needed for ACPu-backed translation cache" + } +} diff --git a/vendor/phpmyadmin/motranslator/phpunit.xml.dist b/vendor/phpmyadmin/motranslator/phpunit.xml.dist new file mode 100644 index 0000000..d49892d --- /dev/null +++ b/vendor/phpmyadmin/motranslator/phpunit.xml.dist @@ -0,0 +1,31 @@ + + + + + + + + ./tests + + + + + src/ + + + + + + + + diff --git a/vendor/phpmyadmin/motranslator/src/Cache/ApcuCache.php b/vendor/phpmyadmin/motranslator/src/Cache/ApcuCache.php new file mode 100644 index 0000000..43b957e --- /dev/null +++ b/vendor/phpmyadmin/motranslator/src/Cache/ApcuCache.php @@ -0,0 +1,136 @@ +parser = $parser; + $this->locale = $locale; + $this->domain = $domain; + $this->ttl = $ttl; + $this->reloadOnMiss = $reloadOnMiss; + $this->prefix = $prefix; + + $this->ensureTranslationsLoaded(); + } + + public function get(string $msgid): string + { + $msgstr = apcu_fetch($this->getKey($msgid), $success); + if ($success && is_string($msgstr)) { + return $msgstr; + } + + if (! $this->reloadOnMiss) { + return $msgid; + } + + return $this->reloadOnMiss($msgid); + } + + private function reloadOnMiss(string $msgid): string + { + // store original if translation is not present + $cached = apcu_entry($this->getKey($msgid), static function () use ($msgid) { + return $msgid; + }, $this->ttl); + // if another process has updated cache, return early + if ($cached !== $msgid && is_string($cached)) { + return $cached; + } + + // reload .mo file, in case entry has been evicted + $this->parser->parseIntoCache($this); + + $msgstr = apcu_fetch($this->getKey($msgid), $success); + + return $success && is_string($msgstr) ? $msgstr : $msgid; + } + + public function set(string $msgid, string $msgstr): void + { + apcu_store($this->getKey($msgid), $msgstr, $this->ttl); + } + + public function has(string $msgid): bool + { + return apcu_exists($this->getKey($msgid)); + } + + public function setAll(array $translations): void + { + $keys = array_map(function (string $msgid): string { + return $this->getKey($msgid); + }, array_keys($translations)); + $translations = array_combine($keys, $translations); + assert(is_array($translations)); + + apcu_store($translations, null, $this->ttl); + } + + private function getKey(string $msgid): string + { + return $this->prefix . $this->locale . '.' . $this->domain . '.' . $msgid; + } + + private function ensureTranslationsLoaded(): void + { + // Try to prevent cache slam if multiple processes are trying to load translations. There is still a race + // between the exists check and creating the entry, but at least it's small + $key = $this->getKey(self::LOADED_KEY); + $loaded = apcu_exists($key) || apcu_entry($key, static function (): int { + return 0; + }, $this->ttl); + if ($loaded) { + return; + } + + $this->parser->parseIntoCache($this); + apcu_store($this->getKey(self::LOADED_KEY), 1, $this->ttl); + } +} diff --git a/vendor/phpmyadmin/motranslator/src/Cache/ApcuCacheFactory.php b/vendor/phpmyadmin/motranslator/src/Cache/ApcuCacheFactory.php new file mode 100644 index 0000000..e30540d --- /dev/null +++ b/vendor/phpmyadmin/motranslator/src/Cache/ApcuCacheFactory.php @@ -0,0 +1,29 @@ +ttl = $ttl; + $this->reloadOnMiss = $reloadOnMiss; + $this->prefix = $prefix; + } + + public function getInstance(MoParser $parser, string $locale, string $domain): CacheInterface + { + return new ApcuCache($parser, $locale, $domain, $this->ttl, $this->reloadOnMiss, $this->prefix); + } +} diff --git a/vendor/phpmyadmin/motranslator/src/Cache/CacheFactoryInterface.php b/vendor/phpmyadmin/motranslator/src/Cache/CacheFactoryInterface.php new file mode 100644 index 0000000..5e3fc3f --- /dev/null +++ b/vendor/phpmyadmin/motranslator/src/Cache/CacheFactoryInterface.php @@ -0,0 +1,12 @@ + $msgstr` entries + * + * This will overwrite existing values for `$msgid`, but is not guaranteed to clear cache of existing entries + * not present in `$translations`. + * + * @param array $translations + */ + public function setAll(array $translations): void; +} diff --git a/vendor/phpmyadmin/motranslator/src/Cache/GetAllInterface.php b/vendor/phpmyadmin/motranslator/src/Cache/GetAllInterface.php new file mode 100644 index 0000000..c391438 --- /dev/null +++ b/vendor/phpmyadmin/motranslator/src/Cache/GetAllInterface.php @@ -0,0 +1,13 @@ + + */ + public function getAll(): array; +} diff --git a/vendor/phpmyadmin/motranslator/src/Cache/InMemoryCache.php b/vendor/phpmyadmin/motranslator/src/Cache/InMemoryCache.php new file mode 100644 index 0000000..bf7bc1d --- /dev/null +++ b/vendor/phpmyadmin/motranslator/src/Cache/InMemoryCache.php @@ -0,0 +1,49 @@ + */ + private $cache; + + public function __construct(MoParser $parser) + { + $this->cache = []; + $parser->parseIntoCache($this); + } + + public function get(string $msgid): string + { + return array_key_exists($msgid, $this->cache) ? $this->cache[$msgid] : $msgid; + } + + public function set(string $msgid, string $msgstr): void + { + $this->cache[$msgid] = $msgstr; + } + + public function has(string $msgid): bool + { + return array_key_exists($msgid, $this->cache); + } + + public function setAll(array $translations): void + { + $this->cache = $translations; + } + + /** + * @inheritDoc + */ + public function getAll(): array + { + return $this->cache; + } +} diff --git a/vendor/phpmyadmin/motranslator/src/CacheException.php b/vendor/phpmyadmin/motranslator/src/CacheException.php new file mode 100644 index 0000000..aa37fae --- /dev/null +++ b/vendor/phpmyadmin/motranslator/src/CacheException.php @@ -0,0 +1,11 @@ + + Copyright (c) 2009 Danilo Segan + Copyright (c) 2016 Michal Čihař + + This file is part of MoTranslator. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +namespace PhpMyAdmin\MoTranslator; + +use PhpMyAdmin\MoTranslator\Cache\CacheFactoryInterface; +use PhpMyAdmin\MoTranslator\Cache\InMemoryCache; + +use function array_push; +use function file_exists; +use function getenv; +use function in_array; +use function preg_match; +use function sprintf; + +class Loader +{ + /** + * Loader instance. + * + * @static + * @var Loader + */ + private static $instance = null; + + /** + * Factory to return a factory responsible for returning a `CacheInterface` + * + * @static + * @var CacheFactoryInterface|null + */ + private static $cacheFactory = null; + + /** + * Default gettext domain to use. + * + * @var string + */ + private $defaultDomain = ''; + + /** + * Configured locale. + * + * @var string + */ + private $locale = ''; + + /** + * Loaded domains. + * + * @var array> + */ + private $domains = []; + + /** + * Bound paths for domains. + * + * @var array + */ + private $paths = ['' => './']; + + /** + * Returns the singleton Loader object. + * + * @return Loader object + */ + public static function getInstance(): Loader + { + if (self::$instance === null) { + self::$instance = new self(); + } + + return self::$instance; + } + + /** + * Loads global localization functions. + */ + public static function loadFunctions(): void + { + require_once __DIR__ . '/functions.php'; + } + + /** + * Figure out all possible locale names and start with the most + * specific ones. I.e. for sr_CS.UTF-8@latin, look through all of + * sr_CS.UTF-8@latin, sr_CS@latin, sr@latin, sr_CS.UTF-8, sr_CS, sr. + * + * @param string $locale Locale code + * + * @return string[] list of locales to try for any POSIX-style locale specification + */ + public static function listLocales(string $locale): array + { + $localeNames = []; + + if ($locale) { + if ( + preg_match( + '/^(?P[a-z]{2,3})' // language code + . '(?:_(?P[A-Z]{2}))?' // country code + . '(?:\\.(?P[-A-Za-z0-9_]+))?' // charset + . '(?:@(?P[-A-Za-z0-9_]+))?$/', // @ modifier + $locale, + $matches + ) + ) { + $lang = $matches['lang'] ?? null; + $country = $matches['country'] ?? null; + $charset = $matches['charset'] ?? null; + $modifier = $matches['modifier'] ?? null; + + if ($modifier) { + if ($country) { + if ($charset) { + array_push( + $localeNames, + sprintf('%s_%s.%s@%s', $lang, $country, $charset, $modifier) + ); + } + + array_push( + $localeNames, + sprintf('%s_%s@%s', $lang, $country, $modifier) + ); + } elseif ($charset) { + array_push( + $localeNames, + sprintf('%s.%s@%s', $lang, $charset, $modifier) + ); + } + + array_push( + $localeNames, + sprintf('%s@%s', $lang, $modifier) + ); + } + + if ($country) { + if ($charset) { + array_push( + $localeNames, + sprintf('%s_%s.%s', $lang, $country, $charset) + ); + } + + array_push( + $localeNames, + sprintf('%s_%s', $lang, $country) + ); + } elseif ($charset) { + array_push( + $localeNames, + sprintf('%s.%s', $lang, $charset) + ); + } + + array_push($localeNames, $lang); + } + + // If the locale name doesn't match POSIX style, just include it as-is. + if (! in_array($locale, $localeNames)) { + array_push($localeNames, $locale); + } + } + + return $localeNames; + } + + /** + * Sets factory responsible for composing a `CacheInterface` + */ + public static function setCacheFactory(?CacheFactoryInterface $cacheFactory): void + { + self::$cacheFactory = $cacheFactory; + } + + /** + * Returns Translator object for domain or for default domain. + * + * @param string $domain Translation domain + */ + public function getTranslator(string $domain = ''): Translator + { + if (empty($domain)) { + $domain = $this->defaultDomain; + } + + if (! isset($this->domains[$this->locale])) { + $this->domains[$this->locale] = []; + } + + if (! isset($this->domains[$this->locale][$domain])) { + if (isset($this->paths[$domain])) { + $base = $this->paths[$domain]; + } else { + $base = './'; + } + + $localeNames = $this->listLocales($this->locale); + + $filename = ''; + foreach ($localeNames as $locale) { + $filename = $base . '/' . $locale . '/LC_MESSAGES/' . $domain . '.mo'; + if (file_exists($filename)) { + break; + } + } + + // We don't care about invalid path, we will get fallback + // translator here + $moParser = new MoParser($filename); + if (self::$cacheFactory instanceof CacheFactoryInterface) { + $cache = self::$cacheFactory->getInstance($moParser, $this->locale, $domain); + } else { + $cache = new InMemoryCache($moParser); + } + + $this->domains[$this->locale][$domain] = new Translator($cache); + } + + return $this->domains[$this->locale][$domain]; + } + + /** + * Sets the path for a domain. + * + * @param string $domain Domain name + * @param string $path Path where to find locales + */ + public function bindtextdomain(string $domain, string $path): void + { + $this->paths[$domain] = $path; + } + + /** + * Sets the default domain. + * + * @param string $domain Domain name + */ + public function textdomain(string $domain): void + { + $this->defaultDomain = $domain; + } + + /** + * Sets a requested locale. + * + * @param string $locale Locale name + * + * @return string Set or current locale + */ + public function setlocale(string $locale): string + { + if (! empty($locale)) { + $this->locale = $locale; + } + + return $this->locale; + } + + /** + * Detects currently configured locale. + * + * It checks: + * + * - global lang variable + * - environment for LC_ALL, LC_MESSAGES and LANG + * + * @return string with locale name + */ + public function detectlocale(): string + { + if (isset($GLOBALS['lang'])) { + return $GLOBALS['lang']; + } + + $locale = getenv('LC_ALL'); + if ($locale !== false) { + return $locale; + } + + $locale = getenv('LC_MESSAGES'); + if ($locale !== false) { + return $locale; + } + + $locale = getenv('LANG'); + if ($locale !== false) { + return $locale; + } + + return 'en'; + } +} diff --git a/vendor/phpmyadmin/motranslator/src/MoParser.php b/vendor/phpmyadmin/motranslator/src/MoParser.php new file mode 100644 index 0000000..ff176ed --- /dev/null +++ b/vendor/phpmyadmin/motranslator/src/MoParser.php @@ -0,0 +1,109 @@ +filename = $filename; + } + + /** + * Parses .mo file and stores results to `$cache` + */ + public function parseIntoCache(CacheInterface $cache): void + { + if ($this->filename === null) { + return; + } + + if (! is_readable($this->filename)) { + $this->error = self::ERROR_DOES_NOT_EXIST; + + return; + } + + $stream = new StringReader($this->filename); + + try { + $magic = $stream->read(0, 4); + if (strcmp($magic, self::MAGIC_LE) === 0) { + $unpack = 'V'; + } elseif (strcmp($magic, self::MAGIC_BE) === 0) { + $unpack = 'N'; + } else { + $this->error = self::ERROR_BAD_MAGIC; + + return; + } + + /* Parse header */ + $total = $stream->readint($unpack, 8); + $originals = $stream->readint($unpack, 12); + $translations = $stream->readint($unpack, 16); + + /* get original and translations tables */ + $totalTimesTwo = (int) ($total * 2);// Fix for issue #36 on ARM + $tableOriginals = $stream->readintarray($unpack, $originals, $totalTimesTwo); + $tableTranslations = $stream->readintarray($unpack, $translations, $totalTimesTwo); + + /* read all strings to the cache */ + for ($i = 0; $i < $total; ++$i) { + $iTimesTwo = $i * 2; + $iPlusOne = $iTimesTwo + 1; + $iPlusTwo = $iTimesTwo + 2; + $original = $stream->read($tableOriginals[$iPlusTwo], $tableOriginals[$iPlusOne]); + $translation = $stream->read($tableTranslations[$iPlusTwo], $tableTranslations[$iPlusOne]); + $cache->set($original, $translation); + } + } catch (ReaderException $e) { + $this->error = self::ERROR_READING; + + return; + } + } +} diff --git a/vendor/phpmyadmin/motranslator/src/ReaderException.php b/vendor/phpmyadmin/motranslator/src/ReaderException.php new file mode 100644 index 0000000..3c06394 --- /dev/null +++ b/vendor/phpmyadmin/motranslator/src/ReaderException.php @@ -0,0 +1,35 @@ +. + Copyright (c) 2016 Michal Čihař + + This file is part of MoTranslator. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +namespace PhpMyAdmin\MoTranslator; + +use Exception; + +/** + * Exception thrown when file can not be read. + */ +class ReaderException extends Exception +{ +} diff --git a/vendor/phpmyadmin/motranslator/src/StringReader.php b/vendor/phpmyadmin/motranslator/src/StringReader.php new file mode 100644 index 0000000..a40a9f6 --- /dev/null +++ b/vendor/phpmyadmin/motranslator/src/StringReader.php @@ -0,0 +1,116 @@ +. + Copyright (c) 2016 Michal Čihař + + This file is part of MoTranslator. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +namespace PhpMyAdmin\MoTranslator; + +use function file_get_contents; +use function strlen; +use function substr; +use function unpack; + +use const PHP_INT_MAX; + +/** + * Simple wrapper around string buffer for + * random access and values parsing. + */ +class StringReader +{ + /** @var string */ + private $string; + /** @var int */ + private $length; + + /** + * @param string $filename Name of file to load + */ + public function __construct(string $filename) + { + $this->string = (string) file_get_contents($filename); + $this->length = strlen($this->string); + } + + /** + * Read number of bytes from given offset. + * + * @param int $pos Offset + * @param int $bytes Number of bytes to read + */ + public function read(int $pos, int $bytes): string + { + if ($pos + $bytes > $this->length) { + throw new ReaderException('Not enough bytes!'); + } + + $data = substr($this->string, $pos, $bytes); + + return $data === false ? '' : $data; + } + + /** + * Reads a 32bit integer from the stream. + * + * @param string $unpack Unpack string + * @param int $pos Position + * + * @return int Integer from the stream + */ + public function readint(string $unpack, int $pos): int + { + $data = unpack($unpack, $this->read($pos, 4)); + if ($data === false) { + return PHP_INT_MAX; + } + + $result = $data[1]; + + /* We're reading unsigned int, but PHP will happily + * give us negative number on 32-bit platforms. + * + * See also documentation: + * https://secure.php.net/manual/en/function.unpack.php#refsect1-function.unpack-notes + */ + return $result < 0 ? PHP_INT_MAX : $result; + } + + /** + * Reads an array of integers from the stream. + * + * @param string $unpack Unpack string + * @param int $pos Position + * @param int $count How many elements should be read + * + * @return int[] Array of Integers + */ + public function readintarray(string $unpack, int $pos, int $count): array + { + $data = unpack($unpack . $count, $this->read($pos, 4 * $count)); + if ($data === false) { + return []; + } + + return $data; + } +} diff --git a/vendor/phpmyadmin/motranslator/src/Translator.php b/vendor/phpmyadmin/motranslator/src/Translator.php new file mode 100644 index 0000000..bb423cf --- /dev/null +++ b/vendor/phpmyadmin/motranslator/src/Translator.php @@ -0,0 +1,388 @@ +. + Copyright (c) 2005 Nico Kaiser + Copyright (c) 2016 Michal Čihař + + This file is part of MoTranslator. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +namespace PhpMyAdmin\MoTranslator; + +use PhpMyAdmin\MoTranslator\Cache\CacheInterface; +use PhpMyAdmin\MoTranslator\Cache\GetAllInterface; +use PhpMyAdmin\MoTranslator\Cache\InMemoryCache; +use Symfony\Component\ExpressionLanguage\ExpressionLanguage; +use Throwable; + +use function chr; +use function count; +use function explode; +use function get_class; +use function implode; +use function intval; +use function ltrim; +use function preg_replace; +use function rtrim; +use function sprintf; +use function stripos; +use function strpos; +use function strtolower; +use function substr; +use function trim; + +/** + * Provides a simple gettext replacement that works independently from + * the system's gettext abilities. + * It can read MO files and use them for translating strings. + * + * It caches ll strings and translations to speed up the string lookup. + */ +class Translator +{ + /** + * None error. + */ + public const ERROR_NONE = 0; + /** + * File does not exist. + */ + public const ERROR_DOES_NOT_EXIST = 1; + /** + * File has bad magic number. + */ + public const ERROR_BAD_MAGIC = 2; + /** + * Error while reading file, probably too short. + */ + public const ERROR_READING = 3; + + /** + * Big endian mo file magic bytes. + */ + public const MAGIC_BE = "\x95\x04\x12\xde"; + /** + * Little endian mo file magic bytes. + */ + public const MAGIC_LE = "\xde\x12\x04\x95"; + + /** + * Parse error code (0 if no error). + * + * @var int + */ + public $error = self::ERROR_NONE; + + /** + * Cache header field for plural forms. + * + * @var string|null + */ + private $pluralEquation = null; + + /** @var ExpressionLanguage|null Evaluator for plurals */ + private $pluralExpression = null; + + /** @var int|null number of plurals */ + private $pluralCount = null; + + /** @var CacheInterface */ + private $cache; + + /** + * @param CacheInterface|string|null $cache Mo file to load (null for no file) or a CacheInterface implementation + */ + public function __construct($cache) + { + if (! $cache instanceof CacheInterface) { + $cache = new InMemoryCache(new MoParser($cache)); + } + + $this->cache = $cache; + } + + /** + * Translates a string. + * + * @param string $msgid String to be translated + * + * @return string translated string (or original, if not found) + */ + public function gettext(string $msgid): string + { + return $this->cache->get($msgid); + } + + /** + * Check if a string is translated. + * + * @param string $msgid String to be checked + */ + public function exists(string $msgid): bool + { + return $this->cache->has($msgid); + } + + /** + * Sanitize plural form expression for use in ExpressionLanguage. + * + * @param string $expr Expression to sanitize + * + * @return string sanitized plural form expression + */ + public static function sanitizePluralExpression(string $expr): string + { + // Parse equation + $expr = explode(';', $expr); + if (count($expr) >= 2) { + $expr = $expr[1]; + } else { + $expr = $expr[0]; + } + + $expr = trim(strtolower($expr)); + // Strip plural prefix + if (substr($expr, 0, 6) === 'plural') { + $expr = ltrim(substr($expr, 6)); + } + + // Strip equals + if (substr($expr, 0, 1) === '=') { + $expr = ltrim(substr($expr, 1)); + } + + // Cleanup from unwanted chars + $expr = preg_replace('@[^n0-9:\(\)\?=!<>/%&| ]@', '', $expr); + + return (string) $expr; + } + + /** + * Extracts number of plurals from plurals form expression. + * + * @param string $expr Expression to process + * + * @return int Total number of plurals + */ + public static function extractPluralCount(string $expr): int + { + $parts = explode(';', $expr, 2); + $nplurals = explode('=', trim($parts[0]), 2); + if (strtolower(rtrim($nplurals[0])) !== 'nplurals') { + return 1; + } + + if (count($nplurals) === 1) { + return 1; + } + + return intval($nplurals[1]); + } + + /** + * Parse full PO header and extract only plural forms line. + * + * @param string $header Gettext header + * + * @return string verbatim plural form header field + */ + public static function extractPluralsForms(string $header): string + { + $headers = explode("\n", $header); + $expr = 'nplurals=2; plural=n == 1 ? 0 : 1;'; + foreach ($headers as $header) { + if (stripos($header, 'Plural-Forms:') !== 0) { + continue; + } + + $expr = substr($header, 13); + } + + return $expr; + } + + /** + * Get possible plural forms from MO header. + * + * @return string plural form header + */ + private function getPluralForms(): string + { + // lets assume message number 0 is header + // this is true, right? + + // cache header field for plural forms + if ($this->pluralEquation === null) { + $header = $this->cache->get(''); + + $expr = $this->extractPluralsForms($header); + $this->pluralEquation = $this->sanitizePluralExpression($expr); + $this->pluralCount = $this->extractPluralCount($expr); + } + + return $this->pluralEquation; + } + + /** + * Detects which plural form to take. + * + * @param int $n count of objects + * + * @return int array index of the right plural form + */ + private function selectString(int $n): int + { + if ($this->pluralExpression === null) { + $this->pluralExpression = new ExpressionLanguage(); + } + + try { + $plural = (int) $this->pluralExpression->evaluate( + $this->getPluralForms(), + ['n' => $n] + ); + } catch (Throwable $e) { + $plural = 0; + } + + if ($plural >= $this->pluralCount) { + $plural = $this->pluralCount - 1; + } + + return $plural; + } + + /** + * Plural version of gettext. + * + * @param string $msgid Single form + * @param string $msgidPlural Plural form + * @param int $number Number of objects + * + * @return string translated plural form + */ + public function ngettext(string $msgid, string $msgidPlural, int $number): string + { + // this should contains all strings separated by NULLs + $key = implode(chr(0), [$msgid, $msgidPlural]); + if (! $this->cache->has($key)) { + return $number !== 1 ? $msgidPlural : $msgid; + } + + $result = $this->cache->get($key); + + // find out the appropriate form + $select = $this->selectString($number); + + $list = explode(chr(0), $result); + // @codeCoverageIgnoreStart + if ($list === false) { + // This was added in 3ff2c63bcf85f81b3a205ce7222de11b33e2bf56 for phpstan + // But according to the php manual it should never happen + return ''; + } + + // @codeCoverageIgnoreEnd + + if (! isset($list[$select])) { + return $list[0]; + } + + return $list[$select]; + } + + /** + * Translate with context. + * + * @param string $msgctxt Context + * @param string $msgid String to be translated + * + * @return string translated plural form + */ + public function pgettext(string $msgctxt, string $msgid): string + { + $key = implode(chr(4), [$msgctxt, $msgid]); + $ret = $this->gettext($key); + if (strpos($ret, chr(4)) !== false) { + return $msgid; + } + + return $ret; + } + + /** + * Plural version of pgettext. + * + * @param string $msgctxt Context + * @param string $msgid Single form + * @param string $msgidPlural Plural form + * @param int $number Number of objects + * + * @return string translated plural form + */ + public function npgettext(string $msgctxt, string $msgid, string $msgidPlural, int $number): string + { + $key = implode(chr(4), [$msgctxt, $msgid]); + $ret = $this->ngettext($key, $msgidPlural, $number); + if (strpos($ret, chr(4)) !== false) { + return $msgid; + } + + return $ret; + } + + /** + * Set translation in place + * + * @param string $msgid String to be set + * @param string $msgstr Translation + */ + public function setTranslation(string $msgid, string $msgstr): void + { + $this->cache->set($msgid, $msgstr); + } + + /** + * Set the translations + * + * @param array $translations The translations "key => value" array + */ + public function setTranslations(array $translations): void + { + $this->cache->setAll($translations); + } + + /** + * Get the translations + * + * @return array The translations "key => value" array + */ + public function getTranslations(): array + { + if ($this->cache instanceof GetAllInterface) { + return $this->cache->getAll(); + } + + throw new CacheException(sprintf( + "Cache '%s' does not support getting translations", + get_class($this->cache) + )); + } +} diff --git a/vendor/phpmyadmin/motranslator/src/functions.php b/vendor/phpmyadmin/motranslator/src/functions.php new file mode 100644 index 0000000..2ef8392 --- /dev/null +++ b/vendor/phpmyadmin/motranslator/src/functions.php @@ -0,0 +1,226 @@ + + Copyright (c) 2009 Danilo Segan + Copyright (c) 2016 Michal Čihař + + This file is part of MoTranslator. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +use PhpMyAdmin\MoTranslator\Loader; + +if (! function_exists('_setlocale')) { + /** + * Sets a requested locale. + * + * @param int $category Locale category, ignored + * @param string $locale Locale name + * + * @return string Set or current locale + */ + function _setlocale(int $category, string $locale): string + { + return Loader::getInstance()->setlocale($locale); + } +} + +if (! function_exists('_bindtextdomain')) { + /** + * Sets the path for a domain. + * + * @param string $domain Domain name + * @param string $path Path where to find locales + */ + function _bindtextdomain(string $domain, string $path): void + { + Loader::getInstance()->bindtextdomain($domain, $path); + } +} + +if (! function_exists('_bind_textdomain_codeset')) { + /** + * Dummy compatibility function, MoTranslator assumes + * everything is using same character set on input and + * output. + * + * Generally it is wise to output in UTF-8 and have + * mo files in UTF-8. + * + * @param string $domain Domain where to set character set + * @param string $codeset Character set to set + */ + function _bind_textdomain_codeset($domain, $codeset): void + { + } +} + +if (! function_exists('_textdomain')) { + /** + * Sets the default domain. + * + * @param string $domain Domain name + */ + function _textdomain(string $domain): void + { + Loader::getInstance()->textdomain($domain); + } +} + +if (! function_exists('_gettext')) { + /** + * Translates a string. + * + * @param string $msgid String to be translated + * + * @return string translated string (or original, if not found) + */ + function _gettext(string $msgid): string + { + return Loader::getInstance()->getTranslator()->gettext($msgid); + } +} + +if (! function_exists('__')) { + /** + * Translates a string, alias for _gettext. + * + * @param string $msgid String to be translated + * + * @return string translated string (or original, if not found) + */ + function __(string $msgid): string + { + return Loader::getInstance()->getTranslator()->gettext($msgid); + } +} + +if (! function_exists('_ngettext')) { + /** + * Plural version of gettext. + * + * @param string $msgid Single form + * @param string $msgidPlural Plural form + * @param int $number Number of objects + * + * @return string translated plural form + */ + function _ngettext(string $msgid, string $msgidPlural, int $number): string + { + return Loader::getInstance()->getTranslator()->ngettext($msgid, $msgidPlural, $number); + } +} + +if (! function_exists('_pgettext')) { + /** + * Translate with context. + * + * @param string $msgctxt Context + * @param string $msgid String to be translated + * + * @return string translated plural form + */ + function _pgettext(string $msgctxt, string $msgid): string + { + return Loader::getInstance()->getTranslator()->pgettext($msgctxt, $msgid); + } +} + +if (! function_exists('_npgettext')) { + /** + * Plural version of pgettext. + * + * @param string $msgctxt Context + * @param string $msgid Single form + * @param string $msgidPlural Plural form + * @param int $number Number of objects + * + * @return string translated plural form + */ + function _npgettext(string $msgctxt, string $msgid, string $msgidPlural, int $number): string + { + return Loader::getInstance()->getTranslator()->npgettext($msgctxt, $msgid, $msgidPlural, $number); + } +} + +if (! function_exists('_dgettext')) { + /** + * Translates a string. + * + * @param string $domain Domain to use + * @param string $msgid String to be translated + * + * @return string translated string (or original, if not found) + */ + function _dgettext(string $domain, string $msgid): string + { + return Loader::getInstance()->getTranslator($domain)->gettext($msgid); + } +} + +if (! function_exists('_dngettext')) { + /** + * Plural version of gettext. + * + * @param string $domain Domain to use + * @param string $msgid Single form + * @param string $msgidPlural Plural form + * @param int $number Number of objects + * + * @return string translated plural form + */ + function _dngettext(string $domain, string $msgid, string $msgidPlural, int $number): string + { + return Loader::getInstance()->getTranslator($domain)->ngettext($msgid, $msgidPlural, $number); + } +} + +if (! function_exists('_dpgettext')) { + /** + * Translate with context. + * + * @param string $domain Domain to use + * @param string $msgctxt Context + * @param string $msgid String to be translated + * + * @return string translated plural form + */ + function _dpgettext(string $domain, string $msgctxt, string $msgid): string + { + return Loader::getInstance()->getTranslator($domain)->pgettext($msgctxt, $msgid); + } +} + +if (! function_exists('_dnpgettext')) { + /** + * Plural version of pgettext. + * + * @param string $domain Domain to use + * @param string $msgctxt Context + * @param string $msgid Single form + * @param string $msgidPlural Plural form + * @param int $number Number of objects + * + * @return string translated plural form + */ + function _dnpgettext(string $domain, string $msgctxt, string $msgid, string $msgidPlural, int $number): string + { + return Loader::getInstance()->getTranslator($domain)->npgettext($msgctxt, $msgid, $msgidPlural, $number); + } +} diff --git a/vendor/phpmyadmin/motranslator/tests/Cache/ApcuCacheFactoryTest.php b/vendor/phpmyadmin/motranslator/tests/Cache/ApcuCacheFactoryTest.php new file mode 100644 index 0000000..b6262b1 --- /dev/null +++ b/vendor/phpmyadmin/motranslator/tests/Cache/ApcuCacheFactoryTest.php @@ -0,0 +1,98 @@ +markTestSkipped('ACPu extension is not installed and enabled for CLI'); + } + + protected function tearDown(): void + { + parent::tearDown(); + + apcu_clear_cache(); + } + + public function testGetInstanceReturnApcuCache(): void + { + $factory = new ApcuCacheFactory(); + $instance = $factory->getInstance(new MoParser(null), 'foo', 'bar'); + $this->assertInstanceOf(ApcuCache::class, $instance); + } + + public function testConstructorSetsTtl(): void + { + $locale = 'foo'; + $domain = 'bar'; + $msgid = 'Column'; + $ttl = 1; + + $factory = new ApcuCacheFactory($ttl); + $parser = new MoParser(__DIR__ . '/../data/little.mo'); + $factory->getInstance($parser, $locale, $domain); + sleep($ttl * 2); + + apcu_fetch('mo_' . $locale . '.' . $domain . '.' . $msgid, $success); + $this->assertFalse($success); + } + + public function testConstructorSetsReloadOnMiss(): void + { + $expected = 'Column'; + $locale = 'foo'; + $domain = 'bar'; + $msgid = 'Column'; + + $factory = new ApcuCacheFactory(0, false); + $parser = new MoParser(__DIR__ . '/../data/little.mo'); + + $instance = $factory->getInstance($parser, $locale, $domain); + + apcu_delete('mo_' . $locale . '.' . $domain . '.' . $msgid); + $actual = $instance->get($msgid); + $this->assertSame($expected, $actual); + } + + public function testConstructorSetsPrefix(): void + { + $expected = 'Pole'; + $locale = 'foo'; + $domain = 'bar'; + $msgid = 'Column'; + $prefix = 'baz_'; + + $factory = new ApcuCacheFactory(0, true, $prefix); + $parser = new MoParser(__DIR__ . '/../data/little.mo'); + + $factory->getInstance($parser, $locale, $domain); + + $actual = apcu_fetch($prefix . $locale . '.' . $domain . '.' . $msgid); + $this->assertSame($expected, $actual); + } +} diff --git a/vendor/phpmyadmin/motranslator/tests/Cache/ApcuCacheTest.php b/vendor/phpmyadmin/motranslator/tests/Cache/ApcuCacheTest.php new file mode 100644 index 0000000..5fa06f4 --- /dev/null +++ b/vendor/phpmyadmin/motranslator/tests/Cache/ApcuCacheTest.php @@ -0,0 +1,270 @@ +markTestSkipped('ACPu extension is not installed and enabled for CLI'); + } + + protected function tearDown(): void + { + parent::tearDown(); + + apcu_clear_cache(); + } + + public function testConstructorLoadsCache(): void + { + $expected = 'Pole'; + $locale = 'foo'; + $domain = 'bar'; + $msgid = 'Column'; + + new ApcuCache(new MoParser(__DIR__ . '/../data/little.mo'), $locale, $domain); + + $actual = apcu_fetch('mo_' . $locale . '.' . $domain . '.' . $msgid); + $this->assertSame($expected, $actual); + } + + public function testConstructorSetsTtl(): void + { + $locale = 'foo'; + $domain = 'bar'; + $msgid = 'Column'; + $ttl = 1; + + new ApcuCache(new MoParser(__DIR__ . '/../data/little.mo'), $locale, $domain, $ttl); + sleep($ttl * 2); + + apcu_fetch('mo_' . $locale . '.' . $domain . '.' . $msgid, $success); + $this->assertFalse($success); + apcu_fetch('mo_' . $locale . '.' . $domain . '.' . ApcuCache::LOADED_KEY, $success); + $this->assertFalse($success); + } + + public function testConstructorSetsReloadOnMiss(): void + { + $expected = 'Column'; + $locale = 'foo'; + $domain = 'bar'; + $msgid = 'Column'; + $prefix = 'baz_'; + + $cache = new ApcuCache( + new MoParser(__DIR__ . '/../data/little.mo'), + $locale, + $domain, + 0, + false, + $prefix + ); + + apcu_delete($prefix . $locale . '.' . $domain . '.' . $msgid); + $actual = $cache->get($msgid); + $this->assertEquals($expected, $actual); + } + + public function testConstructorSetsPrefix(): void + { + $expected = 'Pole'; + $locale = 'foo'; + $domain = 'bar'; + $msgid = 'Column'; + $prefix = 'baz_'; + + new ApcuCache(new MoParser(__DIR__ . '/../data/little.mo'), $locale, $domain, 0, true, $prefix); + + $actual = apcu_fetch($prefix . $locale . '.' . $domain . '.' . $msgid); + $this->assertSame($expected, $actual); + } + + public function testEnsureTranslationsLoadedSetsLoadedKey(): void + { + $expected = 1; + $locale = 'foo'; + $domain = 'bar'; + + new ApcuCache(new MoParser(__DIR__ . '/../data/little.mo'), $locale, $domain); + + $actual = apcu_fetch('mo_' . $locale . '.' . $domain . '.' . ApcuCache::LOADED_KEY); + $this->assertSame($expected, $actual); + } + + public function testEnsureTranslationsLoadedHonorsLock(): void + { + $locale = 'foo'; + $domain = 'bar'; + $msgid = 'Column'; + + $lock = 'mo_' . $locale . '.' . $domain . '.' . ApcuCache::LOADED_KEY; + apcu_entry($lock, static function () { + sleep(1); + + return 1; + }); + + new ApcuCache(new MoParser(__DIR__ . '/../data/little.mo'), $locale, $domain); + + $actual = apcu_fetch($lock); + $this->assertSame(1, $actual); + apcu_fetch('mo_' . $locale . '.' . $domain . '.' . $msgid, $success); + $this->assertFalse($success); + } + + public function testGetReturnsMsgstr(): void + { + $expected = 'Pole'; + $msgid = 'Column'; + + $cache = new ApcuCache(new MoParser(__DIR__ . '/../data/little.mo'), 'foo', 'bar'); + + $actual = $cache->get($msgid); + $this->assertSame($expected, $actual); + } + + public function testGetReturnsMsgidForCacheMiss(): void + { + $expected = 'Column'; + + $cache = new ApcuCache(new MoParser(null), 'foo', 'bar'); + + $actual = $cache->get($expected); + $this->assertSame($expected, $actual); + } + + public function testStoresMsgidOnCacheMiss(): void + { + $expected = 'Column'; + $locale = 'foo'; + $domain = 'bar'; + + $cache = new ApcuCache(new MoParser(null), $locale, $domain); + $cache->get($expected); + + $actual = apcu_fetch('mo_' . $locale . '.' . $domain . '.' . $expected); + $this->assertSame($expected, $actual); + } + + public function testGetReloadsOnCacheMiss(): void + { + $expected = 'Pole'; + $locale = 'foo'; + $domain = 'bar'; + $msgid = 'Column'; + + $cache = new ApcuCache(new MoParser(__DIR__ . '/../data/little.mo'), $locale, $domain); + + apcu_delete('mo_' . $locale . '.' . $domain . '.' . ApcuCache::LOADED_KEY); + $actual = $cache->get($msgid); + $this->assertSame($expected, $actual); + } + + public function testReloadOnMissHonorsLock(): void + { + $expected = 'Pole'; + $locale = 'foo'; + $domain = 'bar'; + $msgid = 'Column'; + + $cache = new ApcuCache(new MoParser(null), $locale, $domain); + + $method = new ReflectionMethod($cache, 'reloadOnMiss'); + $method->setAccessible(true); + + $key = 'mo_' . $locale . '.' . $domain . '.' . $msgid; + apcu_entry($key, static function () use ($expected): string { + sleep(1); + + return $expected; + }); + $actual = $method->invoke($cache, $msgid); + + $this->assertSame($expected, $actual); + } + + public function testSetSetsMsgstr(): void + { + $expected = 'Pole'; + $msgid = 'Column'; + + $cache = new ApcuCache(new MoParser(null), 'foo', 'bar'); + $cache->set($msgid, $expected); + + $actual = $cache->get($msgid); + $this->assertSame($expected, $actual); + } + + public function testHasReturnsFalse(): void + { + $cache = new ApcuCache(new MoParser(null), 'foo', 'bar'); + $actual = $cache->has('Column'); + $this->assertFalse($actual); + } + + public function testHasReturnsTrue(): void + { + $cache = new ApcuCache(new MoParser(__DIR__ . '/../data/little.mo'), 'foo', 'bar'); + $actual = $cache->has('Column'); + $this->assertTrue($actual); + } + + public function testSetAllSetsTranslations(): void + { + $translations = [ + 'foo' => 'bar', + 'and' => 'another', + ]; + + $cache = new ApcuCache(new MoParser(null), 'foo', 'bar'); + $cache->setAll($translations); + + foreach ($translations as $msgid => $expected) { + $actual = $cache->get($msgid); + $this->assertEquals($expected, $actual); + } + } + + public function testCacheStoresPluralForms(): void + { + $expected = ['first', 'second']; + $plural = ["%d pig went to the market\n", "%d pigs went to the market\n"]; + $msgid = implode(chr(0), $plural); + + $cache = new ApcuCache(new MoParser(null), 'foo', 'bar'); + $cache->set($msgid, implode(chr(0), $expected)); + + $msgstr = $cache->get($msgid); + $actual = explode(chr(0), $msgstr); + $this->assertSame($expected, $actual); + } +} diff --git a/vendor/phpmyadmin/motranslator/tests/Cache/ApcuDisabledTest.php b/vendor/phpmyadmin/motranslator/tests/Cache/ApcuDisabledTest.php new file mode 100644 index 0000000..60b4a49 --- /dev/null +++ b/vendor/phpmyadmin/motranslator/tests/Cache/ApcuDisabledTest.php @@ -0,0 +1,27 @@ +markTestSkipped('ext-apcu is enabled'); + } + + $this->expectException(CacheException::class); + $this->expectExceptionMessage('ACPu extension must be installed and enabled'); + new ApcuCache(new MoParser(null), 'foo', 'bar'); + } +} diff --git a/vendor/phpmyadmin/motranslator/tests/Cache/InMemoryCacheTest.php b/vendor/phpmyadmin/motranslator/tests/Cache/InMemoryCacheTest.php new file mode 100644 index 0000000..c81a56e --- /dev/null +++ b/vendor/phpmyadmin/motranslator/tests/Cache/InMemoryCacheTest.php @@ -0,0 +1,82 @@ +get('Column'); + $this->assertSame($expected, $actual); + } + + public function testGetReturnsMsgidForCacheMiss(): void + { + $expected = 'Column'; + $cache = new InMemoryCache(new MoParser(null)); + $actual = $cache->get($expected); + $this->assertSame($expected, $actual); + } + + public function testSetSetsMsgstr(): void + { + $expected = 'Pole'; + $msgid = 'Column'; + $cache = new InMemoryCache(new MoParser(null)); + $cache->set($msgid, $expected); + $actual = $cache->get($msgid); + $this->assertSame($expected, $actual); + } + + public function testHasReturnsFalse(): void + { + $cache = new InMemoryCache(new MoParser(null)); + $actual = $cache->has('Column'); + $this->assertFalse($actual); + } + + public function testHasReturnsTrue(): void + { + $cache = new InMemoryCache(new MoParser(__DIR__ . '/../data/little.mo')); + $actual = $cache->has('Column'); + $this->assertTrue($actual); + } + + public function testSetAllSetsTranslations(): void + { + $translations = [ + 'foo' => 'bar', + 'and' => 'another', + ]; + $cache = new InMemoryCache(new MoParser(null)); + $cache->setAll($translations); + foreach ($translations as $msgid => $expected) { + $actual = $cache->get($msgid); + $this->assertEquals($expected, $actual); + } + } + + public function testGetAllReturnsTranslations(): void + { + $expected = [ + 'foo' => 'bar', + 'and' => 'another', + ]; + $cache = new InMemoryCache(new MoParser(null)); + $cache->setAll($expected); + $actual = $cache->getAll(); + $this->assertSame($expected, $actual); + } +} diff --git a/vendor/phpmyadmin/motranslator/tests/FunctionsTest.php b/vendor/phpmyadmin/motranslator/tests/FunctionsTest.php new file mode 100644 index 0000000..8551957 --- /dev/null +++ b/vendor/phpmyadmin/motranslator/tests/FunctionsTest.php @@ -0,0 +1,102 @@ +assertEquals( + 'Typ', + _gettext('Type') + ); + + $this->assertEquals( + 'Typ', + __('Type') + ); + + $this->assertEquals( + '%d sekundy', + _ngettext( + '%d second', + '%d seconds', + 2 + ) + ); + + $this->assertEquals( + '%d seconds', + _npgettext( + 'context', + '%d second', + '%d seconds', + 2 + ) + ); + + $this->assertEquals( + 'Tabulka', + _pgettext( + 'Display format', + 'Table' + ) + ); + } + + public function testDomain(): void + { + $this->assertEquals( + 'Typ', + _dgettext('phpmyadmin', 'Type') + ); + + $this->assertEquals( + '%d sekundy', + _dngettext( + 'phpmyadmin', + '%d second', + '%d seconds', + 2 + ) + ); + + $this->assertEquals( + '%d seconds', + _dnpgettext( + 'phpmyadmin', + 'context', + '%d second', + '%d seconds', + 2 + ) + ); + + $this->assertEquals( + 'Tabulka', + _dpgettext( + 'phpmyadmin', + 'Display format', + 'Table' + ) + ); + } +} diff --git a/vendor/phpmyadmin/motranslator/tests/LoaderTest.php b/vendor/phpmyadmin/motranslator/tests/LoaderTest.php new file mode 100644 index 0000000..c9ee6d3 --- /dev/null +++ b/vendor/phpmyadmin/motranslator/tests/LoaderTest.php @@ -0,0 +1,325 @@ +assertEquals( + $expected, + Loader::listLocales($locale) + ); + } + + /** + * @return array[] + */ + public function localeList(): array + { + return [ + [ + 'cs_CZ', + [ + 'cs_CZ', + 'cs', + ], + ], + [ + 'sr_CS.UTF-8@latin', + [ + 'sr_CS.UTF-8@latin', + 'sr_CS@latin', + 'sr@latin', + 'sr_CS.UTF-8', + 'sr_CS', + 'sr', + ], + ], + // For a locale containing country code, we prefer + // full locale name, but if that's not found, fall back + // to the language only locale name. + [ + 'sr_RS', + [ + 'sr_RS', + 'sr', + ], + ], + // If language code is used, it's the only thing returned. + [ + 'sr', + ['sr'], + ], + // There is support for language and charset only. + [ + 'sr.UTF-8', + [ + 'sr.UTF-8', + 'sr', + ], + ], + + // It can also split out character set from the full locale name. + [ + 'sr_RS.UTF-8', + [ + 'sr_RS.UTF-8', + 'sr_RS', + 'sr', + ], + ], + + // There is support for @modifier in locale names as well. + [ + 'sr_RS.UTF-8@latin', + [ + 'sr_RS.UTF-8@latin', + 'sr_RS@latin', + 'sr@latin', + 'sr_RS.UTF-8', + 'sr_RS', + 'sr', + ], + ], + [ + 'sr.UTF-8@latin', + [ + 'sr.UTF-8@latin', + 'sr@latin', + 'sr.UTF-8', + 'sr', + ], + ], + + // We can pass in only language and modifier. + [ + 'sr@latin', + [ + 'sr@latin', + 'sr', + ], + ], + + // If locale name is not following the regular POSIX pattern, + // it's used verbatim. + [ + 'something', + ['something'], + ], + + // Passing in an empty string returns an empty array. + [ + '', + [], + ], + ]; + } + + private function getLoader(string $domain, string $locale): Loader + { + $loader = new Loader(); + $loader->setlocale($locale); + $loader->textdomain($domain); + $loader->bindtextdomain($domain, __DIR__ . '/data/locale/'); + + return $loader; + } + + public function testLocaleChange(): void + { + $loader = new Loader(); + $loader->setlocale('cs'); + $loader->textdomain('phpmyadmin'); + $loader->bindtextdomain('phpmyadmin', __DIR__ . '/data/locale/'); + $translator = $loader->getTranslator('phpmyadmin'); + $this->assertEquals('Typ', $translator->gettext('Type')); + $loader->setlocale('be_BY'); + $translator = $loader->getTranslator('phpmyadmin'); + $this->assertEquals('Тып', $translator->gettext('Type')); + } + + /** + * @dataProvider translatorData + */ + public function testGetTranslator(string $domain, string $locale, string $otherdomain, string $expected): void + { + $loader = $this->getLoader($domain, $locale); + $translator = $loader->getTranslator($otherdomain); + $this->assertEquals( + $expected, + $translator->gettext('Type') + ); + } + + /** + * @return array[] + */ + public function translatorData(): array + { + return [ + [ + 'phpmyadmin', + 'cs', + '', + 'Typ', + ], + [ + 'phpmyadmin', + 'cs_CZ', + '', + 'Typ', + ], + [ + 'phpmyadmin', + 'be_BY', + '', + 'Тып', + ], + [ + 'phpmyadmin', + 'be@latin', + '', + 'Typ', + ], + [ + 'phpmyadmin', + 'cs', + 'other', + 'Type', + ], + [ + 'other', + 'cs', + 'phpmyadmin', + 'Type', + ], + ]; + } + + public function testInstance(): void + { + $loader = Loader::getInstance(); + $loader->setlocale('cs'); + $loader->textdomain('phpmyadmin'); + $loader->bindtextdomain('phpmyadmin', __DIR__ . '/data/locale/'); + + $translator = $loader->getTranslator(); + $this->assertEquals( + 'Typ', + $translator->gettext('Type') + ); + + /* Ensure the object survives */ + $loader = Loader::getInstance(); + $translator = $loader->getTranslator(); + $this->assertEquals( + 'Typ', + $translator->gettext('Type') + ); + + /* Ensure the object can support different locale files for the same domain */ + $loader = Loader::getInstance(); + $loader->setlocale('be_BY'); + $loader->bindtextdomain('phpmyadmin', __DIR__ . '/data/locale/'); + $translator = $loader->getTranslator(); + $this->assertEquals( + 'Тып', + $translator->gettext('Type') + ); + } + + public function testDetect(): void + { + $GLOBALS['lang'] = 'foo'; + $loader = Loader::getInstance(); + $this->assertEquals( + 'foo', + $loader->detectlocale() + ); + unset($GLOBALS['lang']); + } + + public function testDetectEnv(): void + { + $loader = Loader::getInstance(); + foreach (['LC_MESSAGES', 'LC_ALL', 'LANG'] as $var) { + putenv($var); + if (getenv($var) === false) { + continue; + } + + $this->markTestSkipped('Unsetting environment does not work'); + } + + unset($GLOBALS['lang']); + putenv('LC_ALL=baz'); + $this->assertEquals( + 'baz', + $loader->detectlocale() + ); + putenv('LC_ALL'); + putenv('LC_MESSAGES=bar'); + $this->assertEquals( + 'bar', + $loader->detectlocale() + ); + putenv('LC_MESSAGES'); + putenv('LANG=barr'); + $this->assertEquals( + 'barr', + $loader->detectlocale() + ); + putenv('LANG'); + $this->assertEquals( + 'en', + $loader->detectlocale() + ); + } + + public function testSetCacheFactory(): void + { + $expected = 'Foo'; + $locale = 'be_BY'; + $domain = 'apcu'; + + $cache = $this->createMock(CacheInterface::class); + $cache->method('get') + ->willReturn($expected); + /** @var CacheFactoryInterface&MockObject $factory */ + $factory = $this->createMock(CacheFactoryInterface::class); + $factory->expects($this->once()) + ->method('getInstance') + ->with($this->isInstanceOf(MoParser::class), $locale, $domain) + ->willReturn($cache); + + Loader::setCacheFactory($factory); + $loader = Loader::getInstance(); + $loader->setlocale($locale); + $loader->bindtextdomain($domain, __DIR__ . '/data/locale/'); + $translator = $loader->getTranslator($domain); + + $actual = $translator->gettext('Type'); + $this->assertEquals($expected, $actual); + } +} diff --git a/vendor/phpmyadmin/motranslator/tests/MoFilesTest.php b/vendor/phpmyadmin/motranslator/tests/MoFilesTest.php new file mode 100644 index 0000000..b75bf75 --- /dev/null +++ b/vendor/phpmyadmin/motranslator/tests/MoFilesTest.php @@ -0,0 +1,230 @@ +getTranslator($filename); + $this->assertEquals( + 'Pole', + $parser->gettext('Column') + ); + // Non existing string + $this->assertEquals( + 'Column parser', + $parser->gettext('Column parser') + ); + } + + /** + * @dataProvider provideMoFiles + */ + public function testMoFilePlurals(string $filename): void + { + $parser = $this->getTranslator($filename); + $expected2 = '%d sekundy'; + if (strpos($filename, 'invalid-formula.mo') !== false || strpos($filename, 'lessplurals.mo') !== false) { + $expected0 = '%d sekunda'; + $expected2 = '%d sekunda'; + } elseif (strpos($filename, 'plurals.mo') !== false || strpos($filename, 'noheader.mo') !== false) { + $expected0 = '%d sekundy'; + } else { + $expected0 = '%d sekund'; + } + + $this->assertEquals( + $expected0, + $parser->ngettext( + '%d second', + '%d seconds', + 0 + ) + ); + $this->assertEquals( + '%d sekunda', + $parser->ngettext( + '%d second', + '%d seconds', + 1 + ) + ); + $this->assertEquals( + $expected2, + $parser->ngettext( + '%d second', + '%d seconds', + 2 + ) + ); + $this->assertEquals( + $expected0, + $parser->ngettext( + '%d second', + '%d seconds', + 5 + ) + ); + $this->assertEquals( + $expected0, + $parser->ngettext( + '%d second', + '%d seconds', + 10 + ) + ); + // Non existing string + $this->assertEquals( + '"%d" seconds', + $parser->ngettext( + '"%d" second', + '"%d" seconds', + 10 + ) + ); + } + + /** + * @dataProvider provideMoFiles + */ + public function testMoFileContext(string $filename): void + { + $parser = $this->getTranslator($filename); + $this->assertEquals( + 'Tabulka', + $parser->pgettext( + 'Display format', + 'Table' + ) + ); + } + + /** + * @dataProvider provideNotTranslatedFiles + */ + public function testMoFileNotTranslated(string $filename): void + { + $parser = $this->getTranslator($filename); + $this->assertEquals( + '%d second', + $parser->ngettext( + '%d second', + '%d seconds', + 1 + ) + ); + } + + /** + * @return array[] + */ + public function provideMoFiles(): array + { + return $this->getFiles('./tests/data/*.mo'); + } + + /** + * @return array[] + */ + public function provideErrorMoFiles(): array + { + return $this->getFiles('./tests/data/error/*.mo'); + } + + /** + * @return array[] + */ + public function provideNotTranslatedFiles(): array + { + return $this->getFiles('./tests/data/not-translated/*.mo'); + } + + /** + * @dataProvider provideErrorMoFiles + */ + public function testEmptyMoFile(string $file): void + { + $parser = new MoParser($file); + $translator = new Translator(new InMemoryCache($parser)); + if (basename($file) === 'magic.mo') { + $this->assertEquals(Translator::ERROR_BAD_MAGIC, $parser->error); + } else { + $this->assertEquals(Translator::ERROR_READING, $parser->error); + } + + $this->assertEquals( + 'Table', + $translator->pgettext( + 'Display format', + 'Table' + ) + ); + $this->assertEquals( + '"%d" seconds', + $translator->ngettext( + '"%d" second', + '"%d" seconds', + 10 + ) + ); + } + + /** + * @dataProvider provideMoFiles + */ + public function testExists(string $file): void + { + $parser = $this->getTranslator($file); + $this->assertEquals( + true, + $parser->exists('Column') + ); + $this->assertEquals( + false, + $parser->exists('Column parser') + ); + } + + /** + * @param string $pattern path names pattern to match + * + * @return array[] + */ + private function getFiles(string $pattern): array + { + $files = glob($pattern); + if ($files === false) { + return []; + } + + $result = []; + foreach ($files as $file) { + $result[] = [$file]; + } + + return $result; + } + + private function getTranslator(string $filename): Translator + { + return new Translator(new InMemoryCache(new MoParser($filename))); + } +} diff --git a/vendor/phpmyadmin/motranslator/tests/PluralFormulaTest.php b/vendor/phpmyadmin/motranslator/tests/PluralFormulaTest.php new file mode 100644 index 0000000..769f121 --- /dev/null +++ b/vendor/phpmyadmin/motranslator/tests/PluralFormulaTest.php @@ -0,0 +1,152 @@ +assertEquals( + $expected, + Translator::extractPluralsForms($header) + ); + } + + /** + * @return array[] + */ + public function pluralExtractionData(): array + { + return [ + // It defaults to a "Western-style" plural header. + [ + '', + 'nplurals=2; plural=n == 1 ? 0 : 1;', + ], + // Extracting it from the middle of the header works. + [ + "Content-type: text/html; charset=UTF-8\n" + . "Plural-Forms: nplurals=1; plural=0;\n" + . "Last-Translator: nobody\n", + ' nplurals=1; plural=0;', + ], + // It's also case-insensitive. + [ + "PLURAL-forms: nplurals=1; plural=0;\n", + ' nplurals=1; plural=0;', + ], + // It falls back to default if it's not on a separate line. + [ + 'Content-type: text/html; charset=UTF-8' // note the missing \n here + . "Plural-Forms: nplurals=1; plural=0;\n" + . "Last-Translator: nobody\n", + 'nplurals=2; plural=n == 1 ? 0 : 1;', + ], + ]; + } + + /** + * @dataProvider pluralCounts + */ + public function testPluralCounts(string $expr, int $expected): void + { + $this->assertEquals( + $expected, + Translator::extractPluralCount($expr) + ); + } + + /** + * @return array[] + */ + public function pluralCounts(): array + { + return [ + [ + '', + 1, + ], + [ + 'foo=2; expr', + 1, + ], + [ + 'nplurals=2; epxr', + 2, + ], + [ + ' nplurals = 3 ; epxr', + 3, + ], + [ + ' nplurals = 4 ; epxr ; ', + 4, + ], + [ + 'nplurals', + 1, + ], + ]; + } + + /** + * @dataProvider pluralExpressions + */ + public function testPluralExpression(string $expr, string $expected): void + { + $this->assertEquals( + $expected, + Translator::sanitizePluralExpression($expr) + ); + } + + /** + * @return array[] + */ + public function pluralExpressions(): array + { + return [ + [ + '', + '', + ], + [ + 'nplurals=2; plural=n == 1 ? 0 : 1;', + 'n == 1 ? 0 : 1', + ], + [ + ' nplurals=1; plural=0;', + '0', + ], + [ + "nplurals=6; plural=n==0 ? 0 : n==1 ? 1 : n==2 ? 2 : n%100>=3 && n%100<=10 ? 3 : n%100>=11 ? 4 : 5;\n", + 'n==0 ? 0 : n==1 ? 1 : n==2 ? 2 : n%100>=3 && n%100<=10 ? 3 : n%100>=11 ? 4 : 5', + ], + [ + ' nplurals=1; plural=baz(n);', + '(n)', + ], + [ + ' plural=n', + 'n', + ], + [ + 'nplurals', + 'n', + ], + ]; + } +} diff --git a/vendor/phpmyadmin/motranslator/tests/PluralTest.php b/vendor/phpmyadmin/motranslator/tests/PluralTest.php new file mode 100644 index 0000000..28da262 --- /dev/null +++ b/vendor/phpmyadmin/motranslator/tests/PluralTest.php @@ -0,0 +1,125 @@ +getTranslator(''); + $result = $parser->npgettext('context', "%d pig went to the market\n", "%d pigs went to the market\n", $number); + $this->assertSame($expected, $result); + } + + /** + * Data provider for test_npgettext. + * + * @return array[] + */ + public static function providerTestNpgettext(): array + { + return [ + [ + 1, + "%d pig went to the market\n", + ], + [ + 2, + "%d pigs went to the market\n", + ], + ]; + } + + /** + * Test for ngettext + */ + public function testNgettext(): void + { + $parser = $this->getTranslator(''); + $translationKey = implode(chr(0), ["%d pig went to the market\n", "%d pigs went to the market\n"]); + $parser->setTranslation($translationKey, ''); + $result = $parser->ngettext("%d pig went to the market\n", "%d pigs went to the market\n", 1); + $this->assertSame('', $result); + } + + /** + * @return array[] + */ + public function dataProviderPluralForms(): array + { + return [ + ['Plural-Forms: nplurals=2; plural=n != 1;'], + ['Plural-Forms: nplurals=1; plural=0;'], + ['Plural-Forms: nplurals=2; plural=(n > 1);'], + [ + 'Plural-Forms: nplurals=3; plural=n%10==1 && n%100!=11 ? 0 : n' + . '%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2;', + ], + ['Plural-Forms: nplurals=2; plural=n >= 2 && (n < 11 || n > 99);'], + ['Plural-Forms: nplurals=4; plural=n%100==1 ? 0 : n%100==2 ? 1 : n%100==3 || n%100==4 ? 2 : 3;'], + ['Plural-Forms: nplurals=3; plural=n==1 ? 0 : (n==0 || (n%100 > 0 && n%100 < 20)) ? 1 : 2;'], + [ + 'Plural-Forms: nplurals=2; plural=n != 1 && n != 2 && n != 3 &' + . '& (n % 10 == 4 || n % 10 == 6 || n % 10 == 9);', + ], + ]; + } + + /** + * Test for ngettext + * + * @see https://github.com/phpmyadmin/motranslator/issues/37 + * + * @dataProvider dataProviderPluralForms + */ + public function testNgettextSelectString(string $pluralForms): void + { + $parser = $this->getTranslator(''); + $parser->setTranslation( + '', + "Project-Id-Version: phpMyAdmin 5.1.0-dev\n" + . "Report-Msgid-Bugs-To: translators@phpmyadmin.net\n" + . "PO-Revision-Date: 2020-09-01 09:12+0000\n" + . "Last-Translator: William Desportes \n" + . 'Language-Team: English (United Kingdom) ' + . "\n" + . "Language: en_GB\n" + . "MIME-Version: 1.0\n" + . "Content-Type: text\/plain; charset=UTF-8\n" + . "Content-Transfer-Encoding: 8bit\n" + . $pluralForms . "\n" + . "X-Generator: Weblate 4.2.1-dev\n" + . '' + ); + $translationKey = implode(chr(0), ["%d pig went to the market\n", "%d pigs went to the market\n"]); + $parser->setTranslation($translationKey, 'ok'); + $result = $parser->ngettext("%d pig went to the market\n", "%d pigs went to the market\n", 1); + $this->assertSame('ok', $result); + } + + private function getTranslator(string $filename): Translator + { + return new Translator(new InMemoryCache(new MoParser($filename))); + } +} diff --git a/vendor/phpmyadmin/motranslator/tests/StringReaderTest.php b/vendor/phpmyadmin/motranslator/tests/StringReaderTest.php new file mode 100644 index 0000000..17dc794 --- /dev/null +++ b/vendor/phpmyadmin/motranslator/tests/StringReaderTest.php @@ -0,0 +1,40 @@ +assertFileExists($tempFile); + $stringReader = new StringReader($tempFile); + unlink($tempFile); + $actual = $stringReader->read(-1, -1); + $this->assertSame('', $actual); + } + + public function testReadIntArray(): void + { + $tempFile = (string) tempnam(sys_get_temp_dir(), 'phpMyAdmin_StringReaderTest'); + file_put_contents($tempFile, "\0\0\0\0\0\0\0\0\0\0\0\0"); + $this->assertFileExists($tempFile); + $stringReader = new StringReader($tempFile); + unlink($tempFile); + $actual = $stringReader->readintarray('V', 2, 2); + $this->assertSame([ + 1 => 0, + 2 => 0, + ], $actual); + } +} diff --git a/vendor/phpmyadmin/motranslator/tests/TranslatorTest.php b/vendor/phpmyadmin/motranslator/tests/TranslatorTest.php new file mode 100644 index 0000000..dd135b5 --- /dev/null +++ b/vendor/phpmyadmin/motranslator/tests/TranslatorTest.php @@ -0,0 +1,98 @@ +gettext('Column'); + $this->assertSame($expected, $actual); + } + + public function testConstructorWithNullParam(): void + { + $expected = 'Column'; + $translator = new Translator(null); + $actual = $translator->gettext($expected); + $this->assertSame($expected, $actual); + } + + /** + * Test on empty gettext + */ + public function testGettext(): void + { + $translator = $this->getTranslator(''); + $this->assertEquals('Test', $translator->gettext('Test')); + } + + /** + * Test set a translation + */ + public function testSetTranslation(): void + { + $translator = $this->getTranslator(''); + $translator->setTranslation('Test', 'Translation'); + $this->assertEquals('Translation', $translator->gettext('Test')); + } + + /** + * Test get and set all translations + */ + public function testGetSetTranslations(): void + { + $transTable = ['Test' => 'Translation']; + $translator = $this->getTranslator(''); + $translator->setTranslations($transTable); + $this->assertEquals('Translation', $translator->gettext('Test')); + $this->assertSame($transTable, $translator->getTranslations()); + $translator = $this->getTranslator(null); + $translator->setTranslations($transTable); + $this->assertSame($transTable, $translator->getTranslations()); + $this->assertEquals('Translation', $translator->gettext('Test')); + $transTable = [ + 'Test' => 'Translation', + 'shouldIWriteTests' => 'as much as possible', + 'is it hard' => 'it depends', + ]; + $translator = $this->getTranslator(''); + $translator->setTranslations($transTable); + $this->assertSame($transTable, $translator->getTranslations()); + $this->assertEquals('as much as possible', $translator->gettext('shouldIWriteTests')); + $translator = $this->getTranslator(null); + $translator->setTranslations($transTable); + $this->assertSame($transTable, $translator->getTranslations()); + $this->assertEquals('it depends', $translator->gettext('is it hard')); + } + + public function testGetTranslationsThrowsException(): void + { + /** @var CacheInterface&MockObject $cache */ + $cache = $this->createMock(CacheInterface::class); + $translator = new Translator($cache); + + $this->expectException(CacheException::class); + $translator->getTranslations(); + } + + private function getTranslator(?string $filename): Translator + { + return new Translator(new InMemoryCache(new MoParser($filename))); + } +} diff --git a/vendor/phpmyadmin/motranslator/tests/data/big.mo b/vendor/phpmyadmin/motranslator/tests/data/big.mo new file mode 100644 index 0000000..d913c35 Binary files /dev/null and b/vendor/phpmyadmin/motranslator/tests/data/big.mo differ diff --git a/vendor/phpmyadmin/motranslator/tests/data/error/big.mo b/vendor/phpmyadmin/motranslator/tests/data/error/big.mo new file mode 100644 index 0000000..cec12d5 --- /dev/null +++ b/vendor/phpmyadmin/motranslator/tests/data/error/big.mo @@ -0,0 +1 @@ +•Ž1111’’’’ \ No newline at end of file diff --git a/vendor/phpmyadmin/motranslator/tests/data/error/dos.mo b/vendor/phpmyadmin/motranslator/tests/data/error/dos.mo new file mode 100644 index 0000000..a86b22f --- /dev/null +++ b/vendor/phpmyadmin/motranslator/tests/data/error/dos.mo @@ -0,0 +1 @@ +•Ž1111’’’ \ No newline at end of file diff --git a/vendor/phpmyadmin/motranslator/tests/data/error/empty.mo b/vendor/phpmyadmin/motranslator/tests/data/error/empty.mo new file mode 100644 index 0000000..e69de29 diff --git a/vendor/phpmyadmin/motranslator/tests/data/error/fpd.mo b/vendor/phpmyadmin/motranslator/tests/data/error/fpd.mo new file mode 100644 index 0000000..6c5e67a Binary files /dev/null and b/vendor/phpmyadmin/motranslator/tests/data/error/fpd.mo differ diff --git a/vendor/phpmyadmin/motranslator/tests/data/error/fpdle.mo b/vendor/phpmyadmin/motranslator/tests/data/error/fpdle.mo new file mode 100644 index 0000000..7b55e41 --- /dev/null +++ b/vendor/phpmyadmin/motranslator/tests/data/error/fpdle.mo @@ -0,0 +1 @@ +Ž•’’’’’’’’’’’’’’’’’’’’’’’’ \ No newline at end of file diff --git a/vendor/phpmyadmin/motranslator/tests/data/error/magic.mo b/vendor/phpmyadmin/motranslator/tests/data/error/magic.mo new file mode 100644 index 0000000..81c545e --- /dev/null +++ b/vendor/phpmyadmin/motranslator/tests/data/error/magic.mo @@ -0,0 +1 @@ +1234 diff --git a/vendor/phpmyadmin/motranslator/tests/data/invalid-formula.mo b/vendor/phpmyadmin/motranslator/tests/data/invalid-formula.mo new file mode 100644 index 0000000..5838d69 Binary files /dev/null and b/vendor/phpmyadmin/motranslator/tests/data/invalid-formula.mo differ diff --git a/vendor/phpmyadmin/motranslator/tests/data/lessplurals.mo b/vendor/phpmyadmin/motranslator/tests/data/lessplurals.mo new file mode 100644 index 0000000..5288d9e Binary files /dev/null and b/vendor/phpmyadmin/motranslator/tests/data/lessplurals.mo differ diff --git a/vendor/phpmyadmin/motranslator/tests/data/little.mo b/vendor/phpmyadmin/motranslator/tests/data/little.mo new file mode 100644 index 0000000..1d83885 Binary files /dev/null and b/vendor/phpmyadmin/motranslator/tests/data/little.mo differ diff --git a/vendor/phpmyadmin/motranslator/tests/data/locale/be/LC_MESSAGES/phpmyadmin.mo b/vendor/phpmyadmin/motranslator/tests/data/locale/be/LC_MESSAGES/phpmyadmin.mo new file mode 100644 index 0000000..1394f80 Binary files /dev/null and b/vendor/phpmyadmin/motranslator/tests/data/locale/be/LC_MESSAGES/phpmyadmin.mo differ diff --git a/vendor/phpmyadmin/motranslator/tests/data/locale/be@latin/LC_MESSAGES/phpmyadmin.mo b/vendor/phpmyadmin/motranslator/tests/data/locale/be@latin/LC_MESSAGES/phpmyadmin.mo new file mode 100644 index 0000000..c1633d3 Binary files /dev/null and b/vendor/phpmyadmin/motranslator/tests/data/locale/be@latin/LC_MESSAGES/phpmyadmin.mo differ diff --git a/vendor/phpmyadmin/motranslator/tests/data/locale/cs/LC_MESSAGES/phpmyadmin.mo b/vendor/phpmyadmin/motranslator/tests/data/locale/cs/LC_MESSAGES/phpmyadmin.mo new file mode 100644 index 0000000..1d83885 Binary files /dev/null and b/vendor/phpmyadmin/motranslator/tests/data/locale/cs/LC_MESSAGES/phpmyadmin.mo differ diff --git a/vendor/phpmyadmin/motranslator/tests/data/noheader.mo b/vendor/phpmyadmin/motranslator/tests/data/noheader.mo new file mode 100644 index 0000000..599315c Binary files /dev/null and b/vendor/phpmyadmin/motranslator/tests/data/noheader.mo differ diff --git a/vendor/phpmyadmin/motranslator/tests/data/not-translated/fpd1.mo b/vendor/phpmyadmin/motranslator/tests/data/not-translated/fpd1.mo new file mode 100644 index 0000000..031ce08 Binary files /dev/null and b/vendor/phpmyadmin/motranslator/tests/data/not-translated/fpd1.mo differ diff --git a/vendor/phpmyadmin/motranslator/tests/data/not-translated/invalid-equation.mo b/vendor/phpmyadmin/motranslator/tests/data/not-translated/invalid-equation.mo new file mode 100644 index 0000000..69788df Binary files /dev/null and b/vendor/phpmyadmin/motranslator/tests/data/not-translated/invalid-equation.mo differ diff --git a/vendor/phpmyadmin/motranslator/tests/data/plurals.mo b/vendor/phpmyadmin/motranslator/tests/data/plurals.mo new file mode 100644 index 0000000..8885bb6 Binary files /dev/null and b/vendor/phpmyadmin/motranslator/tests/data/plurals.mo differ diff --git a/vendor/psr/cache/CHANGELOG.md b/vendor/psr/cache/CHANGELOG.md new file mode 100644 index 0000000..58ddab0 --- /dev/null +++ b/vendor/psr/cache/CHANGELOG.md @@ -0,0 +1,16 @@ +# Changelog + +All notable changes to this project will be documented in this file, in reverse chronological order by release. + +## 1.0.1 - 2016-08-06 + +### Fixed + +- Make spacing consistent in phpdoc annotations php-fig/cache#9 - chalasr +- Fix grammar in phpdoc annotations php-fig/cache#10 - chalasr +- Be more specific in docblocks that `getItems()` and `deleteItems()` take an array of strings (`string[]`) compared to just `array` php-fig/cache#8 - GrahamCampbell +- For `expiresAt()` and `expiresAfter()` in CacheItemInterface fix docblock to specify null as a valid parameters as well as an implementation of DateTimeInterface php-fig/cache#7 - GrahamCampbell + +## 1.0.0 - 2015-12-11 + +Initial stable release; reflects accepted PSR-6 specification diff --git a/vendor/psr/cache/LICENSE.txt b/vendor/psr/cache/LICENSE.txt new file mode 100644 index 0000000..b1c2c97 --- /dev/null +++ b/vendor/psr/cache/LICENSE.txt @@ -0,0 +1,19 @@ +Copyright (c) 2015 PHP Framework Interoperability Group + +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/vendor/psr/cache/README.md b/vendor/psr/cache/README.md new file mode 100644 index 0000000..9855a31 --- /dev/null +++ b/vendor/psr/cache/README.md @@ -0,0 +1,12 @@ +Caching Interface +============== + +This repository holds all interfaces related to [PSR-6 (Caching Interface)][psr-url]. + +Note that this is not a Caching implementation of its own. It is merely interfaces that describe the components of a Caching mechanism. + +The installable [package][package-url] and [implementations][implementation-url] are listed on Packagist. + +[psr-url]: https://www.php-fig.org/psr/psr-6/ +[package-url]: https://packagist.org/packages/psr/cache +[implementation-url]: https://packagist.org/providers/psr/cache-implementation diff --git a/vendor/psr/cache/composer.json b/vendor/psr/cache/composer.json new file mode 100644 index 0000000..4b68797 --- /dev/null +++ b/vendor/psr/cache/composer.json @@ -0,0 +1,25 @@ +{ + "name": "psr/cache", + "description": "Common interface for caching libraries", + "keywords": ["psr", "psr-6", "cache"], + "license": "MIT", + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "require": { + "php": ">=8.0.0" + }, + "autoload": { + "psr-4": { + "Psr\\Cache\\": "src/" + } + }, + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + } +} diff --git a/vendor/psr/cache/src/CacheException.php b/vendor/psr/cache/src/CacheException.php new file mode 100644 index 0000000..bb785f4 --- /dev/null +++ b/vendor/psr/cache/src/CacheException.php @@ -0,0 +1,10 @@ +=7.4.0" + }, + "autoload": { + "psr-4": { + "Psr\\Container\\": "src/" + } + }, + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + } +} diff --git a/vendor/psr/container/src/ContainerExceptionInterface.php b/vendor/psr/container/src/ContainerExceptionInterface.php new file mode 100644 index 0000000..0f213f2 --- /dev/null +++ b/vendor/psr/container/src/ContainerExceptionInterface.php @@ -0,0 +1,12 @@ +logger = $logger; + } + + public function doSomething() + { + if ($this->logger) { + $this->logger->info('Doing work'); + } + + try { + $this->doSomethingElse(); + } catch (Exception $exception) { + $this->logger->error('Oh no!', array('exception' => $exception)); + } + + // do something useful + } +} +``` + +You can then pick one of the implementations of the interface to get a logger. + +If you want to implement the interface, you can require this package and +implement `Psr\Log\LoggerInterface` in your code. Please read the +[specification text](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-3-logger-interface.md) +for details. diff --git a/vendor/psr/log/composer.json b/vendor/psr/log/composer.json new file mode 100644 index 0000000..879fc6f --- /dev/null +++ b/vendor/psr/log/composer.json @@ -0,0 +1,26 @@ +{ + "name": "psr/log", + "description": "Common interface for logging libraries", + "keywords": ["psr", "psr-3", "log"], + "homepage": "https://github.com/php-fig/log", + "license": "MIT", + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "require": { + "php": ">=8.0.0" + }, + "autoload": { + "psr-4": { + "Psr\\Log\\": "src" + } + }, + "extra": { + "branch-alias": { + "dev-master": "3.x-dev" + } + } +} diff --git a/vendor/psr/log/src/AbstractLogger.php b/vendor/psr/log/src/AbstractLogger.php new file mode 100644 index 0000000..d60a091 --- /dev/null +++ b/vendor/psr/log/src/AbstractLogger.php @@ -0,0 +1,15 @@ +logger = $logger; + } +} diff --git a/vendor/psr/log/src/LoggerInterface.php b/vendor/psr/log/src/LoggerInterface.php new file mode 100644 index 0000000..cb4cf64 --- /dev/null +++ b/vendor/psr/log/src/LoggerInterface.php @@ -0,0 +1,98 @@ +log(LogLevel::EMERGENCY, $message, $context); + } + + /** + * Action must be taken immediately. + * + * Example: Entire website down, database unavailable, etc. This should + * trigger the SMS alerts and wake you up. + */ + public function alert(string|\Stringable $message, array $context = []): void + { + $this->log(LogLevel::ALERT, $message, $context); + } + + /** + * Critical conditions. + * + * Example: Application component unavailable, unexpected exception. + */ + public function critical(string|\Stringable $message, array $context = []): void + { + $this->log(LogLevel::CRITICAL, $message, $context); + } + + /** + * Runtime errors that do not require immediate action but should typically + * be logged and monitored. + */ + public function error(string|\Stringable $message, array $context = []): void + { + $this->log(LogLevel::ERROR, $message, $context); + } + + /** + * Exceptional occurrences that are not errors. + * + * Example: Use of deprecated APIs, poor use of an API, undesirable things + * that are not necessarily wrong. + */ + public function warning(string|\Stringable $message, array $context = []): void + { + $this->log(LogLevel::WARNING, $message, $context); + } + + /** + * Normal but significant events. + */ + public function notice(string|\Stringable $message, array $context = []): void + { + $this->log(LogLevel::NOTICE, $message, $context); + } + + /** + * Interesting events. + * + * Example: User logs in, SQL logs. + */ + public function info(string|\Stringable $message, array $context = []): void + { + $this->log(LogLevel::INFO, $message, $context); + } + + /** + * Detailed debug information. + */ + public function debug(string|\Stringable $message, array $context = []): void + { + $this->log(LogLevel::DEBUG, $message, $context); + } + + /** + * Logs with an arbitrary level. + * + * @param mixed $level + * + * @throws \Psr\Log\InvalidArgumentException + */ + abstract public function log($level, string|\Stringable $message, array $context = []): void; +} diff --git a/vendor/psr/log/src/NullLogger.php b/vendor/psr/log/src/NullLogger.php new file mode 100644 index 0000000..de0561e --- /dev/null +++ b/vendor/psr/log/src/NullLogger.php @@ -0,0 +1,26 @@ +logger) { }` + * blocks. + */ +class NullLogger extends AbstractLogger +{ + /** + * Logs with an arbitrary level. + * + * @param mixed[] $context + * + * @throws \Psr\Log\InvalidArgumentException + */ + public function log($level, string|\Stringable $message, array $context = []): void + { + // noop + } +} diff --git a/vendor/symfony/cache-contracts/CHANGELOG.md b/vendor/symfony/cache-contracts/CHANGELOG.md new file mode 100644 index 0000000..7932e26 --- /dev/null +++ b/vendor/symfony/cache-contracts/CHANGELOG.md @@ -0,0 +1,5 @@ +CHANGELOG +========= + +The changelog is maintained for all Symfony contracts at the following URL: +https://github.com/symfony/contracts/blob/main/CHANGELOG.md diff --git a/vendor/symfony/cache-contracts/CacheInterface.php b/vendor/symfony/cache-contracts/CacheInterface.php new file mode 100644 index 0000000..3e4aaf6 --- /dev/null +++ b/vendor/symfony/cache-contracts/CacheInterface.php @@ -0,0 +1,59 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Contracts\Cache; + +use Psr\Cache\CacheItemInterface; +use Psr\Cache\InvalidArgumentException; + +/** + * Covers most simple to advanced caching needs. + * + * @author Nicolas Grekas + */ +interface CacheInterface +{ + /** + * Fetches a value from the pool or computes it if not found. + * + * On cache misses, a callback is called that should return the missing value. + * This callback is given a PSR-6 CacheItemInterface instance corresponding to the + * requested key, that could be used e.g. for expiration control. It could also + * be an ItemInterface instance when its additional features are needed. + * + * @template T + * + * @param string $key The key of the item to retrieve from the cache + * @param (callable(CacheItemInterface,bool):T)|(callable(ItemInterface,bool):T)|CallbackInterface $callback + * @param float|null $beta A float that, as it grows, controls the likeliness of triggering + * early expiration. 0 disables it, INF forces immediate expiration. + * The default (or providing null) is implementation dependent but should + * typically be 1.0, which should provide optimal stampede protection. + * See https://en.wikipedia.org/wiki/Cache_stampede#Probabilistic_early_expiration + * @param array &$metadata The metadata of the cached item {@see ItemInterface::getMetadata()} + * + * @return T + * + * @throws InvalidArgumentException When $key is not valid or when $beta is negative + */ + public function get(string $key, callable $callback, ?float $beta = null, ?array &$metadata = null): mixed; + + /** + * Removes an item from the pool. + * + * @param string $key The key to delete + * + * @return bool True if the item was successfully removed, false if there was any error + * + * @throws InvalidArgumentException When $key is not valid + */ + public function delete(string $key): bool; +} diff --git a/vendor/symfony/cache-contracts/CacheTrait.php b/vendor/symfony/cache-contracts/CacheTrait.php new file mode 100644 index 0000000..c2f6580 --- /dev/null +++ b/vendor/symfony/cache-contracts/CacheTrait.php @@ -0,0 +1,72 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Contracts\Cache; + +use Psr\Cache\CacheItemPoolInterface; +use Psr\Cache\InvalidArgumentException; +use Psr\Log\LoggerInterface; + +// Help opcache.preload discover always-needed symbols +class_exists(InvalidArgumentException::class); + +/** + * An implementation of CacheInterface for PSR-6 CacheItemPoolInterface classes. + * + * @author Nicolas Grekas + */ +trait CacheTrait +{ + public function get(string $key, callable $callback, ?float $beta = null, ?array &$metadata = null): mixed + { + return $this->doGet($this, $key, $callback, $beta, $metadata); + } + + public function delete(string $key): bool + { + return $this->deleteItem($key); + } + + private function doGet(CacheItemPoolInterface $pool, string $key, callable $callback, ?float $beta, ?array &$metadata = null, ?LoggerInterface $logger = null): mixed + { + if (0 > $beta ??= 1.0) { + throw new class(sprintf('Argument "$beta" provided to "%s::get()" must be a positive number, %f given.', static::class, $beta)) extends \InvalidArgumentException implements InvalidArgumentException {}; + } + + $item = $pool->getItem($key); + $recompute = !$item->isHit() || \INF === $beta; + $metadata = $item instanceof ItemInterface ? $item->getMetadata() : []; + + if (!$recompute && $metadata) { + $expiry = $metadata[ItemInterface::METADATA_EXPIRY] ?? false; + $ctime = $metadata[ItemInterface::METADATA_CTIME] ?? false; + + if ($recompute = $ctime && $expiry && $expiry <= ($now = microtime(true)) - $ctime / 1000 * $beta * log(random_int(1, \PHP_INT_MAX) / \PHP_INT_MAX)) { + // force applying defaultLifetime to expiry + $item->expiresAt(null); + $logger?->info('Item "{key}" elected for early recomputation {delta}s before its expiration', [ + 'key' => $key, + 'delta' => sprintf('%.1f', $expiry - $now), + ]); + } + } + + if ($recompute) { + $save = true; + $item->set($callback($item, $save)); + if ($save) { + $pool->save($item); + } + } + + return $item->get(); + } +} diff --git a/vendor/symfony/cache-contracts/CallbackInterface.php b/vendor/symfony/cache-contracts/CallbackInterface.php new file mode 100644 index 0000000..15941e9 --- /dev/null +++ b/vendor/symfony/cache-contracts/CallbackInterface.php @@ -0,0 +1,32 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Contracts\Cache; + +use Psr\Cache\CacheItemInterface; + +/** + * Computes and returns the cached value of an item. + * + * @author Nicolas Grekas + * + * @template T + */ +interface CallbackInterface +{ + /** + * @param CacheItemInterface|ItemInterface $item The item to compute the value for + * @param bool &$save Should be set to false when the value should not be saved in the pool + * + * @return T The computed value for the passed item + */ + public function __invoke(CacheItemInterface $item, bool &$save): mixed; +} diff --git a/vendor/symfony/cache-contracts/ItemInterface.php b/vendor/symfony/cache-contracts/ItemInterface.php new file mode 100644 index 0000000..8c4c512 --- /dev/null +++ b/vendor/symfony/cache-contracts/ItemInterface.php @@ -0,0 +1,65 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Contracts\Cache; + +use Psr\Cache\CacheException; +use Psr\Cache\CacheItemInterface; +use Psr\Cache\InvalidArgumentException; + +/** + * Augments PSR-6's CacheItemInterface with support for tags and metadata. + * + * @author Nicolas Grekas + */ +interface ItemInterface extends CacheItemInterface +{ + /** + * References the Unix timestamp stating when the item will expire. + */ + public const METADATA_EXPIRY = 'expiry'; + + /** + * References the time the item took to be created, in milliseconds. + */ + public const METADATA_CTIME = 'ctime'; + + /** + * References the list of tags that were assigned to the item, as string[]. + */ + public const METADATA_TAGS = 'tags'; + + /** + * Reserved characters that cannot be used in a key or tag. + */ + public const RESERVED_CHARACTERS = '{}()/\@:'; + + /** + * Adds a tag to a cache item. + * + * Tags are strings that follow the same validation rules as keys. + * + * @param string|string[] $tags A tag or array of tags + * + * @return $this + * + * @throws InvalidArgumentException When $tag is not valid + * @throws CacheException When the item comes from a pool that is not tag-aware + */ + public function tag(string|iterable $tags): static; + + /** + * Returns a list of metadata info that were saved alongside with the cached value. + * + * See ItemInterface::METADATA_* consts for keys potentially found in the returned array. + */ + public function getMetadata(): array; +} diff --git a/vendor/symfony/cache-contracts/LICENSE b/vendor/symfony/cache-contracts/LICENSE new file mode 100644 index 0000000..7536cae --- /dev/null +++ b/vendor/symfony/cache-contracts/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2018-present Fabien Potencier + +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/vendor/symfony/cache-contracts/README.md b/vendor/symfony/cache-contracts/README.md new file mode 100644 index 0000000..ffe0833 --- /dev/null +++ b/vendor/symfony/cache-contracts/README.md @@ -0,0 +1,9 @@ +Symfony Cache Contracts +======================= + +A set of abstractions extracted out of the Symfony components. + +Can be used to build on semantics that the Symfony components proved useful and +that already have battle tested implementations. + +See https://github.com/symfony/contracts/blob/main/README.md for more information. diff --git a/vendor/symfony/cache-contracts/TagAwareCacheInterface.php b/vendor/symfony/cache-contracts/TagAwareCacheInterface.php new file mode 100644 index 0000000..8e0b6be --- /dev/null +++ b/vendor/symfony/cache-contracts/TagAwareCacheInterface.php @@ -0,0 +1,38 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Contracts\Cache; + +use Psr\Cache\InvalidArgumentException; + +/** + * Allows invalidating cached items using tags. + * + * @author Nicolas Grekas + */ +interface TagAwareCacheInterface extends CacheInterface +{ + /** + * Invalidates cached items using tags. + * + * When implemented on a PSR-6 pool, invalidation should not apply + * to deferred items. Instead, they should be committed as usual. + * This allows replacing old tagged values by new ones without + * race conditions. + * + * @param string[] $tags An array of tags to invalidate + * + * @return bool True on success + * + * @throws InvalidArgumentException When $tags is not valid + */ + public function invalidateTags(array $tags): bool; +} diff --git a/vendor/symfony/cache-contracts/composer.json b/vendor/symfony/cache-contracts/composer.json new file mode 100644 index 0000000..fe261d1 --- /dev/null +++ b/vendor/symfony/cache-contracts/composer.json @@ -0,0 +1,35 @@ +{ + "name": "symfony/cache-contracts", + "type": "library", + "description": "Generic abstractions related to caching", + "keywords": ["abstractions", "contracts", "decoupling", "interfaces", "interoperability", "standards"], + "homepage": "https://symfony.com", + "license": "MIT", + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "require": { + "php": ">=8.1", + "psr/cache": "^3.0" + }, + "autoload": { + "psr-4": { "Symfony\\Contracts\\Cache\\": "" } + }, + "minimum-stability": "dev", + "extra": { + "branch-alias": { + "dev-main": "3.5-dev" + }, + "thanks": { + "name": "symfony/contracts", + "url": "https://github.com/symfony/contracts" + } + } +} diff --git a/vendor/symfony/cache/Adapter/AbstractAdapter.php b/vendor/symfony/cache/Adapter/AbstractAdapter.php new file mode 100644 index 0000000..c03868d --- /dev/null +++ b/vendor/symfony/cache/Adapter/AbstractAdapter.php @@ -0,0 +1,191 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Cache\Adapter; + +use Psr\Log\LoggerAwareInterface; +use Psr\Log\LoggerInterface; +use Symfony\Component\Cache\CacheItem; +use Symfony\Component\Cache\Exception\InvalidArgumentException; +use Symfony\Component\Cache\ResettableInterface; +use Symfony\Component\Cache\Traits\AbstractAdapterTrait; +use Symfony\Component\Cache\Traits\ContractsTrait; +use Symfony\Contracts\Cache\CacheInterface; + +/** + * @author Nicolas Grekas + */ +abstract class AbstractAdapter implements AdapterInterface, CacheInterface, LoggerAwareInterface, ResettableInterface +{ + use AbstractAdapterTrait; + use ContractsTrait; + + /** + * @internal + */ + protected const NS_SEPARATOR = ':'; + + private static bool $apcuSupported; + + protected function __construct(string $namespace = '', int $defaultLifetime = 0) + { + $this->namespace = '' === $namespace ? '' : CacheItem::validateKey($namespace).static::NS_SEPARATOR; + $this->defaultLifetime = $defaultLifetime; + if (null !== $this->maxIdLength && \strlen($namespace) > $this->maxIdLength - 24) { + throw new InvalidArgumentException(\sprintf('Namespace must be %d chars max, %d given ("%s").', $this->maxIdLength - 24, \strlen($namespace), $namespace)); + } + self::$createCacheItem ??= \Closure::bind( + static function ($key, $value, $isHit) { + $item = new CacheItem(); + $item->key = $key; + $item->value = $value; + $item->isHit = $isHit; + $item->unpack(); + + return $item; + }, + null, + CacheItem::class + ); + self::$mergeByLifetime ??= \Closure::bind( + static function ($deferred, $namespace, &$expiredIds, $getId, $defaultLifetime) { + $byLifetime = []; + $now = microtime(true); + $expiredIds = []; + + foreach ($deferred as $key => $item) { + $key = (string) $key; + if (null === $item->expiry) { + $ttl = 0 < $defaultLifetime ? $defaultLifetime : 0; + } elseif (!$item->expiry) { + $ttl = 0; + } elseif (0 >= $ttl = (int) (0.1 + $item->expiry - $now)) { + $expiredIds[] = $getId($key); + continue; + } + $byLifetime[$ttl][$getId($key)] = $item->pack(); + } + + return $byLifetime; + }, + null, + CacheItem::class + ); + } + + /** + * Returns the best possible adapter that your runtime supports. + * + * Using ApcuAdapter makes system caches compatible with read-only filesystems. + */ + public static function createSystemCache(string $namespace, int $defaultLifetime, string $version, string $directory, ?LoggerInterface $logger = null): AdapterInterface + { + $opcache = new PhpFilesAdapter($namespace, $defaultLifetime, $directory, true); + if (null !== $logger) { + $opcache->setLogger($logger); + } + + if (!self::$apcuSupported ??= ApcuAdapter::isSupported()) { + return $opcache; + } + + if ('cli' === \PHP_SAPI && !filter_var(\ini_get('apc.enable_cli'), \FILTER_VALIDATE_BOOL)) { + return $opcache; + } + + $apcu = new ApcuAdapter($namespace, intdiv($defaultLifetime, 5), $version); + if (null !== $logger) { + $apcu->setLogger($logger); + } + + return new ChainAdapter([$apcu, $opcache]); + } + + public static function createConnection(#[\SensitiveParameter] string $dsn, array $options = []): mixed + { + if (str_starts_with($dsn, 'redis:') || str_starts_with($dsn, 'rediss:')) { + return RedisAdapter::createConnection($dsn, $options); + } + if (str_starts_with($dsn, 'memcached:')) { + return MemcachedAdapter::createConnection($dsn, $options); + } + if (str_starts_with($dsn, 'couchbase:')) { + if (class_exists('CouchbaseBucket') && CouchbaseBucketAdapter::isSupported()) { + return CouchbaseBucketAdapter::createConnection($dsn, $options); + } + + return CouchbaseCollectionAdapter::createConnection($dsn, $options); + } + if (preg_match('/^(mysql|oci|pgsql|sqlsrv|sqlite):/', $dsn)) { + return PdoAdapter::createConnection($dsn, $options); + } + + throw new InvalidArgumentException('Unsupported DSN: it does not start with "redis[s]:", "memcached:", "couchbase:", "mysql:", "oci:", "pgsql:", "sqlsrv:" nor "sqlite:".'); + } + + public function commit(): bool + { + $ok = true; + $byLifetime = (self::$mergeByLifetime)($this->deferred, $this->namespace, $expiredIds, $this->getId(...), $this->defaultLifetime); + $retry = $this->deferred = []; + + if ($expiredIds) { + try { + $this->doDelete($expiredIds); + } catch (\Exception $e) { + $ok = false; + CacheItem::log($this->logger, 'Failed to delete expired items: '.$e->getMessage(), ['exception' => $e, 'cache-adapter' => get_debug_type($this)]); + } + } + foreach ($byLifetime as $lifetime => $values) { + try { + $e = $this->doSave($values, $lifetime); + } catch (\Exception $e) { + } + if (true === $e || [] === $e) { + continue; + } + if (\is_array($e) || 1 === \count($values)) { + foreach (\is_array($e) ? $e : array_keys($values) as $id) { + $ok = false; + $v = $values[$id]; + $type = get_debug_type($v); + $message = \sprintf('Failed to save key "{key}" of type %s%s', $type, $e instanceof \Exception ? ': '.$e->getMessage() : '.'); + CacheItem::log($this->logger, $message, ['key' => substr($id, \strlen($this->namespace)), 'exception' => $e instanceof \Exception ? $e : null, 'cache-adapter' => get_debug_type($this)]); + } + } else { + foreach ($values as $id => $v) { + $retry[$lifetime][] = $id; + } + } + } + + // When bulk-save failed, retry each item individually + foreach ($retry as $lifetime => $ids) { + foreach ($ids as $id) { + try { + $v = $byLifetime[$lifetime][$id]; + $e = $this->doSave([$id => $v], $lifetime); + } catch (\Exception $e) { + } + if (true === $e || [] === $e) { + continue; + } + $ok = false; + $type = get_debug_type($v); + $message = \sprintf('Failed to save key "{key}" of type %s%s', $type, $e instanceof \Exception ? ': '.$e->getMessage() : '.'); + CacheItem::log($this->logger, $message, ['key' => substr($id, \strlen($this->namespace)), 'exception' => $e instanceof \Exception ? $e : null, 'cache-adapter' => get_debug_type($this)]); + } + } + + return $ok; + } +} diff --git a/vendor/symfony/cache/Adapter/AbstractTagAwareAdapter.php b/vendor/symfony/cache/Adapter/AbstractTagAwareAdapter.php new file mode 100644 index 0000000..822c30f --- /dev/null +++ b/vendor/symfony/cache/Adapter/AbstractTagAwareAdapter.php @@ -0,0 +1,320 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Cache\Adapter; + +use Psr\Log\LoggerAwareInterface; +use Symfony\Component\Cache\CacheItem; +use Symfony\Component\Cache\Exception\InvalidArgumentException; +use Symfony\Component\Cache\ResettableInterface; +use Symfony\Component\Cache\Traits\AbstractAdapterTrait; +use Symfony\Component\Cache\Traits\ContractsTrait; +use Symfony\Contracts\Cache\TagAwareCacheInterface; + +/** + * Abstract for native TagAware adapters. + * + * To keep info on tags, the tags are both serialized as part of cache value and provided as tag ids + * to Adapters on operations when needed for storage to doSave(), doDelete() & doInvalidate(). + * + * @author Nicolas Grekas + * @author AndrĆ© RĆømcke + * + * @internal + */ +abstract class AbstractTagAwareAdapter implements TagAwareAdapterInterface, TagAwareCacheInterface, LoggerAwareInterface, ResettableInterface +{ + use AbstractAdapterTrait; + use ContractsTrait; + + private const TAGS_PREFIX = "\1tags\1"; + + protected function __construct(string $namespace = '', int $defaultLifetime = 0) + { + $this->namespace = '' === $namespace ? '' : CacheItem::validateKey($namespace).':'; + $this->defaultLifetime = $defaultLifetime; + if (null !== $this->maxIdLength && \strlen($namespace) > $this->maxIdLength - 24) { + throw new InvalidArgumentException(\sprintf('Namespace must be %d chars max, %d given ("%s").', $this->maxIdLength - 24, \strlen($namespace), $namespace)); + } + self::$createCacheItem ??= \Closure::bind( + static function ($key, $value, $isHit) { + $item = new CacheItem(); + $item->key = $key; + $item->isTaggable = true; + // If structure does not match what we expect return item as is (no value and not a hit) + if (!\is_array($value) || !\array_key_exists('value', $value)) { + return $item; + } + $item->isHit = $isHit; + // Extract value, tags and meta data from the cache value + $item->value = $value['value']; + $item->metadata[CacheItem::METADATA_TAGS] = isset($value['tags']) ? array_combine($value['tags'], $value['tags']) : []; + if (isset($value['meta'])) { + // For compactness these values are packed, & expiry is offset to reduce size + $v = unpack('Ve/Nc', $value['meta']); + $item->metadata[CacheItem::METADATA_EXPIRY] = $v['e'] + CacheItem::METADATA_EXPIRY_OFFSET; + $item->metadata[CacheItem::METADATA_CTIME] = $v['c']; + } + + return $item; + }, + null, + CacheItem::class + ); + self::$mergeByLifetime ??= \Closure::bind( + static function ($deferred, &$expiredIds, $getId, $tagPrefix, $defaultLifetime) { + $byLifetime = []; + $now = microtime(true); + $expiredIds = []; + + foreach ($deferred as $key => $item) { + $key = (string) $key; + if (null === $item->expiry) { + $ttl = 0 < $defaultLifetime ? $defaultLifetime : 0; + } elseif (!$item->expiry) { + $ttl = 0; + } elseif (0 >= $ttl = (int) (0.1 + $item->expiry - $now)) { + $expiredIds[] = $getId($key); + continue; + } + // Store Value and Tags on the cache value + if (isset(($metadata = $item->newMetadata)[CacheItem::METADATA_TAGS])) { + $value = ['value' => $item->value, 'tags' => $metadata[CacheItem::METADATA_TAGS]]; + unset($metadata[CacheItem::METADATA_TAGS]); + } else { + $value = ['value' => $item->value, 'tags' => []]; + } + + if ($metadata) { + // For compactness, expiry and creation duration are packed, using magic numbers as separators + $value['meta'] = pack('VN', (int) (0.1 + $metadata[CacheItem::METADATA_EXPIRY] - CacheItem::METADATA_EXPIRY_OFFSET), $metadata[CacheItem::METADATA_CTIME]); + } + + // Extract tag changes, these should be removed from values in doSave() + $value['tag-operations'] = ['add' => [], 'remove' => []]; + $oldTags = $item->metadata[CacheItem::METADATA_TAGS] ?? []; + foreach (array_diff_key($value['tags'], $oldTags) as $addedTag) { + $value['tag-operations']['add'][] = $getId($tagPrefix.$addedTag); + } + foreach (array_diff_key($oldTags, $value['tags']) as $removedTag) { + $value['tag-operations']['remove'][] = $getId($tagPrefix.$removedTag); + } + $value['tags'] = array_keys($value['tags']); + + $byLifetime[$ttl][$getId($key)] = $value; + $item->metadata = $item->newMetadata; + } + + return $byLifetime; + }, + null, + CacheItem::class + ); + } + + /** + * Persists several cache items immediately. + * + * @param array $values The values to cache, indexed by their cache identifier + * @param int $lifetime The lifetime of the cached values, 0 for persisting until manual cleaning + * @param array[] $addTagData Hash where key is tag id, and array value is list of cache id's to add to tag + * @param array[] $removeTagData Hash where key is tag id, and array value is list of cache id's to remove to tag + * + * @return array The identifiers that failed to be cached or a boolean stating if caching succeeded or not + */ + abstract protected function doSave(array $values, int $lifetime, array $addTagData = [], array $removeTagData = []): array; + + /** + * Removes multiple items from the pool and their corresponding tags. + * + * @param array $ids An array of identifiers that should be removed from the pool + */ + abstract protected function doDelete(array $ids): bool; + + /** + * Removes relations between tags and deleted items. + * + * @param array $tagData Array of tag => key identifiers that should be removed from the pool + */ + abstract protected function doDeleteTagRelations(array $tagData): bool; + + /** + * Invalidates cached items using tags. + * + * @param string[] $tagIds An array of tags to invalidate, key is tag and value is tag id + */ + abstract protected function doInvalidate(array $tagIds): bool; + + /** + * Delete items and yields the tags they were bound to. + */ + protected function doDeleteYieldTags(array $ids): iterable + { + foreach ($this->doFetch($ids) as $id => $value) { + yield $id => \is_array($value) && \is_array($value['tags'] ?? null) ? $value['tags'] : []; + } + + $this->doDelete($ids); + } + + public function commit(): bool + { + $ok = true; + $byLifetime = (self::$mergeByLifetime)($this->deferred, $expiredIds, $this->getId(...), self::TAGS_PREFIX, $this->defaultLifetime); + $retry = $this->deferred = []; + + if ($expiredIds) { + // Tags are not cleaned up in this case, however that is done on invalidateTags(). + try { + $this->doDelete($expiredIds); + } catch (\Exception $e) { + $ok = false; + CacheItem::log($this->logger, 'Failed to delete expired items: '.$e->getMessage(), ['exception' => $e, 'cache-adapter' => get_debug_type($this)]); + } + } + foreach ($byLifetime as $lifetime => $values) { + try { + $values = $this->extractTagData($values, $addTagData, $removeTagData); + $e = $this->doSave($values, $lifetime, $addTagData, $removeTagData); + } catch (\Exception $e) { + } + if ([] === $e) { + continue; + } + if (\is_array($e) || 1 === \count($values)) { + foreach (\is_array($e) ? $e : array_keys($values) as $id) { + $ok = false; + $v = $values[$id]; + $type = get_debug_type($v); + $message = \sprintf('Failed to save key "{key}" of type %s%s', $type, $e instanceof \Exception ? ': '.$e->getMessage() : '.'); + CacheItem::log($this->logger, $message, ['key' => substr($id, \strlen($this->namespace)), 'exception' => $e instanceof \Exception ? $e : null, 'cache-adapter' => get_debug_type($this)]); + } + } else { + foreach ($values as $id => $v) { + $retry[$lifetime][] = $id; + } + } + } + + // When bulk-save failed, retry each item individually + foreach ($retry as $lifetime => $ids) { + foreach ($ids as $id) { + try { + $v = $byLifetime[$lifetime][$id]; + $values = $this->extractTagData([$id => $v], $addTagData, $removeTagData); + $e = $this->doSave($values, $lifetime, $addTagData, $removeTagData); + } catch (\Exception $e) { + } + if ([] === $e) { + continue; + } + $ok = false; + $type = get_debug_type($v); + $message = \sprintf('Failed to save key "{key}" of type %s%s', $type, $e instanceof \Exception ? ': '.$e->getMessage() : '.'); + CacheItem::log($this->logger, $message, ['key' => substr($id, \strlen($this->namespace)), 'exception' => $e instanceof \Exception ? $e : null, 'cache-adapter' => get_debug_type($this)]); + } + } + + return $ok; + } + + public function deleteItems(array $keys): bool + { + if (!$keys) { + return true; + } + + $ok = true; + $ids = []; + $tagData = []; + + foreach ($keys as $key) { + $ids[$key] = $this->getId($key); + unset($this->deferred[$key]); + } + + try { + foreach ($this->doDeleteYieldTags(array_values($ids)) as $id => $tags) { + foreach ($tags as $tag) { + $tagData[$this->getId(self::TAGS_PREFIX.$tag)][] = $id; + } + } + } catch (\Exception) { + $ok = false; + } + + try { + if ((!$tagData || $this->doDeleteTagRelations($tagData)) && $ok) { + return true; + } + } catch (\Exception) { + } + + // When bulk-delete failed, retry each item individually + foreach ($ids as $key => $id) { + try { + $e = null; + if ($this->doDelete([$id])) { + continue; + } + } catch (\Exception $e) { + } + $message = 'Failed to delete key "{key}"'.($e instanceof \Exception ? ': '.$e->getMessage() : '.'); + CacheItem::log($this->logger, $message, ['key' => $key, 'exception' => $e, 'cache-adapter' => get_debug_type($this)]); + $ok = false; + } + + return $ok; + } + + public function invalidateTags(array $tags): bool + { + if (!$tags) { + return false; + } + + $tagIds = []; + foreach (array_unique($tags) as $tag) { + $tagIds[] = $this->getId(self::TAGS_PREFIX.$tag); + } + + try { + if ($this->doInvalidate($tagIds)) { + return true; + } + } catch (\Exception $e) { + CacheItem::log($this->logger, 'Failed to invalidate tags: '.$e->getMessage(), ['exception' => $e, 'cache-adapter' => get_debug_type($this)]); + } + + return false; + } + + /** + * Extracts tags operation data from $values set in mergeByLifetime, and returns values without it. + */ + private function extractTagData(array $values, ?array &$addTagData, ?array &$removeTagData): array + { + $addTagData = $removeTagData = []; + foreach ($values as $id => $value) { + foreach ($value['tag-operations']['add'] as $tag => $tagId) { + $addTagData[$tagId][] = $id; + } + + foreach ($value['tag-operations']['remove'] as $tag => $tagId) { + $removeTagData[$tagId][] = $id; + } + + unset($values[$id]['tag-operations']); + } + + return $values; + } +} diff --git a/vendor/symfony/cache/Adapter/AdapterInterface.php b/vendor/symfony/cache/Adapter/AdapterInterface.php new file mode 100644 index 0000000..e556720 --- /dev/null +++ b/vendor/symfony/cache/Adapter/AdapterInterface.php @@ -0,0 +1,35 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Cache\Adapter; + +use Psr\Cache\CacheItemPoolInterface; +use Symfony\Component\Cache\CacheItem; + +// Help opcache.preload discover always-needed symbols +class_exists(CacheItem::class); + +/** + * Interface for adapters managing instances of Symfony's CacheItem. + * + * @author KĆ©vin Dunglas + */ +interface AdapterInterface extends CacheItemPoolInterface +{ + public function getItem(mixed $key): CacheItem; + + /** + * @return iterable + */ + public function getItems(array $keys = []): iterable; + + public function clear(string $prefix = ''): bool; +} diff --git a/vendor/symfony/cache/Adapter/ApcuAdapter.php b/vendor/symfony/cache/Adapter/ApcuAdapter.php new file mode 100644 index 0000000..ddb7210 --- /dev/null +++ b/vendor/symfony/cache/Adapter/ApcuAdapter.php @@ -0,0 +1,116 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Cache\Adapter; + +use Symfony\Component\Cache\CacheItem; +use Symfony\Component\Cache\Exception\CacheException; +use Symfony\Component\Cache\Marshaller\MarshallerInterface; + +/** + * @author Nicolas Grekas + */ +class ApcuAdapter extends AbstractAdapter +{ + /** + * @throws CacheException if APCu is not enabled + */ + public function __construct( + string $namespace = '', + int $defaultLifetime = 0, + ?string $version = null, + private ?MarshallerInterface $marshaller = null, + ) { + if (!static::isSupported()) { + throw new CacheException('APCu is not enabled.'); + } + if ('cli' === \PHP_SAPI) { + ini_set('apc.use_request_time', 0); + } + parent::__construct($namespace, $defaultLifetime); + + if (null !== $version) { + CacheItem::validateKey($version); + + if (!apcu_exists($version.'@'.$namespace)) { + $this->doClear($namespace); + apcu_add($version.'@'.$namespace, null); + } + } + } + + public static function isSupported(): bool + { + return \function_exists('apcu_fetch') && filter_var(\ini_get('apc.enabled'), \FILTER_VALIDATE_BOOL); + } + + protected function doFetch(array $ids): iterable + { + $unserializeCallbackHandler = ini_set('unserialize_callback_func', __CLASS__.'::handleUnserializeCallback'); + try { + $values = []; + foreach (apcu_fetch($ids, $ok) ?: [] as $k => $v) { + if (null !== $v || $ok) { + $values[$k] = null !== $this->marshaller ? $this->marshaller->unmarshall($v) : $v; + } + } + + return $values; + } catch (\Error $e) { + throw new \ErrorException($e->getMessage(), $e->getCode(), \E_ERROR, $e->getFile(), $e->getLine()); + } finally { + ini_set('unserialize_callback_func', $unserializeCallbackHandler); + } + } + + protected function doHave(string $id): bool + { + return apcu_exists($id); + } + + protected function doClear(string $namespace): bool + { + return isset($namespace[0]) && class_exists(\APCUIterator::class, false) && ('cli' !== \PHP_SAPI || filter_var(\ini_get('apc.enable_cli'), \FILTER_VALIDATE_BOOL)) + ? apcu_delete(new \APCUIterator(\sprintf('/^%s/', preg_quote($namespace, '/')), \APC_ITER_KEY)) + : apcu_clear_cache(); + } + + protected function doDelete(array $ids): bool + { + foreach ($ids as $id) { + apcu_delete($id); + } + + return true; + } + + protected function doSave(array $values, int $lifetime): array|bool + { + if (null !== $this->marshaller && (!$values = $this->marshaller->marshall($values, $failed))) { + return $failed; + } + + try { + if (false === $failures = apcu_store($values, null, $lifetime)) { + $failures = $values; + } + + return array_keys($failures); + } catch (\Throwable $e) { + if (1 === \count($values)) { + // Workaround https://github.com/krakjoe/apcu/issues/170 + apcu_delete(array_key_first($values)); + } + + throw $e; + } + } +} diff --git a/vendor/symfony/cache/Adapter/ArrayAdapter.php b/vendor/symfony/cache/Adapter/ArrayAdapter.php new file mode 100644 index 0000000..7b92387 --- /dev/null +++ b/vendor/symfony/cache/Adapter/ArrayAdapter.php @@ -0,0 +1,366 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Cache\Adapter; + +use Psr\Cache\CacheItemInterface; +use Psr\Clock\ClockInterface; +use Psr\Log\LoggerAwareInterface; +use Psr\Log\LoggerAwareTrait; +use Symfony\Component\Cache\CacheItem; +use Symfony\Component\Cache\Exception\InvalidArgumentException; +use Symfony\Component\Cache\ResettableInterface; +use Symfony\Contracts\Cache\CacheInterface; + +/** + * An in-memory cache storage. + * + * Acts as a least-recently-used (LRU) storage when configured with a maximum number of items. + * + * @author Nicolas Grekas + */ +class ArrayAdapter implements AdapterInterface, CacheInterface, LoggerAwareInterface, ResettableInterface +{ + use LoggerAwareTrait; + + private array $values = []; + private array $tags = []; + private array $expiries = []; + + private static \Closure $createCacheItem; + + /** + * @param bool $storeSerialized Disabling serialization can lead to cache corruptions when storing mutable values but increases performance otherwise + */ + public function __construct( + private int $defaultLifetime = 0, + private bool $storeSerialized = true, + private float $maxLifetime = 0, + private int $maxItems = 0, + private ?ClockInterface $clock = null, + ) { + if (0 > $maxLifetime) { + throw new InvalidArgumentException(\sprintf('Argument $maxLifetime must be positive, %F passed.', $maxLifetime)); + } + + if (0 > $maxItems) { + throw new InvalidArgumentException(\sprintf('Argument $maxItems must be a positive integer, %d passed.', $maxItems)); + } + + self::$createCacheItem ??= \Closure::bind( + static function ($key, $value, $isHit, $tags) { + $item = new CacheItem(); + $item->key = $key; + $item->value = $value; + $item->isHit = $isHit; + if (null !== $tags) { + $item->metadata[CacheItem::METADATA_TAGS] = $tags; + } + + return $item; + }, + null, + CacheItem::class + ); + } + + public function get(string $key, callable $callback, ?float $beta = null, ?array &$metadata = null): mixed + { + $item = $this->getItem($key); + $metadata = $item->getMetadata(); + + // ArrayAdapter works in memory, we don't care about stampede protection + if (\INF === $beta || !$item->isHit()) { + $save = true; + $item->set($callback($item, $save)); + if ($save) { + $this->save($item); + } + } + + return $item->get(); + } + + public function delete(string $key): bool + { + return $this->deleteItem($key); + } + + public function hasItem(mixed $key): bool + { + if (\is_string($key) && isset($this->expiries[$key]) && $this->expiries[$key] > $this->getCurrentTime()) { + if ($this->maxItems) { + // Move the item last in the storage + $value = $this->values[$key]; + unset($this->values[$key]); + $this->values[$key] = $value; + } + + return true; + } + \assert('' !== CacheItem::validateKey($key)); + + return isset($this->expiries[$key]) && !$this->deleteItem($key); + } + + public function getItem(mixed $key): CacheItem + { + if (!$isHit = $this->hasItem($key)) { + $value = null; + + if (!$this->maxItems) { + // Track misses in non-LRU mode only + $this->values[$key] = null; + } + } else { + $value = $this->storeSerialized ? $this->unfreeze($key, $isHit) : $this->values[$key]; + } + + return (self::$createCacheItem)($key, $value, $isHit, $this->tags[$key] ?? null); + } + + public function getItems(array $keys = []): iterable + { + \assert(self::validateKeys($keys)); + + return $this->generateItems($keys, $this->getCurrentTime(), self::$createCacheItem); + } + + public function deleteItem(mixed $key): bool + { + \assert('' !== CacheItem::validateKey($key)); + unset($this->values[$key], $this->tags[$key], $this->expiries[$key]); + + return true; + } + + public function deleteItems(array $keys): bool + { + foreach ($keys as $key) { + $this->deleteItem($key); + } + + return true; + } + + public function save(CacheItemInterface $item): bool + { + if (!$item instanceof CacheItem) { + return false; + } + $item = (array) $item; + $key = $item["\0*\0key"]; + $value = $item["\0*\0value"]; + $expiry = $item["\0*\0expiry"]; + + $now = $this->getCurrentTime(); + + if (null !== $expiry) { + if (!$expiry) { + $expiry = \PHP_INT_MAX; + } elseif ($expiry <= $now) { + $this->deleteItem($key); + + return true; + } + } + if ($this->storeSerialized && null === $value = $this->freeze($value, $key)) { + return false; + } + if (null === $expiry && 0 < $this->defaultLifetime) { + $expiry = $this->defaultLifetime; + $expiry = $now + ($expiry > ($this->maxLifetime ?: $expiry) ? $this->maxLifetime : $expiry); + } elseif ($this->maxLifetime && (null === $expiry || $expiry > $now + $this->maxLifetime)) { + $expiry = $now + $this->maxLifetime; + } + + if ($this->maxItems) { + unset($this->values[$key], $this->tags[$key]); + + // Iterate items and vacuum expired ones while we are at it + foreach ($this->values as $k => $v) { + if ($this->expiries[$k] > $now && \count($this->values) < $this->maxItems) { + break; + } + + unset($this->values[$k], $this->tags[$k], $this->expiries[$k]); + } + } + + $this->values[$key] = $value; + $this->expiries[$key] = $expiry ?? \PHP_INT_MAX; + + if (null === $this->tags[$key] = $item["\0*\0newMetadata"][CacheItem::METADATA_TAGS] ?? null) { + unset($this->tags[$key]); + } + + return true; + } + + public function saveDeferred(CacheItemInterface $item): bool + { + return $this->save($item); + } + + public function commit(): bool + { + return true; + } + + public function clear(string $prefix = ''): bool + { + if ('' !== $prefix) { + $now = $this->getCurrentTime(); + + foreach ($this->values as $key => $value) { + if (!isset($this->expiries[$key]) || $this->expiries[$key] <= $now || str_starts_with($key, $prefix)) { + unset($this->values[$key], $this->tags[$key], $this->expiries[$key]); + } + } + + if ($this->values) { + return true; + } + } + + $this->values = $this->tags = $this->expiries = []; + + return true; + } + + /** + * Returns all cached values, with cache miss as null. + */ + public function getValues(): array + { + if (!$this->storeSerialized) { + return $this->values; + } + + $values = $this->values; + foreach ($values as $k => $v) { + if (null === $v || 'N;' === $v) { + continue; + } + if (!\is_string($v) || !isset($v[2]) || ':' !== $v[1]) { + $values[$k] = serialize($v); + } + } + + return $values; + } + + public function reset(): void + { + $this->clear(); + } + + private function generateItems(array $keys, float $now, \Closure $f): \Generator + { + foreach ($keys as $i => $key) { + if (!$isHit = isset($this->expiries[$key]) && ($this->expiries[$key] > $now || !$this->deleteItem($key))) { + $value = null; + + if (!$this->maxItems) { + // Track misses in non-LRU mode only + $this->values[$key] = null; + } + } else { + if ($this->maxItems) { + // Move the item last in the storage + $value = $this->values[$key]; + unset($this->values[$key]); + $this->values[$key] = $value; + } + + $value = $this->storeSerialized ? $this->unfreeze($key, $isHit) : $this->values[$key]; + } + unset($keys[$i]); + + yield $key => $f($key, $value, $isHit, $this->tags[$key] ?? null); + } + + foreach ($keys as $key) { + yield $key => $f($key, null, false); + } + } + + private function freeze($value, string $key): string|int|float|bool|array|\UnitEnum|null + { + if (null === $value) { + return 'N;'; + } + if (\is_string($value)) { + // Serialize strings if they could be confused with serialized objects or arrays + if ('N;' === $value || (isset($value[2]) && ':' === $value[1])) { + return serialize($value); + } + } elseif (!\is_scalar($value)) { + try { + $serialized = serialize($value); + } catch (\Exception $e) { + unset($this->values[$key], $this->tags[$key]); + $type = get_debug_type($value); + $message = \sprintf('Failed to save key "{key}" of type %s: %s', $type, $e->getMessage()); + CacheItem::log($this->logger, $message, ['key' => $key, 'exception' => $e, 'cache-adapter' => get_debug_type($this)]); + + return null; + } + // Keep value serialized if it contains any objects or any internal references + if ('C' === $serialized[0] || 'O' === $serialized[0] || preg_match('/;[OCRr]:[1-9]/', $serialized)) { + return $serialized; + } + } + + return $value; + } + + private function unfreeze(string $key, bool &$isHit): mixed + { + if ('N;' === $value = $this->values[$key]) { + return null; + } + if (\is_string($value) && isset($value[2]) && ':' === $value[1]) { + try { + $value = unserialize($value); + } catch (\Exception $e) { + CacheItem::log($this->logger, 'Failed to unserialize key "{key}": '.$e->getMessage(), ['key' => $key, 'exception' => $e, 'cache-adapter' => get_debug_type($this)]); + $value = false; + } + if (false === $value) { + $value = null; + $isHit = false; + + if (!$this->maxItems) { + $this->values[$key] = null; + } + } + } + + return $value; + } + + private function validateKeys(array $keys): bool + { + foreach ($keys as $key) { + if (!\is_string($key) || !isset($this->expiries[$key])) { + CacheItem::validateKey($key); + } + } + + return true; + } + + private function getCurrentTime(): float + { + return $this->clock?->now()->format('U.u') ?? microtime(true); + } +} diff --git a/vendor/symfony/cache/Adapter/ChainAdapter.php b/vendor/symfony/cache/Adapter/ChainAdapter.php new file mode 100644 index 0000000..09fcfdc --- /dev/null +++ b/vendor/symfony/cache/Adapter/ChainAdapter.php @@ -0,0 +1,291 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Cache\Adapter; + +use Psr\Cache\CacheItemInterface; +use Psr\Cache\CacheItemPoolInterface; +use Symfony\Component\Cache\CacheItem; +use Symfony\Component\Cache\Exception\InvalidArgumentException; +use Symfony\Component\Cache\PruneableInterface; +use Symfony\Component\Cache\ResettableInterface; +use Symfony\Component\Cache\Traits\ContractsTrait; +use Symfony\Contracts\Cache\CacheInterface; +use Symfony\Contracts\Service\ResetInterface; + +/** + * Chains several adapters together. + * + * Cached items are fetched from the first adapter having them in its data store. + * They are saved and deleted in all adapters at once. + * + * @author KĆ©vin Dunglas + */ +class ChainAdapter implements AdapterInterface, CacheInterface, PruneableInterface, ResettableInterface +{ + use ContractsTrait; + + private array $adapters = []; + private int $adapterCount; + + private static \Closure $syncItem; + + /** + * @param CacheItemPoolInterface[] $adapters The ordered list of adapters used to fetch cached items + * @param int $defaultLifetime The default lifetime of items propagated from lower adapters to upper ones + */ + public function __construct( + array $adapters, + private int $defaultLifetime = 0, + ) { + if (!$adapters) { + throw new InvalidArgumentException('At least one adapter must be specified.'); + } + + foreach ($adapters as $adapter) { + if (!$adapter instanceof CacheItemPoolInterface) { + throw new InvalidArgumentException(\sprintf('The class "%s" does not implement the "%s" interface.', get_debug_type($adapter), CacheItemPoolInterface::class)); + } + if ('cli' === \PHP_SAPI && $adapter instanceof ApcuAdapter && !filter_var(\ini_get('apc.enable_cli'), \FILTER_VALIDATE_BOOL)) { + continue; // skip putting APCu in the chain when the backend is disabled + } + + if ($adapter instanceof AdapterInterface) { + $this->adapters[] = $adapter; + } else { + $this->adapters[] = new ProxyAdapter($adapter); + } + } + $this->adapterCount = \count($this->adapters); + + self::$syncItem ??= \Closure::bind( + static function ($sourceItem, $item, $defaultLifetime, $sourceMetadata = null) { + $sourceItem->isTaggable = false; + $sourceMetadata ??= $sourceItem->metadata; + + $item->value = $sourceItem->value; + $item->isHit = $sourceItem->isHit; + $item->metadata = $item->newMetadata = $sourceItem->metadata = $sourceMetadata; + + if (isset($item->metadata[CacheItem::METADATA_EXPIRY])) { + $item->expiresAt(\DateTimeImmutable::createFromFormat('U.u', \sprintf('%.6F', $item->metadata[CacheItem::METADATA_EXPIRY]))); + } elseif (0 < $defaultLifetime) { + $item->expiresAfter($defaultLifetime); + } + + return $item; + }, + null, + CacheItem::class + ); + } + + public function get(string $key, callable $callback, ?float $beta = null, ?array &$metadata = null): mixed + { + $doSave = true; + $callback = static function (CacheItem $item, bool &$save) use ($callback, &$doSave) { + $value = $callback($item, $save); + $doSave = $save; + + return $value; + }; + + $wrap = function (?CacheItem $item = null, bool &$save = true) use ($key, $callback, $beta, &$wrap, &$doSave, &$metadata) { + static $lastItem; + static $i = 0; + $adapter = $this->adapters[$i]; + if (isset($this->adapters[++$i])) { + $callback = $wrap; + $beta = \INF === $beta ? \INF : 0; + } + if ($adapter instanceof CacheInterface) { + $value = $adapter->get($key, $callback, $beta, $metadata); + } else { + $value = $this->doGet($adapter, $key, $callback, $beta, $metadata); + } + if (null !== $item) { + (self::$syncItem)($lastItem ??= $item, $item, $this->defaultLifetime, $metadata); + } + $save = $doSave; + + return $value; + }; + + return $wrap(); + } + + public function getItem(mixed $key): CacheItem + { + $syncItem = self::$syncItem; + $misses = []; + + foreach ($this->adapters as $i => $adapter) { + $item = $adapter->getItem($key); + + if ($item->isHit()) { + while (0 <= --$i) { + $this->adapters[$i]->save($syncItem($item, $misses[$i], $this->defaultLifetime)); + } + + return $item; + } + + $misses[$i] = $item; + } + + return $item; + } + + public function getItems(array $keys = []): iterable + { + return $this->generateItems($this->adapters[0]->getItems($keys), 0); + } + + private function generateItems(iterable $items, int $adapterIndex): \Generator + { + $missing = []; + $misses = []; + $nextAdapterIndex = $adapterIndex + 1; + $nextAdapter = $this->adapters[$nextAdapterIndex] ?? null; + + foreach ($items as $k => $item) { + if (!$nextAdapter || $item->isHit()) { + yield $k => $item; + } else { + $missing[] = $k; + $misses[$k] = $item; + } + } + + if ($missing) { + $syncItem = self::$syncItem; + $adapter = $this->adapters[$adapterIndex]; + $items = $this->generateItems($nextAdapter->getItems($missing), $nextAdapterIndex); + + foreach ($items as $k => $item) { + if ($item->isHit()) { + $adapter->save($syncItem($item, $misses[$k], $this->defaultLifetime)); + } + + yield $k => $item; + } + } + } + + public function hasItem(mixed $key): bool + { + foreach ($this->adapters as $adapter) { + if ($adapter->hasItem($key)) { + return true; + } + } + + return false; + } + + public function clear(string $prefix = ''): bool + { + $cleared = true; + $i = $this->adapterCount; + + while ($i--) { + if ($this->adapters[$i] instanceof AdapterInterface) { + $cleared = $this->adapters[$i]->clear($prefix) && $cleared; + } else { + $cleared = $this->adapters[$i]->clear() && $cleared; + } + } + + return $cleared; + } + + public function deleteItem(mixed $key): bool + { + $deleted = true; + $i = $this->adapterCount; + + while ($i--) { + $deleted = $this->adapters[$i]->deleteItem($key) && $deleted; + } + + return $deleted; + } + + public function deleteItems(array $keys): bool + { + $deleted = true; + $i = $this->adapterCount; + + while ($i--) { + $deleted = $this->adapters[$i]->deleteItems($keys) && $deleted; + } + + return $deleted; + } + + public function save(CacheItemInterface $item): bool + { + $saved = true; + $i = $this->adapterCount; + + while ($i--) { + $saved = $this->adapters[$i]->save($item) && $saved; + } + + return $saved; + } + + public function saveDeferred(CacheItemInterface $item): bool + { + $saved = true; + $i = $this->adapterCount; + + while ($i--) { + $saved = $this->adapters[$i]->saveDeferred($item) && $saved; + } + + return $saved; + } + + public function commit(): bool + { + $committed = true; + $i = $this->adapterCount; + + while ($i--) { + $committed = $this->adapters[$i]->commit() && $committed; + } + + return $committed; + } + + public function prune(): bool + { + $pruned = true; + + foreach ($this->adapters as $adapter) { + if ($adapter instanceof PruneableInterface) { + $pruned = $adapter->prune() && $pruned; + } + } + + return $pruned; + } + + public function reset(): void + { + foreach ($this->adapters as $adapter) { + if ($adapter instanceof ResetInterface) { + $adapter->reset(); + } + } + } +} diff --git a/vendor/symfony/cache/Adapter/CouchbaseBucketAdapter.php b/vendor/symfony/cache/Adapter/CouchbaseBucketAdapter.php new file mode 100644 index 0000000..106d7fd --- /dev/null +++ b/vendor/symfony/cache/Adapter/CouchbaseBucketAdapter.php @@ -0,0 +1,237 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Cache\Adapter; + +use Symfony\Component\Cache\Exception\CacheException; +use Symfony\Component\Cache\Exception\InvalidArgumentException; +use Symfony\Component\Cache\Marshaller\DefaultMarshaller; +use Symfony\Component\Cache\Marshaller\MarshallerInterface; + +trigger_deprecation('symfony/cache', '7.1', 'The "%s" class is deprecated, use "%s" instead.', CouchbaseBucketAdapter::class, CouchbaseCollectionAdapter::class); + +/** + * @author Antonio Jose Cerezo Aranda + * + * @deprecated since Symfony 7.1, use {@see CouchbaseCollectionAdapter} instead + */ +class CouchbaseBucketAdapter extends AbstractAdapter +{ + private const THIRTY_DAYS_IN_SECONDS = 2592000; + private const MAX_KEY_LENGTH = 250; + private const KEY_NOT_FOUND = 13; + private const VALID_DSN_OPTIONS = [ + 'operationTimeout', + 'configTimeout', + 'configNodeTimeout', + 'n1qlTimeout', + 'httpTimeout', + 'configDelay', + 'htconfigIdleTimeout', + 'durabilityInterval', + 'durabilityTimeout', + ]; + + private MarshallerInterface $marshaller; + + public function __construct( + private \CouchbaseBucket $bucket, + string $namespace = '', + int $defaultLifetime = 0, + ?MarshallerInterface $marshaller = null, + ) { + if (!static::isSupported()) { + throw new CacheException('Couchbase >= 2.6.0 < 3.0.0 is required.'); + } + + $this->maxIdLength = static::MAX_KEY_LENGTH; + + parent::__construct($namespace, $defaultLifetime); + $this->enableVersioning(); + $this->marshaller = $marshaller ?? new DefaultMarshaller(); + } + + public static function createConnection(#[\SensitiveParameter] array|string $servers, array $options = []): \CouchbaseBucket + { + if (\is_string($servers)) { + $servers = [$servers]; + } + + if (!static::isSupported()) { + throw new CacheException('Couchbase >= 2.6.0 < 3.0.0 is required.'); + } + + set_error_handler(static fn ($type, $msg, $file, $line) => throw new \ErrorException($msg, 0, $type, $file, $line)); + + $dsnPattern = '/^(?couchbase(?:s)?)\:\/\/(?:(?[^\:]+)\:(?[^\@]{6,})@)?' + .'(?[^\:]+(?:\:\d+)?)(?:\/(?[^\?]+))(?:\?(?.*))?$/i'; + + $newServers = []; + $protocol = 'couchbase'; + try { + $options = self::initOptions($options); + $username = $options['username']; + $password = $options['password']; + + foreach ($servers as $dsn) { + if (!str_starts_with($dsn, 'couchbase:')) { + throw new InvalidArgumentException('Invalid Couchbase DSN: it does not start with "couchbase:".'); + } + + preg_match($dsnPattern, $dsn, $matches); + + $username = $matches['username'] ?: $username; + $password = $matches['password'] ?: $password; + $protocol = $matches['protocol'] ?: $protocol; + + if (isset($matches['options'])) { + $optionsInDsn = self::getOptions($matches['options']); + + foreach ($optionsInDsn as $parameter => $value) { + $options[$parameter] = $value; + } + } + + $newServers[] = $matches['host']; + } + + $connectionString = $protocol.'://'.implode(',', $newServers); + + $client = new \CouchbaseCluster($connectionString); + $client->authenticateAs($username, $password); + + $bucket = $client->openBucket($matches['bucketName']); + + unset($options['username'], $options['password']); + foreach ($options as $option => $value) { + if ($value) { + $bucket->$option = $value; + } + } + + return $bucket; + } finally { + restore_error_handler(); + } + } + + public static function isSupported(): bool + { + return \extension_loaded('couchbase') && version_compare(phpversion('couchbase'), '2.6.0', '>=') && version_compare(phpversion('couchbase'), '3.0', '<'); + } + + private static function getOptions(string $options): array + { + $results = []; + $optionsInArray = explode('&', $options); + + foreach ($optionsInArray as $option) { + [$key, $value] = explode('=', $option); + + if (\in_array($key, static::VALID_DSN_OPTIONS, true)) { + $results[$key] = $value; + } + } + + return $results; + } + + private static function initOptions(array $options): array + { + $options['username'] ??= ''; + $options['password'] ??= ''; + $options['operationTimeout'] ??= 0; + $options['configTimeout'] ??= 0; + $options['configNodeTimeout'] ??= 0; + $options['n1qlTimeout'] ??= 0; + $options['httpTimeout'] ??= 0; + $options['configDelay'] ??= 0; + $options['htconfigIdleTimeout'] ??= 0; + $options['durabilityInterval'] ??= 0; + $options['durabilityTimeout'] ??= 0; + + return $options; + } + + protected function doFetch(array $ids): iterable + { + $resultsCouchbase = $this->bucket->get($ids); + + $results = []; + foreach ($resultsCouchbase as $key => $value) { + if (null !== $value->error) { + continue; + } + $results[$key] = $this->marshaller->unmarshall($value->value); + } + + return $results; + } + + protected function doHave(string $id): bool + { + return false !== $this->bucket->get($id); + } + + protected function doClear(string $namespace): bool + { + if ('' === $namespace) { + $this->bucket->manager()->flush(); + + return true; + } + + return false; + } + + protected function doDelete(array $ids): bool + { + $results = $this->bucket->remove(array_values($ids)); + + foreach ($results as $key => $result) { + if (null !== $result->error && static::KEY_NOT_FOUND !== $result->error->getCode()) { + continue; + } + unset($results[$key]); + } + + return 0 === \count($results); + } + + protected function doSave(array $values, int $lifetime): array|bool + { + if (!$values = $this->marshaller->marshall($values, $failed)) { + return $failed; + } + + $lifetime = $this->normalizeExpiry($lifetime); + + $ko = []; + foreach ($values as $key => $value) { + $result = $this->bucket->upsert($key, $value, ['expiry' => $lifetime]); + + if (null !== $result->error) { + $ko[$key] = $result; + } + } + + return [] === $ko ? true : $ko; + } + + private function normalizeExpiry(int $expiry): int + { + if ($expiry && $expiry > static::THIRTY_DAYS_IN_SECONDS) { + $expiry += time(); + } + + return $expiry; + } +} diff --git a/vendor/symfony/cache/Adapter/CouchbaseCollectionAdapter.php b/vendor/symfony/cache/Adapter/CouchbaseCollectionAdapter.php new file mode 100644 index 0000000..9646bc3 --- /dev/null +++ b/vendor/symfony/cache/Adapter/CouchbaseCollectionAdapter.php @@ -0,0 +1,198 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Cache\Adapter; + +use Couchbase\Bucket; +use Couchbase\Cluster; +use Couchbase\ClusterOptions; +use Couchbase\Collection; +use Couchbase\DocumentNotFoundException; +use Couchbase\UpsertOptions; +use Symfony\Component\Cache\Exception\CacheException; +use Symfony\Component\Cache\Exception\InvalidArgumentException; +use Symfony\Component\Cache\Marshaller\DefaultMarshaller; +use Symfony\Component\Cache\Marshaller\MarshallerInterface; + +/** + * @author Antonio Jose Cerezo Aranda + */ +class CouchbaseCollectionAdapter extends AbstractAdapter +{ + private const MAX_KEY_LENGTH = 250; + + private MarshallerInterface $marshaller; + + public function __construct( + private Collection $connection, + string $namespace = '', + int $defaultLifetime = 0, + ?MarshallerInterface $marshaller = null, + ) { + if (!static::isSupported()) { + throw new CacheException('Couchbase >= 3.0.5 < 4.0.0 is required.'); + } + + $this->maxIdLength = static::MAX_KEY_LENGTH; + + parent::__construct($namespace, $defaultLifetime); + $this->enableVersioning(); + $this->marshaller = $marshaller ?? new DefaultMarshaller(); + } + + public static function createConnection(#[\SensitiveParameter] array|string $dsn, array $options = []): Bucket|Collection + { + if (\is_string($dsn)) { + $dsn = [$dsn]; + } + + if (!static::isSupported()) { + throw new CacheException('Couchbase >= 3.0.5 < 4.0.0 is required.'); + } + + set_error_handler(static fn ($type, $msg, $file, $line) => throw new \ErrorException($msg, 0, $type, $file, $line)); + + $pathPattern = '/^(?:\/(?[^\/\?]+))(?:(?:\/(?[^\/]+))(?:\/(?[^\/\?]+)))?(?:\/)?$/'; + $newServers = []; + $protocol = 'couchbase'; + try { + $username = $options['username'] ?? ''; + $password = $options['password'] ?? ''; + + foreach ($dsn as $server) { + if (!str_starts_with($server, 'couchbase:')) { + throw new InvalidArgumentException('Invalid Couchbase DSN: it does not start with "couchbase:".'); + } + + $params = parse_url($server); + + $username = isset($params['user']) ? rawurldecode($params['user']) : $username; + $password = isset($params['pass']) ? rawurldecode($params['pass']) : $password; + $protocol = $params['scheme'] ?? $protocol; + + if (isset($params['query'])) { + $optionsInDsn = self::getOptions($params['query']); + + foreach ($optionsInDsn as $parameter => $value) { + $options[$parameter] = $value; + } + } + + $newServers[] = $params['host']; + } + + $option = isset($params['query']) ? '?'.$params['query'] : ''; + $connectionString = $protocol.'://'.implode(',', $newServers).$option; + + $clusterOptions = new ClusterOptions(); + $clusterOptions->credentials($username, $password); + + $client = new Cluster($connectionString, $clusterOptions); + + preg_match($pathPattern, $params['path'] ?? '', $matches); + $bucket = $client->bucket($matches['bucketName']); + $collection = $bucket->defaultCollection(); + if (!empty($matches['scopeName'])) { + $scope = $bucket->scope($matches['scopeName']); + $collection = $scope->collection($matches['collectionName']); + } + + return $collection; + } finally { + restore_error_handler(); + } + } + + public static function isSupported(): bool + { + return \extension_loaded('couchbase') && version_compare(phpversion('couchbase'), '3.0.5', '>=') && version_compare(phpversion('couchbase'), '4.0', '<'); + } + + private static function getOptions(string $options): array + { + $results = []; + $optionsInArray = explode('&', $options); + + foreach ($optionsInArray as $option) { + [$key, $value] = explode('=', $option); + + $results[$key] = $value; + } + + return $results; + } + + protected function doFetch(array $ids): array + { + $results = []; + foreach ($ids as $id) { + try { + $resultCouchbase = $this->connection->get($id); + } catch (DocumentNotFoundException) { + continue; + } + + $content = $resultCouchbase->value ?? $resultCouchbase->content(); + + $results[$id] = $this->marshaller->unmarshall($content); + } + + return $results; + } + + protected function doHave($id): bool + { + return $this->connection->exists($id)->exists(); + } + + protected function doClear($namespace): bool + { + return false; + } + + protected function doDelete(array $ids): bool + { + $idsErrors = []; + foreach ($ids as $id) { + try { + $result = $this->connection->remove($id); + + if (null === $result->mutationToken()) { + $idsErrors[] = $id; + } + } catch (DocumentNotFoundException) { + } + } + + return 0 === \count($idsErrors); + } + + protected function doSave(array $values, $lifetime): array|bool + { + if (!$values = $this->marshaller->marshall($values, $failed)) { + return $failed; + } + + $upsertOptions = new UpsertOptions(); + $upsertOptions->expiry($lifetime); + + $ko = []; + foreach ($values as $key => $value) { + try { + $this->connection->upsert($key, $value, $upsertOptions); + } catch (\Exception) { + $ko[$key] = ''; + } + } + + return [] === $ko ? true : $ko; + } +} diff --git a/vendor/symfony/cache/Adapter/DoctrineDbalAdapter.php b/vendor/symfony/cache/Adapter/DoctrineDbalAdapter.php new file mode 100644 index 0000000..c69c777 --- /dev/null +++ b/vendor/symfony/cache/Adapter/DoctrineDbalAdapter.php @@ -0,0 +1,383 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Cache\Adapter; + +use Doctrine\DBAL\ArrayParameterType; +use Doctrine\DBAL\Configuration; +use Doctrine\DBAL\Connection; +use Doctrine\DBAL\DriverManager; +use Doctrine\DBAL\Exception as DBALException; +use Doctrine\DBAL\Exception\TableNotFoundException; +use Doctrine\DBAL\ParameterType; +use Doctrine\DBAL\Schema\DefaultSchemaManagerFactory; +use Doctrine\DBAL\Schema\Schema; +use Doctrine\DBAL\Tools\DsnParser; +use Symfony\Component\Cache\Exception\InvalidArgumentException; +use Symfony\Component\Cache\Marshaller\DefaultMarshaller; +use Symfony\Component\Cache\Marshaller\MarshallerInterface; +use Symfony\Component\Cache\PruneableInterface; + +class DoctrineDbalAdapter extends AbstractAdapter implements PruneableInterface +{ + private const MAX_KEY_LENGTH = 255; + + private MarshallerInterface $marshaller; + private Connection $conn; + private string $platformName; + private string $table = 'cache_items'; + private string $idCol = 'item_id'; + private string $dataCol = 'item_data'; + private string $lifetimeCol = 'item_lifetime'; + private string $timeCol = 'item_time'; + + /** + * You can either pass an existing database Doctrine DBAL Connection or + * a DSN string that will be used to connect to the database. + * + * The cache table is created automatically when possible. + * Otherwise, use the createTable() method. + * + * List of available options: + * * db_table: The name of the table [default: cache_items] + * * db_id_col: The column where to store the cache id [default: item_id] + * * db_data_col: The column where to store the cache data [default: item_data] + * * db_lifetime_col: The column where to store the lifetime [default: item_lifetime] + * * db_time_col: The column where to store the timestamp [default: item_time] + * + * @throws InvalidArgumentException When namespace contains invalid characters + */ + public function __construct( + Connection|string $connOrDsn, + private string $namespace = '', + int $defaultLifetime = 0, + array $options = [], + ?MarshallerInterface $marshaller = null, + ) { + if (isset($namespace[0]) && preg_match('#[^-+.A-Za-z0-9]#', $namespace, $match)) { + throw new InvalidArgumentException(\sprintf('Namespace contains "%s" but only characters in [-+.A-Za-z0-9] are allowed.', $match[0])); + } + + if ($connOrDsn instanceof Connection) { + $this->conn = $connOrDsn; + } else { + if (!class_exists(DriverManager::class)) { + throw new InvalidArgumentException('Failed to parse DSN. Try running "composer require doctrine/dbal".'); + } + $params = (new DsnParser([ + 'db2' => 'ibm_db2', + 'mssql' => 'pdo_sqlsrv', + 'mysql' => 'pdo_mysql', + 'mysql2' => 'pdo_mysql', + 'postgres' => 'pdo_pgsql', + 'postgresql' => 'pdo_pgsql', + 'pgsql' => 'pdo_pgsql', + 'sqlite' => 'pdo_sqlite', + 'sqlite3' => 'pdo_sqlite', + ]))->parse($connOrDsn); + + $config = new Configuration(); + $config->setSchemaManagerFactory(new DefaultSchemaManagerFactory()); + + $this->conn = DriverManager::getConnection($params, $config); + } + + $this->maxIdLength = self::MAX_KEY_LENGTH; + $this->table = $options['db_table'] ?? $this->table; + $this->idCol = $options['db_id_col'] ?? $this->idCol; + $this->dataCol = $options['db_data_col'] ?? $this->dataCol; + $this->lifetimeCol = $options['db_lifetime_col'] ?? $this->lifetimeCol; + $this->timeCol = $options['db_time_col'] ?? $this->timeCol; + $this->marshaller = $marshaller ?? new DefaultMarshaller(); + + parent::__construct($namespace, $defaultLifetime); + } + + /** + * Creates the table to store cache items which can be called once for setup. + * + * Cache ID are saved in a column of maximum length 255. Cache data is + * saved in a BLOB. + * + * @throws DBALException When the table already exists + */ + public function createTable(): void + { + $schema = new Schema(); + $this->addTableToSchema($schema); + + foreach ($schema->toSql($this->conn->getDatabasePlatform()) as $sql) { + $this->conn->executeStatement($sql); + } + } + + public function configureSchema(Schema $schema, Connection $forConnection, \Closure $isSameDatabase): void + { + if ($schema->hasTable($this->table)) { + return; + } + + if ($forConnection !== $this->conn && !$isSameDatabase($this->conn->executeStatement(...))) { + return; + } + + $this->addTableToSchema($schema); + } + + public function prune(): bool + { + $deleteSql = "DELETE FROM $this->table WHERE $this->lifetimeCol + $this->timeCol <= ?"; + $params = [time()]; + $paramTypes = [ParameterType::INTEGER]; + + if ('' !== $this->namespace) { + $deleteSql .= " AND $this->idCol LIKE ?"; + $params[] = \sprintf('%s%%', $this->namespace); + $paramTypes[] = ParameterType::STRING; + } + + try { + $this->conn->executeStatement($deleteSql, $params, $paramTypes); + } catch (TableNotFoundException) { + } + + return true; + } + + protected function doFetch(array $ids): iterable + { + $now = time(); + $expired = []; + + $sql = "SELECT $this->idCol, CASE WHEN $this->lifetimeCol IS NULL OR $this->lifetimeCol + $this->timeCol > ? THEN $this->dataCol ELSE NULL END FROM $this->table WHERE $this->idCol IN (?)"; + $result = $this->conn->executeQuery($sql, [ + $now, + $ids, + ], [ + ParameterType::INTEGER, + ArrayParameterType::STRING, + ])->iterateNumeric(); + + foreach ($result as $row) { + if (null === $row[1]) { + $expired[] = $row[0]; + } else { + yield $row[0] => $this->marshaller->unmarshall(\is_resource($row[1]) ? stream_get_contents($row[1]) : $row[1]); + } + } + + if ($expired) { + $sql = "DELETE FROM $this->table WHERE $this->lifetimeCol + $this->timeCol <= ? AND $this->idCol IN (?)"; + $this->conn->executeStatement($sql, [ + $now, + $expired, + ], [ + ParameterType::INTEGER, + ArrayParameterType::STRING, + ]); + } + } + + protected function doHave(string $id): bool + { + $sql = "SELECT 1 FROM $this->table WHERE $this->idCol = ? AND ($this->lifetimeCol IS NULL OR $this->lifetimeCol + $this->timeCol > ?)"; + $result = $this->conn->executeQuery($sql, [ + $id, + time(), + ], [ + ParameterType::STRING, + ParameterType::INTEGER, + ]); + + return (bool) $result->fetchOne(); + } + + protected function doClear(string $namespace): bool + { + if ('' === $namespace) { + $sql = $this->conn->getDatabasePlatform()->getTruncateTableSQL($this->table); + } else { + $sql = "DELETE FROM $this->table WHERE $this->idCol LIKE '$namespace%'"; + } + + try { + $this->conn->executeStatement($sql); + } catch (TableNotFoundException) { + } + + return true; + } + + protected function doDelete(array $ids): bool + { + $sql = "DELETE FROM $this->table WHERE $this->idCol IN (?)"; + try { + $this->conn->executeStatement($sql, [array_values($ids)], [ArrayParameterType::STRING]); + } catch (TableNotFoundException) { + } + + return true; + } + + protected function doSave(array $values, int $lifetime): array|bool + { + if (!$values = $this->marshaller->marshall($values, $failed)) { + return $failed; + } + + $platformName = $this->getPlatformName(); + $insertSql = "INSERT INTO $this->table ($this->idCol, $this->dataCol, $this->lifetimeCol, $this->timeCol) VALUES (?, ?, ?, ?)"; + + switch ($platformName) { + case 'mysql': + $sql = $insertSql." ON DUPLICATE KEY UPDATE $this->dataCol = VALUES($this->dataCol), $this->lifetimeCol = VALUES($this->lifetimeCol), $this->timeCol = VALUES($this->timeCol)"; + break; + case 'oci': + // DUAL is Oracle specific dummy table + $sql = "MERGE INTO $this->table USING DUAL ON ($this->idCol = ?) ". + "WHEN NOT MATCHED THEN INSERT ($this->idCol, $this->dataCol, $this->lifetimeCol, $this->timeCol) VALUES (?, ?, ?, ?) ". + "WHEN MATCHED THEN UPDATE SET $this->dataCol = ?, $this->lifetimeCol = ?, $this->timeCol = ?"; + break; + case 'sqlsrv': + // MERGE is only available since SQL Server 2008 and must be terminated by semicolon + // It also requires HOLDLOCK according to http://weblogs.sqlteam.com/dang/archive/2009/01/31/UPSERT-Race-Condition-With-MERGE.aspx + $sql = "MERGE INTO $this->table WITH (HOLDLOCK) USING (SELECT 1 AS dummy) AS src ON ($this->idCol = ?) ". + "WHEN NOT MATCHED THEN INSERT ($this->idCol, $this->dataCol, $this->lifetimeCol, $this->timeCol) VALUES (?, ?, ?, ?) ". + "WHEN MATCHED THEN UPDATE SET $this->dataCol = ?, $this->lifetimeCol = ?, $this->timeCol = ?;"; + break; + case 'sqlite': + $sql = 'INSERT OR REPLACE'.substr($insertSql, 6); + break; + case 'pgsql': + $sql = $insertSql." ON CONFLICT ($this->idCol) DO UPDATE SET ($this->dataCol, $this->lifetimeCol, $this->timeCol) = (EXCLUDED.$this->dataCol, EXCLUDED.$this->lifetimeCol, EXCLUDED.$this->timeCol)"; + break; + default: + $platformName = null; + $sql = "UPDATE $this->table SET $this->dataCol = ?, $this->lifetimeCol = ?, $this->timeCol = ? WHERE $this->idCol = ?"; + break; + } + + $now = time(); + $lifetime = $lifetime ?: null; + try { + $stmt = $this->conn->prepare($sql); + } catch (TableNotFoundException) { + if (!$this->conn->isTransactionActive() || \in_array($platformName, ['pgsql', 'sqlite', 'sqlsrv'], true)) { + $this->createTable(); + } + $stmt = $this->conn->prepare($sql); + } + + if ('sqlsrv' === $platformName || 'oci' === $platformName) { + $bind = static function ($id, $data) use ($stmt) { + $stmt->bindValue(1, $id); + $stmt->bindValue(2, $id); + $stmt->bindValue(3, $data, ParameterType::LARGE_OBJECT); + $stmt->bindValue(6, $data, ParameterType::LARGE_OBJECT); + }; + $stmt->bindValue(4, $lifetime, ParameterType::INTEGER); + $stmt->bindValue(5, $now, ParameterType::INTEGER); + $stmt->bindValue(7, $lifetime, ParameterType::INTEGER); + $stmt->bindValue(8, $now, ParameterType::INTEGER); + } elseif (null !== $platformName) { + $bind = static function ($id, $data) use ($stmt) { + $stmt->bindValue(1, $id); + $stmt->bindValue(2, $data, ParameterType::LARGE_OBJECT); + }; + $stmt->bindValue(3, $lifetime, ParameterType::INTEGER); + $stmt->bindValue(4, $now, ParameterType::INTEGER); + } else { + $stmt->bindValue(2, $lifetime, ParameterType::INTEGER); + $stmt->bindValue(3, $now, ParameterType::INTEGER); + + $insertStmt = $this->conn->prepare($insertSql); + $insertStmt->bindValue(3, $lifetime, ParameterType::INTEGER); + $insertStmt->bindValue(4, $now, ParameterType::INTEGER); + + $bind = static function ($id, $data) use ($stmt, $insertStmt) { + $stmt->bindValue(1, $data, ParameterType::LARGE_OBJECT); + $stmt->bindValue(4, $id); + $insertStmt->bindValue(1, $id); + $insertStmt->bindValue(2, $data, ParameterType::LARGE_OBJECT); + }; + } + + foreach ($values as $id => $data) { + $bind($id, $data); + try { + $rowCount = $stmt->executeStatement(); + } catch (TableNotFoundException) { + if (!$this->conn->isTransactionActive() || \in_array($platformName, ['pgsql', 'sqlite', 'sqlsrv'], true)) { + $this->createTable(); + } + $rowCount = $stmt->executeStatement(); + } + if (null === $platformName && 0 === $rowCount) { + try { + $insertStmt->executeStatement(); + } catch (DBALException) { + // A concurrent write won, let it be + } + } + } + + return $failed; + } + + /** + * @internal + */ + protected function getId(mixed $key): string + { + if ('pgsql' !== $this->platformName ??= $this->getPlatformName()) { + return parent::getId($key); + } + + if (str_contains($key, "\0") || str_contains($key, '%') || !preg_match('//u', $key)) { + $key = rawurlencode($key); + } + + return parent::getId($key); + } + + private function getPlatformName(): string + { + if (isset($this->platformName)) { + return $this->platformName; + } + + $platform = $this->conn->getDatabasePlatform(); + + return $this->platformName = match (true) { + $platform instanceof \Doctrine\DBAL\Platforms\AbstractMySQLPlatform => 'mysql', + $platform instanceof \Doctrine\DBAL\Platforms\SqlitePlatform => 'sqlite', + $platform instanceof \Doctrine\DBAL\Platforms\PostgreSQLPlatform => 'pgsql', + $platform instanceof \Doctrine\DBAL\Platforms\OraclePlatform => 'oci', + $platform instanceof \Doctrine\DBAL\Platforms\SQLServerPlatform => 'sqlsrv', + default => $platform::class, + }; + } + + private function addTableToSchema(Schema $schema): void + { + $types = [ + 'mysql' => 'binary', + 'sqlite' => 'text', + ]; + + $table = $schema->createTable($this->table); + $table->addColumn($this->idCol, $types[$this->getPlatformName()] ?? 'string', ['length' => 255]); + $table->addColumn($this->dataCol, 'blob', ['length' => 16777215]); + $table->addColumn($this->lifetimeCol, 'integer', ['unsigned' => true, 'notnull' => false]); + $table->addColumn($this->timeCol, 'integer', ['unsigned' => true]); + $table->setPrimaryKey([$this->idCol]); + } +} diff --git a/vendor/symfony/cache/Adapter/FilesystemAdapter.php b/vendor/symfony/cache/Adapter/FilesystemAdapter.php new file mode 100644 index 0000000..13daa56 --- /dev/null +++ b/vendor/symfony/cache/Adapter/FilesystemAdapter.php @@ -0,0 +1,29 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Cache\Adapter; + +use Symfony\Component\Cache\Marshaller\DefaultMarshaller; +use Symfony\Component\Cache\Marshaller\MarshallerInterface; +use Symfony\Component\Cache\PruneableInterface; +use Symfony\Component\Cache\Traits\FilesystemTrait; + +class FilesystemAdapter extends AbstractAdapter implements PruneableInterface +{ + use FilesystemTrait; + + public function __construct(string $namespace = '', int $defaultLifetime = 0, ?string $directory = null, ?MarshallerInterface $marshaller = null) + { + $this->marshaller = $marshaller ?? new DefaultMarshaller(); + parent::__construct('', $defaultLifetime); + $this->init($namespace, $directory); + } +} diff --git a/vendor/symfony/cache/Adapter/FilesystemTagAwareAdapter.php b/vendor/symfony/cache/Adapter/FilesystemTagAwareAdapter.php new file mode 100644 index 0000000..80edee4 --- /dev/null +++ b/vendor/symfony/cache/Adapter/FilesystemTagAwareAdapter.php @@ -0,0 +1,267 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Cache\Adapter; + +use Symfony\Component\Cache\Marshaller\MarshallerInterface; +use Symfony\Component\Cache\Marshaller\TagAwareMarshaller; +use Symfony\Component\Cache\PruneableInterface; +use Symfony\Component\Cache\Traits\FilesystemTrait; + +/** + * Stores tag id <> cache id relationship as a symlink, and lookup on invalidation calls. + * + * @author Nicolas Grekas + * @author AndrĆ© RĆømcke + */ +class FilesystemTagAwareAdapter extends AbstractTagAwareAdapter implements PruneableInterface +{ + use FilesystemTrait { + prune as private doPrune; + doClear as private doClearCache; + doSave as private doSaveCache; + } + + /** + * Folder used for tag symlinks. + */ + private const TAG_FOLDER = 'tags'; + + public function __construct(string $namespace = '', int $defaultLifetime = 0, ?string $directory = null, ?MarshallerInterface $marshaller = null) + { + $this->marshaller = new TagAwareMarshaller($marshaller); + parent::__construct('', $defaultLifetime); + $this->init($namespace, $directory); + } + + public function prune(): bool + { + $ok = $this->doPrune(); + + set_error_handler(static function () {}); + $chars = '+-ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'; + + try { + foreach ($this->scanHashDir($this->directory.self::TAG_FOLDER.\DIRECTORY_SEPARATOR) as $dir) { + $dir .= \DIRECTORY_SEPARATOR; + $keepDir = false; + for ($i = 0; $i < 38; ++$i) { + if (!is_dir($dir.$chars[$i])) { + continue; + } + for ($j = 0; $j < 38; ++$j) { + if (!is_dir($d = $dir.$chars[$i].\DIRECTORY_SEPARATOR.$chars[$j])) { + continue; + } + foreach (scandir($d, \SCANDIR_SORT_NONE) ?: [] as $link) { + if ('.' === $link || '..' === $link) { + continue; + } + if ('_' !== $dir[-2] && realpath($d.\DIRECTORY_SEPARATOR.$link)) { + $keepDir = true; + } else { + unlink($d.\DIRECTORY_SEPARATOR.$link); + } + } + $keepDir ?: rmdir($d); + } + $keepDir ?: rmdir($dir.$chars[$i]); + } + $keepDir ?: rmdir($dir); + } + } finally { + restore_error_handler(); + } + + return $ok; + } + + protected function doClear(string $namespace): bool + { + $ok = $this->doClearCache($namespace); + + if ('' !== $namespace) { + return $ok; + } + + set_error_handler(static function () {}); + $chars = '+-ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'; + + $this->tmpSuffix ??= str_replace('/', '-', base64_encode(random_bytes(6))); + + try { + foreach ($this->scanHashDir($this->directory.self::TAG_FOLDER.\DIRECTORY_SEPARATOR) as $dir) { + if (rename($dir, $renamed = substr_replace($dir, $this->tmpSuffix.'_', -9))) { + $dir = $renamed.\DIRECTORY_SEPARATOR; + } else { + $dir .= \DIRECTORY_SEPARATOR; + $renamed = null; + } + + for ($i = 0; $i < 38; ++$i) { + if (!is_dir($dir.$chars[$i])) { + continue; + } + for ($j = 0; $j < 38; ++$j) { + if (!is_dir($d = $dir.$chars[$i].\DIRECTORY_SEPARATOR.$chars[$j])) { + continue; + } + foreach (scandir($d, \SCANDIR_SORT_NONE) ?: [] as $link) { + if ('.' !== $link && '..' !== $link && (null !== $renamed || !realpath($d.\DIRECTORY_SEPARATOR.$link))) { + unlink($d.\DIRECTORY_SEPARATOR.$link); + } + } + null === $renamed ?: rmdir($d); + } + null === $renamed ?: rmdir($dir.$chars[$i]); + } + null === $renamed ?: rmdir($renamed); + } + } finally { + restore_error_handler(); + } + + return $ok; + } + + protected function doSave(array $values, int $lifetime, array $addTagData = [], array $removeTagData = []): array + { + $failed = $this->doSaveCache($values, $lifetime); + + // Add Tags as symlinks + foreach ($addTagData as $tagId => $ids) { + $tagFolder = $this->getTagFolder($tagId); + foreach ($ids as $id) { + if ($failed && \in_array($id, $failed, true)) { + continue; + } + + $file = $this->getFile($id); + + if (!@symlink($file, $tagLink = $this->getFile($id, true, $tagFolder)) && !is_link($tagLink)) { + @unlink($file); + $failed[] = $id; + } + } + } + + // Unlink removed Tags + foreach ($removeTagData as $tagId => $ids) { + $tagFolder = $this->getTagFolder($tagId); + foreach ($ids as $id) { + if ($failed && \in_array($id, $failed, true)) { + continue; + } + + @unlink($this->getFile($id, false, $tagFolder)); + } + } + + return $failed; + } + + protected function doDeleteYieldTags(array $ids): iterable + { + foreach ($ids as $id) { + $file = $this->getFile($id); + if (!is_file($file) || !$h = @fopen($file, 'r')) { + continue; + } + + if (!@unlink($file)) { + fclose($h); + continue; + } + + $meta = explode("\n", fread($h, 4096), 3)[2] ?? ''; + + // detect the compact format used in marshall() using magic numbers in the form 9D-..-..-..-..-00-..-..-..-5F + if (13 < \strlen($meta) && "\x9D" === $meta[0] && "\0" === $meta[5] && "\x5F" === $meta[9]) { + $meta[9] = "\0"; + $tagLen = unpack('Nlen', $meta, 9)['len']; + $meta = substr($meta, 13, $tagLen); + + if (0 < $tagLen -= \strlen($meta)) { + $meta .= fread($h, $tagLen); + } + + try { + yield $id => '' === $meta ? [] : $this->marshaller->unmarshall($meta); + } catch (\Exception) { + yield $id => []; + } + } + + fclose($h); + } + } + + protected function doDeleteTagRelations(array $tagData): bool + { + foreach ($tagData as $tagId => $idList) { + $tagFolder = $this->getTagFolder($tagId); + foreach ($idList as $id) { + @unlink($this->getFile($id, false, $tagFolder)); + } + } + + return true; + } + + protected function doInvalidate(array $tagIds): bool + { + foreach ($tagIds as $tagId) { + if (!is_dir($tagFolder = $this->getTagFolder($tagId))) { + continue; + } + + $this->tmpSuffix ??= str_replace('/', '-', base64_encode(random_bytes(6))); + + set_error_handler(static function () {}); + + try { + if (rename($tagFolder, $renamed = substr_replace($tagFolder, $this->tmpSuffix.'_', -10))) { + $tagFolder = $renamed.\DIRECTORY_SEPARATOR; + } else { + $renamed = null; + } + + foreach ($this->scanHashDir($tagFolder) as $itemLink) { + unlink(realpath($itemLink) ?: $itemLink); + unlink($itemLink); + } + + if (null === $renamed) { + continue; + } + + $chars = '+-ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'; + + for ($i = 0; $i < 38; ++$i) { + for ($j = 0; $j < 38; ++$j) { + rmdir($tagFolder.$chars[$i].\DIRECTORY_SEPARATOR.$chars[$j]); + } + rmdir($tagFolder.$chars[$i]); + } + rmdir($renamed); + } finally { + restore_error_handler(); + } + } + + return true; + } + + private function getTagFolder(string $tagId): string + { + return $this->getFile($tagId, false, $this->directory.self::TAG_FOLDER.\DIRECTORY_SEPARATOR).\DIRECTORY_SEPARATOR; + } +} diff --git a/vendor/symfony/cache/Adapter/MemcachedAdapter.php b/vendor/symfony/cache/Adapter/MemcachedAdapter.php new file mode 100644 index 0000000..d882ef1 --- /dev/null +++ b/vendor/symfony/cache/Adapter/MemcachedAdapter.php @@ -0,0 +1,329 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Cache\Adapter; + +use Symfony\Component\Cache\Exception\CacheException; +use Symfony\Component\Cache\Exception\InvalidArgumentException; +use Symfony\Component\Cache\Marshaller\DefaultMarshaller; +use Symfony\Component\Cache\Marshaller\MarshallerInterface; + +/** + * @author Rob Frawley 2nd + * @author Nicolas Grekas + */ +class MemcachedAdapter extends AbstractAdapter +{ + /** + * We are replacing characters that are illegal in Memcached keys with reserved characters from + * {@see \Symfony\Contracts\Cache\ItemInterface::RESERVED_CHARACTERS} that are legal in Memcached. + * Note: don’t use {@see AbstractAdapter::NS_SEPARATOR}. + */ + private const RESERVED_MEMCACHED = " \n\r\t\v\f\0"; + private const RESERVED_PSR6 = '@()\{}/'; + private const MAX_KEY_LENGTH = 250; + + private MarshallerInterface $marshaller; + private \Memcached $client; + private \Memcached $lazyClient; + + /** + * Using a MemcachedAdapter with a TagAwareAdapter for storing tags is discouraged. + * Using a RedisAdapter is recommended instead. If you cannot do otherwise, be aware that: + * - the Memcached::OPT_BINARY_PROTOCOL must be enabled + * (that's the default when using MemcachedAdapter::createConnection()); + * - tags eviction by Memcached's LRU algorithm will break by-tags invalidation; + * your Memcached memory should be large enough to never trigger LRU. + * + * Using a MemcachedAdapter as a pure items store is fine. + */ + public function __construct(\Memcached $client, string $namespace = '', int $defaultLifetime = 0, ?MarshallerInterface $marshaller = null) + { + if (!static::isSupported()) { + throw new CacheException('Memcached > 3.1.5 is required.'); + } + $this->maxIdLength = self::MAX_KEY_LENGTH; + + if ('Memcached' === $client::class) { + $opt = $client->getOption(\Memcached::OPT_SERIALIZER); + if (\Memcached::SERIALIZER_PHP !== $opt && \Memcached::SERIALIZER_IGBINARY !== $opt) { + throw new CacheException('MemcachedAdapter: "serializer" option must be "php" or "igbinary".'); + } + $this->maxIdLength -= \strlen($client->getOption(\Memcached::OPT_PREFIX_KEY)); + $this->client = $client; + } else { + $this->lazyClient = $client; + } + + parent::__construct($namespace, $defaultLifetime); + $this->enableVersioning(); + $this->marshaller = $marshaller ?? new DefaultMarshaller(); + } + + public static function isSupported(): bool + { + return \extension_loaded('memcached') && version_compare(phpversion('memcached'), '3.1.6', '>='); + } + + /** + * Creates a Memcached instance. + * + * By default, the binary protocol, no block, and libketama compatible options are enabled. + * + * Examples for servers: + * - 'memcached://user:pass@localhost?weight=33' + * - [['localhost', 11211, 33]] + * + * @param array[]|string|string[] $servers An array of servers, a DSN, or an array of DSNs + * + * @throws \ErrorException When invalid options or servers are provided + */ + public static function createConnection(#[\SensitiveParameter] array|string $servers, array $options = []): \Memcached + { + if (\is_string($servers)) { + $servers = [$servers]; + } + if (!static::isSupported()) { + throw new CacheException('Memcached > 3.1.5 is required.'); + } + set_error_handler(static fn ($type, $msg, $file, $line) => throw new \ErrorException($msg, 0, $type, $file, $line)); + try { + $client = new \Memcached($options['persistent_id'] ?? null); + $username = $options['username'] ?? null; + $password = $options['password'] ?? null; + + // parse any DSN in $servers + foreach ($servers as $i => $dsn) { + if (\is_array($dsn)) { + continue; + } + if (!str_starts_with($dsn, 'memcached:')) { + throw new InvalidArgumentException('Invalid Memcached DSN: it does not start with "memcached:".'); + } + $params = preg_replace_callback('#^memcached:(//)?(?:([^@]*+)@)?#', function ($m) use (&$username, &$password) { + if (!empty($m[2])) { + [$username, $password] = explode(':', $m[2], 2) + [1 => null]; + $username = rawurldecode($username); + $password = null !== $password ? rawurldecode($password) : null; + } + + return 'file:'.($m[1] ?? ''); + }, $dsn); + if (false === $params = parse_url($params)) { + throw new InvalidArgumentException('Invalid Memcached DSN.'); + } + $query = $hosts = []; + if (isset($params['query'])) { + parse_str($params['query'], $query); + + if (isset($query['host'])) { + if (!\is_array($hosts = $query['host'])) { + throw new InvalidArgumentException('Invalid Memcached DSN: query parameter "host" must be an array.'); + } + foreach ($hosts as $host => $weight) { + if (false === $port = strrpos($host, ':')) { + $hosts[$host] = [$host, 11211, (int) $weight]; + } else { + $hosts[$host] = [substr($host, 0, $port), (int) substr($host, 1 + $port), (int) $weight]; + } + } + $hosts = array_values($hosts); + unset($query['host']); + } + if ($hosts && !isset($params['host']) && !isset($params['path'])) { + unset($servers[$i]); + $servers = array_merge($servers, $hosts); + continue; + } + } + if (!isset($params['host']) && !isset($params['path'])) { + throw new InvalidArgumentException('Invalid Memcached DSN: missing host or path.'); + } + if (isset($params['path']) && preg_match('#/(\d+)$#', $params['path'], $m)) { + $params['weight'] = $m[1]; + $params['path'] = substr($params['path'], 0, -\strlen($m[0])); + } + $params += [ + 'host' => $params['host'] ?? $params['path'], + 'port' => isset($params['host']) ? 11211 : null, + 'weight' => 0, + ]; + if ($query) { + $params += $query; + $options = $query + $options; + } + + $servers[$i] = [$params['host'], $params['port'], $params['weight']]; + + if ($hosts) { + $servers = array_merge($servers, $hosts); + } + } + + // set client's options + unset($options['persistent_id'], $options['username'], $options['password'], $options['weight'], $options['lazy']); + $options = array_change_key_case($options, \CASE_UPPER); + $client->setOption(\Memcached::OPT_BINARY_PROTOCOL, true); + $client->setOption(\Memcached::OPT_NO_BLOCK, true); + $client->setOption(\Memcached::OPT_TCP_NODELAY, true); + if (!\array_key_exists('LIBKETAMA_COMPATIBLE', $options) && !\array_key_exists(\Memcached::OPT_LIBKETAMA_COMPATIBLE, $options)) { + $client->setOption(\Memcached::OPT_LIBKETAMA_COMPATIBLE, true); + } + foreach ($options as $name => $value) { + if (\is_int($name)) { + continue; + } + if ('HASH' === $name || 'SERIALIZER' === $name || 'DISTRIBUTION' === $name) { + $value = \constant('Memcached::'.$name.'_'.strtoupper($value)); + } + unset($options[$name]); + + if (\defined('Memcached::OPT_'.$name)) { + $options[\constant('Memcached::OPT_'.$name)] = $value; + } + } + $client->setOptions($options + [\Memcached::OPT_SERIALIZER => \Memcached::SERIALIZER_PHP]); + + // set client's servers, taking care of persistent connections + if (!$client->isPristine()) { + $oldServers = []; + foreach ($client->getServerList() as $server) { + $oldServers[] = [$server['host'], $server['port']]; + } + + $newServers = []; + foreach ($servers as $server) { + if (1 < \count($server)) { + $server = array_values($server); + unset($server[2]); + $server[1] = (int) $server[1]; + } + $newServers[] = $server; + } + + if ($oldServers !== $newServers) { + $client->resetServerList(); + $client->addServers($servers); + } + } else { + $client->addServers($servers); + } + + if (null !== $username || null !== $password) { + if (!method_exists($client, 'setSaslAuthData')) { + trigger_error('Missing SASL support: the memcached extension must be compiled with --enable-memcached-sasl.'); + } + $client->setSaslAuthData($username, $password); + } + + return $client; + } finally { + restore_error_handler(); + } + } + + protected function doSave(array $values, int $lifetime): array|bool + { + if (!$values = $this->marshaller->marshall($values, $failed)) { + return $failed; + } + + if ($lifetime && $lifetime > 30 * 86400) { + $lifetime += time(); + } + + $encodedValues = []; + foreach ($values as $key => $value) { + $encodedValues[self::encodeKey($key)] = $value; + } + + return $this->checkResultCode($this->getClient()->setMulti($encodedValues, $lifetime)) ? $failed : false; + } + + protected function doFetch(array $ids): iterable + { + try { + $encodedIds = array_map([__CLASS__, 'encodeKey'], $ids); + + $encodedResult = $this->checkResultCode($this->getClient()->getMulti($encodedIds)); + + $result = []; + foreach ($encodedResult as $key => $value) { + $result[self::decodeKey($key)] = $this->marshaller->unmarshall($value); + } + + return $result; + } catch (\Error $e) { + throw new \ErrorException($e->getMessage(), $e->getCode(), \E_ERROR, $e->getFile(), $e->getLine()); + } + } + + protected function doHave(string $id): bool + { + return false !== $this->getClient()->get(self::encodeKey($id)) || $this->checkResultCode(\Memcached::RES_SUCCESS === $this->client->getResultCode()); + } + + protected function doDelete(array $ids): bool + { + $ok = true; + $encodedIds = array_map([__CLASS__, 'encodeKey'], $ids); + foreach ($this->checkResultCode($this->getClient()->deleteMulti($encodedIds)) as $result) { + if (\Memcached::RES_SUCCESS !== $result && \Memcached::RES_NOTFOUND !== $result) { + $ok = false; + } + } + + return $ok; + } + + protected function doClear(string $namespace): bool + { + return '' === $namespace && $this->getClient()->flush(); + } + + private function checkResultCode(mixed $result): mixed + { + $code = $this->client->getResultCode(); + + if (\Memcached::RES_SUCCESS === $code || \Memcached::RES_NOTFOUND === $code) { + return $result; + } + + throw new CacheException('MemcachedAdapter client error: '.strtolower($this->client->getResultMessage())); + } + + private function getClient(): \Memcached + { + if (isset($this->client)) { + return $this->client; + } + + $opt = $this->lazyClient->getOption(\Memcached::OPT_SERIALIZER); + if (\Memcached::SERIALIZER_PHP !== $opt && \Memcached::SERIALIZER_IGBINARY !== $opt) { + throw new CacheException('MemcachedAdapter: "serializer" option must be "php" or "igbinary".'); + } + if ('' !== $prefix = (string) $this->lazyClient->getOption(\Memcached::OPT_PREFIX_KEY)) { + throw new CacheException(\sprintf('MemcachedAdapter: "prefix_key" option must be empty when using proxified connections, "%s" given.', $prefix)); + } + + return $this->client = $this->lazyClient; + } + + private static function encodeKey(string $key): string + { + return strtr($key, self::RESERVED_MEMCACHED, self::RESERVED_PSR6); + } + + private static function decodeKey(string $key): string + { + return strtr($key, self::RESERVED_PSR6, self::RESERVED_MEMCACHED); + } +} diff --git a/vendor/symfony/cache/Adapter/NullAdapter.php b/vendor/symfony/cache/Adapter/NullAdapter.php new file mode 100644 index 0000000..d5d2ef6 --- /dev/null +++ b/vendor/symfony/cache/Adapter/NullAdapter.php @@ -0,0 +1,105 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Cache\Adapter; + +use Psr\Cache\CacheItemInterface; +use Symfony\Component\Cache\CacheItem; +use Symfony\Contracts\Cache\CacheInterface; + +/** + * @author Titouan Galopin + */ +class NullAdapter implements AdapterInterface, CacheInterface +{ + private static \Closure $createCacheItem; + + public function __construct() + { + self::$createCacheItem ??= \Closure::bind( + static function ($key) { + $item = new CacheItem(); + $item->key = $key; + $item->isHit = false; + + return $item; + }, + null, + CacheItem::class + ); + } + + public function get(string $key, callable $callback, ?float $beta = null, ?array &$metadata = null): mixed + { + $save = true; + + return $callback((self::$createCacheItem)($key), $save); + } + + public function getItem(mixed $key): CacheItem + { + return (self::$createCacheItem)($key); + } + + public function getItems(array $keys = []): iterable + { + return $this->generateItems($keys); + } + + public function hasItem(mixed $key): bool + { + return false; + } + + public function clear(string $prefix = ''): bool + { + return true; + } + + public function deleteItem(mixed $key): bool + { + return true; + } + + public function deleteItems(array $keys): bool + { + return true; + } + + public function save(CacheItemInterface $item): bool + { + return true; + } + + public function saveDeferred(CacheItemInterface $item): bool + { + return true; + } + + public function commit(): bool + { + return true; + } + + public function delete(string $key): bool + { + return $this->deleteItem($key); + } + + private function generateItems(array $keys): \Generator + { + $f = self::$createCacheItem; + + foreach ($keys as $key) { + yield $key => $f($key); + } + } +} diff --git a/vendor/symfony/cache/Adapter/ParameterNormalizer.php b/vendor/symfony/cache/Adapter/ParameterNormalizer.php new file mode 100644 index 0000000..483df1c --- /dev/null +++ b/vendor/symfony/cache/Adapter/ParameterNormalizer.php @@ -0,0 +1,35 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Cache\Adapter; + +/** + * @author Lars Strojny + */ +final class ParameterNormalizer +{ + public static function normalizeDuration(string $duration): int + { + if (is_numeric($duration)) { + return $duration; + } + + if (false !== $time = strtotime($duration, 0)) { + return $time; + } + + try { + return \DateTimeImmutable::createFromFormat('U', 0)->add(new \DateInterval($duration))->getTimestamp(); + } catch (\Exception $e) { + throw new \InvalidArgumentException(\sprintf('Cannot parse date interval "%s".', $duration), 0, $e); + } + } +} diff --git a/vendor/symfony/cache/Adapter/PdoAdapter.php b/vendor/symfony/cache/Adapter/PdoAdapter.php new file mode 100644 index 0000000..525e2c6 --- /dev/null +++ b/vendor/symfony/cache/Adapter/PdoAdapter.php @@ -0,0 +1,398 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Cache\Adapter; + +use Symfony\Component\Cache\Exception\InvalidArgumentException; +use Symfony\Component\Cache\Marshaller\DefaultMarshaller; +use Symfony\Component\Cache\Marshaller\MarshallerInterface; +use Symfony\Component\Cache\PruneableInterface; + +class PdoAdapter extends AbstractAdapter implements PruneableInterface +{ + private const MAX_KEY_LENGTH = 255; + + private MarshallerInterface $marshaller; + private \PDO $conn; + private string $dsn; + private string $driver; + private string $serverVersion; + private string $table = 'cache_items'; + private string $idCol = 'item_id'; + private string $dataCol = 'item_data'; + private string $lifetimeCol = 'item_lifetime'; + private string $timeCol = 'item_time'; + private ?string $username = null; + private ?string $password = null; + private array $connectionOptions = []; + private string $namespace; + + /** + * You can either pass an existing database connection as PDO instance or + * a DSN string that will be used to lazy-connect to the database when the + * cache is actually used. + * + * List of available options: + * * db_table: The name of the table [default: cache_items] + * * db_id_col: The column where to store the cache id [default: item_id] + * * db_data_col: The column where to store the cache data [default: item_data] + * * db_lifetime_col: The column where to store the lifetime [default: item_lifetime] + * * db_time_col: The column where to store the timestamp [default: item_time] + * * db_username: The username when lazy-connect [default: ''] + * * db_password: The password when lazy-connect [default: ''] + * * db_connection_options: An array of driver-specific connection options [default: []] + * + * @throws InvalidArgumentException When first argument is not PDO nor Connection nor string + * @throws InvalidArgumentException When PDO error mode is not PDO::ERRMODE_EXCEPTION + * @throws InvalidArgumentException When namespace contains invalid characters + */ + public function __construct(#[\SensitiveParameter] \PDO|string $connOrDsn, string $namespace = '', int $defaultLifetime = 0, array $options = [], ?MarshallerInterface $marshaller = null) + { + if (\is_string($connOrDsn) && str_contains($connOrDsn, '://')) { + throw new InvalidArgumentException(\sprintf('Usage of Doctrine DBAL URL with "%s" is not supported. Use a PDO DSN or "%s" instead.', __CLASS__, DoctrineDbalAdapter::class)); + } + + if (isset($namespace[0]) && preg_match('#[^-+.A-Za-z0-9]#', $namespace, $match)) { + throw new InvalidArgumentException(\sprintf('Namespace contains "%s" but only characters in [-+.A-Za-z0-9] are allowed.', $match[0])); + } + + if ($connOrDsn instanceof \PDO) { + if (\PDO::ERRMODE_EXCEPTION !== $connOrDsn->getAttribute(\PDO::ATTR_ERRMODE)) { + throw new InvalidArgumentException(\sprintf('"%s" requires PDO error mode attribute be set to throw Exceptions (i.e. $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION)).', __CLASS__)); + } + + $this->conn = $connOrDsn; + } else { + $this->dsn = $connOrDsn; + } + + $this->maxIdLength = self::MAX_KEY_LENGTH; + $this->table = $options['db_table'] ?? $this->table; + $this->idCol = $options['db_id_col'] ?? $this->idCol; + $this->dataCol = $options['db_data_col'] ?? $this->dataCol; + $this->lifetimeCol = $options['db_lifetime_col'] ?? $this->lifetimeCol; + $this->timeCol = $options['db_time_col'] ?? $this->timeCol; + $this->username = $options['db_username'] ?? $this->username; + $this->password = $options['db_password'] ?? $this->password; + $this->connectionOptions = $options['db_connection_options'] ?? $this->connectionOptions; + $this->namespace = $namespace; + $this->marshaller = $marshaller ?? new DefaultMarshaller(); + + parent::__construct($namespace, $defaultLifetime); + } + + public static function createConnection(#[\SensitiveParameter] string $dsn, array $options = []): \PDO|string + { + if ($options['lazy'] ?? true) { + return $dsn; + } + + $pdo = new \PDO($dsn); + $pdo->setAttribute(\PDO::ATTR_ERRMODE, \PDO::ERRMODE_EXCEPTION); + + return $pdo; + } + + /** + * Creates the table to store cache items which can be called once for setup. + * + * Cache ID are saved in a column of maximum length 255. Cache data is + * saved in a BLOB. + * + * @throws \PDOException When the table already exists + * @throws \DomainException When an unsupported PDO driver is used + */ + public function createTable(): void + { + $sql = match ($driver = $this->getDriver()) { + // We use varbinary for the ID column because it prevents unwanted conversions: + // - character set conversions between server and client + // - trailing space removal + // - case-insensitivity + // - language processing like Ć© == e + 'mysql' => "CREATE TABLE $this->table ($this->idCol VARBINARY(255) NOT NULL PRIMARY KEY, $this->dataCol MEDIUMBLOB NOT NULL, $this->lifetimeCol INTEGER UNSIGNED, $this->timeCol INTEGER UNSIGNED NOT NULL) COLLATE utf8mb4_bin, ENGINE = InnoDB", + 'sqlite' => "CREATE TABLE $this->table ($this->idCol TEXT NOT NULL PRIMARY KEY, $this->dataCol BLOB NOT NULL, $this->lifetimeCol INTEGER, $this->timeCol INTEGER NOT NULL)", + 'pgsql' => "CREATE TABLE $this->table ($this->idCol VARCHAR(255) NOT NULL PRIMARY KEY, $this->dataCol BYTEA NOT NULL, $this->lifetimeCol INTEGER, $this->timeCol INTEGER NOT NULL)", + 'oci' => "CREATE TABLE $this->table ($this->idCol VARCHAR2(255) NOT NULL PRIMARY KEY, $this->dataCol BLOB NOT NULL, $this->lifetimeCol INTEGER, $this->timeCol INTEGER NOT NULL)", + 'sqlsrv' => "CREATE TABLE $this->table ($this->idCol VARCHAR(255) NOT NULL PRIMARY KEY, $this->dataCol VARBINARY(MAX) NOT NULL, $this->lifetimeCol INTEGER, $this->timeCol INTEGER NOT NULL)", + default => throw new \DomainException(\sprintf('Creating the cache table is currently not implemented for PDO driver "%s".', $driver)), + }; + + $this->getConnection()->exec($sql); + } + + public function prune(): bool + { + $deleteSql = "DELETE FROM $this->table WHERE $this->lifetimeCol + $this->timeCol <= :time"; + + if ('' !== $this->namespace) { + $deleteSql .= " AND $this->idCol LIKE :namespace"; + } + + $connection = $this->getConnection(); + + try { + $delete = $connection->prepare($deleteSql); + } catch (\PDOException) { + return true; + } + $delete->bindValue(':time', time(), \PDO::PARAM_INT); + + if ('' !== $this->namespace) { + $delete->bindValue(':namespace', \sprintf('%s%%', $this->namespace), \PDO::PARAM_STR); + } + try { + return $delete->execute(); + } catch (\PDOException) { + return true; + } + } + + protected function doFetch(array $ids): iterable + { + $connection = $this->getConnection(); + + $now = time(); + $expired = []; + + $sql = str_pad('', (\count($ids) << 1) - 1, '?,'); + $sql = "SELECT $this->idCol, CASE WHEN $this->lifetimeCol IS NULL OR $this->lifetimeCol + $this->timeCol > ? THEN $this->dataCol ELSE NULL END FROM $this->table WHERE $this->idCol IN ($sql)"; + $stmt = $connection->prepare($sql); + $stmt->bindValue($i = 1, $now, \PDO::PARAM_INT); + foreach ($ids as $id) { + $stmt->bindValue(++$i, $id); + } + $result = $stmt->execute(); + + if (\is_object($result)) { + $result = $result->iterateNumeric(); + } else { + $stmt->setFetchMode(\PDO::FETCH_NUM); + $result = $stmt; + } + + foreach ($result as $row) { + if (null === $row[1]) { + $expired[] = $row[0]; + } else { + yield $row[0] => $this->marshaller->unmarshall(\is_resource($row[1]) ? stream_get_contents($row[1]) : $row[1]); + } + } + + if ($expired) { + $sql = str_pad('', (\count($expired) << 1) - 1, '?,'); + $sql = "DELETE FROM $this->table WHERE $this->lifetimeCol + $this->timeCol <= ? AND $this->idCol IN ($sql)"; + $stmt = $connection->prepare($sql); + $stmt->bindValue($i = 1, $now, \PDO::PARAM_INT); + foreach ($expired as $id) { + $stmt->bindValue(++$i, $id); + } + $stmt->execute(); + } + } + + protected function doHave(string $id): bool + { + $connection = $this->getConnection(); + + $sql = "SELECT 1 FROM $this->table WHERE $this->idCol = :id AND ($this->lifetimeCol IS NULL OR $this->lifetimeCol + $this->timeCol > :time)"; + $stmt = $connection->prepare($sql); + + $stmt->bindValue(':id', $id); + $stmt->bindValue(':time', time(), \PDO::PARAM_INT); + $stmt->execute(); + + return (bool) $stmt->fetchColumn(); + } + + protected function doClear(string $namespace): bool + { + $conn = $this->getConnection(); + + if ('' === $namespace) { + if ('sqlite' === $this->getDriver()) { + $sql = "DELETE FROM $this->table"; + } else { + $sql = "TRUNCATE TABLE $this->table"; + } + } else { + $sql = "DELETE FROM $this->table WHERE $this->idCol LIKE '$namespace%'"; + } + + try { + $conn->exec($sql); + } catch (\PDOException) { + } + + return true; + } + + protected function doDelete(array $ids): bool + { + $sql = str_pad('', (\count($ids) << 1) - 1, '?,'); + $sql = "DELETE FROM $this->table WHERE $this->idCol IN ($sql)"; + try { + $stmt = $this->getConnection()->prepare($sql); + $stmt->execute(array_values($ids)); + } catch (\PDOException) { + } + + return true; + } + + protected function doSave(array $values, int $lifetime): array|bool + { + if (!$values = $this->marshaller->marshall($values, $failed)) { + return $failed; + } + + $conn = $this->getConnection(); + + $driver = $this->getDriver(); + $insertSql = "INSERT INTO $this->table ($this->idCol, $this->dataCol, $this->lifetimeCol, $this->timeCol) VALUES (:id, :data, :lifetime, :time)"; + + switch (true) { + case 'mysql' === $driver: + $sql = $insertSql." ON DUPLICATE KEY UPDATE $this->dataCol = VALUES($this->dataCol), $this->lifetimeCol = VALUES($this->lifetimeCol), $this->timeCol = VALUES($this->timeCol)"; + break; + case 'oci' === $driver: + // DUAL is Oracle specific dummy table + $sql = "MERGE INTO $this->table USING DUAL ON ($this->idCol = ?) ". + "WHEN NOT MATCHED THEN INSERT ($this->idCol, $this->dataCol, $this->lifetimeCol, $this->timeCol) VALUES (?, ?, ?, ?) ". + "WHEN MATCHED THEN UPDATE SET $this->dataCol = ?, $this->lifetimeCol = ?, $this->timeCol = ?"; + break; + case 'sqlsrv' === $driver && version_compare($this->getServerVersion(), '10', '>='): + // MERGE is only available since SQL Server 2008 and must be terminated by semicolon + // It also requires HOLDLOCK according to http://weblogs.sqlteam.com/dang/archive/2009/01/31/UPSERT-Race-Condition-With-MERGE.aspx + $sql = "MERGE INTO $this->table WITH (HOLDLOCK) USING (SELECT 1 AS dummy) AS src ON ($this->idCol = ?) ". + "WHEN NOT MATCHED THEN INSERT ($this->idCol, $this->dataCol, $this->lifetimeCol, $this->timeCol) VALUES (?, ?, ?, ?) ". + "WHEN MATCHED THEN UPDATE SET $this->dataCol = ?, $this->lifetimeCol = ?, $this->timeCol = ?;"; + break; + case 'sqlite' === $driver: + $sql = 'INSERT OR REPLACE'.substr($insertSql, 6); + break; + case 'pgsql' === $driver && version_compare($this->getServerVersion(), '9.5', '>='): + $sql = $insertSql." ON CONFLICT ($this->idCol) DO UPDATE SET ($this->dataCol, $this->lifetimeCol, $this->timeCol) = (EXCLUDED.$this->dataCol, EXCLUDED.$this->lifetimeCol, EXCLUDED.$this->timeCol)"; + break; + default: + $driver = null; + $sql = "UPDATE $this->table SET $this->dataCol = :data, $this->lifetimeCol = :lifetime, $this->timeCol = :time WHERE $this->idCol = :id"; + break; + } + + $now = time(); + $lifetime = $lifetime ?: null; + try { + $stmt = $conn->prepare($sql); + } catch (\PDOException $e) { + if ($this->isTableMissing($e) && (!$conn->inTransaction() || \in_array($driver, ['pgsql', 'sqlite', 'sqlsrv'], true))) { + $this->createTable(); + } + $stmt = $conn->prepare($sql); + } + + // $id and $data are defined later in the loop. Binding is done by reference, values are read on execution. + if ('sqlsrv' === $driver || 'oci' === $driver) { + $stmt->bindParam(1, $id); + $stmt->bindParam(2, $id); + $stmt->bindParam(3, $data, \PDO::PARAM_LOB); + $stmt->bindValue(4, $lifetime, \PDO::PARAM_INT); + $stmt->bindValue(5, $now, \PDO::PARAM_INT); + $stmt->bindParam(6, $data, \PDO::PARAM_LOB); + $stmt->bindValue(7, $lifetime, \PDO::PARAM_INT); + $stmt->bindValue(8, $now, \PDO::PARAM_INT); + } else { + $stmt->bindParam(':id', $id); + $stmt->bindParam(':data', $data, \PDO::PARAM_LOB); + $stmt->bindValue(':lifetime', $lifetime, \PDO::PARAM_INT); + $stmt->bindValue(':time', $now, \PDO::PARAM_INT); + } + if (null === $driver) { + $insertStmt = $conn->prepare($insertSql); + + $insertStmt->bindParam(':id', $id); + $insertStmt->bindParam(':data', $data, \PDO::PARAM_LOB); + $insertStmt->bindValue(':lifetime', $lifetime, \PDO::PARAM_INT); + $insertStmt->bindValue(':time', $now, \PDO::PARAM_INT); + } + + foreach ($values as $id => $data) { + try { + $stmt->execute(); + } catch (\PDOException $e) { + if ($this->isTableMissing($e) && (!$conn->inTransaction() || \in_array($driver, ['pgsql', 'sqlite', 'sqlsrv'], true))) { + $this->createTable(); + } + $stmt->execute(); + } + if (null === $driver && !$stmt->rowCount()) { + try { + $insertStmt->execute(); + } catch (\PDOException) { + // A concurrent write won, let it be + } + } + } + + return $failed; + } + + /** + * @internal + */ + protected function getId(mixed $key): string + { + if ('pgsql' !== $this->getDriver()) { + return parent::getId($key); + } + + if (str_contains($key, "\0") || str_contains($key, '%') || !preg_match('//u', $key)) { + $key = rawurlencode($key); + } + + return parent::getId($key); + } + + private function getConnection(): \PDO + { + if (!isset($this->conn)) { + $this->conn = new \PDO($this->dsn, $this->username, $this->password, $this->connectionOptions); + $this->conn->setAttribute(\PDO::ATTR_ERRMODE, \PDO::ERRMODE_EXCEPTION); + } + + return $this->conn; + } + + private function getDriver(): string + { + return $this->driver ??= $this->getConnection()->getAttribute(\PDO::ATTR_DRIVER_NAME); + } + + private function getServerVersion(): string + { + return $this->serverVersion ??= $this->getConnection()->getAttribute(\PDO::ATTR_SERVER_VERSION); + } + + private function isTableMissing(\PDOException $exception): bool + { + $driver = $this->getDriver(); + [$sqlState, $code] = $exception->errorInfo ?? [null, $exception->getCode()]; + + return match ($driver) { + 'pgsql' => '42P01' === $sqlState, + 'sqlite' => str_contains($exception->getMessage(), 'no such table:'), + 'oci' => 942 === $code, + 'sqlsrv' => 208 === $code, + 'mysql' => 1146 === $code, + default => false, + }; + } +} diff --git a/vendor/symfony/cache/Adapter/PhpArrayAdapter.php b/vendor/symfony/cache/Adapter/PhpArrayAdapter.php new file mode 100644 index 0000000..8047a8a --- /dev/null +++ b/vendor/symfony/cache/Adapter/PhpArrayAdapter.php @@ -0,0 +1,389 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Cache\Adapter; + +use Psr\Cache\CacheItemInterface; +use Psr\Cache\CacheItemPoolInterface; +use Symfony\Component\Cache\CacheItem; +use Symfony\Component\Cache\Exception\InvalidArgumentException; +use Symfony\Component\Cache\PruneableInterface; +use Symfony\Component\Cache\ResettableInterface; +use Symfony\Component\Cache\Traits\ContractsTrait; +use Symfony\Component\Cache\Traits\ProxyTrait; +use Symfony\Component\VarExporter\VarExporter; +use Symfony\Contracts\Cache\CacheInterface; + +/** + * Caches items at warm up time using a PHP array that is stored in shared memory by OPCache since PHP 7.0. + * Warmed up items are read-only and run-time discovered items are cached using a fallback adapter. + * + * @author Titouan Galopin + * @author Nicolas Grekas + */ +class PhpArrayAdapter implements AdapterInterface, CacheInterface, PruneableInterface, ResettableInterface +{ + use ContractsTrait; + use ProxyTrait; + + private array $keys; + private array $values; + + private static \Closure $createCacheItem; + private static array $valuesCache = []; + + /** + * @param string $file The PHP file where values are cached + * @param AdapterInterface $fallbackPool A pool to fallback on when an item is not hit + */ + public function __construct( + private string $file, + AdapterInterface $fallbackPool, + ) { + $this->pool = $fallbackPool; + self::$createCacheItem ??= \Closure::bind( + static function ($key, $value, $isHit) { + $item = new CacheItem(); + $item->key = $key; + $item->value = $value; + $item->isHit = $isHit; + + return $item; + }, + null, + CacheItem::class + ); + } + + /** + * This adapter takes advantage of how PHP stores arrays in its latest versions. + * + * @param string $file The PHP file were values are cached + * @param CacheItemPoolInterface $fallbackPool A pool to fallback on when an item is not hit + */ + public static function create(string $file, CacheItemPoolInterface $fallbackPool): CacheItemPoolInterface + { + if (!$fallbackPool instanceof AdapterInterface) { + $fallbackPool = new ProxyAdapter($fallbackPool); + } + + return new static($file, $fallbackPool); + } + + public function get(string $key, callable $callback, ?float $beta = null, ?array &$metadata = null): mixed + { + if (!isset($this->values)) { + $this->initialize(); + } + if (!isset($this->keys[$key])) { + get_from_pool: + if ($this->pool instanceof CacheInterface) { + return $this->pool->get($key, $callback, $beta, $metadata); + } + + return $this->doGet($this->pool, $key, $callback, $beta, $metadata); + } + $value = $this->values[$this->keys[$key]]; + + if ('N;' === $value) { + return null; + } + try { + if ($value instanceof \Closure) { + return $value(); + } + } catch (\Throwable) { + unset($this->keys[$key]); + goto get_from_pool; + } + + return $value; + } + + public function getItem(mixed $key): CacheItem + { + if (!\is_string($key)) { + throw new InvalidArgumentException(\sprintf('Cache key must be string, "%s" given.', get_debug_type($key))); + } + if (!isset($this->values)) { + $this->initialize(); + } + if (!isset($this->keys[$key])) { + return $this->pool->getItem($key); + } + + $value = $this->values[$this->keys[$key]]; + $isHit = true; + + if ('N;' === $value) { + $value = null; + } elseif ($value instanceof \Closure) { + try { + $value = $value(); + } catch (\Throwable) { + $value = null; + $isHit = false; + } + } + + return (self::$createCacheItem)($key, $value, $isHit); + } + + public function getItems(array $keys = []): iterable + { + foreach ($keys as $key) { + if (!\is_string($key)) { + throw new InvalidArgumentException(\sprintf('Cache key must be string, "%s" given.', get_debug_type($key))); + } + } + if (!isset($this->values)) { + $this->initialize(); + } + + return $this->generateItems($keys); + } + + public function hasItem(mixed $key): bool + { + if (!\is_string($key)) { + throw new InvalidArgumentException(\sprintf('Cache key must be string, "%s" given.', get_debug_type($key))); + } + if (!isset($this->values)) { + $this->initialize(); + } + + return isset($this->keys[$key]) || $this->pool->hasItem($key); + } + + public function deleteItem(mixed $key): bool + { + if (!\is_string($key)) { + throw new InvalidArgumentException(\sprintf('Cache key must be string, "%s" given.', get_debug_type($key))); + } + if (!isset($this->values)) { + $this->initialize(); + } + + return !isset($this->keys[$key]) && $this->pool->deleteItem($key); + } + + public function deleteItems(array $keys): bool + { + $deleted = true; + $fallbackKeys = []; + + foreach ($keys as $key) { + if (!\is_string($key)) { + throw new InvalidArgumentException(\sprintf('Cache key must be string, "%s" given.', get_debug_type($key))); + } + + if (isset($this->keys[$key])) { + $deleted = false; + } else { + $fallbackKeys[] = $key; + } + } + if (!isset($this->values)) { + $this->initialize(); + } + + if ($fallbackKeys) { + $deleted = $this->pool->deleteItems($fallbackKeys) && $deleted; + } + + return $deleted; + } + + public function save(CacheItemInterface $item): bool + { + if (!isset($this->values)) { + $this->initialize(); + } + + return !isset($this->keys[$item->getKey()]) && $this->pool->save($item); + } + + public function saveDeferred(CacheItemInterface $item): bool + { + if (!isset($this->values)) { + $this->initialize(); + } + + return !isset($this->keys[$item->getKey()]) && $this->pool->saveDeferred($item); + } + + public function commit(): bool + { + return $this->pool->commit(); + } + + public function clear(string $prefix = ''): bool + { + $this->keys = $this->values = []; + + $cleared = @unlink($this->file) || !file_exists($this->file); + unset(self::$valuesCache[$this->file]); + + if ($this->pool instanceof AdapterInterface) { + return $this->pool->clear($prefix) && $cleared; + } + + return $this->pool->clear() && $cleared; + } + + /** + * Store an array of cached values. + * + * @param array $values The cached values + * + * @return string[] A list of classes to preload on PHP 7.4+ + */ + public function warmUp(array $values): array + { + if (file_exists($this->file)) { + if (!is_file($this->file)) { + throw new InvalidArgumentException(\sprintf('Cache path exists and is not a file: "%s".', $this->file)); + } + + if (!is_writable($this->file)) { + throw new InvalidArgumentException(\sprintf('Cache file is not writable: "%s".', $this->file)); + } + } else { + $directory = \dirname($this->file); + + if (!is_dir($directory) && !@mkdir($directory, 0777, true)) { + throw new InvalidArgumentException(\sprintf('Cache directory does not exist and cannot be created: "%s".', $directory)); + } + + if (!is_writable($directory)) { + throw new InvalidArgumentException(\sprintf('Cache directory is not writable: "%s".', $directory)); + } + } + + $preload = []; + $dumpedValues = ''; + $dumpedMap = []; + $dump = <<<'EOF' + $value) { + CacheItem::validateKey(\is_int($key) ? (string) $key : $key); + $isStaticValue = true; + + if (null === $value) { + $value = "'N;'"; + } elseif (\is_object($value) || \is_array($value)) { + try { + $value = VarExporter::export($value, $isStaticValue, $preload); + } catch (\Exception $e) { + throw new InvalidArgumentException(\sprintf('Cache key "%s" has non-serializable "%s" value.', $key, get_debug_type($value)), 0, $e); + } + } elseif (\is_string($value)) { + // Wrap "N;" in a closure to not confuse it with an encoded `null` + if ('N;' === $value) { + $isStaticValue = false; + } + $value = var_export($value, true); + } elseif (!\is_scalar($value)) { + throw new InvalidArgumentException(\sprintf('Cache key "%s" has non-serializable "%s" value.', $key, get_debug_type($value))); + } else { + $value = var_export($value, true); + } + + if (!$isStaticValue) { + $value = str_replace("\n", "\n ", $value); + $value = "static function () {\n return {$value};\n}"; + } + $hash = hash('xxh128', $value); + + if (null === $id = $dumpedMap[$hash] ?? null) { + $id = $dumpedMap[$hash] = \count($dumpedMap); + $dumpedValues .= "{$id} => {$value},\n"; + } + + $dump .= var_export($key, true)." => {$id},\n"; + } + + $dump .= "\n], [\n\n{$dumpedValues}\n]];\n"; + + $tmpFile = tempnam(\dirname($this->file), basename($this->file)); + + file_put_contents($tmpFile, $dump); + @chmod($tmpFile, 0666 & ~umask()); + unset($serialized, $value, $dump); + + @rename($tmpFile, $this->file); + unset(self::$valuesCache[$this->file]); + + $this->initialize(); + + return $preload; + } + + /** + * Load the cache file. + */ + private function initialize(): void + { + if (isset(self::$valuesCache[$this->file])) { + $values = self::$valuesCache[$this->file]; + } elseif (!is_file($this->file)) { + $this->keys = $this->values = []; + + return; + } else { + $values = self::$valuesCache[$this->file] = (include $this->file) ?: [[], []]; + } + + if (2 !== \count($values) || !isset($values[0], $values[1])) { + $this->keys = $this->values = []; + } else { + [$this->keys, $this->values] = $values; + } + } + + private function generateItems(array $keys): \Generator + { + $f = self::$createCacheItem; + $fallbackKeys = []; + + foreach ($keys as $key) { + if (isset($this->keys[$key])) { + $value = $this->values[$this->keys[$key]]; + + if ('N;' === $value) { + yield $key => $f($key, null, true); + } elseif ($value instanceof \Closure) { + try { + yield $key => $f($key, $value(), true); + } catch (\Throwable) { + yield $key => $f($key, null, false); + } + } else { + yield $key => $f($key, $value, true); + } + } else { + $fallbackKeys[] = $key; + } + } + + if ($fallbackKeys) { + yield from $this->pool->getItems($fallbackKeys); + } + } +} diff --git a/vendor/symfony/cache/Adapter/PhpFilesAdapter.php b/vendor/symfony/cache/Adapter/PhpFilesAdapter.php new file mode 100644 index 0000000..df0d0e7 --- /dev/null +++ b/vendor/symfony/cache/Adapter/PhpFilesAdapter.php @@ -0,0 +1,312 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Cache\Adapter; + +use Symfony\Component\Cache\Exception\CacheException; +use Symfony\Component\Cache\Exception\InvalidArgumentException; +use Symfony\Component\Cache\PruneableInterface; +use Symfony\Component\Cache\Traits\FilesystemCommonTrait; +use Symfony\Component\VarExporter\VarExporter; + +/** + * @author Piotr Stankowski + * @author Nicolas Grekas + * @author Rob Frawley 2nd + */ +class PhpFilesAdapter extends AbstractAdapter implements PruneableInterface +{ + use FilesystemCommonTrait { + doClear as private doCommonClear; + doDelete as private doCommonDelete; + } + + private \Closure $includeHandler; + private array $values = []; + private array $files = []; + + private static int $startTime; + private static array $valuesCache = []; + + /** + * @param bool $appendOnly Set to `true` to gain extra performance when the items stored in this pool never expire. + * Doing so is encouraged because it fits perfectly OPcache's memory model. + * + * @throws CacheException if OPcache is not enabled + */ + public function __construct( + string $namespace = '', + int $defaultLifetime = 0, + ?string $directory = null, + private bool $appendOnly = false, + ) { + self::$startTime ??= $_SERVER['REQUEST_TIME'] ?? time(); + parent::__construct('', $defaultLifetime); + $this->init($namespace, $directory); + $this->includeHandler = static function ($type, $msg, $file, $line) { + throw new \ErrorException($msg, 0, $type, $file, $line); + }; + } + + public static function isSupported(): bool + { + self::$startTime ??= $_SERVER['REQUEST_TIME'] ?? time(); + + return \function_exists('opcache_invalidate') && filter_var(\ini_get('opcache.enable'), \FILTER_VALIDATE_BOOL) && (!\in_array(\PHP_SAPI, ['cli', 'phpdbg', 'embed'], true) || filter_var(\ini_get('opcache.enable_cli'), \FILTER_VALIDATE_BOOL)); + } + + public function prune(): bool + { + $time = time(); + $pruned = true; + $getExpiry = true; + + set_error_handler($this->includeHandler); + try { + foreach ($this->scanHashDir($this->directory) as $file) { + try { + if (\is_array($expiresAt = include $file)) { + $expiresAt = $expiresAt[0]; + } + } catch (\ErrorException $e) { + $expiresAt = $time; + } + + if ($time >= $expiresAt) { + $pruned = ($this->doUnlink($file) || !file_exists($file)) && $pruned; + } + } + } finally { + restore_error_handler(); + } + + return $pruned; + } + + protected function doFetch(array $ids): iterable + { + if ($this->appendOnly) { + $now = 0; + $missingIds = []; + } else { + $now = time(); + $missingIds = $ids; + $ids = []; + } + $values = []; + + begin: + $getExpiry = false; + + foreach ($ids as $id) { + if (null === $value = $this->values[$id] ?? null) { + $missingIds[] = $id; + } elseif ('N;' === $value) { + $values[$id] = null; + } elseif (!\is_object($value)) { + $values[$id] = $value; + } elseif (!$value instanceof LazyValue) { + $values[$id] = $value(); + } elseif (false === $values[$id] = include $value->file) { + unset($values[$id], $this->values[$id]); + $missingIds[] = $id; + } + if (!$this->appendOnly) { + unset($this->values[$id]); + } + } + + if (!$missingIds) { + return $values; + } + + set_error_handler($this->includeHandler); + try { + $getExpiry = true; + + foreach ($missingIds as $k => $id) { + try { + $file = $this->files[$id] ??= $this->getFile($id); + + if (isset(self::$valuesCache[$file])) { + [$expiresAt, $this->values[$id]] = self::$valuesCache[$file]; + } elseif (\is_array($expiresAt = include $file)) { + if ($this->appendOnly) { + self::$valuesCache[$file] = $expiresAt; + } + + [$expiresAt, $this->values[$id]] = $expiresAt; + } elseif ($now < $expiresAt) { + $this->values[$id] = new LazyValue($file); + } + + if ($now >= $expiresAt) { + unset($this->values[$id], $missingIds[$k], self::$valuesCache[$file]); + } + } catch (\ErrorException $e) { + unset($missingIds[$k]); + } + } + } finally { + restore_error_handler(); + } + + $ids = $missingIds; + $missingIds = []; + goto begin; + } + + protected function doHave(string $id): bool + { + if ($this->appendOnly && isset($this->values[$id])) { + return true; + } + + set_error_handler($this->includeHandler); + try { + $file = $this->files[$id] ??= $this->getFile($id); + $getExpiry = true; + + if (isset(self::$valuesCache[$file])) { + [$expiresAt, $value] = self::$valuesCache[$file]; + } elseif (\is_array($expiresAt = include $file)) { + if ($this->appendOnly) { + self::$valuesCache[$file] = $expiresAt; + } + + [$expiresAt, $value] = $expiresAt; + } elseif ($this->appendOnly) { + $value = new LazyValue($file); + } + } catch (\ErrorException) { + return false; + } finally { + restore_error_handler(); + } + if ($this->appendOnly) { + $now = 0; + $this->values[$id] = $value; + } else { + $now = time(); + } + + return $now < $expiresAt; + } + + protected function doSave(array $values, int $lifetime): array|bool + { + $ok = true; + $expiry = $lifetime ? time() + $lifetime : 'PHP_INT_MAX'; + $allowCompile = self::isSupported(); + + foreach ($values as $key => $value) { + unset($this->values[$key]); + $isStaticValue = true; + if (null === $value) { + $value = "'N;'"; + } elseif (\is_object($value) || \is_array($value)) { + try { + $value = VarExporter::export($value, $isStaticValue); + } catch (\Exception $e) { + throw new InvalidArgumentException(\sprintf('Cache key "%s" has non-serializable "%s" value.', $key, get_debug_type($value)), 0, $e); + } + } elseif (\is_string($value)) { + // Wrap "N;" in a closure to not confuse it with an encoded `null` + if ('N;' === $value) { + $isStaticValue = false; + } + $value = var_export($value, true); + } elseif (!\is_scalar($value)) { + throw new InvalidArgumentException(\sprintf('Cache key "%s" has non-serializable "%s" value.', $key, get_debug_type($value))); + } else { + $value = var_export($value, true); + } + + $encodedKey = rawurlencode($key); + + if ($isStaticValue) { + $value = "return [{$expiry}, {$value}];"; + } elseif ($this->appendOnly) { + $value = "return [{$expiry}, static fn () => {$value}];"; + } else { + // We cannot use a closure here because of https://bugs.php.net/76982 + $value = str_replace('\Symfony\Component\VarExporter\Internal\\', '', $value); + $value = "namespace Symfony\Component\VarExporter\Internal;\n\nreturn \$getExpiry ? {$expiry} : {$value};"; + } + + $file = $this->files[$key] = $this->getFile($key, true); + // Since OPcache only compiles files older than the script execution start, set the file's mtime in the past + $ok = $this->write($file, "directory)) { + throw new CacheException(\sprintf('Cache directory is not writable (%s).', $this->directory)); + } + + return $ok; + } + + protected function doClear(string $namespace): bool + { + $this->values = []; + + return $this->doCommonClear($namespace); + } + + protected function doDelete(array $ids): bool + { + foreach ($ids as $id) { + unset($this->values[$id]); + } + + return $this->doCommonDelete($ids); + } + + protected function doUnlink(string $file): bool + { + unset(self::$valuesCache[$file]); + + if (self::isSupported()) { + @opcache_invalidate($file, true); + } + + return @unlink($file); + } + + private function getFileKey(string $file): string + { + if (!$h = @fopen($file, 'r')) { + return ''; + } + + $encodedKey = substr(fgets($h), 8); + fclose($h); + + return rawurldecode(rtrim($encodedKey)); + } +} + +/** + * @internal + */ +class LazyValue +{ + public function __construct( + public string $file, + ) { + } +} diff --git a/vendor/symfony/cache/Adapter/ProxyAdapter.php b/vendor/symfony/cache/Adapter/ProxyAdapter.php new file mode 100644 index 0000000..5621226 --- /dev/null +++ b/vendor/symfony/cache/Adapter/ProxyAdapter.php @@ -0,0 +1,206 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Cache\Adapter; + +use Psr\Cache\CacheItemInterface; +use Psr\Cache\CacheItemPoolInterface; +use Symfony\Component\Cache\CacheItem; +use Symfony\Component\Cache\PruneableInterface; +use Symfony\Component\Cache\ResettableInterface; +use Symfony\Component\Cache\Traits\ContractsTrait; +use Symfony\Component\Cache\Traits\ProxyTrait; +use Symfony\Contracts\Cache\CacheInterface; + +/** + * @author Nicolas Grekas + */ +class ProxyAdapter implements AdapterInterface, CacheInterface, PruneableInterface, ResettableInterface +{ + use ContractsTrait; + use ProxyTrait; + + private string $namespace = ''; + private int $namespaceLen; + private string $poolHash; + private int $defaultLifetime; + + private static \Closure $createCacheItem; + private static \Closure $setInnerItem; + + public function __construct(CacheItemPoolInterface $pool, string $namespace = '', int $defaultLifetime = 0) + { + $this->pool = $pool; + $this->poolHash = spl_object_hash($pool); + if ('' !== $namespace) { + \assert('' !== CacheItem::validateKey($namespace)); + $this->namespace = $namespace; + } + $this->namespaceLen = \strlen($namespace); + $this->defaultLifetime = $defaultLifetime; + self::$createCacheItem ??= \Closure::bind( + static function ($key, $innerItem, $poolHash) { + $item = new CacheItem(); + $item->key = $key; + + if (null === $innerItem) { + return $item; + } + + $item->value = $innerItem->get(); + $item->isHit = $innerItem->isHit(); + $item->innerItem = $innerItem; + $item->poolHash = $poolHash; + + if (!$item->unpack() && $innerItem instanceof CacheItem) { + $item->metadata = $innerItem->metadata; + } + $innerItem->set(null); + + return $item; + }, + null, + CacheItem::class + ); + self::$setInnerItem ??= \Closure::bind( + static function (CacheItemInterface $innerItem, CacheItem $item, $expiry = null) { + $innerItem->set($item->pack()); + $innerItem->expiresAt(($expiry ?? $item->expiry) ? \DateTimeImmutable::createFromFormat('U.u', \sprintf('%.6F', $expiry ?? $item->expiry)) : null); + }, + null, + CacheItem::class + ); + } + + public function get(string $key, callable $callback, ?float $beta = null, ?array &$metadata = null): mixed + { + if (!$this->pool instanceof CacheInterface) { + return $this->doGet($this, $key, $callback, $beta, $metadata); + } + + return $this->pool->get($this->getId($key), function ($innerItem, bool &$save) use ($key, $callback) { + $item = (self::$createCacheItem)($key, $innerItem, $this->poolHash); + $item->set($value = $callback($item, $save)); + (self::$setInnerItem)($innerItem, $item); + + return $value; + }, $beta, $metadata); + } + + public function getItem(mixed $key): CacheItem + { + $item = $this->pool->getItem($this->getId($key)); + + return (self::$createCacheItem)($key, $item, $this->poolHash); + } + + public function getItems(array $keys = []): iterable + { + if ($this->namespaceLen) { + foreach ($keys as $i => $key) { + $keys[$i] = $this->getId($key); + } + } + + return $this->generateItems($this->pool->getItems($keys)); + } + + public function hasItem(mixed $key): bool + { + return $this->pool->hasItem($this->getId($key)); + } + + public function clear(string $prefix = ''): bool + { + if ($this->pool instanceof AdapterInterface) { + return $this->pool->clear($this->namespace.$prefix); + } + + return $this->pool->clear(); + } + + public function deleteItem(mixed $key): bool + { + return $this->pool->deleteItem($this->getId($key)); + } + + public function deleteItems(array $keys): bool + { + if ($this->namespaceLen) { + foreach ($keys as $i => $key) { + $keys[$i] = $this->getId($key); + } + } + + return $this->pool->deleteItems($keys); + } + + public function save(CacheItemInterface $item): bool + { + return $this->doSave($item, __FUNCTION__); + } + + public function saveDeferred(CacheItemInterface $item): bool + { + return $this->doSave($item, __FUNCTION__); + } + + public function commit(): bool + { + return $this->pool->commit(); + } + + private function doSave(CacheItemInterface $item, string $method): bool + { + if (!$item instanceof CacheItem) { + return false; + } + $castItem = (array) $item; + + if (null === $castItem["\0*\0expiry"] && 0 < $this->defaultLifetime) { + $castItem["\0*\0expiry"] = microtime(true) + $this->defaultLifetime; + } + + if ($castItem["\0*\0poolHash"] === $this->poolHash && $castItem["\0*\0innerItem"]) { + $innerItem = $castItem["\0*\0innerItem"]; + } elseif ($this->pool instanceof AdapterInterface) { + // this is an optimization specific for AdapterInterface implementations + // so we can save a round-trip to the backend by just creating a new item + $innerItem = (self::$createCacheItem)($this->namespace.$castItem["\0*\0key"], null, $this->poolHash); + } else { + $innerItem = $this->pool->getItem($this->namespace.$castItem["\0*\0key"]); + } + + (self::$setInnerItem)($innerItem, $item, $castItem["\0*\0expiry"]); + + return $this->pool->$method($innerItem); + } + + private function generateItems(iterable $items): \Generator + { + $f = self::$createCacheItem; + + foreach ($items as $key => $item) { + if ($this->namespaceLen) { + $key = substr($key, $this->namespaceLen); + } + + yield $key => $f($key, $item, $this->poolHash); + } + } + + private function getId(mixed $key): string + { + \assert('' !== CacheItem::validateKey($key)); + + return $this->namespace.$key; + } +} diff --git a/vendor/symfony/cache/Adapter/Psr16Adapter.php b/vendor/symfony/cache/Adapter/Psr16Adapter.php new file mode 100644 index 0000000..a72b037 --- /dev/null +++ b/vendor/symfony/cache/Adapter/Psr16Adapter.php @@ -0,0 +1,71 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Cache\Adapter; + +use Psr\SimpleCache\CacheInterface; +use Symfony\Component\Cache\PruneableInterface; +use Symfony\Component\Cache\ResettableInterface; +use Symfony\Component\Cache\Traits\ProxyTrait; + +/** + * Turns a PSR-16 cache into a PSR-6 one. + * + * @author Nicolas Grekas + */ +class Psr16Adapter extends AbstractAdapter implements PruneableInterface, ResettableInterface +{ + use ProxyTrait; + + /** + * @internal + */ + protected const NS_SEPARATOR = '_'; + + private object $miss; + + public function __construct(CacheInterface $pool, string $namespace = '', int $defaultLifetime = 0) + { + parent::__construct($namespace, $defaultLifetime); + + $this->pool = $pool; + $this->miss = new \stdClass(); + } + + protected function doFetch(array $ids): iterable + { + foreach ($this->pool->getMultiple($ids, $this->miss) as $key => $value) { + if ($this->miss !== $value) { + yield $key => $value; + } + } + } + + protected function doHave(string $id): bool + { + return $this->pool->has($id); + } + + protected function doClear(string $namespace): bool + { + return $this->pool->clear(); + } + + protected function doDelete(array $ids): bool + { + return $this->pool->deleteMultiple($ids); + } + + protected function doSave(array $values, int $lifetime): array|bool + { + return $this->pool->setMultiple($values, 0 === $lifetime ? null : $lifetime); + } +} diff --git a/vendor/symfony/cache/Adapter/RedisAdapter.php b/vendor/symfony/cache/Adapter/RedisAdapter.php new file mode 100644 index 0000000..e33f2f6 --- /dev/null +++ b/vendor/symfony/cache/Adapter/RedisAdapter.php @@ -0,0 +1,25 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Cache\Adapter; + +use Symfony\Component\Cache\Marshaller\MarshallerInterface; +use Symfony\Component\Cache\Traits\RedisTrait; + +class RedisAdapter extends AbstractAdapter +{ + use RedisTrait; + + public function __construct(\Redis|\RedisArray|\RedisCluster|\Predis\ClientInterface|\Relay\Relay $redis, string $namespace = '', int $defaultLifetime = 0, ?MarshallerInterface $marshaller = null) + { + $this->init($redis, $namespace, $defaultLifetime, $marshaller); + } +} diff --git a/vendor/symfony/cache/Adapter/RedisTagAwareAdapter.php b/vendor/symfony/cache/Adapter/RedisTagAwareAdapter.php new file mode 100644 index 0000000..7b28237 --- /dev/null +++ b/vendor/symfony/cache/Adapter/RedisTagAwareAdapter.php @@ -0,0 +1,310 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Cache\Adapter; + +use Predis\Connection\Aggregate\ClusterInterface; +use Predis\Connection\Aggregate\PredisCluster; +use Predis\Connection\Aggregate\ReplicationInterface; +use Predis\Response\ErrorInterface; +use Predis\Response\Status; +use Relay\Relay; +use Symfony\Component\Cache\CacheItem; +use Symfony\Component\Cache\Exception\InvalidArgumentException; +use Symfony\Component\Cache\Exception\LogicException; +use Symfony\Component\Cache\Marshaller\DeflateMarshaller; +use Symfony\Component\Cache\Marshaller\MarshallerInterface; +use Symfony\Component\Cache\Marshaller\TagAwareMarshaller; +use Symfony\Component\Cache\Traits\RedisTrait; + +/** + * Stores tag id <> cache id relationship as a Redis Set. + * + * Set (tag relation info) is stored without expiry (non-volatile), while cache always gets an expiry (volatile) even + * if not set by caller. Thus if you configure redis with the right eviction policy you can be safe this tag <> cache + * relationship survives eviction (cache cleanup when Redis runs out of memory). + * + * Redis server 2.8+ with any `volatile-*` eviction policy, OR `noeviction` if you're sure memory will NEVER fill up + * + * Design limitations: + * - Max 4 billion cache keys per cache tag as limited by Redis Set datatype. + * E.g. If you use a "all" items tag for expiry instead of clear(), that limits you to 4 billion cache items also. + * + * @see https://redis.io/topics/lru-cache#eviction-policies Documentation for Redis eviction policies. + * @see https://redis.io/topics/data-types#sets Documentation for Redis Set datatype. + * + * @author Nicolas Grekas + * @author AndrĆ© RĆømcke + */ +class RedisTagAwareAdapter extends AbstractTagAwareAdapter +{ + use RedisTrait; + + /** + * On cache items without a lifetime set, we set it to 100 days. This is to make sure cache items are + * preferred to be evicted over tag Sets, if eviction policy is configured according to requirements. + */ + private const DEFAULT_CACHE_TTL = 8640000; + + /** + * detected eviction policy used on Redis server. + */ + private string $redisEvictionPolicy; + + public function __construct( + \Redis|Relay|\RedisArray|\RedisCluster|\Predis\ClientInterface $redis, + private string $namespace = '', + int $defaultLifetime = 0, + ?MarshallerInterface $marshaller = null, + ) { + if ($redis instanceof \Predis\ClientInterface && $redis->getConnection() instanceof ClusterInterface && !$redis->getConnection() instanceof PredisCluster) { + throw new InvalidArgumentException(\sprintf('Unsupported Predis cluster connection: only "%s" is, "%s" given.', PredisCluster::class, get_debug_type($redis->getConnection()))); + } + + $isRelay = $redis instanceof Relay; + if ($isRelay || \defined('Redis::OPT_COMPRESSION') && \in_array($redis::class, [\Redis::class, \RedisArray::class, \RedisCluster::class], true)) { + $compression = $redis->getOption($isRelay ? Relay::OPT_COMPRESSION : \Redis::OPT_COMPRESSION); + + foreach (\is_array($compression) ? $compression : [$compression] as $c) { + if ($isRelay ? Relay::COMPRESSION_NONE : \Redis::COMPRESSION_NONE !== $c) { + throw new InvalidArgumentException(\sprintf('redis compression must be disabled when using "%s", use "%s" instead.', static::class, DeflateMarshaller::class)); + } + } + } + + $this->init($redis, $namespace, $defaultLifetime, new TagAwareMarshaller($marshaller)); + } + + protected function doSave(array $values, int $lifetime, array $addTagData = [], array $delTagData = []): array + { + $eviction = $this->getRedisEvictionPolicy(); + if ('noeviction' !== $eviction && !str_starts_with($eviction, 'volatile-')) { + throw new LogicException(\sprintf('Redis maxmemory-policy setting "%s" is *not* supported by RedisTagAwareAdapter, use "noeviction" or "volatile-*" eviction policies.', $eviction)); + } + + // serialize values + if (!$serialized = $this->marshaller->marshall($values, $failed)) { + return $failed; + } + + // While pipeline isn't supported on RedisCluster, other setups will at least benefit from doing this in one op + $results = $this->pipeline(static function () use ($serialized, $lifetime, $addTagData, $delTagData, $failed) { + // Store cache items, force a ttl if none is set, as there is no MSETEX we need to set each one + foreach ($serialized as $id => $value) { + yield 'setEx' => [ + $id, + 0 >= $lifetime ? self::DEFAULT_CACHE_TTL : $lifetime, + $value, + ]; + } + + // Add and Remove Tags + foreach ($addTagData as $tagId => $ids) { + if (!$failed || $ids = array_diff($ids, $failed)) { + yield 'sAdd' => array_merge([$tagId], $ids); + } + } + + foreach ($delTagData as $tagId => $ids) { + if (!$failed || $ids = array_diff($ids, $failed)) { + yield 'sRem' => array_merge([$tagId], $ids); + } + } + }); + + foreach ($results as $id => $result) { + // Skip results of SADD/SREM operations, they'll be 1 or 0 depending on if set value already existed or not + if (is_numeric($result)) { + continue; + } + // setEx results + if (true !== $result && (!$result instanceof Status || Status::get('OK') !== $result)) { + $failed[] = $id; + } + } + + return $failed; + } + + protected function doDeleteYieldTags(array $ids): iterable + { + $lua = <<<'EOLUA' + local v = redis.call('GET', KEYS[1]) + local e = redis.pcall('UNLINK', KEYS[1]) + + if type(e) ~= 'number' then + redis.call('DEL', KEYS[1]) + end + + if not v or v:len() <= 13 or v:byte(1) ~= 0x9D or v:byte(6) ~= 0 or v:byte(10) ~= 0x5F then + return '' + end + + return v:sub(14, 13 + v:byte(13) + v:byte(12) * 256 + v:byte(11) * 65536) +EOLUA; + + $results = $this->pipeline(function () use ($ids, $lua) { + foreach ($ids as $id) { + yield 'eval' => $this->redis instanceof \Predis\ClientInterface ? [$lua, 1, $id] : [$lua, [$id], 1]; + } + }); + + foreach ($results as $id => $result) { + if ($result instanceof \RedisException || $result instanceof \Relay\Exception || $result instanceof ErrorInterface) { + CacheItem::log($this->logger, 'Failed to delete key "{key}": '.$result->getMessage(), ['key' => substr($id, \strlen($this->namespace)), 'exception' => $result]); + + continue; + } + + try { + yield $id => !\is_string($result) || '' === $result ? [] : $this->marshaller->unmarshall($result); + } catch (\Exception) { + yield $id => []; + } + } + } + + protected function doDeleteTagRelations(array $tagData): bool + { + $results = $this->pipeline(static function () use ($tagData) { + foreach ($tagData as $tagId => $idList) { + array_unshift($idList, $tagId); + yield 'sRem' => $idList; + } + }); + foreach ($results as $result) { + // no-op + } + + return true; + } + + protected function doInvalidate(array $tagIds): bool + { + // This script scans the set of items linked to tag: it empties the set + // and removes the linked items. When the set is still not empty after + // the scan, it means we're in cluster mode and that the linked items + // are on other nodes: we move the links to a temporary set and we + // garbage collect that set from the client side. + + $lua = <<<'EOLUA' + redis.replicate_commands() + + local cursor = '0' + local id = KEYS[1] + repeat + local result = redis.call('SSCAN', id, cursor, 'COUNT', 5000); + cursor = result[1]; + local rems = {} + + for _, v in ipairs(result[2]) do + local ok, _ = pcall(redis.call, 'DEL', ARGV[1]..v) + if ok then + table.insert(rems, v) + end + end + if 0 < #rems then + redis.call('SREM', id, unpack(rems)) + end + until '0' == cursor; + + redis.call('SUNIONSTORE', '{'..id..'}'..id, id) + redis.call('DEL', id) + + return redis.call('SSCAN', '{'..id..'}'..id, '0', 'COUNT', 5000) +EOLUA; + + $results = $this->pipeline(function () use ($tagIds, $lua) { + if ($this->redis instanceof \Predis\ClientInterface) { + $prefix = $this->redis->getOptions()->prefix ? $this->redis->getOptions()->prefix->getPrefix() : ''; + } elseif (\is_array($prefix = $this->redis->getOption($this->redis instanceof Relay ? Relay::OPT_PREFIX : \Redis::OPT_PREFIX) ?? '')) { + $prefix = current($prefix); + } + + foreach ($tagIds as $id) { + yield 'eval' => $this->redis instanceof \Predis\ClientInterface ? [$lua, 1, $id, $prefix] : [$lua, [$id, $prefix], 1]; + } + }); + + $lua = <<<'EOLUA' + redis.replicate_commands() + + local id = KEYS[1] + local cursor = table.remove(ARGV) + redis.call('SREM', '{'..id..'}'..id, unpack(ARGV)) + + return redis.call('SSCAN', '{'..id..'}'..id, cursor, 'COUNT', 5000) +EOLUA; + + $success = true; + foreach ($results as $id => $values) { + if ($values instanceof \RedisException || $values instanceof \Relay\Exception || $values instanceof ErrorInterface) { + CacheItem::log($this->logger, 'Failed to invalidate key "{key}": '.$values->getMessage(), ['key' => substr($id, \strlen($this->namespace)), 'exception' => $values]); + $success = false; + + continue; + } + + [$cursor, $ids] = $values; + + while ($ids || '0' !== $cursor) { + $this->doDelete($ids); + + $evalArgs = [$id, $cursor]; + array_splice($evalArgs, 1, 0, $ids); + + if ($this->redis instanceof \Predis\ClientInterface) { + array_unshift($evalArgs, $lua, 1); + } else { + $evalArgs = [$lua, $evalArgs, 1]; + } + + $results = $this->pipeline(function () use ($evalArgs) { + yield 'eval' => $evalArgs; + }); + + foreach ($results as [$cursor, $ids]) { + // no-op + } + } + } + + return $success; + } + + private function getRedisEvictionPolicy(): string + { + if (isset($this->redisEvictionPolicy)) { + return $this->redisEvictionPolicy; + } + + $hosts = $this->getHosts(); + $host = reset($hosts); + if ($host instanceof \Predis\Client && $host->getConnection() instanceof ReplicationInterface) { + // Predis supports info command only on the master in replication environments + $hosts = [$host->getClientFor('master')]; + } + + foreach ($hosts as $host) { + $info = $host->info('Memory'); + + if (false === $info || null === $info || $info instanceof ErrorInterface) { + continue; + } + + $info = $info['Memory'] ?? $info; + + return $this->redisEvictionPolicy = $info['maxmemory_policy'] ?? ''; + } + + return $this->redisEvictionPolicy = ''; + } +} diff --git a/vendor/symfony/cache/Adapter/TagAwareAdapter.php b/vendor/symfony/cache/Adapter/TagAwareAdapter.php new file mode 100644 index 0000000..53c9890 --- /dev/null +++ b/vendor/symfony/cache/Adapter/TagAwareAdapter.php @@ -0,0 +1,368 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Cache\Adapter; + +use Psr\Cache\CacheItemInterface; +use Psr\Cache\InvalidArgumentException; +use Psr\Log\LoggerAwareInterface; +use Psr\Log\LoggerAwareTrait; +use Symfony\Component\Cache\CacheItem; +use Symfony\Component\Cache\PruneableInterface; +use Symfony\Component\Cache\ResettableInterface; +use Symfony\Component\Cache\Traits\ContractsTrait; +use Symfony\Contracts\Cache\TagAwareCacheInterface; + +/** + * Implements simple and robust tag-based invalidation suitable for use with volatile caches. + * + * This adapter works by storing a version for each tags. When saving an item, it is stored together with its tags and + * their corresponding versions. When retrieving an item, those tag versions are compared to the current version of + * each tags. Invalidation is achieved by deleting tags, thereby ensuring that their versions change even when the + * storage is out of space. When versions of non-existing tags are requested for item commits, this adapter assigns a + * new random version to them. + * + * @author Nicolas Grekas + * @author Sergey Belyshkin + */ +class TagAwareAdapter implements TagAwareAdapterInterface, TagAwareCacheInterface, PruneableInterface, ResettableInterface, LoggerAwareInterface +{ + use ContractsTrait; + use LoggerAwareTrait; + + public const TAGS_PREFIX = "\1tags\1"; + + private array $deferred = []; + private AdapterInterface $pool; + private AdapterInterface $tags; + private array $knownTagVersions = []; + + private static \Closure $setCacheItemTags; + private static \Closure $setTagVersions; + private static \Closure $getTagsByKey; + private static \Closure $saveTags; + + public function __construct( + AdapterInterface $itemsPool, + ?AdapterInterface $tagsPool = null, + private float $knownTagVersionsTtl = 0.15, + ) { + $this->pool = $itemsPool; + $this->tags = $tagsPool ?? $itemsPool; + self::$setCacheItemTags ??= \Closure::bind( + static function (array $items, array $itemTags) { + foreach ($items as $key => $item) { + $item->isTaggable = true; + + if (isset($itemTags[$key])) { + $tags = array_keys($itemTags[$key]); + $item->metadata[CacheItem::METADATA_TAGS] = array_combine($tags, $tags); + } else { + $item->value = null; + $item->isHit = false; + $item->metadata = []; + } + } + + return $items; + }, + null, + CacheItem::class + ); + self::$setTagVersions ??= \Closure::bind( + static function (array $items, array $tagVersions) { + foreach ($items as $item) { + $item->newMetadata[CacheItem::METADATA_TAGS] = array_intersect_key($tagVersions, $item->newMetadata[CacheItem::METADATA_TAGS] ?? []); + } + }, + null, + CacheItem::class + ); + self::$getTagsByKey ??= \Closure::bind( + static function ($deferred) { + $tagsByKey = []; + foreach ($deferred as $key => $item) { + $tagsByKey[$key] = $item->newMetadata[CacheItem::METADATA_TAGS] ?? []; + $item->metadata = $item->newMetadata; + } + + return $tagsByKey; + }, + null, + CacheItem::class + ); + self::$saveTags ??= \Closure::bind( + static function (AdapterInterface $tagsAdapter, array $tags) { + ksort($tags); + + foreach ($tags as $v) { + $v->expiry = 0; + $tagsAdapter->saveDeferred($v); + } + + return $tagsAdapter->commit(); + }, + null, + CacheItem::class + ); + } + + public function invalidateTags(array $tags): bool + { + $ids = []; + foreach ($tags as $tag) { + \assert('' !== CacheItem::validateKey($tag)); + unset($this->knownTagVersions[$tag]); + $ids[] = $tag.static::TAGS_PREFIX; + } + + return !$tags || $this->tags->deleteItems($ids); + } + + public function hasItem(mixed $key): bool + { + return $this->getItem($key)->isHit(); + } + + public function getItem(mixed $key): CacheItem + { + foreach ($this->getItems([$key]) as $item) { + return $item; + } + } + + public function getItems(array $keys = []): iterable + { + $tagKeys = []; + $commit = false; + + foreach ($keys as $key) { + if ('' !== $key && \is_string($key)) { + $commit = $commit || isset($this->deferred[$key]); + } + } + + if ($commit) { + $this->commit(); + } + + try { + $items = $this->pool->getItems($keys); + } catch (InvalidArgumentException $e) { + $this->pool->getItems($keys); // Should throw an exception + + throw $e; + } + + $bufferedItems = $itemTags = []; + + foreach ($items as $key => $item) { + if (null !== $tags = $item->getMetadata()[CacheItem::METADATA_TAGS] ?? null) { + $itemTags[$key] = $tags; + } + + $bufferedItems[$key] = $item; + + if (null === $tags) { + $key = "\0tags\0".$key; + $tagKeys[$key] = $key; // BC with pools populated before v6.1 + } + } + + if ($tagKeys) { + foreach ($this->pool->getItems($tagKeys) as $key => $item) { + if ($item->isHit()) { + $itemTags[substr($key, \strlen("\0tags\0"))] = $item->get() ?: []; + } + } + } + + $tagVersions = $this->getTagVersions($itemTags, false); + foreach ($itemTags as $key => $tags) { + foreach ($tags as $tag => $version) { + if ($tagVersions[$tag] !== $version) { + unset($itemTags[$key]); + continue 2; + } + } + } + $tagVersions = null; + + return (self::$setCacheItemTags)($bufferedItems, $itemTags); + } + + public function clear(string $prefix = ''): bool + { + if ('' !== $prefix) { + foreach ($this->deferred as $key => $item) { + if (str_starts_with($key, $prefix)) { + unset($this->deferred[$key]); + } + } + + return $this->pool->clear($prefix); + } else { + $this->deferred = []; + } + + return $this->pool->clear(); + } + + public function deleteItem(mixed $key): bool + { + return $this->deleteItems([$key]); + } + + public function deleteItems(array $keys): bool + { + foreach ($keys as $key) { + if ('' !== $key && \is_string($key)) { + $keys[] = "\0tags\0".$key; // BC with pools populated before v6.1 + } + } + + return $this->pool->deleteItems($keys); + } + + public function save(CacheItemInterface $item): bool + { + if (!$item instanceof CacheItem) { + return false; + } + $this->deferred[$item->getKey()] = $item; + + return $this->commit(); + } + + public function saveDeferred(CacheItemInterface $item): bool + { + if (!$item instanceof CacheItem) { + return false; + } + $this->deferred[$item->getKey()] = $item; + + return true; + } + + public function commit(): bool + { + if (!$items = $this->deferred) { + return true; + } + + $tagVersions = $this->getTagVersions((self::$getTagsByKey)($items), true); + (self::$setTagVersions)($items, $tagVersions); + + $ok = true; + foreach ($items as $key => $item) { + if ($this->pool->saveDeferred($item)) { + unset($this->deferred[$key]); + } else { + $ok = false; + } + } + $ok = $this->pool->commit() && $ok; + + $tagVersions = array_keys($tagVersions); + (self::$setTagVersions)($items, array_combine($tagVersions, $tagVersions)); + + return $ok; + } + + public function prune(): bool + { + return $this->pool instanceof PruneableInterface && $this->pool->prune(); + } + + public function reset(): void + { + $this->commit(); + $this->knownTagVersions = []; + $this->pool instanceof ResettableInterface && $this->pool->reset(); + $this->tags instanceof ResettableInterface && $this->tags->reset(); + } + + public function __sleep(): array + { + throw new \BadMethodCallException('Cannot serialize '.__CLASS__); + } + + public function __wakeup(): void + { + throw new \BadMethodCallException('Cannot unserialize '.__CLASS__); + } + + public function __destruct() + { + $this->commit(); + } + + private function getTagVersions(array $tagsByKey, bool $persistTags): array + { + $tagVersions = []; + $fetchTagVersions = $persistTags; + + foreach ($tagsByKey as $tags) { + $tagVersions += $tags; + if ($fetchTagVersions) { + continue; + } + foreach ($tags as $tag => $version) { + if ($tagVersions[$tag] !== $version) { + $fetchTagVersions = true; + } + } + } + + if (!$tagVersions) { + return []; + } + + $now = microtime(true); + $tags = []; + foreach ($tagVersions as $tag => $version) { + $tags[$tag.static::TAGS_PREFIX] = $tag; + $knownTagVersion = $this->knownTagVersions[$tag] ?? [0, null]; + if ($fetchTagVersions || $now > $knownTagVersion[0] || $knownTagVersion[1] !== $version) { + // reuse previously fetched tag versions until the expiration + $fetchTagVersions = true; + } + } + + if (!$fetchTagVersions) { + return $tagVersions; + } + + $newTags = []; + $newVersion = null; + $expiration = $now + $this->knownTagVersionsTtl; + foreach ($this->tags->getItems(array_keys($tags)) as $tag => $version) { + unset($this->knownTagVersions[$tag = $tags[$tag]]); // update FIFO + if (null !== $tagVersions[$tag] = $version->get()) { + $this->knownTagVersions[$tag] = [$expiration, $tagVersions[$tag]]; + } elseif ($persistTags) { + $newTags[$tag] = $version->set($newVersion ??= random_bytes(6)); + $tagVersions[$tag] = $newVersion; + $this->knownTagVersions[$tag] = [$expiration, $newVersion]; + } + } + + if ($newTags) { + (self::$saveTags)($this->tags, $newTags); + } + + while ($now > ($this->knownTagVersions[$tag = array_key_first($this->knownTagVersions)][0] ?? \INF)) { + unset($this->knownTagVersions[$tag]); + } + + return $tagVersions; + } +} diff --git a/vendor/symfony/cache/Adapter/TagAwareAdapterInterface.php b/vendor/symfony/cache/Adapter/TagAwareAdapterInterface.php new file mode 100644 index 0000000..9242779 --- /dev/null +++ b/vendor/symfony/cache/Adapter/TagAwareAdapterInterface.php @@ -0,0 +1,31 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Cache\Adapter; + +use Psr\Cache\InvalidArgumentException; + +/** + * Interface for invalidating cached items using tags. + * + * @author Nicolas Grekas + */ +interface TagAwareAdapterInterface extends AdapterInterface +{ + /** + * Invalidates cached items using tags. + * + * @param string[] $tags An array of tags to invalidate + * + * @throws InvalidArgumentException When $tags is not valid + */ + public function invalidateTags(array $tags): bool; +} diff --git a/vendor/symfony/cache/Adapter/TraceableAdapter.php b/vendor/symfony/cache/Adapter/TraceableAdapter.php new file mode 100644 index 0000000..8fe6cf3 --- /dev/null +++ b/vendor/symfony/cache/Adapter/TraceableAdapter.php @@ -0,0 +1,249 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Cache\Adapter; + +use Psr\Cache\CacheItemInterface; +use Symfony\Component\Cache\CacheItem; +use Symfony\Component\Cache\PruneableInterface; +use Symfony\Component\Cache\ResettableInterface; +use Symfony\Contracts\Cache\CacheInterface; +use Symfony\Contracts\Service\ResetInterface; + +/** + * An adapter that collects data about all cache calls. + * + * @author Aaron Scherer + * @author Tobias Nyholm + * @author Nicolas Grekas + */ +class TraceableAdapter implements AdapterInterface, CacheInterface, PruneableInterface, ResettableInterface +{ + private array $calls = []; + + public function __construct( + protected AdapterInterface $pool, + ) { + } + + public function get(string $key, callable $callback, ?float $beta = null, ?array &$metadata = null): mixed + { + if (!$this->pool instanceof CacheInterface) { + throw new \BadMethodCallException(\sprintf('Cannot call "%s::get()": this class doesn\'t implement "%s".', get_debug_type($this->pool), CacheInterface::class)); + } + + $isHit = true; + $callback = function (CacheItem $item, bool &$save) use ($callback, &$isHit) { + $isHit = $item->isHit(); + + return $callback($item, $save); + }; + + $event = $this->start(__FUNCTION__); + try { + $value = $this->pool->get($key, $callback, $beta, $metadata); + $event->result[$key] = get_debug_type($value); + } finally { + $event->end = microtime(true); + } + if ($isHit) { + ++$event->hits; + } else { + ++$event->misses; + } + + return $value; + } + + public function getItem(mixed $key): CacheItem + { + $event = $this->start(__FUNCTION__); + try { + $item = $this->pool->getItem($key); + } finally { + $event->end = microtime(true); + } + if ($event->result[$key] = $item->isHit()) { + ++$event->hits; + } else { + ++$event->misses; + } + + return $item; + } + + public function hasItem(mixed $key): bool + { + $event = $this->start(__FUNCTION__); + try { + return $event->result[$key] = $this->pool->hasItem($key); + } finally { + $event->end = microtime(true); + } + } + + public function deleteItem(mixed $key): bool + { + $event = $this->start(__FUNCTION__); + try { + return $event->result[$key] = $this->pool->deleteItem($key); + } finally { + $event->end = microtime(true); + } + } + + public function save(CacheItemInterface $item): bool + { + $event = $this->start(__FUNCTION__); + try { + return $event->result[$item->getKey()] = $this->pool->save($item); + } finally { + $event->end = microtime(true); + } + } + + public function saveDeferred(CacheItemInterface $item): bool + { + $event = $this->start(__FUNCTION__); + try { + return $event->result[$item->getKey()] = $this->pool->saveDeferred($item); + } finally { + $event->end = microtime(true); + } + } + + public function getItems(array $keys = []): iterable + { + $event = $this->start(__FUNCTION__); + try { + $result = $this->pool->getItems($keys); + } finally { + $event->end = microtime(true); + } + $f = function () use ($result, $event) { + $event->result = []; + foreach ($result as $key => $item) { + if ($event->result[$key] = $item->isHit()) { + ++$event->hits; + } else { + ++$event->misses; + } + yield $key => $item; + } + }; + + return $f(); + } + + public function clear(string $prefix = ''): bool + { + $event = $this->start(__FUNCTION__); + try { + if ($this->pool instanceof AdapterInterface) { + return $event->result = $this->pool->clear($prefix); + } + + return $event->result = $this->pool->clear(); + } finally { + $event->end = microtime(true); + } + } + + public function deleteItems(array $keys): bool + { + $event = $this->start(__FUNCTION__); + $event->result['keys'] = $keys; + try { + return $event->result['result'] = $this->pool->deleteItems($keys); + } finally { + $event->end = microtime(true); + } + } + + public function commit(): bool + { + $event = $this->start(__FUNCTION__); + try { + return $event->result = $this->pool->commit(); + } finally { + $event->end = microtime(true); + } + } + + public function prune(): bool + { + if (!$this->pool instanceof PruneableInterface) { + return false; + } + $event = $this->start(__FUNCTION__); + try { + return $event->result = $this->pool->prune(); + } finally { + $event->end = microtime(true); + } + } + + public function reset(): void + { + if ($this->pool instanceof ResetInterface) { + $this->pool->reset(); + } + + $this->clearCalls(); + } + + public function delete(string $key): bool + { + $event = $this->start(__FUNCTION__); + try { + return $event->result[$key] = $this->pool->deleteItem($key); + } finally { + $event->end = microtime(true); + } + } + + public function getCalls(): array + { + return $this->calls; + } + + public function clearCalls(): void + { + $this->calls = []; + } + + public function getPool(): AdapterInterface + { + return $this->pool; + } + + protected function start(string $name): TraceableAdapterEvent + { + $this->calls[] = $event = new TraceableAdapterEvent(); + $event->name = $name; + $event->start = microtime(true); + + return $event; + } +} + +/** + * @internal + */ +class TraceableAdapterEvent +{ + public string $name; + public float $start; + public float $end; + public array|bool $result; + public int $hits = 0; + public int $misses = 0; +} diff --git a/vendor/symfony/cache/Adapter/TraceableTagAwareAdapter.php b/vendor/symfony/cache/Adapter/TraceableTagAwareAdapter.php new file mode 100644 index 0000000..c85d199 --- /dev/null +++ b/vendor/symfony/cache/Adapter/TraceableTagAwareAdapter.php @@ -0,0 +1,35 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Cache\Adapter; + +use Symfony\Contracts\Cache\TagAwareCacheInterface; + +/** + * @author Robin Chalas + */ +class TraceableTagAwareAdapter extends TraceableAdapter implements TagAwareAdapterInterface, TagAwareCacheInterface +{ + public function __construct(TagAwareAdapterInterface $pool) + { + parent::__construct($pool); + } + + public function invalidateTags(array $tags): bool + { + $event = $this->start(__FUNCTION__); + try { + return $event->result = $this->pool->invalidateTags($tags); + } finally { + $event->end = microtime(true); + } + } +} diff --git a/vendor/symfony/cache/CHANGELOG.md b/vendor/symfony/cache/CHANGELOG.md new file mode 100644 index 0000000..038915c --- /dev/null +++ b/vendor/symfony/cache/CHANGELOG.md @@ -0,0 +1,154 @@ +CHANGELOG +========= + +7.2 +--- + + * `igbinary_serialize()` is no longer used instead of `serialize()` by default when the igbinary extension is installed, + due to behavior compatibilities between the two + * Add optional `Psr\Clock\ClockInterface` parameter to `ArrayAdapter` + +7.1 +--- + + * Add option `sentinel_master` as an alias for `redis_sentinel` + * Deprecate `CouchbaseBucketAdapter`, use `CouchbaseCollectionAdapter` + * Add support for URL encoded characters in Couchbase DSN + * Add support for using DSN with PDOAdapter + * The algorithm for the default cache namespace changed from SHA256 to XXH128 + +7.0 +--- + + * Add parameter `$isSameDatabase` to `DoctrineDbalAdapter::configureSchema()` + * Drop support for Postgres < 9.5 and SQL Server < 2008 in `DoctrineDbalAdapter` + +6.4 +--- + + * `EarlyExpirationHandler` no longer implements `MessageHandlerInterface`, rely on `AsMessageHandler` instead + +6.3 +--- + + * Add support for Relay PHP extension for Redis + * Updates to allow Redis cluster connections using predis/predis:^2.0 + * Add optional parameter `$isSameDatabase` to `DoctrineDbalAdapter::configureSchema()` + +6.1 +--- + + * Add support for ACL auth in RedisAdapter + * Improve reliability and performance of `TagAwareAdapter` by making tag versions an integral part of item value + +6.0 +--- + + * Remove `DoctrineProvider` and `DoctrineAdapter` + * Remove support of Doctrine DBAL in `PdoAdapter` + +5.4 +--- + + * Deprecate `DoctrineProvider` and `DoctrineAdapter` because these classes have been added to the `doctrine/cache` package + * Add `DoctrineDbalAdapter` identical to `PdoAdapter` for `Doctrine\DBAL\Connection` or DBAL URL + * Deprecate usage of `PdoAdapter` with `Doctrine\DBAL\Connection` or DBAL URL + +5.3 +--- + + * added support for connecting to Redis Sentinel clusters when using the Redis PHP extension + * add support for a custom serializer to the `ApcuAdapter` class + +5.2.0 +----- + + * added integration with Messenger to allow computing cached values in a worker + * allow ISO 8601 time intervals to specify default lifetime + +5.1.0 +----- + + * added max-items + LRU + max-lifetime capabilities to `ArrayCache` + * added `CouchbaseBucketAdapter` + * added context `cache-adapter` to log messages + +5.0.0 +----- + + * removed all PSR-16 implementations in the `Simple` namespace + * removed `SimpleCacheAdapter` + * removed `AbstractAdapter::unserialize()` + * removed `CacheItem::getPreviousTags()` + +4.4.0 +----- + + * added support for connecting to Redis Sentinel clusters + * added argument `$prefix` to `AdapterInterface::clear()` + * improved `RedisTagAwareAdapter` to support Redis server >= 2.8 and up to 4B items per tag + * added `TagAwareMarshaller` for optimized data storage when using `AbstractTagAwareAdapter` + * added `DeflateMarshaller` to compress serialized values + * removed support for phpredis 4 `compression` + * [BC BREAK] `RedisTagAwareAdapter` is not compatible with `RedisCluster` from `Predis` anymore, use `phpredis` instead + * Marked the `CacheDataCollector` class as `@final`. + * added `SodiumMarshaller` to encrypt/decrypt values using libsodium + +4.3.0 +----- + + * removed `psr/simple-cache` dependency, run `composer require psr/simple-cache` if you need it + * deprecated all PSR-16 adapters, use `Psr16Cache` or `Symfony\Contracts\Cache\CacheInterface` implementations instead + * deprecated `SimpleCacheAdapter`, use `Psr16Adapter` instead + +4.2.0 +----- + + * added support for connecting to Redis clusters via DSN + * added support for configuring multiple Memcached servers via DSN + * added `MarshallerInterface` and `DefaultMarshaller` to allow changing the serializer and provide one that automatically uses igbinary when available + * implemented `CacheInterface`, which provides stampede protection via probabilistic early expiration and should become the preferred way to use a cache + * added sub-second expiry accuracy for backends that support it + * added support for phpredis 4 `compression` and `tcp_keepalive` options + * added automatic table creation when using Doctrine DBAL with PDO-based backends + * throw `LogicException` when `CacheItem::tag()` is called on an item coming from a non tag-aware pool + * deprecated `CacheItem::getPreviousTags()`, use `CacheItem::getMetadata()` instead + * deprecated the `AbstractAdapter::unserialize()` and `AbstractCache::unserialize()` methods + * added `CacheCollectorPass` (originally in `FrameworkBundle`) + * added `CachePoolClearerPass` (originally in `FrameworkBundle`) + * added `CachePoolPass` (originally in `FrameworkBundle`) + * added `CachePoolPrunerPass` (originally in `FrameworkBundle`) + +3.4.0 +----- + + * added using options from Memcached DSN + * added PruneableInterface so PSR-6 or PSR-16 cache implementations can declare support for manual stale cache pruning + * added prune logic to FilesystemTrait, PhpFilesTrait, PdoTrait, TagAwareAdapter and ChainTrait + * now FilesystemAdapter, PhpFilesAdapter, FilesystemCache, PhpFilesCache, PdoAdapter, PdoCache, ChainAdapter, and + ChainCache implement PruneableInterface and support manual stale cache pruning + +3.3.0 +----- + + * added CacheItem::getPreviousTags() to get bound tags coming from the pool storage if any + * added PSR-16 "Simple Cache" implementations for all existing PSR-6 adapters + * added Psr6Cache and SimpleCacheAdapter for bidirectional interoperability between PSR-6 and PSR-16 + * added MemcachedAdapter (PSR-6) and MemcachedCache (PSR-16) + * added TraceableAdapter (PSR-6) and TraceableCache (PSR-16) + +3.2.0 +----- + + * added TagAwareAdapter for tags-based invalidation + * added PdoAdapter with PDO and Doctrine DBAL support + * added PhpArrayAdapter and PhpFilesAdapter for OPcache-backed shared memory storage (PHP 7+ only) + * added NullAdapter + +3.1.0 +----- + + * added the component with strict PSR-6 implementations + * added ApcuAdapter, ArrayAdapter, FilesystemAdapter and RedisAdapter + * added AbstractAdapter, ChainAdapter and ProxyAdapter + * added DoctrineAdapter and DoctrineProvider for bidirectional interoperability with Doctrine Cache diff --git a/vendor/symfony/cache/CacheItem.php b/vendor/symfony/cache/CacheItem.php new file mode 100644 index 0000000..ec2c0c5 --- /dev/null +++ b/vendor/symfony/cache/CacheItem.php @@ -0,0 +1,198 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Cache; + +use Psr\Log\LoggerInterface; +use Symfony\Component\Cache\Exception\InvalidArgumentException; +use Symfony\Component\Cache\Exception\LogicException; +use Symfony\Contracts\Cache\ItemInterface; + +/** + * @author Nicolas Grekas + */ +final class CacheItem implements ItemInterface +{ + private const METADATA_EXPIRY_OFFSET = 1527506807; + private const VALUE_WRAPPER = "\xA9"; + + protected string $key; + protected mixed $value = null; + protected bool $isHit = false; + protected float|int|null $expiry = null; + protected array $metadata = []; + protected array $newMetadata = []; + protected ?ItemInterface $innerItem = null; + protected ?string $poolHash = null; + protected bool $isTaggable = false; + + public function getKey(): string + { + return $this->key; + } + + public function get(): mixed + { + return $this->value; + } + + public function isHit(): bool + { + return $this->isHit; + } + + /** + * @return $this + */ + public function set($value): static + { + $this->value = $value; + + return $this; + } + + /** + * @return $this + */ + public function expiresAt(?\DateTimeInterface $expiration): static + { + $this->expiry = null !== $expiration ? (float) $expiration->format('U.u') : null; + + return $this; + } + + /** + * @return $this + */ + public function expiresAfter(mixed $time): static + { + if (null === $time) { + $this->expiry = null; + } elseif ($time instanceof \DateInterval) { + $this->expiry = microtime(true) + \DateTimeImmutable::createFromFormat('U', 0)->add($time)->format('U.u'); + } elseif (\is_int($time)) { + $this->expiry = $time + microtime(true); + } else { + throw new InvalidArgumentException(\sprintf('Expiration date must be an integer, a DateInterval or null, "%s" given.', get_debug_type($time))); + } + + return $this; + } + + public function tag(mixed $tags): static + { + if (!$this->isTaggable) { + throw new LogicException(\sprintf('Cache item "%s" comes from a non tag-aware pool: you cannot tag it.', $this->key)); + } + if (!\is_array($tags) && !$tags instanceof \Traversable) { // don't use is_iterable(), it's slow + $tags = [$tags]; + } + foreach ($tags as $tag) { + if (!\is_string($tag) && !$tag instanceof \Stringable) { + throw new InvalidArgumentException(\sprintf('Cache tag must be string or object that implements __toString(), "%s" given.', get_debug_type($tag))); + } + $tag = (string) $tag; + if (isset($this->newMetadata[self::METADATA_TAGS][$tag])) { + continue; + } + if ('' === $tag) { + throw new InvalidArgumentException('Cache tag length must be greater than zero.'); + } + if (false !== strpbrk($tag, self::RESERVED_CHARACTERS)) { + throw new InvalidArgumentException(\sprintf('Cache tag "%s" contains reserved characters "%s".', $tag, self::RESERVED_CHARACTERS)); + } + $this->newMetadata[self::METADATA_TAGS][$tag] = $tag; + } + + return $this; + } + + public function getMetadata(): array + { + return $this->metadata; + } + + /** + * Validates a cache key according to PSR-6. + * + * @param mixed $key The key to validate + * + * @throws InvalidArgumentException When $key is not valid + */ + public static function validateKey($key): string + { + if (!\is_string($key)) { + throw new InvalidArgumentException(\sprintf('Cache key must be string, "%s" given.', get_debug_type($key))); + } + if ('' === $key) { + throw new InvalidArgumentException('Cache key length must be greater than zero.'); + } + if (false !== strpbrk($key, self::RESERVED_CHARACTERS)) { + throw new InvalidArgumentException(\sprintf('Cache key "%s" contains reserved characters "%s".', $key, self::RESERVED_CHARACTERS)); + } + + return $key; + } + + /** + * Internal logging helper. + * + * @internal + */ + public static function log(?LoggerInterface $logger, string $message, array $context = []): void + { + if ($logger) { + $logger->warning($message, $context); + } else { + $replace = []; + foreach ($context as $k => $v) { + if (\is_scalar($v)) { + $replace['{'.$k.'}'] = $v; + } + } + @trigger_error(strtr($message, $replace), \E_USER_WARNING); + } + } + + private function pack(): mixed + { + if (!$m = $this->newMetadata) { + return $this->value; + } + $valueWrapper = self::VALUE_WRAPPER; + + return new $valueWrapper($this->value, $m + ['expiry' => $this->expiry]); + } + + private function unpack(): bool + { + $v = $this->value; + $valueWrapper = self::VALUE_WRAPPER; + + if ($v instanceof $valueWrapper) { + $this->value = $v->value; + $this->metadata = $v->metadata; + + return true; + } + + if (!\is_array($v) || 1 !== \count($v) || 10 !== \strlen($k = (string) array_key_first($v)) || "\x9D" !== $k[0] || "\0" !== $k[5] || "\x5F" !== $k[9]) { + return false; + } + + // BC with pools populated before v6.1 + $this->value = $v[$k]; + $this->metadata = unpack('Vexpiry/Nctime', substr($k, 1, -1)); + $this->metadata['expiry'] += self::METADATA_EXPIRY_OFFSET; + + return true; + } +} diff --git a/vendor/symfony/cache/DataCollector/CacheDataCollector.php b/vendor/symfony/cache/DataCollector/CacheDataCollector.php new file mode 100644 index 0000000..b9bcdaf --- /dev/null +++ b/vendor/symfony/cache/DataCollector/CacheDataCollector.php @@ -0,0 +1,184 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Cache\DataCollector; + +use Symfony\Component\Cache\Adapter\TraceableAdapter; +use Symfony\Component\Cache\Adapter\TraceableAdapterEvent; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\HttpKernel\DataCollector\DataCollector; +use Symfony\Component\HttpKernel\DataCollector\LateDataCollectorInterface; + +/** + * @author Aaron Scherer + * @author Tobias Nyholm + * + * @final + */ +class CacheDataCollector extends DataCollector implements LateDataCollectorInterface +{ + /** + * @var TraceableAdapter[] + */ + private array $instances = []; + + public function addInstance(string $name, TraceableAdapter $instance): void + { + $this->instances[$name] = $instance; + } + + public function collect(Request $request, Response $response, ?\Throwable $exception = null): void + { + $empty = ['calls' => [], 'adapters' => [], 'config' => [], 'options' => [], 'statistics' => []]; + $this->data = ['instances' => $empty, 'total' => $empty]; + foreach ($this->instances as $name => $instance) { + $this->data['instances']['calls'][$name] = $instance->getCalls(); + $this->data['instances']['adapters'][$name] = get_debug_type($instance->getPool()); + } + + $this->data['instances']['statistics'] = $this->calculateStatistics(); + $this->data['total']['statistics'] = $this->calculateTotalStatistics(); + } + + public function reset(): void + { + $this->data = []; + foreach ($this->instances as $instance) { + $instance->clearCalls(); + } + } + + public function lateCollect(): void + { + $this->data['instances']['calls'] = $this->cloneVar($this->data['instances']['calls']); + } + + public function getName(): string + { + return 'cache'; + } + + /** + * Method returns amount of logged Cache reads: "get" calls. + */ + public function getStatistics(): array + { + return $this->data['instances']['statistics']; + } + + /** + * Method returns the statistic totals. + */ + public function getTotals(): array + { + return $this->data['total']['statistics']; + } + + /** + * Method returns all logged Cache call objects. + */ + public function getCalls(): mixed + { + return $this->data['instances']['calls']; + } + + /** + * Method returns all logged Cache adapter classes. + */ + public function getAdapters(): array + { + return $this->data['instances']['adapters']; + } + + private function calculateStatistics(): array + { + $statistics = []; + foreach ($this->data['instances']['calls'] as $name => $calls) { + $statistics[$name] = [ + 'calls' => 0, + 'time' => 0, + 'reads' => 0, + 'writes' => 0, + 'deletes' => 0, + 'hits' => 0, + 'misses' => 0, + ]; + /** @var TraceableAdapterEvent $call */ + foreach ($calls as $call) { + ++$statistics[$name]['calls']; + $statistics[$name]['time'] += ($call->end ?? microtime(true)) - $call->start; + if ('get' === $call->name) { + ++$statistics[$name]['reads']; + if ($call->hits) { + ++$statistics[$name]['hits']; + } else { + ++$statistics[$name]['misses']; + ++$statistics[$name]['writes']; + } + } elseif ('getItem' === $call->name) { + ++$statistics[$name]['reads']; + if ($call->hits) { + ++$statistics[$name]['hits']; + } else { + ++$statistics[$name]['misses']; + } + } elseif ('getItems' === $call->name) { + $statistics[$name]['reads'] += $call->hits + $call->misses; + $statistics[$name]['hits'] += $call->hits; + $statistics[$name]['misses'] += $call->misses; + } elseif ('hasItem' === $call->name) { + ++$statistics[$name]['reads']; + foreach ($call->result ?? [] as $result) { + ++$statistics[$name][$result ? 'hits' : 'misses']; + } + } elseif ('save' === $call->name) { + ++$statistics[$name]['writes']; + } elseif ('deleteItem' === $call->name) { + ++$statistics[$name]['deletes']; + } + } + if ($statistics[$name]['reads']) { + $statistics[$name]['hit_read_ratio'] = round(100 * $statistics[$name]['hits'] / $statistics[$name]['reads'], 2); + } else { + $statistics[$name]['hit_read_ratio'] = null; + } + } + + return $statistics; + } + + private function calculateTotalStatistics(): array + { + $statistics = $this->getStatistics(); + $totals = [ + 'calls' => 0, + 'time' => 0, + 'reads' => 0, + 'writes' => 0, + 'deletes' => 0, + 'hits' => 0, + 'misses' => 0, + ]; + foreach ($statistics as $name => $values) { + foreach ($totals as $key => $value) { + $totals[$key] += $statistics[$name][$key]; + } + } + if ($totals['reads']) { + $totals['hit_read_ratio'] = round(100 * $totals['hits'] / $totals['reads'], 2); + } else { + $totals['hit_read_ratio'] = null; + } + + return $totals; + } +} diff --git a/vendor/symfony/cache/DependencyInjection/CacheCollectorPass.php b/vendor/symfony/cache/DependencyInjection/CacheCollectorPass.php new file mode 100644 index 0000000..ed95740 --- /dev/null +++ b/vendor/symfony/cache/DependencyInjection/CacheCollectorPass.php @@ -0,0 +1,75 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Cache\DependencyInjection; + +use Symfony\Component\Cache\Adapter\TagAwareAdapterInterface; +use Symfony\Component\Cache\Adapter\TraceableAdapter; +use Symfony\Component\Cache\Adapter\TraceableTagAwareAdapter; +use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\Definition; +use Symfony\Component\DependencyInjection\Reference; + +/** + * Inject a data collector to all the cache services to be able to get detailed statistics. + * + * @author Tobias Nyholm + */ +class CacheCollectorPass implements CompilerPassInterface +{ + public function process(ContainerBuilder $container): void + { + if (!$container->hasDefinition('data_collector.cache')) { + return; + } + + foreach ($container->findTaggedServiceIds('cache.pool') as $id => $attributes) { + $poolName = $attributes[0]['name'] ?? $id; + + $this->addToCollector($id, $poolName, $container); + } + } + + private function addToCollector(string $id, string $name, ContainerBuilder $container): void + { + $definition = $container->getDefinition($id); + if ($definition->isAbstract()) { + return; + } + + $collectorDefinition = $container->getDefinition('data_collector.cache'); + $recorder = new Definition(is_subclass_of($definition->getClass(), TagAwareAdapterInterface::class) ? TraceableTagAwareAdapter::class : TraceableAdapter::class); + $recorder->setTags($definition->getTags()); + if (!$definition->isPublic() || !$definition->isPrivate()) { + $recorder->setPublic($definition->isPublic()); + } + $recorder->setArguments([new Reference($innerId = $id.'.recorder_inner')]); + + foreach ($definition->getMethodCalls() as [$method, $args]) { + if ('setCallbackWrapper' !== $method || !$args[0] instanceof Definition || !($args[0]->getArguments()[2] ?? null) instanceof Definition) { + continue; + } + if ([new Reference($id), 'setCallbackWrapper'] == $args[0]->getArguments()[2]->getFactory()) { + $args[0]->getArguments()[2]->setFactory([new Reference($innerId), 'setCallbackWrapper']); + } + } + + $definition->setTags([]); + $definition->setPublic(false); + + $container->setDefinition($innerId, $definition); + $container->setDefinition($id, $recorder); + + // Tell the collector to add the new instance + $collectorDefinition->addMethodCall('addInstance', [$name, new Reference($id)]); + } +} diff --git a/vendor/symfony/cache/DependencyInjection/CachePoolClearerPass.php b/vendor/symfony/cache/DependencyInjection/CachePoolClearerPass.php new file mode 100644 index 0000000..5449720 --- /dev/null +++ b/vendor/symfony/cache/DependencyInjection/CachePoolClearerPass.php @@ -0,0 +1,38 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Cache\DependencyInjection; + +use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\Reference; + +/** + * @author Nicolas Grekas + */ +class CachePoolClearerPass implements CompilerPassInterface +{ + public function process(ContainerBuilder $container): void + { + $container->getParameterBag()->remove('cache.prefix.seed'); + + foreach ($container->findTaggedServiceIds('cache.pool.clearer') as $id => $attr) { + $clearer = $container->getDefinition($id); + $pools = []; + foreach ($clearer->getArgument(0) as $name => $ref) { + if ($container->hasDefinition($ref)) { + $pools[$name] = new Reference($ref); + } + } + $clearer->replaceArgument(0, $pools); + } + } +} diff --git a/vendor/symfony/cache/DependencyInjection/CachePoolPass.php b/vendor/symfony/cache/DependencyInjection/CachePoolPass.php new file mode 100644 index 0000000..e62febb --- /dev/null +++ b/vendor/symfony/cache/DependencyInjection/CachePoolPass.php @@ -0,0 +1,243 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Cache\DependencyInjection; + +use Symfony\Component\Cache\Adapter\AbstractAdapter; +use Symfony\Component\Cache\Adapter\ArrayAdapter; +use Symfony\Component\Cache\Adapter\ChainAdapter; +use Symfony\Component\Cache\Adapter\NullAdapter; +use Symfony\Component\Cache\Adapter\ParameterNormalizer; +use Symfony\Component\Cache\Messenger\EarlyExpirationDispatcher; +use Symfony\Component\DependencyInjection\ChildDefinition; +use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\Definition; +use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException; +use Symfony\Component\DependencyInjection\Reference; + +/** + * @author Nicolas Grekas + */ +class CachePoolPass implements CompilerPassInterface +{ + public function process(ContainerBuilder $container): void + { + if ($container->hasParameter('cache.prefix.seed')) { + $seed = $container->getParameterBag()->resolveValue($container->getParameter('cache.prefix.seed')); + } else { + $seed = '_'.$container->getParameter('kernel.project_dir'); + $seed .= '.'.$container->getParameter('kernel.container_class'); + } + + $needsMessageHandler = false; + $allPools = []; + $clearers = []; + $attributes = [ + 'provider', + 'name', + 'namespace', + 'default_lifetime', + 'early_expiration_message_bus', + 'reset', + ]; + foreach ($container->findTaggedServiceIds('cache.pool') as $id => $tags) { + $adapter = $pool = $container->getDefinition($id); + if ($pool->isAbstract()) { + continue; + } + $class = $adapter->getClass(); + while ($adapter instanceof ChildDefinition) { + $adapter = $container->findDefinition($adapter->getParent()); + $class = $class ?: $adapter->getClass(); + if ($t = $adapter->getTag('cache.pool')) { + $tags[0] += $t[0]; + } + } + $name = $tags[0]['name'] ?? $id; + if (!isset($tags[0]['namespace'])) { + $namespaceSeed = $seed; + if (null !== $class) { + $namespaceSeed .= '.'.$class; + } + + $tags[0]['namespace'] = $this->getNamespace($namespaceSeed, $name); + } + if (isset($tags[0]['clearer'])) { + $clearer = $tags[0]['clearer']; + while ($container->hasAlias($clearer)) { + $clearer = (string) $container->getAlias($clearer); + } + } else { + $clearer = null; + } + unset($tags[0]['clearer'], $tags[0]['name']); + + if (isset($tags[0]['provider'])) { + $tags[0]['provider'] = new Reference(static::getServiceProvider($container, $tags[0]['provider'])); + } + + if (ChainAdapter::class === $class) { + $adapters = []; + foreach ($adapter->getArgument(0) as $provider => $adapter) { + if ($adapter instanceof ChildDefinition) { + $chainedPool = $adapter; + } else { + $chainedPool = $adapter = new ChildDefinition($adapter); + } + + $chainedTags = [\is_int($provider) ? [] : ['provider' => $provider]]; + $chainedClass = ''; + + while ($adapter instanceof ChildDefinition) { + $adapter = $container->findDefinition($adapter->getParent()); + $chainedClass = $chainedClass ?: $adapter->getClass(); + if ($t = $adapter->getTag('cache.pool')) { + $chainedTags[0] += $t[0]; + } + } + + if (ChainAdapter::class === $chainedClass) { + throw new InvalidArgumentException(\sprintf('Invalid service "%s": chain of adapters cannot reference another chain, found "%s".', $id, $chainedPool->getParent())); + } + + $i = 0; + + if (isset($chainedTags[0]['provider'])) { + $chainedPool->replaceArgument($i++, new Reference(static::getServiceProvider($container, $chainedTags[0]['provider']))); + } + + if (isset($tags[0]['namespace']) && !\in_array($adapter->getClass(), [ArrayAdapter::class, NullAdapter::class], true)) { + $chainedPool->replaceArgument($i++, $tags[0]['namespace']); + } + + if (isset($tags[0]['default_lifetime'])) { + $chainedPool->replaceArgument($i++, $tags[0]['default_lifetime']); + } + + $adapters[] = $chainedPool; + } + + $pool->replaceArgument(0, $adapters); + unset($tags[0]['provider'], $tags[0]['namespace']); + $i = 1; + } else { + $i = 0; + } + + foreach ($attributes as $attr) { + if (!isset($tags[0][$attr])) { + // no-op + } elseif ('reset' === $attr) { + if ($tags[0][$attr]) { + $pool->addTag('kernel.reset', ['method' => $tags[0][$attr]]); + } + } elseif ('early_expiration_message_bus' === $attr) { + $needsMessageHandler = true; + $pool->addMethodCall('setCallbackWrapper', [(new Definition(EarlyExpirationDispatcher::class)) + ->addArgument(new Reference($tags[0]['early_expiration_message_bus'])) + ->addArgument(new Reference('reverse_container')) + ->addArgument((new Definition('callable')) + ->setFactory([new Reference($id), 'setCallbackWrapper']) + ->addArgument(null) + ), + ]); + $pool->addTag('container.reversible'); + } elseif ('namespace' !== $attr || !\in_array($class, [ArrayAdapter::class, NullAdapter::class], true)) { + $argument = $tags[0][$attr]; + + if ('default_lifetime' === $attr && !is_numeric($argument)) { + $argument = (new Definition('int', [$argument])) + ->setFactory([ParameterNormalizer::class, 'normalizeDuration']); + } + + $pool->replaceArgument($i++, $argument); + } + unset($tags[0][$attr]); + } + if (!empty($tags[0])) { + throw new InvalidArgumentException(\sprintf('Invalid "cache.pool" tag for service "%s": accepted attributes are "clearer", "provider", "name", "namespace", "default_lifetime", "early_expiration_message_bus" and "reset", found "%s".', $id, implode('", "', array_keys($tags[0])))); + } + + if (null !== $clearer) { + $clearers[$clearer][$name] = new Reference($id, $container::IGNORE_ON_UNINITIALIZED_REFERENCE); + } + + $allPools[$name] = new Reference($id, $container::IGNORE_ON_UNINITIALIZED_REFERENCE); + } + + if (!$needsMessageHandler) { + $container->removeDefinition('cache.early_expiration_handler'); + } + + $notAliasedCacheClearerId = 'cache.global_clearer'; + while ($container->hasAlias($notAliasedCacheClearerId)) { + $notAliasedCacheClearerId = (string) $container->getAlias($notAliasedCacheClearerId); + } + if ($container->hasDefinition($notAliasedCacheClearerId)) { + $clearers[$notAliasedCacheClearerId] = $allPools; + } + + foreach ($clearers as $id => $pools) { + $clearer = $container->getDefinition($id); + if ($clearer instanceof ChildDefinition) { + $clearer->replaceArgument(0, $pools); + } else { + $clearer->setArgument(0, $pools); + } + $clearer->addTag('cache.pool.clearer'); + + if ('cache.system_clearer' === $id) { + $clearer->addTag('kernel.cache_clearer'); + } + } + + $allPoolsKeys = array_keys($allPools); + + if ($container->hasDefinition('console.command.cache_pool_list')) { + $container->getDefinition('console.command.cache_pool_list')->replaceArgument(0, $allPoolsKeys); + } + + if ($container->hasDefinition('console.command.cache_pool_clear')) { + $container->getDefinition('console.command.cache_pool_clear')->addArgument($allPoolsKeys); + } + + if ($container->hasDefinition('console.command.cache_pool_delete')) { + $container->getDefinition('console.command.cache_pool_delete')->addArgument($allPoolsKeys); + } + } + + private function getNamespace(string $seed, string $id): string + { + return substr(str_replace('/', '-', base64_encode(hash('xxh128', $id.$seed, true))), 0, 10); + } + + /** + * @internal + */ + public static function getServiceProvider(ContainerBuilder $container, string $name): string + { + $container->resolveEnvPlaceholders($name, null, $usedEnvs); + + if ($usedEnvs || preg_match('#^[a-z]++:#', $name)) { + $dsn = $name; + + if (!$container->hasDefinition($name = '.cache_connection.'.ContainerBuilder::hash($dsn))) { + $definition = new Definition(AbstractAdapter::class); + $definition->setFactory([AbstractAdapter::class, 'createConnection']); + $definition->setArguments([$dsn, ['lazy' => true]]); + $container->setDefinition($name, $definition); + } + } + + return $name; + } +} diff --git a/vendor/symfony/cache/DependencyInjection/CachePoolPrunerPass.php b/vendor/symfony/cache/DependencyInjection/CachePoolPrunerPass.php new file mode 100644 index 0000000..68386f5 --- /dev/null +++ b/vendor/symfony/cache/DependencyInjection/CachePoolPrunerPass.php @@ -0,0 +1,48 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Cache\DependencyInjection; + +use Symfony\Component\Cache\PruneableInterface; +use Symfony\Component\DependencyInjection\Argument\IteratorArgument; +use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException; +use Symfony\Component\DependencyInjection\Reference; + +/** + * @author Rob Frawley 2nd + */ +class CachePoolPrunerPass implements CompilerPassInterface +{ + public function process(ContainerBuilder $container): void + { + if (!$container->hasDefinition('console.command.cache_pool_prune')) { + return; + } + + $services = []; + + foreach ($container->findTaggedServiceIds('cache.pool') as $id => $tags) { + $class = $container->getParameterBag()->resolveValue($container->getDefinition($id)->getClass()); + + if (!$reflection = $container->getReflectionClass($class)) { + throw new InvalidArgumentException(\sprintf('Class "%s" used for service "%s" cannot be found.', $class, $id)); + } + + if ($reflection->implementsInterface(PruneableInterface::class)) { + $services[$id] = new Reference($id); + } + } + + $container->getDefinition('console.command.cache_pool_prune')->replaceArgument(0, new IteratorArgument($services)); + } +} diff --git a/vendor/symfony/cache/Exception/CacheException.php b/vendor/symfony/cache/Exception/CacheException.php new file mode 100644 index 0000000..d2e975b --- /dev/null +++ b/vendor/symfony/cache/Exception/CacheException.php @@ -0,0 +1,25 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Cache\Exception; + +use Psr\Cache\CacheException as Psr6CacheInterface; +use Psr\SimpleCache\CacheException as SimpleCacheInterface; + +if (interface_exists(SimpleCacheInterface::class)) { + class CacheException extends \Exception implements Psr6CacheInterface, SimpleCacheInterface + { + } +} else { + class CacheException extends \Exception implements Psr6CacheInterface + { + } +} diff --git a/vendor/symfony/cache/Exception/InvalidArgumentException.php b/vendor/symfony/cache/Exception/InvalidArgumentException.php new file mode 100644 index 0000000..7f9584a --- /dev/null +++ b/vendor/symfony/cache/Exception/InvalidArgumentException.php @@ -0,0 +1,25 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Cache\Exception; + +use Psr\Cache\InvalidArgumentException as Psr6CacheInterface; +use Psr\SimpleCache\InvalidArgumentException as SimpleCacheInterface; + +if (interface_exists(SimpleCacheInterface::class)) { + class InvalidArgumentException extends \InvalidArgumentException implements Psr6CacheInterface, SimpleCacheInterface + { + } +} else { + class InvalidArgumentException extends \InvalidArgumentException implements Psr6CacheInterface + { + } +} diff --git a/vendor/symfony/cache/Exception/LogicException.php b/vendor/symfony/cache/Exception/LogicException.php new file mode 100644 index 0000000..9ffa7ed --- /dev/null +++ b/vendor/symfony/cache/Exception/LogicException.php @@ -0,0 +1,25 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Cache\Exception; + +use Psr\Cache\CacheException as Psr6CacheInterface; +use Psr\SimpleCache\CacheException as SimpleCacheInterface; + +if (interface_exists(SimpleCacheInterface::class)) { + class LogicException extends \LogicException implements Psr6CacheInterface, SimpleCacheInterface + { + } +} else { + class LogicException extends \LogicException implements Psr6CacheInterface + { + } +} diff --git a/vendor/symfony/cache/LICENSE b/vendor/symfony/cache/LICENSE new file mode 100644 index 0000000..0223acd --- /dev/null +++ b/vendor/symfony/cache/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2016-present Fabien Potencier + +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/vendor/symfony/cache/LockRegistry.php b/vendor/symfony/cache/LockRegistry.php new file mode 100644 index 0000000..6923b40 --- /dev/null +++ b/vendor/symfony/cache/LockRegistry.php @@ -0,0 +1,166 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Cache; + +use Psr\Log\LoggerInterface; +use Symfony\Contracts\Cache\CacheInterface; +use Symfony\Contracts\Cache\ItemInterface; + +/** + * LockRegistry is used internally by existing adapters to protect against cache stampede. + * + * It does so by wrapping the computation of items in a pool of locks. + * Foreach each apps, there can be at most 20 concurrent processes that + * compute items at the same time and only one per cache-key. + * + * @author Nicolas Grekas + */ +final class LockRegistry +{ + private static array $openedFiles = []; + private static ?array $lockedFiles = null; + private static \Exception $signalingException; + private static \Closure $signalingCallback; + + /** + * The number of items in this list controls the max number of concurrent processes. + */ + private static array $files = [ + __DIR__.\DIRECTORY_SEPARATOR.'Adapter'.\DIRECTORY_SEPARATOR.'AbstractAdapter.php', + __DIR__.\DIRECTORY_SEPARATOR.'Adapter'.\DIRECTORY_SEPARATOR.'AbstractTagAwareAdapter.php', + __DIR__.\DIRECTORY_SEPARATOR.'Adapter'.\DIRECTORY_SEPARATOR.'AdapterInterface.php', + __DIR__.\DIRECTORY_SEPARATOR.'Adapter'.\DIRECTORY_SEPARATOR.'ApcuAdapter.php', + __DIR__.\DIRECTORY_SEPARATOR.'Adapter'.\DIRECTORY_SEPARATOR.'ArrayAdapter.php', + __DIR__.\DIRECTORY_SEPARATOR.'Adapter'.\DIRECTORY_SEPARATOR.'ChainAdapter.php', + __DIR__.\DIRECTORY_SEPARATOR.'Adapter'.\DIRECTORY_SEPARATOR.'CouchbaseBucketAdapter.php', + __DIR__.\DIRECTORY_SEPARATOR.'Adapter'.\DIRECTORY_SEPARATOR.'CouchbaseCollectionAdapter.php', + __DIR__.\DIRECTORY_SEPARATOR.'Adapter'.\DIRECTORY_SEPARATOR.'DoctrineDbalAdapter.php', + __DIR__.\DIRECTORY_SEPARATOR.'Adapter'.\DIRECTORY_SEPARATOR.'FilesystemAdapter.php', + __DIR__.\DIRECTORY_SEPARATOR.'Adapter'.\DIRECTORY_SEPARATOR.'FilesystemTagAwareAdapter.php', + __DIR__.\DIRECTORY_SEPARATOR.'Adapter'.\DIRECTORY_SEPARATOR.'MemcachedAdapter.php', + __DIR__.\DIRECTORY_SEPARATOR.'Adapter'.\DIRECTORY_SEPARATOR.'NullAdapter.php', + __DIR__.\DIRECTORY_SEPARATOR.'Adapter'.\DIRECTORY_SEPARATOR.'ParameterNormalizer.php', + __DIR__.\DIRECTORY_SEPARATOR.'Adapter'.\DIRECTORY_SEPARATOR.'PdoAdapter.php', + __DIR__.\DIRECTORY_SEPARATOR.'Adapter'.\DIRECTORY_SEPARATOR.'PhpArrayAdapter.php', + __DIR__.\DIRECTORY_SEPARATOR.'Adapter'.\DIRECTORY_SEPARATOR.'PhpFilesAdapter.php', + __DIR__.\DIRECTORY_SEPARATOR.'Adapter'.\DIRECTORY_SEPARATOR.'ProxyAdapter.php', + __DIR__.\DIRECTORY_SEPARATOR.'Adapter'.\DIRECTORY_SEPARATOR.'Psr16Adapter.php', + __DIR__.\DIRECTORY_SEPARATOR.'Adapter'.\DIRECTORY_SEPARATOR.'RedisAdapter.php', + __DIR__.\DIRECTORY_SEPARATOR.'Adapter'.\DIRECTORY_SEPARATOR.'RedisTagAwareAdapter.php', + __DIR__.\DIRECTORY_SEPARATOR.'Adapter'.\DIRECTORY_SEPARATOR.'TagAwareAdapter.php', + __DIR__.\DIRECTORY_SEPARATOR.'Adapter'.\DIRECTORY_SEPARATOR.'TagAwareAdapterInterface.php', + __DIR__.\DIRECTORY_SEPARATOR.'Adapter'.\DIRECTORY_SEPARATOR.'TraceableAdapter.php', + __DIR__.\DIRECTORY_SEPARATOR.'Adapter'.\DIRECTORY_SEPARATOR.'TraceableTagAwareAdapter.php', + ]; + + /** + * Defines a set of existing files that will be used as keys to acquire locks. + * + * @return array The previously defined set of files + */ + public static function setFiles(array $files): array + { + $previousFiles = self::$files; + self::$files = $files; + + foreach (self::$openedFiles as $file) { + if ($file) { + flock($file, \LOCK_UN); + fclose($file); + } + } + self::$openedFiles = self::$lockedFiles = []; + + return $previousFiles; + } + + public static function compute(callable $callback, ItemInterface $item, bool &$save, CacheInterface $pool, ?\Closure $setMetadata = null, ?LoggerInterface $logger = null): mixed + { + if ('\\' === \DIRECTORY_SEPARATOR && null === self::$lockedFiles) { + // disable locking on Windows by default + self::$files = self::$lockedFiles = []; + } + + $key = self::$files ? abs(crc32($item->getKey())) % \count(self::$files) : -1; + + if ($key < 0 || self::$lockedFiles || !$lock = self::open($key)) { + return $callback($item, $save); + } + + self::$signalingException ??= unserialize("O:9:\"Exception\":1:{s:16:\"\0Exception\0trace\";a:0:{}}"); + self::$signalingCallback ??= fn () => throw self::$signalingException; + + while (true) { + try { + // race to get the lock in non-blocking mode + $locked = flock($lock, \LOCK_EX | \LOCK_NB, $wouldBlock); + + if ($locked || !$wouldBlock) { + $logger?->info(\sprintf('Lock %s, now computing item "{key}"', $locked ? 'acquired' : 'not supported'), ['key' => $item->getKey()]); + self::$lockedFiles[$key] = true; + + $value = $callback($item, $save); + + if ($save) { + if ($setMetadata) { + $setMetadata($item); + } + + $pool->save($item->set($value)); + $save = false; + } + + return $value; + } + // if we failed the race, retry locking in blocking mode to wait for the winner + $logger?->info('Item "{key}" is locked, waiting for it to be released', ['key' => $item->getKey()]); + flock($lock, \LOCK_SH); + } finally { + flock($lock, \LOCK_UN); + unset(self::$lockedFiles[$key]); + } + + try { + $value = $pool->get($item->getKey(), self::$signalingCallback, 0); + $logger?->info('Item "{key}" retrieved after lock was released', ['key' => $item->getKey()]); + $save = false; + + return $value; + } catch (\Exception $e) { + if (self::$signalingException !== $e) { + throw $e; + } + $logger?->info('Item "{key}" not found while lock was released, now retrying', ['key' => $item->getKey()]); + } + } + + return null; + } + + /** + * @return resource|false + */ + private static function open(int $key) + { + if (null !== $h = self::$openedFiles[$key] ?? null) { + return $h; + } + set_error_handler(static fn () => null); + try { + $h = fopen(self::$files[$key], 'r+'); + } finally { + restore_error_handler(); + } + + return self::$openedFiles[$key] = $h ?: @fopen(self::$files[$key], 'r'); + } +} diff --git a/vendor/symfony/cache/Marshaller/DefaultMarshaller.php b/vendor/symfony/cache/Marshaller/DefaultMarshaller.php new file mode 100644 index 0000000..02ebcfa --- /dev/null +++ b/vendor/symfony/cache/Marshaller/DefaultMarshaller.php @@ -0,0 +1,96 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Cache\Marshaller; + +use Symfony\Component\Cache\Exception\CacheException; + +/** + * Serializes/unserializes values using igbinary_serialize() if available, serialize() otherwise. + * + * @author Nicolas Grekas + */ +class DefaultMarshaller implements MarshallerInterface +{ + private bool $useIgbinarySerialize = false; + private bool $throwOnSerializationFailure = false; + + public function __construct(?bool $useIgbinarySerialize = null, bool $throwOnSerializationFailure = false) + { + if ($useIgbinarySerialize && (!\extension_loaded('igbinary') || version_compare('3.1.6', phpversion('igbinary'), '>'))) { + throw new CacheException(\extension_loaded('igbinary') ? 'Please upgrade the "igbinary" PHP extension to v3.1.6 or higher.' : 'The "igbinary" PHP extension is not loaded.'); + } + $this->useIgbinarySerialize = true === $useIgbinarySerialize; + $this->throwOnSerializationFailure = $throwOnSerializationFailure; + } + + public function marshall(array $values, ?array &$failed): array + { + $serialized = $failed = []; + + foreach ($values as $id => $value) { + try { + if ($this->useIgbinarySerialize) { + $serialized[$id] = igbinary_serialize($value); + } else { + $serialized[$id] = serialize($value); + } + } catch (\Exception $e) { + if ($this->throwOnSerializationFailure) { + throw new \ValueError($e->getMessage(), 0, $e); + } + $failed[] = $id; + } + } + + return $serialized; + } + + public function unmarshall(string $value): mixed + { + if ('b:0;' === $value) { + return false; + } + if ('N;' === $value) { + return null; + } + static $igbinaryNull; + if ($value === $igbinaryNull ??= \extension_loaded('igbinary') ? igbinary_serialize(null) : false) { + return null; + } + $unserializeCallbackHandler = ini_set('unserialize_callback_func', __CLASS__.'::handleUnserializeCallback'); + try { + if (':' === ($value[1] ?? ':')) { + if (false !== $value = unserialize($value)) { + return $value; + } + } elseif (false === $igbinaryNull) { + throw new \RuntimeException('Failed to unserialize values, did you forget to install the "igbinary" extension?'); + } elseif (null !== $value = igbinary_unserialize($value)) { + return $value; + } + + throw new \DomainException(error_get_last() ? error_get_last()['message'] : 'Failed to unserialize values.'); + } catch (\Error $e) { + throw new \ErrorException($e->getMessage(), $e->getCode(), \E_ERROR, $e->getFile(), $e->getLine()); + } finally { + ini_set('unserialize_callback_func', $unserializeCallbackHandler); + } + } + + /** + * @internal + */ + public static function handleUnserializeCallback(string $class): never + { + throw new \DomainException('Class not found: '.$class); + } +} diff --git a/vendor/symfony/cache/Marshaller/DeflateMarshaller.php b/vendor/symfony/cache/Marshaller/DeflateMarshaller.php new file mode 100644 index 0000000..f35241c --- /dev/null +++ b/vendor/symfony/cache/Marshaller/DeflateMarshaller.php @@ -0,0 +1,44 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Cache\Marshaller; + +use Symfony\Component\Cache\Exception\CacheException; + +/** + * Compresses values using gzdeflate(). + * + * @author Nicolas Grekas + */ +class DeflateMarshaller implements MarshallerInterface +{ + public function __construct( + private MarshallerInterface $marshaller, + ) { + if (!\function_exists('gzdeflate')) { + throw new CacheException('The "zlib" PHP extension is not loaded.'); + } + } + + public function marshall(array $values, ?array &$failed): array + { + return array_map('gzdeflate', $this->marshaller->marshall($values, $failed)); + } + + public function unmarshall(string $value): mixed + { + if (false !== $inflatedValue = @gzinflate($value)) { + $value = $inflatedValue; + } + + return $this->marshaller->unmarshall($value); + } +} diff --git a/vendor/symfony/cache/Marshaller/MarshallerInterface.php b/vendor/symfony/cache/Marshaller/MarshallerInterface.php new file mode 100644 index 0000000..5b81aad --- /dev/null +++ b/vendor/symfony/cache/Marshaller/MarshallerInterface.php @@ -0,0 +1,38 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Cache\Marshaller; + +/** + * Serializes/unserializes PHP values. + * + * Implementations of this interface MUST deal with errors carefully. They MUST + * also deal with forward and backward compatibility at the storage format level. + * + * @author Nicolas Grekas + */ +interface MarshallerInterface +{ + /** + * Serializes a list of values. + * + * When serialization fails for a specific value, no exception should be + * thrown. Instead, its key should be listed in $failed. + */ + public function marshall(array $values, ?array &$failed): array; + + /** + * Unserializes a single value and throws an exception if anything goes wrong. + * + * @throws \Exception Whenever unserialization fails + */ + public function unmarshall(string $value): mixed; +} diff --git a/vendor/symfony/cache/Marshaller/SodiumMarshaller.php b/vendor/symfony/cache/Marshaller/SodiumMarshaller.php new file mode 100644 index 0000000..77d16e8 --- /dev/null +++ b/vendor/symfony/cache/Marshaller/SodiumMarshaller.php @@ -0,0 +1,74 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Cache\Marshaller; + +use Symfony\Component\Cache\Exception\CacheException; +use Symfony\Component\Cache\Exception\InvalidArgumentException; + +/** + * Encrypt/decrypt values using Libsodium. + * + * @author Ahmed TAILOULOUTE + */ +class SodiumMarshaller implements MarshallerInterface +{ + private MarshallerInterface $marshaller; + + /** + * @param string[] $decryptionKeys The key at index "0" is required and is used to decrypt and encrypt values; + * more rotating keys can be provided to decrypt values; + * each key must be generated using sodium_crypto_box_keypair() + */ + public function __construct( + private array $decryptionKeys, + ?MarshallerInterface $marshaller = null, + ) { + if (!self::isSupported()) { + throw new CacheException('The "sodium" PHP extension is not loaded.'); + } + + if (!isset($decryptionKeys[0])) { + throw new InvalidArgumentException('At least one decryption key must be provided at index "0".'); + } + + $this->marshaller = $marshaller ?? new DefaultMarshaller(); + } + + public static function isSupported(): bool + { + return \function_exists('sodium_crypto_box_seal'); + } + + public function marshall(array $values, ?array &$failed): array + { + $encryptionKey = sodium_crypto_box_publickey($this->decryptionKeys[0]); + + $encryptedValues = []; + foreach ($this->marshaller->marshall($values, $failed) as $k => $v) { + $encryptedValues[$k] = sodium_crypto_box_seal($v, $encryptionKey); + } + + return $encryptedValues; + } + + public function unmarshall(string $value): mixed + { + foreach ($this->decryptionKeys as $k) { + if (false !== $decryptedValue = @sodium_crypto_box_seal_open($value, $k)) { + $value = $decryptedValue; + break; + } + } + + return $this->marshaller->unmarshall($value); + } +} diff --git a/vendor/symfony/cache/Marshaller/TagAwareMarshaller.php b/vendor/symfony/cache/Marshaller/TagAwareMarshaller.php new file mode 100644 index 0000000..825f32c --- /dev/null +++ b/vendor/symfony/cache/Marshaller/TagAwareMarshaller.php @@ -0,0 +1,83 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Cache\Marshaller; + +/** + * A marshaller optimized for data structures generated by AbstractTagAwareAdapter. + * + * @author Nicolas Grekas + */ +class TagAwareMarshaller implements MarshallerInterface +{ + private MarshallerInterface $marshaller; + + public function __construct(?MarshallerInterface $marshaller = null) + { + $this->marshaller = $marshaller ?? new DefaultMarshaller(); + } + + public function marshall(array $values, ?array &$failed): array + { + $failed = $notSerialized = $serialized = []; + + foreach ($values as $id => $value) { + if (\is_array($value) && \is_array($value['tags'] ?? null) && \array_key_exists('value', $value) && \count($value) === 2 + (\is_string($value['meta'] ?? null) && 8 === \strlen($value['meta']))) { + // if the value is an array with keys "tags", "value" and "meta", use a compact serialization format + // magic numbers in the form 9D-..-..-..-..-00-..-..-..-5F allow detecting this format quickly in unmarshall() + + $v = $this->marshaller->marshall($value, $f); + + if ($f) { + $f = []; + $failed[] = $id; + } else { + if ([] === $value['tags']) { + $v['tags'] = ''; + } + + $serialized[$id] = "\x9D".($value['meta'] ?? "\0\0\0\0\0\0\0\0").pack('N', \strlen($v['tags'])).$v['tags'].$v['value']; + $serialized[$id][9] = "\x5F"; + } + } else { + // other arbitrary values are serialized using the decorated marshaller below + $notSerialized[$id] = $value; + } + } + + if ($notSerialized) { + $serialized += $this->marshaller->marshall($notSerialized, $f); + $failed = array_merge($failed, $f); + } + + return $serialized; + } + + public function unmarshall(string $value): mixed + { + // detect the compact format used in marshall() using magic numbers in the form 9D-..-..-..-..-00-..-..-..-5F + if (13 >= \strlen($value) || "\x9D" !== $value[0] || "\0" !== $value[5] || "\x5F" !== $value[9]) { + return $this->marshaller->unmarshall($value); + } + + // data consists of value, tags and metadata which we need to unpack + $meta = substr($value, 1, 12); + $meta[8] = "\0"; + $tagLen = unpack('Nlen', $meta, 8)['len']; + $meta = substr($meta, 0, 8); + + return [ + 'value' => $this->marshaller->unmarshall(substr($value, 13 + $tagLen)), + 'tags' => $tagLen ? $this->marshaller->unmarshall(substr($value, 13, $tagLen)) : [], + 'meta' => "\0\0\0\0\0\0\0\0" === $meta ? null : $meta, + ]; + } +} diff --git a/vendor/symfony/cache/Messenger/EarlyExpirationDispatcher.php b/vendor/symfony/cache/Messenger/EarlyExpirationDispatcher.php new file mode 100644 index 0000000..d972634 --- /dev/null +++ b/vendor/symfony/cache/Messenger/EarlyExpirationDispatcher.php @@ -0,0 +1,60 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Cache\Messenger; + +use Psr\Log\LoggerInterface; +use Symfony\Component\Cache\Adapter\AdapterInterface; +use Symfony\Component\Cache\CacheItem; +use Symfony\Component\DependencyInjection\ReverseContainer; +use Symfony\Component\Messenger\MessageBusInterface; +use Symfony\Component\Messenger\Stamp\HandledStamp; + +/** + * Sends the computation of cached values to a message bus. + */ +class EarlyExpirationDispatcher +{ + private ?\Closure $callbackWrapper; + + public function __construct( + private MessageBusInterface $bus, + private ReverseContainer $reverseContainer, + ?callable $callbackWrapper = null, + ) { + $this->callbackWrapper = null === $callbackWrapper ? null : $callbackWrapper(...); + } + + public function __invoke(callable $callback, CacheItem $item, bool &$save, AdapterInterface $pool, \Closure $setMetadata, ?LoggerInterface $logger = null): mixed + { + if (!$item->isHit() || null === $message = EarlyExpirationMessage::create($this->reverseContainer, $callback, $item, $pool)) { + // The item is stale or the callback cannot be reversed: we must compute the value now + $logger?->info('Computing item "{key}" online: '.($item->isHit() ? 'callback cannot be reversed' : 'item is stale'), ['key' => $item->getKey()]); + + return null !== $this->callbackWrapper ? ($this->callbackWrapper)($callback, $item, $save, $pool, $setMetadata, $logger) : $callback($item, $save); + } + + $envelope = $this->bus->dispatch($message); + + if ($logger) { + if ($envelope->last(HandledStamp::class)) { + $logger->info('Item "{key}" was computed online', ['key' => $item->getKey()]); + } else { + $logger->info('Item "{key}" sent for recomputation', ['key' => $item->getKey()]); + } + } + + // The item's value is not stale, no need to write it to the backend + $save = false; + + return $message->getItem()->get() ?? $item->get(); + } +} diff --git a/vendor/symfony/cache/Messenger/EarlyExpirationHandler.php b/vendor/symfony/cache/Messenger/EarlyExpirationHandler.php new file mode 100644 index 0000000..adaeb5b --- /dev/null +++ b/vendor/symfony/cache/Messenger/EarlyExpirationHandler.php @@ -0,0 +1,81 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Cache\Messenger; + +use Symfony\Component\Cache\CacheItem; +use Symfony\Component\DependencyInjection\ReverseContainer; +use Symfony\Component\Messenger\Attribute\AsMessageHandler; + +/** + * Computes cached values sent to a message bus. + */ +#[AsMessageHandler] +class EarlyExpirationHandler +{ + private array $processedNonces = []; + + public function __construct( + private ReverseContainer $reverseContainer, + ) { + } + + public function __invoke(EarlyExpirationMessage $message): void + { + $item = $message->getItem(); + $metadata = $item->getMetadata(); + $expiry = $metadata[CacheItem::METADATA_EXPIRY] ?? 0; + $ctime = $metadata[CacheItem::METADATA_CTIME] ?? 0; + + if ($expiry && $ctime) { + // skip duplicate or expired messages + + $processingNonce = [$expiry, $ctime]; + $pool = $message->getPool(); + $key = $item->getKey(); + + if (($this->processedNonces[$pool][$key] ?? null) === $processingNonce) { + return; + } + + if (microtime(true) >= $expiry) { + return; + } + + $this->processedNonces[$pool] = [$key => $processingNonce] + ($this->processedNonces[$pool] ?? []); + + if (\count($this->processedNonces[$pool]) > 100) { + array_pop($this->processedNonces[$pool]); + } + } + + static $setMetadata; + + $setMetadata ??= \Closure::bind( + function (CacheItem $item, float $startTime) { + if ($item->expiry > $endTime = microtime(true)) { + $item->newMetadata[CacheItem::METADATA_EXPIRY] = $item->expiry; + $item->newMetadata[CacheItem::METADATA_CTIME] = (int) ceil(1000 * ($endTime - $startTime)); + } + }, + null, + CacheItem::class + ); + + $startTime = microtime(true); + $pool = $message->findPool($this->reverseContainer); + $callback = $message->findCallback($this->reverseContainer); + $save = true; + $value = $callback($item, $save); + $setMetadata($item, $startTime); + $pool->save($item->set($value)); + } +} diff --git a/vendor/symfony/cache/Messenger/EarlyExpirationMessage.php b/vendor/symfony/cache/Messenger/EarlyExpirationMessage.php new file mode 100644 index 0000000..de8256b --- /dev/null +++ b/vendor/symfony/cache/Messenger/EarlyExpirationMessage.php @@ -0,0 +1,96 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Cache\Messenger; + +use Symfony\Component\Cache\Adapter\AdapterInterface; +use Symfony\Component\Cache\CacheItem; +use Symfony\Component\DependencyInjection\ReverseContainer; + +/** + * Conveys a cached value that needs to be computed. + */ +final class EarlyExpirationMessage +{ + public static function create(ReverseContainer $reverseContainer, callable $callback, CacheItem $item, AdapterInterface $pool): ?self + { + try { + $item = clone $item; + $item->set(null); + } catch (\Exception) { + return null; + } + + $pool = $reverseContainer->getId($pool); + + if (\is_object($callback)) { + if (null === $id = $reverseContainer->getId($callback)) { + return null; + } + + $callback = '@'.$id; + } elseif (!\is_array($callback)) { + $callback = (string) $callback; + } elseif (!\is_object($callback[0])) { + $callback = [(string) $callback[0], (string) $callback[1]]; + } else { + if (null === $id = $reverseContainer->getId($callback[0])) { + return null; + } + + $callback = ['@'.$id, (string) $callback[1]]; + } + + return new self($item, $pool, $callback); + } + + public function getItem(): CacheItem + { + return $this->item; + } + + public function getPool(): string + { + return $this->pool; + } + + /** + * @return string|string[] + */ + public function getCallback(): string|array + { + return $this->callback; + } + + public function findPool(ReverseContainer $reverseContainer): AdapterInterface + { + return $reverseContainer->getService($this->pool); + } + + public function findCallback(ReverseContainer $reverseContainer): callable + { + if (\is_string($callback = $this->callback)) { + return '@' === $callback[0] ? $reverseContainer->getService(substr($callback, 1)) : $callback; + } + if ('@' === $callback[0][0]) { + $callback[0] = $reverseContainer->getService(substr($callback[0], 1)); + } + + return $callback; + } + + private function __construct( + private CacheItem $item, + private string $pool, + private string|array $callback, + ) { + } +} diff --git a/vendor/symfony/cache/PruneableInterface.php b/vendor/symfony/cache/PruneableInterface.php new file mode 100644 index 0000000..3095c80 --- /dev/null +++ b/vendor/symfony/cache/PruneableInterface.php @@ -0,0 +1,20 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Cache; + +/** + * Interface extends psr-6 and psr-16 caches to allow for pruning (deletion) of all expired cache items. + */ +interface PruneableInterface +{ + public function prune(): bool; +} diff --git a/vendor/symfony/cache/Psr16Cache.php b/vendor/symfony/cache/Psr16Cache.php new file mode 100644 index 0000000..01494d7 --- /dev/null +++ b/vendor/symfony/cache/Psr16Cache.php @@ -0,0 +1,240 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Cache; + +use Psr\Cache\CacheException as Psr6CacheException; +use Psr\Cache\CacheItemPoolInterface; +use Psr\SimpleCache\CacheException as SimpleCacheException; +use Psr\SimpleCache\CacheInterface; +use Symfony\Component\Cache\Adapter\AdapterInterface; +use Symfony\Component\Cache\Exception\InvalidArgumentException; +use Symfony\Component\Cache\Traits\ProxyTrait; + +/** + * Turns a PSR-6 cache into a PSR-16 one. + * + * @author Nicolas Grekas + */ +class Psr16Cache implements CacheInterface, PruneableInterface, ResettableInterface +{ + use ProxyTrait; + + private ?\Closure $createCacheItem = null; + private ?CacheItem $cacheItemPrototype = null; + private static \Closure $packCacheItem; + + public function __construct(CacheItemPoolInterface $pool) + { + $this->pool = $pool; + + if (!$pool instanceof AdapterInterface) { + return; + } + $cacheItemPrototype = &$this->cacheItemPrototype; + $createCacheItem = \Closure::bind( + static function ($key, $value, $allowInt = false) use (&$cacheItemPrototype) { + $item = clone $cacheItemPrototype; + $item->poolHash = $item->innerItem = null; + if ($allowInt && \is_int($key)) { + $item->key = (string) $key; + } else { + \assert('' !== CacheItem::validateKey($key)); + $item->key = $key; + } + $item->value = $value; + $item->isHit = false; + + return $item; + }, + null, + CacheItem::class + ); + $this->createCacheItem = function ($key, $value, $allowInt = false) use ($createCacheItem) { + if (null === $this->cacheItemPrototype) { + $this->get($allowInt && \is_int($key) ? (string) $key : $key); + } + $this->createCacheItem = $createCacheItem; + + return $createCacheItem($key, null, $allowInt)->set($value); + }; + self::$packCacheItem ??= \Closure::bind( + static function (CacheItem $item) { + $item->newMetadata = $item->metadata; + + return $item->pack(); + }, + null, + CacheItem::class + ); + } + + public function get($key, $default = null): mixed + { + try { + $item = $this->pool->getItem($key); + } catch (SimpleCacheException $e) { + throw $e; + } catch (Psr6CacheException $e) { + throw new InvalidArgumentException($e->getMessage(), $e->getCode(), $e); + } + if (null === $this->cacheItemPrototype) { + $this->cacheItemPrototype = clone $item; + $this->cacheItemPrototype->set(null); + } + + return $item->isHit() ? $item->get() : $default; + } + + public function set($key, $value, $ttl = null): bool + { + try { + if (null !== $f = $this->createCacheItem) { + $item = $f($key, $value); + } else { + $item = $this->pool->getItem($key)->set($value); + } + } catch (SimpleCacheException $e) { + throw $e; + } catch (Psr6CacheException $e) { + throw new InvalidArgumentException($e->getMessage(), $e->getCode(), $e); + } + if (null !== $ttl) { + $item->expiresAfter($ttl); + } + + return $this->pool->save($item); + } + + public function delete($key): bool + { + try { + return $this->pool->deleteItem($key); + } catch (SimpleCacheException $e) { + throw $e; + } catch (Psr6CacheException $e) { + throw new InvalidArgumentException($e->getMessage(), $e->getCode(), $e); + } + } + + public function clear(): bool + { + return $this->pool->clear(); + } + + public function getMultiple($keys, $default = null): iterable + { + if ($keys instanceof \Traversable) { + $keys = iterator_to_array($keys, false); + } elseif (!\is_array($keys)) { + throw new InvalidArgumentException(\sprintf('Cache keys must be array or Traversable, "%s" given.', get_debug_type($keys))); + } + + try { + $items = $this->pool->getItems($keys); + } catch (SimpleCacheException $e) { + throw $e; + } catch (Psr6CacheException $e) { + throw new InvalidArgumentException($e->getMessage(), $e->getCode(), $e); + } + $values = []; + + if (!$this->pool instanceof AdapterInterface) { + foreach ($items as $key => $item) { + $values[$key] = $item->isHit() ? $item->get() : $default; + } + + return $values; + } + + foreach ($items as $key => $item) { + $values[$key] = $item->isHit() ? (self::$packCacheItem)($item) : $default; + } + + return $values; + } + + public function setMultiple($values, $ttl = null): bool + { + $valuesIsArray = \is_array($values); + if (!$valuesIsArray && !$values instanceof \Traversable) { + throw new InvalidArgumentException(\sprintf('Cache values must be array or Traversable, "%s" given.', get_debug_type($values))); + } + $items = []; + + try { + if (null !== $f = $this->createCacheItem) { + $valuesIsArray = false; + foreach ($values as $key => $value) { + $items[$key] = $f($key, $value, true); + } + } elseif ($valuesIsArray) { + $items = []; + foreach ($values as $key => $value) { + $items[] = (string) $key; + } + $items = $this->pool->getItems($items); + } else { + foreach ($values as $key => $value) { + if (\is_int($key)) { + $key = (string) $key; + } + $items[$key] = $this->pool->getItem($key)->set($value); + } + } + } catch (SimpleCacheException $e) { + throw $e; + } catch (Psr6CacheException $e) { + throw new InvalidArgumentException($e->getMessage(), $e->getCode(), $e); + } + $ok = true; + + foreach ($items as $key => $item) { + if ($valuesIsArray) { + $item->set($values[$key]); + } + if (null !== $ttl) { + $item->expiresAfter($ttl); + } + $ok = $this->pool->saveDeferred($item) && $ok; + } + + return $this->pool->commit() && $ok; + } + + public function deleteMultiple($keys): bool + { + if ($keys instanceof \Traversable) { + $keys = iterator_to_array($keys, false); + } elseif (!\is_array($keys)) { + throw new InvalidArgumentException(\sprintf('Cache keys must be array or Traversable, "%s" given.', get_debug_type($keys))); + } + + try { + return $this->pool->deleteItems($keys); + } catch (SimpleCacheException $e) { + throw $e; + } catch (Psr6CacheException $e) { + throw new InvalidArgumentException($e->getMessage(), $e->getCode(), $e); + } + } + + public function has($key): bool + { + try { + return $this->pool->hasItem($key); + } catch (SimpleCacheException $e) { + throw $e; + } catch (Psr6CacheException $e) { + throw new InvalidArgumentException($e->getMessage(), $e->getCode(), $e); + } + } +} diff --git a/vendor/symfony/cache/README.md b/vendor/symfony/cache/README.md new file mode 100644 index 0000000..c466d57 --- /dev/null +++ b/vendor/symfony/cache/README.md @@ -0,0 +1,19 @@ +Symfony PSR-6 implementation for caching +======================================== + +The Cache component provides extended +[PSR-6](https://www.php-fig.org/psr/psr-6/) implementations for adding cache to +your applications. It is designed to have a low overhead so that caching is +fastest. It ships with adapters for the most widespread caching backends. +It also provides a [PSR-16](https://www.php-fig.org/psr/psr-16/) adapter, +and implementations for [symfony/cache-contracts](https://github.com/symfony/cache-contracts)' +`CacheInterface` and `TagAwareCacheInterface`. + +Resources +--------- + + * [Documentation](https://symfony.com/doc/current/components/cache.html) + * [Contributing](https://symfony.com/doc/current/contributing/index.html) + * [Report issues](https://github.com/symfony/symfony/issues) and + [send Pull Requests](https://github.com/symfony/symfony/pulls) + in the [main Symfony repository](https://github.com/symfony/symfony) diff --git a/vendor/symfony/cache/ResettableInterface.php b/vendor/symfony/cache/ResettableInterface.php new file mode 100644 index 0000000..7b0a853 --- /dev/null +++ b/vendor/symfony/cache/ResettableInterface.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Cache; + +use Symfony\Contracts\Service\ResetInterface; + +/** + * Resets a pool's local state. + */ +interface ResettableInterface extends ResetInterface +{ +} diff --git a/vendor/symfony/cache/Traits/AbstractAdapterTrait.php b/vendor/symfony/cache/Traits/AbstractAdapterTrait.php new file mode 100644 index 0000000..6a71674 --- /dev/null +++ b/vendor/symfony/cache/Traits/AbstractAdapterTrait.php @@ -0,0 +1,377 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Cache\Traits; + +use Psr\Cache\CacheItemInterface; +use Psr\Log\LoggerAwareTrait; +use Symfony\Component\Cache\CacheItem; +use Symfony\Component\Cache\Exception\InvalidArgumentException; + +/** + * @author Nicolas Grekas + * + * @internal + */ +trait AbstractAdapterTrait +{ + use LoggerAwareTrait; + + /** + * needs to be set by class, signature is function(string , mixed , bool ). + */ + private static \Closure $createCacheItem; + + /** + * needs to be set by class, signature is function(array , string , array <&expiredIds>). + */ + private static \Closure $mergeByLifetime; + + private string $namespace = ''; + private int $defaultLifetime; + private string $namespaceVersion = ''; + private bool $versioningIsEnabled = false; + private array $deferred = []; + private array $ids = []; + + /** + * The maximum length to enforce for identifiers or null when no limit applies. + */ + protected ?int $maxIdLength = null; + + /** + * Fetches several cache items. + * + * @param array $ids The cache identifiers to fetch + */ + abstract protected function doFetch(array $ids): iterable; + + /** + * Confirms if the cache contains specified cache item. + * + * @param string $id The identifier for which to check existence + */ + abstract protected function doHave(string $id): bool; + + /** + * Deletes all items in the pool. + * + * @param string $namespace The prefix used for all identifiers managed by this pool + */ + abstract protected function doClear(string $namespace): bool; + + /** + * Removes multiple items from the pool. + * + * @param array $ids An array of identifiers that should be removed from the pool + */ + abstract protected function doDelete(array $ids): bool; + + /** + * Persists several cache items immediately. + * + * @param array $values The values to cache, indexed by their cache identifier + * @param int $lifetime The lifetime of the cached values, 0 for persisting until manual cleaning + * + * @return array|bool The identifiers that failed to be cached or a boolean stating if caching succeeded or not + */ + abstract protected function doSave(array $values, int $lifetime): array|bool; + + public function hasItem(mixed $key): bool + { + $id = $this->getId($key); + + if (isset($this->deferred[$key])) { + $this->commit(); + } + + try { + return $this->doHave($id); + } catch (\Exception $e) { + CacheItem::log($this->logger, 'Failed to check if key "{key}" is cached: '.$e->getMessage(), ['key' => $key, 'exception' => $e, 'cache-adapter' => get_debug_type($this)]); + + return false; + } + } + + public function clear(string $prefix = ''): bool + { + $this->deferred = []; + if ($cleared = $this->versioningIsEnabled) { + if ('' === $namespaceVersionToClear = $this->namespaceVersion) { + foreach ($this->doFetch([static::NS_SEPARATOR.$this->namespace]) as $v) { + $namespaceVersionToClear = $v; + } + } + $namespaceToClear = $this->namespace.$namespaceVersionToClear; + $namespaceVersion = self::formatNamespaceVersion(mt_rand()); + try { + $e = $this->doSave([static::NS_SEPARATOR.$this->namespace => $namespaceVersion], 0); + } catch (\Exception $e) { + } + if (true !== $e && [] !== $e) { + $cleared = false; + $message = 'Failed to save the new namespace'.($e instanceof \Exception ? ': '.$e->getMessage() : '.'); + CacheItem::log($this->logger, $message, ['exception' => $e instanceof \Exception ? $e : null, 'cache-adapter' => get_debug_type($this)]); + } else { + $this->namespaceVersion = $namespaceVersion; + $this->ids = []; + } + } else { + $namespaceToClear = $this->namespace.$prefix; + } + + try { + return $this->doClear($namespaceToClear) || $cleared; + } catch (\Exception $e) { + CacheItem::log($this->logger, 'Failed to clear the cache: '.$e->getMessage(), ['exception' => $e, 'cache-adapter' => get_debug_type($this)]); + + return false; + } + } + + public function deleteItem(mixed $key): bool + { + return $this->deleteItems([$key]); + } + + public function deleteItems(array $keys): bool + { + $ids = []; + + foreach ($keys as $key) { + $ids[$key] = $this->getId($key); + unset($this->deferred[$key]); + } + + try { + if ($this->doDelete($ids)) { + return true; + } + } catch (\Exception) { + } + + $ok = true; + + // When bulk-delete failed, retry each item individually + foreach ($ids as $key => $id) { + try { + $e = null; + if ($this->doDelete([$id])) { + continue; + } + } catch (\Exception $e) { + } + $message = 'Failed to delete key "{key}"'.($e instanceof \Exception ? ': '.$e->getMessage() : '.'); + CacheItem::log($this->logger, $message, ['key' => $key, 'exception' => $e, 'cache-adapter' => get_debug_type($this)]); + $ok = false; + } + + return $ok; + } + + public function getItem(mixed $key): CacheItem + { + $id = $this->getId($key); + + if (isset($this->deferred[$key])) { + $this->commit(); + } + + $isHit = false; + $value = null; + + try { + foreach ($this->doFetch([$id]) as $value) { + $isHit = true; + } + + return (self::$createCacheItem)($key, $value, $isHit); + } catch (\Exception $e) { + CacheItem::log($this->logger, 'Failed to fetch key "{key}": '.$e->getMessage(), ['key' => $key, 'exception' => $e, 'cache-adapter' => get_debug_type($this)]); + } + + return (self::$createCacheItem)($key, null, false); + } + + public function getItems(array $keys = []): iterable + { + $ids = []; + $commit = false; + + foreach ($keys as $key) { + $ids[] = $this->getId($key); + $commit = $commit || isset($this->deferred[$key]); + } + + if ($commit) { + $this->commit(); + } + + try { + $items = $this->doFetch($ids); + } catch (\Exception $e) { + CacheItem::log($this->logger, 'Failed to fetch items: '.$e->getMessage(), ['keys' => $keys, 'exception' => $e, 'cache-adapter' => get_debug_type($this)]); + $items = []; + } + $ids = array_combine($ids, $keys); + + return $this->generateItems($items, $ids); + } + + public function save(CacheItemInterface $item): bool + { + if (!$item instanceof CacheItem) { + return false; + } + $this->deferred[$item->getKey()] = $item; + + return $this->commit(); + } + + public function saveDeferred(CacheItemInterface $item): bool + { + if (!$item instanceof CacheItem) { + return false; + } + $this->deferred[$item->getKey()] = $item; + + return true; + } + + /** + * Enables/disables versioning of items. + * + * When versioning is enabled, clearing the cache is atomic and doesn't require listing existing keys to proceed, + * but old keys may need garbage collection and extra round-trips to the back-end are required. + * + * Calling this method also clears the memoized namespace version and thus forces a resynchronization of it. + * + * @return bool the previous state of versioning + */ + public function enableVersioning(bool $enable = true): bool + { + $wasEnabled = $this->versioningIsEnabled; + $this->versioningIsEnabled = $enable; + $this->namespaceVersion = ''; + $this->ids = []; + + return $wasEnabled; + } + + public function reset(): void + { + if ($this->deferred) { + $this->commit(); + } + $this->namespaceVersion = ''; + $this->ids = []; + } + + public function __sleep(): array + { + throw new \BadMethodCallException('Cannot serialize '.__CLASS__); + } + + public function __wakeup(): void + { + throw new \BadMethodCallException('Cannot unserialize '.__CLASS__); + } + + public function __destruct() + { + if ($this->deferred) { + $this->commit(); + } + } + + private function generateItems(iterable $items, array &$keys): \Generator + { + $f = self::$createCacheItem; + + try { + foreach ($items as $id => $value) { + if (!isset($keys[$id])) { + throw new InvalidArgumentException(\sprintf('Could not match value id "%s" to keys "%s".', $id, implode('", "', $keys))); + } + $key = $keys[$id]; + unset($keys[$id]); + yield $key => $f($key, $value, true); + } + } catch (\Exception $e) { + CacheItem::log($this->logger, 'Failed to fetch items: '.$e->getMessage(), ['keys' => array_values($keys), 'exception' => $e, 'cache-adapter' => get_debug_type($this)]); + } + + foreach ($keys as $key) { + yield $key => $f($key, null, false); + } + } + + /** + * @internal + */ + protected function getId(mixed $key): string + { + if ($this->versioningIsEnabled && '' === $this->namespaceVersion) { + $this->ids = []; + $this->namespaceVersion = '1'.static::NS_SEPARATOR; + try { + foreach ($this->doFetch([static::NS_SEPARATOR.$this->namespace]) as $v) { + $this->namespaceVersion = $v; + } + $e = true; + if ('1'.static::NS_SEPARATOR === $this->namespaceVersion) { + $this->namespaceVersion = self::formatNamespaceVersion(time()); + $e = $this->doSave([static::NS_SEPARATOR.$this->namespace => $this->namespaceVersion], 0); + } + } catch (\Exception $e) { + } + if (true !== $e && [] !== $e) { + $message = 'Failed to save the new namespace'.($e instanceof \Exception ? ': '.$e->getMessage() : '.'); + CacheItem::log($this->logger, $message, ['exception' => $e instanceof \Exception ? $e : null, 'cache-adapter' => get_debug_type($this)]); + } + } + + if (\is_string($key) && isset($this->ids[$key])) { + return $this->namespace.$this->namespaceVersion.$this->ids[$key]; + } + \assert('' !== CacheItem::validateKey($key)); + $this->ids[$key] = $key; + + if (\count($this->ids) > 1000) { + $this->ids = \array_slice($this->ids, 500, null, true); // stop memory leak if there are many keys + } + + if (null === $this->maxIdLength) { + return $this->namespace.$this->namespaceVersion.$key; + } + if (\strlen($id = $this->namespace.$this->namespaceVersion.$key) > $this->maxIdLength) { + // Use xxh128 to favor speed over security, which is not an issue here + $this->ids[$key] = $id = substr_replace(base64_encode(hash('xxh128', $key, true)), static::NS_SEPARATOR, -(\strlen($this->namespaceVersion) + 2)); + $id = $this->namespace.$this->namespaceVersion.$id; + } + + return $id; + } + + /** + * @internal + */ + public static function handleUnserializeCallback(string $class): never + { + throw new \DomainException('Class not found: '.$class); + } + + private static function formatNamespaceVersion(int $value): string + { + return strtr(substr_replace(base64_encode(pack('V', $value)), static::NS_SEPARATOR, 5), '/', '_'); + } +} diff --git a/vendor/symfony/cache/Traits/ContractsTrait.php b/vendor/symfony/cache/Traits/ContractsTrait.php new file mode 100644 index 0000000..d461582 --- /dev/null +++ b/vendor/symfony/cache/Traits/ContractsTrait.php @@ -0,0 +1,113 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Cache\Traits; + +use Psr\Log\LoggerInterface; +use Symfony\Component\Cache\Adapter\AdapterInterface; +use Symfony\Component\Cache\CacheItem; +use Symfony\Component\Cache\Exception\InvalidArgumentException; +use Symfony\Component\Cache\LockRegistry; +use Symfony\Contracts\Cache\CacheInterface; +use Symfony\Contracts\Cache\CacheTrait; +use Symfony\Contracts\Cache\ItemInterface; + +/** + * @author Nicolas Grekas + * + * @internal + */ +trait ContractsTrait +{ + use CacheTrait { + doGet as private contractsGet; + } + + private \Closure $callbackWrapper; + private array $computing = []; + + /** + * Wraps the callback passed to ->get() in a callable. + * + * @return callable the previous callback wrapper + */ + public function setCallbackWrapper(?callable $callbackWrapper): callable + { + if (!isset($this->callbackWrapper)) { + $this->callbackWrapper = LockRegistry::compute(...); + + if (\in_array(\PHP_SAPI, ['cli', 'phpdbg', 'embed'], true)) { + $this->setCallbackWrapper(null); + } + } + + if (null !== $callbackWrapper && !$callbackWrapper instanceof \Closure) { + $callbackWrapper = $callbackWrapper(...); + } + + $previousWrapper = $this->callbackWrapper; + $this->callbackWrapper = $callbackWrapper ?? static fn (callable $callback, ItemInterface $item, bool &$save, CacheInterface $pool, \Closure $setMetadata, ?LoggerInterface $logger) => $callback($item, $save); + + return $previousWrapper; + } + + private function doGet(AdapterInterface $pool, string $key, callable $callback, ?float $beta, ?array &$metadata = null): mixed + { + if (0 > $beta ??= 1.0) { + throw new InvalidArgumentException(\sprintf('Argument "$beta" provided to "%s::get()" must be a positive number, %f given.', static::class, $beta)); + } + + static $setMetadata; + + $setMetadata ??= \Closure::bind( + static function (CacheItem $item, float $startTime, ?array &$metadata) { + if ($item->expiry > $endTime = microtime(true)) { + $item->newMetadata[CacheItem::METADATA_EXPIRY] = $metadata[CacheItem::METADATA_EXPIRY] = $item->expiry; + $item->newMetadata[CacheItem::METADATA_CTIME] = $metadata[CacheItem::METADATA_CTIME] = (int) ceil(1000 * ($endTime - $startTime)); + } else { + unset($metadata[CacheItem::METADATA_EXPIRY], $metadata[CacheItem::METADATA_CTIME], $metadata[CacheItem::METADATA_TAGS]); + } + }, + null, + CacheItem::class + ); + + $this->callbackWrapper ??= LockRegistry::compute(...); + + return $this->contractsGet($pool, $key, function (CacheItem $item, bool &$save) use ($pool, $callback, $setMetadata, &$metadata, $key) { + // don't wrap nor save recursive calls + if (isset($this->computing[$key])) { + $value = $callback($item, $save); + $save = false; + + return $value; + } + + $this->computing[$key] = $key; + $startTime = microtime(true); + + if (!isset($this->callbackWrapper)) { + $this->setCallbackWrapper($this->setCallbackWrapper(null)); + } + + try { + $value = ($this->callbackWrapper)($callback, $item, $save, $pool, function (CacheItem $item) use ($setMetadata, $startTime, &$metadata) { + $setMetadata($item, $startTime, $metadata); + }, $this->logger ?? null); + $setMetadata($item, $startTime, $metadata); + + return $value; + } finally { + unset($this->computing[$key]); + } + }, $beta, $metadata, $this->logger ?? null); + } +} diff --git a/vendor/symfony/cache/Traits/FilesystemCommonTrait.php b/vendor/symfony/cache/Traits/FilesystemCommonTrait.php new file mode 100644 index 0000000..98e0d3e --- /dev/null +++ b/vendor/symfony/cache/Traits/FilesystemCommonTrait.php @@ -0,0 +1,190 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Cache\Traits; + +use Symfony\Component\Cache\Exception\InvalidArgumentException; + +/** + * @author Nicolas Grekas + * + * @internal + */ +trait FilesystemCommonTrait +{ + private string $directory; + private string $tmpSuffix; + + private function init(string $namespace, ?string $directory): void + { + if (!isset($directory[0])) { + $directory = sys_get_temp_dir().\DIRECTORY_SEPARATOR.'symfony-cache'; + } else { + $directory = realpath($directory) ?: $directory; + } + if (isset($namespace[0])) { + if (preg_match('#[^-+_.A-Za-z0-9]#', $namespace, $match)) { + throw new InvalidArgumentException(\sprintf('Namespace contains "%s" but only characters in [-+_.A-Za-z0-9] are allowed.', $match[0])); + } + $directory .= \DIRECTORY_SEPARATOR.$namespace; + } else { + $directory .= \DIRECTORY_SEPARATOR.'@'; + } + if (!is_dir($directory)) { + @mkdir($directory, 0777, true); + } + $directory .= \DIRECTORY_SEPARATOR; + // On Windows the whole path is limited to 258 chars + if ('\\' === \DIRECTORY_SEPARATOR && \strlen($directory) > 234) { + throw new InvalidArgumentException(\sprintf('Cache directory too long (%s).', $directory)); + } + + $this->directory = $directory; + } + + protected function doClear(string $namespace): bool + { + $ok = true; + + foreach ($this->scanHashDir($this->directory) as $file) { + if ('' !== $namespace && !str_starts_with($this->getFileKey($file), $namespace)) { + continue; + } + + $ok = ($this->doUnlink($file) || !file_exists($file)) && $ok; + } + + return $ok; + } + + protected function doDelete(array $ids): bool + { + $ok = true; + + foreach ($ids as $id) { + $file = $this->getFile($id); + $ok = (!is_file($file) || $this->doUnlink($file) || !file_exists($file)) && $ok; + } + + return $ok; + } + + protected function doUnlink(string $file): bool + { + return @unlink($file); + } + + private function write(string $file, string $data, ?int $expiresAt = null): bool + { + $unlink = false; + set_error_handler(static fn ($type, $message, $file, $line) => throw new \ErrorException($message, 0, $type, $file, $line)); + try { + $tmp = $this->directory.$this->tmpSuffix ??= str_replace('/', '-', base64_encode(random_bytes(6))); + try { + $h = fopen($tmp, 'x'); + } catch (\ErrorException $e) { + if (!str_contains($e->getMessage(), 'File exists')) { + throw $e; + } + + $tmp = $this->directory.$this->tmpSuffix = str_replace('/', '-', base64_encode(random_bytes(6))); + $h = fopen($tmp, 'x'); + } + fwrite($h, $data); + fclose($h); + $unlink = true; + + if (null !== $expiresAt) { + touch($tmp, $expiresAt ?: time() + 31556952); // 1 year in seconds + } + + if ('\\' === \DIRECTORY_SEPARATOR) { + $success = copy($tmp, $file); + } else { + $success = rename($tmp, $file); + $unlink = !$success; + } + + return $success; + } finally { + restore_error_handler(); + + if ($unlink) { + @unlink($tmp); + } + } + } + + private function getFile(string $id, bool $mkdir = false, ?string $directory = null): string + { + // Use xxh128 to favor speed over security, which is not an issue here + $hash = str_replace('/', '-', base64_encode(hash('xxh128', static::class.$id, true))); + $dir = ($directory ?? $this->directory).strtoupper($hash[0].\DIRECTORY_SEPARATOR.$hash[1].\DIRECTORY_SEPARATOR); + + if ($mkdir && !is_dir($dir)) { + @mkdir($dir, 0777, true); + } + + return $dir.substr($hash, 2, 20); + } + + private function getFileKey(string $file): string + { + return ''; + } + + private function scanHashDir(string $directory): \Generator + { + if (!is_dir($directory)) { + return; + } + + $chars = '+-ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'; + + for ($i = 0; $i < 38; ++$i) { + if (!is_dir($directory.$chars[$i])) { + continue; + } + + for ($j = 0; $j < 38; ++$j) { + if (!is_dir($dir = $directory.$chars[$i].\DIRECTORY_SEPARATOR.$chars[$j])) { + continue; + } + + foreach (@scandir($dir, \SCANDIR_SORT_NONE) ?: [] as $file) { + if ('.' !== $file && '..' !== $file) { + yield $dir.\DIRECTORY_SEPARATOR.$file; + } + } + } + } + } + + public function __sleep(): array + { + throw new \BadMethodCallException('Cannot serialize '.__CLASS__); + } + + public function __wakeup(): void + { + throw new \BadMethodCallException('Cannot unserialize '.__CLASS__); + } + + public function __destruct() + { + if (method_exists(parent::class, '__destruct')) { + parent::__destruct(); + } + if (isset($this->tmpSuffix) && is_file($this->directory.$this->tmpSuffix)) { + unlink($this->directory.$this->tmpSuffix); + } + } +} diff --git a/vendor/symfony/cache/Traits/FilesystemTrait.php b/vendor/symfony/cache/Traits/FilesystemTrait.php new file mode 100644 index 0000000..05fa10e --- /dev/null +++ b/vendor/symfony/cache/Traits/FilesystemTrait.php @@ -0,0 +1,113 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Cache\Traits; + +use Symfony\Component\Cache\Exception\CacheException; +use Symfony\Component\Cache\Marshaller\MarshallerInterface; + +/** + * @author Nicolas Grekas + * @author Rob Frawley 2nd + * + * @internal + */ +trait FilesystemTrait +{ + use FilesystemCommonTrait; + + private MarshallerInterface $marshaller; + + public function prune(): bool + { + $time = time(); + $pruned = true; + + foreach ($this->scanHashDir($this->directory) as $file) { + if (!$h = @fopen($file, 'r')) { + continue; + } + + if (($expiresAt = (int) fgets($h)) && $time >= $expiresAt) { + fclose($h); + $pruned = (@unlink($file) || !file_exists($file)) && $pruned; + } else { + fclose($h); + } + } + + return $pruned; + } + + protected function doFetch(array $ids): iterable + { + $values = []; + $now = time(); + + foreach ($ids as $id) { + $file = $this->getFile($id); + if (!is_file($file) || !$h = @fopen($file, 'r')) { + continue; + } + if (($expiresAt = (int) fgets($h)) && $now >= $expiresAt) { + fclose($h); + @unlink($file); + } else { + $i = rawurldecode(rtrim(fgets($h))); + $value = stream_get_contents($h); + fclose($h); + if ($i === $id) { + $values[$id] = $this->marshaller->unmarshall($value); + } + } + } + + return $values; + } + + protected function doHave(string $id): bool + { + $file = $this->getFile($id); + + return is_file($file) && (@filemtime($file) > time() || $this->doFetch([$id])); + } + + protected function doSave(array $values, int $lifetime): array|bool + { + $expiresAt = $lifetime ? (time() + $lifetime) : 0; + $values = $this->marshaller->marshall($values, $failed); + + foreach ($values as $id => $value) { + if (!$this->write($this->getFile($id, true), $expiresAt."\n".rawurlencode($id)."\n".$value, $expiresAt)) { + $failed[] = $id; + } + } + + if ($failed && !is_writable($this->directory)) { + throw new CacheException(\sprintf('Cache directory is not writable (%s).', $this->directory)); + } + + return $failed; + } + + private function getFileKey(string $file): string + { + if (!$h = @fopen($file, 'r')) { + return ''; + } + + fgets($h); // expiry + $encodedKey = fgets($h); + fclose($h); + + return rawurldecode(rtrim($encodedKey)); + } +} diff --git a/vendor/symfony/cache/Traits/ProxyTrait.php b/vendor/symfony/cache/Traits/ProxyTrait.php new file mode 100644 index 0000000..ba7c11f --- /dev/null +++ b/vendor/symfony/cache/Traits/ProxyTrait.php @@ -0,0 +1,37 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Cache\Traits; + +use Symfony\Component\Cache\PruneableInterface; +use Symfony\Contracts\Service\ResetInterface; + +/** + * @author Nicolas Grekas + * + * @internal + */ +trait ProxyTrait +{ + private object $pool; + + public function prune(): bool + { + return $this->pool instanceof PruneableInterface && $this->pool->prune(); + } + + public function reset(): void + { + if ($this->pool instanceof ResetInterface) { + $this->pool->reset(); + } + } +} diff --git a/vendor/symfony/cache/Traits/Redis5Proxy.php b/vendor/symfony/cache/Traits/Redis5Proxy.php new file mode 100644 index 0000000..b2402f2 --- /dev/null +++ b/vendor/symfony/cache/Traits/Redis5Proxy.php @@ -0,0 +1,1225 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Cache\Traits; + +use Symfony\Component\VarExporter\LazyObjectInterface; +use Symfony\Contracts\Service\ResetInterface; + +// Help opcache.preload discover always-needed symbols +class_exists(\Symfony\Component\VarExporter\Internal\Hydrator::class); +class_exists(\Symfony\Component\VarExporter\Internal\LazyObjectRegistry::class); +class_exists(\Symfony\Component\VarExporter\Internal\LazyObjectState::class); + +/** + * @internal + */ +class Redis5Proxy extends \Redis implements ResetInterface, LazyObjectInterface +{ + use RedisProxyTrait { + resetLazyObject as reset; + } + + public function __construct() + { + $this->initializeLazyObject()->__construct(...\func_get_args()); + } + + public function _prefix($key) + { + return $this->initializeLazyObject()->_prefix(...\func_get_args()); + } + + public function _serialize($value) + { + return $this->initializeLazyObject()->_serialize(...\func_get_args()); + } + + public function _unserialize($value) + { + return $this->initializeLazyObject()->_unserialize(...\func_get_args()); + } + + public function _pack($value) + { + return $this->initializeLazyObject()->_pack(...\func_get_args()); + } + + public function _unpack($value) + { + return $this->initializeLazyObject()->_unpack(...\func_get_args()); + } + + public function _compress($value) + { + return $this->initializeLazyObject()->_compress(...\func_get_args()); + } + + public function _uncompress($value) + { + return $this->initializeLazyObject()->_uncompress(...\func_get_args()); + } + + public function acl($subcmd, ...$args) + { + return $this->initializeLazyObject()->acl(...\func_get_args()); + } + + public function append($key, $value) + { + return $this->initializeLazyObject()->append(...\func_get_args()); + } + + public function auth(#[\SensitiveParameter] $auth) + { + return $this->initializeLazyObject()->auth(...\func_get_args()); + } + + public function bgSave() + { + return $this->initializeLazyObject()->bgSave(...\func_get_args()); + } + + public function bgrewriteaof() + { + return $this->initializeLazyObject()->bgrewriteaof(...\func_get_args()); + } + + public function bitcount($key) + { + return $this->initializeLazyObject()->bitcount(...\func_get_args()); + } + + public function bitop($operation, $ret_key, $key, ...$other_keys) + { + return $this->initializeLazyObject()->bitop(...\func_get_args()); + } + + public function bitpos($key, $bit, $start = null, $end = null) + { + return $this->initializeLazyObject()->bitpos(...\func_get_args()); + } + + public function blPop($key, $timeout_or_key, ...$extra_args) + { + return $this->initializeLazyObject()->blPop(...\func_get_args()); + } + + public function brPop($key, $timeout_or_key, ...$extra_args) + { + return $this->initializeLazyObject()->brPop(...\func_get_args()); + } + + public function brpoplpush($src, $dst, $timeout) + { + return $this->initializeLazyObject()->brpoplpush(...\func_get_args()); + } + + public function bzPopMax($key, $timeout_or_key, ...$extra_args) + { + return $this->initializeLazyObject()->bzPopMax(...\func_get_args()); + } + + public function bzPopMin($key, $timeout_or_key, ...$extra_args) + { + return $this->initializeLazyObject()->bzPopMin(...\func_get_args()); + } + + public function clearLastError() + { + return $this->initializeLazyObject()->clearLastError(...\func_get_args()); + } + + public function client($cmd, ...$args) + { + return $this->initializeLazyObject()->client(...\func_get_args()); + } + + public function close() + { + return $this->initializeLazyObject()->close(...\func_get_args()); + } + + public function command(...$args) + { + return $this->initializeLazyObject()->command(...\func_get_args()); + } + + public function config($cmd, $key, $value = null) + { + return $this->initializeLazyObject()->config(...\func_get_args()); + } + + public function connect($host, $port = null, $timeout = null, $retry_interval = null) + { + return $this->initializeLazyObject()->connect(...\func_get_args()); + } + + public function dbSize() + { + return $this->initializeLazyObject()->dbSize(...\func_get_args()); + } + + public function debug($key) + { + return $this->initializeLazyObject()->debug(...\func_get_args()); + } + + public function decr($key) + { + return $this->initializeLazyObject()->decr(...\func_get_args()); + } + + public function decrBy($key, $value) + { + return $this->initializeLazyObject()->decrBy(...\func_get_args()); + } + + public function del($key, ...$other_keys) + { + return $this->initializeLazyObject()->del(...\func_get_args()); + } + + public function discard() + { + return $this->initializeLazyObject()->discard(...\func_get_args()); + } + + public function dump($key) + { + return $this->initializeLazyObject()->dump(...\func_get_args()); + } + + public function echo($msg) + { + return $this->initializeLazyObject()->echo(...\func_get_args()); + } + + public function eval($script, $args = null, $num_keys = null) + { + return $this->initializeLazyObject()->eval(...\func_get_args()); + } + + public function evalsha($script_sha, $args = null, $num_keys = null) + { + return $this->initializeLazyObject()->evalsha(...\func_get_args()); + } + + public function exec() + { + return $this->initializeLazyObject()->exec(...\func_get_args()); + } + + public function exists($key, ...$other_keys) + { + return $this->initializeLazyObject()->exists(...\func_get_args()); + } + + public function expire($key, $timeout) + { + return $this->initializeLazyObject()->expire(...\func_get_args()); + } + + public function expireAt($key, $timestamp) + { + return $this->initializeLazyObject()->expireAt(...\func_get_args()); + } + + public function flushAll($async = null) + { + return $this->initializeLazyObject()->flushAll(...\func_get_args()); + } + + public function flushDB($async = null) + { + return $this->initializeLazyObject()->flushDB(...\func_get_args()); + } + + public function geoadd($key, $lng, $lat, $member, ...$other_triples) + { + return $this->initializeLazyObject()->geoadd(...\func_get_args()); + } + + public function geodist($key, $src, $dst, $unit = null) + { + return $this->initializeLazyObject()->geodist(...\func_get_args()); + } + + public function geohash($key, $member, ...$other_members) + { + return $this->initializeLazyObject()->geohash(...\func_get_args()); + } + + public function geopos($key, $member, ...$other_members) + { + return $this->initializeLazyObject()->geopos(...\func_get_args()); + } + + public function georadius($key, $lng, $lan, $radius, $unit, $opts = null) + { + return $this->initializeLazyObject()->georadius(...\func_get_args()); + } + + public function georadius_ro($key, $lng, $lan, $radius, $unit, $opts = null) + { + return $this->initializeLazyObject()->georadius_ro(...\func_get_args()); + } + + public function georadiusbymember($key, $member, $radius, $unit, $opts = null) + { + return $this->initializeLazyObject()->georadiusbymember(...\func_get_args()); + } + + public function georadiusbymember_ro($key, $member, $radius, $unit, $opts = null) + { + return $this->initializeLazyObject()->georadiusbymember_ro(...\func_get_args()); + } + + public function get($key) + { + return $this->initializeLazyObject()->get(...\func_get_args()); + } + + public function getAuth() + { + return $this->initializeLazyObject()->getAuth(...\func_get_args()); + } + + public function getBit($key, $offset) + { + return $this->initializeLazyObject()->getBit(...\func_get_args()); + } + + public function getDBNum() + { + return $this->initializeLazyObject()->getDBNum(...\func_get_args()); + } + + public function getHost() + { + return $this->initializeLazyObject()->getHost(...\func_get_args()); + } + + public function getLastError() + { + return $this->initializeLazyObject()->getLastError(...\func_get_args()); + } + + public function getMode() + { + return $this->initializeLazyObject()->getMode(...\func_get_args()); + } + + public function getOption($option) + { + return $this->initializeLazyObject()->getOption(...\func_get_args()); + } + + public function getPersistentID() + { + return $this->initializeLazyObject()->getPersistentID(...\func_get_args()); + } + + public function getPort() + { + return $this->initializeLazyObject()->getPort(...\func_get_args()); + } + + public function getRange($key, $start, $end) + { + return $this->initializeLazyObject()->getRange(...\func_get_args()); + } + + public function getReadTimeout() + { + return $this->initializeLazyObject()->getReadTimeout(...\func_get_args()); + } + + public function getSet($key, $value) + { + return $this->initializeLazyObject()->getSet(...\func_get_args()); + } + + public function getTimeout() + { + return $this->initializeLazyObject()->getTimeout(...\func_get_args()); + } + + public function hDel($key, $member, ...$other_members) + { + return $this->initializeLazyObject()->hDel(...\func_get_args()); + } + + public function hExists($key, $member) + { + return $this->initializeLazyObject()->hExists(...\func_get_args()); + } + + public function hGet($key, $member) + { + return $this->initializeLazyObject()->hGet(...\func_get_args()); + } + + public function hGetAll($key) + { + return $this->initializeLazyObject()->hGetAll(...\func_get_args()); + } + + public function hIncrBy($key, $member, $value) + { + return $this->initializeLazyObject()->hIncrBy(...\func_get_args()); + } + + public function hIncrByFloat($key, $member, $value) + { + return $this->initializeLazyObject()->hIncrByFloat(...\func_get_args()); + } + + public function hKeys($key) + { + return $this->initializeLazyObject()->hKeys(...\func_get_args()); + } + + public function hLen($key) + { + return $this->initializeLazyObject()->hLen(...\func_get_args()); + } + + public function hMget($key, $keys) + { + return $this->initializeLazyObject()->hMget(...\func_get_args()); + } + + public function hMset($key, $pairs) + { + return $this->initializeLazyObject()->hMset(...\func_get_args()); + } + + public function hSet($key, $member, $value) + { + return $this->initializeLazyObject()->hSet(...\func_get_args()); + } + + public function hSetNx($key, $member, $value) + { + return $this->initializeLazyObject()->hSetNx(...\func_get_args()); + } + + public function hStrLen($key, $member) + { + return $this->initializeLazyObject()->hStrLen(...\func_get_args()); + } + + public function hVals($key) + { + return $this->initializeLazyObject()->hVals(...\func_get_args()); + } + + public function hscan($str_key, &$i_iterator, $str_pattern = null, $i_count = null) + { + return $this->initializeLazyObject()->hscan($str_key, $i_iterator, ...\array_slice(\func_get_args(), 2)); + } + + public function incr($key) + { + return $this->initializeLazyObject()->incr(...\func_get_args()); + } + + public function incrBy($key, $value) + { + return $this->initializeLazyObject()->incrBy(...\func_get_args()); + } + + public function incrByFloat($key, $value) + { + return $this->initializeLazyObject()->incrByFloat(...\func_get_args()); + } + + public function info($option = null) + { + return $this->initializeLazyObject()->info(...\func_get_args()); + } + + public function isConnected() + { + return $this->initializeLazyObject()->isConnected(...\func_get_args()); + } + + public function keys($pattern) + { + return $this->initializeLazyObject()->keys(...\func_get_args()); + } + + public function lInsert($key, $position, $pivot, $value) + { + return $this->initializeLazyObject()->lInsert(...\func_get_args()); + } + + public function lLen($key) + { + return $this->initializeLazyObject()->lLen(...\func_get_args()); + } + + public function lPop($key) + { + return $this->initializeLazyObject()->lPop(...\func_get_args()); + } + + public function lPush($key, $value) + { + return $this->initializeLazyObject()->lPush(...\func_get_args()); + } + + public function lPushx($key, $value) + { + return $this->initializeLazyObject()->lPushx(...\func_get_args()); + } + + public function lSet($key, $index, $value) + { + return $this->initializeLazyObject()->lSet(...\func_get_args()); + } + + public function lastSave() + { + return $this->initializeLazyObject()->lastSave(...\func_get_args()); + } + + public function lindex($key, $index) + { + return $this->initializeLazyObject()->lindex(...\func_get_args()); + } + + public function lrange($key, $start, $end) + { + return $this->initializeLazyObject()->lrange(...\func_get_args()); + } + + public function lrem($key, $value, $count) + { + return $this->initializeLazyObject()->lrem(...\func_get_args()); + } + + public function ltrim($key, $start, $stop) + { + return $this->initializeLazyObject()->ltrim(...\func_get_args()); + } + + public function mget($keys) + { + return $this->initializeLazyObject()->mget(...\func_get_args()); + } + + public function migrate($host, $port, $key, $db, $timeout, $copy = null, $replace = null) + { + return $this->initializeLazyObject()->migrate(...\func_get_args()); + } + + public function move($key, $dbindex) + { + return $this->initializeLazyObject()->move(...\func_get_args()); + } + + public function mset($pairs) + { + return $this->initializeLazyObject()->mset(...\func_get_args()); + } + + public function msetnx($pairs) + { + return $this->initializeLazyObject()->msetnx(...\func_get_args()); + } + + public function multi($mode = null) + { + return $this->initializeLazyObject()->multi(...\func_get_args()); + } + + public function object($field, $key) + { + return $this->initializeLazyObject()->object(...\func_get_args()); + } + + public function pconnect($host, $port = null, $timeout = null) + { + return $this->initializeLazyObject()->pconnect(...\func_get_args()); + } + + public function persist($key) + { + return $this->initializeLazyObject()->persist(...\func_get_args()); + } + + public function pexpire($key, $timestamp) + { + return $this->initializeLazyObject()->pexpire(...\func_get_args()); + } + + public function pexpireAt($key, $timestamp) + { + return $this->initializeLazyObject()->pexpireAt(...\func_get_args()); + } + + public function pfadd($key, $elements) + { + return $this->initializeLazyObject()->pfadd(...\func_get_args()); + } + + public function pfcount($key) + { + return $this->initializeLazyObject()->pfcount(...\func_get_args()); + } + + public function pfmerge($dstkey, $keys) + { + return $this->initializeLazyObject()->pfmerge(...\func_get_args()); + } + + public function ping() + { + return $this->initializeLazyObject()->ping(...\func_get_args()); + } + + public function pipeline() + { + return $this->initializeLazyObject()->pipeline(...\func_get_args()); + } + + public function psetex($key, $expire, $value) + { + return $this->initializeLazyObject()->psetex(...\func_get_args()); + } + + public function psubscribe($patterns, $callback) + { + return $this->initializeLazyObject()->psubscribe(...\func_get_args()); + } + + public function pttl($key) + { + return $this->initializeLazyObject()->pttl(...\func_get_args()); + } + + public function publish($channel, $message) + { + return $this->initializeLazyObject()->publish(...\func_get_args()); + } + + public function pubsub($cmd, ...$args) + { + return $this->initializeLazyObject()->pubsub(...\func_get_args()); + } + + public function punsubscribe($pattern, ...$other_patterns) + { + return $this->initializeLazyObject()->punsubscribe(...\func_get_args()); + } + + public function rPop($key) + { + return $this->initializeLazyObject()->rPop(...\func_get_args()); + } + + public function rPush($key, $value) + { + return $this->initializeLazyObject()->rPush(...\func_get_args()); + } + + public function rPushx($key, $value) + { + return $this->initializeLazyObject()->rPushx(...\func_get_args()); + } + + public function randomKey() + { + return $this->initializeLazyObject()->randomKey(...\func_get_args()); + } + + public function rawcommand($cmd, ...$args) + { + return $this->initializeLazyObject()->rawcommand(...\func_get_args()); + } + + public function rename($key, $newkey) + { + return $this->initializeLazyObject()->rename(...\func_get_args()); + } + + public function renameNx($key, $newkey) + { + return $this->initializeLazyObject()->renameNx(...\func_get_args()); + } + + public function restore($ttl, $key, $value) + { + return $this->initializeLazyObject()->restore(...\func_get_args()); + } + + public function role() + { + return $this->initializeLazyObject()->role(...\func_get_args()); + } + + public function rpoplpush($src, $dst) + { + return $this->initializeLazyObject()->rpoplpush(...\func_get_args()); + } + + public function sAdd($key, $value) + { + return $this->initializeLazyObject()->sAdd(...\func_get_args()); + } + + public function sAddArray($key, $options) + { + return $this->initializeLazyObject()->sAddArray(...\func_get_args()); + } + + public function sDiff($key, ...$other_keys) + { + return $this->initializeLazyObject()->sDiff(...\func_get_args()); + } + + public function sDiffStore($dst, $key, ...$other_keys) + { + return $this->initializeLazyObject()->sDiffStore(...\func_get_args()); + } + + public function sInter($key, ...$other_keys) + { + return $this->initializeLazyObject()->sInter(...\func_get_args()); + } + + public function sInterStore($dst, $key, ...$other_keys) + { + return $this->initializeLazyObject()->sInterStore(...\func_get_args()); + } + + public function sMembers($key) + { + return $this->initializeLazyObject()->sMembers(...\func_get_args()); + } + + public function sMisMember($key, $member, ...$other_members) + { + return $this->initializeLazyObject()->sMisMember(...\func_get_args()); + } + + public function sMove($src, $dst, $value) + { + return $this->initializeLazyObject()->sMove(...\func_get_args()); + } + + public function sPop($key) + { + return $this->initializeLazyObject()->sPop(...\func_get_args()); + } + + public function sRandMember($key, $count = null) + { + return $this->initializeLazyObject()->sRandMember(...\func_get_args()); + } + + public function sUnion($key, ...$other_keys) + { + return $this->initializeLazyObject()->sUnion(...\func_get_args()); + } + + public function sUnionStore($dst, $key, ...$other_keys) + { + return $this->initializeLazyObject()->sUnionStore(...\func_get_args()); + } + + public function save() + { + return $this->initializeLazyObject()->save(...\func_get_args()); + } + + public function scan(&$i_iterator, $str_pattern = null, $i_count = null) + { + return $this->initializeLazyObject()->scan($i_iterator, ...\array_slice(\func_get_args(), 1)); + } + + public function scard($key) + { + return $this->initializeLazyObject()->scard(...\func_get_args()); + } + + public function script($cmd, ...$args) + { + return $this->initializeLazyObject()->script(...\func_get_args()); + } + + public function select($dbindex) + { + return $this->initializeLazyObject()->select(...\func_get_args()); + } + + public function set($key, $value, $opts = null) + { + return $this->initializeLazyObject()->set(...\func_get_args()); + } + + public function setBit($key, $offset, $value) + { + return $this->initializeLazyObject()->setBit(...\func_get_args()); + } + + public function setOption($option, $value) + { + return $this->initializeLazyObject()->setOption(...\func_get_args()); + } + + public function setRange($key, $offset, $value) + { + return $this->initializeLazyObject()->setRange(...\func_get_args()); + } + + public function setex($key, $expire, $value) + { + return $this->initializeLazyObject()->setex(...\func_get_args()); + } + + public function setnx($key, $value) + { + return $this->initializeLazyObject()->setnx(...\func_get_args()); + } + + public function sismember($key, $value) + { + return $this->initializeLazyObject()->sismember(...\func_get_args()); + } + + public function slaveof($host = null, $port = null) + { + return $this->initializeLazyObject()->slaveof(...\func_get_args()); + } + + public function slowlog($arg, $option = null) + { + return $this->initializeLazyObject()->slowlog(...\func_get_args()); + } + + public function sort($key, $options = null) + { + return $this->initializeLazyObject()->sort(...\func_get_args()); + } + + public function sortAsc($key, $pattern = null, $get = null, $start = null, $end = null, $getList = null) + { + return $this->initializeLazyObject()->sortAsc(...\func_get_args()); + } + + public function sortAscAlpha($key, $pattern = null, $get = null, $start = null, $end = null, $getList = null) + { + return $this->initializeLazyObject()->sortAscAlpha(...\func_get_args()); + } + + public function sortDesc($key, $pattern = null, $get = null, $start = null, $end = null, $getList = null) + { + return $this->initializeLazyObject()->sortDesc(...\func_get_args()); + } + + public function sortDescAlpha($key, $pattern = null, $get = null, $start = null, $end = null, $getList = null) + { + return $this->initializeLazyObject()->sortDescAlpha(...\func_get_args()); + } + + public function srem($key, $member, ...$other_members) + { + return $this->initializeLazyObject()->srem(...\func_get_args()); + } + + public function sscan($str_key, &$i_iterator, $str_pattern = null, $i_count = null) + { + return $this->initializeLazyObject()->sscan($str_key, $i_iterator, ...\array_slice(\func_get_args(), 2)); + } + + public function strlen($key) + { + return $this->initializeLazyObject()->strlen(...\func_get_args()); + } + + public function subscribe($channels, $callback) + { + return $this->initializeLazyObject()->subscribe(...\func_get_args()); + } + + public function swapdb($srcdb, $dstdb) + { + return $this->initializeLazyObject()->swapdb(...\func_get_args()); + } + + public function time() + { + return $this->initializeLazyObject()->time(...\func_get_args()); + } + + public function ttl($key) + { + return $this->initializeLazyObject()->ttl(...\func_get_args()); + } + + public function type($key) + { + return $this->initializeLazyObject()->type(...\func_get_args()); + } + + public function unlink($key, ...$other_keys) + { + return $this->initializeLazyObject()->unlink(...\func_get_args()); + } + + public function unsubscribe($channel, ...$other_channels) + { + return $this->initializeLazyObject()->unsubscribe(...\func_get_args()); + } + + public function unwatch() + { + return $this->initializeLazyObject()->unwatch(...\func_get_args()); + } + + public function wait($numslaves, $timeout) + { + return $this->initializeLazyObject()->wait(...\func_get_args()); + } + + public function watch($key, ...$other_keys) + { + return $this->initializeLazyObject()->watch(...\func_get_args()); + } + + public function xack($str_key, $str_group, $arr_ids) + { + return $this->initializeLazyObject()->xack(...\func_get_args()); + } + + public function xadd($str_key, $str_id, $arr_fields, $i_maxlen = null, $boo_approximate = null) + { + return $this->initializeLazyObject()->xadd(...\func_get_args()); + } + + public function xclaim($str_key, $str_group, $str_consumer, $i_min_idle, $arr_ids, $arr_opts = null) + { + return $this->initializeLazyObject()->xclaim(...\func_get_args()); + } + + public function xdel($str_key, $arr_ids) + { + return $this->initializeLazyObject()->xdel(...\func_get_args()); + } + + public function xgroup($str_operation, $str_key = null, $str_arg1 = null, $str_arg2 = null, $str_arg3 = null) + { + return $this->initializeLazyObject()->xgroup(...\func_get_args()); + } + + public function xinfo($str_cmd, $str_key = null, $str_group = null) + { + return $this->initializeLazyObject()->xinfo(...\func_get_args()); + } + + public function xlen($key) + { + return $this->initializeLazyObject()->xlen(...\func_get_args()); + } + + public function xpending($str_key, $str_group, $str_start = null, $str_end = null, $i_count = null, $str_consumer = null) + { + return $this->initializeLazyObject()->xpending(...\func_get_args()); + } + + public function xrange($str_key, $str_start, $str_end, $i_count = null) + { + return $this->initializeLazyObject()->xrange(...\func_get_args()); + } + + public function xread($arr_streams, $i_count = null, $i_block = null) + { + return $this->initializeLazyObject()->xread(...\func_get_args()); + } + + public function xreadgroup($str_group, $str_consumer, $arr_streams, $i_count = null, $i_block = null) + { + return $this->initializeLazyObject()->xreadgroup(...\func_get_args()); + } + + public function xrevrange($str_key, $str_start, $str_end, $i_count = null) + { + return $this->initializeLazyObject()->xrevrange(...\func_get_args()); + } + + public function xtrim($str_key, $i_maxlen, $boo_approximate = null) + { + return $this->initializeLazyObject()->xtrim(...\func_get_args()); + } + + public function zAdd($key, $score, $value, ...$extra_args) + { + return $this->initializeLazyObject()->zAdd(...\func_get_args()); + } + + public function zCard($key) + { + return $this->initializeLazyObject()->zCard(...\func_get_args()); + } + + public function zCount($key, $min, $max) + { + return $this->initializeLazyObject()->zCount(...\func_get_args()); + } + + public function zIncrBy($key, $value, $member) + { + return $this->initializeLazyObject()->zIncrBy(...\func_get_args()); + } + + public function zLexCount($key, $min, $max) + { + return $this->initializeLazyObject()->zLexCount(...\func_get_args()); + } + + public function zPopMax($key) + { + return $this->initializeLazyObject()->zPopMax(...\func_get_args()); + } + + public function zPopMin($key) + { + return $this->initializeLazyObject()->zPopMin(...\func_get_args()); + } + + public function zRange($key, $start, $end, $scores = null) + { + return $this->initializeLazyObject()->zRange(...\func_get_args()); + } + + public function zRangeByLex($key, $min, $max, $offset = null, $limit = null) + { + return $this->initializeLazyObject()->zRangeByLex(...\func_get_args()); + } + + public function zRangeByScore($key, $start, $end, $options = null) + { + return $this->initializeLazyObject()->zRangeByScore(...\func_get_args()); + } + + public function zRank($key, $member) + { + return $this->initializeLazyObject()->zRank(...\func_get_args()); + } + + public function zRem($key, $member, ...$other_members) + { + return $this->initializeLazyObject()->zRem(...\func_get_args()); + } + + public function zRemRangeByLex($key, $min, $max) + { + return $this->initializeLazyObject()->zRemRangeByLex(...\func_get_args()); + } + + public function zRemRangeByRank($key, $start, $end) + { + return $this->initializeLazyObject()->zRemRangeByRank(...\func_get_args()); + } + + public function zRemRangeByScore($key, $min, $max) + { + return $this->initializeLazyObject()->zRemRangeByScore(...\func_get_args()); + } + + public function zRevRange($key, $start, $end, $scores = null) + { + return $this->initializeLazyObject()->zRevRange(...\func_get_args()); + } + + public function zRevRangeByLex($key, $min, $max, $offset = null, $limit = null) + { + return $this->initializeLazyObject()->zRevRangeByLex(...\func_get_args()); + } + + public function zRevRangeByScore($key, $start, $end, $options = null) + { + return $this->initializeLazyObject()->zRevRangeByScore(...\func_get_args()); + } + + public function zRevRank($key, $member) + { + return $this->initializeLazyObject()->zRevRank(...\func_get_args()); + } + + public function zScore($key, $member) + { + return $this->initializeLazyObject()->zScore(...\func_get_args()); + } + + public function zinterstore($key, $keys, $weights = null, $aggregate = null) + { + return $this->initializeLazyObject()->zinterstore(...\func_get_args()); + } + + public function zscan($str_key, &$i_iterator, $str_pattern = null, $i_count = null) + { + return $this->initializeLazyObject()->zscan($str_key, $i_iterator, ...\array_slice(\func_get_args(), 2)); + } + + public function zunionstore($key, $keys, $weights = null, $aggregate = null) + { + return $this->initializeLazyObject()->zunionstore(...\func_get_args()); + } + + public function delete($key, ...$other_keys) + { + return $this->initializeLazyObject()->delete(...\func_get_args()); + } + + public function evaluate($script, $args = null, $num_keys = null) + { + return $this->initializeLazyObject()->evaluate(...\func_get_args()); + } + + public function evaluateSha($script_sha, $args = null, $num_keys = null) + { + return $this->initializeLazyObject()->evaluateSha(...\func_get_args()); + } + + public function getKeys($pattern) + { + return $this->initializeLazyObject()->getKeys(...\func_get_args()); + } + + public function getMultiple($keys) + { + return $this->initializeLazyObject()->getMultiple(...\func_get_args()); + } + + public function lGet($key, $index) + { + return $this->initializeLazyObject()->lGet(...\func_get_args()); + } + + public function lGetRange($key, $start, $end) + { + return $this->initializeLazyObject()->lGetRange(...\func_get_args()); + } + + public function lRemove($key, $value, $count) + { + return $this->initializeLazyObject()->lRemove(...\func_get_args()); + } + + public function lSize($key) + { + return $this->initializeLazyObject()->lSize(...\func_get_args()); + } + + public function listTrim($key, $start, $stop) + { + return $this->initializeLazyObject()->listTrim(...\func_get_args()); + } + + public function open($host, $port = null, $timeout = null, $retry_interval = null) + { + return $this->initializeLazyObject()->open(...\func_get_args()); + } + + public function popen($host, $port = null, $timeout = null) + { + return $this->initializeLazyObject()->popen(...\func_get_args()); + } + + public function renameKey($key, $newkey) + { + return $this->initializeLazyObject()->renameKey(...\func_get_args()); + } + + public function sContains($key, $value) + { + return $this->initializeLazyObject()->sContains(...\func_get_args()); + } + + public function sGetMembers($key) + { + return $this->initializeLazyObject()->sGetMembers(...\func_get_args()); + } + + public function sRemove($key, $member, ...$other_members) + { + return $this->initializeLazyObject()->sRemove(...\func_get_args()); + } + + public function sSize($key) + { + return $this->initializeLazyObject()->sSize(...\func_get_args()); + } + + public function sendEcho($msg) + { + return $this->initializeLazyObject()->sendEcho(...\func_get_args()); + } + + public function setTimeout($key, $timeout) + { + return $this->initializeLazyObject()->setTimeout(...\func_get_args()); + } + + public function substr($key, $start, $end) + { + return $this->initializeLazyObject()->substr(...\func_get_args()); + } + + public function zDelete($key, $member, ...$other_members) + { + return $this->initializeLazyObject()->zDelete(...\func_get_args()); + } + + public function zDeleteRangeByRank($key, $min, $max) + { + return $this->initializeLazyObject()->zDeleteRangeByRank(...\func_get_args()); + } + + public function zDeleteRangeByScore($key, $min, $max) + { + return $this->initializeLazyObject()->zDeleteRangeByScore(...\func_get_args()); + } + + public function zInter($key, $keys, $weights = null, $aggregate = null) + { + return $this->initializeLazyObject()->zInter(...\func_get_args()); + } + + public function zRemove($key, $member, ...$other_members) + { + return $this->initializeLazyObject()->zRemove(...\func_get_args()); + } + + public function zRemoveRangeByScore($key, $min, $max) + { + return $this->initializeLazyObject()->zRemoveRangeByScore(...\func_get_args()); + } + + public function zReverseRange($key, $start, $end, $scores = null) + { + return $this->initializeLazyObject()->zReverseRange(...\func_get_args()); + } + + public function zSize($key) + { + return $this->initializeLazyObject()->zSize(...\func_get_args()); + } + + public function zUnion($key, $keys, $weights = null, $aggregate = null) + { + return $this->initializeLazyObject()->zUnion(...\func_get_args()); + } +} diff --git a/vendor/symfony/cache/Traits/Redis6Proxy.php b/vendor/symfony/cache/Traits/Redis6Proxy.php new file mode 100644 index 0000000..c7e05cd --- /dev/null +++ b/vendor/symfony/cache/Traits/Redis6Proxy.php @@ -0,0 +1,1266 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Cache\Traits; + +use Symfony\Component\VarExporter\LazyObjectInterface; +use Symfony\Contracts\Service\ResetInterface; + +// Help opcache.preload discover always-needed symbols +class_exists(\Symfony\Component\VarExporter\Internal\Hydrator::class); +class_exists(\Symfony\Component\VarExporter\Internal\LazyObjectRegistry::class); +class_exists(\Symfony\Component\VarExporter\Internal\LazyObjectState::class); + +/** + * @internal + */ +class Redis6Proxy extends \Redis implements ResetInterface, LazyObjectInterface +{ + use Redis6ProxyTrait; + use RedisProxyTrait { + resetLazyObject as reset; + } + + public function __construct($options = null) + { + $this->initializeLazyObject()->__construct(...\func_get_args()); + } + + public function _compress($value): string + { + return $this->initializeLazyObject()->_compress(...\func_get_args()); + } + + public function _uncompress($value): string + { + return $this->initializeLazyObject()->_uncompress(...\func_get_args()); + } + + public function _prefix($key): string + { + return $this->initializeLazyObject()->_prefix(...\func_get_args()); + } + + public function _serialize($value): string + { + return $this->initializeLazyObject()->_serialize(...\func_get_args()); + } + + public function _unserialize($value): mixed + { + return $this->initializeLazyObject()->_unserialize(...\func_get_args()); + } + + public function _pack($value): string + { + return $this->initializeLazyObject()->_pack(...\func_get_args()); + } + + public function _unpack($value): mixed + { + return $this->initializeLazyObject()->_unpack(...\func_get_args()); + } + + public function acl($subcmd, ...$args): mixed + { + return $this->initializeLazyObject()->acl(...\func_get_args()); + } + + public function append($key, $value): \Redis|false|int + { + return $this->initializeLazyObject()->append(...\func_get_args()); + } + + public function auth(#[\SensitiveParameter] $credentials): \Redis|bool + { + return $this->initializeLazyObject()->auth(...\func_get_args()); + } + + public function bgSave(): \Redis|bool + { + return $this->initializeLazyObject()->bgSave(...\func_get_args()); + } + + public function bgrewriteaof(): \Redis|bool + { + return $this->initializeLazyObject()->bgrewriteaof(...\func_get_args()); + } + + public function bitcount($key, $start = 0, $end = -1, $bybit = false): \Redis|false|int + { + return $this->initializeLazyObject()->bitcount(...\func_get_args()); + } + + public function bitop($operation, $deskey, $srckey, ...$other_keys): \Redis|false|int + { + return $this->initializeLazyObject()->bitop(...\func_get_args()); + } + + public function bitpos($key, $bit, $start = 0, $end = -1, $bybit = false): \Redis|false|int + { + return $this->initializeLazyObject()->bitpos(...\func_get_args()); + } + + public function blPop($key_or_keys, $timeout_or_key, ...$extra_args): \Redis|array|false|null + { + return $this->initializeLazyObject()->blPop(...\func_get_args()); + } + + public function brPop($key_or_keys, $timeout_or_key, ...$extra_args): \Redis|array|false|null + { + return $this->initializeLazyObject()->brPop(...\func_get_args()); + } + + public function brpoplpush($src, $dst, $timeout): \Redis|false|string + { + return $this->initializeLazyObject()->brpoplpush(...\func_get_args()); + } + + public function bzPopMax($key, $timeout_or_key, ...$extra_args): \Redis|array|false + { + return $this->initializeLazyObject()->bzPopMax(...\func_get_args()); + } + + public function bzPopMin($key, $timeout_or_key, ...$extra_args): \Redis|array|false + { + return $this->initializeLazyObject()->bzPopMin(...\func_get_args()); + } + + public function bzmpop($timeout, $keys, $from, $count = 1): \Redis|array|false|null + { + return $this->initializeLazyObject()->bzmpop(...\func_get_args()); + } + + public function zmpop($keys, $from, $count = 1): \Redis|array|false|null + { + return $this->initializeLazyObject()->zmpop(...\func_get_args()); + } + + public function blmpop($timeout, $keys, $from, $count = 1): \Redis|array|false|null + { + return $this->initializeLazyObject()->blmpop(...\func_get_args()); + } + + public function lmpop($keys, $from, $count = 1): \Redis|array|false|null + { + return $this->initializeLazyObject()->lmpop(...\func_get_args()); + } + + public function clearLastError(): bool + { + return $this->initializeLazyObject()->clearLastError(...\func_get_args()); + } + + public function client($opt, ...$args): mixed + { + return $this->initializeLazyObject()->client(...\func_get_args()); + } + + public function close(): bool + { + return $this->initializeLazyObject()->close(...\func_get_args()); + } + + public function command($opt = null, ...$args): mixed + { + return $this->initializeLazyObject()->command(...\func_get_args()); + } + + public function config($operation, $key_or_settings = null, $value = null): mixed + { + return $this->initializeLazyObject()->config(...\func_get_args()); + } + + public function connect($host, $port = 6379, $timeout = 0, $persistent_id = null, $retry_interval = 0, $read_timeout = 0, $context = null): bool + { + return $this->initializeLazyObject()->connect(...\func_get_args()); + } + + public function copy($src, $dst, $options = null): \Redis|bool + { + return $this->initializeLazyObject()->copy(...\func_get_args()); + } + + public function dbSize(): \Redis|false|int + { + return $this->initializeLazyObject()->dbSize(...\func_get_args()); + } + + public function debug($key): \Redis|string + { + return $this->initializeLazyObject()->debug(...\func_get_args()); + } + + public function decr($key, $by = 1): \Redis|false|int + { + return $this->initializeLazyObject()->decr(...\func_get_args()); + } + + public function decrBy($key, $value): \Redis|false|int + { + return $this->initializeLazyObject()->decrBy(...\func_get_args()); + } + + public function del($key, ...$other_keys): \Redis|false|int + { + return $this->initializeLazyObject()->del(...\func_get_args()); + } + + public function delete($key, ...$other_keys): \Redis|false|int + { + return $this->initializeLazyObject()->delete(...\func_get_args()); + } + + public function discard(): \Redis|bool + { + return $this->initializeLazyObject()->discard(...\func_get_args()); + } + + public function echo($str): \Redis|false|string + { + return $this->initializeLazyObject()->echo(...\func_get_args()); + } + + public function eval($script, $args = [], $num_keys = 0): mixed + { + return $this->initializeLazyObject()->eval(...\func_get_args()); + } + + public function eval_ro($script_sha, $args = [], $num_keys = 0): mixed + { + return $this->initializeLazyObject()->eval_ro(...\func_get_args()); + } + + public function evalsha($sha1, $args = [], $num_keys = 0): mixed + { + return $this->initializeLazyObject()->evalsha(...\func_get_args()); + } + + public function evalsha_ro($sha1, $args = [], $num_keys = 0): mixed + { + return $this->initializeLazyObject()->evalsha_ro(...\func_get_args()); + } + + public function exec(): \Redis|array|false + { + return $this->initializeLazyObject()->exec(...\func_get_args()); + } + + public function exists($key, ...$other_keys): \Redis|bool|int + { + return $this->initializeLazyObject()->exists(...\func_get_args()); + } + + public function expire($key, $timeout, $mode = null): \Redis|bool + { + return $this->initializeLazyObject()->expire(...\func_get_args()); + } + + public function expireAt($key, $timestamp, $mode = null): \Redis|bool + { + return $this->initializeLazyObject()->expireAt(...\func_get_args()); + } + + public function failover($to = null, $abort = false, $timeout = 0): \Redis|bool + { + return $this->initializeLazyObject()->failover(...\func_get_args()); + } + + public function expiretime($key): \Redis|false|int + { + return $this->initializeLazyObject()->expiretime(...\func_get_args()); + } + + public function pexpiretime($key): \Redis|false|int + { + return $this->initializeLazyObject()->pexpiretime(...\func_get_args()); + } + + public function fcall($fn, $keys = [], $args = []): mixed + { + return $this->initializeLazyObject()->fcall(...\func_get_args()); + } + + public function fcall_ro($fn, $keys = [], $args = []): mixed + { + return $this->initializeLazyObject()->fcall_ro(...\func_get_args()); + } + + public function flushAll($sync = null): \Redis|bool + { + return $this->initializeLazyObject()->flushAll(...\func_get_args()); + } + + public function flushDB($sync = null): \Redis|bool + { + return $this->initializeLazyObject()->flushDB(...\func_get_args()); + } + + public function function($operation, ...$args): \Redis|array|bool|string + { + return $this->initializeLazyObject()->function(...\func_get_args()); + } + + public function geoadd($key, $lng, $lat, $member, ...$other_triples_and_options): \Redis|false|int + { + return $this->initializeLazyObject()->geoadd(...\func_get_args()); + } + + public function geodist($key, $src, $dst, $unit = null): \Redis|false|float + { + return $this->initializeLazyObject()->geodist(...\func_get_args()); + } + + public function geohash($key, $member, ...$other_members): \Redis|array|false + { + return $this->initializeLazyObject()->geohash(...\func_get_args()); + } + + public function geopos($key, $member, ...$other_members): \Redis|array|false + { + return $this->initializeLazyObject()->geopos(...\func_get_args()); + } + + public function georadius($key, $lng, $lat, $radius, $unit, $options = []): mixed + { + return $this->initializeLazyObject()->georadius(...\func_get_args()); + } + + public function georadius_ro($key, $lng, $lat, $radius, $unit, $options = []): mixed + { + return $this->initializeLazyObject()->georadius_ro(...\func_get_args()); + } + + public function georadiusbymember($key, $member, $radius, $unit, $options = []): mixed + { + return $this->initializeLazyObject()->georadiusbymember(...\func_get_args()); + } + + public function georadiusbymember_ro($key, $member, $radius, $unit, $options = []): mixed + { + return $this->initializeLazyObject()->georadiusbymember_ro(...\func_get_args()); + } + + public function geosearch($key, $position, $shape, $unit, $options = []): array + { + return $this->initializeLazyObject()->geosearch(...\func_get_args()); + } + + public function geosearchstore($dst, $src, $position, $shape, $unit, $options = []): \Redis|array|false|int + { + return $this->initializeLazyObject()->geosearchstore(...\func_get_args()); + } + + public function get($key): mixed + { + return $this->initializeLazyObject()->get(...\func_get_args()); + } + + public function getAuth(): mixed + { + return $this->initializeLazyObject()->getAuth(...\func_get_args()); + } + + public function getBit($key, $idx): \Redis|false|int + { + return $this->initializeLazyObject()->getBit(...\func_get_args()); + } + + public function getEx($key, $options = []): \Redis|bool|string + { + return $this->initializeLazyObject()->getEx(...\func_get_args()); + } + + public function getDBNum(): int + { + return $this->initializeLazyObject()->getDBNum(...\func_get_args()); + } + + public function getDel($key): \Redis|bool|string + { + return $this->initializeLazyObject()->getDel(...\func_get_args()); + } + + public function getHost(): string + { + return $this->initializeLazyObject()->getHost(...\func_get_args()); + } + + public function getLastError(): ?string + { + return $this->initializeLazyObject()->getLastError(...\func_get_args()); + } + + public function getMode(): int + { + return $this->initializeLazyObject()->getMode(...\func_get_args()); + } + + public function getOption($option): mixed + { + return $this->initializeLazyObject()->getOption(...\func_get_args()); + } + + public function getPersistentID(): ?string + { + return $this->initializeLazyObject()->getPersistentID(...\func_get_args()); + } + + public function getPort(): int + { + return $this->initializeLazyObject()->getPort(...\func_get_args()); + } + + public function getRange($key, $start, $end): \Redis|false|string + { + return $this->initializeLazyObject()->getRange(...\func_get_args()); + } + + public function lcs($key1, $key2, $options = null): \Redis|array|false|int|string + { + return $this->initializeLazyObject()->lcs(...\func_get_args()); + } + + public function getReadTimeout(): float + { + return $this->initializeLazyObject()->getReadTimeout(...\func_get_args()); + } + + public function getset($key, $value): \Redis|false|string + { + return $this->initializeLazyObject()->getset(...\func_get_args()); + } + + public function getTimeout(): false|float + { + return $this->initializeLazyObject()->getTimeout(...\func_get_args()); + } + + public function getTransferredBytes(): array + { + return $this->initializeLazyObject()->getTransferredBytes(...\func_get_args()); + } + + public function clearTransferredBytes(): void + { + $this->initializeLazyObject()->clearTransferredBytes(...\func_get_args()); + } + + public function hDel($key, $field, ...$other_fields): \Redis|false|int + { + return $this->initializeLazyObject()->hDel(...\func_get_args()); + } + + public function hExists($key, $field): \Redis|bool + { + return $this->initializeLazyObject()->hExists(...\func_get_args()); + } + + public function hGet($key, $member): mixed + { + return $this->initializeLazyObject()->hGet(...\func_get_args()); + } + + public function hGetAll($key): \Redis|array|false + { + return $this->initializeLazyObject()->hGetAll(...\func_get_args()); + } + + public function hIncrBy($key, $field, $value): \Redis|false|int + { + return $this->initializeLazyObject()->hIncrBy(...\func_get_args()); + } + + public function hIncrByFloat($key, $field, $value): \Redis|false|float + { + return $this->initializeLazyObject()->hIncrByFloat(...\func_get_args()); + } + + public function hKeys($key): \Redis|array|false + { + return $this->initializeLazyObject()->hKeys(...\func_get_args()); + } + + public function hLen($key): \Redis|false|int + { + return $this->initializeLazyObject()->hLen(...\func_get_args()); + } + + public function hMget($key, $fields): \Redis|array|false + { + return $this->initializeLazyObject()->hMget(...\func_get_args()); + } + + public function hMset($key, $fieldvals): \Redis|bool + { + return $this->initializeLazyObject()->hMset(...\func_get_args()); + } + + public function hSetNx($key, $field, $value): \Redis|bool + { + return $this->initializeLazyObject()->hSetNx(...\func_get_args()); + } + + public function hStrLen($key, $field): \Redis|false|int + { + return $this->initializeLazyObject()->hStrLen(...\func_get_args()); + } + + public function hVals($key): \Redis|array|false + { + return $this->initializeLazyObject()->hVals(...\func_get_args()); + } + + public function hscan($key, &$iterator, $pattern = null, $count = 0): \Redis|array|bool + { + return $this->initializeLazyObject()->hscan($key, $iterator, ...\array_slice(\func_get_args(), 2)); + } + + public function incr($key, $by = 1): \Redis|false|int + { + return $this->initializeLazyObject()->incr(...\func_get_args()); + } + + public function incrBy($key, $value): \Redis|false|int + { + return $this->initializeLazyObject()->incrBy(...\func_get_args()); + } + + public function incrByFloat($key, $value): \Redis|false|float + { + return $this->initializeLazyObject()->incrByFloat(...\func_get_args()); + } + + public function info(...$sections): \Redis|array|false + { + return $this->initializeLazyObject()->info(...\func_get_args()); + } + + public function isConnected(): bool + { + return $this->initializeLazyObject()->isConnected(...\func_get_args()); + } + + public function keys($pattern) + { + return $this->initializeLazyObject()->keys(...\func_get_args()); + } + + public function lInsert($key, $pos, $pivot, $value) + { + return $this->initializeLazyObject()->lInsert(...\func_get_args()); + } + + public function lLen($key): \Redis|false|int + { + return $this->initializeLazyObject()->lLen(...\func_get_args()); + } + + public function lMove($src, $dst, $wherefrom, $whereto): \Redis|false|string + { + return $this->initializeLazyObject()->lMove(...\func_get_args()); + } + + public function blmove($src, $dst, $wherefrom, $whereto, $timeout): \Redis|false|string + { + return $this->initializeLazyObject()->blmove(...\func_get_args()); + } + + public function lPop($key, $count = 0): \Redis|array|bool|string + { + return $this->initializeLazyObject()->lPop(...\func_get_args()); + } + + public function lPos($key, $value, $options = null): \Redis|array|bool|int|null + { + return $this->initializeLazyObject()->lPos(...\func_get_args()); + } + + public function lPush($key, ...$elements): \Redis|false|int + { + return $this->initializeLazyObject()->lPush(...\func_get_args()); + } + + public function rPush($key, ...$elements): \Redis|false|int + { + return $this->initializeLazyObject()->rPush(...\func_get_args()); + } + + public function lPushx($key, $value): \Redis|false|int + { + return $this->initializeLazyObject()->lPushx(...\func_get_args()); + } + + public function rPushx($key, $value): \Redis|false|int + { + return $this->initializeLazyObject()->rPushx(...\func_get_args()); + } + + public function lSet($key, $index, $value): \Redis|bool + { + return $this->initializeLazyObject()->lSet(...\func_get_args()); + } + + public function lastSave(): int + { + return $this->initializeLazyObject()->lastSave(...\func_get_args()); + } + + public function lindex($key, $index): mixed + { + return $this->initializeLazyObject()->lindex(...\func_get_args()); + } + + public function lrange($key, $start, $end): \Redis|array|false + { + return $this->initializeLazyObject()->lrange(...\func_get_args()); + } + + public function lrem($key, $value, $count = 0): \Redis|false|int + { + return $this->initializeLazyObject()->lrem(...\func_get_args()); + } + + public function ltrim($key, $start, $end): \Redis|bool + { + return $this->initializeLazyObject()->ltrim(...\func_get_args()); + } + + public function migrate($host, $port, $key, $dstdb, $timeout, $copy = false, $replace = false, #[\SensitiveParameter] $credentials = null): \Redis|bool + { + return $this->initializeLazyObject()->migrate(...\func_get_args()); + } + + public function move($key, $index): \Redis|bool + { + return $this->initializeLazyObject()->move(...\func_get_args()); + } + + public function mset($key_values): \Redis|bool + { + return $this->initializeLazyObject()->mset(...\func_get_args()); + } + + public function msetnx($key_values): \Redis|bool + { + return $this->initializeLazyObject()->msetnx(...\func_get_args()); + } + + public function multi($value = \Redis::MULTI): \Redis|bool + { + return $this->initializeLazyObject()->multi(...\func_get_args()); + } + + public function object($subcommand, $key): \Redis|false|int|string + { + return $this->initializeLazyObject()->object(...\func_get_args()); + } + + public function open($host, $port = 6379, $timeout = 0, $persistent_id = null, $retry_interval = 0, $read_timeout = 0, $context = null): bool + { + return $this->initializeLazyObject()->open(...\func_get_args()); + } + + public function pconnect($host, $port = 6379, $timeout = 0, $persistent_id = null, $retry_interval = 0, $read_timeout = 0, $context = null): bool + { + return $this->initializeLazyObject()->pconnect(...\func_get_args()); + } + + public function persist($key): \Redis|bool + { + return $this->initializeLazyObject()->persist(...\func_get_args()); + } + + public function pexpire($key, $timeout, $mode = null): bool + { + return $this->initializeLazyObject()->pexpire(...\func_get_args()); + } + + public function pexpireAt($key, $timestamp, $mode = null): \Redis|bool + { + return $this->initializeLazyObject()->pexpireAt(...\func_get_args()); + } + + public function pfadd($key, $elements): \Redis|int + { + return $this->initializeLazyObject()->pfadd(...\func_get_args()); + } + + public function pfcount($key_or_keys): \Redis|false|int + { + return $this->initializeLazyObject()->pfcount(...\func_get_args()); + } + + public function pfmerge($dst, $srckeys): \Redis|bool + { + return $this->initializeLazyObject()->pfmerge(...\func_get_args()); + } + + public function ping($message = null): \Redis|bool|string + { + return $this->initializeLazyObject()->ping(...\func_get_args()); + } + + public function pipeline(): \Redis|bool + { + return $this->initializeLazyObject()->pipeline(...\func_get_args()); + } + + public function popen($host, $port = 6379, $timeout = 0, $persistent_id = null, $retry_interval = 0, $read_timeout = 0, $context = null): bool + { + return $this->initializeLazyObject()->popen(...\func_get_args()); + } + + public function psetex($key, $expire, $value): \Redis|bool + { + return $this->initializeLazyObject()->psetex(...\func_get_args()); + } + + public function psubscribe($patterns, $cb): bool + { + return $this->initializeLazyObject()->psubscribe(...\func_get_args()); + } + + public function pttl($key): \Redis|false|int + { + return $this->initializeLazyObject()->pttl(...\func_get_args()); + } + + public function publish($channel, $message): \Redis|false|int + { + return $this->initializeLazyObject()->publish(...\func_get_args()); + } + + public function pubsub($command, $arg = null): mixed + { + return $this->initializeLazyObject()->pubsub(...\func_get_args()); + } + + public function punsubscribe($patterns): \Redis|array|bool + { + return $this->initializeLazyObject()->punsubscribe(...\func_get_args()); + } + + public function rPop($key, $count = 0): \Redis|array|bool|string + { + return $this->initializeLazyObject()->rPop(...\func_get_args()); + } + + public function randomKey(): \Redis|false|string + { + return $this->initializeLazyObject()->randomKey(...\func_get_args()); + } + + public function rawcommand($command, ...$args): mixed + { + return $this->initializeLazyObject()->rawcommand(...\func_get_args()); + } + + public function rename($old_name, $new_name): \Redis|bool + { + return $this->initializeLazyObject()->rename(...\func_get_args()); + } + + public function renameNx($key_src, $key_dst): \Redis|bool + { + return $this->initializeLazyObject()->renameNx(...\func_get_args()); + } + + public function restore($key, $ttl, $value, $options = null): \Redis|bool + { + return $this->initializeLazyObject()->restore(...\func_get_args()); + } + + public function role(): mixed + { + return $this->initializeLazyObject()->role(...\func_get_args()); + } + + public function rpoplpush($srckey, $dstkey): \Redis|false|string + { + return $this->initializeLazyObject()->rpoplpush(...\func_get_args()); + } + + public function sAdd($key, $value, ...$other_values): \Redis|false|int + { + return $this->initializeLazyObject()->sAdd(...\func_get_args()); + } + + public function sAddArray($key, $values): int + { + return $this->initializeLazyObject()->sAddArray(...\func_get_args()); + } + + public function sDiff($key, ...$other_keys): \Redis|array|false + { + return $this->initializeLazyObject()->sDiff(...\func_get_args()); + } + + public function sDiffStore($dst, $key, ...$other_keys): \Redis|false|int + { + return $this->initializeLazyObject()->sDiffStore(...\func_get_args()); + } + + public function sInter($key, ...$other_keys): \Redis|array|false + { + return $this->initializeLazyObject()->sInter(...\func_get_args()); + } + + public function sintercard($keys, $limit = -1): \Redis|false|int + { + return $this->initializeLazyObject()->sintercard(...\func_get_args()); + } + + public function sInterStore($key, ...$other_keys): \Redis|false|int + { + return $this->initializeLazyObject()->sInterStore(...\func_get_args()); + } + + public function sMembers($key): \Redis|array|false + { + return $this->initializeLazyObject()->sMembers(...\func_get_args()); + } + + public function sMisMember($key, $member, ...$other_members): \Redis|array|false + { + return $this->initializeLazyObject()->sMisMember(...\func_get_args()); + } + + public function sMove($src, $dst, $value): \Redis|bool + { + return $this->initializeLazyObject()->sMove(...\func_get_args()); + } + + public function sPop($key, $count = 0): \Redis|array|false|string + { + return $this->initializeLazyObject()->sPop(...\func_get_args()); + } + + public function sUnion($key, ...$other_keys): \Redis|array|false + { + return $this->initializeLazyObject()->sUnion(...\func_get_args()); + } + + public function sUnionStore($dst, $key, ...$other_keys): \Redis|false|int + { + return $this->initializeLazyObject()->sUnionStore(...\func_get_args()); + } + + public function save(): \Redis|bool + { + return $this->initializeLazyObject()->save(...\func_get_args()); + } + + public function scan(&$iterator, $pattern = null, $count = 0, $type = null): array|false + { + return $this->initializeLazyObject()->scan($iterator, ...\array_slice(\func_get_args(), 1)); + } + + public function scard($key): \Redis|false|int + { + return $this->initializeLazyObject()->scard(...\func_get_args()); + } + + public function script($command, ...$args): mixed + { + return $this->initializeLazyObject()->script(...\func_get_args()); + } + + public function select($db): \Redis|bool + { + return $this->initializeLazyObject()->select(...\func_get_args()); + } + + public function set($key, $value, $options = null): \Redis|bool|string + { + return $this->initializeLazyObject()->set(...\func_get_args()); + } + + public function setBit($key, $idx, $value): \Redis|false|int + { + return $this->initializeLazyObject()->setBit(...\func_get_args()); + } + + public function setRange($key, $index, $value): \Redis|false|int + { + return $this->initializeLazyObject()->setRange(...\func_get_args()); + } + + public function setOption($option, $value): bool + { + return $this->initializeLazyObject()->setOption(...\func_get_args()); + } + + public function setex($key, $expire, $value) + { + return $this->initializeLazyObject()->setex(...\func_get_args()); + } + + public function setnx($key, $value): \Redis|bool + { + return $this->initializeLazyObject()->setnx(...\func_get_args()); + } + + public function sismember($key, $value): \Redis|bool + { + return $this->initializeLazyObject()->sismember(...\func_get_args()); + } + + public function slaveof($host = null, $port = 6379): \Redis|bool + { + return $this->initializeLazyObject()->slaveof(...\func_get_args()); + } + + public function replicaof($host = null, $port = 6379): \Redis|bool + { + return $this->initializeLazyObject()->replicaof(...\func_get_args()); + } + + public function touch($key_or_array, ...$more_keys): \Redis|false|int + { + return $this->initializeLazyObject()->touch(...\func_get_args()); + } + + public function slowlog($operation, $length = 0): mixed + { + return $this->initializeLazyObject()->slowlog(...\func_get_args()); + } + + public function sort($key, $options = null): mixed + { + return $this->initializeLazyObject()->sort(...\func_get_args()); + } + + public function sort_ro($key, $options = null): mixed + { + return $this->initializeLazyObject()->sort_ro(...\func_get_args()); + } + + public function sortAsc($key, $pattern = null, $get = null, $offset = -1, $count = -1, $store = null): array + { + return $this->initializeLazyObject()->sortAsc(...\func_get_args()); + } + + public function sortAscAlpha($key, $pattern = null, $get = null, $offset = -1, $count = -1, $store = null): array + { + return $this->initializeLazyObject()->sortAscAlpha(...\func_get_args()); + } + + public function sortDesc($key, $pattern = null, $get = null, $offset = -1, $count = -1, $store = null): array + { + return $this->initializeLazyObject()->sortDesc(...\func_get_args()); + } + + public function sortDescAlpha($key, $pattern = null, $get = null, $offset = -1, $count = -1, $store = null): array + { + return $this->initializeLazyObject()->sortDescAlpha(...\func_get_args()); + } + + public function srem($key, $value, ...$other_values): \Redis|false|int + { + return $this->initializeLazyObject()->srem(...\func_get_args()); + } + + public function sscan($key, &$iterator, $pattern = null, $count = 0): array|false + { + return $this->initializeLazyObject()->sscan($key, $iterator, ...\array_slice(\func_get_args(), 2)); + } + + public function ssubscribe($channels, $cb): bool + { + return $this->initializeLazyObject()->ssubscribe(...\func_get_args()); + } + + public function strlen($key): \Redis|false|int + { + return $this->initializeLazyObject()->strlen(...\func_get_args()); + } + + public function subscribe($channels, $cb): bool + { + return $this->initializeLazyObject()->subscribe(...\func_get_args()); + } + + public function sunsubscribe($channels): \Redis|array|bool + { + return $this->initializeLazyObject()->sunsubscribe(...\func_get_args()); + } + + public function swapdb($src, $dst): \Redis|bool + { + return $this->initializeLazyObject()->swapdb(...\func_get_args()); + } + + public function time(): \Redis|array + { + return $this->initializeLazyObject()->time(...\func_get_args()); + } + + public function ttl($key): \Redis|false|int + { + return $this->initializeLazyObject()->ttl(...\func_get_args()); + } + + public function type($key): \Redis|false|int + { + return $this->initializeLazyObject()->type(...\func_get_args()); + } + + public function unlink($key, ...$other_keys): \Redis|false|int + { + return $this->initializeLazyObject()->unlink(...\func_get_args()); + } + + public function unsubscribe($channels): \Redis|array|bool + { + return $this->initializeLazyObject()->unsubscribe(...\func_get_args()); + } + + public function unwatch(): \Redis|bool + { + return $this->initializeLazyObject()->unwatch(...\func_get_args()); + } + + public function watch($key, ...$other_keys): \Redis|bool + { + return $this->initializeLazyObject()->watch(...\func_get_args()); + } + + public function wait($numreplicas, $timeout): false|int + { + return $this->initializeLazyObject()->wait(...\func_get_args()); + } + + public function xack($key, $group, $ids): false|int + { + return $this->initializeLazyObject()->xack(...\func_get_args()); + } + + public function xadd($key, $id, $values, $maxlen = 0, $approx = false, $nomkstream = false): \Redis|false|string + { + return $this->initializeLazyObject()->xadd(...\func_get_args()); + } + + public function xautoclaim($key, $group, $consumer, $min_idle, $start, $count = -1, $justid = false): \Redis|array|bool + { + return $this->initializeLazyObject()->xautoclaim(...\func_get_args()); + } + + public function xclaim($key, $group, $consumer, $min_idle, $ids, $options): \Redis|array|bool + { + return $this->initializeLazyObject()->xclaim(...\func_get_args()); + } + + public function xdel($key, $ids): \Redis|false|int + { + return $this->initializeLazyObject()->xdel(...\func_get_args()); + } + + public function xgroup($operation, $key = null, $group = null, $id_or_consumer = null, $mkstream = false, $entries_read = -2): mixed + { + return $this->initializeLazyObject()->xgroup(...\func_get_args()); + } + + public function xinfo($operation, $arg1 = null, $arg2 = null, $count = -1): mixed + { + return $this->initializeLazyObject()->xinfo(...\func_get_args()); + } + + public function xlen($key): \Redis|false|int + { + return $this->initializeLazyObject()->xlen(...\func_get_args()); + } + + public function xpending($key, $group, $start = null, $end = null, $count = -1, $consumer = null): \Redis|array|false + { + return $this->initializeLazyObject()->xpending(...\func_get_args()); + } + + public function xrange($key, $start, $end, $count = -1): \Redis|array|bool + { + return $this->initializeLazyObject()->xrange(...\func_get_args()); + } + + public function xread($streams, $count = -1, $block = -1): \Redis|array|bool + { + return $this->initializeLazyObject()->xread(...\func_get_args()); + } + + public function xreadgroup($group, $consumer, $streams, $count = 1, $block = 1): \Redis|array|bool + { + return $this->initializeLazyObject()->xreadgroup(...\func_get_args()); + } + + public function xrevrange($key, $end, $start, $count = -1): \Redis|array|bool + { + return $this->initializeLazyObject()->xrevrange(...\func_get_args()); + } + + public function xtrim($key, $threshold, $approx = false, $minid = false, $limit = -1): \Redis|false|int + { + return $this->initializeLazyObject()->xtrim(...\func_get_args()); + } + + public function zAdd($key, $score_or_options, ...$more_scores_and_mems): \Redis|false|float|int + { + return $this->initializeLazyObject()->zAdd(...\func_get_args()); + } + + public function zCard($key): \Redis|false|int + { + return $this->initializeLazyObject()->zCard(...\func_get_args()); + } + + public function zCount($key, $start, $end): \Redis|false|int + { + return $this->initializeLazyObject()->zCount(...\func_get_args()); + } + + public function zIncrBy($key, $value, $member): \Redis|false|float + { + return $this->initializeLazyObject()->zIncrBy(...\func_get_args()); + } + + public function zLexCount($key, $min, $max): \Redis|false|int + { + return $this->initializeLazyObject()->zLexCount(...\func_get_args()); + } + + public function zMscore($key, $member, ...$other_members): \Redis|array|false + { + return $this->initializeLazyObject()->zMscore(...\func_get_args()); + } + + public function zPopMax($key, $count = null): \Redis|array|false + { + return $this->initializeLazyObject()->zPopMax(...\func_get_args()); + } + + public function zPopMin($key, $count = null): \Redis|array|false + { + return $this->initializeLazyObject()->zPopMin(...\func_get_args()); + } + + public function zRange($key, $start, $end, $options = null): \Redis|array|false + { + return $this->initializeLazyObject()->zRange(...\func_get_args()); + } + + public function zRangeByLex($key, $min, $max, $offset = -1, $count = -1): \Redis|array|false + { + return $this->initializeLazyObject()->zRangeByLex(...\func_get_args()); + } + + public function zRangeByScore($key, $start, $end, $options = []): \Redis|array|false + { + return $this->initializeLazyObject()->zRangeByScore(...\func_get_args()); + } + + public function zrangestore($dstkey, $srckey, $start, $end, $options = null): \Redis|false|int + { + return $this->initializeLazyObject()->zrangestore(...\func_get_args()); + } + + public function zRandMember($key, $options = null): \Redis|array|string + { + return $this->initializeLazyObject()->zRandMember(...\func_get_args()); + } + + public function zRank($key, $member): \Redis|false|int + { + return $this->initializeLazyObject()->zRank(...\func_get_args()); + } + + public function zRem($key, $member, ...$other_members): \Redis|false|int + { + return $this->initializeLazyObject()->zRem(...\func_get_args()); + } + + public function zRemRangeByLex($key, $min, $max): \Redis|false|int + { + return $this->initializeLazyObject()->zRemRangeByLex(...\func_get_args()); + } + + public function zRemRangeByRank($key, $start, $end): \Redis|false|int + { + return $this->initializeLazyObject()->zRemRangeByRank(...\func_get_args()); + } + + public function zRemRangeByScore($key, $start, $end): \Redis|false|int + { + return $this->initializeLazyObject()->zRemRangeByScore(...\func_get_args()); + } + + public function zRevRange($key, $start, $end, $scores = null): \Redis|array|false + { + return $this->initializeLazyObject()->zRevRange(...\func_get_args()); + } + + public function zRevRangeByLex($key, $max, $min, $offset = -1, $count = -1): \Redis|array|false + { + return $this->initializeLazyObject()->zRevRangeByLex(...\func_get_args()); + } + + public function zRevRangeByScore($key, $max, $min, $options = []): \Redis|array|false + { + return $this->initializeLazyObject()->zRevRangeByScore(...\func_get_args()); + } + + public function zRevRank($key, $member): \Redis|false|int + { + return $this->initializeLazyObject()->zRevRank(...\func_get_args()); + } + + public function zScore($key, $member): \Redis|false|float + { + return $this->initializeLazyObject()->zScore(...\func_get_args()); + } + + public function zdiff($keys, $options = null): \Redis|array|false + { + return $this->initializeLazyObject()->zdiff(...\func_get_args()); + } + + public function zdiffstore($dst, $keys): \Redis|false|int + { + return $this->initializeLazyObject()->zdiffstore(...\func_get_args()); + } + + public function zinter($keys, $weights = null, $options = null): \Redis|array|false + { + return $this->initializeLazyObject()->zinter(...\func_get_args()); + } + + public function zintercard($keys, $limit = -1): \Redis|false|int + { + return $this->initializeLazyObject()->zintercard(...\func_get_args()); + } + + public function zinterstore($dst, $keys, $weights = null, $aggregate = null): \Redis|false|int + { + return $this->initializeLazyObject()->zinterstore(...\func_get_args()); + } + + public function zscan($key, &$iterator, $pattern = null, $count = 0): \Redis|array|false + { + return $this->initializeLazyObject()->zscan($key, $iterator, ...\array_slice(\func_get_args(), 2)); + } + + public function zunion($keys, $weights = null, $options = null): \Redis|array|false + { + return $this->initializeLazyObject()->zunion(...\func_get_args()); + } + + public function zunionstore($dst, $keys, $weights = null, $aggregate = null): \Redis|false|int + { + return $this->initializeLazyObject()->zunionstore(...\func_get_args()); + } +} diff --git a/vendor/symfony/cache/Traits/Redis6ProxyTrait.php b/vendor/symfony/cache/Traits/Redis6ProxyTrait.php new file mode 100644 index 0000000..bb8d978 --- /dev/null +++ b/vendor/symfony/cache/Traits/Redis6ProxyTrait.php @@ -0,0 +1,81 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Cache\Traits; + +if (version_compare(phpversion('redis'), '6.1.0-dev', '>=')) { + /** + * @internal + */ + trait Redis6ProxyTrait + { + public function dump($key): \Redis|string|false + { + return $this->initializeLazyObject()->dump(...\func_get_args()); + } + + public function hRandField($key, $options = null): \Redis|array|string|false + { + return $this->initializeLazyObject()->hRandField(...\func_get_args()); + } + + public function hSet($key, ...$fields_and_vals): \Redis|false|int + { + return $this->initializeLazyObject()->hSet(...\func_get_args()); + } + + public function mget($keys): \Redis|array|false + { + return $this->initializeLazyObject()->mget(...\func_get_args()); + } + + public function sRandMember($key, $count = 0): mixed + { + return $this->initializeLazyObject()->sRandMember(...\func_get_args()); + } + + public function waitaof($numlocal, $numreplicas, $timeout): \Redis|array|false + { + return $this->initializeLazyObject()->waitaof(...\func_get_args()); + } + } +} else { + /** + * @internal + */ + trait Redis6ProxyTrait + { + public function dump($key): \Redis|string + { + return $this->initializeLazyObject()->dump(...\func_get_args()); + } + + public function hRandField($key, $options = null): \Redis|array|string + { + return $this->initializeLazyObject()->hRandField(...\func_get_args()); + } + + public function hSet($key, $member, $value): \Redis|false|int + { + return $this->initializeLazyObject()->hSet(...\func_get_args()); + } + + public function mget($keys): \Redis|array + { + return $this->initializeLazyObject()->mget(...\func_get_args()); + } + + public function sRandMember($key, $count = 0): \Redis|array|false|string + { + return $this->initializeLazyObject()->sRandMember(...\func_get_args()); + } + } +} diff --git a/vendor/symfony/cache/Traits/RedisCluster5Proxy.php b/vendor/symfony/cache/Traits/RedisCluster5Proxy.php new file mode 100644 index 0000000..43f3404 --- /dev/null +++ b/vendor/symfony/cache/Traits/RedisCluster5Proxy.php @@ -0,0 +1,980 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Cache\Traits; + +use Symfony\Component\VarExporter\LazyObjectInterface; +use Symfony\Contracts\Service\ResetInterface; + +// Help opcache.preload discover always-needed symbols +class_exists(\Symfony\Component\VarExporter\Internal\Hydrator::class); +class_exists(\Symfony\Component\VarExporter\Internal\LazyObjectRegistry::class); +class_exists(\Symfony\Component\VarExporter\Internal\LazyObjectState::class); + +/** + * @internal + */ +class RedisCluster5Proxy extends \RedisCluster implements ResetInterface, LazyObjectInterface +{ + use RedisProxyTrait { + resetLazyObject as reset; + } + + public function __construct($name, $seeds = null, $timeout = null, $read_timeout = null, $persistent = null, #[\SensitiveParameter] $auth = null) + { + $this->initializeLazyObject()->__construct(...\func_get_args()); + } + + public function _masters() + { + return $this->initializeLazyObject()->_masters(...\func_get_args()); + } + + public function _prefix($key) + { + return $this->initializeLazyObject()->_prefix(...\func_get_args()); + } + + public function _redir() + { + return $this->initializeLazyObject()->_redir(...\func_get_args()); + } + + public function _serialize($value) + { + return $this->initializeLazyObject()->_serialize(...\func_get_args()); + } + + public function _unserialize($value) + { + return $this->initializeLazyObject()->_unserialize(...\func_get_args()); + } + + public function _compress($value) + { + return $this->initializeLazyObject()->_compress(...\func_get_args()); + } + + public function _uncompress($value) + { + return $this->initializeLazyObject()->_uncompress(...\func_get_args()); + } + + public function _pack($value) + { + return $this->initializeLazyObject()->_pack(...\func_get_args()); + } + + public function _unpack($value) + { + return $this->initializeLazyObject()->_unpack(...\func_get_args()); + } + + public function acl($key_or_address, $subcmd, ...$args) + { + return $this->initializeLazyObject()->acl(...\func_get_args()); + } + + public function append($key, $value) + { + return $this->initializeLazyObject()->append(...\func_get_args()); + } + + public function bgrewriteaof($key_or_address) + { + return $this->initializeLazyObject()->bgrewriteaof(...\func_get_args()); + } + + public function bgsave($key_or_address) + { + return $this->initializeLazyObject()->bgsave(...\func_get_args()); + } + + public function bitcount($key) + { + return $this->initializeLazyObject()->bitcount(...\func_get_args()); + } + + public function bitop($operation, $ret_key, $key, ...$other_keys) + { + return $this->initializeLazyObject()->bitop(...\func_get_args()); + } + + public function bitpos($key, $bit, $start = null, $end = null) + { + return $this->initializeLazyObject()->bitpos(...\func_get_args()); + } + + public function blpop($key, $timeout_or_key, ...$extra_args) + { + return $this->initializeLazyObject()->blpop(...\func_get_args()); + } + + public function brpop($key, $timeout_or_key, ...$extra_args) + { + return $this->initializeLazyObject()->brpop(...\func_get_args()); + } + + public function brpoplpush($src, $dst, $timeout) + { + return $this->initializeLazyObject()->brpoplpush(...\func_get_args()); + } + + public function clearlasterror() + { + return $this->initializeLazyObject()->clearlasterror(...\func_get_args()); + } + + public function bzpopmax($key, $timeout_or_key, ...$extra_args) + { + return $this->initializeLazyObject()->bzpopmax(...\func_get_args()); + } + + public function bzpopmin($key, $timeout_or_key, ...$extra_args) + { + return $this->initializeLazyObject()->bzpopmin(...\func_get_args()); + } + + public function client($key_or_address, $arg = null, ...$other_args) + { + return $this->initializeLazyObject()->client(...\func_get_args()); + } + + public function close() + { + return $this->initializeLazyObject()->close(...\func_get_args()); + } + + public function cluster($key_or_address, $arg = null, ...$other_args) + { + return $this->initializeLazyObject()->cluster(...\func_get_args()); + } + + public function command(...$args) + { + return $this->initializeLazyObject()->command(...\func_get_args()); + } + + public function config($key_or_address, $arg = null, ...$other_args) + { + return $this->initializeLazyObject()->config(...\func_get_args()); + } + + public function dbsize($key_or_address) + { + return $this->initializeLazyObject()->dbsize(...\func_get_args()); + } + + public function decr($key) + { + return $this->initializeLazyObject()->decr(...\func_get_args()); + } + + public function decrby($key, $value) + { + return $this->initializeLazyObject()->decrby(...\func_get_args()); + } + + public function del($key, ...$other_keys) + { + return $this->initializeLazyObject()->del(...\func_get_args()); + } + + public function discard() + { + return $this->initializeLazyObject()->discard(...\func_get_args()); + } + + public function dump($key) + { + return $this->initializeLazyObject()->dump(...\func_get_args()); + } + + public function echo($msg) + { + return $this->initializeLazyObject()->echo(...\func_get_args()); + } + + public function eval($script, $args = null, $num_keys = null) + { + return $this->initializeLazyObject()->eval(...\func_get_args()); + } + + public function evalsha($script_sha, $args = null, $num_keys = null) + { + return $this->initializeLazyObject()->evalsha(...\func_get_args()); + } + + public function exec() + { + return $this->initializeLazyObject()->exec(...\func_get_args()); + } + + public function exists($key) + { + return $this->initializeLazyObject()->exists(...\func_get_args()); + } + + public function expire($key, $timeout) + { + return $this->initializeLazyObject()->expire(...\func_get_args()); + } + + public function expireat($key, $timestamp) + { + return $this->initializeLazyObject()->expireat(...\func_get_args()); + } + + public function flushall($key_or_address, $async = null) + { + return $this->initializeLazyObject()->flushall(...\func_get_args()); + } + + public function flushdb($key_or_address, $async = null) + { + return $this->initializeLazyObject()->flushdb(...\func_get_args()); + } + + public function geoadd($key, $lng, $lat, $member, ...$other_triples) + { + return $this->initializeLazyObject()->geoadd(...\func_get_args()); + } + + public function geodist($key, $src, $dst, $unit = null) + { + return $this->initializeLazyObject()->geodist(...\func_get_args()); + } + + public function geohash($key, $member, ...$other_members) + { + return $this->initializeLazyObject()->geohash(...\func_get_args()); + } + + public function geopos($key, $member, ...$other_members) + { + return $this->initializeLazyObject()->geopos(...\func_get_args()); + } + + public function georadius($key, $lng, $lan, $radius, $unit, $opts = null) + { + return $this->initializeLazyObject()->georadius(...\func_get_args()); + } + + public function georadius_ro($key, $lng, $lan, $radius, $unit, $opts = null) + { + return $this->initializeLazyObject()->georadius_ro(...\func_get_args()); + } + + public function georadiusbymember($key, $member, $radius, $unit, $opts = null) + { + return $this->initializeLazyObject()->georadiusbymember(...\func_get_args()); + } + + public function georadiusbymember_ro($key, $member, $radius, $unit, $opts = null) + { + return $this->initializeLazyObject()->georadiusbymember_ro(...\func_get_args()); + } + + public function get($key) + { + return $this->initializeLazyObject()->get(...\func_get_args()); + } + + public function getbit($key, $offset) + { + return $this->initializeLazyObject()->getbit(...\func_get_args()); + } + + public function getlasterror() + { + return $this->initializeLazyObject()->getlasterror(...\func_get_args()); + } + + public function getmode() + { + return $this->initializeLazyObject()->getmode(...\func_get_args()); + } + + public function getoption($option) + { + return $this->initializeLazyObject()->getoption(...\func_get_args()); + } + + public function getrange($key, $start, $end) + { + return $this->initializeLazyObject()->getrange(...\func_get_args()); + } + + public function getset($key, $value) + { + return $this->initializeLazyObject()->getset(...\func_get_args()); + } + + public function hdel($key, $member, ...$other_members) + { + return $this->initializeLazyObject()->hdel(...\func_get_args()); + } + + public function hexists($key, $member) + { + return $this->initializeLazyObject()->hexists(...\func_get_args()); + } + + public function hget($key, $member) + { + return $this->initializeLazyObject()->hget(...\func_get_args()); + } + + public function hgetall($key) + { + return $this->initializeLazyObject()->hgetall(...\func_get_args()); + } + + public function hincrby($key, $member, $value) + { + return $this->initializeLazyObject()->hincrby(...\func_get_args()); + } + + public function hincrbyfloat($key, $member, $value) + { + return $this->initializeLazyObject()->hincrbyfloat(...\func_get_args()); + } + + public function hkeys($key) + { + return $this->initializeLazyObject()->hkeys(...\func_get_args()); + } + + public function hlen($key) + { + return $this->initializeLazyObject()->hlen(...\func_get_args()); + } + + public function hmget($key, $keys) + { + return $this->initializeLazyObject()->hmget(...\func_get_args()); + } + + public function hmset($key, $pairs) + { + return $this->initializeLazyObject()->hmset(...\func_get_args()); + } + + public function hscan($str_key, &$i_iterator, $str_pattern = null, $i_count = null) + { + return $this->initializeLazyObject()->hscan($str_key, $i_iterator, ...\array_slice(\func_get_args(), 2)); + } + + public function hset($key, $member, $value) + { + return $this->initializeLazyObject()->hset(...\func_get_args()); + } + + public function hsetnx($key, $member, $value) + { + return $this->initializeLazyObject()->hsetnx(...\func_get_args()); + } + + public function hstrlen($key, $member) + { + return $this->initializeLazyObject()->hstrlen(...\func_get_args()); + } + + public function hvals($key) + { + return $this->initializeLazyObject()->hvals(...\func_get_args()); + } + + public function incr($key) + { + return $this->initializeLazyObject()->incr(...\func_get_args()); + } + + public function incrby($key, $value) + { + return $this->initializeLazyObject()->incrby(...\func_get_args()); + } + + public function incrbyfloat($key, $value) + { + return $this->initializeLazyObject()->incrbyfloat(...\func_get_args()); + } + + public function info($key_or_address, $option = null) + { + return $this->initializeLazyObject()->info(...\func_get_args()); + } + + public function keys($pattern) + { + return $this->initializeLazyObject()->keys(...\func_get_args()); + } + + public function lastsave($key_or_address) + { + return $this->initializeLazyObject()->lastsave(...\func_get_args()); + } + + public function lget($key, $index) + { + return $this->initializeLazyObject()->lget(...\func_get_args()); + } + + public function lindex($key, $index) + { + return $this->initializeLazyObject()->lindex(...\func_get_args()); + } + + public function linsert($key, $position, $pivot, $value) + { + return $this->initializeLazyObject()->linsert(...\func_get_args()); + } + + public function llen($key) + { + return $this->initializeLazyObject()->llen(...\func_get_args()); + } + + public function lpop($key) + { + return $this->initializeLazyObject()->lpop(...\func_get_args()); + } + + public function lpush($key, $value) + { + return $this->initializeLazyObject()->lpush(...\func_get_args()); + } + + public function lpushx($key, $value) + { + return $this->initializeLazyObject()->lpushx(...\func_get_args()); + } + + public function lrange($key, $start, $end) + { + return $this->initializeLazyObject()->lrange(...\func_get_args()); + } + + public function lrem($key, $value) + { + return $this->initializeLazyObject()->lrem(...\func_get_args()); + } + + public function lset($key, $index, $value) + { + return $this->initializeLazyObject()->lset(...\func_get_args()); + } + + public function ltrim($key, $start, $stop) + { + return $this->initializeLazyObject()->ltrim(...\func_get_args()); + } + + public function mget($keys) + { + return $this->initializeLazyObject()->mget(...\func_get_args()); + } + + public function mset($pairs) + { + return $this->initializeLazyObject()->mset(...\func_get_args()); + } + + public function msetnx($pairs) + { + return $this->initializeLazyObject()->msetnx(...\func_get_args()); + } + + public function multi() + { + return $this->initializeLazyObject()->multi(...\func_get_args()); + } + + public function object($field, $key) + { + return $this->initializeLazyObject()->object(...\func_get_args()); + } + + public function persist($key) + { + return $this->initializeLazyObject()->persist(...\func_get_args()); + } + + public function pexpire($key, $timestamp) + { + return $this->initializeLazyObject()->pexpire(...\func_get_args()); + } + + public function pexpireat($key, $timestamp) + { + return $this->initializeLazyObject()->pexpireat(...\func_get_args()); + } + + public function pfadd($key, $elements) + { + return $this->initializeLazyObject()->pfadd(...\func_get_args()); + } + + public function pfcount($key) + { + return $this->initializeLazyObject()->pfcount(...\func_get_args()); + } + + public function pfmerge($dstkey, $keys) + { + return $this->initializeLazyObject()->pfmerge(...\func_get_args()); + } + + public function ping($key_or_address) + { + return $this->initializeLazyObject()->ping(...\func_get_args()); + } + + public function psetex($key, $expire, $value) + { + return $this->initializeLazyObject()->psetex(...\func_get_args()); + } + + public function psubscribe($patterns, $callback) + { + return $this->initializeLazyObject()->psubscribe(...\func_get_args()); + } + + public function pttl($key) + { + return $this->initializeLazyObject()->pttl(...\func_get_args()); + } + + public function publish($channel, $message) + { + return $this->initializeLazyObject()->publish(...\func_get_args()); + } + + public function pubsub($key_or_address, $arg = null, ...$other_args) + { + return $this->initializeLazyObject()->pubsub(...\func_get_args()); + } + + public function punsubscribe($pattern, ...$other_patterns) + { + return $this->initializeLazyObject()->punsubscribe(...\func_get_args()); + } + + public function randomkey($key_or_address) + { + return $this->initializeLazyObject()->randomkey(...\func_get_args()); + } + + public function rawcommand($cmd, ...$args) + { + return $this->initializeLazyObject()->rawcommand(...\func_get_args()); + } + + public function rename($key, $newkey) + { + return $this->initializeLazyObject()->rename(...\func_get_args()); + } + + public function renamenx($key, $newkey) + { + return $this->initializeLazyObject()->renamenx(...\func_get_args()); + } + + public function restore($ttl, $key, $value) + { + return $this->initializeLazyObject()->restore(...\func_get_args()); + } + + public function role() + { + return $this->initializeLazyObject()->role(...\func_get_args()); + } + + public function rpop($key) + { + return $this->initializeLazyObject()->rpop(...\func_get_args()); + } + + public function rpoplpush($src, $dst) + { + return $this->initializeLazyObject()->rpoplpush(...\func_get_args()); + } + + public function rpush($key, $value) + { + return $this->initializeLazyObject()->rpush(...\func_get_args()); + } + + public function rpushx($key, $value) + { + return $this->initializeLazyObject()->rpushx(...\func_get_args()); + } + + public function sadd($key, $value) + { + return $this->initializeLazyObject()->sadd(...\func_get_args()); + } + + public function saddarray($key, $options) + { + return $this->initializeLazyObject()->saddarray(...\func_get_args()); + } + + public function save($key_or_address) + { + return $this->initializeLazyObject()->save(...\func_get_args()); + } + + public function scan(&$i_iterator, $str_node, $str_pattern = null, $i_count = null) + { + return $this->initializeLazyObject()->scan($i_iterator, ...\array_slice(\func_get_args(), 1)); + } + + public function scard($key) + { + return $this->initializeLazyObject()->scard(...\func_get_args()); + } + + public function script($key_or_address, $arg = null, ...$other_args) + { + return $this->initializeLazyObject()->script(...\func_get_args()); + } + + public function sdiff($key, ...$other_keys) + { + return $this->initializeLazyObject()->sdiff(...\func_get_args()); + } + + public function sdiffstore($dst, $key, ...$other_keys) + { + return $this->initializeLazyObject()->sdiffstore(...\func_get_args()); + } + + public function set($key, $value, $opts = null) + { + return $this->initializeLazyObject()->set(...\func_get_args()); + } + + public function setbit($key, $offset, $value) + { + return $this->initializeLazyObject()->setbit(...\func_get_args()); + } + + public function setex($key, $expire, $value) + { + return $this->initializeLazyObject()->setex(...\func_get_args()); + } + + public function setnx($key, $value) + { + return $this->initializeLazyObject()->setnx(...\func_get_args()); + } + + public function setoption($option, $value) + { + return $this->initializeLazyObject()->setoption(...\func_get_args()); + } + + public function setrange($key, $offset, $value) + { + return $this->initializeLazyObject()->setrange(...\func_get_args()); + } + + public function sinter($key, ...$other_keys) + { + return $this->initializeLazyObject()->sinter(...\func_get_args()); + } + + public function sinterstore($dst, $key, ...$other_keys) + { + return $this->initializeLazyObject()->sinterstore(...\func_get_args()); + } + + public function sismember($key, $value) + { + return $this->initializeLazyObject()->sismember(...\func_get_args()); + } + + public function slowlog($key_or_address, $arg = null, ...$other_args) + { + return $this->initializeLazyObject()->slowlog(...\func_get_args()); + } + + public function smembers($key) + { + return $this->initializeLazyObject()->smembers(...\func_get_args()); + } + + public function smove($src, $dst, $value) + { + return $this->initializeLazyObject()->smove(...\func_get_args()); + } + + public function sort($key, $options = null) + { + return $this->initializeLazyObject()->sort(...\func_get_args()); + } + + public function spop($key) + { + return $this->initializeLazyObject()->spop(...\func_get_args()); + } + + public function srandmember($key, $count = null) + { + return $this->initializeLazyObject()->srandmember(...\func_get_args()); + } + + public function srem($key, $value) + { + return $this->initializeLazyObject()->srem(...\func_get_args()); + } + + public function sscan($str_key, &$i_iterator, $str_pattern = null, $i_count = null) + { + return $this->initializeLazyObject()->sscan($str_key, $i_iterator, ...\array_slice(\func_get_args(), 2)); + } + + public function strlen($key) + { + return $this->initializeLazyObject()->strlen(...\func_get_args()); + } + + public function subscribe($channels, $callback) + { + return $this->initializeLazyObject()->subscribe(...\func_get_args()); + } + + public function sunion($key, ...$other_keys) + { + return $this->initializeLazyObject()->sunion(...\func_get_args()); + } + + public function sunionstore($dst, $key, ...$other_keys) + { + return $this->initializeLazyObject()->sunionstore(...\func_get_args()); + } + + public function time() + { + return $this->initializeLazyObject()->time(...\func_get_args()); + } + + public function ttl($key) + { + return $this->initializeLazyObject()->ttl(...\func_get_args()); + } + + public function type($key) + { + return $this->initializeLazyObject()->type(...\func_get_args()); + } + + public function unsubscribe($channel, ...$other_channels) + { + return $this->initializeLazyObject()->unsubscribe(...\func_get_args()); + } + + public function unlink($key, ...$other_keys) + { + return $this->initializeLazyObject()->unlink(...\func_get_args()); + } + + public function unwatch() + { + return $this->initializeLazyObject()->unwatch(...\func_get_args()); + } + + public function watch($key, ...$other_keys) + { + return $this->initializeLazyObject()->watch(...\func_get_args()); + } + + public function xack($str_key, $str_group, $arr_ids) + { + return $this->initializeLazyObject()->xack(...\func_get_args()); + } + + public function xadd($str_key, $str_id, $arr_fields, $i_maxlen = null, $boo_approximate = null) + { + return $this->initializeLazyObject()->xadd(...\func_get_args()); + } + + public function xclaim($str_key, $str_group, $str_consumer, $i_min_idle, $arr_ids, $arr_opts = null) + { + return $this->initializeLazyObject()->xclaim(...\func_get_args()); + } + + public function xdel($str_key, $arr_ids) + { + return $this->initializeLazyObject()->xdel(...\func_get_args()); + } + + public function xgroup($str_operation, $str_key = null, $str_arg1 = null, $str_arg2 = null, $str_arg3 = null) + { + return $this->initializeLazyObject()->xgroup(...\func_get_args()); + } + + public function xinfo($str_cmd, $str_key = null, $str_group = null) + { + return $this->initializeLazyObject()->xinfo(...\func_get_args()); + } + + public function xlen($key) + { + return $this->initializeLazyObject()->xlen(...\func_get_args()); + } + + public function xpending($str_key, $str_group, $str_start = null, $str_end = null, $i_count = null, $str_consumer = null) + { + return $this->initializeLazyObject()->xpending(...\func_get_args()); + } + + public function xrange($str_key, $str_start, $str_end, $i_count = null) + { + return $this->initializeLazyObject()->xrange(...\func_get_args()); + } + + public function xread($arr_streams, $i_count = null, $i_block = null) + { + return $this->initializeLazyObject()->xread(...\func_get_args()); + } + + public function xreadgroup($str_group, $str_consumer, $arr_streams, $i_count = null, $i_block = null) + { + return $this->initializeLazyObject()->xreadgroup(...\func_get_args()); + } + + public function xrevrange($str_key, $str_start, $str_end, $i_count = null) + { + return $this->initializeLazyObject()->xrevrange(...\func_get_args()); + } + + public function xtrim($str_key, $i_maxlen, $boo_approximate = null) + { + return $this->initializeLazyObject()->xtrim(...\func_get_args()); + } + + public function zadd($key, $score, $value, ...$extra_args) + { + return $this->initializeLazyObject()->zadd(...\func_get_args()); + } + + public function zcard($key) + { + return $this->initializeLazyObject()->zcard(...\func_get_args()); + } + + public function zcount($key, $min, $max) + { + return $this->initializeLazyObject()->zcount(...\func_get_args()); + } + + public function zincrby($key, $value, $member) + { + return $this->initializeLazyObject()->zincrby(...\func_get_args()); + } + + public function zinterstore($key, $keys, $weights = null, $aggregate = null) + { + return $this->initializeLazyObject()->zinterstore(...\func_get_args()); + } + + public function zlexcount($key, $min, $max) + { + return $this->initializeLazyObject()->zlexcount(...\func_get_args()); + } + + public function zpopmax($key) + { + return $this->initializeLazyObject()->zpopmax(...\func_get_args()); + } + + public function zpopmin($key) + { + return $this->initializeLazyObject()->zpopmin(...\func_get_args()); + } + + public function zrange($key, $start, $end, $scores = null) + { + return $this->initializeLazyObject()->zrange(...\func_get_args()); + } + + public function zrangebylex($key, $min, $max, $offset = null, $limit = null) + { + return $this->initializeLazyObject()->zrangebylex(...\func_get_args()); + } + + public function zrangebyscore($key, $start, $end, $options = null) + { + return $this->initializeLazyObject()->zrangebyscore(...\func_get_args()); + } + + public function zrank($key, $member) + { + return $this->initializeLazyObject()->zrank(...\func_get_args()); + } + + public function zrem($key, $member, ...$other_members) + { + return $this->initializeLazyObject()->zrem(...\func_get_args()); + } + + public function zremrangebylex($key, $min, $max) + { + return $this->initializeLazyObject()->zremrangebylex(...\func_get_args()); + } + + public function zremrangebyrank($key, $min, $max) + { + return $this->initializeLazyObject()->zremrangebyrank(...\func_get_args()); + } + + public function zremrangebyscore($key, $min, $max) + { + return $this->initializeLazyObject()->zremrangebyscore(...\func_get_args()); + } + + public function zrevrange($key, $start, $end, $scores = null) + { + return $this->initializeLazyObject()->zrevrange(...\func_get_args()); + } + + public function zrevrangebylex($key, $min, $max, $offset = null, $limit = null) + { + return $this->initializeLazyObject()->zrevrangebylex(...\func_get_args()); + } + + public function zrevrangebyscore($key, $start, $end, $options = null) + { + return $this->initializeLazyObject()->zrevrangebyscore(...\func_get_args()); + } + + public function zrevrank($key, $member) + { + return $this->initializeLazyObject()->zrevrank(...\func_get_args()); + } + + public function zscan($str_key, &$i_iterator, $str_pattern = null, $i_count = null) + { + return $this->initializeLazyObject()->zscan($str_key, $i_iterator, ...\array_slice(\func_get_args(), 2)); + } + + public function zscore($key, $member) + { + return $this->initializeLazyObject()->zscore(...\func_get_args()); + } + + public function zunionstore($key, $keys, $weights = null, $aggregate = null) + { + return $this->initializeLazyObject()->zunionstore(...\func_get_args()); + } +} diff --git a/vendor/symfony/cache/Traits/RedisCluster6Proxy.php b/vendor/symfony/cache/Traits/RedisCluster6Proxy.php new file mode 100644 index 0000000..38dedf7 --- /dev/null +++ b/vendor/symfony/cache/Traits/RedisCluster6Proxy.php @@ -0,0 +1,1136 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Cache\Traits; + +use Symfony\Component\VarExporter\LazyObjectInterface; +use Symfony\Contracts\Service\ResetInterface; + +// Help opcache.preload discover always-needed symbols +class_exists(\Symfony\Component\VarExporter\Internal\Hydrator::class); +class_exists(\Symfony\Component\VarExporter\Internal\LazyObjectRegistry::class); +class_exists(\Symfony\Component\VarExporter\Internal\LazyObjectState::class); + +/** + * @internal + */ +class RedisCluster6Proxy extends \RedisCluster implements ResetInterface, LazyObjectInterface +{ + use RedisCluster6ProxyTrait; + use RedisProxyTrait { + resetLazyObject as reset; + } + + public function __construct($name, $seeds = null, $timeout = 0, $read_timeout = 0, $persistent = false, #[\SensitiveParameter] $auth = null, $context = null) + { + $this->initializeLazyObject()->__construct(...\func_get_args()); + } + + public function _compress($value): string + { + return $this->initializeLazyObject()->_compress(...\func_get_args()); + } + + public function _uncompress($value): string + { + return $this->initializeLazyObject()->_uncompress(...\func_get_args()); + } + + public function _serialize($value): bool|string + { + return $this->initializeLazyObject()->_serialize(...\func_get_args()); + } + + public function _unserialize($value): mixed + { + return $this->initializeLazyObject()->_unserialize(...\func_get_args()); + } + + public function _pack($value): string + { + return $this->initializeLazyObject()->_pack(...\func_get_args()); + } + + public function _unpack($value): mixed + { + return $this->initializeLazyObject()->_unpack(...\func_get_args()); + } + + public function _prefix($key): bool|string + { + return $this->initializeLazyObject()->_prefix(...\func_get_args()); + } + + public function _masters(): array + { + return $this->initializeLazyObject()->_masters(...\func_get_args()); + } + + public function _redir(): ?string + { + return $this->initializeLazyObject()->_redir(...\func_get_args()); + } + + public function acl($key_or_address, $subcmd, ...$args): mixed + { + return $this->initializeLazyObject()->acl(...\func_get_args()); + } + + public function append($key, $value): \RedisCluster|bool|int + { + return $this->initializeLazyObject()->append(...\func_get_args()); + } + + public function bgrewriteaof($key_or_address): \RedisCluster|bool + { + return $this->initializeLazyObject()->bgrewriteaof(...\func_get_args()); + } + + public function bgsave($key_or_address): \RedisCluster|bool + { + return $this->initializeLazyObject()->bgsave(...\func_get_args()); + } + + public function bitcount($key, $start = 0, $end = -1, $bybit = false): \RedisCluster|bool|int + { + return $this->initializeLazyObject()->bitcount(...\func_get_args()); + } + + public function bitop($operation, $deskey, $srckey, ...$otherkeys): \RedisCluster|bool|int + { + return $this->initializeLazyObject()->bitop(...\func_get_args()); + } + + public function bitpos($key, $bit, $start = 0, $end = -1, $bybit = false): \RedisCluster|false|int + { + return $this->initializeLazyObject()->bitpos(...\func_get_args()); + } + + public function blpop($key, $timeout_or_key, ...$extra_args): \RedisCluster|array|false|null + { + return $this->initializeLazyObject()->blpop(...\func_get_args()); + } + + public function brpop($key, $timeout_or_key, ...$extra_args): \RedisCluster|array|false|null + { + return $this->initializeLazyObject()->brpop(...\func_get_args()); + } + + public function brpoplpush($srckey, $deskey, $timeout): mixed + { + return $this->initializeLazyObject()->brpoplpush(...\func_get_args()); + } + + public function lmove($src, $dst, $wherefrom, $whereto): \Redis|false|string + { + return $this->initializeLazyObject()->lmove(...\func_get_args()); + } + + public function blmove($src, $dst, $wherefrom, $whereto, $timeout): \Redis|false|string + { + return $this->initializeLazyObject()->blmove(...\func_get_args()); + } + + public function bzpopmax($key, $timeout_or_key, ...$extra_args): array + { + return $this->initializeLazyObject()->bzpopmax(...\func_get_args()); + } + + public function bzpopmin($key, $timeout_or_key, ...$extra_args): array + { + return $this->initializeLazyObject()->bzpopmin(...\func_get_args()); + } + + public function bzmpop($timeout, $keys, $from, $count = 1): \RedisCluster|array|false|null + { + return $this->initializeLazyObject()->bzmpop(...\func_get_args()); + } + + public function zmpop($keys, $from, $count = 1): \RedisCluster|array|false|null + { + return $this->initializeLazyObject()->zmpop(...\func_get_args()); + } + + public function blmpop($timeout, $keys, $from, $count = 1): \RedisCluster|array|false|null + { + return $this->initializeLazyObject()->blmpop(...\func_get_args()); + } + + public function lmpop($keys, $from, $count = 1): \RedisCluster|array|false|null + { + return $this->initializeLazyObject()->lmpop(...\func_get_args()); + } + + public function clearlasterror(): bool + { + return $this->initializeLazyObject()->clearlasterror(...\func_get_args()); + } + + public function client($key_or_address, $subcommand, $arg = null): array|bool|string + { + return $this->initializeLazyObject()->client(...\func_get_args()); + } + + public function close(): bool + { + return $this->initializeLazyObject()->close(...\func_get_args()); + } + + public function cluster($key_or_address, $command, ...$extra_args): mixed + { + return $this->initializeLazyObject()->cluster(...\func_get_args()); + } + + public function command(...$extra_args): mixed + { + return $this->initializeLazyObject()->command(...\func_get_args()); + } + + public function config($key_or_address, $subcommand, ...$extra_args): mixed + { + return $this->initializeLazyObject()->config(...\func_get_args()); + } + + public function dbsize($key_or_address): \RedisCluster|int + { + return $this->initializeLazyObject()->dbsize(...\func_get_args()); + } + + public function copy($src, $dst, $options = null): \RedisCluster|bool + { + return $this->initializeLazyObject()->copy(...\func_get_args()); + } + + public function decr($key, $by = 1): \RedisCluster|false|int + { + return $this->initializeLazyObject()->decr(...\func_get_args()); + } + + public function decrby($key, $value): \RedisCluster|false|int + { + return $this->initializeLazyObject()->decrby(...\func_get_args()); + } + + public function decrbyfloat($key, $value): float + { + return $this->initializeLazyObject()->decrbyfloat(...\func_get_args()); + } + + public function del($key, ...$other_keys): \RedisCluster|false|int + { + return $this->initializeLazyObject()->del(...\func_get_args()); + } + + public function discard(): bool + { + return $this->initializeLazyObject()->discard(...\func_get_args()); + } + + public function dump($key): \RedisCluster|false|string + { + return $this->initializeLazyObject()->dump(...\func_get_args()); + } + + public function echo($key_or_address, $msg): \RedisCluster|false|string + { + return $this->initializeLazyObject()->echo(...\func_get_args()); + } + + public function eval($script, $args = [], $num_keys = 0): mixed + { + return $this->initializeLazyObject()->eval(...\func_get_args()); + } + + public function eval_ro($script, $args = [], $num_keys = 0): mixed + { + return $this->initializeLazyObject()->eval_ro(...\func_get_args()); + } + + public function evalsha($script_sha, $args = [], $num_keys = 0): mixed + { + return $this->initializeLazyObject()->evalsha(...\func_get_args()); + } + + public function evalsha_ro($script_sha, $args = [], $num_keys = 0): mixed + { + return $this->initializeLazyObject()->evalsha_ro(...\func_get_args()); + } + + public function exec(): array|false + { + return $this->initializeLazyObject()->exec(...\func_get_args()); + } + + public function exists($key, ...$other_keys): \RedisCluster|bool|int + { + return $this->initializeLazyObject()->exists(...\func_get_args()); + } + + public function touch($key, ...$other_keys): \RedisCluster|bool|int + { + return $this->initializeLazyObject()->touch(...\func_get_args()); + } + + public function expire($key, $timeout, $mode = null): \RedisCluster|bool + { + return $this->initializeLazyObject()->expire(...\func_get_args()); + } + + public function expireat($key, $timestamp, $mode = null): \RedisCluster|bool + { + return $this->initializeLazyObject()->expireat(...\func_get_args()); + } + + public function expiretime($key): \RedisCluster|false|int + { + return $this->initializeLazyObject()->expiretime(...\func_get_args()); + } + + public function pexpiretime($key): \RedisCluster|false|int + { + return $this->initializeLazyObject()->pexpiretime(...\func_get_args()); + } + + public function flushall($key_or_address, $async = false): \RedisCluster|bool + { + return $this->initializeLazyObject()->flushall(...\func_get_args()); + } + + public function flushdb($key_or_address, $async = false): \RedisCluster|bool + { + return $this->initializeLazyObject()->flushdb(...\func_get_args()); + } + + public function geoadd($key, $lng, $lat, $member, ...$other_triples_and_options): \RedisCluster|false|int + { + return $this->initializeLazyObject()->geoadd(...\func_get_args()); + } + + public function geodist($key, $src, $dest, $unit = null): \RedisCluster|false|float + { + return $this->initializeLazyObject()->geodist(...\func_get_args()); + } + + public function geohash($key, $member, ...$other_members): \RedisCluster|array|false + { + return $this->initializeLazyObject()->geohash(...\func_get_args()); + } + + public function geopos($key, $member, ...$other_members): \RedisCluster|array|false + { + return $this->initializeLazyObject()->geopos(...\func_get_args()); + } + + public function georadius($key, $lng, $lat, $radius, $unit, $options = []): mixed + { + return $this->initializeLazyObject()->georadius(...\func_get_args()); + } + + public function georadius_ro($key, $lng, $lat, $radius, $unit, $options = []): mixed + { + return $this->initializeLazyObject()->georadius_ro(...\func_get_args()); + } + + public function georadiusbymember($key, $member, $radius, $unit, $options = []): mixed + { + return $this->initializeLazyObject()->georadiusbymember(...\func_get_args()); + } + + public function georadiusbymember_ro($key, $member, $radius, $unit, $options = []): mixed + { + return $this->initializeLazyObject()->georadiusbymember_ro(...\func_get_args()); + } + + public function geosearch($key, $position, $shape, $unit, $options = []): \RedisCluster|array + { + return $this->initializeLazyObject()->geosearch(...\func_get_args()); + } + + public function geosearchstore($dst, $src, $position, $shape, $unit, $options = []): \RedisCluster|array|false|int + { + return $this->initializeLazyObject()->geosearchstore(...\func_get_args()); + } + + public function get($key): mixed + { + return $this->initializeLazyObject()->get(...\func_get_args()); + } + + public function getbit($key, $value): \RedisCluster|false|int + { + return $this->initializeLazyObject()->getbit(...\func_get_args()); + } + + public function getlasterror(): ?string + { + return $this->initializeLazyObject()->getlasterror(...\func_get_args()); + } + + public function getmode(): int + { + return $this->initializeLazyObject()->getmode(...\func_get_args()); + } + + public function getoption($option): mixed + { + return $this->initializeLazyObject()->getoption(...\func_get_args()); + } + + public function getrange($key, $start, $end): \RedisCluster|false|string + { + return $this->initializeLazyObject()->getrange(...\func_get_args()); + } + + public function lcs($key1, $key2, $options = null): \RedisCluster|array|false|int|string + { + return $this->initializeLazyObject()->lcs(...\func_get_args()); + } + + public function getset($key, $value): \RedisCluster|bool|string + { + return $this->initializeLazyObject()->getset(...\func_get_args()); + } + + public function gettransferredbytes(): array|false + { + return $this->initializeLazyObject()->gettransferredbytes(...\func_get_args()); + } + + public function cleartransferredbytes(): void + { + $this->initializeLazyObject()->cleartransferredbytes(...\func_get_args()); + } + + public function hdel($key, $member, ...$other_members): \RedisCluster|false|int + { + return $this->initializeLazyObject()->hdel(...\func_get_args()); + } + + public function hexists($key, $member): \RedisCluster|bool + { + return $this->initializeLazyObject()->hexists(...\func_get_args()); + } + + public function hget($key, $member): mixed + { + return $this->initializeLazyObject()->hget(...\func_get_args()); + } + + public function hgetall($key): \RedisCluster|array|false + { + return $this->initializeLazyObject()->hgetall(...\func_get_args()); + } + + public function hincrby($key, $member, $value): \RedisCluster|false|int + { + return $this->initializeLazyObject()->hincrby(...\func_get_args()); + } + + public function hincrbyfloat($key, $member, $value): \RedisCluster|false|float + { + return $this->initializeLazyObject()->hincrbyfloat(...\func_get_args()); + } + + public function hkeys($key): \RedisCluster|array|false + { + return $this->initializeLazyObject()->hkeys(...\func_get_args()); + } + + public function hlen($key): \RedisCluster|false|int + { + return $this->initializeLazyObject()->hlen(...\func_get_args()); + } + + public function hmget($key, $keys): \RedisCluster|array|false + { + return $this->initializeLazyObject()->hmget(...\func_get_args()); + } + + public function hmset($key, $key_values): \RedisCluster|bool + { + return $this->initializeLazyObject()->hmset(...\func_get_args()); + } + + public function hscan($key, &$iterator, $pattern = null, $count = 0): array|bool + { + return $this->initializeLazyObject()->hscan($key, $iterator, ...\array_slice(\func_get_args(), 2)); + } + + public function hrandfield($key, $options = null): \RedisCluster|array|string + { + return $this->initializeLazyObject()->hrandfield(...\func_get_args()); + } + + public function hset($key, $member, $value): \RedisCluster|false|int + { + return $this->initializeLazyObject()->hset(...\func_get_args()); + } + + public function hsetnx($key, $member, $value): \RedisCluster|bool + { + return $this->initializeLazyObject()->hsetnx(...\func_get_args()); + } + + public function hstrlen($key, $field): \RedisCluster|false|int + { + return $this->initializeLazyObject()->hstrlen(...\func_get_args()); + } + + public function hvals($key): \RedisCluster|array|false + { + return $this->initializeLazyObject()->hvals(...\func_get_args()); + } + + public function incr($key, $by = 1): \RedisCluster|false|int + { + return $this->initializeLazyObject()->incr(...\func_get_args()); + } + + public function incrby($key, $value): \RedisCluster|false|int + { + return $this->initializeLazyObject()->incrby(...\func_get_args()); + } + + public function incrbyfloat($key, $value): \RedisCluster|false|float + { + return $this->initializeLazyObject()->incrbyfloat(...\func_get_args()); + } + + public function info($key_or_address, ...$sections): \RedisCluster|array|false + { + return $this->initializeLazyObject()->info(...\func_get_args()); + } + + public function keys($pattern): \RedisCluster|array|false + { + return $this->initializeLazyObject()->keys(...\func_get_args()); + } + + public function lastsave($key_or_address): \RedisCluster|false|int + { + return $this->initializeLazyObject()->lastsave(...\func_get_args()); + } + + public function lget($key, $index): \RedisCluster|bool|string + { + return $this->initializeLazyObject()->lget(...\func_get_args()); + } + + public function lindex($key, $index): mixed + { + return $this->initializeLazyObject()->lindex(...\func_get_args()); + } + + public function linsert($key, $pos, $pivot, $value): \RedisCluster|false|int + { + return $this->initializeLazyObject()->linsert(...\func_get_args()); + } + + public function llen($key): \RedisCluster|bool|int + { + return $this->initializeLazyObject()->llen(...\func_get_args()); + } + + public function lpop($key, $count = 0): \RedisCluster|array|bool|string + { + return $this->initializeLazyObject()->lpop(...\func_get_args()); + } + + public function lpos($key, $value, $options = null): \Redis|array|bool|int|null + { + return $this->initializeLazyObject()->lpos(...\func_get_args()); + } + + public function lpush($key, $value, ...$other_values): \RedisCluster|bool|int + { + return $this->initializeLazyObject()->lpush(...\func_get_args()); + } + + public function lpushx($key, $value): \RedisCluster|bool|int + { + return $this->initializeLazyObject()->lpushx(...\func_get_args()); + } + + public function lrange($key, $start, $end): \RedisCluster|array|false + { + return $this->initializeLazyObject()->lrange(...\func_get_args()); + } + + public function lrem($key, $value, $count = 0): \RedisCluster|bool|int + { + return $this->initializeLazyObject()->lrem(...\func_get_args()); + } + + public function lset($key, $index, $value): \RedisCluster|bool + { + return $this->initializeLazyObject()->lset(...\func_get_args()); + } + + public function ltrim($key, $start, $end): \RedisCluster|bool + { + return $this->initializeLazyObject()->ltrim(...\func_get_args()); + } + + public function mget($keys): \RedisCluster|array|false + { + return $this->initializeLazyObject()->mget(...\func_get_args()); + } + + public function mset($key_values): \RedisCluster|bool + { + return $this->initializeLazyObject()->mset(...\func_get_args()); + } + + public function msetnx($key_values): \RedisCluster|array|false + { + return $this->initializeLazyObject()->msetnx(...\func_get_args()); + } + + public function multi($value = \Redis::MULTI): \RedisCluster|bool + { + return $this->initializeLazyObject()->multi(...\func_get_args()); + } + + public function object($subcommand, $key): \RedisCluster|false|int|string + { + return $this->initializeLazyObject()->object(...\func_get_args()); + } + + public function persist($key): \RedisCluster|bool + { + return $this->initializeLazyObject()->persist(...\func_get_args()); + } + + public function pexpire($key, $timeout, $mode = null): \RedisCluster|bool + { + return $this->initializeLazyObject()->pexpire(...\func_get_args()); + } + + public function pexpireat($key, $timestamp, $mode = null): \RedisCluster|bool + { + return $this->initializeLazyObject()->pexpireat(...\func_get_args()); + } + + public function pfadd($key, $elements): \RedisCluster|bool + { + return $this->initializeLazyObject()->pfadd(...\func_get_args()); + } + + public function pfcount($key): \RedisCluster|false|int + { + return $this->initializeLazyObject()->pfcount(...\func_get_args()); + } + + public function pfmerge($key, $keys): \RedisCluster|bool + { + return $this->initializeLazyObject()->pfmerge(...\func_get_args()); + } + + public function ping($key_or_address, $message = null): mixed + { + return $this->initializeLazyObject()->ping(...\func_get_args()); + } + + public function psetex($key, $timeout, $value): \RedisCluster|bool + { + return $this->initializeLazyObject()->psetex(...\func_get_args()); + } + + public function psubscribe($patterns, $callback): void + { + $this->initializeLazyObject()->psubscribe(...\func_get_args()); + } + + public function pttl($key): \RedisCluster|false|int + { + return $this->initializeLazyObject()->pttl(...\func_get_args()); + } + + public function pubsub($key_or_address, ...$values): mixed + { + return $this->initializeLazyObject()->pubsub(...\func_get_args()); + } + + public function punsubscribe($pattern, ...$other_patterns): array|bool + { + return $this->initializeLazyObject()->punsubscribe(...\func_get_args()); + } + + public function randomkey($key_or_address): \RedisCluster|bool|string + { + return $this->initializeLazyObject()->randomkey(...\func_get_args()); + } + + public function rawcommand($key_or_address, $command, ...$args): mixed + { + return $this->initializeLazyObject()->rawcommand(...\func_get_args()); + } + + public function rename($key_src, $key_dst): \RedisCluster|bool + { + return $this->initializeLazyObject()->rename(...\func_get_args()); + } + + public function renamenx($key, $newkey): \RedisCluster|bool + { + return $this->initializeLazyObject()->renamenx(...\func_get_args()); + } + + public function restore($key, $timeout, $value, $options = null): \RedisCluster|bool + { + return $this->initializeLazyObject()->restore(...\func_get_args()); + } + + public function role($key_or_address): mixed + { + return $this->initializeLazyObject()->role(...\func_get_args()); + } + + public function rpop($key, $count = 0): \RedisCluster|array|bool|string + { + return $this->initializeLazyObject()->rpop(...\func_get_args()); + } + + public function rpoplpush($src, $dst): \RedisCluster|bool|string + { + return $this->initializeLazyObject()->rpoplpush(...\func_get_args()); + } + + public function rpush($key, ...$elements): \RedisCluster|false|int + { + return $this->initializeLazyObject()->rpush(...\func_get_args()); + } + + public function rpushx($key, $value): \RedisCluster|bool|int + { + return $this->initializeLazyObject()->rpushx(...\func_get_args()); + } + + public function sadd($key, $value, ...$other_values): \RedisCluster|false|int + { + return $this->initializeLazyObject()->sadd(...\func_get_args()); + } + + public function saddarray($key, $values): \RedisCluster|bool|int + { + return $this->initializeLazyObject()->saddarray(...\func_get_args()); + } + + public function save($key_or_address): \RedisCluster|bool + { + return $this->initializeLazyObject()->save(...\func_get_args()); + } + + public function scan(&$iterator, $key_or_address, $pattern = null, $count = 0): array|bool + { + return $this->initializeLazyObject()->scan($iterator, ...\array_slice(\func_get_args(), 1)); + } + + public function scard($key): \RedisCluster|false|int + { + return $this->initializeLazyObject()->scard(...\func_get_args()); + } + + public function script($key_or_address, ...$args): mixed + { + return $this->initializeLazyObject()->script(...\func_get_args()); + } + + public function sdiff($key, ...$other_keys): \RedisCluster|array|false + { + return $this->initializeLazyObject()->sdiff(...\func_get_args()); + } + + public function sdiffstore($dst, $key, ...$other_keys): \RedisCluster|false|int + { + return $this->initializeLazyObject()->sdiffstore(...\func_get_args()); + } + + public function set($key, $value, $options = null): \RedisCluster|bool|string + { + return $this->initializeLazyObject()->set(...\func_get_args()); + } + + public function setbit($key, $offset, $onoff): \RedisCluster|false|int + { + return $this->initializeLazyObject()->setbit(...\func_get_args()); + } + + public function setex($key, $expire, $value): \RedisCluster|bool + { + return $this->initializeLazyObject()->setex(...\func_get_args()); + } + + public function setnx($key, $value): \RedisCluster|bool + { + return $this->initializeLazyObject()->setnx(...\func_get_args()); + } + + public function setoption($option, $value): bool + { + return $this->initializeLazyObject()->setoption(...\func_get_args()); + } + + public function setrange($key, $offset, $value): \RedisCluster|false|int + { + return $this->initializeLazyObject()->setrange(...\func_get_args()); + } + + public function sinter($key, ...$other_keys): \RedisCluster|array|false + { + return $this->initializeLazyObject()->sinter(...\func_get_args()); + } + + public function sintercard($keys, $limit = -1): \RedisCluster|false|int + { + return $this->initializeLazyObject()->sintercard(...\func_get_args()); + } + + public function sinterstore($key, ...$other_keys): \RedisCluster|false|int + { + return $this->initializeLazyObject()->sinterstore(...\func_get_args()); + } + + public function sismember($key, $value): \RedisCluster|bool + { + return $this->initializeLazyObject()->sismember(...\func_get_args()); + } + + public function smismember($key, $member, ...$other_members): \RedisCluster|array|false + { + return $this->initializeLazyObject()->smismember(...\func_get_args()); + } + + public function slowlog($key_or_address, ...$args): mixed + { + return $this->initializeLazyObject()->slowlog(...\func_get_args()); + } + + public function smembers($key): \RedisCluster|array|false + { + return $this->initializeLazyObject()->smembers(...\func_get_args()); + } + + public function smove($src, $dst, $member): \RedisCluster|bool + { + return $this->initializeLazyObject()->smove(...\func_get_args()); + } + + public function sort($key, $options = null): \RedisCluster|array|bool|int|string + { + return $this->initializeLazyObject()->sort(...\func_get_args()); + } + + public function sort_ro($key, $options = null): \RedisCluster|array|bool|int|string + { + return $this->initializeLazyObject()->sort_ro(...\func_get_args()); + } + + public function spop($key, $count = 0): \RedisCluster|array|false|string + { + return $this->initializeLazyObject()->spop(...\func_get_args()); + } + + public function srandmember($key, $count = 0): \RedisCluster|array|false|string + { + return $this->initializeLazyObject()->srandmember(...\func_get_args()); + } + + public function srem($key, $value, ...$other_values): \RedisCluster|false|int + { + return $this->initializeLazyObject()->srem(...\func_get_args()); + } + + public function sscan($key, &$iterator, $pattern = null, $count = 0): array|false + { + return $this->initializeLazyObject()->sscan($key, $iterator, ...\array_slice(\func_get_args(), 2)); + } + + public function strlen($key): \RedisCluster|false|int + { + return $this->initializeLazyObject()->strlen(...\func_get_args()); + } + + public function subscribe($channels, $cb): void + { + $this->initializeLazyObject()->subscribe(...\func_get_args()); + } + + public function sunion($key, ...$other_keys): \RedisCluster|array|bool + { + return $this->initializeLazyObject()->sunion(...\func_get_args()); + } + + public function sunionstore($dst, $key, ...$other_keys): \RedisCluster|false|int + { + return $this->initializeLazyObject()->sunionstore(...\func_get_args()); + } + + public function time($key_or_address): \RedisCluster|array|bool + { + return $this->initializeLazyObject()->time(...\func_get_args()); + } + + public function ttl($key): \RedisCluster|false|int + { + return $this->initializeLazyObject()->ttl(...\func_get_args()); + } + + public function type($key): \RedisCluster|false|int + { + return $this->initializeLazyObject()->type(...\func_get_args()); + } + + public function unsubscribe($channels): array|bool + { + return $this->initializeLazyObject()->unsubscribe(...\func_get_args()); + } + + public function unlink($key, ...$other_keys): \RedisCluster|false|int + { + return $this->initializeLazyObject()->unlink(...\func_get_args()); + } + + public function unwatch(): bool + { + return $this->initializeLazyObject()->unwatch(...\func_get_args()); + } + + public function watch($key, ...$other_keys): \RedisCluster|bool + { + return $this->initializeLazyObject()->watch(...\func_get_args()); + } + + public function xack($key, $group, $ids): \RedisCluster|false|int + { + return $this->initializeLazyObject()->xack(...\func_get_args()); + } + + public function xadd($key, $id, $values, $maxlen = 0, $approx = false): \RedisCluster|false|string + { + return $this->initializeLazyObject()->xadd(...\func_get_args()); + } + + public function xclaim($key, $group, $consumer, $min_iddle, $ids, $options): \RedisCluster|array|false|string + { + return $this->initializeLazyObject()->xclaim(...\func_get_args()); + } + + public function xdel($key, $ids): \RedisCluster|false|int + { + return $this->initializeLazyObject()->xdel(...\func_get_args()); + } + + public function xgroup($operation, $key = null, $group = null, $id_or_consumer = null, $mkstream = false, $entries_read = -2): mixed + { + return $this->initializeLazyObject()->xgroup(...\func_get_args()); + } + + public function xautoclaim($key, $group, $consumer, $min_idle, $start, $count = -1, $justid = false): \RedisCluster|array|bool + { + return $this->initializeLazyObject()->xautoclaim(...\func_get_args()); + } + + public function xinfo($operation, $arg1 = null, $arg2 = null, $count = -1): mixed + { + return $this->initializeLazyObject()->xinfo(...\func_get_args()); + } + + public function xlen($key): \RedisCluster|false|int + { + return $this->initializeLazyObject()->xlen(...\func_get_args()); + } + + public function xpending($key, $group, $start = null, $end = null, $count = -1, $consumer = null): \RedisCluster|array|false + { + return $this->initializeLazyObject()->xpending(...\func_get_args()); + } + + public function xrange($key, $start, $end, $count = -1): \RedisCluster|array|bool + { + return $this->initializeLazyObject()->xrange(...\func_get_args()); + } + + public function xread($streams, $count = -1, $block = -1): \RedisCluster|array|bool + { + return $this->initializeLazyObject()->xread(...\func_get_args()); + } + + public function xreadgroup($group, $consumer, $streams, $count = 1, $block = 1): \RedisCluster|array|bool + { + return $this->initializeLazyObject()->xreadgroup(...\func_get_args()); + } + + public function xrevrange($key, $start, $end, $count = -1): \RedisCluster|array|bool + { + return $this->initializeLazyObject()->xrevrange(...\func_get_args()); + } + + public function xtrim($key, $maxlen, $approx = false, $minid = false, $limit = -1): \RedisCluster|false|int + { + return $this->initializeLazyObject()->xtrim(...\func_get_args()); + } + + public function zadd($key, $score_or_options, ...$more_scores_and_mems): \RedisCluster|false|float|int + { + return $this->initializeLazyObject()->zadd(...\func_get_args()); + } + + public function zcard($key): \RedisCluster|false|int + { + return $this->initializeLazyObject()->zcard(...\func_get_args()); + } + + public function zcount($key, $start, $end): \RedisCluster|false|int + { + return $this->initializeLazyObject()->zcount(...\func_get_args()); + } + + public function zincrby($key, $value, $member): \RedisCluster|false|float + { + return $this->initializeLazyObject()->zincrby(...\func_get_args()); + } + + public function zinterstore($dst, $keys, $weights = null, $aggregate = null): \RedisCluster|false|int + { + return $this->initializeLazyObject()->zinterstore(...\func_get_args()); + } + + public function zintercard($keys, $limit = -1): \RedisCluster|false|int + { + return $this->initializeLazyObject()->zintercard(...\func_get_args()); + } + + public function zlexcount($key, $min, $max): \RedisCluster|false|int + { + return $this->initializeLazyObject()->zlexcount(...\func_get_args()); + } + + public function zpopmax($key, $value = null): \RedisCluster|array|bool + { + return $this->initializeLazyObject()->zpopmax(...\func_get_args()); + } + + public function zpopmin($key, $value = null): \RedisCluster|array|bool + { + return $this->initializeLazyObject()->zpopmin(...\func_get_args()); + } + + public function zrange($key, $start, $end, $options = null): \RedisCluster|array|bool + { + return $this->initializeLazyObject()->zrange(...\func_get_args()); + } + + public function zrangestore($dstkey, $srckey, $start, $end, $options = null): \RedisCluster|false|int + { + return $this->initializeLazyObject()->zrangestore(...\func_get_args()); + } + + public function zrandmember($key, $options = null): \RedisCluster|array|string + { + return $this->initializeLazyObject()->zrandmember(...\func_get_args()); + } + + public function zrangebylex($key, $min, $max, $offset = -1, $count = -1): \RedisCluster|array|false + { + return $this->initializeLazyObject()->zrangebylex(...\func_get_args()); + } + + public function zrangebyscore($key, $start, $end, $options = []): \RedisCluster|array|false + { + return $this->initializeLazyObject()->zrangebyscore(...\func_get_args()); + } + + public function zrank($key, $member): \RedisCluster|false|int + { + return $this->initializeLazyObject()->zrank(...\func_get_args()); + } + + public function zrem($key, $value, ...$other_values): \RedisCluster|false|int + { + return $this->initializeLazyObject()->zrem(...\func_get_args()); + } + + public function zremrangebylex($key, $min, $max): \RedisCluster|false|int + { + return $this->initializeLazyObject()->zremrangebylex(...\func_get_args()); + } + + public function zremrangebyrank($key, $min, $max): \RedisCluster|false|int + { + return $this->initializeLazyObject()->zremrangebyrank(...\func_get_args()); + } + + public function zremrangebyscore($key, $min, $max): \RedisCluster|false|int + { + return $this->initializeLazyObject()->zremrangebyscore(...\func_get_args()); + } + + public function zrevrange($key, $min, $max, $options = null): \RedisCluster|array|bool + { + return $this->initializeLazyObject()->zrevrange(...\func_get_args()); + } + + public function zrevrangebylex($key, $min, $max, $options = null): \RedisCluster|array|bool + { + return $this->initializeLazyObject()->zrevrangebylex(...\func_get_args()); + } + + public function zrevrangebyscore($key, $min, $max, $options = null): \RedisCluster|array|bool + { + return $this->initializeLazyObject()->zrevrangebyscore(...\func_get_args()); + } + + public function zrevrank($key, $member): \RedisCluster|false|int + { + return $this->initializeLazyObject()->zrevrank(...\func_get_args()); + } + + public function zscan($key, &$iterator, $pattern = null, $count = 0): \RedisCluster|array|bool + { + return $this->initializeLazyObject()->zscan($key, $iterator, ...\array_slice(\func_get_args(), 2)); + } + + public function zscore($key, $member): \RedisCluster|false|float + { + return $this->initializeLazyObject()->zscore(...\func_get_args()); + } + + public function zmscore($key, $member, ...$other_members): \Redis|array|false + { + return $this->initializeLazyObject()->zmscore(...\func_get_args()); + } + + public function zunionstore($dst, $keys, $weights = null, $aggregate = null): \RedisCluster|false|int + { + return $this->initializeLazyObject()->zunionstore(...\func_get_args()); + } + + public function zinter($keys, $weights = null, $options = null): \RedisCluster|array|false + { + return $this->initializeLazyObject()->zinter(...\func_get_args()); + } + + public function zdiffstore($dst, $keys): \RedisCluster|false|int + { + return $this->initializeLazyObject()->zdiffstore(...\func_get_args()); + } + + public function zunion($keys, $weights = null, $options = null): \RedisCluster|array|false + { + return $this->initializeLazyObject()->zunion(...\func_get_args()); + } + + public function zdiff($keys, $options = null): \RedisCluster|array|false + { + return $this->initializeLazyObject()->zdiff(...\func_get_args()); + } +} diff --git a/vendor/symfony/cache/Traits/RedisCluster6ProxyTrait.php b/vendor/symfony/cache/Traits/RedisCluster6ProxyTrait.php new file mode 100644 index 0000000..5033c01 --- /dev/null +++ b/vendor/symfony/cache/Traits/RedisCluster6ProxyTrait.php @@ -0,0 +1,46 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Cache\Traits; + +if (version_compare(phpversion('redis'), '6.1.0-dev', '>')) { + /** + * @internal + */ + trait RedisCluster6ProxyTrait + { + public function getex($key, $options = []): \RedisCluster|string|false + { + return $this->initializeLazyObject()->getex(...\func_get_args()); + } + + public function publish($channel, $message): \RedisCluster|bool|int + { + return $this->initializeLazyObject()->publish(...\func_get_args()); + } + + public function waitaof($key_or_address, $numlocal, $numreplicas, $timeout): \RedisCluster|array|false + { + return $this->initializeLazyObject()->waitaof(...\func_get_args()); + } + } +} else { + /** + * @internal + */ + trait RedisCluster6ProxyTrait + { + public function publish($channel, $message): \RedisCluster|bool + { + return $this->initializeLazyObject()->publish(...\func_get_args()); + } + } +} diff --git a/vendor/symfony/cache/Traits/RedisClusterNodeProxy.php b/vendor/symfony/cache/Traits/RedisClusterNodeProxy.php new file mode 100644 index 0000000..f5c0baa --- /dev/null +++ b/vendor/symfony/cache/Traits/RedisClusterNodeProxy.php @@ -0,0 +1,47 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Cache\Traits; + +/** + * This file acts as a wrapper to the \RedisCluster implementation so it can accept the same type of calls as + * individual \Redis objects. + * + * Calls are made to individual nodes via: RedisCluster->{method}($host, ...args)' + * according to https://github.com/phpredis/phpredis/blob/develop/cluster.markdown#directed-node-commands + * + * @author Jack Thomas + * + * @internal + */ +class RedisClusterNodeProxy +{ + public function __construct( + private array $host, + private \RedisCluster $redis, + ) { + } + + public function __call(string $method, array $args) + { + return $this->redis->{$method}($this->host, ...$args); + } + + public function scan(&$iIterator, $strPattern = null, $iCount = null) + { + return $this->redis->scan($iIterator, $this->host, $strPattern, $iCount); + } + + public function getOption($name) + { + return $this->redis->getOption($name); + } +} diff --git a/vendor/symfony/cache/Traits/RedisClusterProxy.php b/vendor/symfony/cache/Traits/RedisClusterProxy.php new file mode 100644 index 0000000..c67d534 --- /dev/null +++ b/vendor/symfony/cache/Traits/RedisClusterProxy.php @@ -0,0 +1,23 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Cache\Traits; + +class_alias(6.0 <= (float) phpversion('redis') ? RedisCluster6Proxy::class : RedisCluster5Proxy::class, RedisClusterProxy::class); + +if (false) { + /** + * @internal + */ + class RedisClusterProxy extends \RedisCluster + { + } +} diff --git a/vendor/symfony/cache/Traits/RedisProxy.php b/vendor/symfony/cache/Traits/RedisProxy.php new file mode 100644 index 0000000..7f4537b --- /dev/null +++ b/vendor/symfony/cache/Traits/RedisProxy.php @@ -0,0 +1,23 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Cache\Traits; + +class_alias(6.0 <= (float) phpversion('redis') ? Redis6Proxy::class : Redis5Proxy::class, RedisProxy::class); + +if (false) { + /** + * @internal + */ + class RedisProxy extends \Redis + { + } +} diff --git a/vendor/symfony/cache/Traits/RedisProxyTrait.php b/vendor/symfony/cache/Traits/RedisProxyTrait.php new file mode 100644 index 0000000..1acbdf1 --- /dev/null +++ b/vendor/symfony/cache/Traits/RedisProxyTrait.php @@ -0,0 +1,51 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Cache\Traits; + +/** + * @internal + */ +trait RedisProxyTrait +{ + private \Closure $initializer; + private ?parent $realInstance = null; + + public static function createLazyProxy(\Closure $initializer, ?self $instance = null): static + { + $instance ??= (new \ReflectionClass(static::class))->newInstanceWithoutConstructor(); + $instance->realInstance = null; + $instance->initializer = $initializer; + + return $instance; + } + + public function isLazyObjectInitialized(bool $partial = false): bool + { + return isset($this->realInstance); + } + + public function initializeLazyObject(): object + { + return $this->realInstance ??= ($this->initializer)(); + } + + public function resetLazyObject(): bool + { + $this->realInstance = null; + + return true; + } + + public function __destruct() + { + } +} diff --git a/vendor/symfony/cache/Traits/RedisTrait.php b/vendor/symfony/cache/Traits/RedisTrait.php new file mode 100644 index 0000000..87c6892 --- /dev/null +++ b/vendor/symfony/cache/Traits/RedisTrait.php @@ -0,0 +1,682 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Cache\Traits; + +use Predis\Command\Redis\UNLINK; +use Predis\Connection\Aggregate\ClusterInterface; +use Predis\Connection\Aggregate\RedisCluster; +use Predis\Connection\Aggregate\ReplicationInterface; +use Predis\Connection\Cluster\ClusterInterface as Predis2ClusterInterface; +use Predis\Connection\Cluster\RedisCluster as Predis2RedisCluster; +use Predis\Response\ErrorInterface; +use Predis\Response\Status; +use Relay\Relay; +use Relay\Sentinel; +use Symfony\Component\Cache\Exception\CacheException; +use Symfony\Component\Cache\Exception\InvalidArgumentException; +use Symfony\Component\Cache\Marshaller\DefaultMarshaller; +use Symfony\Component\Cache\Marshaller\MarshallerInterface; + +/** + * @author Aurimas Niekis + * @author Nicolas Grekas + * + * @internal + */ +trait RedisTrait +{ + private static array $defaultConnectionOptions = [ + 'class' => null, + 'persistent' => 0, + 'persistent_id' => null, + 'timeout' => 30, + 'read_timeout' => 0, + 'retry_interval' => 0, + 'tcp_keepalive' => 0, + 'lazy' => null, + 'redis_cluster' => false, + 'redis_sentinel' => null, + 'dbindex' => 0, + 'failover' => 'none', + 'ssl' => null, // see https://php.net/context.ssl + ]; + private \Redis|Relay|\RedisArray|\RedisCluster|\Predis\ClientInterface $redis; + private MarshallerInterface $marshaller; + + private function init(\Redis|Relay|\RedisArray|\RedisCluster|\Predis\ClientInterface $redis, string $namespace, int $defaultLifetime, ?MarshallerInterface $marshaller): void + { + parent::__construct($namespace, $defaultLifetime); + + if (preg_match('#[^-+_.A-Za-z0-9]#', $namespace, $match)) { + throw new InvalidArgumentException(\sprintf('RedisAdapter namespace contains "%s" but only characters in [-+_.A-Za-z0-9] are allowed.', $match[0])); + } + + if ($redis instanceof \Predis\ClientInterface && $redis->getOptions()->exceptions) { + $options = clone $redis->getOptions(); + \Closure::bind(function () { $this->options['exceptions'] = false; }, $options, $options)(); + $redis = new $redis($redis->getConnection(), $options); + } + + $this->redis = $redis; + $this->marshaller = $marshaller ?? new DefaultMarshaller(); + } + + /** + * Creates a Redis connection using a DSN configuration. + * + * Example DSN: + * - redis://localhost + * - redis://example.com:1234 + * - redis://secret@example.com/13 + * - redis:///var/run/redis.sock + * - redis://secret@/var/run/redis.sock/13 + * + * @param array $options See self::$defaultConnectionOptions + * + * @throws InvalidArgumentException when the DSN is invalid + */ + public static function createConnection(#[\SensitiveParameter] string $dsn, array $options = []): \Redis|\RedisArray|\RedisCluster|\Predis\ClientInterface|Relay + { + if (str_starts_with($dsn, 'redis:')) { + $scheme = 'redis'; + } elseif (str_starts_with($dsn, 'rediss:')) { + $scheme = 'rediss'; + } else { + throw new InvalidArgumentException('Invalid Redis DSN: it does not start with "redis[s]:".'); + } + + if (!\extension_loaded('redis') && !class_exists(\Predis\Client::class)) { + throw new CacheException('Cannot find the "redis" extension nor the "predis/predis" package.'); + } + + $params = preg_replace_callback('#^'.$scheme.':(//)?(?:(?:(?[^:@]*+):)?(?[^@]*+)@)?#', function ($m) use (&$auth) { + if (isset($m['password'])) { + if (\in_array($m['user'], ['', 'default'], true)) { + $auth = rawurldecode($m['password']); + } else { + $auth = [rawurldecode($m['user']), rawurldecode($m['password'])]; + } + + if ('' === $auth) { + $auth = null; + } + } + + return 'file:'.($m[1] ?? ''); + }, $dsn); + + if (false === $params = parse_url($params)) { + throw new InvalidArgumentException('Invalid Redis DSN.'); + } + + $query = $hosts = []; + + $tls = 'rediss' === $scheme; + $tcpScheme = $tls ? 'tls' : 'tcp'; + + if (isset($params['query'])) { + parse_str($params['query'], $query); + + if (isset($query['host'])) { + if (!\is_array($hosts = $query['host'])) { + throw new InvalidArgumentException('Invalid Redis DSN: query parameter "host" must be an array.'); + } + foreach ($hosts as $host => $parameters) { + if (\is_string($parameters)) { + parse_str($parameters, $parameters); + } + if (false === $i = strrpos($host, ':')) { + $hosts[$host] = ['scheme' => $tcpScheme, 'host' => $host, 'port' => 6379] + $parameters; + } elseif ($port = (int) substr($host, 1 + $i)) { + $hosts[$host] = ['scheme' => $tcpScheme, 'host' => substr($host, 0, $i), 'port' => $port] + $parameters; + } else { + $hosts[$host] = ['scheme' => 'unix', 'path' => substr($host, 0, $i)] + $parameters; + } + } + $hosts = array_values($hosts); + } + } + + if (isset($params['host']) || isset($params['path'])) { + if (!isset($params['dbindex']) && isset($params['path'])) { + if (preg_match('#/(\d+)?$#', $params['path'], $m)) { + $params['dbindex'] = $m[1] ?? $query['dbindex'] ?? '0'; + $params['path'] = substr($params['path'], 0, -\strlen($m[0])); + } elseif (isset($params['host'])) { + throw new InvalidArgumentException('Invalid Redis DSN: parameter "dbindex" must be a number.'); + } + } + + if (isset($params['host'])) { + array_unshift($hosts, ['scheme' => $tcpScheme, 'host' => $params['host'], 'port' => $params['port'] ?? 6379]); + } else { + array_unshift($hosts, ['scheme' => 'unix', 'path' => $params['path']]); + } + } + + if (!$hosts) { + throw new InvalidArgumentException('Invalid Redis DSN: missing host.'); + } + + if (isset($params['dbindex'], $query['dbindex']) && $params['dbindex'] !== $query['dbindex']) { + throw new InvalidArgumentException('Invalid Redis DSN: path and query "dbindex" parameters mismatch.'); + } + + $params += $query + $options + self::$defaultConnectionOptions; + + if (isset($params['redis_sentinel']) && isset($params['sentinel_master'])) { + throw new InvalidArgumentException('Cannot use both "redis_sentinel" and "sentinel_master" at the same time.'); + } + + $params['redis_sentinel'] ??= $params['sentinel_master'] ?? null; + + if (isset($params['redis_sentinel']) && !class_exists(\Predis\Client::class) && !class_exists(\RedisSentinel::class) && !class_exists(Sentinel::class)) { + throw new CacheException('Redis Sentinel support requires one of: "predis/predis", "ext-redis >= 5.2", "ext-relay".'); + } + + if (isset($params['lazy'])) { + $params['lazy'] = filter_var($params['lazy'], \FILTER_VALIDATE_BOOLEAN); + } + $params['redis_cluster'] = filter_var($params['redis_cluster'], \FILTER_VALIDATE_BOOLEAN); + + if ($params['redis_cluster'] && isset($params['redis_sentinel'])) { + throw new InvalidArgumentException('Cannot use both "redis_cluster" and "redis_sentinel" at the same time.'); + } + + $class = $params['class'] ?? match (true) { + $params['redis_cluster'] => \extension_loaded('redis') ? \RedisCluster::class : \Predis\Client::class, + isset($params['redis_sentinel']) => match (true) { + \extension_loaded('redis') => \Redis::class, + \extension_loaded('relay') => Relay::class, + default => \Predis\Client::class, + }, + 1 < \count($hosts) && \extension_loaded('redis') => \RedisArray::class, + \extension_loaded('redis') => \Redis::class, + \extension_loaded('relay') => Relay::class, + default => \Predis\Client::class, + }; + + if (isset($params['redis_sentinel']) && !is_a($class, \Predis\Client::class, true) && !class_exists(\RedisSentinel::class) && !class_exists(Sentinel::class)) { + throw new CacheException(\sprintf('Cannot use Redis Sentinel: class "%s" does not extend "Predis\Client" and neither ext-redis >= 5.2 nor ext-relay have been found.', $class)); + } + + $isRedisExt = is_a($class, \Redis::class, true); + $isRelayExt = !$isRedisExt && is_a($class, Relay::class, true); + + if ($isRedisExt || $isRelayExt) { + $connect = $params['persistent'] || $params['persistent_id'] ? 'pconnect' : 'connect'; + + $initializer = static function () use ($class, $isRedisExt, $connect, $params, $auth, $hosts, $tls) { + $sentinelClass = $isRedisExt ? \RedisSentinel::class : Sentinel::class; + $redis = new $class(); + $hostIndex = 0; + do { + $host = $hosts[$hostIndex]['host'] ?? $hosts[$hostIndex]['path']; + $port = $hosts[$hostIndex]['port'] ?? 0; + $passAuth = isset($params['auth']) && (!$isRedisExt || \defined('Redis::OPT_NULL_MULTIBULK_AS_NULL')); + $address = false; + + if (isset($hosts[$hostIndex]['host']) && $tls) { + $host = 'tls://'.$host; + } + + if (!isset($params['redis_sentinel'])) { + break; + } + + try { + if (version_compare(phpversion('redis'), '6.0.0', '>=') && $isRedisExt) { + $options = [ + 'host' => $host, + 'port' => $port, + 'connectTimeout' => (float) $params['timeout'], + 'persistent' => $params['persistent_id'], + 'retryInterval' => (int) $params['retry_interval'], + 'readTimeout' => (float) $params['read_timeout'], + ]; + + if ($passAuth) { + $options['auth'] = $params['auth']; + } + + $sentinel = new \RedisSentinel($options); + } else { + $extra = $passAuth ? [$params['auth']] : []; + + $sentinel = @new $sentinelClass($host, $port, $params['timeout'], (string) $params['persistent_id'], $params['retry_interval'], $params['read_timeout'], ...$extra); + } + + if ($address = @$sentinel->getMasterAddrByName($params['redis_sentinel'])) { + [$host, $port] = $address; + } + } catch (\RedisException|\Relay\Exception $redisException) { + } + } while (++$hostIndex < \count($hosts) && !$address); + + if (isset($params['redis_sentinel']) && !$address) { + throw new InvalidArgumentException(\sprintf('Failed to retrieve master information from sentinel "%s".', $params['redis_sentinel']), previous: $redisException ?? null); + } + + try { + $extra = [ + 'stream' => $params['ssl'] ?? null, + ]; + $booleanStreamOptions = [ + 'allow_self_signed', + 'capture_peer_cert', + 'capture_peer_cert_chain', + 'disable_compression', + 'SNI_enabled', + 'verify_peer', + 'verify_peer_name', + ]; + + foreach ($extra['stream'] ?? [] as $streamOption => $value) { + if (\in_array($streamOption, $booleanStreamOptions, true) && \is_string($value)) { + $extra['stream'][$streamOption] = filter_var($value, \FILTER_VALIDATE_BOOL); + } + } + + if (isset($params['auth'])) { + $extra['auth'] = $params['auth']; + } + @$redis->{$connect}($host, $port, (float) $params['timeout'], (string) $params['persistent_id'], $params['retry_interval'], $params['read_timeout'], ...\defined('Redis::SCAN_PREFIX') || !$isRedisExt ? [$extra] : []); + + set_error_handler(function ($type, $msg) use (&$error) { $error = $msg; }); + try { + $isConnected = $redis->isConnected(); + } finally { + restore_error_handler(); + } + if (!$isConnected) { + $error = preg_match('/^Redis::p?connect\(\): (.*)/', $error ?? $redis->getLastError() ?? '', $error) ? \sprintf(' (%s)', $error[1]) : ''; + throw new InvalidArgumentException('Redis connection failed: '.$error.'.'); + } + + if ((null !== $auth && !$redis->auth($auth)) + // Due to a bug in phpredis we must always select the dbindex if persistent pooling is enabled + // @see https://github.com/phpredis/phpredis/issues/1920 + // @see https://github.com/symfony/symfony/issues/51578 + || (($params['dbindex'] || ('pconnect' === $connect && '0' !== \ini_get('redis.pconnect.pooling_enabled'))) && !$redis->select($params['dbindex'])) + ) { + $e = preg_replace('/^ERR /', '', $redis->getLastError()); + throw new InvalidArgumentException('Redis connection failed: '.$e.'.'); + } + + if (0 < $params['tcp_keepalive'] && (!$isRedisExt || \defined('Redis::OPT_TCP_KEEPALIVE'))) { + $redis->setOption($isRedisExt ? \Redis::OPT_TCP_KEEPALIVE : Relay::OPT_TCP_KEEPALIVE, $params['tcp_keepalive']); + } + } catch (\RedisException|\Relay\Exception $e) { + throw new InvalidArgumentException('Redis connection failed: '.$e->getMessage()); + } + + return $redis; + }; + + if ($params['lazy']) { + $redis = $isRedisExt ? RedisProxy::createLazyProxy($initializer) : RelayProxy::createLazyProxy($initializer); + } else { + $redis = $initializer(); + } + } elseif (is_a($class, \RedisArray::class, true)) { + foreach ($hosts as $i => $host) { + $hosts[$i] = match ($host['scheme']) { + 'tcp' => $host['host'].':'.$host['port'], + 'tls' => 'tls://'.$host['host'].':'.$host['port'], + default => $host['path'], + }; + } + $params['lazy_connect'] = $params['lazy'] ?? true; + $params['connect_timeout'] = $params['timeout']; + + try { + $redis = new $class($hosts, $params); + } catch (\RedisClusterException $e) { + throw new InvalidArgumentException('Redis connection failed: '.$e->getMessage()); + } + + if (0 < $params['tcp_keepalive'] && (!$isRedisExt || \defined('Redis::OPT_TCP_KEEPALIVE'))) { + $redis->setOption($isRedisExt ? \Redis::OPT_TCP_KEEPALIVE : Relay::OPT_TCP_KEEPALIVE, $params['tcp_keepalive']); + } + } elseif (is_a($class, \RedisCluster::class, true)) { + $initializer = static function () use ($isRedisExt, $class, $params, $hosts) { + foreach ($hosts as $i => $host) { + $hosts[$i] = match ($host['scheme']) { + 'tcp' => $host['host'].':'.$host['port'], + 'tls' => 'tls://'.$host['host'].':'.$host['port'], + default => $host['path'], + }; + } + + try { + $redis = new $class(null, $hosts, $params['timeout'], $params['read_timeout'], (bool) $params['persistent'], $params['auth'] ?? '', ...\defined('Redis::SCAN_PREFIX') ? [$params['ssl'] ?? null] : []); + } catch (\RedisClusterException $e) { + throw new InvalidArgumentException('Redis connection failed: '.$e->getMessage()); + } + + if (0 < $params['tcp_keepalive'] && (!$isRedisExt || \defined('Redis::OPT_TCP_KEEPALIVE'))) { + $redis->setOption($isRedisExt ? \Redis::OPT_TCP_KEEPALIVE : Relay::OPT_TCP_KEEPALIVE, $params['tcp_keepalive']); + } + $redis->setOption(\RedisCluster::OPT_SLAVE_FAILOVER, match ($params['failover']) { + 'error' => \RedisCluster::FAILOVER_ERROR, + 'distribute' => \RedisCluster::FAILOVER_DISTRIBUTE, + 'slaves' => \RedisCluster::FAILOVER_DISTRIBUTE_SLAVES, + 'none' => \RedisCluster::FAILOVER_NONE, + }); + + return $redis; + }; + + $redis = $params['lazy'] ? RedisClusterProxy::createLazyProxy($initializer) : $initializer(); + } elseif (is_a($class, \Predis\ClientInterface::class, true)) { + if ($params['redis_cluster']) { + $params['cluster'] = 'redis'; + } elseif (isset($params['redis_sentinel'])) { + $params['replication'] = 'sentinel'; + $params['service'] = $params['redis_sentinel']; + } + $params += ['parameters' => []]; + $params['parameters'] += [ + 'persistent' => $params['persistent'], + 'timeout' => $params['timeout'], + 'read_write_timeout' => $params['read_timeout'], + 'tcp_nodelay' => true, + ]; + if ($params['dbindex']) { + $params['parameters']['database'] = $params['dbindex']; + } + if (null !== $auth) { + if (\is_array($auth)) { + // ACL + $params['parameters']['username'] = $auth[0]; + $params['parameters']['password'] = $auth[1]; + } else { + $params['parameters']['password'] = $auth; + } + } + + if (isset($params['ssl'])) { + foreach ($hosts as $i => $host) { + $hosts[$i]['ssl'] ??= $params['ssl']; + } + } + + if (1 === \count($hosts) && !($params['redis_cluster'] || $params['redis_sentinel'])) { + $hosts = $hosts[0]; + } elseif (\in_array($params['failover'], ['slaves', 'distribute'], true) && !isset($params['replication'])) { + $params['replication'] = true; + $hosts[0] += ['alias' => 'master']; + } + $params['exceptions'] = false; + + $redis = new $class($hosts, array_diff_key($params, self::$defaultConnectionOptions)); + if (isset($params['redis_sentinel'])) { + $redis->getConnection()->setSentinelTimeout($params['timeout']); + } + } elseif (class_exists($class, false)) { + throw new InvalidArgumentException(\sprintf('"%s" is not a subclass of "Redis", "RedisArray", "RedisCluster", "Relay\Relay" nor "Predis\ClientInterface".', $class)); + } else { + throw new InvalidArgumentException(\sprintf('Class "%s" does not exist.', $class)); + } + + return $redis; + } + + protected function doFetch(array $ids): iterable + { + if (!$ids) { + return []; + } + + $result = []; + + if ($this->redis instanceof \Predis\ClientInterface && ($this->redis->getConnection() instanceof ClusterInterface || $this->redis->getConnection() instanceof Predis2ClusterInterface)) { + $values = $this->pipeline(function () use ($ids) { + foreach ($ids as $id) { + yield 'get' => [$id]; + } + }); + } else { + $values = $this->redis->mget($ids); + + if (!\is_array($values) || \count($values) !== \count($ids)) { + return []; + } + + $values = array_combine($ids, $values); + } + + foreach ($values as $id => $v) { + if ($v) { + $result[$id] = $this->marshaller->unmarshall($v); + } + } + + return $result; + } + + protected function doHave(string $id): bool + { + return (bool) $this->redis->exists($id); + } + + protected function doClear(string $namespace): bool + { + if ($this->redis instanceof \Predis\ClientInterface) { + $prefix = $this->redis->getOptions()->prefix ? $this->redis->getOptions()->prefix->getPrefix() : ''; + $prefixLen = \strlen($prefix ?? ''); + } + + $cleared = true; + $hosts = $this->getHosts(); + $host = reset($hosts); + if ($host instanceof \Predis\Client && $host->getConnection() instanceof ReplicationInterface) { + // Predis supports info command only on the master in replication environments + $hosts = [$host->getClientFor('master')]; + } + + foreach ($hosts as $host) { + if (!isset($namespace[0])) { + $cleared = $host->flushDb() && $cleared; + continue; + } + + $info = $host->info('Server'); + $info = !$info instanceof ErrorInterface ? $info['Server'] ?? $info : ['redis_version' => '2.0']; + + if ($host instanceof Relay) { + $prefix = Relay::SCAN_PREFIX & $host->getOption(Relay::OPT_SCAN) ? '' : $host->getOption(Relay::OPT_PREFIX); + $prefixLen = \strlen($host->getOption(Relay::OPT_PREFIX) ?? ''); + } elseif (!$host instanceof \Predis\ClientInterface) { + $prefix = \defined('Redis::SCAN_PREFIX') && (\Redis::SCAN_PREFIX & $host->getOption(\Redis::OPT_SCAN)) ? '' : $host->getOption(\Redis::OPT_PREFIX); + $prefixLen = \strlen($host->getOption(\Redis::OPT_PREFIX) ?? ''); + } + $pattern = $prefix.$namespace.'*'; + + if (!version_compare($info['redis_version'], '2.8', '>=')) { + // As documented in Redis documentation (http://redis.io/commands/keys) using KEYS + // can hang your server when it is executed against large databases (millions of items). + // Whenever you hit this scale, you should really consider upgrading to Redis 2.8 or above. + $unlink = version_compare($info['redis_version'], '4.0', '>=') ? 'UNLINK' : 'DEL'; + $args = $this->redis instanceof \Predis\ClientInterface ? [0, $pattern] : [[$pattern], 0]; + $cleared = $host->eval("local keys=redis.call('KEYS',ARGV[1]) for i=1,#keys,5000 do redis.call('$unlink',unpack(keys,i,math.min(i+4999,#keys))) end return 1", $args[0], $args[1]) && $cleared; + continue; + } + + $cursor = null; + do { + $keys = $host instanceof \Predis\ClientInterface ? $host->scan($cursor ?? 0, 'MATCH', $pattern, 'COUNT', 1000) : $host->scan($cursor, $pattern, 1000); + if (isset($keys[1]) && \is_array($keys[1])) { + $cursor = $keys[0]; + $keys = $keys[1]; + } + if ($keys) { + if ($prefixLen) { + foreach ($keys as $i => $key) { + $keys[$i] = substr($key, $prefixLen); + } + } + $this->doDelete($keys); + } + } while ($cursor); + } + + return $cleared; + } + + protected function doDelete(array $ids): bool + { + if (!$ids) { + return true; + } + + if ($this->redis instanceof \Predis\ClientInterface && ($this->redis->getConnection() instanceof ClusterInterface || $this->redis->getConnection() instanceof Predis2ClusterInterface)) { + static $del; + $del ??= (class_exists(UNLINK::class) ? 'unlink' : 'del'); + + $this->pipeline(function () use ($ids, $del) { + foreach ($ids as $id) { + yield $del => [$id]; + } + })->rewind(); + } else { + static $unlink = true; + + if ($unlink) { + try { + $unlink = false !== $this->redis->unlink($ids); + } catch (\Throwable) { + $unlink = false; + } + } + + if (!$unlink) { + $this->redis->del($ids); + } + } + + return true; + } + + protected function doSave(array $values, int $lifetime): array|bool + { + if (!$values = $this->marshaller->marshall($values, $failed)) { + return $failed; + } + + $results = $this->pipeline(function () use ($values, $lifetime) { + foreach ($values as $id => $value) { + if (0 >= $lifetime) { + yield 'set' => [$id, $value]; + } else { + yield 'setEx' => [$id, $lifetime, $value]; + } + } + }); + + foreach ($results as $id => $result) { + if (true !== $result && (!$result instanceof Status || Status::get('OK') !== $result)) { + $failed[] = $id; + } + } + + return $failed; + } + + private function pipeline(\Closure $generator, ?object $redis = null): \Generator + { + $ids = []; + $redis ??= $this->redis; + + if ($redis instanceof \RedisCluster || ($redis instanceof \Predis\ClientInterface && ($redis->getConnection() instanceof RedisCluster || $redis->getConnection() instanceof Predis2RedisCluster))) { + // phpredis & predis don't support pipelining with RedisCluster + // see https://github.com/phpredis/phpredis/blob/develop/cluster.markdown#pipelining + // see https://github.com/nrk/predis/issues/267#issuecomment-123781423 + $results = []; + foreach ($generator() as $command => $args) { + $results[] = $redis->{$command}(...$args); + $ids[] = 'eval' === $command ? ($redis instanceof \Predis\ClientInterface ? $args[2] : $args[1][0]) : $args[0]; + } + } elseif ($redis instanceof \Predis\ClientInterface) { + $results = $redis->pipeline(static function ($redis) use ($generator, &$ids) { + foreach ($generator() as $command => $args) { + $redis->{$command}(...$args); + $ids[] = 'eval' === $command ? $args[2] : $args[0]; + } + }); + } elseif ($redis instanceof \RedisArray) { + $connections = $results = []; + foreach ($generator() as $command => $args) { + $id = 'eval' === $command ? $args[1][0] : $args[0]; + if (!isset($connections[$h = $redis->_target($id)])) { + $connections[$h] = [$redis->_instance($h), -1]; + $connections[$h][0]->multi(\Redis::PIPELINE); + } + $connections[$h][0]->{$command}(...$args); + $results[] = [$h, ++$connections[$h][1]]; + $ids[] = $id; + } + foreach ($connections as $h => $c) { + $connections[$h] = $c[0]->exec(); + } + foreach ($results as $k => [$h, $c]) { + $results[$k] = $connections[$h][$c]; + } + } else { + $redis->multi($redis instanceof Relay ? Relay::PIPELINE : \Redis::PIPELINE); + foreach ($generator() as $command => $args) { + $redis->{$command}(...$args); + $ids[] = 'eval' === $command ? $args[1][0] : $args[0]; + } + $results = $redis->exec(); + } + + if (!$redis instanceof \Predis\ClientInterface && 'eval' === $command && $redis->getLastError()) { + $e = $redis instanceof Relay ? new \Relay\Exception($redis->getLastError()) : new \RedisException($redis->getLastError()); + $results = array_map(fn ($v) => false === $v ? $e : $v, (array) $results); + } + + if (\is_bool($results)) { + return; + } + + foreach ($ids as $k => $id) { + yield $id => $results[$k]; + } + } + + private function getHosts(): array + { + $hosts = [$this->redis]; + if ($this->redis instanceof \Predis\ClientInterface) { + $connection = $this->redis->getConnection(); + if (($connection instanceof ClusterInterface || $connection instanceof Predis2ClusterInterface) && $connection instanceof \Traversable) { + $hosts = []; + foreach ($connection as $c) { + $hosts[] = new \Predis\Client($c); + } + } + } elseif ($this->redis instanceof \RedisArray) { + $hosts = []; + foreach ($this->redis->_hosts() as $host) { + $hosts[] = $this->redis->_instance($host); + } + } elseif ($this->redis instanceof \RedisCluster) { + $hosts = []; + foreach ($this->redis->_masters() as $host) { + $hosts[] = new RedisClusterNodeProxy($host, $this->redis); + } + } + + return $hosts; + } +} diff --git a/vendor/symfony/cache/Traits/Relay/CopyTrait.php b/vendor/symfony/cache/Traits/Relay/CopyTrait.php new file mode 100644 index 0000000..84d52f4 --- /dev/null +++ b/vendor/symfony/cache/Traits/Relay/CopyTrait.php @@ -0,0 +1,36 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Cache\Traits\Relay; + +if (version_compare(phpversion('relay'), '0.8.1', '>=')) { + /** + * @internal + */ + trait CopyTrait + { + public function copy($src, $dst, $options = null): \Relay\Relay|bool + { + return $this->initializeLazyObject()->copy(...\func_get_args()); + } + } +} else { + /** + * @internal + */ + trait CopyTrait + { + public function copy($src, $dst, $options = null): \Relay\Relay|false|int + { + return $this->initializeLazyObject()->copy(...\func_get_args()); + } + } +} diff --git a/vendor/symfony/cache/Traits/Relay/GeosearchTrait.php b/vendor/symfony/cache/Traits/Relay/GeosearchTrait.php new file mode 100644 index 0000000..a358f80 --- /dev/null +++ b/vendor/symfony/cache/Traits/Relay/GeosearchTrait.php @@ -0,0 +1,36 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Cache\Traits\Relay; + +if (version_compare(phpversion('relay'), '0.9.0', '>=')) { + /** + * @internal + */ + trait GeosearchTrait + { + public function geosearch($key, $position, $shape, $unit, $options = []): \Relay\Relay|array|false + { + return $this->initializeLazyObject()->geosearch(...\func_get_args()); + } + } +} else { + /** + * @internal + */ + trait GeosearchTrait + { + public function geosearch($key, $position, $shape, $unit, $options = []): \Relay\Relay|array + { + return $this->initializeLazyObject()->geosearch(...\func_get_args()); + } + } +} diff --git a/vendor/symfony/cache/Traits/Relay/GetrangeTrait.php b/vendor/symfony/cache/Traits/Relay/GetrangeTrait.php new file mode 100644 index 0000000..f26333e --- /dev/null +++ b/vendor/symfony/cache/Traits/Relay/GetrangeTrait.php @@ -0,0 +1,36 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Cache\Traits\Relay; + +if (version_compare(phpversion('relay'), '0.9.0', '>=')) { + /** + * @internal + */ + trait GetrangeTrait + { + public function getrange($key, $start, $end): mixed + { + return $this->initializeLazyObject()->getrange(...\func_get_args()); + } + } +} else { + /** + * @internal + */ + trait GetrangeTrait + { + public function getrange($key, $start, $end): \Relay\Relay|false|string + { + return $this->initializeLazyObject()->getrange(...\func_get_args()); + } + } +} diff --git a/vendor/symfony/cache/Traits/Relay/HsetTrait.php b/vendor/symfony/cache/Traits/Relay/HsetTrait.php new file mode 100644 index 0000000..8334244 --- /dev/null +++ b/vendor/symfony/cache/Traits/Relay/HsetTrait.php @@ -0,0 +1,36 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Cache\Traits\Relay; + +if (version_compare(phpversion('relay'), '0.9.0', '>=')) { + /** + * @internal + */ + trait HsetTrait + { + public function hset($key, ...$keys_and_vals): \Relay\Relay|false|int + { + return $this->initializeLazyObject()->hset(...\func_get_args()); + } + } +} else { + /** + * @internal + */ + trait HsetTrait + { + public function hset($key, $mem, $val, ...$kvals): \Relay\Relay|false|int + { + return $this->initializeLazyObject()->hset(...\func_get_args()); + } + } +} diff --git a/vendor/symfony/cache/Traits/Relay/MoveTrait.php b/vendor/symfony/cache/Traits/Relay/MoveTrait.php new file mode 100644 index 0000000..18086f6 --- /dev/null +++ b/vendor/symfony/cache/Traits/Relay/MoveTrait.php @@ -0,0 +1,46 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Cache\Traits\Relay; + +if (version_compare(phpversion('relay'), '0.9.0', '>=')) { + /** + * @internal + */ + trait MoveTrait + { + public function blmove($srckey, $dstkey, $srcpos, $dstpos, $timeout): mixed + { + return $this->initializeLazyObject()->blmove(...\func_get_args()); + } + + public function lmove($srckey, $dstkey, $srcpos, $dstpos): mixed + { + return $this->initializeLazyObject()->lmove(...\func_get_args()); + } + } +} else { + /** + * @internal + */ + trait MoveTrait + { + public function blmove($srckey, $dstkey, $srcpos, $dstpos, $timeout): \Relay\Relay|false|string|null + { + return $this->initializeLazyObject()->blmove(...\func_get_args()); + } + + public function lmove($srckey, $dstkey, $srcpos, $dstpos): \Relay\Relay|false|string|null + { + return $this->initializeLazyObject()->lmove(...\func_get_args()); + } + } +} diff --git a/vendor/symfony/cache/Traits/Relay/NullableReturnTrait.php b/vendor/symfony/cache/Traits/Relay/NullableReturnTrait.php new file mode 100644 index 0000000..661ec47 --- /dev/null +++ b/vendor/symfony/cache/Traits/Relay/NullableReturnTrait.php @@ -0,0 +1,96 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Cache\Traits\Relay; + +if (version_compare(phpversion('relay'), '0.9.0', '>=')) { + /** + * @internal + */ + trait NullableReturnTrait + { + public function dump($key): \Relay\Relay|false|string|null + { + return $this->initializeLazyObject()->dump(...\func_get_args()); + } + + public function geodist($key, $src, $dst, $unit = null): \Relay\Relay|false|float|null + { + return $this->initializeLazyObject()->geodist(...\func_get_args()); + } + + public function hrandfield($hash, $options = null): \Relay\Relay|array|false|string|null + { + return $this->initializeLazyObject()->hrandfield(...\func_get_args()); + } + + public function xadd($key, $id, $values, $maxlen = 0, $approx = false, $nomkstream = false): \Relay\Relay|false|string|null + { + return $this->initializeLazyObject()->xadd(...\func_get_args()); + } + + public function zrank($key, $rank, $withscore = false): \Relay\Relay|array|false|int|null + { + return $this->initializeLazyObject()->zrank(...\func_get_args()); + } + + public function zrevrank($key, $rank, $withscore = false): \Relay\Relay|array|false|int|null + { + return $this->initializeLazyObject()->zrevrank(...\func_get_args()); + } + + public function zscore($key, $member): \Relay\Relay|false|float|null + { + return $this->initializeLazyObject()->zscore(...\func_get_args()); + } + } +} else { + /** + * @internal + */ + trait NullableReturnTrait + { + public function dump($key): \Relay\Relay|false|string + { + return $this->initializeLazyObject()->dump(...\func_get_args()); + } + + public function geodist($key, $src, $dst, $unit = null): \Relay\Relay|false|float + { + return $this->initializeLazyObject()->geodist(...\func_get_args()); + } + + public function hrandfield($hash, $options = null): \Relay\Relay|array|false|string + { + return $this->initializeLazyObject()->hrandfield(...\func_get_args()); + } + + public function xadd($key, $id, $values, $maxlen = 0, $approx = false, $nomkstream = false): \Relay\Relay|false|string + { + return $this->initializeLazyObject()->xadd(...\func_get_args()); + } + + public function zrank($key, $rank, $withscore = false): \Relay\Relay|array|false|int + { + return $this->initializeLazyObject()->zrank(...\func_get_args()); + } + + public function zrevrank($key, $rank, $withscore = false): \Relay\Relay|array|false|int + { + return $this->initializeLazyObject()->zrevrank(...\func_get_args()); + } + + public function zscore($key, $member): \Relay\Relay|false|float + { + return $this->initializeLazyObject()->zscore(...\func_get_args()); + } + } +} diff --git a/vendor/symfony/cache/Traits/Relay/PfcountTrait.php b/vendor/symfony/cache/Traits/Relay/PfcountTrait.php new file mode 100644 index 0000000..84e5c59 --- /dev/null +++ b/vendor/symfony/cache/Traits/Relay/PfcountTrait.php @@ -0,0 +1,36 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Cache\Traits\Relay; + +if (version_compare(phpversion('relay'), '0.9.0', '>=')) { + /** + * @internal + */ + trait PfcountTrait + { + public function pfcount($key_or_keys): \Relay\Relay|false|int + { + return $this->initializeLazyObject()->pfcount(...\func_get_args()); + } + } +} else { + /** + * @internal + */ + trait PfcountTrait + { + public function pfcount($key): \Relay\Relay|false|int + { + return $this->initializeLazyObject()->pfcount(...\func_get_args()); + } + } +} diff --git a/vendor/symfony/cache/Traits/RelayProxy.php b/vendor/symfony/cache/Traits/RelayProxy.php new file mode 100644 index 0000000..e0ca887 --- /dev/null +++ b/vendor/symfony/cache/Traits/RelayProxy.php @@ -0,0 +1,1265 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Cache\Traits; + +use Symfony\Component\Cache\Traits\Relay\CopyTrait; +use Symfony\Component\Cache\Traits\Relay\GeosearchTrait; +use Symfony\Component\Cache\Traits\Relay\GetrangeTrait; +use Symfony\Component\Cache\Traits\Relay\HsetTrait; +use Symfony\Component\Cache\Traits\Relay\MoveTrait; +use Symfony\Component\Cache\Traits\Relay\NullableReturnTrait; +use Symfony\Component\Cache\Traits\Relay\PfcountTrait; +use Symfony\Component\VarExporter\LazyObjectInterface; +use Symfony\Contracts\Service\ResetInterface; + +// Help opcache.preload discover always-needed symbols +class_exists(\Symfony\Component\VarExporter\Internal\Hydrator::class); +class_exists(\Symfony\Component\VarExporter\Internal\LazyObjectRegistry::class); +class_exists(\Symfony\Component\VarExporter\Internal\LazyObjectState::class); + +/** + * @internal + */ +class RelayProxy extends \Relay\Relay implements ResetInterface, LazyObjectInterface +{ + use CopyTrait; + use GeosearchTrait; + use GetrangeTrait; + use HsetTrait; + use MoveTrait; + use NullableReturnTrait; + use PfcountTrait; + use RedisProxyTrait { + resetLazyObject as reset; + } + use RelayProxyTrait; + + public function __construct($host = null, $port = 6379, $connect_timeout = 0.0, $command_timeout = 0.0, #[\SensitiveParameter] $context = [], $database = 0) + { + $this->initializeLazyObject()->__construct(...\func_get_args()); + } + + public function connect($host, $port = 6379, $timeout = 0.0, $persistent_id = null, $retry_interval = 0, $read_timeout = 0.0, #[\SensitiveParameter] $context = [], $database = 0): bool + { + return $this->initializeLazyObject()->connect(...\func_get_args()); + } + + public function pconnect($host, $port = 6379, $timeout = 0.0, $persistent_id = null, $retry_interval = 0, $read_timeout = 0.0, #[\SensitiveParameter] $context = [], $database = 0): bool + { + return $this->initializeLazyObject()->pconnect(...\func_get_args()); + } + + public function close(): bool + { + return $this->initializeLazyObject()->close(...\func_get_args()); + } + + public function pclose(): bool + { + return $this->initializeLazyObject()->pclose(...\func_get_args()); + } + + public function listen($callback): bool + { + return $this->initializeLazyObject()->listen(...\func_get_args()); + } + + public function onFlushed($callback): bool + { + return $this->initializeLazyObject()->onFlushed(...\func_get_args()); + } + + public function onInvalidated($callback, $pattern = null): bool + { + return $this->initializeLazyObject()->onInvalidated(...\func_get_args()); + } + + public function dispatchEvents(): false|int + { + return $this->initializeLazyObject()->dispatchEvents(...\func_get_args()); + } + + public function getOption($option): mixed + { + return $this->initializeLazyObject()->getOption(...\func_get_args()); + } + + public function option($option, $value = null): mixed + { + return $this->initializeLazyObject()->option(...\func_get_args()); + } + + public function setOption($option, $value): bool + { + return $this->initializeLazyObject()->setOption(...\func_get_args()); + } + + public function addIgnorePatterns(...$pattern): int + { + return $this->initializeLazyObject()->addIgnorePatterns(...\func_get_args()); + } + + public function addAllowPatterns(...$pattern): int + { + return $this->initializeLazyObject()->addAllowPatterns(...\func_get_args()); + } + + public function getTimeout(): false|float + { + return $this->initializeLazyObject()->getTimeout(...\func_get_args()); + } + + public function timeout(): false|float + { + return $this->initializeLazyObject()->timeout(...\func_get_args()); + } + + public function getReadTimeout(): false|float + { + return $this->initializeLazyObject()->getReadTimeout(...\func_get_args()); + } + + public function readTimeout(): false|float + { + return $this->initializeLazyObject()->readTimeout(...\func_get_args()); + } + + public function getBytes(): array + { + return $this->initializeLazyObject()->getBytes(...\func_get_args()); + } + + public function bytes(): array + { + return $this->initializeLazyObject()->bytes(...\func_get_args()); + } + + public function getHost(): false|string + { + return $this->initializeLazyObject()->getHost(...\func_get_args()); + } + + public function isConnected(): bool + { + return $this->initializeLazyObject()->isConnected(...\func_get_args()); + } + + public function getPort(): false|int + { + return $this->initializeLazyObject()->getPort(...\func_get_args()); + } + + public function getAuth(): mixed + { + return $this->initializeLazyObject()->getAuth(...\func_get_args()); + } + + public function getDbNum(): mixed + { + return $this->initializeLazyObject()->getDbNum(...\func_get_args()); + } + + public function _serialize($value): mixed + { + return $this->initializeLazyObject()->_serialize(...\func_get_args()); + } + + public function _unserialize($value): mixed + { + return $this->initializeLazyObject()->_unserialize(...\func_get_args()); + } + + public function _compress($value): string + { + return $this->initializeLazyObject()->_compress(...\func_get_args()); + } + + public function _uncompress($value): string + { + return $this->initializeLazyObject()->_uncompress(...\func_get_args()); + } + + public function _pack($value): string + { + return $this->initializeLazyObject()->_pack(...\func_get_args()); + } + + public function _unpack($value): mixed + { + return $this->initializeLazyObject()->_unpack(...\func_get_args()); + } + + public function _prefix($value): string + { + return $this->initializeLazyObject()->_prefix(...\func_get_args()); + } + + public function getLastError(): ?string + { + return $this->initializeLazyObject()->getLastError(...\func_get_args()); + } + + public function clearLastError(): bool + { + return $this->initializeLazyObject()->clearLastError(...\func_get_args()); + } + + public function endpointId(): false|string + { + return $this->initializeLazyObject()->endpointId(...\func_get_args()); + } + + public function getPersistentID(): false|string + { + return $this->initializeLazyObject()->getPersistentID(...\func_get_args()); + } + + public function socketId(): false|string + { + return $this->initializeLazyObject()->socketId(...\func_get_args()); + } + + public function rawCommand($cmd, ...$args): mixed + { + return $this->initializeLazyObject()->rawCommand(...\func_get_args()); + } + + public function select($db): \Relay\Relay|bool + { + return $this->initializeLazyObject()->select(...\func_get_args()); + } + + public function auth(#[\SensitiveParameter] $auth): bool + { + return $this->initializeLazyObject()->auth(...\func_get_args()); + } + + public function info(...$sections): \Relay\Relay|array|false + { + return $this->initializeLazyObject()->info(...\func_get_args()); + } + + public function flushdb($sync = null): \Relay\Relay|bool + { + return $this->initializeLazyObject()->flushdb(...\func_get_args()); + } + + public function flushall($sync = null): \Relay\Relay|bool + { + return $this->initializeLazyObject()->flushall(...\func_get_args()); + } + + public function fcall($name, $keys = [], $argv = [], $handler = null): mixed + { + return $this->initializeLazyObject()->fcall(...\func_get_args()); + } + + public function fcall_ro($name, $keys = [], $argv = [], $handler = null): mixed + { + return $this->initializeLazyObject()->fcall_ro(...\func_get_args()); + } + + public function function($op, ...$args): mixed + { + return $this->initializeLazyObject()->function(...\func_get_args()); + } + + public function dbsize(): \Relay\Relay|false|int + { + return $this->initializeLazyObject()->dbsize(...\func_get_args()); + } + + public function replicaof($host = null, $port = 0): \Relay\Relay|bool + { + return $this->initializeLazyObject()->replicaof(...\func_get_args()); + } + + public function waitaof($numlocal, $numremote, $timeout): \Relay\Relay|array|false + { + return $this->initializeLazyObject()->waitaof(...\func_get_args()); + } + + public function restore($key, $ttl, $value, $options = null): \Relay\Relay|bool + { + return $this->initializeLazyObject()->restore(...\func_get_args()); + } + + public function migrate($host, $port, $key, $dstdb, $timeout, $copy = false, $replace = false, #[\SensitiveParameter] $credentials = null): \Relay\Relay|bool + { + return $this->initializeLazyObject()->migrate(...\func_get_args()); + } + + public function echo($arg): \Relay\Relay|bool|string + { + return $this->initializeLazyObject()->echo(...\func_get_args()); + } + + public function ping($arg = null): \Relay\Relay|bool|string + { + return $this->initializeLazyObject()->ping(...\func_get_args()); + } + + public function idleTime(): \Relay\Relay|false|int + { + return $this->initializeLazyObject()->idleTime(...\func_get_args()); + } + + public function randomkey(): \Relay\Relay|bool|null|string + { + return $this->initializeLazyObject()->randomkey(...\func_get_args()); + } + + public function time(): \Relay\Relay|array|false + { + return $this->initializeLazyObject()->time(...\func_get_args()); + } + + public function bgrewriteaof(): \Relay\Relay|bool + { + return $this->initializeLazyObject()->bgrewriteaof(...\func_get_args()); + } + + public function lastsave(): \Relay\Relay|false|int + { + return $this->initializeLazyObject()->lastsave(...\func_get_args()); + } + + public function lcs($key1, $key2, $options = null): mixed + { + return $this->initializeLazyObject()->lcs(...\func_get_args()); + } + + public function bgsave($schedule = false): \Relay\Relay|bool + { + return $this->initializeLazyObject()->bgsave(...\func_get_args()); + } + + public function save(): \Relay\Relay|bool + { + return $this->initializeLazyObject()->save(...\func_get_args()); + } + + public function role(): \Relay\Relay|array|false + { + return $this->initializeLazyObject()->role(...\func_get_args()); + } + + public function ttl($key): \Relay\Relay|false|int + { + return $this->initializeLazyObject()->ttl(...\func_get_args()); + } + + public function pttl($key): \Relay\Relay|false|int + { + return $this->initializeLazyObject()->pttl(...\func_get_args()); + } + + public function exists(...$keys): \Relay\Relay|bool|int + { + return $this->initializeLazyObject()->exists(...\func_get_args()); + } + + public function eval($script, $args = [], $num_keys = 0): mixed + { + return $this->initializeLazyObject()->eval(...\func_get_args()); + } + + public function eval_ro($script, $args = [], $num_keys = 0): mixed + { + return $this->initializeLazyObject()->eval_ro(...\func_get_args()); + } + + public function evalsha($sha, $args = [], $num_keys = 0): mixed + { + return $this->initializeLazyObject()->evalsha(...\func_get_args()); + } + + public function evalsha_ro($sha, $args = [], $num_keys = 0): mixed + { + return $this->initializeLazyObject()->evalsha_ro(...\func_get_args()); + } + + public function client($operation, ...$args): mixed + { + return $this->initializeLazyObject()->client(...\func_get_args()); + } + + public function geoadd($key, $lng, $lat, $member, ...$other_triples_and_options): \Relay\Relay|false|int + { + return $this->initializeLazyObject()->geoadd(...\func_get_args()); + } + + public function geohash($key, $member, ...$other_members): \Relay\Relay|array|false + { + return $this->initializeLazyObject()->geohash(...\func_get_args()); + } + + public function georadius($key, $lng, $lat, $radius, $unit, $options = []): mixed + { + return $this->initializeLazyObject()->georadius(...\func_get_args()); + } + + public function georadiusbymember($key, $member, $radius, $unit, $options = []): mixed + { + return $this->initializeLazyObject()->georadiusbymember(...\func_get_args()); + } + + public function georadiusbymember_ro($key, $member, $radius, $unit, $options = []): mixed + { + return $this->initializeLazyObject()->georadiusbymember_ro(...\func_get_args()); + } + + public function georadius_ro($key, $lng, $lat, $radius, $unit, $options = []): mixed + { + return $this->initializeLazyObject()->georadius_ro(...\func_get_args()); + } + + public function geosearchstore($dst, $src, $position, $shape, $unit, $options = []): \Relay\Relay|false|int + { + return $this->initializeLazyObject()->geosearchstore(...\func_get_args()); + } + + public function get($key): mixed + { + return $this->initializeLazyObject()->get(...\func_get_args()); + } + + public function getset($key, $value): mixed + { + return $this->initializeLazyObject()->getset(...\func_get_args()); + } + + public function setrange($key, $start, $value): \Relay\Relay|false|int + { + return $this->initializeLazyObject()->setrange(...\func_get_args()); + } + + public function getbit($key, $pos): \Relay\Relay|false|int + { + return $this->initializeLazyObject()->getbit(...\func_get_args()); + } + + public function bitcount($key, $start = 0, $end = -1, $by_bit = false): \Relay\Relay|false|int + { + return $this->initializeLazyObject()->bitcount(...\func_get_args()); + } + + public function bitfield($key, ...$args): \Relay\Relay|array|false + { + return $this->initializeLazyObject()->bitfield(...\func_get_args()); + } + + public function config($operation, $key = null, $value = null): \Relay\Relay|array|bool + { + return $this->initializeLazyObject()->config(...\func_get_args()); + } + + public function command(...$args): \Relay\Relay|array|false|int + { + return $this->initializeLazyObject()->command(...\func_get_args()); + } + + public function bitop($operation, $dstkey, $srckey, ...$other_keys): \Relay\Relay|false|int + { + return $this->initializeLazyObject()->bitop(...\func_get_args()); + } + + public function bitpos($key, $bit, $start = null, $end = null, $bybit = false): \Relay\Relay|false|int + { + return $this->initializeLazyObject()->bitpos(...\func_get_args()); + } + + public function setbit($key, $pos, $val): \Relay\Relay|false|int + { + return $this->initializeLazyObject()->setbit(...\func_get_args()); + } + + public function acl($cmd, ...$args): mixed + { + return $this->initializeLazyObject()->acl(...\func_get_args()); + } + + public function append($key, $value): \Relay\Relay|false|int + { + return $this->initializeLazyObject()->append(...\func_get_args()); + } + + public function set($key, $value, $options = null): mixed + { + return $this->initializeLazyObject()->set(...\func_get_args()); + } + + public function getex($key, $options = null): mixed + { + return $this->initializeLazyObject()->getex(...\func_get_args()); + } + + public function getdel($key): mixed + { + return $this->initializeLazyObject()->getdel(...\func_get_args()); + } + + public function setex($key, $seconds, $value): \Relay\Relay|bool + { + return $this->initializeLazyObject()->setex(...\func_get_args()); + } + + public function pfadd($key, $elements): \Relay\Relay|false|int + { + return $this->initializeLazyObject()->pfadd(...\func_get_args()); + } + + public function pfmerge($dst, $srckeys): \Relay\Relay|bool + { + return $this->initializeLazyObject()->pfmerge(...\func_get_args()); + } + + public function psetex($key, $milliseconds, $value): \Relay\Relay|bool + { + return $this->initializeLazyObject()->psetex(...\func_get_args()); + } + + public function publish($channel, $message): \Relay\Relay|false|int + { + return $this->initializeLazyObject()->publish(...\func_get_args()); + } + + public function pubsub($operation, ...$args): mixed + { + return $this->initializeLazyObject()->pubsub(...\func_get_args()); + } + + public function spublish($channel, $message): \Relay\Relay|false|int + { + return $this->initializeLazyObject()->spublish(...\func_get_args()); + } + + public function setnx($key, $value): \Relay\Relay|bool + { + return $this->initializeLazyObject()->setnx(...\func_get_args()); + } + + public function mget($keys): \Relay\Relay|array|false + { + return $this->initializeLazyObject()->mget(...\func_get_args()); + } + + public function move($key, $db): \Relay\Relay|false|int + { + return $this->initializeLazyObject()->move(...\func_get_args()); + } + + public function mset($kvals): \Relay\Relay|bool + { + return $this->initializeLazyObject()->mset(...\func_get_args()); + } + + public function msetnx($kvals): \Relay\Relay|bool + { + return $this->initializeLazyObject()->msetnx(...\func_get_args()); + } + + public function rename($key, $newkey): \Relay\Relay|bool + { + return $this->initializeLazyObject()->rename(...\func_get_args()); + } + + public function renamenx($key, $newkey): \Relay\Relay|bool + { + return $this->initializeLazyObject()->renamenx(...\func_get_args()); + } + + public function del(...$keys): \Relay\Relay|bool|int + { + return $this->initializeLazyObject()->del(...\func_get_args()); + } + + public function unlink(...$keys): \Relay\Relay|false|int + { + return $this->initializeLazyObject()->unlink(...\func_get_args()); + } + + public function expire($key, $seconds, $mode = null): \Relay\Relay|bool + { + return $this->initializeLazyObject()->expire(...\func_get_args()); + } + + public function pexpire($key, $milliseconds): \Relay\Relay|bool + { + return $this->initializeLazyObject()->pexpire(...\func_get_args()); + } + + public function expireat($key, $timestamp): \Relay\Relay|bool + { + return $this->initializeLazyObject()->expireat(...\func_get_args()); + } + + public function expiretime($key): \Relay\Relay|false|int + { + return $this->initializeLazyObject()->expiretime(...\func_get_args()); + } + + public function pexpireat($key, $timestamp_ms): \Relay\Relay|bool + { + return $this->initializeLazyObject()->pexpireat(...\func_get_args()); + } + + public function pexpiretime($key): \Relay\Relay|false|int + { + return $this->initializeLazyObject()->pexpiretime(...\func_get_args()); + } + + public function persist($key): \Relay\Relay|bool + { + return $this->initializeLazyObject()->persist(...\func_get_args()); + } + + public function type($key): \Relay\Relay|bool|int|string + { + return $this->initializeLazyObject()->type(...\func_get_args()); + } + + public function lrange($key, $start, $stop): \Relay\Relay|array|false + { + return $this->initializeLazyObject()->lrange(...\func_get_args()); + } + + public function lpush($key, $mem, ...$mems): \Relay\Relay|false|int + { + return $this->initializeLazyObject()->lpush(...\func_get_args()); + } + + public function rpush($key, $mem, ...$mems): \Relay\Relay|false|int + { + return $this->initializeLazyObject()->rpush(...\func_get_args()); + } + + public function lpushx($key, $mem, ...$mems): \Relay\Relay|false|int + { + return $this->initializeLazyObject()->lpushx(...\func_get_args()); + } + + public function rpushx($key, $mem, ...$mems): \Relay\Relay|false|int + { + return $this->initializeLazyObject()->rpushx(...\func_get_args()); + } + + public function lset($key, $index, $mem): \Relay\Relay|bool + { + return $this->initializeLazyObject()->lset(...\func_get_args()); + } + + public function lpop($key, $count = 1): mixed + { + return $this->initializeLazyObject()->lpop(...\func_get_args()); + } + + public function lpos($key, $value, $options = null): \Relay\Relay|array|false|int|null + { + return $this->initializeLazyObject()->lpos(...\func_get_args()); + } + + public function rpop($key, $count = 1): mixed + { + return $this->initializeLazyObject()->rpop(...\func_get_args()); + } + + public function rpoplpush($source, $dest): mixed + { + return $this->initializeLazyObject()->rpoplpush(...\func_get_args()); + } + + public function brpoplpush($source, $dest, $timeout): mixed + { + return $this->initializeLazyObject()->brpoplpush(...\func_get_args()); + } + + public function blpop($key, $timeout_or_key, ...$extra_args): \Relay\Relay|array|false|null + { + return $this->initializeLazyObject()->blpop(...\func_get_args()); + } + + public function blmpop($timeout, $keys, $from, $count = 1): \Relay\Relay|array|false|null + { + return $this->initializeLazyObject()->blmpop(...\func_get_args()); + } + + public function bzmpop($timeout, $keys, $from, $count = 1): \Relay\Relay|array|false|null + { + return $this->initializeLazyObject()->bzmpop(...\func_get_args()); + } + + public function lmpop($keys, $from, $count = 1): \Relay\Relay|array|false|null + { + return $this->initializeLazyObject()->lmpop(...\func_get_args()); + } + + public function zmpop($keys, $from, $count = 1): \Relay\Relay|array|false|null + { + return $this->initializeLazyObject()->zmpop(...\func_get_args()); + } + + public function brpop($key, $timeout_or_key, ...$extra_args): \Relay\Relay|array|false|null + { + return $this->initializeLazyObject()->brpop(...\func_get_args()); + } + + public function bzpopmax($key, $timeout_or_key, ...$extra_args): \Relay\Relay|array|false|null + { + return $this->initializeLazyObject()->bzpopmax(...\func_get_args()); + } + + public function bzpopmin($key, $timeout_or_key, ...$extra_args): \Relay\Relay|array|false|null + { + return $this->initializeLazyObject()->bzpopmin(...\func_get_args()); + } + + public function object($op, $key): mixed + { + return $this->initializeLazyObject()->object(...\func_get_args()); + } + + public function geopos($key, ...$members): \Relay\Relay|array|false + { + return $this->initializeLazyObject()->geopos(...\func_get_args()); + } + + public function lrem($key, $mem, $count = 0): \Relay\Relay|false|int + { + return $this->initializeLazyObject()->lrem(...\func_get_args()); + } + + public function lindex($key, $index): mixed + { + return $this->initializeLazyObject()->lindex(...\func_get_args()); + } + + public function linsert($key, $op, $pivot, $element): \Relay\Relay|false|int + { + return $this->initializeLazyObject()->linsert(...\func_get_args()); + } + + public function ltrim($key, $start, $end): \Relay\Relay|bool + { + return $this->initializeLazyObject()->ltrim(...\func_get_args()); + } + + public function hget($hash, $member): mixed + { + return $this->initializeLazyObject()->hget(...\func_get_args()); + } + + public function hstrlen($hash, $member): \Relay\Relay|false|int + { + return $this->initializeLazyObject()->hstrlen(...\func_get_args()); + } + + public function hgetall($hash): \Relay\Relay|array|false + { + return $this->initializeLazyObject()->hgetall(...\func_get_args()); + } + + public function hkeys($hash): \Relay\Relay|array|false + { + return $this->initializeLazyObject()->hkeys(...\func_get_args()); + } + + public function hvals($hash): \Relay\Relay|array|false + { + return $this->initializeLazyObject()->hvals(...\func_get_args()); + } + + public function hmget($hash, $members): \Relay\Relay|array|false + { + return $this->initializeLazyObject()->hmget(...\func_get_args()); + } + + public function hmset($hash, $members): \Relay\Relay|bool + { + return $this->initializeLazyObject()->hmset(...\func_get_args()); + } + + public function hexists($hash, $member): \Relay\Relay|bool + { + return $this->initializeLazyObject()->hexists(...\func_get_args()); + } + + public function hsetnx($hash, $member, $value): \Relay\Relay|bool + { + return $this->initializeLazyObject()->hsetnx(...\func_get_args()); + } + + public function hdel($key, $mem, ...$mems): \Relay\Relay|false|int + { + return $this->initializeLazyObject()->hdel(...\func_get_args()); + } + + public function hincrby($key, $mem, $value): \Relay\Relay|false|int + { + return $this->initializeLazyObject()->hincrby(...\func_get_args()); + } + + public function hincrbyfloat($key, $mem, $value): \Relay\Relay|bool|float + { + return $this->initializeLazyObject()->hincrbyfloat(...\func_get_args()); + } + + public function incr($key, $by = 1): \Relay\Relay|false|int + { + return $this->initializeLazyObject()->incr(...\func_get_args()); + } + + public function decr($key, $by = 1): \Relay\Relay|false|int + { + return $this->initializeLazyObject()->decr(...\func_get_args()); + } + + public function incrby($key, $value): \Relay\Relay|false|int + { + return $this->initializeLazyObject()->incrby(...\func_get_args()); + } + + public function decrby($key, $value): \Relay\Relay|false|int + { + return $this->initializeLazyObject()->decrby(...\func_get_args()); + } + + public function incrbyfloat($key, $value): \Relay\Relay|false|float + { + return $this->initializeLazyObject()->incrbyfloat(...\func_get_args()); + } + + public function sdiff($key, ...$other_keys): \Relay\Relay|array|false + { + return $this->initializeLazyObject()->sdiff(...\func_get_args()); + } + + public function sdiffstore($key, ...$other_keys): \Relay\Relay|false|int + { + return $this->initializeLazyObject()->sdiffstore(...\func_get_args()); + } + + public function sinter($key, ...$other_keys): \Relay\Relay|array|false + { + return $this->initializeLazyObject()->sinter(...\func_get_args()); + } + + public function sintercard($keys, $limit = -1): \Relay\Relay|false|int + { + return $this->initializeLazyObject()->sintercard(...\func_get_args()); + } + + public function sinterstore($key, ...$other_keys): \Relay\Relay|false|int + { + return $this->initializeLazyObject()->sinterstore(...\func_get_args()); + } + + public function sunion($key, ...$other_keys): \Relay\Relay|array|false + { + return $this->initializeLazyObject()->sunion(...\func_get_args()); + } + + public function sunionstore($key, ...$other_keys): \Relay\Relay|false|int + { + return $this->initializeLazyObject()->sunionstore(...\func_get_args()); + } + + public function subscribe($channels, $callback): bool + { + return $this->initializeLazyObject()->subscribe(...\func_get_args()); + } + + public function unsubscribe($channels = []): bool + { + return $this->initializeLazyObject()->unsubscribe(...\func_get_args()); + } + + public function psubscribe($patterns, $callback): bool + { + return $this->initializeLazyObject()->psubscribe(...\func_get_args()); + } + + public function punsubscribe($patterns = []): bool + { + return $this->initializeLazyObject()->punsubscribe(...\func_get_args()); + } + + public function ssubscribe($channels, $callback): bool + { + return $this->initializeLazyObject()->ssubscribe(...\func_get_args()); + } + + public function sunsubscribe($channels = []): bool + { + return $this->initializeLazyObject()->sunsubscribe(...\func_get_args()); + } + + public function touch($key_or_array, ...$more_keys): \Relay\Relay|false|int + { + return $this->initializeLazyObject()->touch(...\func_get_args()); + } + + public function pipeline(): \Relay\Relay|bool + { + return $this->initializeLazyObject()->pipeline(...\func_get_args()); + } + + public function multi($mode = 0): \Relay\Relay|bool + { + return $this->initializeLazyObject()->multi(...\func_get_args()); + } + + public function exec(): \Relay\Relay|array|bool + { + return $this->initializeLazyObject()->exec(...\func_get_args()); + } + + public function wait($replicas, $timeout): \Relay\Relay|false|int + { + return $this->initializeLazyObject()->wait(...\func_get_args()); + } + + public function watch($key, ...$other_keys): \Relay\Relay|bool + { + return $this->initializeLazyObject()->watch(...\func_get_args()); + } + + public function unwatch(): \Relay\Relay|bool + { + return $this->initializeLazyObject()->unwatch(...\func_get_args()); + } + + public function discard(): bool + { + return $this->initializeLazyObject()->discard(...\func_get_args()); + } + + public function getMode($masked = false): int + { + return $this->initializeLazyObject()->getMode(...\func_get_args()); + } + + public function clearBytes(): void + { + $this->initializeLazyObject()->clearBytes(...\func_get_args()); + } + + public function scan(&$iterator, $match = null, $count = 0, $type = null): array|false + { + return $this->initializeLazyObject()->scan($iterator, ...\array_slice(\func_get_args(), 1)); + } + + public function hscan($key, &$iterator, $match = null, $count = 0): array|false + { + return $this->initializeLazyObject()->hscan($key, $iterator, ...\array_slice(\func_get_args(), 2)); + } + + public function sscan($key, &$iterator, $match = null, $count = 0): array|false + { + return $this->initializeLazyObject()->sscan($key, $iterator, ...\array_slice(\func_get_args(), 2)); + } + + public function zscan($key, &$iterator, $match = null, $count = 0): array|false + { + return $this->initializeLazyObject()->zscan($key, $iterator, ...\array_slice(\func_get_args(), 2)); + } + + public function keys($pattern): \Relay\Relay|array|false + { + return $this->initializeLazyObject()->keys(...\func_get_args()); + } + + public function slowlog($operation, ...$extra_args): \Relay\Relay|array|bool|int + { + return $this->initializeLazyObject()->slowlog(...\func_get_args()); + } + + public function smembers($set): \Relay\Relay|array|false + { + return $this->initializeLazyObject()->smembers(...\func_get_args()); + } + + public function sismember($set, $member): \Relay\Relay|bool + { + return $this->initializeLazyObject()->sismember(...\func_get_args()); + } + + public function smismember($set, ...$members): \Relay\Relay|array|false + { + return $this->initializeLazyObject()->smismember(...\func_get_args()); + } + + public function srem($set, $member, ...$members): \Relay\Relay|false|int + { + return $this->initializeLazyObject()->srem(...\func_get_args()); + } + + public function sadd($set, $member, ...$members): \Relay\Relay|false|int + { + return $this->initializeLazyObject()->sadd(...\func_get_args()); + } + + public function sort($key, $options = []): \Relay\Relay|array|false|int + { + return $this->initializeLazyObject()->sort(...\func_get_args()); + } + + public function sort_ro($key, $options = []): \Relay\Relay|array|false + { + return $this->initializeLazyObject()->sort_ro(...\func_get_args()); + } + + public function smove($srcset, $dstset, $member): \Relay\Relay|bool + { + return $this->initializeLazyObject()->smove(...\func_get_args()); + } + + public function spop($set, $count = 1): mixed + { + return $this->initializeLazyObject()->spop(...\func_get_args()); + } + + public function srandmember($set, $count = 1): mixed + { + return $this->initializeLazyObject()->srandmember(...\func_get_args()); + } + + public function scard($key): \Relay\Relay|false|int + { + return $this->initializeLazyObject()->scard(...\func_get_args()); + } + + public function script($command, ...$args): mixed + { + return $this->initializeLazyObject()->script(...\func_get_args()); + } + + public function strlen($key): \Relay\Relay|false|int + { + return $this->initializeLazyObject()->strlen(...\func_get_args()); + } + + public function hlen($key): \Relay\Relay|false|int + { + return $this->initializeLazyObject()->hlen(...\func_get_args()); + } + + public function llen($key): \Relay\Relay|false|int + { + return $this->initializeLazyObject()->llen(...\func_get_args()); + } + + public function xack($key, $group, $ids): \Relay\Relay|false|int + { + return $this->initializeLazyObject()->xack(...\func_get_args()); + } + + public function xclaim($key, $group, $consumer, $min_idle, $ids, $options): \Relay\Relay|array|bool + { + return $this->initializeLazyObject()->xclaim(...\func_get_args()); + } + + public function xautoclaim($key, $group, $consumer, $min_idle, $start, $count = -1, $justid = false): \Relay\Relay|array|bool + { + return $this->initializeLazyObject()->xautoclaim(...\func_get_args()); + } + + public function xlen($key): \Relay\Relay|false|int + { + return $this->initializeLazyObject()->xlen(...\func_get_args()); + } + + public function xgroup($operation, $key = null, $group = null, $id_or_consumer = null, $mkstream = false, $entries_read = -2): mixed + { + return $this->initializeLazyObject()->xgroup(...\func_get_args()); + } + + public function xdel($key, $ids): \Relay\Relay|false|int + { + return $this->initializeLazyObject()->xdel(...\func_get_args()); + } + + public function xinfo($operation, $arg1 = null, $arg2 = null, $count = -1): mixed + { + return $this->initializeLazyObject()->xinfo(...\func_get_args()); + } + + public function xpending($key, $group, $start = null, $end = null, $count = -1, $consumer = null, $idle = 0): \Relay\Relay|array|false + { + return $this->initializeLazyObject()->xpending(...\func_get_args()); + } + + public function xrange($key, $start, $end, $count = -1): \Relay\Relay|array|false + { + return $this->initializeLazyObject()->xrange(...\func_get_args()); + } + + public function xrevrange($key, $end, $start, $count = -1): \Relay\Relay|array|bool + { + return $this->initializeLazyObject()->xrevrange(...\func_get_args()); + } + + public function xread($streams, $count = -1, $block = -1): \Relay\Relay|array|bool|null + { + return $this->initializeLazyObject()->xread(...\func_get_args()); + } + + public function xreadgroup($group, $consumer, $streams, $count = 1, $block = 1): \Relay\Relay|array|bool|null + { + return $this->initializeLazyObject()->xreadgroup(...\func_get_args()); + } + + public function xtrim($key, $threshold, $approx = false, $minid = false, $limit = -1): \Relay\Relay|false|int + { + return $this->initializeLazyObject()->xtrim(...\func_get_args()); + } + + public function zadd($key, ...$args): mixed + { + return $this->initializeLazyObject()->zadd(...\func_get_args()); + } + + public function zrandmember($key, $options = null): mixed + { + return $this->initializeLazyObject()->zrandmember(...\func_get_args()); + } + + public function zrange($key, $start, $end, $options = null): \Relay\Relay|array|false + { + return $this->initializeLazyObject()->zrange(...\func_get_args()); + } + + public function zrevrange($key, $start, $end, $options = null): \Relay\Relay|array|false + { + return $this->initializeLazyObject()->zrevrange(...\func_get_args()); + } + + public function zrangebyscore($key, $start, $end, $options = null): \Relay\Relay|array|false + { + return $this->initializeLazyObject()->zrangebyscore(...\func_get_args()); + } + + public function zrevrangebyscore($key, $start, $end, $options = null): \Relay\Relay|array|false + { + return $this->initializeLazyObject()->zrevrangebyscore(...\func_get_args()); + } + + public function zrangestore($dst, $src, $start, $end, $options = null): \Relay\Relay|false|int + { + return $this->initializeLazyObject()->zrangestore(...\func_get_args()); + } + + public function zrangebylex($key, $min, $max, $offset = -1, $count = -1): \Relay\Relay|array|false + { + return $this->initializeLazyObject()->zrangebylex(...\func_get_args()); + } + + public function zrevrangebylex($key, $max, $min, $offset = -1, $count = -1): \Relay\Relay|array|false + { + return $this->initializeLazyObject()->zrevrangebylex(...\func_get_args()); + } + + public function zrem($key, ...$args): \Relay\Relay|false|int + { + return $this->initializeLazyObject()->zrem(...\func_get_args()); + } + + public function zremrangebylex($key, $min, $max): \Relay\Relay|false|int + { + return $this->initializeLazyObject()->zremrangebylex(...\func_get_args()); + } + + public function zremrangebyrank($key, $start, $end): \Relay\Relay|false|int + { + return $this->initializeLazyObject()->zremrangebyrank(...\func_get_args()); + } + + public function zremrangebyscore($key, $min, $max): \Relay\Relay|false|int + { + return $this->initializeLazyObject()->zremrangebyscore(...\func_get_args()); + } + + public function zcard($key): \Relay\Relay|false|int + { + return $this->initializeLazyObject()->zcard(...\func_get_args()); + } + + public function zcount($key, $min, $max): \Relay\Relay|false|int + { + return $this->initializeLazyObject()->zcount(...\func_get_args()); + } + + public function zdiff($keys, $options = null): \Relay\Relay|array|false + { + return $this->initializeLazyObject()->zdiff(...\func_get_args()); + } + + public function zdiffstore($dst, $keys): \Relay\Relay|false|int + { + return $this->initializeLazyObject()->zdiffstore(...\func_get_args()); + } + + public function zincrby($key, $score, $mem): \Relay\Relay|false|float + { + return $this->initializeLazyObject()->zincrby(...\func_get_args()); + } + + public function zlexcount($key, $min, $max): \Relay\Relay|false|int + { + return $this->initializeLazyObject()->zlexcount(...\func_get_args()); + } + + public function zmscore($key, ...$mems): \Relay\Relay|array|false + { + return $this->initializeLazyObject()->zmscore(...\func_get_args()); + } + + public function zinter($keys, $weights = null, $options = null): \Relay\Relay|array|false + { + return $this->initializeLazyObject()->zinter(...\func_get_args()); + } + + public function zintercard($keys, $limit = -1): \Relay\Relay|false|int + { + return $this->initializeLazyObject()->zintercard(...\func_get_args()); + } + + public function zinterstore($dst, $keys, $weights = null, $options = null): \Relay\Relay|false|int + { + return $this->initializeLazyObject()->zinterstore(...\func_get_args()); + } + + public function zunion($keys, $weights = null, $options = null): \Relay\Relay|array|false + { + return $this->initializeLazyObject()->zunion(...\func_get_args()); + } + + public function zunionstore($dst, $keys, $weights = null, $options = null): \Relay\Relay|false|int + { + return $this->initializeLazyObject()->zunionstore(...\func_get_args()); + } + + public function zpopmin($key, $count = 1): \Relay\Relay|array|false + { + return $this->initializeLazyObject()->zpopmin(...\func_get_args()); + } + + public function zpopmax($key, $count = 1): \Relay\Relay|array|false + { + return $this->initializeLazyObject()->zpopmax(...\func_get_args()); + } + + public function _getKeys() + { + return $this->initializeLazyObject()->_getKeys(...\func_get_args()); + } +} diff --git a/vendor/symfony/cache/Traits/RelayProxyTrait.php b/vendor/symfony/cache/Traits/RelayProxyTrait.php new file mode 100644 index 0000000..6077b44 --- /dev/null +++ b/vendor/symfony/cache/Traits/RelayProxyTrait.php @@ -0,0 +1,147 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Cache\Traits; + +if (version_compare(phpversion('relay'), '0.8.1', '>=')) { + /** + * @internal + */ + trait RelayProxyTrait + { + public function jsonArrAppend($key, $value_or_array, $path = null): \Relay\Relay|array|false + { + return $this->initializeLazyObject()->jsonArrAppend(...\func_get_args()); + } + + public function jsonArrIndex($key, $path, $value, $start = 0, $stop = -1): \Relay\Relay|array|false + { + return $this->initializeLazyObject()->jsonArrIndex(...\func_get_args()); + } + + public function jsonArrInsert($key, $path, $index, $value, ...$other_values): \Relay\Relay|array|false + { + return $this->initializeLazyObject()->jsonArrInsert(...\func_get_args()); + } + + public function jsonArrLen($key, $path = null): \Relay\Relay|array|false + { + return $this->initializeLazyObject()->jsonArrLen(...\func_get_args()); + } + + public function jsonArrPop($key, $path = null, $index = -1): \Relay\Relay|array|false + { + return $this->initializeLazyObject()->jsonArrPop(...\func_get_args()); + } + + public function jsonArrTrim($key, $path, $start, $stop): \Relay\Relay|array|false + { + return $this->initializeLazyObject()->jsonArrTrim(...\func_get_args()); + } + + public function jsonClear($key, $path = null): \Relay\Relay|false|int + { + return $this->initializeLazyObject()->jsonClear(...\func_get_args()); + } + + public function jsonDebug($command, $key, $path = null): \Relay\Relay|false|int + { + return $this->initializeLazyObject()->jsonDebug(...\func_get_args()); + } + + public function jsonDel($key, $path = null): \Relay\Relay|false|int + { + return $this->initializeLazyObject()->jsonDel(...\func_get_args()); + } + + public function jsonForget($key, $path = null): \Relay\Relay|false|int + { + return $this->initializeLazyObject()->jsonForget(...\func_get_args()); + } + + public function jsonGet($key, $options = [], ...$paths): mixed + { + return $this->initializeLazyObject()->jsonGet(...\func_get_args()); + } + + public function jsonMerge($key, $path, $value): \Relay\Relay|bool + { + return $this->initializeLazyObject()->jsonMerge(...\func_get_args()); + } + + public function jsonMget($key_or_array, $path): \Relay\Relay|array|false + { + return $this->initializeLazyObject()->jsonMget(...\func_get_args()); + } + + public function jsonMset($key, $path, $value, ...$other_triples): \Relay\Relay|bool + { + return $this->initializeLazyObject()->jsonMset(...\func_get_args()); + } + + public function jsonNumIncrBy($key, $path, $value): \Relay\Relay|array|false + { + return $this->initializeLazyObject()->jsonNumIncrBy(...\func_get_args()); + } + + public function jsonNumMultBy($key, $path, $value): \Relay\Relay|array|false + { + return $this->initializeLazyObject()->jsonNumMultBy(...\func_get_args()); + } + + public function jsonObjKeys($key, $path = null): \Relay\Relay|array|false + { + return $this->initializeLazyObject()->jsonObjKeys(...\func_get_args()); + } + + public function jsonObjLen($key, $path = null): \Relay\Relay|array|false + { + return $this->initializeLazyObject()->jsonObjLen(...\func_get_args()); + } + + public function jsonResp($key, $path = null): \Relay\Relay|array|false|int|string + { + return $this->initializeLazyObject()->jsonResp(...\func_get_args()); + } + + public function jsonSet($key, $path, $value, $condition = null): \Relay\Relay|bool + { + return $this->initializeLazyObject()->jsonSet(...\func_get_args()); + } + + public function jsonStrAppend($key, $value, $path = null): \Relay\Relay|array|false + { + return $this->initializeLazyObject()->jsonStrAppend(...\func_get_args()); + } + + public function jsonStrLen($key, $path = null): \Relay\Relay|array|false + { + return $this->initializeLazyObject()->jsonStrLen(...\func_get_args()); + } + + public function jsonToggle($key, $path): \Relay\Relay|array|false + { + return $this->initializeLazyObject()->jsonToggle(...\func_get_args()); + } + + public function jsonType($key, $path = null): \Relay\Relay|array|false + { + return $this->initializeLazyObject()->jsonType(...\func_get_args()); + } + } +} else { + /** + * @internal + */ + trait RelayProxyTrait + { + } +} diff --git a/vendor/symfony/cache/Traits/ValueWrapper.php b/vendor/symfony/cache/Traits/ValueWrapper.php new file mode 100644 index 0000000..718a23d --- /dev/null +++ b/vendor/symfony/cache/Traits/ValueWrapper.php @@ -0,0 +1,81 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/** + * A short namespace-less class to serialize items with metadata. + * + * @author Nicolas Grekas + * + * @internal + */ +class © +{ + private const EXPIRY_OFFSET = 1648206727; + private const INT32_MAX = 2147483647; + + public readonly mixed $value; + public readonly array $metadata; + + public function __construct(mixed $value, array $metadata) + { + $this->value = $value; + $this->metadata = $metadata; + } + + public function __serialize(): array + { + // pack 31-bits ctime into 14bits + $c = $this->metadata['ctime'] ?? 0; + $c = match (true) { + $c > self::INT32_MAX - 2 => self::INT32_MAX, + $c > 0 => 1 + $c, + default => 1, + }; + $e = 0; + while (!(0x40000000 & $c)) { + $c <<= 1; + ++$e; + } + $c = (0x7FE0 & ($c >> 16)) | $e; + + $pack = pack('Vn', (int) (0.1 + ($this->metadata['expiry'] ?: self::INT32_MAX + self::EXPIRY_OFFSET) - self::EXPIRY_OFFSET), $c); + + if (isset($this->metadata['tags'])) { + $pack[4] = $pack[4] | "\x80"; + } + + return [$pack => $this->value] + ($this->metadata['tags'] ?? []); + } + + public function __unserialize(array $data): void + { + $pack = array_key_first($data); + $this->value = $data[$pack]; + + if ($hasTags = "\x80" === ($pack[4] & "\x80")) { + unset($data[$pack]); + $pack[4] = $pack[4] & "\x7F"; + } + + $metadata = unpack('Vexpiry/nctime', $pack); + $metadata['expiry'] += self::EXPIRY_OFFSET; + + if (!$metadata['ctime'] = ((0x4000 | $metadata['ctime']) << 16 >> (0x1F & $metadata['ctime'])) - 1) { + unset($metadata['ctime']); + } + + if ($hasTags) { + $metadata['tags'] = $data; + } + + $this->metadata = $metadata; + } +} diff --git a/vendor/symfony/cache/composer.json b/vendor/symfony/cache/composer.json new file mode 100644 index 0000000..bdb461b --- /dev/null +++ b/vendor/symfony/cache/composer.json @@ -0,0 +1,61 @@ +{ + "name": "symfony/cache", + "type": "library", + "description": "Provides extended PSR-6, PSR-16 (and tags) implementations", + "keywords": ["caching", "psr6"], + "homepage": "https://symfony.com", + "license": "MIT", + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "provide": { + "psr/cache-implementation": "2.0|3.0", + "psr/simple-cache-implementation": "1.0|2.0|3.0", + "symfony/cache-implementation": "1.1|2.0|3.0" + }, + "require": { + "php": ">=8.2", + "psr/cache": "^2.0|^3.0", + "psr/log": "^1.1|^2|^3", + "symfony/cache-contracts": "^2.5|^3", + "symfony/deprecation-contracts": "^2.5|^3.0", + "symfony/service-contracts": "^2.5|^3", + "symfony/var-exporter": "^6.4|^7.0" + }, + "require-dev": { + "cache/integration-tests": "dev-master", + "doctrine/dbal": "^3.6|^4", + "predis/predis": "^1.1|^2.0", + "psr/simple-cache": "^1.0|^2.0|^3.0", + "symfony/clock": "^6.4|^7.0", + "symfony/config": "^6.4|^7.0", + "symfony/dependency-injection": "^6.4|^7.0", + "symfony/filesystem": "^6.4|^7.0", + "symfony/http-kernel": "^6.4|^7.0", + "symfony/messenger": "^6.4|^7.0", + "symfony/var-dumper": "^6.4|^7.0" + }, + "conflict": { + "doctrine/dbal": "<3.6", + "symfony/dependency-injection": "<6.4", + "symfony/http-kernel": "<6.4", + "symfony/var-dumper": "<6.4" + }, + "autoload": { + "psr-4": { "Symfony\\Component\\Cache\\": "" }, + "classmap": [ + "Traits/ValueWrapper.php" + ], + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "minimum-stability": "dev" +} diff --git a/vendor/symfony/deprecation-contracts/CHANGELOG.md b/vendor/symfony/deprecation-contracts/CHANGELOG.md new file mode 100644 index 0000000..7932e26 --- /dev/null +++ b/vendor/symfony/deprecation-contracts/CHANGELOG.md @@ -0,0 +1,5 @@ +CHANGELOG +========= + +The changelog is maintained for all Symfony contracts at the following URL: +https://github.com/symfony/contracts/blob/main/CHANGELOG.md diff --git a/vendor/symfony/deprecation-contracts/LICENSE b/vendor/symfony/deprecation-contracts/LICENSE new file mode 100644 index 0000000..0ed3a24 --- /dev/null +++ b/vendor/symfony/deprecation-contracts/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2020-present Fabien Potencier + +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/vendor/symfony/deprecation-contracts/README.md b/vendor/symfony/deprecation-contracts/README.md new file mode 100644 index 0000000..9814864 --- /dev/null +++ b/vendor/symfony/deprecation-contracts/README.md @@ -0,0 +1,26 @@ +Symfony Deprecation Contracts +============================= + +A generic function and convention to trigger deprecation notices. + +This package provides a single global function named `trigger_deprecation()` that triggers silenced deprecation notices. + +By using a custom PHP error handler such as the one provided by the Symfony ErrorHandler component, +the triggered deprecations can be caught and logged for later discovery, both on dev and prod environments. + +The function requires at least 3 arguments: + - the name of the Composer package that is triggering the deprecation + - the version of the package that introduced the deprecation + - the message of the deprecation + - more arguments can be provided: they will be inserted in the message using `printf()` formatting + +Example: +```php +trigger_deprecation('symfony/blockchain', '8.9', 'Using "%s" is deprecated, use "%s" instead.', 'bitcoin', 'fabcoin'); +``` + +This will generate the following message: +`Since symfony/blockchain 8.9: Using "bitcoin" is deprecated, use "fabcoin" instead.` + +While not recommended, the deprecation notices can be completely ignored by declaring an empty +`function trigger_deprecation() {}` in your application. diff --git a/vendor/symfony/deprecation-contracts/composer.json b/vendor/symfony/deprecation-contracts/composer.json new file mode 100644 index 0000000..ceb6c07 --- /dev/null +++ b/vendor/symfony/deprecation-contracts/composer.json @@ -0,0 +1,35 @@ +{ + "name": "symfony/deprecation-contracts", + "type": "library", + "description": "A generic function and convention to trigger deprecation notices", + "homepage": "https://symfony.com", + "license": "MIT", + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "require": { + "php": ">=8.1" + }, + "autoload": { + "files": [ + "function.php" + ] + }, + "minimum-stability": "dev", + "extra": { + "branch-alias": { + "dev-main": "3.5-dev" + }, + "thanks": { + "name": "symfony/contracts", + "url": "https://github.com/symfony/contracts" + } + } +} diff --git a/vendor/symfony/deprecation-contracts/function.php b/vendor/symfony/deprecation-contracts/function.php new file mode 100644 index 0000000..2d56512 --- /dev/null +++ b/vendor/symfony/deprecation-contracts/function.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +if (!function_exists('trigger_deprecation')) { + /** + * Triggers a silenced deprecation notice. + * + * @param string $package The name of the Composer package that is triggering the deprecation + * @param string $version The version of the package that introduced the deprecation + * @param string $message The message of the deprecation + * @param mixed ...$args Values to insert in the message using printf() formatting + * + * @author Nicolas Grekas + */ + function trigger_deprecation(string $package, string $version, string $message, mixed ...$args): void + { + @trigger_error(($package || $version ? "Since $package $version: " : '').($args ? vsprintf($message, $args) : $message), \E_USER_DEPRECATED); + } +} diff --git a/vendor/symfony/expression-language/CHANGELOG.md b/vendor/symfony/expression-language/CHANGELOG.md new file mode 100644 index 0000000..b06620c --- /dev/null +++ b/vendor/symfony/expression-language/CHANGELOG.md @@ -0,0 +1,46 @@ +CHANGELOG +========= + +6.3 +--- + + * Add `enum` expression function + * Deprecate loose comparisons when using the "in" operator; normalize the array parameter + so it only has the expected types or implement loose matching in your own expression function + +6.2 +--- + + * Add support for null-coalescing syntax + +6.1 +--- + + * Add support for null-safe syntax when parsing object's methods and properties + * Add new operators: `contains`, `starts with` and `ends with` + * Support lexing numbers with the numeric literal separator `_` + * Support lexing decimals with no leading zero + +5.1.0 +----- + + * added `lint` method to `ExpressionLanguage` class + * added `lint` method to `Parser` class + +4.0.0 +----- + + * the first argument of the `ExpressionLanguage` constructor must be an instance + of `CacheItemPoolInterface` + * removed the `ArrayParserCache` and `ParserCacheAdapter` classes + * removed the `ParserCacheInterface` + +2.6.0 +----- + + * Added ExpressionFunction and ExpressionFunctionProviderInterface + +2.4.0 +----- + + * added the component diff --git a/vendor/symfony/expression-language/Compiler.php b/vendor/symfony/expression-language/Compiler.php new file mode 100644 index 0000000..ab50d36 --- /dev/null +++ b/vendor/symfony/expression-language/Compiler.php @@ -0,0 +1,149 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\ExpressionLanguage; + +use Symfony\Contracts\Service\ResetInterface; + +/** + * Compiles a node to PHP code. + * + * @author Fabien Potencier + */ +class Compiler implements ResetInterface +{ + private string $source = ''; + private array $functions; + + public function __construct(array $functions) + { + $this->functions = $functions; + } + + /** + * @return array + */ + public function getFunction(string $name) + { + return $this->functions[$name]; + } + + /** + * Gets the current PHP code after compilation. + */ + public function getSource(): string + { + return $this->source; + } + + /** + * @return $this + */ + public function reset(): static + { + $this->source = ''; + + return $this; + } + + /** + * Compiles a node. + * + * @return $this + */ + public function compile(Node\Node $node): static + { + $node->compile($this); + + return $this; + } + + /** + * @return string + */ + public function subcompile(Node\Node $node) + { + $current = $this->source; + $this->source = ''; + + $node->compile($this); + + $source = $this->source; + $this->source = $current; + + return $source; + } + + /** + * Adds a raw string to the compiled code. + * + * @return $this + */ + public function raw(string $string): static + { + $this->source .= $string; + + return $this; + } + + /** + * Adds a quoted string to the compiled code. + * + * @return $this + */ + public function string(string $value): static + { + $this->source .= sprintf('"%s"', addcslashes($value, "\0\t\"\$\\")); + + return $this; + } + + /** + * Returns a PHP representation of a given value. + * + * @return $this + */ + public function repr(mixed $value): static + { + if (\is_int($value) || \is_float($value)) { + if (false !== $locale = setlocale(\LC_NUMERIC, 0)) { + setlocale(\LC_NUMERIC, 'C'); + } + + $this->raw($value); + + if (false !== $locale) { + setlocale(\LC_NUMERIC, $locale); + } + } elseif (null === $value) { + $this->raw('null'); + } elseif (\is_bool($value)) { + $this->raw($value ? 'true' : 'false'); + } elseif (\is_array($value)) { + $this->raw('['); + $first = true; + foreach ($value as $key => $value) { + if (!$first) { + $this->raw(', '); + } + $first = false; + $this->repr($key); + $this->raw(' => '); + $this->repr($value); + } + $this->raw(']'); + } else { + $this->string($value); + } + + return $this; + } +} diff --git a/vendor/symfony/expression-language/Expression.php b/vendor/symfony/expression-language/Expression.php new file mode 100644 index 0000000..3cab1df --- /dev/null +++ b/vendor/symfony/expression-language/Expression.php @@ -0,0 +1,35 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\ExpressionLanguage; + +/** + * Represents an expression. + * + * @author Fabien Potencier + */ +class Expression +{ + protected $expression; + + public function __construct(string $expression) + { + $this->expression = $expression; + } + + /** + * Gets the expression. + */ + public function __toString(): string + { + return $this->expression; + } +} diff --git a/vendor/symfony/expression-language/ExpressionFunction.php b/vendor/symfony/expression-language/ExpressionFunction.php new file mode 100644 index 0000000..0b3d6c4 --- /dev/null +++ b/vendor/symfony/expression-language/ExpressionFunction.php @@ -0,0 +1,91 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\ExpressionLanguage; + +/** + * Represents a function that can be used in an expression. + * + * A function is defined by two PHP callables. The callables are used + * by the language to compile and/or evaluate the function. + * + * The "compiler" function is used at compilation time and must return a + * PHP representation of the function call (it receives the function + * arguments as arguments). + * + * The "evaluator" function is used for expression evaluation and must return + * the value of the function call based on the values defined for the + * expression (it receives the values as a first argument and the function + * arguments as remaining arguments). + * + * @author Fabien Potencier + */ +class ExpressionFunction +{ + private string $name; + private \Closure $compiler; + private \Closure $evaluator; + + /** + * @param string $name The function name + * @param callable $compiler A callable able to compile the function + * @param callable $evaluator A callable able to evaluate the function + */ + public function __construct(string $name, callable $compiler, callable $evaluator) + { + $this->name = $name; + $this->compiler = $compiler(...); + $this->evaluator = $evaluator(...); + } + + public function getName(): string + { + return $this->name; + } + + public function getCompiler(): \Closure + { + return $this->compiler; + } + + public function getEvaluator(): \Closure + { + return $this->evaluator; + } + + /** + * Creates an ExpressionFunction from a PHP function name. + * + * @param string|null $expressionFunctionName The expression function name (default: same than the PHP function name) + * + * @throws \InvalidArgumentException if given PHP function name does not exist + * @throws \InvalidArgumentException if given PHP function name is in namespace + * and expression function name is not defined + */ + public static function fromPhp(string $phpFunctionName, ?string $expressionFunctionName = null): self + { + $phpFunctionName = ltrim($phpFunctionName, '\\'); + if (!\function_exists($phpFunctionName)) { + throw new \InvalidArgumentException(sprintf('PHP function "%s" does not exist.', $phpFunctionName)); + } + + $parts = explode('\\', $phpFunctionName); + if (!$expressionFunctionName && \count($parts) > 1) { + throw new \InvalidArgumentException(sprintf('An expression function name must be defined when PHP function "%s" is namespaced.', $phpFunctionName)); + } + + $compiler = fn (...$args) => sprintf('\%s(%s)', $phpFunctionName, implode(', ', $args)); + + $evaluator = fn ($p, ...$args) => $phpFunctionName(...$args); + + return new self($expressionFunctionName ?: end($parts), $compiler, $evaluator); + } +} diff --git a/vendor/symfony/expression-language/ExpressionFunctionProviderInterface.php b/vendor/symfony/expression-language/ExpressionFunctionProviderInterface.php new file mode 100644 index 0000000..479aeef --- /dev/null +++ b/vendor/symfony/expression-language/ExpressionFunctionProviderInterface.php @@ -0,0 +1,23 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\ExpressionLanguage; + +/** + * @author Fabien Potencier + */ +interface ExpressionFunctionProviderInterface +{ + /** + * @return ExpressionFunction[] + */ + public function getFunctions(); +} diff --git a/vendor/symfony/expression-language/ExpressionLanguage.php b/vendor/symfony/expression-language/ExpressionLanguage.php new file mode 100644 index 0000000..a7f249f --- /dev/null +++ b/vendor/symfony/expression-language/ExpressionLanguage.php @@ -0,0 +1,183 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\ExpressionLanguage; + +use Psr\Cache\CacheItemPoolInterface; +use Symfony\Component\Cache\Adapter\ArrayAdapter; + +// Help opcache.preload discover always-needed symbols +class_exists(ParsedExpression::class); + +/** + * Allows to compile and evaluate expressions written in your own DSL. + * + * @author Fabien Potencier + */ +class ExpressionLanguage +{ + private CacheItemPoolInterface $cache; + private Lexer $lexer; + private Parser $parser; + private Compiler $compiler; + + protected array $functions = []; + + /** + * @param ExpressionFunctionProviderInterface[] $providers + */ + public function __construct(?CacheItemPoolInterface $cache = null, array $providers = []) + { + $this->cache = $cache ?? new ArrayAdapter(); + $this->registerFunctions(); + foreach ($providers as $provider) { + $this->registerProvider($provider); + } + } + + /** + * Compiles an expression source code. + */ + public function compile(Expression|string $expression, array $names = []): string + { + return $this->getCompiler()->compile($this->parse($expression, $names)->getNodes())->getSource(); + } + + /** + * Evaluate an expression. + */ + public function evaluate(Expression|string $expression, array $values = []): mixed + { + return $this->parse($expression, array_keys($values))->getNodes()->evaluate($this->functions, $values); + } + + /** + * Parses an expression. + */ + public function parse(Expression|string $expression, array $names): ParsedExpression + { + if ($expression instanceof ParsedExpression) { + return $expression; + } + + asort($names); + $cacheKeyItems = []; + + foreach ($names as $nameKey => $name) { + $cacheKeyItems[] = \is_int($nameKey) ? $name : $nameKey.':'.$name; + } + + $cacheItem = $this->cache->getItem(rawurlencode($expression.'//'.implode('|', $cacheKeyItems))); + + if (null === $parsedExpression = $cacheItem->get()) { + $nodes = $this->getParser()->parse($this->getLexer()->tokenize((string) $expression), $names); + $parsedExpression = new ParsedExpression((string) $expression, $nodes); + + $cacheItem->set($parsedExpression); + $this->cache->save($cacheItem); + } + + return $parsedExpression; + } + + /** + * Validates the syntax of an expression. + * + * @param array|null $names The list of acceptable variable names in the expression, or null to accept any names + * + * @throws SyntaxError When the passed expression is invalid + */ + public function lint(Expression|string $expression, ?array $names): void + { + if ($expression instanceof ParsedExpression) { + return; + } + + $this->getParser()->lint($this->getLexer()->tokenize((string) $expression), $names); + } + + /** + * Registers a function. + * + * @param callable $compiler A callable able to compile the function + * @param callable $evaluator A callable able to evaluate the function + * + * @return void + * + * @throws \LogicException when registering a function after calling evaluate(), compile() or parse() + * + * @see ExpressionFunction + */ + public function register(string $name, callable $compiler, callable $evaluator) + { + if (isset($this->parser)) { + throw new \LogicException('Registering functions after calling evaluate(), compile() or parse() is not supported.'); + } + + $this->functions[$name] = ['compiler' => $compiler, 'evaluator' => $evaluator]; + } + + /** + * @return void + */ + public function addFunction(ExpressionFunction $function) + { + $this->register($function->getName(), $function->getCompiler(), $function->getEvaluator()); + } + + /** + * @return void + */ + public function registerProvider(ExpressionFunctionProviderInterface $provider) + { + foreach ($provider->getFunctions() as $function) { + $this->addFunction($function); + } + } + + /** + * @return void + */ + protected function registerFunctions() + { + $this->addFunction(ExpressionFunction::fromPhp('constant')); + + $this->addFunction(new ExpressionFunction('enum', + static fn ($str): string => sprintf("(\constant(\$v = (%s))) instanceof \UnitEnum ? \constant(\$v) : throw new \TypeError(\sprintf('The string \"%%s\" is not the name of a valid enum case.', \$v))", $str), + static function ($arguments, $str): \UnitEnum { + $value = \constant($str); + + if (!$value instanceof \UnitEnum) { + throw new \TypeError(sprintf('The string "%s" is not the name of a valid enum case.', $str)); + } + + return $value; + } + )); + } + + private function getLexer(): Lexer + { + return $this->lexer ??= new Lexer(); + } + + private function getParser(): Parser + { + return $this->parser ??= new Parser($this->functions); + } + + private function getCompiler(): Compiler + { + $this->compiler ??= new Compiler($this->functions); + + return $this->compiler->reset(); + } +} diff --git a/vendor/symfony/expression-language/LICENSE b/vendor/symfony/expression-language/LICENSE new file mode 100644 index 0000000..0138f8f --- /dev/null +++ b/vendor/symfony/expression-language/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2004-present Fabien Potencier + +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/vendor/symfony/expression-language/Lexer.php b/vendor/symfony/expression-language/Lexer.php new file mode 100644 index 0000000..ab32342 --- /dev/null +++ b/vendor/symfony/expression-language/Lexer.php @@ -0,0 +1,107 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\ExpressionLanguage; + +/** + * Lexes an expression. + * + * @author Fabien Potencier + */ +class Lexer +{ + /** + * Tokenizes an expression. + * + * @throws SyntaxError + */ + public function tokenize(string $expression): TokenStream + { + $expression = str_replace(["\r", "\n", "\t", "\v", "\f"], ' ', $expression); + $cursor = 0; + $tokens = []; + $brackets = []; + $end = \strlen($expression); + + while ($cursor < $end) { + if (' ' == $expression[$cursor]) { + ++$cursor; + + continue; + } + + if (preg_match('/ + (?(DEFINE)(?P[0-9]+(_[0-9]+)*)) + (?:\.(?&LNUM)|(?&LNUM)(?:\.(?!\.)(?&LNUM)?)?)(?:[eE][+-]?(?&LNUM))?/Ax', + $expression, $match, 0, $cursor) + ) { + // numbers + $tokens[] = new Token(Token::NUMBER_TYPE, 0 + str_replace('_', '', $match[0]), $cursor + 1); + $cursor += \strlen($match[0]); + } elseif (str_contains('([{', $expression[$cursor])) { + // opening bracket + $brackets[] = [$expression[$cursor], $cursor]; + + $tokens[] = new Token(Token::PUNCTUATION_TYPE, $expression[$cursor], $cursor + 1); + ++$cursor; + } elseif (str_contains(')]}', $expression[$cursor])) { + // closing bracket + if (!$brackets) { + throw new SyntaxError(sprintf('Unexpected "%s".', $expression[$cursor]), $cursor, $expression); + } + + [$expect, $cur] = array_pop($brackets); + if ($expression[$cursor] != strtr($expect, '([{', ')]}')) { + throw new SyntaxError(sprintf('Unclosed "%s".', $expect), $cur, $expression); + } + + $tokens[] = new Token(Token::PUNCTUATION_TYPE, $expression[$cursor], $cursor + 1); + ++$cursor; + } elseif (preg_match('/"([^"\\\\]*(?:\\\\.[^"\\\\]*)*)"|\'([^\'\\\\]*(?:\\\\.[^\'\\\\]*)*)\'/As', $expression, $match, 0, $cursor)) { + // strings + $tokens[] = new Token(Token::STRING_TYPE, stripcslashes(substr($match[0], 1, -1)), $cursor + 1); + $cursor += \strlen($match[0]); + } elseif (preg_match('/(?<=^|[\s(])starts with(?=[\s(])|(?<=^|[\s(])ends with(?=[\s(])|(?<=^|[\s(])contains(?=[\s(])|(?<=^|[\s(])matches(?=[\s(])|(?<=^|[\s(])not in(?=[\s(])|(?<=^|[\s(])not(?=[\s(])|(?<=^|[\s(])and(?=[\s(])|\=\=\=|\!\=\=|(?<=^|[\s(])or(?=[\s(])|\|\||&&|\=\=|\!\=|\>\=|\<\=|(?<=^|[\s(])in(?=[\s(])|\.\.|\*\*|\!|\||\^|&|\<|\>|\+|\-|~|\*|\/|%/A', $expression, $match, 0, $cursor)) { + // operators + $tokens[] = new Token(Token::OPERATOR_TYPE, $match[0], $cursor + 1); + $cursor += \strlen($match[0]); + } elseif ('?' === $expression[$cursor] && '.' === ($expression[$cursor + 1] ?? '')) { + // null-safe + $tokens[] = new Token(Token::PUNCTUATION_TYPE, '?.', ++$cursor); + ++$cursor; + } elseif ('?' === $expression[$cursor] && '?' === ($expression[$cursor + 1] ?? '')) { + // null-coalescing + $tokens[] = new Token(Token::PUNCTUATION_TYPE, '??', ++$cursor); + ++$cursor; + } elseif (str_contains('.,?:', $expression[$cursor])) { + // punctuation + $tokens[] = new Token(Token::PUNCTUATION_TYPE, $expression[$cursor], $cursor + 1); + ++$cursor; + } elseif (preg_match('/[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*/A', $expression, $match, 0, $cursor)) { + // names + $tokens[] = new Token(Token::NAME_TYPE, $match[0], $cursor + 1); + $cursor += \strlen($match[0]); + } else { + // unlexable + throw new SyntaxError(sprintf('Unexpected character "%s".', $expression[$cursor]), $cursor, $expression); + } + } + + $tokens[] = new Token(Token::EOF_TYPE, null, $cursor + 1); + + if ($brackets) { + [$expect, $cur] = array_pop($brackets); + throw new SyntaxError(sprintf('Unclosed "%s".', $expect), $cur, $expression); + } + + return new TokenStream($tokens, $expression); + } +} diff --git a/vendor/symfony/expression-language/Node/ArgumentsNode.php b/vendor/symfony/expression-language/Node/ArgumentsNode.php new file mode 100644 index 0000000..981cae2 --- /dev/null +++ b/vendor/symfony/expression-language/Node/ArgumentsNode.php @@ -0,0 +1,40 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\ExpressionLanguage\Node; + +use Symfony\Component\ExpressionLanguage\Compiler; + +/** + * @author Fabien Potencier + * + * @internal + */ +class ArgumentsNode extends ArrayNode +{ + public function compile(Compiler $compiler): void + { + $this->compileArguments($compiler, false); + } + + public function toArray(): array + { + $array = []; + + foreach ($this->getKeyValuePairs() as $pair) { + $array[] = $pair['value']; + $array[] = ', '; + } + array_pop($array); + + return $array; + } +} diff --git a/vendor/symfony/expression-language/Node/ArrayNode.php b/vendor/symfony/expression-language/Node/ArrayNode.php new file mode 100644 index 0000000..79eade2 --- /dev/null +++ b/vendor/symfony/expression-language/Node/ArrayNode.php @@ -0,0 +1,116 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\ExpressionLanguage\Node; + +use Symfony\Component\ExpressionLanguage\Compiler; + +/** + * @author Fabien Potencier + * + * @internal + */ +class ArrayNode extends Node +{ + protected int $index; + + public function __construct() + { + $this->index = -1; + } + + public function addElement(Node $value, ?Node $key = null): void + { + $key ??= new ConstantNode(++$this->index); + + array_push($this->nodes, $key, $value); + } + + /** + * Compiles the node to PHP. + */ + public function compile(Compiler $compiler): void + { + $compiler->raw('['); + $this->compileArguments($compiler); + $compiler->raw(']'); + } + + public function evaluate(array $functions, array $values): array + { + $result = []; + foreach ($this->getKeyValuePairs() as $pair) { + $result[$pair['key']->evaluate($functions, $values)] = $pair['value']->evaluate($functions, $values); + } + + return $result; + } + + public function toArray(): array + { + $value = []; + foreach ($this->getKeyValuePairs() as $pair) { + $value[$pair['key']->attributes['value']] = $pair['value']; + } + + $array = []; + + if ($this->isHash($value)) { + foreach ($value as $k => $v) { + $array[] = ', '; + $array[] = new ConstantNode($k); + $array[] = ': '; + $array[] = $v; + } + $array[0] = '{'; + $array[] = '}'; + } else { + foreach ($value as $v) { + $array[] = ', '; + $array[] = $v; + } + $array[0] = '['; + $array[] = ']'; + } + + return $array; + } + + protected function getKeyValuePairs(): array + { + $pairs = []; + foreach (array_chunk($this->nodes, 2) as $pair) { + $pairs[] = ['key' => $pair[0], 'value' => $pair[1]]; + } + + return $pairs; + } + + protected function compileArguments(Compiler $compiler, bool $withKeys = true): void + { + $first = true; + foreach ($this->getKeyValuePairs() as $pair) { + if (!$first) { + $compiler->raw(', '); + } + $first = false; + + if ($withKeys) { + $compiler + ->compile($pair['key']) + ->raw(' => ') + ; + } + + $compiler->compile($pair['value']); + } + } +} diff --git a/vendor/symfony/expression-language/Node/BinaryNode.php b/vendor/symfony/expression-language/Node/BinaryNode.php new file mode 100644 index 0000000..0cacfc6 --- /dev/null +++ b/vendor/symfony/expression-language/Node/BinaryNode.php @@ -0,0 +1,204 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\ExpressionLanguage\Node; + +use Symfony\Component\ExpressionLanguage\Compiler; +use Symfony\Component\ExpressionLanguage\SyntaxError; + +/** + * @author Fabien Potencier + * + * @internal + */ +class BinaryNode extends Node +{ + private const OPERATORS = [ + '~' => '.', + 'and' => '&&', + 'or' => '||', + ]; + + private const FUNCTIONS = [ + '**' => 'pow', + '..' => 'range', + 'in' => '\\'.self::class.'::inArray', + 'not in' => '!\\'.self::class.'::inArray', + 'contains' => 'str_contains', + 'starts with' => 'str_starts_with', + 'ends with' => 'str_ends_with', + ]; + + public function __construct(string $operator, Node $left, Node $right) + { + parent::__construct( + ['left' => $left, 'right' => $right], + ['operator' => $operator] + ); + } + + public function compile(Compiler $compiler): void + { + $operator = $this->attributes['operator']; + + if ('matches' == $operator) { + if ($this->nodes['right'] instanceof ConstantNode) { + $this->evaluateMatches($this->nodes['right']->evaluate([], []), ''); + } + + $compiler + ->raw('(static function ($regexp, $str) { set_error_handler(static fn ($t, $m) => throw new \Symfony\Component\ExpressionLanguage\SyntaxError(sprintf(\'Regexp "%s" passed to "matches" is not valid\', $regexp).substr($m, 12))); try { return preg_match($regexp, (string) $str); } finally { restore_error_handler(); } })(') + ->compile($this->nodes['right']) + ->raw(', ') + ->compile($this->nodes['left']) + ->raw(')') + ; + + return; + } + + if (isset(self::FUNCTIONS[$operator])) { + $compiler + ->raw(sprintf('%s(', self::FUNCTIONS[$operator])) + ->compile($this->nodes['left']) + ->raw(', ') + ->compile($this->nodes['right']) + ->raw(')') + ; + + return; + } + + if (isset(self::OPERATORS[$operator])) { + $operator = self::OPERATORS[$operator]; + } + + $compiler + ->raw('(') + ->compile($this->nodes['left']) + ->raw(' ') + ->raw($operator) + ->raw(' ') + ->compile($this->nodes['right']) + ->raw(')') + ; + } + + public function evaluate(array $functions, array $values): mixed + { + $operator = $this->attributes['operator']; + $left = $this->nodes['left']->evaluate($functions, $values); + + if (isset(self::FUNCTIONS[$operator])) { + $right = $this->nodes['right']->evaluate($functions, $values); + + if ('not in' === $operator) { + return !self::inArray($left, $right); + } + $f = self::FUNCTIONS[$operator]; + + return $f($left, $right); + } + + switch ($operator) { + case 'or': + case '||': + return $left || $this->nodes['right']->evaluate($functions, $values); + case 'and': + case '&&': + return $left && $this->nodes['right']->evaluate($functions, $values); + } + + $right = $this->nodes['right']->evaluate($functions, $values); + + switch ($operator) { + case '|': + return $left | $right; + case '^': + return $left ^ $right; + case '&': + return $left & $right; + case '==': + return $left == $right; + case '===': + return $left === $right; + case '!=': + return $left != $right; + case '!==': + return $left !== $right; + case '<': + return $left < $right; + case '>': + return $left > $right; + case '>=': + return $left >= $right; + case '<=': + return $left <= $right; + case 'not in': + return !self::inArray($left, $right); + case 'in': + return self::inArray($left, $right); + case '+': + return $left + $right; + case '-': + return $left - $right; + case '~': + return $left.$right; + case '*': + return $left * $right; + case '/': + if (0 == $right) { + throw new \DivisionByZeroError('Division by zero.'); + } + + return $left / $right; + case '%': + if (0 == $right) { + throw new \DivisionByZeroError('Modulo by zero.'); + } + + return $left % $right; + case 'matches': + return $this->evaluateMatches($right, $left); + } + } + + public function toArray(): array + { + return ['(', $this->nodes['left'], ' '.$this->attributes['operator'].' ', $this->nodes['right'], ')']; + } + + /** + * @internal to be replaced by an inline strict call to in_array() in version 7.0 + */ + public static function inArray($value, array $array): bool + { + if (false === $key = array_search($value, $array)) { + return false; + } + + if (!\in_array($value, $array, true)) { + trigger_deprecation('symfony/expression-language', '6.3', 'The "in" operator will use strict comparisons in Symfony 7.0. Loose match found with key "%s" for value %s. Normalize the array parameter so it only has the expected types or implement loose matching in your own expression function.', $key, json_encode($value)); + } + + return true; + } + + private function evaluateMatches(string $regexp, ?string $str): int + { + set_error_handler(static fn ($t, $m) => throw new SyntaxError(sprintf('Regexp "%s" passed to "matches" is not valid', $regexp).substr($m, 12))); + try { + return preg_match($regexp, (string) $str); + } finally { + restore_error_handler(); + } + } +} diff --git a/vendor/symfony/expression-language/Node/ConditionalNode.php b/vendor/symfony/expression-language/Node/ConditionalNode.php new file mode 100644 index 0000000..7d02544 --- /dev/null +++ b/vendor/symfony/expression-language/Node/ConditionalNode.php @@ -0,0 +1,56 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\ExpressionLanguage\Node; + +use Symfony\Component\ExpressionLanguage\Compiler; + +/** + * @author Fabien Potencier + * + * @internal + */ +class ConditionalNode extends Node +{ + public function __construct(Node $expr1, Node $expr2, Node $expr3) + { + parent::__construct( + ['expr1' => $expr1, 'expr2' => $expr2, 'expr3' => $expr3] + ); + } + + public function compile(Compiler $compiler): void + { + $compiler + ->raw('((') + ->compile($this->nodes['expr1']) + ->raw(') ? (') + ->compile($this->nodes['expr2']) + ->raw(') : (') + ->compile($this->nodes['expr3']) + ->raw('))') + ; + } + + public function evaluate(array $functions, array $values): mixed + { + if ($this->nodes['expr1']->evaluate($functions, $values)) { + return $this->nodes['expr2']->evaluate($functions, $values); + } + + return $this->nodes['expr3']->evaluate($functions, $values); + } + + public function toArray(): array + { + return ['(', $this->nodes['expr1'], ' ? ', $this->nodes['expr2'], ' : ', $this->nodes['expr3'], ')']; + } +} diff --git a/vendor/symfony/expression-language/Node/ConstantNode.php b/vendor/symfony/expression-language/Node/ConstantNode.php new file mode 100644 index 0000000..37beee8 --- /dev/null +++ b/vendor/symfony/expression-language/Node/ConstantNode.php @@ -0,0 +1,83 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\ExpressionLanguage\Node; + +use Symfony\Component\ExpressionLanguage\Compiler; + +/** + * @author Fabien Potencier + * + * @internal + */ +class ConstantNode extends Node +{ + public readonly bool $isNullSafe; + private bool $isIdentifier; + + public function __construct(mixed $value, bool $isIdentifier = false, bool $isNullSafe = false) + { + $this->isIdentifier = $isIdentifier; + $this->isNullSafe = $isNullSafe; + parent::__construct( + [], + ['value' => $value] + ); + } + + public function compile(Compiler $compiler): void + { + $compiler->repr($this->attributes['value']); + } + + public function evaluate(array $functions, array $values): mixed + { + return $this->attributes['value']; + } + + public function toArray(): array + { + $array = []; + $value = $this->attributes['value']; + + if ($this->isIdentifier) { + $array[] = $value; + } elseif (true === $value) { + $array[] = 'true'; + } elseif (false === $value) { + $array[] = 'false'; + } elseif (null === $value) { + $array[] = 'null'; + } elseif (is_numeric($value)) { + $array[] = $value; + } elseif (!\is_array($value)) { + $array[] = $this->dumpString($value); + } elseif ($this->isHash($value)) { + foreach ($value as $k => $v) { + $array[] = ', '; + $array[] = new self($k); + $array[] = ': '; + $array[] = new self($v); + } + $array[0] = '{'; + $array[] = '}'; + } else { + foreach ($value as $v) { + $array[] = ', '; + $array[] = new self($v); + } + $array[0] = '['; + $array[] = ']'; + } + + return $array; + } +} diff --git a/vendor/symfony/expression-language/Node/FunctionNode.php b/vendor/symfony/expression-language/Node/FunctionNode.php new file mode 100644 index 0000000..33323f3 --- /dev/null +++ b/vendor/symfony/expression-language/Node/FunctionNode.php @@ -0,0 +1,70 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\ExpressionLanguage\Node; + +use Symfony\Component\ExpressionLanguage\Compiler; + +/** + * @author Fabien Potencier + * + * @internal + */ +class FunctionNode extends Node +{ + public function __construct(string $name, Node $arguments) + { + parent::__construct( + ['arguments' => $arguments], + ['name' => $name] + ); + } + + public function compile(Compiler $compiler): void + { + $arguments = []; + foreach ($this->nodes['arguments']->nodes as $node) { + $arguments[] = $compiler->subcompile($node); + } + + $function = $compiler->getFunction($this->attributes['name']); + + $compiler->raw($function['compiler'](...$arguments)); + } + + public function evaluate(array $functions, array $values): mixed + { + $arguments = [$values]; + foreach ($this->nodes['arguments']->nodes as $node) { + $arguments[] = $node->evaluate($functions, $values); + } + + return $functions[$this->attributes['name']]['evaluator'](...$arguments); + } + + /** + * @return array + */ + public function toArray() + { + $array = []; + $array[] = $this->attributes['name']; + + foreach ($this->nodes['arguments']->nodes as $node) { + $array[] = ', '; + $array[] = $node; + } + $array[1] = '('; + $array[] = ')'; + + return $array; + } +} diff --git a/vendor/symfony/expression-language/Node/GetAttrNode.php b/vendor/symfony/expression-language/Node/GetAttrNode.php new file mode 100644 index 0000000..984247e --- /dev/null +++ b/vendor/symfony/expression-language/Node/GetAttrNode.php @@ -0,0 +1,166 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\ExpressionLanguage\Node; + +use Symfony\Component\ExpressionLanguage\Compiler; + +/** + * @author Fabien Potencier + * + * @internal + */ +class GetAttrNode extends Node +{ + public const PROPERTY_CALL = 1; + public const METHOD_CALL = 2; + public const ARRAY_CALL = 3; + + /** + * @param self::* $type + */ + public function __construct(Node $node, Node $attribute, ArrayNode $arguments, int $type) + { + parent::__construct( + ['node' => $node, 'attribute' => $attribute, 'arguments' => $arguments], + ['type' => $type, 'is_null_coalesce' => false, 'is_short_circuited' => false], + ); + } + + public function compile(Compiler $compiler): void + { + $nullSafe = $this->nodes['attribute'] instanceof ConstantNode && $this->nodes['attribute']->isNullSafe; + switch ($this->attributes['type']) { + case self::PROPERTY_CALL: + $compiler + ->compile($this->nodes['node']) + ->raw($nullSafe ? '?->' : '->') + ->raw($this->nodes['attribute']->attributes['value']) + ; + break; + + case self::METHOD_CALL: + $compiler + ->compile($this->nodes['node']) + ->raw($nullSafe ? '?->' : '->') + ->raw($this->nodes['attribute']->attributes['value']) + ->raw('(') + ->compile($this->nodes['arguments']) + ->raw(')') + ; + break; + + case self::ARRAY_CALL: + $compiler + ->compile($this->nodes['node']) + ->raw('[') + ->compile($this->nodes['attribute'])->raw(']') + ; + break; + } + } + + public function evaluate(array $functions, array $values): mixed + { + switch ($this->attributes['type']) { + case self::PROPERTY_CALL: + $obj = $this->nodes['node']->evaluate($functions, $values); + if (null === $obj && ($this->nodes['attribute']->isNullSafe || $this->attributes['is_null_coalesce'])) { + $this->attributes['is_short_circuited'] = true; + + return null; + } + if (null === $obj && $this->isShortCircuited()) { + return null; + } + + if (!\is_object($obj)) { + throw new \RuntimeException(sprintf('Unable to get property "%s" of non-object "%s".', $this->nodes['attribute']->dump(), $this->nodes['node']->dump())); + } + + $property = $this->nodes['attribute']->attributes['value']; + + if ($this->attributes['is_null_coalesce']) { + return $obj->$property ?? null; + } + + return $obj->$property; + + case self::METHOD_CALL: + $obj = $this->nodes['node']->evaluate($functions, $values); + + if (null === $obj && $this->nodes['attribute']->isNullSafe) { + $this->attributes['is_short_circuited'] = true; + + return null; + } + if (null === $obj && $this->isShortCircuited()) { + return null; + } + + if (!\is_object($obj)) { + throw new \RuntimeException(sprintf('Unable to call method "%s" of non-object "%s".', $this->nodes['attribute']->dump(), $this->nodes['node']->dump())); + } + if (!\is_callable($toCall = [$obj, $this->nodes['attribute']->attributes['value']])) { + throw new \RuntimeException(sprintf('Unable to call method "%s" of object "%s".', $this->nodes['attribute']->attributes['value'], get_debug_type($obj))); + } + + return $toCall(...array_values($this->nodes['arguments']->evaluate($functions, $values))); + + case self::ARRAY_CALL: + $array = $this->nodes['node']->evaluate($functions, $values); + + if (null === $array && $this->isShortCircuited()) { + return null; + } + + if (!\is_array($array) && !$array instanceof \ArrayAccess && !(null === $array && $this->attributes['is_null_coalesce'])) { + throw new \RuntimeException(sprintf('Unable to get an item of non-array "%s".', $this->nodes['node']->dump())); + } + + if ($this->attributes['is_null_coalesce']) { + return $array[$this->nodes['attribute']->evaluate($functions, $values)] ?? null; + } + + return $array[$this->nodes['attribute']->evaluate($functions, $values)]; + } + } + + private function isShortCircuited(): bool + { + return $this->attributes['is_short_circuited'] || ($this->nodes['node'] instanceof self && $this->nodes['node']->isShortCircuited()); + } + + public function toArray(): array + { + switch ($this->attributes['type']) { + case self::PROPERTY_CALL: + return [$this->nodes['node'], '.', $this->nodes['attribute']]; + + case self::METHOD_CALL: + return [$this->nodes['node'], '.', $this->nodes['attribute'], '(', $this->nodes['arguments'], ')']; + + case self::ARRAY_CALL: + return [$this->nodes['node'], '[', $this->nodes['attribute'], ']']; + } + } + + /** + * Provides BC with instances serialized before v6.2. + */ + public function __unserialize(array $data): void + { + $this->nodes = $data['nodes']; + $this->attributes = $data['attributes']; + $this->attributes['is_null_coalesce'] ??= false; + $this->attributes['is_short_circuited'] ??= $data["\x00Symfony\Component\ExpressionLanguage\Node\GetAttrNode\x00isShortCircuited"] ?? false; + } +} diff --git a/vendor/symfony/expression-language/Node/NameNode.php b/vendor/symfony/expression-language/Node/NameNode.php new file mode 100644 index 0000000..b9d0e1a --- /dev/null +++ b/vendor/symfony/expression-language/Node/NameNode.php @@ -0,0 +1,45 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\ExpressionLanguage\Node; + +use Symfony\Component\ExpressionLanguage\Compiler; + +/** + * @author Fabien Potencier + * + * @internal + */ +class NameNode extends Node +{ + public function __construct(string $name) + { + parent::__construct( + [], + ['name' => $name] + ); + } + + public function compile(Compiler $compiler): void + { + $compiler->raw('$'.$this->attributes['name']); + } + + public function evaluate(array $functions, array $values): mixed + { + return $values[$this->attributes['name']]; + } + + public function toArray(): array + { + return [$this->attributes['name']]; + } +} diff --git a/vendor/symfony/expression-language/Node/Node.php b/vendor/symfony/expression-language/Node/Node.php new file mode 100644 index 0000000..91fcc36 --- /dev/null +++ b/vendor/symfony/expression-language/Node/Node.php @@ -0,0 +1,130 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\ExpressionLanguage\Node; + +use Symfony\Component\ExpressionLanguage\Compiler; + +/** + * Represents a node in the AST. + * + * @author Fabien Potencier + */ +class Node +{ + public $nodes = []; + public $attributes = []; + + /** + * @param array $nodes An array of nodes + * @param array $attributes An array of attributes + */ + public function __construct(array $nodes = [], array $attributes = []) + { + $this->nodes = $nodes; + $this->attributes = $attributes; + } + + public function __toString(): string + { + $attributes = []; + foreach ($this->attributes as $name => $value) { + $attributes[] = sprintf('%s: %s', $name, str_replace("\n", '', var_export($value, true))); + } + + $repr = [str_replace('Symfony\Component\ExpressionLanguage\Node\\', '', static::class).'('.implode(', ', $attributes)]; + + if (\count($this->nodes)) { + foreach ($this->nodes as $node) { + foreach (explode("\n", (string) $node) as $line) { + $repr[] = ' '.$line; + } + } + + $repr[] = ')'; + } else { + $repr[0] .= ')'; + } + + return implode("\n", $repr); + } + + /** + * @return void + */ + public function compile(Compiler $compiler) + { + foreach ($this->nodes as $node) { + $node->compile($compiler); + } + } + + /** + * @return mixed + */ + public function evaluate(array $functions, array $values) + { + $results = []; + foreach ($this->nodes as $node) { + $results[] = $node->evaluate($functions, $values); + } + + return $results; + } + + /** + * @return array + * + * @throws \BadMethodCallException when this node cannot be transformed to an array + */ + public function toArray() + { + throw new \BadMethodCallException(sprintf('Dumping a "%s" instance is not supported yet.', static::class)); + } + + /** + * @return string + */ + public function dump() + { + $dump = ''; + + foreach ($this->toArray() as $v) { + $dump .= \is_scalar($v) ? $v : $v->dump(); + } + + return $dump; + } + + /** + * @return string + */ + protected function dumpString(string $value) + { + return sprintf('"%s"', addcslashes($value, "\0\t\"\\")); + } + + /** + * @return bool + */ + protected function isHash(array $value) + { + $expectedKey = 0; + + foreach ($value as $key => $val) { + if ($key !== $expectedKey++) { + return true; + } + } + + return false; + } +} diff --git a/vendor/symfony/expression-language/Node/NullCoalesceNode.php b/vendor/symfony/expression-language/Node/NullCoalesceNode.php new file mode 100644 index 0000000..025bb1a --- /dev/null +++ b/vendor/symfony/expression-language/Node/NullCoalesceNode.php @@ -0,0 +1,65 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\ExpressionLanguage\Node; + +use Symfony\Component\ExpressionLanguage\Compiler; + +/** + * @author Fabien Potencier + * + * @internal + */ +class NullCoalesceNode extends Node +{ + public function __construct(Node $expr1, Node $expr2) + { + parent::__construct(['expr1' => $expr1, 'expr2' => $expr2]); + } + + public function compile(Compiler $compiler): void + { + $compiler + ->raw('((') + ->compile($this->nodes['expr1']) + ->raw(') ?? (') + ->compile($this->nodes['expr2']) + ->raw('))') + ; + } + + public function evaluate(array $functions, array $values): mixed + { + if ($this->nodes['expr1'] instanceof GetAttrNode) { + $this->addNullCoalesceAttributeToGetAttrNodes($this->nodes['expr1']); + } + + return $this->nodes['expr1']->evaluate($functions, $values) ?? $this->nodes['expr2']->evaluate($functions, $values); + } + + public function toArray(): array + { + return ['(', $this->nodes['expr1'], ') ?? (', $this->nodes['expr2'], ')']; + } + + private function addNullCoalesceAttributeToGetAttrNodes(Node $node): void + { + if (!$node instanceof GetAttrNode) { + return; + } + + $node->attributes['is_null_coalesce'] = true; + + foreach ($node->nodes as $node) { + $this->addNullCoalesceAttributeToGetAttrNodes($node); + } + } +} diff --git a/vendor/symfony/expression-language/Node/UnaryNode.php b/vendor/symfony/expression-language/Node/UnaryNode.php new file mode 100644 index 0000000..55e2121 --- /dev/null +++ b/vendor/symfony/expression-language/Node/UnaryNode.php @@ -0,0 +1,64 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\ExpressionLanguage\Node; + +use Symfony\Component\ExpressionLanguage\Compiler; + +/** + * @author Fabien Potencier + * + * @internal + */ +class UnaryNode extends Node +{ + private const OPERATORS = [ + '!' => '!', + 'not' => '!', + '+' => '+', + '-' => '-', + ]; + + public function __construct(string $operator, Node $node) + { + parent::__construct( + ['node' => $node], + ['operator' => $operator] + ); + } + + public function compile(Compiler $compiler): void + { + $compiler + ->raw('(') + ->raw(self::OPERATORS[$this->attributes['operator']]) + ->compile($this->nodes['node']) + ->raw(')') + ; + } + + public function evaluate(array $functions, array $values): mixed + { + $value = $this->nodes['node']->evaluate($functions, $values); + + return match ($this->attributes['operator']) { + 'not', + '!' => !$value, + '-' => -$value, + default => $value, + }; + } + + public function toArray(): array + { + return ['(', $this->attributes['operator'].' ', $this->nodes['node'], ')']; + } +} diff --git a/vendor/symfony/expression-language/ParsedExpression.php b/vendor/symfony/expression-language/ParsedExpression.php new file mode 100644 index 0000000..239624e --- /dev/null +++ b/vendor/symfony/expression-language/ParsedExpression.php @@ -0,0 +1,39 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\ExpressionLanguage; + +use Symfony\Component\ExpressionLanguage\Node\Node; + +/** + * Represents an already parsed expression. + * + * @author Fabien Potencier + */ +class ParsedExpression extends Expression +{ + private Node $nodes; + + public function __construct(string $expression, Node $nodes) + { + parent::__construct($expression); + + $this->nodes = $nodes; + } + + /** + * @return Node + */ + public function getNodes() + { + return $this->nodes; + } +} diff --git a/vendor/symfony/expression-language/Parser.php b/vendor/symfony/expression-language/Parser.php new file mode 100644 index 0000000..a163a7a --- /dev/null +++ b/vendor/symfony/expression-language/Parser.php @@ -0,0 +1,439 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\ExpressionLanguage; + +/** + * Parsers a token stream. + * + * This parser implements a "Precedence climbing" algorithm. + * + * @see http://www.engr.mun.ca/~theo/Misc/exp_parsing.htm + * @see http://en.wikipedia.org/wiki/Operator-precedence_parser + * + * @author Fabien Potencier + */ +class Parser +{ + public const OPERATOR_LEFT = 1; + public const OPERATOR_RIGHT = 2; + + private TokenStream $stream; + private array $unaryOperators; + private array $binaryOperators; + private array $functions; + private ?array $names; + private bool $lint = false; + + public function __construct(array $functions) + { + $this->functions = $functions; + + $this->unaryOperators = [ + 'not' => ['precedence' => 50], + '!' => ['precedence' => 50], + '-' => ['precedence' => 500], + '+' => ['precedence' => 500], + ]; + $this->binaryOperators = [ + 'or' => ['precedence' => 10, 'associativity' => self::OPERATOR_LEFT], + '||' => ['precedence' => 10, 'associativity' => self::OPERATOR_LEFT], + 'and' => ['precedence' => 15, 'associativity' => self::OPERATOR_LEFT], + '&&' => ['precedence' => 15, 'associativity' => self::OPERATOR_LEFT], + '|' => ['precedence' => 16, 'associativity' => self::OPERATOR_LEFT], + '^' => ['precedence' => 17, 'associativity' => self::OPERATOR_LEFT], + '&' => ['precedence' => 18, 'associativity' => self::OPERATOR_LEFT], + '==' => ['precedence' => 20, 'associativity' => self::OPERATOR_LEFT], + '===' => ['precedence' => 20, 'associativity' => self::OPERATOR_LEFT], + '!=' => ['precedence' => 20, 'associativity' => self::OPERATOR_LEFT], + '!==' => ['precedence' => 20, 'associativity' => self::OPERATOR_LEFT], + '<' => ['precedence' => 20, 'associativity' => self::OPERATOR_LEFT], + '>' => ['precedence' => 20, 'associativity' => self::OPERATOR_LEFT], + '>=' => ['precedence' => 20, 'associativity' => self::OPERATOR_LEFT], + '<=' => ['precedence' => 20, 'associativity' => self::OPERATOR_LEFT], + 'not in' => ['precedence' => 20, 'associativity' => self::OPERATOR_LEFT], + 'in' => ['precedence' => 20, 'associativity' => self::OPERATOR_LEFT], + 'contains' => ['precedence' => 20, 'associativity' => self::OPERATOR_LEFT], + 'starts with' => ['precedence' => 20, 'associativity' => self::OPERATOR_LEFT], + 'ends with' => ['precedence' => 20, 'associativity' => self::OPERATOR_LEFT], + 'matches' => ['precedence' => 20, 'associativity' => self::OPERATOR_LEFT], + '..' => ['precedence' => 25, 'associativity' => self::OPERATOR_LEFT], + '+' => ['precedence' => 30, 'associativity' => self::OPERATOR_LEFT], + '-' => ['precedence' => 30, 'associativity' => self::OPERATOR_LEFT], + '~' => ['precedence' => 40, 'associativity' => self::OPERATOR_LEFT], + '*' => ['precedence' => 60, 'associativity' => self::OPERATOR_LEFT], + '/' => ['precedence' => 60, 'associativity' => self::OPERATOR_LEFT], + '%' => ['precedence' => 60, 'associativity' => self::OPERATOR_LEFT], + '**' => ['precedence' => 200, 'associativity' => self::OPERATOR_RIGHT], + ]; + } + + /** + * Converts a token stream to a node tree. + * + * The valid names is an array where the values + * are the names that the user can use in an expression. + * + * If the variable name in the compiled PHP code must be + * different, define it as the key. + * + * For instance, ['this' => 'container'] means that the + * variable 'container' can be used in the expression + * but the compiled code will use 'this'. + * + * @throws SyntaxError + */ + public function parse(TokenStream $stream, array $names = []): Node\Node + { + $this->lint = false; + + return $this->doParse($stream, $names); + } + + /** + * Validates the syntax of an expression. + * + * The syntax of the passed expression will be checked, but not parsed. + * If you want to skip checking dynamic variable names, pass `null` instead of the array. + * + * @throws SyntaxError When the passed expression is invalid + */ + public function lint(TokenStream $stream, ?array $names = []): void + { + $this->lint = true; + $this->doParse($stream, $names); + } + + /** + * @throws SyntaxError + */ + private function doParse(TokenStream $stream, ?array $names = []): Node\Node + { + $this->stream = $stream; + $this->names = $names; + + $node = $this->parseExpression(); + if (!$stream->isEOF()) { + throw new SyntaxError(sprintf('Unexpected token "%s" of value "%s".', $stream->current->type, $stream->current->value), $stream->current->cursor, $stream->getExpression()); + } + + unset($this->stream, $this->names); + + return $node; + } + + /** + * @return Node\Node + */ + public function parseExpression(int $precedence = 0) + { + $expr = $this->getPrimary(); + $token = $this->stream->current; + while ($token->test(Token::OPERATOR_TYPE) && isset($this->binaryOperators[$token->value]) && $this->binaryOperators[$token->value]['precedence'] >= $precedence) { + $op = $this->binaryOperators[$token->value]; + $this->stream->next(); + + $expr1 = $this->parseExpression(self::OPERATOR_LEFT === $op['associativity'] ? $op['precedence'] + 1 : $op['precedence']); + $expr = new Node\BinaryNode($token->value, $expr, $expr1); + + $token = $this->stream->current; + } + + if (0 === $precedence) { + return $this->parseConditionalExpression($expr); + } + + return $expr; + } + + /** + * @return Node\Node + */ + protected function getPrimary() + { + $token = $this->stream->current; + + if ($token->test(Token::OPERATOR_TYPE) && isset($this->unaryOperators[$token->value])) { + $operator = $this->unaryOperators[$token->value]; + $this->stream->next(); + $expr = $this->parseExpression($operator['precedence']); + + return $this->parsePostfixExpression(new Node\UnaryNode($token->value, $expr)); + } + + if ($token->test(Token::PUNCTUATION_TYPE, '(')) { + $this->stream->next(); + $expr = $this->parseExpression(); + $this->stream->expect(Token::PUNCTUATION_TYPE, ')', 'An opened parenthesis is not properly closed'); + + return $this->parsePostfixExpression($expr); + } + + return $this->parsePrimaryExpression(); + } + + /** + * @return Node\Node + */ + protected function parseConditionalExpression(Node\Node $expr) + { + while ($this->stream->current->test(Token::PUNCTUATION_TYPE, '??')) { + $this->stream->next(); + $expr2 = $this->parseExpression(); + + $expr = new Node\NullCoalesceNode($expr, $expr2); + } + + while ($this->stream->current->test(Token::PUNCTUATION_TYPE, '?')) { + $this->stream->next(); + if (!$this->stream->current->test(Token::PUNCTUATION_TYPE, ':')) { + $expr2 = $this->parseExpression(); + if ($this->stream->current->test(Token::PUNCTUATION_TYPE, ':')) { + $this->stream->next(); + $expr3 = $this->parseExpression(); + } else { + $expr3 = new Node\ConstantNode(null); + } + } else { + $this->stream->next(); + $expr2 = $expr; + $expr3 = $this->parseExpression(); + } + + $expr = new Node\ConditionalNode($expr, $expr2, $expr3); + } + + return $expr; + } + + /** + * @return Node\Node + */ + public function parsePrimaryExpression() + { + $token = $this->stream->current; + switch ($token->type) { + case Token::NAME_TYPE: + $this->stream->next(); + switch ($token->value) { + case 'true': + case 'TRUE': + return new Node\ConstantNode(true); + + case 'false': + case 'FALSE': + return new Node\ConstantNode(false); + + case 'null': + case 'NULL': + return new Node\ConstantNode(null); + + default: + if ('(' === $this->stream->current->value) { + if (false === isset($this->functions[$token->value])) { + throw new SyntaxError(sprintf('The function "%s" does not exist.', $token->value), $token->cursor, $this->stream->getExpression(), $token->value, array_keys($this->functions)); + } + + $node = new Node\FunctionNode($token->value, $this->parseArguments()); + } else { + if (!$this->lint || \is_array($this->names)) { + if (!\in_array($token->value, $this->names, true)) { + throw new SyntaxError(sprintf('Variable "%s" is not valid.', $token->value), $token->cursor, $this->stream->getExpression(), $token->value, $this->names); + } + + // is the name used in the compiled code different + // from the name used in the expression? + if (\is_int($name = array_search($token->value, $this->names))) { + $name = $token->value; + } + } else { + $name = $token->value; + } + + $node = new Node\NameNode($name); + } + } + break; + + case Token::NUMBER_TYPE: + case Token::STRING_TYPE: + $this->stream->next(); + + return new Node\ConstantNode($token->value); + + default: + if ($token->test(Token::PUNCTUATION_TYPE, '[')) { + $node = $this->parseArrayExpression(); + } elseif ($token->test(Token::PUNCTUATION_TYPE, '{')) { + $node = $this->parseHashExpression(); + } else { + throw new SyntaxError(sprintf('Unexpected token "%s" of value "%s".', $token->type, $token->value), $token->cursor, $this->stream->getExpression()); + } + } + + return $this->parsePostfixExpression($node); + } + + /** + * @return Node\ArrayNode + */ + public function parseArrayExpression() + { + $this->stream->expect(Token::PUNCTUATION_TYPE, '[', 'An array element was expected'); + + $node = new Node\ArrayNode(); + $first = true; + while (!$this->stream->current->test(Token::PUNCTUATION_TYPE, ']')) { + if (!$first) { + $this->stream->expect(Token::PUNCTUATION_TYPE, ',', 'An array element must be followed by a comma'); + + // trailing ,? + if ($this->stream->current->test(Token::PUNCTUATION_TYPE, ']')) { + break; + } + } + $first = false; + + $node->addElement($this->parseExpression()); + } + $this->stream->expect(Token::PUNCTUATION_TYPE, ']', 'An opened array is not properly closed'); + + return $node; + } + + /** + * @return Node\ArrayNode + */ + public function parseHashExpression() + { + $this->stream->expect(Token::PUNCTUATION_TYPE, '{', 'A hash element was expected'); + + $node = new Node\ArrayNode(); + $first = true; + while (!$this->stream->current->test(Token::PUNCTUATION_TYPE, '}')) { + if (!$first) { + $this->stream->expect(Token::PUNCTUATION_TYPE, ',', 'A hash value must be followed by a comma'); + + // trailing ,? + if ($this->stream->current->test(Token::PUNCTUATION_TYPE, '}')) { + break; + } + } + $first = false; + + // a hash key can be: + // + // * a number -- 12 + // * a string -- 'a' + // * a name, which is equivalent to a string -- a + // * an expression, which must be enclosed in parentheses -- (1 + 2) + if ($this->stream->current->test(Token::STRING_TYPE) || $this->stream->current->test(Token::NAME_TYPE) || $this->stream->current->test(Token::NUMBER_TYPE)) { + $key = new Node\ConstantNode($this->stream->current->value); + $this->stream->next(); + } elseif ($this->stream->current->test(Token::PUNCTUATION_TYPE, '(')) { + $key = $this->parseExpression(); + } else { + $current = $this->stream->current; + + throw new SyntaxError(sprintf('A hash key must be a quoted string, a number, a name, or an expression enclosed in parentheses (unexpected token "%s" of value "%s".', $current->type, $current->value), $current->cursor, $this->stream->getExpression()); + } + + $this->stream->expect(Token::PUNCTUATION_TYPE, ':', 'A hash key must be followed by a colon (:)'); + $value = $this->parseExpression(); + + $node->addElement($value, $key); + } + $this->stream->expect(Token::PUNCTUATION_TYPE, '}', 'An opened hash is not properly closed'); + + return $node; + } + + /** + * @return Node\GetAttrNode|Node\Node + */ + public function parsePostfixExpression(Node\Node $node) + { + $token = $this->stream->current; + while (Token::PUNCTUATION_TYPE == $token->type) { + if ('.' === $token->value || '?.' === $token->value) { + $isNullSafe = '?.' === $token->value; + $this->stream->next(); + $token = $this->stream->current; + $this->stream->next(); + + if ( + Token::NAME_TYPE !== $token->type + // Operators like "not" and "matches" are valid method or property names, + // + // In other words, besides NAME_TYPE, OPERATOR_TYPE could also be parsed as a property or method. + // This is because operators are processed by the lexer prior to names. So "not" in "foo.not()" or "matches" in "foo.matches" will be recognized as an operator first. + // But in fact, "not" and "matches" in such expressions shall be parsed as method or property names. + // + // And this ONLY works if the operator consists of valid characters for a property or method name. + // + // Other types, such as STRING_TYPE and NUMBER_TYPE, can't be parsed as property nor method names. + // + // As a result, if $token is NOT an operator OR $token->value is NOT a valid property or method name, an exception shall be thrown. + && (Token::OPERATOR_TYPE !== $token->type || !preg_match('/[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*/A', $token->value)) + ) { + throw new SyntaxError('Expected name.', $token->cursor, $this->stream->getExpression()); + } + + $arg = new Node\ConstantNode($token->value, true, $isNullSafe); + + $arguments = new Node\ArgumentsNode(); + if ($this->stream->current->test(Token::PUNCTUATION_TYPE, '(')) { + $type = Node\GetAttrNode::METHOD_CALL; + foreach ($this->parseArguments()->nodes as $n) { + $arguments->addElement($n); + } + } else { + $type = Node\GetAttrNode::PROPERTY_CALL; + } + + $node = new Node\GetAttrNode($node, $arg, $arguments, $type); + } elseif ('[' === $token->value) { + $this->stream->next(); + $arg = $this->parseExpression(); + $this->stream->expect(Token::PUNCTUATION_TYPE, ']'); + + $node = new Node\GetAttrNode($node, $arg, new Node\ArgumentsNode(), Node\GetAttrNode::ARRAY_CALL); + } else { + break; + } + + $token = $this->stream->current; + } + + return $node; + } + + /** + * Parses arguments. + * + * @return Node\Node + */ + public function parseArguments() + { + $args = []; + $this->stream->expect(Token::PUNCTUATION_TYPE, '(', 'A list of arguments must begin with an opening parenthesis'); + while (!$this->stream->current->test(Token::PUNCTUATION_TYPE, ')')) { + if ($args) { + $this->stream->expect(Token::PUNCTUATION_TYPE, ',', 'Arguments must be separated by a comma'); + } + + $args[] = $this->parseExpression(); + } + $this->stream->expect(Token::PUNCTUATION_TYPE, ')', 'A list of arguments must be closed by a parenthesis'); + + return new Node\Node($args); + } +} diff --git a/vendor/symfony/expression-language/README.md b/vendor/symfony/expression-language/README.md new file mode 100644 index 0000000..2b2e3de --- /dev/null +++ b/vendor/symfony/expression-language/README.md @@ -0,0 +1,15 @@ +ExpressionLanguage Component +============================ + +The ExpressionLanguage component provides an engine that can compile and +evaluate expressions. An expression is a one-liner that returns a value +(mostly, but not limited to, Booleans). + +Resources +--------- + + * [Documentation](https://symfony.com/doc/current/components/expression_language/introduction.html) + * [Contributing](https://symfony.com/doc/current/contributing/index.html) + * [Report issues](https://github.com/symfony/symfony/issues) and + [send Pull Requests](https://github.com/symfony/symfony/pulls) + in the [main Symfony repository](https://github.com/symfony/symfony) diff --git a/vendor/symfony/expression-language/Resources/bin/generate_operator_regex.php b/vendor/symfony/expression-language/Resources/bin/generate_operator_regex.php new file mode 100644 index 0000000..179810e --- /dev/null +++ b/vendor/symfony/expression-language/Resources/bin/generate_operator_regex.php @@ -0,0 +1,31 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +if ('cli' !== \PHP_SAPI) { + throw new Exception('This script must be run from the command line.'); +} + +$operators = ['not', '!', 'or', '||', '&&', 'and', '|', '^', '&', '==', '===', '!=', '!==', '<', '>', '>=', '<=', 'not in', 'in', '..', '+', '-', '~', '*', '/', '%', 'contains', 'starts with', 'ends with', 'matches', '**']; +$operators = array_combine($operators, array_map('strlen', $operators)); +arsort($operators); + +$regex = []; +foreach ($operators as $operator => $length) { + // Collisions of character operators: + // - an operator that begins with a character must have a space or a parenthesis before or starting at the beginning of a string + // - an operator that ends with a character must be followed by a whitespace or a parenthesis + $regex[] = + (ctype_alpha($operator[0]) ? '(?<=^|[\s(])' : '') + .preg_quote($operator, '/') + .(ctype_alpha($operator[$length - 1]) ? '(?=[\s(])' : ''); +} + +echo '/'.implode('|', $regex).'/A'; diff --git a/vendor/symfony/expression-language/SerializedParsedExpression.php b/vendor/symfony/expression-language/SerializedParsedExpression.php new file mode 100644 index 0000000..5691907 --- /dev/null +++ b/vendor/symfony/expression-language/SerializedParsedExpression.php @@ -0,0 +1,42 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\ExpressionLanguage; + +use Symfony\Component\ExpressionLanguage\Node\Node; + +/** + * Represents an already parsed expression. + * + * @author Fabien Potencier + */ +class SerializedParsedExpression extends ParsedExpression +{ + private string $nodes; + + /** + * @param string $expression An expression + * @param string $nodes The serialized nodes for the expression + */ + public function __construct(string $expression, string $nodes) + { + $this->expression = $expression; + $this->nodes = $nodes; + } + + /** + * @return Node + */ + public function getNodes() + { + return unserialize($this->nodes); + } +} diff --git a/vendor/symfony/expression-language/SyntaxError.php b/vendor/symfony/expression-language/SyntaxError.php new file mode 100644 index 0000000..e165dc2 --- /dev/null +++ b/vendor/symfony/expression-language/SyntaxError.php @@ -0,0 +1,41 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\ExpressionLanguage; + +class SyntaxError extends \LogicException +{ + public function __construct(string $message, int $cursor = 0, string $expression = '', ?string $subject = null, ?array $proposals = null) + { + $message = sprintf('%s around position %d', rtrim($message, '.'), $cursor); + if ($expression) { + $message = sprintf('%s for expression `%s`', $message, $expression); + } + $message .= '.'; + + if (null !== $subject && null !== $proposals) { + $minScore = \INF; + foreach ($proposals as $proposal) { + $distance = levenshtein($subject, $proposal); + if ($distance < $minScore) { + $guess = $proposal; + $minScore = $distance; + } + } + + if (isset($guess) && $minScore < 3) { + $message .= sprintf(' Did you mean "%s"?', $guess); + } + } + + parent::__construct($message); + } +} diff --git a/vendor/symfony/expression-language/Token.php b/vendor/symfony/expression-language/Token.php new file mode 100644 index 0000000..99f721f --- /dev/null +++ b/vendor/symfony/expression-language/Token.php @@ -0,0 +1,58 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\ExpressionLanguage; + +/** + * Represents a Token. + * + * @author Fabien Potencier + */ +class Token +{ + public $value; + public $type; + public $cursor; + + public const EOF_TYPE = 'end of expression'; + public const NAME_TYPE = 'name'; + public const NUMBER_TYPE = 'number'; + public const STRING_TYPE = 'string'; + public const OPERATOR_TYPE = 'operator'; + public const PUNCTUATION_TYPE = 'punctuation'; + + /** + * @param self::*_TYPE $type + * @param int|null $cursor The cursor position in the source + */ + public function __construct(string $type, string|int|float|null $value, ?int $cursor) + { + $this->type = $type; + $this->value = $value; + $this->cursor = $cursor; + } + + /** + * Returns a string representation of the token. + */ + public function __toString(): string + { + return sprintf('%3d %-11s %s', $this->cursor, strtoupper($this->type), $this->value); + } + + /** + * Tests the current token for a type and/or a value. + */ + public function test(string $type, ?string $value = null): bool + { + return $this->type === $type && (null === $value || $this->value == $value); + } +} diff --git a/vendor/symfony/expression-language/TokenStream.php b/vendor/symfony/expression-language/TokenStream.php new file mode 100644 index 0000000..9512a10 --- /dev/null +++ b/vendor/symfony/expression-language/TokenStream.php @@ -0,0 +1,87 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\ExpressionLanguage; + +/** + * Represents a token stream. + * + * @author Fabien Potencier + */ +class TokenStream +{ + public $current; + + private array $tokens; + private int $position = 0; + private string $expression; + + public function __construct(array $tokens, string $expression = '') + { + $this->tokens = $tokens; + $this->current = $tokens[0]; + $this->expression = $expression; + } + + /** + * Returns a string representation of the token stream. + */ + public function __toString(): string + { + return implode("\n", $this->tokens); + } + + /** + * Sets the pointer to the next token and returns the old one. + * + * @return void + */ + public function next() + { + ++$this->position; + + if (!isset($this->tokens[$this->position])) { + throw new SyntaxError('Unexpected end of expression.', $this->current->cursor, $this->expression); + } + + $this->current = $this->tokens[$this->position]; + } + + /** + * @param string|null $message The syntax error message + * + * @return void + */ + public function expect(string $type, ?string $value = null, ?string $message = null) + { + $token = $this->current; + if (!$token->test($type, $value)) { + throw new SyntaxError(sprintf('%sUnexpected token "%s" of value "%s" ("%s" expected%s).', $message ? $message.'. ' : '', $token->type, $token->value, $type, $value ? sprintf(' with value "%s"', $value) : ''), $token->cursor, $this->expression); + } + $this->next(); + } + + /** + * Checks if end of stream was reached. + */ + public function isEOF(): bool + { + return Token::EOF_TYPE === $this->current->type; + } + + /** + * @internal + */ + public function getExpression(): string + { + return $this->expression; + } +} diff --git a/vendor/symfony/expression-language/composer.json b/vendor/symfony/expression-language/composer.json new file mode 100644 index 0000000..b123a5c --- /dev/null +++ b/vendor/symfony/expression-language/composer.json @@ -0,0 +1,31 @@ +{ + "name": "symfony/expression-language", + "type": "library", + "description": "Provides an engine that can compile and evaluate expressions", + "keywords": [], + "homepage": "https://symfony.com", + "license": "MIT", + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "require": { + "php": ">=8.1", + "symfony/deprecation-contracts": "^2.5|^3", + "symfony/cache": "^5.4|^6.0|^7.0", + "symfony/service-contracts": "^2.5|^3" + }, + "autoload": { + "psr-4": { "Symfony\\Component\\ExpressionLanguage\\": "" }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "minimum-stability": "dev" +} diff --git a/vendor/symfony/service-contracts/Attribute/Required.php b/vendor/symfony/service-contracts/Attribute/Required.php new file mode 100644 index 0000000..9df8511 --- /dev/null +++ b/vendor/symfony/service-contracts/Attribute/Required.php @@ -0,0 +1,25 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Contracts\Service\Attribute; + +/** + * A required dependency. + * + * This attribute indicates that a property holds a required dependency. The annotated property or method should be + * considered during the instantiation process of the containing class. + * + * @author Alexander M. Turek + */ +#[\Attribute(\Attribute::TARGET_METHOD | \Attribute::TARGET_PROPERTY)] +final class Required +{ +} diff --git a/vendor/symfony/service-contracts/Attribute/SubscribedService.php b/vendor/symfony/service-contracts/Attribute/SubscribedService.php new file mode 100644 index 0000000..f850b84 --- /dev/null +++ b/vendor/symfony/service-contracts/Attribute/SubscribedService.php @@ -0,0 +1,47 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Contracts\Service\Attribute; + +use Symfony\Contracts\Service\ServiceMethodsSubscriberTrait; +use Symfony\Contracts\Service\ServiceSubscriberInterface; + +/** + * For use as the return value for {@see ServiceSubscriberInterface}. + * + * @example new SubscribedService('http_client', HttpClientInterface::class, false, new Target('githubApi')) + * + * Use with {@see ServiceMethodsSubscriberTrait} to mark a method's return type + * as a subscribed service. + * + * @author Kevin Bond + */ +#[\Attribute(\Attribute::TARGET_METHOD)] +final class SubscribedService +{ + /** @var object[] */ + public array $attributes; + + /** + * @param string|null $key The key to use for the service + * @param class-string|null $type The service class + * @param bool $nullable Whether the service is optional + * @param object|object[] $attributes One or more dependency injection attributes to use + */ + public function __construct( + public ?string $key = null, + public ?string $type = null, + public bool $nullable = false, + array|object $attributes = [], + ) { + $this->attributes = \is_array($attributes) ? $attributes : [$attributes]; + } +} diff --git a/vendor/symfony/service-contracts/CHANGELOG.md b/vendor/symfony/service-contracts/CHANGELOG.md new file mode 100644 index 0000000..7932e26 --- /dev/null +++ b/vendor/symfony/service-contracts/CHANGELOG.md @@ -0,0 +1,5 @@ +CHANGELOG +========= + +The changelog is maintained for all Symfony contracts at the following URL: +https://github.com/symfony/contracts/blob/main/CHANGELOG.md diff --git a/vendor/symfony/service-contracts/LICENSE b/vendor/symfony/service-contracts/LICENSE new file mode 100644 index 0000000..7536cae --- /dev/null +++ b/vendor/symfony/service-contracts/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2018-present Fabien Potencier + +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/vendor/symfony/service-contracts/README.md b/vendor/symfony/service-contracts/README.md new file mode 100644 index 0000000..42841a5 --- /dev/null +++ b/vendor/symfony/service-contracts/README.md @@ -0,0 +1,9 @@ +Symfony Service Contracts +========================= + +A set of abstractions extracted out of the Symfony components. + +Can be used to build on semantics that the Symfony components proved useful and +that already have battle tested implementations. + +See https://github.com/symfony/contracts/blob/main/README.md for more information. diff --git a/vendor/symfony/service-contracts/ResetInterface.php b/vendor/symfony/service-contracts/ResetInterface.php new file mode 100644 index 0000000..a4f389b --- /dev/null +++ b/vendor/symfony/service-contracts/ResetInterface.php @@ -0,0 +1,33 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Contracts\Service; + +/** + * Provides a way to reset an object to its initial state. + * + * When calling the "reset()" method on an object, it should be put back to its + * initial state. This usually means clearing any internal buffers and forwarding + * the call to internal dependencies. All properties of the object should be put + * back to the same state it had when it was first ready to use. + * + * This method could be called, for example, to recycle objects that are used as + * services, so that they can be used to handle several requests in the same + * process loop (note that we advise making your services stateless instead of + * implementing this interface when possible.) + */ +interface ResetInterface +{ + /** + * @return void + */ + public function reset(); +} diff --git a/vendor/symfony/service-contracts/ServiceCollectionInterface.php b/vendor/symfony/service-contracts/ServiceCollectionInterface.php new file mode 100644 index 0000000..2333139 --- /dev/null +++ b/vendor/symfony/service-contracts/ServiceCollectionInterface.php @@ -0,0 +1,26 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Contracts\Service; + +/** + * A ServiceProviderInterface that is also countable and iterable. + * + * @author Kevin Bond + * + * @template-covariant T of mixed + * + * @extends ServiceProviderInterface + * @extends \IteratorAggregate + */ +interface ServiceCollectionInterface extends ServiceProviderInterface, \Countable, \IteratorAggregate +{ +} diff --git a/vendor/symfony/service-contracts/ServiceLocatorTrait.php b/vendor/symfony/service-contracts/ServiceLocatorTrait.php new file mode 100644 index 0000000..b62ec3e --- /dev/null +++ b/vendor/symfony/service-contracts/ServiceLocatorTrait.php @@ -0,0 +1,115 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Contracts\Service; + +use Psr\Container\ContainerExceptionInterface; +use Psr\Container\NotFoundExceptionInterface; + +// Help opcache.preload discover always-needed symbols +class_exists(ContainerExceptionInterface::class); +class_exists(NotFoundExceptionInterface::class); + +/** + * A trait to help implement ServiceProviderInterface. + * + * @author Robin Chalas + * @author Nicolas Grekas + */ +trait ServiceLocatorTrait +{ + private array $factories; + private array $loading = []; + private array $providedTypes; + + /** + * @param array $factories + */ + public function __construct(array $factories) + { + $this->factories = $factories; + } + + public function has(string $id): bool + { + return isset($this->factories[$id]); + } + + public function get(string $id): mixed + { + if (!isset($this->factories[$id])) { + throw $this->createNotFoundException($id); + } + + if (isset($this->loading[$id])) { + $ids = array_values($this->loading); + $ids = \array_slice($this->loading, array_search($id, $ids)); + $ids[] = $id; + + throw $this->createCircularReferenceException($id, $ids); + } + + $this->loading[$id] = $id; + try { + return $this->factories[$id]($this); + } finally { + unset($this->loading[$id]); + } + } + + public function getProvidedServices(): array + { + if (!isset($this->providedTypes)) { + $this->providedTypes = []; + + foreach ($this->factories as $name => $factory) { + if (!\is_callable($factory)) { + $this->providedTypes[$name] = '?'; + } else { + $type = (new \ReflectionFunction($factory))->getReturnType(); + + $this->providedTypes[$name] = $type ? ($type->allowsNull() ? '?' : '').($type instanceof \ReflectionNamedType ? $type->getName() : $type) : '?'; + } + } + } + + return $this->providedTypes; + } + + private function createNotFoundException(string $id): NotFoundExceptionInterface + { + if (!$alternatives = array_keys($this->factories)) { + $message = 'is empty...'; + } else { + $last = array_pop($alternatives); + if ($alternatives) { + $message = sprintf('only knows about the "%s" and "%s" services.', implode('", "', $alternatives), $last); + } else { + $message = sprintf('only knows about the "%s" service.', $last); + } + } + + if ($this->loading) { + $message = sprintf('The service "%s" has a dependency on a non-existent service "%s". This locator %s', end($this->loading), $id, $message); + } else { + $message = sprintf('Service "%s" not found: the current service locator %s', $id, $message); + } + + return new class($message) extends \InvalidArgumentException implements NotFoundExceptionInterface { + }; + } + + private function createCircularReferenceException(string $id, array $path): ContainerExceptionInterface + { + return new class(sprintf('Circular reference detected for service "%s", path: "%s".', $id, implode(' -> ', $path))) extends \RuntimeException implements ContainerExceptionInterface { + }; + } +} diff --git a/vendor/symfony/service-contracts/ServiceMethodsSubscriberTrait.php b/vendor/symfony/service-contracts/ServiceMethodsSubscriberTrait.php new file mode 100644 index 0000000..0d89d9f --- /dev/null +++ b/vendor/symfony/service-contracts/ServiceMethodsSubscriberTrait.php @@ -0,0 +1,80 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Contracts\Service; + +use Psr\Container\ContainerInterface; +use Symfony\Contracts\Service\Attribute\Required; +use Symfony\Contracts\Service\Attribute\SubscribedService; + +/** + * Implementation of ServiceSubscriberInterface that determines subscribed services + * from methods that have the #[SubscribedService] attribute. + * + * Service ids are available as "ClassName::methodName" so that the implementation + * of subscriber methods can be just `return $this->container->get(__METHOD__);`. + * + * @author Kevin Bond + */ +trait ServiceMethodsSubscriberTrait +{ + protected ContainerInterface $container; + + public static function getSubscribedServices(): array + { + $services = method_exists(get_parent_class(self::class) ?: '', __FUNCTION__) ? parent::getSubscribedServices() : []; + + foreach ((new \ReflectionClass(self::class))->getMethods() as $method) { + if (self::class !== $method->getDeclaringClass()->name) { + continue; + } + + if (!$attribute = $method->getAttributes(SubscribedService::class)[0] ?? null) { + continue; + } + + if ($method->isStatic() || $method->isAbstract() || $method->isGenerator() || $method->isInternal() || $method->getNumberOfRequiredParameters()) { + throw new \LogicException(sprintf('Cannot use "%s" on method "%s::%s()" (can only be used on non-static, non-abstract methods with no parameters).', SubscribedService::class, self::class, $method->name)); + } + + if (!$returnType = $method->getReturnType()) { + throw new \LogicException(sprintf('Cannot use "%s" on methods without a return type in "%s::%s()".', SubscribedService::class, $method->name, self::class)); + } + + /* @var SubscribedService $attribute */ + $attribute = $attribute->newInstance(); + $attribute->key ??= self::class.'::'.$method->name; + $attribute->type ??= $returnType instanceof \ReflectionNamedType ? $returnType->getName() : (string) $returnType; + $attribute->nullable = $returnType->allowsNull(); + + if ($attribute->attributes) { + $services[] = $attribute; + } else { + $services[$attribute->key] = ($attribute->nullable ? '?' : '').$attribute->type; + } + } + + return $services; + } + + #[Required] + public function setContainer(ContainerInterface $container): ?ContainerInterface + { + $ret = null; + if (method_exists(get_parent_class(self::class) ?: '', __FUNCTION__)) { + $ret = parent::setContainer($container); + } + + $this->container = $container; + + return $ret; + } +} diff --git a/vendor/symfony/service-contracts/ServiceProviderInterface.php b/vendor/symfony/service-contracts/ServiceProviderInterface.php new file mode 100644 index 0000000..2e71f00 --- /dev/null +++ b/vendor/symfony/service-contracts/ServiceProviderInterface.php @@ -0,0 +1,45 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Contracts\Service; + +use Psr\Container\ContainerInterface; + +/** + * A ServiceProviderInterface exposes the identifiers and the types of services provided by a container. + * + * @author Nicolas Grekas + * @author Mateusz Sip + * + * @template-covariant T of mixed + */ +interface ServiceProviderInterface extends ContainerInterface +{ + /** + * @return T + */ + public function get(string $id): mixed; + + public function has(string $id): bool; + + /** + * Returns an associative array of service types keyed by the identifiers provided by the current container. + * + * Examples: + * + * * ['logger' => 'Psr\Log\LoggerInterface'] means the object provides a service named "logger" that implements Psr\Log\LoggerInterface + * * ['foo' => '?'] means the container provides service name "foo" of unspecified type + * * ['bar' => '?Bar\Baz'] means the container provides a service "bar" of type Bar\Baz|null + * + * @return array The provided service types, keyed by service names + */ + public function getProvidedServices(): array; +} diff --git a/vendor/symfony/service-contracts/ServiceSubscriberInterface.php b/vendor/symfony/service-contracts/ServiceSubscriberInterface.php new file mode 100644 index 0000000..3da1916 --- /dev/null +++ b/vendor/symfony/service-contracts/ServiceSubscriberInterface.php @@ -0,0 +1,62 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Contracts\Service; + +use Symfony\Contracts\Service\Attribute\SubscribedService; + +/** + * A ServiceSubscriber exposes its dependencies via the static {@link getSubscribedServices} method. + * + * The getSubscribedServices method returns an array of service types required by such instances, + * optionally keyed by the service names used internally. Service types that start with an interrogation + * mark "?" are optional, while the other ones are mandatory service dependencies. + * + * The injected service locators SHOULD NOT allow access to any other services not specified by the method. + * + * It is expected that ServiceSubscriber instances consume PSR-11-based service locators internally. + * This interface does not dictate any injection method for these service locators, although constructor + * injection is recommended. + * + * @author Nicolas Grekas + */ +interface ServiceSubscriberInterface +{ + /** + * Returns an array of service types (or {@see SubscribedService} objects) required + * by such instances, optionally keyed by the service names used internally. + * + * For mandatory dependencies: + * + * * ['logger' => 'Psr\Log\LoggerInterface'] means the objects use the "logger" name + * internally to fetch a service which must implement Psr\Log\LoggerInterface. + * * ['loggers' => 'Psr\Log\LoggerInterface[]'] means the objects use the "loggers" name + * internally to fetch an iterable of Psr\Log\LoggerInterface instances. + * * ['Psr\Log\LoggerInterface'] is a shortcut for + * * ['Psr\Log\LoggerInterface' => 'Psr\Log\LoggerInterface'] + * + * otherwise: + * + * * ['logger' => '?Psr\Log\LoggerInterface'] denotes an optional dependency + * * ['loggers' => '?Psr\Log\LoggerInterface[]'] denotes an optional iterable dependency + * * ['?Psr\Log\LoggerInterface'] is a shortcut for + * * ['Psr\Log\LoggerInterface' => '?Psr\Log\LoggerInterface'] + * + * additionally, an array of {@see SubscribedService}'s can be returned: + * + * * [new SubscribedService('logger', Psr\Log\LoggerInterface::class)] + * * [new SubscribedService(type: Psr\Log\LoggerInterface::class, nullable: true)] + * * [new SubscribedService('http_client', HttpClientInterface::class, attributes: new Target('githubApi'))] + * + * @return string[]|SubscribedService[] The required service types, optionally keyed by service names + */ + public static function getSubscribedServices(): array; +} diff --git a/vendor/symfony/service-contracts/ServiceSubscriberTrait.php b/vendor/symfony/service-contracts/ServiceSubscriberTrait.php new file mode 100644 index 0000000..cc3bc32 --- /dev/null +++ b/vendor/symfony/service-contracts/ServiceSubscriberTrait.php @@ -0,0 +1,84 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Contracts\Service; + +use Psr\Container\ContainerInterface; +use Symfony\Contracts\Service\Attribute\Required; +use Symfony\Contracts\Service\Attribute\SubscribedService; + +trigger_deprecation('symfony/contracts', 'v3.5', '"%s" is deprecated, use "ServiceMethodsSubscriberTrait" instead.', ServiceSubscriberTrait::class); + +/** + * Implementation of ServiceSubscriberInterface that determines subscribed services + * from methods that have the #[SubscribedService] attribute. + * + * Service ids are available as "ClassName::methodName" so that the implementation + * of subscriber methods can be just `return $this->container->get(__METHOD__);`. + * + * @property ContainerInterface $container + * + * @author Kevin Bond + * + * @deprecated since symfony/contracts v3.5, use ServiceMethodsSubscriberTrait instead + */ +trait ServiceSubscriberTrait +{ + public static function getSubscribedServices(): array + { + $services = method_exists(get_parent_class(self::class) ?: '', __FUNCTION__) ? parent::getSubscribedServices() : []; + + foreach ((new \ReflectionClass(self::class))->getMethods() as $method) { + if (self::class !== $method->getDeclaringClass()->name) { + continue; + } + + if (!$attribute = $method->getAttributes(SubscribedService::class)[0] ?? null) { + continue; + } + + if ($method->isStatic() || $method->isAbstract() || $method->isGenerator() || $method->isInternal() || $method->getNumberOfRequiredParameters()) { + throw new \LogicException(sprintf('Cannot use "%s" on method "%s::%s()" (can only be used on non-static, non-abstract methods with no parameters).', SubscribedService::class, self::class, $method->name)); + } + + if (!$returnType = $method->getReturnType()) { + throw new \LogicException(sprintf('Cannot use "%s" on methods without a return type in "%s::%s()".', SubscribedService::class, $method->name, self::class)); + } + + /* @var SubscribedService $attribute */ + $attribute = $attribute->newInstance(); + $attribute->key ??= self::class.'::'.$method->name; + $attribute->type ??= $returnType instanceof \ReflectionNamedType ? $returnType->getName() : (string) $returnType; + $attribute->nullable = $returnType->allowsNull(); + + if ($attribute->attributes) { + $services[] = $attribute; + } else { + $services[$attribute->key] = ($attribute->nullable ? '?' : '').$attribute->type; + } + } + + return $services; + } + + #[Required] + public function setContainer(ContainerInterface $container): ?ContainerInterface + { + $ret = null; + if (method_exists(get_parent_class(self::class) ?: '', __FUNCTION__)) { + $ret = parent::setContainer($container); + } + + $this->container = $container; + + return $ret; + } +} diff --git a/vendor/symfony/service-contracts/Test/ServiceLocatorTest.php b/vendor/symfony/service-contracts/Test/ServiceLocatorTest.php new file mode 100644 index 0000000..07d12b4 --- /dev/null +++ b/vendor/symfony/service-contracts/Test/ServiceLocatorTest.php @@ -0,0 +1,23 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Contracts\Service\Test; + +class_alias(ServiceLocatorTestCase::class, ServiceLocatorTest::class); + +if (false) { + /** + * @deprecated since PHPUnit 9.6 + */ + class ServiceLocatorTest + { + } +} diff --git a/vendor/symfony/service-contracts/Test/ServiceLocatorTestCase.php b/vendor/symfony/service-contracts/Test/ServiceLocatorTestCase.php new file mode 100644 index 0000000..65a3fe3 --- /dev/null +++ b/vendor/symfony/service-contracts/Test/ServiceLocatorTestCase.php @@ -0,0 +1,96 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Contracts\Service\Test; + +use PHPUnit\Framework\TestCase; +use Psr\Container\ContainerExceptionInterface; +use Psr\Container\ContainerInterface; +use Psr\Container\NotFoundExceptionInterface; +use Symfony\Contracts\Service\ServiceLocatorTrait; + +abstract class ServiceLocatorTestCase extends TestCase +{ + protected function getServiceLocator(array $factories): ContainerInterface + { + return new class($factories) implements ContainerInterface { + use ServiceLocatorTrait; + }; + } + + public function testHas() + { + $locator = $this->getServiceLocator([ + 'foo' => fn () => 'bar', + 'bar' => fn () => 'baz', + fn () => 'dummy', + ]); + + $this->assertTrue($locator->has('foo')); + $this->assertTrue($locator->has('bar')); + $this->assertFalse($locator->has('dummy')); + } + + public function testGet() + { + $locator = $this->getServiceLocator([ + 'foo' => fn () => 'bar', + 'bar' => fn () => 'baz', + ]); + + $this->assertSame('bar', $locator->get('foo')); + $this->assertSame('baz', $locator->get('bar')); + } + + public function testGetDoesNotMemoize() + { + $i = 0; + $locator = $this->getServiceLocator([ + 'foo' => function () use (&$i) { + ++$i; + + return 'bar'; + }, + ]); + + $this->assertSame('bar', $locator->get('foo')); + $this->assertSame('bar', $locator->get('foo')); + $this->assertSame(2, $i); + } + + public function testThrowsOnUndefinedInternalService() + { + $locator = $this->getServiceLocator([ + 'foo' => function () use (&$locator) { return $locator->get('bar'); }, + ]); + + if (!$this->getExpectedException()) { + $this->expectException(NotFoundExceptionInterface::class); + $this->expectExceptionMessage('The service "foo" has a dependency on a non-existent service "bar". This locator only knows about the "foo" service.'); + } + + $locator->get('foo'); + } + + public function testThrowsOnCircularReference() + { + $locator = $this->getServiceLocator([ + 'foo' => function () use (&$locator) { return $locator->get('bar'); }, + 'bar' => function () use (&$locator) { return $locator->get('baz'); }, + 'baz' => function () use (&$locator) { return $locator->get('bar'); }, + ]); + + $this->expectException(ContainerExceptionInterface::class); + $this->expectExceptionMessage('Circular reference detected for service "bar", path: "bar -> baz -> bar".'); + + $locator->get('foo'); + } +} diff --git a/vendor/symfony/service-contracts/composer.json b/vendor/symfony/service-contracts/composer.json new file mode 100644 index 0000000..fc8674a --- /dev/null +++ b/vendor/symfony/service-contracts/composer.json @@ -0,0 +1,42 @@ +{ + "name": "symfony/service-contracts", + "type": "library", + "description": "Generic abstractions related to writing services", + "keywords": ["abstractions", "contracts", "decoupling", "interfaces", "interoperability", "standards"], + "homepage": "https://symfony.com", + "license": "MIT", + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "require": { + "php": ">=8.1", + "psr/container": "^1.1|^2.0", + "symfony/deprecation-contracts": "^2.5|^3" + }, + "conflict": { + "ext-psr": "<1.1|>=2" + }, + "autoload": { + "psr-4": { "Symfony\\Contracts\\Service\\": "" }, + "exclude-from-classmap": [ + "/Test/" + ] + }, + "minimum-stability": "dev", + "extra": { + "branch-alias": { + "dev-main": "3.5-dev" + }, + "thanks": { + "name": "symfony/contracts", + "url": "https://github.com/symfony/contracts" + } + } +} diff --git a/vendor/symfony/var-exporter/CHANGELOG.md b/vendor/symfony/var-exporter/CHANGELOG.md new file mode 100644 index 0000000..74333ea --- /dev/null +++ b/vendor/symfony/var-exporter/CHANGELOG.md @@ -0,0 +1,30 @@ +CHANGELOG +========= + +7.2 +--- + + * Allow reinitializing lazy objects with a new initializer + +6.4 +--- + + * Deprecate per-property lazy-initializers + +6.2 +--- + + * Add support for lazy ghost objects and virtual proxies + * Add `Hydrator::hydrate()` + * Preserve PHP references also when using `Hydrator::hydrate()` or `Instantiator::instantiate()` + * Add support for hydrating from native (array) casts + +5.1.0 +----- + + * added argument `array &$foundClasses` to `VarExporter::export()` to ease with preloading exported values + +4.2.0 +----- + + * added the component diff --git a/vendor/symfony/var-exporter/Exception/ClassNotFoundException.php b/vendor/symfony/var-exporter/Exception/ClassNotFoundException.php new file mode 100644 index 0000000..2acecc4 --- /dev/null +++ b/vendor/symfony/var-exporter/Exception/ClassNotFoundException.php @@ -0,0 +1,20 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarExporter\Exception; + +class ClassNotFoundException extends \Exception implements ExceptionInterface +{ + public function __construct(string $class, ?\Throwable $previous = null) + { + parent::__construct(\sprintf('Class "%s" not found.', $class), 0, $previous); + } +} diff --git a/vendor/symfony/var-exporter/Exception/ExceptionInterface.php b/vendor/symfony/var-exporter/Exception/ExceptionInterface.php new file mode 100644 index 0000000..adfaed4 --- /dev/null +++ b/vendor/symfony/var-exporter/Exception/ExceptionInterface.php @@ -0,0 +1,16 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarExporter\Exception; + +interface ExceptionInterface extends \Throwable +{ +} diff --git a/vendor/symfony/var-exporter/Exception/LogicException.php b/vendor/symfony/var-exporter/Exception/LogicException.php new file mode 100644 index 0000000..619d055 --- /dev/null +++ b/vendor/symfony/var-exporter/Exception/LogicException.php @@ -0,0 +1,16 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarExporter\Exception; + +class LogicException extends \LogicException implements ExceptionInterface +{ +} diff --git a/vendor/symfony/var-exporter/Exception/NotInstantiableTypeException.php b/vendor/symfony/var-exporter/Exception/NotInstantiableTypeException.php new file mode 100644 index 0000000..bc2bcaa --- /dev/null +++ b/vendor/symfony/var-exporter/Exception/NotInstantiableTypeException.php @@ -0,0 +1,20 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarExporter\Exception; + +class NotInstantiableTypeException extends \Exception implements ExceptionInterface +{ + public function __construct(string $type, ?\Throwable $previous = null) + { + parent::__construct(\sprintf('Type "%s" is not instantiable.', $type), 0, $previous); + } +} diff --git a/vendor/symfony/var-exporter/Hydrator.php b/vendor/symfony/var-exporter/Hydrator.php new file mode 100644 index 0000000..5f456fb --- /dev/null +++ b/vendor/symfony/var-exporter/Hydrator.php @@ -0,0 +1,78 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarExporter; + +use Symfony\Component\VarExporter\Internal\Hydrator as InternalHydrator; + +/** + * Utility class to hydrate the properties of an object. + * + * @author Nicolas Grekas + */ +final class Hydrator +{ + /** + * Sets the properties of an object, including private and protected ones. + * + * For example: + * + * // Sets the public or protected $object->propertyName property + * Hydrator::hydrate($object, ['propertyName' => $propertyValue]); + * + * // Sets a private property defined on its parent Bar class: + * Hydrator::hydrate($object, ["\0Bar\0privateBarProperty" => $propertyValue]); + * + * // Alternative way to set the private $object->privateBarProperty property + * Hydrator::hydrate($object, [], [ + * Bar::class => ['privateBarProperty' => $propertyValue], + * ]); + * + * Instances of ArrayObject, ArrayIterator and SplObjectStorage can be hydrated + * by using the special "\0" property name to define their internal value: + * + * // Hydrates an SplObjectStorage where $info1 is attached to $obj1, etc. + * Hydrator::hydrate($object, ["\0" => [$obj1, $info1, $obj2, $info2...]]); + * + * // Hydrates an ArrayObject populated with $inputArray + * Hydrator::hydrate($object, ["\0" => [$inputArray]]); + * + * @template T of object + * + * @param T $instance The object to hydrate + * @param array $properties The properties to set on the instance + * @param array> $scopedProperties The properties to set on the instance, + * keyed by their declaring class + * + * @return T + */ + public static function hydrate(object $instance, array $properties = [], array $scopedProperties = []): object + { + if ($properties) { + $class = $instance::class; + $propertyScopes = InternalHydrator::$propertyScopes[$class] ??= InternalHydrator::getPropertyScopes($class); + + foreach ($properties as $name => &$value) { + [$scope, $name, $readonlyScope] = $propertyScopes[$name] ?? [$class, $name, $class]; + $scopedProperties[$readonlyScope ?? $scope][$name] = &$value; + } + unset($value); + } + + foreach ($scopedProperties as $scope => $properties) { + if ($properties) { + (InternalHydrator::$simpleHydrators[$scope] ??= InternalHydrator::getSimpleHydrator($scope))($properties, $instance); + } + } + + return $instance; + } +} diff --git a/vendor/symfony/var-exporter/Instantiator.php b/vendor/symfony/var-exporter/Instantiator.php new file mode 100644 index 0000000..10200c0 --- /dev/null +++ b/vendor/symfony/var-exporter/Instantiator.php @@ -0,0 +1,59 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarExporter; + +use Symfony\Component\VarExporter\Exception\ExceptionInterface; +use Symfony\Component\VarExporter\Exception\NotInstantiableTypeException; +use Symfony\Component\VarExporter\Internal\Registry; + +/** + * A utility class to create objects without calling their constructor. + * + * @author Nicolas Grekas + */ +final class Instantiator +{ + /** + * Creates an object and sets its properties without calling its constructor nor any other methods. + * + * @see Hydrator::hydrate() for examples + * + * @template T of object + * + * @param class-string $class The class of the instance to create + * @param array $properties The properties to set on the instance + * @param array> $scopedProperties The properties to set on the instance, + * keyed by their declaring class + * + * @return T + * + * @throws ExceptionInterface When the instance cannot be created + */ + public static function instantiate(string $class, array $properties = [], array $scopedProperties = []): object + { + $reflector = Registry::$reflectors[$class] ??= Registry::getClassReflector($class); + + if (Registry::$cloneable[$class]) { + $instance = clone Registry::$prototypes[$class]; + } elseif (Registry::$instantiableWithoutConstructor[$class]) { + $instance = $reflector->newInstanceWithoutConstructor(); + } elseif (null === Registry::$prototypes[$class]) { + throw new NotInstantiableTypeException($class); + } elseif ($reflector->implementsInterface('Serializable') && !method_exists($class, '__unserialize')) { + $instance = unserialize('C:'.\strlen($class).':"'.$class.'":0:{}'); + } else { + $instance = unserialize('O:'.\strlen($class).':"'.$class.'":0:{}'); + } + + return $properties || $scopedProperties ? Hydrator::hydrate($instance, $properties, $scopedProperties) : $instance; + } +} diff --git a/vendor/symfony/var-exporter/Internal/Exporter.php b/vendor/symfony/var-exporter/Internal/Exporter.php new file mode 100644 index 0000000..6544ae2 --- /dev/null +++ b/vendor/symfony/var-exporter/Internal/Exporter.php @@ -0,0 +1,418 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarExporter\Internal; + +use Symfony\Component\VarExporter\Exception\NotInstantiableTypeException; + +/** + * @author Nicolas Grekas + * + * @internal + */ +class Exporter +{ + /** + * Prepares an array of values for VarExporter. + * + * For performance this method is public and has no type-hints. + * + * @param array &$values + * @param \SplObjectStorage $objectsPool + * @param array &$refsPool + * @param int &$objectsCount + * @param bool &$valuesAreStatic + * + * @return array + * + * @throws NotInstantiableTypeException When a value cannot be serialized + */ + public static function prepare($values, $objectsPool, &$refsPool, &$objectsCount, &$valuesAreStatic) + { + $refs = $values; + foreach ($values as $k => $value) { + if (\is_resource($value)) { + throw new NotInstantiableTypeException(get_resource_type($value).' resource'); + } + $refs[$k] = $objectsPool; + + if ($isRef = !$valueIsStatic = $values[$k] !== $objectsPool) { + $values[$k] = &$value; // Break hard references to make $values completely + unset($value); // independent from the original structure + $refs[$k] = $value = $values[$k]; + if ($value instanceof Reference && 0 > $value->id) { + $valuesAreStatic = false; + ++$value->count; + continue; + } + $refsPool[] = [&$refs[$k], $value, &$value]; + $refs[$k] = $values[$k] = new Reference(-\count($refsPool), $value); + } + + if (\is_array($value)) { + if ($value) { + $value = self::prepare($value, $objectsPool, $refsPool, $objectsCount, $valueIsStatic); + } + goto handle_value; + } elseif (!\is_object($value) || $value instanceof \UnitEnum) { + goto handle_value; + } + + $valueIsStatic = false; + if (isset($objectsPool[$value])) { + ++$objectsCount; + $value = new Reference($objectsPool[$value][0]); + goto handle_value; + } + + $class = $value::class; + $reflector = Registry::$reflectors[$class] ??= Registry::getClassReflector($class); + $properties = []; + + if ($reflector->hasMethod('__serialize')) { + if (!$reflector->getMethod('__serialize')->isPublic()) { + throw new \Error(\sprintf('Call to %s method "%s::__serialize()".', $reflector->getMethod('__serialize')->isProtected() ? 'protected' : 'private', $class)); + } + + if (!\is_array($serializeProperties = $value->__serialize())) { + throw new \TypeError($class.'::__serialize() must return an array'); + } + + if ($reflector->hasMethod('__unserialize')) { + $properties = $serializeProperties; + } else { + foreach ($serializeProperties as $n => $v) { + $c = $reflector->hasProperty($n) && ($p = $reflector->getProperty($n))->isReadOnly() ? $p->class : 'stdClass'; + $properties[$c][$n] = $v; + } + } + + goto prepare_value; + } + + $sleep = null; + $proto = Registry::$prototypes[$class]; + + if (($value instanceof \ArrayIterator || $value instanceof \ArrayObject) && null !== $proto) { + // ArrayIterator and ArrayObject need special care because their "flags" + // option changes the behavior of the (array) casting operator. + [$arrayValue, $properties] = self::getArrayObjectProperties($value, $proto); + + // populates Registry::$prototypes[$class] with a new instance + Registry::getClassReflector($class, Registry::$instantiableWithoutConstructor[$class], Registry::$cloneable[$class]); + } elseif ($value instanceof \SplObjectStorage && Registry::$cloneable[$class] && null !== $proto) { + // By implementing Serializable, SplObjectStorage breaks + // internal references; let's deal with it on our own. + foreach (clone $value as $v) { + $properties[] = $v; + $properties[] = $value[$v]; + } + $properties = ['SplObjectStorage' => ["\0" => $properties]]; + $arrayValue = (array) $value; + } elseif ($value instanceof \Serializable + || $value instanceof \__PHP_Incomplete_Class + ) { + ++$objectsCount; + $objectsPool[$value] = [$id = \count($objectsPool), serialize($value), [], 0]; + $value = new Reference($id); + goto handle_value; + } else { + if (method_exists($class, '__sleep')) { + if (!\is_array($sleep = $value->__sleep())) { + trigger_error('serialize(): __sleep should return an array only containing the names of instance-variables to serialize', \E_USER_NOTICE); + $value = null; + goto handle_value; + } + $sleep = array_flip($sleep); + } + + $arrayValue = (array) $value; + } + + $proto = (array) $proto; + + foreach ($arrayValue as $name => $v) { + $i = 0; + $n = (string) $name; + if ('' === $n || "\0" !== $n[0]) { + $c = $reflector->hasProperty($n) && ($p = $reflector->getProperty($n))->isReadOnly() ? $p->class : 'stdClass'; + } elseif ('*' === $n[1]) { + $n = substr($n, 3); + $c = $reflector->getProperty($n)->class; + if ('Error' === $c) { + $c = 'TypeError'; + } elseif ('Exception' === $c) { + $c = 'ErrorException'; + } + } else { + $i = strpos($n, "\0", 2); + $c = substr($n, 1, $i - 1); + $n = substr($n, 1 + $i); + } + if (null !== $sleep) { + if (!isset($sleep[$name]) && (!isset($sleep[$n]) || ($i && $c !== $class))) { + unset($arrayValue[$name]); + continue; + } + unset($sleep[$name], $sleep[$n]); + } + if (!\array_key_exists($name, $proto) || $proto[$name] !== $v || "\x00Error\x00trace" === $name || "\x00Exception\x00trace" === $name) { + $properties[$c][$n] = $v; + } + } + if ($sleep) { + foreach ($sleep as $n => $v) { + trigger_error(\sprintf('serialize(): "%s" returned as member variable from __sleep() but does not exist', $n), \E_USER_NOTICE); + } + } + if (method_exists($class, '__unserialize')) { + $properties = $arrayValue; + } + + prepare_value: + $objectsPool[$value] = [$id = \count($objectsPool)]; + $properties = self::prepare($properties, $objectsPool, $refsPool, $objectsCount, $valueIsStatic); + ++$objectsCount; + $objectsPool[$value] = [$id, $class, $properties, method_exists($class, '__unserialize') ? -$objectsCount : (method_exists($class, '__wakeup') ? $objectsCount : 0)]; + + $value = new Reference($id); + + handle_value: + if ($isRef) { + unset($value); // Break the hard reference created above + } elseif (!$valueIsStatic) { + $values[$k] = $value; + } + $valuesAreStatic = $valueIsStatic && $valuesAreStatic; + } + + return $values; + } + + public static function export($value, $indent = '') + { + switch (true) { + case \is_int($value) || \is_float($value): return var_export($value, true); + case [] === $value: return '[]'; + case false === $value: return 'false'; + case true === $value: return 'true'; + case null === $value: return 'null'; + case '' === $value: return "''"; + case $value instanceof \UnitEnum: return '\\'.ltrim(var_export($value, true), '\\'); + } + + if ($value instanceof Reference) { + if (0 <= $value->id) { + return '$o['.$value->id.']'; + } + if (!$value->count) { + return self::export($value->value, $indent); + } + $value = -$value->id; + + return '&$r['.$value.']'; + } + $subIndent = $indent.' '; + + if (\is_string($value)) { + $code = \sprintf("'%s'", addcslashes($value, "'\\")); + + $code = preg_replace_callback("/((?:[\\0\\r\\n]|\u{202A}|\u{202B}|\u{202D}|\u{202E}|\u{2066}|\u{2067}|\u{2068}|\u{202C}|\u{2069})++)(.)/", function ($m) use ($subIndent) { + $m[1] = \sprintf('\'."%s".\'', str_replace( + ["\0", "\r", "\n", "\u{202A}", "\u{202B}", "\u{202D}", "\u{202E}", "\u{2066}", "\u{2067}", "\u{2068}", "\u{202C}", "\u{2069}", '\n\\'], + ['\0', '\r', '\n', '\u{202A}', '\u{202B}', '\u{202D}', '\u{202E}', '\u{2066}', '\u{2067}', '\u{2068}', '\u{202C}', '\u{2069}', '\n"'."\n".$subIndent.'."\\'], + $m[1] + )); + + if ("'" === $m[2]) { + return substr($m[1], 0, -2); + } + + if (str_ends_with($m[1], 'n".\'')) { + return substr_replace($m[1], "\n".$subIndent.".'".$m[2], -2); + } + + return $m[1].$m[2]; + }, $code, -1, $count); + + if ($count && str_starts_with($code, "''.")) { + $code = substr($code, 3); + } + + return $code; + } + + if (\is_array($value)) { + $j = -1; + $code = ''; + foreach ($value as $k => $v) { + $code .= $subIndent; + if (!\is_int($k) || 1 !== $k - $j) { + $code .= self::export($k, $subIndent).' => '; + } + if (\is_int($k) && $k > $j) { + $j = $k; + } + $code .= self::export($v, $subIndent).",\n"; + } + + return "[\n".$code.$indent.']'; + } + + if ($value instanceof Values) { + $code = $subIndent."\$r = [],\n"; + foreach ($value->values as $k => $v) { + $code .= $subIndent.'$r['.$k.'] = '.self::export($v, $subIndent).",\n"; + } + + return "[\n".$code.$indent.']'; + } + + if ($value instanceof Registry) { + return self::exportRegistry($value, $indent, $subIndent); + } + + if ($value instanceof Hydrator) { + return self::exportHydrator($value, $indent, $subIndent); + } + + throw new \UnexpectedValueException(\sprintf('Cannot export value of type "%s".', get_debug_type($value))); + } + + private static function exportRegistry(Registry $value, string $indent, string $subIndent): string + { + $code = ''; + $serializables = []; + $seen = []; + $prototypesAccess = 0; + $factoriesAccess = 0; + $r = '\\'.Registry::class; + $j = -1; + + foreach ($value->classes as $k => $class) { + if (':' === ($class[1] ?? null)) { + $serializables[$k] = $class; + continue; + } + if (!Registry::$instantiableWithoutConstructor[$class]) { + if (is_subclass_of($class, 'Serializable') && !method_exists($class, '__unserialize')) { + $serializables[$k] = 'C:'.\strlen($class).':"'.$class.'":0:{}'; + } else { + $serializables[$k] = 'O:'.\strlen($class).':"'.$class.'":0:{}'; + } + if (is_subclass_of($class, 'Throwable')) { + $eol = is_subclass_of($class, 'Error') ? "\0Error\0" : "\0Exception\0"; + $serializables[$k] = substr_replace($serializables[$k], '1:{s:'.(5 + \strlen($eol)).':"'.$eol.'trace";a:0:{}}', -4); + } + continue; + } + $code .= $subIndent.(1 !== $k - $j ? $k.' => ' : ''); + $j = $k; + $eol = ",\n"; + $c = '['.self::export($class).']'; + + if ($seen[$class] ?? false) { + if (Registry::$cloneable[$class]) { + ++$prototypesAccess; + $code .= 'clone $p'.$c; + } else { + ++$factoriesAccess; + $code .= '$f'.$c.'()'; + } + } else { + $seen[$class] = true; + if (Registry::$cloneable[$class]) { + $code .= 'clone ('.($prototypesAccess++ ? '$p' : '($p = &'.$r.'::$prototypes)').$c.' ?? '.$r.'::p'; + } else { + $code .= '('.($factoriesAccess++ ? '$f' : '($f = &'.$r.'::$factories)').$c.' ?? '.$r.'::f'; + $eol = '()'.$eol; + } + $code .= '('.substr($c, 1, -1).'))'; + } + $code .= $eol; + } + + if (1 === $prototypesAccess) { + $code = str_replace('($p = &'.$r.'::$prototypes)', $r.'::$prototypes', $code); + } + if (1 === $factoriesAccess) { + $code = str_replace('($f = &'.$r.'::$factories)', $r.'::$factories', $code); + } + if ('' !== $code) { + $code = "\n".$code.$indent; + } + + if ($serializables) { + $code = $r.'::unserialize(['.$code.'], '.self::export($serializables, $indent).')'; + } else { + $code = '['.$code.']'; + } + + return '$o = '.$code; + } + + private static function exportHydrator(Hydrator $value, string $indent, string $subIndent): string + { + $code = ''; + foreach ($value->properties as $class => $properties) { + $code .= $subIndent.' '.self::export($class).' => '.self::export($properties, $subIndent.' ').",\n"; + } + + $code = [ + self::export($value->registry, $subIndent), + self::export($value->values, $subIndent), + '' !== $code ? "[\n".$code.$subIndent.']' : '[]', + self::export($value->value, $subIndent), + self::export($value->wakeups, $subIndent), + ]; + + return '\\'.$value::class."::hydrate(\n".$subIndent.implode(",\n".$subIndent, $code)."\n".$indent.')'; + } + + /** + * @param \ArrayIterator|\ArrayObject $value + * @param \ArrayIterator|\ArrayObject $proto + */ + private static function getArrayObjectProperties($value, $proto): array + { + $reflector = $value instanceof \ArrayIterator ? 'ArrayIterator' : 'ArrayObject'; + $reflector = Registry::$reflectors[$reflector] ??= Registry::getClassReflector($reflector); + + $properties = [ + $arrayValue = (array) $value, + $reflector->getMethod('getFlags')->invoke($value), + $value instanceof \ArrayObject ? $reflector->getMethod('getIteratorClass')->invoke($value) : 'ArrayIterator', + ]; + + $reflector = $reflector->getMethod('setFlags'); + $reflector->invoke($proto, \ArrayObject::STD_PROP_LIST); + + if ($properties[1] & \ArrayObject::STD_PROP_LIST) { + $reflector->invoke($value, 0); + $properties[0] = (array) $value; + } else { + $reflector->invoke($value, \ArrayObject::STD_PROP_LIST); + $arrayValue = (array) $value; + } + $reflector->invoke($value, $properties[1]); + + if ([[], 0, 'ArrayIterator'] === $properties) { + $properties = []; + } else { + if ('ArrayIterator' === $properties[2]) { + unset($properties[2]); + } + $properties = [$reflector->class => ["\0" => $properties]]; + } + + return [$arrayValue, $properties]; + } +} diff --git a/vendor/symfony/var-exporter/Internal/Hydrator.php b/vendor/symfony/var-exporter/Internal/Hydrator.php new file mode 100644 index 0000000..4092363 --- /dev/null +++ b/vendor/symfony/var-exporter/Internal/Hydrator.php @@ -0,0 +1,298 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarExporter\Internal; + +use Symfony\Component\VarExporter\Exception\ClassNotFoundException; + +/** + * @author Nicolas Grekas + * + * @internal + */ +class Hydrator +{ + public static array $hydrators = []; + public static array $simpleHydrators = []; + public static array $propertyScopes = []; + + public function __construct( + public readonly Registry $registry, + public readonly ?Values $values, + public readonly array $properties, + public readonly mixed $value, + public readonly array $wakeups, + ) { + } + + public static function hydrate($objects, $values, $properties, $value, $wakeups) + { + foreach ($properties as $class => $vars) { + (self::$hydrators[$class] ??= self::getHydrator($class))($vars, $objects); + } + foreach ($wakeups as $k => $v) { + if (\is_array($v)) { + $objects[-$k]->__unserialize($v); + } else { + $objects[$v]->__wakeup(); + } + } + + return $value; + } + + public static function getHydrator($class) + { + $baseHydrator = self::$hydrators['stdClass'] ??= static function ($properties, $objects) { + foreach ($properties as $name => $values) { + foreach ($values as $i => $v) { + $objects[$i]->$name = $v; + } + } + }; + + switch ($class) { + case 'stdClass': + return $baseHydrator; + + case 'ErrorException': + return $baseHydrator->bindTo(null, new class extends \ErrorException { + }); + + case 'TypeError': + return $baseHydrator->bindTo(null, new class extends \Error { + }); + + case 'SplObjectStorage': + return static function ($properties, $objects) { + foreach ($properties as $name => $values) { + if ("\0" === $name) { + foreach ($values as $i => $v) { + for ($j = 0; $j < \count($v); ++$j) { + $objects[$i]->attach($v[$j], $v[++$j]); + } + } + continue; + } + foreach ($values as $i => $v) { + $objects[$i]->$name = $v; + } + } + }; + } + + if (!class_exists($class) && !interface_exists($class, false) && !trait_exists($class, false)) { + throw new ClassNotFoundException($class); + } + $classReflector = new \ReflectionClass($class); + + switch ($class) { + case 'ArrayIterator': + case 'ArrayObject': + $constructor = $classReflector->getConstructor()->invokeArgs(...); + + return static function ($properties, $objects) use ($constructor) { + foreach ($properties as $name => $values) { + if ("\0" !== $name) { + foreach ($values as $i => $v) { + $objects[$i]->$name = $v; + } + } + } + foreach ($properties["\0"] ?? [] as $i => $v) { + $constructor($objects[$i], $v); + } + }; + } + + if (!$classReflector->isInternal()) { + return $baseHydrator->bindTo(null, $class); + } + + if ($classReflector->name !== $class) { + return self::$hydrators[$classReflector->name] ??= self::getHydrator($classReflector->name); + } + + $propertySetters = []; + foreach ($classReflector->getProperties() as $propertyReflector) { + if (!$propertyReflector->isStatic()) { + $propertySetters[$propertyReflector->name] = $propertyReflector->setValue(...); + } + } + + if (!$propertySetters) { + return $baseHydrator; + } + + return static function ($properties, $objects) use ($propertySetters) { + foreach ($properties as $name => $values) { + if ($setValue = $propertySetters[$name] ?? null) { + foreach ($values as $i => $v) { + $setValue($objects[$i], $v); + } + continue; + } + foreach ($values as $i => $v) { + $objects[$i]->$name = $v; + } + } + }; + } + + public static function getSimpleHydrator($class) + { + $baseHydrator = self::$simpleHydrators['stdClass'] ??= (function ($properties, $object) { + $readonly = (array) $this; + + foreach ($properties as $name => &$value) { + $object->$name = $value; + + if (!($readonly[$name] ?? false)) { + $object->$name = &$value; + } + } + })->bindTo(new \stdClass()); + + switch ($class) { + case 'stdClass': + return $baseHydrator; + + case 'ErrorException': + return $baseHydrator->bindTo(new \stdClass(), new class extends \ErrorException { + }); + + case 'TypeError': + return $baseHydrator->bindTo(new \stdClass(), new class extends \Error { + }); + + case 'SplObjectStorage': + return static function ($properties, $object) { + foreach ($properties as $name => &$value) { + if ("\0" !== $name) { + $object->$name = $value; + $object->$name = &$value; + continue; + } + for ($i = 0; $i < \count($value); ++$i) { + $object->attach($value[$i], $value[++$i]); + } + } + }; + } + + if (!class_exists($class) && !interface_exists($class, false) && !trait_exists($class, false)) { + throw new ClassNotFoundException($class); + } + $classReflector = new \ReflectionClass($class); + + switch ($class) { + case 'ArrayIterator': + case 'ArrayObject': + $constructor = $classReflector->getConstructor()->invokeArgs(...); + + return static function ($properties, $object) use ($constructor) { + foreach ($properties as $name => &$value) { + if ("\0" === $name) { + $constructor($object, $value); + } else { + $object->$name = $value; + $object->$name = &$value; + } + } + }; + } + + if (!$classReflector->isInternal()) { + $readonly = new \stdClass(); + foreach ($classReflector->getProperties(\ReflectionProperty::IS_READONLY) as $propertyReflector) { + if ($class === $propertyReflector->class) { + $readonly->{$propertyReflector->name} = true; + } + } + + return $baseHydrator->bindTo($readonly, $class); + } + + if ($classReflector->name !== $class) { + return self::$simpleHydrators[$classReflector->name] ??= self::getSimpleHydrator($classReflector->name); + } + + $propertySetters = []; + foreach ($classReflector->getProperties() as $propertyReflector) { + if (!$propertyReflector->isStatic()) { + $propertySetters[$propertyReflector->name] = $propertyReflector->setValue(...); + } + } + + if (!$propertySetters) { + return $baseHydrator; + } + + return static function ($properties, $object) use ($propertySetters) { + foreach ($properties as $name => &$value) { + if ($setValue = $propertySetters[$name] ?? null) { + $setValue($object, $value); + } else { + $object->$name = $value; + $object->$name = &$value; + } + } + }; + } + + public static function getPropertyScopes($class): array + { + $propertyScopes = []; + $r = new \ReflectionClass($class); + + foreach ($r->getProperties() as $property) { + $flags = $property->getModifiers(); + + if (\ReflectionProperty::IS_STATIC & $flags) { + continue; + } + $name = $property->name; + $readonlyScope = null; + + if (\ReflectionProperty::IS_PRIVATE & $flags) { + if ($flags & \ReflectionProperty::IS_READONLY) { + $readonlyScope = $class; + } + $propertyScopes["\0$class\0$name"] = $propertyScopes[$name] = [$class, $name, $readonlyScope, $property]; + + continue; + } + if ($flags & \ReflectionProperty::IS_READONLY) { + $readonlyScope = $property->class; + } + $propertyScopes[$name] = [$class, $name, $readonlyScope, $property]; + + if (\ReflectionProperty::IS_PROTECTED & $flags) { + $propertyScopes["\0*\0$name"] = $propertyScopes[$name]; + } + } + + while ($r = $r->getParentClass()) { + $class = $r->name; + + foreach ($r->getProperties(\ReflectionProperty::IS_PRIVATE) as $property) { + if (!$property->isStatic()) { + $name = $property->name; + $readonlyScope = $property->isReadOnly() ? $class : null; + $propertyScopes["\0$class\0$name"] = [$class, $name, $readonlyScope, $property]; + $propertyScopes[$name] ??= [$class, $name, $readonlyScope, $property]; + } + } + } + + return $propertyScopes; + } +} diff --git a/vendor/symfony/var-exporter/Internal/LazyObjectRegistry.php b/vendor/symfony/var-exporter/Internal/LazyObjectRegistry.php new file mode 100644 index 0000000..f6450ce --- /dev/null +++ b/vendor/symfony/var-exporter/Internal/LazyObjectRegistry.php @@ -0,0 +1,138 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarExporter\Internal; + +/** + * Stores the state of lazy objects and caches related reflection information. + * + * As a micro-optimization, this class uses no type declarations. + * + * @internal + */ +class LazyObjectRegistry +{ + /** + * @var array + */ + public static array $classReflectors = []; + + /** + * @var array> + */ + public static array $defaultProperties = []; + + /** + * @var array> + */ + public static array $classResetters = []; + + /** + * @var array + */ + public static array $classAccessors = []; + + /** + * @var array + */ + public static array $parentMethods = []; + + public static ?\Closure $noInitializerState = null; + + public static function getClassResetters($class) + { + $classProperties = []; + + if ((self::$classReflectors[$class] ??= new \ReflectionClass($class))->isInternal()) { + $propertyScopes = []; + } else { + $propertyScopes = Hydrator::$propertyScopes[$class] ??= Hydrator::getPropertyScopes($class); + } + + foreach ($propertyScopes as $key => [$scope, $name, $readonlyScope]) { + $propertyScopes[$k = "\0$scope\0$name"] ?? $propertyScopes[$k = "\0*\0$name"] ?? $k = $name; + + if ($k === $key && "\0$class\0lazyObjectState" !== $k) { + $classProperties[$readonlyScope ?? $scope][$name] = $key; + } + } + + $resetters = []; + foreach ($classProperties as $scope => $properties) { + $resetters[] = \Closure::bind(static function ($instance, $skippedProperties) use ($properties) { + foreach ($properties as $name => $key) { + if (!\array_key_exists($key, $skippedProperties)) { + unset($instance->$name); + } + } + }, null, $scope); + } + + return $resetters; + } + + public static function getClassAccessors($class) + { + return \Closure::bind(static fn () => [ + 'get' => static function &($instance, $name, $readonly) { + if (!$readonly) { + return $instance->$name; + } + $value = $instance->$name; + + return $value; + }, + 'set' => static function ($instance, $name, $value) { + $instance->$name = $value; + }, + 'isset' => static fn ($instance, $name) => isset($instance->$name), + 'unset' => static function ($instance, $name) { + unset($instance->$name); + }, + ], null, \Closure::class === $class ? null : $class)(); + } + + public static function getParentMethods($class) + { + $parent = get_parent_class($class); + $methods = []; + + foreach (['set', 'isset', 'unset', 'clone', 'serialize', 'unserialize', 'sleep', 'wakeup', 'destruct', 'get'] as $method) { + if (!$parent || !method_exists($parent, '__'.$method)) { + $methods[$method] = false; + } else { + $m = new \ReflectionMethod($parent, '__'.$method); + $methods[$method] = !$m->isAbstract() && !$m->isPrivate(); + } + } + + $methods['get'] = $methods['get'] ? ($m->returnsReference() ? 2 : 1) : 0; + + return $methods; + } + + public static function getScope($propertyScopes, $class, $property, $readonlyScope = null) + { + if (null === $readonlyScope && !isset($propertyScopes[$k = "\0$class\0$property"]) && !isset($propertyScopes[$k = "\0*\0$property"])) { + return null; + } + $frame = debug_backtrace(\DEBUG_BACKTRACE_PROVIDE_OBJECT | \DEBUG_BACKTRACE_IGNORE_ARGS, 3)[2]; + + if (\ReflectionProperty::class === $scope = $frame['class'] ?? \Closure::class) { + $scope = $frame['object']->class; + } + if (null === $readonlyScope && '*' === $k[1] && ($class === $scope || (is_subclass_of($class, $scope) && !isset($propertyScopes["\0$scope\0$property"])))) { + return null; + } + + return $scope; + } +} diff --git a/vendor/symfony/var-exporter/Internal/LazyObjectState.php b/vendor/symfony/var-exporter/Internal/LazyObjectState.php new file mode 100644 index 0000000..30fbff1 --- /dev/null +++ b/vendor/symfony/var-exporter/Internal/LazyObjectState.php @@ -0,0 +1,97 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarExporter\Internal; + +use Symfony\Component\VarExporter\Hydrator as PublicHydrator; + +/** + * Keeps the state of lazy objects. + * + * As a micro-optimization, this class uses no type declarations. + * + * @internal + */ +class LazyObjectState +{ + public const STATUS_UNINITIALIZED_FULL = 1; + public const STATUS_UNINITIALIZED_PARTIAL = 2; + public const STATUS_INITIALIZED_FULL = 3; + public const STATUS_INITIALIZED_PARTIAL = 4; + + /** + * @var self::STATUS_* + */ + public int $status = self::STATUS_UNINITIALIZED_FULL; + + public object $realInstance; + + /** + * @param array $skippedProperties + */ + public function __construct( + public \Closure $initializer, + public array $skippedProperties = [], + ) { + } + + public function initialize($instance, $propertyName, $propertyScope) + { + if (self::STATUS_UNINITIALIZED_FULL !== $this->status) { + return $this->status; + } + + $this->status = self::STATUS_INITIALIZED_PARTIAL; + + try { + if ($defaultProperties = array_diff_key(LazyObjectRegistry::$defaultProperties[$instance::class], $this->skippedProperties)) { + PublicHydrator::hydrate($instance, $defaultProperties); + } + + ($this->initializer)($instance); + } catch (\Throwable $e) { + $this->status = self::STATUS_UNINITIALIZED_FULL; + $this->reset($instance); + + throw $e; + } + + return $this->status = self::STATUS_INITIALIZED_FULL; + } + + public function reset($instance): void + { + $class = $instance::class; + $propertyScopes = Hydrator::$propertyScopes[$class] ??= Hydrator::getPropertyScopes($class); + $skippedProperties = $this->skippedProperties; + $properties = (array) $instance; + + foreach ($propertyScopes as $key => [$scope, $name, $readonlyScope]) { + $propertyScopes[$k = "\0$scope\0$name"] ?? $propertyScopes[$k = "\0*\0$name"] ?? $k = $name; + + if ($k === $key && (null !== $readonlyScope || !\array_key_exists($k, $properties))) { + $skippedProperties[$k] = true; + } + } + + foreach (LazyObjectRegistry::$classResetters[$class] as $reset) { + $reset($instance, $skippedProperties); + } + + foreach ((array) $instance as $name => $value) { + if ("\0" !== ($name[0] ?? '') && !\array_key_exists($name, $skippedProperties)) { + unset($instance->$name); + } + } + + $this->status = self::STATUS_UNINITIALIZED_FULL; + } +} diff --git a/vendor/symfony/var-exporter/Internal/LazyObjectTrait.php b/vendor/symfony/var-exporter/Internal/LazyObjectTrait.php new file mode 100644 index 0000000..4a6f232 --- /dev/null +++ b/vendor/symfony/var-exporter/Internal/LazyObjectTrait.php @@ -0,0 +1,34 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarExporter\Internal; + +use Symfony\Component\Serializer\Attribute\Ignore; + +if (\PHP_VERSION_ID >= 80300) { + /** + * @internal + */ + trait LazyObjectTrait + { + #[Ignore] + private readonly LazyObjectState $lazyObjectState; + } +} else { + /** + * @internal + */ + trait LazyObjectTrait + { + #[Ignore] + private LazyObjectState $lazyObjectState; + } +} diff --git a/vendor/symfony/var-exporter/Internal/Reference.php b/vendor/symfony/var-exporter/Internal/Reference.php new file mode 100644 index 0000000..2c7bd7b --- /dev/null +++ b/vendor/symfony/var-exporter/Internal/Reference.php @@ -0,0 +1,28 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarExporter\Internal; + +/** + * @author Nicolas Grekas + * + * @internal + */ +class Reference +{ + public int $count = 0; + + public function __construct( + public readonly int $id, + public readonly mixed $value = null, + ) { + } +} diff --git a/vendor/symfony/var-exporter/Internal/Registry.php b/vendor/symfony/var-exporter/Internal/Registry.php new file mode 100644 index 0000000..9c41684 --- /dev/null +++ b/vendor/symfony/var-exporter/Internal/Registry.php @@ -0,0 +1,142 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarExporter\Internal; + +use Symfony\Component\VarExporter\Exception\ClassNotFoundException; +use Symfony\Component\VarExporter\Exception\NotInstantiableTypeException; + +/** + * @author Nicolas Grekas + * + * @internal + */ +class Registry +{ + public static array $reflectors = []; + public static array $prototypes = []; + public static array $factories = []; + public static array $cloneable = []; + public static array $instantiableWithoutConstructor = []; + + public function __construct( + public readonly array $classes, + ) { + } + + public static function unserialize($objects, $serializables) + { + $unserializeCallback = ini_set('unserialize_callback_func', __CLASS__.'::getClassReflector'); + + try { + foreach ($serializables as $k => $v) { + $objects[$k] = unserialize($v); + } + } finally { + ini_set('unserialize_callback_func', $unserializeCallback); + } + + return $objects; + } + + public static function p($class) + { + self::getClassReflector($class, true, true); + + return self::$prototypes[$class]; + } + + public static function f($class) + { + $reflector = self::$reflectors[$class] ??= self::getClassReflector($class, true, false); + + return self::$factories[$class] = [$reflector, 'newInstanceWithoutConstructor'](...); + } + + public static function getClassReflector($class, $instantiableWithoutConstructor = false, $cloneable = null) + { + if (!($isClass = class_exists($class)) && !interface_exists($class, false) && !trait_exists($class, false)) { + throw new ClassNotFoundException($class); + } + $reflector = new \ReflectionClass($class); + + if ($instantiableWithoutConstructor) { + $proto = $reflector->newInstanceWithoutConstructor(); + } elseif (!$isClass || $reflector->isAbstract()) { + throw new NotInstantiableTypeException($class); + } elseif ($reflector->name !== $class) { + $reflector = self::$reflectors[$name = $reflector->name] ??= self::getClassReflector($name, false, $cloneable); + self::$cloneable[$class] = self::$cloneable[$name]; + self::$instantiableWithoutConstructor[$class] = self::$instantiableWithoutConstructor[$name]; + self::$prototypes[$class] = self::$prototypes[$name]; + + return $reflector; + } else { + try { + $proto = $reflector->newInstanceWithoutConstructor(); + $instantiableWithoutConstructor = true; + } catch (\ReflectionException) { + $proto = $reflector->implementsInterface('Serializable') && !method_exists($class, '__unserialize') ? 'C:' : 'O:'; + if ('C:' === $proto && !$reflector->getMethod('unserialize')->isInternal()) { + $proto = null; + } else { + try { + $proto = @unserialize($proto.\strlen($class).':"'.$class.'":0:{}'); + } catch (\Exception $e) { + if (__FILE__ !== $e->getFile()) { + throw $e; + } + throw new NotInstantiableTypeException($class, $e); + } + if (false === $proto) { + throw new NotInstantiableTypeException($class); + } + } + } + if (null !== $proto && !$proto instanceof \Throwable && !$proto instanceof \Serializable && !method_exists($class, '__sleep') && !method_exists($class, '__serialize')) { + try { + serialize($proto); + } catch (\Exception $e) { + throw new NotInstantiableTypeException($class, $e); + } + } + } + + if (null === $cloneable) { + if (($proto instanceof \Reflector || $proto instanceof \ReflectionGenerator || $proto instanceof \ReflectionType || $proto instanceof \IteratorIterator || $proto instanceof \RecursiveIteratorIterator) && (!$proto instanceof \Serializable && !method_exists($proto, '__wakeup') && !method_exists($class, '__unserialize'))) { + throw new NotInstantiableTypeException($class); + } + + $cloneable = $reflector->isCloneable() && !$reflector->hasMethod('__clone'); + } + + self::$cloneable[$class] = $cloneable; + self::$instantiableWithoutConstructor[$class] = $instantiableWithoutConstructor; + self::$prototypes[$class] = $proto; + + if ($proto instanceof \Throwable) { + static $setTrace; + + if (null === $setTrace) { + $setTrace = [ + new \ReflectionProperty(\Error::class, 'trace'), + new \ReflectionProperty(\Exception::class, 'trace'), + ]; + $setTrace[0] = $setTrace[0]->setValue(...); + $setTrace[1] = $setTrace[1]->setValue(...); + } + + $setTrace[$proto instanceof \Exception]($proto, []); + } + + return $reflector; + } +} diff --git a/vendor/symfony/var-exporter/Internal/Values.php b/vendor/symfony/var-exporter/Internal/Values.php new file mode 100644 index 0000000..4f20a82 --- /dev/null +++ b/vendor/symfony/var-exporter/Internal/Values.php @@ -0,0 +1,25 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarExporter\Internal; + +/** + * @author Nicolas Grekas + * + * @internal + */ +class Values +{ + public function __construct( + public readonly array $values, + ) { + } +} diff --git a/vendor/symfony/var-exporter/LICENSE b/vendor/symfony/var-exporter/LICENSE new file mode 100644 index 0000000..7536cae --- /dev/null +++ b/vendor/symfony/var-exporter/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2018-present Fabien Potencier + +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/vendor/symfony/var-exporter/LazyGhostTrait.php b/vendor/symfony/var-exporter/LazyGhostTrait.php new file mode 100644 index 0000000..72a96d4 --- /dev/null +++ b/vendor/symfony/var-exporter/LazyGhostTrait.php @@ -0,0 +1,363 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarExporter; + +use Symfony\Component\Serializer\Attribute\Ignore; +use Symfony\Component\VarExporter\Internal\Hydrator; +use Symfony\Component\VarExporter\Internal\LazyObjectRegistry as Registry; +use Symfony\Component\VarExporter\Internal\LazyObjectState; +use Symfony\Component\VarExporter\Internal\LazyObjectTrait; + +trait LazyGhostTrait +{ + use LazyObjectTrait; + + /** + * Creates a lazy-loading ghost instance. + * + * Skipped properties should be indexed by their array-cast identifier, see + * https://php.net/manual/language.types.array#language.types.array.casting + * + * @param \Closure(static):void $initializer The closure should initialize the object it receives as argument + * @param array|null $skippedProperties An array indexed by the properties to skip, a.k.a. the ones + * that the initializer doesn't initialize, if any + * @param static|null $instance + */ + public static function createLazyGhost(\Closure $initializer, ?array $skippedProperties = null, ?object $instance = null): static + { + if (self::class !== $class = $instance ? $instance::class : static::class) { + $skippedProperties["\0".self::class."\0lazyObjectState"] = true; + } + + if (!isset(Registry::$defaultProperties[$class])) { + Registry::$classReflectors[$class] ??= new \ReflectionClass($class); + $instance ??= Registry::$classReflectors[$class]->newInstanceWithoutConstructor(); + Registry::$defaultProperties[$class] ??= (array) $instance; + Registry::$classResetters[$class] ??= Registry::getClassResetters($class); + + if (self::class === $class && \defined($class.'::LAZY_OBJECT_PROPERTY_SCOPES')) { + Hydrator::$propertyScopes[$class] ??= $class::LAZY_OBJECT_PROPERTY_SCOPES; + } + } else { + $instance ??= Registry::$classReflectors[$class]->newInstanceWithoutConstructor(); + } + + if (isset($instance->lazyObjectState)) { + $instance->lazyObjectState->initializer = $initializer; + $instance->lazyObjectState->skippedProperties = $skippedProperties ??= []; + + if (LazyObjectState::STATUS_UNINITIALIZED_FULL !== $instance->lazyObjectState->status) { + $instance->lazyObjectState->reset($instance); + } + + return $instance; + } + + $instance->lazyObjectState = new LazyObjectState($initializer, $skippedProperties ??= []); + + foreach (Registry::$classResetters[$class] as $reset) { + $reset($instance, $skippedProperties); + } + + return $instance; + } + + /** + * Returns whether the object is initialized. + * + * @param bool $partial Whether partially initialized objects should be considered as initialized + */ + #[Ignore] + public function isLazyObjectInitialized(bool $partial = false): bool + { + if (!$state = $this->lazyObjectState ?? null) { + return true; + } + + return LazyObjectState::STATUS_INITIALIZED_FULL === $state->status; + } + + /** + * Forces initialization of a lazy object and returns it. + */ + public function initializeLazyObject(): static + { + if (!$state = $this->lazyObjectState ?? null) { + return $this; + } + + if (LazyObjectState::STATUS_UNINITIALIZED_FULL === $state->status) { + $state->initialize($this, '', null); + } + + return $this; + } + + /** + * @return bool Returns false when the object cannot be reset, ie when it's not a lazy object + */ + public function resetLazyObject(): bool + { + if (!$state = $this->lazyObjectState ?? null) { + return false; + } + + if (LazyObjectState::STATUS_UNINITIALIZED_FULL !== $state->status) { + $state->reset($this); + } + + return true; + } + + public function &__get($name): mixed + { + $propertyScopes = Hydrator::$propertyScopes[$this::class] ??= Hydrator::getPropertyScopes($this::class); + $scope = null; + + if ([$class, , $readonlyScope] = $propertyScopes[$name] ?? null) { + $scope = Registry::getScope($propertyScopes, $class, $name); + $state = $this->lazyObjectState ?? null; + + if ($state && (null === $scope || isset($propertyScopes["\0$scope\0$name"]))) { + if (LazyObjectState::STATUS_INITIALIZED_FULL === $state->status) { + // Work around php/php-src#12695 + $property = null === $scope ? $name : "\0$scope\0$name"; + $property = $propertyScopes[$property][3] + ?? Hydrator::$propertyScopes[$this::class][$property][3] = new \ReflectionProperty($scope ?? $class, $name); + } else { + $property = null; + } + + if ($property?->isInitialized($this) ?? LazyObjectState::STATUS_UNINITIALIZED_PARTIAL !== $state->initialize($this, $name, $readonlyScope ?? $scope)) { + goto get_in_scope; + } + } + } + + if ($parent = (Registry::$parentMethods[self::class] ??= Registry::getParentMethods(self::class))['get']) { + if (2 === $parent) { + return parent::__get($name); + } + $value = parent::__get($name); + + return $value; + } + + if (null === $class) { + $frame = debug_backtrace(\DEBUG_BACKTRACE_IGNORE_ARGS, 1)[0]; + trigger_error(\sprintf('Undefined property: %s::$%s in %s on line %s', $this::class, $name, $frame['file'], $frame['line']), \E_USER_NOTICE); + } + + get_in_scope: + + try { + if (null === $scope) { + if (null === $readonlyScope) { + return $this->$name; + } + $value = $this->$name; + + return $value; + } + $accessor = Registry::$classAccessors[$scope] ??= Registry::getClassAccessors($scope); + + return $accessor['get']($this, $name, null !== $readonlyScope); + } catch (\Error $e) { + if (\Error::class !== $e::class || !str_starts_with($e->getMessage(), 'Cannot access uninitialized non-nullable property')) { + throw $e; + } + + try { + if (null === $scope) { + $this->$name = []; + + return $this->$name; + } + + $accessor['set']($this, $name, []); + + return $accessor['get']($this, $name, null !== $readonlyScope); + } catch (\Error) { + if (preg_match('/^Cannot access uninitialized non-nullable property ([^ ]++) by reference$/', $e->getMessage(), $matches)) { + throw new \Error('Typed property '.$matches[1].' must not be accessed before initialization', $e->getCode(), $e->getPrevious()); + } + + throw $e; + } + } + } + + public function __set($name, $value): void + { + $propertyScopes = Hydrator::$propertyScopes[$this::class] ??= Hydrator::getPropertyScopes($this::class); + $scope = null; + + if ([$class, , $readonlyScope] = $propertyScopes[$name] ?? null) { + $scope = Registry::getScope($propertyScopes, $class, $name, $readonlyScope); + $state = $this->lazyObjectState ?? null; + + if ($state && ($readonlyScope === $scope || isset($propertyScopes["\0$scope\0$name"])) + && LazyObjectState::STATUS_INITIALIZED_FULL !== $state->status + ) { + if (LazyObjectState::STATUS_UNINITIALIZED_FULL === $state->status) { + $state->initialize($this, $name, $readonlyScope ?? $scope); + } + goto set_in_scope; + } + } + + if ((Registry::$parentMethods[self::class] ??= Registry::getParentMethods(self::class))['set']) { + parent::__set($name, $value); + + return; + } + + set_in_scope: + + if (null === $scope) { + $this->$name = $value; + } else { + $accessor = Registry::$classAccessors[$scope] ??= Registry::getClassAccessors($scope); + $accessor['set']($this, $name, $value); + } + } + + public function __isset($name): bool + { + $propertyScopes = Hydrator::$propertyScopes[$this::class] ??= Hydrator::getPropertyScopes($this::class); + $scope = null; + + if ([$class, , $readonlyScope] = $propertyScopes[$name] ?? null) { + $scope = Registry::getScope($propertyScopes, $class, $name); + $state = $this->lazyObjectState ?? null; + + if ($state && (null === $scope || isset($propertyScopes["\0$scope\0$name"])) + && LazyObjectState::STATUS_INITIALIZED_FULL !== $state->status + && LazyObjectState::STATUS_UNINITIALIZED_PARTIAL !== $state->initialize($this, $name, $readonlyScope ?? $scope) + ) { + goto isset_in_scope; + } + } + + if ((Registry::$parentMethods[self::class] ??= Registry::getParentMethods(self::class))['isset']) { + return parent::__isset($name); + } + + isset_in_scope: + + if (null === $scope) { + return isset($this->$name); + } + $accessor = Registry::$classAccessors[$scope] ??= Registry::getClassAccessors($scope); + + return $accessor['isset']($this, $name); + } + + public function __unset($name): void + { + $propertyScopes = Hydrator::$propertyScopes[$this::class] ??= Hydrator::getPropertyScopes($this::class); + $scope = null; + + if ([$class, , $readonlyScope] = $propertyScopes[$name] ?? null) { + $scope = Registry::getScope($propertyScopes, $class, $name, $readonlyScope); + $state = $this->lazyObjectState ?? null; + + if ($state && ($readonlyScope === $scope || isset($propertyScopes["\0$scope\0$name"])) + && LazyObjectState::STATUS_INITIALIZED_FULL !== $state->status + ) { + if (LazyObjectState::STATUS_UNINITIALIZED_FULL === $state->status) { + $state->initialize($this, $name, $readonlyScope ?? $scope); + } + goto unset_in_scope; + } + } + + if ((Registry::$parentMethods[self::class] ??= Registry::getParentMethods(self::class))['unset']) { + parent::__unset($name); + + return; + } + + unset_in_scope: + + if (null === $scope) { + unset($this->$name); + } else { + $accessor = Registry::$classAccessors[$scope] ??= Registry::getClassAccessors($scope); + $accessor['unset']($this, $name); + } + } + + public function __clone(): void + { + if ($state = $this->lazyObjectState ?? null) { + $this->lazyObjectState = clone $state; + } + + if ((Registry::$parentMethods[self::class] ??= Registry::getParentMethods(self::class))['clone']) { + parent::__clone(); + } + } + + public function __serialize(): array + { + $class = self::class; + + if ((Registry::$parentMethods[$class] ??= Registry::getParentMethods($class))['serialize']) { + $properties = parent::__serialize(); + } else { + $this->initializeLazyObject(); + $properties = (array) $this; + } + unset($properties["\0$class\0lazyObjectState"]); + + if (Registry::$parentMethods[$class]['serialize'] || !Registry::$parentMethods[$class]['sleep']) { + return $properties; + } + + $scope = get_parent_class($class); + $data = []; + + foreach (parent::__sleep() as $name) { + $value = $properties[$k = $name] ?? $properties[$k = "\0*\0$name"] ?? $properties[$k = "\0$class\0$name"] ?? $properties[$k = "\0$scope\0$name"] ?? $k = null; + + if (null === $k) { + trigger_error(\sprintf('serialize(): "%s" returned as member variable from __sleep() but does not exist', $name), \E_USER_NOTICE); + } else { + $data[$k] = $value; + } + } + + return $data; + } + + public function __destruct() + { + $state = $this->lazyObjectState ?? null; + + if (LazyObjectState::STATUS_UNINITIALIZED_FULL === $state?->status) { + return; + } + + if ((Registry::$parentMethods[self::class] ??= Registry::getParentMethods(self::class))['destruct']) { + parent::__destruct(); + } + } + + #[Ignore] + private function setLazyObjectAsInitialized(bool $initialized): void + { + if ($state = $this->lazyObjectState ?? null) { + $state->status = $initialized ? LazyObjectState::STATUS_INITIALIZED_FULL : LazyObjectState::STATUS_UNINITIALIZED_FULL; + } + } +} diff --git a/vendor/symfony/var-exporter/LazyObjectInterface.php b/vendor/symfony/var-exporter/LazyObjectInterface.php new file mode 100644 index 0000000..3422dc6 --- /dev/null +++ b/vendor/symfony/var-exporter/LazyObjectInterface.php @@ -0,0 +1,32 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarExporter; + +interface LazyObjectInterface +{ + /** + * Returns whether the object is initialized. + * + * @param bool $partial Whether partially initialized objects should be considered as initialized + */ + public function isLazyObjectInitialized(bool $partial = false): bool; + + /** + * Forces initialization of a lazy object and returns it. + */ + public function initializeLazyObject(): object; + + /** + * @return bool Returns false when the object cannot be reset, ie when it's not a lazy object + */ + public function resetLazyObject(): bool; +} diff --git a/vendor/symfony/var-exporter/LazyProxyTrait.php b/vendor/symfony/var-exporter/LazyProxyTrait.php new file mode 100644 index 0000000..795e4d7 --- /dev/null +++ b/vendor/symfony/var-exporter/LazyProxyTrait.php @@ -0,0 +1,366 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarExporter; + +use Symfony\Component\Serializer\Attribute\Ignore; +use Symfony\Component\VarExporter\Hydrator as PublicHydrator; +use Symfony\Component\VarExporter\Internal\Hydrator; +use Symfony\Component\VarExporter\Internal\LazyObjectRegistry as Registry; +use Symfony\Component\VarExporter\Internal\LazyObjectState; +use Symfony\Component\VarExporter\Internal\LazyObjectTrait; + +trait LazyProxyTrait +{ + use LazyObjectTrait; + + /** + * Creates a lazy-loading virtual proxy. + * + * @param \Closure():object $initializer Returns the proxied object + * @param static|null $instance + */ + public static function createLazyProxy(\Closure $initializer, ?object $instance = null): static + { + if (self::class !== $class = $instance ? $instance::class : static::class) { + $skippedProperties = ["\0".self::class."\0lazyObjectState" => true]; + } + + if (!isset(Registry::$defaultProperties[$class])) { + Registry::$classReflectors[$class] ??= new \ReflectionClass($class); + $instance ??= Registry::$classReflectors[$class]->newInstanceWithoutConstructor(); + Registry::$defaultProperties[$class] ??= (array) $instance; + Registry::$classResetters[$class] ??= Registry::getClassResetters($class); + + if (self::class === $class && \defined($class.'::LAZY_OBJECT_PROPERTY_SCOPES')) { + Hydrator::$propertyScopes[$class] ??= $class::LAZY_OBJECT_PROPERTY_SCOPES; + } + } else { + $instance ??= Registry::$classReflectors[$class]->newInstanceWithoutConstructor(); + } + + if (isset($instance->lazyObjectState)) { + $instance->lazyObjectState->initializer = $initializer; + unset($instance->lazyObjectState->realInstance); + + return $instance; + } + + $instance->lazyObjectState = new LazyObjectState($initializer); + + foreach (Registry::$classResetters[$class] as $reset) { + $reset($instance, $skippedProperties ??= []); + } + + return $instance; + } + + /** + * Returns whether the object is initialized. + * + * @param bool $partial Whether partially initialized objects should be considered as initialized + */ + #[Ignore] + public function isLazyObjectInitialized(bool $partial = false): bool + { + return !isset($this->lazyObjectState) || isset($this->lazyObjectState->realInstance) || Registry::$noInitializerState === $this->lazyObjectState->initializer; + } + + /** + * Forces initialization of a lazy object and returns it. + */ + public function initializeLazyObject(): parent + { + if ($state = $this->lazyObjectState ?? null) { + return $state->realInstance ??= ($state->initializer)(); + } + + return $this; + } + + /** + * @return bool Returns false when the object cannot be reset, ie when it's not a lazy object + */ + public function resetLazyObject(): bool + { + if (!isset($this->lazyObjectState) || Registry::$noInitializerState === $this->lazyObjectState->initializer) { + return false; + } + + unset($this->lazyObjectState->realInstance); + + return true; + } + + public function &__get($name): mixed + { + $propertyScopes = Hydrator::$propertyScopes[$this::class] ??= Hydrator::getPropertyScopes($this::class); + $scope = null; + $instance = $this; + + if ([$class, , $readonlyScope] = $propertyScopes[$name] ?? null) { + $scope = Registry::getScope($propertyScopes, $class, $name); + + if (null === $scope || isset($propertyScopes["\0$scope\0$name"])) { + if ($state = $this->lazyObjectState ?? null) { + $instance = $state->realInstance ??= ($state->initializer)(); + } + $parent = 2; + goto get_in_scope; + } + } + $parent = (Registry::$parentMethods[self::class] ??= Registry::getParentMethods(self::class))['get']; + + if ($state = $this->lazyObjectState ?? null) { + $instance = $state->realInstance ??= ($state->initializer)(); + } else { + if (2 === $parent) { + return parent::__get($name); + } + $value = parent::__get($name); + + return $value; + } + + if (!$parent && null === $class && !\array_key_exists($name, (array) $instance)) { + $frame = debug_backtrace(\DEBUG_BACKTRACE_IGNORE_ARGS, 1)[0]; + trigger_error(\sprintf('Undefined property: %s::$%s in %s on line %s', $instance::class, $name, $frame['file'], $frame['line']), \E_USER_NOTICE); + } + + get_in_scope: + + try { + if (null === $scope) { + if (null === $readonlyScope && 1 !== $parent) { + return $instance->$name; + } + $value = $instance->$name; + + return $value; + } + $accessor = Registry::$classAccessors[$scope] ??= Registry::getClassAccessors($scope); + + return $accessor['get']($instance, $name, null !== $readonlyScope || 1 === $parent); + } catch (\Error $e) { + if (\Error::class !== $e::class || !str_starts_with($e->getMessage(), 'Cannot access uninitialized non-nullable property')) { + throw $e; + } + + try { + if (null === $scope) { + $instance->$name = []; + + return $instance->$name; + } + + $accessor['set']($instance, $name, []); + + return $accessor['get']($instance, $name, null !== $readonlyScope || 1 === $parent); + } catch (\Error) { + throw $e; + } + } + } + + public function __set($name, $value): void + { + $propertyScopes = Hydrator::$propertyScopes[$this::class] ??= Hydrator::getPropertyScopes($this::class); + $scope = null; + $instance = $this; + + if ([$class, , $readonlyScope] = $propertyScopes[$name] ?? null) { + $scope = Registry::getScope($propertyScopes, $class, $name, $readonlyScope); + + if ($readonlyScope === $scope || isset($propertyScopes["\0$scope\0$name"])) { + if ($state = $this->lazyObjectState ?? null) { + $instance = $state->realInstance ??= ($state->initializer)(); + } + goto set_in_scope; + } + } + + if ($state = $this->lazyObjectState ?? null) { + $instance = $state->realInstance ??= ($state->initializer)(); + } elseif ((Registry::$parentMethods[self::class] ??= Registry::getParentMethods(self::class))['set']) { + parent::__set($name, $value); + + return; + } + + set_in_scope: + + if (null === $scope) { + $instance->$name = $value; + } else { + $accessor = Registry::$classAccessors[$scope] ??= Registry::getClassAccessors($scope); + $accessor['set']($instance, $name, $value); + } + } + + public function __isset($name): bool + { + $propertyScopes = Hydrator::$propertyScopes[$this::class] ??= Hydrator::getPropertyScopes($this::class); + $scope = null; + $instance = $this; + + if ([$class] = $propertyScopes[$name] ?? null) { + $scope = Registry::getScope($propertyScopes, $class, $name); + + if (null === $scope || isset($propertyScopes["\0$scope\0$name"])) { + if ($state = $this->lazyObjectState ?? null) { + $instance = $state->realInstance ??= ($state->initializer)(); + } + goto isset_in_scope; + } + } + + if ($state = $this->lazyObjectState ?? null) { + $instance = $state->realInstance ??= ($state->initializer)(); + } elseif ((Registry::$parentMethods[self::class] ??= Registry::getParentMethods(self::class))['isset']) { + return parent::__isset($name); + } + + isset_in_scope: + + if (null === $scope) { + return isset($instance->$name); + } + $accessor = Registry::$classAccessors[$scope] ??= Registry::getClassAccessors($scope); + + return $accessor['isset']($instance, $name); + } + + public function __unset($name): void + { + $propertyScopes = Hydrator::$propertyScopes[$this::class] ??= Hydrator::getPropertyScopes($this::class); + $scope = null; + $instance = $this; + + if ([$class, , $readonlyScope] = $propertyScopes[$name] ?? null) { + $scope = Registry::getScope($propertyScopes, $class, $name, $readonlyScope); + + if ($readonlyScope === $scope || isset($propertyScopes["\0$scope\0$name"])) { + if ($state = $this->lazyObjectState ?? null) { + $instance = $state->realInstance ??= ($state->initializer)(); + } + goto unset_in_scope; + } + } + + if ($state = $this->lazyObjectState ?? null) { + $instance = $state->realInstance ??= ($state->initializer)(); + } elseif ((Registry::$parentMethods[self::class] ??= Registry::getParentMethods(self::class))['unset']) { + parent::__unset($name); + + return; + } + + unset_in_scope: + + if (null === $scope) { + unset($instance->$name); + } else { + $accessor = Registry::$classAccessors[$scope] ??= Registry::getClassAccessors($scope); + $accessor['unset']($instance, $name); + } + } + + public function __clone(): void + { + if (!isset($this->lazyObjectState)) { + if ((Registry::$parentMethods[self::class] ??= Registry::getParentMethods(self::class))['clone']) { + parent::__clone(); + } + + return; + } + + $this->lazyObjectState = clone $this->lazyObjectState; + + if (isset($this->lazyObjectState->realInstance)) { + $this->lazyObjectState->realInstance = clone $this->lazyObjectState->realInstance; + } + } + + public function __serialize(): array + { + $class = self::class; + $state = $this->lazyObjectState ?? null; + + if (!$state && (Registry::$parentMethods[$class] ??= Registry::getParentMethods($class))['serialize']) { + $properties = parent::__serialize(); + } else { + $properties = (array) $this; + + if ($state) { + unset($properties["\0$class\0lazyObjectState"]); + $properties["\0$class\0lazyObjectReal"] = $state->realInstance ??= ($state->initializer)(); + } + } + + if ($state || Registry::$parentMethods[$class]['serialize'] || !Registry::$parentMethods[$class]['sleep']) { + return $properties; + } + + $scope = get_parent_class($class); + $data = []; + + foreach (parent::__sleep() as $name) { + $value = $properties[$k = $name] ?? $properties[$k = "\0*\0$name"] ?? $properties[$k = "\0$class\0$name"] ?? $properties[$k = "\0$scope\0$name"] ?? $k = null; + + if (null === $k) { + trigger_error(\sprintf('serialize(): "%s" returned as member variable from __sleep() but does not exist', $name), \E_USER_NOTICE); + } else { + $data[$k] = $value; + } + } + + return $data; + } + + public function __unserialize(array $data): void + { + $class = self::class; + + if ($instance = $data["\0$class\0lazyObjectReal"] ?? null) { + unset($data["\0$class\0lazyObjectReal"]); + + foreach (Registry::$classResetters[$class] ??= Registry::getClassResetters($class) as $reset) { + $reset($this, $data); + } + + if ($data) { + PublicHydrator::hydrate($this, $data); + } + $this->lazyObjectState = new LazyObjectState(Registry::$noInitializerState ??= static fn () => throw new \LogicException('Lazy proxy has no initializer.')); + $this->lazyObjectState->realInstance = $instance; + } elseif ((Registry::$parentMethods[$class] ??= Registry::getParentMethods($class))['unserialize']) { + parent::__unserialize($data); + } else { + PublicHydrator::hydrate($this, $data); + + if (Registry::$parentMethods[$class]['wakeup']) { + parent::__wakeup(); + } + } + } + + public function __destruct() + { + if (isset($this->lazyObjectState)) { + return; + } + + if ((Registry::$parentMethods[self::class] ??= Registry::getParentMethods(self::class))['destruct']) { + parent::__destruct(); + } + } +} diff --git a/vendor/symfony/var-exporter/ProxyHelper.php b/vendor/symfony/var-exporter/ProxyHelper.php new file mode 100644 index 0000000..e0cc84f --- /dev/null +++ b/vendor/symfony/var-exporter/ProxyHelper.php @@ -0,0 +1,412 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarExporter; + +use Symfony\Component\VarExporter\Exception\LogicException; +use Symfony\Component\VarExporter\Internal\Hydrator; +use Symfony\Component\VarExporter\Internal\LazyObjectRegistry; + +/** + * @author Nicolas Grekas + */ +final class ProxyHelper +{ + /** + * Helps generate lazy-loading ghost objects. + * + * @throws LogicException When the class is incompatible with ghost objects + */ + public static function generateLazyGhost(\ReflectionClass $class): string + { + if (\PHP_VERSION_ID < 80300 && $class->isReadOnly()) { + throw new LogicException(\sprintf('Cannot generate lazy ghost with PHP < 8.3: class "%s" is readonly.', $class->name)); + } + if ($class->isFinal()) { + throw new LogicException(\sprintf('Cannot generate lazy ghost: class "%s" is final.', $class->name)); + } + if ($class->isInterface() || $class->isAbstract()) { + throw new LogicException(\sprintf('Cannot generate lazy ghost: "%s" is not a concrete class.', $class->name)); + } + if (\stdClass::class !== $class->name && $class->isInternal()) { + throw new LogicException(\sprintf('Cannot generate lazy ghost: class "%s" is internal.', $class->name)); + } + if ($class->hasMethod('__get') && 'mixed' !== (self::exportType($class->getMethod('__get')) ?? 'mixed')) { + throw new LogicException(\sprintf('Cannot generate lazy ghost: return type of method "%s::__get()" should be "mixed".', $class->name)); + } + + static $traitMethods; + $traitMethods ??= (new \ReflectionClass(LazyGhostTrait::class))->getMethods(); + + foreach ($traitMethods as $method) { + if ($class->hasMethod($method->name) && $class->getMethod($method->name)->isFinal()) { + throw new LogicException(\sprintf('Cannot generate lazy ghost: method "%s::%s()" is final.', $class->name, $method->name)); + } + } + + $parent = $class; + while ($parent = $parent->getParentClass()) { + if (\stdClass::class !== $parent->name && $parent->isInternal()) { + throw new LogicException(\sprintf('Cannot generate lazy ghost: class "%s" extends "%s" which is internal.', $class->name, $parent->name)); + } + } + $propertyScopes = self::exportPropertyScopes($class->name); + + return <<name} implements \Symfony\Component\VarExporter\LazyObjectInterface + { + use \Symfony\Component\VarExporter\LazyGhostTrait; + + private const LAZY_OBJECT_PROPERTY_SCOPES = {$propertyScopes}; + } + + // Help opcache.preload discover always-needed symbols + class_exists(\Symfony\Component\VarExporter\Internal\Hydrator::class); + class_exists(\Symfony\Component\VarExporter\Internal\LazyObjectRegistry::class); + class_exists(\Symfony\Component\VarExporter\Internal\LazyObjectState::class); + + EOPHP; + } + + /** + * Helps generate lazy-loading virtual proxies. + * + * @param \ReflectionClass[] $interfaces + * + * @throws LogicException When the class is incompatible with virtual proxies + */ + public static function generateLazyProxy(?\ReflectionClass $class, array $interfaces = []): string + { + if (!class_exists($class?->name ?? \stdClass::class, false)) { + throw new LogicException(\sprintf('Cannot generate lazy proxy: "%s" is not a class.', $class->name)); + } + if ($class?->isFinal()) { + throw new LogicException(\sprintf('Cannot generate lazy proxy: class "%s" is final.', $class->name)); + } + if (\PHP_VERSION_ID < 80300 && $class?->isReadOnly()) { + throw new LogicException(\sprintf('Cannot generate lazy proxy with PHP < 8.3: class "%s" is readonly.', $class->name)); + } + + $methodReflectors = [$class?->getMethods(\ReflectionMethod::IS_PUBLIC | \ReflectionMethod::IS_PROTECTED) ?? []]; + foreach ($interfaces as $interface) { + if (!$interface->isInterface()) { + throw new LogicException(\sprintf('Cannot generate lazy proxy: "%s" is not an interface.', $interface->name)); + } + $methodReflectors[] = $interface->getMethods(); + } + $methodReflectors = array_merge(...$methodReflectors); + + $extendsInternalClass = false; + if ($parent = $class) { + do { + $extendsInternalClass = \stdClass::class !== $parent->name && $parent->isInternal(); + } while (!$extendsInternalClass && $parent = $parent->getParentClass()); + } + $methodsHaveToBeProxied = $extendsInternalClass; + $methods = []; + + foreach ($methodReflectors as $method) { + if ('__get' !== strtolower($method->name) || 'mixed' === ($type = self::exportType($method) ?? 'mixed')) { + continue; + } + $methodsHaveToBeProxied = true; + $trait = new \ReflectionMethod(LazyProxyTrait::class, '__get'); + $body = \array_slice(file($trait->getFileName()), $trait->getStartLine() - 1, $trait->getEndLine() - $trait->getStartLine()); + $body[0] = str_replace('): mixed', '): '.$type, $body[0]); + $methods['__get'] = strtr(implode('', $body).' }', [ + 'Hydrator' => '\\'.Hydrator::class, + 'Registry' => '\\'.LazyObjectRegistry::class, + ]); + break; + } + + foreach ($methodReflectors as $method) { + if (($method->isStatic() && !$method->isAbstract()) || isset($methods[$lcName = strtolower($method->name)])) { + continue; + } + if ($method->isFinal()) { + if ($extendsInternalClass || $methodsHaveToBeProxied || method_exists(LazyProxyTrait::class, $method->name)) { + throw new LogicException(\sprintf('Cannot generate lazy proxy: method "%s::%s()" is final.', $class->name, $method->name)); + } + continue; + } + if (method_exists(LazyProxyTrait::class, $method->name) || ($method->isProtected() && !$method->isAbstract())) { + continue; + } + + $signature = self::exportSignature($method, true, $args); + $parentCall = $method->isAbstract() ? "throw new \BadMethodCallException('Cannot forward abstract method \"{$method->class}::{$method->name}()\".')" : "parent::{$method->name}({$args})"; + + if ($method->isStatic()) { + $body = " $parentCall;"; + } elseif (str_ends_with($signature, '): never') || str_ends_with($signature, '): void')) { + $body = <<lazyObjectState)) { + (\$this->lazyObjectState->realInstance ??= (\$this->lazyObjectState->initializer)())->{$method->name}({$args}); + } else { + {$parentCall}; + } + EOPHP; + } else { + if (!$methodsHaveToBeProxied && !$method->isAbstract()) { + // Skip proxying methods that might return $this + foreach (preg_split('/[()|&]++/', self::exportType($method) ?? 'static') as $type) { + if (\in_array($type = ltrim($type, '?'), ['static', 'object'], true)) { + continue 2; + } + foreach ([$class, ...$interfaces] as $r) { + if ($r && is_a($r->name, $type, true)) { + continue 3; + } + } + } + } + + $body = <<lazyObjectState)) { + return (\$this->lazyObjectState->realInstance ??= (\$this->lazyObjectState->initializer)())->{$method->name}({$args}); + } + + return {$parentCall}; + EOPHP; + } + $methods[$lcName] = " {$signature}\n {\n{$body}\n }"; + } + + $types = $interfaces = array_unique(array_column($interfaces, 'name')); + $interfaces[] = LazyObjectInterface::class; + $interfaces = implode(', \\', $interfaces); + $parent = $class ? ' extends \\'.$class->name : ''; + array_unshift($types, $class ? 'parent' : ''); + $type = ltrim(implode('&\\', $types), '&'); + + if (!$class) { + $trait = new \ReflectionMethod(LazyProxyTrait::class, 'initializeLazyObject'); + $body = \array_slice(file($trait->getFileName()), $trait->getStartLine() - 1, $trait->getEndLine() - $trait->getStartLine()); + $body[0] = str_replace('): parent', '): '.$type, $body[0]); + $methods = ['initializeLazyObject' => implode('', $body).' }'] + $methods; + } + $body = $methods ? "\n".implode("\n\n", $methods)."\n" : ''; + $propertyScopes = $class ? self::exportPropertyScopes($class->name) : '[]'; + + if ( + $class?->hasMethod('__unserialize') + && !$class->getMethod('__unserialize')->getParameters()[0]->getType() + ) { + // fix contravariance type problem when $class declares a `__unserialize()` method without typehint. + $lazyProxyTraitStatement = <<__doUnserialize(\$data); + } + + EOPHP; + } else { + $lazyProxyTraitStatement = <<class : $function->getNamespaceName().'\\'; + $namespace = substr($namespace, 0, strrpos($namespace, '\\') ?: 0); + foreach ($function->getParameters() as $param) { + $parameters[] = ($param->getAttributes(\SensitiveParameter::class) ? '#[\SensitiveParameter] ' : '') + .($withParameterTypes && $param->hasType() ? self::exportType($param).' ' : '') + .($param->isPassedByReference() ? '&' : '') + .($param->isVariadic() ? '...' : '').'$'.$param->name + .($param->isOptional() && !$param->isVariadic() ? ' = '.self::exportDefault($param, $namespace) : ''); + if ($param->isPassedByReference()) { + $byRefIndex = 1 + $param->getPosition(); + } + $args .= ($param->isVariadic() ? '...$' : '$').$param->name.', '; + } + + if (!$param || !$byRefIndex) { + $args = '...\func_get_args()'; + } elseif ($param->isVariadic()) { + $args = substr($args, 0, -2); + } else { + $args = explode(', ', $args, 1 + $byRefIndex); + $args[$byRefIndex] = \sprintf('...\array_slice(\func_get_args(), %d)', $byRefIndex); + $args = implode(', ', $args); + } + + $signature = 'function '.($function->returnsReference() ? '&' : '') + .($function->isClosure() ? '' : $function->name).'('.implode(', ', $parameters).')'; + + if ($function instanceof \ReflectionMethod) { + $signature = ($function->isPublic() ? 'public ' : ($function->isProtected() ? 'protected ' : 'private ')) + .($function->isStatic() ? 'static ' : '').$signature; + } + if ($function->hasReturnType()) { + $signature .= ': '.self::exportType($function); + } + + static $getPrototype; + $getPrototype ??= (new \ReflectionMethod(\ReflectionMethod::class, 'getPrototype'))->invoke(...); + + while ($function) { + if ($function->hasTentativeReturnType()) { + return '#[\ReturnTypeWillChange] '.$signature; + } + + try { + $function = $function instanceof \ReflectionMethod && $function->isAbstract() ? false : $getPrototype($function); + } catch (\ReflectionException) { + break; + } + } + + return $signature; + } + + public static function exportType(\ReflectionFunctionAbstract|\ReflectionProperty|\ReflectionParameter $owner, bool $noBuiltin = false, ?\ReflectionType $type = null): ?string + { + if (!$type ??= $owner instanceof \ReflectionFunctionAbstract ? $owner->getReturnType() : $owner->getType()) { + return null; + } + $class = null; + $types = []; + if ($type instanceof \ReflectionUnionType) { + $reflectionTypes = $type->getTypes(); + $glue = '|'; + } elseif ($type instanceof \ReflectionIntersectionType) { + $reflectionTypes = $type->getTypes(); + $glue = '&'; + } else { + $reflectionTypes = [$type]; + $glue = null; + } + + foreach ($reflectionTypes as $type) { + if ($type instanceof \ReflectionIntersectionType) { + if ('' !== $name = '('.self::exportType($owner, $noBuiltin, $type).')') { + $types[] = $name; + } + continue; + } + $name = $type->getName(); + + if ($noBuiltin && $type->isBuiltin()) { + continue; + } + if (\in_array($name, ['parent', 'self'], true) && $class ??= $owner->getDeclaringClass()) { + $name = 'parent' === $name ? ($class->getParentClass() ?: null)?->name ?? 'parent' : $class->name; + } + + $types[] = ($noBuiltin || $type->isBuiltin() || 'static' === $name ? '' : '\\').$name; + } + + if (!$types) { + return ''; + } + if (null === $glue) { + return (!$noBuiltin && $type->allowsNull() && !\in_array($name, ['mixed', 'null'], true) ? '?' : '').$types[0]; + } + sort($types); + + return implode($glue, $types); + } + + private static function exportPropertyScopes(string $parent): string + { + $propertyScopes = Hydrator::$propertyScopes[$parent] ??= Hydrator::getPropertyScopes($parent); + uksort($propertyScopes, 'strnatcmp'); + foreach ($propertyScopes as $k => $v) { + unset($propertyScopes[$k][3]); + } + $propertyScopes = VarExporter::export($propertyScopes); + $propertyScopes = str_replace(VarExporter::export($parent), 'parent::class', $propertyScopes); + $propertyScopes = preg_replace("/(?|(,)\n( ) |\n |,\n (\]))/", '$1$2', $propertyScopes); + + return str_replace("\n", "\n ", $propertyScopes); + } + + private static function exportDefault(\ReflectionParameter $param, $namespace): string + { + $default = rtrim(substr(explode('$'.$param->name.' = ', (string) $param, 2)[1] ?? '', 0, -2)); + + if (\in_array($default, ['', 'NULL'], true)) { + return 'null'; + } + if (str_ends_with($default, "...'") && preg_match("/^'(?:[^'\\\\]*+(?:\\\\.)*+)*+'$/", $default)) { + return VarExporter::export($param->getDefaultValue()); + } + + $regexp = "/(\"(?:[^\"\\\\]*+(?:\\\\.)*+)*+\"|'(?:[^'\\\\]*+(?:\\\\.)*+)*+')/"; + $parts = preg_split($regexp, $default, -1, \PREG_SPLIT_DELIM_CAPTURE | \PREG_SPLIT_NO_EMPTY); + + $regexp = '/([\[\( ]|^)([a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*+(?:\\\\[a-zA-Z0-9_\x7f-\xff]++)*+)(\(?)(?!: )/'; + $callback = (false !== strpbrk($default, "\\:('") && $class = $param->getDeclaringClass()) + ? fn ($m) => $m[1].match ($m[2]) { + 'new', 'false', 'true', 'null' => $m[2], + 'NULL' => 'null', + 'self' => '\\'.$class->name, + 'namespace\\parent', + 'parent' => ($parent = $class->getParentClass()) ? '\\'.$parent->name : 'parent', + default => self::exportSymbol($m[2], '(' !== $m[3], $namespace), + }.$m[3] + : fn ($m) => $m[1].match ($m[2]) { + 'new', 'false', 'true', 'null', 'self', 'parent' => $m[2], + 'NULL' => 'null', + default => self::exportSymbol($m[2], '(' !== $m[3], $namespace), + }.$m[3]; + + return implode('', array_map(fn ($part) => match ($part[0]) { + '"' => $part, // for internal classes only + "'" => false !== strpbrk($part, "\\\0\r\n") ? '"'.substr(str_replace(['$', "\0", "\r", "\n"], ['\$', '\0', '\r', '\n'], $part), 1, -1).'"' : $part, + default => preg_replace_callback($regexp, $callback, $part), + }, $parts)); + } + + private static function exportSymbol(string $symbol, bool $mightBeRootConst, string $namespace): string + { + if (!$mightBeRootConst + || false === ($ns = strrpos($symbol, '\\')) + || substr($symbol, 0, $ns) !== $namespace + || \defined($symbol) + || !\defined(substr($symbol, $ns + 1)) + ) { + return '\\'.$symbol; + } + + return '\\'.substr($symbol, $ns + 1); + } +} diff --git a/vendor/symfony/var-exporter/README.md b/vendor/symfony/var-exporter/README.md new file mode 100644 index 0000000..7195270 --- /dev/null +++ b/vendor/symfony/var-exporter/README.md @@ -0,0 +1,137 @@ +VarExporter Component +===================== + +The VarExporter component provides various tools to deal with the internal state +of objects: + +- `VarExporter::export()` allows exporting any serializable PHP data structure to + plain PHP code. While doing so, it preserves all the semantics associated with + the serialization mechanism of PHP (`__wakeup`, `__sleep`, `Serializable`, + `__serialize`, `__unserialize`); +- `Instantiator::instantiate()` creates an object and sets its properties without + calling its constructor nor any other methods; +- `Hydrator::hydrate()` can set the properties of an existing object; +- `Lazy*Trait` can make a class behave as a lazy-loading ghost or virtual proxy. + +VarExporter::export() +--------------------- + +The reason to use `VarExporter::export()` *vs* `serialize()` or +[igbinary](https://github.com/igbinary/igbinary) is performance: thanks to +OPcache, the resulting code is significantly faster and more memory efficient +than using `unserialize()` or `igbinary_unserialize()`. + +Unlike `var_export()`, this works on any serializable PHP value. + +It also provides a few improvements over `var_export()`/`serialize()`: + + * the output is PSR-2 compatible; + * the output can be re-indented without messing up with `\r` or `\n` in the data; + * missing classes throw a `ClassNotFoundException` instead of being unserialized + to `PHP_Incomplete_Class` objects; + * references involving `SplObjectStorage`, `ArrayObject` or `ArrayIterator` + instances are preserved; + * `Reflection*`, `IteratorIterator` and `RecursiveIteratorIterator` classes + throw an exception when being serialized (their unserialized version is broken + anyway, see https://bugs.php.net/76737). + +Instantiator and Hydrator +------------------------- + +`Instantiator::instantiate($class)` creates an object of the given class without +calling its constructor nor any other methods. + +`Hydrator::hydrate()` sets the properties of an existing object, including +private and protected ones. For example: + +```php +// Sets the public or protected $object->propertyName property +Hydrator::hydrate($object, ['propertyName' => $propertyValue]); + +// Sets a private property defined on its parent Bar class: +Hydrator::hydrate($object, ["\0Bar\0privateBarProperty" => $propertyValue]); + +// Alternative way to set the private $object->privateBarProperty property +Hydrator::hydrate($object, [], [ + Bar::class => ['privateBarProperty' => $propertyValue], +]); +``` + +`Lazy*Trait` +------------ + +The component provides two lazy-loading patterns: ghost objects and virtual +proxies (see https://martinfowler.com/eaaCatalog/lazyLoad.html for reference). + +Ghost objects work only with concrete and non-internal classes. In the generic +case, they are not compatible with using factories in their initializer. + +Virtual proxies work with concrete, abstract or internal classes. They provide an +API that looks like the actual objects and forward calls to them. They can cause +identity problems because proxies might not be seen as equivalents to the actual +objects they proxy. + +Because of this identity problem, ghost objects should be preferred when +possible. Exceptions thrown by the `ProxyHelper` class can help decide when it +can be used or not. + +Ghost objects and virtual proxies both provide implementations for the +`LazyObjectInterface` which allows resetting them to their initial state or to +forcibly initialize them when needed. Note that resetting a ghost object skips +its read-only properties. You should use a virtual proxy to reset read-only +properties. + +### `LazyGhostTrait` + +By using `LazyGhostTrait` either directly in your classes or by using +`ProxyHelper::generateLazyGhost()`, you can make their instances lazy-loadable. +This works by creating these instances empty and by computing their state only +when accessing a property. + +```php +class FooLazyGhost extends Foo +{ + use LazyGhostTrait; +} + +$foo = FooLazyGhost::createLazyGhost(initializer: function (Foo $instance): void { + // [...] Use whatever heavy logic you need here + // to compute the $dependencies of the $instance + $instance->__construct(...$dependencies); + // [...] Call setters, etc. if needed +}); + +// $foo is now a lazy-loading ghost object. The initializer will +// be called only when and if a *property* is accessed. +``` + +### `LazyProxyTrait` + +Alternatively, `LazyProxyTrait` can be used to create virtual proxies: + +```php +$proxyCode = ProxyHelper::generateLazyProxy(new ReflectionClass(Foo::class)); +// $proxyCode contains the reference to LazyProxyTrait +// and should be dumped into a file in production envs +eval('class FooLazyProxy'.$proxyCode); + +$foo = FooLazyProxy::createLazyProxy(initializer: function (): Foo { + // [...] Use whatever heavy logic you need here + // to compute the $dependencies of the $instance + $instance = new Foo(...$dependencies); + // [...] Call setters, etc. if needed + + return $instance; +}); +// $foo is now a lazy-loading virtual proxy object. The initializer will +// be called only when and if a *method* is called. +``` + +Resources +--------- + + * [Documentation](https://symfony.com/doc/current/components/var_exporter.html) + * [Contributing](https://symfony.com/doc/current/contributing/index.html) + * [Report issues](https://github.com/symfony/symfony/issues) and + [send Pull Requests](https://github.com/symfony/symfony/pulls) + in the [main Symfony repository](https://github.com/symfony/symfony) diff --git a/vendor/symfony/var-exporter/VarExporter.php b/vendor/symfony/var-exporter/VarExporter.php new file mode 100644 index 0000000..606a9fd --- /dev/null +++ b/vendor/symfony/var-exporter/VarExporter.php @@ -0,0 +1,114 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarExporter; + +use Symfony\Component\VarExporter\Exception\ExceptionInterface; +use Symfony\Component\VarExporter\Internal\Exporter; +use Symfony\Component\VarExporter\Internal\Hydrator; +use Symfony\Component\VarExporter\Internal\Registry; +use Symfony\Component\VarExporter\Internal\Values; + +/** + * Exports serializable PHP values to PHP code. + * + * VarExporter allows serializing PHP data structures to plain PHP code (like var_export()) + * while preserving all the semantics associated with serialize() (unlike var_export()). + * + * By leveraging OPcache, the generated PHP code is faster than doing the same with unserialize(). + * + * @author Nicolas Grekas + */ +final class VarExporter +{ + /** + * Exports a serializable PHP value to PHP code. + * + * @param bool &$isStaticValue Set to true after execution if the provided value is static, false otherwise + * @param array &$foundClasses Classes found in the value are added to this list as both keys and values + * + * @throws ExceptionInterface When the provided value cannot be serialized + */ + public static function export(mixed $value, ?bool &$isStaticValue = null, array &$foundClasses = []): string + { + $isStaticValue = true; + + if (!\is_object($value) && !(\is_array($value) && $value) && !\is_resource($value) || $value instanceof \UnitEnum) { + return Exporter::export($value); + } + + $objectsPool = new \SplObjectStorage(); + $refsPool = []; + $objectsCount = 0; + + try { + $value = Exporter::prepare([$value], $objectsPool, $refsPool, $objectsCount, $isStaticValue)[0]; + } finally { + $references = []; + foreach ($refsPool as $i => $v) { + if ($v[0]->count) { + $references[1 + $i] = $v[2]; + } + $v[0] = $v[1]; + } + } + + if ($isStaticValue) { + return Exporter::export($value); + } + + $classes = []; + $values = []; + $states = []; + foreach ($objectsPool as $i => $v) { + [, $class, $values[], $wakeup] = $objectsPool[$v]; + $foundClasses[$class] = $classes[] = $class; + + if (0 < $wakeup) { + $states[$wakeup] = $i; + } elseif (0 > $wakeup) { + $states[-$wakeup] = [$i, array_pop($values)]; + $values[] = []; + } + } + ksort($states); + + $wakeups = [null]; + foreach ($states as $v) { + if (\is_array($v)) { + $wakeups[-$v[0]] = $v[1]; + } else { + $wakeups[] = $v; + } + } + + if (null === $wakeups[0]) { + unset($wakeups[0]); + } + + $properties = []; + foreach ($values as $i => $vars) { + foreach ($vars as $class => $values) { + foreach ($values as $name => $v) { + $properties[$class][$name][$i] = $v; + } + } + } + + if ($classes || $references) { + $value = new Hydrator(new Registry($classes), $references ? new Values($references) : null, $properties, $value, $wakeups); + } else { + $isStaticValue = true; + } + + return Exporter::export($value); + } +} diff --git a/vendor/symfony/var-exporter/composer.json b/vendor/symfony/var-exporter/composer.json new file mode 100644 index 0000000..f3a227e --- /dev/null +++ b/vendor/symfony/var-exporter/composer.json @@ -0,0 +1,33 @@ +{ + "name": "symfony/var-exporter", + "type": "library", + "description": "Allows exporting any serializable PHP data structure to plain PHP code", + "keywords": ["export", "serialize", "instantiate", "hydrate", "construct", "clone", "lazy-loading", "proxy"], + "homepage": "https://symfony.com", + "license": "MIT", + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "require": { + "php": ">=8.2" + }, + "require-dev": { + "symfony/property-access": "^6.4|^7.0", + "symfony/serializer": "^6.4|^7.0", + "symfony/var-dumper": "^6.4|^7.0" + }, + "autoload": { + "psr-4": { "Symfony\\Component\\VarExporter\\": "" }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "minimum-stability": "dev" +}