"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
- "content-hash": "3e87f0369e4799fc35d98f399c67f1e9",
+ "content-hash": "a7276eb2d2108a26699f69c750d02d27",
"packages": [
{
"name": "nikic/fast-route",
},
{
"name": "psr/container",
- "version": "2.0.2",
+ "version": "1.1.2",
"source": {
"type": "git",
"url": "https://github.com/php-fig/container.git",
- "reference": "c71ecc56dfe541dbd90c5360474fbc405f8d5963"
+ "reference": "513e0666f7216c7459170d56df27dfcefe1689ea"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/php-fig/container/zipball/c71ecc56dfe541dbd90c5360474fbc405f8d5963",
- "reference": "c71ecc56dfe541dbd90c5360474fbc405f8d5963",
+ "url": "https://api.github.com/repos/php-fig/container/zipball/513e0666f7216c7459170d56df27dfcefe1689ea",
+ "reference": "513e0666f7216c7459170d56df27dfcefe1689ea",
"shasum": ""
},
"require": {
"php": ">=7.4.0"
},
"type": "library",
- "extra": {
- "branch-alias": {
- "dev-master": "2.0.x-dev"
- }
- },
"autoload": {
"psr-4": {
"Psr\\Container\\": "src/"
"container-interop",
"psr"
],
- "time": "2021-11-05T16:47:00+00:00"
+ "time": "2021-11-05T16:50:12+00:00"
},
{
"name": "psr/http-factory",
},
{
"name": "psr/http-message",
- "version": "1.1",
+ "version": "2.0",
"source": {
"type": "git",
"url": "https://github.com/php-fig/http-message.git",
- "reference": "cb6ce4845ce34a8ad9e68117c10ee90a29919eba"
+ "reference": "402d35bcb92c70c026d1a6a9883f06b2ead23d71"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/php-fig/http-message/zipball/cb6ce4845ce34a8ad9e68117c10ee90a29919eba",
- "reference": "cb6ce4845ce34a8ad9e68117c10ee90a29919eba",
+ "url": "https://api.github.com/repos/php-fig/http-message/zipball/402d35bcb92c70c026d1a6a9883f06b2ead23d71",
+ "reference": "402d35bcb92c70c026d1a6a9883f06b2ead23d71",
"shasum": ""
},
"require": {
"type": "library",
"extra": {
"branch-alias": {
- "dev-master": "1.1.x-dev"
+ "dev-master": "2.0.x-dev"
}
},
"autoload": {
"authors": [
{
"name": "PHP-FIG",
- "homepage": "http://www.php-fig.org/"
+ "homepage": "https://www.php-fig.org/"
}
],
"description": "Common interface for HTTP messages",
"request",
"response"
],
- "time": "2023-04-04T09:50:52+00:00"
+ "time": "2023-04-04T09:54:51+00:00"
},
{
"name": "psr/http-server-handler",
],
"time": "2021-05-03T11:20:27+00:00"
},
- {
- "name": "psr/simple-cache",
- "version": "1.0.1",
- "source": {
- "type": "git",
- "url": "https://github.com/php-fig/simple-cache.git",
- "reference": "408d5eafb83c57f6365a3ca330ff23aa4a5fa39b"
- },
- "dist": {
- "type": "zip",
- "url": "https://api.github.com/repos/php-fig/simple-cache/zipball/408d5eafb83c57f6365a3ca330ff23aa4a5fa39b",
- "reference": "408d5eafb83c57f6365a3ca330ff23aa4a5fa39b",
- "shasum": ""
- },
- "require": {
- "php": ">=5.3.0"
- },
- "type": "library",
- "extra": {
- "branch-alias": {
- "dev-master": "1.0.x-dev"
- }
- },
- "autoload": {
- "psr-4": {
- "Psr\\SimpleCache\\": "src/"
- }
- },
- "notification-url": "https://packagist.org/downloads/",
- "license": [
- "MIT"
- ],
- "authors": [
- {
- "name": "PHP-FIG",
- "homepage": "http://www.php-fig.org/"
- }
- ],
- "description": "Common interfaces for simple caching",
- "keywords": [
- "cache",
- "caching",
- "psr",
- "psr-16",
- "simple-cache"
- ],
- "time": "2017-10-23T01:57:42+00:00"
- },
{
"name": "slim/slim",
- "version": "4.12.0",
+ "version": "4.13.0",
"source": {
"type": "git",
"url": "https://github.com/slimphp/Slim.git",
- "reference": "e9e99c2b24398b967841c6c4c3048622cc7e2b18"
+ "reference": "038fd5713d5a41636fdff0e8dcceedecdd17fc17"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/slimphp/Slim/zipball/e9e99c2b24398b967841c6c4c3048622cc7e2b18",
- "reference": "e9e99c2b24398b967841c6c4c3048622cc7e2b18",
+ "url": "https://api.github.com/repos/slimphp/Slim/zipball/038fd5713d5a41636fdff0e8dcceedecdd17fc17",
+ "reference": "038fd5713d5a41636fdff0e8dcceedecdd17fc17",
"shasum": ""
},
"require": {
"php": "^7.4 || ^8.0",
"psr/container": "^1.0 || ^2.0",
"psr/http-factory": "^1.0",
- "psr/http-message": "^1.1",
+ "psr/http-message": "^1.1 || ^2.0",
"psr/http-server-handler": "^1.0",
"psr/http-server-middleware": "^1.0",
"psr/log": "^1.1 || ^2.0 || ^3.0"
"require-dev": {
"adriansuter/php-autoload-override": "^1.4",
"ext-simplexml": "*",
- "guzzlehttp/psr7": "^2.5",
+ "guzzlehttp/psr7": "^2.6",
"httpsoft/http-message": "^1.1",
"httpsoft/http-server-request": "^1.1",
- "laminas/laminas-diactoros": "^2.17",
+ "laminas/laminas-diactoros": "^2.17 || ^3",
"nyholm/psr7": "^1.8",
- "nyholm/psr7-server": "^1.0",
- "phpspec/prophecy": "^1.17",
- "phpspec/prophecy-phpunit": "^2.0",
+ "nyholm/psr7-server": "^1.1",
+ "phpspec/prophecy": "^1.19",
+ "phpspec/prophecy-phpunit": "^2.1",
"phpstan/phpstan": "^1.10",
"phpunit/phpunit": "^9.6",
"slim/http": "^1.3",
"slim/psr7": "^1.6",
- "squizlabs/php_codesniffer": "^3.7"
+ "squizlabs/php_codesniffer": "^3.9"
},
"suggest": {
"ext-simplexml": "Needed to support XML format in BodyParsingMiddleware",
"type": "tidelift"
}
],
- "time": "2023-07-23T04:54:29+00:00"
+ "time": "2024-03-03T21:25:30+00:00"
},
{
"name": "symfony/cache",
- "version": "v3.4.47",
+ "version": "v4.4.48",
"source": {
"type": "git",
"url": "https://github.com/symfony/cache.git",
- "reference": "a7a14c4832760bd1fbd31be2859ffedc9b6ff813"
+ "reference": "3b98ed664887ad197b8ede3da2432787212eb915"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/cache/zipball/a7a14c4832760bd1fbd31be2859ffedc9b6ff813",
- "reference": "a7a14c4832760bd1fbd31be2859ffedc9b6ff813",
+ "url": "https://api.github.com/repos/symfony/cache/zipball/3b98ed664887ad197b8ede3da2432787212eb915",
+ "reference": "3b98ed664887ad197b8ede3da2432787212eb915",
"shasum": ""
},
"require": {
- "php": "^5.5.9|>=7.0.8",
- "psr/cache": "~1.0",
- "psr/log": "~1.0",
- "psr/simple-cache": "^1.0",
- "symfony/polyfill-apcu": "~1.1"
+ "php": ">=7.1.3",
+ "psr/cache": "^1.0|^2.0",
+ "psr/log": "^1|^2|^3",
+ "symfony/cache-contracts": "^1.1.7|^2",
+ "symfony/polyfill-php73": "^1.9",
+ "symfony/polyfill-php80": "^1.16",
+ "symfony/service-contracts": "^1.1|^2",
+ "symfony/var-exporter": "^4.2|^5.0"
},
"conflict": {
- "symfony/var-dumper": "<3.3"
+ "doctrine/dbal": "<2.7",
+ "symfony/dependency-injection": "<3.4",
+ "symfony/http-kernel": "<4.4|>=5.0",
+ "symfony/var-dumper": "<4.4"
},
"provide": {
- "psr/cache-implementation": "1.0",
- "psr/simple-cache-implementation": "1.0"
+ "psr/cache-implementation": "1.0|2.0",
+ "psr/simple-cache-implementation": "1.0|2.0",
+ "symfony/cache-implementation": "1.0|2.0"
},
"require-dev": {
"cache/integration-tests": "dev-master",
- "doctrine/cache": "^1.6",
- "doctrine/dbal": "^2.4|^3.0",
- "predis/predis": "^1.0"
+ "doctrine/cache": "^1.6|^2.0",
+ "doctrine/dbal": "^2.7|^3.0",
+ "predis/predis": "^1.1",
+ "psr/simple-cache": "^1.0|^2.0",
+ "symfony/config": "^4.2|^5.0",
+ "symfony/dependency-injection": "^3.4|^4.1|^5.0",
+ "symfony/filesystem": "^4.4|^5.0",
+ "symfony/http-kernel": "^4.4",
+ "symfony/var-dumper": "^4.4|^5.0"
},
"type": "library",
"autoload": {
"homepage": "https://symfony.com/contributors"
}
],
- "description": "Symfony Cache component with PSR-6, PSR-16, and tags",
+ "description": "Provides extended PSR-6, PSR-16 (and tags) implementations",
"homepage": "https://symfony.com",
"keywords": [
"caching",
"type": "tidelift"
}
],
- "time": "2020-10-24T10:57:07+00:00"
+ "time": "2022-10-17T20:21:54+00:00"
+ },
+ {
+ "name": "symfony/cache-contracts",
+ "version": "v2.5.2",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/cache-contracts.git",
+ "reference": "64be4a7acb83b6f2bf6de9a02cee6dad41277ebc"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/cache-contracts/zipball/64be4a7acb83b6f2bf6de9a02cee6dad41277ebc",
+ "reference": "64be4a7acb83b6f2bf6de9a02cee6dad41277ebc",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=7.2.5",
+ "psr/cache": "^1.0|^2.0|^3.0"
+ },
+ "suggest": {
+ "symfony/cache-implementation": ""
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-main": "2.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"
+ ],
+ "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": "2022-01-02T09:53:40+00:00"
+ },
+ {
+ "name": "symfony/deprecation-contracts",
+ "version": "v2.5.2",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/deprecation-contracts.git",
+ "reference": "e8b495ea28c1d97b5e0c121748d6f9b53d075c66"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/e8b495ea28c1d97b5e0c121748d6f9b53d075c66",
+ "reference": "e8b495ea28c1d97b5e0c121748d6f9b53d075c66",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=7.1"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-main": "2.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",
+ "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": "2022-01-02T09:53:40+00:00"
},
{
"name": "symfony/expression-language",
"time": "2020-10-24T10:57:07+00:00"
},
{
- "name": "symfony/polyfill-apcu",
- "version": "v1.28.0",
+ "name": "symfony/polyfill-php70",
+ "version": "v1.20.0",
"source": {
"type": "git",
- "url": "https://github.com/symfony/polyfill-apcu.git",
- "reference": "c6c2c0f5f4cb0b100c5dfea807ef5cd27bbe9899"
+ "url": "https://github.com/symfony/polyfill-php70.git",
+ "reference": "5f03a781d984aae42cebd18e7912fa80f02ee644"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/polyfill-apcu/zipball/c6c2c0f5f4cb0b100c5dfea807ef5cd27bbe9899",
- "reference": "c6c2c0f5f4cb0b100c5dfea807ef5cd27bbe9899",
+ "url": "https://api.github.com/repos/symfony/polyfill-php70/zipball/5f03a781d984aae42cebd18e7912fa80f02ee644",
+ "reference": "5f03a781d984aae42cebd18e7912fa80f02ee644",
"shasum": ""
},
"require": {
"php": ">=7.1"
},
- "type": "library",
+ "type": "metapackage",
"extra": {
"branch-alias": {
- "dev-main": "1.28-dev"
+ "dev-main": "1.20-dev"
},
"thanks": {
"name": "symfony/polyfill",
"url": "https://github.com/symfony/polyfill"
}
},
+ "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": "Symfony polyfill backporting some PHP 7.0+ features to lower PHP versions",
+ "homepage": "https://symfony.com",
+ "keywords": [
+ "compatibility",
+ "polyfill",
+ "portable",
+ "shim"
+ ],
+ "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": "2020-10-23T14:02:19+00:00"
+ },
+ {
+ "name": "symfony/polyfill-php73",
+ "version": "v1.29.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/polyfill-php73.git",
+ "reference": "21bd091060673a1177ae842c0ef8fe30893114d2"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/polyfill-php73/zipball/21bd091060673a1177ae842c0ef8fe30893114d2",
+ "reference": "21bd091060673a1177ae842c0ef8fe30893114d2",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=7.1"
+ },
+ "type": "library",
+ "extra": {
+ "thanks": {
+ "name": "symfony/polyfill",
+ "url": "https://github.com/symfony/polyfill"
+ }
+ },
"autoload": {
"files": [
"bootstrap.php"
],
"psr-4": {
- "Symfony\\Polyfill\\Apcu\\": ""
- }
+ "Symfony\\Polyfill\\Php73\\": ""
+ },
+ "classmap": [
+ "Resources/stubs"
+ ]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"homepage": "https://symfony.com/contributors"
}
],
- "description": "Symfony polyfill backporting apcu_* functions to lower PHP versions",
+ "description": "Symfony polyfill backporting some PHP 7.3+ features to lower PHP versions",
"homepage": "https://symfony.com",
"keywords": [
- "apcu",
"compatibility",
"polyfill",
"portable",
"type": "tidelift"
}
],
- "time": "2023-01-26T09:26:14+00:00"
+ "time": "2024-01-29T20:11:03+00:00"
},
{
- "name": "symfony/polyfill-php70",
- "version": "v1.20.0",
+ "name": "symfony/polyfill-php80",
+ "version": "v1.29.0",
"source": {
"type": "git",
- "url": "https://github.com/symfony/polyfill-php70.git",
- "reference": "5f03a781d984aae42cebd18e7912fa80f02ee644"
+ "url": "https://github.com/symfony/polyfill-php80.git",
+ "reference": "87b68208d5c1188808dd7839ee1e6c8ec3b02f1b"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/polyfill-php70/zipball/5f03a781d984aae42cebd18e7912fa80f02ee644",
- "reference": "5f03a781d984aae42cebd18e7912fa80f02ee644",
+ "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/87b68208d5c1188808dd7839ee1e6c8ec3b02f1b",
+ "reference": "87b68208d5c1188808dd7839ee1e6c8ec3b02f1b",
"shasum": ""
},
"require": {
"php": ">=7.1"
},
- "type": "metapackage",
+ "type": "library",
"extra": {
- "branch-alias": {
- "dev-main": "1.20-dev"
- },
"thanks": {
"name": "symfony/polyfill",
"url": "https://github.com/symfony/polyfill"
}
},
+ "autoload": {
+ "files": [
+ "bootstrap.php"
+ ],
+ "psr-4": {
+ "Symfony\\Polyfill\\Php80\\": ""
+ },
+ "classmap": [
+ "Resources/stubs"
+ ]
+ },
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
+ {
+ "name": "Ion Bazan",
+ "email": "ion.bazan@gmail.com"
+ },
{
"name": "Nicolas Grekas",
"email": "p@tchwork.com"
"homepage": "https://symfony.com/contributors"
}
],
- "description": "Symfony polyfill backporting some PHP 7.0+ features to lower PHP versions",
+ "description": "Symfony polyfill backporting some PHP 8.0+ features to lower PHP versions",
"homepage": "https://symfony.com",
"keywords": [
"compatibility",
"type": "tidelift"
}
],
- "time": "2020-10-23T14:02:19+00:00"
+ "time": "2024-01-29T20:11:03+00:00"
+ },
+ {
+ "name": "symfony/service-contracts",
+ "version": "v2.5.2",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/service-contracts.git",
+ "reference": "4b426aac47d6427cc1a1d0f7e2ac724627f5966c"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/service-contracts/zipball/4b426aac47d6427cc1a1d0f7e2ac724627f5966c",
+ "reference": "4b426aac47d6427cc1a1d0f7e2ac724627f5966c",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=7.2.5",
+ "psr/container": "^1.1",
+ "symfony/deprecation-contracts": "^2.1|^3"
+ },
+ "conflict": {
+ "ext-psr": "<1.1|>=2"
+ },
+ "suggest": {
+ "symfony/service-implementation": ""
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-main": "2.5-dev"
+ },
+ "thanks": {
+ "name": "symfony/contracts",
+ "url": "https://github.com/symfony/contracts"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Symfony\\Contracts\\Service\\": ""
+ }
+ },
+ "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"
+ ],
+ "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": "2022-05-30T19:17:29+00:00"
+ },
+ {
+ "name": "symfony/var-exporter",
+ "version": "v5.4.35",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/var-exporter.git",
+ "reference": "abb0a151b62d6b07e816487e20040464af96cae7"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/var-exporter/zipball/abb0a151b62d6b07e816487e20040464af96cae7",
+ "reference": "abb0a151b62d6b07e816487e20040464af96cae7",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=7.2.5",
+ "symfony/polyfill-php80": "^1.16"
+ },
+ "require-dev": {
+ "symfony/var-dumper": "^4.4.9|^5.0.9|^6.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",
+ "serialize"
+ ],
+ "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-01-23T13:51:25+00:00"
}
],
"packages-dev": [],
"stability-flags": [],
"prefer-stable": false,
"prefer-lowest": false,
- "platform": {
- "php": ">=5.6.0"
- },
+ "platform": [],
"platform-dev": [],
+ "platform-overrides": {
+ "php": "7.4"
+ },
"plugin-api-version": "1.1.0"
}
$baseDir = dirname($vendorDir);
return array(
+ 'Attribute' => $vendorDir . '/symfony/polyfill-php80/Resources/stubs/Attribute.php',
'FastRoute\\BadRouteException' => $vendorDir . '/nikic/fast-route/src/BadRouteException.php',
'FastRoute\\DataGenerator' => $vendorDir . '/nikic/fast-route/src/DataGenerator.php',
'FastRoute\\DataGenerator\\CharCountBased' => $vendorDir . '/nikic/fast-route/src/DataGenerator/CharCountBased.php',
'FastRoute\\RouteCollector' => $vendorDir . '/nikic/fast-route/src/RouteCollector.php',
'FastRoute\\RouteParser' => $vendorDir . '/nikic/fast-route/src/RouteParser.php',
'FastRoute\\RouteParser\\Std' => $vendorDir . '/nikic/fast-route/src/RouteParser/Std.php',
+ 'JsonException' => $vendorDir . '/symfony/polyfill-php73/Resources/stubs/JsonException.php',
+ 'PhpToken' => $vendorDir . '/symfony/polyfill-php80/Resources/stubs/PhpToken.php',
'Psr\\Cache\\CacheException' => $vendorDir . '/psr/cache/src/CacheException.php',
'Psr\\Cache\\CacheItemInterface' => $vendorDir . '/psr/cache/src/CacheItemInterface.php',
'Psr\\Cache\\CacheItemPoolInterface' => $vendorDir . '/psr/cache/src/CacheItemPoolInterface.php',
'Psr\\Log\\Test\\DummyTest' => $vendorDir . '/psr/log/Psr/Log/Test/DummyTest.php',
'Psr\\Log\\Test\\LoggerInterfaceTest' => $vendorDir . '/psr/log/Psr/Log/Test/LoggerInterfaceTest.php',
'Psr\\Log\\Test\\TestLogger' => $vendorDir . '/psr/log/Psr/Log/Test/TestLogger.php',
- 'Psr\\SimpleCache\\CacheException' => $vendorDir . '/psr/simple-cache/src/CacheException.php',
- 'Psr\\SimpleCache\\CacheInterface' => $vendorDir . '/psr/simple-cache/src/CacheInterface.php',
- 'Psr\\SimpleCache\\InvalidArgumentException' => $vendorDir . '/psr/simple-cache/src/InvalidArgumentException.php',
'Slim\\App' => $vendorDir . '/slim/slim/Slim/App.php',
'Slim\\CallableResolver' => $vendorDir . '/slim/slim/Slim/CallableResolver.php',
'Slim\\Error\\AbstractErrorRenderer' => $vendorDir . '/slim/slim/Slim/Error/AbstractErrorRenderer.php',
'Slim\\Exception\\HttpNotFoundException' => $vendorDir . '/slim/slim/Slim/Exception/HttpNotFoundException.php',
'Slim\\Exception\\HttpNotImplementedException' => $vendorDir . '/slim/slim/Slim/Exception/HttpNotImplementedException.php',
'Slim\\Exception\\HttpSpecializedException' => $vendorDir . '/slim/slim/Slim/Exception/HttpSpecializedException.php',
+ 'Slim\\Exception\\HttpTooManyRequestsException' => $vendorDir . '/slim/slim/Slim/Exception/HttpTooManyRequestsException.php',
'Slim\\Exception\\HttpUnauthorizedException' => $vendorDir . '/slim/slim/Slim/Exception/HttpUnauthorizedException.php',
'Slim\\Factory\\AppFactory' => $vendorDir . '/slim/slim/Slim/Factory/AppFactory.php',
'Slim\\Factory\\Psr17\\GuzzlePsr17Factory' => $vendorDir . '/slim/slim/Slim/Factory/Psr17/GuzzlePsr17Factory.php',
'Slim\\Routing\\RouteResolver' => $vendorDir . '/slim/slim/Slim/Routing/RouteResolver.php',
'Slim\\Routing\\RouteRunner' => $vendorDir . '/slim/slim/Slim/Routing/RouteRunner.php',
'Slim\\Routing\\RoutingResults' => $vendorDir . '/slim/slim/Slim/Routing/RoutingResults.php',
+ 'Stringable' => $vendorDir . '/symfony/polyfill-php80/Resources/stubs/Stringable.php',
'Symfony\\Component\\Cache\\Adapter\\AbstractAdapter' => $vendorDir . '/symfony/cache/Adapter/AbstractAdapter.php',
+ 'Symfony\\Component\\Cache\\Adapter\\AbstractTagAwareAdapter' => $vendorDir . '/symfony/cache/Adapter/AbstractTagAwareAdapter.php',
'Symfony\\Component\\Cache\\Adapter\\AdapterInterface' => $vendorDir . '/symfony/cache/Adapter/AdapterInterface.php',
'Symfony\\Component\\Cache\\Adapter\\ApcuAdapter' => $vendorDir . '/symfony/cache/Adapter/ApcuAdapter.php',
'Symfony\\Component\\Cache\\Adapter\\ArrayAdapter' => $vendorDir . '/symfony/cache/Adapter/ArrayAdapter.php',
'Symfony\\Component\\Cache\\Adapter\\ChainAdapter' => $vendorDir . '/symfony/cache/Adapter/ChainAdapter.php',
'Symfony\\Component\\Cache\\Adapter\\DoctrineAdapter' => $vendorDir . '/symfony/cache/Adapter/DoctrineAdapter.php',
'Symfony\\Component\\Cache\\Adapter\\FilesystemAdapter' => $vendorDir . '/symfony/cache/Adapter/FilesystemAdapter.php',
+ 'Symfony\\Component\\Cache\\Adapter\\FilesystemTagAwareAdapter' => $vendorDir . '/symfony/cache/Adapter/FilesystemTagAwareAdapter.php',
'Symfony\\Component\\Cache\\Adapter\\MemcachedAdapter' => $vendorDir . '/symfony/cache/Adapter/MemcachedAdapter.php',
'Symfony\\Component\\Cache\\Adapter\\NullAdapter' => $vendorDir . '/symfony/cache/Adapter/NullAdapter.php',
'Symfony\\Component\\Cache\\Adapter\\PdoAdapter' => $vendorDir . '/symfony/cache/Adapter/PdoAdapter.php',
'Symfony\\Component\\Cache\\Adapter\\PhpArrayAdapter' => $vendorDir . '/symfony/cache/Adapter/PhpArrayAdapter.php',
'Symfony\\Component\\Cache\\Adapter\\PhpFilesAdapter' => $vendorDir . '/symfony/cache/Adapter/PhpFilesAdapter.php',
'Symfony\\Component\\Cache\\Adapter\\ProxyAdapter' => $vendorDir . '/symfony/cache/Adapter/ProxyAdapter.php',
+ 'Symfony\\Component\\Cache\\Adapter\\Psr16Adapter' => $vendorDir . '/symfony/cache/Adapter/Psr16Adapter.php',
'Symfony\\Component\\Cache\\Adapter\\RedisAdapter' => $vendorDir . '/symfony/cache/Adapter/RedisAdapter.php',
+ 'Symfony\\Component\\Cache\\Adapter\\RedisTagAwareAdapter' => $vendorDir . '/symfony/cache/Adapter/RedisTagAwareAdapter.php',
'Symfony\\Component\\Cache\\Adapter\\SimpleCacheAdapter' => $vendorDir . '/symfony/cache/Adapter/SimpleCacheAdapter.php',
'Symfony\\Component\\Cache\\Adapter\\TagAwareAdapter' => $vendorDir . '/symfony/cache/Adapter/TagAwareAdapter.php',
'Symfony\\Component\\Cache\\Adapter\\TagAwareAdapterInterface' => $vendorDir . '/symfony/cache/Adapter/TagAwareAdapterInterface.php',
'Symfony\\Component\\Cache\\Adapter\\TraceableTagAwareAdapter' => $vendorDir . '/symfony/cache/Adapter/TraceableTagAwareAdapter.php',
'Symfony\\Component\\Cache\\CacheItem' => $vendorDir . '/symfony/cache/CacheItem.php',
'Symfony\\Component\\Cache\\DataCollector\\CacheDataCollector' => $vendorDir . '/symfony/cache/DataCollector/CacheDataCollector.php',
+ 'Symfony\\Component\\Cache\\DependencyInjection\\CacheCollectorPass' => $vendorDir . '/symfony/cache/DependencyInjection/CacheCollectorPass.php',
+ 'Symfony\\Component\\Cache\\DependencyInjection\\CachePoolClearerPass' => $vendorDir . '/symfony/cache/DependencyInjection/CachePoolClearerPass.php',
+ 'Symfony\\Component\\Cache\\DependencyInjection\\CachePoolPass' => $vendorDir . '/symfony/cache/DependencyInjection/CachePoolPass.php',
+ 'Symfony\\Component\\Cache\\DependencyInjection\\CachePoolPrunerPass' => $vendorDir . '/symfony/cache/DependencyInjection/CachePoolPrunerPass.php',
'Symfony\\Component\\Cache\\DoctrineProvider' => $vendorDir . '/symfony/cache/DoctrineProvider.php',
'Symfony\\Component\\Cache\\Exception\\CacheException' => $vendorDir . '/symfony/cache/Exception/CacheException.php',
'Symfony\\Component\\Cache\\Exception\\InvalidArgumentException' => $vendorDir . '/symfony/cache/Exception/InvalidArgumentException.php',
+ 'Symfony\\Component\\Cache\\Exception\\LogicException' => $vendorDir . '/symfony/cache/Exception/LogicException.php',
+ 'Symfony\\Component\\Cache\\LockRegistry' => $vendorDir . '/symfony/cache/LockRegistry.php',
+ 'Symfony\\Component\\Cache\\Marshaller\\DefaultMarshaller' => $vendorDir . '/symfony/cache/Marshaller/DefaultMarshaller.php',
+ 'Symfony\\Component\\Cache\\Marshaller\\DeflateMarshaller' => $vendorDir . '/symfony/cache/Marshaller/DeflateMarshaller.php',
+ 'Symfony\\Component\\Cache\\Marshaller\\MarshallerInterface' => $vendorDir . '/symfony/cache/Marshaller/MarshallerInterface.php',
+ 'Symfony\\Component\\Cache\\Marshaller\\TagAwareMarshaller' => $vendorDir . '/symfony/cache/Marshaller/TagAwareMarshaller.php',
'Symfony\\Component\\Cache\\PruneableInterface' => $vendorDir . '/symfony/cache/PruneableInterface.php',
+ 'Symfony\\Component\\Cache\\Psr16Cache' => $vendorDir . '/symfony/cache/Psr16Cache.php',
'Symfony\\Component\\Cache\\ResettableInterface' => $vendorDir . '/symfony/cache/ResettableInterface.php',
'Symfony\\Component\\Cache\\Simple\\AbstractCache' => $vendorDir . '/symfony/cache/Simple/AbstractCache.php',
'Symfony\\Component\\Cache\\Simple\\ApcuCache' => $vendorDir . '/symfony/cache/Simple/ApcuCache.php',
'Symfony\\Component\\Cache\\Simple\\RedisCache' => $vendorDir . '/symfony/cache/Simple/RedisCache.php',
'Symfony\\Component\\Cache\\Simple\\TraceableCache' => $vendorDir . '/symfony/cache/Simple/TraceableCache.php',
'Symfony\\Component\\Cache\\Simple\\TraceableCacheEvent' => $vendorDir . '/symfony/cache/Simple/TraceableCache.php',
+ 'Symfony\\Component\\Cache\\Traits\\AbstractAdapterTrait' => $vendorDir . '/symfony/cache/Traits/AbstractAdapterTrait.php',
'Symfony\\Component\\Cache\\Traits\\AbstractTrait' => $vendorDir . '/symfony/cache/Traits/AbstractTrait.php',
'Symfony\\Component\\Cache\\Traits\\ApcuTrait' => $vendorDir . '/symfony/cache/Traits/ApcuTrait.php',
'Symfony\\Component\\Cache\\Traits\\ArrayTrait' => $vendorDir . '/symfony/cache/Traits/ArrayTrait.php',
+ 'Symfony\\Component\\Cache\\Traits\\ContractsTrait' => $vendorDir . '/symfony/cache/Traits/ContractsTrait.php',
'Symfony\\Component\\Cache\\Traits\\DoctrineTrait' => $vendorDir . '/symfony/cache/Traits/DoctrineTrait.php',
'Symfony\\Component\\Cache\\Traits\\FilesystemCommonTrait' => $vendorDir . '/symfony/cache/Traits/FilesystemCommonTrait.php',
'Symfony\\Component\\Cache\\Traits\\FilesystemTrait' => $vendorDir . '/symfony/cache/Traits/FilesystemTrait.php',
+ 'Symfony\\Component\\Cache\\Traits\\LazyValue' => $vendorDir . '/symfony/cache/Traits/PhpFilesTrait.php',
'Symfony\\Component\\Cache\\Traits\\MemcachedTrait' => $vendorDir . '/symfony/cache/Traits/MemcachedTrait.php',
'Symfony\\Component\\Cache\\Traits\\PdoTrait' => $vendorDir . '/symfony/cache/Traits/PdoTrait.php',
'Symfony\\Component\\Cache\\Traits\\PhpArrayTrait' => $vendorDir . '/symfony/cache/Traits/PhpArrayTrait.php',
'Symfony\\Component\\Cache\\Traits\\PhpFilesTrait' => $vendorDir . '/symfony/cache/Traits/PhpFilesTrait.php',
'Symfony\\Component\\Cache\\Traits\\ProxyTrait' => $vendorDir . '/symfony/cache/Traits/ProxyTrait.php',
+ 'Symfony\\Component\\Cache\\Traits\\RedisClusterNodeProxy' => $vendorDir . '/symfony/cache/Traits/RedisClusterNodeProxy.php',
+ 'Symfony\\Component\\Cache\\Traits\\RedisClusterProxy' => $vendorDir . '/symfony/cache/Traits/RedisClusterProxy.php',
'Symfony\\Component\\Cache\\Traits\\RedisProxy' => $vendorDir . '/symfony/cache/Traits/RedisProxy.php',
'Symfony\\Component\\Cache\\Traits\\RedisTrait' => $vendorDir . '/symfony/cache/Traits/RedisTrait.php',
'Symfony\\Component\\ExpressionLanguage\\Compiler' => $vendorDir . '/symfony/expression-language/Compiler.php',
'Symfony\\Component\\ExpressionLanguage\\SyntaxError' => $vendorDir . '/symfony/expression-language/SyntaxError.php',
'Symfony\\Component\\ExpressionLanguage\\Token' => $vendorDir . '/symfony/expression-language/Token.php',
'Symfony\\Component\\ExpressionLanguage\\TokenStream' => $vendorDir . '/symfony/expression-language/TokenStream.php',
- 'Symfony\\Polyfill\\Apcu\\Apcu' => $vendorDir . '/symfony/polyfill-apcu/Apcu.php',
+ 'Symfony\\Component\\VarExporter\\Exception\\ClassNotFoundException' => $vendorDir . '/symfony/var-exporter/Exception/ClassNotFoundException.php',
+ 'Symfony\\Component\\VarExporter\\Exception\\ExceptionInterface' => $vendorDir . '/symfony/var-exporter/Exception/ExceptionInterface.php',
+ 'Symfony\\Component\\VarExporter\\Exception\\NotInstantiableTypeException' => $vendorDir . '/symfony/var-exporter/Exception/NotInstantiableTypeException.php',
+ 'Symfony\\Component\\VarExporter\\Instantiator' => $vendorDir . '/symfony/var-exporter/Instantiator.php',
+ 'Symfony\\Component\\VarExporter\\Internal\\Exporter' => $vendorDir . '/symfony/var-exporter/Internal/Exporter.php',
+ 'Symfony\\Component\\VarExporter\\Internal\\Hydrator' => $vendorDir . '/symfony/var-exporter/Internal/Hydrator.php',
+ 'Symfony\\Component\\VarExporter\\Internal\\Reference' => $vendorDir . '/symfony/var-exporter/Internal/Reference.php',
+ 'Symfony\\Component\\VarExporter\\Internal\\Registry' => $vendorDir . '/symfony/var-exporter/Internal/Registry.php',
+ 'Symfony\\Component\\VarExporter\\Internal\\Values' => $vendorDir . '/symfony/var-exporter/Internal/Values.php',
+ 'Symfony\\Component\\VarExporter\\VarExporter' => $vendorDir . '/symfony/var-exporter/VarExporter.php',
+ 'Symfony\\Contracts\\Cache\\CacheInterface' => $vendorDir . '/symfony/cache-contracts/CacheInterface.php',
+ 'Symfony\\Contracts\\Cache\\CacheTrait' => $vendorDir . '/symfony/cache-contracts/CacheTrait.php',
+ 'Symfony\\Contracts\\Cache\\CallbackInterface' => $vendorDir . '/symfony/cache-contracts/CallbackInterface.php',
+ 'Symfony\\Contracts\\Cache\\ItemInterface' => $vendorDir . '/symfony/cache-contracts/ItemInterface.php',
+ 'Symfony\\Contracts\\Cache\\TagAwareCacheInterface' => $vendorDir . '/symfony/cache-contracts/TagAwareCacheInterface.php',
+ 'Symfony\\Contracts\\Service\\Attribute\\Required' => $vendorDir . '/symfony/service-contracts/Attribute/Required.php',
+ 'Symfony\\Contracts\\Service\\Attribute\\SubscribedService' => $vendorDir . '/symfony/service-contracts/Attribute/SubscribedService.php',
+ 'Symfony\\Contracts\\Service\\ResetInterface' => $vendorDir . '/symfony/service-contracts/ResetInterface.php',
+ 'Symfony\\Contracts\\Service\\ServiceLocatorTrait' => $vendorDir . '/symfony/service-contracts/ServiceLocatorTrait.php',
+ 'Symfony\\Contracts\\Service\\ServiceProviderInterface' => $vendorDir . '/symfony/service-contracts/ServiceProviderInterface.php',
+ 'Symfony\\Contracts\\Service\\ServiceSubscriberInterface' => $vendorDir . '/symfony/service-contracts/ServiceSubscriberInterface.php',
+ 'Symfony\\Contracts\\Service\\ServiceSubscriberTrait' => $vendorDir . '/symfony/service-contracts/ServiceSubscriberTrait.php',
+ 'Symfony\\Contracts\\Service\\Test\\ServiceLocatorTest' => $vendorDir . '/symfony/service-contracts/Test/ServiceLocatorTest.php',
+ 'Symfony\\Polyfill\\Php73\\Php73' => $vendorDir . '/symfony/polyfill-php73/Php73.php',
+ 'Symfony\\Polyfill\\Php80\\Php80' => $vendorDir . '/symfony/polyfill-php80/Php80.php',
+ 'Symfony\\Polyfill\\Php80\\PhpToken' => $vendorDir . '/symfony/polyfill-php80/PhpToken.php',
+ 'UnhandledMatchError' => $vendorDir . '/symfony/polyfill-php80/Resources/stubs/UnhandledMatchError.php',
+ 'ValueError' => $vendorDir . '/symfony/polyfill-php80/Resources/stubs/ValueError.php',
);
$baseDir = dirname($vendorDir);
return array(
- '32dcc8afd4335739640db7d200c1971d' => $vendorDir . '/symfony/polyfill-apcu/bootstrap.php',
+ 'a4a119a56e50fbb293281d9a48007e0e' => $vendorDir . '/symfony/polyfill-php80/bootstrap.php',
+ '6e3fae29631ef280660b3cdad06f25a8' => $vendorDir . '/symfony/deprecation-contracts/function.php',
+ '0d59ee240a4cd96ddbb4ff164fccea4d' => $vendorDir . '/symfony/polyfill-php73/bootstrap.php',
'253c157292f75eb38082b5acb06f3f01' => $vendorDir . '/nikic/fast-route/src/functions.php',
);
$baseDir = dirname($vendorDir);
return array(
- 'Symfony\\Polyfill\\Apcu\\' => array($vendorDir . '/symfony/polyfill-apcu'),
+ 'Symfony\\Polyfill\\Php80\\' => array($vendorDir . '/symfony/polyfill-php80'),
+ 'Symfony\\Polyfill\\Php73\\' => array($vendorDir . '/symfony/polyfill-php73'),
+ 'Symfony\\Contracts\\Service\\' => 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'),
'Slim\\' => array($vendorDir . '/slim/slim/Slim'),
- 'Psr\\SimpleCache\\' => array($vendorDir . '/psr/simple-cache/src'),
'Psr\\Log\\' => array($vendorDir . '/psr/log/Psr/Log'),
'Psr\\Http\\Server\\' => array($vendorDir . '/psr/http-server-handler/src', $vendorDir . '/psr/http-server-middleware/src'),
'Psr\\Http\\Message\\' => array($vendorDir . '/psr/http-factory/src', $vendorDir . '/psr/http-message/src'),
class ComposerStaticInitAdvancedContentFilterAddon
{
public static $files = array (
- '32dcc8afd4335739640db7d200c1971d' => __DIR__ . '/..' . '/symfony/polyfill-apcu/bootstrap.php',
+ 'a4a119a56e50fbb293281d9a48007e0e' => __DIR__ . '/..' . '/symfony/polyfill-php80/bootstrap.php',
+ '6e3fae29631ef280660b3cdad06f25a8' => __DIR__ . '/..' . '/symfony/deprecation-contracts/function.php',
+ '0d59ee240a4cd96ddbb4ff164fccea4d' => __DIR__ . '/..' . '/symfony/polyfill-php73/bootstrap.php',
'253c157292f75eb38082b5acb06f3f01' => __DIR__ . '/..' . '/nikic/fast-route/src/functions.php',
);
public static $prefixLengthsPsr4 = array (
'S' =>
array (
- 'Symfony\\Polyfill\\Apcu\\' => 22,
+ 'Symfony\\Polyfill\\Php80\\' => 23,
+ 'Symfony\\Polyfill\\Php73\\' => 23,
+ 'Symfony\\Contracts\\Service\\' => 26,
+ 'Symfony\\Contracts\\Cache\\' => 24,
+ 'Symfony\\Component\\VarExporter\\' => 30,
'Symfony\\Component\\ExpressionLanguage\\' => 37,
'Symfony\\Component\\Cache\\' => 24,
'Slim\\' => 5,
),
'P' =>
array (
- 'Psr\\SimpleCache\\' => 16,
'Psr\\Log\\' => 8,
'Psr\\Http\\Server\\' => 16,
'Psr\\Http\\Message\\' => 17,
);
public static $prefixDirsPsr4 = array (
- 'Symfony\\Polyfill\\Apcu\\' =>
+ 'Symfony\\Polyfill\\Php80\\' =>
array (
- 0 => __DIR__ . '/..' . '/symfony/polyfill-apcu',
+ 0 => __DIR__ . '/..' . '/symfony/polyfill-php80',
+ ),
+ 'Symfony\\Polyfill\\Php73\\' =>
+ array (
+ 0 => __DIR__ . '/..' . '/symfony/polyfill-php73',
+ ),
+ '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 (
array (
0 => __DIR__ . '/..' . '/slim/slim/Slim',
),
- 'Psr\\SimpleCache\\' =>
- array (
- 0 => __DIR__ . '/..' . '/psr/simple-cache/src',
- ),
'Psr\\Log\\' =>
array (
0 => __DIR__ . '/..' . '/psr/log/Psr/Log',
);
public static $classMap = array (
+ 'Attribute' => __DIR__ . '/..' . '/symfony/polyfill-php80/Resources/stubs/Attribute.php',
'FastRoute\\BadRouteException' => __DIR__ . '/..' . '/nikic/fast-route/src/BadRouteException.php',
'FastRoute\\DataGenerator' => __DIR__ . '/..' . '/nikic/fast-route/src/DataGenerator.php',
'FastRoute\\DataGenerator\\CharCountBased' => __DIR__ . '/..' . '/nikic/fast-route/src/DataGenerator/CharCountBased.php',
'FastRoute\\RouteCollector' => __DIR__ . '/..' . '/nikic/fast-route/src/RouteCollector.php',
'FastRoute\\RouteParser' => __DIR__ . '/..' . '/nikic/fast-route/src/RouteParser.php',
'FastRoute\\RouteParser\\Std' => __DIR__ . '/..' . '/nikic/fast-route/src/RouteParser/Std.php',
+ 'JsonException' => __DIR__ . '/..' . '/symfony/polyfill-php73/Resources/stubs/JsonException.php',
+ 'PhpToken' => __DIR__ . '/..' . '/symfony/polyfill-php80/Resources/stubs/PhpToken.php',
'Psr\\Cache\\CacheException' => __DIR__ . '/..' . '/psr/cache/src/CacheException.php',
'Psr\\Cache\\CacheItemInterface' => __DIR__ . '/..' . '/psr/cache/src/CacheItemInterface.php',
'Psr\\Cache\\CacheItemPoolInterface' => __DIR__ . '/..' . '/psr/cache/src/CacheItemPoolInterface.php',
'Psr\\Log\\Test\\DummyTest' => __DIR__ . '/..' . '/psr/log/Psr/Log/Test/DummyTest.php',
'Psr\\Log\\Test\\LoggerInterfaceTest' => __DIR__ . '/..' . '/psr/log/Psr/Log/Test/LoggerInterfaceTest.php',
'Psr\\Log\\Test\\TestLogger' => __DIR__ . '/..' . '/psr/log/Psr/Log/Test/TestLogger.php',
- 'Psr\\SimpleCache\\CacheException' => __DIR__ . '/..' . '/psr/simple-cache/src/CacheException.php',
- 'Psr\\SimpleCache\\CacheInterface' => __DIR__ . '/..' . '/psr/simple-cache/src/CacheInterface.php',
- 'Psr\\SimpleCache\\InvalidArgumentException' => __DIR__ . '/..' . '/psr/simple-cache/src/InvalidArgumentException.php',
'Slim\\App' => __DIR__ . '/..' . '/slim/slim/Slim/App.php',
'Slim\\CallableResolver' => __DIR__ . '/..' . '/slim/slim/Slim/CallableResolver.php',
'Slim\\Error\\AbstractErrorRenderer' => __DIR__ . '/..' . '/slim/slim/Slim/Error/AbstractErrorRenderer.php',
'Slim\\Exception\\HttpNotFoundException' => __DIR__ . '/..' . '/slim/slim/Slim/Exception/HttpNotFoundException.php',
'Slim\\Exception\\HttpNotImplementedException' => __DIR__ . '/..' . '/slim/slim/Slim/Exception/HttpNotImplementedException.php',
'Slim\\Exception\\HttpSpecializedException' => __DIR__ . '/..' . '/slim/slim/Slim/Exception/HttpSpecializedException.php',
+ 'Slim\\Exception\\HttpTooManyRequestsException' => __DIR__ . '/..' . '/slim/slim/Slim/Exception/HttpTooManyRequestsException.php',
'Slim\\Exception\\HttpUnauthorizedException' => __DIR__ . '/..' . '/slim/slim/Slim/Exception/HttpUnauthorizedException.php',
'Slim\\Factory\\AppFactory' => __DIR__ . '/..' . '/slim/slim/Slim/Factory/AppFactory.php',
'Slim\\Factory\\Psr17\\GuzzlePsr17Factory' => __DIR__ . '/..' . '/slim/slim/Slim/Factory/Psr17/GuzzlePsr17Factory.php',
'Slim\\Routing\\RouteResolver' => __DIR__ . '/..' . '/slim/slim/Slim/Routing/RouteResolver.php',
'Slim\\Routing\\RouteRunner' => __DIR__ . '/..' . '/slim/slim/Slim/Routing/RouteRunner.php',
'Slim\\Routing\\RoutingResults' => __DIR__ . '/..' . '/slim/slim/Slim/Routing/RoutingResults.php',
+ 'Stringable' => __DIR__ . '/..' . '/symfony/polyfill-php80/Resources/stubs/Stringable.php',
'Symfony\\Component\\Cache\\Adapter\\AbstractAdapter' => __DIR__ . '/..' . '/symfony/cache/Adapter/AbstractAdapter.php',
+ 'Symfony\\Component\\Cache\\Adapter\\AbstractTagAwareAdapter' => __DIR__ . '/..' . '/symfony/cache/Adapter/AbstractTagAwareAdapter.php',
'Symfony\\Component\\Cache\\Adapter\\AdapterInterface' => __DIR__ . '/..' . '/symfony/cache/Adapter/AdapterInterface.php',
'Symfony\\Component\\Cache\\Adapter\\ApcuAdapter' => __DIR__ . '/..' . '/symfony/cache/Adapter/ApcuAdapter.php',
'Symfony\\Component\\Cache\\Adapter\\ArrayAdapter' => __DIR__ . '/..' . '/symfony/cache/Adapter/ArrayAdapter.php',
'Symfony\\Component\\Cache\\Adapter\\ChainAdapter' => __DIR__ . '/..' . '/symfony/cache/Adapter/ChainAdapter.php',
'Symfony\\Component\\Cache\\Adapter\\DoctrineAdapter' => __DIR__ . '/..' . '/symfony/cache/Adapter/DoctrineAdapter.php',
'Symfony\\Component\\Cache\\Adapter\\FilesystemAdapter' => __DIR__ . '/..' . '/symfony/cache/Adapter/FilesystemAdapter.php',
+ 'Symfony\\Component\\Cache\\Adapter\\FilesystemTagAwareAdapter' => __DIR__ . '/..' . '/symfony/cache/Adapter/FilesystemTagAwareAdapter.php',
'Symfony\\Component\\Cache\\Adapter\\MemcachedAdapter' => __DIR__ . '/..' . '/symfony/cache/Adapter/MemcachedAdapter.php',
'Symfony\\Component\\Cache\\Adapter\\NullAdapter' => __DIR__ . '/..' . '/symfony/cache/Adapter/NullAdapter.php',
'Symfony\\Component\\Cache\\Adapter\\PdoAdapter' => __DIR__ . '/..' . '/symfony/cache/Adapter/PdoAdapter.php',
'Symfony\\Component\\Cache\\Adapter\\PhpArrayAdapter' => __DIR__ . '/..' . '/symfony/cache/Adapter/PhpArrayAdapter.php',
'Symfony\\Component\\Cache\\Adapter\\PhpFilesAdapter' => __DIR__ . '/..' . '/symfony/cache/Adapter/PhpFilesAdapter.php',
'Symfony\\Component\\Cache\\Adapter\\ProxyAdapter' => __DIR__ . '/..' . '/symfony/cache/Adapter/ProxyAdapter.php',
+ 'Symfony\\Component\\Cache\\Adapter\\Psr16Adapter' => __DIR__ . '/..' . '/symfony/cache/Adapter/Psr16Adapter.php',
'Symfony\\Component\\Cache\\Adapter\\RedisAdapter' => __DIR__ . '/..' . '/symfony/cache/Adapter/RedisAdapter.php',
+ 'Symfony\\Component\\Cache\\Adapter\\RedisTagAwareAdapter' => __DIR__ . '/..' . '/symfony/cache/Adapter/RedisTagAwareAdapter.php',
'Symfony\\Component\\Cache\\Adapter\\SimpleCacheAdapter' => __DIR__ . '/..' . '/symfony/cache/Adapter/SimpleCacheAdapter.php',
'Symfony\\Component\\Cache\\Adapter\\TagAwareAdapter' => __DIR__ . '/..' . '/symfony/cache/Adapter/TagAwareAdapter.php',
'Symfony\\Component\\Cache\\Adapter\\TagAwareAdapterInterface' => __DIR__ . '/..' . '/symfony/cache/Adapter/TagAwareAdapterInterface.php',
'Symfony\\Component\\Cache\\Adapter\\TraceableTagAwareAdapter' => __DIR__ . '/..' . '/symfony/cache/Adapter/TraceableTagAwareAdapter.php',
'Symfony\\Component\\Cache\\CacheItem' => __DIR__ . '/..' . '/symfony/cache/CacheItem.php',
'Symfony\\Component\\Cache\\DataCollector\\CacheDataCollector' => __DIR__ . '/..' . '/symfony/cache/DataCollector/CacheDataCollector.php',
+ 'Symfony\\Component\\Cache\\DependencyInjection\\CacheCollectorPass' => __DIR__ . '/..' . '/symfony/cache/DependencyInjection/CacheCollectorPass.php',
+ 'Symfony\\Component\\Cache\\DependencyInjection\\CachePoolClearerPass' => __DIR__ . '/..' . '/symfony/cache/DependencyInjection/CachePoolClearerPass.php',
+ 'Symfony\\Component\\Cache\\DependencyInjection\\CachePoolPass' => __DIR__ . '/..' . '/symfony/cache/DependencyInjection/CachePoolPass.php',
+ 'Symfony\\Component\\Cache\\DependencyInjection\\CachePoolPrunerPass' => __DIR__ . '/..' . '/symfony/cache/DependencyInjection/CachePoolPrunerPass.php',
'Symfony\\Component\\Cache\\DoctrineProvider' => __DIR__ . '/..' . '/symfony/cache/DoctrineProvider.php',
'Symfony\\Component\\Cache\\Exception\\CacheException' => __DIR__ . '/..' . '/symfony/cache/Exception/CacheException.php',
'Symfony\\Component\\Cache\\Exception\\InvalidArgumentException' => __DIR__ . '/..' . '/symfony/cache/Exception/InvalidArgumentException.php',
+ 'Symfony\\Component\\Cache\\Exception\\LogicException' => __DIR__ . '/..' . '/symfony/cache/Exception/LogicException.php',
+ 'Symfony\\Component\\Cache\\LockRegistry' => __DIR__ . '/..' . '/symfony/cache/LockRegistry.php',
+ 'Symfony\\Component\\Cache\\Marshaller\\DefaultMarshaller' => __DIR__ . '/..' . '/symfony/cache/Marshaller/DefaultMarshaller.php',
+ 'Symfony\\Component\\Cache\\Marshaller\\DeflateMarshaller' => __DIR__ . '/..' . '/symfony/cache/Marshaller/DeflateMarshaller.php',
+ 'Symfony\\Component\\Cache\\Marshaller\\MarshallerInterface' => __DIR__ . '/..' . '/symfony/cache/Marshaller/MarshallerInterface.php',
+ 'Symfony\\Component\\Cache\\Marshaller\\TagAwareMarshaller' => __DIR__ . '/..' . '/symfony/cache/Marshaller/TagAwareMarshaller.php',
'Symfony\\Component\\Cache\\PruneableInterface' => __DIR__ . '/..' . '/symfony/cache/PruneableInterface.php',
+ 'Symfony\\Component\\Cache\\Psr16Cache' => __DIR__ . '/..' . '/symfony/cache/Psr16Cache.php',
'Symfony\\Component\\Cache\\ResettableInterface' => __DIR__ . '/..' . '/symfony/cache/ResettableInterface.php',
'Symfony\\Component\\Cache\\Simple\\AbstractCache' => __DIR__ . '/..' . '/symfony/cache/Simple/AbstractCache.php',
'Symfony\\Component\\Cache\\Simple\\ApcuCache' => __DIR__ . '/..' . '/symfony/cache/Simple/ApcuCache.php',
'Symfony\\Component\\Cache\\Simple\\RedisCache' => __DIR__ . '/..' . '/symfony/cache/Simple/RedisCache.php',
'Symfony\\Component\\Cache\\Simple\\TraceableCache' => __DIR__ . '/..' . '/symfony/cache/Simple/TraceableCache.php',
'Symfony\\Component\\Cache\\Simple\\TraceableCacheEvent' => __DIR__ . '/..' . '/symfony/cache/Simple/TraceableCache.php',
+ 'Symfony\\Component\\Cache\\Traits\\AbstractAdapterTrait' => __DIR__ . '/..' . '/symfony/cache/Traits/AbstractAdapterTrait.php',
'Symfony\\Component\\Cache\\Traits\\AbstractTrait' => __DIR__ . '/..' . '/symfony/cache/Traits/AbstractTrait.php',
'Symfony\\Component\\Cache\\Traits\\ApcuTrait' => __DIR__ . '/..' . '/symfony/cache/Traits/ApcuTrait.php',
'Symfony\\Component\\Cache\\Traits\\ArrayTrait' => __DIR__ . '/..' . '/symfony/cache/Traits/ArrayTrait.php',
+ 'Symfony\\Component\\Cache\\Traits\\ContractsTrait' => __DIR__ . '/..' . '/symfony/cache/Traits/ContractsTrait.php',
'Symfony\\Component\\Cache\\Traits\\DoctrineTrait' => __DIR__ . '/..' . '/symfony/cache/Traits/DoctrineTrait.php',
'Symfony\\Component\\Cache\\Traits\\FilesystemCommonTrait' => __DIR__ . '/..' . '/symfony/cache/Traits/FilesystemCommonTrait.php',
'Symfony\\Component\\Cache\\Traits\\FilesystemTrait' => __DIR__ . '/..' . '/symfony/cache/Traits/FilesystemTrait.php',
+ 'Symfony\\Component\\Cache\\Traits\\LazyValue' => __DIR__ . '/..' . '/symfony/cache/Traits/PhpFilesTrait.php',
'Symfony\\Component\\Cache\\Traits\\MemcachedTrait' => __DIR__ . '/..' . '/symfony/cache/Traits/MemcachedTrait.php',
'Symfony\\Component\\Cache\\Traits\\PdoTrait' => __DIR__ . '/..' . '/symfony/cache/Traits/PdoTrait.php',
'Symfony\\Component\\Cache\\Traits\\PhpArrayTrait' => __DIR__ . '/..' . '/symfony/cache/Traits/PhpArrayTrait.php',
'Symfony\\Component\\Cache\\Traits\\PhpFilesTrait' => __DIR__ . '/..' . '/symfony/cache/Traits/PhpFilesTrait.php',
'Symfony\\Component\\Cache\\Traits\\ProxyTrait' => __DIR__ . '/..' . '/symfony/cache/Traits/ProxyTrait.php',
+ 'Symfony\\Component\\Cache\\Traits\\RedisClusterNodeProxy' => __DIR__ . '/..' . '/symfony/cache/Traits/RedisClusterNodeProxy.php',
+ 'Symfony\\Component\\Cache\\Traits\\RedisClusterProxy' => __DIR__ . '/..' . '/symfony/cache/Traits/RedisClusterProxy.php',
'Symfony\\Component\\Cache\\Traits\\RedisProxy' => __DIR__ . '/..' . '/symfony/cache/Traits/RedisProxy.php',
'Symfony\\Component\\Cache\\Traits\\RedisTrait' => __DIR__ . '/..' . '/symfony/cache/Traits/RedisTrait.php',
'Symfony\\Component\\ExpressionLanguage\\Compiler' => __DIR__ . '/..' . '/symfony/expression-language/Compiler.php',
'Symfony\\Component\\ExpressionLanguage\\SyntaxError' => __DIR__ . '/..' . '/symfony/expression-language/SyntaxError.php',
'Symfony\\Component\\ExpressionLanguage\\Token' => __DIR__ . '/..' . '/symfony/expression-language/Token.php',
'Symfony\\Component\\ExpressionLanguage\\TokenStream' => __DIR__ . '/..' . '/symfony/expression-language/TokenStream.php',
- 'Symfony\\Polyfill\\Apcu\\Apcu' => __DIR__ . '/..' . '/symfony/polyfill-apcu/Apcu.php',
+ 'Symfony\\Component\\VarExporter\\Exception\\ClassNotFoundException' => __DIR__ . '/..' . '/symfony/var-exporter/Exception/ClassNotFoundException.php',
+ 'Symfony\\Component\\VarExporter\\Exception\\ExceptionInterface' => __DIR__ . '/..' . '/symfony/var-exporter/Exception/ExceptionInterface.php',
+ 'Symfony\\Component\\VarExporter\\Exception\\NotInstantiableTypeException' => __DIR__ . '/..' . '/symfony/var-exporter/Exception/NotInstantiableTypeException.php',
+ 'Symfony\\Component\\VarExporter\\Instantiator' => __DIR__ . '/..' . '/symfony/var-exporter/Instantiator.php',
+ 'Symfony\\Component\\VarExporter\\Internal\\Exporter' => __DIR__ . '/..' . '/symfony/var-exporter/Internal/Exporter.php',
+ 'Symfony\\Component\\VarExporter\\Internal\\Hydrator' => __DIR__ . '/..' . '/symfony/var-exporter/Internal/Hydrator.php',
+ 'Symfony\\Component\\VarExporter\\Internal\\Reference' => __DIR__ . '/..' . '/symfony/var-exporter/Internal/Reference.php',
+ 'Symfony\\Component\\VarExporter\\Internal\\Registry' => __DIR__ . '/..' . '/symfony/var-exporter/Internal/Registry.php',
+ 'Symfony\\Component\\VarExporter\\Internal\\Values' => __DIR__ . '/..' . '/symfony/var-exporter/Internal/Values.php',
+ 'Symfony\\Component\\VarExporter\\VarExporter' => __DIR__ . '/..' . '/symfony/var-exporter/VarExporter.php',
+ 'Symfony\\Contracts\\Cache\\CacheInterface' => __DIR__ . '/..' . '/symfony/cache-contracts/CacheInterface.php',
+ 'Symfony\\Contracts\\Cache\\CacheTrait' => __DIR__ . '/..' . '/symfony/cache-contracts/CacheTrait.php',
+ 'Symfony\\Contracts\\Cache\\CallbackInterface' => __DIR__ . '/..' . '/symfony/cache-contracts/CallbackInterface.php',
+ 'Symfony\\Contracts\\Cache\\ItemInterface' => __DIR__ . '/..' . '/symfony/cache-contracts/ItemInterface.php',
+ 'Symfony\\Contracts\\Cache\\TagAwareCacheInterface' => __DIR__ . '/..' . '/symfony/cache-contracts/TagAwareCacheInterface.php',
+ 'Symfony\\Contracts\\Service\\Attribute\\Required' => __DIR__ . '/..' . '/symfony/service-contracts/Attribute/Required.php',
+ 'Symfony\\Contracts\\Service\\Attribute\\SubscribedService' => __DIR__ . '/..' . '/symfony/service-contracts/Attribute/SubscribedService.php',
+ 'Symfony\\Contracts\\Service\\ResetInterface' => __DIR__ . '/..' . '/symfony/service-contracts/ResetInterface.php',
+ 'Symfony\\Contracts\\Service\\ServiceLocatorTrait' => __DIR__ . '/..' . '/symfony/service-contracts/ServiceLocatorTrait.php',
+ 'Symfony\\Contracts\\Service\\ServiceProviderInterface' => __DIR__ . '/..' . '/symfony/service-contracts/ServiceProviderInterface.php',
+ 'Symfony\\Contracts\\Service\\ServiceSubscriberInterface' => __DIR__ . '/..' . '/symfony/service-contracts/ServiceSubscriberInterface.php',
+ 'Symfony\\Contracts\\Service\\ServiceSubscriberTrait' => __DIR__ . '/..' . '/symfony/service-contracts/ServiceSubscriberTrait.php',
+ 'Symfony\\Contracts\\Service\\Test\\ServiceLocatorTest' => __DIR__ . '/..' . '/symfony/service-contracts/Test/ServiceLocatorTest.php',
+ 'Symfony\\Polyfill\\Php73\\Php73' => __DIR__ . '/..' . '/symfony/polyfill-php73/Php73.php',
+ 'Symfony\\Polyfill\\Php80\\Php80' => __DIR__ . '/..' . '/symfony/polyfill-php80/Php80.php',
+ 'Symfony\\Polyfill\\Php80\\PhpToken' => __DIR__ . '/..' . '/symfony/polyfill-php80/PhpToken.php',
+ 'UnhandledMatchError' => __DIR__ . '/..' . '/symfony/polyfill-php80/Resources/stubs/UnhandledMatchError.php',
+ 'ValueError' => __DIR__ . '/..' . '/symfony/polyfill-php80/Resources/stubs/ValueError.php',
);
public static function getInitializer(ClassLoader $loader)
},
{
"name": "psr/container",
- "version": "2.0.2",
- "version_normalized": "2.0.2.0",
+ "version": "1.1.2",
+ "version_normalized": "1.1.2.0",
"source": {
"type": "git",
"url": "https://github.com/php-fig/container.git",
- "reference": "c71ecc56dfe541dbd90c5360474fbc405f8d5963"
+ "reference": "513e0666f7216c7459170d56df27dfcefe1689ea"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/php-fig/container/zipball/c71ecc56dfe541dbd90c5360474fbc405f8d5963",
- "reference": "c71ecc56dfe541dbd90c5360474fbc405f8d5963",
+ "url": "https://api.github.com/repos/php-fig/container/zipball/513e0666f7216c7459170d56df27dfcefe1689ea",
+ "reference": "513e0666f7216c7459170d56df27dfcefe1689ea",
"shasum": ""
},
"require": {
"php": ">=7.4.0"
},
- "time": "2021-11-05T16:47:00+00:00",
+ "time": "2021-11-05T16:50:12+00:00",
"type": "library",
- "extra": {
- "branch-alias": {
- "dev-master": "2.0.x-dev"
- }
- },
"installation-source": "dist",
"autoload": {
"psr-4": {
},
{
"name": "psr/http-message",
- "version": "1.1",
- "version_normalized": "1.1.0.0",
+ "version": "2.0",
+ "version_normalized": "2.0.0.0",
"source": {
"type": "git",
"url": "https://github.com/php-fig/http-message.git",
- "reference": "cb6ce4845ce34a8ad9e68117c10ee90a29919eba"
+ "reference": "402d35bcb92c70c026d1a6a9883f06b2ead23d71"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/php-fig/http-message/zipball/cb6ce4845ce34a8ad9e68117c10ee90a29919eba",
- "reference": "cb6ce4845ce34a8ad9e68117c10ee90a29919eba",
+ "url": "https://api.github.com/repos/php-fig/http-message/zipball/402d35bcb92c70c026d1a6a9883f06b2ead23d71",
+ "reference": "402d35bcb92c70c026d1a6a9883f06b2ead23d71",
"shasum": ""
},
"require": {
"php": "^7.2 || ^8.0"
},
- "time": "2023-04-04T09:50:52+00:00",
+ "time": "2023-04-04T09:54:51+00:00",
"type": "library",
"extra": {
"branch-alias": {
- "dev-master": "1.1.x-dev"
+ "dev-master": "2.0.x-dev"
}
},
"installation-source": "dist",
"authors": [
{
"name": "PHP-FIG",
- "homepage": "http://www.php-fig.org/"
+ "homepage": "https://www.php-fig.org/"
}
],
"description": "Common interface for HTTP messages",
"psr-3"
]
},
- {
- "name": "psr/simple-cache",
- "version": "1.0.1",
- "version_normalized": "1.0.1.0",
- "source": {
- "type": "git",
- "url": "https://github.com/php-fig/simple-cache.git",
- "reference": "408d5eafb83c57f6365a3ca330ff23aa4a5fa39b"
- },
- "dist": {
- "type": "zip",
- "url": "https://api.github.com/repos/php-fig/simple-cache/zipball/408d5eafb83c57f6365a3ca330ff23aa4a5fa39b",
- "reference": "408d5eafb83c57f6365a3ca330ff23aa4a5fa39b",
- "shasum": ""
- },
- "require": {
- "php": ">=5.3.0"
- },
- "time": "2017-10-23T01:57:42+00:00",
- "type": "library",
- "extra": {
- "branch-alias": {
- "dev-master": "1.0.x-dev"
- }
- },
- "installation-source": "dist",
- "autoload": {
- "psr-4": {
- "Psr\\SimpleCache\\": "src/"
- }
- },
- "notification-url": "https://packagist.org/downloads/",
- "license": [
- "MIT"
- ],
- "authors": [
- {
- "name": "PHP-FIG",
- "homepage": "http://www.php-fig.org/"
- }
- ],
- "description": "Common interfaces for simple caching",
- "keywords": [
- "cache",
- "caching",
- "psr",
- "psr-16",
- "simple-cache"
- ]
- },
{
"name": "slim/slim",
- "version": "4.12.0",
- "version_normalized": "4.12.0.0",
+ "version": "4.13.0",
+ "version_normalized": "4.13.0.0",
"source": {
"type": "git",
"url": "https://github.com/slimphp/Slim.git",
- "reference": "e9e99c2b24398b967841c6c4c3048622cc7e2b18"
+ "reference": "038fd5713d5a41636fdff0e8dcceedecdd17fc17"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/slimphp/Slim/zipball/e9e99c2b24398b967841c6c4c3048622cc7e2b18",
- "reference": "e9e99c2b24398b967841c6c4c3048622cc7e2b18",
+ "url": "https://api.github.com/repos/slimphp/Slim/zipball/038fd5713d5a41636fdff0e8dcceedecdd17fc17",
+ "reference": "038fd5713d5a41636fdff0e8dcceedecdd17fc17",
"shasum": ""
},
"require": {
"php": "^7.4 || ^8.0",
"psr/container": "^1.0 || ^2.0",
"psr/http-factory": "^1.0",
- "psr/http-message": "^1.1",
+ "psr/http-message": "^1.1 || ^2.0",
"psr/http-server-handler": "^1.0",
"psr/http-server-middleware": "^1.0",
"psr/log": "^1.1 || ^2.0 || ^3.0"
"require-dev": {
"adriansuter/php-autoload-override": "^1.4",
"ext-simplexml": "*",
- "guzzlehttp/psr7": "^2.5",
+ "guzzlehttp/psr7": "^2.6",
"httpsoft/http-message": "^1.1",
"httpsoft/http-server-request": "^1.1",
- "laminas/laminas-diactoros": "^2.17",
+ "laminas/laminas-diactoros": "^2.17 || ^3",
"nyholm/psr7": "^1.8",
- "nyholm/psr7-server": "^1.0",
- "phpspec/prophecy": "^1.17",
- "phpspec/prophecy-phpunit": "^2.0",
+ "nyholm/psr7-server": "^1.1",
+ "phpspec/prophecy": "^1.19",
+ "phpspec/prophecy-phpunit": "^2.1",
"phpstan/phpstan": "^1.10",
"phpunit/phpunit": "^9.6",
"slim/http": "^1.3",
"slim/psr7": "^1.6",
- "squizlabs/php_codesniffer": "^3.7"
+ "squizlabs/php_codesniffer": "^3.9"
},
"suggest": {
"ext-simplexml": "Needed to support XML format in BodyParsingMiddleware",
"php-di/php-di": "PHP-DI is the recommended container library to be used with Slim",
"slim/psr7": "Slim PSR-7 implementation. See https://www.slimframework.com/docs/v4/start/installation.html for more information."
},
- "time": "2023-07-23T04:54:29+00:00",
+ "time": "2024-03-03T21:25:30+00:00",
"type": "library",
"installation-source": "dist",
"autoload": {
},
{
"name": "symfony/cache",
- "version": "v3.4.47",
- "version_normalized": "3.4.47.0",
+ "version": "v4.4.48",
+ "version_normalized": "4.4.48.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/cache.git",
- "reference": "a7a14c4832760bd1fbd31be2859ffedc9b6ff813"
+ "reference": "3b98ed664887ad197b8ede3da2432787212eb915"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/cache/zipball/a7a14c4832760bd1fbd31be2859ffedc9b6ff813",
- "reference": "a7a14c4832760bd1fbd31be2859ffedc9b6ff813",
+ "url": "https://api.github.com/repos/symfony/cache/zipball/3b98ed664887ad197b8ede3da2432787212eb915",
+ "reference": "3b98ed664887ad197b8ede3da2432787212eb915",
"shasum": ""
},
"require": {
- "php": "^5.5.9|>=7.0.8",
- "psr/cache": "~1.0",
- "psr/log": "~1.0",
- "psr/simple-cache": "^1.0",
- "symfony/polyfill-apcu": "~1.1"
+ "php": ">=7.1.3",
+ "psr/cache": "^1.0|^2.0",
+ "psr/log": "^1|^2|^3",
+ "symfony/cache-contracts": "^1.1.7|^2",
+ "symfony/polyfill-php73": "^1.9",
+ "symfony/polyfill-php80": "^1.16",
+ "symfony/service-contracts": "^1.1|^2",
+ "symfony/var-exporter": "^4.2|^5.0"
},
"conflict": {
- "symfony/var-dumper": "<3.3"
+ "doctrine/dbal": "<2.7",
+ "symfony/dependency-injection": "<3.4",
+ "symfony/http-kernel": "<4.4|>=5.0",
+ "symfony/var-dumper": "<4.4"
},
"provide": {
- "psr/cache-implementation": "1.0",
- "psr/simple-cache-implementation": "1.0"
+ "psr/cache-implementation": "1.0|2.0",
+ "psr/simple-cache-implementation": "1.0|2.0",
+ "symfony/cache-implementation": "1.0|2.0"
},
"require-dev": {
"cache/integration-tests": "dev-master",
- "doctrine/cache": "^1.6",
- "doctrine/dbal": "^2.4|^3.0",
- "predis/predis": "^1.0"
- },
- "time": "2020-10-24T10:57:07+00:00",
+ "doctrine/cache": "^1.6|^2.0",
+ "doctrine/dbal": "^2.7|^3.0",
+ "predis/predis": "^1.1",
+ "psr/simple-cache": "^1.0|^2.0",
+ "symfony/config": "^4.2|^5.0",
+ "symfony/dependency-injection": "^3.4|^4.1|^5.0",
+ "symfony/filesystem": "^4.4|^5.0",
+ "symfony/http-kernel": "^4.4",
+ "symfony/var-dumper": "^4.4|^5.0"
+ },
+ "time": "2022-10-17T20:21:54+00:00",
"type": "library",
"installation-source": "dist",
"autoload": {
"homepage": "https://symfony.com/contributors"
}
],
- "description": "Symfony Cache component with PSR-6, PSR-16, and tags",
+ "description": "Provides extended PSR-6, PSR-16 (and tags) implementations",
"homepage": "https://symfony.com",
"keywords": [
"caching",
}
]
},
+ {
+ "name": "symfony/cache-contracts",
+ "version": "v2.5.2",
+ "version_normalized": "2.5.2.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/cache-contracts.git",
+ "reference": "64be4a7acb83b6f2bf6de9a02cee6dad41277ebc"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/cache-contracts/zipball/64be4a7acb83b6f2bf6de9a02cee6dad41277ebc",
+ "reference": "64be4a7acb83b6f2bf6de9a02cee6dad41277ebc",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=7.2.5",
+ "psr/cache": "^1.0|^2.0|^3.0"
+ },
+ "suggest": {
+ "symfony/cache-implementation": ""
+ },
+ "time": "2022-01-02T09:53:40+00:00",
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-main": "2.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"
+ ],
+ "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"
+ }
+ ]
+ },
+ {
+ "name": "symfony/deprecation-contracts",
+ "version": "v2.5.2",
+ "version_normalized": "2.5.2.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/deprecation-contracts.git",
+ "reference": "e8b495ea28c1d97b5e0c121748d6f9b53d075c66"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/e8b495ea28c1d97b5e0c121748d6f9b53d075c66",
+ "reference": "e8b495ea28c1d97b5e0c121748d6f9b53d075c66",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=7.1"
+ },
+ "time": "2022-01-02T09:53:40+00:00",
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-main": "2.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",
+ "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"
+ }
+ ]
+ },
{
"name": "symfony/expression-language",
"version": "v3.4.47",
]
},
{
- "name": "symfony/polyfill-apcu",
- "version": "v1.28.0",
- "version_normalized": "1.28.0.0",
+ "name": "symfony/polyfill-php70",
+ "version": "v1.20.0",
+ "version_normalized": "1.20.0.0",
"source": {
"type": "git",
- "url": "https://github.com/symfony/polyfill-apcu.git",
- "reference": "c6c2c0f5f4cb0b100c5dfea807ef5cd27bbe9899"
+ "url": "https://github.com/symfony/polyfill-php70.git",
+ "reference": "5f03a781d984aae42cebd18e7912fa80f02ee644"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/polyfill-apcu/zipball/c6c2c0f5f4cb0b100c5dfea807ef5cd27bbe9899",
- "reference": "c6c2c0f5f4cb0b100c5dfea807ef5cd27bbe9899",
+ "url": "https://api.github.com/repos/symfony/polyfill-php70/zipball/5f03a781d984aae42cebd18e7912fa80f02ee644",
+ "reference": "5f03a781d984aae42cebd18e7912fa80f02ee644",
"shasum": ""
},
"require": {
"php": ">=7.1"
},
- "time": "2023-01-26T09:26:14+00:00",
- "type": "library",
+ "time": "2020-10-23T14:02:19+00:00",
+ "type": "metapackage",
"extra": {
"branch-alias": {
- "dev-main": "1.28-dev"
+ "dev-main": "1.20-dev"
+ },
+ "thanks": {
+ "name": "symfony/polyfill",
+ "url": "https://github.com/symfony/polyfill"
+ }
+ },
+ "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": "Symfony polyfill backporting some PHP 7.0+ features to lower PHP versions",
+ "homepage": "https://symfony.com",
+ "keywords": [
+ "compatibility",
+ "polyfill",
+ "portable",
+ "shim"
+ ],
+ "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"
+ }
+ ]
+ },
+ {
+ "name": "symfony/polyfill-php73",
+ "version": "v1.29.0",
+ "version_normalized": "1.29.0.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/polyfill-php73.git",
+ "reference": "21bd091060673a1177ae842c0ef8fe30893114d2"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/polyfill-php73/zipball/21bd091060673a1177ae842c0ef8fe30893114d2",
+ "reference": "21bd091060673a1177ae842c0ef8fe30893114d2",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=7.1"
+ },
+ "time": "2024-01-29T20:11:03+00:00",
+ "type": "library",
+ "extra": {
"thanks": {
"name": "symfony/polyfill",
"url": "https://github.com/symfony/polyfill"
"bootstrap.php"
],
"psr-4": {
- "Symfony\\Polyfill\\Apcu\\": ""
- }
+ "Symfony\\Polyfill\\Php73\\": ""
+ },
+ "classmap": [
+ "Resources/stubs"
+ ]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"homepage": "https://symfony.com/contributors"
}
],
- "description": "Symfony polyfill backporting apcu_* functions to lower PHP versions",
+ "description": "Symfony polyfill backporting some PHP 7.3+ features to lower PHP versions",
"homepage": "https://symfony.com",
"keywords": [
- "apcu",
"compatibility",
"polyfill",
"portable",
]
},
{
- "name": "symfony/polyfill-php70",
- "version": "v1.20.0",
- "version_normalized": "1.20.0.0",
+ "name": "symfony/polyfill-php80",
+ "version": "v1.29.0",
+ "version_normalized": "1.29.0.0",
"source": {
"type": "git",
- "url": "https://github.com/symfony/polyfill-php70.git",
- "reference": "5f03a781d984aae42cebd18e7912fa80f02ee644"
+ "url": "https://github.com/symfony/polyfill-php80.git",
+ "reference": "87b68208d5c1188808dd7839ee1e6c8ec3b02f1b"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/polyfill-php70/zipball/5f03a781d984aae42cebd18e7912fa80f02ee644",
- "reference": "5f03a781d984aae42cebd18e7912fa80f02ee644",
+ "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/87b68208d5c1188808dd7839ee1e6c8ec3b02f1b",
+ "reference": "87b68208d5c1188808dd7839ee1e6c8ec3b02f1b",
"shasum": ""
},
"require": {
"php": ">=7.1"
},
- "time": "2020-10-23T14:02:19+00:00",
- "type": "metapackage",
+ "time": "2024-01-29T20:11:03+00:00",
+ "type": "library",
"extra": {
- "branch-alias": {
- "dev-main": "1.20-dev"
- },
"thanks": {
"name": "symfony/polyfill",
"url": "https://github.com/symfony/polyfill"
}
},
+ "installation-source": "dist",
+ "autoload": {
+ "files": [
+ "bootstrap.php"
+ ],
+ "psr-4": {
+ "Symfony\\Polyfill\\Php80\\": ""
+ },
+ "classmap": [
+ "Resources/stubs"
+ ]
+ },
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
+ {
+ "name": "Ion Bazan",
+ "email": "ion.bazan@gmail.com"
+ },
{
"name": "Nicolas Grekas",
"email": "p@tchwork.com"
"homepage": "https://symfony.com/contributors"
}
],
- "description": "Symfony polyfill backporting some PHP 7.0+ features to lower PHP versions",
+ "description": "Symfony polyfill backporting some PHP 8.0+ features to lower PHP versions",
"homepage": "https://symfony.com",
"keywords": [
"compatibility",
"type": "tidelift"
}
]
+ },
+ {
+ "name": "symfony/service-contracts",
+ "version": "v2.5.2",
+ "version_normalized": "2.5.2.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/service-contracts.git",
+ "reference": "4b426aac47d6427cc1a1d0f7e2ac724627f5966c"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/service-contracts/zipball/4b426aac47d6427cc1a1d0f7e2ac724627f5966c",
+ "reference": "4b426aac47d6427cc1a1d0f7e2ac724627f5966c",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=7.2.5",
+ "psr/container": "^1.1",
+ "symfony/deprecation-contracts": "^2.1|^3"
+ },
+ "conflict": {
+ "ext-psr": "<1.1|>=2"
+ },
+ "suggest": {
+ "symfony/service-implementation": ""
+ },
+ "time": "2022-05-30T19:17:29+00:00",
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-main": "2.5-dev"
+ },
+ "thanks": {
+ "name": "symfony/contracts",
+ "url": "https://github.com/symfony/contracts"
+ }
+ },
+ "installation-source": "dist",
+ "autoload": {
+ "psr-4": {
+ "Symfony\\Contracts\\Service\\": ""
+ }
+ },
+ "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"
+ ],
+ "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"
+ }
+ ]
+ },
+ {
+ "name": "symfony/var-exporter",
+ "version": "v5.4.35",
+ "version_normalized": "5.4.35.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/var-exporter.git",
+ "reference": "abb0a151b62d6b07e816487e20040464af96cae7"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/var-exporter/zipball/abb0a151b62d6b07e816487e20040464af96cae7",
+ "reference": "abb0a151b62d6b07e816487e20040464af96cae7",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=7.2.5",
+ "symfony/polyfill-php80": "^1.16"
+ },
+ "require-dev": {
+ "symfony/var-dumper": "^4.4.9|^5.0.9|^6.0"
+ },
+ "time": "2024-01-23T13:51:25+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",
+ "serialize"
+ ],
+ "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"
+ }
+ ]
}
]
"psr-4": {
"Psr\\Container\\": "src/"
}
- },
- "extra": {
- "branch-alias": {
- "dev-master": "2.0.x-dev"
- }
}
}
*
* @return bool
*/
- public function has(string $id): bool;
+ public function has(string $id);
}
"authors": [
{
"name": "PHP-FIG",
- "homepage": "http://www.php-fig.org/"
+ "homepage": "https://www.php-fig.org/"
}
],
"require": {
},
"extra": {
"branch-alias": {
- "dev-master": "1.1.x-dev"
+ "dev-master": "2.0.x-dev"
}
}
}
<?php
-declare(strict_types=1);
-
namespace Psr\Http\Message;
/**
*
* @return string HTTP protocol version.
*/
- public function getProtocolVersion();
+ public function getProtocolVersion(): string;
/**
* Return an instance with the specified HTTP protocol version.
* @param string $version HTTP protocol version
* @return static
*/
- public function withProtocolVersion(string $version);
+ public function withProtocolVersion(string $version): MessageInterface;
/**
* Retrieves all message header values.
* key MUST be a header name, and each value MUST be an array of strings
* for that header.
*/
- public function getHeaders();
+ public function getHeaders(): array;
/**
* Checks if a header exists by the given case-insensitive name.
* name using a case-insensitive string comparison. Returns false if
* no matching header name is found in the message.
*/
- public function hasHeader(string $name);
+ public function hasHeader(string $name): bool;
/**
* Retrieves a message header value by the given case-insensitive name.
* header. If the header does not appear in the message, this method MUST
* return an empty array.
*/
- public function getHeader(string $name);
+ public function getHeader(string $name): array;
/**
* Retrieves a comma-separated string of the values for a single header.
* concatenated together using a comma. If the header does not appear in
* the message, this method MUST return an empty string.
*/
- public function getHeaderLine(string $name);
+ public function getHeaderLine(string $name): string;
/**
* Return an instance with the provided value replacing the specified header.
* @return static
* @throws \InvalidArgumentException for invalid header names or values.
*/
- public function withHeader(string $name, $value);
+ public function withHeader(string $name, $value): MessageInterface;
/**
* Return an instance with the specified header appended with the given value.
* @return static
* @throws \InvalidArgumentException for invalid header names or values.
*/
- public function withAddedHeader(string $name, $value);
+ public function withAddedHeader(string $name, $value): MessageInterface;
/**
* Return an instance without the specified header.
* @param string $name Case-insensitive header field name to remove.
* @return static
*/
- public function withoutHeader(string $name);
+ public function withoutHeader(string $name): MessageInterface;
/**
* Gets the body of the message.
*
* @return StreamInterface Returns the body as a stream.
*/
- public function getBody();
+ public function getBody(): StreamInterface;
/**
* Return an instance with the specified message body.
* @return static
* @throws \InvalidArgumentException When the body is not valid.
*/
- public function withBody(StreamInterface $body);
+ public function withBody(StreamInterface $body): MessageInterface;
}
<?php
-declare(strict_types=1);
-
namespace Psr\Http\Message;
/**
*
* @return string
*/
- public function getRequestTarget();
+ public function getRequestTarget(): string;
/**
* Return an instance with the specific request-target.
* @param string $requestTarget
* @return static
*/
- public function withRequestTarget(string $requestTarget);
+ public function withRequestTarget(string $requestTarget): RequestInterface;
+
/**
* Retrieves the HTTP method of the request.
*
* @return string Returns the request method.
*/
- public function getMethod();
+ public function getMethod(): string;
/**
* Return an instance with the provided HTTP method.
* @return static
* @throws \InvalidArgumentException for invalid HTTP methods.
*/
- public function withMethod(string $method);
+ public function withMethod(string $method): RequestInterface;
/**
* Retrieves the URI instance.
* @return UriInterface Returns a UriInterface instance
* representing the URI of the request.
*/
- public function getUri();
+ public function getUri(): UriInterface;
/**
* Returns an instance with the provided URI.
* @param bool $preserveHost Preserve the original state of the Host header.
* @return static
*/
- public function withUri(UriInterface $uri, bool $preserveHost = false);
+ public function withUri(UriInterface $uri, bool $preserveHost = false): RequestInterface;
}
<?php
-declare(strict_types=1);
-
namespace Psr\Http\Message;
/**
*
* @return int Status code.
*/
- public function getStatusCode();
+ public function getStatusCode(): int;
/**
* Return an instance with the specified status code and, optionally, reason phrase.
* @return static
* @throws \InvalidArgumentException For invalid status code arguments.
*/
- public function withStatus(int $code, string $reasonPhrase = '');
+ public function withStatus(int $code, string $reasonPhrase = ''): ResponseInterface;
/**
* Gets the response reason phrase associated with the status code.
* @link http://www.iana.org/assignments/http-status-codes/http-status-codes.xhtml
* @return string Reason phrase; must return an empty string if none present.
*/
- public function getReasonPhrase();
+ public function getReasonPhrase(): string;
}
<?php
-declare(strict_types=1);
-
namespace Psr\Http\Message;
/**
*
* @return array
*/
- public function getServerParams();
+ public function getServerParams(): array;
/**
* Retrieve cookies.
*
* @return array
*/
- public function getCookieParams();
+ public function getCookieParams(): array;
/**
* Return an instance with the specified cookies.
* @param array $cookies Array of key/value pairs representing cookies.
* @return static
*/
- public function withCookieParams(array $cookies);
+ public function withCookieParams(array $cookies): ServerRequestInterface;
/**
* Retrieve query string arguments.
*
* @return array
*/
- public function getQueryParams();
+ public function getQueryParams(): array;
/**
* Return an instance with the specified query string arguments.
* $_GET.
* @return static
*/
- public function withQueryParams(array $query);
+ public function withQueryParams(array $query): ServerRequestInterface;
/**
* Retrieve normalized file upload data.
* @return array An array tree of UploadedFileInterface instances; an empty
* array MUST be returned if no data is present.
*/
- public function getUploadedFiles();
+ public function getUploadedFiles(): array;
/**
* Create a new instance with the specified uploaded files.
* @return static
* @throws \InvalidArgumentException if an invalid structure is provided.
*/
- public function withUploadedFiles(array $uploadedFiles);
+ public function withUploadedFiles(array $uploadedFiles): ServerRequestInterface;
/**
* Retrieve any parameters provided in the request body.
* @throws \InvalidArgumentException if an unsupported argument type is
* provided.
*/
- public function withParsedBody($data);
+ public function withParsedBody($data): ServerRequestInterface;
/**
* Retrieve attributes derived from the request.
*
* @return array Attributes derived from the request.
*/
- public function getAttributes();
+ public function getAttributes(): array;
/**
* Retrieve a single derived request attribute.
* @param mixed $value The value of the attribute.
* @return static
*/
- public function withAttribute(string $name, $value);
+ public function withAttribute(string $name, $value): ServerRequestInterface;
/**
* Return an instance that removes the specified derived request attribute.
* @param string $name The attribute name.
* @return static
*/
- public function withoutAttribute(string $name);
+ public function withoutAttribute(string $name): ServerRequestInterface;
}
<?php
-declare(strict_types=1);
-
namespace Psr\Http\Message;
/**
* @see http://php.net/manual/en/language.oop5.magic.php#object.tostring
* @return string
*/
- public function __toString();
+ public function __toString(): string;
/**
* Closes the stream and any underlying resources.
*
* @return void
*/
- public function close();
+ public function close(): void;
/**
* Separates any underlying resources from the stream.
*
* @return int|null Returns the size in bytes if known, or null if unknown.
*/
- public function getSize();
+ public function getSize(): ?int;
/**
* Returns the current position of the file read/write pointer
* @return int Position of the file pointer
* @throws \RuntimeException on error.
*/
- public function tell();
+ public function tell(): int;
/**
* Returns true if the stream is at the end of the stream.
*
* @return bool
*/
- public function eof();
+ public function eof(): bool;
/**
* Returns whether or not the stream is seekable.
*
* @return bool
*/
- public function isSeekable();
+ public function isSeekable(): bool;
/**
* Seek to a position in the stream.
* SEEK_END: Set position to end-of-stream plus offset.
* @throws \RuntimeException on failure.
*/
- public function seek(int $offset, int $whence = SEEK_SET);
+ public function seek(int $offset, int $whence = SEEK_SET): void;
/**
* Seek to the beginning of the stream.
* @link http://www.php.net/manual/en/function.fseek.php
* @throws \RuntimeException on failure.
*/
- public function rewind();
+ public function rewind(): void;
/**
* Returns whether or not the stream is writable.
*
* @return bool
*/
- public function isWritable();
+ public function isWritable(): bool;
/**
* Write data to the stream.
* @return int Returns the number of bytes written to the stream.
* @throws \RuntimeException on failure.
*/
- public function write(string $string);
+ public function write(string $string): int;
/**
* Returns whether or not the stream is readable.
*
* @return bool
*/
- public function isReadable();
+ public function isReadable(): bool;
/**
* Read data from the stream.
* if no bytes are available.
* @throws \RuntimeException if an error occurs.
*/
- public function read(int $length);
+ public function read(int $length): string;
/**
* Returns the remaining contents in a string
* @throws \RuntimeException if unable to read or an error occurs while
* reading.
*/
- public function getContents();
+ public function getContents(): string;
/**
* Get stream metadata as an associative array or retrieve a specific key.
<?php
-declare(strict_types=1);
-
namespace Psr\Http\Message;
/**
* @throws \RuntimeException in cases when no stream is available or can be
* created.
*/
- public function getStream();
+ public function getStream(): StreamInterface;
/**
* Move the uploaded file to a new location.
* @throws \RuntimeException on any error during the move operation, or on
* the second or subsequent call to the method.
*/
- public function moveTo(string $targetPath);
+ public function moveTo(string $targetPath): void;
/**
* Retrieve the file size.
*
* @return int|null The file size in bytes or null if unknown.
*/
- public function getSize();
+ public function getSize(): ?int;
/**
* Retrieve the error associated with the uploaded file.
* @see http://php.net/manual/en/features.file-upload.errors.php
* @return int One of PHP's UPLOAD_ERR_XXX constants.
*/
- public function getError();
+ public function getError(): int;
/**
* Retrieve the filename sent by the client.
* @return string|null The filename sent by the client or null if none
* was provided.
*/
- public function getClientFilename();
+ public function getClientFilename(): ?string;
/**
* Retrieve the media type sent by the client.
* @return string|null The media type sent by the client or null if none
* was provided.
*/
- public function getClientMediaType();
+ public function getClientMediaType(): ?string;
}
<?php
-declare(strict_types=1);
-
namespace Psr\Http\Message;
/**
* @see https://tools.ietf.org/html/rfc3986#section-3.1
* @return string The URI scheme.
*/
- public function getScheme();
+ public function getScheme(): string;
/**
* Retrieve the authority component of the URI.
* @see https://tools.ietf.org/html/rfc3986#section-3.2
* @return string The URI authority, in "[user-info@]host[:port]" format.
*/
- public function getAuthority();
+ public function getAuthority(): string;
/**
* Retrieve the user information component of the URI.
*
* @return string The URI user information, in "username[:password]" format.
*/
- public function getUserInfo();
+ public function getUserInfo(): string;
/**
* Retrieve the host component of the URI.
* @see http://tools.ietf.org/html/rfc3986#section-3.2.2
* @return string The URI host.
*/
- public function getHost();
+ public function getHost(): string;
/**
* Retrieve the port component of the URI.
*
* @return null|int The URI port.
*/
- public function getPort();
+ public function getPort(): ?int;
/**
* Retrieve the path component of the URI.
* @see https://tools.ietf.org/html/rfc3986#section-3.3
* @return string The URI path.
*/
- public function getPath();
+ public function getPath(): string;
/**
* Retrieve the query string of the URI.
* @see https://tools.ietf.org/html/rfc3986#section-3.4
* @return string The URI query string.
*/
- public function getQuery();
+ public function getQuery(): string;
/**
* Retrieve the fragment component of the URI.
* @see https://tools.ietf.org/html/rfc3986#section-3.5
* @return string The URI fragment.
*/
- public function getFragment();
+ public function getFragment(): string;
/**
* Return an instance with the specified scheme.
* @return static A new instance with the specified scheme.
* @throws \InvalidArgumentException for invalid or unsupported schemes.
*/
- public function withScheme(string $scheme);
+ public function withScheme(string $scheme): UriInterface;
/**
* Return an instance with the specified user information.
* @param null|string $password The password associated with $user.
* @return static A new instance with the specified user information.
*/
- public function withUserInfo(string $user, ?string $password = null);
+ public function withUserInfo(string $user, ?string $password = null): UriInterface;
/**
* Return an instance with the specified host.
* @return static A new instance with the specified host.
* @throws \InvalidArgumentException for invalid hostnames.
*/
- public function withHost(string $host);
+ public function withHost(string $host): UriInterface;
/**
* Return an instance with the specified port.
* @return static A new instance with the specified port.
* @throws \InvalidArgumentException for invalid ports.
*/
- public function withPort(?int $port);
+ public function withPort(?int $port): UriInterface;
/**
* Return an instance with the specified path.
* @return static A new instance with the specified path.
* @throws \InvalidArgumentException for invalid paths.
*/
- public function withPath(string $path);
+ public function withPath(string $path): UriInterface;
/**
* Return an instance with the specified query string.
* @return static A new instance with the specified query string.
* @throws \InvalidArgumentException for invalid query strings.
*/
- public function withQuery(string $query);
+ public function withQuery(string $query): UriInterface;
/**
* Return an instance with the specified URI fragment.
* @param string $fragment The fragment to use with the new instance.
* @return static A new instance with the specified fragment.
*/
- public function withFragment(string $fragment);
+ public function withFragment(string $fragment): UriInterface;
/**
* Return the string representation as a URI reference.
* @see http://tools.ietf.org/html/rfc3986#section-4.1
* @return string
*/
- public function __toString();
+ public function __toString(): string;
}
--- /dev/null
+<?php
+
+/**
+ * Slim Framework (https://slimframework.com)
+ *
+ * @license https://github.com/slimphp/Slim/blob/4.x/LICENSE.md (MIT License)
+ */
+
+declare(strict_types=1);
+
+namespace Slim\Exception;
+
+class HttpTooManyRequestsException extends HttpSpecializedException
+{
+ /**
+ * @var int
+ */
+ protected $code = 429;
+
+ /**
+ * @var string
+ */
+ protected $message = 'Too many requests.';
+
+ protected string $title = '429 Too Many Requests';
+ protected string $description = 'The client application has surpassed its rate limit, ' .
+ 'or number of requests they can send in a given period of time.';
+}
"nikic/fast-route": "^1.3",
"psr/container": "^1.0 || ^2.0",
"psr/http-factory": "^1.0",
- "psr/http-message": "^1.1",
+ "psr/http-message": "^1.1 || ^2.0",
"psr/http-server-handler": "^1.0",
"psr/http-server-middleware": "^1.0",
"psr/log": "^1.1 || ^2.0 || ^3.0"
"require-dev": {
"ext-simplexml": "*",
"adriansuter/php-autoload-override": "^1.4",
- "guzzlehttp/psr7": "^2.5",
+ "guzzlehttp/psr7": "^2.6",
"httpsoft/http-message": "^1.1",
"httpsoft/http-server-request": "^1.1",
- "laminas/laminas-diactoros": "^2.17",
+ "laminas/laminas-diactoros": "^2.17 || ^3",
"nyholm/psr7": "^1.8",
- "nyholm/psr7-server": "^1.0",
- "phpspec/prophecy": "^1.17",
- "phpspec/prophecy-phpunit": "^2.0",
+ "nyholm/psr7-server": "^1.1",
+ "phpspec/prophecy": "^1.19",
+ "phpspec/prophecy-phpunit": "^2.1",
"phpstan/phpstan": "^1.10",
"phpunit/phpunit": "^9.6",
"slim/http": "^1.3",
"slim/psr7": "^1.6",
- "squizlabs/php_codesniffer": "^3.7"
+ "squizlabs/php_codesniffer": "^3.9"
},
"autoload": {
"psr-4": {
--- /dev/null
+vendor/
+composer.lock
+phpunit.xml
--- /dev/null
+CHANGELOG
+=========
+
+The changelog is maintained for all Symfony contracts at the following URL:
+https://github.com/symfony/contracts/blob/main/CHANGELOG.md
--- /dev/null
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * 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 <p@tchwork.com>
+ */
+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.
+ *
+ * @param string $key The key of the item to retrieve from the cache
+ * @param callable|CallbackInterface $callback Should return the computed value for the given key/item
+ * @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 mixed
+ *
+ * @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);
+
+ /**
+ * Removes an item from the pool.
+ *
+ * @param string $key The key to delete
+ *
+ * @throws InvalidArgumentException When $key is not valid
+ *
+ * @return bool True if the item was successfully removed, false if there was any error
+ */
+ public function delete(string $key): bool;
+}
--- /dev/null
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * 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 <p@tchwork.com>
+ */
+trait CacheTrait
+{
+ /**
+ * {@inheritdoc}
+ *
+ * @return mixed
+ */
+ public function get(string $key, callable $callback, float $beta = null, array &$metadata = null)
+ {
+ return $this->doGet($this, $key, $callback, $beta, $metadata);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ 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)
+ {
+ if (0 > $beta = $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 && $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();
+ }
+}
--- /dev/null
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * 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 <p@tchwork.com>
+ */
+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 mixed The computed value for the passed item
+ */
+ public function __invoke(CacheItemInterface $item, bool &$save);
+}
--- /dev/null
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * 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 <p@tchwork.com>
+ */
+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($tags): self;
+
+ /**
+ * 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;
+}
--- /dev/null
+Copyright (c) 2018-2022 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.
--- /dev/null
+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.
--- /dev/null
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * 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 <p@tchwork.com>
+ */
+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);
+}
--- /dev/null
+{
+ "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": ">=7.2.5",
+ "psr/cache": "^1.0|^2.0|^3.0"
+ },
+ "suggest": {
+ "symfony/cache-implementation": ""
+ },
+ "autoload": {
+ "psr-4": { "Symfony\\Contracts\\Cache\\": "" }
+ },
+ "minimum-stability": "dev",
+ "extra": {
+ "branch-alias": {
+ "dev-main": "2.5-dev"
+ },
+ "thanks": {
+ "name": "symfony/contracts",
+ "url": "https://github.com/symfony/contracts"
+ }
+ }
+}
namespace Symfony\Component\Cache\Adapter;
-use Psr\Cache\CacheItemInterface;
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\AbstractTrait;
+use Symfony\Component\Cache\Traits\AbstractAdapterTrait;
+use Symfony\Component\Cache\Traits\ContractsTrait;
+use Symfony\Contracts\Cache\CacheInterface;
/**
* @author Nicolas Grekas <p@tchwork.com>
*/
-abstract class AbstractAdapter implements AdapterInterface, LoggerAwareInterface, ResettableInterface
+abstract class AbstractAdapter implements AdapterInterface, CacheInterface, LoggerAwareInterface, ResettableInterface
{
+ use AbstractAdapterTrait;
+ use ContractsTrait;
+
/**
* @internal
*/
- const NS_SEPARATOR = ':';
-
- use AbstractTrait;
+ protected const NS_SEPARATOR = ':';
private static $apcuSupported;
private static $phpFilesSupported;
- private $createCacheItem;
- private $mergeByLifetime;
-
- /**
- * @param string $namespace
- * @param int $defaultLifetime
- */
- protected function __construct($namespace = '', $defaultLifetime = 0)
+ protected function __construct(string $namespace = '', int $defaultLifetime = 0)
{
$this->namespace = '' === $namespace ? '' : CacheItem::validateKey($namespace).static::NS_SEPARATOR;
if (null !== $this->maxIdLength && \strlen($namespace) > $this->maxIdLength - 24) {
static function ($key, $value, $isHit) {
$item = new CacheItem();
$item->key = $key;
- $item->value = $value;
+ $item->value = $v = $value;
$item->isHit = $isHit;
+ // Detect wrapped values that encode for their expiry and creation duration
+ // For compactness, these values are packed in the key of an array using
+ // magic numbers in the form 9D-..-..-..-..-00-..-..-..-5F
+ if (\is_array($v) && 1 === \count($v) && 10 === \strlen($k = (string) array_key_first($v)) && "\x9D" === $k[0] && "\0" === $k[5] && "\x5F" === $k[9]) {
+ $item->value = $v[$k];
+ $v = unpack('Ve/Nc', substr($k, 1, -1));
+ $item->metadata[CacheItem::METADATA_EXPIRY] = $v['e'] + CacheItem::METADATA_EXPIRY_OFFSET;
+ $item->metadata[CacheItem::METADATA_CTIME] = $v['c'];
+ }
return $item;
},
null,
CacheItem::class
);
- $getId = function ($key) { return $this->getId((string) $key); };
+ $getId = \Closure::fromCallable([$this, 'getId']);
$this->mergeByLifetime = \Closure::bind(
static function ($deferred, $namespace, &$expiredIds) use ($getId, $defaultLifetime) {
$byLifetime = [];
- $now = time();
+ $now = microtime(true);
$expiredIds = [];
foreach ($deferred as $key => $item) {
+ $key = (string) $key;
if (null === $item->expiry) {
- $byLifetime[0 < $defaultLifetime ? $defaultLifetime : 0][$getId($key)] = $item->value;
- } elseif (0 === $item->expiry) {
- $byLifetime[0][$getId($key)] = $item->value;
- } elseif ($item->expiry > $now) {
- $byLifetime[$item->expiry - $now][$getId($key)] = $item->value;
- } else {
+ $ttl = 0 < $defaultLifetime ? $defaultLifetime : 0;
+ } elseif (!$item->expiry) {
+ $ttl = 0;
+ } elseif (0 >= $ttl = (int) (0.1 + $item->expiry - $now)) {
$expiredIds[] = $getId($key);
+ continue;
+ }
+ if (isset(($metadata = $item->newMetadata)[CacheItem::METADATA_TAGS])) {
+ unset($metadata[CacheItem::METADATA_TAGS]);
}
+ // For compactness, expiry and creation duration are packed in the key of an array, using magic numbers as separators
+ $byLifetime[$ttl][$getId($key)] = $metadata ? ["\x9D".pack('VN', (int) (0.1 + $metadata[self::METADATA_EXPIRY] - self::METADATA_EXPIRY_OFFSET), $metadata[self::METADATA_CTIME])."\x5F" => $item->value] : $item->value;
}
return $byLifetime;
}
/**
+ * Returns the best possible adapter that your runtime supports.
+ *
+ * Using ApcuAdapter makes system caches compatible with read-only filesystems.
+ *
* @param string $namespace
* @param int $defaultLifetime
* @param string $version
*/
public static function createSystemCache($namespace, $defaultLifetime, $version, $directory, LoggerInterface $logger = null)
{
- if (null === self::$apcuSupported) {
- self::$apcuSupported = ApcuAdapter::isSupported();
- }
-
- if (!self::$apcuSupported && null === self::$phpFilesSupported) {
- self::$phpFilesSupported = PhpFilesAdapter::isSupported();
+ $opcache = new PhpFilesAdapter($namespace, $defaultLifetime, $directory, true);
+ if (null !== $logger) {
+ $opcache->setLogger($logger);
}
- if (self::$phpFilesSupported) {
- $opcache = new PhpFilesAdapter($namespace, $defaultLifetime, $directory);
- if (null !== $logger) {
- $opcache->setLogger($logger);
- }
-
+ if (!self::$apcuSupported = self::$apcuSupported ?? ApcuAdapter::isSupported()) {
return $opcache;
}
- $fs = new FilesystemAdapter($namespace, $defaultLifetime, $directory);
- if (null !== $logger) {
- $fs->setLogger($logger);
- }
- if (!self::$apcuSupported || (\in_array(\PHP_SAPI, ['cli', 'phpdbg'], true) && !filter_var(ini_get('apc.enable_cli'), \FILTER_VALIDATE_BOOLEAN))) {
- return $fs;
+ if (\in_array(\PHP_SAPI, ['cli', 'phpdbg'], true) && !filter_var(\ini_get('apc.enable_cli'), \FILTER_VALIDATE_BOOLEAN)) {
+ return $opcache;
}
- $apcu = new ApcuAdapter($namespace, (int) $defaultLifetime / 5, $version);
+ $apcu = new ApcuAdapter($namespace, intdiv($defaultLifetime, 5), $version);
if (null !== $logger) {
$apcu->setLogger($logger);
}
- return new ChainAdapter([$apcu, $fs]);
+ return new ChainAdapter([$apcu, $opcache]);
}
public static function createConnection($dsn, array $options = [])
if (!\is_string($dsn)) {
throw new InvalidArgumentException(sprintf('The "%s()" method expect argument #1 to be string, "%s" given.', __METHOD__, \gettype($dsn)));
}
- if (0 === strpos($dsn, 'redis://')) {
+ if (str_starts_with($dsn, 'redis:') || str_starts_with($dsn, 'rediss:')) {
return RedisAdapter::createConnection($dsn, $options);
}
- if (0 === strpos($dsn, 'memcached://')) {
+ if (str_starts_with($dsn, 'memcached:')) {
return MemcachedAdapter::createConnection($dsn, $options);
}
/**
* {@inheritdoc}
- */
- public function getItem($key)
- {
- if ($this->deferred) {
- $this->commit();
- }
- $id = $this->getId($key);
-
- $f = $this->createCacheItem;
- $isHit = false;
- $value = null;
-
- try {
- foreach ($this->doFetch([$id]) as $value) {
- $isHit = true;
- }
- } catch (\Exception $e) {
- CacheItem::log($this->logger, 'Failed to fetch key "{key}"', ['key' => $key, 'exception' => $e]);
- }
-
- return $f($key, $value, $isHit);
- }
-
- /**
- * {@inheritdoc}
- */
- public function getItems(array $keys = [])
- {
- if ($this->deferred) {
- $this->commit();
- }
- $ids = [];
-
- foreach ($keys as $key) {
- $ids[] = $this->getId($key);
- }
- try {
- $items = $this->doFetch($ids);
- } catch (\Exception $e) {
- CacheItem::log($this->logger, 'Failed to fetch requested items', ['keys' => $keys, 'exception' => $e]);
- $items = [];
- }
- $ids = array_combine($ids, $keys);
-
- return $this->generateItems($items, $ids);
- }
-
- /**
- * {@inheritdoc}
- */
- public function save(CacheItemInterface $item)
- {
- if (!$item instanceof CacheItem) {
- return false;
- }
- $this->deferred[$item->getKey()] = $item;
-
- return $this->commit();
- }
-
- /**
- * {@inheritdoc}
- */
- public function saveDeferred(CacheItemInterface $item)
- {
- if (!$item instanceof CacheItem) {
- return false;
- }
- $this->deferred[$item->getKey()] = $item;
-
- return true;
- }
-
- /**
- * {@inheritdoc}
+ *
+ * @return bool
*/
public function commit()
{
$retry = $this->deferred = [];
if ($expiredIds) {
- $this->doDelete($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 {
$ok = false;
$v = $values[$id];
$type = \is_object($v) ? \get_class($v) : \gettype($v);
- CacheItem::log($this->logger, 'Failed to save key "{key}" ({type})', ['key' => substr($id, \strlen($this->namespace)), 'type' => $type, 'exception' => $e instanceof \Exception ? $e : null]);
+ $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]);
}
} else {
foreach ($values as $id => $v) {
}
$ok = false;
$type = \is_object($v) ? \get_class($v) : \gettype($v);
- CacheItem::log($this->logger, 'Failed to save key "{key}" ({type})', ['key' => substr($id, \strlen($this->namespace)), 'type' => $type, 'exception' => $e instanceof \Exception ? $e : null]);
+ $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]);
}
}
return $ok;
}
-
- public function __sleep()
- {
- throw new \BadMethodCallException('Cannot serialize '.__CLASS__);
- }
-
- public function __wakeup()
- {
- throw new \BadMethodCallException('Cannot unserialize '.__CLASS__);
- }
-
- public function __destruct()
- {
- if ($this->deferred) {
- $this->commit();
- }
- }
-
- private function generateItems($items, &$keys)
- {
- $f = $this->createCacheItem;
-
- try {
- foreach ($items as $id => $value) {
- if (!isset($keys[$id])) {
- $id = key($keys);
- }
- $key = $keys[$id];
- unset($keys[$id]);
- yield $key => $f($key, $value, true);
- }
- } catch (\Exception $e) {
- CacheItem::log($this->logger, 'Failed to fetch requested items', ['keys' => array_values($keys), 'exception' => $e]);
- }
-
- foreach ($keys as $key) {
- yield $key => $f($key, null, false);
- }
- }
}
--- /dev/null
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * 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 <p@tchwork.com>
+ * @author André Rømcke <andre.romcke+symfony@gmail.com>
+ *
+ * @internal
+ */
+abstract class AbstractTagAwareAdapter implements TagAwareAdapterInterface, TagAwareCacheInterface, LoggerAwareInterface, ResettableInterface
+{
+ use AbstractAdapterTrait;
+ use ContractsTrait;
+
+ private const TAGS_PREFIX = "\0tags\0";
+
+ protected function __construct(string $namespace = '', int $defaultLifetime = 0)
+ {
+ $this->namespace = '' === $namespace ? '' : CacheItem::validateKey($namespace).':';
+ 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));
+ }
+ $this->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] = $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
+ );
+ $getId = \Closure::fromCallable([$this, 'getId']);
+ $tagPrefix = self::TAGS_PREFIX;
+ $this->mergeByLifetime = \Closure::bind(
+ static function ($deferred, &$expiredIds) use ($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[self::METADATA_EXPIRY] - self::METADATA_EXPIRY_OFFSET), $metadata[self::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($value['tags'], $oldTags) as $addedTag) {
+ $value['tag-operations']['add'][] = $getId($tagPrefix.$addedTag);
+ }
+ foreach (array_diff($oldTags, $value['tags']) as $removedTag) {
+ $value['tag-operations']['remove'][] = $getId($tagPrefix.$removedTag);
+ }
+
+ $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
+ *
+ * @return bool True if the items were successfully removed, false otherwise
+ */
+ abstract protected function doDelete(array $ids);
+
+ /**
+ * 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
+ *
+ * @return bool True on success
+ */
+ 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);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function commit(): bool
+ {
+ $ok = true;
+ $byLifetime = $this->mergeByLifetime;
+ $byLifetime = $byLifetime($this->deferred, $expiredIds);
+ $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 (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 = \is_object($v) ? \get_class($v) : \gettype($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]);
+ }
+ } 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 (true === $e || [] === $e) {
+ continue;
+ }
+ $ok = false;
+ $type = \is_object($v) ? \get_class($v) : \gettype($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]);
+ }
+ }
+
+ return $ok;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ 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 $e) {
+ $ok = false;
+ }
+
+ try {
+ if ((!$tagData || $this->doDeleteTagRelations($tagData)) && $ok) {
+ return true;
+ }
+ } catch (\Exception $e) {
+ }
+
+ // 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]);
+ $ok = false;
+ }
+
+ return $ok;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function invalidateTags(array $tags)
+ {
+ if (empty($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;
+ }
+}
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.
*
* @return \Traversable|CacheItem[]
*/
public function getItems(array $keys = []);
+
+ /**
+ * {@inheritdoc}
+ *
+ * @param string $prefix
+ *
+ * @return bool
+ */
+ public function clear(/* string $prefix = '' */);
}
use ApcuTrait;
/**
- * @param string $namespace
- * @param int $defaultLifetime
- * @param string|null $version
- *
* @throws CacheException if APCu is not enabled
*/
- public function __construct($namespace = '', $defaultLifetime = 0, $version = null)
+ public function __construct(string $namespace = '', int $defaultLifetime = 0, string $version = null)
{
$this->init($namespace, $defaultLifetime, $version);
}
use Symfony\Component\Cache\CacheItem;
use Symfony\Component\Cache\ResettableInterface;
use Symfony\Component\Cache\Traits\ArrayTrait;
+use Symfony\Contracts\Cache\CacheInterface;
/**
* @author Nicolas Grekas <p@tchwork.com>
*/
-class ArrayAdapter implements AdapterInterface, LoggerAwareInterface, ResettableInterface
+class ArrayAdapter implements AdapterInterface, CacheInterface, LoggerAwareInterface, ResettableInterface
{
use ArrayTrait;
private $defaultLifetime;
/**
- * @param int $defaultLifetime
* @param bool $storeSerialized Disabling serialization can lead to cache corruptions when storing mutable values but increases performance otherwise
*/
- public function __construct($defaultLifetime = 0, $storeSerialized = true)
+ public function __construct(int $defaultLifetime = 0, bool $storeSerialized = true)
{
$this->defaultLifetime = $defaultLifetime;
$this->storeSerialized = $storeSerialized;
/**
* {@inheritdoc}
*/
- public function getItem($key)
+ public function get(string $key, callable $callback, float $beta = null, array &$metadata = null)
{
- $isHit = $this->hasItem($key);
- try {
- if (!$isHit) {
- $this->values[$key] = $value = null;
- } elseif (!$this->storeSerialized) {
- $value = $this->values[$key];
- } elseif ('b:0;' === $value = $this->values[$key]) {
- $value = false;
- } elseif (false === $value = unserialize($value)) {
- $this->values[$key] = $value = null;
- $isHit = false;
+ $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);
}
- } catch (\Exception $e) {
- CacheItem::log($this->logger, 'Failed to unserialize key "{key}"', ['key' => $key, 'exception' => $e]);
+ }
+
+ return $item->get();
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getItem($key)
+ {
+ if (!$isHit = $this->hasItem($key)) {
$this->values[$key] = $value = null;
- $isHit = false;
+ } else {
+ $value = $this->storeSerialized ? $this->unfreeze($key, $isHit) : $this->values[$key];
}
$f = $this->createCacheItem;
public function getItems(array $keys = [])
{
foreach ($keys as $key) {
- CacheItem::validateKey($key);
+ if (!\is_string($key) || !isset($this->expiries[$key])) {
+ CacheItem::validateKey($key);
+ }
}
- return $this->generateItems($keys, time(), $this->createCacheItem);
+ return $this->generateItems($keys, microtime(true), $this->createCacheItem);
}
/**
* {@inheritdoc}
+ *
+ * @return bool
*/
public function deleteItems(array $keys)
{
/**
* {@inheritdoc}
+ *
+ * @return bool
*/
public function save(CacheItemInterface $item)
{
$value = $item["\0*\0value"];
$expiry = $item["\0*\0expiry"];
- if (0 === $expiry) {
- $expiry = \PHP_INT_MAX;
- }
+ if (null !== $expiry) {
+ if (!$expiry) {
+ $expiry = \PHP_INT_MAX;
+ } elseif ($expiry <= microtime(true)) {
+ $this->deleteItem($key);
- if (null !== $expiry && $expiry <= time()) {
- $this->deleteItem($key);
-
- return true;
- }
- if ($this->storeSerialized) {
- try {
- $value = serialize($value);
- } catch (\Exception $e) {
- $type = \is_object($value) ? \get_class($value) : \gettype($value);
- CacheItem::log($this->logger, 'Failed to save key "{key}" ({type})', ['key' => $key, 'type' => $type, 'exception' => $e]);
-
- return false;
+ return true;
}
}
+ if ($this->storeSerialized && null === $value = $this->freeze($value, $key)) {
+ return false;
+ }
if (null === $expiry && 0 < $this->defaultLifetime) {
- $expiry = time() + $this->defaultLifetime;
+ $expiry = microtime(true) + $this->defaultLifetime;
}
$this->values[$key] = $value;
- $this->expiries[$key] = null !== $expiry ? $expiry : \PHP_INT_MAX;
+ $this->expiries[$key] = $expiry ?? \PHP_INT_MAX;
return true;
}
/**
* {@inheritdoc}
+ *
+ * @return bool
*/
public function saveDeferred(CacheItemInterface $item)
{
/**
* {@inheritdoc}
+ *
+ * @return bool
*/
public function commit()
{
return true;
}
+
+ /**
+ * {@inheritdoc}
+ */
+ public function delete(string $key): bool
+ {
+ return $this->deleteItem($key);
+ }
}
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.
*
* @author Kévin Dunglas <dunglas@gmail.com>
*/
-class ChainAdapter implements AdapterInterface, PruneableInterface, ResettableInterface
+class ChainAdapter implements AdapterInterface, CacheInterface, PruneableInterface, ResettableInterface
{
+ use ContractsTrait;
+
private $adapters = [];
private $adapterCount;
private $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, $defaultLifetime = 0)
+ public function __construct(array $adapters, int $defaultLifetime = 0)
{
if (!$adapters) {
throw new InvalidArgumentException('At least one adapter must be specified.');
if (!$adapter instanceof CacheItemPoolInterface) {
throw new InvalidArgumentException(sprintf('The class "%s" does not implement the "%s" interface.', \get_class($adapter), CacheItemPoolInterface::class));
}
- if (\in_array(\PHP_SAPI, ['cli', 'phpdbg'], true) && $adapter instanceof ApcuAdapter && !filter_var(ini_get('apc.enable_cli'), \FILTER_VALIDATE_BOOLEAN)) {
+ if (\in_array(\PHP_SAPI, ['cli', 'phpdbg'], true) && $adapter instanceof ApcuAdapter && !filter_var(\ini_get('apc.enable_cli'), \FILTER_VALIDATE_BOOLEAN)) {
continue; // skip putting APCu in the chain when the backend is disabled
}
$this->adapterCount = \count($this->adapters);
$this->syncItem = \Closure::bind(
- static function ($sourceItem, $item) use ($defaultLifetime) {
+ static function ($sourceItem, $item, $sourceMetadata = null) use ($defaultLifetime) {
+ $sourceItem->isTaggable = false;
+ $sourceMetadata = $sourceMetadata ?? $sourceItem->metadata;
+ unset($sourceMetadata[CacheItem::METADATA_TAGS]);
+
$item->value = $sourceItem->value;
$item->isHit = $sourceItem->isHit;
+ $item->metadata = $item->newMetadata = $sourceItem->metadata = $sourceMetadata;
- if (0 < $defaultLifetime) {
+ if (isset($item->metadata[CacheItem::METADATA_EXPIRY])) {
+ $item->expiresAt(\DateTime::createFromFormat('U.u', sprintf('%.6F', $item->metadata[CacheItem::METADATA_EXPIRY])));
+ } elseif (0 < $defaultLifetime) {
$item->expiresAfter($defaultLifetime);
}
);
}
+ /**
+ * {@inheritdoc}
+ */
+ public function get(string $key, callable $callback, float $beta = null, array &$metadata = null)
+ {
+ $doSave = true;
+ $callback = static function (CacheItem $item, bool &$save) use ($callback, &$doSave) {
+ $value = $callback($item, $save);
+ $doSave = $save;
+
+ return $value;
+ };
+
+ $lastItem = null;
+ $i = 0;
+ $wrap = function (CacheItem $item = null, bool &$save = true) use ($key, $callback, $beta, &$wrap, &$i, &$doSave, &$lastItem, &$metadata) {
+ $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) {
+ ($this->syncItem)($lastItem = $lastItem ?? $item, $item, $metadata);
+ }
+ $save = $doSave;
+
+ return $value;
+ };
+
+ return $wrap();
+ }
+
/**
* {@inheritdoc}
*/
return $this->generateItems($this->adapters[0]->getItems($keys), 0);
}
- private function generateItems($items, $adapterIndex)
+ private function generateItems(iterable $items, int $adapterIndex)
{
$missing = [];
$misses = [];
$nextAdapterIndex = $adapterIndex + 1;
- $nextAdapter = isset($this->adapters[$nextAdapterIndex]) ? $this->adapters[$nextAdapterIndex] : null;
+ $nextAdapter = $this->adapters[$nextAdapterIndex] ?? null;
foreach ($items as $k => $item) {
if (!$nextAdapter || $item->isHit()) {
/**
* {@inheritdoc}
+ *
+ * @return bool
*/
public function hasItem($key)
{
/**
* {@inheritdoc}
+ *
+ * @param string $prefix
+ *
+ * @return bool
*/
- public function clear()
+ public function clear(/* string $prefix = '' */)
{
+ $prefix = 0 < \func_num_args() ? (string) func_get_arg(0) : '';
$cleared = true;
$i = $this->adapterCount;
while ($i--) {
- $cleared = $this->adapters[$i]->clear() && $cleared;
+ if ($this->adapters[$i] instanceof AdapterInterface) {
+ $cleared = $this->adapters[$i]->clear($prefix) && $cleared;
+ } else {
+ $cleared = $this->adapters[$i]->clear() && $cleared;
+ }
}
return $cleared;
/**
* {@inheritdoc}
+ *
+ * @return bool
*/
public function deleteItem($key)
{
/**
* {@inheritdoc}
+ *
+ * @return bool
*/
public function deleteItems(array $keys)
{
/**
* {@inheritdoc}
+ *
+ * @return bool
*/
public function save(CacheItemInterface $item)
{
/**
* {@inheritdoc}
+ *
+ * @return bool
*/
public function saveDeferred(CacheItemInterface $item)
{
/**
* {@inheritdoc}
+ *
+ * @return bool
*/
public function commit()
{
public function reset()
{
foreach ($this->adapters as $adapter) {
- if ($adapter instanceof ResettableInterface) {
+ if ($adapter instanceof ResetInterface) {
$adapter->reset();
}
}
{
use DoctrineTrait;
- /**
- * @param string $namespace
- * @param int $defaultLifetime
- */
- public function __construct(CacheProvider $provider, $namespace = '', $defaultLifetime = 0)
+ public function __construct(CacheProvider $provider, string $namespace = '', int $defaultLifetime = 0)
{
parent::__construct('', $defaultLifetime);
$this->provider = $provider;
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;
{
use FilesystemTrait;
- /**
- * @param string $namespace
- * @param int $defaultLifetime
- * @param string|null $directory
- */
- public function __construct($namespace = '', $defaultLifetime = 0, $directory = null)
+ 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);
}
--- /dev/null
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * 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 <p@tchwork.com>
+ * @author André Rømcke <andre.romcke+symfony@gmail.com>
+ */
+class FilesystemTagAwareAdapter extends AbstractTagAwareAdapter implements PruneableInterface
+{
+ use FilesystemTrait {
+ 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);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function doClear($namespace)
+ {
+ $ok = $this->doClearCache($namespace);
+
+ if ('' !== $namespace) {
+ return $ok;
+ }
+
+ set_error_handler(static function () {});
+ $chars = '+-ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
+
+ try {
+ foreach ($this->scanHashDir($this->directory.self::TAG_FOLDER.\DIRECTORY_SEPARATOR) as $dir) {
+ if (rename($dir, $renamed = substr_replace($dir, bin2hex(random_bytes(4)), -8))) {
+ $dir = $renamed.\DIRECTORY_SEPARATOR;
+ } else {
+ $dir .= \DIRECTORY_SEPARATOR;
+ $renamed = null;
+ }
+
+ for ($i = 0; $i < 38; ++$i) {
+ if (!file_exists($dir.$chars[$i])) {
+ continue;
+ }
+ for ($j = 0; $j < 38; ++$j) {
+ if (!file_exists($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;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ 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;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function doDeleteYieldTags(array $ids): iterable
+ {
+ foreach ($ids as $id) {
+ $file = $this->getFile($id);
+ if (!file_exists($file) || !$h = @fopen($file, 'r')) {
+ continue;
+ }
+
+ if ((\PHP_VERSION_ID >= 70300 || '\\' !== \DIRECTORY_SEPARATOR) && !@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 $e) {
+ yield $id => [];
+ }
+ }
+
+ fclose($h);
+
+ if (\PHP_VERSION_ID < 70300 && '\\' === \DIRECTORY_SEPARATOR) {
+ @unlink($file);
+ }
+ }
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ 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;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function doInvalidate(array $tagIds): bool
+ {
+ foreach ($tagIds as $tagId) {
+ if (!file_exists($tagFolder = $this->getTagFolder($tagId))) {
+ continue;
+ }
+
+ set_error_handler(static function () {});
+
+ try {
+ if (rename($tagFolder, $renamed = substr_replace($tagFolder, bin2hex(random_bytes(4)), -9))) {
+ $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;
+ }
+}
namespace Symfony\Component\Cache\Adapter;
+use Symfony\Component\Cache\Marshaller\MarshallerInterface;
use Symfony\Component\Cache\Traits\MemcachedTrait;
class MemcachedAdapter extends AbstractAdapter
*
* Using a MemcachedAdapter as a pure items store is fine.
*/
- public function __construct(\Memcached $client, $namespace = '', $defaultLifetime = 0)
+ public function __construct(\Memcached $client, string $namespace = '', int $defaultLifetime = 0, MarshallerInterface $marshaller = null)
{
- $this->init($client, $namespace, $defaultLifetime);
+ $this->init($client, $namespace, $defaultLifetime, $marshaller);
}
}
use Psr\Cache\CacheItemInterface;
use Symfony\Component\Cache\CacheItem;
+use Symfony\Contracts\Cache\CacheInterface;
/**
* @author Titouan Galopin <galopintitouan@gmail.com>
*/
-class NullAdapter implements AdapterInterface
+class NullAdapter implements AdapterInterface, CacheInterface
{
private $createCacheItem;
);
}
+ /**
+ * {@inheritdoc}
+ */
+ public function get(string $key, callable $callback, float $beta = null, array &$metadata = null)
+ {
+ $save = true;
+
+ return $callback(($this->createCacheItem)($key), $save);
+ }
+
/**
* {@inheritdoc}
*/
/**
* {@inheritdoc}
+ *
+ * @return bool
*/
public function hasItem($key)
{
/**
* {@inheritdoc}
+ *
+ * @param string $prefix
+ *
+ * @return bool
*/
- public function clear()
+ public function clear(/* string $prefix = '' */)
{
return true;
}
/**
* {@inheritdoc}
+ *
+ * @return bool
*/
public function deleteItem($key)
{
/**
* {@inheritdoc}
+ *
+ * @return bool
*/
public function deleteItems(array $keys)
{
/**
* {@inheritdoc}
+ *
+ * @return bool
*/
public function save(CacheItemInterface $item)
{
- return false;
+ return true;
}
/**
* {@inheritdoc}
+ *
+ * @return bool
*/
public function saveDeferred(CacheItemInterface $item)
{
- return false;
+ return true;
}
/**
* {@inheritdoc}
+ *
+ * @return bool
*/
public function commit()
{
- return false;
+ return true;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function delete(string $key): bool
+ {
+ return $this->deleteItem($key);
}
private function generateItems(array $keys)
use Doctrine\DBAL\Connection;
use Symfony\Component\Cache\Exception\InvalidArgumentException;
+use Symfony\Component\Cache\Marshaller\MarshallerInterface;
use Symfony\Component\Cache\PruneableInterface;
use Symfony\Component\Cache\Traits\PdoTrait;
* a Doctrine DBAL Connection or a DSN string that will be used to
* lazy-connect to the database when the cache is actually used.
*
+ * When a Doctrine DBAL Connection is passed, 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_password: The password when lazy-connect [default: '']
* * db_connection_options: An array of driver-specific connection options [default: []]
*
- * @param \PDO|Connection|string $connOrDsn A \PDO or Connection instance or DSN string or null
- * @param string $namespace
- * @param int $defaultLifetime
- * @param array $options An associative array of options
+ * @param \PDO|Connection|string $connOrDsn a \PDO or Connection instance or DSN string or null
*
* @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($connOrDsn, $namespace = '', $defaultLifetime = 0, array $options = [])
+ public function __construct($connOrDsn, string $namespace = '', int $defaultLifetime = 0, array $options = [], MarshallerInterface $marshaller = null)
{
- $this->init($connOrDsn, $namespace, $defaultLifetime, $options);
+ $this->init($connOrDsn, $namespace, $defaultLifetime, $options, $marshaller);
}
}
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\PhpArrayTrait;
+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.
* @author Titouan Galopin <galopintitouan@gmail.com>
* @author Nicolas Grekas <p@tchwork.com>
*/
-class PhpArrayAdapter implements AdapterInterface, PruneableInterface, ResettableInterface
+class PhpArrayAdapter implements AdapterInterface, CacheInterface, PruneableInterface, ResettableInterface
{
+ use ContractsTrait;
use PhpArrayTrait;
private $createCacheItem;
* @param string $file The PHP file were values are cached
* @param AdapterInterface $fallbackPool A pool to fallback on when an item is not hit
*/
- public function __construct($file, AdapterInterface $fallbackPool)
+ public function __construct(string $file, AdapterInterface $fallbackPool)
{
$this->file = $file;
$this->pool = $fallbackPool;
- $this->zendDetectUnicode = filter_var(ini_get('zend.detect_unicode'), \FILTER_VALIDATE_BOOLEAN);
$this->createCacheItem = \Closure::bind(
static function ($key, $value, $isHit) {
$item = new CacheItem();
}
/**
- * This adapter should only be used on PHP 7.0+ to take advantage of how PHP
- * stores arrays in its latest versions. This factory method decorates the given
- * fallback pool with this adapter only if the current PHP version is supported.
+ * 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($file, CacheItemPoolInterface $fallbackPool)
{
- if (\PHP_VERSION_ID >= 70000) {
- if (!$fallbackPool instanceof AdapterInterface) {
- $fallbackPool = new ProxyAdapter($fallbackPool);
+ if (!$fallbackPool instanceof AdapterInterface) {
+ $fallbackPool = new ProxyAdapter($fallbackPool);
+ }
+
+ return new static($file, $fallbackPool);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function get(string $key, callable $callback, float $beta = null, array &$metadata = null)
+ {
+ if (null === $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 new static($file, $fallbackPool);
+ return $this->doGet($this->pool, $key, $callback, $beta, $metadata);
}
+ $value = $this->values[$this->keys[$key]];
- return $fallbackPool;
+ if ('N;' === $value) {
+ return null;
+ }
+ try {
+ if ($value instanceof \Closure) {
+ return $value();
+ }
+ } catch (\Throwable $e) {
+ unset($this->keys[$key]);
+ goto get_from_pool;
+ }
+
+ return $value;
}
/**
if (null === $this->values) {
$this->initialize();
}
- if (!isset($this->values[$key])) {
+ if (!isset($this->keys[$key])) {
return $this->pool->getItem($key);
}
- $value = $this->values[$key];
+ $value = $this->values[$this->keys[$key]];
$isHit = true;
if ('N;' === $value) {
$value = null;
- } elseif (\is_string($value) && isset($value[2]) && ':' === $value[1]) {
+ } elseif ($value instanceof \Closure) {
try {
- $e = null;
- $value = unserialize($value);
- } catch (\Error $e) {
- } catch (\Exception $e) {
- }
- if (null !== $e) {
+ $value = $value();
+ } catch (\Throwable $e) {
$value = null;
$isHit = false;
}
/**
* {@inheritdoc}
+ *
+ * @return bool
*/
public function hasItem($key)
{
$this->initialize();
}
- return isset($this->values[$key]) || $this->pool->hasItem($key);
+ return isset($this->keys[$key]) || $this->pool->hasItem($key);
}
/**
* {@inheritdoc}
+ *
+ * @return bool
*/
public function deleteItem($key)
{
$this->initialize();
}
- return !isset($this->values[$key]) && $this->pool->deleteItem($key);
+ return !isset($this->keys[$key]) && $this->pool->deleteItem($key);
}
/**
* {@inheritdoc}
+ *
+ * @return bool
*/
public function deleteItems(array $keys)
{
throw new InvalidArgumentException(sprintf('Cache key must be string, "%s" given.', \is_object($key) ? \get_class($key) : \gettype($key)));
}
- if (isset($this->values[$key])) {
+ if (isset($this->keys[$key])) {
$deleted = false;
} else {
$fallbackKeys[] = $key;
/**
* {@inheritdoc}
+ *
+ * @return bool
*/
public function save(CacheItemInterface $item)
{
$this->initialize();
}
- return !isset($this->values[$item->getKey()]) && $this->pool->save($item);
+ return !isset($this->keys[$item->getKey()]) && $this->pool->save($item);
}
/**
* {@inheritdoc}
+ *
+ * @return bool
*/
public function saveDeferred(CacheItemInterface $item)
{
$this->initialize();
}
- return !isset($this->values[$item->getKey()]) && $this->pool->saveDeferred($item);
+ return !isset($this->keys[$item->getKey()]) && $this->pool->saveDeferred($item);
}
/**
* {@inheritdoc}
+ *
+ * @return bool
*/
public function commit()
{
return $this->pool->commit();
}
- /**
- * @return \Generator
- */
- private function generateItems(array $keys)
+ private function generateItems(array $keys): \Generator
{
$f = $this->createCacheItem;
$fallbackKeys = [];
foreach ($keys as $key) {
- if (isset($this->values[$key])) {
- $value = $this->values[$key];
+ if (isset($this->keys[$key])) {
+ $value = $this->values[$this->keys[$key]];
if ('N;' === $value) {
yield $key => $f($key, null, true);
- } elseif (\is_string($value) && isset($value[2]) && ':' === $value[1]) {
+ } elseif ($value instanceof \Closure) {
try {
- yield $key => $f($key, unserialize($value), true);
- } catch (\Error $e) {
- yield $key => $f($key, null, false);
- } catch (\Exception $e) {
+ yield $key => $f($key, $value(), true);
+ } catch (\Throwable $e) {
yield $key => $f($key, null, false);
}
} else {
}
if ($fallbackKeys) {
- foreach ($this->pool->getItems($fallbackKeys) as $key => $item) {
- yield $key => $item;
- }
+ yield from $this->pool->getItems($fallbackKeys);
}
}
use PhpFilesTrait;
/**
- * @param string $namespace
- * @param int $defaultLifetime
- * @param string|null $directory
+ * @param $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($namespace = '', $defaultLifetime = 0, $directory = null)
+ public function __construct(string $namespace = '', int $defaultLifetime = 0, string $directory = null, bool $appendOnly = false)
{
- if (!static::isSupported()) {
- throw new CacheException('OPcache is not enabled.');
- }
+ $this->appendOnly = $appendOnly;
+ self::$startTime = self::$startTime ?? $_SERVER['REQUEST_TIME'] ?? time();
parent::__construct('', $defaultLifetime);
$this->init($namespace, $directory);
-
- $e = new \Exception();
- $this->includeHandler = function () use ($e) { throw $e; };
- $this->zendDetectUnicode = filter_var(ini_get('zend.detect_unicode'), \FILTER_VALIDATE_BOOLEAN);
+ $this->includeHandler = static function ($type, $msg, $file, $line) {
+ throw new \ErrorException($msg, 0, $type, $file, $line);
+ };
}
}
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 <p@tchwork.com>
*/
-class ProxyAdapter implements AdapterInterface, PruneableInterface, ResettableInterface
+class ProxyAdapter implements AdapterInterface, CacheInterface, PruneableInterface, ResettableInterface
{
+ use ContractsTrait;
use ProxyTrait;
private $namespace;
private $namespaceLen;
private $createCacheItem;
+ private $setInnerItem;
private $poolHash;
private $defaultLifetime;
- /**
- * @param string $namespace
- * @param int $defaultLifetime
- */
- public function __construct(CacheItemPoolInterface $pool, $namespace = '', $defaultLifetime = 0)
+ public function __construct(CacheItemPoolInterface $pool, string $namespace = '', int $defaultLifetime = 0)
{
$this->pool = $pool;
$this->poolHash = $poolHash = spl_object_hash($pool);
static function ($key, $innerItem) use ($poolHash) {
$item = new CacheItem();
$item->key = $key;
+
+ if (null === $innerItem) {
+ return $item;
+ }
+
+ $item->value = $v = $innerItem->get();
+ $item->isHit = $innerItem->isHit();
+ $item->innerItem = $innerItem;
$item->poolHash = $poolHash;
- if (null !== $innerItem) {
- $item->value = $innerItem->get();
- $item->isHit = $innerItem->isHit();
- $item->innerItem = $innerItem;
- $innerItem->set(null);
+ // Detect wrapped values that encode for their expiry and creation duration
+ // For compactness, these values are packed in the key of an array using
+ // magic numbers in the form 9D-..-..-..-..-00-..-..-..-5F
+ if (\is_array($v) && 1 === \count($v) && 10 === \strlen($k = (string) array_key_first($v)) && "\x9D" === $k[0] && "\0" === $k[5] && "\x5F" === $k[9]) {
+ $item->value = $v[$k];
+ $v = unpack('Ve/Nc', substr($k, 1, -1));
+ $item->metadata[CacheItem::METADATA_EXPIRY] = $v['e'] + CacheItem::METADATA_EXPIRY_OFFSET;
+ $item->metadata[CacheItem::METADATA_CTIME] = $v['c'];
+ } elseif ($innerItem instanceof CacheItem) {
+ $item->metadata = $innerItem->metadata;
}
+ $innerItem->set(null);
return $item;
},
null,
CacheItem::class
);
+ $this->setInnerItem = \Closure::bind(
+ /**
+ * @param array $item A CacheItem cast to (array); accessing protected properties requires adding the "\0*\0" PHP prefix
+ */
+ static function (CacheItemInterface $innerItem, array $item) {
+ // Tags are stored separately, no need to account for them when considering this item's newly set metadata
+ if (isset(($metadata = $item["\0*\0newMetadata"])[CacheItem::METADATA_TAGS])) {
+ unset($metadata[CacheItem::METADATA_TAGS]);
+ }
+ if ($metadata) {
+ // For compactness, expiry and creation duration are packed in the key of an array, using magic numbers as separators
+ $item["\0*\0value"] = ["\x9D".pack('VN', (int) (0.1 + $metadata[self::METADATA_EXPIRY] - self::METADATA_EXPIRY_OFFSET), $metadata[self::METADATA_CTIME])."\x5F" => $item["\0*\0value"]];
+ }
+ $innerItem->set($item["\0*\0value"]);
+ $innerItem->expiresAt(null !== $item["\0*\0expiry"] ? \DateTime::createFromFormat('U.u', sprintf('%.6F', $item["\0*\0expiry"])) : null);
+ },
+ null,
+ CacheItem::class
+ );
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function get(string $key, callable $callback, float $beta = null, array &$metadata = null)
+ {
+ 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 = ($this->createCacheItem)($key, $innerItem);
+ $item->set($value = $callback($item, $save));
+ ($this->setInnerItem)($innerItem, (array) $item);
+
+ return $value;
+ }, $beta, $metadata);
}
/**
/**
* {@inheritdoc}
+ *
+ * @return bool
*/
public function hasItem($key)
{
/**
* {@inheritdoc}
+ *
+ * @param string $prefix
+ *
+ * @return bool
*/
- public function clear()
+ public function clear(/* string $prefix = '' */)
{
+ $prefix = 0 < \func_num_args() ? (string) func_get_arg(0) : '';
+
+ if ($this->pool instanceof AdapterInterface) {
+ return $this->pool->clear($this->namespace.$prefix);
+ }
+
return $this->pool->clear();
}
/**
* {@inheritdoc}
+ *
+ * @return bool
*/
public function deleteItem($key)
{
/**
* {@inheritdoc}
+ *
+ * @return bool
*/
public function deleteItems(array $keys)
{
/**
* {@inheritdoc}
+ *
+ * @return bool
*/
public function save(CacheItemInterface $item)
{
/**
* {@inheritdoc}
+ *
+ * @return bool
*/
public function saveDeferred(CacheItemInterface $item)
{
/**
* {@inheritdoc}
+ *
+ * @return bool
*/
public function commit()
{
return $this->pool->commit();
}
- private function doSave(CacheItemInterface $item, $method)
+ private function doSave(CacheItemInterface $item, string $method)
{
if (!$item instanceof CacheItem) {
return false;
}
$item = (array) $item;
- $expiry = $item["\0*\0expiry"];
- if (null === $expiry && 0 < $this->defaultLifetime) {
- $expiry = time() + $this->defaultLifetime;
+ if (null === $item["\0*\0expiry"] && 0 < $this->defaultLifetime) {
+ $item["\0*\0expiry"] = microtime(true) + $this->defaultLifetime;
}
if ($item["\0*\0poolHash"] === $this->poolHash && $item["\0*\0innerItem"]) {
$innerItem = $this->pool->getItem($this->namespace.$item["\0*\0key"]);
}
- $innerItem->set($item["\0*\0value"]);
- $innerItem->expiresAt(null !== $expiry ? \DateTime::createFromFormat('U', $expiry) : null);
+ ($this->setInnerItem)($innerItem, $item);
return $this->pool->$method($innerItem);
}
- private function generateItems($items)
+ private function generateItems(iterable $items)
{
$f = $this->createCacheItem;
}
}
- private function getId($key)
+ private function getId($key): string
{
CacheItem::validateKey($key);
--- /dev/null
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * 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 <p@tchwork.com>
+ */
+class Psr16Adapter extends AbstractAdapter implements PruneableInterface, ResettableInterface
+{
+ use ProxyTrait;
+
+ /**
+ * @internal
+ */
+ protected const NS_SEPARATOR = '_';
+
+ private $miss;
+
+ public function __construct(CacheInterface $pool, string $namespace = '', int $defaultLifetime = 0)
+ {
+ parent::__construct($namespace, $defaultLifetime);
+
+ $this->pool = $pool;
+ $this->miss = new \stdClass();
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function doFetch(array $ids)
+ {
+ foreach ($this->pool->getMultiple($ids, $this->miss) as $key => $value) {
+ if ($this->miss !== $value) {
+ yield $key => $value;
+ }
+ }
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function doHave($id)
+ {
+ return $this->pool->has($id);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function doClear($namespace)
+ {
+ return $this->pool->clear();
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function doDelete(array $ids)
+ {
+ return $this->pool->deleteMultiple($ids);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function doSave(array $values, int $lifetime)
+ {
+ return $this->pool->setMultiple($values, 0 === $lifetime ? null : $lifetime);
+ }
+}
namespace Symfony\Component\Cache\Adapter;
+use Symfony\Component\Cache\Marshaller\MarshallerInterface;
+use Symfony\Component\Cache\Traits\RedisClusterProxy;
+use Symfony\Component\Cache\Traits\RedisProxy;
use Symfony\Component\Cache\Traits\RedisTrait;
class RedisAdapter extends AbstractAdapter
use RedisTrait;
/**
- * @param \Redis|\RedisArray|\RedisCluster|\Predis\Client $redisClient The redis client
- * @param string $namespace The default namespace
- * @param int $defaultLifetime The default lifetime
+ * @param \Redis|\RedisArray|\RedisCluster|\Predis\ClientInterface|RedisProxy|RedisClusterProxy $redis The redis client
+ * @param string $namespace The default namespace
+ * @param int $defaultLifetime The default lifetime
*/
- public function __construct($redisClient, $namespace = '', $defaultLifetime = 0)
+ public function __construct($redis, string $namespace = '', int $defaultLifetime = 0, MarshallerInterface $marshaller = null)
{
- $this->init($redisClient, $namespace, $defaultLifetime);
+ $this->init($redis, $namespace, $defaultLifetime, $marshaller);
}
}
--- /dev/null
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * 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 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\RedisClusterProxy;
+use Symfony\Component\Cache\Traits\RedisProxy;
+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 <p@tchwork.com>
+ * @author André Rømcke <andre.romcke+symfony@gmail.com>
+ */
+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;
+
+ /**
+ * @var string|null detected eviction policy used on Redis server
+ */
+ private $redisEvictionPolicy;
+ private $namespace;
+
+ /**
+ * @param \Redis|\RedisArray|\RedisCluster|\Predis\ClientInterface|RedisProxy|RedisClusterProxy $redis The redis client
+ * @param string $namespace The default namespace
+ * @param int $defaultLifetime The default lifetime
+ */
+ public function __construct($redis, 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_class($redis->getConnection())));
+ }
+
+ if (\defined('Redis::OPT_COMPRESSION') && ($redis instanceof \Redis || $redis instanceof \RedisArray || $redis instanceof \RedisCluster)) {
+ $compression = $redis->getOption(\Redis::OPT_COMPRESSION);
+
+ foreach (\is_array($compression) ? $compression : [$compression] as $c) {
+ if (\Redis::COMPRESSION_NONE !== $c) {
+ throw new InvalidArgumentException(sprintf('phpredis compression must be disabled when using "%s", use "%s" instead.', static::class, DeflateMarshaller::class));
+ }
+ }
+ }
+
+ $this->init($redis, $namespace, $defaultLifetime, new TagAwareMarshaller($marshaller));
+ $this->namespace = $namespace;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ 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;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function doDeleteYieldTags(array $ids): iterable
+ {
+ $lua = <<<'EOLUA'
+ local v = redis.call('GET', KEYS[1])
+ redis.call('DEL', KEYS[1])
+
+ 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 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 $e) {
+ yield $id => [];
+ }
+ }
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ 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;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ 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(\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 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 (null !== $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 ($info instanceof ErrorInterface) {
+ continue;
+ }
+
+ $info = $info['Memory'] ?? $info;
+
+ return $this->redisEvictionPolicy = $info['maxmemory_policy'];
+ }
+
+ return $this->redisEvictionPolicy = '';
+ }
+}
namespace Symfony\Component\Cache\Adapter;
-use Psr\SimpleCache\CacheInterface;
-use Symfony\Component\Cache\PruneableInterface;
-use Symfony\Component\Cache\Traits\ProxyTrait;
+@trigger_error(sprintf('The "%s" class is @deprecated since Symfony 4.3, use "Psr16Adapter" instead.', SimpleCacheAdapter::class), \E_USER_DEPRECATED);
/**
- * @author Nicolas Grekas <p@tchwork.com>
+ * @deprecated since Symfony 4.3, use Psr16Adapter instead.
*/
-class SimpleCacheAdapter extends AbstractAdapter implements PruneableInterface
+class SimpleCacheAdapter extends Psr16Adapter
{
- /**
- * @internal
- */
- const NS_SEPARATOR = '_';
-
- use ProxyTrait;
-
- private $miss;
-
- public function __construct(CacheInterface $pool, $namespace = '', $defaultLifetime = 0)
- {
- parent::__construct($namespace, $defaultLifetime);
-
- $this->pool = $pool;
- $this->miss = new \stdClass();
- }
-
- /**
- * {@inheritdoc}
- */
- protected function doFetch(array $ids)
- {
- foreach ($this->pool->getMultiple($ids, $this->miss) as $key => $value) {
- if ($this->miss !== $value) {
- yield $key => $value;
- }
- }
- }
-
- /**
- * {@inheritdoc}
- */
- protected function doHave($id)
- {
- return $this->pool->has($id);
- }
-
- /**
- * {@inheritdoc}
- */
- protected function doClear($namespace)
- {
- return $this->pool->clear();
- }
-
- /**
- * {@inheritdoc}
- */
- protected function doDelete(array $ids)
- {
- return $this->pool->deleteMultiple($ids);
- }
-
- /**
- * {@inheritdoc}
- */
- protected function doSave(array $values, $lifetime)
- {
- return $this->pool->setMultiple($values, 0 === $lifetime ? null : $lifetime);
- }
}
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\Component\Cache\Traits\ProxyTrait;
+use Symfony\Contracts\Cache\TagAwareCacheInterface;
/**
* @author Nicolas Grekas <p@tchwork.com>
*/
-class TagAwareAdapter implements TagAwareAdapterInterface, PruneableInterface, ResettableInterface
+class TagAwareAdapter implements TagAwareAdapterInterface, TagAwareCacheInterface, PruneableInterface, ResettableInterface, LoggerAwareInterface
{
- const TAGS_PREFIX = "\0tags\0";
-
+ use ContractsTrait;
+ use LoggerAwareTrait;
use ProxyTrait;
+ public const TAGS_PREFIX = "\0tags\0";
+
private $deferred = [];
private $createCacheItem;
private $setCacheItemTags;
private $knownTagVersions = [];
private $knownTagVersionsTtl;
- public function __construct(AdapterInterface $itemsPool, AdapterInterface $tagsPool = null, $knownTagVersionsTtl = 0.15)
+ public function __construct(AdapterInterface $itemsPool, AdapterInterface $tagsPool = null, float $knownTagVersionsTtl = 0.15)
{
$this->pool = $itemsPool;
$this->tags = $tagsPool ?: $itemsPool;
);
$this->setCacheItemTags = \Closure::bind(
static function (CacheItem $item, $key, array &$itemTags) {
+ $item->isTaggable = true;
if (!$item->isHit) {
return $item;
}
if (isset($itemTags[$key])) {
foreach ($itemTags[$key] as $tag => $version) {
- $item->prevTags[$tag] = $tag;
+ $item->metadata[CacheItem::METADATA_TAGS][$tag] = $tag;
}
unset($itemTags[$key]);
} else {
static function ($deferred) {
$tagsByKey = [];
foreach ($deferred as $key => $item) {
- $tagsByKey[$key] = $item->tags;
+ $tagsByKey[$key] = $item->newMetadata[CacheItem::METADATA_TAGS] ?? [];
+ $item->metadata = $item->newMetadata;
}
return $tagsByKey;
/**
* {@inheritdoc}
+ *
+ * @return bool
*/
public function hasItem($key)
{
- if ($this->deferred) {
+ if (\is_string($key) && isset($this->deferred[$key])) {
$this->commit();
}
+
if (!$this->pool->hasItem($key)) {
return false;
}
}
foreach ($this->getTagVersions([$itemTags]) as $tag => $version) {
- if ($itemTags[$tag] !== $version && 1 !== $itemTags[$tag] - $version) {
- return false;
+ if ($itemTags[$tag] === $version || \is_int($itemTags[$tag]) && \is_int($version) && 1 === $itemTags[$tag] - $version) {
+ continue;
}
+
+ return false;
}
return true;
*/
public function getItems(array $keys = [])
{
- if ($this->deferred) {
- $this->commit();
- }
$tagKeys = [];
+ $commit = false;
foreach ($keys as $key) {
if ('' !== $key && \is_string($key)) {
+ $commit = $commit || isset($this->deferred[$key]);
$key = static::TAGS_PREFIX.$key;
$tagKeys[$key] = $key;
}
}
+ if ($commit) {
+ $this->commit();
+ }
+
try {
$items = $this->pool->getItems($tagKeys + $keys);
} catch (InvalidArgumentException $e) {
/**
* {@inheritdoc}
+ *
+ * @param string $prefix
+ *
+ * @return bool
*/
- public function clear()
+ public function clear(/* string $prefix = '' */)
{
- $this->deferred = [];
+ $prefix = 0 < \func_num_args() ? (string) func_get_arg(0) : '';
+
+ if ('' !== $prefix) {
+ foreach ($this->deferred as $key => $item) {
+ if (str_starts_with($key, $prefix)) {
+ unset($this->deferred[$key]);
+ }
+ }
+ } else {
+ $this->deferred = [];
+ }
+
+ if ($this->pool instanceof AdapterInterface) {
+ return $this->pool->clear($prefix);
+ }
return $this->pool->clear();
}
/**
* {@inheritdoc}
+ *
+ * @return bool
*/
public function deleteItem($key)
{
/**
* {@inheritdoc}
+ *
+ * @return bool
*/
public function deleteItems(array $keys)
{
/**
* {@inheritdoc}
+ *
+ * @return bool
*/
public function save(CacheItemInterface $item)
{
/**
* {@inheritdoc}
+ *
+ * @return bool
*/
public function saveDeferred(CacheItemInterface $item)
{
/**
* {@inheritdoc}
+ *
+ * @return bool
*/
public function commit()
{
return $this->invalidateTags([]);
}
+ /**
+ * @return array
+ */
public function __sleep()
{
throw new \BadMethodCallException('Cannot serialize '.__CLASS__);
$this->commit();
}
- private function generateItems($items, array $tagKeys)
+ private function generateItems(iterable $items, array $tagKeys)
{
$bufferedItems = $itemTags = [];
$f = $this->setCacheItemTags;
foreach ($itemTags as $key => $tags) {
foreach ($tags as $tag => $version) {
- if ($tagVersions[$tag] !== $version && 1 !== $version - $tagVersions[$tag]) {
- unset($itemTags[$key]);
- continue 2;
+ if ($tagVersions[$tag] === $version || \is_int($version) && \is_int($tagVersions[$tag]) && 1 === $version - $tagVersions[$tag]) {
+ continue;
}
+ unset($itemTags[$key]);
+ continue 2;
}
}
$tagVersions = $tagKeys = null;
$tags = [];
foreach ($tagVersions as $tag => $version) {
$tags[$tag.static::TAGS_PREFIX] = $tag;
- if ($fetchTagVersions || !isset($this->knownTagVersions[$tag])) {
+ if ($fetchTagVersions || !isset($this->knownTagVersions[$tag]) || !\is_int($version)) {
$fetchTagVersions = true;
continue;
}
if (isset($invalidatedTags[$tag])) {
$invalidatedTags[$tag] = $version->set(++$tagVersions[$tag]);
}
+ if (!\is_int($tagVersions[$tag])) {
+ unset($this->knownTagVersions[$tag]);
+ continue;
+ }
$this->knownTagVersions[$tag] = [$now, $tagVersions[$tag]];
}
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 Tobias Nyholm <tobias.nyholm@gmail.com>
* @author Nicolas Grekas <p@tchwork.com>
*/
-class TraceableAdapter implements AdapterInterface, PruneableInterface, ResettableInterface
+class TraceableAdapter implements AdapterInterface, CacheInterface, PruneableInterface, ResettableInterface
{
protected $pool;
private $calls = [];
$this->pool = $pool;
}
+ /**
+ * {@inheritdoc}
+ */
+ public function get(string $key, callable $callback, float $beta = null, array &$metadata = null)
+ {
+ if (!$this->pool instanceof CacheInterface) {
+ throw new \BadMethodCallException(sprintf('Cannot call "%s::get()": this class doesn\'t implement "%s".', \get_class($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] = \is_object($value) ? \get_class($value) : \gettype($value);
+ } finally {
+ $event->end = microtime(true);
+ }
+ if ($isHit) {
+ ++$event->hits;
+ } else {
+ ++$event->misses;
+ }
+
+ return $value;
+ }
+
/**
* {@inheritdoc}
*/
/**
* {@inheritdoc}
+ *
+ * @return bool
*/
public function hasItem($key)
{
/**
* {@inheritdoc}
+ *
+ * @return bool
*/
public function deleteItem($key)
{
/**
* {@inheritdoc}
+ *
+ * @return bool
*/
public function save(CacheItemInterface $item)
{
/**
* {@inheritdoc}
+ *
+ * @return bool
*/
public function saveDeferred(CacheItemInterface $item)
{
/**
* {@inheritdoc}
+ *
+ * @param string $prefix
+ *
+ * @return bool
*/
- public function clear()
+ public function clear(/* string $prefix = '' */)
{
+ $prefix = 0 < \func_num_args() ? (string) func_get_arg(0) : '';
$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);
/**
* {@inheritdoc}
+ *
+ * @return bool
*/
public function deleteItems(array $keys)
{
/**
* {@inheritdoc}
+ *
+ * @return bool
*/
public function commit()
{
*/
public function reset()
{
- if ($this->pool instanceof ResettableInterface) {
+ if ($this->pool instanceof ResetInterface) {
$this->pool->reset();
}
$this->clearCalls();
}
+ /**
+ * {@inheritdoc}
+ */
+ 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()
{
return $this->calls;
namespace Symfony\Component\Cache\Adapter;
+use Symfony\Contracts\Cache\TagAwareCacheInterface;
+
/**
* @author Robin Chalas <robin.chalas@gmail.com>
*/
-class TraceableTagAwareAdapter extends TraceableAdapter implements TagAwareAdapterInterface
+class TraceableTagAwareAdapter extends TraceableAdapter implements TagAwareAdapterInterface, TagAwareCacheInterface
{
public function __construct(TagAwareAdapterInterface $pool)
{
CHANGELOG
=========
+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`.
+
+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
-----
3.3.0
-----
- * [EXPERIMENTAL] added CacheItem::getPreviousTags() to get bound tags coming from the pool storage if any
+ * 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)
namespace Symfony\Component\Cache;
-use Psr\Cache\CacheItemInterface;
use Psr\Log\LoggerInterface;
use Symfony\Component\Cache\Exception\InvalidArgumentException;
+use Symfony\Component\Cache\Exception\LogicException;
+use Symfony\Contracts\Cache\ItemInterface;
/**
* @author Nicolas Grekas <p@tchwork.com>
*/
-final class CacheItem implements CacheItemInterface
+final class CacheItem implements ItemInterface
{
+ private const METADATA_EXPIRY_OFFSET = 1527506807;
+
protected $key;
protected $value;
protected $isHit = false;
protected $expiry;
- protected $tags = [];
- protected $prevTags = [];
+ protected $metadata = [];
+ protected $newMetadata = [];
protected $innerItem;
protected $poolHash;
+ protected $isTaggable = false;
/**
* {@inheritdoc}
*/
- public function getKey()
+ public function getKey(): string
{
return $this->key;
}
/**
* {@inheritdoc}
+ *
+ * @return mixed
*/
public function get()
{
/**
* {@inheritdoc}
*/
- public function isHit()
+ public function isHit(): bool
{
return $this->isHit;
}
*
* @return $this
*/
- public function set($value)
+ public function set($value): self
{
$this->value = $value;
*
* @return $this
*/
- public function expiresAt($expiration)
+ public function expiresAt($expiration): self
{
if (null === $expiration) {
$this->expiry = null;
} elseif ($expiration instanceof \DateTimeInterface) {
- $this->expiry = (int) $expiration->format('U');
+ $this->expiry = (float) $expiration->format('U.u');
} else {
throw new InvalidArgumentException(sprintf('Expiration date must implement DateTimeInterface or be null, "%s" given.', \is_object($expiration) ? \get_class($expiration) : \gettype($expiration)));
}
*
* @return $this
*/
- public function expiresAfter($time)
+ public function expiresAfter($time): self
{
if (null === $time) {
$this->expiry = null;
} elseif ($time instanceof \DateInterval) {
- $this->expiry = (int) \DateTime::createFromFormat('U', time())->add($time)->format('U');
+ $this->expiry = microtime(true) + \DateTime::createFromFormat('U', 0)->add($time)->format('U.u');
} elseif (\is_int($time)) {
- $this->expiry = $time + time();
+ $this->expiry = $time + microtime(true);
} else {
throw new InvalidArgumentException(sprintf('Expiration date must be an integer, a DateInterval or null, "%s" given.', \is_object($time) ? \get_class($time) : \gettype($time)));
}
}
/**
- * Adds a tag to a cache item.
- *
- * @param string|string[] $tags A tag or array of tags
- *
- * @return $this
- *
- * @throws InvalidArgumentException When $tag is not valid
+ * {@inheritdoc}
*/
- public function tag($tags)
+ public function tag($tags): ItemInterface
{
- if (!\is_array($tags)) {
+ 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_iterable($tags)) {
$tags = [$tags];
}
foreach ($tags as $tag) {
- if (!\is_string($tag)) {
- throw new InvalidArgumentException(sprintf('Cache tag must be string, "%s" given.', \is_object($tag) ? \get_class($tag) : \gettype($tag)));
+ if (!\is_string($tag) && !(\is_object($tag) && method_exists($tag, '__toString'))) {
+ throw new InvalidArgumentException(sprintf('Cache tag must be string or object that implements __toString(), "%s" given.', \is_object($tag) ? \get_class($tag) : \gettype($tag)));
}
- if (isset($this->tags[$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, '{}()/\@:')) {
- throw new InvalidArgumentException(sprintf('Cache tag "%s" contains reserved characters {}()/\@:.', $tag));
+ if (false !== strpbrk($tag, self::RESERVED_CHARACTERS)) {
+ throw new InvalidArgumentException(sprintf('Cache tag "%s" contains reserved characters "%s".', $tag, self::RESERVED_CHARACTERS));
}
- $this->tags[$tag] = $tag;
+ $this->newMetadata[self::METADATA_TAGS][$tag] = $tag;
}
return $this;
}
+ /**
+ * {@inheritdoc}
+ */
+ public function getMetadata(): array
+ {
+ return $this->metadata;
+ }
+
/**
* Returns the list of tags bound to the value coming from the pool storage if any.
*
- * @return array
+ * @deprecated since Symfony 4.2, use the "getMetadata()" method instead.
*/
- public function getPreviousTags()
+ public function getPreviousTags(): array
{
- return $this->prevTags;
+ @trigger_error(sprintf('The "%s()" method is deprecated since Symfony 4.2, use the "getMetadata()" method instead.', __METHOD__), \E_USER_DEPRECATED);
+
+ return $this->metadata[self::METADATA_TAGS] ?? [];
}
/**
* Validates a cache key according to PSR-6.
*
- * @param string $key The key to validate
- *
- * @return string
+ * @param mixed $key The key to validate
*
* @throws InvalidArgumentException When $key is not valid
*/
- public static function validateKey($key)
+ public static function validateKey($key): string
{
if (!\is_string($key)) {
throw new InvalidArgumentException(sprintf('Cache key must be string, "%s" given.', \is_object($key) ? \get_class($key) : \gettype($key)));
if ('' === $key) {
throw new InvalidArgumentException('Cache key length must be greater than zero.');
}
- if (false !== strpbrk($key, '{}()/\@:')) {
- throw new InvalidArgumentException(sprintf('Cache key "%s" contains reserved characters {}()/\@:.', $key));
+ 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
*/
- public static function log(LoggerInterface $logger = null, $message, $context = [])
+ public static function log(?LoggerInterface $logger, string $message, array $context = [])
{
if ($logger) {
$logger->warning($message, $context);
} else {
$replace = [];
foreach ($context as $k => $v) {
- if (is_scalar($v)) {
+ if (\is_scalar($v)) {
$replace['{'.$k.'}'] = $v;
}
}
/**
* @author Aaron Scherer <aequasi@gmail.com>
* @author Tobias Nyholm <tobias.nyholm@gmail.com>
+ *
+ * @final since Symfony 4.4
*/
class CacheDataCollector extends DataCollector implements LateDataCollectorInterface
{
/**
* {@inheritdoc}
+ *
+ * @param \Throwable|null $exception
*/
- public function collect(Request $request, Response $response, \Exception $exception = null)
+ public function collect(Request $request, Response $response/* , \Throwable $exception = null */)
{
$empty = ['calls' => [], 'config' => [], 'options' => [], 'statistics' => []];
$this->data = ['instances' => $empty, 'total' => $empty];
public function lateCollect()
{
- $this->data = $this->cloneVar($this->data);
+ $this->data['instances']['calls'] = $this->cloneVar($this->data['instances']['calls']);
}
/**
return $this->data['instances']['calls'];
}
- /**
- * @return array
- */
- private function calculateStatistics()
+ private function calculateStatistics(): array
{
$statistics = [];
foreach ($this->data['instances']['calls'] as $name => $calls) {
foreach ($calls as $call) {
++$statistics[$name]['calls'];
$statistics[$name]['time'] += $call->end - $call->start;
- if ('getItem' === $call->name) {
+ 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'];
return $statistics;
}
- /**
- * @return array
- */
- private function calculateTotalStatistics()
+ private function calculateTotalStatistics(): array
{
$statistics = $this->getStatistics();
$totals = [
--- /dev/null
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * 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 <tobias.nyholm@gmail.com>
+ */
+class CacheCollectorPass implements CompilerPassInterface
+{
+ private $dataCollectorCacheId;
+ private $cachePoolTag;
+ private $cachePoolRecorderInnerSuffix;
+
+ public function __construct(string $dataCollectorCacheId = 'data_collector.cache', string $cachePoolTag = 'cache.pool', string $cachePoolRecorderInnerSuffix = '.recorder_inner')
+ {
+ $this->dataCollectorCacheId = $dataCollectorCacheId;
+ $this->cachePoolTag = $cachePoolTag;
+ $this->cachePoolRecorderInnerSuffix = $cachePoolRecorderInnerSuffix;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function process(ContainerBuilder $container)
+ {
+ if (!$container->hasDefinition($this->dataCollectorCacheId)) {
+ return;
+ }
+
+ foreach ($container->findTaggedServiceIds($this->cachePoolTag) as $id => $attributes) {
+ $poolName = $attributes[0]['name'] ?? $id;
+
+ $this->addToCollector($id, $poolName, $container);
+ }
+ }
+
+ private function addToCollector(string $id, string $name, ContainerBuilder $container)
+ {
+ $definition = $container->getDefinition($id);
+ if ($definition->isAbstract()) {
+ return;
+ }
+
+ $collectorDefinition = $container->getDefinition($this->dataCollectorCacheId);
+ $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.$this->cachePoolRecorderInnerSuffix)]);
+
+ $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)]);
+ $collectorDefinition->setPublic(false);
+ }
+}
--- /dev/null
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * 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 <p@tchwork.com>
+ */
+class CachePoolClearerPass implements CompilerPassInterface
+{
+ private $cachePoolClearerTag;
+
+ public function __construct(string $cachePoolClearerTag = 'cache.pool.clearer')
+ {
+ $this->cachePoolClearerTag = $cachePoolClearerTag;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function process(ContainerBuilder $container)
+ {
+ $container->getParameterBag()->remove('cache.prefix.seed');
+
+ foreach ($container->findTaggedServiceIds($this->cachePoolClearerTag) 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);
+ }
+ }
+}
--- /dev/null
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * 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\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 <p@tchwork.com>
+ */
+class CachePoolPass implements CompilerPassInterface
+{
+ private $cachePoolTag;
+ private $kernelResetTag;
+ private $cacheClearerId;
+ private $cachePoolClearerTag;
+ private $cacheSystemClearerId;
+ private $cacheSystemClearerTag;
+
+ public function __construct(string $cachePoolTag = 'cache.pool', string $kernelResetTag = 'kernel.reset', string $cacheClearerId = 'cache.global_clearer', string $cachePoolClearerTag = 'cache.pool.clearer', string $cacheSystemClearerId = 'cache.system_clearer', string $cacheSystemClearerTag = 'kernel.cache_clearer')
+ {
+ $this->cachePoolTag = $cachePoolTag;
+ $this->kernelResetTag = $kernelResetTag;
+ $this->cacheClearerId = $cacheClearerId;
+ $this->cachePoolClearerTag = $cachePoolClearerTag;
+ $this->cacheSystemClearerId = $cacheSystemClearerId;
+ $this->cacheSystemClearerTag = $cacheSystemClearerTag;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function process(ContainerBuilder $container)
+ {
+ 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');
+
+ $allPools = [];
+ $clearers = [];
+ $attributes = [
+ 'provider',
+ 'name',
+ 'namespace',
+ 'default_lifetime',
+ 'reset',
+ ];
+ foreach ($container->findTaggedServiceIds($this->cachePoolTag) 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($this->cachePoolTag)) {
+ $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($this->cachePoolTag)) {
+ $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($this->kernelResetTag, ['method' => $tags[0][$attr]]);
+ }
+ } elseif ('namespace' !== $attr || !\in_array($class, [ArrayAdapter::class, NullAdapter::class], true)) {
+ $pool->replaceArgument($i++, $tags[0][$attr]);
+ }
+ unset($tags[0][$attr]);
+ }
+ if (!empty($tags[0])) {
+ throw new InvalidArgumentException(sprintf('Invalid "%s" tag for service "%s": accepted attributes are "clearer", "provider", "name", "namespace", "default_lifetime" and "reset", found "%s".', $this->cachePoolTag, $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);
+ }
+
+ $notAliasedCacheClearerId = $this->cacheClearerId;
+ while ($container->hasAlias($this->cacheClearerId)) {
+ $this->cacheClearerId = (string) $container->getAlias($this->cacheClearerId);
+ }
+ if ($container->hasDefinition($this->cacheClearerId)) {
+ $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($this->cachePoolClearerTag);
+
+ if ($this->cacheSystemClearerId === $id) {
+ $clearer->addTag($this->cacheSystemClearerTag);
+ }
+ }
+
+ if ($container->hasDefinition('console.command.cache_pool_list')) {
+ $container->getDefinition('console.command.cache_pool_list')->replaceArgument(0, array_keys($allPools));
+ }
+ }
+
+ private function getNamespace(string $seed, string $id)
+ {
+ return substr(str_replace('/', '-', base64_encode(hash('sha256', $id.$seed, true))), 0, 10);
+ }
+
+ /**
+ * @internal
+ */
+ public static function getServiceProvider(ContainerBuilder $container, $name)
+ {
+ $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->setPublic(false);
+ $definition->setFactory([AbstractAdapter::class, 'createConnection']);
+ $definition->setArguments([$dsn, ['lazy' => true]]);
+ $container->setDefinition($name, $definition);
+ }
+ }
+
+ return $name;
+ }
+}
--- /dev/null
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * 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 <rmf@src.run>
+ */
+class CachePoolPrunerPass implements CompilerPassInterface
+{
+ private $cacheCommandServiceId;
+ private $cachePoolTag;
+
+ public function __construct(string $cacheCommandServiceId = 'console.command.cache_pool_prune', string $cachePoolTag = 'cache.pool')
+ {
+ $this->cacheCommandServiceId = $cacheCommandServiceId;
+ $this->cachePoolTag = $cachePoolTag;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function process(ContainerBuilder $container)
+ {
+ if (!$container->hasDefinition($this->cacheCommandServiceId)) {
+ return;
+ }
+
+ $services = [];
+
+ foreach ($container->findTaggedServiceIds($this->cachePoolTag) 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($this->cacheCommandServiceId)->replaceArgument(0, new IteratorArgument($services));
+ }
+}
use Doctrine\Common\Cache\CacheProvider;
use Psr\Cache\CacheItemPoolInterface;
+use Symfony\Contracts\Service\ResetInterface;
+
+if (!class_exists(CacheProvider::class)) {
+ return;
+}
/**
* @author Nicolas Grekas <p@tchwork.com>
*/
public function reset()
{
- if ($this->pool instanceof ResettableInterface) {
+ if ($this->pool instanceof ResetInterface) {
$this->pool->reset();
}
$this->setNamespace($this->getNamespace());
/**
* {@inheritdoc}
+ *
+ * @return mixed
*/
protected function doFetch($id)
{
/**
* {@inheritdoc}
+ *
+ * @return bool
*/
protected function doContains($id)
{
/**
* {@inheritdoc}
+ *
+ * @return bool
*/
protected function doSave($id, $data, $lifeTime = 0)
{
/**
* {@inheritdoc}
+ *
+ * @return bool
*/
protected function doDelete($id)
{
/**
* {@inheritdoc}
+ *
+ * @return bool
*/
protected function doFlush()
{
/**
* {@inheritdoc}
+ *
+ * @return array|null
*/
protected function doGetStats()
{
use Psr\Cache\CacheException as Psr6CacheInterface;
use Psr\SimpleCache\CacheException as SimpleCacheInterface;
-class CacheException extends \Exception implements Psr6CacheInterface, SimpleCacheInterface
-{
+if (interface_exists(SimpleCacheInterface::class)) {
+ class CacheException extends \Exception implements Psr6CacheInterface, SimpleCacheInterface
+ {
+ }
+} else {
+ class CacheException extends \Exception implements Psr6CacheInterface
+ {
+ }
}
use Psr\Cache\InvalidArgumentException as Psr6CacheInterface;
use Psr\SimpleCache\InvalidArgumentException as SimpleCacheInterface;
-class InvalidArgumentException extends \InvalidArgumentException implements Psr6CacheInterface, SimpleCacheInterface
-{
+if (interface_exists(SimpleCacheInterface::class)) {
+ class InvalidArgumentException extends \InvalidArgumentException implements Psr6CacheInterface, SimpleCacheInterface
+ {
+ }
+} else {
+ class InvalidArgumentException extends \InvalidArgumentException implements Psr6CacheInterface
+ {
+ }
}
--- /dev/null
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * 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
+ {
+ }
+}
-Copyright (c) 2016-2020 Fabien Potencier
+Copyright (c) 2016-2022 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
--- /dev/null
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * 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 <p@tchwork.com>
+ */
+final class LockRegistry
+{
+ private static $openedFiles = [];
+ private static $lockedFiles;
+ private static $signalingException;
+ private static $signalingCallback;
+
+ /**
+ * The number of items in this list controls the max number of concurrent processes.
+ */
+ private static $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.'DoctrineAdapter.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.'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.'SimpleCacheAdapter.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)
+ {
+ 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 ?? self::$signalingException = unserialize("O:9:\"Exception\":1:{s:16:\"\0Exception\0trace\";a:0:{}}");
+ self::$signalingCallback ?? self::$signalingCallback = function () { 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 && $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 && $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 && $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 && $logger->info('Item "{key}" not found while lock was released, now retrying', ['key' => $item->getKey()]);
+ }
+ }
+
+ return null;
+ }
+
+ private static function open(int $key)
+ {
+ if (null !== $h = self::$openedFiles[$key] ?? null) {
+ return $h;
+ }
+ set_error_handler(function () {});
+ try {
+ $h = fopen(self::$files[$key], 'r+');
+ } finally {
+ restore_error_handler();
+ }
+
+ return self::$openedFiles[$key] = $h ?: @fopen(self::$files[$key], 'r');
+ }
+}
--- /dev/null
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * 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 <p@tchwork.com>
+ */
+class DefaultMarshaller implements MarshallerInterface
+{
+ private $useIgbinarySerialize = true;
+
+ public function __construct(bool $useIgbinarySerialize = null)
+ {
+ if (null === $useIgbinarySerialize) {
+ $useIgbinarySerialize = \extension_loaded('igbinary') && (\PHP_VERSION_ID < 70400 || version_compare('3.1.6', phpversion('igbinary'), '<='));
+ } elseif ($useIgbinarySerialize && (!\extension_loaded('igbinary') || (\PHP_VERSION_ID >= 70400 && version_compare('3.1.6', phpversion('igbinary'), '>')))) {
+ throw new CacheException(\extension_loaded('igbinary') && \PHP_VERSION_ID >= 70400 ? 'Please upgrade the "igbinary" PHP extension to v3.1.6 or higher.' : 'The "igbinary" PHP extension is not loaded.');
+ }
+ $this->useIgbinarySerialize = $useIgbinarySerialize;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ 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) {
+ $failed[] = $id;
+ }
+ }
+
+ return $serialized;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function unmarshall(string $value)
+ {
+ if ('b:0;' === $value) {
+ return false;
+ }
+ if ('N;' === $value) {
+ return null;
+ }
+ static $igbinaryNull;
+ if ($value === ($igbinaryNull ?? $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($class)
+ {
+ throw new \DomainException('Class not found: '.$class);
+ }
+}
--- /dev/null
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * 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 <p@tchwork.com>
+ */
+class DeflateMarshaller implements MarshallerInterface
+{
+ private $marshaller;
+
+ public function __construct(MarshallerInterface $marshaller)
+ {
+ if (!\function_exists('gzdeflate')) {
+ throw new CacheException('The "zlib" PHP extension is not loaded.');
+ }
+
+ $this->marshaller = $marshaller;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function marshall(array $values, ?array &$failed): array
+ {
+ return array_map('gzdeflate', $this->marshaller->marshall($values, $failed));
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function unmarshall(string $value)
+ {
+ if (false !== $inflatedValue = @gzinflate($value)) {
+ $value = $inflatedValue;
+ }
+
+ return $this->marshaller->unmarshall($value);
+ }
+}
--- /dev/null
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * 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 <p@tchwork.com>
+ */
+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.
+ *
+ * @return mixed
+ *
+ * @throws \Exception Whenever unserialization fails
+ */
+ public function unmarshall(string $value);
+}
--- /dev/null
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * 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 <p@tchwork.com>
+ */
+class TagAwareMarshaller implements MarshallerInterface
+{
+ private $marshaller;
+
+ public function __construct(MarshallerInterface $marshaller = null)
+ {
+ $this->marshaller = $marshaller ?? new DefaultMarshaller();
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ 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 arbitratry 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;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function unmarshall(string $value)
+ {
+ // 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,
+ ];
+ }
+}
--- /dev/null
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * 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;
+
+if (null !== (new \ReflectionMethod(CacheInterface::class, 'get'))->getReturnType()) {
+ throw new \LogicException('psr/simple-cache 3.0+ is not compatible with this version of symfony/cache. Please upgrade symfony/cache to 6.0+ or downgrade psr/simple-cache to 1.x or 2.x.');
+}
+
+/**
+ * Turns a PSR-6 cache into a PSR-16 one.
+ *
+ * @author Nicolas Grekas <p@tchwork.com>
+ */
+class Psr16Cache implements CacheInterface, PruneableInterface, ResettableInterface
+{
+ use ProxyTrait;
+
+ private const METADATA_EXPIRY_OFFSET = 1527506807;
+
+ private $createCacheItem;
+ private $cacheItemPrototype;
+
+ 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;
+ $item->key = $allowInt && \is_int($key) ? (string) $key : CacheItem::validateKey($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);
+ };
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @return mixed
+ */
+ public function get($key, $default = null)
+ {
+ 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;
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @return bool
+ */
+ public function set($key, $value, $ttl = null)
+ {
+ 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);
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @return bool
+ */
+ public function delete($key)
+ {
+ try {
+ return $this->pool->deleteItem($key);
+ } catch (SimpleCacheException $e) {
+ throw $e;
+ } catch (Psr6CacheException $e) {
+ throw new InvalidArgumentException($e->getMessage(), $e->getCode(), $e);
+ }
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @return bool
+ */
+ public function clear()
+ {
+ return $this->pool->clear();
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @return iterable
+ */
+ public function getMultiple($keys, $default = null)
+ {
+ 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.', \is_object($keys) ? \get_class($keys) : \gettype($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) {
+ if (!$item->isHit()) {
+ $values[$key] = $default;
+ continue;
+ }
+ $values[$key] = $item->get();
+
+ if (!$metadata = $item->getMetadata()) {
+ continue;
+ }
+ unset($metadata[CacheItem::METADATA_TAGS]);
+
+ if ($metadata) {
+ $values[$key] = ["\x9D".pack('VN', (int) (0.1 + $metadata[CacheItem::METADATA_EXPIRY] - self::METADATA_EXPIRY_OFFSET), $metadata[CacheItem::METADATA_CTIME])."\x5F" => $values[$key]];
+ }
+ }
+
+ return $values;
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @return bool
+ */
+ public function setMultiple($values, $ttl = null)
+ {
+ $valuesIsArray = \is_array($values);
+ if (!$valuesIsArray && !$values instanceof \Traversable) {
+ throw new InvalidArgumentException(sprintf('Cache values must be array or Traversable, "%s" given.', \is_object($values) ? \get_class($values) : \gettype($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;
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @return bool
+ */
+ public function deleteMultiple($keys)
+ {
+ 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.', \is_object($keys) ? \get_class($keys) : \gettype($keys)));
+ }
+
+ try {
+ return $this->pool->deleteItems($keys);
+ } catch (SimpleCacheException $e) {
+ throw $e;
+ } catch (Psr6CacheException $e) {
+ throw new InvalidArgumentException($e->getMessage(), $e->getCode(), $e);
+ }
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @return bool
+ */
+ public function has($key)
+ {
+ try {
+ return $this->pool->hasItem($key);
+ } catch (SimpleCacheException $e) {
+ throw $e;
+ } catch (Psr6CacheException $e) {
+ throw new InvalidArgumentException($e->getMessage(), $e->getCode(), $e);
+ }
+ }
+}
Symfony PSR-6 implementation for caching
========================================
-This component provides an extended [PSR-6](http://www.php-fig.org/psr/psr-6/)
-implementation for adding cache to your applications. It is designed to have a
-low overhead so that caching is fastest. It ships with a few caching adapters
-for the most widespread and suited to caching backends. It also provides a
-`doctrine/cache` proxy adapter to cover more advanced caching needs and a proxy
-adapter for greater interoperability between PSR-6 implementations.
+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)
+ * [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)
namespace Symfony\Component\Cache;
+use Symfony\Contracts\Service\ResetInterface;
+
/**
* Resets a pool's local state.
*/
-interface ResettableInterface
+interface ResettableInterface extends ResetInterface
{
- public function reset();
}
namespace Symfony\Component\Cache\Simple;
use Psr\Log\LoggerAwareInterface;
-use Psr\SimpleCache\CacheInterface;
+use Psr\SimpleCache\CacheInterface as Psr16CacheInterface;
+use Symfony\Component\Cache\Adapter\AbstractAdapter;
use Symfony\Component\Cache\CacheItem;
use Symfony\Component\Cache\Exception\InvalidArgumentException;
use Symfony\Component\Cache\ResettableInterface;
use Symfony\Component\Cache\Traits\AbstractTrait;
+use Symfony\Contracts\Cache\CacheInterface;
+
+@trigger_error(sprintf('The "%s" class is deprecated since Symfony 4.3, use "%s" and type-hint for "%s" instead.', AbstractCache::class, AbstractAdapter::class, CacheInterface::class), \E_USER_DEPRECATED);
/**
- * @author Nicolas Grekas <p@tchwork.com>
+ * @deprecated since Symfony 4.3, use AbstractAdapter and type-hint for CacheInterface instead.
*/
-abstract class AbstractCache implements CacheInterface, LoggerAwareInterface, ResettableInterface
+abstract class AbstractCache implements Psr16CacheInterface, LoggerAwareInterface, ResettableInterface
{
- /**
- * @internal
- */
- const NS_SEPARATOR = ':';
-
use AbstractTrait {
deleteItems as private;
AbstractTrait::deleteItem as delete;
AbstractTrait::hasItem as has;
}
- private $defaultLifetime;
-
/**
- * @param string $namespace
- * @param int $defaultLifetime
+ * @internal
*/
- protected function __construct($namespace = '', $defaultLifetime = 0)
+ protected const NS_SEPARATOR = ':';
+
+ private $defaultLifetime;
+
+ protected function __construct(string $namespace = '', int $defaultLifetime = 0)
{
- $this->defaultLifetime = max(0, (int) $defaultLifetime);
+ $this->defaultLifetime = max(0, $defaultLifetime);
$this->namespace = '' === $namespace ? '' : CacheItem::validateKey($namespace).':';
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));
return $value;
}
} catch (\Exception $e) {
- CacheItem::log($this->logger, 'Failed to fetch key "{key}"', ['key' => $key, 'exception' => $e]);
+ CacheItem::log($this->logger, 'Failed to fetch key "{key}": '.$e->getMessage(), ['key' => $key, 'exception' => $e]);
}
return $default;
/**
* {@inheritdoc}
+ *
+ * @return bool
*/
public function set($key, $value, $ttl = null)
{
/**
* {@inheritdoc}
+ *
+ * @return iterable
*/
public function getMultiple($keys, $default = null)
{
try {
$values = $this->doFetch($ids);
} catch (\Exception $e) {
- CacheItem::log($this->logger, 'Failed to fetch requested values', ['keys' => $keys, 'exception' => $e]);
+ CacheItem::log($this->logger, 'Failed to fetch values: '.$e->getMessage(), ['keys' => $keys, 'exception' => $e]);
$values = [];
}
$ids = array_combine($ids, $keys);
/**
* {@inheritdoc}
+ *
+ * @return bool
*/
public function setMultiple($values, $ttl = null)
{
foreach (\is_array($e) ? $e : array_keys($valuesById) as $id) {
$keys[] = substr($id, \strlen($this->namespace));
}
- CacheItem::log($this->logger, 'Failed to save values', ['keys' => $keys, 'exception' => $e instanceof \Exception ? $e : null]);
+ $message = 'Failed to save values'.($e instanceof \Exception ? ': '.$e->getMessage() : '.');
+ CacheItem::log($this->logger, $message, ['keys' => $keys, 'exception' => $e instanceof \Exception ? $e : null]);
return false;
}
/**
* {@inheritdoc}
+ *
+ * @return bool
*/
public function deleteMultiple($keys)
{
throw new InvalidArgumentException(sprintf('Expiration date must be an integer, a DateInterval or null, "%s" given.', \is_object($ttl) ? \get_class($ttl) : \gettype($ttl)));
}
- private function generateValues($values, &$keys, $default)
+ private function generateValues(iterable $values, array &$keys, $default): iterable
{
try {
foreach ($values as $id => $value) {
if (!isset($keys[$id])) {
- $id = key($keys);
+ throw new InvalidArgumentException(sprintf('Could not match value id "%s" to keys "%s".', $id, implode('", "', $keys)));
}
$key = $keys[$id];
unset($keys[$id]);
yield $key => $value;
}
} catch (\Exception $e) {
- CacheItem::log($this->logger, 'Failed to fetch requested values', ['keys' => array_values($keys), 'exception' => $e]);
+ CacheItem::log($this->logger, 'Failed to fetch values: '.$e->getMessage(), ['keys' => array_values($keys), 'exception' => $e]);
}
foreach ($keys as $key) {
namespace Symfony\Component\Cache\Simple;
+use Symfony\Component\Cache\Adapter\ApcuAdapter;
use Symfony\Component\Cache\Traits\ApcuTrait;
+use Symfony\Contracts\Cache\CacheInterface;
+@trigger_error(sprintf('The "%s" class is deprecated since Symfony 4.3, use "%s" and type-hint for "%s" instead.', ApcuCache::class, ApcuAdapter::class, CacheInterface::class), \E_USER_DEPRECATED);
+
+/**
+ * @deprecated since Symfony 4.3, use ApcuAdapter and type-hint for CacheInterface instead.
+ */
class ApcuCache extends AbstractCache
{
use ApcuTrait;
- /**
- * @param string $namespace
- * @param int $defaultLifetime
- * @param string|null $version
- */
- public function __construct($namespace = '', $defaultLifetime = 0, $version = null)
+ public function __construct(string $namespace = '', int $defaultLifetime = 0, string $version = null)
{
$this->init($namespace, $defaultLifetime, $version);
}
namespace Symfony\Component\Cache\Simple;
use Psr\Log\LoggerAwareInterface;
-use Psr\SimpleCache\CacheInterface;
+use Psr\SimpleCache\CacheInterface as Psr16CacheInterface;
+use Symfony\Component\Cache\Adapter\ArrayAdapter;
use Symfony\Component\Cache\CacheItem;
use Symfony\Component\Cache\Exception\InvalidArgumentException;
use Symfony\Component\Cache\ResettableInterface;
use Symfony\Component\Cache\Traits\ArrayTrait;
+use Symfony\Contracts\Cache\CacheInterface;
+
+@trigger_error(sprintf('The "%s" class is deprecated since Symfony 4.3, use "%s" and type-hint for "%s" instead.', ArrayCache::class, ArrayAdapter::class, CacheInterface::class), \E_USER_DEPRECATED);
/**
- * @author Nicolas Grekas <p@tchwork.com>
+ * @deprecated since Symfony 4.3, use ArrayAdapter and type-hint for CacheInterface instead.
*/
-class ArrayCache implements CacheInterface, LoggerAwareInterface, ResettableInterface
+class ArrayCache implements Psr16CacheInterface, LoggerAwareInterface, ResettableInterface
{
use ArrayTrait {
ArrayTrait::deleteItem as delete;
private $defaultLifetime;
/**
- * @param int $defaultLifetime
* @param bool $storeSerialized Disabling serialization can lead to cache corruptions when storing mutable values but increases performance otherwise
*/
- public function __construct($defaultLifetime = 0, $storeSerialized = true)
+ public function __construct(int $defaultLifetime = 0, bool $storeSerialized = true)
{
- $this->defaultLifetime = (int) $defaultLifetime;
+ $this->defaultLifetime = $defaultLifetime;
$this->storeSerialized = $storeSerialized;
}
*/
public function get($key, $default = null)
{
- foreach ($this->getMultiple([$key], $default) as $v) {
- return $v;
+ if (!\is_string($key) || !isset($this->expiries[$key])) {
+ CacheItem::validateKey($key);
+ }
+ if (!$isHit = isset($this->expiries[$key]) && ($this->expiries[$key] > microtime(true) || !$this->delete($key))) {
+ $this->values[$key] = null;
+
+ return $default;
}
+ if (!$this->storeSerialized) {
+ return $this->values[$key];
+ }
+ $value = $this->unfreeze($key, $isHit);
+
+ return $isHit ? $value : $default;
}
/**
* {@inheritdoc}
+ *
+ * @return iterable
*/
public function getMultiple($keys, $default = null)
{
throw new InvalidArgumentException(sprintf('Cache keys must be array or Traversable, "%s" given.', \is_object($keys) ? \get_class($keys) : \gettype($keys)));
}
foreach ($keys as $key) {
- CacheItem::validateKey($key);
+ if (!\is_string($key) || !isset($this->expiries[$key])) {
+ CacheItem::validateKey($key);
+ }
}
- return $this->generateItems($keys, time(), function ($k, $v, $hit) use ($default) { return $hit ? $v : $default; });
+ return $this->generateItems($keys, microtime(true), function ($k, $v, $hit) use ($default) { return $hit ? $v : $default; });
}
/**
* {@inheritdoc}
+ *
+ * @return bool
*/
public function deleteMultiple($keys)
{
/**
* {@inheritdoc}
+ *
+ * @return bool
*/
public function set($key, $value, $ttl = null)
{
- CacheItem::validateKey($key);
+ if (!\is_string($key)) {
+ CacheItem::validateKey($key);
+ }
return $this->setMultiple([$key => $value], $ttl);
}
/**
* {@inheritdoc}
+ *
+ * @return bool
*/
public function setMultiple($values, $ttl = null)
{
$valuesArray = [];
foreach ($values as $key => $value) {
- \is_int($key) || CacheItem::validateKey($key);
+ if (!\is_int($key) && !(\is_string($key) && isset($this->expiries[$key]))) {
+ CacheItem::validateKey($key);
+ }
$valuesArray[$key] = $value;
}
if (false === $ttl = $this->normalizeTtl($ttl)) {
return $this->deleteMultiple(array_keys($valuesArray));
}
- if ($this->storeSerialized) {
- foreach ($valuesArray as $key => $value) {
- try {
- $valuesArray[$key] = serialize($value);
- } catch (\Exception $e) {
- $type = \is_object($value) ? \get_class($value) : \gettype($value);
- CacheItem::log($this->logger, 'Failed to save key "{key}" ({type})', ['key' => $key, 'type' => $type, 'exception' => $e]);
-
- return false;
- }
- }
- }
- $expiry = 0 < $ttl ? time() + $ttl : \PHP_INT_MAX;
+ $expiry = 0 < $ttl ? microtime(true) + $ttl : \PHP_INT_MAX;
foreach ($valuesArray as $key => $value) {
+ if ($this->storeSerialized && null === $value = $this->freeze($value, $key)) {
+ return false;
+ }
$this->values[$key] = $value;
$this->expiries[$key] = $expiry;
}
namespace Symfony\Component\Cache\Simple;
-use Psr\SimpleCache\CacheInterface;
+use Psr\SimpleCache\CacheInterface as Psr16CacheInterface;
+use Symfony\Component\Cache\Adapter\ChainAdapter;
use Symfony\Component\Cache\Exception\InvalidArgumentException;
use Symfony\Component\Cache\PruneableInterface;
use Symfony\Component\Cache\ResettableInterface;
+use Symfony\Contracts\Cache\CacheInterface;
+use Symfony\Contracts\Service\ResetInterface;
+
+@trigger_error(sprintf('The "%s" class is deprecated since Symfony 4.3, use "%s" and type-hint for "%s" instead.', ChainCache::class, ChainAdapter::class, CacheInterface::class), \E_USER_DEPRECATED);
/**
* Chains several caches together.
* Cached items are fetched from the first cache having them in its data store.
* They are saved and deleted in all caches at once.
*
- * @author Nicolas Grekas <p@tchwork.com>
+ * @deprecated since Symfony 4.3, use ChainAdapter and type-hint for CacheInterface instead.
*/
-class ChainCache implements CacheInterface, PruneableInterface, ResettableInterface
+class ChainCache implements Psr16CacheInterface, PruneableInterface, ResettableInterface
{
private $miss;
private $caches = [];
private $cacheCount;
/**
- * @param CacheInterface[] $caches The ordered list of caches used to fetch cached items
- * @param int $defaultLifetime The lifetime of items propagated from lower caches to upper ones
+ * @param Psr16CacheInterface[] $caches The ordered list of caches used to fetch cached items
+ * @param int $defaultLifetime The lifetime of items propagated from lower caches to upper ones
*/
- public function __construct(array $caches, $defaultLifetime = 0)
+ public function __construct(array $caches, int $defaultLifetime = 0)
{
if (!$caches) {
throw new InvalidArgumentException('At least one cache must be specified.');
}
foreach ($caches as $cache) {
- if (!$cache instanceof CacheInterface) {
- throw new InvalidArgumentException(sprintf('The class "%s" does not implement the "%s" interface.', \get_class($cache), CacheInterface::class));
+ if (!$cache instanceof Psr16CacheInterface) {
+ throw new InvalidArgumentException(sprintf('The class "%s" does not implement the "%s" interface.', \get_class($cache), Psr16CacheInterface::class));
}
}
$this->miss = new \stdClass();
$this->caches = array_values($caches);
$this->cacheCount = \count($this->caches);
- $this->defaultLifetime = 0 < $defaultLifetime ? (int) $defaultLifetime : null;
+ $this->defaultLifetime = 0 < $defaultLifetime ? $defaultLifetime : null;
}
/**
/**
* {@inheritdoc}
+ *
+ * @return iterable
*/
public function getMultiple($keys, $default = null)
{
return $this->generateItems($this->caches[0]->getMultiple($keys, $miss), 0, $miss, $default);
}
- private function generateItems($values, $cacheIndex, $miss, $default)
+ private function generateItems(iterable $values, int $cacheIndex, $miss, $default): iterable
{
$missing = [];
$nextCacheIndex = $cacheIndex + 1;
- $nextCache = isset($this->caches[$nextCacheIndex]) ? $this->caches[$nextCacheIndex] : null;
+ $nextCache = $this->caches[$nextCacheIndex] ?? null;
foreach ($values as $k => $value) {
if ($miss !== $value) {
/**
* {@inheritdoc}
+ *
+ * @return bool
*/
public function has($key)
{
/**
* {@inheritdoc}
+ *
+ * @return bool
*/
public function clear()
{
/**
* {@inheritdoc}
+ *
+ * @return bool
*/
public function delete($key)
{
/**
* {@inheritdoc}
+ *
+ * @return bool
*/
public function deleteMultiple($keys)
{
/**
* {@inheritdoc}
+ *
+ * @return bool
*/
public function set($key, $value, $ttl = null)
{
/**
* {@inheritdoc}
+ *
+ * @return bool
*/
public function setMultiple($values, $ttl = null)
{
public function reset()
{
foreach ($this->caches as $cache) {
- if ($cache instanceof ResettableInterface) {
+ if ($cache instanceof ResetInterface) {
$cache->reset();
}
}
namespace Symfony\Component\Cache\Simple;
use Doctrine\Common\Cache\CacheProvider;
+use Symfony\Component\Cache\Adapter\DoctrineAdapter;
use Symfony\Component\Cache\Traits\DoctrineTrait;
+use Symfony\Contracts\Cache\CacheInterface;
+@trigger_error(sprintf('The "%s" class is deprecated since Symfony 4.3, use "%s" and type-hint for "%s" instead.', DoctrineCache::class, DoctrineAdapter::class, CacheInterface::class), \E_USER_DEPRECATED);
+
+/**
+ * @deprecated since Symfony 4.3, use DoctrineAdapter and type-hint for CacheInterface instead.
+ */
class DoctrineCache extends AbstractCache
{
use DoctrineTrait;
- /**
- * @param string $namespace
- * @param int $defaultLifetime
- */
- public function __construct(CacheProvider $provider, $namespace = '', $defaultLifetime = 0)
+ public function __construct(CacheProvider $provider, string $namespace = '', int $defaultLifetime = 0)
{
parent::__construct('', $defaultLifetime);
$this->provider = $provider;
namespace Symfony\Component\Cache\Simple;
+use Symfony\Component\Cache\Adapter\FilesystemAdapter;
+use Symfony\Component\Cache\Marshaller\DefaultMarshaller;
+use Symfony\Component\Cache\Marshaller\MarshallerInterface;
use Symfony\Component\Cache\PruneableInterface;
use Symfony\Component\Cache\Traits\FilesystemTrait;
+use Symfony\Contracts\Cache\CacheInterface;
+@trigger_error(sprintf('The "%s" class is deprecated since Symfony 4.3, use "%s" and type-hint for "%s" instead.', FilesystemCache::class, FilesystemAdapter::class, CacheInterface::class), \E_USER_DEPRECATED);
+
+/**
+ * @deprecated since Symfony 4.3, use FilesystemAdapter and type-hint for CacheInterface instead.
+ */
class FilesystemCache extends AbstractCache implements PruneableInterface
{
use FilesystemTrait;
- /**
- * @param string $namespace
- * @param int $defaultLifetime
- * @param string|null $directory
- */
- public function __construct($namespace = '', $defaultLifetime = 0, $directory = null)
+ 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);
}
namespace Symfony\Component\Cache\Simple;
+use Symfony\Component\Cache\Adapter\MemcachedAdapter;
+use Symfony\Component\Cache\Marshaller\MarshallerInterface;
use Symfony\Component\Cache\Traits\MemcachedTrait;
+use Symfony\Contracts\Cache\CacheInterface;
+@trigger_error(sprintf('The "%s" class is deprecated since Symfony 4.3, use "%s" and type-hint for "%s" instead.', MemcachedCache::class, MemcachedAdapter::class, CacheInterface::class), \E_USER_DEPRECATED);
+
+/**
+ * @deprecated since Symfony 4.3, use MemcachedAdapter and type-hint for CacheInterface instead.
+ */
class MemcachedCache extends AbstractCache
{
use MemcachedTrait;
protected $maxIdLength = 250;
- /**
- * @param string $namespace
- * @param int $defaultLifetime
- */
- public function __construct(\Memcached $client, $namespace = '', $defaultLifetime = 0)
+ public function __construct(\Memcached $client, string $namespace = '', int $defaultLifetime = 0, MarshallerInterface $marshaller = null)
{
- $this->init($client, $namespace, $defaultLifetime);
+ $this->init($client, $namespace, $defaultLifetime, $marshaller);
}
}
namespace Symfony\Component\Cache\Simple;
-use Psr\SimpleCache\CacheInterface;
+use Psr\SimpleCache\CacheInterface as Psr16CacheInterface;
+use Symfony\Component\Cache\Adapter\NullAdapter;
+use Symfony\Contracts\Cache\CacheInterface;
+
+@trigger_error(sprintf('The "%s" class is deprecated since Symfony 4.3, use "%s" and type-hint for "%s" instead.', NullCache::class, NullAdapter::class, CacheInterface::class), \E_USER_DEPRECATED);
/**
- * @author Nicolas Grekas <p@tchwork.com>
+ * @deprecated since Symfony 4.3, use NullAdapter and type-hint for CacheInterface instead.
*/
-class NullCache implements CacheInterface
+class NullCache implements Psr16CacheInterface
{
/**
* {@inheritdoc}
/**
* {@inheritdoc}
+ *
+ * @return iterable
*/
public function getMultiple($keys, $default = null)
{
/**
* {@inheritdoc}
+ *
+ * @return bool
*/
public function has($key)
{
/**
* {@inheritdoc}
+ *
+ * @return bool
*/
public function clear()
{
/**
* {@inheritdoc}
+ *
+ * @return bool
*/
public function delete($key)
{
/**
* {@inheritdoc}
+ *
+ * @return bool
*/
public function deleteMultiple($keys)
{
/**
* {@inheritdoc}
+ *
+ * @return bool
*/
public function set($key, $value, $ttl = null)
{
/**
* {@inheritdoc}
+ *
+ * @return bool
*/
public function setMultiple($values, $ttl = null)
{
namespace Symfony\Component\Cache\Simple;
+use Symfony\Component\Cache\Adapter\PdoAdapter;
+use Symfony\Component\Cache\Marshaller\MarshallerInterface;
use Symfony\Component\Cache\PruneableInterface;
use Symfony\Component\Cache\Traits\PdoTrait;
+use Symfony\Contracts\Cache\CacheInterface;
+@trigger_error(sprintf('The "%s" class is deprecated since Symfony 4.3, use "%s" and type-hint for "%s" instead.', PdoCache::class, PdoAdapter::class, CacheInterface::class), \E_USER_DEPRECATED);
+
+/**
+ * @deprecated since Symfony 4.3, use PdoAdapter and type-hint for CacheInterface instead.
+ */
class PdoCache extends AbstractCache implements PruneableInterface
{
use PdoTrait;
* a Doctrine DBAL Connection or a DSN string that will be used to
* lazy-connect to the database when the cache is actually used.
*
+ * When a Doctrine DBAL Connection is passed, 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_password: The password when lazy-connect [default: '']
* * db_connection_options: An array of driver-specific connection options [default: []]
*
- * @param \PDO|Connection|string $connOrDsn A \PDO or Connection instance or DSN string or null
- * @param string $namespace
- * @param int $defaultLifetime
- * @param array $options An associative array of options
+ * @param \PDO|Connection|string $connOrDsn a \PDO or Connection instance or DSN string or null
*
* @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($connOrDsn, $namespace = '', $defaultLifetime = 0, array $options = [])
+ public function __construct($connOrDsn, string $namespace = '', int $defaultLifetime = 0, array $options = [], MarshallerInterface $marshaller = null)
{
- $this->init($connOrDsn, $namespace, $defaultLifetime, $options);
+ $this->init($connOrDsn, $namespace, $defaultLifetime, $options, $marshaller);
}
}
namespace Symfony\Component\Cache\Simple;
-use Psr\SimpleCache\CacheInterface;
+use Psr\SimpleCache\CacheInterface as Psr16CacheInterface;
+use Symfony\Component\Cache\Adapter\PhpArrayAdapter;
use Symfony\Component\Cache\Exception\InvalidArgumentException;
use Symfony\Component\Cache\PruneableInterface;
use Symfony\Component\Cache\ResettableInterface;
use Symfony\Component\Cache\Traits\PhpArrayTrait;
+use Symfony\Contracts\Cache\CacheInterface;
+
+@trigger_error(sprintf('The "%s" class is deprecated since Symfony 4.3, use "%s" and type-hint for "%s" instead.', PhpArrayCache::class, PhpArrayAdapter::class, CacheInterface::class), \E_USER_DEPRECATED);
/**
- * 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 <galopintitouan@gmail.com>
- * @author Nicolas Grekas <p@tchwork.com>
+ * @deprecated since Symfony 4.3, use PhpArrayAdapter and type-hint for CacheInterface instead.
*/
-class PhpArrayCache implements CacheInterface, PruneableInterface, ResettableInterface
+class PhpArrayCache implements Psr16CacheInterface, PruneableInterface, ResettableInterface
{
use PhpArrayTrait;
/**
- * @param string $file The PHP file were values are cached
- * @param CacheInterface $fallbackPool A pool to fallback on when an item is not hit
+ * @param string $file The PHP file were values are cached
+ * @param Psr16CacheInterface $fallbackPool A pool to fallback on when an item is not hit
*/
- public function __construct($file, CacheInterface $fallbackPool)
+ public function __construct(string $file, Psr16CacheInterface $fallbackPool)
{
$this->file = $file;
$this->pool = $fallbackPool;
- $this->zendDetectUnicode = filter_var(ini_get('zend.detect_unicode'), \FILTER_VALIDATE_BOOLEAN);
}
/**
- * This adapter should only be used on PHP 7.0+ to take advantage of how PHP
- * stores arrays in its latest versions. This factory method decorates the given
- * fallback pool with this adapter only if the current PHP version is supported.
+ * This adapter takes advantage of how PHP stores arrays in its latest versions.
*
* @param string $file The PHP file were values are cached
* @param CacheInterface $fallbackPool A pool to fallback on when an item is not hit
*
- * @return CacheInterface
+ * @return Psr16CacheInterface
*/
- public static function create($file, CacheInterface $fallbackPool)
+ public static function create($file, Psr16CacheInterface $fallbackPool)
{
- if (\PHP_VERSION_ID >= 70000) {
- return new static($file, $fallbackPool);
- }
-
- return $fallbackPool;
+ return new static($file, $fallbackPool);
}
/**
if (null === $this->values) {
$this->initialize();
}
- if (!isset($this->values[$key])) {
+ if (!isset($this->keys[$key])) {
return $this->pool->get($key, $default);
}
-
- $value = $this->values[$key];
+ $value = $this->values[$this->keys[$key]];
if ('N;' === $value) {
- $value = null;
- } elseif (\is_string($value) && isset($value[2]) && ':' === $value[1]) {
+ return null;
+ }
+ if ($value instanceof \Closure) {
try {
- $e = null;
- $value = unserialize($value);
- } catch (\Error $e) {
- } catch (\Exception $e) {
- }
- if (null !== $e) {
+ return $value();
+ } catch (\Throwable $e) {
return $default;
}
}
/**
* {@inheritdoc}
+ *
+ * @return iterable
*/
public function getMultiple($keys, $default = null)
{
/**
* {@inheritdoc}
+ *
+ * @return bool
*/
public function has($key)
{
$this->initialize();
}
- return isset($this->values[$key]) || $this->pool->has($key);
+ return isset($this->keys[$key]) || $this->pool->has($key);
}
/**
* {@inheritdoc}
+ *
+ * @return bool
*/
public function delete($key)
{
$this->initialize();
}
- return !isset($this->values[$key]) && $this->pool->delete($key);
+ return !isset($this->keys[$key]) && $this->pool->delete($key);
}
/**
* {@inheritdoc}
+ *
+ * @return bool
*/
public function deleteMultiple($keys)
{
throw new InvalidArgumentException(sprintf('Cache key must be string, "%s" given.', \is_object($key) ? \get_class($key) : \gettype($key)));
}
- if (isset($this->values[$key])) {
+ if (isset($this->keys[$key])) {
$deleted = false;
} else {
$fallbackKeys[] = $key;
/**
* {@inheritdoc}
+ *
+ * @return bool
*/
public function set($key, $value, $ttl = null)
{
$this->initialize();
}
- return !isset($this->values[$key]) && $this->pool->set($key, $value, $ttl);
+ return !isset($this->keys[$key]) && $this->pool->set($key, $value, $ttl);
}
/**
* {@inheritdoc}
+ *
+ * @return bool
*/
public function setMultiple($values, $ttl = null)
{
throw new InvalidArgumentException(sprintf('Cache key must be string, "%s" given.', \is_object($key) ? \get_class($key) : \gettype($key)));
}
- if (isset($this->values[$key])) {
+ if (isset($this->keys[$key])) {
$saved = false;
} else {
$fallbackValues[$key] = $value;
return $saved;
}
- private function generateItems(array $keys, $default)
+ private function generateItems(array $keys, $default): iterable
{
$fallbackKeys = [];
foreach ($keys as $key) {
- if (isset($this->values[$key])) {
- $value = $this->values[$key];
+ if (isset($this->keys[$key])) {
+ $value = $this->values[$this->keys[$key]];
if ('N;' === $value) {
yield $key => null;
- } elseif (\is_string($value) && isset($value[2]) && ':' === $value[1]) {
+ } elseif ($value instanceof \Closure) {
try {
- yield $key => unserialize($value);
- } catch (\Error $e) {
- yield $key => $default;
- } catch (\Exception $e) {
+ yield $key => $value();
+ } catch (\Throwable $e) {
yield $key => $default;
}
} else {
}
if ($fallbackKeys) {
- foreach ($this->pool->getMultiple($fallbackKeys, $default) as $key => $item) {
- yield $key => $item;
- }
+ yield from $this->pool->getMultiple($fallbackKeys, $default);
}
}
}
namespace Symfony\Component\Cache\Simple;
+use Symfony\Component\Cache\Adapter\PhpFilesAdapter;
use Symfony\Component\Cache\Exception\CacheException;
use Symfony\Component\Cache\PruneableInterface;
use Symfony\Component\Cache\Traits\PhpFilesTrait;
+use Symfony\Contracts\Cache\CacheInterface;
+@trigger_error(sprintf('The "%s" class is deprecated since Symfony 4.3, use "%s" and type-hint for "%s" instead.', PhpFilesCache::class, PhpFilesAdapter::class, CacheInterface::class), \E_USER_DEPRECATED);
+
+/**
+ * @deprecated since Symfony 4.3, use PhpFilesAdapter and type-hint for CacheInterface instead.
+ */
class PhpFilesCache extends AbstractCache implements PruneableInterface
{
use PhpFilesTrait;
/**
- * @param string $namespace
- * @param int $defaultLifetime
- * @param string|null $directory
+ * @param $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($namespace = '', $defaultLifetime = 0, $directory = null)
+ public function __construct(string $namespace = '', int $defaultLifetime = 0, string $directory = null, bool $appendOnly = false)
{
- if (!static::isSupported()) {
- throw new CacheException('OPcache is not enabled.');
- }
+ $this->appendOnly = $appendOnly;
+ self::$startTime = self::$startTime ?? $_SERVER['REQUEST_TIME'] ?? time();
parent::__construct('', $defaultLifetime);
$this->init($namespace, $directory);
-
- $e = new \Exception();
- $this->includeHandler = function () use ($e) { throw $e; };
- $this->zendDetectUnicode = filter_var(ini_get('zend.detect_unicode'), \FILTER_VALIDATE_BOOLEAN);
+ $this->includeHandler = static function ($type, $msg, $file, $line) {
+ throw new \ErrorException($msg, 0, $type, $file, $line);
+ };
}
}
namespace Symfony\Component\Cache\Simple;
-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\CacheItem;
-use Symfony\Component\Cache\Exception\InvalidArgumentException;
-use Symfony\Component\Cache\PruneableInterface;
-use Symfony\Component\Cache\ResettableInterface;
-use Symfony\Component\Cache\Traits\ProxyTrait;
+use Symfony\Component\Cache\Psr16Cache;
+
+@trigger_error(sprintf('The "%s" class is deprecated since Symfony 4.3, use "%s" instead.', Psr6Cache::class, Psr16Cache::class), \E_USER_DEPRECATED);
/**
- * @author Nicolas Grekas <p@tchwork.com>
+ * @deprecated since Symfony 4.3, use Psr16Cache instead.
*/
-class Psr6Cache implements CacheInterface, PruneableInterface, ResettableInterface
+class Psr6Cache extends Psr16Cache
{
- use ProxyTrait;
-
- private $createCacheItem;
- private $cacheItemPrototype;
-
- 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->key = $allowInt && \is_int($key) ? (string) $key : CacheItem::validateKey($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, $value, $allowInt);
- };
- }
-
- /**
- * {@inheritdoc}
- */
- public function get($key, $default = null)
- {
- 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;
- }
-
- /**
- * {@inheritdoc}
- */
- public function set($key, $value, $ttl = null)
- {
- 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);
- }
-
- /**
- * {@inheritdoc}
- */
- public function delete($key)
- {
- try {
- return $this->pool->deleteItem($key);
- } catch (SimpleCacheException $e) {
- throw $e;
- } catch (Psr6CacheException $e) {
- throw new InvalidArgumentException($e->getMessage(), $e->getCode(), $e);
- }
- }
-
- /**
- * {@inheritdoc}
- */
- public function clear()
- {
- return $this->pool->clear();
- }
-
- /**
- * {@inheritdoc}
- */
- public function getMultiple($keys, $default = null)
- {
- 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.', \is_object($keys) ? \get_class($keys) : \gettype($keys)));
- }
-
- try {
- $items = $this->pool->getItems($keys);
- } catch (SimpleCacheException $e) {
- throw $e;
- } catch (Psr6CacheException $e) {
- throw new InvalidArgumentException($e->getMessage(), $e->getCode(), $e);
- }
- $values = [];
-
- foreach ($items as $key => $item) {
- $values[$key] = $item->isHit() ? $item->get() : $default;
- }
-
- return $values;
- }
-
- /**
- * {@inheritdoc}
- */
- public function setMultiple($values, $ttl = null)
- {
- $valuesIsArray = \is_array($values);
- if (!$valuesIsArray && !$values instanceof \Traversable) {
- throw new InvalidArgumentException(sprintf('Cache values must be array or Traversable, "%s" given.', \is_object($values) ? \get_class($values) : \gettype($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;
- }
-
- /**
- * {@inheritdoc}
- */
- public function deleteMultiple($keys)
- {
- 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.', \is_object($keys) ? \get_class($keys) : \gettype($keys)));
- }
-
- try {
- return $this->pool->deleteItems($keys);
- } catch (SimpleCacheException $e) {
- throw $e;
- } catch (Psr6CacheException $e) {
- throw new InvalidArgumentException($e->getMessage(), $e->getCode(), $e);
- }
- }
-
- /**
- * {@inheritdoc}
- */
- public function has($key)
- {
- try {
- return $this->pool->hasItem($key);
- } catch (SimpleCacheException $e) {
- throw $e;
- } catch (Psr6CacheException $e) {
- throw new InvalidArgumentException($e->getMessage(), $e->getCode(), $e);
- }
- }
}
namespace Symfony\Component\Cache\Simple;
+use Symfony\Component\Cache\Adapter\RedisAdapter;
+use Symfony\Component\Cache\Marshaller\MarshallerInterface;
+use Symfony\Component\Cache\Traits\RedisClusterProxy;
+use Symfony\Component\Cache\Traits\RedisProxy;
use Symfony\Component\Cache\Traits\RedisTrait;
+use Symfony\Contracts\Cache\CacheInterface;
+@trigger_error(sprintf('The "%s" class is deprecated since Symfony 4.3, use "%s" and type-hint for "%s" instead.', RedisCache::class, RedisAdapter::class, CacheInterface::class), \E_USER_DEPRECATED);
+
+/**
+ * @deprecated since Symfony 4.3, use RedisAdapter and type-hint for CacheInterface instead.
+ */
class RedisCache extends AbstractCache
{
use RedisTrait;
/**
- * @param \Redis|\RedisArray|\RedisCluster|\Predis\Client $redisClient
- * @param string $namespace
- * @param int $defaultLifetime
+ * @param \Redis|\RedisArray|\RedisCluster|\Predis\ClientInterface|RedisProxy|RedisClusterProxy $redis
*/
- public function __construct($redisClient, $namespace = '', $defaultLifetime = 0)
+ public function __construct($redis, string $namespace = '', int $defaultLifetime = 0, MarshallerInterface $marshaller = null)
{
- $this->init($redisClient, $namespace, $defaultLifetime);
+ $this->init($redis, $namespace, $defaultLifetime, $marshaller);
}
}
namespace Symfony\Component\Cache\Simple;
-use Psr\SimpleCache\CacheInterface;
+use Psr\SimpleCache\CacheInterface as Psr16CacheInterface;
+use Symfony\Component\Cache\Adapter\TraceableAdapter;
use Symfony\Component\Cache\PruneableInterface;
use Symfony\Component\Cache\ResettableInterface;
+use Symfony\Contracts\Cache\CacheInterface;
+use Symfony\Contracts\Service\ResetInterface;
+
+@trigger_error(sprintf('The "%s" class is deprecated since Symfony 4.3, use "%s" and type-hint for "%s" instead.', TraceableCache::class, TraceableAdapter::class, CacheInterface::class), \E_USER_DEPRECATED);
/**
- * An adapter that collects data about all cache calls.
- *
- * @author Nicolas Grekas <p@tchwork.com>
+ * @deprecated since Symfony 4.3, use TraceableAdapter and type-hint for CacheInterface instead.
*/
-class TraceableCache implements CacheInterface, PruneableInterface, ResettableInterface
+class TraceableCache implements Psr16CacheInterface, PruneableInterface, ResettableInterface
{
private $pool;
private $miss;
private $calls = [];
- public function __construct(CacheInterface $pool)
+ public function __construct(Psr16CacheInterface $pool)
{
$this->pool = $pool;
$this->miss = new \stdClass();
/**
* {@inheritdoc}
+ *
+ * @return bool
*/
public function has($key)
{
/**
* {@inheritdoc}
+ *
+ * @return bool
*/
public function delete($key)
{
/**
* {@inheritdoc}
+ *
+ * @return bool
*/
public function set($key, $value, $ttl = null)
{
/**
* {@inheritdoc}
+ *
+ * @return bool
*/
public function setMultiple($values, $ttl = null)
{
/**
* {@inheritdoc}
+ *
+ * @return iterable
*/
public function getMultiple($keys, $default = null)
{
/**
* {@inheritdoc}
+ *
+ * @return bool
*/
public function clear()
{
/**
* {@inheritdoc}
+ *
+ * @return bool
*/
public function deleteMultiple($keys)
{
*/
public function reset()
{
- if (!$this->pool instanceof ResettableInterface) {
+ if (!$this->pool instanceof ResetInterface) {
return;
}
$event = $this->start(__FUNCTION__);
}
}
- private function start($name)
+ private function start(string $name): TraceableCacheEvent
{
$this->calls[] = $event = new TraceableCacheEvent();
$event->name = $name;
--- /dev/null
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * 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 Symfony\Component\Cache\CacheItem;
+use Symfony\Component\Cache\Exception\InvalidArgumentException;
+
+/**
+ * @author Nicolas Grekas <p@tchwork.com>
+ *
+ * @internal
+ */
+trait AbstractAdapterTrait
+{
+ use AbstractTrait;
+
+ /**
+ * @var \Closure needs to be set by class, signature is function(string <key>, mixed <value>, bool <isHit>)
+ */
+ private $createCacheItem;
+
+ /**
+ * @var \Closure needs to be set by class, signature is function(array <deferred>, string <namespace>, array <&expiredIds>)
+ */
+ private $mergeByLifetime;
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getItem($key)
+ {
+ $id = $this->getId($key);
+
+ if (isset($this->deferred[$key])) {
+ $this->commit();
+ }
+
+ $f = $this->createCacheItem;
+ $isHit = false;
+ $value = null;
+
+ try {
+ foreach ($this->doFetch([$id]) as $value) {
+ $isHit = true;
+ }
+
+ return $f($key, $value, $isHit);
+ } catch (\Exception $e) {
+ CacheItem::log($this->logger, 'Failed to fetch key "{key}": '.$e->getMessage(), ['key' => $key, 'exception' => $e]);
+ }
+
+ return $f($key, null, false);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getItems(array $keys = [])
+ {
+ $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]);
+ $items = [];
+ }
+ $ids = array_combine($ids, $keys);
+
+ return $this->generateItems($items, $ids);
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @return bool
+ */
+ public function save(CacheItemInterface $item)
+ {
+ if (!$item instanceof CacheItem) {
+ return false;
+ }
+ $this->deferred[$item->getKey()] = $item;
+
+ return $this->commit();
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @return bool
+ */
+ public function saveDeferred(CacheItemInterface $item)
+ {
+ if (!$item instanceof CacheItem) {
+ return false;
+ }
+ $this->deferred[$item->getKey()] = $item;
+
+ return true;
+ }
+
+ /**
+ * @return array
+ */
+ public function __sleep()
+ {
+ throw new \BadMethodCallException('Cannot serialize '.__CLASS__);
+ }
+
+ public function __wakeup()
+ {
+ throw new \BadMethodCallException('Cannot unserialize '.__CLASS__);
+ }
+
+ public function __destruct()
+ {
+ if ($this->deferred) {
+ $this->commit();
+ }
+ }
+
+ private function generateItems(iterable $items, array &$keys): iterable
+ {
+ $f = $this->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]);
+ }
+
+ foreach ($keys as $key) {
+ yield $key => $f($key, null, false);
+ }
+ }
+}
private $namespaceVersion = '';
private $versioningIsEnabled = false;
private $deferred = [];
+ private $ids = [];
/**
* @var int|null The maximum length to enforce for identifiers or null when no limit applies
*
* @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, $lifetime);
+ abstract protected function doSave(array $values, int $lifetime);
/**
* {@inheritdoc}
+ *
+ * @return bool
*/
public function hasItem($key)
{
try {
return $this->doHave($id);
} catch (\Exception $e) {
- CacheItem::log($this->logger, 'Failed to check if key "{key}" is cached', ['key' => $key, 'exception' => $e]);
+ CacheItem::log($this->logger, 'Failed to check if key "{key}" is cached: '.$e->getMessage(), ['key' => $key, 'exception' => $e]);
return false;
}
/**
* {@inheritdoc}
+ *
+ * @param string $prefix
+ *
+ * @return bool
*/
- public function clear()
+ public function clear(/* string $prefix = '' */)
{
$this->deferred = [];
if ($cleared = $this->versioningIsEnabled) {
- $namespaceVersion = substr_replace(base64_encode(pack('V', mt_rand())), static::NS_SEPARATOR, 5);
+ 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 {
- $cleared = $this->doSave([static::NS_SEPARATOR.$this->namespace => $namespaceVersion], 0);
+ $e = $this->doSave([static::NS_SEPARATOR.$this->namespace => $namespaceVersion], 0);
} catch (\Exception $e) {
- $cleared = false;
}
- if ($cleared = true === $cleared || [] === $cleared) {
+ 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]);
+ } else {
$this->namespaceVersion = $namespaceVersion;
+ $this->ids = [];
}
+ } else {
+ $prefix = 0 < \func_num_args() ? (string) func_get_arg(0) : '';
+ $namespaceToClear = $this->namespace.$prefix;
}
try {
- return $this->doClear($this->namespace) || $cleared;
+ return $this->doClear($namespaceToClear) || $cleared;
} catch (\Exception $e) {
- CacheItem::log($this->logger, 'Failed to clear the cache', ['exception' => $e]);
+ CacheItem::log($this->logger, 'Failed to clear the cache: '.$e->getMessage(), ['exception' => $e]);
return false;
}
/**
* {@inheritdoc}
+ *
+ * @return bool
*/
public function deleteItem($key)
{
/**
* {@inheritdoc}
+ *
+ * @return bool
*/
public function deleteItems(array $keys)
{
}
} catch (\Exception $e) {
}
- CacheItem::log($this->logger, 'Failed to delete key "{key}"', ['key' => $key, 'exception' => $e]);
+ $message = 'Failed to delete key "{key}"'.($e instanceof \Exception ? ': '.$e->getMessage() : '.');
+ CacheItem::log($this->logger, $message, ['key' => $key, 'exception' => $e]);
$ok = false;
}
$wasEnabled = $this->versioningIsEnabled;
$this->versioningIsEnabled = (bool) $enable;
$this->namespaceVersion = '';
+ $this->ids = [];
return $wasEnabled;
}
$this->commit();
}
$this->namespaceVersion = '';
+ $this->ids = [];
}
/**
* @return mixed
*
* @throws \Exception
+ *
+ * @deprecated since Symfony 4.2, use DefaultMarshaller instead.
*/
protected static function unserialize($value)
{
+ @trigger_error(sprintf('The "%s::unserialize()" method is deprecated since Symfony 4.2, use DefaultMarshaller instead.', __CLASS__), \E_USER_DEPRECATED);
+
if ('b:0;' === $value) {
return false;
}
}
}
- private function getId($key)
+ private function getId($key): string
{
- CacheItem::validateKey($key);
-
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 = substr_replace(base64_encode(pack('V', time())), static::NS_SEPARATOR, 5);
- $this->doSave([static::NS_SEPARATOR.$this->namespace => $this->namespaceVersion], 0);
+ $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]);
+ }
+ }
+
+ if (\is_string($key) && isset($this->ids[$key])) {
+ return $this->namespace.$this->namespaceVersion.$this->ids[$key];
+ }
+ 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) {
- $id = $this->namespace.$this->namespaceVersion.substr_replace(base64_encode(hash('sha256', $key, true)), static::NS_SEPARATOR, -(\strlen($this->namespaceVersion) + 22));
+ // Use MD5 to favor speed over security, which is not an issue here
+ $this->ids[$key] = $id = substr_replace(base64_encode(hash('md5', $key, true)), static::NS_SEPARATOR, -(\strlen($this->namespaceVersion) + 2));
+ $id = $this->namespace.$this->namespaceVersion.$id;
}
return $id;
{
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), '/', '_');
+ }
}
{
public static function isSupported()
{
- return \function_exists('apcu_fetch') && filter_var(ini_get('apc.enabled'), \FILTER_VALIDATE_BOOLEAN);
+ return \function_exists('apcu_fetch') && filter_var(\ini_get('apc.enabled'), \FILTER_VALIDATE_BOOLEAN);
}
- private function init($namespace, $defaultLifetime, $version)
+ private function init(string $namespace, int $defaultLifetime, ?string $version)
{
if (!static::isSupported()) {
throw new CacheException('APCu is not enabled.');
*/
protected function doFetch(array $ids)
{
+ $unserializeCallbackHandler = ini_set('unserialize_callback_func', __CLASS__.'::handleUnserializeCallback');
try {
- foreach (apcu_fetch($ids, $ok) ?: [] as $k => $v) {
+ $values = [];
+ $ids = array_flip($ids);
+ foreach (apcu_fetch(array_keys($ids), $ok) ?: [] as $k => $v) {
+ if (!isset($ids[$k])) {
+ // work around https://github.com/krakjoe/apcu/issues/247
+ $k = key($ids);
+ }
+ unset($ids[$k]);
+
if (null !== $v || $ok) {
- yield $k => $v;
+ $values[$k] = $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 doClear($namespace)
{
- return isset($namespace[0]) && class_exists('APCuIterator', false) && ('cli' !== \PHP_SAPI || filter_var(ini_get('apc.enable_cli'), \FILTER_VALIDATE_BOOLEAN))
- ? apcu_delete(new \APCuIterator(sprintf('/^%s/', preg_quote($namespace, '/')), \APC_ITER_KEY))
+ return isset($namespace[0]) && class_exists(\APCUIterator::class, false) && ('cli' !== \PHP_SAPI || filter_var(\ini_get('apc.enable_cli'), \FILTER_VALIDATE_BOOLEAN))
+ ? apcu_delete(new \APCUIterator(sprintf('/^%s/', preg_quote($namespace, '/')), \APC_ITER_KEY))
: apcu_clear_cache();
}
/**
* {@inheritdoc}
*/
- protected function doSave(array $values, $lifetime)
+ protected function doSave(array $values, int $lifetime)
{
try {
if (false === $failures = apcu_store($values, null, $lifetime)) {
}
return array_keys($failures);
- } catch (\Error $e) {
- } catch (\Exception $e) {
- }
+ } catch (\Throwable $e) {
+ if (1 === \count($values)) {
+ // Workaround https://github.com/krakjoe/apcu/issues/170
+ apcu_delete(array_key_first($values));
+ }
- if (1 === \count($values)) {
- // Workaround https://github.com/krakjoe/apcu/issues/170
- apcu_delete(key($values));
+ throw $e;
}
-
- throw $e;
}
}
*/
public function getValues()
{
- return $this->values;
+ 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;
}
/**
* {@inheritdoc}
+ *
+ * @return bool
*/
public function hasItem($key)
{
+ if (\is_string($key) && isset($this->expiries[$key]) && $this->expiries[$key] > microtime(true)) {
+ return true;
+ }
CacheItem::validateKey($key);
- return isset($this->expiries[$key]) && ($this->expiries[$key] > time() || !$this->deleteItem($key));
+ return isset($this->expiries[$key]) && !$this->deleteItem($key);
}
/**
* {@inheritdoc}
+ *
+ * @param string $prefix
+ *
+ * @return bool
*/
- public function clear()
+ public function clear(/* string $prefix = '' */)
{
- $this->values = $this->expiries = [];
+ $prefix = 0 < \func_num_args() ? (string) func_get_arg(0) : '';
+
+ if ('' !== $prefix) {
+ foreach ($this->values as $key => $value) {
+ if (str_starts_with($key, $prefix)) {
+ unset($this->values[$key], $this->expiries[$key]);
+ }
+ }
+ } else {
+ $this->values = $this->expiries = [];
+ }
return true;
}
/**
* {@inheritdoc}
+ *
+ * @return bool
*/
public function deleteItem($key)
{
- CacheItem::validateKey($key);
-
+ if (!\is_string($key) || !isset($this->expiries[$key])) {
+ CacheItem::validateKey($key);
+ }
unset($this->values[$key], $this->expiries[$key]);
return true;
$this->clear();
}
- private function generateItems(array $keys, $now, $f)
+ private function generateItems(array $keys, float $now, callable $f): iterable
{
foreach ($keys as $i => $key) {
- try {
- if (!$isHit = isset($this->expiries[$key]) && ($this->expiries[$key] > $now || !$this->deleteItem($key))) {
- $this->values[$key] = $value = null;
- } elseif (!$this->storeSerialized) {
- $value = $this->values[$key];
- } elseif ('b:0;' === $value = $this->values[$key]) {
- $value = false;
- } elseif (false === $value = unserialize($value)) {
- $this->values[$key] = $value = null;
- $isHit = false;
- }
- } catch (\Exception $e) {
- CacheItem::log($this->logger, 'Failed to unserialize key "{key}"', ['key' => $key, 'exception' => $e]);
+ if (!$isHit = isset($this->expiries[$key]) && ($this->expiries[$key] > $now || !$this->deleteItem($key))) {
$this->values[$key] = $value = null;
- $isHit = false;
+ } else {
+ $value = $this->storeSerialized ? $this->unfreeze($key, $isHit) : $this->values[$key];
}
unset($keys[$i]);
yield $key => $f($key, null, false);
}
}
+
+ private function freeze($value, $key)
+ {
+ 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]);
+ $type = \is_object($value) ? \get_class($value) : \gettype($value);
+ $message = sprintf('Failed to save key "{key}" of type %s: ', $type).$e->getMessage();
+ CacheItem::log($this->logger, $message, ['key' => $key, 'exception' => $e]);
+
+ 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)
+ {
+ 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]);
+ $value = false;
+ }
+ if (false === $value) {
+ $this->values[$key] = $value = null;
+ $isHit = false;
+ }
+ }
+
+ return $value;
+ }
}
--- /dev/null
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * 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 <p@tchwork.com>
+ *
+ * @internal
+ */
+trait ContractsTrait
+{
+ use CacheTrait {
+ doGet as private contractsGet;
+ }
+
+ private $callbackWrapper;
+ private $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 = \Closure::fromCallable([LockRegistry::class, 'compute']);
+
+ if (\in_array(\PHP_SAPI, ['cli', 'phpdbg'], true)) {
+ $this->setCallbackWrapper(null);
+ }
+ }
+
+ $previousWrapper = $this->callbackWrapper;
+ $this->callbackWrapper = $callbackWrapper ?? static function (callable $callback, ItemInterface $item, bool &$save, CacheInterface $pool, \Closure $setMetadata, ?LoggerInterface $logger) {
+ return $callback($item, $save);
+ };
+
+ return $previousWrapper;
+ }
+
+ private function doGet(AdapterInterface $pool, string $key, callable $callback, ?float $beta, array &$metadata = null)
+ {
+ if (0 > $beta = $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 = $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]);
+ }
+ },
+ null,
+ CacheItem::class
+ );
+
+ 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);
+ }
+}
/**
* {@inheritdoc}
*/
- protected function doSave(array $values, $lifetime)
+ protected function doSave(array $values, int $lifetime)
{
return $this->provider->saveMultiple($values, $lifetime);
}
private $directory;
private $tmp;
- private function init($namespace, $directory)
+ private function init(string $namespace, ?string $directory)
{
if (!isset($directory[0])) {
- $directory = sys_get_temp_dir().'/symfony-cache';
+ $directory = sys_get_temp_dir().\DIRECTORY_SEPARATOR.'symfony-cache';
} else {
$directory = realpath($directory) ?: $directory;
}
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 (!file_exists($directory)) {
@mkdir($directory, 0777, true);
{
$ok = true;
- foreach (new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($this->directory, \FilesystemIterator::SKIP_DOTS)) as $file) {
- $ok = ($file->isDir() || @unlink($file) || !file_exists($file)) && $ok;
+ 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;
foreach ($ids as $id) {
$file = $this->getFile($id);
- $ok = (!file_exists($file) || @unlink($file) || !file_exists($file)) && $ok;
+ $ok = (!file_exists($file) || $this->doUnlink($file) || !file_exists($file)) && $ok;
}
return $ok;
}
- private function write($file, $data, $expiresAt = null)
+ protected function doUnlink($file)
+ {
+ return @unlink($file);
+ }
+
+ private function write(string $file, string $data, int $expiresAt = null)
{
set_error_handler(__CLASS__.'::throwError');
try {
if (null === $this->tmp) {
- $this->tmp = $this->directory.uniqid('', true);
+ $this->tmp = $this->directory.bin2hex(random_bytes(6));
}
- file_put_contents($this->tmp, $data);
+ try {
+ $h = fopen($this->tmp, 'x');
+ } catch (\ErrorException $e) {
+ if (!str_contains($e->getMessage(), 'File exists')) {
+ throw $e;
+ }
+
+ $this->tmp = $this->directory.bin2hex(random_bytes(6));
+ $h = fopen($this->tmp, 'x');
+ }
+ fwrite($h, $data);
+ fclose($h);
if (null !== $expiresAt) {
- touch($this->tmp, $expiresAt);
+ touch($this->tmp, $expiresAt ?: time() + 31556952); // 1 year in seconds
}
return rename($this->tmp, $file);
}
}
- private function getFile($id, $mkdir = false)
+ private function getFile(string $id, bool $mkdir = false, string $directory = null)
{
- $hash = str_replace('/', '-', base64_encode(hash('sha256', static::class.$id, true)));
- $dir = $this->directory.strtoupper($hash[0].\DIRECTORY_SEPARATOR.$hash[1].\DIRECTORY_SEPARATOR);
+ // Use MD5 to favor speed over security, which is not an issue here
+ $hash = str_replace('/', '-', base64_encode(hash('md5', static::class.$id, true)));
+ $dir = ($directory ?? $this->directory).strtoupper($hash[0].\DIRECTORY_SEPARATOR.$hash[1].\DIRECTORY_SEPARATOR);
if ($mkdir && !file_exists($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 (!file_exists($directory)) {
+ return;
+ }
+
+ $chars = '+-ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
+
+ for ($i = 0; $i < 38; ++$i) {
+ if (!file_exists($directory.$chars[$i])) {
+ continue;
+ }
+
+ for ($j = 0; $j < 38; ++$j) {
+ if (!file_exists($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;
+ }
+ }
+ }
+ }
+ }
+
/**
* @internal
*/
throw new \ErrorException($message, 0, $type, $file, $line);
}
+ /**
+ * @return array
+ */
public function __sleep()
{
throw new \BadMethodCallException('Cannot serialize '.__CLASS__);
{
use FilesystemCommonTrait;
+ private $marshaller;
+
/**
* @return bool
*/
$time = time();
$pruned = true;
- foreach (new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($this->directory, \FilesystemIterator::SKIP_DOTS), \RecursiveIteratorIterator::LEAVES_ONLY) as $file) {
- if (!$h = @fopen($file, 'rb')) {
+ foreach ($this->scanHashDir($this->directory) as $file) {
+ if (!$h = @fopen($file, 'r')) {
continue;
}
foreach ($ids as $id) {
$file = $this->getFile($id);
- if (!file_exists($file) || !$h = @fopen($file, 'rb')) {
+ if (!file_exists($file) || !$h = @fopen($file, 'r')) {
continue;
}
if (($expiresAt = (int) fgets($h)) && $now >= $expiresAt) {
$value = stream_get_contents($h);
fclose($h);
if ($i === $id) {
- $values[$id] = parent::unserialize($value);
+ $values[$id] = $this->marshaller->unmarshall($value);
}
}
}
/**
* {@inheritdoc}
*/
- protected function doSave(array $values, $lifetime)
+ protected function doSave(array $values, int $lifetime)
{
- $ok = true;
$expiresAt = $lifetime ? (time() + $lifetime) : 0;
+ $values = $this->marshaller->marshall($values, $failed);
foreach ($values as $id => $value) {
- $ok = $this->write($this->getFile($id, true), $expiresAt."\n".rawurlencode($id)."\n".serialize($value), $expiresAt) && $ok;
+ if (!$this->write($this->getFile($id, true), $expiresAt."\n".rawurlencode($id)."\n".$value, $expiresAt)) {
+ $failed[] = $id;
+ }
}
- if (!$ok && !is_writable($this->directory)) {
+ if ($failed && !is_writable($this->directory)) {
throw new CacheException(sprintf('Cache directory is not writable (%s).', $this->directory));
}
- return $ok;
+ 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));
}
}
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 <rmf@src.run>
\Memcached::OPT_SERIALIZER => \Memcached::SERIALIZER_PHP,
];
+ /**
+ * 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 \Symfony\Component\Cache\Adapter\AbstractAdapter::NS_SEPARATOR}.
+ */
+ private static $RESERVED_MEMCACHED = " \n\r\t\v\f\0";
+ private static $RESERVED_PSR6 = '@()\{}/';
+
+ private $marshaller;
private $client;
private $lazyClient;
public static function isSupported()
{
- return \extension_loaded('memcached') && version_compare(phpversion('memcached'), '2.2.0', '>=');
+ return \extension_loaded('memcached') && version_compare(phpversion('memcached'), \PHP_VERSION_ID >= 80100 ? '3.1.6' : '2.2.0', '>=');
}
- private function init(\Memcached $client, $namespace, $defaultLifetime)
+ private function init(\Memcached $client, string $namespace, int $defaultLifetime, ?MarshallerInterface $marshaller)
{
if (!static::isSupported()) {
- throw new CacheException('Memcached >= 2.2.0 is required.');
+ throw new CacheException('Memcached '.(\PHP_VERSION_ID >= 80100 ? '> 3.1.5' : '>= 2.2.0').' is required.');
}
if ('Memcached' === \get_class($client)) {
$opt = $client->getOption(\Memcached::OPT_SERIALIZER);
parent::__construct($namespace, $defaultLifetime);
$this->enableVersioning();
+ $this->marshaller = $marshaller ?? new DefaultMarshaller();
}
/**
* - [['localhost', 11211, 33]]
*
* @param array[]|string|string[] $servers An array of servers, a DSN, or an array of DSNs
- * @param array $options An array of options
*
* @return \Memcached
*
throw new InvalidArgumentException(sprintf('MemcachedAdapter::createClient() expects array or string as first argument, "%s" given.', \gettype($servers)));
}
if (!static::isSupported()) {
- throw new CacheException('Memcached >= 2.2.0 is required.');
+ throw new CacheException('Memcached '.(\PHP_VERSION_ID >= 80100 ? '> 3.1.5' : '>= 2.2.0').' is required.');
}
set_error_handler(function ($type, $msg, $file, $line) { throw new \ErrorException($msg, 0, $type, $file, $line); });
try {
if (\is_array($dsn)) {
continue;
}
- if (0 !== strpos($dsn, 'memcached://')) {
- throw new InvalidArgumentException(sprintf('Invalid Memcached DSN: "%s" does not start with "memcached://".', $dsn));
+ if (!str_starts_with($dsn, 'memcached:')) {
+ throw new InvalidArgumentException(sprintf('Invalid Memcached DSN: "%s" does not start with "memcached:".', $dsn));
}
- $params = preg_replace_callback('#^memcached://(?:([^@]*+)@)?#', function ($m) use (&$username, &$password) {
- if (!empty($m[1])) {
- list($username, $password) = explode(':', $m[1], 2) + [1 => null];
+ $params = preg_replace_callback('#^memcached:(//)?(?:([^@]*+)@)?#', function ($m) use (&$username, &$password) {
+ if (!empty($m[2])) {
+ [$username, $password] = explode(':', $m[2], 2) + [1 => null];
}
- return 'file://';
+ return 'file:'.($m[1] ?? '');
}, $dsn);
if (false === $params = parse_url($params)) {
throw new InvalidArgumentException(sprintf('Invalid Memcached DSN: "%s".', $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(sprintf('Invalid Memcached DSN: "%s".', $dsn));
+ }
+ 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(sprintf('Invalid Memcached DSN: "%s".', $dsn));
}
$params['path'] = substr($params['path'], 0, -\strlen($m[0]));
}
$params += [
- 'host' => isset($params['host']) ? $params['host'] : $params['path'],
+ 'host' => $params['host'] ?? $params['path'],
'port' => isset($params['host']) ? 11211 : null,
'weight' => 0,
];
- if (isset($params['query'])) {
- parse_str($params['query'], $query);
+ 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
/**
* {@inheritdoc}
*/
- protected function doSave(array $values, $lifetime)
+ protected function doSave(array $values, int $lifetime)
{
+ if (!$values = $this->marshaller->marshall($values, $failed)) {
+ return $failed;
+ }
+
if ($lifetime && $lifetime > 30 * 86400) {
$lifetime += time();
}
$encodedValues = [];
foreach ($values as $key => $value) {
- $encodedValues[rawurlencode($key)] = $value;
+ $encodedValues[self::encodeKey($key)] = $value;
}
- return $this->checkResultCode($this->getClient()->setMulti($encodedValues, $lifetime));
+ return $this->checkResultCode($this->getClient()->setMulti($encodedValues, $lifetime)) ? $failed : false;
}
/**
*/
protected function doFetch(array $ids)
{
- $unserializeCallbackHandler = ini_set('unserialize_callback_func', __CLASS__.'::handleUnserializeCallback');
try {
- $encodedIds = array_map('rawurlencode', $ids);
+ $encodedIds = array_map([__CLASS__, 'encodeKey'], $ids);
$encodedResult = $this->checkResultCode($this->getClient()->getMulti($encodedIds));
$result = [];
foreach ($encodedResult as $key => $value) {
- $result[rawurldecode($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());
- } finally {
- ini_set('unserialize_callback_func', $unserializeCallbackHandler);
}
}
*/
protected function doHave($id)
{
- return false !== $this->getClient()->get(rawurlencode($id)) || $this->checkResultCode(\Memcached::RES_SUCCESS === $this->client->getResultCode());
+ return false !== $this->getClient()->get(self::encodeKey($id)) || $this->checkResultCode(\Memcached::RES_SUCCESS === $this->client->getResultCode());
}
/**
protected function doDelete(array $ids)
{
$ok = true;
- $encodedIds = array_map('rawurlencode', $ids);
+ $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;
throw new CacheException('MemcachedAdapter client error: '.strtolower($this->client->getResultMessage()));
}
- /**
- * @return \Memcached
- */
- private function getClient()
+ private function getClient(): \Memcached
{
if ($this->client) {
return $this->client;
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);
+ }
}
use Doctrine\DBAL\Connection;
use Doctrine\DBAL\DBALException;
use Doctrine\DBAL\Driver\ServerInfoAwareConnection;
+use Doctrine\DBAL\DriverManager;
+use Doctrine\DBAL\Exception;
+use Doctrine\DBAL\Exception\TableNotFoundException;
+use Doctrine\DBAL\ParameterType;
use Doctrine\DBAL\Schema\Schema;
+use Doctrine\DBAL\Statement;
use Symfony\Component\Cache\Exception\InvalidArgumentException;
+use Symfony\Component\Cache\Marshaller\DefaultMarshaller;
+use Symfony\Component\Cache\Marshaller\MarshallerInterface;
/**
* @internal
*/
trait PdoTrait
{
+ private $marshaller;
private $conn;
private $dsn;
private $driver;
private $connectionOptions = [];
private $namespace;
- private function init($connOrDsn, $namespace, $defaultLifetime, array $options)
+ private function init($connOrDsn, string $namespace, int $defaultLifetime, array $options, ?MarshallerInterface $marshaller)
{
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]));
throw new InvalidArgumentException(sprintf('"%s" requires PDO or Doctrine\DBAL\Connection instance or DSN string as first argument, "%s" given.', __CLASS__, \is_object($connOrDsn) ? \get_class($connOrDsn) : \gettype($connOrDsn)));
}
- $this->table = isset($options['db_table']) ? $options['db_table'] : $this->table;
- $this->idCol = isset($options['db_id_col']) ? $options['db_id_col'] : $this->idCol;
- $this->dataCol = isset($options['db_data_col']) ? $options['db_data_col'] : $this->dataCol;
- $this->lifetimeCol = isset($options['db_lifetime_col']) ? $options['db_lifetime_col'] : $this->lifetimeCol;
- $this->timeCol = isset($options['db_time_col']) ? $options['db_time_col'] : $this->timeCol;
- $this->username = isset($options['db_username']) ? $options['db_username'] : $this->username;
- $this->password = isset($options['db_password']) ? $options['db_password'] : $this->password;
- $this->connectionOptions = isset($options['db_connection_options']) ? $options['db_connection_options'] : $this->connectionOptions;
+ $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);
}
*
* @throws \PDOException When the table already exists
* @throws DBALException When the table already exists
+ * @throws Exception When the table already exists
* @throws \DomainException When an unsupported PDO driver is used
*/
public function createTable()
throw new \DomainException(sprintf('Creating the cache table is currently not implemented for PDO driver "%s".', $this->driver));
}
- if (method_exists($conn, 'executeStatement')) {
+ if ($conn instanceof Connection && method_exists($conn, 'executeStatement')) {
$conn->executeStatement($sql);
} else {
$conn->exec($sql);
$deleteSql .= " AND $this->idCol LIKE :namespace";
}
- $delete = $this->getConnection()->prepare($deleteSql);
- $delete->bindValue(':time', time(), \PDO::PARAM_INT);
+ $connection = $this->getConnection();
+ $useDbalConstants = $connection instanceof Connection;
+
+ try {
+ $delete = $connection->prepare($deleteSql);
+ } catch (TableNotFoundException $e) {
+ return true;
+ } catch (\PDOException $e) {
+ return true;
+ }
+ $delete->bindValue(':time', time(), $useDbalConstants ? ParameterType::INTEGER : \PDO::PARAM_INT);
if ('' !== $this->namespace) {
- $delete->bindValue(':namespace', sprintf('%s%%', $this->namespace), \PDO::PARAM_STR);
+ $delete->bindValue(':namespace', sprintf('%s%%', $this->namespace), $useDbalConstants ? ParameterType::STRING : \PDO::PARAM_STR);
}
+ try {
+ // Doctrine DBAL ^2.13 || >= 3.1
+ if ($delete instanceof Statement && method_exists($delete, 'executeStatement')) {
+ $delete->executeStatement();
- return $delete->execute();
+ return true;
+ }
+
+ return $delete->execute();
+ } catch (TableNotFoundException $e) {
+ return true;
+ } catch (\PDOException $e) {
+ return true;
+ }
}
/**
*/
protected function doFetch(array $ids)
{
+ $connection = $this->getConnection();
+ $useDbalConstants = $connection instanceof Connection;
+
$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 = $this->getConnection()->prepare($sql);
- $stmt->bindValue($i = 1, $now, \PDO::PARAM_INT);
+ $stmt = $connection->prepare($sql);
+ $stmt->bindValue($i = 1, $now, $useDbalConstants ? ParameterType::INTEGER : \PDO::PARAM_INT);
foreach ($ids as $id) {
$stmt->bindValue(++$i, $id);
}
if (null === $row[1]) {
$expired[] = $row[0];
} else {
- yield $row[0] => parent::unserialize(\is_resource($row[1]) ? stream_get_contents($row[1]) : $row[1]);
+ 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 = $this->getConnection()->prepare($sql);
- $stmt->bindValue($i = 1, $now, \PDO::PARAM_INT);
+ $stmt = $connection->prepare($sql);
+ $stmt->bindValue($i = 1, $now, $useDbalConstants ? ParameterType::INTEGER : \PDO::PARAM_INT);
foreach ($expired as $id) {
$stmt->bindValue(++$i, $id);
}
*/
protected function doHave($id)
{
+ $connection = $this->getConnection();
+ $useDbalConstants = $connection instanceof Connection;
+
$sql = "SELECT 1 FROM $this->table WHERE $this->idCol = :id AND ($this->lifetimeCol IS NULL OR $this->lifetimeCol + $this->timeCol > :time)";
- $stmt = $this->getConnection()->prepare($sql);
+ $stmt = $connection->prepare($sql);
$stmt->bindValue(':id', $id);
- $stmt->bindValue(':time', time(), \PDO::PARAM_INT);
+ $stmt->bindValue(':time', time(), $useDbalConstants ? ParameterType::INTEGER : \PDO::PARAM_INT);
$result = $stmt->execute();
return (bool) (\is_object($result) ? $result->fetchOne() : $stmt->fetchColumn());
$sql = "DELETE FROM $this->table WHERE $this->idCol LIKE '$namespace%'";
}
- if (method_exists($conn, 'executeStatement')) {
- $conn->executeStatement($sql);
- } else {
- $conn->exec($sql);
+ try {
+ if ($conn instanceof Connection && method_exists($conn, 'executeStatement')) {
+ $conn->executeStatement($sql);
+ } else {
+ $conn->exec($sql);
+ }
+ } catch (TableNotFoundException $e) {
+ } catch (\PDOException $e) {
}
return true;
{
$sql = str_pad('', (\count($ids) << 1) - 1, '?,');
$sql = "DELETE FROM $this->table WHERE $this->idCol IN ($sql)";
- $stmt = $this->getConnection()->prepare($sql);
- $stmt->execute(array_values($ids));
+ try {
+ $stmt = $this->getConnection()->prepare($sql);
+ $stmt->execute(array_values($ids));
+ } catch (TableNotFoundException $e) {
+ } catch (\PDOException $e) {
+ }
return true;
}
/**
* {@inheritdoc}
*/
- protected function doSave(array $values, $lifetime)
+ protected function doSave(array $values, int $lifetime)
{
- $serialized = [];
- $failed = [];
-
- foreach ($values as $id => $value) {
- try {
- $serialized[$id] = serialize($value);
- } catch (\Exception $e) {
- $failed[] = $id;
- }
- }
-
- if (!$serialized) {
+ if (!$values = $this->marshaller->marshall($values, $failed)) {
return $failed;
}
$conn = $this->getConnection();
+ $useDbalConstants = $conn instanceof Connection;
+
$driver = $this->driver;
$insertSql = "INSERT INTO $this->table ($this->idCol, $this->dataCol, $this->lifetimeCol, $this->timeCol) VALUES (:id, :data, :lifetime, :time)";
$now = time();
$lifetime = $lifetime ?: null;
- $stmt = $conn->prepare($sql);
+ try {
+ $stmt = $conn->prepare($sql);
+ } catch (TableNotFoundException $e) {
+ if (!$conn->isTransactionActive() || \in_array($this->driver, ['pgsql', 'sqlite', 'sqlsrv'], true)) {
+ $this->createTable();
+ }
+ $stmt = $conn->prepare($sql);
+ } catch (\PDOException $e) {
+ if (!$conn->inTransaction() || \in_array($this->driver, ['pgsql', 'sqlite', 'sqlsrv'], true)) {
+ $this->createTable();
+ }
+ $stmt = $conn->prepare($sql);
+ }
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);
+ $stmt->bindParam(3, $data, $useDbalConstants ? ParameterType::LARGE_OBJECT : \PDO::PARAM_LOB);
+ $stmt->bindValue(4, $lifetime, $useDbalConstants ? ParameterType::INTEGER : \PDO::PARAM_INT);
+ $stmt->bindValue(5, $now, $useDbalConstants ? ParameterType::INTEGER : \PDO::PARAM_INT);
+ $stmt->bindParam(6, $data, $useDbalConstants ? ParameterType::LARGE_OBJECT : \PDO::PARAM_LOB);
+ $stmt->bindValue(7, $lifetime, $useDbalConstants ? ParameterType::INTEGER : \PDO::PARAM_INT);
+ $stmt->bindValue(8, $now, $useDbalConstants ? ParameterType::INTEGER : \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);
+ $stmt->bindParam(':data', $data, $useDbalConstants ? ParameterType::LARGE_OBJECT : \PDO::PARAM_LOB);
+ $stmt->bindValue(':lifetime', $lifetime, $useDbalConstants ? ParameterType::INTEGER : \PDO::PARAM_INT);
+ $stmt->bindValue(':time', $now, $useDbalConstants ? ParameterType::INTEGER : \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);
+ $insertStmt->bindParam(':data', $data, $useDbalConstants ? ParameterType::LARGE_OBJECT : \PDO::PARAM_LOB);
+ $insertStmt->bindValue(':lifetime', $lifetime, $useDbalConstants ? ParameterType::INTEGER : \PDO::PARAM_INT);
+ $insertStmt->bindValue(':time', $now, $useDbalConstants ? ParameterType::INTEGER : \PDO::PARAM_INT);
}
- foreach ($serialized as $id => $data) {
- $result = $stmt->execute();
-
+ foreach ($values as $id => $data) {
+ try {
+ $result = $stmt->execute();
+ } catch (TableNotFoundException $e) {
+ if (!$conn->isTransactionActive() || \in_array($this->driver, ['pgsql', 'sqlite', 'sqlsrv'], true)) {
+ $this->createTable();
+ }
+ $result = $stmt->execute();
+ } catch (\PDOException $e) {
+ if (!$conn->inTransaction() || \in_array($this->driver, ['pgsql', 'sqlite', 'sqlsrv'], true)) {
+ $this->createTable();
+ }
+ $result = $stmt->execute();
+ }
if (null === $driver && !(\is_object($result) ? $result->rowCount() : $stmt->rowCount())) {
try {
$insertStmt->execute();
- } catch (DBALException $e) {
+ } catch (DBALException|Exception $e) {
} catch (\PDOException $e) {
// A concurrent write won, let it be
}
private function getConnection()
{
if (null === $this->conn) {
- $this->conn = new \PDO($this->dsn, $this->username, $this->password, $this->connectionOptions);
- $this->conn->setAttribute(\PDO::ATTR_ERRMODE, \PDO::ERRMODE_EXCEPTION);
+ if (strpos($this->dsn, '://')) {
+ if (!class_exists(DriverManager::class)) {
+ throw new InvalidArgumentException(sprintf('Failed to parse the DSN "%s". Try running "composer require doctrine/dbal".', $this->dsn));
+ }
+ $this->conn = DriverManager::getConnection(['url' => $this->dsn]);
+ } else {
+ $this->conn = new \PDO($this->dsn, $this->username, $this->password, $this->connectionOptions);
+ $this->conn->setAttribute(\PDO::ATTR_ERRMODE, \PDO::ERRMODE_EXCEPTION);
+ }
}
if (null === $this->driver) {
if ($this->conn instanceof \PDO) {
$driver = $this->conn->getDriver();
switch (true) {
- case $driver instanceof \Doctrine\DBAL\Driver\AbstractMySQLDriver:
- case $driver instanceof \Doctrine\DBAL\Driver\DrizzlePDOMySql\Driver:
case $driver instanceof \Doctrine\DBAL\Driver\Mysqli\Driver:
- case $driver instanceof \Doctrine\DBAL\Driver\PDOMySql\Driver:
- case $driver instanceof \Doctrine\DBAL\Driver\PDO\MySQL\Driver:
+ throw new \LogicException(sprintf('The adapter "%s" does not support the mysqli driver, use pdo_mysql instead.', static::class));
+ case $driver instanceof \Doctrine\DBAL\Driver\AbstractMySQLDriver:
$this->driver = 'mysql';
break;
case $driver instanceof \Doctrine\DBAL\Driver\PDOSqlite\Driver:
case $driver instanceof \Doctrine\DBAL\Driver\PDO\SQLSrv\Driver:
$this->driver = 'sqlsrv';
break;
+ case $driver instanceof \Doctrine\DBAL\Driver:
+ $this->driver = [
+ 'mssql' => 'sqlsrv',
+ 'oracle' => 'oci',
+ 'postgresql' => 'pgsql',
+ 'sqlite' => 'sqlite',
+ 'mysql' => 'mysql',
+ ][$driver->getDatabasePlatform()->getName()] ?? \get_class($driver);
+ break;
default:
$this->driver = \get_class($driver);
break;
return $this->conn;
}
- /**
- * @return string
- */
- private function getServerVersion()
+ private function getServerVersion(): string
{
if (null === $this->serverVersion) {
$conn = $this->conn instanceof \PDO ? $this->conn : $this->conn->getWrappedConnection();
namespace Symfony\Component\Cache\Traits;
+use Symfony\Component\Cache\Adapter\AdapterInterface;
use Symfony\Component\Cache\CacheItem;
use Symfony\Component\Cache\Exception\InvalidArgumentException;
+use Symfony\Component\VarExporter\VarExporter;
/**
* @author Titouan Galopin <galopintitouan@gmail.com>
use ProxyTrait;
private $file;
+ private $keys;
private $values;
- private $zendDetectUnicode;
private static $valuesCache = [];
}
}
+ $dumpedValues = '';
+ $dumpedMap = [];
$dump = <<<'EOF'
<?php
// This file has been auto-generated by the Symfony Cache Component.
-return [
+return [[
EOF;
foreach ($values as $key => $value) {
CacheItem::validateKey(\is_int($key) ? (string) $key : $key);
+ $isStaticValue = true;
- if (null === $value || \is_object($value)) {
+ if (null === $value) {
+ $value = "'N;'";
+ } elseif (\is_object($value) || \is_array($value)) {
try {
- $value = serialize($value);
+ $value = VarExporter::export($value, $isStaticValue);
} catch (\Exception $e) {
- throw new InvalidArgumentException(sprintf('Cache key "%s" has non-serializable "%s" value.', $key, \get_class($value)), 0, $e);
- }
- } elseif (\is_array($value)) {
- try {
- $serialized = serialize($value);
- $unserialized = unserialize($serialized);
- } catch (\Exception $e) {
- throw new InvalidArgumentException(sprintf('Cache key "%s" has non-serializable array value.', $key), 0, $e);
- }
- // Store arrays serialized if they contain any objects or references
- if ($unserialized !== $value || (false !== strpos($serialized, ';R:') && preg_match('/;R:[1-9]/', $serialized))) {
- $value = $serialized;
+ throw new InvalidArgumentException(sprintf('Cache key "%s" has non-serializable "%s" value.', $key, \is_object($value) ? \get_class($value) : 'array'), 0, $e);
}
} elseif (\is_string($value)) {
- // Serialize strings if they could be confused with serialized objects or arrays
- if ('N;' === $value || (isset($value[2]) && ':' === $value[1])) {
- $value = serialize($value);
+ // Wrap "N;" in a closure to not confuse it with an encoded `null`
+ if ('N;' === $value) {
+ $isStaticValue = false;
}
- } elseif (!is_scalar($value)) {
+ $value = var_export($value, true);
+ } elseif (!\is_scalar($value)) {
throw new InvalidArgumentException(sprintf('Cache key "%s" has non-serializable "%s" value.', $key, \gettype($value)));
+ } else {
+ $value = var_export($value, true);
+ }
+
+ if (!$isStaticValue) {
+ $value = str_replace("\n", "\n ", $value);
+ $value = "static function () {\n return {$value};\n}";
+ }
+ $hash = hash('md5', $value);
+
+ if (null === $id = $dumpedMap[$hash] ?? null) {
+ $id = $dumpedMap[$hash] = \count($dumpedMap);
+ $dumpedValues .= "{$id} => {$value},\n";
}
- $dump .= var_export($key, true).' => '.var_export($value, true).",\n";
+ $dump .= var_export($key, true)." => {$id},\n";
}
- $dump .= "\n];\n";
- $dump = str_replace("' . \"\\0\" . '", "\0", $dump);
+ $dump .= "\n], [\n\n{$dumpedValues}\n]];\n";
$tmpFile = uniqid($this->file, true);
file_put_contents($tmpFile, $dump);
@chmod($tmpFile, 0666 & ~umask());
- unset($serialized, $unserialized, $value, $dump);
+ unset($serialized, $value, $dump);
@rename($tmpFile, $this->file);
unset(self::$valuesCache[$this->file]);
/**
* {@inheritdoc}
+ *
+ * @param string $prefix
+ *
+ * @return bool
*/
- public function clear()
+ public function clear(/* string $prefix = '' */)
{
- $this->values = [];
+ $prefix = 0 < \func_num_args() ? (string) func_get_arg(0) : '';
+ $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;
}
private function initialize()
{
if (isset(self::$valuesCache[$this->file])) {
- $this->values = self::$valuesCache[$this->file];
+ $values = self::$valuesCache[$this->file];
+ } elseif (!file_exists($this->file)) {
+ $this->keys = $this->values = [];
return;
+ } else {
+ $values = self::$valuesCache[$this->file] = (include $this->file) ?: [[], []];
}
- if ($this->zendDetectUnicode) {
- $zmb = ini_set('zend.detect_unicode', 0);
- }
- try {
- $this->values = self::$valuesCache[$this->file] = file_exists($this->file) ? (include $this->file ?: []) : [];
- } finally {
- if ($this->zendDetectUnicode) {
- ini_set('zend.detect_unicode', $zmb);
- }
+ if (2 !== \count($values) || !isset($values[0], $values[1])) {
+ $this->keys = $this->values = [];
+ } else {
+ [$this->keys, $this->values] = $values;
}
}
}
use Symfony\Component\Cache\Exception\CacheException;
use Symfony\Component\Cache\Exception\InvalidArgumentException;
+use Symfony\Component\VarExporter\VarExporter;
/**
* @author Piotr Stankowski <git@trakos.pl>
*/
trait PhpFilesTrait
{
- use FilesystemCommonTrait;
+ use FilesystemCommonTrait {
+ doClear as private doCommonClear;
+ doDelete as private doCommonDelete;
+ }
private $includeHandler;
- private $zendDetectUnicode;
+ private $appendOnly;
+ private $values = [];
+ private $files = [];
+
+ private static $startTime;
+ private static $valuesCache = [];
public static function isSupported()
{
- return \function_exists('opcache_invalidate') && filter_var(ini_get('opcache.enable'), \FILTER_VALIDATE_BOOLEAN);
+ self::$startTime = self::$startTime ?? $_SERVER['REQUEST_TIME'] ?? time();
+
+ return \function_exists('opcache_invalidate') && filter_var(\ini_get('opcache.enable'), \FILTER_VALIDATE_BOOLEAN) && (!\in_array(\PHP_SAPI, ['cli', 'phpdbg'], true) || filter_var(\ini_get('opcache.enable_cli'), \FILTER_VALIDATE_BOOLEAN));
}
/**
{
$time = time();
$pruned = true;
- $allowCompile = 'cli' !== \PHP_SAPI || filter_var(ini_get('opcache.enable_cli'), \FILTER_VALIDATE_BOOLEAN);
+ $getExpiry = true;
set_error_handler($this->includeHandler);
try {
- foreach (new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($this->directory, \FilesystemIterator::SKIP_DOTS), \RecursiveIteratorIterator::LEAVES_ONLY) as $file) {
- list($expiresAt) = include $file;
+ 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 = @unlink($file) && !file_exists($file) && $pruned;
-
- if ($allowCompile) {
- @opcache_invalidate($file, true);
- }
+ $pruned = $this->doUnlink($file) && !file_exists($file) && $pruned;
}
}
} finally {
*/
protected function doFetch(array $ids)
{
+ if ($this->appendOnly) {
+ $now = 0;
+ $missingIds = [];
+ } else {
+ $now = time();
+ $missingIds = $ids;
+ $ids = [];
+ }
$values = [];
- $now = time();
- if ($this->zendDetectUnicode) {
- $zmb = ini_set('zend.detect_unicode', 0);
+ 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 {
- foreach ($ids as $id) {
+ $getExpiry = true;
+
+ foreach ($missingIds as $k => $id) {
try {
- $file = $this->getFile($id);
- list($expiresAt, $values[$id]) = include $file;
+ $file = $this->files[$id] ?? $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($values[$id]);
+ unset($this->values[$id], $missingIds[$k], self::$valuesCache[$file]);
}
- } catch (\Exception $e) {
- continue;
+ } catch (\ErrorException $e) {
+ unset($missingIds[$k]);
}
}
} finally {
restore_error_handler();
- if ($this->zendDetectUnicode) {
- ini_set('zend.detect_unicode', $zmb);
- }
- }
-
- foreach ($values as $id => $value) {
- if ('N;' === $value) {
- $values[$id] = null;
- } elseif (\is_string($value) && isset($value[2]) && ':' === $value[1]) {
- $values[$id] = parent::unserialize($value);
- }
}
- return $values;
+ $ids = $missingIds;
+ $missingIds = [];
+ goto begin;
}
/**
*/
protected function doHave($id)
{
- return (bool) $this->doFetch([$id]);
+ if ($this->appendOnly && isset($this->values[$id])) {
+ return true;
+ }
+
+ set_error_handler($this->includeHandler);
+ try {
+ $file = $this->files[$id] ?? $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 $e) {
+ return false;
+ } finally {
+ restore_error_handler();
+ }
+ if ($this->appendOnly) {
+ $now = 0;
+ $this->values[$id] = $value;
+ } else {
+ $now = time();
+ }
+
+ return $now < $expiresAt;
}
/**
* {@inheritdoc}
*/
- protected function doSave(array $values, $lifetime)
+ protected function doSave(array $values, int $lifetime)
{
$ok = true;
- $data = [$lifetime ? time() + $lifetime : \PHP_INT_MAX, ''];
- $allowCompile = 'cli' !== \PHP_SAPI || filter_var(ini_get('opcache.enable_cli'), \FILTER_VALIDATE_BOOLEAN);
+ $expiry = $lifetime ? time() + $lifetime : 'PHP_INT_MAX';
+ $allowCompile = self::isSupported();
foreach ($values as $key => $value) {
- if (null === $value || \is_object($value)) {
- $value = serialize($value);
- } elseif (\is_array($value)) {
- $serialized = serialize($value);
- $unserialized = parent::unserialize($serialized);
- // Store arrays serialized if they contain any objects or references
- if ($unserialized !== $value || (false !== strpos($serialized, ';R:') && preg_match('/;R:[1-9]/', $serialized))) {
- $value = $serialized;
+ 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, \is_object($value) ? \get_class($value) : 'array'), 0, $e);
}
} elseif (\is_string($value)) {
- // Serialize strings if they could be confused with serialized objects or arrays
- if ('N;' === $value || (isset($value[2]) && ':' === $value[1])) {
- $value = serialize($value);
+ // Wrap "N;" in a closure to not confuse it with an encoded `null`
+ if ('N;' === $value) {
+ $isStaticValue = false;
}
- } elseif (!is_scalar($value)) {
+ $value = var_export($value, true);
+ } elseif (!\is_scalar($value)) {
throw new InvalidArgumentException(sprintf('Cache key "%s" has non-serializable "%s" value.', $key, \gettype($value)));
+ } else {
+ $value = var_export($value, true);
}
- $data[1] = $value;
- $file = $this->getFile($key, true);
- $ok = $this->write($file, '<?php return '.var_export($data, true).';') && $ok;
+ $encodedKey = rawurlencode($key);
+
+ if ($isStaticValue) {
+ $value = "return [{$expiry}, {$value}];";
+ } elseif ($this->appendOnly) {
+ $value = "return [{$expiry}, static function () { return {$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, "<?php //{$encodedKey}\n\n{$value}\n", self::$startTime - 10) && $ok;
if ($allowCompile) {
@opcache_invalidate($file, true);
+ @opcache_compile_file($file);
}
+ unset(self::$valuesCache[$file]);
}
if (!$ok && !is_writable($this->directory)) {
return $ok;
}
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function doClear($namespace)
+ {
+ $this->values = [];
+
+ return $this->doCommonClear($namespace);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function doDelete(array $ids)
+ {
+ foreach ($ids as $id) {
+ unset($this->values[$id]);
+ }
+
+ return $this->doCommonDelete($ids);
+ }
+
+ protected function doUnlink($file)
+ {
+ 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 $file;
+
+ public function __construct(string $file)
+ {
+ $this->file = $file;
+ }
}
namespace Symfony\Component\Cache\Traits;
use Symfony\Component\Cache\PruneableInterface;
-use Symfony\Component\Cache\ResettableInterface;
+use Symfony\Contracts\Service\ResetInterface;
/**
* @author Nicolas Grekas <p@tchwork.com>
*/
public function reset()
{
- if ($this->pool instanceof ResettableInterface) {
+ if ($this->pool instanceof ResetInterface) {
$this->pool->reset();
}
}
--- /dev/null
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * 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 <jack.thomas@solidalpha.com>
+ *
+ * @internal
+ */
+class RedisClusterNodeProxy
+{
+ private $host;
+ private $redis;
+
+ /**
+ * @param \RedisCluster|RedisClusterProxy $redis
+ */
+ public function __construct(array $host, $redis)
+ {
+ $this->host = $host;
+ $this->redis = $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);
+ }
+}
--- /dev/null
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Cache\Traits;
+
+/**
+ * @author Alessandro Chitolina <alekitto@gmail.com>
+ *
+ * @internal
+ */
+class RedisClusterProxy
+{
+ private $redis;
+ private $initializer;
+
+ public function __construct(\Closure $initializer)
+ {
+ $this->initializer = $initializer;
+ }
+
+ public function __call($method, array $args)
+ {
+ $this->redis ?: $this->redis = $this->initializer->__invoke();
+
+ return $this->redis->{$method}(...$args);
+ }
+
+ public function hscan($strKey, &$iIterator, $strPattern = null, $iCount = null)
+ {
+ $this->redis ?: $this->redis = $this->initializer->__invoke();
+
+ return $this->redis->hscan($strKey, $iIterator, $strPattern, $iCount);
+ }
+
+ public function scan(&$iIterator, $strPattern = null, $iCount = null)
+ {
+ $this->redis ?: $this->redis = $this->initializer->__invoke();
+
+ return $this->redis->scan($iIterator, $strPattern, $iCount);
+ }
+
+ public function sscan($strKey, &$iIterator, $strPattern = null, $iCount = null)
+ {
+ $this->redis ?: $this->redis = $this->initializer->__invoke();
+
+ return $this->redis->sscan($strKey, $iIterator, $strPattern, $iCount);
+ }
+
+ public function zscan($strKey, &$iIterator, $strPattern = null, $iCount = null)
+ {
+ $this->redis ?: $this->redis = $this->initializer->__invoke();
+
+ return $this->redis->zscan($strKey, $iIterator, $strPattern, $iCount);
+ }
+}
{
$this->ready ?: $this->ready = $this->initializer->__invoke($this->redis);
- return \call_user_func_array([$this->redis, $method], $args);
+ return $this->redis->{$method}(...$args);
}
public function hscan($strKey, &$iIterator, $strPattern = null, $iCount = null)
use Predis\Connection\Aggregate\ClusterInterface;
use Predis\Connection\Aggregate\RedisCluster;
-use Predis\Connection\Factory;
+use Predis\Connection\Aggregate\ReplicationInterface;
+use Predis\Response\ErrorInterface;
use Predis\Response\Status;
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 <aurimas@niekis.lt>
'timeout' => 30,
'read_timeout' => 0,
'retry_interval' => 0,
- 'lazy' => false,
+ '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;
+ private $marshaller;
/**
- * @param \Redis|\RedisArray|\RedisCluster|\Predis\Client $redisClient
+ * @param \Redis|\RedisArray|\RedisCluster|\Predis\ClientInterface|RedisProxy|RedisClusterProxy $redis
*/
- private function init($redisClient, $namespace = '', $defaultLifetime = 0)
+ private function init($redis, string $namespace, int $defaultLifetime, ?MarshallerInterface $marshaller)
{
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 (!$redisClient instanceof \Redis && !$redisClient instanceof \RedisArray && !$redisClient instanceof \RedisCluster && !$redisClient instanceof \Predis\Client && !$redisClient instanceof RedisProxy) {
- throw new InvalidArgumentException(sprintf('"%s()" expects parameter 1 to be Redis, RedisArray, RedisCluster or Predis\Client, "%s" given.', __METHOD__, \is_object($redisClient) ? \get_class($redisClient) : \gettype($redisClient)));
+
+ if (!$redis instanceof \Redis && !$redis instanceof \RedisArray && !$redis instanceof \RedisCluster && !$redis instanceof \Predis\ClientInterface && !$redis instanceof RedisProxy && !$redis instanceof RedisClusterProxy) {
+ throw new InvalidArgumentException(sprintf('"%s()" expects parameter 1 to be Redis, RedisArray, RedisCluster or Predis\ClientInterface, "%s" given.', __METHOD__, \is_object($redis) ? \get_class($redis) : \gettype($redis)));
+ }
+
+ 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 = $redisClient;
+
+ $this->redis = $redis;
+ $this->marshaller = $marshaller ?? new DefaultMarshaller();
}
/**
*
* @throws InvalidArgumentException when the DSN is invalid
*
- * @return \Redis|\Predis\Client According to the "class" option
+ * @return \Redis|\RedisCluster|RedisClusterProxy|RedisProxy|\Predis\ClientInterface According to the "class" option
*/
public static function createConnection($dsn, array $options = [])
{
- if (0 !== strpos($dsn, 'redis://')) {
- throw new InvalidArgumentException(sprintf('Invalid Redis DSN: "%s" does not start with "redis://".', $dsn));
+ if (str_starts_with($dsn, 'redis:')) {
+ $scheme = 'redis';
+ } elseif (str_starts_with($dsn, 'rediss:')) {
+ $scheme = 'rediss';
+ } else {
+ throw new InvalidArgumentException(sprintf('Invalid Redis DSN: "%s" does not start with "redis:" or "rediss".', $dsn));
+ }
+
+ if (!\extension_loaded('redis') && !class_exists(\Predis\Client::class)) {
+ throw new CacheException(sprintf('Cannot find the "redis" extension nor the "predis/predis" package: "%s".', $dsn));
}
- $params = preg_replace_callback('#^redis://(?:(?:[^:@]*+:)?([^@]*+)@)?#', function ($m) use (&$auth) {
- if (isset($m[1])) {
- $auth = $m[1];
+
+ $params = preg_replace_callback('#^'.$scheme.':(//)?(?:(?:[^:@]*+:)?([^@]*+)@)?#', function ($m) use (&$auth) {
+ if (isset($m[2])) {
+ $auth = $m[2];
+
+ if ('' === $auth) {
+ $auth = null;
+ }
}
- return 'file://';
+ return 'file:'.($m[1] ?? '');
}, $dsn);
+
if (false === $params = parse_url($params)) {
throw new InvalidArgumentException(sprintf('Invalid Redis DSN: "%s".', $dsn));
}
- if (!isset($params['host']) && !isset($params['path'])) {
- throw new InvalidArgumentException(sprintf('Invalid Redis DSN: "%s".', $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(sprintf('Invalid Redis DSN: "%s".', $dsn));
+ }
+ 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['path']) && preg_match('#/(\d+)$#', $params['path'], $m)) {
- $params['dbindex'] = $m[1];
- $params['path'] = substr($params['path'], 0, -\strlen($m[0]));
+
+ 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];
+ $params['path'] = substr($params['path'], 0, -\strlen($m[0]));
+ } elseif (isset($params['host'])) {
+ throw new InvalidArgumentException(sprintf('Invalid Redis DSN: "%s", the "dbindex" parameter must be a number.', $dsn));
+ }
+ }
+
+ 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 (isset($params['host'])) {
- $scheme = 'tcp';
- } else {
- $scheme = 'unix';
+
+ if (!$hosts) {
+ throw new InvalidArgumentException(sprintf('Invalid Redis DSN: "%s".', $dsn));
}
- $params += [
- 'host' => isset($params['host']) ? $params['host'] : $params['path'],
- 'port' => isset($params['host']) ? 6379 : null,
- 'dbindex' => 0,
- ];
- if (isset($params['query'])) {
- parse_str($params['query'], $query);
- $params += $query;
+
+ $params += $query + $options + self::$defaultConnectionOptions;
+
+ if (isset($params['redis_sentinel']) && !class_exists(\Predis\Client::class)) {
+ throw new CacheException(sprintf('Redis Sentinel support requires the "predis/predis" package: "%s".', $dsn));
}
- $params += $options + self::$defaultConnectionOptions;
- if (null === $params['class'] && !\extension_loaded('redis') && !class_exists(\Predis\Client::class)) {
- throw new CacheException(sprintf('Cannot find the "redis" extension, and "predis/predis" is not installed: "%s".', $dsn));
+
+ if (null === $params['class'] && !isset($params['redis_sentinel']) && \extension_loaded('redis')) {
+ $class = $params['redis_cluster'] ? \RedisCluster::class : (1 < \count($hosts) ? \RedisArray::class : \Redis::class);
+ } else {
+ $class = $params['class'] ?? \Predis\Client::class;
+
+ if (isset($params['redis_sentinel']) && !is_a($class, \Predis\Client::class, true)) {
+ throw new CacheException(sprintf('Cannot use Redis Sentinel: class "%s" does not extend "Predis\Client": "%s".', $class, $dsn));
+ }
}
- $class = null === $params['class'] ? (\extension_loaded('redis') ? \Redis::class : \Predis\Client::class) : $params['class'];
if (is_a($class, \Redis::class, true)) {
$connect = $params['persistent'] || $params['persistent_id'] ? 'pconnect' : 'connect';
$redis = new $class();
- $initializer = function ($redis) use ($connect, $params, $dsn, $auth) {
+ $initializer = static function ($redis) use ($connect, $params, $dsn, $auth, $hosts, $tls) {
+ $host = $hosts[0]['host'] ?? $hosts[0]['path'];
+ $port = $hosts[0]['port'] ?? 0;
+
+ if (isset($hosts[0]['host']) && $tls) {
+ $host = 'tls://'.$host;
+ }
+
try {
- @$redis->{$connect}($params['host'], $params['port'], $params['timeout'], $params['persistent_id'], $params['retry_interval']);
+ @$redis->{$connect}($host, $port, $params['timeout'], (string) $params['persistent_id'], $params['retry_interval'], $params['read_timeout'], ...\defined('Redis::SCAN_PREFIX') ? [['stream' => $params['ssl'] ?? null]] : []);
set_error_handler(function ($type, $msg) use (&$error) { $error = $msg; });
$isConnected = $redis->isConnected();
if ((null !== $auth && !$redis->auth($auth))
|| ($params['dbindex'] && !$redis->select($params['dbindex']))
- || ($params['read_timeout'] && !$redis->setOption(\Redis::OPT_READ_TIMEOUT, $params['read_timeout']))
) {
$e = preg_replace('/^ERR /', '', $redis->getLastError());
throw new InvalidArgumentException(sprintf('Redis connection "%s" failed: ', $dsn).$e.'.');
}
+
+ if (0 < $params['tcp_keepalive'] && \defined('Redis::OPT_TCP_KEEPALIVE')) {
+ $redis->setOption(\Redis::OPT_TCP_KEEPALIVE, $params['tcp_keepalive']);
+ }
} catch (\RedisException $e) {
throw new InvalidArgumentException(sprintf('Redis connection "%s" failed: ', $dsn).$e->getMessage());
}
} else {
$initializer($redis);
}
- } elseif (is_a($class, \Predis\Client::class, true)) {
- $params['scheme'] = $scheme;
- $params['database'] = $params['dbindex'] ?: null;
- $params['password'] = $auth;
- $redis = new $class((new Factory())->create($params));
+ } elseif (is_a($class, \RedisArray::class, true)) {
+ foreach ($hosts as $i => $host) {
+ switch ($host['scheme']) {
+ case 'tcp': $hosts[$i] = $host['host'].':'.$host['port']; break;
+ case 'tls': $hosts[$i] = 'tls://'.$host['host'].':'.$host['port']; break;
+ default: $hosts[$i] = $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(sprintf('Redis connection "%s" failed: ', $dsn).$e->getMessage());
+ }
+
+ if (0 < $params['tcp_keepalive'] && \defined('Redis::OPT_TCP_KEEPALIVE')) {
+ $redis->setOption(\Redis::OPT_TCP_KEEPALIVE, $params['tcp_keepalive']);
+ }
+ } elseif (is_a($class, \RedisCluster::class, true)) {
+ $initializer = static function () use ($class, $params, $dsn, $hosts) {
+ foreach ($hosts as $i => $host) {
+ switch ($host['scheme']) {
+ case 'tcp': $hosts[$i] = $host['host'].':'.$host['port']; break;
+ case 'tls': $hosts[$i] = 'tls://'.$host['host'].':'.$host['port']; break;
+ default: $hosts[$i] = $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(sprintf('Redis connection "%s" failed: ', $dsn).$e->getMessage());
+ }
+
+ if (0 < $params['tcp_keepalive'] && \defined('Redis::OPT_TCP_KEEPALIVE')) {
+ $redis->setOption(\Redis::OPT_TCP_KEEPALIVE, $params['tcp_keepalive']);
+ }
+ switch ($params['failover']) {
+ case 'error': $redis->setOption(\RedisCluster::OPT_SLAVE_FAILOVER, \RedisCluster::FAILOVER_ERROR); break;
+ case 'distribute': $redis->setOption(\RedisCluster::OPT_SLAVE_FAILOVER, \RedisCluster::FAILOVER_DISTRIBUTE); break;
+ case 'slaves': $redis->setOption(\RedisCluster::OPT_SLAVE_FAILOVER, \RedisCluster::FAILOVER_DISTRIBUTE_SLAVES); break;
+ }
+
+ return $redis;
+ };
+
+ $redis = $params['lazy'] ? new RedisClusterProxy($initializer) : $initializer();
+ } elseif (is_a($class, \Predis\ClientInterface::class, true)) {
+ if ($params['redis_cluster']) {
+ $params['cluster'] = 'redis';
+ if (isset($params['redis_sentinel'])) {
+ throw new InvalidArgumentException(sprintf('Cannot use both "redis_cluster" and "redis_sentinel" at the same time: "%s".', $dsn));
+ }
+ } 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) {
+ $params['parameters']['password'] = $auth;
+ }
+ 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, array_diff_key(self::$defaultConnectionOptions, ['ssl' => null])));
+ 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" or "Predis\Client".', $class));
+ throw new InvalidArgumentException(sprintf('"%s" is not a subclass of "Redis", "RedisArray", "RedisCluster" nor "Predis\ClientInterface".', $class));
} else {
throw new InvalidArgumentException(sprintf('Class "%s" does not exist.', $class));
}
$result = [];
- if ($this->redis instanceof \Predis\Client && $this->redis->getConnection() instanceof ClusterInterface) {
+ if ($this->redis instanceof \Predis\ClientInterface && $this->redis->getConnection() instanceof ClusterInterface) {
$values = $this->pipeline(function () use ($ids) {
foreach ($ids as $id) {
yield 'get' => [$id];
foreach ($values as $id => $v) {
if ($v) {
- $result[$id] = parent::unserialize($v);
+ $result[$id] = $this->marshaller->unmarshall($v);
}
}
*/
protected function doClear($namespace)
{
- $cleared = true;
- $hosts = [$this->redis];
- $evalArgs = [[$namespace], 0];
-
- if ($this->redis instanceof \Predis\Client) {
- $evalArgs = [0, $namespace];
+ if ($this->redis instanceof \Predis\ClientInterface) {
+ $prefix = $this->redis->getOptions()->prefix ? $this->redis->getOptions()->prefix->getPrefix() : '';
+ $prefixLen = \strlen($prefix ?? '');
+ }
- $connection = $this->redis->getConnection();
- if ($connection instanceof ClusterInterface && $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[] = $h = new \Redis();
- $h->connect($host[0], $host[1]);
- }
+ $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;
}
$info = $host->info('Server');
- $info = isset($info['Server']) ? $info['Server'] : $info;
+ $info = !$info instanceof ErrorInterface ? $info['Server'] ?? $info : ['redis_version' => '2.0'];
+
+ if (!$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.
- $cleared = $host->eval("local keys=redis.call('KEYS',ARGV[1]..'*') for i=1,#keys,5000 do redis.call('DEL',unpack(keys,i,math.min(i+4999,#keys))) end return 1", $evalArgs[0], $evalArgs[1]) && $cleared;
+ $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('DEL',unpack(keys,i,math.min(i+4999,#keys))) end return 1", $args[0], $args[1]) && $cleared;
continue;
}
$cursor = null;
do {
- $keys = $host instanceof \Predis\Client ? $host->scan($cursor, 'MATCH', $namespace.'*', 'COUNT', 1000) : $host->scan($cursor, $namespace.'*', 1000);
+ $keys = $host instanceof \Predis\ClientInterface ? $host->scan($cursor, '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 = (int) $cursor);
return true;
}
- if ($this->redis instanceof \Predis\Client && $this->redis->getConnection() instanceof ClusterInterface) {
+ if ($this->redis instanceof \Predis\ClientInterface && $this->redis->getConnection() instanceof ClusterInterface) {
$this->pipeline(function () use ($ids) {
foreach ($ids as $id) {
yield 'del' => [$id];
/**
* {@inheritdoc}
*/
- protected function doSave(array $values, $lifetime)
+ protected function doSave(array $values, int $lifetime)
{
- $serialized = [];
- $failed = [];
-
- foreach ($values as $id => $value) {
- try {
- $serialized[$id] = serialize($value);
- } catch (\Exception $e) {
- $failed[] = $id;
- }
- }
-
- if (!$serialized) {
+ if (!$values = $this->marshaller->marshall($values, $failed)) {
return $failed;
}
- $results = $this->pipeline(function () use ($serialized, $lifetime) {
- foreach ($serialized as $id => $value) {
+ $results = $this->pipeline(function () use ($values, $lifetime) {
+ foreach ($values as $id => $value) {
if (0 >= $lifetime) {
yield 'set' => [$id, $value];
} else {
}
}
});
+
foreach ($results as $id => $result) {
- if (true !== $result && (!$result instanceof Status || $result !== Status::get('OK'))) {
+ if (true !== $result && (!$result instanceof Status || Status::get('OK') !== $result)) {
$failed[] = $id;
}
}
return $failed;
}
- private function pipeline(\Closure $generator)
+ private function pipeline(\Closure $generator, $redis = null): \Generator
{
$ids = [];
+ $redis = $redis ?? $this->redis;
- if ($this->redis instanceof \RedisCluster || ($this->redis instanceof \Predis\Client && $this->redis->getConnection() instanceof RedisCluster)) {
+ if ($redis instanceof RedisClusterProxy || $redis instanceof \RedisCluster || ($redis instanceof \Predis\ClientInterface && $redis->getConnection() instanceof RedisCluster)) {
// 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[] = \call_user_func_array([$this->redis, $command], $args);
- $ids[] = $args[0];
+ $results[] = $redis->{$command}(...$args);
+ $ids[] = 'eval' === $command ? ($redis instanceof \Predis\ClientInterface ? $args[2] : $args[1][0]) : $args[0];
}
- } elseif ($this->redis instanceof \Predis\Client) {
- $results = $this->redis->pipeline(function ($redis) use ($generator, &$ids) {
+ } elseif ($redis instanceof \Predis\ClientInterface) {
+ $results = $redis->pipeline(static function ($redis) use ($generator, &$ids) {
foreach ($generator() as $command => $args) {
- \call_user_func_array([$redis, $command], $args);
- $ids[] = $args[0];
+ $redis->{$command}(...$args);
+ $ids[] = 'eval' === $command ? $args[2] : $args[0];
}
});
- } elseif ($this->redis instanceof \RedisArray) {
+ } elseif ($redis instanceof \RedisArray) {
$connections = $results = $ids = [];
foreach ($generator() as $command => $args) {
- if (!isset($connections[$h = $this->redis->_target($args[0])])) {
- $connections[$h] = [$this->redis->_instance($h), -1];
+ $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);
}
- \call_user_func_array([$connections[$h][0], $command], $args);
+ $connections[$h][0]->{$command}(...$args);
$results[] = [$h, ++$connections[$h][1]];
- $ids[] = $args[0];
+ $ids[] = $id;
}
foreach ($connections as $h => $c) {
$connections[$h] = $c[0]->exec();
}
- foreach ($results as $k => list($h, $c)) {
+ foreach ($results as $k => [$h, $c]) {
$results[$k] = $connections[$h][$c];
}
} else {
- $this->redis->multi(\Redis::PIPELINE);
+ $redis->multi(\Redis::PIPELINE);
foreach ($generator() as $command => $args) {
- \call_user_func_array([$this->redis, $command], $args);
- $ids[] = $args[0];
+ $redis->{$command}(...$args);
+ $ids[] = 'eval' === $command ? $args[1][0] : $args[0];
}
- $results = $this->redis->exec();
+ $results = $redis->exec();
+ }
+
+ if (!$redis instanceof \Predis\ClientInterface && 'eval' === $command && $redis->getLastError()) {
+ $e = new \RedisException($redis->getLastError());
+ $results = array_map(function ($v) use ($e) { return false === $v ? $e : $v; }, $results);
}
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 \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 RedisClusterProxy || $this->redis instanceof \RedisCluster) {
+ $hosts = [];
+ foreach ($this->redis->_masters() as $host) {
+ $hosts[] = new RedisClusterNodeProxy($host, $this->redis);
+ }
+ }
+
+ return $hosts;
+ }
}
{
"name": "symfony/cache",
"type": "library",
- "description": "Symfony Cache component with PSR-6, PSR-16, and tags",
+ "description": "Provides extended PSR-6, PSR-16 (and tags) implementations",
"keywords": ["caching", "psr6"],
"homepage": "https://symfony.com",
"license": "MIT",
}
],
"provide": {
- "psr/cache-implementation": "1.0",
- "psr/simple-cache-implementation": "1.0"
+ "psr/cache-implementation": "1.0|2.0",
+ "psr/simple-cache-implementation": "1.0|2.0",
+ "symfony/cache-implementation": "1.0|2.0"
},
"require": {
- "php": "^5.5.9|>=7.0.8",
- "psr/cache": "~1.0",
- "psr/log": "~1.0",
- "psr/simple-cache": "^1.0",
- "symfony/polyfill-apcu": "~1.1"
+ "php": ">=7.1.3",
+ "psr/cache": "^1.0|^2.0",
+ "psr/log": "^1|^2|^3",
+ "symfony/cache-contracts": "^1.1.7|^2",
+ "symfony/polyfill-php73": "^1.9",
+ "symfony/polyfill-php80": "^1.16",
+ "symfony/service-contracts": "^1.1|^2",
+ "symfony/var-exporter": "^4.2|^5.0"
},
"require-dev": {
"cache/integration-tests": "dev-master",
- "doctrine/cache": "^1.6",
- "doctrine/dbal": "^2.4|^3.0",
- "predis/predis": "^1.0"
+ "doctrine/cache": "^1.6|^2.0",
+ "doctrine/dbal": "^2.7|^3.0",
+ "predis/predis": "^1.1",
+ "psr/simple-cache": "^1.0|^2.0",
+ "symfony/config": "^4.2|^5.0",
+ "symfony/dependency-injection": "^3.4|^4.1|^5.0",
+ "symfony/filesystem": "^4.4|^5.0",
+ "symfony/http-kernel": "^4.4",
+ "symfony/var-dumper": "^4.4|^5.0"
},
"conflict": {
- "symfony/var-dumper": "<3.3"
+ "doctrine/dbal": "<2.7",
+ "symfony/dependency-injection": "<3.4",
+ "symfony/http-kernel": "<4.4|>=5.0",
+ "symfony/var-dumper": "<4.4"
},
"autoload": {
"psr-4": { "Symfony\\Component\\Cache\\": "" },
--- /dev/null
+vendor/
+composer.lock
+phpunit.xml
--- /dev/null
+CHANGELOG
+=========
+
+The changelog is maintained for all Symfony contracts at the following URL:
+https://github.com/symfony/contracts/blob/main/CHANGELOG.md
--- /dev/null
+Copyright (c) 2020-2022 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.
--- /dev/null
+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 necessarily recommended, the deprecation notices can be completely ignored by declaring an empty
+`function trigger_deprecation() {}` in your application.
--- /dev/null
+{
+ "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": ">=7.1"
+ },
+ "autoload": {
+ "files": [
+ "function.php"
+ ]
+ },
+ "minimum-stability": "dev",
+ "extra": {
+ "branch-alias": {
+ "dev-main": "2.5-dev"
+ },
+ "thanks": {
+ "name": "symfony/contracts",
+ "url": "https://github.com/symfony/contracts"
+ }
+ }
+}
--- /dev/null
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * 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 <p@tchwork.com>
+ */
+ function trigger_deprecation(string $package, string $version, string $message, ...$args): void
+ {
+ @trigger_error(($package || $version ? "Since $package $version: " : '').($args ? vsprintf($message, $args) : $message), \E_USER_DEPRECATED);
+ }
+}
--- /dev/null
+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.
--- /dev/null
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Polyfill\Php73;
+
+/**
+ * @author Gabriel Caruso <carusogabriel34@gmail.com>
+ * @author Ion Bazan <ion.bazan@gmail.com>
+ *
+ * @internal
+ */
+final class Php73
+{
+ public static $startAt = 1533462603;
+
+ /**
+ * @param bool $asNum
+ *
+ * @return array|float|int
+ */
+ public static function hrtime($asNum = false)
+ {
+ $ns = microtime(false);
+ $s = substr($ns, 11) - self::$startAt;
+ $ns = 1E9 * (float) $ns;
+
+ if ($asNum) {
+ $ns += $s * 1E9;
+
+ return \PHP_INT_SIZE === 4 ? $ns : (int) $ns;
+ }
+
+ return [$s, (int) $ns];
+ }
+}
--- /dev/null
+Symfony Polyfill / Php73
+========================
+
+This component provides functions added to PHP 7.3 core:
+
+- [`array_key_first`](https://php.net/array_key_first)
+- [`array_key_last`](https://php.net/array_key_last)
+- [`hrtime`](https://php.net/function.hrtime)
+- [`is_countable`](https://php.net/is_countable)
+- [`JsonException`](https://php.net/JsonException)
+
+More information can be found in the
+[main Polyfill README](https://github.com/symfony/polyfill/blob/main/README.md).
+
+License
+=======
+
+This library is released under the [MIT license](LICENSE).
--- /dev/null
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+if (\PHP_VERSION_ID < 70300) {
+ class JsonException extends Exception
+ {
+ }
+}
--- /dev/null
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+use Symfony\Polyfill\Php73 as p;
+
+if (\PHP_VERSION_ID >= 70300) {
+ return;
+}
+
+if (!function_exists('is_countable')) {
+ function is_countable($value) { return is_array($value) || $value instanceof Countable || $value instanceof ResourceBundle || $value instanceof SimpleXmlElement; }
+}
+if (!function_exists('hrtime')) {
+ require_once __DIR__.'/Php73.php';
+ p\Php73::$startAt = (int) microtime(true);
+ function hrtime($as_number = false) { return p\Php73::hrtime($as_number); }
+}
+if (!function_exists('array_key_first')) {
+ function array_key_first(array $array) { foreach ($array as $key => $value) { return $key; } }
+}
+if (!function_exists('array_key_last')) {
+ function array_key_last(array $array) { return key(array_slice($array, -1, 1, true)); }
+}
--- /dev/null
+{
+ "name": "symfony/polyfill-php73",
+ "type": "library",
+ "description": "Symfony polyfill backporting some PHP 7.3+ features to lower PHP versions",
+ "keywords": ["polyfill", "shim", "compatibility", "portable"],
+ "homepage": "https://symfony.com",
+ "license": "MIT",
+ "authors": [
+ {
+ "name": "Nicolas Grekas",
+ "email": "p@tchwork.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "require": {
+ "php": ">=7.1"
+ },
+ "autoload": {
+ "psr-4": { "Symfony\\Polyfill\\Php73\\": "" },
+ "files": [ "bootstrap.php" ],
+ "classmap": [ "Resources/stubs" ]
+ },
+ "minimum-stability": "dev",
+ "extra": {
+ "thanks": {
+ "name": "symfony/polyfill",
+ "url": "https://github.com/symfony/polyfill"
+ }
+ }
+}
--- /dev/null
+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.
--- /dev/null
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Polyfill\Php80;
+
+/**
+ * @author Ion Bazan <ion.bazan@gmail.com>
+ * @author Nico Oelgart <nicoswd@gmail.com>
+ * @author Nicolas Grekas <p@tchwork.com>
+ *
+ * @internal
+ */
+final class Php80
+{
+ public static function fdiv(float $dividend, float $divisor): float
+ {
+ return @($dividend / $divisor);
+ }
+
+ public static function get_debug_type($value): string
+ {
+ switch (true) {
+ case null === $value: return 'null';
+ case \is_bool($value): return 'bool';
+ case \is_string($value): return 'string';
+ case \is_array($value): return 'array';
+ case \is_int($value): return 'int';
+ case \is_float($value): return 'float';
+ case \is_object($value): break;
+ case $value instanceof \__PHP_Incomplete_Class: return '__PHP_Incomplete_Class';
+ default:
+ if (null === $type = @get_resource_type($value)) {
+ return 'unknown';
+ }
+
+ if ('Unknown' === $type) {
+ $type = 'closed';
+ }
+
+ return "resource ($type)";
+ }
+
+ $class = \get_class($value);
+
+ if (false === strpos($class, '@')) {
+ return $class;
+ }
+
+ return (get_parent_class($class) ?: key(class_implements($class)) ?: 'class').'@anonymous';
+ }
+
+ public static function get_resource_id($res): int
+ {
+ if (!\is_resource($res) && null === @get_resource_type($res)) {
+ throw new \TypeError(sprintf('Argument 1 passed to get_resource_id() must be of the type resource, %s given', get_debug_type($res)));
+ }
+
+ return (int) $res;
+ }
+
+ public static function preg_last_error_msg(): string
+ {
+ switch (preg_last_error()) {
+ case \PREG_INTERNAL_ERROR:
+ return 'Internal error';
+ case \PREG_BAD_UTF8_ERROR:
+ return 'Malformed UTF-8 characters, possibly incorrectly encoded';
+ case \PREG_BAD_UTF8_OFFSET_ERROR:
+ return 'The offset did not correspond to the beginning of a valid UTF-8 code point';
+ case \PREG_BACKTRACK_LIMIT_ERROR:
+ return 'Backtrack limit exhausted';
+ case \PREG_RECURSION_LIMIT_ERROR:
+ return 'Recursion limit exhausted';
+ case \PREG_JIT_STACKLIMIT_ERROR:
+ return 'JIT stack limit exhausted';
+ case \PREG_NO_ERROR:
+ return 'No error';
+ default:
+ return 'Unknown error';
+ }
+ }
+
+ public static function str_contains(string $haystack, string $needle): bool
+ {
+ return '' === $needle || false !== strpos($haystack, $needle);
+ }
+
+ public static function str_starts_with(string $haystack, string $needle): bool
+ {
+ return 0 === strncmp($haystack, $needle, \strlen($needle));
+ }
+
+ public static function str_ends_with(string $haystack, string $needle): bool
+ {
+ if ('' === $needle || $needle === $haystack) {
+ return true;
+ }
+
+ if ('' === $haystack) {
+ return false;
+ }
+
+ $needleLength = \strlen($needle);
+
+ return $needleLength <= \strlen($haystack) && 0 === substr_compare($haystack, $needle, -$needleLength);
+ }
+}
--- /dev/null
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Polyfill\Php80;
+
+/**
+ * @author Fedonyuk Anton <info@ensostudio.ru>
+ *
+ * @internal
+ */
+class PhpToken implements \Stringable
+{
+ /**
+ * @var int
+ */
+ public $id;
+
+ /**
+ * @var string
+ */
+ public $text;
+
+ /**
+ * @var int
+ */
+ public $line;
+
+ /**
+ * @var int
+ */
+ public $pos;
+
+ public function __construct(int $id, string $text, int $line = -1, int $position = -1)
+ {
+ $this->id = $id;
+ $this->text = $text;
+ $this->line = $line;
+ $this->pos = $position;
+ }
+
+ public function getTokenName(): ?string
+ {
+ if ('UNKNOWN' === $name = token_name($this->id)) {
+ $name = \strlen($this->text) > 1 || \ord($this->text) < 32 ? null : $this->text;
+ }
+
+ return $name;
+ }
+
+ /**
+ * @param int|string|array $kind
+ */
+ public function is($kind): bool
+ {
+ foreach ((array) $kind as $value) {
+ if (\in_array($value, [$this->id, $this->text], true)) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ public function isIgnorable(): bool
+ {
+ return \in_array($this->id, [\T_WHITESPACE, \T_COMMENT, \T_DOC_COMMENT, \T_OPEN_TAG], true);
+ }
+
+ public function __toString(): string
+ {
+ return (string) $this->text;
+ }
+
+ /**
+ * @return static[]
+ */
+ public static function tokenize(string $code, int $flags = 0): array
+ {
+ $line = 1;
+ $position = 0;
+ $tokens = token_get_all($code, $flags);
+ foreach ($tokens as $index => $token) {
+ if (\is_string($token)) {
+ $id = \ord($token);
+ $text = $token;
+ } else {
+ [$id, $text, $line] = $token;
+ }
+ $tokens[$index] = new static($id, $text, $line, $position);
+ $position += \strlen($text);
+ }
+
+ return $tokens;
+ }
+}
--- /dev/null
+Symfony Polyfill / Php80
+========================
+
+This component provides features added to PHP 8.0 core:
+
+- [`Stringable`](https://php.net/stringable) interface
+- [`fdiv`](https://php.net/fdiv)
+- [`ValueError`](https://php.net/valueerror) class
+- [`UnhandledMatchError`](https://php.net/unhandledmatcherror) class
+- `FILTER_VALIDATE_BOOL` constant
+- [`get_debug_type`](https://php.net/get_debug_type)
+- [`PhpToken`](https://php.net/phptoken) class
+- [`preg_last_error_msg`](https://php.net/preg_last_error_msg)
+- [`str_contains`](https://php.net/str_contains)
+- [`str_starts_with`](https://php.net/str_starts_with)
+- [`str_ends_with`](https://php.net/str_ends_with)
+- [`get_resource_id`](https://php.net/get_resource_id)
+
+More information can be found in the
+[main Polyfill README](https://github.com/symfony/polyfill/blob/main/README.md).
+
+License
+=======
+
+This library is released under the [MIT license](LICENSE).
--- /dev/null
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+#[Attribute(Attribute::TARGET_CLASS)]
+final class Attribute
+{
+ public const TARGET_CLASS = 1;
+ public const TARGET_FUNCTION = 2;
+ public const TARGET_METHOD = 4;
+ public const TARGET_PROPERTY = 8;
+ public const TARGET_CLASS_CONSTANT = 16;
+ public const TARGET_PARAMETER = 32;
+ public const TARGET_ALL = 63;
+ public const IS_REPEATABLE = 64;
+
+ /** @var int */
+ public $flags;
+
+ public function __construct(int $flags = self::TARGET_ALL)
+ {
+ $this->flags = $flags;
+ }
+}
--- /dev/null
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+if (\PHP_VERSION_ID < 80000 && extension_loaded('tokenizer')) {
+ class PhpToken extends Symfony\Polyfill\Php80\PhpToken
+ {
+ }
+}
--- /dev/null
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+if (\PHP_VERSION_ID < 80000) {
+ interface Stringable
+ {
+ /**
+ * @return string
+ */
+ public function __toString();
+ }
+}
--- /dev/null
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+if (\PHP_VERSION_ID < 80000) {
+ class UnhandledMatchError extends Error
+ {
+ }
+}
--- /dev/null
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+if (\PHP_VERSION_ID < 80000) {
+ class ValueError extends Error
+ {
+ }
+}
--- /dev/null
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+use Symfony\Polyfill\Php80 as p;
+
+if (\PHP_VERSION_ID >= 80000) {
+ return;
+}
+
+if (!defined('FILTER_VALIDATE_BOOL') && defined('FILTER_VALIDATE_BOOLEAN')) {
+ define('FILTER_VALIDATE_BOOL', \FILTER_VALIDATE_BOOLEAN);
+}
+
+if (!function_exists('fdiv')) {
+ function fdiv(float $num1, float $num2): float { return p\Php80::fdiv($num1, $num2); }
+}
+if (!function_exists('preg_last_error_msg')) {
+ function preg_last_error_msg(): string { return p\Php80::preg_last_error_msg(); }
+}
+if (!function_exists('str_contains')) {
+ function str_contains(?string $haystack, ?string $needle): bool { return p\Php80::str_contains($haystack ?? '', $needle ?? ''); }
+}
+if (!function_exists('str_starts_with')) {
+ function str_starts_with(?string $haystack, ?string $needle): bool { return p\Php80::str_starts_with($haystack ?? '', $needle ?? ''); }
+}
+if (!function_exists('str_ends_with')) {
+ function str_ends_with(?string $haystack, ?string $needle): bool { return p\Php80::str_ends_with($haystack ?? '', $needle ?? ''); }
+}
+if (!function_exists('get_debug_type')) {
+ function get_debug_type($value): string { return p\Php80::get_debug_type($value); }
+}
+if (!function_exists('get_resource_id')) {
+ function get_resource_id($resource): int { return p\Php80::get_resource_id($resource); }
+}
--- /dev/null
+{
+ "name": "symfony/polyfill-php80",
+ "type": "library",
+ "description": "Symfony polyfill backporting some PHP 8.0+ features to lower PHP versions",
+ "keywords": ["polyfill", "shim", "compatibility", "portable"],
+ "homepage": "https://symfony.com",
+ "license": "MIT",
+ "authors": [
+ {
+ "name": "Ion Bazan",
+ "email": "ion.bazan@gmail.com"
+ },
+ {
+ "name": "Nicolas Grekas",
+ "email": "p@tchwork.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "require": {
+ "php": ">=7.1"
+ },
+ "autoload": {
+ "psr-4": { "Symfony\\Polyfill\\Php80\\": "" },
+ "files": [ "bootstrap.php" ],
+ "classmap": [ "Resources/stubs" ]
+ },
+ "minimum-stability": "dev",
+ "extra": {
+ "thanks": {
+ "name": "symfony/polyfill",
+ "url": "https://github.com/symfony/polyfill"
+ }
+ }
+}
--- /dev/null
+vendor/
+composer.lock
+phpunit.xml
--- /dev/null
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * 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 <me@derrabus.de>
+ */
+#[\Attribute(\Attribute::TARGET_METHOD | \Attribute::TARGET_PROPERTY)]
+final class Required
+{
+}
--- /dev/null
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * 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\ServiceSubscriberTrait;
+
+/**
+ * Use with {@see ServiceSubscriberTrait} to mark a method's return type
+ * as a subscribed service.
+ *
+ * @author Kevin Bond <kevinbond@gmail.com>
+ */
+#[\Attribute(\Attribute::TARGET_METHOD)]
+final class SubscribedService
+{
+ /**
+ * @param string|null $key The key to use for the service
+ * If null, use "ClassName::methodName"
+ */
+ public function __construct(
+ public ?string $key = null
+ ) {
+ }
+}
--- /dev/null
+CHANGELOG
+=========
+
+The changelog is maintained for all Symfony contracts at the following URL:
+https://github.com/symfony/contracts/blob/main/CHANGELOG.md
--- /dev/null
+Copyright (c) 2018-2022 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.
--- /dev/null
+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.
--- /dev/null
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * 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
+{
+ public function reset();
+}
--- /dev/null
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * 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 <robin.chalas@gmail.com>
+ * @author Nicolas Grekas <p@tchwork.com>
+ */
+trait ServiceLocatorTrait
+{
+ private $factories;
+ private $loading = [];
+ private $providedTypes;
+
+ /**
+ * @param callable[] $factories
+ */
+ public function __construct(array $factories)
+ {
+ $this->factories = $factories;
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @return bool
+ */
+ public function has(string $id)
+ {
+ return isset($this->factories[$id]);
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @return mixed
+ */
+ public function get(string $id)
+ {
+ 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]);
+ }
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getProvidedServices(): array
+ {
+ if (null === $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 {
+ };
+ }
+}
--- /dev/null
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * 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 <p@tchwork.com>
+ * @author Mateusz Sip <mateusz.sip@gmail.com>
+ */
+interface ServiceProviderInterface extends ContainerInterface
+{
+ /**
+ * 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 string[] The provided service types, keyed by service names
+ */
+ public function getProvidedServices(): array;
+}
--- /dev/null
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Contracts\Service;
+
+/**
+ * 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 <p@tchwork.com>
+ */
+interface ServiceSubscriberInterface
+{
+ /**
+ * Returns an array of service types 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']
+ *
+ * @return string[] The required service types, optionally keyed by service names
+ */
+ public static function getSubscribedServices();
+}
--- /dev/null
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * 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\SubscribedService;
+
+/**
+ * Implementation of ServiceSubscriberInterface that determines subscribed services from
+ * method return types. Service ids are available as "ClassName::methodName".
+ *
+ * @author Kevin Bond <kevinbond@gmail.com>
+ */
+trait ServiceSubscriberTrait
+{
+ /** @var ContainerInterface */
+ protected $container;
+
+ /**
+ * {@inheritdoc}
+ */
+ public static function getSubscribedServices(): array
+ {
+ $services = method_exists(get_parent_class(self::class) ?: '', __FUNCTION__) ? parent::getSubscribedServices() : [];
+ $attributeOptIn = false;
+
+ if (\PHP_VERSION_ID >= 80000) {
+ 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));
+ }
+
+ $serviceId = $returnType instanceof \ReflectionNamedType ? $returnType->getName() : (string) $returnType;
+
+ if ($returnType->allowsNull()) {
+ $serviceId = '?'.$serviceId;
+ }
+
+ $services[$attribute->newInstance()->key ?? self::class.'::'.$method->name] = $serviceId;
+ $attributeOptIn = true;
+ }
+ }
+
+ if (!$attributeOptIn) {
+ foreach ((new \ReflectionClass(self::class))->getMethods() as $method) {
+ if ($method->isStatic() || $method->isAbstract() || $method->isGenerator() || $method->isInternal() || $method->getNumberOfRequiredParameters()) {
+ continue;
+ }
+
+ if (self::class !== $method->getDeclaringClass()->name) {
+ continue;
+ }
+
+ if (!($returnType = $method->getReturnType()) instanceof \ReflectionNamedType) {
+ continue;
+ }
+
+ if ($returnType->isBuiltin()) {
+ continue;
+ }
+
+ if (\PHP_VERSION_ID >= 80000) {
+ trigger_deprecation('symfony/service-contracts', '2.5', 'Using "%s" in "%s" without using the "%s" attribute on any method is deprecated.', ServiceSubscriberTrait::class, self::class, SubscribedService::class);
+ }
+
+ $services[self::class.'::'.$method->name] = '?'.($returnType instanceof \ReflectionNamedType ? $returnType->getName() : $returnType);
+ }
+ }
+
+ return $services;
+ }
+
+ /**
+ * @required
+ *
+ * @return ContainerInterface|null
+ */
+ public function setContainer(ContainerInterface $container)
+ {
+ $this->container = $container;
+
+ if (method_exists(get_parent_class(self::class) ?: '', __FUNCTION__)) {
+ return parent::setContainer($container);
+ }
+
+ return null;
+ }
+}
--- /dev/null
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * 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\ContainerInterface;
+use Symfony\Contracts\Service\ServiceLocatorTrait;
+
+abstract class ServiceLocatorTest extends TestCase
+{
+ /**
+ * @return ContainerInterface
+ */
+ protected function getServiceLocator(array $factories)
+ {
+ return new class($factories) implements ContainerInterface {
+ use ServiceLocatorTrait;
+ };
+ }
+
+ public function testHas()
+ {
+ $locator = $this->getServiceLocator([
+ 'foo' => function () { return 'bar'; },
+ 'bar' => function () { return 'baz'; },
+ function () { return 'dummy'; },
+ ]);
+
+ $this->assertTrue($locator->has('foo'));
+ $this->assertTrue($locator->has('bar'));
+ $this->assertFalse($locator->has('dummy'));
+ }
+
+ public function testGet()
+ {
+ $locator = $this->getServiceLocator([
+ 'foo' => function () { return 'bar'; },
+ 'bar' => function () { return '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()
+ {
+ if (!$this->getExpectedException()) {
+ $this->expectException(\Psr\Container\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 = $this->getServiceLocator([
+ 'foo' => function () use (&$locator) { return $locator->get('bar'); },
+ ]);
+
+ $locator->get('foo');
+ }
+
+ public function testThrowsOnCircularReference()
+ {
+ $this->expectException(\Psr\Container\ContainerExceptionInterface::class);
+ $this->expectExceptionMessage('Circular reference detected for service "bar", path: "bar -> baz -> bar".');
+ $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'); },
+ ]);
+
+ $locator->get('foo');
+ }
+}
--- /dev/null
+{
+ "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": ">=7.2.5",
+ "psr/container": "^1.1",
+ "symfony/deprecation-contracts": "^2.1|^3"
+ },
+ "conflict": {
+ "ext-psr": "<1.1|>=2"
+ },
+ "suggest": {
+ "symfony/service-implementation": ""
+ },
+ "autoload": {
+ "psr-4": { "Symfony\\Contracts\\Service\\": "" }
+ },
+ "minimum-stability": "dev",
+ "extra": {
+ "branch-alias": {
+ "dev-main": "2.5-dev"
+ },
+ "thanks": {
+ "name": "symfony/contracts",
+ "url": "https://github.com/symfony/contracts"
+ }
+ }
+}
--- /dev/null
+CHANGELOG
+=========
+
+5.1.0
+-----
+
+ * added argument `array &$foundClasses` to `VarExporter::export()` to ease with preloading exported values
+
+4.2.0
+-----
+
+ * added the component
--- /dev/null
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * 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);
+ }
+}
--- /dev/null
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * 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
+{
+}
--- /dev/null
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * 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);
+ }
+}
--- /dev/null
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * 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\Hydrator;
+use Symfony\Component\VarExporter\Internal\Registry;
+
+/**
+ * A utility class to create objects without calling their constructor.
+ *
+ * @author Nicolas Grekas <p@tchwork.com>
+ */
+final class Instantiator
+{
+ /**
+ * Creates an object and sets its properties without calling its constructor nor any other methods.
+ *
+ * For example:
+ *
+ * // creates an empty instance of Foo
+ * Instantiator::instantiate(Foo::class);
+ *
+ * // creates a Foo instance and sets one of its properties
+ * Instantiator::instantiate(Foo::class, ['propertyName' => $propertyValue]);
+ *
+ * // creates a Foo instance and sets a private property defined on its parent Bar class
+ * Instantiator::instantiate(Foo::class, [], [
+ * Bar::class => ['privateBarProperty' => $propertyValue],
+ * ]);
+ *
+ * Instances of ArrayObject, ArrayIterator and SplObjectStorage can be created
+ * by using the special "\0" property name to define their internal value:
+ *
+ * // creates an SplObjectStorage where $info1 is attached to $obj1, etc.
+ * Instantiator::instantiate(SplObjectStorage::class, ["\0" => [$obj1, $info1, $obj2, $info2...]]);
+ *
+ * // creates an ArrayObject populated with $inputArray
+ * Instantiator::instantiate(ArrayObject::class, ["\0" => [$inputArray]]);
+ *
+ * @param string $class The class of the instance to create
+ * @param array $properties The properties to set on the instance
+ * @param array $privateProperties The private properties to set on the instance,
+ * keyed by their declaring class
+ *
+ * @throws ExceptionInterface When the instance cannot be created
+ */
+ public static function instantiate(string $class, array $properties = [], array $privateProperties = []): object
+ {
+ $reflector = Registry::$reflectors[$class] ?? Registry::getClassReflector($class);
+
+ if (Registry::$cloneable[$class]) {
+ $wrappedInstance = [clone Registry::$prototypes[$class]];
+ } elseif (Registry::$instantiableWithoutConstructor[$class]) {
+ $wrappedInstance = [$reflector->newInstanceWithoutConstructor()];
+ } elseif (null === Registry::$prototypes[$class]) {
+ throw new NotInstantiableTypeException($class);
+ } elseif ($reflector->implementsInterface('Serializable') && (\PHP_VERSION_ID < 70400 || !method_exists($class, '__unserialize'))) {
+ $wrappedInstance = [unserialize('C:'.\strlen($class).':"'.$class.'":0:{}')];
+ } else {
+ $wrappedInstance = [unserialize('O:'.\strlen($class).':"'.$class.'":0:{}')];
+ }
+
+ if ($properties) {
+ $privateProperties[$class] = isset($privateProperties[$class]) ? $properties + $privateProperties[$class] : $properties;
+ }
+
+ foreach ($privateProperties as $class => $properties) {
+ if (!$properties) {
+ continue;
+ }
+ foreach ($properties as $name => $value) {
+ // because they're also used for "unserialization", hydrators
+ // deal with array of instances, so we need to wrap values
+ $properties[$name] = [$value];
+ }
+ (Hydrator::$hydrators[$class] ?? Hydrator::getHydrator($class))($properties, $wrappedInstance);
+ }
+
+ return $wrappedInstance[0];
+ }
+}
--- /dev/null
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * 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 <p@tchwork.com>
+ *
+ * @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
+ *
+ * @throws NotInstantiableTypeException When a value cannot be serialized
+ */
+ public static function prepare($values, $objectsPool, &$refsPool, &$objectsCount, &$valuesAreStatic): array
+ {
+ $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 = \get_class($value);
+ $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 = \PHP_VERSION_ID >= 80100 && $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
+ || \PHP_VERSION_ID < 80200 && $value instanceof \DatePeriod
+ ) {
+ ++$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 = \PHP_VERSION_ID >= 80100 && $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, string $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 ('n".\'' === substr($m[1], -4)) {
+ 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 '\\'.\get_class($value)."::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];
+ }
+}
--- /dev/null
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * 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 <p@tchwork.com>
+ *
+ * @internal
+ */
+class Hydrator
+{
+ public static $hydrators = [];
+
+ public $registry;
+ public $values;
+ public $properties;
+ public $value;
+ public $wakeups;
+
+ public function __construct(?Registry $registry, ?Values $values, array $properties, $value, array $wakeups)
+ {
+ $this->registry = $registry;
+ $this->values = $values;
+ $this->properties = $properties;
+ $this->value = $value;
+ $this->wakeups = $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)
+ {
+ switch ($class) {
+ case 'stdClass':
+ return self::$hydrators[$class] = static function ($properties, $objects) {
+ foreach ($properties as $name => $values) {
+ foreach ($values as $i => $v) {
+ $objects[$i]->$name = $v;
+ }
+ }
+ };
+
+ case 'ErrorException':
+ return self::$hydrators[$class] = (self::$hydrators['stdClass'] ?? self::getHydrator('stdClass'))->bindTo(null, new class() extends \ErrorException {
+ });
+
+ case 'TypeError':
+ return self::$hydrators[$class] = (self::$hydrators['stdClass'] ?? self::getHydrator('stdClass'))->bindTo(null, new class() extends \Error {
+ });
+
+ case 'SplObjectStorage':
+ return self::$hydrators[$class] = 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 = \Closure::fromCallable([$classReflector->getConstructor(), 'invokeArgs']);
+
+ return self::$hydrators[$class] = 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 self::$hydrators[$class] = (self::$hydrators['stdClass'] ?? self::getHydrator('stdClass'))->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()) {
+ $propertyReflector->setAccessible(true);
+ $propertySetters[$propertyReflector->name] = \Closure::fromCallable([$propertyReflector, 'setValue']);
+ }
+ }
+
+ if (!$propertySetters) {
+ return self::$hydrators[$class] = self::$hydrators['stdClass'] ?? self::getHydrator('stdClass');
+ }
+
+ return self::$hydrators[$class] = 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;
+ }
+ }
+ };
+ }
+}
--- /dev/null
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * 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 <p@tchwork.com>
+ *
+ * @internal
+ */
+class Reference
+{
+ public $id;
+ public $value;
+ public $count = 0;
+
+ public function __construct(int $id, $value = null)
+ {
+ $this->id = $id;
+ $this->value = $value;
+ }
+}
--- /dev/null
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * 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 <p@tchwork.com>
+ *
+ * @internal
+ */
+class Registry
+{
+ public static $reflectors = [];
+ public static $prototypes = [];
+ public static $factories = [];
+ public static $cloneable = [];
+ public static $instantiableWithoutConstructor = [];
+
+ public $classes = [];
+
+ public function __construct(array $classes)
+ {
+ $this->classes = $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] = \Closure::fromCallable([$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 self::$reflectors[$class] = $reflector;
+ } else {
+ try {
+ $proto = $reflector->newInstanceWithoutConstructor();
+ $instantiableWithoutConstructor = true;
+ } catch (\ReflectionException $e) {
+ $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') && (\PHP_VERSION_ID < 70400 || !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') && (\PHP_VERSION_ID < 70400 || !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]->setAccessible(true);
+ $setTrace[1]->setAccessible(true);
+ $setTrace[0] = \Closure::fromCallable([$setTrace[0], 'setValue']);
+ $setTrace[1] = \Closure::fromCallable([$setTrace[1], 'setValue']);
+ }
+
+ $setTrace[$proto instanceof \Exception]($proto, []);
+ }
+
+ return self::$reflectors[$class] = $reflector;
+ }
+}
--- /dev/null
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * 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 <p@tchwork.com>
+ *
+ * @internal
+ */
+class Values
+{
+ public $values;
+
+ public function __construct(array $values)
+ {
+ $this->values = $values;
+ }
+}
--- /dev/null
+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.
--- /dev/null
+VarExporter Component
+=====================
+
+The VarExporter component 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`).
+
+It also provides an instantiator that allows creating and populating objects
+without calling their constructor nor any other methods.
+
+The reason to use this component *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).
+
+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)
--- /dev/null
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * 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 <p@tchwork.com>
+ */
+final class VarExporter
+{
+ /**
+ * Exports a serializable PHP value to PHP code.
+ *
+ * @param mixed $value The value to export
+ * @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($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);
+ }
+}
--- /dev/null
+{
+ "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"],
+ "homepage": "https://symfony.com",
+ "license": "MIT",
+ "authors": [
+ {
+ "name": "Nicolas Grekas",
+ "email": "p@tchwork.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "require": {
+ "php": ">=7.2.5",
+ "symfony/polyfill-php80": "^1.16"
+ },
+ "require-dev": {
+ "symfony/var-dumper": "^4.4.9|^5.0.9|^6.0"
+ },
+ "autoload": {
+ "psr-4": { "Symfony\\Component\\VarExporter\\": "" },
+ "exclude-from-classmap": [
+ "/Tests/"
+ ]
+ },
+ "minimum-stability": "dev"
+}