Merge pull request '[various] Update Composer dependencies ahead of release' (#1487) from MrPetovan/friendica-addons:task/composer into 2024.03-rc

Reviewed-on: friendica/friendica-addons#1487
This commit is contained in:
heluecht 2024-03-20 10:16:50 +01:00
commit 569931986d
315 changed files with 12599 additions and 3210 deletions

View file

@ -27,3 +27,6 @@ indent_size = 2
[*.json]
indent_style = space
indent_size = 2
[composer.json]
indent_size = 4

View file

@ -54,7 +54,7 @@ new Vue({
self.rules.push(responseJSON.rule);
self.resetForm();
}, function (response) {
self.errorMessage = response.responseJSON.message;
self.errorMessage = response.responseJSON.exception[0].message;
});
}
},
@ -74,7 +74,7 @@ new Vue({
self.rules[self.editedIndex] = rule;
self.resetForm();
}, function (response) {
self.errorMessage = response.responseJSON.message;
self.errorMessage = response.responseJSON.exception[0].message;
});
},

View file

@ -1,24 +1,27 @@
{
"name": "friendica-addons/advancedcontentfilter",
"description": "Advanced Content Filter addon for Friendica",
"type": "friendica-addon",
"authors": [
{
"name": "Hypolite Petovan",
"email": "hypolite@mrpetovan.com",
"homepage": "https://friendica.mrpetovan.com/profile/hypolite",
"role": "Developer"
}
],
"require": {
"slim/slim": "^4",
"symfony/expression-language": "^3.4"
},
"license": "3-clause BSD license",
"minimum-stability": "stable",
"config": {
"optimize-autoloader": true,
"autoloader-suffix": "AdvancedContentFilterAddon",
"preferred-install": "dist"
}
"name": "friendica-addons/advancedcontentfilter",
"description": "Advanced Content Filter addon for Friendica",
"type": "friendica-addon",
"authors": [
{
"name": "Hypolite Petovan",
"email": "hypolite@mrpetovan.com",
"homepage": "https://friendica.mrpetovan.com/profile/hypolite",
"role": "Developer"
}
],
"require": {
"slim/slim": "^4",
"symfony/expression-language": "^3.4"
},
"license": "3-clause BSD license",
"minimum-stability": "stable",
"config": {
"platform": {
"php": "7.4"
},
"optimize-autoloader": true,
"autoloader-suffix": "AdvancedContentFilterAddon",
"preferred-install": "dist"
}
}

View file

@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
"content-hash": "3e87f0369e4799fc35d98f399c67f1e9",
"content-hash": "a7276eb2d2108a26699f69c750d02d27",
"packages": [
{
"name": "nikic/fast-route",
@ -100,27 +100,22 @@
},
{
"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/"
@ -145,7 +140,7 @@
"container-interop",
"psr"
],
"time": "2021-11-05T16:47:00+00:00"
"time": "2021-11-05T16:50:12+00:00"
},
{
"name": "psr/http-factory",
@ -201,16 +196,16 @@
},
{
"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": {
@ -219,7 +214,7 @@
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "1.1.x-dev"
"dev-master": "2.0.x-dev"
}
},
"autoload": {
@ -234,7 +229,7 @@
"authors": [
{
"name": "PHP-FIG",
"homepage": "http://www.php-fig.org/"
"homepage": "https://www.php-fig.org/"
}
],
"description": "Common interface for HTTP messages",
@ -247,7 +242,7 @@
"request",
"response"
],
"time": "2023-04-04T09:50:52+00:00"
"time": "2023-04-04T09:54:51+00:00"
},
{
"name": "psr/http-server-handler",
@ -402,66 +397,18 @@
],
"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": {
@ -470,7 +417,7 @@
"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"
@ -478,19 +425,19 @@
"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",
@ -553,41 +500,54 @@
"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": {
@ -612,7 +572,7 @@
"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",
@ -632,7 +592,147 @@
"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",
@ -694,80 +794,6 @@
],
"time": "2020-10-24T10:57:07+00:00"
},
{
"name": "symfony/polyfill-apcu",
"version": "v1.28.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/polyfill-apcu.git",
"reference": "c6c2c0f5f4cb0b100c5dfea807ef5cd27bbe9899"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/polyfill-apcu/zipball/c6c2c0f5f4cb0b100c5dfea807ef5cd27bbe9899",
"reference": "c6c2c0f5f4cb0b100c5dfea807ef5cd27bbe9899",
"shasum": ""
},
"require": {
"php": ">=7.1"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-main": "1.28-dev"
},
"thanks": {
"name": "symfony/polyfill",
"url": "https://github.com/symfony/polyfill"
}
},
"autoload": {
"files": [
"bootstrap.php"
],
"psr-4": {
"Symfony\\Polyfill\\Apcu\\": ""
}
},
"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 apcu_* functions to lower PHP versions",
"homepage": "https://symfony.com",
"keywords": [
"apcu",
"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": "2023-01-26T09:26:14+00:00"
},
{
"name": "symfony/polyfill-php70",
"version": "v1.20.0",
@ -832,6 +858,306 @@
}
],
"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\\Php73\\": ""
},
"classmap": [
"Resources/stubs"
]
},
"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.3+ 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": "2024-01-29T20:11:03+00:00"
},
{
"name": "symfony/polyfill-php80",
"version": "v1.29.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/polyfill-php80.git",
"reference": "87b68208d5c1188808dd7839ee1e6c8ec3b02f1b"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/87b68208d5c1188808dd7839ee1e6c8ec3b02f1b",
"reference": "87b68208d5c1188808dd7839ee1e6c8ec3b02f1b",
"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\\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"
},
{
"name": "Symfony Community",
"homepage": "https://symfony.com/contributors"
}
],
"description": "Symfony polyfill backporting some PHP 8.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": "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": [],
@ -840,9 +1166,10 @@
"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"
}

View file

@ -29,4 +29,4 @@ use Friendica\DI;
*/
$slim->addRoutingMiddleware();
$errorMiddleware = $slim->addErrorMiddleware(true, true, true);
$errorMiddleware = $slim->addErrorMiddleware(true, true, true, DI::logger());

View file

@ -6,6 +6,7 @@ $vendorDir = dirname(dirname(__FILE__));
$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',
@ -23,6 +24,8 @@ return array(
'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',
@ -56,9 +59,6 @@ return array(
'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',
@ -75,6 +75,7 @@ return array(
'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',
@ -130,20 +131,25 @@ return array(
'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',
@ -152,10 +158,21 @@ return array(
'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',
@ -172,17 +189,22 @@ return array(
'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',
@ -210,5 +232,32 @@ return array(
'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',
);

View file

@ -6,6 +6,8 @@ $vendorDir = dirname(dirname(__FILE__));
$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',
);

View file

@ -6,11 +6,14 @@ $vendorDir = dirname(dirname(__FILE__));
$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'),

View file

@ -7,21 +7,26 @@ namespace Composer\Autoload;
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,
@ -35,9 +40,25 @@ class ComposerStaticInitAdvancedContentFilterAddon
);
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 (
@ -51,10 +72,6 @@ class ComposerStaticInitAdvancedContentFilterAddon
array (
0 => __DIR__ . '/..' . '/slim/slim/Slim',
),
'Psr\\SimpleCache\\' =>
array (
0 => __DIR__ . '/..' . '/psr/simple-cache/src',
),
'Psr\\Log\\' =>
array (
0 => __DIR__ . '/..' . '/psr/log/Psr/Log',
@ -84,6 +101,7 @@ class ComposerStaticInitAdvancedContentFilterAddon
);
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',
@ -101,6 +119,8 @@ class ComposerStaticInitAdvancedContentFilterAddon
'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',
@ -134,9 +154,6 @@ class ComposerStaticInitAdvancedContentFilterAddon
'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',
@ -153,6 +170,7 @@ class ComposerStaticInitAdvancedContentFilterAddon
'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',
@ -208,20 +226,25 @@ class ComposerStaticInitAdvancedContentFilterAddon
'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',
@ -230,10 +253,21 @@ class ComposerStaticInitAdvancedContentFilterAddon
'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',
@ -250,17 +284,22 @@ class ComposerStaticInitAdvancedContentFilterAddon
'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',
@ -288,7 +327,34 @@ class ComposerStaticInitAdvancedContentFilterAddon
'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)

View file

@ -97,29 +97,24 @@
},
{
"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": {
@ -202,27 +197,27 @@
},
{
"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",
@ -238,7 +233,7 @@
"authors": [
{
"name": "PHP-FIG",
"homepage": "http://www.php-fig.org/"
"homepage": "https://www.php-fig.org/"
}
],
"description": "Common interface for HTTP messages",
@ -411,69 +406,19 @@
"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": {
@ -482,7 +427,7 @@
"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"
@ -490,19 +435,19 @@
"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",
@ -510,7 +455,7 @@
"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": {
@ -570,40 +515,53 @@
},
{
"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"
"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": "2020-10-24T10:57:07+00:00",
"time": "2022-10-17T20:21:54+00:00",
"type": "library",
"installation-source": "dist",
"autoload": {
@ -628,7 +586,7 @@
"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",
@ -649,6 +607,150 @@
}
]
},
{
"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",
@ -711,82 +813,6 @@
}
]
},
{
"name": "symfony/polyfill-apcu",
"version": "v1.28.0",
"version_normalized": "1.28.0.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/polyfill-apcu.git",
"reference": "c6c2c0f5f4cb0b100c5dfea807ef5cd27bbe9899"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/polyfill-apcu/zipball/c6c2c0f5f4cb0b100c5dfea807ef5cd27bbe9899",
"reference": "c6c2c0f5f4cb0b100c5dfea807ef5cd27bbe9899",
"shasum": ""
},
"require": {
"php": ">=7.1"
},
"time": "2023-01-26T09:26:14+00:00",
"type": "library",
"extra": {
"branch-alias": {
"dev-main": "1.28-dev"
},
"thanks": {
"name": "symfony/polyfill",
"url": "https://github.com/symfony/polyfill"
}
},
"installation-source": "dist",
"autoload": {
"files": [
"bootstrap.php"
],
"psr-4": {
"Symfony\\Polyfill\\Apcu\\": ""
}
},
"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 apcu_* functions to lower PHP versions",
"homepage": "https://symfony.com",
"keywords": [
"apcu",
"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-php70",
"version": "v1.20.0",
@ -852,5 +878,313 @@
"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"
}
},
"installation-source": "dist",
"autoload": {
"files": [
"bootstrap.php"
],
"psr-4": {
"Symfony\\Polyfill\\Php73\\": ""
},
"classmap": [
"Resources/stubs"
]
},
"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.3+ 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-php80",
"version": "v1.29.0",
"version_normalized": "1.29.0.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/polyfill-php80.git",
"reference": "87b68208d5c1188808dd7839ee1e6c8ec3b02f1b"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/87b68208d5c1188808dd7839ee1e6c8ec3b02f1b",
"reference": "87b68208d5c1188808dd7839ee1e6c8ec3b02f1b",
"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"
}
},
"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"
},
{
"name": "Symfony Community",
"homepage": "https://symfony.com/contributors"
}
],
"description": "Symfony polyfill backporting some PHP 8.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/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"
}
]
}
]

View file

@ -18,10 +18,5 @@
"psr-4": {
"Psr\\Container\\": "src/"
}
},
"extra": {
"branch-alias": {
"dev-master": "2.0.x-dev"
}
}
}

View file

@ -32,5 +32,5 @@ interface ContainerInterface
*
* @return bool
*/
public function has(string $id): bool;
public function has(string $id);
}

View file

@ -7,7 +7,7 @@
"authors": [
{
"name": "PHP-FIG",
"homepage": "http://www.php-fig.org/"
"homepage": "https://www.php-fig.org/"
}
],
"require": {
@ -20,7 +20,7 @@
},
"extra": {
"branch-alias": {
"dev-master": "1.1.x-dev"
"dev-master": "2.0.x-dev"
}
}
}

View file

@ -1,7 +1,5 @@
<?php
declare(strict_types=1);
namespace Psr\Http\Message;
/**
@ -25,7 +23,7 @@ interface MessageInterface
*
* @return string HTTP protocol version.
*/
public function getProtocolVersion();
public function getProtocolVersion(): string;
/**
* Return an instance with the specified HTTP protocol version.
@ -40,7 +38,7 @@ interface MessageInterface
* @param string $version HTTP protocol version
* @return static
*/
public function withProtocolVersion(string $version);
public function withProtocolVersion(string $version): MessageInterface;
/**
* Retrieves all message header values.
@ -67,7 +65,7 @@ interface MessageInterface
* 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.
@ -77,7 +75,7 @@ interface MessageInterface
* 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.
@ -93,7 +91,7 @@ interface MessageInterface
* 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.
@ -114,7 +112,7 @@ interface MessageInterface
* 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.
@ -131,7 +129,7 @@ interface MessageInterface
* @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.
@ -149,7 +147,7 @@ interface MessageInterface
* @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.
@ -163,14 +161,14 @@ interface MessageInterface
* @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.
@ -185,5 +183,5 @@ interface MessageInterface
* @return static
* @throws \InvalidArgumentException When the body is not valid.
*/
public function withBody(StreamInterface $body);
public function withBody(StreamInterface $body): MessageInterface;
}

View file

@ -1,7 +1,5 @@
<?php
declare(strict_types=1);
namespace Psr\Http\Message;
/**
@ -41,7 +39,7 @@ interface RequestInterface extends MessageInterface
*
* @return string
*/
public function getRequestTarget();
public function getRequestTarget(): string;
/**
* Return an instance with the specific request-target.
@ -60,14 +58,15 @@ interface RequestInterface extends MessageInterface
* @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.
@ -84,7 +83,7 @@ interface RequestInterface extends MessageInterface
* @return static
* @throws \InvalidArgumentException for invalid HTTP methods.
*/
public function withMethod(string $method);
public function withMethod(string $method): RequestInterface;
/**
* Retrieves the URI instance.
@ -95,7 +94,7 @@ interface RequestInterface extends MessageInterface
* @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.
@ -127,5 +126,5 @@ interface RequestInterface extends MessageInterface
* @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;
}

View file

@ -1,7 +1,5 @@
<?php
declare(strict_types=1);
namespace Psr\Http\Message;
/**
@ -29,7 +27,7 @@ interface ResponseInterface extends MessageInterface
*
* @return int Status code.
*/
public function getStatusCode();
public function getStatusCode(): int;
/**
* Return an instance with the specified status code and, optionally, reason phrase.
@ -51,7 +49,7 @@ interface ResponseInterface extends MessageInterface
* @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.
@ -66,5 +64,5 @@ interface ResponseInterface extends MessageInterface
* @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;
}

View file

@ -1,7 +1,5 @@
<?php
declare(strict_types=1);
namespace Psr\Http\Message;
/**
@ -53,7 +51,7 @@ interface ServerRequestInterface extends RequestInterface
*
* @return array
*/
public function getServerParams();
public function getServerParams(): array;
/**
* Retrieve cookies.
@ -65,7 +63,7 @@ interface ServerRequestInterface extends RequestInterface
*
* @return array
*/
public function getCookieParams();
public function getCookieParams(): array;
/**
* Return an instance with the specified cookies.
@ -84,7 +82,7 @@ interface ServerRequestInterface extends RequestInterface
* @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.
@ -98,7 +96,7 @@ interface ServerRequestInterface extends RequestInterface
*
* @return array
*/
public function getQueryParams();
public function getQueryParams(): array;
/**
* Return an instance with the specified query string arguments.
@ -122,7 +120,7 @@ interface ServerRequestInterface extends RequestInterface
* $_GET.
* @return static
*/
public function withQueryParams(array $query);
public function withQueryParams(array $query): ServerRequestInterface;
/**
* Retrieve normalized file upload data.
@ -136,7 +134,7 @@ interface ServerRequestInterface extends RequestInterface
* @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.
@ -149,7 +147,7 @@ interface ServerRequestInterface extends RequestInterface
* @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.
@ -196,7 +194,7 @@ interface ServerRequestInterface extends RequestInterface
* @throws \InvalidArgumentException if an unsupported argument type is
* provided.
*/
public function withParsedBody($data);
public function withParsedBody($data): ServerRequestInterface;
/**
* Retrieve attributes derived from the request.
@ -209,7 +207,7 @@ interface ServerRequestInterface extends RequestInterface
*
* @return array Attributes derived from the request.
*/
public function getAttributes();
public function getAttributes(): array;
/**
* Retrieve a single derived request attribute.
@ -243,7 +241,7 @@ interface ServerRequestInterface extends RequestInterface
* @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.
@ -259,5 +257,5 @@ interface ServerRequestInterface extends RequestInterface
* @param string $name The attribute name.
* @return static
*/
public function withoutAttribute(string $name);
public function withoutAttribute(string $name): ServerRequestInterface;
}

View file

@ -1,7 +1,5 @@
<?php
declare(strict_types=1);
namespace Psr\Http\Message;
/**
@ -27,14 +25,14 @@ interface StreamInterface
* @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.
@ -50,7 +48,7 @@ interface StreamInterface
*
* @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
@ -58,21 +56,21 @@ interface StreamInterface
* @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.
@ -86,7 +84,7 @@ interface StreamInterface
* 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.
@ -98,14 +96,14 @@ interface StreamInterface
* @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.
@ -114,14 +112,14 @@ interface StreamInterface
* @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.
@ -133,7 +131,7 @@ interface StreamInterface
* 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
@ -142,7 +140,7 @@ interface StreamInterface
* @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.

View file

@ -1,7 +1,5 @@
<?php
declare(strict_types=1);
namespace Psr\Http\Message;
/**
@ -30,7 +28,7 @@ interface UploadedFileInterface
* @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.
@ -64,7 +62,7 @@ interface UploadedFileInterface
* @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.
@ -75,7 +73,7 @@ interface UploadedFileInterface
*
* @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.
@ -91,7 +89,7 @@ interface UploadedFileInterface
* @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.
@ -106,7 +104,7 @@ interface UploadedFileInterface
* @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.
@ -121,5 +119,5 @@ interface UploadedFileInterface
* @return string|null The media type sent by the client or null if none
* was provided.
*/
public function getClientMediaType();
public function getClientMediaType(): ?string;
}

View file

@ -1,7 +1,5 @@
<?php
declare(strict_types=1);
namespace Psr\Http\Message;
/**
@ -40,7 +38,7 @@ interface UriInterface
* @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.
@ -60,7 +58,7 @@ interface UriInterface
* @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.
@ -77,7 +75,7 @@ interface UriInterface
*
* @return string The URI user information, in "username[:password]" format.
*/
public function getUserInfo();
public function getUserInfo(): string;
/**
* Retrieve the host component of the URI.
@ -90,7 +88,7 @@ interface UriInterface
* @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.
@ -107,7 +105,7 @@ interface UriInterface
*
* @return null|int The URI port.
*/
public function getPort();
public function getPort(): ?int;
/**
* Retrieve the path component of the URI.
@ -134,7 +132,7 @@ interface UriInterface
* @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.
@ -156,7 +154,7 @@ interface UriInterface
* @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.
@ -174,7 +172,7 @@ interface UriInterface
* @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.
@ -191,7 +189,7 @@ interface UriInterface
* @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.
@ -207,7 +205,7 @@ interface UriInterface
* @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.
@ -221,7 +219,7 @@ interface UriInterface
* @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.
@ -240,7 +238,7 @@ interface UriInterface
* @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.
@ -264,7 +262,7 @@ interface UriInterface
* @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.
@ -281,7 +279,7 @@ interface UriInterface
* @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.
@ -297,7 +295,7 @@ interface UriInterface
* @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.
@ -322,5 +320,5 @@ interface UriInterface
* @see http://tools.ietf.org/html/rfc3986#section-4.1
* @return string
*/
public function __toString();
public function __toString(): string;
}

View file

@ -0,0 +1,28 @@
<?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.';
}

View file

@ -48,7 +48,7 @@
"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"
@ -56,19 +56,19 @@
"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": {

View file

@ -0,0 +1,3 @@
vendor/
composer.lock
phpunit.xml

View file

@ -0,0 +1,5 @@
CHANGELOG
=========
The changelog is maintained for all Symfony contracts at the following URL:
https://github.com/symfony/contracts/blob/main/CHANGELOG.md

View file

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

View file

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

View file

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

View file

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

View file

@ -0,0 +1,19 @@
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.

View file

@ -0,0 +1,9 @@
Symfony Cache Contracts
=======================
A set of abstractions extracted out of the Symfony components.
Can be used to build on semantics that the Symfony components proved useful - and
that already have battle tested implementations.
See https://github.com/symfony/contracts/blob/main/README.md for more information.

View file

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

View file

@ -0,0 +1,38 @@
{
"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"
}
}
}

View file

@ -11,37 +11,32 @@
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) {
@ -51,31 +46,45 @@ abstract class AbstractAdapter implements AdapterInterface, LoggerAwareInterface
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;
@ -86,6 +95,10 @@ abstract class AbstractAdapter implements AdapterInterface, LoggerAwareInterface
}
/**
* 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
@ -95,37 +108,25 @@ abstract class AbstractAdapter implements AdapterInterface, LoggerAwareInterface
*/
public static function createSystemCache($namespace, $defaultLifetime, $version, $directory, LoggerInterface $logger = null)
{
if (null === self::$apcuSupported) {
self::$apcuSupported = ApcuAdapter::isSupported();
$opcache = new PhpFilesAdapter($namespace, $defaultLifetime, $directory, true);
if (null !== $logger) {
$opcache->setLogger($logger);
}
if (!self::$apcuSupported && null === self::$phpFilesSupported) {
self::$phpFilesSupported = PhpFilesAdapter::isSupported();
}
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 = [])
@ -133,10 +134,10 @@ abstract class AbstractAdapter implements AdapterInterface, LoggerAwareInterface
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);
}
@ -145,81 +146,8 @@ abstract class AbstractAdapter implements AdapterInterface, LoggerAwareInterface
/**
* {@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()
{
@ -229,7 +157,12 @@ abstract class AbstractAdapter implements AdapterInterface, LoggerAwareInterface
$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 {
@ -244,7 +177,8 @@ abstract class AbstractAdapter implements AdapterInterface, LoggerAwareInterface
$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) {
@ -266,49 +200,11 @@ abstract class AbstractAdapter implements AdapterInterface, LoggerAwareInterface
}
$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);
}
}
}

View file

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

View file

@ -14,6 +14,9 @@ namespace Symfony\Component\Cache\Adapter;
use Psr\Cache\CacheItemPoolInterface;
use Symfony\Component\Cache\CacheItem;
// Help opcache.preload discover always-needed symbols
class_exists(CacheItem::class);
/**
* Interface for adapters managing instances of Symfony's CacheItem.
*
@ -34,4 +37,13 @@ interface AdapterInterface extends CacheItemPoolInterface
* @return \Traversable|CacheItem[]
*/
public function getItems(array $keys = []);
/**
* {@inheritdoc}
*
* @param string $prefix
*
* @return bool
*/
public function clear(/* string $prefix = '' */);
}

View file

@ -18,13 +18,9 @@ class ApcuAdapter extends AbstractAdapter
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);
}

View file

@ -16,11 +16,12 @@ use Psr\Log\LoggerAwareInterface;
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;
@ -28,10 +29,9 @@ class ArrayAdapter implements AdapterInterface, LoggerAwareInterface, Resettable
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;
@ -49,27 +49,35 @@ class ArrayAdapter implements AdapterInterface, LoggerAwareInterface, Resettable
);
}
/**
* {@inheritdoc}
*/
public function get(string $key, callable $callback, float $beta = null, array &$metadata = null)
{
$item = $this->getItem($key);
$metadata = $item->getMetadata();
// ArrayAdapter works in memory, we don't care about stampede protection
if (\INF === $beta || !$item->isHit()) {
$save = true;
$item->set($callback($item, $save));
if ($save) {
$this->save($item);
}
}
return $item->get();
}
/**
* {@inheritdoc}
*/
public function getItem($key)
{
$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;
}
} catch (\Exception $e) {
CacheItem::log($this->logger, 'Failed to unserialize key "{key}"', ['key' => $key, 'exception' => $e]);
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;
@ -82,14 +90,18 @@ class ArrayAdapter implements AdapterInterface, LoggerAwareInterface, Resettable
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)
{
@ -102,6 +114,8 @@ class ArrayAdapter implements AdapterInterface, LoggerAwareInterface, Resettable
/**
* {@inheritdoc}
*
* @return bool
*/
public function save(CacheItemInterface $item)
{
@ -113,37 +127,32 @@ class ArrayAdapter implements AdapterInterface, LoggerAwareInterface, Resettable
$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)
{
@ -152,9 +161,19 @@ class ArrayAdapter implements AdapterInterface, LoggerAwareInterface, Resettable
/**
* {@inheritdoc}
*
* @return bool
*/
public function commit()
{
return true;
}
/**
* {@inheritdoc}
*/
public function delete(string $key): bool
{
return $this->deleteItem($key);
}
}

View file

@ -17,6 +17,9 @@ use Symfony\Component\Cache\CacheItem;
use Symfony\Component\Cache\Exception\InvalidArgumentException;
use Symfony\Component\Cache\PruneableInterface;
use Symfony\Component\Cache\ResettableInterface;
use Symfony\Component\Cache\Traits\ContractsTrait;
use Symfony\Contracts\Cache\CacheInterface;
use Symfony\Contracts\Service\ResetInterface;
/**
* Chains several adapters together.
@ -26,8 +29,10 @@ use Symfony\Component\Cache\ResettableInterface;
*
* @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;
@ -36,7 +41,7 @@ class ChainAdapter implements AdapterInterface, PruneableInterface, ResettableIn
* @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.');
@ -46,7 +51,7 @@ class ChainAdapter implements AdapterInterface, PruneableInterface, ResettableIn
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
}
@ -59,11 +64,18 @@ class ChainAdapter implements AdapterInterface, PruneableInterface, ResettableIn
$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);
}
@ -74,6 +86,43 @@ class ChainAdapter implements AdapterInterface, PruneableInterface, ResettableIn
);
}
/**
* {@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}
*/
@ -107,12 +156,12 @@ class ChainAdapter implements AdapterInterface, PruneableInterface, ResettableIn
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()) {
@ -140,6 +189,8 @@ class ChainAdapter implements AdapterInterface, PruneableInterface, ResettableIn
/**
* {@inheritdoc}
*
* @return bool
*/
public function hasItem($key)
{
@ -154,14 +205,23 @@ class ChainAdapter implements AdapterInterface, PruneableInterface, ResettableIn
/**
* {@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;
@ -169,6 +229,8 @@ class ChainAdapter implements AdapterInterface, PruneableInterface, ResettableIn
/**
* {@inheritdoc}
*
* @return bool
*/
public function deleteItem($key)
{
@ -184,6 +246,8 @@ class ChainAdapter implements AdapterInterface, PruneableInterface, ResettableIn
/**
* {@inheritdoc}
*
* @return bool
*/
public function deleteItems(array $keys)
{
@ -199,6 +263,8 @@ class ChainAdapter implements AdapterInterface, PruneableInterface, ResettableIn
/**
* {@inheritdoc}
*
* @return bool
*/
public function save(CacheItemInterface $item)
{
@ -214,6 +280,8 @@ class ChainAdapter implements AdapterInterface, PruneableInterface, ResettableIn
/**
* {@inheritdoc}
*
* @return bool
*/
public function saveDeferred(CacheItemInterface $item)
{
@ -229,6 +297,8 @@ class ChainAdapter implements AdapterInterface, PruneableInterface, ResettableIn
/**
* {@inheritdoc}
*
* @return bool
*/
public function commit()
{
@ -264,7 +334,7 @@ class ChainAdapter implements AdapterInterface, PruneableInterface, ResettableIn
public function reset()
{
foreach ($this->adapters as $adapter) {
if ($adapter instanceof ResettableInterface) {
if ($adapter instanceof ResetInterface) {
$adapter->reset();
}
}

View file

@ -18,11 +18,7 @@ class DoctrineAdapter extends AbstractAdapter
{
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;

View file

@ -11,6 +11,8 @@
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;
@ -18,13 +20,9 @@ class FilesystemAdapter extends AbstractAdapter 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);
}

View file

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

View file

@ -11,6 +11,7 @@
namespace Symfony\Component\Cache\Adapter;
use Symfony\Component\Cache\Marshaller\MarshallerInterface;
use Symfony\Component\Cache\Traits\MemcachedTrait;
class MemcachedAdapter extends AbstractAdapter
@ -29,8 +30,8 @@ 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);
}
}

View file

@ -13,11 +13,12 @@ namespace Symfony\Component\Cache\Adapter;
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;
@ -36,6 +37,16 @@ class NullAdapter implements AdapterInterface
);
}
/**
* {@inheritdoc}
*/
public function get(string $key, callable $callback, float $beta = null, array &$metadata = null)
{
$save = true;
return $callback(($this->createCacheItem)($key), $save);
}
/**
* {@inheritdoc}
*/
@ -56,6 +67,8 @@ class NullAdapter implements AdapterInterface
/**
* {@inheritdoc}
*
* @return bool
*/
public function hasItem($key)
{
@ -64,14 +77,20 @@ class NullAdapter implements AdapterInterface
/**
* {@inheritdoc}
*
* @param string $prefix
*
* @return bool
*/
public function clear()
public function clear(/* string $prefix = '' */)
{
return true;
}
/**
* {@inheritdoc}
*
* @return bool
*/
public function deleteItem($key)
{
@ -80,6 +99,8 @@ class NullAdapter implements AdapterInterface
/**
* {@inheritdoc}
*
* @return bool
*/
public function deleteItems(array $keys)
{
@ -88,26 +109,40 @@ class NullAdapter implements AdapterInterface
/**
* {@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 true;
}
/**
* {@inheritdoc}
*/
public function commit()
public function delete(string $key): bool
{
return false;
return $this->deleteItem($key);
}
private function generateItems(array $keys)

View file

@ -13,6 +13,7 @@ namespace Symfony\Component\Cache\Adapter;
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;
@ -27,6 +28,9 @@ class PdoAdapter extends AbstractAdapter implements PruneableInterface
* 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]
@ -37,17 +41,14 @@ class PdoAdapter extends AbstractAdapter implements PruneableInterface
* * 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);
}
}

View file

@ -17,7 +17,9 @@ use Symfony\Component\Cache\CacheItem;
use Symfony\Component\Cache\Exception\InvalidArgumentException;
use Symfony\Component\Cache\PruneableInterface;
use Symfony\Component\Cache\ResettableInterface;
use Symfony\Component\Cache\Traits\ContractsTrait;
use Symfony\Component\Cache\Traits\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.
@ -26,8 +28,9 @@ use Symfony\Component\Cache\Traits\PhpArrayTrait;
* @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;
@ -36,11 +39,10 @@ class PhpArrayAdapter implements AdapterInterface, PruneableInterface, Resettabl
* @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();
@ -56,9 +58,7 @@ class PhpArrayAdapter implements AdapterInterface, PruneableInterface, Resettabl
}
/**
* 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
@ -67,15 +67,44 @@ class PhpArrayAdapter implements AdapterInterface, PruneableInterface, Resettabl
*/
public static function create($file, CacheItemPoolInterface $fallbackPool)
{
if (\PHP_VERSION_ID >= 70000) {
if (!$fallbackPool instanceof AdapterInterface) {
$fallbackPool = new ProxyAdapter($fallbackPool);
}
return new static($file, $fallbackPool);
if (!$fallbackPool instanceof AdapterInterface) {
$fallbackPool = new ProxyAdapter($fallbackPool);
}
return $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 $this->doGet($this->pool, $key, $callback, $beta, $metadata);
}
$value = $this->values[$this->keys[$key]];
if ('N;' === $value) {
return null;
}
try {
if ($value instanceof \Closure) {
return $value();
}
} catch (\Throwable $e) {
unset($this->keys[$key]);
goto get_from_pool;
}
return $value;
}
/**
@ -89,23 +118,19 @@ class PhpArrayAdapter implements AdapterInterface, PruneableInterface, Resettabl
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;
}
@ -135,6 +160,8 @@ class PhpArrayAdapter implements AdapterInterface, PruneableInterface, Resettabl
/**
* {@inheritdoc}
*
* @return bool
*/
public function hasItem($key)
{
@ -145,11 +172,13 @@ class PhpArrayAdapter implements AdapterInterface, PruneableInterface, Resettabl
$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)
{
@ -160,11 +189,13 @@ class PhpArrayAdapter implements AdapterInterface, PruneableInterface, Resettabl
$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)
{
@ -176,7 +207,7 @@ class PhpArrayAdapter implements AdapterInterface, PruneableInterface, Resettabl
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;
@ -195,6 +226,8 @@ class PhpArrayAdapter implements AdapterInterface, PruneableInterface, Resettabl
/**
* {@inheritdoc}
*
* @return bool
*/
public function save(CacheItemInterface $item)
{
@ -202,11 +235,13 @@ class PhpArrayAdapter implements AdapterInterface, PruneableInterface, Resettabl
$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)
{
@ -214,37 +249,34 @@ class PhpArrayAdapter implements AdapterInterface, PruneableInterface, Resettabl
$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 {
@ -256,9 +288,7 @@ class PhpArrayAdapter implements AdapterInterface, PruneableInterface, Resettabl
}
if ($fallbackKeys) {
foreach ($this->pool->getItems($fallbackKeys) as $key => $item) {
yield $key => $item;
}
yield from $this->pool->getItems($fallbackKeys);
}
}

View file

@ -20,22 +20,19 @@ class PhpFilesAdapter extends AbstractAdapter 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);
};
}
}

View file

@ -16,26 +16,26 @@ use Psr\Cache\CacheItemPoolInterface;
use Symfony\Component\Cache\CacheItem;
use Symfony\Component\Cache\PruneableInterface;
use Symfony\Component\Cache\ResettableInterface;
use Symfony\Component\Cache\Traits\ContractsTrait;
use Symfony\Component\Cache\Traits\ProxyTrait;
use Symfony\Contracts\Cache\CacheInterface;
/**
* @author Nicolas Grekas <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);
@ -46,20 +46,71 @@ class ProxyAdapter implements AdapterInterface, PruneableInterface, ResettableIn
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);
}
/**
@ -89,6 +140,8 @@ class ProxyAdapter implements AdapterInterface, PruneableInterface, ResettableIn
/**
* {@inheritdoc}
*
* @return bool
*/
public function hasItem($key)
{
@ -97,14 +150,26 @@ class ProxyAdapter implements AdapterInterface, PruneableInterface, ResettableIn
/**
* {@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)
{
@ -113,6 +178,8 @@ class ProxyAdapter implements AdapterInterface, PruneableInterface, ResettableIn
/**
* {@inheritdoc}
*
* @return bool
*/
public function deleteItems(array $keys)
{
@ -127,6 +194,8 @@ class ProxyAdapter implements AdapterInterface, PruneableInterface, ResettableIn
/**
* {@inheritdoc}
*
* @return bool
*/
public function save(CacheItemInterface $item)
{
@ -135,6 +204,8 @@ class ProxyAdapter implements AdapterInterface, PruneableInterface, ResettableIn
/**
* {@inheritdoc}
*
* @return bool
*/
public function saveDeferred(CacheItemInterface $item)
{
@ -143,21 +214,22 @@ class ProxyAdapter implements AdapterInterface, PruneableInterface, ResettableIn
/**
* {@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"]) {
@ -171,13 +243,12 @@ class ProxyAdapter implements AdapterInterface, PruneableInterface, ResettableIn
$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;
@ -190,7 +261,7 @@ class ProxyAdapter implements AdapterInterface, PruneableInterface, ResettableIn
}
}
private function getId($key)
private function getId($key): string
{
CacheItem::validateKey($key);

View file

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

View file

@ -11,6 +11,9 @@
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
@ -18,12 +21,12 @@ 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);
}
}

View file

@ -0,0 +1,321 @@
<?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 = '';
}
}

View file

@ -11,73 +11,11 @@
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);
}
}

View file

@ -13,20 +13,26 @@ namespace Symfony\Component\Cache\Adapter;
use Psr\Cache\CacheItemInterface;
use Psr\Cache\InvalidArgumentException;
use Psr\Log\LoggerAwareInterface;
use Psr\Log\LoggerAwareTrait;
use Symfony\Component\Cache\CacheItem;
use Symfony\Component\Cache\PruneableInterface;
use Symfony\Component\Cache\ResettableInterface;
use Symfony\Component\Cache\Traits\ContractsTrait;
use Symfony\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;
@ -36,7 +42,7 @@ class TagAwareAdapter implements TagAwareAdapterInterface, PruneableInterface, R
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;
@ -56,12 +62,13 @@ class TagAwareAdapter implements TagAwareAdapterInterface, PruneableInterface, R
);
$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 {
@ -78,7 +85,8 @@ class TagAwareAdapter implements TagAwareAdapterInterface, PruneableInterface, R
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;
@ -145,12 +153,15 @@ class TagAwareAdapter implements TagAwareAdapterInterface, PruneableInterface, R
/**
* {@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;
}
@ -166,9 +177,11 @@ class TagAwareAdapter implements TagAwareAdapterInterface, PruneableInterface, R
}
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;
@ -191,18 +204,21 @@ class TagAwareAdapter implements TagAwareAdapterInterface, PruneableInterface, R
*/
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) {
@ -216,16 +232,36 @@ class TagAwareAdapter implements TagAwareAdapterInterface, PruneableInterface, R
/**
* {@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)
{
@ -234,6 +270,8 @@ class TagAwareAdapter implements TagAwareAdapterInterface, PruneableInterface, R
/**
* {@inheritdoc}
*
* @return bool
*/
public function deleteItems(array $keys)
{
@ -248,6 +286,8 @@ class TagAwareAdapter implements TagAwareAdapterInterface, PruneableInterface, R
/**
* {@inheritdoc}
*
* @return bool
*/
public function save(CacheItemInterface $item)
{
@ -261,6 +301,8 @@ class TagAwareAdapter implements TagAwareAdapterInterface, PruneableInterface, R
/**
* {@inheritdoc}
*
* @return bool
*/
public function saveDeferred(CacheItemInterface $item)
{
@ -274,12 +316,17 @@ class TagAwareAdapter implements TagAwareAdapterInterface, PruneableInterface, R
/**
* {@inheritdoc}
*
* @return bool
*/
public function commit()
{
return $this->invalidateTags([]);
}
/**
* @return array
*/
public function __sleep()
{
throw new \BadMethodCallException('Cannot serialize '.__CLASS__);
@ -295,7 +342,7 @@ class TagAwareAdapter implements TagAwareAdapterInterface, PruneableInterface, R
$this->commit();
}
private function generateItems($items, array $tagKeys)
private function generateItems(iterable $items, array $tagKeys)
{
$bufferedItems = $itemTags = [];
$f = $this->setCacheItemTags;
@ -321,10 +368,11 @@ class TagAwareAdapter implements TagAwareAdapterInterface, PruneableInterface, R
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;
@ -363,7 +411,7 @@ class TagAwareAdapter implements TagAwareAdapterInterface, PruneableInterface, R
$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;
}
@ -385,6 +433,10 @@ class TagAwareAdapter implements TagAwareAdapterInterface, PruneableInterface, R
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]];
}

View file

@ -12,8 +12,11 @@
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.
@ -22,7 +25,7 @@ use Symfony\Component\Cache\ResettableInterface;
* @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 = [];
@ -32,6 +35,38 @@ class TraceableAdapter implements AdapterInterface, PruneableInterface, Resettab
$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}
*/
@ -54,6 +89,8 @@ class TraceableAdapter implements AdapterInterface, PruneableInterface, Resettab
/**
* {@inheritdoc}
*
* @return bool
*/
public function hasItem($key)
{
@ -67,6 +104,8 @@ class TraceableAdapter implements AdapterInterface, PruneableInterface, Resettab
/**
* {@inheritdoc}
*
* @return bool
*/
public function deleteItem($key)
{
@ -80,6 +119,8 @@ class TraceableAdapter implements AdapterInterface, PruneableInterface, Resettab
/**
* {@inheritdoc}
*
* @return bool
*/
public function save(CacheItemInterface $item)
{
@ -93,6 +134,8 @@ class TraceableAdapter implements AdapterInterface, PruneableInterface, Resettab
/**
* {@inheritdoc}
*
* @return bool
*/
public function saveDeferred(CacheItemInterface $item)
{
@ -132,11 +175,20 @@ class TraceableAdapter implements AdapterInterface, PruneableInterface, Resettab
/**
* {@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);
@ -145,6 +197,8 @@ class TraceableAdapter implements AdapterInterface, PruneableInterface, Resettab
/**
* {@inheritdoc}
*
* @return bool
*/
public function deleteItems(array $keys)
{
@ -159,6 +213,8 @@ class TraceableAdapter implements AdapterInterface, PruneableInterface, Resettab
/**
* {@inheritdoc}
*
* @return bool
*/
public function commit()
{
@ -191,13 +247,26 @@ class TraceableAdapter implements AdapterInterface, PruneableInterface, Resettab
*/
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;

View file

@ -11,10 +11,12 @@
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)
{

View file

@ -1,6 +1,43 @@
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
-----
@ -13,7 +50,7 @@ CHANGELOG
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)

View file

@ -11,34 +11,40 @@
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()
{
@ -48,7 +54,7 @@ final class CacheItem implements CacheItemInterface
/**
* {@inheritdoc}
*/
public function isHit()
public function isHit(): bool
{
return $this->isHit;
}
@ -58,7 +64,7 @@ final class CacheItem implements CacheItemInterface
*
* @return $this
*/
public function set($value)
public function set($value): self
{
$this->value = $value;
@ -70,12 +76,12 @@ final class CacheItem implements CacheItemInterface
*
* @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)));
}
@ -88,14 +94,14 @@ final class CacheItem implements CacheItemInterface
*
* @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)));
}
@ -104,58 +110,64 @@ final class CacheItem implements CacheItemInterface
}
/**
* 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)));
@ -163,8 +175,8 @@ final class CacheItem implements CacheItemInterface
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;
@ -175,14 +187,14 @@ final class CacheItem implements CacheItemInterface
*
* @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;
}
}

View file

@ -21,6 +21,8 @@ use Symfony\Component\HttpKernel\DataCollector\LateDataCollectorInterface;
/**
* @author Aaron Scherer <aequasi@gmail.com>
* @author Tobias Nyholm <tobias.nyholm@gmail.com>
*
* @final since Symfony 4.4
*/
class CacheDataCollector extends DataCollector implements LateDataCollectorInterface
{
@ -39,8 +41,10 @@ class CacheDataCollector extends DataCollector implements LateDataCollectorInter
/**
* {@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];
@ -62,7 +66,7 @@ class CacheDataCollector extends DataCollector implements LateDataCollectorInter
public function lateCollect()
{
$this->data = $this->cloneVar($this->data);
$this->data['instances']['calls'] = $this->cloneVar($this->data['instances']['calls']);
}
/**
@ -103,10 +107,7 @@ class CacheDataCollector extends DataCollector implements LateDataCollectorInter
return $this->data['instances']['calls'];
}
/**
* @return array
*/
private function calculateStatistics()
private function calculateStatistics(): array
{
$statistics = [];
foreach ($this->data['instances']['calls'] as $name => $calls) {
@ -123,7 +124,15 @@ class CacheDataCollector extends DataCollector implements LateDataCollectorInter
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'];
@ -157,10 +166,7 @@ class CacheDataCollector extends DataCollector implements LateDataCollectorInter
return $statistics;
}
/**
* @return array
*/
private function calculateTotalStatistics()
private function calculateTotalStatistics(): array
{
$statistics = $this->getStatistics();
$totals = [

View file

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

View file

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

View file

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

View file

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

View file

@ -13,6 +13,11 @@ namespace Symfony\Component\Cache;
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>
@ -39,7 +44,7 @@ class DoctrineProvider extends CacheProvider implements PruneableInterface, Rese
*/
public function reset()
{
if ($this->pool instanceof ResettableInterface) {
if ($this->pool instanceof ResetInterface) {
$this->pool->reset();
}
$this->setNamespace($this->getNamespace());
@ -47,6 +52,8 @@ class DoctrineProvider extends CacheProvider implements PruneableInterface, Rese
/**
* {@inheritdoc}
*
* @return mixed
*/
protected function doFetch($id)
{
@ -57,6 +64,8 @@ class DoctrineProvider extends CacheProvider implements PruneableInterface, Rese
/**
* {@inheritdoc}
*
* @return bool
*/
protected function doContains($id)
{
@ -65,6 +74,8 @@ class DoctrineProvider extends CacheProvider implements PruneableInterface, Rese
/**
* {@inheritdoc}
*
* @return bool
*/
protected function doSave($id, $data, $lifeTime = 0)
{
@ -79,6 +90,8 @@ class DoctrineProvider extends CacheProvider implements PruneableInterface, Rese
/**
* {@inheritdoc}
*
* @return bool
*/
protected function doDelete($id)
{
@ -87,6 +100,8 @@ class DoctrineProvider extends CacheProvider implements PruneableInterface, Rese
/**
* {@inheritdoc}
*
* @return bool
*/
protected function doFlush()
{
@ -95,6 +110,8 @@ class DoctrineProvider extends CacheProvider implements PruneableInterface, Rese
/**
* {@inheritdoc}
*
* @return array|null
*/
protected function doGetStats()
{

View file

@ -14,6 +14,12 @@ namespace Symfony\Component\Cache\Exception;
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
{
}
}

View file

@ -14,6 +14,12 @@ namespace Symfony\Component\Cache\Exception;
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
{
}
}

View file

@ -0,0 +1,25 @@
<?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
{
}
}

View file

@ -1,4 +1,4 @@
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

View file

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

View file

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

View file

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

View file

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

View file

@ -0,0 +1,89 @@
<?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,
];
}
}

View file

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

View file

@ -1,18 +1,19 @@
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)

View file

@ -11,10 +11,11 @@
namespace Symfony\Component\Cache;
use Symfony\Contracts\Service\ResetInterface;
/**
* Resets a pool's local state.
*/
interface ResettableInterface
interface ResettableInterface extends ResetInterface
{
public function reset();
}

View file

@ -12,37 +12,37 @@
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;
}
/**
* @internal
*/
protected const NS_SEPARATOR = ':';
private $defaultLifetime;
/**
* @param string $namespace
* @param int $defaultLifetime
*/
protected function __construct($namespace = '', $defaultLifetime = 0)
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));
@ -61,7 +61,7 @@ abstract class AbstractCache implements CacheInterface, LoggerAwareInterface, Re
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;
@ -69,6 +69,8 @@ abstract class AbstractCache implements CacheInterface, LoggerAwareInterface, Re
/**
* {@inheritdoc}
*
* @return bool
*/
public function set($key, $value, $ttl = null)
{
@ -79,6 +81,8 @@ abstract class AbstractCache implements CacheInterface, LoggerAwareInterface, Re
/**
* {@inheritdoc}
*
* @return iterable
*/
public function getMultiple($keys, $default = null)
{
@ -95,7 +99,7 @@ abstract class AbstractCache implements CacheInterface, LoggerAwareInterface, Re
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);
@ -105,6 +109,8 @@ abstract class AbstractCache implements CacheInterface, LoggerAwareInterface, Re
/**
* {@inheritdoc}
*
* @return bool
*/
public function setMultiple($values, $ttl = null)
{
@ -134,13 +140,16 @@ abstract class AbstractCache implements CacheInterface, LoggerAwareInterface, Re
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)
{
@ -168,19 +177,19 @@ abstract class AbstractCache implements CacheInterface, LoggerAwareInterface, Re
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) {

View file

@ -11,18 +11,20 @@
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);
}

View file

@ -12,16 +12,20 @@
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;
@ -31,12 +35,11 @@ class ArrayCache implements CacheInterface, LoggerAwareInterface, ResettableInte
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;
}
@ -45,13 +48,26 @@ class ArrayCache implements CacheInterface, LoggerAwareInterface, ResettableInte
*/
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)
{
@ -61,14 +77,18 @@ class ArrayCache implements CacheInterface, LoggerAwareInterface, ResettableInte
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)
{
@ -84,16 +104,22 @@ class ArrayCache implements CacheInterface, LoggerAwareInterface, ResettableInte
/**
* {@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)
{
@ -103,27 +129,20 @@ class ArrayCache implements CacheInterface, LoggerAwareInterface, ResettableInte
$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;
}

View file

@ -11,10 +11,15 @@
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.
@ -22,9 +27,9 @@ use Symfony\Component\Cache\ResettableInterface;
* 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 = [];
@ -32,25 +37,25 @@ class ChainCache implements CacheInterface, PruneableInterface, ResettableInterf
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;
}
/**
@ -77,6 +82,8 @@ class ChainCache implements CacheInterface, PruneableInterface, ResettableInterf
/**
* {@inheritdoc}
*
* @return iterable
*/
public function getMultiple($keys, $default = null)
{
@ -85,11 +92,11 @@ class ChainCache implements CacheInterface, PruneableInterface, ResettableInterf
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) {
@ -118,6 +125,8 @@ class ChainCache implements CacheInterface, PruneableInterface, ResettableInterf
/**
* {@inheritdoc}
*
* @return bool
*/
public function has($key)
{
@ -132,6 +141,8 @@ class ChainCache implements CacheInterface, PruneableInterface, ResettableInterf
/**
* {@inheritdoc}
*
* @return bool
*/
public function clear()
{
@ -147,6 +158,8 @@ class ChainCache implements CacheInterface, PruneableInterface, ResettableInterf
/**
* {@inheritdoc}
*
* @return bool
*/
public function delete($key)
{
@ -162,6 +175,8 @@ class ChainCache implements CacheInterface, PruneableInterface, ResettableInterf
/**
* {@inheritdoc}
*
* @return bool
*/
public function deleteMultiple($keys)
{
@ -180,6 +195,8 @@ class ChainCache implements CacheInterface, PruneableInterface, ResettableInterf
/**
* {@inheritdoc}
*
* @return bool
*/
public function set($key, $value, $ttl = null)
{
@ -195,6 +212,8 @@ class ChainCache implements CacheInterface, PruneableInterface, ResettableInterf
/**
* {@inheritdoc}
*
* @return bool
*/
public function setMultiple($values, $ttl = null)
{
@ -244,7 +263,7 @@ class ChainCache implements CacheInterface, PruneableInterface, ResettableInterf
public function reset()
{
foreach ($this->caches as $cache) {
if ($cache instanceof ResettableInterface) {
if ($cache instanceof ResetInterface) {
$cache->reset();
}
}

View file

@ -12,17 +12,20 @@
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;

View file

@ -11,20 +11,25 @@
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);
}

View file

@ -11,20 +11,24 @@
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);
}
}

View file

@ -11,12 +11,16 @@
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}
@ -28,6 +32,8 @@ class NullCache implements CacheInterface
/**
* {@inheritdoc}
*
* @return iterable
*/
public function getMultiple($keys, $default = null)
{
@ -38,6 +44,8 @@ class NullCache implements CacheInterface
/**
* {@inheritdoc}
*
* @return bool
*/
public function has($key)
{
@ -46,6 +54,8 @@ class NullCache implements CacheInterface
/**
* {@inheritdoc}
*
* @return bool
*/
public function clear()
{
@ -54,6 +64,8 @@ class NullCache implements CacheInterface
/**
* {@inheritdoc}
*
* @return bool
*/
public function delete($key)
{
@ -62,6 +74,8 @@ class NullCache implements CacheInterface
/**
* {@inheritdoc}
*
* @return bool
*/
public function deleteMultiple($keys)
{
@ -70,6 +84,8 @@ class NullCache implements CacheInterface
/**
* {@inheritdoc}
*
* @return bool
*/
public function set($key, $value, $ttl = null)
{
@ -78,6 +94,8 @@ class NullCache implements CacheInterface
/**
* {@inheritdoc}
*
* @return bool
*/
public function setMultiple($values, $ttl = null)
{

View file

@ -11,9 +11,17 @@
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;
@ -25,6 +33,9 @@ class PdoCache extends AbstractCache implements PruneableInterface
* 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]
@ -35,17 +46,14 @@ class PdoCache extends AbstractCache implements PruneableInterface
* * 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);
}
}

View file

@ -11,51 +11,44 @@
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);
}
/**
@ -69,22 +62,18 @@ class PhpArrayCache implements CacheInterface, PruneableInterface, ResettableInt
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;
}
}
@ -94,6 +83,8 @@ class PhpArrayCache implements CacheInterface, PruneableInterface, ResettableInt
/**
* {@inheritdoc}
*
* @return iterable
*/
public function getMultiple($keys, $default = null)
{
@ -116,6 +107,8 @@ class PhpArrayCache implements CacheInterface, PruneableInterface, ResettableInt
/**
* {@inheritdoc}
*
* @return bool
*/
public function has($key)
{
@ -126,11 +119,13 @@ class PhpArrayCache implements CacheInterface, PruneableInterface, ResettableInt
$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)
{
@ -141,11 +136,13 @@ class PhpArrayCache implements CacheInterface, PruneableInterface, ResettableInt
$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)
{
@ -161,7 +158,7 @@ class PhpArrayCache implements CacheInterface, PruneableInterface, ResettableInt
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;
@ -180,6 +177,8 @@ class PhpArrayCache implements CacheInterface, PruneableInterface, ResettableInt
/**
* {@inheritdoc}
*
* @return bool
*/
public function set($key, $value, $ttl = null)
{
@ -190,11 +189,13 @@ class PhpArrayCache implements CacheInterface, PruneableInterface, ResettableInt
$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)
{
@ -210,7 +211,7 @@ class PhpArrayCache implements CacheInterface, PruneableInterface, ResettableInt
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;
@ -224,22 +225,20 @@ class PhpArrayCache implements CacheInterface, PruneableInterface, ResettableInt
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 {
@ -251,9 +250,7 @@ class PhpArrayCache implements CacheInterface, PruneableInterface, ResettableInt
}
if ($fallbackKeys) {
foreach ($this->pool->getMultiple($fallbackKeys, $default) as $key => $item) {
yield $key => $item;
}
yield from $this->pool->getMultiple($fallbackKeys, $default);
}
}
}

View file

@ -11,31 +11,35 @@
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);
};
}
}

View file

@ -11,231 +11,13 @@
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);
}
}
}

View file

@ -11,19 +11,27 @@
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);
}
}

View file

@ -11,22 +11,25 @@
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();
@ -56,6 +59,8 @@ class TraceableCache implements CacheInterface, PruneableInterface, ResettableIn
/**
* {@inheritdoc}
*
* @return bool
*/
public function has($key)
{
@ -69,6 +74,8 @@ class TraceableCache implements CacheInterface, PruneableInterface, ResettableIn
/**
* {@inheritdoc}
*
* @return bool
*/
public function delete($key)
{
@ -82,6 +89,8 @@ class TraceableCache implements CacheInterface, PruneableInterface, ResettableIn
/**
* {@inheritdoc}
*
* @return bool
*/
public function set($key, $value, $ttl = null)
{
@ -95,6 +104,8 @@ class TraceableCache implements CacheInterface, PruneableInterface, ResettableIn
/**
* {@inheritdoc}
*
* @return bool
*/
public function setMultiple($values, $ttl = null)
{
@ -122,6 +133,8 @@ class TraceableCache implements CacheInterface, PruneableInterface, ResettableIn
/**
* {@inheritdoc}
*
* @return iterable
*/
public function getMultiple($keys, $default = null)
{
@ -150,6 +163,8 @@ class TraceableCache implements CacheInterface, PruneableInterface, ResettableIn
/**
* {@inheritdoc}
*
* @return bool
*/
public function clear()
{
@ -163,6 +178,8 @@ class TraceableCache implements CacheInterface, PruneableInterface, ResettableIn
/**
* {@inheritdoc}
*
* @return bool
*/
public function deleteMultiple($keys)
{
@ -200,7 +217,7 @@ class TraceableCache implements CacheInterface, PruneableInterface, ResettableIn
*/
public function reset()
{
if (!$this->pool instanceof ResettableInterface) {
if (!$this->pool instanceof ResetInterface) {
return;
}
$event = $this->start(__FUNCTION__);
@ -220,7 +237,7 @@ class TraceableCache implements CacheInterface, PruneableInterface, ResettableIn
}
}
private function start($name)
private function start(string $name): TraceableCacheEvent
{
$this->calls[] = $event = new TraceableCacheEvent();
$event->name = $name;

View file

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

View file

@ -27,6 +27,7 @@ trait AbstractTrait
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
@ -77,10 +78,12 @@ trait AbstractTrait
*
* @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)
{
@ -93,7 +96,7 @@ trait AbstractTrait
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;
}
@ -101,26 +104,43 @@ trait AbstractTrait
/**
* {@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) {
}
if (true !== $e && [] !== $e) {
$cleared = false;
}
if ($cleared = true === $cleared || [] === $cleared) {
$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;
}
@ -128,6 +148,8 @@ trait AbstractTrait
/**
* {@inheritdoc}
*
* @return bool
*/
public function deleteItem($key)
{
@ -136,6 +158,8 @@ trait AbstractTrait
/**
* {@inheritdoc}
*
* @return bool
*/
public function deleteItems(array $keys)
{
@ -164,7 +188,8 @@ trait AbstractTrait
}
} 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;
}
@ -188,6 +213,7 @@ trait AbstractTrait
$wasEnabled = $this->versioningIsEnabled;
$this->versioningIsEnabled = (bool) $enable;
$this->namespaceVersion = '';
$this->ids = [];
return $wasEnabled;
}
@ -201,6 +227,7 @@ trait AbstractTrait
$this->commit();
}
$this->namespaceVersion = '';
$this->ids = [];
}
/**
@ -211,9 +238,13 @@ trait AbstractTrait
* @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;
}
@ -230,29 +261,45 @@ trait AbstractTrait
}
}
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;
@ -265,4 +312,9 @@ trait AbstractTrait
{
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), '/', '_');
}
}

View file

@ -23,10 +23,10 @@ trait ApcuTrait
{
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.');
@ -51,14 +51,27 @@ trait ApcuTrait
*/
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);
}
}
@ -75,8 +88,8 @@ trait ApcuTrait
*/
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();
}
@ -95,7 +108,7 @@ trait ApcuTrait
/**
* {@inheritdoc}
*/
protected function doSave(array $values, $lifetime)
protected function doSave(array $values, int $lifetime)
{
try {
if (false === $failures = apcu_store($values, null, $lifetime)) {
@ -103,15 +116,13 @@ trait ApcuTrait
}
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;
}
}

View file

@ -34,36 +34,72 @@ trait ArrayTrait
*/
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;
@ -77,24 +113,13 @@ trait ArrayTrait
$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]);
@ -105,4 +130,55 @@ trait ArrayTrait
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;
}
}

View file

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

View file

@ -91,7 +91,7 @@ trait DoctrineTrait
/**
* {@inheritdoc}
*/
protected function doSave(array $values, $lifetime)
protected function doSave(array $values, int $lifetime)
{
return $this->provider->saveMultiple($values, $lifetime);
}

View file

@ -23,10 +23,10 @@ trait FilesystemCommonTrait
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;
}
@ -35,6 +35,8 @@ trait FilesystemCommonTrait
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);
@ -55,8 +57,12 @@ trait FilesystemCommonTrait
{
$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;
@ -71,23 +77,39 @@ trait FilesystemCommonTrait
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);
@ -96,10 +118,11 @@ trait FilesystemCommonTrait
}
}
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);
@ -108,6 +131,38 @@ trait FilesystemCommonTrait
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
*/
@ -116,6 +171,9 @@ trait FilesystemCommonTrait
throw new \ErrorException($message, 0, $type, $file, $line);
}
/**
* @return array
*/
public function __sleep()
{
throw new \BadMethodCallException('Cannot serialize '.__CLASS__);

View file

@ -23,6 +23,8 @@ trait FilesystemTrait
{
use FilesystemCommonTrait;
private $marshaller;
/**
* @return bool
*/
@ -31,8 +33,8 @@ trait FilesystemTrait
$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;
}
@ -57,7 +59,7 @@ trait FilesystemTrait
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) {
@ -68,7 +70,7 @@ trait FilesystemTrait
$value = stream_get_contents($h);
fclose($h);
if ($i === $id) {
$values[$id] = parent::unserialize($value);
$values[$id] = $this->marshaller->unmarshall($value);
}
}
}
@ -89,19 +91,34 @@ trait FilesystemTrait
/**
* {@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));
}
}

View file

@ -13,6 +13,8 @@ namespace Symfony\Component\Cache\Traits;
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>
@ -29,18 +31,27 @@ trait MemcachedTrait
\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: dont 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);
@ -55,6 +66,7 @@ trait MemcachedTrait
parent::__construct($namespace, $defaultLifetime);
$this->enableVersioning();
$this->marshaller = $marshaller ?? new DefaultMarshaller();
}
/**
@ -67,7 +79,6 @@ trait MemcachedTrait
* - [['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
*
@ -81,7 +92,7 @@ trait MemcachedTrait
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 {
@ -95,19 +106,43 @@ trait MemcachedTrait
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));
}
@ -116,17 +151,20 @@ trait MemcachedTrait
$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
@ -193,18 +231,22 @@ trait MemcachedTrait
/**
* {@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;
}
/**
@ -212,22 +254,19 @@ trait MemcachedTrait
*/
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);
}
}
@ -236,7 +275,7 @@ trait MemcachedTrait
*/
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());
}
/**
@ -245,7 +284,7 @@ trait MemcachedTrait
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;
@ -275,10 +314,7 @@ trait MemcachedTrait
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;
@ -294,4 +330,14 @@ trait MemcachedTrait
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);
}
}

View file

@ -14,14 +14,22 @@ namespace Symfony\Component\Cache\Traits;
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;
@ -36,7 +44,7 @@ trait PdoTrait
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]));
@ -56,15 +64,16 @@ trait PdoTrait
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);
}
@ -77,6 +86,7 @@ trait PdoTrait
*
* @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()
@ -140,7 +150,7 @@ trait PdoTrait
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);
@ -158,14 +168,35 @@ trait PdoTrait
$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;
}
}
/**
@ -173,13 +204,16 @@ trait PdoTrait
*/
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);
}
@ -196,15 +230,15 @@ trait PdoTrait
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);
}
@ -217,11 +251,14 @@ trait PdoTrait
*/
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());
@ -244,10 +281,14 @@ trait PdoTrait
$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;
@ -260,8 +301,12 @@ trait PdoTrait
{
$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;
}
@ -269,24 +314,15 @@ trait PdoTrait
/**
* {@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)";
@ -321,39 +357,62 @@ trait PdoTrait
$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
}
@ -369,8 +428,15 @@ trait PdoTrait
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) {
@ -379,11 +445,9 @@ trait PdoTrait
$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:
@ -404,6 +468,15 @@ trait PdoTrait
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;
@ -414,10 +487,7 @@ trait PdoTrait
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();

View file

@ -11,8 +11,10 @@
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>
@ -25,8 +27,8 @@ trait PhpArrayTrait
use ProxyTrait;
private $file;
private $keys;
private $values;
private $zendDetectUnicode;
private static $valuesCache = [];
@ -57,56 +59,63 @@ trait PhpArrayTrait
}
}
$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);
}
$dump .= var_export($key, true).' => '.var_export($value, true).",\n";
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)." => {$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]);
@ -116,14 +125,23 @@ EOF;
/**
* {@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;
}
@ -133,20 +151,19 @@ EOF;
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;
}
}
}

View file

@ -13,6 +13,7 @@ namespace Symfony\Component\Cache\Traits;
use Symfony\Component\Cache\Exception\CacheException;
use Symfony\Component\Cache\Exception\InvalidArgumentException;
use Symfony\Component\VarExporter\VarExporter;
/**
* @author Piotr Stankowski <git@trakos.pl>
@ -23,14 +24,24 @@ use Symfony\Component\Cache\Exception\InvalidArgumentException;
*/
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));
}
/**
@ -40,19 +51,21 @@ trait PhpFilesTrait
{
$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 {
@ -67,41 +80,75 @@ trait PhpFilesTrait
*/
protected function doFetch(array $ids)
{
$values = [];
$now = time();
if ($this->zendDetectUnicode) {
$zmb = ini_set('zend.detect_unicode', 0);
if ($this->appendOnly) {
$now = 0;
$missingIds = [];
} else {
$now = time();
$missingIds = $ids;
$ids = [];
}
$values = [];
begin:
$getExpiry = false;
foreach ($ids as $id) {
if (null === $value = $this->values[$id] ?? null) {
$missingIds[] = $id;
} elseif ('N;' === $value) {
$values[$id] = null;
} elseif (!\is_object($value)) {
$values[$id] = $value;
} elseif (!$value instanceof LazyValue) {
$values[$id] = $value();
} elseif (false === $values[$id] = include $value->file) {
unset($values[$id], $this->values[$id]);
$missingIds[] = $id;
}
if (!$this->appendOnly) {
unset($this->values[$id]);
}
}
if (!$missingIds) {
return $values;
}
set_error_handler($this->includeHandler);
try {
foreach ($ids as $id) {
$getExpiry = true;
foreach ($missingIds as $k => $id) {
try {
$file = $this->getFile($id);
list($expiresAt, $values[$id]) = include $file;
if ($now >= $expiresAt) {
unset($values[$id]);
$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);
}
} catch (\Exception $e) {
continue;
if ($now >= $expiresAt) {
unset($this->values[$id], $missingIds[$k], self::$valuesCache[$file]);
}
} 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;
}
/**
@ -109,44 +156,94 @@ trait PhpFilesTrait
*/
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)) {
@ -155,4 +252,62 @@ trait PhpFilesTrait
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;
}
}

View file

@ -12,7 +12,7 @@
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>
@ -36,7 +36,7 @@ trait ProxyTrait
*/
public function reset()
{
if ($this->pool instanceof ResettableInterface) {
if ($this->pool instanceof ResetInterface) {
$this->pool->reset();
}
}

View file

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

View file

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

View file

@ -32,7 +32,7 @@ class RedisProxy
{
$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)

View file

@ -13,10 +13,13 @@ namespace Symfony\Component\Cache\Traits;
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>
@ -33,24 +36,40 @@ trait RedisTrait
'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)));
}
$this->redis = $redisClient;
if ($redis instanceof \Predis\ClientInterface && $redis->getOptions()->exceptions) {
$options = clone $redis->getOptions();
\Closure::bind(function () { $this->options['exceptions'] = false; }, $options, $options)();
$redis = new $redis($redis->getConnection(), $options);
}
$this->redis = $redis;
$this->marshaller = $marshaller ?? new DefaultMarshaller();
}
/**
@ -68,57 +87,117 @@ trait RedisTrait
*
* @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));
}
$params = preg_replace_callback('#^redis://(?:(?:[^:@]*+:)?([^@]*+)@)?#', function ($m) use (&$auth) {
if (isset($m[1])) {
$auth = $m[1];
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('#^'.$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));
}
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'])) {
$scheme = 'tcp';
} else {
$scheme = 'unix';
}
$params += [
'host' => isset($params['host']) ? $params['host'] : $params['path'],
'port' => isset($params['host']) ? 6379 : null,
'dbindex' => 0,
];
$query = $hosts = [];
$tls = 'rediss' === $scheme;
$tcpScheme = $tls ? 'tls' : 'tcp';
if (isset($params['query'])) {
parse_str($params['query'], $query);
$params += $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);
}
}
$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 (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 (!$hosts) {
throw new InvalidArgumentException(sprintf('Invalid Redis DSN: "%s".', $dsn));
}
$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));
}
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();
@ -130,11 +209,14 @@ trait RedisTrait
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());
}
@ -147,13 +229,92 @@ trait RedisTrait
} 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));
}
@ -172,7 +333,7 @@ trait RedisTrait
$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];
@ -190,7 +351,7 @@ trait RedisTrait
foreach ($values as $id => $v) {
if ($v) {
$result[$id] = parent::unserialize($v);
$result[$id] = $this->marshaller->unmarshall($v);
}
}
@ -210,32 +371,19 @@ trait RedisTrait
*/
protected function doClear($namespace)
{
$cleared = true;
$hosts = [$this->redis];
$evalArgs = [[$namespace], 0];
if ($this->redis instanceof \Predis\Client) {
$evalArgs = [0, $namespace];
$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]);
}
if ($this->redis instanceof \Predis\ClientInterface) {
$prefix = $this->redis->getOptions()->prefix ? $this->redis->getOptions()->prefix->getPrefix() : '';
$prefixLen = \strlen($prefix ?? '');
}
$cleared = true;
$hosts = $this->getHosts();
$host = reset($hosts);
if ($host instanceof \Predis\Client && $host->getConnection() instanceof ReplicationInterface) {
// Predis supports info command only on the master in replication environments
$hosts = [$host->getClientFor('master')];
}
foreach ($hosts as $host) {
if (!isset($namespace[0])) {
$cleared = $host->flushDb() && $cleared;
@ -243,24 +391,36 @@ trait RedisTrait
}
$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);
@ -278,7 +438,7 @@ trait RedisTrait
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];
@ -294,25 +454,14 @@ trait RedisTrait
/**
* {@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 {
@ -320,8 +469,9 @@ trait RedisTrait
}
}
});
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;
}
}
@ -329,54 +479,87 @@ trait RedisTrait
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;
}
}

View file

@ -1,7 +1,7 @@
{
"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",
@ -16,24 +16,37 @@
}
],
"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\\": "" },

View file

@ -0,0 +1,3 @@
vendor/
composer.lock
phpunit.xml

View file

@ -0,0 +1,5 @@
CHANGELOG
=========
The changelog is maintained for all Symfony contracts at the following URL:
https://github.com/symfony/contracts/blob/main/CHANGELOG.md

View file

@ -0,0 +1,19 @@
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.

View file

@ -0,0 +1,26 @@
Symfony Deprecation Contracts
=============================
A generic function and convention to trigger deprecation notices.
This package provides a single global function named `trigger_deprecation()` that triggers silenced deprecation notices.
By using a custom PHP error handler such as the one provided by the Symfony ErrorHandler component,
the triggered deprecations can be caught and logged for later discovery, both on dev and prod environments.
The function requires at least 3 arguments:
- the name of the Composer package that is triggering the deprecation
- the version of the package that introduced the deprecation
- the message of the deprecation
- more arguments can be provided: they will be inserted in the message using `printf()` formatting
Example:
```php
trigger_deprecation('symfony/blockchain', '8.9', 'Using "%s" is deprecated, use "%s" instead.', 'bitcoin', 'fabcoin');
```
This will generate the following message:
`Since symfony/blockchain 8.9: Using "bitcoin" is deprecated, use "fabcoin" instead.`
While not necessarily recommended, the deprecation notices can be completely ignored by declaring an empty
`function trigger_deprecation() {}` in your application.

View file

@ -0,0 +1,35 @@
{
"name": "symfony/deprecation-contracts",
"type": "library",
"description": "A generic function and convention to trigger deprecation notices",
"homepage": "https://symfony.com",
"license": "MIT",
"authors": [
{
"name": "Nicolas Grekas",
"email": "p@tchwork.com"
},
{
"name": "Symfony Community",
"homepage": "https://symfony.com/contributors"
}
],
"require": {
"php": ">=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"
}
}
}

View file

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

View file

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

View file

@ -0,0 +1,43 @@
<?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];
}
}

View file

@ -0,0 +1,18 @@
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).

View file

@ -0,0 +1,16 @@
<?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
{
}
}

View file

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

View file

@ -0,0 +1,33 @@
{
"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"
}
}
}

View file

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

View file

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

View file

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

View file

@ -0,0 +1,25 @@
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).

View file

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

View file

@ -0,0 +1,16 @@
<?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
{
}
}

View file

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

View file

@ -0,0 +1,16 @@
<?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
{
}
}

View file

@ -0,0 +1,16 @@
<?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
{
}
}

View file

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

View file

@ -0,0 +1,37 @@
{
"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"
}
}
}

View file

@ -0,0 +1,3 @@
vendor/
composer.lock
phpunit.xml

View file

@ -0,0 +1,25 @@
<?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
{
}

View file

@ -0,0 +1,33 @@
<?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
) {
}
}

View file

@ -0,0 +1,5 @@
CHANGELOG
=========
The changelog is maintained for all Symfony contracts at the following URL:
https://github.com/symfony/contracts/blob/main/CHANGELOG.md

View file

@ -0,0 +1,19 @@
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.

View file

@ -0,0 +1,9 @@
Symfony Service Contracts
=========================
A set of abstractions extracted out of the Symfony components.
Can be used to build on semantics that the Symfony components proved useful - and
that already have battle tested implementations.
See https://github.com/symfony/contracts/blob/main/README.md for more information.

View file

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

View file

@ -0,0 +1,128 @@
<?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 {
};
}
}

View file

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

View file

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

View file

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

View file

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

View file

@ -0,0 +1,42 @@
{
"name": "symfony/service-contracts",
"type": "library",
"description": "Generic abstractions related to writing services",
"keywords": ["abstractions", "contracts", "decoupling", "interfaces", "interoperability", "standards"],
"homepage": "https://symfony.com",
"license": "MIT",
"authors": [
{
"name": "Nicolas Grekas",
"email": "p@tchwork.com"
},
{
"name": "Symfony Community",
"homepage": "https://symfony.com/contributors"
}
],
"require": {
"php": ">=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"
}
}
}

View file

@ -0,0 +1,12 @@
CHANGELOG
=========
5.1.0
-----
* added argument `array &$foundClasses` to `VarExporter::export()` to ease with preloading exported values
4.2.0
-----
* added the component

View file

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

View file

@ -0,0 +1,16 @@
<?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
{
}

View file

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

View file

@ -0,0 +1,92 @@
<?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];
}
}

View file

@ -0,0 +1,417 @@
<?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];
}
}

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -0,0 +1,38 @@
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)

View file

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

View file

@ -0,0 +1,32 @@
{
"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"
}

View file

@ -1,24 +1,24 @@
{
"name": "friendica-addons/blockbot",
"description": "Blocking bots based on detecting bots/crawlers/spiders via the user agent and http_from header.",
"type": "friendica-addon",
"authors": [
{
"name": "Philipp Holzer",
"email": "admin@philipp.info",
"homepage": "https://friendica.philipp.info/profile/nupplaphil",
"role": "Developer"
}
],
"require": {
"php": ">=5.6.0",
"jaybizzle/crawler-detect": "1.*"
},
"license": "3-clause BSD license",
"minimum-stability": "stable",
"config": {
"optimize-autoloader": true,
"autoloader-suffix": "BlockBotAddon",
"preferred-install": "dist"
}
"name": "friendica-addons/blockbot",
"description": "Blocking bots based on detecting bots/crawlers/spiders via the user agent and http_from header.",
"type": "friendica-addon",
"authors": [
{
"name": "Philipp Holzer",
"email": "admin@philipp.info",
"homepage": "https://friendica.philipp.info/profile/nupplaphil",
"role": "Developer"
}
],
"require": {
"php": ">=5.6.0",
"jaybizzle/crawler-detect": "1.*"
},
"license": "3-clause BSD license",
"minimum-stability": "stable",
"config": {
"optimize-autoloader": true,
"autoloader-suffix": "BlockBotAddon",
"preferred-install": "dist"
}
}

16
blockbot/composer.lock generated
View file

@ -8,24 +8,23 @@
"packages": [
{
"name": "jaybizzle/crawler-detect",
"version": "v1.2.80",
"version": "v1.2.116",
"source": {
"type": "git",
"url": "https://github.com/JayBizzle/Crawler-Detect.git",
"reference": "af6a36e6d69670df3f0a3ed8e21d4b8cc67a7847"
"reference": "97e9fe30219e60092e107651abb379a38b342921"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/JayBizzle/Crawler-Detect/zipball/af6a36e6d69670df3f0a3ed8e21d4b8cc67a7847",
"reference": "af6a36e6d69670df3f0a3ed8e21d4b8cc67a7847",
"url": "https://api.github.com/repos/JayBizzle/Crawler-Detect/zipball/97e9fe30219e60092e107651abb379a38b342921",
"reference": "97e9fe30219e60092e107651abb379a38b342921",
"shasum": ""
},
"require": {
"php": ">=5.3.0"
},
"require-dev": {
"phpunit/phpunit": "^4.8|^5.5|^6.5",
"satooshi/php-coveralls": "1.*"
"phpunit/phpunit": "^4.8|^5.5|^6.5|^9.4"
},
"type": "library",
"autoload": {
@ -53,7 +52,7 @@
"crawlerdetect",
"php crawler detect"
],
"time": "2019-04-05T19:52:02+00:00"
"time": "2023-07-21T15:49:49+00:00"
}
],
"packages-dev": [],
@ -65,5 +64,6 @@
"platform": {
"php": ">=5.6.0"
},
"platform-dev": []
"platform-dev": [],
"plugin-api-version": "1.1.0"
}

View file

@ -60,7 +60,7 @@ class ClassLoader
public function getPrefixes()
{
if (!empty($this->prefixesPsr0)) {
return call_user_func_array('array_merge', $this->prefixesPsr0);
return call_user_func_array('array_merge', array_values($this->prefixesPsr0));
}
return array();
@ -279,7 +279,7 @@ class ClassLoader
*/
public function setApcuPrefix($apcuPrefix)
{
$this->apcuPrefix = function_exists('apcu_fetch') && ini_get('apc.enabled') ? $apcuPrefix : null;
$this->apcuPrefix = function_exists('apcu_fetch') && filter_var(ini_get('apc.enabled'), FILTER_VALIDATE_BOOLEAN) ? $apcuPrefix : null;
}
/**

View file

@ -13,6 +13,9 @@ class ComposerAutoloaderInitBlockBotAddon
}
}
/**
* @return \Composer\Autoload\ClassLoader
*/
public static function getLoader()
{
if (null !== self::$loader) {

View file

@ -1,27 +1,26 @@
[
{
"name": "jaybizzle/crawler-detect",
"version": "v1.2.80",
"version_normalized": "1.2.80.0",
"version": "v1.2.116",
"version_normalized": "1.2.116.0",
"source": {
"type": "git",
"url": "https://github.com/JayBizzle/Crawler-Detect.git",
"reference": "af6a36e6d69670df3f0a3ed8e21d4b8cc67a7847"
"reference": "97e9fe30219e60092e107651abb379a38b342921"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/JayBizzle/Crawler-Detect/zipball/af6a36e6d69670df3f0a3ed8e21d4b8cc67a7847",
"reference": "af6a36e6d69670df3f0a3ed8e21d4b8cc67a7847",
"url": "https://api.github.com/repos/JayBizzle/Crawler-Detect/zipball/97e9fe30219e60092e107651abb379a38b342921",
"reference": "97e9fe30219e60092e107651abb379a38b342921",
"shasum": ""
},
"require": {
"php": ">=5.3.0"
},
"require-dev": {
"phpunit/phpunit": "^4.8|^5.5|^6.5",
"satooshi/php-coveralls": "1.*"
"phpunit/phpunit": "^4.8|^5.5|^6.5|^9.4"
},
"time": "2019-04-05T19:52:02+00:00",
"time": "2023-07-21T15:49:49+00:00",
"type": "library",
"installation-source": "dist",
"autoload": {

View file

@ -0,0 +1,23 @@
name: Check & fix styling
on: [ push ]
jobs:
php-cs-fixer:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v2
with:
ref: ${{ github.head_ref }}
- name: Run PHP CS Fixer
uses: docker://oskarstark/php-cs-fixer-ga:2.18.6
with:
args: --config=.php_cs.dist --allow-risky=yes
- name: Commit changes
uses: stefanzweifel/git-auto-commit-action@v4
with:
commit_message: Fix styling

View file

@ -0,0 +1,56 @@
name: Test
on:
push:
branches:
- "master"
pull_request:
jobs:
build:
runs-on: ubuntu-latest
strategy:
fail-fast: true
matrix:
php: [5.3, 5.4, 5.5, 5.6, 7.0, 7.1, 7.2, 7.3, 7.4, 8.0, 8.1, 8.2]
name: PHP:${{ matrix.php }}
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Setup PHP, with composer
uses: shivammathur/setup-php@v2
with:
php-version: ${{ matrix.php }}
tools: composer:v2
coverage: xdebug
- name: Get composer cache directory
id: composer-cache
run: |
echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT
shell: bash
- name: Cache composer dependencies
uses: actions/cache@v3
with:
path: ${{ steps.composer-cache.outputs.dir }}
key: dependencies-php-${{ matrix.php }}-composer-${{ hashFiles('composer.json') }}
restore-keys: dependencies-php-${{ matrix.php }}-composer-
- name: Install Composer dependencies
run: |
composer install --prefer-dist --no-interaction --no-suggest
- name: Run Unit tests
run: |
vendor/bin/phpunit --coverage-clover=tests/logs/clover.xml
- name: Upload coverage results to Coveralls
env:
COVERALLS_REPO_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
composer global require php-coveralls/php-coveralls "^1.0"
coveralls --coverage_clover=tests/logs/clover.xml -v

View file

@ -0,0 +1,33 @@
<?php
$finder = Symfony\Component\Finder\Finder::create()
->in([
__DIR__.'/src',
__DIR__.'/tests',
])
->name('*.php')
->ignoreDotFiles(true)
->ignoreVCS(true);
return PhpCsFixer\Config::create()
->setRules([
'@PSR2' => true,
'array_syntax' => ['syntax' => 'long'],
'ordered_imports' => ['sortAlgorithm' => 'alpha'],
'no_unused_imports' => true,
'not_operator_with_successor_space' => true,
'trailing_comma_in_multiline_array' => true,
'phpdoc_scalar' => true,
'unary_operator_spaces' => true,
'binary_operator_spaces' => true,
'blank_line_before_statement' => [
'statements' => ['break', 'continue', 'declare', 'return', 'throw', 'try'],
],
'phpdoc_single_line_var_spacing' => true,
'phpdoc_var_without_name' => true,
'method_argument_space' => [
'on_multiline' => 'ensure_fully_multiline',
'keep_multiple_spaces_after_comma' => true,
],
])
->setFinder($finder);

View file

@ -1,6 +1,6 @@
The MIT License (MIT)
Copyright (c) 2015-2018 Mark Beech
Copyright (c) 2015-2020 Mark Beech
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal

View file

@ -1,24 +1,23 @@
<p align="center"><a href="http://crawlerdetect.io/" target="_blank"><img src="https://cloud.githubusercontent.com/assets/340752/23082173/1bd1a396-f550-11e6-8aba-4d3c75edea2f.png" width="321" height="219" /></a><br><br>
<a href="http://crawlerdetect.io/" target="_blank">crawlerdetect.io</a>
<p align="center"><a href="https://crawlerdetect.io/" target="_blank"><img src="https://cloud.githubusercontent.com/assets/340752/23082173/1bd1a396-f550-11e6-8aba-4d3c75edea2f.png" width="321" height="219" /></a><br><br>
<a href="https://crawlerdetect.io/" target="_blank">crawlerdetect.io</a>
<br><br>
</p>
<p align="center">
<a href="https://travis-ci.org/JayBizzle/Crawler-Detect"><img src="https://img.shields.io/travis/JayBizzle/Crawler-Detect/master.svg?style=flat-square" /></a>
<a href="https://github.com/JayBizzle/Crawler-Detect/actions"><img alt="GitHub Workflow Status" src="https://img.shields.io/github/actions/workflow/status/JayBizzle/Crawler-Detect/test.yml?branch=master&style=flat-square"></a>
<a href="https://packagist.org/packages/jaybizzle/crawler-detect"><img src="https://img.shields.io/packagist/dm/JayBizzle/Crawler-Detect.svg?style=flat-square" /></a>
<a href="https://scrutinizer-ci.com/g/JayBizzle/Crawler-Detect/?branch=master"><img src="https://img.shields.io/scrutinizer/g/JayBizzle/Crawler-Detect.svg?style=flat-square" /></a>
<a href="https://github.com/JayBizzle/Crawler-Detect"><img src="https://img.shields.io/badge/license-MIT-ff69b4.svg?style=flat-square" /></a>
<a href="https://packagist.org/packages/jaybizzle/crawler-detect"><img src="https://img.shields.io/packagist/v/jaybizzle/Crawler-Detect.svg?style=flat-square" /></a>
<a href="https://styleci.io/repos/32755917"><img src="https://styleci.io/repos/32755917/shield" /></a>
<a href="https://coveralls.io/github/JayBizzle/Crawler-Detect"><img src="https://img.shields.io/coveralls/JayBizzle/Crawler-Detect/master.svg?style=flat-square" /></a>
</p>
## About CrawlerDetect
CrawlerDetect is a PHP class for detecting bots/crawlers/spiders via the user agent and http_from header. Currently able to detect 1,000's of bots/spiders/crawlers.
CrawlerDetect is a PHP class for detecting bots/crawlers/spiders via the `user agent` and `http_from` header. Currently able to detect 1,000's of bots/spiders/crawlers.
### Installation
Run `composer require jaybizzle/crawler-detect 1.*` or add `"jaybizzle/crawler-detect" :"1.*"` to your `composer.json`.
```
composer require jaybizzle/crawler-detect
```
### Usage
```PHP
@ -46,7 +45,7 @@ If you find a bot/spider/crawler user agent that CrawlerDetect fails to detect,
Failing that, just create an issue with the user agent you have found, and we'll take it from there :)
### Laravel Package
If you would like to use this with Laravel 4/5, please see [Laravel-Crawler-Detect](https://github.com/JayBizzle/Laravel-Crawler-Detect)
If you would like to use this with Laravel, please see [Laravel-Crawler-Detect](https://github.com/JayBizzle/Laravel-Crawler-Detect)
### Symfony Bundle
To use this library with Symfony 2/3/4, check out the [CrawlerDetectBundle](https://github.com/nicolasmure/CrawlerDetectBundle).
@ -57,16 +56,21 @@ To use this library with the YII2 framework, check out [yii2-crawler-detect](htt
### ES6 Library
To use this library with NodeJS or any ES6 application based, check out [es6-crawler-detect](https://github.com/JefferyHus/es6-crawler-detect).
### Python Library
To use this library in a Python project, check out [crawlerdetect](https://github.com/moskrc/CrawlerDetect).
### JVM Library (written in Java)
To use this library in a JVM project (including Java, Scala, Kotlin, etc.), check out [CrawlerDetect](https://github.com/nekosoftllc/crawler-detect).
### .NET Library
To use this library in a .net standard (including .net core) based project, check out [NetCrawlerDetect](https://github.com/gplumb/NetCrawlerDetect).
### Nette Extension
To use this library with the Nette framework, checkout [NetteCrawlerDetect](https://github.com/JanGalek/Crawler-Detect).
### Ruby Gem
To use this library with Ruby on Rails or any Ruby-based application, check out [crawler_detect](https://github.com/loadkpi/crawler_detect) gem.
### Go Module
To use this library with Go, check out the [crawlerdetect](https://github.com/x-way/crawlerdetect) module.
_Parts of this class are based on the brilliant [MobileDetect](https://github.com/serbanghita/Mobile-Detect)_
[![Analytics](https://ga-beacon.appspot.com/UA-72430465-1/Crawler-Detect/readme?pixel)](https://github.com/JayBizzle/Crawler-Detect)

View file

@ -16,8 +16,7 @@
"php": ">=5.3.0"
},
"require-dev": {
"phpunit/phpunit": "^4.8|^5.5|^6.5",
"satooshi/php-coveralls": "1.*"
"phpunit/phpunit": "^4.8|^5.5|^6.5|^9.4"
},
"autoload": {
"psr-4": {

View file

@ -37,5 +37,5 @@ function outputJson($object)
function outputTxt($object)
{
$className = (new ReflectionClass($object))->getShortName();
file_put_contents("raw/$className.txt", implode($object->getAll(), PHP_EOL));
file_put_contents("raw/$className.txt", implode(PHP_EOL, $object->getAll()));
}

File diff suppressed because one or more lines are too long

View file

@ -1,27 +1,46 @@
.*Java.*outbrain
YLT
^Aether
^Amazon Simple Notification Service Agent$
^Amazon-Route53-Health-Check-Service
^b0t$
^bluefish
^Calypso v\/
^COMODO DCV
^Corax
^DangDang
^DavClnt
^DHSH
^docker\/[0-9]
^Expanse
^FDM
^git\/
^Goose\/
^Grabber
^Gradle\/
^HTTPClient\/
^HTTPing
^Java\/
^Jeode\/
^Jetty\/
^Mail\/
^Mget
^Microsoft URL Control
^Mikrotik\/
^Netlab360
^NG\/[0-9\.]
^NING\/
^PHP\/[0-9]
^npm\/
^Nuclei
^PHP-AYMAPI\/
^PHP\/
^pip\/
^pnpm\/
^RMA\/
^Ruby|Ruby\/[0-9]
^Swurl
^TLS tester
^twine\/
^ureq
^VSE\/[0-9]
^WordPress\.com
^XRL\/[0-9]
@ -29,17 +48,18 @@
008\/
13TABS
192\.comAgent
2GDPR\/
2ip\.ru
404enemy
7Siters
80legs
a\.pr-cy\.ru
a3logics\.in
A6-Indexer
Abonti
Aboundex
aboutthedomain
Accoona-AI-Agent
acebookexternalhit\/
acoon
acrylicapps\.com\/pulp
Acunetix
@ -49,8 +69,14 @@ AddThis
ADmantX
AdminLabs
adressendeutschland
adreview\/
adscanner
adstxt-worker
Adstxtaggregator
adstxt\.com
Adyen HttpClient
AffiliateLabz\/
affilimate-puppeteer
agentslug
AHC
aihit
@ -71,20 +97,23 @@ AndroidDownloadManager
Anemone
AngleSharp
annotate_google
Ant\.com
Anthill
Anturis Agent
Ant\.com
AnyEvent-HTTP\/
Apache Ant\/
Apache Droid
Apache OpenOffice
Apache-HttpAsyncClient
Apache-HttpClient
ApacheBench
Apexoo
apimon\.de
APIs-Google
AportWorm\/
AppBeat\/
AppEngine-Google
AppStoreScraperZ
AppleSyndication
Aprc\/[0-9]
Arachmo
arachnode
@ -92,21 +121,28 @@ Arachnophilia
aria2
Arukereso
asafaweb
AskQuickly
Asana\/
Ask Jeeves
AskQuickly
ASPSeek
Asterias
Astute
asynchttp
Attach
attohttpc
autocite
AutomaticWPTester
Autonomy
awin\.com
AWS Security Scanner
axios\/
a\.pr-cy\.ru
B-l-i-t-z-B-O-T
Backlink-Ceck
backlink-check
BacklinkHttpStatus
BackStreet
BackupLand
BackWeb
Bad-Neighborhood
Badass
@ -121,6 +157,7 @@ BBBike
BCKLINKS
BDFetch
BegunAdvertising
Bewica-security-scan
Bidtellect
BigBozz
Bigfoot
@ -130,11 +167,14 @@ BingPreview
binlar
biNu image cacher
Bitacle
Bitrix link preview
biz_Directory
BKCTwitterUnshortener\/
Black Hole
Blackboard Safeassign
BlackWidow
BlockNote\.Net
BlogBridge
Bloglines
Bloglovin
BlogPulseLive
@ -142,6 +182,7 @@ BlogSearch
Blogtrottr
BlowFish
boitho\.com-dc
Boost\.Beast
BPImageWalker
Braintree-Webhooks
Branch Metrics API
@ -169,12 +210,15 @@ catexplorador
CC Metadata Scaper
Cegbfeieh
censys
centuryb.o.t9[at]gmail.com
Cerberian Drtrs
CERT\.at-Statistics-Survey
cf-facebook
cg-eye
changedetection
ChangesMeter
Charlotte
chatterino-api-cache
CheckHost
checkprivacy
CherryPicker
@ -185,6 +229,7 @@ Chlooe
Chromaxa
CirrusExplorer
CISPA Vulnerability Notification
CISPA Web Analyser
Citoid
CJNetworkQuality
Clarsentia
@ -192,27 +237,34 @@ clips\.ua\.ac\.be
Cloud mapping
CloudEndure
CloudFlare-AlwaysOnline
Cloudflare-Healthchecks
Cloudinary
cmcm\.com
coccoc
cognitiveseo
ColdFusion
colly -
CommaFeed
Commons-HttpClient
commonscan
contactbigdatafr
contentkingapp
Contextual Code Sites Explorer
convera
CookieReports
copyright sheriff
CopyRightCheck
Copyscape
cortex\/
Cosmos4j\.feedback
Covario-IDS
Craw\/
Crescent
Crowsnest
Criteo
Crowsnest
CSHttp
CSSCheck
Cula\/
curb
Curious George
curl
@ -222,12 +274,17 @@ DAP\/NetHTTP
DareBoost
DatabaseDriverMysqli
DataCha0s
DatadogSynthetics
Datafeedwatch
Datanyze
DataparkSearch
dataprovider
DataXu
Daum(oa)?[ \/][0-9]
dBpoweramp
ddline
deeris
delve\.ai
Demon
DeuSu
developers\.google\.com\/\+\/web\/snippet\/
@ -237,8 +294,8 @@ Digincore
DigitalPebble
Dirbuster
Discourse Forum Onebox
Disqus\/
Dispatch\/
Disqus\/
DittoSpyder
dlvr
DMBrowser
@ -246,6 +303,8 @@ DNSPod-reporting
docoloc
Dolphin http client
DomainAppender
DomainLabz
Domains Project\/
Donuts Content Explorer
dotMailer content retrieval
dotSemantic
@ -258,6 +317,8 @@ drupact
Drupal \(\+http:\/\/drupal\.org\/\)
DTS Agent
dubaiindex
DuplexWeb-Google
DynatraceSynthetic
EARTHCOM
Easy-Thumb
EasyDL
@ -288,20 +349,22 @@ ExactSearch
ExaleadCloudview
Excel\/
exif
ExoRank
Exploratodo
Express WebPictures
Extreme Picture Finder
EyeNetIE
ezooms
facebookexternalhit
facebookexternalua
facebookplatform
fairshare
Faraday v
fasthttp
Faveeo
Favicon downloader
faviconkit
faviconarchive
faviconkit
FavOrg
Feed Wrangler
Feedable\/
@ -313,7 +376,9 @@ FeedBurner
feeder
Feedly
FeedshowOnline
Feedshow\/
Feedspot
FeedViewer\/
Feedwind\/
FeedZcollector
feeltiptop
@ -321,6 +386,8 @@ Fetch API
Fetch\/[0-9]
Fever\/[0-9]
FHscan
Fiery%20Feeds
Filestack
Fimap
findlink
findthatfile
@ -329,18 +396,24 @@ FlipboardBrowserProxy
FlipboardProxy
FlipboardRSS
Flock\/
Florienzh\/
fluffy
Flunky
flynxapp
forensiq
ForusP
FoundSeoTool
http:\/\/www.neomo.de\/
fragFINN\.de
free thumbnails
Freeuploader
FreshRSS
frontman
Funnelback
Fuzz Faster U Fool
G-i-g-a-b-o-t
g00g1e\.net
ganarvisitas
gdnplus\.com
geek-tools
Genieo
GentleSource
@ -352,18 +425,24 @@ GetRight
getroot
GetURLInfo\/
GetWeb
Geziyor
Ghost Inspector
GigablastOpenSource
GIS-LABS
github-camo
GitHub-Hookshot
github\.com
Go [\d\.]* package http
Go http package
Go [\d\.]* package http
Go!Zilla
Go-Ahead-Got-It
Go-http-client
Go!Zilla
go-mtasts\/
gobuster
gobyus
Gofeed
gofetch
Goldfire Server
GomezAgent
gooblog
Goodzer\/
@ -376,24 +455,31 @@ Google Page Speed Insights
Google PP Default
Google Search Console
Google Web Preview
Google-Ads-Creatives-Assistant
Google-Ads-Overview
Google-Adwords
Google-Apps-Script
Google-Calendar-Importer
Google-HotelAdsVerifier
Google-HTTP-Java-Client
Google-InspectionTool
Google-Podcast
Google-Publisher-Plugin
Google-Read-Aloud
Google-SearchByImage
Google-Site-Verification
Google-SMTP-STS
Google-speakr
Google-Structured-Data-Testing-Tool
Google-Youtube-Links
Google-Transparency-Report
google-xrawler
Google-Youtube-Links
GoogleDocs
GoogleHC\/
GoogleProber
GoogleProducer
GoogleSites
Google-Transparency-Report
Gookey
GoScraper
GoSpotCheck
gosquared-thumbnailer
Gotit
@ -419,13 +505,16 @@ Haansoft
hackney\/
Hadi Agent
HappyApps-WebCheck
Hardenize
Hatena
Havij
HaxerMen
HeadlessChrome
HEADMasterSEO
HeartRails_Capture
help@dataminr\.com
heritrix
Hexometer
historious
hkedcity
hledejLevne\.cz
@ -442,28 +531,31 @@ htdig
HTMLparser
htmlyse
HTTP Banner Detection
HTTP_Compression_Test
http_request2
http_requester
http-get
HTTP-Header-Abfrage
http-kit
http-request\/
HTTP-Tiny
HTTP::Lite
http\.rb\/
http_get
http:\/\/www.neomo.de\/
HttpComponents
httphr
HTTPie
HTTPMon
httpRequest
httpscheck
httpssites_power
httpunit
HttpUrlConnection
http\.rb\/
HTTP_Compression_Test
http_get
http_request2
http_requester
httrack
huaweisymantec
HubSpot
HubSpot-Link-Resolver
Humanlinks
i2kconnect\/
Iblog
@ -474,6 +566,7 @@ IDG Twitter Links Resolver
IDwhois\/
Iframely
igdeSpyder
iGooglePortal
IlTrovatore
Image Fetch
Image Sucker
@ -498,13 +591,15 @@ Integrity
integromedb
Intelliseek
InterGET
internet_archive
Internet Ninja
InternetSeer
internetVista monitor
internetwache
internet_archive
intraVnews
IODC
IOI
Inboxb0t
iplabel
ips-agent
IPS\/[0-9]
@ -516,10 +611,13 @@ isitup\.org
iskanie
isUp\.li
iThemes Sync\/
IZaBEE
iZSearch
JAHHO
janforman
Jaunt\/
Java.*outbrain
javelin\.io
Jbrofuzz
Jersey\/
JetCar
@ -535,6 +633,7 @@ JolokiaPwn
Joomla
Jorgee
JS-Kit
JungleKeyThumbnail
JustView
Kaspersky Lab CFR link resolver
Kelny\/
@ -542,6 +641,7 @@ Kerrigan\/
KeyCDN
Keyword Density
Keywords Research
khttp\/
KickFire
KimonoLabs\/
Kml-Google
@ -549,11 +649,12 @@ knows\.is
KOCMOHABT
kouio
kube-probe
kubectl
kulturarw3
KumKie
L\.webis
Larbin
Lavf\/
leakix\.net
LeechFTP
LeechGet
letsencrypt
@ -564,41 +665,50 @@ Libwhisker
libwww
Licorne
Liferea\/
Lightspeedsystems
Lighthouse
Lightspeedsystems
Likse
limber\.io
Link Valet
link_thumbnailer
LinkAlarm\/
LinkAnalyser
linkCheck
linkdex
LinkExaminer
linkfluence
linkpeek
LinkPreviewGenerator
LinkPreview
LinkScan
LinksManager
LinkTiger
LinkWalker
link_thumbnailer
Lipperhey
Litemage_walker
livedoor ScreenShot
LoadImpactRload
localsearch-web
LongURL API
longurl-r-package
looid\.com
looksystems\.net
ltx71
lua-resty-http
Lucee \(CFML Engine\)
Lush Http Client
lwp-request
lwp-trivial
LWP::Simple
lycos
LYT\.SR
L\.webis
mabontland
MacOutlook\/
Mag-Net
MagpieRSS
Mail\.Ru
Mail::STS
MailChimp
Mail\.Ru
Majestic12
makecontact\/
Mandrill
@ -609,6 +719,8 @@ MarkWatch
Mass Downloader
masscan\/
Mata Hari
mattermost
Mediametric
Mediapartners-Google
mediawords
MegaIndex\.ru
@ -619,28 +731,31 @@ Metaspinner
MetaURI
MFC_Tear_Sample
Microsearch
Microsoft Office
Microsoft Data Access
Microsoft Office
Microsoft Outlook
Microsoft Windows Network Diagnostics
Microsoft-WebDAV-MiniRedir
Microsoft Data Access
Microsoft\.Data\.Mashup
MIDown tool
MIIxpc
Mindjet
Miniature\.io
Miniflux
mio_httpc
Miro-HttpClient
Mister PiX
mixdata dot com
mixed-content-scan
Mixmax-LinkPreview
mixnode
Mnogosearch
mogimogi
Mojeek
Mojolicious \(Perl\)
Monit\/
Mollie
monitis
Monitority\/
Monit\/
montastic
MonTools
Moreover
@ -649,6 +764,7 @@ Morning Paper
MovableType
mowser
Mrcgiguy
Mr\.4x3 Powered
MS Web Services Client Protocol
MSFrontPage
mShots
@ -656,10 +772,13 @@ MuckRack\/
muhstik-scan
MVAClient
MxToolbox\/
myseosnapshot
nagios
Najdi\.si
Name Intelligence
NameFo\.com
Nameprotect
nationalarchives
Navroad
NearSite
Needle
@ -674,6 +793,8 @@ Netpursual
netresearch
NetShelter ContentScan
Netsparker
NetSystemsResearch
nettle
NetTrack
Netvibes
NetZIP
@ -686,22 +807,26 @@ newsme
newspaper\/
Nexgate Ruby Client
NG-Search
nghttp2
Nibbler
NICErsPRO
NihilScio
Nikto
nineconnections
NLNZ_IAHarvester
Nmap Scripting Engine
node-fetch
node-superagent
node-urllib
node\.io
Nodemeter
NodePing
node\.io
nominet\.org\.uk
nominet\.uk
Norton-Safeweb
Notifixious
notifyninja
NotionEmbedder
nuhk
nutch
Nuzzel
@ -709,39 +834,45 @@ nWormFeedFinder
nyawc\/
Nymesis
NYU
Observatory\/
Ocelli\/
Octopus
oegp
Offline Explorer
Offline Navigator
og-scraper
OgScrper
okhttp
omgili
OMSC
Online Domain Tools
Open Source RSS
OpenCalaisSemanticProxy
Openfind
OpenLinkProfiler
Openstat\/
OpenVAS
OPPO A33
Optimizer
Orbiter
OrgProbe\/
orion-semantics
Outlook-Express
Outlook-iOS
ow\.ly
Owler
Owlin
ownCloud News
ow\.ly
OxfordCloudService
Page Valet
page_verifier
page scorer
Page Valet
page2rss
PageFreezer
PageGrabber
PagePeeker
PageScorer
Pagespeed\/
PageThing
page_verifier
Panopta
panscient
Papa Foto
@ -750,6 +881,7 @@ Pavuk
PayPal IPN
pcBrowser
Pcore-HTTP
PDF24 URL To PDF
Pearltrees
PECL::HTTP
peerindex
@ -759,44 +891,52 @@ Perlu -
PhantomJS Screenshoter
PhantomJS\/
Photon\/
php-requests
phpservermon
Pi-Monster
Picscout
Picsearch
PictureFinder
Pimonster
ping\.blo\.gs
Pingability
PingAdmin\.Ru
Pingdom
Pingoscope
PingSpot
ping\.blo\.gs
pinterest\.com
Pixray
Pizilla
Plagger\/
Pleroma
Ploetz \+ Zeller
Plukkie
plumanalytics
PocketImageCache
PocketParser
Pockey
PodcastAddict\/
POE-Component-Client-HTTP
Polymail\/
Pompos
Porkbun
Port Monitor
postano
postfix-mta-sts-resolver
PostmanRuntime
postplanner\.com
PostPost
postrank
PowerPoint\/
Prebid
Prerender
Priceonomics Analysis Engine
PrintFriendly
PritTorrent
Prlog
probethenet
Project 25499
Project ?25499
Project-Resonance
prospectb2b
Protopage
ProWebWalker
@ -805,9 +945,9 @@ PRTG Network Monitor
pshtt, https scanning
PTST
PTST\/[0-9]+
Pulsepoint XT3 web scraper
Pump
Python-httplib2
python-httpx
python-requests
Python-urllib
Qirina Hurdler
@ -817,12 +957,17 @@ Qseero
Qualidator
QueryN Metasearch
queuedriver
quic-go-HTTP\/
QuiteRSS
Quora Link Preview
Qwantify
Radian6
RadioPublicImageResizer
Railgun\/
RankActive
RankFlex
RankSonicSiteAuditor
RapidLoad\/
Re-re Studio
ReactorNetty
Readability
@ -833,6 +978,7 @@ Recorder
RecurPost\/
redback\/
ReederForMac
Reeder\/
ReGet
RepoMonkey
request\.js
@ -845,38 +991,44 @@ Robosourcer
Robozilla
ROI Hunter
RPT-HTTPClient
RSSMix\/
RSSOwl
RyowlEngine
safe-agent-scanner
SalesIntelligent
Saleslift
Sendsay\.Ru
SAP NetWeaver Application Server
SauceNAO
SBIder
sc-downloader
scalaj-http
scan\.lol
Scamadviser-Frontend
ScanAlert
scan\.lol
Scoop
scooter
ScopeContentAG-HTTP-Client
ScoutJet
ScoutURLMonitor
ScrapeBox Page Scanner
SimpleScraper
Scrapy
Screaming
ScreenShotService
Scrubby
Scrutiny\/
search\.thunderstone
Search37
searchenginepromotionhelp
Searchestate
SearchExpress
SearchSight
SearchWP
search\.thunderstone
Seeker
semanticdiscovery
semanticjuice
Semiocast HTTP client
Semrush
Sendsay\.Ru
sentry\/
SEO Browser
Seo Servis
@ -885,11 +1037,13 @@ seo4ajax
Seobility
SEOCentro
SeoCheck
seocompany
SEOkicks
SEOlizer
Seomoz
SEOprofiler
SEOsearch
seoscanners
SEOsearch
seositecheckup
SEOstats
servernfo
@ -900,14 +1054,16 @@ Shodan
Shoppimon
ShopWiki
ShortLinkTranslate
shortURL lengthener
shrinktheweb
Sideqik
Siege
SimplePie
SimplyFast
Siphon
SISTRIX
Site-Shot\/
Site Sucker
Site-Shot\/
Site24x7
SiteBar
Sitebeam
@ -928,6 +1084,7 @@ Sitevigil
sitexy\.com
SkypeUriPreview
Slack\/
sli-systems\.com
slider\.com
slurp
SlySearch
@ -950,9 +1107,11 @@ SpaceBison
SpamExperts
Spammen
Spanner
Spawning-AI
spaziodati
SPDYCheck
Specificfeeds
SpeedKit
speedy
SPEng
Spinn3r
@ -966,9 +1125,11 @@ SSL Labs
ssl-tools
StackRambler
Statastico\/
Statically-
StatusCake
Steeler
Stratagems Kumo
Stripe\/
Stroke\.cz
StudioFACA
StumbleUpon
@ -978,9 +1139,11 @@ summify
SuperHTTP
Surphace Scout
Suzuran
SwiteScraper
swcd
Symfony BrowserKit
Symfony2 BrowserKit
Synapse\/
Syndirella\/
SynHttpClient-Built
Sysomos
sysscan
@ -990,6 +1153,8 @@ tAkeOut
Tarantula\/
Taringa UGC
TarmotGezgin
tchelebi\.io
techiaith\.cymru
Teleport
Telesoft
Telesphoreo
@ -1000,13 +1165,15 @@ terrainformatica
Test Certificate Info
testuri
Tetrahedron
TextRazor Downloader
The Drop Reaper
The Expert HTML Source Viewer
The Knowledge AI
The Intraformant
The Knowledge AI
theinternetrules
TheNomad
Thinklab
Thumbor
Thumbshots
ThumbSniper
timewe\.net
@ -1040,9 +1207,11 @@ Typhoeus
ubermetrics-technologies
uclassify
UdmSearch
ultimate_sitemap_parser
unchaos
unirest-java
UniversalFeedParser
unshortenit
Unshorten\.It
Untiny
UnwindFetchor
@ -1050,8 +1219,8 @@ updated
updown\.io daemon
Upflow
Uptimia
Urlcheckr
URL Verifier
Urlcheckr
URLitor
urlresolver
Urlstat
@ -1059,15 +1228,16 @@ URLTester
UrlTrends Ranking Updater
URLy Warning
URLy\.Warning
URL\/Emacs
Vacuum
Vagabondo
VB Project
vBSEO
VCI
via ggpht\.com GoogleImageProxy
VidibleScraper
Virusdie
visionutils
Visual Rights Group
vkShare
VoidEYE
Voil
@ -1078,19 +1248,18 @@ VSB-TUO\/
Vulnbusters Meter
VYU2
w3af\.org
W3C_Unicorn
W3C-checklink
W3C-mobileOK
W3C_Unicorn
WAC-OFU
Wallpapers\/[0-9]+
WakeletLinkExpander
WallpapersHD
Wallpapers\/[0-9]+
wangling
Wappalyzer
WatchMouse
WbSrch\/
WDT\.io
web-capture\.net
Web-sniffer
Web Auto
Web Collage
Web Enhancer
@ -1098,7 +1267,10 @@ Web Fetch
Web Fuck
Web Pix
Web Sauger
Web spyder
Web Sucker
web-capture\.net
Web-sniffer
Webalta
Webauskunft
WebAuto
@ -1136,8 +1308,9 @@ WebSniffer
Webster
WebStripper
WebSucker
Webthumb\/
webtech\/
WebThumbnail
Webthumb\/
WebWhacker
WebZIP
WeLikeLinks
@ -1146,24 +1319,31 @@ WeSEE
wf84
Wfuzz\/
wget
WhatCMS
WhatsApp
WhatsMyIP
WhatWeb
WhereGoes\?
Whibse
WhoAPI\/
WhoRunsCoinHive
Whynder Magnet
Windows-RSS-Platform
WinHttp-Autoproxy-Service
WinHTTP\/
WinPodder
wkhtmlto
wmtips
Woko
Wolfram HTTPClient
woorankreview
Word\/
WordPress\/
WordupinfoSearch
Word\/
worldping-api
wotbox
WP Engine Install Performance API
WP Rocket
wpif
wprecon\.com survey
WPScan
@ -1173,20 +1353,21 @@ WWW-Collector-E
WWW-Mechanize
WWW::Document
WWW::Mechanize
www\.monitor\.us
WWWOFFLE
www\.monitor\.us
x09Mozilla
x22Mozilla
XaxisSemanticsClassifier
XenForo\/
Xenu Link Sleuth
XING-contenttabreceiver
xpymep([0-9]?)\.exe
Y!J-(ASR|BSC)
Y\!J-BRW
Y!J-[A-Z][A-Z][A-Z]
Yaanb
yacy
Yahoo Link Preview
YahooCacheSystem
YahooMailProxy
YahooYSMcm
YandeG
Yandex(?!Search)
@ -1194,6 +1375,7 @@ yanga
yeti
Yo-yo
Yoleo Consumer
yomins\.com
yoogliFetchAgent
YottaaMonitor
Your-Website-Sucks
@ -1205,13 +1387,15 @@ Zade
Zao
Zauba
Zemanta Aggregator
Zend_Http_Client
Zend\\Http\\Client
Zend_Http_Client
Zermelo
Zeus
zgrab
ZnajdzFoto
ZnHTTP
Zombie\.js
Zoom\.Mac
ZoteroTranslationServer
ZyBorg
[a-z0-9\-_]*(bot|crawl|archiver|transcoder|spider|uptime|validator|fetcher|cron|checker|reader|extractor|monitoring|analyzer)
[a-z0-9\-_]*(bot|crawl|archiver|transcoder|spider|uptime|validator|fetcher|cron|checker|reader|extractor|monitoring|analyzer|scraper)

View file

@ -1 +1 @@
["Safari.[\\d\\.]*","Firefox.[\\d\\.]*"," Chrome.[\\d\\.]*","Chromium.[\\d\\.]*","MSIE.[\\d\\.]","Opera\\\/[\\d\\.]*","Mozilla.[\\d\\.]*","AppleWebKit.[\\d\\.]*","Trident.[\\d\\.]*","Windows NT.[\\d\\.]*","Android [\\d\\.]*","Macintosh.","Ubuntu","Linux","[ ]Intel","Mac OS X [\\d_]*","(like )?Gecko(.[\\d\\.]*)?","KHTML,","CriOS.[\\d\\.]*","CPU iPhone OS ([0-9_])* like Mac OS X","CPU OS ([0-9_])* like Mac OS X","iPod","compatible","x86_..","i686","x64","X11","rv:[\\d\\.]*","Version.[\\d\\.]*","WOW64","Win64","Dalvik.[\\d\\.]*"," \\.NET CLR [\\d\\.]*","Presto.[\\d\\.]*","Media Center PC","BlackBerry","Build","Opera Mini\\\/\\d{1,2}\\.\\d{1,2}\\.[\\d\\.]*\\\/\\d{1,2}\\.","Opera"," \\.NET[\\d\\.]*","cubot","; M bot","; CRONO","; B bot","; IDbot","; ID bot","; POWER BOT",";"]
["Safari.[\\d\\.]*","Firefox.[\\d\\.]*"," Chrome.[\\d\\.]*","Chromium.[\\d\\.]*","MSIE.[\\d\\.]","Opera\\\/[\\d\\.]*","Mozilla.[\\d\\.]*","AppleWebKit.[\\d\\.]*","Trident.[\\d\\.]*","Windows NT.[\\d\\.]*","Android [\\d\\.]*","Macintosh.","Ubuntu","Linux","[ ]Intel","Mac OS X [\\d_]*","(like )?Gecko(.[\\d\\.]*)?","KHTML,","CriOS.[\\d\\.]*","CPU iPhone OS ([0-9_])* like Mac OS X","CPU OS ([0-9_])* like Mac OS X","iPod","compatible","x86_..","i686","x64","X11","rv:[\\d\\.]*","Version.[\\d\\.]*","WOW64","Win64","Dalvik.[\\d\\.]*"," \\.NET CLR [\\d\\.]*","Presto.[\\d\\.]*","Media Center PC","BlackBerry","Build","Opera Mini\\\/\\d{1,2}\\.\\d{1,2}\\.[\\d\\.]*\\\/\\d{1,2}\\.","Opera"," \\.NET[\\d\\.]*","cubot","; M bot","; CRONO","; B bot","; IDbot","; ID bot","; POWER BOT","OCTOPUS-CORE","htc_botdugls","super\\\/\\d+\\\/Android\\\/\\d+"]

View file

@ -45,4 +45,6 @@ cubot
; IDbot
; ID bot
; POWER BOT
;
OCTOPUS-CORE
htc_botdugls
super\/\d+\/Android\/\d+

View file

@ -20,9 +20,9 @@ class CrawlerDetect
/**
* The user agent.
*
* @var null
* @var string|null
*/
protected $userAgent = null;
protected $userAgent;
/**
* Headers that contain a user agent.
@ -93,7 +93,7 @@ class CrawlerDetect
* Compile the regex patterns into one regex string.
*
* @param array
*
*
* @return string
*/
public function compileRegex($patterns)
@ -138,7 +138,7 @@ class CrawlerDetect
/**
* Set the user agent.
*
* @param string $userAgent
* @param string|null $userAgent
*/
public function setUserAgent($userAgent)
{
@ -165,20 +165,14 @@ class CrawlerDetect
$agent = trim(preg_replace(
"/{$this->compiledExclusions}/i",
'',
$userAgent ?: $this->userAgent
$userAgent ?: $this->userAgent ?: ''
));
if ($agent == '') {
if ($agent === '') {
return false;
}
$result = preg_match("/{$this->compiledRegex}/i", $agent, $matches);
if ($matches) {
$this->matches = $matches;
}
return (bool) $result;
return (bool) preg_match("/{$this->compiledRegex}/i", $agent, $this->matches);
}
/**
@ -190,4 +184,13 @@ class CrawlerDetect
{
return isset($this->matches[0]) ? $this->matches[0] : null;
}
/**
* @return string|null
*/
public function getUserAgent()
{
return $this->userAgent;
}
}

View file

@ -15,14 +15,14 @@ abstract class AbstractProvider
{
/**
* The data set.
*
*
* @var array
*/
protected $data;
/**
* Return the data set.
*
*
* @return array
*/
public function getAll()

View file

@ -19,30 +19,49 @@ class Crawlers extends AbstractProvider
* @var array
*/
protected $data = array(
'.*Java.*outbrain',
' YLT',
'^Aether',
'^Amazon Simple Notification Service Agent$',
'^Amazon-Route53-Health-Check-Service',
'^b0t$',
'^bluefish ',
'^Calypso v\/',
'^COMODO DCV',
'^Corax',
'^DangDang',
'^DavClnt',
'^DHSH',
'^docker\/[0-9]',
'^Expanse',
'^FDM ',
'^git\/',
'^Goose\/',
'^Grabber',
'^Gradle\/',
'^HTTPClient\/',
'^HTTPing',
'^Java\/',
'^Jeode\/',
'^Jetty\/',
'^Mail\/',
'^Mget',
'^Microsoft URL Control',
'^Mikrotik\/',
'^Netlab360',
'^NG\/[0-9\.]',
'^NING\/',
'^PHP\/[0-9]',
'^npm\/',
'^Nuclei',
'^PHP-AYMAPI\/',
'^PHP\/',
'^pip\/',
'^pnpm\/',
'^RMA\/',
'^Ruby|Ruby\/[0-9]',
'^Swurl ',
'^TLS tester ',
'^twine\/',
'^ureq',
'^VSE\/[0-9]',
'^WordPress\.com',
'^XRL\/[0-9]',
@ -50,17 +69,18 @@ class Crawlers extends AbstractProvider
'008\/',
'13TABS',
'192\.comAgent',
'2GDPR\/',
'2ip\.ru',
'404enemy',
'7Siters',
'80legs',
'a\.pr-cy\.ru',
'a3logics\.in',
'A6-Indexer',
'Abonti',
'Aboundex',
'aboutthedomain',
'Accoona-AI-Agent',
'acebookexternalhit\/',
'acoon',
'acrylicapps\.com\/pulp',
'Acunetix',
@ -70,8 +90,14 @@ class Crawlers extends AbstractProvider
'ADmantX',
'AdminLabs',
'adressendeutschland',
'adreview\/',
'adscanner',
'adstxt-worker',
'Adstxtaggregator',
'adstxt\.com',
'Adyen HttpClient',
'AffiliateLabz\/',
'affilimate-puppeteer',
'agentslug',
'AHC',
'aihit',
@ -92,20 +118,23 @@ class Crawlers extends AbstractProvider
'Anemone',
'AngleSharp',
'annotate_google',
'Ant\.com',
'Anthill',
'Anturis Agent',
'Ant\.com',
'AnyEvent-HTTP\/',
'Apache Ant\/',
'Apache Droid',
'Apache OpenOffice',
'Apache-HttpAsyncClient',
'Apache-HttpClient',
'ApacheBench',
'Apexoo',
'apimon\.de',
'APIs-Google',
'AportWorm\/',
'AppBeat\/',
'AppEngine-Google',
'AppStoreScraperZ',
'AppleSyndication',
'Aprc\/[0-9]',
'Arachmo',
'arachnode',
@ -113,21 +142,28 @@ class Crawlers extends AbstractProvider
'aria2',
'Arukereso',
'asafaweb',
'AskQuickly',
'Asana\/',
'Ask Jeeves',
'AskQuickly',
'ASPSeek',
'Asterias',
'Astute',
'asynchttp',
'Attach',
'attohttpc',
'autocite',
'AutomaticWPTester',
'Autonomy',
'awin\.com',
'AWS Security Scanner',
'axios\/',
'a\.pr-cy\.ru',
'B-l-i-t-z-B-O-T',
'Backlink-Ceck',
'backlink-check',
'BacklinkHttpStatus',
'BackStreet',
'BackupLand',
'BackWeb',
'Bad-Neighborhood',
'Badass',
@ -142,6 +178,7 @@ class Crawlers extends AbstractProvider
'BCKLINKS',
'BDFetch',
'BegunAdvertising',
'Bewica-security-scan',
'Bidtellect',
'BigBozz',
'Bigfoot',
@ -151,11 +188,14 @@ class Crawlers extends AbstractProvider
'binlar',
'biNu image cacher',
'Bitacle',
'Bitrix link preview',
'biz_Directory',
'BKCTwitterUnshortener\/',
'Black Hole',
'Blackboard Safeassign',
'BlackWidow',
'BlockNote\.Net',
'BlogBridge',
'Bloglines',
'Bloglovin',
'BlogPulseLive',
@ -163,6 +203,7 @@ class Crawlers extends AbstractProvider
'Blogtrottr',
'BlowFish',
'boitho\.com-dc',
'Boost\.Beast',
'BPImageWalker',
'Braintree-Webhooks',
'Branch Metrics API',
@ -190,12 +231,15 @@ class Crawlers extends AbstractProvider
'CC Metadata Scaper',
'Cegbfeieh',
'censys',
'centuryb.o.t9[at]gmail.com',
'Cerberian Drtrs',
'CERT\.at-Statistics-Survey',
'cf-facebook',
'cg-eye',
'changedetection',
'ChangesMeter',
'Charlotte',
'chatterino-api-cache',
'CheckHost',
'checkprivacy',
'CherryPicker',
@ -206,6 +250,7 @@ class Crawlers extends AbstractProvider
'Chromaxa',
'CirrusExplorer',
'CISPA Vulnerability Notification',
'CISPA Web Analyser',
'Citoid',
'CJNetworkQuality',
'Clarsentia',
@ -213,27 +258,34 @@ class Crawlers extends AbstractProvider
'Cloud mapping',
'CloudEndure',
'CloudFlare-AlwaysOnline',
'Cloudflare-Healthchecks',
'Cloudinary',
'cmcm\.com',
'coccoc',
'cognitiveseo',
'ColdFusion',
'colly -',
'CommaFeed',
'Commons-HttpClient',
'commonscan',
'contactbigdatafr',
'contentkingapp',
'Contextual Code Sites Explorer',
'convera',
'CookieReports',
'copyright sheriff',
'CopyRightCheck',
'Copyscape',
'cortex\/',
'Cosmos4j\.feedback',
'Covario-IDS',
'Craw\/',
'Crescent',
'Crowsnest',
'Criteo',
'Crowsnest',
'CSHttp',
'CSSCheck',
'Cula\/',
'curb',
'Curious George',
'curl',
@ -243,12 +295,17 @@ class Crawlers extends AbstractProvider
'DareBoost',
'DatabaseDriverMysqli',
'DataCha0s',
'DatadogSynthetics',
'Datafeedwatch',
'Datanyze',
'DataparkSearch',
'dataprovider',
'DataXu',
'Daum(oa)?[ \/][0-9]',
'dBpoweramp',
'ddline',
'deeris',
'delve\.ai',
'Demon',
'DeuSu',
'developers\.google\.com\/\+\/web\/snippet\/',
@ -258,8 +315,8 @@ class Crawlers extends AbstractProvider
'DigitalPebble',
'Dirbuster',
'Discourse Forum Onebox',
'Disqus\/',
'Dispatch\/',
'Disqus\/',
'DittoSpyder',
'dlvr',
'DMBrowser',
@ -267,6 +324,8 @@ class Crawlers extends AbstractProvider
'docoloc',
'Dolphin http client',
'DomainAppender',
'DomainLabz',
'Domains Project\/',
'Donuts Content Explorer',
'dotMailer content retrieval',
'dotSemantic',
@ -279,6 +338,8 @@ class Crawlers extends AbstractProvider
'Drupal \(\+http:\/\/drupal\.org\/\)',
'DTS Agent',
'dubaiindex',
'DuplexWeb-Google',
'DynatraceSynthetic',
'EARTHCOM',
'Easy-Thumb',
'EasyDL',
@ -309,20 +370,22 @@ class Crawlers extends AbstractProvider
'ExaleadCloudview',
'Excel\/',
'exif',
'ExoRank',
'Exploratodo',
'Express WebPictures',
'Extreme Picture Finder',
'EyeNetIE',
'ezooms',
'facebookexternalhit',
'facebookexternalua',
'facebookplatform',
'fairshare',
'Faraday v',
'fasthttp',
'Faveeo',
'Favicon downloader',
'faviconkit',
'faviconarchive',
'faviconkit',
'FavOrg',
'Feed Wrangler',
'Feedable\/',
@ -334,7 +397,9 @@ class Crawlers extends AbstractProvider
'feeder',
'Feedly',
'FeedshowOnline',
'Feedshow\/',
'Feedspot',
'FeedViewer\/',
'Feedwind\/',
'FeedZcollector',
'feeltiptop',
@ -342,6 +407,8 @@ class Crawlers extends AbstractProvider
'Fetch\/[0-9]',
'Fever\/[0-9]',
'FHscan',
'Fiery%20Feeds',
'Filestack',
'Fimap',
'findlink',
'findthatfile',
@ -350,18 +417,24 @@ class Crawlers extends AbstractProvider
'FlipboardProxy',
'FlipboardRSS',
'Flock\/',
'Florienzh\/',
'fluffy',
'Flunky',
'flynxapp',
'forensiq',
'ForusP',
'FoundSeoTool',
'http:\/\/www.neomo.de\/', //'Francis [Bot]'
'fragFINN\.de',
'free thumbnails',
'Freeuploader',
'FreshRSS',
'frontman',
'Funnelback',
'Fuzz Faster U Fool',
'G-i-g-a-b-o-t',
'g00g1e\.net',
'ganarvisitas',
'gdnplus\.com',
'geek-tools',
'Genieo',
'GentleSource',
@ -373,18 +446,24 @@ class Crawlers extends AbstractProvider
'getroot',
'GetURLInfo\/',
'GetWeb',
'Geziyor',
'Ghost Inspector',
'GigablastOpenSource',
'GIS-LABS',
'github-camo',
'GitHub-Hookshot',
'github\.com',
'Go [\d\.]* package http',
'Go http package',
'Go [\d\.]* package http',
'Go!Zilla',
'Go-Ahead-Got-It',
'Go-http-client',
'Go!Zilla',
'go-mtasts\/',
'gobuster',
'gobyus',
'Gofeed',
'gofetch',
'Goldfire Server',
'GomezAgent',
'gooblog',
'Goodzer\/',
@ -397,24 +476,31 @@ class Crawlers extends AbstractProvider
'Google PP Default',
'Google Search Console',
'Google Web Preview',
'Google-Ads-Creatives-Assistant',
'Google-Ads-Overview',
'Google-Adwords',
'Google-Apps-Script',
'Google-Calendar-Importer',
'Google-HotelAdsVerifier',
'Google-HTTP-Java-Client',
'Google-InspectionTool',
'Google-Podcast',
'Google-Publisher-Plugin',
'Google-Read-Aloud',
'Google-SearchByImage',
'Google-Site-Verification',
'Google-SMTP-STS',
'Google-speakr',
'Google-Structured-Data-Testing-Tool',
'Google-Youtube-Links',
'Google-Transparency-Report',
'google-xrawler',
'Google-Youtube-Links',
'GoogleDocs',
'GoogleHC\/',
'GoogleProber',
'GoogleProducer',
'GoogleSites',
'Google-Transparency-Report',
'Gookey',
'GoScraper',
'GoSpotCheck',
'gosquared-thumbnailer',
'Gotit',
@ -440,13 +526,16 @@ class Crawlers extends AbstractProvider
'hackney\/',
'Hadi Agent',
'HappyApps-WebCheck',
'Hardenize',
'Hatena',
'Havij',
'HaxerMen',
'HeadlessChrome',
'HEADMasterSEO',
'HeartRails_Capture',
'help@dataminr\.com',
'heritrix',
'Hexometer',
'historious',
'hkedcity',
'hledejLevne\.cz',
@ -463,28 +552,31 @@ class Crawlers extends AbstractProvider
'HTMLparser',
'htmlyse',
'HTTP Banner Detection',
'HTTP_Compression_Test',
'http_request2',
'http_requester',
'http-get',
'HTTP-Header-Abfrage',
'http-kit',
'http-request\/',
'HTTP-Tiny',
'HTTP::Lite',
'http\.rb\/',
'http_get',
'http:\/\/www.neomo.de\/', //'Francis [Bot]'
'HttpComponents',
'httphr',
'HTTPie',
'HTTPMon',
'httpRequest',
'httpscheck',
'httpssites_power',
'httpunit',
'HttpUrlConnection',
'http\.rb\/',
'HTTP_Compression_Test',
'http_get',
'http_request2',
'http_requester',
'httrack',
'huaweisymantec',
'HubSpot ',
'HubSpot-Link-Resolver',
'Humanlinks',
'i2kconnect\/',
'Iblog',
@ -495,6 +587,7 @@ class Crawlers extends AbstractProvider
'IDwhois\/',
'Iframely',
'igdeSpyder',
'iGooglePortal',
'IlTrovatore',
'Image Fetch',
'Image Sucker',
@ -519,13 +612,15 @@ class Crawlers extends AbstractProvider
'integromedb',
'Intelliseek',
'InterGET',
'internet_archive',
'Internet Ninja',
'InternetSeer',
'internetVista monitor',
'internetwache',
'internet_archive',
'intraVnews',
'IODC',
'IOI',
'Inboxb0t',
'iplabel',
'ips-agent',
'IPS\/[0-9]',
@ -537,10 +632,13 @@ class Crawlers extends AbstractProvider
'iskanie',
'isUp\.li',
'iThemes Sync\/',
'IZaBEE',
'iZSearch',
'JAHHO',
'janforman',
'Jaunt\/',
'Java.*outbrain',
'javelin\.io',
'Jbrofuzz',
'Jersey\/',
'JetCar',
@ -556,6 +654,7 @@ class Crawlers extends AbstractProvider
'Joomla',
'Jorgee',
'JS-Kit',
'JungleKeyThumbnail',
'JustView',
'Kaspersky Lab CFR link resolver',
'Kelny\/',
@ -563,6 +662,7 @@ class Crawlers extends AbstractProvider
'KeyCDN',
'Keyword Density',
'Keywords Research',
'khttp\/',
'KickFire',
'KimonoLabs\/',
'Kml-Google',
@ -570,11 +670,12 @@ class Crawlers extends AbstractProvider
'KOCMOHABT',
'kouio',
'kube-probe',
'kubectl',
'kulturarw3',
'KumKie',
'L\.webis',
'Larbin',
'Lavf\/',
'leakix\.net',
'LeechFTP',
'LeechGet',
'letsencrypt',
@ -585,41 +686,50 @@ class Crawlers extends AbstractProvider
'libwww',
'Licorne',
'Liferea\/',
'Lightspeedsystems',
'Lighthouse',
'Lightspeedsystems',
'Likse',
'limber\.io',
'Link Valet',
'link_thumbnailer',
'LinkAlarm\/',
'LinkAnalyser',
'linkCheck',
'linkdex',
'LinkExaminer',
'linkfluence',
'linkpeek',
'LinkPreviewGenerator',
'LinkPreview',
'LinkScan',
'LinksManager',
'LinkTiger',
'LinkWalker',
'link_thumbnailer',
'Lipperhey',
'Litemage_walker',
'livedoor ScreenShot',
'LoadImpactRload',
'localsearch-web',
'LongURL API',
'longurl-r-package',
'looid\.com',
'looksystems\.net',
'ltx71',
'lua-resty-http',
'Lucee \(CFML Engine\)',
'Lush Http Client',
'lwp-request',
'lwp-trivial',
'LWP::Simple',
'lycos',
'LYT\.SR',
'L\.webis',
'mabontland',
'MacOutlook\/',
'Mag-Net',
'MagpieRSS',
'Mail\.Ru',
'Mail::STS',
'MailChimp',
'Mail\.Ru',
'Majestic12',
'makecontact\/',
'Mandrill',
@ -630,6 +740,8 @@ class Crawlers extends AbstractProvider
'Mass Downloader',
'masscan\/',
'Mata Hari',
'mattermost',
'Mediametric',
'Mediapartners-Google',
'mediawords',
'MegaIndex\.ru',
@ -640,28 +752,31 @@ class Crawlers extends AbstractProvider
'MetaURI',
'MFC_Tear_Sample',
'Microsearch',
'Microsoft Office ',
'Microsoft Data Access',
'Microsoft Office',
'Microsoft Outlook',
'Microsoft Windows Network Diagnostics',
'Microsoft-WebDAV-MiniRedir',
'Microsoft Data Access',
'Microsoft\.Data\.Mashup',
'MIDown tool',
'MIIxpc',
'Mindjet',
'Miniature\.io',
'Miniflux',
'mio_httpc',
'Miro-HttpClient',
'Mister PiX',
'mixdata dot com',
'mixed-content-scan',
'Mixmax-LinkPreview',
'mixnode',
'Mnogosearch',
'mogimogi',
'Mojeek',
'Mojolicious \(Perl\)',
'Monit\/',
'Mollie',
'monitis',
'Monitority\/',
'Monit\/',
'montastic',
'MonTools',
'Moreover',
@ -670,6 +785,7 @@ class Crawlers extends AbstractProvider
'MovableType',
'mowser',
'Mrcgiguy',
'Mr\.4x3 Powered',
'MS Web Services Client Protocol',
'MSFrontPage',
'mShots',
@ -677,10 +793,13 @@ class Crawlers extends AbstractProvider
'muhstik-scan',
'MVAClient',
'MxToolbox\/',
'myseosnapshot',
'nagios',
'Najdi\.si',
'Name Intelligence',
'NameFo\.com',
'Nameprotect',
'nationalarchives',
'Navroad',
'NearSite',
'Needle',
@ -695,6 +814,8 @@ class Crawlers extends AbstractProvider
'netresearch',
'NetShelter ContentScan',
'Netsparker',
'NetSystemsResearch',
'nettle',
'NetTrack',
'Netvibes',
'NetZIP',
@ -707,22 +828,26 @@ class Crawlers extends AbstractProvider
'newspaper\/',
'Nexgate Ruby Client',
'NG-Search',
'nghttp2',
'Nibbler',
'NICErsPRO',
'NihilScio',
'Nikto',
'nineconnections',
'NLNZ_IAHarvester',
'Nmap Scripting Engine',
'node-fetch',
'node-superagent',
'node-urllib',
'node\.io',
'Nodemeter',
'NodePing',
'node\.io',
'nominet\.org\.uk',
'nominet\.uk',
'Norton-Safeweb',
'Notifixious',
'notifyninja',
'NotionEmbedder',
'nuhk',
'nutch',
'Nuzzel',
@ -730,39 +855,45 @@ class Crawlers extends AbstractProvider
'nyawc\/',
'Nymesis',
'NYU',
'Observatory\/',
'Ocelli\/',
'Octopus',
'oegp',
'Offline Explorer',
'Offline Navigator',
'og-scraper',
'OgScrper',
'okhttp',
'omgili',
'OMSC',
'Online Domain Tools',
'Open Source RSS',
'OpenCalaisSemanticProxy',
'Openfind',
'OpenLinkProfiler',
'Openstat\/',
'OpenVAS',
'OPPO A33',
'Optimizer',
'Orbiter',
'OrgProbe\/',
'orion-semantics',
'Outlook-Express',
'Outlook-iOS',
'ow\.ly',
'Owler',
'Owlin',
'ownCloud News',
'ow\.ly',
'OxfordCloudService',
'Page Valet',
'page_verifier',
'page scorer',
'Page Valet',
'page2rss',
'PageFreezer',
'PageGrabber',
'PagePeeker',
'PageScorer',
'Pagespeed\/',
'PageThing',
'page_verifier',
'Panopta',
'panscient',
'Papa Foto',
@ -771,6 +902,7 @@ class Crawlers extends AbstractProvider
'PayPal IPN',
'pcBrowser',
'Pcore-HTTP',
'PDF24 URL To PDF',
'Pearltrees',
'PECL::HTTP',
'peerindex',
@ -780,44 +912,52 @@ class Crawlers extends AbstractProvider
'PhantomJS Screenshoter',
'PhantomJS\/',
'Photon\/',
'php-requests',
'phpservermon',
'Pi-Monster',
'Picscout',
'Picsearch',
'PictureFinder',
'Pimonster',
'ping\.blo\.gs',
'Pingability',
'PingAdmin\.Ru',
'Pingdom',
'Pingoscope',
'PingSpot',
'ping\.blo\.gs',
'pinterest\.com',
'Pixray',
'Pizilla',
'Plagger\/',
'Pleroma ',
'Ploetz \+ Zeller',
'Plukkie',
'plumanalytics',
'PocketImageCache',
'PocketParser',
'Pockey',
'PodcastAddict\/',
'POE-Component-Client-HTTP',
'Polymail\/',
'Pompos',
'Porkbun',
'Port Monitor',
'postano',
'postfix-mta-sts-resolver',
'PostmanRuntime',
'postplanner\.com',
'PostPost',
'postrank',
'PowerPoint\/',
'Prebid',
'Prerender',
'Priceonomics Analysis Engine',
'PrintFriendly',
'PritTorrent',
'Prlog',
'probethenet',
'Project 25499',
'Project ?25499',
'Project-Resonance',
'prospectb2b',
'Protopage',
'ProWebWalker',
@ -826,9 +966,9 @@ class Crawlers extends AbstractProvider
'pshtt, https scanning',
'PTST ',
'PTST\/[0-9]+',
'Pulsepoint XT3 web scraper',
'Pump',
'Python-httplib2',
'python-httpx',
'python-requests',
'Python-urllib',
'Qirina Hurdler',
@ -838,12 +978,17 @@ class Crawlers extends AbstractProvider
'Qualidator',
'QueryN Metasearch',
'queuedriver',
'quic-go-HTTP\/',
'QuiteRSS',
'Quora Link Preview',
'Qwantify',
'Radian6',
'RadioPublicImageResizer',
'Railgun\/',
'RankActive',
'RankFlex',
'RankSonicSiteAuditor',
'RapidLoad\/',
'Re-re Studio',
'ReactorNetty',
'Readability',
@ -854,6 +999,7 @@ class Crawlers extends AbstractProvider
'RecurPost\/',
'redback\/',
'ReederForMac',
'Reeder\/',
'ReGet',
'RepoMonkey',
'request\.js',
@ -866,38 +1012,44 @@ class Crawlers extends AbstractProvider
'Robozilla',
'ROI Hunter',
'RPT-HTTPClient',
'RSSMix\/',
'RSSOwl',
'RyowlEngine',
'safe-agent-scanner',
'SalesIntelligent',
'Saleslift',
'Sendsay\.Ru',
'SAP NetWeaver Application Server',
'SauceNAO',
'SBIder',
'sc-downloader',
'scalaj-http',
'scan\.lol',
'Scamadviser-Frontend',
'ScanAlert',
'scan\.lol',
'Scoop',
'scooter',
'ScopeContentAG-HTTP-Client',
'ScoutJet',
'ScoutURLMonitor',
'ScrapeBox Page Scanner',
'SimpleScraper',
'Scrapy',
'Screaming',
'ScreenShotService',
'Scrubby',
'Scrutiny\/',
'search\.thunderstone',
'Search37',
'searchenginepromotionhelp',
'Searchestate',
'SearchExpress',
'SearchSight',
'SearchWP',
'search\.thunderstone',
'Seeker',
'semanticdiscovery',
'semanticjuice',
'Semiocast HTTP client',
'Semrush',
'Sendsay\.Ru',
'sentry\/',
'SEO Browser',
'Seo Servis',
@ -906,11 +1058,13 @@ class Crawlers extends AbstractProvider
'Seobility',
'SEOCentro',
'SeoCheck',
'seocompany',
'SEOkicks',
'SEOlizer',
'Seomoz',
'SEOprofiler',
'SEOsearch',
'seoscanners',
'SEOsearch',
'seositecheckup',
'SEOstats',
'servernfo',
@ -921,14 +1075,16 @@ class Crawlers extends AbstractProvider
'Shoppimon',
'ShopWiki',
'ShortLinkTranslate',
'shortURL lengthener',
'shrinktheweb',
'Sideqik',
'Siege',
'SimplePie',
'SimplyFast',
'Siphon',
'SISTRIX',
'Site-Shot\/',
'Site Sucker',
'Site-Shot\/',
'Site24x7',
'SiteBar',
'Sitebeam',
@ -949,6 +1105,7 @@ class Crawlers extends AbstractProvider
'sitexy\.com',
'SkypeUriPreview',
'Slack\/',
'sli-systems\.com',
'slider\.com',
'slurp',
'SlySearch',
@ -971,9 +1128,11 @@ class Crawlers extends AbstractProvider
'SpamExperts',
'Spammen',
'Spanner',
'Spawning-AI',
'spaziodati',
'SPDYCheck',
'Specificfeeds',
'SpeedKit',
'speedy',
'SPEng',
'Spinn3r',
@ -987,9 +1146,11 @@ class Crawlers extends AbstractProvider
'ssl-tools',
'StackRambler',
'Statastico\/',
'Statically-',
'StatusCake',
'Steeler',
'Stratagems Kumo',
'Stripe\/',
'Stroke\.cz',
'StudioFACA',
'StumbleUpon',
@ -999,9 +1160,11 @@ class Crawlers extends AbstractProvider
'SuperHTTP',
'Surphace Scout',
'Suzuran',
'SwiteScraper',
'swcd ',
'Symfony BrowserKit',
'Symfony2 BrowserKit',
'Synapse\/',
'Syndirella\/',
'SynHttpClient-Built',
'Sysomos',
'sysscan',
@ -1011,6 +1174,8 @@ class Crawlers extends AbstractProvider
'Tarantula\/',
'Taringa UGC',
'TarmotGezgin',
'tchelebi\.io',
'techiaith\.cymru',
'Teleport',
'Telesoft',
'Telesphoreo',
@ -1021,13 +1186,15 @@ class Crawlers extends AbstractProvider
'Test Certificate Info',
'testuri',
'Tetrahedron',
'TextRazor Downloader',
'The Drop Reaper',
'The Expert HTML Source Viewer',
'The Knowledge AI',
'The Intraformant',
'The Knowledge AI',
'theinternetrules',
'TheNomad',
'Thinklab',
'Thumbor',
'Thumbshots',
'ThumbSniper',
'timewe\.net',
@ -1061,9 +1228,11 @@ class Crawlers extends AbstractProvider
'ubermetrics-technologies',
'uclassify',
'UdmSearch',
'ultimate_sitemap_parser',
'unchaos',
'unirest-java',
'UniversalFeedParser',
'unshortenit',
'Unshorten\.It',
'Untiny',
'UnwindFetchor',
@ -1071,8 +1240,8 @@ class Crawlers extends AbstractProvider
'updown\.io daemon',
'Upflow',
'Uptimia',
'Urlcheckr',
'URL Verifier',
'Urlcheckr',
'URLitor',
'urlresolver',
'Urlstat',
@ -1080,15 +1249,16 @@ class Crawlers extends AbstractProvider
'UrlTrends Ranking Updater',
'URLy Warning',
'URLy\.Warning',
'URL\/Emacs',
'Vacuum',
'Vagabondo',
'VB Project',
'vBSEO',
'VCI',
'via ggpht\.com GoogleImageProxy',
'VidibleScraper',
'Virusdie',
'visionutils',
'Visual Rights Group',
'vkShare',
'VoidEYE',
'Voil',
@ -1099,19 +1269,18 @@ class Crawlers extends AbstractProvider
'Vulnbusters Meter',
'VYU2',
'w3af\.org',
'W3C_Unicorn',
'W3C-checklink',
'W3C-mobileOK',
'W3C_Unicorn',
'WAC-OFU',
'Wallpapers\/[0-9]+',
'WakeletLinkExpander',
'WallpapersHD',
'Wallpapers\/[0-9]+',
'wangling',
'Wappalyzer',
'WatchMouse',
'WbSrch\/',
'WDT\.io',
'web-capture\.net',
'Web-sniffer',
'Web Auto',
'Web Collage',
'Web Enhancer',
@ -1119,7 +1288,10 @@ class Crawlers extends AbstractProvider
'Web Fuck',
'Web Pix',
'Web Sauger',
'Web spyder',
'Web Sucker',
'web-capture\.net',
'Web-sniffer',
'Webalta',
'Webauskunft',
'WebAuto',
@ -1157,8 +1329,9 @@ class Crawlers extends AbstractProvider
'Webster',
'WebStripper',
'WebSucker',
'Webthumb\/',
'webtech\/',
'WebThumbnail',
'Webthumb\/',
'WebWhacker',
'WebZIP',
'WeLikeLinks',
@ -1167,24 +1340,31 @@ class Crawlers extends AbstractProvider
'wf84',
'Wfuzz\/',
'wget',
'WhatCMS',
'WhatsApp',
'WhatsMyIP',
'WhatWeb',
'WhereGoes\?',
'Whibse',
'WhoAPI\/',
'WhoRunsCoinHive',
'Whynder Magnet',
'Windows-RSS-Platform',
'WinHttp-Autoproxy-Service',
'WinHTTP\/',
'WinPodder',
'wkhtmlto',
'wmtips',
'Woko',
'Wolfram HTTPClient',
'woorankreview',
'Word\/',
'WordPress\/',
'WordupinfoSearch',
'Word\/',
'worldping-api',
'wotbox',
'WP Engine Install Performance API',
'WP Rocket',
'wpif',
'wprecon\.com survey',
'WPScan',
@ -1194,20 +1374,21 @@ class Crawlers extends AbstractProvider
'WWW-Mechanize',
'WWW::Document',
'WWW::Mechanize',
'www\.monitor\.us',
'WWWOFFLE',
'www\.monitor\.us',
'x09Mozilla',
'x22Mozilla',
'XaxisSemanticsClassifier',
'XenForo\/',
'Xenu Link Sleuth',
'XING-contenttabreceiver',
'xpymep([0-9]?)\.exe',
'Y!J-(ASR|BSC)',
'Y\!J-BRW',
'Y!J-[A-Z][A-Z][A-Z]',
'Yaanb',
'yacy',
'Yahoo Link Preview',
'YahooCacheSystem',
'YahooMailProxy',
'YahooYSMcm',
'YandeG',
'Yandex(?!Search)',
@ -1215,6 +1396,7 @@ class Crawlers extends AbstractProvider
'yeti',
'Yo-yo',
'Yoleo Consumer',
'yomins\.com',
'yoogliFetchAgent',
'YottaaMonitor',
'Your-Website-Sucks',
@ -1226,15 +1408,17 @@ class Crawlers extends AbstractProvider
'Zao',
'Zauba',
'Zemanta Aggregator',
'Zend_Http_Client',
'Zend\\\\Http\\\\Client',
'Zend_Http_Client',
'Zermelo',
'Zeus ',
'zgrab',
'ZnajdzFoto',
'ZnHTTP',
'Zombie\.js',
'Zoom\.Mac',
'ZoteroTranslationServer',
'ZyBorg',
'[a-z0-9\-_]*(bot|crawl|archiver|transcoder|spider|uptime|validator|fetcher|cron|checker|reader|extractor|monitoring|analyzer)',
'[a-z0-9\-_]*(bot|crawl|archiver|transcoder|spider|uptime|validator|fetcher|cron|checker|reader|extractor|monitoring|analyzer|scraper)',
);
}

View file

@ -67,6 +67,8 @@ class Exclusions extends AbstractProvider
'; IDbot',
'; ID bot',
'; POWER BOT',
';', // Remove the following characters ;
'OCTOPUS-CORE',
'htc_botdugls',
'super\/\d+\/Android\/\d+',
);
}

View file

@ -1,23 +1,26 @@
{
"name": "friendica-addons/monolog",
"description": "Monolog can send your logs to files, sockets, inboxes, database, etc..",
"type": "friendica-addon",
"authors": [
{
"name": "Philipp Holzer",
"email": "admin@philipp.info",
"homepage": "https://blog.philipp.info",
"role": "Developer"
}
],
"require": {
"php": ">=7.3",
"monolog/monolog": "^2.9"
},
"license": "3-clause BSD license",
"config": {
"optimize-autoloader": true,
"autoloader-suffix": "MonologAddon",
"preferred-install": "dist"
}
"name": "friendica-addons/monolog",
"description": "Monolog can send your logs to files, sockets, inboxes, database, etc..",
"type": "friendica-addon",
"authors": [
{
"name": "Philipp Holzer",
"email": "admin@philipp.info",
"homepage": "https://blog.philipp.info",
"role": "Developer"
}
],
"require": {
"php": ">=7.3",
"monolog/monolog": "^2.9"
},
"license": "3-clause BSD license",
"config": {
"platform": {
"php": "7.4"
},
"optimize-autoloader": true,
"autoloader-suffix": "MonologAddon",
"preferred-install": "dist"
}
}

15
monolog/composer.lock generated
View file

@ -4,20 +4,20 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
"content-hash": "6fd294bd163b37ac6cc400e0f8785222",
"content-hash": "037f2db47e77c9af2960dde65ebafa8d",
"packages": [
{
"name": "monolog/monolog",
"version": "2.9.1",
"version": "2.9.2",
"source": {
"type": "git",
"url": "https://github.com/Seldaek/monolog.git",
"reference": "f259e2b15fb95494c83f52d3caad003bbf5ffaa1"
"reference": "437cb3628f4cf6042cc10ae97fc2b8472e48ca1f"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/Seldaek/monolog/zipball/f259e2b15fb95494c83f52d3caad003bbf5ffaa1",
"reference": "f259e2b15fb95494c83f52d3caad003bbf5ffaa1",
"url": "https://api.github.com/repos/Seldaek/monolog/zipball/437cb3628f4cf6042cc10ae97fc2b8472e48ca1f",
"reference": "437cb3628f4cf6042cc10ae97fc2b8472e48ca1f",
"shasum": ""
},
"require": {
@ -102,7 +102,7 @@
"type": "tidelift"
}
],
"time": "2023-02-06T13:44:46+00:00"
"time": "2023-10-27T15:25:26+00:00"
},
{
"name": "psr/log",
@ -162,5 +162,8 @@
"php": ">=7.3"
},
"platform-dev": [],
"platform-overrides": {
"php": "7.4"
},
"plugin-api-version": "1.1.0"
}

View file

@ -121,15 +121,4 @@ return array(
'Monolog\\SignalHandler' => $vendorDir . '/monolog/monolog/src/Monolog/SignalHandler.php',
'Monolog\\Test\\TestCase' => $vendorDir . '/monolog/monolog/src/Monolog/Test/TestCase.php',
'Monolog\\Utils' => $vendorDir . '/monolog/monolog/src/Monolog/Utils.php',
'Psr\\Log\\AbstractLogger' => $vendorDir . '/psr/log/Psr/Log/AbstractLogger.php',
'Psr\\Log\\InvalidArgumentException' => $vendorDir . '/psr/log/Psr/Log/InvalidArgumentException.php',
'Psr\\Log\\LogLevel' => $vendorDir . '/psr/log/Psr/Log/LogLevel.php',
'Psr\\Log\\LoggerAwareInterface' => $vendorDir . '/psr/log/Psr/Log/LoggerAwareInterface.php',
'Psr\\Log\\LoggerAwareTrait' => $vendorDir . '/psr/log/Psr/Log/LoggerAwareTrait.php',
'Psr\\Log\\LoggerInterface' => $vendorDir . '/psr/log/Psr/Log/LoggerInterface.php',
'Psr\\Log\\LoggerTrait' => $vendorDir . '/psr/log/Psr/Log/LoggerTrait.php',
'Psr\\Log\\NullLogger' => $vendorDir . '/psr/log/Psr/Log/NullLogger.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',
);

View file

@ -144,17 +144,6 @@ class ComposerStaticInitMonologAddon
'Monolog\\SignalHandler' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/SignalHandler.php',
'Monolog\\Test\\TestCase' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/Test/TestCase.php',
'Monolog\\Utils' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/Utils.php',
'Psr\\Log\\AbstractLogger' => __DIR__ . '/..' . '/psr/log/Psr/Log/AbstractLogger.php',
'Psr\\Log\\InvalidArgumentException' => __DIR__ . '/..' . '/psr/log/Psr/Log/InvalidArgumentException.php',
'Psr\\Log\\LogLevel' => __DIR__ . '/..' . '/psr/log/Psr/Log/LogLevel.php',
'Psr\\Log\\LoggerAwareInterface' => __DIR__ . '/..' . '/psr/log/Psr/Log/LoggerAwareInterface.php',
'Psr\\Log\\LoggerAwareTrait' => __DIR__ . '/..' . '/psr/log/Psr/Log/LoggerAwareTrait.php',
'Psr\\Log\\LoggerInterface' => __DIR__ . '/..' . '/psr/log/Psr/Log/LoggerInterface.php',
'Psr\\Log\\LoggerTrait' => __DIR__ . '/..' . '/psr/log/Psr/Log/LoggerTrait.php',
'Psr\\Log\\NullLogger' => __DIR__ . '/..' . '/psr/log/Psr/Log/NullLogger.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',
);
public static function getInitializer(ClassLoader $loader)

View file

@ -1,17 +1,17 @@
[
{
"name": "monolog/monolog",
"version": "2.9.1",
"version_normalized": "2.9.1.0",
"version": "2.9.2",
"version_normalized": "2.9.2.0",
"source": {
"type": "git",
"url": "https://github.com/Seldaek/monolog.git",
"reference": "f259e2b15fb95494c83f52d3caad003bbf5ffaa1"
"reference": "437cb3628f4cf6042cc10ae97fc2b8472e48ca1f"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/Seldaek/monolog/zipball/f259e2b15fb95494c83f52d3caad003bbf5ffaa1",
"reference": "f259e2b15fb95494c83f52d3caad003bbf5ffaa1",
"url": "https://api.github.com/repos/Seldaek/monolog/zipball/437cb3628f4cf6042cc10ae97fc2b8472e48ca1f",
"reference": "437cb3628f4cf6042cc10ae97fc2b8472e48ca1f",
"shasum": ""
},
"require": {
@ -57,7 +57,7 @@
"rollbar/rollbar": "Allow sending log messages to Rollbar",
"ruflin/elastica": "Allow sending log messages to an Elastic Search server"
},
"time": "2023-02-06T13:44:46+00:00",
"time": "2023-10-27T15:25:26+00:00",
"type": "library",
"extra": {
"branch-alias": {

View file

@ -1,3 +1,9 @@
### 2.9.2 (2023-10-27)
* Fixed display_errors parsing in ErrorHandler which did not support string values (#1804)
* Fixed bug where the previous error handler would not be restored in some cases where StreamHandler fails (#1815)
* Fixed normalization error when normalizing incomplete classes (#1833)
### 2.9.1 (2023-02-06)
* Fixed Logger not being serializable anymore (#1792)

View file

@ -0,0 +1,72 @@
### 2.0.0
- `Monolog\Logger::API` can be used to distinguish between a Monolog `1` and `2`
install of Monolog when writing integration code.
- Removed non-PSR-3 methods to add records, all the `add*` (e.g. `addWarning`)
methods as well as `emerg`, `crit`, `err` and `warn`.
- DateTime are now formatted with a timezone and microseconds (unless disabled).
Various formatters and log output might be affected, which may mess with log parsing
in some cases.
- The `datetime` in every record array is now a DateTimeImmutable, not that you
should have been modifying these anyway.
- The timezone is now set per Logger instance and not statically, either
via ->setTimezone or passed in the constructor. Calls to Logger::setTimezone
should be converted.
- `HandlerInterface` has been split off and two new interfaces now exist for
more granular controls: `ProcessableHandlerInterface` and
`FormattableHandlerInterface`. Handlers not extending `AbstractHandler`
should make sure to implement the relevant interfaces.
- `HandlerInterface` now requires the `close` method to be implemented. This
only impacts you if you implement the interface yourself, but you can extend
the new `Monolog\Handler\Handler` base class too.
- There is no more default handler configured on empty Logger instances, if
you were relying on that you will not get any output anymore, make sure to
configure the handler you need.
#### LogglyFormatter
- The records' `datetime` is not sent anymore. Only `timestamp` is sent to Loggly.
#### AmqpHandler
- Log levels are not shortened to 4 characters anymore. e.g. a warning record
will be sent using the `warning.channel` routing key instead of `warn.channel`
as in 1.x.
- The exchange name does not default to 'log' anymore, and it is completely ignored
now for the AMQP extension users. Only PHPAmqpLib uses it if provided.
#### RotatingFileHandler
- The file name format must now contain `{date}` and the date format must be set
to one of the predefined FILE_PER_* constants to avoid issues with file rotation.
See `setFilenameFormat`.
#### LogstashFormatter
- Removed Logstash V0 support
- Context/extra prefix has been removed in favor of letting users configure the exact key being sent
- Context/extra data are now sent as an object instead of single keys
#### HipChatHandler
- Removed deprecated HipChat handler, migrate to Slack and use SlackWebhookHandler or SlackHandler instead
#### SlackbotHandler
- Removed deprecated SlackbotHandler handler, use SlackWebhookHandler or SlackHandler instead
#### RavenHandler
- Removed deprecated RavenHandler handler, use sentry/sentry 2.x and their Sentry\Monolog\Handler instead
#### ElasticSearchHandler
- As support for the official Elasticsearch library was added, the former ElasticSearchHandler has been
renamed to ElasticaHandler and the new one added as ElasticsearchHandler.

View file

@ -198,7 +198,7 @@ class ErrorHandler
($this->previousExceptionHandler)($e);
}
if (!headers_sent() && !ini_get('display_errors')) {
if (!headers_sent() && in_array(strtolower((string) ini_get('display_errors')), ['0', '', 'false', 'off', 'none', 'no'], true)) {
http_response_code(500);
}

View file

@ -155,7 +155,7 @@ class LineFormatter extends NormalizerFormatter
do {
$depth++;
if ($depth > $this->maxNormalizeDepth) {
$str .= '\n[previous exception] Over ' . $this->maxNormalizeDepth . ' levels deep, aborting normalization';
$str .= "\n[previous exception] Over " . $this->maxNormalizeDepth . ' levels deep, aborting normalization';
break;
}

View file

@ -174,6 +174,9 @@ class NormalizerFormatter implements FormatterInterface
if ($data instanceof \JsonSerializable) {
/** @var null|scalar|array<array|scalar|null> $value */
$value = $data->jsonSerialize();
} elseif (\get_class($data) === '__PHP_Incomplete_Class') {
$accessor = new \ArrayObject($data);
$value = (string) $accessor['__PHP_Incomplete_Class_Name'];
} elseif (method_exists($data, '__toString')) {
/** @var string $value */
$value = $data->__toString();

View file

@ -135,11 +135,14 @@ class StreamHandler extends AbstractProcessingHandler
$this->createDir($url);
$this->errorMessage = null;
set_error_handler([$this, 'customErrorHandler']);
$stream = fopen($url, 'a');
if ($this->filePermission !== null) {
@chmod($url, $this->filePermission);
try {
$stream = fopen($url, 'a');
if ($this->filePermission !== null) {
@chmod($url, $this->filePermission);
}
} finally {
restore_error_handler();
}
restore_error_handler();
if (!is_resource($stream)) {
$this->stream = null;

View file

@ -0,0 +1,115 @@
<?php declare(strict_types=1);
/*
* This file is part of the Monolog package.
*
* (c) Jordi Boggiano <j.boggiano@seld.be>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Monolog\Handler;
use Monolog\Logger;
use Monolog\Utils;
use Monolog\Formatter\FormatterInterface;
use Monolog\Formatter\LineFormatter;
use Swift_Message;
use Swift;
/**
* SwiftMailerHandler uses Swift_Mailer to send the emails
*
* @author Gyula Sallai
*
* @phpstan-import-type Record from \Monolog\Logger
* @deprecated Since Monolog 2.6. Use SymfonyMailerHandler instead.
*/
class SwiftMailerHandler extends MailHandler
{
/** @var \Swift_Mailer */
protected $mailer;
/** @var Swift_Message|callable(string, Record[]): Swift_Message */
private $messageTemplate;
/**
* @psalm-param Swift_Message|callable(string, Record[]): Swift_Message $message
*
* @param \Swift_Mailer $mailer The mailer to use
* @param callable|Swift_Message $message An example message for real messages, only the body will be replaced
*/
public function __construct(\Swift_Mailer $mailer, $message, $level = Logger::ERROR, bool $bubble = true)
{
parent::__construct($level, $bubble);
@trigger_error('The SwiftMailerHandler is deprecated since Monolog 2.6. Use SymfonyMailerHandler instead.', E_USER_DEPRECATED);
$this->mailer = $mailer;
$this->messageTemplate = $message;
}
/**
* {@inheritDoc}
*/
protected function send(string $content, array $records): void
{
$this->mailer->send($this->buildMessage($content, $records));
}
/**
* Gets the formatter for the Swift_Message subject.
*
* @param string|null $format The format of the subject
*/
protected function getSubjectFormatter(?string $format): FormatterInterface
{
return new LineFormatter($format);
}
/**
* Creates instance of Swift_Message to be sent
*
* @param string $content formatted email body to be sent
* @param array $records Log records that formed the content
* @return Swift_Message
*
* @phpstan-param Record[] $records
*/
protected function buildMessage(string $content, array $records): Swift_Message
{
$message = null;
if ($this->messageTemplate instanceof Swift_Message) {
$message = clone $this->messageTemplate;
$message->generateId();
} elseif (is_callable($this->messageTemplate)) {
$message = ($this->messageTemplate)($content, $records);
}
if (!$message instanceof Swift_Message) {
$record = reset($records);
throw new \InvalidArgumentException('Could not resolve message as instance of Swift_Message or a callable returning it' . ($record ? Utils::getRecordMessageForException($record) : ''));
}
if ($records) {
$subjectFormatter = $this->getSubjectFormatter($message->getSubject());
$message->setSubject($subjectFormatter->format($this->getHighestRecord($records)));
}
$mime = 'text/plain';
if ($this->isHtmlBody($content)) {
$mime = 'text/html';
}
$message->setBody($content, $mime);
/** @phpstan-ignore-next-line */
if (version_compare(Swift::VERSION, '6.0.0', '>=')) {
$message->setDate(new \DateTimeImmutable());
} else {
/** @phpstan-ignore-next-line */
$message->setDate(time());
}
return $message;
}
}

View file

@ -1,22 +1,22 @@
{
"name": "friendica-addons/phpmailer",
"description": "Replaces the default `mail()` function by the `PHPMailer` library",
"type": "friendica-addon",
"authors": [
{
"name": "Marcus Mueller",
"role": "Developer"
}
],
"require": {
"php": ">=7.0",
"phpmailer/phpmailer": "^6.1"
},
"license": "3-clause BSD license",
"minimum-stability": "stable",
"config": {
"optimize-autoloader": true,
"autoloader-suffix": "PhpMailerAddon",
"preferred-install": "dist"
}
"name": "friendica-addons/phpmailer",
"description": "Replaces the default `mail()` function by the `PHPMailer` library",
"type": "friendica-addon",
"authors": [
{
"name": "Marcus Mueller",
"role": "Developer"
}
],
"require": {
"php": ">=7.0",
"phpmailer/phpmailer": "^6.1"
},
"license": "3-clause BSD license",
"minimum-stability": "stable",
"config": {
"optimize-autoloader": true,
"autoloader-suffix": "PhpMailerAddon",
"preferred-install": "dist"
}
}

View file

@ -8,16 +8,16 @@
"packages": [
{
"name": "phpmailer/phpmailer",
"version": "v6.5.0",
"version": "v6.9.1",
"source": {
"type": "git",
"url": "https://github.com/PHPMailer/PHPMailer.git",
"reference": "a5b5c43e50b7fba655f793ad27303cd74c57363c"
"reference": "039de174cd9c17a8389754d3b877a2ed22743e18"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/PHPMailer/PHPMailer/zipball/a5b5c43e50b7fba655f793ad27303cd74c57363c",
"reference": "a5b5c43e50b7fba655f793ad27303cd74c57363c",
"url": "https://api.github.com/repos/PHPMailer/PHPMailer/zipball/039de174cd9c17a8389754d3b877a2ed22743e18",
"reference": "039de174cd9c17a8389754d3b877a2ed22743e18",
"shasum": ""
},
"require": {
@ -27,20 +27,25 @@
"php": ">=5.5.0"
},
"require-dev": {
"dealerdirect/phpcodesniffer-composer-installer": "^0.7.0",
"doctrine/annotations": "^1.2",
"dealerdirect/phpcodesniffer-composer-installer": "^1.0",
"doctrine/annotations": "^1.2.6 || ^1.13.3",
"php-parallel-lint/php-console-highlighter": "^1.0.0",
"php-parallel-lint/php-parallel-lint": "^1.3.2",
"phpcompatibility/php-compatibility": "^9.3.5",
"roave/security-advisories": "dev-latest",
"squizlabs/php_codesniffer": "^3.5.6",
"yoast/phpunit-polyfills": "^0.2.0"
"squizlabs/php_codesniffer": "^3.7.2",
"yoast/phpunit-polyfills": "^1.0.4"
},
"suggest": {
"decomplexity/SendOauth2": "Adapter for using XOAUTH2 authentication",
"ext-mbstring": "Needed to send email in multibyte encoding charset or decode encoded addresses",
"ext-openssl": "Needed for secure SMTP sending and DKIM signing",
"greew/oauth2-azure-provider": "Needed for Microsoft Azure XOAUTH2 authentication",
"hayageek/oauth2-yahoo": "Needed for Yahoo XOAUTH2 authentication",
"league/oauth2-google": "Needed for Google XOAUTH2 authentication",
"psr/log": "For optional PSR-3 debug logging",
"stevenmaguire/oauth2-microsoft": "Needed for Microsoft XOAUTH2 authentication",
"symfony/polyfill-mbstring": "To support UTF-8 if the Mbstring PHP extension is not enabled (^1.2)"
"symfony/polyfill-mbstring": "To support UTF-8 if the Mbstring PHP extension is not enabled (^1.2)",
"thenetworg/oauth2-azure": "Needed for Microsoft XOAUTH2 authentication"
},
"type": "library",
"autoload": {
@ -70,17 +75,13 @@
}
],
"description": "PHPMailer is a full-featured email creation and transfer class for PHP",
"support": {
"issues": "https://github.com/PHPMailer/PHPMailer/issues",
"source": "https://github.com/PHPMailer/PHPMailer/tree/v6.5.0"
},
"funding": [
{
"url": "https://github.com/Synchro",
"type": "github"
}
],
"time": "2021-06-16T14:33:43+00:00"
"time": "2023-11-25T22:23:28+00:00"
}
],
"packages-dev": [],
@ -93,5 +94,5 @@
"php": ">=7.0"
},
"platform-dev": [],
"plugin-api-version": "2.1.0"
"plugin-api-version": "1.1.0"
}

View file

@ -6,8 +6,10 @@ $vendorDir = dirname(dirname(__FILE__));
$baseDir = dirname($vendorDir);
return array(
'PHPMailer\\PHPMailer\\DSNConfigurator' => $vendorDir . '/phpmailer/phpmailer/src/DSNConfigurator.php',
'PHPMailer\\PHPMailer\\Exception' => $vendorDir . '/phpmailer/phpmailer/src/Exception.php',
'PHPMailer\\PHPMailer\\OAuth' => $vendorDir . '/phpmailer/phpmailer/src/OAuth.php',
'PHPMailer\\PHPMailer\\OAuthTokenProvider' => $vendorDir . '/phpmailer/phpmailer/src/OAuthTokenProvider.php',
'PHPMailer\\PHPMailer\\PHPMailer' => $vendorDir . '/phpmailer/phpmailer/src/PHPMailer.php',
'PHPMailer\\PHPMailer\\POP3' => $vendorDir . '/phpmailer/phpmailer/src/POP3.php',
'PHPMailer\\PHPMailer\\SMTP' => $vendorDir . '/phpmailer/phpmailer/src/SMTP.php',

View file

@ -21,8 +21,10 @@ class ComposerStaticInitPhpMailerAddon
);
public static $classMap = array (
'PHPMailer\\PHPMailer\\DSNConfigurator' => __DIR__ . '/..' . '/phpmailer/phpmailer/src/DSNConfigurator.php',
'PHPMailer\\PHPMailer\\Exception' => __DIR__ . '/..' . '/phpmailer/phpmailer/src/Exception.php',
'PHPMailer\\PHPMailer\\OAuth' => __DIR__ . '/..' . '/phpmailer/phpmailer/src/OAuth.php',
'PHPMailer\\PHPMailer\\OAuthTokenProvider' => __DIR__ . '/..' . '/phpmailer/phpmailer/src/OAuthTokenProvider.php',
'PHPMailer\\PHPMailer\\PHPMailer' => __DIR__ . '/..' . '/phpmailer/phpmailer/src/PHPMailer.php',
'PHPMailer\\PHPMailer\\POP3' => __DIR__ . '/..' . '/phpmailer/phpmailer/src/POP3.php',
'PHPMailer\\PHPMailer\\SMTP' => __DIR__ . '/..' . '/phpmailer/phpmailer/src/SMTP.php',

View file

@ -1,17 +1,17 @@
[
{
"name": "phpmailer/phpmailer",
"version": "v6.5.0",
"version_normalized": "6.5.0.0",
"version": "v6.9.1",
"version_normalized": "6.9.1.0",
"source": {
"type": "git",
"url": "https://github.com/PHPMailer/PHPMailer.git",
"reference": "a5b5c43e50b7fba655f793ad27303cd74c57363c"
"reference": "039de174cd9c17a8389754d3b877a2ed22743e18"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/PHPMailer/PHPMailer/zipball/a5b5c43e50b7fba655f793ad27303cd74c57363c",
"reference": "a5b5c43e50b7fba655f793ad27303cd74c57363c",
"url": "https://api.github.com/repos/PHPMailer/PHPMailer/zipball/039de174cd9c17a8389754d3b877a2ed22743e18",
"reference": "039de174cd9c17a8389754d3b877a2ed22743e18",
"shasum": ""
},
"require": {
@ -21,22 +21,27 @@
"php": ">=5.5.0"
},
"require-dev": {
"dealerdirect/phpcodesniffer-composer-installer": "^0.7.0",
"doctrine/annotations": "^1.2",
"dealerdirect/phpcodesniffer-composer-installer": "^1.0",
"doctrine/annotations": "^1.2.6 || ^1.13.3",
"php-parallel-lint/php-console-highlighter": "^1.0.0",
"php-parallel-lint/php-parallel-lint": "^1.3.2",
"phpcompatibility/php-compatibility": "^9.3.5",
"roave/security-advisories": "dev-latest",
"squizlabs/php_codesniffer": "^3.5.6",
"yoast/phpunit-polyfills": "^0.2.0"
"squizlabs/php_codesniffer": "^3.7.2",
"yoast/phpunit-polyfills": "^1.0.4"
},
"suggest": {
"decomplexity/SendOauth2": "Adapter for using XOAUTH2 authentication",
"ext-mbstring": "Needed to send email in multibyte encoding charset or decode encoded addresses",
"ext-openssl": "Needed for secure SMTP sending and DKIM signing",
"greew/oauth2-azure-provider": "Needed for Microsoft Azure XOAUTH2 authentication",
"hayageek/oauth2-yahoo": "Needed for Yahoo XOAUTH2 authentication",
"league/oauth2-google": "Needed for Google XOAUTH2 authentication",
"psr/log": "For optional PSR-3 debug logging",
"stevenmaguire/oauth2-microsoft": "Needed for Microsoft XOAUTH2 authentication",
"symfony/polyfill-mbstring": "To support UTF-8 if the Mbstring PHP extension is not enabled (^1.2)"
"symfony/polyfill-mbstring": "To support UTF-8 if the Mbstring PHP extension is not enabled (^1.2)",
"thenetworg/oauth2-azure": "Needed for Microsoft XOAUTH2 authentication"
},
"time": "2021-06-16T14:33:43+00:00",
"time": "2023-11-25T22:23:28+00:00",
"type": "library",
"installation-source": "dist",
"autoload": {
@ -66,10 +71,6 @@
}
],
"description": "PHPMailer is a full-featured email creation and transfer class for PHP",
"support": {
"issues": "https://github.com/PHPMailer/PHPMailer/issues",
"source": "https://github.com/PHPMailer/PHPMailer/tree/v6.5.0"
},
"funding": [
{
"url": "https://github.com/Synchro",

View file

@ -0,0 +1,15 @@
root = true
[*]
charset = utf-8
indent_size = 4
indent_style = space
end_of_line = lf
insert_final_newline = true
trim_trailing_whitespace = true
[*.md]
trim_trailing_whitespace = false
[*.{yml,yaml}]
indent_size = 2

View file

@ -1,14 +1,22 @@
[![SWUbanner](https://raw.githubusercontent.com/vshymanskyy/StandWithUkraine/main/banner2-direct.svg)](https://supportukrainenow.org/)
![PHPMailer](https://raw.github.com/PHPMailer/PHPMailer/master/examples/images/phpmailer.png)
# PHPMailer A full-featured email creation and transfer class for PHP
[![Test status](https://github.com/PHPMailer/PHPMailer/workflows/Tests/badge.svg)](https://github.com/PHPMailer/PHPMailer/actions) [![Latest Stable Version](https://poser.pugx.org/phpmailer/phpmailer/v/stable.svg)](https://packagist.org/packages/phpmailer/phpmailer) [![Total Downloads](https://poser.pugx.org/phpmailer/phpmailer/downloads)](https://packagist.org/packages/phpmailer/phpmailer) [![License](https://poser.pugx.org/phpmailer/phpmailer/license.svg)](https://packagist.org/packages/phpmailer/phpmailer) [![API Docs](https://github.com/phpmailer/phpmailer/workflows/Docs/badge.svg)](https://phpmailer.github.io/PHPMailer/)
[![Test status](https://github.com/PHPMailer/PHPMailer/workflows/Tests/badge.svg)](https://github.com/PHPMailer/PHPMailer/actions)
[![codecov.io](https://codecov.io/gh/PHPMailer/PHPMailer/branch/master/graph/badge.svg?token=iORZpwmYmM)](https://codecov.io/gh/PHPMailer/PHPMailer)
[![Latest Stable Version](https://poser.pugx.org/phpmailer/phpmailer/v/stable.svg)](https://packagist.org/packages/phpmailer/phpmailer)
[![Total Downloads](https://poser.pugx.org/phpmailer/phpmailer/downloads)](https://packagist.org/packages/phpmailer/phpmailer)
[![License](https://poser.pugx.org/phpmailer/phpmailer/license.svg)](https://packagist.org/packages/phpmailer/phpmailer)
[![API Docs](https://github.com/phpmailer/phpmailer/workflows/Docs/badge.svg)](https://phpmailer.github.io/PHPMailer/)
[![OpenSSF Scorecard](https://api.securityscorecards.dev/projects/github.com/PHPMailer/PHPMailer/badge)](https://api.securityscorecards.dev/projects/github.com/PHPMailer/PHPMailer)
## Features
- Probably the world's most popular code for sending email from PHP!
- Used by many open-source projects: WordPress, Drupal, 1CRM, SugarCRM, Yii, Joomla! and many more
- Integrated SMTP support send without a local mail server
- Send emails with multiple To, CC, BCC and Reply-to addresses
- Send emails with multiple To, CC, BCC, and Reply-to addresses
- Multipart/alternative emails for mail clients that do not read HTML email
- Add attachments, including inline
- Support for UTF-8 content and 8bit, base64, binary, and quoted-printable encodings
@ -17,7 +25,7 @@
- Protects against header injection attacks
- Error messages in over 50 languages!
- DKIM and S/MIME signing support
- Compatible with PHP 5.5 and later, including PHP 8.0
- Compatible with PHP 5.5 and later, including PHP 8.2
- Namespaced to prevent name clashes
- Much more!
@ -30,7 +38,7 @@ The PHP `mail()` function usually sends via a local mail server, typically front
*Please* don't be tempted to do it yourself if you don't use PHPMailer, there are many other excellent libraries that
you should look at before rolling your own. Try [SwiftMailer](https://swiftmailer.symfony.com/)
, [Laminas/Mail](https://docs.laminas.dev/laminas-mail/), [ZetaComponents](https://github.com/zetacomponents/Mail) etc.
, [Laminas/Mail](https://docs.laminas.dev/laminas-mail/), [ZetaComponents](https://github.com/zetacomponents/Mail), etc.
## License
This software is distributed under the [LGPL 2.1](http://www.gnu.org/licenses/lgpl-2.1.html) license, along with the [GPL Cooperation Commitment](https://gplcc.github.io/gplcc/). Please read [LICENSE](https://github.com/PHPMailer/PHPMailer/blob/master/LICENSE) for information on the software availability and distribution.
@ -39,7 +47,7 @@ This software is distributed under the [LGPL 2.1](http://www.gnu.org/licenses/lg
PHPMailer is available on [Packagist](https://packagist.org/packages/phpmailer/phpmailer) (using semantic versioning), and installation via [Composer](https://getcomposer.org) is the recommended way to install PHPMailer. Just add this line to your `composer.json` file:
```json
"phpmailer/phpmailer": "^6.2"
"phpmailer/phpmailer": "^6.9.1"
```
or run
@ -50,7 +58,8 @@ composer require phpmailer/phpmailer
Note that the `vendor` folder and the `vendor/autoload.php` script are generated by Composer; they are not part of PHPMailer.
If you want to use the Gmail XOAUTH2 authentication class, you will also need to add a dependency on the `league/oauth2-client` package in your `composer.json`.
If you want to use XOAUTH2 authentication, you will also need to add a dependency on the `league/oauth2-client` and appropriate service adapters package in your `composer.json`, or take a look at
by @decomplexity's [SendOauth2 wrapper](https://github.com/decomplexity/SendOauth2), especially if you're using Microsoft services.
Alternatively, if you're not using Composer, you
can [download PHPMailer as a zip file](https://github.com/PHPMailer/PHPMailer/archive/master.zip), (note that docs and examples are not included in the zip file), then copy the contents of the PHPMailer folder into one of the `include_path` directories specified in your PHP configuration and load each class file manually:
@ -89,7 +98,7 @@ use PHPMailer\PHPMailer\Exception;
//Load Composer's autoloader
require 'vendor/autoload.php';
//Instantiation and passing `true` enables exceptions
//Create an instance; passing `true` enables exceptions
$mail = new PHPMailer(true);
try {
@ -100,8 +109,8 @@ try {
$mail->SMTPAuth = true; //Enable SMTP authentication
$mail->Username = 'user@example.com'; //SMTP username
$mail->Password = 'secret'; //SMTP password
$mail->SMTPSecure = PHPMailer::ENCRYPTION_STARTTLS; //Enable TLS encryption; `PHPMailer::ENCRYPTION_SMTPS` encouraged
$mail->Port = 587; //TCP port to connect to, use 465 for `PHPMailer::ENCRYPTION_SMTPS` above
$mail->SMTPSecure = PHPMailer::ENCRYPTION_SMTPS; //Enable implicit TLS encryption
$mail->Port = 465; //TCP port to connect to; use 587 if you have set `SMTPSecure = PHPMailer::ENCRYPTION_STARTTLS`
//Recipients
$mail->setFrom('from@example.com', 'Mailer');
@ -128,21 +137,21 @@ try {
}
```
You'll find plenty to play with in the [examples](https://github.com/PHPMailer/PHPMailer/tree/master/examples) folder, which covers many common scenarios including sending through gmail, building contact forms, sending to mailing lists, and more.
You'll find plenty to play with in the [examples](https://github.com/PHPMailer/PHPMailer/tree/master/examples) folder, which covers many common scenarios including sending through Gmail, building contact forms, sending to mailing lists, and more.
If you are re-using the instance (e.g. when sending to a mailing list), you may need to clear the recipient list to avoid sending duplicate messages. See [the mailing list example](https://github.com/PHPMailer/PHPMailer/blob/master/examples/mailing_list.phps) for further guidance.
That's it. You should now be ready to use PHPMailer!
## Localization
PHPMailer defaults to English, but in the [language](https://github.com/PHPMailer/PHPMailer/tree/master/language/) folder you'll find many translations for PHPMailer error messages that you may encounter. Their filenames contain [ISO 639-1](http://en.wikipedia.org/wiki/ISO_639-1) language code for the translations, for example `fr` for French. To specify a language, you need to tell PHPMailer which one to use, like this:
PHPMailer defaults to English, but in the [language](https://github.com/PHPMailer/PHPMailer/tree/master/language/) folder, you'll find many translations for PHPMailer error messages that you may encounter. Their filenames contain [ISO 639-1](http://en.wikipedia.org/wiki/ISO_639-1) language code for the translations, for example `fr` for French. To specify a language, you need to tell PHPMailer which one to use, like this:
```php
//To load the French version
$mail->setLanguage('fr', '/optional/path/to/language/directory/');
```
We welcome corrections and new languages if you're looking for corrections, run the [PHPMailerLangTest.php](https://github.com/PHPMailer/PHPMailer/tree/master/test/PHPMailerLangTest.php) script in the tests folder and it will show any missing translations.
We welcome corrections and new languages if you're looking for corrections, run the [Language/TranslationCompletenessTest.php](https://github.com/PHPMailer/PHPMailer/blob/master/test/Language/TranslationCompletenessTest.php) script in the tests folder and it will show any missing translations.
## Documentation
Start reading at the [GitHub wiki](https://github.com/PHPMailer/PHPMailer/wiki). If you're having trouble, head for [the troubleshooting guide](https://github.com/PHPMailer/PHPMailer/wiki/Troubleshooting) as it's frequently updated.
@ -170,9 +179,9 @@ Please disclose any vulnerabilities found responsibly report security issues
See [SECURITY](https://github.com/PHPMailer/PHPMailer/tree/master/SECURITY.md) and [PHPMailer's security advisories on GitHub](https://github.com/PHPMailer/PHPMailer/security).
## Contributing
Please submit bug reports, suggestions and pull requests to the [GitHub issue tracker](https://github.com/PHPMailer/PHPMailer/issues).
Please submit bug reports, suggestions, and pull requests to the [GitHub issue tracker](https://github.com/PHPMailer/PHPMailer/issues).
We're particularly interested in fixing edge-cases, expanding test coverage and updating translations.
We're particularly interested in fixing edge cases, expanding test coverage, and updating translations.
If you found a mistake in the docs, or want to add something, go ahead and amend the wiki anyone can edit it.
@ -196,7 +205,7 @@ Donations are very welcome, whether in beer 🍺, T-shirts 👕, or cold, hard c
Available as part of the Tidelift Subscription.
The maintainers of PHPMailer and thousands of other packages are working with Tidelift to deliver commercial
support and maintenance for the open source packages you use to build your applications. Save time, reduce risk, and
support and maintenance for the open-source packages you use to build your applications. Save time, reduce risk, and
improve code health, while paying the maintainers of the exact packages you
use. [Learn more.](https://tidelift.com/subscription/pkg/packagist-phpmailer-phpmailer?utm_source=packagist-phpmailer-phpmailer&utm_medium=referral&utm_campaign=enterprise&utm_term=repo)
@ -214,9 +223,9 @@ See [changelog](changelog.md).
### What's changed since moving from SourceForge?
- Official successor to the SourceForge and Google Code projects.
- Test suite.
- Continuous integration with Github Actions.
- Continuous integration with GitHub Actions.
- Composer support.
- Public development.
- Additional languages and language strings.
- CRAM-MD5 authentication support.
- Preserves full repo history of authors, commits and branches from the original SourceForge project.
- Preserves full repo history of authors, commits, and branches from the original SourceForge project.

View file

@ -1 +1 @@
6.5.0
6.9.1

View file

@ -25,6 +25,11 @@
"type": "github"
}
],
"config": {
"allow-plugins": {
"dealerdirect/phpcodesniffer-composer-installer": true
}
},
"require": {
"php": ">=5.5.0",
"ext-ctype": "*",
@ -32,19 +37,24 @@
"ext-hash": "*"
},
"require-dev": {
"dealerdirect/phpcodesniffer-composer-installer": "^0.7.0",
"doctrine/annotations": "^1.2",
"dealerdirect/phpcodesniffer-composer-installer": "^1.0",
"doctrine/annotations": "^1.2.6 || ^1.13.3",
"php-parallel-lint/php-console-highlighter": "^1.0.0",
"php-parallel-lint/php-parallel-lint": "^1.3.2",
"phpcompatibility/php-compatibility": "^9.3.5",
"roave/security-advisories": "dev-latest",
"squizlabs/php_codesniffer": "^3.5.6",
"yoast/phpunit-polyfills": "^0.2.0"
"squizlabs/php_codesniffer": "^3.7.2",
"yoast/phpunit-polyfills": "^1.0.4"
},
"suggest": {
"decomplexity/SendOauth2": "Adapter for using XOAUTH2 authentication",
"ext-mbstring": "Needed to send email in multibyte encoding charset or decode encoded addresses",
"ext-openssl": "Needed for secure SMTP sending and DKIM signing",
"greew/oauth2-azure-provider": "Needed for Microsoft Azure XOAUTH2 authentication",
"hayageek/oauth2-yahoo": "Needed for Yahoo XOAUTH2 authentication",
"league/oauth2-google": "Needed for Google XOAUTH2 authentication",
"psr/log": "For optional PSR-3 debug logging",
"stevenmaguire/oauth2-microsoft": "Needed for Microsoft XOAUTH2 authentication",
"thenetworg/oauth2-azure": "Needed for Microsoft XOAUTH2 authentication",
"symfony/polyfill-mbstring": "To support UTF-8 if the Mbstring PHP extension is not enabled (^1.2)"
},
"autoload": {
@ -60,6 +70,10 @@
"license": "LGPL-2.1-only",
"scripts": {
"check": "./vendor/bin/phpcs",
"test": "./vendor/bin/phpunit"
"test": "./vendor/bin/phpunit --no-coverage",
"coverage": "./vendor/bin/phpunit",
"lint": [
"@php ./vendor/php-parallel-lint/php-parallel-lint/parallel-lint . --show-deprecated -e php,phps --exclude vendor --exclude .git --exclude build"
]
}
}

View file

@ -44,14 +44,31 @@ use League\OAuth2\Client\Provider\Google;
use Hayageek\OAuth2\Client\Provider\Yahoo;
//@see https://github.com/stevenmaguire/oauth2-microsoft
use Stevenmaguire\OAuth2\Client\Provider\Microsoft;
//@see https://github.com/greew/oauth2-azure-provider
use Greew\OAuth2\Client\Provider\Azure;
if (!isset($_GET['code']) && !isset($_GET['provider'])) {
if (!isset($_GET['code']) && !isset($_POST['provider'])) {
?>
<html>
<body>Select Provider:<br>
<a href='?provider=Google'>Google</a><br>
<a href='?provider=Yahoo'>Yahoo</a><br>
<a href='?provider=Microsoft'>Microsoft/Outlook/Hotmail/Live/Office365</a><br>
<body>
<form method="post">
<h1>Select Provider</h1>
<input type="radio" name="provider" value="Google" id="providerGoogle">
<label for="providerGoogle">Google</label><br>
<input type="radio" name="provider" value="Yahoo" id="providerYahoo">
<label for="providerYahoo">Yahoo</label><br>
<input type="radio" name="provider" value="Microsoft" id="providerMicrosoft">
<label for="providerMicrosoft">Microsoft</label><br>
<input type="radio" name="provider" value="Azure" id="providerAzure">
<label for="providerAzure">Azure</label><br>
<h1>Enter id and secret</h1>
<p>These details are obtained by setting up an app in your provider's developer console.
</p>
<p>ClientId: <input type="text" name="clientId"><p>
<p>ClientSecret: <input type="text" name="clientSecret"></p>
<p>TenantID (only relevant for Azure): <input type="text" name="tenantId"></p>
<input type="submit" value="Continue">
</form>
</body>
</html>
<?php
@ -63,21 +80,29 @@ require 'vendor/autoload.php';
session_start();
$providerName = '';
$clientId = '';
$clientSecret = '';
$tenantId = '';
if (array_key_exists('provider', $_GET)) {
$providerName = $_GET['provider'];
if (array_key_exists('provider', $_POST)) {
$providerName = $_POST['provider'];
$clientId = $_POST['clientId'];
$clientSecret = $_POST['clientSecret'];
$tenantId = $_POST['tenantId'];
$_SESSION['provider'] = $providerName;
$_SESSION['clientId'] = $clientId;
$_SESSION['clientSecret'] = $clientSecret;
$_SESSION['tenantId'] = $tenantId;
} elseif (array_key_exists('provider', $_SESSION)) {
$providerName = $_SESSION['provider'];
}
if (!in_array($providerName, ['Google', 'Microsoft', 'Yahoo'])) {
exit('Only Google, Microsoft and Yahoo OAuth2 providers are currently supported in this script.');
$clientId = $_SESSION['clientId'];
$clientSecret = $_SESSION['clientSecret'];
$tenantId = $_SESSION['tenantId'];
}
//These details are obtained by setting up an app in the Google developer console,
//or whichever provider you're using.
$clientId = 'RANDOMCHARS-----duv1n2.apps.googleusercontent.com';
$clientSecret = 'RANDOMCHARS-----lGyjPcRtvP';
//If you don't want to use the built-in form, set your client id and secret here
//$clientId = 'RANDOMCHARS-----duv1n2.apps.googleusercontent.com';
//$clientSecret = 'RANDOMCHARS-----lGyjPcRtvP';
//If this automatic URL doesn't work, set it yourself manually to the URL of this script
$redirectUri = (isset($_SERVER['HTTPS']) ? 'https://' : 'http://') . $_SERVER['HTTP_HOST'] . $_SERVER['PHP_SELF'];
@ -114,6 +139,17 @@ switch ($providerName) {
]
];
break;
case 'Azure':
$params['tenantId'] = $tenantId;
$provider = new Azure($params);
$options = [
'scope' => [
'https://outlook.office.com/SMTP.Send',
'offline_access'
]
];
break;
}
if (null === $provider) {

View file

@ -0,0 +1,35 @@
<?php
/**
* Assamese PHPMailer language file: refer to English translation for definitive list
* @package PHPMailer
* @author Manish Sarkar <manish.n.manish@gmail.com>
*/
$PHPMAILER_LANG['authenticate'] = 'SMTP ত্ৰুটি: প্ৰমাণীকৰণ কৰিব নোৱাৰি';
$PHPMAILER_LANG['buggy_php'] = 'আপোনাৰ PHP সংস্কৰণ এটা বাগৰ দ্বাৰা প্ৰভাৱিত হয় যাৰ ফলত নষ্ট বাৰ্তা হব পাৰে । ইয়াক সমাধান কৰিবলে, প্ৰেৰণ কৰিবলে SMTP ব্যৱহাৰ কৰক, আপোনাৰ php.ini ত mail.add_x_header বিকল্প নিষ্ক্ৰিয় কৰক, MacOS বা Linux লৈ সলনি কৰক, বা আপোনাৰ PHP সংস্কৰণ 7.0.17+ বা 7.1.3+ লৈ সলনি কৰক ।';
$PHPMAILER_LANG['connect_host'] = 'SMTP ত্ৰুটি: SMTP চাৰ্ভাৰৰ সৈতে সংযোগ কৰিবলে অক্ষম';
$PHPMAILER_LANG['data_not_accepted'] = 'SMTP ত্ৰুটি: তথ্য গ্ৰহণ কৰা হোৱা নাই';
$PHPMAILER_LANG['empty_message'] = 'বাৰ্তাৰ মূখ্য অংশ খালী।';
$PHPMAILER_LANG['encoding'] = 'অজ্ঞাত এনকোডিং: ';
$PHPMAILER_LANG['execute'] = 'এক্সিকিউট কৰিব নোৱাৰি: ';
$PHPMAILER_LANG['extension_missing'] = 'সম্প্ৰসাৰণ নোহোৱা হৈছে: ';
$PHPMAILER_LANG['file_access'] = 'ফাইল অভিগম কৰিবলে অক্ষম: ';
$PHPMAILER_LANG['file_open'] = 'ফাইল ত্ৰুটি: ফাইল খোলিবলৈ অক্ষম: ';
$PHPMAILER_LANG['from_failed'] = 'নিম্নলিখিত প্ৰেৰকৰ ঠিকনা(সমূহ) ব্যৰ্থ: ';
$PHPMAILER_LANG['instantiate'] = 'মেইল ফাংচনৰ এটা উদাহৰণ সৃষ্টি কৰিবলে অক্ষম';
$PHPMAILER_LANG['invalid_address'] = 'প্ৰেৰণ কৰিব নোৱাৰি: অবৈধ ইমেইল ঠিকনা: ';
$PHPMAILER_LANG['invalid_header'] = 'অবৈধ হেডাৰৰ নাম বা মান';
$PHPMAILER_LANG['invalid_hostentry'] = 'অবৈধ হোষ্টেন্ট্ৰি: ';
$PHPMAILER_LANG['invalid_host'] = 'অবৈধ হস্ট:';
$PHPMAILER_LANG['mailer_not_supported'] = 'মেইলাৰ সমৰ্থিত নহয়।';
$PHPMAILER_LANG['provide_address'] = 'আপুনি অন্ততঃ এটা গন্তব্য ইমেইল ঠিকনা দিব লাগিব';
$PHPMAILER_LANG['recipients_failed'] = 'SMTP ত্ৰুটি: নিম্নলিখিত গন্তব্যস্থানসমূহ ব্যৰ্থ: ';
$PHPMAILER_LANG['signing'] = 'স্বাক্ষৰ কৰাত ব্যৰ্থ: ';
$PHPMAILER_LANG['smtp_code'] = 'SMTP কড: ';
$PHPMAILER_LANG['smtp_code_ex'] = 'অতিৰিক্ত SMTP তথ্য: ';
$PHPMAILER_LANG['smtp_detail'] = 'বিৱৰণ:';
$PHPMAILER_LANG['smtp_connect_failed'] = 'SMTP সংযোগ() ব্যৰ্থ';
$PHPMAILER_LANG['smtp_error'] = 'SMTP চাৰ্ভাৰৰ ত্ৰুটি: ';
$PHPMAILER_LANG['variable_set'] = 'চলক নিৰ্ধাৰণ কৰিব পৰা নগল: ';
$PHPMAILER_LANG['extension_missing'] = 'অনুপস্থিত সম্প্ৰসাৰণ: ';

View file

@ -0,0 +1,35 @@
<?php
/**
* Bengali PHPMailer language file: refer to English translation for definitive list
* @package PHPMailer
* @author Manish Sarkar <manish.n.manish@gmail.com>
*/
$PHPMAILER_LANG['authenticate'] = 'SMTP ত্রুটি: প্রমাণীকরণ করতে অক্ষম৷';
$PHPMAILER_LANG['buggy_php'] = 'আপনার PHP সংস্করণ একটি বাগ দ্বারা প্রভাবিত হয় যার ফলে দূষিত বার্তা হতে পারে। এটি ঠিক করতে, পাঠাতে SMTP ব্যবহার করুন, আপনার php.ini এ mail.add_x_header বিকল্পটি নিষ্ক্রিয় করুন, MacOS বা Linux-এ স্যুইচ করুন, অথবা আপনার PHP সংস্করণকে 7.0.17+ বা 7.1.3+ এ পরিবর্তন করুন।';
$PHPMAILER_LANG['connect_host'] = 'SMTP ত্রুটি: SMTP সার্ভারের সাথে সংযোগ করতে অক্ষম৷';
$PHPMAILER_LANG['data_not_accepted'] = 'SMTP ত্রুটি: ডেটা গ্রহণ করা হয়নি৷';
$PHPMAILER_LANG['empty_message'] = 'বার্তার অংশটি খালি।';
$PHPMAILER_LANG['encoding'] = 'অজানা এনকোডিং: ';
$PHPMAILER_LANG['execute'] = 'নির্বাহ করতে অক্ষম: ';
$PHPMAILER_LANG['extension_missing'] = 'এক্সটেনশন অনুপস্থিত:';
$PHPMAILER_LANG['file_access'] = 'ফাইল অ্যাক্সেস করতে অক্ষম: ';
$PHPMAILER_LANG['file_open'] = 'ফাইল ত্রুটি: ফাইল খুলতে অক্ষম: ';
$PHPMAILER_LANG['from_failed'] = 'নিম্নলিখিত প্রেরকের ঠিকানা(গুলি) ব্যর্থ হয়েছে: ';
$PHPMAILER_LANG['instantiate'] = 'মেল ফাংশনের একটি উদাহরণ তৈরি করতে অক্ষম৷';
$PHPMAILER_LANG['invalid_address'] = 'পাঠাতে অক্ষম: অবৈধ ইমেল ঠিকানা: ';
$PHPMAILER_LANG['invalid_header'] = 'অবৈধ হেডার নাম বা মান';
$PHPMAILER_LANG['invalid_hostentry'] = 'অবৈধ হোস্টেন্ট্রি: ';
$PHPMAILER_LANG['invalid_host'] = 'অবৈধ হোস্ট:';
$PHPMAILER_LANG['mailer_not_supported'] = 'মেইলার সমর্থিত নয়।';
$PHPMAILER_LANG['provide_address'] = 'আপনাকে অবশ্যই অন্তত একটি গন্তব্য ইমেল ঠিকানা প্রদান করতে হবে৷';
$PHPMAILER_LANG['recipients_failed'] = 'SMTP ত্রুটি: নিম্নলিখিত গন্তব্যগুলি ব্যর্থ হয়েছে: ';
$PHPMAILER_LANG['signing'] = 'স্বাক্ষর করতে ব্যর্থ হয়েছে: ';
$PHPMAILER_LANG['smtp_code'] = 'SMTP কোড: ';
$PHPMAILER_LANG['smtp_code_ex'] = 'অতিরিক্ত SMTP তথ্য:';
$PHPMAILER_LANG['smtp_detail'] = 'বর্ণনা: ';
$PHPMAILER_LANG['smtp_connect_failed'] = 'SMTP সংযোগ() ব্যর্থ হয়েছে৷';
$PHPMAILER_LANG['smtp_error'] = 'SMTP সার্ভার ত্রুটি: ';
$PHPMAILER_LANG['variable_set'] = 'পরিবর্তনশীল সেট করা যায়নি: ';
$PHPMAILER_LANG['extension_missing'] = 'অনুপস্থিত এক্সটেনশন: ';

Some files were not shown because too many files have changed in this diff Show more