first commit
This commit is contained in:
+31
@@ -0,0 +1,31 @@
|
||||
# =================== MySQL DEFAULT ===================
|
||||
DEFAULT_DB_DRIVER=mysql
|
||||
DEFAULT_DB_HOST=127.0.0.1
|
||||
DEFAULT_DB_PORT=3306
|
||||
DEFAULT_DB_NAME=meu_banco
|
||||
DEFAULT_DB_USERNAME=root
|
||||
DEFAULT_DB_PASSWORD=senha123
|
||||
DEFAULT_DB_CHARSET=utf8mb4
|
||||
|
||||
# =================== PostgreSQL DEFAULT ===================
|
||||
PGSQL_DB_DRIVER=pgsql
|
||||
PGSQL_DB_HOST=127.0.0.1
|
||||
PGSQL_DB_PORT=5432
|
||||
PGSQL_DB_NAME=meu_banco_pg
|
||||
PGSQL_DB_USERNAME=postgres
|
||||
PGSQL_DB_PASSWORD=senha123
|
||||
PGSQL_DB_SCHEMA=public
|
||||
PGSQL_DB_TIMEZONE=UTC
|
||||
PGSQL_DB_SSLMODE=disable
|
||||
|
||||
# =================== Redis DEFAULT ===================
|
||||
REDIS_HOST=127.0.0.1
|
||||
REDIS_PORT=6379
|
||||
REDIS_PASSWORD=
|
||||
REDIS_DATABASE=0
|
||||
|
||||
# =================== Redis CACHE (opcional) ===================
|
||||
REDIS_CACHE_HOST=127.0.0.1
|
||||
REDIS_CACHE_PORT=6379
|
||||
REDIS_CACHE_PASSWORD=
|
||||
REDIS_CACHE_DATABASE=1
|
||||
@@ -0,0 +1,2 @@
|
||||
# Para não subir a pasta .git
|
||||
.git/
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
{
|
||||
"name": "claudecio/krothiumapi",
|
||||
"description": "Framework PHP para desenvolvimento rápido de api para aplicações web.",
|
||||
"type": "library",
|
||||
"license": "MIT",
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"KrothiumAPI\\": "src/"
|
||||
}
|
||||
},
|
||||
"require": {
|
||||
"php": ">=8.2",
|
||||
"predis/predis": "v3.4.0",
|
||||
"vlucas/phpdotenv": "v5.6.2"
|
||||
},
|
||||
"authors": [
|
||||
{
|
||||
"name": "Claudecio Martins",
|
||||
"email": "contato@claudecio.is-a.dev",
|
||||
"role": "Developer"
|
||||
}
|
||||
]
|
||||
}
|
||||
+610
@@ -0,0 +1,610 @@
|
||||
{
|
||||
"_readme": [
|
||||
"This file locks the dependencies of your project to a known state",
|
||||
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
||||
"This file is @generated automatically"
|
||||
],
|
||||
"content-hash": "4f1705049fc0c5e519bca82b08ba05dc",
|
||||
"packages": [
|
||||
{
|
||||
"name": "graham-campbell/result-type",
|
||||
"version": "v1.1.4",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/GrahamCampbell/Result-Type.git",
|
||||
"reference": "e01f4a821471308ba86aa202fed6698b6b695e3b"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/GrahamCampbell/Result-Type/zipball/e01f4a821471308ba86aa202fed6698b6b695e3b",
|
||||
"reference": "e01f4a821471308ba86aa202fed6698b6b695e3b",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": "^7.2.5 || ^8.0",
|
||||
"phpoption/phpoption": "^1.9.5"
|
||||
},
|
||||
"require-dev": {
|
||||
"phpunit/phpunit": "^8.5.41 || ^9.6.22 || ^10.5.45 || ^11.5.7"
|
||||
},
|
||||
"type": "library",
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"GrahamCampbell\\ResultType\\": "src/"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Graham Campbell",
|
||||
"email": "hello@gjcampbell.co.uk",
|
||||
"homepage": "https://github.com/GrahamCampbell"
|
||||
}
|
||||
],
|
||||
"description": "An Implementation Of The Result Type",
|
||||
"keywords": [
|
||||
"Graham Campbell",
|
||||
"GrahamCampbell",
|
||||
"Result Type",
|
||||
"Result-Type",
|
||||
"result"
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/GrahamCampbell/Result-Type/issues",
|
||||
"source": "https://github.com/GrahamCampbell/Result-Type/tree/v1.1.4"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
"url": "https://github.com/GrahamCampbell",
|
||||
"type": "github"
|
||||
},
|
||||
{
|
||||
"url": "https://tidelift.com/funding/github/packagist/graham-campbell/result-type",
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2025-12-27T19:43:20+00:00"
|
||||
},
|
||||
{
|
||||
"name": "phpoption/phpoption",
|
||||
"version": "1.9.5",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/schmittjoh/php-option.git",
|
||||
"reference": "75365b91986c2405cf5e1e012c5595cd487a98be"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/schmittjoh/php-option/zipball/75365b91986c2405cf5e1e012c5595cd487a98be",
|
||||
"reference": "75365b91986c2405cf5e1e012c5595cd487a98be",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": "^7.2.5 || ^8.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"bamarni/composer-bin-plugin": "^1.8.2",
|
||||
"phpunit/phpunit": "^8.5.44 || ^9.6.25 || ^10.5.53 || ^11.5.34"
|
||||
},
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"bamarni-bin": {
|
||||
"bin-links": true,
|
||||
"forward-command": false
|
||||
},
|
||||
"branch-alias": {
|
||||
"dev-master": "1.9-dev"
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"PhpOption\\": "src/PhpOption/"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"Apache-2.0"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Johannes M. Schmitt",
|
||||
"email": "schmittjoh@gmail.com",
|
||||
"homepage": "https://github.com/schmittjoh"
|
||||
},
|
||||
{
|
||||
"name": "Graham Campbell",
|
||||
"email": "hello@gjcampbell.co.uk",
|
||||
"homepage": "https://github.com/GrahamCampbell"
|
||||
}
|
||||
],
|
||||
"description": "Option Type for PHP",
|
||||
"keywords": [
|
||||
"language",
|
||||
"option",
|
||||
"php",
|
||||
"type"
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/schmittjoh/php-option/issues",
|
||||
"source": "https://github.com/schmittjoh/php-option/tree/1.9.5"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
"url": "https://github.com/GrahamCampbell",
|
||||
"type": "github"
|
||||
},
|
||||
{
|
||||
"url": "https://tidelift.com/funding/github/packagist/phpoption/phpoption",
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2025-12-27T19:41:33+00:00"
|
||||
},
|
||||
{
|
||||
"name": "predis/predis",
|
||||
"version": "v3.4.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/predis/predis.git",
|
||||
"reference": "1183f5732e6b10efd33f64984a96726eaecb59aa"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/predis/predis/zipball/1183f5732e6b10efd33f64984a96726eaecb59aa",
|
||||
"reference": "1183f5732e6b10efd33f64984a96726eaecb59aa",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": "^7.2 || ^8.0",
|
||||
"psr/http-message": "^1.0|^2.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"friendsofphp/php-cs-fixer": "^3.3",
|
||||
"phpstan/phpstan": "^1.9",
|
||||
"phpunit/phpcov": "^6.0 || ^8.0",
|
||||
"phpunit/phpunit": "^8.0 || ~9.4.4"
|
||||
},
|
||||
"suggest": {
|
||||
"ext-relay": "Faster connection with in-memory caching (>=0.6.2)"
|
||||
},
|
||||
"type": "library",
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Predis\\": "src/"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Till Krüss",
|
||||
"homepage": "https://till.im",
|
||||
"role": "Maintainer"
|
||||
}
|
||||
],
|
||||
"description": "A flexible and feature-complete Redis/Valkey client for PHP.",
|
||||
"homepage": "http://github.com/predis/predis",
|
||||
"keywords": [
|
||||
"nosql",
|
||||
"predis",
|
||||
"redis"
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/predis/predis/issues",
|
||||
"source": "https://github.com/predis/predis/tree/v3.4.0"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
"url": "https://github.com/sponsors/tillkruss",
|
||||
"type": "github"
|
||||
}
|
||||
],
|
||||
"time": "2026-02-11T17:30:28+00:00"
|
||||
},
|
||||
{
|
||||
"name": "psr/http-message",
|
||||
"version": "2.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/php-fig/http-message.git",
|
||||
"reference": "402d35bcb92c70c026d1a6a9883f06b2ead23d71"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/php-fig/http-message/zipball/402d35bcb92c70c026d1a6a9883f06b2ead23d71",
|
||||
"reference": "402d35bcb92c70c026d1a6a9883f06b2ead23d71",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": "^7.2 || ^8.0"
|
||||
},
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-master": "2.0.x-dev"
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Psr\\Http\\Message\\": "src/"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "PHP-FIG",
|
||||
"homepage": "https://www.php-fig.org/"
|
||||
}
|
||||
],
|
||||
"description": "Common interface for HTTP messages",
|
||||
"homepage": "https://github.com/php-fig/http-message",
|
||||
"keywords": [
|
||||
"http",
|
||||
"http-message",
|
||||
"psr",
|
||||
"psr-7",
|
||||
"request",
|
||||
"response"
|
||||
],
|
||||
"support": {
|
||||
"source": "https://github.com/php-fig/http-message/tree/2.0"
|
||||
},
|
||||
"time": "2023-04-04T09:54:51+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/polyfill-ctype",
|
||||
"version": "v1.37.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/polyfill-ctype.git",
|
||||
"reference": "141046a8f9477948ff284fa65be2095baafb94f2"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/141046a8f9477948ff284fa65be2095baafb94f2",
|
||||
"reference": "141046a8f9477948ff284fa65be2095baafb94f2",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": ">=7.2"
|
||||
},
|
||||
"provide": {
|
||||
"ext-ctype": "*"
|
||||
},
|
||||
"suggest": {
|
||||
"ext-ctype": "For best performance"
|
||||
},
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"thanks": {
|
||||
"url": "https://github.com/symfony/polyfill",
|
||||
"name": "symfony/polyfill"
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
"files": [
|
||||
"bootstrap.php"
|
||||
],
|
||||
"psr-4": {
|
||||
"Symfony\\Polyfill\\Ctype\\": ""
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Gert de Pagter",
|
||||
"email": "BackEndTea@gmail.com"
|
||||
},
|
||||
{
|
||||
"name": "Symfony Community",
|
||||
"homepage": "https://symfony.com/contributors"
|
||||
}
|
||||
],
|
||||
"description": "Symfony polyfill for ctype functions",
|
||||
"homepage": "https://symfony.com",
|
||||
"keywords": [
|
||||
"compatibility",
|
||||
"ctype",
|
||||
"polyfill",
|
||||
"portable"
|
||||
],
|
||||
"support": {
|
||||
"source": "https://github.com/symfony/polyfill-ctype/tree/v1.37.0"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
"url": "https://symfony.com/sponsor",
|
||||
"type": "custom"
|
||||
},
|
||||
{
|
||||
"url": "https://github.com/fabpot",
|
||||
"type": "github"
|
||||
},
|
||||
{
|
||||
"url": "https://github.com/nicolas-grekas",
|
||||
"type": "github"
|
||||
},
|
||||
{
|
||||
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2026-04-10T16:19:22+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/polyfill-mbstring",
|
||||
"version": "v1.37.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/polyfill-mbstring.git",
|
||||
"reference": "6a21eb99c6973357967f6ce3708cd55a6bec6315"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/6a21eb99c6973357967f6ce3708cd55a6bec6315",
|
||||
"reference": "6a21eb99c6973357967f6ce3708cd55a6bec6315",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"ext-iconv": "*",
|
||||
"php": ">=7.2"
|
||||
},
|
||||
"provide": {
|
||||
"ext-mbstring": "*"
|
||||
},
|
||||
"suggest": {
|
||||
"ext-mbstring": "For best performance"
|
||||
},
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"thanks": {
|
||||
"url": "https://github.com/symfony/polyfill",
|
||||
"name": "symfony/polyfill"
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
"files": [
|
||||
"bootstrap.php"
|
||||
],
|
||||
"psr-4": {
|
||||
"Symfony\\Polyfill\\Mbstring\\": ""
|
||||
}
|
||||
},
|
||||
"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 for the Mbstring extension",
|
||||
"homepage": "https://symfony.com",
|
||||
"keywords": [
|
||||
"compatibility",
|
||||
"mbstring",
|
||||
"polyfill",
|
||||
"portable",
|
||||
"shim"
|
||||
],
|
||||
"support": {
|
||||
"source": "https://github.com/symfony/polyfill-mbstring/tree/v1.37.0"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
"url": "https://symfony.com/sponsor",
|
||||
"type": "custom"
|
||||
},
|
||||
{
|
||||
"url": "https://github.com/fabpot",
|
||||
"type": "github"
|
||||
},
|
||||
{
|
||||
"url": "https://github.com/nicolas-grekas",
|
||||
"type": "github"
|
||||
},
|
||||
{
|
||||
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2026-04-10T17:25:58+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/polyfill-php80",
|
||||
"version": "v1.37.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/polyfill-php80.git",
|
||||
"reference": "dfb55726c3a76ea3b6459fcfda1ec2d80a682411"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/dfb55726c3a76ea3b6459fcfda1ec2d80a682411",
|
||||
"reference": "dfb55726c3a76ea3b6459fcfda1ec2d80a682411",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": ">=7.2"
|
||||
},
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"thanks": {
|
||||
"url": "https://github.com/symfony/polyfill",
|
||||
"name": "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"
|
||||
],
|
||||
"support": {
|
||||
"source": "https://github.com/symfony/polyfill-php80/tree/v1.37.0"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
"url": "https://symfony.com/sponsor",
|
||||
"type": "custom"
|
||||
},
|
||||
{
|
||||
"url": "https://github.com/fabpot",
|
||||
"type": "github"
|
||||
},
|
||||
{
|
||||
"url": "https://github.com/nicolas-grekas",
|
||||
"type": "github"
|
||||
},
|
||||
{
|
||||
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2026-04-10T16:19:22+00:00"
|
||||
},
|
||||
{
|
||||
"name": "vlucas/phpdotenv",
|
||||
"version": "v5.6.2",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/vlucas/phpdotenv.git",
|
||||
"reference": "24ac4c74f91ee2c193fa1aaa5c249cb0822809af"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/vlucas/phpdotenv/zipball/24ac4c74f91ee2c193fa1aaa5c249cb0822809af",
|
||||
"reference": "24ac4c74f91ee2c193fa1aaa5c249cb0822809af",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"ext-pcre": "*",
|
||||
"graham-campbell/result-type": "^1.1.3",
|
||||
"php": "^7.2.5 || ^8.0",
|
||||
"phpoption/phpoption": "^1.9.3",
|
||||
"symfony/polyfill-ctype": "^1.24",
|
||||
"symfony/polyfill-mbstring": "^1.24",
|
||||
"symfony/polyfill-php80": "^1.24"
|
||||
},
|
||||
"require-dev": {
|
||||
"bamarni/composer-bin-plugin": "^1.8.2",
|
||||
"ext-filter": "*",
|
||||
"phpunit/phpunit": "^8.5.34 || ^9.6.13 || ^10.4.2"
|
||||
},
|
||||
"suggest": {
|
||||
"ext-filter": "Required to use the boolean validator."
|
||||
},
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"bamarni-bin": {
|
||||
"bin-links": true,
|
||||
"forward-command": false
|
||||
},
|
||||
"branch-alias": {
|
||||
"dev-master": "5.6-dev"
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Dotenv\\": "src/"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"BSD-3-Clause"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Graham Campbell",
|
||||
"email": "hello@gjcampbell.co.uk",
|
||||
"homepage": "https://github.com/GrahamCampbell"
|
||||
},
|
||||
{
|
||||
"name": "Vance Lucas",
|
||||
"email": "vance@vancelucas.com",
|
||||
"homepage": "https://github.com/vlucas"
|
||||
}
|
||||
],
|
||||
"description": "Loads environment variables from `.env` to `getenv()`, `$_ENV` and `$_SERVER` automagically.",
|
||||
"keywords": [
|
||||
"dotenv",
|
||||
"env",
|
||||
"environment"
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/vlucas/phpdotenv/issues",
|
||||
"source": "https://github.com/vlucas/phpdotenv/tree/v5.6.2"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
"url": "https://github.com/GrahamCampbell",
|
||||
"type": "github"
|
||||
},
|
||||
{
|
||||
"url": "https://tidelift.com/funding/github/packagist/vlucas/phpdotenv",
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2025-04-30T23:37:27+00:00"
|
||||
}
|
||||
],
|
||||
"packages-dev": [],
|
||||
"aliases": [],
|
||||
"minimum-stability": "stable",
|
||||
"stability-flags": [],
|
||||
"prefer-stable": false,
|
||||
"prefer-lowest": false,
|
||||
"platform": {
|
||||
"php": ">=8.2"
|
||||
},
|
||||
"platform-dev": [],
|
||||
"plugin-api-version": "2.6.0"
|
||||
}
|
||||
+45
@@ -0,0 +1,45 @@
|
||||
<?php
|
||||
// Importa autoload do Composer
|
||||
require_once realpath(path: __DIR__ . '/../vendor/autoload.php');
|
||||
|
||||
use Dotenv\Dotenv;
|
||||
use KrothiumAPI\KrothiumAPI;
|
||||
|
||||
// Carrega variáveis de ambiente
|
||||
$dotenv = Dotenv::createImmutable(paths: realpath(path: __DIR__ . '/../'));
|
||||
$dotenv->load();
|
||||
|
||||
// ======================================
|
||||
// Inicializa KrothiumAPI com configs
|
||||
// ======================================
|
||||
KrothiumAPI::init(config: [
|
||||
'errors' => [
|
||||
'error_log' => realpath(path: __DIR__ . '/../storage/Logs/php-error.log'),
|
||||
],
|
||||
'constants' => [
|
||||
'APP_SYS_MODE' => 'DEV', // DEV | PROD
|
||||
'ROOT_SYSTEM_PATH' => realpath(path: __DIR__ . "/.."),
|
||||
'INI_SYSTEM_PATH' => realpath(path: __DIR__ . "/../src"),
|
||||
'MODULE_PATH' => realpath(path: __DIR__ . "/../src/Module"),
|
||||
'STORAGE_FOLDER_PATH' => realpath(path: __DIR__ . "/../storage"),
|
||||
'COMPONENT_PATH' => realpath(path: __DIR__ . "/../src/Common/Component"),
|
||||
'ROUTER_ALLOWED_ORIGINS' => [
|
||||
'*'
|
||||
]
|
||||
],
|
||||
'system' => [
|
||||
'enable_session' => true,
|
||||
'default_timezone' => 'America/Fortaleza',
|
||||
],
|
||||
'logger' => [
|
||||
'driver' => 'FILE',
|
||||
'logDir' => realpath(path: __DIR__ . '/../storage/Logs')
|
||||
]
|
||||
]);
|
||||
|
||||
// Importa rotas da API v0
|
||||
|
||||
// ============================
|
||||
// Dispara o roteador
|
||||
// ============================
|
||||
KrothiumAPI::routerDispatch();
|
||||
@@ -0,0 +1,120 @@
|
||||
<?php
|
||||
namespace KrothiumAPI\Database;
|
||||
|
||||
use PDO;
|
||||
use Dotenv\Dotenv;
|
||||
use RuntimeException;
|
||||
use KrothiumAPI\Database\Drivers\MySQLDriver;
|
||||
use KrothiumAPI\Database\Drivers\PostgreSQLDriver;
|
||||
|
||||
class DBManager {
|
||||
private static array $connections = [];
|
||||
private static bool $envLoaded = false;
|
||||
|
||||
private static function loadEnv(): void {
|
||||
// Garante que ROOT_SYSTEM_PATH esteja definido
|
||||
if (!defined('ROOT_SYSTEM_PATH')) {
|
||||
define('ROOT_SYSTEM_PATH', dirname(dirname(__DIR__)));
|
||||
}
|
||||
if (!self::$envLoaded) {
|
||||
$envFile = ROOT_SYSTEM_PATH . '/.env';
|
||||
if ((ROOT_SYSTEM_PATH !== null) && file_exists(filename: $envFile)) {
|
||||
$dotenv = Dotenv::createImmutable(paths: ROOT_SYSTEM_PATH);
|
||||
$dotenv->load();
|
||||
}
|
||||
self::$envLoaded = true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retorna conexão por nome + schema
|
||||
*/
|
||||
public static function getConnection(string $connectionName = 'DEFAULT', ?string $schema = null): mixed {
|
||||
self::loadEnv();
|
||||
$connectionName = strtoupper(string: $connectionName);
|
||||
$key = $connectionName . ($schema ? "_$schema" : '');
|
||||
if (!isset(self::$connections[$key])) {
|
||||
$driver = $_ENV["{$connectionName}_DB_DRIVER"];
|
||||
switch (strtolower(string: $driver)) {
|
||||
case 'mysql':
|
||||
$conn = new MySQLDriver(envName: $connectionName);
|
||||
break;
|
||||
case 'pgsql':
|
||||
case 'postgresql':
|
||||
$conn = new PostgreSQLDriver(envName: $connectionName, schema: $schema);
|
||||
break;
|
||||
default:
|
||||
throw new RuntimeException(message: "Unknown driver: {$driver}");
|
||||
}
|
||||
self::$connections[$key] = $conn;
|
||||
}
|
||||
return self::$connections[$key];
|
||||
}
|
||||
|
||||
public static function disconnect(string $connectionName = 'DEFAULT', ?string $schema = null): void {
|
||||
$connectionName = strtoupper(string: $connectionName);
|
||||
$key = $connectionName . ($schema ? "_$schema" : '');
|
||||
self::$connections[$key] = null;
|
||||
}
|
||||
|
||||
/** Métodos auxiliares (execute, fetchAll, fetchOne, etc.) */
|
||||
public static function execute(string $sql, array $params = [], string $connectionName = 'DEFAULT', ?string $schema = null): bool {
|
||||
$conn = self::getConnection(connectionName: $connectionName, schema: $schema);
|
||||
if (method_exists(object_or_class: $conn, method: 'execute')) {
|
||||
return $conn->execute($sql, $params);
|
||||
}
|
||||
throw new RuntimeException(message: "The database connection '{$connectionName}' does not support execute()");
|
||||
}
|
||||
|
||||
public static function fetchAll(string $sql, array $params = [], string $connectionName = 'DEFAULT', ?string $schema = null): array {
|
||||
$conn = self::getConnection(connectionName: $connectionName, schema: $schema);
|
||||
if (method_exists(object_or_class: $conn, method: 'fetchAll')) {
|
||||
return $conn->fetchAll($sql, $params);
|
||||
}
|
||||
throw new RuntimeException(message: "The database connection '{$connectionName}' does not support fetchAll()");
|
||||
}
|
||||
|
||||
public static function fetchOne(string $sql, array $params = [], string $connectionName = 'DEFAULT', ?string $schema = null): ?array {
|
||||
$conn = self::getConnection(connectionName: $connectionName, schema: $schema);
|
||||
if (method_exists(object_or_class: $conn, method: 'fetchOne')) {
|
||||
return $conn->fetchOne($sql, $params);
|
||||
}
|
||||
throw new RuntimeException(message: "The database connection '{$connectionName}' does not support fetchOne()");
|
||||
}
|
||||
|
||||
// Transações
|
||||
public static function beginTransaction(string $connectionName = 'DEFAULT', ?string $schema = null): void {
|
||||
$conn = self::getConnection(connectionName: $connectionName, schema: $schema);
|
||||
if (method_exists(object_or_class: $conn, method: 'beginTransaction')) $conn->beginTransaction();
|
||||
}
|
||||
|
||||
public static function commit(string $connectionName = 'DEFAULT', ?string $schema = null): void {
|
||||
$conn = self::getConnection(connectionName: $connectionName, schema: $schema);
|
||||
if ($conn instanceof PDO && $conn->inTransaction()) {
|
||||
$conn->commit();
|
||||
} elseif (method_exists(object_or_class: $conn, method: 'getPDO')) {
|
||||
$pdo = $conn->getPDO();
|
||||
if ($pdo instanceof PDO && $pdo->inTransaction()) {
|
||||
$pdo->commit();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static function rollback(string $connectionName = 'DEFAULT', ?string $schema = null): void {
|
||||
$conn = self::getConnection(connectionName: $connectionName, schema: $schema);
|
||||
if ($conn instanceof PDO && $conn->inTransaction()) {
|
||||
$conn->rollback();
|
||||
} elseif (method_exists(object_or_class: $conn, method: 'getPDO')) {
|
||||
$pdo = $conn->getPDO();
|
||||
if ($pdo instanceof PDO && $pdo->inTransaction()) {
|
||||
$pdo->rollback();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static function lastInsertId(string $connectionName = 'DEFAULT', ?string $schema = null): string {
|
||||
$conn = self::getConnection(connectionName: $connectionName, schema: $schema);
|
||||
if (method_exists(object_or_class: $conn, method: 'getPDO')) return $conn->getPDO()->lastInsertId();
|
||||
throw new RuntimeException(message: "The database connection '{$connectionName}' does not support lastInsertId()");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
<?php
|
||||
namespace KrothiumAPI\Database\Drivers;
|
||||
|
||||
use PDO;
|
||||
use PDOException;
|
||||
use RuntimeException;
|
||||
|
||||
class MySQLDriver extends PDOAbstract {
|
||||
protected function connect(string $envName): void {
|
||||
$host = $_ENV["{$envName}_DB_HOST"];
|
||||
$port = $_ENV["{$envName}_DB_PORT"] ?? 3306;
|
||||
$dbname = $_ENV["{$envName}_DB_NAME"];
|
||||
$user = $_ENV["{$envName}_DB_USERNAME"];
|
||||
$password = $_ENV["{$envName}_DB_PASSWORD"];
|
||||
$charset = $_ENV["{$envName}_DB_CHARSET"] ?? 'utf8mb4';
|
||||
|
||||
try {
|
||||
$this->connection = new PDO(
|
||||
dsn: "mysql:host={$host};port={$port};dbname={$dbname};charset={$charset}",
|
||||
username: $user,
|
||||
password: $password,
|
||||
options: [
|
||||
PDO::ATTR_PERSISTENT => true,
|
||||
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
|
||||
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
|
||||
]
|
||||
);
|
||||
} catch (PDOException $e) {
|
||||
throw new RuntimeException(message: "Error connecting to MySQL: {$e->getMessage()}");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
<?php
|
||||
namespace KrothiumAPI\Database\Drivers;
|
||||
|
||||
use PDO;
|
||||
|
||||
abstract class PDOAbstract {
|
||||
protected PDO $connection;
|
||||
|
||||
public function __construct(string $envName) {
|
||||
$this->connect(envName: $envName);
|
||||
}
|
||||
|
||||
abstract protected function connect(string $envName): void;
|
||||
|
||||
public function execute(string $sql, array $params = []): bool {
|
||||
$stmt = $this->connection->prepare(query: $sql);
|
||||
return $stmt->execute(params: $params);
|
||||
}
|
||||
|
||||
public function fetchAll(string $sql, array $params = []): array {
|
||||
$stmt = $this->connection->prepare(query: $sql);
|
||||
$stmt->execute(params: $params);
|
||||
return $stmt->fetchAll(mode: PDO::FETCH_ASSOC);
|
||||
}
|
||||
|
||||
public function fetchOne(string $sql, array $params = []): ?array {
|
||||
$stmt = $this->connection->prepare(query: $sql);
|
||||
$stmt->execute(params: $params);
|
||||
$result = $stmt->fetch(mode: PDO::FETCH_ASSOC);
|
||||
return $result ?: null;
|
||||
}
|
||||
|
||||
public function beginTransaction(): void {
|
||||
if (!$this->connection->inTransaction()) {
|
||||
$this->connection->beginTransaction();
|
||||
}
|
||||
}
|
||||
|
||||
public function commit(): void {
|
||||
if ($this->connection->inTransaction()) {
|
||||
$this->connection->commit();
|
||||
}
|
||||
}
|
||||
|
||||
public function rollback(): void {
|
||||
if ($this->connection->inTransaction()) {
|
||||
$this->connection->rollBack();
|
||||
}
|
||||
}
|
||||
|
||||
public function getPDO(): PDO {
|
||||
return $this->connection;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
<?php
|
||||
namespace KrothiumAPI\Database\Drivers;
|
||||
|
||||
use PDO;
|
||||
use PDOException;
|
||||
use RuntimeException;
|
||||
|
||||
class PostgreSQLDriver extends PDOAbstract {
|
||||
public function __construct(string $envName, ?string $schema = null) {
|
||||
$this->connect(envName: $envName, schema: $schema);
|
||||
}
|
||||
|
||||
protected function connect(string $envName, ?string $schema = null): void {
|
||||
$host = $_ENV["{$envName}_DB_HOST"];
|
||||
$port = $_ENV["{$envName}_DB_PORT"] ?? 5432;
|
||||
$dbname = $_ENV["{$envName}_DB_NAME"];
|
||||
$user = $_ENV["{$envName}_DB_USERNAME"];
|
||||
$pass = $_ENV["{$envName}_DB_PASSWORD"];
|
||||
$schema ??= $_ENV["{$envName}_DB_SCHEMA"] ?? 'public';
|
||||
$systemTimeZone = $_ENV["{$envName}_DB_TIMEZONE"] ?? 'UTC';
|
||||
$sslMode = $_ENV["{$envName}_DB_SSLMODE"] ?? 'disable'; // disable, require, verify-ca, verify-full
|
||||
|
||||
try {
|
||||
$this->connection = new PDO(
|
||||
dsn: "pgsql:host={$host};port={$port};dbname={$dbname};sslmode={$sslMode}",
|
||||
username: $user,
|
||||
password: $pass,
|
||||
options: [
|
||||
PDO::ATTR_PERSISTENT => false,
|
||||
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
|
||||
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
|
||||
]
|
||||
);
|
||||
// Define o fuso horário da sessão
|
||||
$this->connection->exec(statement: "SET TIME ZONE '{$systemTimeZone}'");
|
||||
|
||||
// Define o schema padrão
|
||||
$this->connection->exec(statement: "SET search_path TO {$schema}, public");
|
||||
} catch (PDOException $e) {
|
||||
throw new RuntimeException(
|
||||
message: "Error connecting to PostgreSQL: {$e->getMessage()}"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Método auxiliar para alterar schema dinamicamente
|
||||
public function setSchema(string $schema): void {
|
||||
$this->connection->exec(statement: "SET search_path TO {$schema}, public");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
<?php
|
||||
namespace KrothiumAPI\Database\Drivers;
|
||||
|
||||
use Predis\Client as PredisClient;
|
||||
use RuntimeException;
|
||||
|
||||
class RedisDriver {
|
||||
protected PredisClient $connection;
|
||||
protected string $envName;
|
||||
|
||||
public function __construct(string $envName = 'DEFAULT') {
|
||||
$this->envName = strtoupper(string: $envName);
|
||||
$this->connect();
|
||||
}
|
||||
|
||||
protected function connect(): void {
|
||||
$host = $_ENV["{$this->envName}_REDIS_HOST"] ?? $_ENV['REDIS_HOST'] ?? '127.0.0.1';
|
||||
$port = (int) ($_ENV["{$this->envName}_REDIS_PORT"] ?? $_ENV['REDIS_PORT'] ?? 6379);
|
||||
$password = $_ENV["{$this->envName}_REDIS_PASSWORD"] ?? $_ENV['REDIS_PASSWORD'] ?? null;
|
||||
$database = isset($_ENV["{$this->envName}_REDIS_DATABASE"]) ? (int) $_ENV["{$this->envName}_REDIS_DATABASE"] : (isset($_ENV['REDIS_DATABASE']) ? (int) $_ENV['REDIS_DATABASE'] : 0);
|
||||
|
||||
// Usa Predis
|
||||
try {
|
||||
$parameters = [
|
||||
'scheme' => 'tcp',
|
||||
'host' => $host,
|
||||
'port' => $port,
|
||||
'database' => $database,
|
||||
];
|
||||
if ($password) $parameters['password'] = $password;
|
||||
|
||||
$this->connection = new PredisClient(parameters: $parameters);
|
||||
// testa a conexão (Predis conecta sob demanda, connect() força handshake)
|
||||
$this->connection->connect();
|
||||
} catch (\Throwable $e) {
|
||||
throw new RuntimeException(message: "Error connecting to Redis: {$e->getMessage()}");
|
||||
}
|
||||
}
|
||||
|
||||
/** Retorna o cliente interno (Predis ou Redis) */
|
||||
public function getClient(): PredisClient {
|
||||
return $this->connection;
|
||||
}
|
||||
|
||||
/** Define um valor (string). Se $ttl for informado, adiciona expire em segundos. */
|
||||
public function set(string $key, mixed $value, ?int $ttl = null): bool {
|
||||
// Predis: set or setex
|
||||
if ($ttl !== null && $ttl > 0) {
|
||||
$resp = $this->connection->setex($key, $ttl, $value);
|
||||
return (bool) $resp;
|
||||
}
|
||||
$resp = $this->connection->set($key, $value);
|
||||
return (bool) $resp;
|
||||
}
|
||||
|
||||
/** Recupera um valor por chave */
|
||||
public function get(string $key): mixed {
|
||||
return $this->connection->get($key);
|
||||
}
|
||||
|
||||
/** Remove chaves, retorna número de chaves removidas */
|
||||
public function del(string ...$keys): int {
|
||||
if (empty($keys)) return 0;
|
||||
return (int) $this->connection->del(...$keys);
|
||||
}
|
||||
|
||||
/** Verifica se uma chave existe */
|
||||
public function exists(string $key): bool {
|
||||
return (bool) $this->connection->exists($key);
|
||||
}
|
||||
|
||||
/** Define TTL (segundos) para uma chave */
|
||||
public function expire(string $key, int $seconds): bool {
|
||||
return (bool) $this->connection->expire($key, $seconds);
|
||||
}
|
||||
|
||||
/** Proxy para comandos não-explicitados */
|
||||
public function __call(string $name, array $arguments) {
|
||||
try {
|
||||
return $this->connection->{$name}(...$arguments);
|
||||
} catch (\Throwable $e) {
|
||||
throw new RuntimeException("Redis command '{$name}' failed: {$e->getMessage()}");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,109 @@
|
||||
<?php
|
||||
namespace KrothiumAPI\Database;
|
||||
|
||||
use KrothiumAPI\Database\Drivers\RedisDriver;
|
||||
|
||||
class RedisManager {
|
||||
/**
|
||||
* Armazena múltiplas conexões Redis nomeadas.
|
||||
* Exemplo: [
|
||||
* 'DEFAULT' => RedisDriver,
|
||||
* 'CACHE' => RedisDriver,
|
||||
* 'QUEUE' => RedisDriver
|
||||
* ]
|
||||
*/
|
||||
private static array $connections = [];
|
||||
|
||||
/**
|
||||
* Retorna a conexão Redis para o ambiente informado.
|
||||
* Se não existir, cria uma nova.
|
||||
*/
|
||||
private static function connect(string $envName = 'DEFAULT'): RedisDriver {
|
||||
if (!isset(self::$connections[$envName])) {
|
||||
self::$connections[$envName] = new RedisDriver(envName: $envName);
|
||||
}
|
||||
return self::$connections[$envName];
|
||||
}
|
||||
|
||||
/**
|
||||
* Armazena um valor no Redis associado a uma chave, com uma TTL opcional.
|
||||
*
|
||||
* Este método garante que a conexão com o Redis seja estabelecida e então delega a operação SET para o driver.
|
||||
*
|
||||
* @param string $key A chave sob a qual o valor será armazenado.
|
||||
* @param mixed $value O valor a ser armazenado (pode ser string, int, array, etc.).
|
||||
* @param int|null $ttl O tempo de vida (Time-To-Live) em segundos para a chave (opcional).
|
||||
* @return bool Retorna `true` em caso de sucesso.
|
||||
*/
|
||||
public static function set(string $key, mixed $value, ?int $ttl = null, string $envName = 'DEFAULT'): bool {
|
||||
$conn = self::connect(envName: $envName);
|
||||
return $conn->set(key: $key, value: $value, ttl: $ttl);
|
||||
}
|
||||
|
||||
/**
|
||||
* Recupera o valor armazenado no Redis associado a uma chave.
|
||||
*
|
||||
* Este método delega a operação GET para o driver de conexão, recuperando o valor de uma chave específica.
|
||||
*
|
||||
* @param string $key A chave cujo valor deve ser recuperado.
|
||||
* @return mixed O valor da chave, ou `null` se a chave não existir.
|
||||
*/
|
||||
public static function get(string $key, string $envName = 'DEFAULT'): mixed {
|
||||
$conn = self::connect(envName: $envName);
|
||||
return $conn->get(key: $key);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove uma ou mais chaves do Redis.
|
||||
*
|
||||
* Este método recebe um array de chaves e remove cada uma delas individualmente do serviço Redis.
|
||||
*
|
||||
* @param array $keys Um array de strings contendo as chaves a serem removidas.
|
||||
* @return int O número total de chaves removidas.
|
||||
*/
|
||||
public static function del(array $keys, string $envName = 'DEFAULT'): int {
|
||||
$conn = self::connect(envName: $envName);
|
||||
$deleted = 0;
|
||||
foreach ($keys as $key) {
|
||||
$deleted += $conn->del(keys: $key);
|
||||
}
|
||||
return $deleted;
|
||||
}
|
||||
|
||||
/**
|
||||
* Verifica a existência de uma chave no Redis.
|
||||
*
|
||||
* @param string $key A chave a ser verificada.
|
||||
* @return bool Retorna `true` se a chave existir no Redis, `false` caso contrário.
|
||||
*/
|
||||
public static function exists(string $key, string $envName = 'DEFAULT'): bool {
|
||||
$conn = self::connect(envName: $envName);
|
||||
return $conn->exists(key: $key);
|
||||
}
|
||||
|
||||
/**
|
||||
* Define um tempo de vida (TTL) para uma chave existente no Redis.
|
||||
*
|
||||
* @param string $key A chave para a qual o TTL será definido.
|
||||
* @param int $seconds O tempo de vida em segundos.
|
||||
* @return bool Retorna `true` se o TTL foi definido com sucesso, `false` caso contrário (ex: a chave não existe).
|
||||
*/
|
||||
public static function expire(string $key, int $seconds, string $envName = 'DEFAULT'): bool {
|
||||
$conn = self::connect(envName: $envName);
|
||||
return $conn->expire(key: $key, seconds: $seconds);
|
||||
}
|
||||
|
||||
/**
|
||||
* Permite chamar qualquer método nativo do driver Redis (método mágico __call).
|
||||
*
|
||||
* Este método atua como um proxy para invocar comandos diretamente no driver subjacente do Redis, permitindo flexibilidade para comandos que não estão mapeados explicitamente nos outros métodos da classe.
|
||||
*
|
||||
* @param string $method O nome do método/comando Redis a ser chamado.
|
||||
* @param array $args Um array de argumentos a serem passados para o método.
|
||||
* @return mixed O resultado da execução do comando Redis.
|
||||
*/
|
||||
public static function call(string $method, array $args = [], string $envName = 'DEFAULT'): mixed {
|
||||
$conn = self::connect(envName: $envName);
|
||||
return $conn->__call(name: $method, arguments: $args);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
<?php
|
||||
namespace KrothiumAPI\Helpers;
|
||||
|
||||
class ConstHelper {
|
||||
/**
|
||||
* Recupera o valor de uma constante global de forma segura, permitindo um valor padrão de fallback.
|
||||
* * Este método atua como um wrapper de proteção para a função nativa `constant()` do PHP. Ele previne
|
||||
* erros de execução (Warning/Error) que ocorreriam ao tentar acessar uma constante não definida
|
||||
* diretamente. É ideal para acessar configurações dinâmicas, variáveis de ambiente ou flags de
|
||||
* sistema onde a existência da constante não é garantida em todos os ambientes (desenvolvimento,
|
||||
* homologação e produção).
|
||||
* *
|
||||
* * ---
|
||||
* ## Lógica de Funcionamento
|
||||
* 1. **Verificação de Existência:** Utiliza `defined()` para checar se o identificador fornecido existe na tabela de símbolos globais do script.
|
||||
* 2. **Resolução de Valor:** Caso a constante exista, o método retorna seu valor original (que pode ser qualquer tipo escalar ou array, dependendo da versão do PHP).
|
||||
* 3. **Tratamento de Fallback:** Se a constante não estiver definida, o método retorna o valor estipulado no parâmetro `$default`, garantindo que o fluxo da aplicação não seja interrompido.
|
||||
* * ---
|
||||
* ## Exemplos de Uso
|
||||
* - **Configuração de API:** `Config::get('API_KEY', 'default_key_123');`
|
||||
* - **Flags de Debug:** `Config::get('DEBUG_MODE', false);`
|
||||
* * @param string $constant_name O nome da constante global a ser verificada e recuperada.
|
||||
* @param mixed $default O valor a ser retornado caso a constante não esteja definida. O padrão é `null`.
|
||||
* @return mixed Retorna o valor da constante se definida; caso contrário, retorna o valor de `$default`.
|
||||
*/
|
||||
public static function get(string $constant_name, $default = null): mixed {
|
||||
if (defined(constant_name: $constant_name)) {
|
||||
return constant(name: $constant_name);
|
||||
}
|
||||
return $default;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,189 @@
|
||||
<?php
|
||||
namespace KrothiumAPI\Helpers;
|
||||
|
||||
class RequestHelper {
|
||||
/**
|
||||
* Extrai, decodifica e filtra dados de requisições HTTP de forma polimórfica.
|
||||
*
|
||||
* Este método é o motor de captura de dados da aplicação. Ele abstrai a complexidade de lidar com
|
||||
* diferentes verbos HTTP (GET, POST, PUT, PATCH, DELETE) e formatos de conteúdo (Form-Data, JSON,
|
||||
* URL-Encoded). O método detecta automaticamente se a requisição contém um payload JSON, gerencia
|
||||
* a leitura do fluxo `php://input` para métodos não-nativos do PHP e aplica filtros de
|
||||
* higienização customizados ou padrões.
|
||||
*
|
||||
*
|
||||
*
|
||||
* ---
|
||||
* ## Fluxo de Inteligência
|
||||
* 1. **Mapeamento de Superglobais:** Utiliza `filter_input_array` para métodos padrão (GET/POST/COOKIE), garantindo acesso seguro às variáveis globais do PHP.
|
||||
* 2. **Processamento JSON:** Se o cabeçalho `Content-Type` indicar JSON, o método lê o corpo bruto da requisição, decodifica-o e valida a sintaxe. Caso o JSON seja inválido, interrompe a execução com um erro 400.
|
||||
* 3. **Suporte a Métodos Modernos (REST):** Para PUT, PATCH e DELETE, extrai os dados via `php://input`. Se não for JSON, processa como string de consulta (`parse_str`), permitindo que esses métodos funcionem como um formulário convencional.
|
||||
* 4. **Limpeza de Protocolo:** Remove automaticamente a chave `_method`, comumente usada para emular métodos HTTP em formulários HTML, evitando que dados de controle "vazem" para a lógica de negócio.
|
||||
* 5. **Filtragem:** Delega ao método interno `applyFilters` a responsabilidade de validar os dados finais contra o mapa de filtros fornecido.
|
||||
*
|
||||
* ---
|
||||
* ## Parâmetros e Retorno
|
||||
* - **Tipos de Retorno:** Pode retornar o `array` processado (padrão) ou a `string` bruta (útil para logs ou validações externas).
|
||||
* - **Segurança:** Bloqueia a execução em caso de payloads corrompidos via `errorJson`.
|
||||
*
|
||||
* @param string $form_type O método de entrada (GET, POST, PUT, PATCH, DELETE, COOKIE, SERVER).
|
||||
* @param array|null $filters Mapa de filtros de higienização (ex: `FILTER_SANITIZE_STRING`).
|
||||
* @param string $return_type Define o formato de saída: 'array' ou 'string'.
|
||||
* @return mixed O conjunto de dados filtrados ou a string bruta da requisição.
|
||||
*/
|
||||
public static function getRequestData(string $form_type = 'GET', ?array $filters = null, string $return_type = 'array'): mixed {
|
||||
$method = strtoupper(string: trim(string: $form_type));
|
||||
$contentType = $_SERVER['CONTENT_TYPE'] ?? '';
|
||||
$isJson = self::isJsonContentType(contentType: $contentType);
|
||||
|
||||
$inputMap = [
|
||||
'GET' => INPUT_GET,
|
||||
'POST' => INPUT_POST,
|
||||
'COOKIE' => INPUT_COOKIE,
|
||||
'SERVER' => INPUT_SERVER,
|
||||
];
|
||||
|
||||
// GET/POST/COOKIE/SERVER
|
||||
if(isset($inputMap[$method])) {
|
||||
// POST JSON: lê o body
|
||||
if($method === 'POST' && $isJson) {
|
||||
$raw = file_get_contents(filename: 'php://input') ?: '';
|
||||
if($return_type === 'string') {
|
||||
return $raw;
|
||||
}
|
||||
$data = json_decode(json: $raw, associative: true);
|
||||
if(json_last_error() !== JSON_ERROR_NONE) {
|
||||
self::errorJson(statusCode: 400, message: 'Invalid JSON: ' . json_last_error_msg());
|
||||
}
|
||||
unset($data['_method']);
|
||||
return self::applyFilters(data: $data ?? [], filters: $filters);
|
||||
}
|
||||
$form = filter_input_array(type: $inputMap[$method], options: $filters ?? FILTER_DEFAULT) ?? [];
|
||||
return is_array(value: $form) ? $form : [];
|
||||
}
|
||||
|
||||
// PUT/PATCH/DELETE
|
||||
if(in_array(needle: $method, haystack: ['PUT', 'PATCH', 'DELETE'], strict: true)) {
|
||||
$raw = file_get_contents(filename: 'php://input') ?: '';
|
||||
if($raw === '') {
|
||||
return [];
|
||||
}
|
||||
if($isJson) {
|
||||
if($return_type === 'string') {
|
||||
return $raw;
|
||||
}
|
||||
$data = json_decode(json: $raw, associative: true);
|
||||
if(json_last_error() !== JSON_ERROR_NONE) {
|
||||
self::errorJson(statusCode: 400, message: 'Invalid JSON: ' . json_last_error_msg());
|
||||
}
|
||||
} else {
|
||||
parse_str($raw, $data);
|
||||
}
|
||||
unset($data['_method']);
|
||||
return self::applyFilters(data: $data ?? [], filters: $filters);
|
||||
}
|
||||
// Default: GET
|
||||
$form = filter_input_array(type: INPUT_GET, options: $filters ?? FILTER_DEFAULT) ?? [];
|
||||
return is_array(value: $form) ? $form : [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Verifica se o cabeçalho de tipo de conteúdo (Content-Type) da requisição indica um formato JSON.
|
||||
*
|
||||
* Este método auxiliar é fundamental para a estratégia de parsing polimórfico do sistema. Em vez de
|
||||
* realizar uma comparação estrita, ele utiliza uma busca de substring insensível a maiúsculas
|
||||
* e minúsculas para identificar a presença da palavra "json". Isso garante compatibilidade com
|
||||
* diversas variações de cabeçalhos comuns em APIs modernas, incluindo definições de charset e
|
||||
* Media Types específicos.
|
||||
*
|
||||
*
|
||||
*
|
||||
* ---
|
||||
* ## Casos de Compatibilidade
|
||||
* O método retorna `true` para padrões como:
|
||||
* - `application/json` (Padrão RFC 4627)
|
||||
* - `application/json; charset=utf-8` (Com especificação de codificação)
|
||||
* - `application/vnd.api+json` (Padrão JSON:API)
|
||||
* - `text/json` (Legado ou variações de servidores específicos)
|
||||
*
|
||||
* ---
|
||||
* ## Lógica de Implementação
|
||||
* 1. **Busca Flexível:** Utiliza `stripos()` para localizar a agulha 'json' em qualquer posição da string.
|
||||
* 2. **Performance:** Por ser um método estático e focado em uma única responsabilidade, oferece baixo custo computacional para o ciclo de vida da requisição.
|
||||
* 3. **Normalização:** Previne erros de detecção causados por variações de caixa (UpperCase vs LowerCase) enviadas por diferentes clientes HTTP (Browsers, Postman, Mobile).
|
||||
*
|
||||
* @param string $contentType O valor bruto extraído do cabeçalho `$_SERVER['CONTENT_TYPE']`.
|
||||
* @return bool Retorna `true` se o formato JSON for detectado, caso contrário, `false`.
|
||||
*/
|
||||
private static function isJsonContentType(string $contentType): bool {
|
||||
// pega application/json, application/json; charset=utf-8, application/vnd.api+json, etc.
|
||||
return stripos(haystack: $contentType, needle: 'json') !== false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Interrompe a execução do script e envia uma resposta de erro padronizada em formato JSON.
|
||||
* * Este método é o mecanismo de terminação segura da aplicação para falhas de requisição. Ele garante
|
||||
* que o cliente (API, Mobile ou Frontend) receba um status code HTTP semanticamente correto e um
|
||||
* corpo de resposta estruturado. Ao utilizar o tipo de retorno `never`, o método sinaliza ao
|
||||
* analisador estático e ao desenvolvedor que o fluxo de código é encerrado imediatamente após
|
||||
* sua invocação, prevenindo a execução de processos subsequentes indesejados.
|
||||
* *
|
||||
* * ---
|
||||
* ## Mecanismo de Resposta
|
||||
* 1. **Definição de Status:** Utiliza `http_response_code` para definir o estado da resposta (ex: 400 para Bad Request, 401 para Unauthorized).
|
||||
* 2. **Negociação de Conteúdo:** Força o cabeçalho `Content-Type` para `application/json` com codificação UTF-8, assegurando que caracteres especiais sejam renderizados corretamente no cliente.
|
||||
* 3. **Padronização de Payload:** Encapsula a mensagem de erro em um objeto JSON com as chaves `status` (fixo como "error") e `message` (dinâmica), facilitando o tratamento de erros no lado do cliente.
|
||||
* 4. **Encerramento:** Finaliza o processo via `exit`, impedindo que qualquer HTML ou saída residual corrompa o JSON enviado.
|
||||
* * ---
|
||||
* ## Exemplos de Aplicação
|
||||
* - Falha na decodificação de payloads JSON malformados.
|
||||
* - Erros de validação crítica em rotas de API.
|
||||
* - Bloqueio de acesso por falta de privilégios.
|
||||
* * @param int $statusCode Código de status HTTP (ex: 400, 403, 404, 500).
|
||||
* @param string $message Mensagem descritiva detalhando o motivo do erro.
|
||||
* @return never Este método encerra a execução do script e nunca retorna ao chamador.
|
||||
*/
|
||||
private static function errorJson(int $statusCode, string $message): never {
|
||||
http_response_code(response_code: $statusCode);
|
||||
header(header: "Content-Type: application/json; charset=utf-8");
|
||||
echo json_encode(value: [
|
||||
"status" => "error",
|
||||
"message" => $message,
|
||||
]);
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Aplica filtros de higienização e validação em um conjunto de dados brutos de forma segura.
|
||||
*
|
||||
* Este método atua como uma camada interna de proteção, permitindo que arrays associativos (como
|
||||
* payloads JSON ou dados de formulários) sejam processados utilizando as mesmas regras nativas do
|
||||
* PHP aplicadas em `filter_input_array`. Ele é essencial para garantir que as entradas de dados
|
||||
* estejam em conformidade com os tipos esperados (inteiros, strings sanitizadas, flags booleanas, etc.)
|
||||
* antes de serem distribuídas para as camadas de serviço ou objetos de transferência (DTOs).
|
||||
*
|
||||
*
|
||||
*
|
||||
* ---
|
||||
* ## Mecanismo de Filtragem
|
||||
* 1. **Verificação de Nulidade:** Se nenhum mapa de filtros for fornecido, o método retorna os dados brutos integralmente, permitindo flexibilidade em rotas que não exigem tipagem estrita inicial.
|
||||
* 2. **Processamento `filter_var_array`:** Utiliza a função nativa do PHP para mapear as chaves do array contra as definições de filtros. A opção `add_empty: false` é crucial para garantir que o resultado contenha apenas os campos originalmente presentes no input, evitando a criação de chaves indesejadas com valores nulos.
|
||||
* 3. **Normalização de Saída:** Como as funções de filtro do PHP podem retornar `false` ou `null` em cenários de erro ou inputs corrompidos, este método assegura a consistência da tipagem sempre retornando um `array` (mesmo que vazio).
|
||||
*
|
||||
* ---
|
||||
* ## Segurança e Integridade
|
||||
* - **Padronização:** Garante que a mesma lógica de segurança usada para superglobais seja aplicada a dados decodificados manualmente (como JSON do `php://input`).
|
||||
* - **Redução de Side-Effects:** O tratamento do retorno previne erros de iteração (foreach) em camadas superiores.
|
||||
*
|
||||
* @param array $data O array associativo de dados brutos a serem processados.
|
||||
* @param array|null $filters O mapa de definições de filtros (ex: `['id' => FILTER_VALIDATE_INT]`).
|
||||
* @return array O conjunto de dados resultantes após a validação e higienização.
|
||||
*/
|
||||
private static function applyFilters(array $data, ?array $filters): array {
|
||||
if($filters === null) {
|
||||
return $data;
|
||||
}
|
||||
$filtered = filter_var_array(array: $data, options: $filters, add_empty: false);
|
||||
return is_array(value: $filtered) ? $filtered : [];
|
||||
}
|
||||
}
|
||||
+396
@@ -0,0 +1,396 @@
|
||||
<?php
|
||||
namespace KrothiumAPI\Http;
|
||||
|
||||
use Exception;
|
||||
use KrothiumAPI\Helpers\ConstHelper;
|
||||
|
||||
class Router {
|
||||
private static $routes = [];
|
||||
private static $params = [];
|
||||
private static $APP_SYS_MODE = null;
|
||||
private static string $basePath = '';
|
||||
private static $currentGroupPrefix = '';
|
||||
private static $currentGroupMiddlewares = [];
|
||||
private static $ROUTER_ALLOWED_ORIGINS = ['*'];
|
||||
private static array $requiredConstants = ['APP_SYS_MODE'];
|
||||
private static array $allowedHttpRequests = ['GET','POST','PUT','PATCH','DELETE','OPTIONS'];
|
||||
|
||||
/**
|
||||
* Inicializa o roteador e define as configurações essenciais, como caminhos base, modos de operação e permissões de CORS.
|
||||
*
|
||||
* Este método estático é o ponto de partida para configurar o roteador (`Router`) e garantir que todas as variáveis de ambiente necessárias estejam prontas para processar requisições.
|
||||
*
|
||||
* #### Fluxo de Operação:
|
||||
* 1. **Verificação de Constantes:** Chama `self::checkRequiredConstants()` para garantir que todas as constantes obrigatórias do sistema estejam definidas. (Se falhar, a execução é encerrada).
|
||||
* 2. **Definição do Caminho Base (`$basePath`):** Verifica se a constante `ROUTER_BASE_PATH` está definida. Se estiver, define o caminho base da aplicação, garantindo que ele comece com `/`.
|
||||
* 3. **Registro na Sessão:** Armazena o caminho base (`$basePath`) na sessão (`$_SESSION['ROUTER_BASE_PATH']`).
|
||||
* 4. **Definição de Modos:** Define as propriedades estáticas `self::$ROUTER_MODE` (modo do roteador, e.g., 'JSON', 'WEB') e `self::$APP_SYS_MODE` (modo do sistema, e.g., 'DEV', 'PROD') com seus valores em
|
||||
* caixa alta (uppercase).
|
||||
* 5. **Configuração de CORS:** Se o roteador estiver no modo 'JSON' e a constante `ROUTER_ALLOWED_ORIGINS` estiver definida, define os domínios permitidos para requisições *Cross-Origin* (CORS).
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function init() {
|
||||
// Verifica as constantes obrigatórias
|
||||
self::checkRequiredConstants();
|
||||
|
||||
// Verifica se tem diretório base definido
|
||||
self::$basePath = ConstHelper::get(constant_name: 'ROUTER_BASE_PATH') ? '/' . trim(string: ConstHelper::get(constant_name: 'ROUTER_BASE_PATH'), characters: '/') : '';
|
||||
$_SESSION['ROUTER_BASE_PATH'] = self::$basePath;
|
||||
|
||||
// Define os modos do roteador e do sistema
|
||||
self::$APP_SYS_MODE = strtoupper(string: ConstHelper::get(constant_name: 'APP_SYS_MODE'));
|
||||
|
||||
// Define os domínios permitidos para CORS
|
||||
if (ConstHelper::get(constant_name: 'ROUTER_ALLOWED_ORIGINS') !== null) {
|
||||
self::$ROUTER_ALLOWED_ORIGINS = ConstHelper::get(constant_name: 'ROUTER_ALLOWED_ORIGINS');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Envia uma resposta JSON padronizada de erro e encerra a execução do script.
|
||||
*
|
||||
* Este método estático é um utilitário para endpoints de API, usado para comunicar falhas de forma consistente. Ele define o código de status HTTP do erro e envia uma mensagem detalhada no corpo da resposta JSON.
|
||||
*
|
||||
* #### Fluxo de Operação:
|
||||
* 1. **Define o Código HTTP:** O código de status HTTP (`$code`) é definido usando `http_response_code()` (ex: 400 Bad Request, 401 Unauthorized, 500 Internal Server Error).
|
||||
* 2. **Define o Cabeçalho:** O cabeçalho `Content-Type` é configurado para `application/json; charset=utf-8`.
|
||||
* 3. **Envia o JSON:** Uma resposta JSON é construída com um status fixo de 'error' e a mensagem de erro fornecida (`$msg`).
|
||||
* 4. **Encerra a Execução:** A execução do script é finalizada com `exit`, impedindo que códigos adicionais sejam processados após o envio da resposta.
|
||||
*
|
||||
* @param int $code O código de status HTTP a ser enviado na resposta de erro.
|
||||
* @param string $msg A mensagem de erro detalhada a ser incluída no corpo da resposta JSON.
|
||||
* @return void Este método não retorna um valor, pois ele finaliza a execução do script.
|
||||
*/
|
||||
private static function error(int $code, string $msg): void {
|
||||
http_response_code(response_code: $code);
|
||||
header(header: 'Content-Type: application/json; charset=utf-8');
|
||||
echo json_encode(value: [
|
||||
"status" => 'error',
|
||||
"message" => $msg
|
||||
]
|
||||
);
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Verifica se todas as constantes de configuração essenciais estão definidas no ambiente de execução.
|
||||
*
|
||||
* Este método estático é um **verificador de saúde** (health check) usado para garantir que o ambiente da
|
||||
* aplicação esteja corretamente configurado antes de prosseguir com a execução. Ele itera sobre uma lista
|
||||
* pré-definida de constantes (`self::$requiredConstants`) que são consideradas cruciais para a operação do sistema.
|
||||
*
|
||||
* #### Fluxo de Operação:
|
||||
* 1. **Iteração:** Percorre o array estático que lista os nomes das constantes obrigatórias.
|
||||
* 2. **Verificação:** Para cada nome de constante, ele usa `defined()` para verificar se a constante existe no escopo global do PHP.
|
||||
* 3. **Ação em Caso de Falha:** Se uma constante obrigatória **não estiver definida**, o método assume uma falha crítica de configuração.
|
||||
* Ele chama o método `self::error()`, que envia uma resposta JSON com o código HTTP **500 Internal Server Error** e uma mensagem
|
||||
* detalhando qual constante está faltando, encerrando a execução do script.
|
||||
*
|
||||
* @return void Este método não retorna um valor em caso de sucesso; ele apenas garante que as constantes existam. Em caso de falha, ele envia uma resposta HTTP de erro e encerra o script.
|
||||
*/
|
||||
private static function checkRequiredConstants(): void {
|
||||
foreach (self::$requiredConstants as $constant) {
|
||||
if (!defined(constant_name: $constant)) {
|
||||
self::error(
|
||||
code: 500,
|
||||
msg: "Constant '{$constant}' not defined."
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// =====================================
|
||||
// Métodos HTTP para definição de rotas
|
||||
// =====================================
|
||||
public static function get(string $uri, array $handler, array $middlewares = []): void {
|
||||
self::addRoute(method: 'GET', uri: $uri, handler: $handler, middlewares: $middlewares);
|
||||
}
|
||||
|
||||
public static function post(string $uri, array $handler, array $middlewares = []): void {
|
||||
self::addRoute(method: 'POST', uri: $uri, handler: $handler, middlewares: $middlewares);
|
||||
}
|
||||
|
||||
public static function put(string $uri, array $handler, array $middlewares = []): void {
|
||||
self::addRoute(method: 'PUT', uri: $uri, handler: $handler, middlewares: $middlewares);
|
||||
}
|
||||
|
||||
public static function patch(string $uri, array $handler, array $middlewares = []): void {
|
||||
self::addRoute(method: 'PATCH', uri: $uri, handler: $handler, middlewares: $middlewares);
|
||||
}
|
||||
|
||||
public static function delete(string $uri, array $handler, array $middlewares = []): void {
|
||||
self::addRoute(method: 'DELETE', uri: $uri, handler: $handler, middlewares: $middlewares);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adiciona uma nova definição de rota à lista de rotas do roteador.
|
||||
*
|
||||
* Este método privado é o núcleo do registro de rotas. Ele constrói o caminho final da rota (URI) combinando o prefixo de grupo atual, se houver, com o URI fornecido, e armazena os detalhes da rota (controlador, ação e middlewares) em um array estático (`self::$routes`).
|
||||
*
|
||||
* @param string $method O método HTTP (e.g., 'GET', 'POST', 'PATCH').
|
||||
* @param string $uri A URI específica da rota (relativa ao prefixo do grupo, se houver).
|
||||
* @param array $handler Um array contendo a classe do controlador e o método de ação (ex: ['Controller', 'method']).
|
||||
* @param array $middlewares Um array opcional de middlewares específicos desta rota.
|
||||
* @return void
|
||||
*/
|
||||
private static function addRoute(string $method, string $uri, array $handler, array $middlewares = []) {
|
||||
$path = '/' . trim(string: self::$currentGroupPrefix . '/' . trim(string: $uri, characters: '/'), characters: '/');
|
||||
[$controller, $action] = $handler;
|
||||
self::$routes[$method][] = [
|
||||
'method' => $method,
|
||||
'path' => $path,
|
||||
'controller' => $controller,
|
||||
'action' => $action,
|
||||
'middlewares' => array_merge(self::$currentGroupMiddlewares, $middlewares)
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Agrupa um conjunto de rotas sob um prefixo de URI e aplica middlewares em comum.
|
||||
*
|
||||
* Este método estático é uma ferramenta poderosa para organizar rotas, permitindo que todas as rotas definidas dentro da função de callback (`$callback`) herdem um prefixo de URI comum e uma lista de middlewares.
|
||||
*
|
||||
* #### Fluxo de Operação:
|
||||
* 1. **Backup de Contexto:** Os prefixos e middlewares atuais (`self::$currentGroupPrefix` e `self::$currentGroupMiddlewares`) são salvos temporariamente. Isso é essencial para suportar o aninhamento (grupos dentro de grupos).
|
||||
* 2. **Definição do Novo Contexto:** O prefixo do novo grupo (`$prefix`) é concatenado ao prefixo existente (`$previousPrefix`), e os novos middlewares são mesclados com os existentes.
|
||||
* 3. **Execução das Rotas:** A função de callback (`$callback`) é executada. Todas as chamadas de rotas (`GET`, `POST`, etc.) feitas aqui dentro usarão o novo prefixo e herdarão os novos middlewares.
|
||||
* 4. **Restauração do Contexto:** Após a execução do callback, os prefixos e middlewares originais são restaurados. Isso garante que rotas definidas após o grupo não sejam afetadas pelo prefixo ou middlewares internos do grupo.
|
||||
*
|
||||
* @param string $prefix O prefixo da URI a ser aplicado a todas as rotas dentro do grupo (ex: '/api/v1').
|
||||
* @param callable $callback A função que contém a definição das rotas a serem agrupadas.
|
||||
* @param array $middlewares Um array opcional de middlewares que serão aplicados a todas as rotas dentro deste grupo e em seus subgrupos.
|
||||
* @return void
|
||||
*/
|
||||
public static function group(string $prefix, callable $callback, array $middlewares = []): void {
|
||||
$previousPrefix = self::$currentGroupPrefix ?? '';
|
||||
$previousMiddlewares = self::$currentGroupMiddlewares ?? [];
|
||||
|
||||
self::$currentGroupPrefix = $previousPrefix . $prefix;
|
||||
self::$currentGroupMiddlewares = array_merge($previousMiddlewares, $middlewares);
|
||||
|
||||
$callback();
|
||||
|
||||
self::$currentGroupPrefix = $previousPrefix;
|
||||
self::$currentGroupMiddlewares = $previousMiddlewares;
|
||||
}
|
||||
|
||||
/**
|
||||
* Verifica se o caminho da requisição (URI) corresponde ao padrão de uma rota registrada.
|
||||
*
|
||||
* Este método estático privado é essencial para o roteador. Ele compara o caminho da URI solicitada pelo
|
||||
* cliente com o padrão de rota (`$routePath`) e extrai quaisquer parâmetros dinâmicos presentes na URI.
|
||||
*
|
||||
* @param string $routePath O padrão de URI da rota registrada (pode conter placeholders como '/users/{id}').
|
||||
* @param string $requestPath A URI real da requisição (ex: '/users/123').
|
||||
* @return bool Retorna `true` se o `$requestPath` corresponder ao `$routePath`; caso contrário, retorna `false`.
|
||||
*/
|
||||
private static function matchPath($routePath, $requestPath): bool {
|
||||
self::$params = [];
|
||||
|
||||
$routeParts = explode(separator: '/', string: trim(string: $routePath, characters: '/'));
|
||||
$reqParts = explode(separator: '/', string: trim(string: $requestPath, characters: '/'));
|
||||
|
||||
if(count($routeParts) !== count($reqParts)) return false;
|
||||
foreach ($routeParts as $i => $part) {
|
||||
if (preg_match(pattern: '/^{\w+}$/', subject: $part)) {
|
||||
self::$params[] = $reqParts[$i];
|
||||
} elseif ($part !== $reqParts[$i]) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepara e retorna o array final de parâmetros a ser passado para o método de ação do controlador.
|
||||
*
|
||||
* Este método estático privado retorna os parâmetros de rota dinâmicos extraídos do URI (`self::$params`).
|
||||
*
|
||||
* @return array Um array indexado numericamente contendo a lista final de argumentos de rota para o método do controlador.
|
||||
*/
|
||||
private static function prepareMethodParameters(): array {
|
||||
return array_values(array: self::$params);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determina se uma rota registrada corresponde ao método HTTP e à URI da requisição atual.
|
||||
*
|
||||
* Este método estático privado é o principal mecanismo de correspondência de rotas do roteador.
|
||||
* Ele verifica se o método HTTP da rota é o mesmo da requisição e, em seguida, usa o método auxiliar
|
||||
* `matchPath` para verificar se o padrão da URI da rota corresponde ao caminho solicitado, considerando
|
||||
* quaisquer parâmetros dinâmicos.
|
||||
*
|
||||
* @param string $method O método HTTP da requisição atual (e.g., 'GET', 'POST').
|
||||
* @param string $uri A URI solicitada pelo cliente (caminho da requisição).
|
||||
* @param array $route Um array de definição de rota contendo as chaves 'method' e 'path'.
|
||||
* @return bool Retorna `true` se o método HTTP e o caminho da URI corresponderem; caso contrário, retorna `false`.
|
||||
*/
|
||||
private static function matchRoute(string $method, string $uri, array $route): bool {
|
||||
return $route['method'] === $method && self::matchPath(routePath: $route['path'], requestPath: $uri);
|
||||
}
|
||||
|
||||
/**
|
||||
* Configura os cabeçalhos Cross-Origin Resource Sharing (CORS) para requisições de API.
|
||||
*
|
||||
* Este método privado verifica se a requisição é permitida de acordo com a política de CORS definida na aplicação.
|
||||
* Ele permite ou nega o acesso de origens externas com base nas configurações e no modo de operação do sistema.
|
||||
*
|
||||
* @param string $method O método HTTP da requisição atual (e.g., 'OPTIONS', 'GET', 'POST').
|
||||
* @return void Este método encerra a execução em caso de requisições OPTIONS ou de origem não permitida.
|
||||
*/
|
||||
private static function corsSetup(string $method): void {
|
||||
$origin = $_SERVER['HTTP_ORIGIN'] ?? '*';
|
||||
$allowAll = in_array(needle: '*', haystack: self::$ROUTER_ALLOWED_ORIGINS);
|
||||
$isAllowed = in_array(needle: $origin, haystack: self::$ROUTER_ALLOWED_ORIGINS);
|
||||
|
||||
if ($allowAll || $isAllowed || self::$APP_SYS_MODE === 'DEV') {
|
||||
header(header: "Access-Control-Allow-Origin: $origin");
|
||||
} else {
|
||||
self::error(code: 403, msg: "Origin '{$origin}' not allowed by CORS.");
|
||||
}
|
||||
|
||||
$allowedRequests = implode(separator: ', ', array: self::$allowedHttpRequests);
|
||||
header(header: "Access-Control-Allow-Methods: {$allowedRequests}");
|
||||
header(header: 'Access-Control-Allow-Headers: Content-Type, Authorization');
|
||||
|
||||
if ($method === 'OPTIONS') {
|
||||
http_response_code(response_code: 204);
|
||||
exit;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Executa sequencialmente a pilha de Middlewares vinculada a uma rota.
|
||||
*
|
||||
* Este método atua como uma barreira de segurança e processamento pré-execução (Pipeline). Ele permite
|
||||
* interceptar a requisição antes que ela chegue ao controlador, sendo ideal para verificações de
|
||||
* autenticação, autorização de perfis (ACL), logs de acesso ou manutenção de sistema. O método
|
||||
* suporta injeção dinâmica de argumentos para os middlewares e oferece dois níveis de bloqueio:
|
||||
* um booleano simples e um detalhado com respostas JSON customizadas.
|
||||
*
|
||||
* ---
|
||||
* ## Mecanismo de Execução
|
||||
* 1. **Validação de Formato:** Verifica se a definição do middleware segue o padrão esperado: `[Classe, 'metodo', ...argumentos]`.
|
||||
* 2. **Instanciação Dinâmica:** Localiza a classe e o método, instanciando-os em tempo de execução.
|
||||
* 3. **Gestão de Bloqueios:**
|
||||
* - **Bloqueio Booleano:** Se o middleware retornar explicitamente `false`, a requisição é negada com um erro 403 padrão.
|
||||
* - **Bloqueio Estruturado:** Se retornar um `array`, o método analisa chaves como `block`, `status` e `response_code` para montar uma resposta JSON rica e encerrar o script.
|
||||
* 4. **Continuidade:** Se todos os middlewares retornarem `true` (ou um array indicando sucesso), o fluxo retorna `true`, permitindo que o `dispatch()` prossiga para o controlador.
|
||||
*
|
||||
* @param array $middlewares Lista de arrays contendo a definição dos middlewares da rota.
|
||||
* @return bool Retorna `true` se a requisição passou por todos os filtros sem ser bloqueada.
|
||||
*/
|
||||
public static function runMiddlewares(array $middlewares): bool {
|
||||
foreach ($middlewares as $middleware) {
|
||||
try {
|
||||
if (!is_array(value: $middleware) || count(value: $middleware) < 2) {
|
||||
self::error(code: 500, msg: "Invalid middleware format. Expected: [Class::class, 'method', ...args]");
|
||||
}
|
||||
|
||||
$class = $middleware[0];
|
||||
$method = $middleware[1];
|
||||
$args = array_slice(array: $middleware, offset: 2);
|
||||
|
||||
if (!class_exists(class: $class)) {
|
||||
self::error(code: 500, msg: "Middleware class '{$class}' not found.");
|
||||
}
|
||||
|
||||
if (!method_exists(object_or_class: $class, method: $method)) {
|
||||
self::error(code: 500, msg: "Method '{$method}' does not exist in class '{$class}'.");
|
||||
}
|
||||
|
||||
$instance = new $class();
|
||||
$result = call_user_func_array(callback: [$instance, $method], args: $args);
|
||||
|
||||
// bloqueio simples
|
||||
if ($result === false) {
|
||||
self::error(code: 403, msg: "{$class}::{$method} blocked the request.");
|
||||
}
|
||||
|
||||
// bloqueio detalhado
|
||||
if (is_array(value: $result)) {
|
||||
$block = $result['block'] ?? null;
|
||||
$status = $result['status'] ?? null;
|
||||
|
||||
$shouldBlock = ($block === true) || ($status !== null && $status !== 'success');
|
||||
if ($shouldBlock) {
|
||||
$code = (int) ($result['response_code'] ?? 403);
|
||||
$msg = (string) ($result['message'] ?? 'Blocked by middleware');
|
||||
$json_response = [
|
||||
"message" => $msg ?? "{$class}::{$method} blocked the request."
|
||||
];
|
||||
if(isset($result['output']) && (!empty($result['output']) || $result['output'] !== null || $result['output'] !== '')) {
|
||||
$json_response['output'] = $result['output'];
|
||||
}
|
||||
|
||||
http_response_code(response_code: $code);
|
||||
header(header: 'Content-Type: application/json; charset=utf-8');
|
||||
echo json_encode(value: $json_response);
|
||||
exit;
|
||||
}
|
||||
}
|
||||
} catch (Exception $e) {
|
||||
self::error(code: 500, msg: $e->getMessage());
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Orquestra o ciclo de vida da requisição, realizando o roteamento e a execução do controlador.
|
||||
* * Este método é o ponto de entrada principal (Front Controller) que transforma uma requisição
|
||||
* HTTP bruta em uma ação de software. Ele gerencia desde a validação de constantes de ambiente
|
||||
* até a resolução de parâmetros dinâmicos da URL, passando por suporte a emulação de métodos REST
|
||||
* (via `_method`), configuração de CORS, execução de Middlewares e, por fim, a invocação do
|
||||
* par Controller/Action correspondente.
|
||||
* * @return void Este método encerra a execução do script (`exit`) ao encontrar e executar uma rota válida.
|
||||
*/
|
||||
public static function dispatch(): void {
|
||||
self::checkRequiredConstants();
|
||||
$method = $_SERVER['REQUEST_METHOD'];
|
||||
$uri = trim(string: parse_url(url: $_SERVER['REQUEST_URI'], component: PHP_URL_PATH), characters: '/');
|
||||
|
||||
if ($method === 'POST' && isset($_POST['_method'])) {
|
||||
$method = strtoupper(string: $_POST['_method']);
|
||||
}
|
||||
if (!in_array(needle: $method, haystack: self::$allowedHttpRequests)) {
|
||||
self::error(code: 405, msg: "HTTP method '{$method}' not allowed.");
|
||||
}
|
||||
|
||||
self::corsSetup(method: $method);
|
||||
|
||||
// remove basePath
|
||||
if (!empty(self::$basePath) && str_starts_with(haystack: $uri, needle: trim(string: self::$basePath, characters: '/'))) {
|
||||
$uri = substr(string: $uri, offset: strlen(string: trim(string: self::$basePath, characters: '/')));
|
||||
}
|
||||
$uri = trim(string: $uri, characters: '/');
|
||||
|
||||
foreach (self::$routes[$method] ?? [] as $route) {
|
||||
if (!self::matchRoute(method: $method, uri: $uri, route: $route)) continue;
|
||||
|
||||
// roda middlewares (se barrar, o runMiddlewares já respondeu JSON)
|
||||
if (!empty($route['middlewares']) && !self::runMiddlewares(middlewares: $route['middlewares'])) {
|
||||
return;
|
||||
}
|
||||
|
||||
$controller = new $route['controller']();
|
||||
$action = $route['action'];
|
||||
|
||||
if (!method_exists(object_or_class: $controller, method: $action)) {
|
||||
self::error(code: 500, msg: "Method '{$action}' not found.");
|
||||
}
|
||||
|
||||
$params = self::prepareMethodParameters();
|
||||
|
||||
// se teu controller já dá echo/json, tu nem precisa setar 200/header aqui
|
||||
call_user_func_array(callback: [$controller, $action], args: $params);
|
||||
exit;
|
||||
}
|
||||
|
||||
self::error(
|
||||
code: 404,
|
||||
msg: 'Page not found.'
|
||||
);
|
||||
}
|
||||
}
|
||||
+178
@@ -0,0 +1,178 @@
|
||||
<?php
|
||||
namespace KrothiumAPI;
|
||||
|
||||
use KrothiumAPI\Http\Router;
|
||||
use KrothiumAPI\Helpers\ConstHelper;
|
||||
use KrothiumAPI\Services\LoggerService;
|
||||
|
||||
class KrothiumAPI {
|
||||
private static array $config;
|
||||
|
||||
/**
|
||||
* Inicializa e configura os componentes essenciais da aplicação.
|
||||
*
|
||||
* Este método estático é o ponto de partida para a inicialização do sistema. Ele carrega as configurações, define o tratamento de erros, gerencia a sessão, configura o fuso horário e inicializa o sistema de logs e o roteador.
|
||||
*
|
||||
* #### Fluxo de Operação:
|
||||
* 1. **Carregamento de Configurações:** Armazena o array de configurações (`$config`) na propriedade estática da classe.
|
||||
* 2. **Setup Essencial:** Chama métodos privados para configurar:
|
||||
* * `setupErrors()`: Configuração de exibição de erros.
|
||||
* * `setupSession()`: Inicialização da sessão PHP.
|
||||
* * `setupTimezone()`: Definição do fuso horário da aplicação.
|
||||
* * `setupConstants()`: Definição de constantes globais (se houver).
|
||||
* * `setupLogger()`: Inicialização do sistema de logs.
|
||||
* * `setupErrorHandlers()`: Definição de manipuladores de exceção e erros customizados.
|
||||
* 3. **Inicialização do Roteador:** Chama `Router::init()` para inicializar o sistema de roteamento, preparando-o para receber e despachar requisições.
|
||||
*
|
||||
* @param array $config Um array de configurações iniciais a serem aplicadas na aplicação. Padrão: `[]`.
|
||||
* @return void
|
||||
*/
|
||||
public static function init(array $config = []) {
|
||||
self::$config = $config;
|
||||
self::setupConstants();
|
||||
// Inicia o router
|
||||
Router::init();
|
||||
|
||||
self::setupErrors();
|
||||
self::setupSession();
|
||||
self::setupTimezone();
|
||||
self::setupLogger();
|
||||
self::setupErrorHandlers();
|
||||
}
|
||||
|
||||
/**
|
||||
* Configura erros
|
||||
*/
|
||||
private static function setupErrors(): void {
|
||||
$errors = self::$config['errors'];
|
||||
if(ConstHelper::get(constant_name: 'APP_SYS_MODE') === 'DEV') {
|
||||
ini_set(option: 'display_errors', value: 1);
|
||||
ini_set(option: 'display_startup_errors', value: 1);
|
||||
ini_set(option: 'log_errors', value: E_ALL);
|
||||
error_reporting(error_level: 1);
|
||||
if(isset($errors['error_log'])) {
|
||||
ini_set(option: 'error_log', value: $errors['error_log']);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Configura constantes
|
||||
*/
|
||||
private static function setupConstants() {
|
||||
$constants = self::$config['constants'];
|
||||
if(!empty($constants)) {
|
||||
foreach ($constants as $name => $value) {
|
||||
if (!defined(constant_name: $name)) {
|
||||
define(constant_name: $name, value: $value);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Configura timezone
|
||||
*/
|
||||
private static function setupTimezone(): void {
|
||||
$timezone = self::$config['system']['default_timezone'];
|
||||
if(!empty($timezone)) {
|
||||
date_default_timezone_set(timezoneId: $timezone);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Configura sessão
|
||||
*/
|
||||
private static function setupSession(): void {
|
||||
$startSession = self::$config['system']['enable_session'];
|
||||
if (($startSession === true) && session_status() === PHP_SESSION_NONE) {
|
||||
session_start();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Configura logger
|
||||
*/
|
||||
private static function setupLogger(): void {
|
||||
$logConfig = self::$config['logger'];
|
||||
if(!empty($logConfig)) {
|
||||
LoggerService::init(
|
||||
driver: $logConfig['driver'],
|
||||
logDir: $logConfig['logDir']
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Configura handlers de erro para modo JSON
|
||||
*/
|
||||
private static function setupErrorHandlers(): void {
|
||||
// Captura warnings / notices
|
||||
set_error_handler(callback: function ($errno, $errstr, $errfile, $errline) {
|
||||
self::jsonErrorResponse(
|
||||
message: "Erro PHP: {$errstr}",
|
||||
code: $errno,
|
||||
extra: [
|
||||
'file' => $errfile,
|
||||
'line' => $errline
|
||||
]
|
||||
);
|
||||
});
|
||||
// Captura exceptions não tratadas
|
||||
set_exception_handler(callback: function ($exception) {
|
||||
self::jsonErrorResponse(
|
||||
message: $exception->getMessage(),
|
||||
code: $exception->getCode(),
|
||||
extra: [
|
||||
'file' => $exception->getFile(),
|
||||
'line' => $exception->getLine(),
|
||||
'trace' => $exception->getTrace()
|
||||
]
|
||||
);
|
||||
});
|
||||
// Captura fatal errors (shutdown)
|
||||
register_shutdown_function(callback: function () {
|
||||
$error = error_get_last();
|
||||
if ($error !== null) {
|
||||
self::jsonErrorResponse(
|
||||
message: $error['message'],
|
||||
code: $error['type'],
|
||||
extra: [
|
||||
'file' => $error['file'],
|
||||
'line' => $error['line']
|
||||
]
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Retorna resposta JSON de erro e encerra execução
|
||||
*/
|
||||
private static function jsonErrorResponse(string $message, int $code = 500, array $extra = []): void {
|
||||
// Evita headers duplicados
|
||||
if (!headers_sent()) {
|
||||
http_response_code(response_code: 500);
|
||||
header(header: 'Content-Type: application/json; charset=utf-8');
|
||||
}
|
||||
echo json_encode(
|
||||
value: [
|
||||
'status' => 'error',
|
||||
'message' => $message,
|
||||
'code' => $code,
|
||||
'extra' => $extra,
|
||||
],
|
||||
flags: JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT
|
||||
);
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Dispara roteador
|
||||
*/
|
||||
public static function routerDispatch() {
|
||||
if (php_sapi_name() !== 'cli') {
|
||||
Router::dispatch();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
<?php
|
||||
namespace KrothiumAPI\Services;
|
||||
|
||||
use DateTime;
|
||||
use Exception;
|
||||
use KrothiumAPI\Helpers\ConstHelper;
|
||||
|
||||
class LoggerService {
|
||||
private static string $logDir;
|
||||
public const DRIVER_FILE = 'FILE';
|
||||
private static bool $initialized = false;
|
||||
private static string $driver = self::DRIVER_FILE;
|
||||
|
||||
/**
|
||||
* Inicializa o Logger
|
||||
*/
|
||||
public static function init(string $driver = self::DRIVER_FILE,?string $logDir = null): void {
|
||||
self::$driver = strtoupper(string: $driver);
|
||||
if (!defined(constant_name: 'STORAGE_FOLDER_PATH')) {
|
||||
throw new Exception(message: "Constant 'STORAGE_FOLDER_PATH' is not defined.");
|
||||
}
|
||||
self::$logDir = $logDir ?? ConstHelper::get(constant_name: 'STORAGE_FOLDER_PATH') . '/logs';
|
||||
if (self::$driver === self::DRIVER_FILE && !is_dir(filename: self::$logDir)) {
|
||||
if (!mkdir(directory: self::$logDir, permissions: 0775, recursive: true) && !is_dir(filename: self::$logDir)) {
|
||||
throw new Exception(message: "Failed to create log directory: " . self::$logDir);
|
||||
}
|
||||
}
|
||||
self::$initialized = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Log genérico
|
||||
*/
|
||||
public static function log(string $message, string $level = 'INFO', array $context = []): void {
|
||||
if (!self::$initialized) {
|
||||
throw new Exception(message: "LoggerService is not initialized. Call LoggerService::init() first.");
|
||||
}
|
||||
switch (self::$driver) {
|
||||
case self::DRIVER_FILE:
|
||||
self::logToFile(message: $message, level: $level);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Métodos auxiliares por nível
|
||||
public static function info(string $message, array $context = []): void { self::log(message: $message, level: 'INFO', context: $context); }
|
||||
|
||||
public static function warning(string $message, array $context = []): void { self::log(message: $message, level: 'WARNING', context: $context); }
|
||||
|
||||
public static function error(string $message, array $context = []): void { self::log(message: $message, level: 'ERROR', context: $context); }
|
||||
|
||||
public static function debug(string $message, array $context = []): void { self::log(message: $message, level: 'DEBUG', context: $context); }
|
||||
|
||||
/**
|
||||
* Log para arquivo
|
||||
*/
|
||||
private static function logToFile(string $message, string $level): void {
|
||||
$date = (new DateTime())->format(format: 'Y-m-d');
|
||||
$now = (new DateTime())->format(format: 'Y-m-d H:i:s');
|
||||
$filename = self::$logDir . "/app-{$date}.log";
|
||||
$logMessage = "[$now][$level] $message" . PHP_EOL;
|
||||
file_put_contents(filename: $filename, data: $logMessage, flags: FILE_APPEND);
|
||||
}
|
||||
}
|
||||
+202
@@ -0,0 +1,202 @@
|
||||
<?php
|
||||
namespace KrothiumAPI\Utils;
|
||||
|
||||
class HttpUtil {
|
||||
/**
|
||||
* Captura e filtra dados de entrada HTTP (GET, POST, COOKIE, SERVER) de forma segura.
|
||||
*
|
||||
* Este método centraliza a obtenção de dados de requisição, aplicando opcionalmente filtros.
|
||||
* Ele lida com submissões **POST** padrão (urlencoded) e submissões **JSON** (tipo "application/json"),
|
||||
* retornando os dados como um array associativo ou como a string JSON bruta, se especificado.
|
||||
* Em caso de JSON inválido, a função encerra a execução e retorna um erro HTTP 500.
|
||||
*
|
||||
* @param string $form_type O tipo de entrada a ser filtrada (constantes PHP: INPUT_GET, INPUT_POST, INPUT_COOKIE, INPUT_SERVER). O padrão é INPUT_GET.
|
||||
* @param array|null $filters Uma matriz de filtros a serem aplicados aos dados de entrada, compatível com a função `filter_input_array()`.
|
||||
* @return array|string Retorna um array associativo dos dados de entrada filtrados (ou array vazio se não houver dados), ou a string JSON bruta se $asJson for true e o POST for JSON.
|
||||
* @return mixed Encerra a execução e envia uma resposta HTTP 500 se ocorrer um erro de decodificação JSON.
|
||||
*/
|
||||
public static function getRequestBody(string $form_type = 'GET', ?array $filters = null, string $return_type = 'array'): mixed {
|
||||
$method = strtoupper(string: trim(string: $form_type));
|
||||
$contentType = $_SERVER['CONTENT_TYPE'] ?? '';
|
||||
$isJson = self::isJsonContentType(contentType: $contentType);
|
||||
|
||||
$inputMap = [
|
||||
'GET' => INPUT_GET,
|
||||
'POST' => INPUT_POST,
|
||||
'COOKIE' => INPUT_COOKIE,
|
||||
'SERVER' => INPUT_SERVER,
|
||||
];
|
||||
|
||||
// GET/POST/COOKIE/SERVER
|
||||
if (isset($inputMap[$method])) {
|
||||
// POST JSON: lê o body
|
||||
if ($method === 'POST' && $isJson) {
|
||||
$raw = file_get_contents(filename: 'php://input') ?: '';
|
||||
if ($return_type === 'string') {
|
||||
return $raw;
|
||||
}
|
||||
$data = json_decode(json: $raw, associative: true);
|
||||
if (json_last_error() !== JSON_ERROR_NONE) {
|
||||
self::errorJson(statusCode: 400, message: 'Invalid JSON: ' . json_last_error_msg());
|
||||
}
|
||||
unset($data['_method']);
|
||||
return self::applyFilters(data: $data ?? [], filters: $filters);
|
||||
}
|
||||
$form = filter_input_array(type: $inputMap[$method], options: $filters ?? FILTER_DEFAULT) ?? [];
|
||||
return is_array(value: $form) ? $form : [];
|
||||
}
|
||||
|
||||
// PUT/PATCH/DELETE
|
||||
if (in_array(needle: $method, haystack: ['PUT', 'PATCH', 'DELETE'], strict: true)) {
|
||||
$raw = file_get_contents(filename: 'php://input') ?: '';
|
||||
if ($raw === '') {
|
||||
return [];
|
||||
}
|
||||
if ($isJson) {
|
||||
if ($return_type === 'string') {
|
||||
return $raw;
|
||||
}
|
||||
$data = json_decode(json: $raw, associative: true);
|
||||
if (json_last_error() !== JSON_ERROR_NONE) {
|
||||
self::errorJson(statusCode: 400, message: 'Invalid JSON: ' . json_last_error_msg());
|
||||
}
|
||||
} else {
|
||||
parse_str($raw, $data);
|
||||
}
|
||||
unset($data['_method']);
|
||||
return self::applyFilters(data: $data ?? [], filters: $filters);
|
||||
}
|
||||
|
||||
// Default: GET
|
||||
$form = filter_input_array(type: INPUT_GET, options: $filters ?? FILTER_DEFAULT) ?? [];
|
||||
return is_array(value: $form) ? $form : [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Despacha uma resposta JSON customizada e encerra a execução do script.
|
||||
* Ele permite a mesclagem (merge) de um array de saída personalizado
|
||||
* diretamente na raiz do objeto JSON, além de suportar mensagens de feedback opcionais.
|
||||
*
|
||||
* @param int $response_code Código de status HTTP (ex: 200, 201, 403).
|
||||
* @param string|null $message Mensagem de texto opcional para o cliente.
|
||||
* @param array|null $output Array associativo de dados extras a serem mesclados na resposta.
|
||||
* @return void Este método interrompe o fluxo do programa imediatamente.
|
||||
*/
|
||||
public static function jsonResponse(int $response_code, ?string $message = null, ?array $output = null): void {
|
||||
// Constrói o array de resposta com os campos "message" e "data" se eles forem fornecidos
|
||||
$response = [];
|
||||
if ($message) {
|
||||
$response['message'] = $message;
|
||||
}
|
||||
if ($output) {
|
||||
$response = array_merge($response, $output);
|
||||
}
|
||||
// Define o código de resposta HTTP e o cabeçalho de conteúdo, e envia a resposta JSON
|
||||
http_response_code(response_code: $response_code);
|
||||
header(header: 'Content-Type: application/json; charset=utf-8');
|
||||
if (!empty($response)) {
|
||||
echo json_encode($response, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT);
|
||||
}
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Realiza o redirecionamento do navegador para uma nova URL e encerra a execução do script.
|
||||
*
|
||||
* @param string $url O endereço de destino (URL absoluta ou caminho relativo) para onde o usuário será redirecionado.
|
||||
* @return void Não retorna valor, pois encerra a execução do processo PHP.
|
||||
*/
|
||||
public static function redirect(string $url): void {
|
||||
// Remove barra final do subpath para evitar //
|
||||
$subpath = rtrim(string: self::getSubpath(), characters: '/');
|
||||
// Garante que a URL comece com /
|
||||
$url = str_starts_with(haystack: $url, needle: '/')
|
||||
? $url
|
||||
: "/{$url}";
|
||||
// Persiste sessão antes do redirect
|
||||
session_write_close();
|
||||
// Monta URL final
|
||||
$location = "{$subpath}{$url}" . self::getQueryString();
|
||||
header(header: "Location: {$location}");
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extrai a string de query da URI da requisição.
|
||||
*
|
||||
* @return string|null A string de query completa, ou `null` se a URI não contiver uma string de query.
|
||||
*/
|
||||
public static function getQueryString(): ?string {
|
||||
$parts = explode(separator: '?', string: $_SERVER['REQUEST_URI'], limit: 2); // limite 2 garante que só divide em duas partes
|
||||
$request = $parts[1] ?? null; // se não existir, define null
|
||||
return ($request !== null && $request !== '') ? "?{$request}" : '';
|
||||
}
|
||||
|
||||
public static function getSubpath(): string {
|
||||
return $_SESSION['ROUTER_BASE_PATH'] ?? '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Verifica se o cabeçalho de tipo de conteúdo (Content-Type) da requisição indica um formato JSON.
|
||||
*
|
||||
* @param string $contentType O valor bruto extraído do cabeçalho `$_SERVER['CONTENT_TYPE']`.
|
||||
* @return bool Retorna `true` se o formato JSON for detectado, caso contrário, `false`.
|
||||
*/
|
||||
private static function isJsonContentType(string $contentType): bool {
|
||||
// pega application/json, application/json; charset=utf-8, application/vnd.api+json, etc.
|
||||
return stripos(haystack: $contentType, needle: 'json') !== false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Interrompe a execução do script e envia uma resposta de erro padronizada em formato JSON.
|
||||
*
|
||||
* @param int $statusCode Código de status HTTP (ex: 400, 403, 404, 500).
|
||||
* @param string $message Mensagem descritiva detalhando o motivo do erro.
|
||||
* @return never Este método encerra a execução do script e nunca retorna ao chamador.
|
||||
*/
|
||||
private static function errorJson(int $statusCode, string $message): never {
|
||||
http_response_code(response_code: $statusCode);
|
||||
header(header: "Content-Type: application/json; charset=utf-8");
|
||||
echo json_encode(value: [
|
||||
"status" => "error",
|
||||
"message" => $message,
|
||||
]);
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Aplica filtros de higienização e validação em um conjunto de dados brutos de forma segura.
|
||||
*
|
||||
* @param array $data O array associativo de dados brutos a serem processados.
|
||||
* @param array|null $filters O mapa de definições de filtros (ex: `['id' => FILTER_VALIDATE_INT]`).
|
||||
* @return array O conjunto de dados resultantes após a validação e higienização.
|
||||
*/
|
||||
private static function applyFilters(array $data, ?array $filters): array {
|
||||
if ($filters === null) {
|
||||
return $data;
|
||||
}
|
||||
$filtered = filter_var_array(array: $data, options: $filters, add_empty: false);
|
||||
return is_array(value: $filtered) ? $filtered : [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Extrai o token de autenticação do tipo 'Bearer' do cabeçalho da requisição HTTP.
|
||||
*
|
||||
* @return string|null O token de autenticação do tipo 'Bearer' como uma string, ou `null`
|
||||
* se o cabeçalho não for encontrado ou não estiver no formato esperado.
|
||||
*/
|
||||
public static function getBearerToken(): ?string {
|
||||
// Tenta pegar o header de todas as fontes possíveis
|
||||
$headers = $_SERVER['Authorization'] ?? $_SERVER['HTTP_AUTHORIZATION'] ?? $_SERVER['REDIRECT_HTTP_AUTHORIZATION'] ?? (function_exists(function: 'apache_request_headers') ? apache_request_headers() : []);
|
||||
// Se veio do apache_request_headers, normaliza chaves
|
||||
if (is_array(value: $headers)) {
|
||||
$headers = array_change_key_case(array: $headers, case: CASE_LOWER);
|
||||
$headers = $headers['authorization'] ?? '';
|
||||
}
|
||||
$headers = trim(string: $headers);
|
||||
if ($headers && preg_match(pattern: '/Bearer\s(\S+)/', subject: $headers, matches: $matches)) {
|
||||
return $matches[1];
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
<?php
|
||||
|
||||
// autoload.php @generated by Composer
|
||||
|
||||
if (PHP_VERSION_ID < 50600) {
|
||||
if (!headers_sent()) {
|
||||
header('HTTP/1.1 500 Internal Server Error');
|
||||
}
|
||||
$err = 'Composer 2.3.0 dropped support for autoloading on PHP <5.6 and you are running '.PHP_VERSION.', please upgrade PHP or use Composer 2.2 LTS via "composer self-update --2.2". Aborting.'.PHP_EOL;
|
||||
if (!ini_get('display_errors')) {
|
||||
if (PHP_SAPI === 'cli' || PHP_SAPI === 'phpdbg') {
|
||||
fwrite(STDERR, $err);
|
||||
} elseif (!headers_sent()) {
|
||||
echo $err;
|
||||
}
|
||||
}
|
||||
trigger_error(
|
||||
$err,
|
||||
E_USER_ERROR
|
||||
);
|
||||
}
|
||||
|
||||
require_once __DIR__ . '/composer/autoload_real.php';
|
||||
|
||||
return ComposerAutoloaderInit5598d6c42b75c33b245408cd5e519a5c::getLoader();
|
||||
@@ -0,0 +1,579 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Composer.
|
||||
*
|
||||
* (c) Nils Adermann <naderman@naderman.de>
|
||||
* 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 Composer\Autoload;
|
||||
|
||||
/**
|
||||
* ClassLoader implements a PSR-0, PSR-4 and classmap class loader.
|
||||
*
|
||||
* $loader = new \Composer\Autoload\ClassLoader();
|
||||
*
|
||||
* // register classes with namespaces
|
||||
* $loader->add('Symfony\Component', __DIR__.'/component');
|
||||
* $loader->add('Symfony', __DIR__.'/framework');
|
||||
*
|
||||
* // activate the autoloader
|
||||
* $loader->register();
|
||||
*
|
||||
* // to enable searching the include path (eg. for PEAR packages)
|
||||
* $loader->setUseIncludePath(true);
|
||||
*
|
||||
* In this example, if you try to use a class in the Symfony\Component
|
||||
* namespace or one of its children (Symfony\Component\Console for instance),
|
||||
* the autoloader will first look for the class under the component/
|
||||
* directory, and it will then fallback to the framework/ directory if not
|
||||
* found before giving up.
|
||||
*
|
||||
* This class is loosely based on the Symfony UniversalClassLoader.
|
||||
*
|
||||
* @author Fabien Potencier <fabien@symfony.com>
|
||||
* @author Jordi Boggiano <j.boggiano@seld.be>
|
||||
* @see https://www.php-fig.org/psr/psr-0/
|
||||
* @see https://www.php-fig.org/psr/psr-4/
|
||||
*/
|
||||
class ClassLoader
|
||||
{
|
||||
/** @var \Closure(string):void */
|
||||
private static $includeFile;
|
||||
|
||||
/** @var string|null */
|
||||
private $vendorDir;
|
||||
|
||||
// PSR-4
|
||||
/**
|
||||
* @var array<string, array<string, int>>
|
||||
*/
|
||||
private $prefixLengthsPsr4 = array();
|
||||
/**
|
||||
* @var array<string, list<string>>
|
||||
*/
|
||||
private $prefixDirsPsr4 = array();
|
||||
/**
|
||||
* @var list<string>
|
||||
*/
|
||||
private $fallbackDirsPsr4 = array();
|
||||
|
||||
// PSR-0
|
||||
/**
|
||||
* List of PSR-0 prefixes
|
||||
*
|
||||
* Structured as array('F (first letter)' => array('Foo\Bar (full prefix)' => array('path', 'path2')))
|
||||
*
|
||||
* @var array<string, array<string, list<string>>>
|
||||
*/
|
||||
private $prefixesPsr0 = array();
|
||||
/**
|
||||
* @var list<string>
|
||||
*/
|
||||
private $fallbackDirsPsr0 = array();
|
||||
|
||||
/** @var bool */
|
||||
private $useIncludePath = false;
|
||||
|
||||
/**
|
||||
* @var array<string, string>
|
||||
*/
|
||||
private $classMap = array();
|
||||
|
||||
/** @var bool */
|
||||
private $classMapAuthoritative = false;
|
||||
|
||||
/**
|
||||
* @var array<string, bool>
|
||||
*/
|
||||
private $missingClasses = array();
|
||||
|
||||
/** @var string|null */
|
||||
private $apcuPrefix;
|
||||
|
||||
/**
|
||||
* @var array<string, self>
|
||||
*/
|
||||
private static $registeredLoaders = array();
|
||||
|
||||
/**
|
||||
* @param string|null $vendorDir
|
||||
*/
|
||||
public function __construct($vendorDir = null)
|
||||
{
|
||||
$this->vendorDir = $vendorDir;
|
||||
self::initializeIncludeClosure();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, list<string>>
|
||||
*/
|
||||
public function getPrefixes()
|
||||
{
|
||||
if (!empty($this->prefixesPsr0)) {
|
||||
return call_user_func_array('array_merge', array_values($this->prefixesPsr0));
|
||||
}
|
||||
|
||||
return array();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, list<string>>
|
||||
*/
|
||||
public function getPrefixesPsr4()
|
||||
{
|
||||
return $this->prefixDirsPsr4;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return list<string>
|
||||
*/
|
||||
public function getFallbackDirs()
|
||||
{
|
||||
return $this->fallbackDirsPsr0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return list<string>
|
||||
*/
|
||||
public function getFallbackDirsPsr4()
|
||||
{
|
||||
return $this->fallbackDirsPsr4;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, string> Array of classname => path
|
||||
*/
|
||||
public function getClassMap()
|
||||
{
|
||||
return $this->classMap;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, string> $classMap Class to filename map
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function addClassMap(array $classMap)
|
||||
{
|
||||
if ($this->classMap) {
|
||||
$this->classMap = array_merge($this->classMap, $classMap);
|
||||
} else {
|
||||
$this->classMap = $classMap;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a set of PSR-0 directories for a given prefix, either
|
||||
* appending or prepending to the ones previously set for this prefix.
|
||||
*
|
||||
* @param string $prefix The prefix
|
||||
* @param list<string>|string $paths The PSR-0 root directories
|
||||
* @param bool $prepend Whether to prepend the directories
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function add($prefix, $paths, $prepend = false)
|
||||
{
|
||||
$paths = (array) $paths;
|
||||
if (!$prefix) {
|
||||
if ($prepend) {
|
||||
$this->fallbackDirsPsr0 = array_merge(
|
||||
$paths,
|
||||
$this->fallbackDirsPsr0
|
||||
);
|
||||
} else {
|
||||
$this->fallbackDirsPsr0 = array_merge(
|
||||
$this->fallbackDirsPsr0,
|
||||
$paths
|
||||
);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$first = $prefix[0];
|
||||
if (!isset($this->prefixesPsr0[$first][$prefix])) {
|
||||
$this->prefixesPsr0[$first][$prefix] = $paths;
|
||||
|
||||
return;
|
||||
}
|
||||
if ($prepend) {
|
||||
$this->prefixesPsr0[$first][$prefix] = array_merge(
|
||||
$paths,
|
||||
$this->prefixesPsr0[$first][$prefix]
|
||||
);
|
||||
} else {
|
||||
$this->prefixesPsr0[$first][$prefix] = array_merge(
|
||||
$this->prefixesPsr0[$first][$prefix],
|
||||
$paths
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a set of PSR-4 directories for a given namespace, either
|
||||
* appending or prepending to the ones previously set for this namespace.
|
||||
*
|
||||
* @param string $prefix The prefix/namespace, with trailing '\\'
|
||||
* @param list<string>|string $paths The PSR-4 base directories
|
||||
* @param bool $prepend Whether to prepend the directories
|
||||
*
|
||||
* @throws \InvalidArgumentException
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function addPsr4($prefix, $paths, $prepend = false)
|
||||
{
|
||||
$paths = (array) $paths;
|
||||
if (!$prefix) {
|
||||
// Register directories for the root namespace.
|
||||
if ($prepend) {
|
||||
$this->fallbackDirsPsr4 = array_merge(
|
||||
$paths,
|
||||
$this->fallbackDirsPsr4
|
||||
);
|
||||
} else {
|
||||
$this->fallbackDirsPsr4 = array_merge(
|
||||
$this->fallbackDirsPsr4,
|
||||
$paths
|
||||
);
|
||||
}
|
||||
} elseif (!isset($this->prefixDirsPsr4[$prefix])) {
|
||||
// Register directories for a new namespace.
|
||||
$length = strlen($prefix);
|
||||
if ('\\' !== $prefix[$length - 1]) {
|
||||
throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator.");
|
||||
}
|
||||
$this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length;
|
||||
$this->prefixDirsPsr4[$prefix] = $paths;
|
||||
} elseif ($prepend) {
|
||||
// Prepend directories for an already registered namespace.
|
||||
$this->prefixDirsPsr4[$prefix] = array_merge(
|
||||
$paths,
|
||||
$this->prefixDirsPsr4[$prefix]
|
||||
);
|
||||
} else {
|
||||
// Append directories for an already registered namespace.
|
||||
$this->prefixDirsPsr4[$prefix] = array_merge(
|
||||
$this->prefixDirsPsr4[$prefix],
|
||||
$paths
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a set of PSR-0 directories for a given prefix,
|
||||
* replacing any others previously set for this prefix.
|
||||
*
|
||||
* @param string $prefix The prefix
|
||||
* @param list<string>|string $paths The PSR-0 base directories
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function set($prefix, $paths)
|
||||
{
|
||||
if (!$prefix) {
|
||||
$this->fallbackDirsPsr0 = (array) $paths;
|
||||
} else {
|
||||
$this->prefixesPsr0[$prefix[0]][$prefix] = (array) $paths;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a set of PSR-4 directories for a given namespace,
|
||||
* replacing any others previously set for this namespace.
|
||||
*
|
||||
* @param string $prefix The prefix/namespace, with trailing '\\'
|
||||
* @param list<string>|string $paths The PSR-4 base directories
|
||||
*
|
||||
* @throws \InvalidArgumentException
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function setPsr4($prefix, $paths)
|
||||
{
|
||||
if (!$prefix) {
|
||||
$this->fallbackDirsPsr4 = (array) $paths;
|
||||
} else {
|
||||
$length = strlen($prefix);
|
||||
if ('\\' !== $prefix[$length - 1]) {
|
||||
throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator.");
|
||||
}
|
||||
$this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length;
|
||||
$this->prefixDirsPsr4[$prefix] = (array) $paths;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Turns on searching the include path for class files.
|
||||
*
|
||||
* @param bool $useIncludePath
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function setUseIncludePath($useIncludePath)
|
||||
{
|
||||
$this->useIncludePath = $useIncludePath;
|
||||
}
|
||||
|
||||
/**
|
||||
* Can be used to check if the autoloader uses the include path to check
|
||||
* for classes.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function getUseIncludePath()
|
||||
{
|
||||
return $this->useIncludePath;
|
||||
}
|
||||
|
||||
/**
|
||||
* Turns off searching the prefix and fallback directories for classes
|
||||
* that have not been registered with the class map.
|
||||
*
|
||||
* @param bool $classMapAuthoritative
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function setClassMapAuthoritative($classMapAuthoritative)
|
||||
{
|
||||
$this->classMapAuthoritative = $classMapAuthoritative;
|
||||
}
|
||||
|
||||
/**
|
||||
* Should class lookup fail if not found in the current class map?
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isClassMapAuthoritative()
|
||||
{
|
||||
return $this->classMapAuthoritative;
|
||||
}
|
||||
|
||||
/**
|
||||
* APCu prefix to use to cache found/not-found classes, if the extension is enabled.
|
||||
*
|
||||
* @param string|null $apcuPrefix
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function setApcuPrefix($apcuPrefix)
|
||||
{
|
||||
$this->apcuPrefix = function_exists('apcu_fetch') && filter_var(ini_get('apc.enabled'), FILTER_VALIDATE_BOOLEAN) ? $apcuPrefix : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* The APCu prefix in use, or null if APCu caching is not enabled.
|
||||
*
|
||||
* @return string|null
|
||||
*/
|
||||
public function getApcuPrefix()
|
||||
{
|
||||
return $this->apcuPrefix;
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers this instance as an autoloader.
|
||||
*
|
||||
* @param bool $prepend Whether to prepend the autoloader or not
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function register($prepend = false)
|
||||
{
|
||||
spl_autoload_register(array($this, 'loadClass'), true, $prepend);
|
||||
|
||||
if (null === $this->vendorDir) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ($prepend) {
|
||||
self::$registeredLoaders = array($this->vendorDir => $this) + self::$registeredLoaders;
|
||||
} else {
|
||||
unset(self::$registeredLoaders[$this->vendorDir]);
|
||||
self::$registeredLoaders[$this->vendorDir] = $this;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Unregisters this instance as an autoloader.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function unregister()
|
||||
{
|
||||
spl_autoload_unregister(array($this, 'loadClass'));
|
||||
|
||||
if (null !== $this->vendorDir) {
|
||||
unset(self::$registeredLoaders[$this->vendorDir]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads the given class or interface.
|
||||
*
|
||||
* @param string $class The name of the class
|
||||
* @return true|null True if loaded, null otherwise
|
||||
*/
|
||||
public function loadClass($class)
|
||||
{
|
||||
if ($file = $this->findFile($class)) {
|
||||
$includeFile = self::$includeFile;
|
||||
$includeFile($file);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds the path to the file where the class is defined.
|
||||
*
|
||||
* @param string $class The name of the class
|
||||
*
|
||||
* @return string|false The path if found, false otherwise
|
||||
*/
|
||||
public function findFile($class)
|
||||
{
|
||||
// class map lookup
|
||||
if (isset($this->classMap[$class])) {
|
||||
return $this->classMap[$class];
|
||||
}
|
||||
if ($this->classMapAuthoritative || isset($this->missingClasses[$class])) {
|
||||
return false;
|
||||
}
|
||||
if (null !== $this->apcuPrefix) {
|
||||
$file = apcu_fetch($this->apcuPrefix.$class, $hit);
|
||||
if ($hit) {
|
||||
return $file;
|
||||
}
|
||||
}
|
||||
|
||||
$file = $this->findFileWithExtension($class, '.php');
|
||||
|
||||
// Search for Hack files if we are running on HHVM
|
||||
if (false === $file && defined('HHVM_VERSION')) {
|
||||
$file = $this->findFileWithExtension($class, '.hh');
|
||||
}
|
||||
|
||||
if (null !== $this->apcuPrefix) {
|
||||
apcu_add($this->apcuPrefix.$class, $file);
|
||||
}
|
||||
|
||||
if (false === $file) {
|
||||
// Remember that this class does not exist.
|
||||
$this->missingClasses[$class] = true;
|
||||
}
|
||||
|
||||
return $file;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the currently registered loaders keyed by their corresponding vendor directories.
|
||||
*
|
||||
* @return array<string, self>
|
||||
*/
|
||||
public static function getRegisteredLoaders()
|
||||
{
|
||||
return self::$registeredLoaders;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $class
|
||||
* @param string $ext
|
||||
* @return string|false
|
||||
*/
|
||||
private function findFileWithExtension($class, $ext)
|
||||
{
|
||||
// PSR-4 lookup
|
||||
$logicalPathPsr4 = strtr($class, '\\', DIRECTORY_SEPARATOR) . $ext;
|
||||
|
||||
$first = $class[0];
|
||||
if (isset($this->prefixLengthsPsr4[$first])) {
|
||||
$subPath = $class;
|
||||
while (false !== $lastPos = strrpos($subPath, '\\')) {
|
||||
$subPath = substr($subPath, 0, $lastPos);
|
||||
$search = $subPath . '\\';
|
||||
if (isset($this->prefixDirsPsr4[$search])) {
|
||||
$pathEnd = DIRECTORY_SEPARATOR . substr($logicalPathPsr4, $lastPos + 1);
|
||||
foreach ($this->prefixDirsPsr4[$search] as $dir) {
|
||||
if (file_exists($file = $dir . $pathEnd)) {
|
||||
return $file;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// PSR-4 fallback dirs
|
||||
foreach ($this->fallbackDirsPsr4 as $dir) {
|
||||
if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr4)) {
|
||||
return $file;
|
||||
}
|
||||
}
|
||||
|
||||
// PSR-0 lookup
|
||||
if (false !== $pos = strrpos($class, '\\')) {
|
||||
// namespaced class name
|
||||
$logicalPathPsr0 = substr($logicalPathPsr4, 0, $pos + 1)
|
||||
. strtr(substr($logicalPathPsr4, $pos + 1), '_', DIRECTORY_SEPARATOR);
|
||||
} else {
|
||||
// PEAR-like class name
|
||||
$logicalPathPsr0 = strtr($class, '_', DIRECTORY_SEPARATOR) . $ext;
|
||||
}
|
||||
|
||||
if (isset($this->prefixesPsr0[$first])) {
|
||||
foreach ($this->prefixesPsr0[$first] as $prefix => $dirs) {
|
||||
if (0 === strpos($class, $prefix)) {
|
||||
foreach ($dirs as $dir) {
|
||||
if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) {
|
||||
return $file;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// PSR-0 fallback dirs
|
||||
foreach ($this->fallbackDirsPsr0 as $dir) {
|
||||
if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) {
|
||||
return $file;
|
||||
}
|
||||
}
|
||||
|
||||
// PSR-0 include paths.
|
||||
if ($this->useIncludePath && $file = stream_resolve_include_path($logicalPathPsr0)) {
|
||||
return $file;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return void
|
||||
*/
|
||||
private static function initializeIncludeClosure()
|
||||
{
|
||||
if (self::$includeFile !== null) {
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Scope isolated include.
|
||||
*
|
||||
* Prevents access to $this/self from included files.
|
||||
*
|
||||
* @param string $file
|
||||
* @return void
|
||||
*/
|
||||
self::$includeFile = \Closure::bind(static function($file) {
|
||||
include $file;
|
||||
}, null, null);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,359 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Composer.
|
||||
*
|
||||
* (c) Nils Adermann <naderman@naderman.de>
|
||||
* 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 Composer;
|
||||
|
||||
use Composer\Autoload\ClassLoader;
|
||||
use Composer\Semver\VersionParser;
|
||||
|
||||
/**
|
||||
* This class is copied in every Composer installed project and available to all
|
||||
*
|
||||
* See also https://getcomposer.org/doc/07-runtime.md#installed-versions
|
||||
*
|
||||
* To require its presence, you can require `composer-runtime-api ^2.0`
|
||||
*
|
||||
* @final
|
||||
*/
|
||||
class InstalledVersions
|
||||
{
|
||||
/**
|
||||
* @var mixed[]|null
|
||||
* @psalm-var array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>}|array{}|null
|
||||
*/
|
||||
private static $installed;
|
||||
|
||||
/**
|
||||
* @var bool|null
|
||||
*/
|
||||
private static $canGetVendors;
|
||||
|
||||
/**
|
||||
* @var array[]
|
||||
* @psalm-var array<string, array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>}>
|
||||
*/
|
||||
private static $installedByVendor = array();
|
||||
|
||||
/**
|
||||
* Returns a list of all package names which are present, either by being installed, replaced or provided
|
||||
*
|
||||
* @return string[]
|
||||
* @psalm-return list<string>
|
||||
*/
|
||||
public static function getInstalledPackages()
|
||||
{
|
||||
$packages = array();
|
||||
foreach (self::getInstalled() as $installed) {
|
||||
$packages[] = array_keys($installed['versions']);
|
||||
}
|
||||
|
||||
if (1 === \count($packages)) {
|
||||
return $packages[0];
|
||||
}
|
||||
|
||||
return array_keys(array_flip(\call_user_func_array('array_merge', $packages)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a list of all package names with a specific type e.g. 'library'
|
||||
*
|
||||
* @param string $type
|
||||
* @return string[]
|
||||
* @psalm-return list<string>
|
||||
*/
|
||||
public static function getInstalledPackagesByType($type)
|
||||
{
|
||||
$packagesByType = array();
|
||||
|
||||
foreach (self::getInstalled() as $installed) {
|
||||
foreach ($installed['versions'] as $name => $package) {
|
||||
if (isset($package['type']) && $package['type'] === $type) {
|
||||
$packagesByType[] = $name;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $packagesByType;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether the given package is installed
|
||||
*
|
||||
* This also returns true if the package name is provided or replaced by another package
|
||||
*
|
||||
* @param string $packageName
|
||||
* @param bool $includeDevRequirements
|
||||
* @return bool
|
||||
*/
|
||||
public static function isInstalled($packageName, $includeDevRequirements = true)
|
||||
{
|
||||
foreach (self::getInstalled() as $installed) {
|
||||
if (isset($installed['versions'][$packageName])) {
|
||||
return $includeDevRequirements || !isset($installed['versions'][$packageName]['dev_requirement']) || $installed['versions'][$packageName]['dev_requirement'] === false;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether the given package satisfies a version constraint
|
||||
*
|
||||
* e.g. If you want to know whether version 2.3+ of package foo/bar is installed, you would call:
|
||||
*
|
||||
* Composer\InstalledVersions::satisfies(new VersionParser, 'foo/bar', '^2.3')
|
||||
*
|
||||
* @param VersionParser $parser Install composer/semver to have access to this class and functionality
|
||||
* @param string $packageName
|
||||
* @param string|null $constraint A version constraint to check for, if you pass one you have to make sure composer/semver is required by your package
|
||||
* @return bool
|
||||
*/
|
||||
public static function satisfies(VersionParser $parser, $packageName, $constraint)
|
||||
{
|
||||
$constraint = $parser->parseConstraints((string) $constraint);
|
||||
$provided = $parser->parseConstraints(self::getVersionRanges($packageName));
|
||||
|
||||
return $provided->matches($constraint);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a version constraint representing all the range(s) which are installed for a given package
|
||||
*
|
||||
* It is easier to use this via isInstalled() with the $constraint argument if you need to check
|
||||
* whether a given version of a package is installed, and not just whether it exists
|
||||
*
|
||||
* @param string $packageName
|
||||
* @return string Version constraint usable with composer/semver
|
||||
*/
|
||||
public static function getVersionRanges($packageName)
|
||||
{
|
||||
foreach (self::getInstalled() as $installed) {
|
||||
if (!isset($installed['versions'][$packageName])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$ranges = array();
|
||||
if (isset($installed['versions'][$packageName]['pretty_version'])) {
|
||||
$ranges[] = $installed['versions'][$packageName]['pretty_version'];
|
||||
}
|
||||
if (array_key_exists('aliases', $installed['versions'][$packageName])) {
|
||||
$ranges = array_merge($ranges, $installed['versions'][$packageName]['aliases']);
|
||||
}
|
||||
if (array_key_exists('replaced', $installed['versions'][$packageName])) {
|
||||
$ranges = array_merge($ranges, $installed['versions'][$packageName]['replaced']);
|
||||
}
|
||||
if (array_key_exists('provided', $installed['versions'][$packageName])) {
|
||||
$ranges = array_merge($ranges, $installed['versions'][$packageName]['provided']);
|
||||
}
|
||||
|
||||
return implode(' || ', $ranges);
|
||||
}
|
||||
|
||||
throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $packageName
|
||||
* @return string|null If the package is being replaced or provided but is not really installed, null will be returned as version, use satisfies or getVersionRanges if you need to know if a given version is present
|
||||
*/
|
||||
public static function getVersion($packageName)
|
||||
{
|
||||
foreach (self::getInstalled() as $installed) {
|
||||
if (!isset($installed['versions'][$packageName])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!isset($installed['versions'][$packageName]['version'])) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $installed['versions'][$packageName]['version'];
|
||||
}
|
||||
|
||||
throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $packageName
|
||||
* @return string|null If the package is being replaced or provided but is not really installed, null will be returned as version, use satisfies or getVersionRanges if you need to know if a given version is present
|
||||
*/
|
||||
public static function getPrettyVersion($packageName)
|
||||
{
|
||||
foreach (self::getInstalled() as $installed) {
|
||||
if (!isset($installed['versions'][$packageName])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!isset($installed['versions'][$packageName]['pretty_version'])) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $installed['versions'][$packageName]['pretty_version'];
|
||||
}
|
||||
|
||||
throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $packageName
|
||||
* @return string|null If the package is being replaced or provided but is not really installed, null will be returned as reference
|
||||
*/
|
||||
public static function getReference($packageName)
|
||||
{
|
||||
foreach (self::getInstalled() as $installed) {
|
||||
if (!isset($installed['versions'][$packageName])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!isset($installed['versions'][$packageName]['reference'])) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $installed['versions'][$packageName]['reference'];
|
||||
}
|
||||
|
||||
throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $packageName
|
||||
* @return string|null If the package is being replaced or provided but is not really installed, null will be returned as install path. Packages of type metapackages also have a null install path.
|
||||
*/
|
||||
public static function getInstallPath($packageName)
|
||||
{
|
||||
foreach (self::getInstalled() as $installed) {
|
||||
if (!isset($installed['versions'][$packageName])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
return isset($installed['versions'][$packageName]['install_path']) ? $installed['versions'][$packageName]['install_path'] : null;
|
||||
}
|
||||
|
||||
throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
* @psalm-return array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}
|
||||
*/
|
||||
public static function getRootPackage()
|
||||
{
|
||||
$installed = self::getInstalled();
|
||||
|
||||
return $installed[0]['root'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the raw installed.php data for custom implementations
|
||||
*
|
||||
* @deprecated Use getAllRawData() instead which returns all datasets for all autoloaders present in the process. getRawData only returns the first dataset loaded, which may not be what you expect.
|
||||
* @return array[]
|
||||
* @psalm-return array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>}
|
||||
*/
|
||||
public static function getRawData()
|
||||
{
|
||||
@trigger_error('getRawData only returns the first dataset loaded, which may not be what you expect. Use getAllRawData() instead which returns all datasets for all autoloaders present in the process.', E_USER_DEPRECATED);
|
||||
|
||||
if (null === self::$installed) {
|
||||
// only require the installed.php file if this file is loaded from its dumped location,
|
||||
// and not from its source location in the composer/composer package, see https://github.com/composer/composer/issues/9937
|
||||
if (substr(__DIR__, -8, 1) !== 'C') {
|
||||
self::$installed = include __DIR__ . '/installed.php';
|
||||
} else {
|
||||
self::$installed = array();
|
||||
}
|
||||
}
|
||||
|
||||
return self::$installed;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the raw data of all installed.php which are currently loaded for custom implementations
|
||||
*
|
||||
* @return array[]
|
||||
* @psalm-return list<array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>}>
|
||||
*/
|
||||
public static function getAllRawData()
|
||||
{
|
||||
return self::getInstalled();
|
||||
}
|
||||
|
||||
/**
|
||||
* Lets you reload the static array from another file
|
||||
*
|
||||
* This is only useful for complex integrations in which a project needs to use
|
||||
* this class but then also needs to execute another project's autoloader in process,
|
||||
* and wants to ensure both projects have access to their version of installed.php.
|
||||
*
|
||||
* A typical case would be PHPUnit, where it would need to make sure it reads all
|
||||
* the data it needs from this class, then call reload() with
|
||||
* `require $CWD/vendor/composer/installed.php` (or similar) as input to make sure
|
||||
* the project in which it runs can then also use this class safely, without
|
||||
* interference between PHPUnit's dependencies and the project's dependencies.
|
||||
*
|
||||
* @param array[] $data A vendor/composer/installed.php data set
|
||||
* @return void
|
||||
*
|
||||
* @psalm-param array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>} $data
|
||||
*/
|
||||
public static function reload($data)
|
||||
{
|
||||
self::$installed = $data;
|
||||
self::$installedByVendor = array();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array[]
|
||||
* @psalm-return list<array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>}>
|
||||
*/
|
||||
private static function getInstalled()
|
||||
{
|
||||
if (null === self::$canGetVendors) {
|
||||
self::$canGetVendors = method_exists('Composer\Autoload\ClassLoader', 'getRegisteredLoaders');
|
||||
}
|
||||
|
||||
$installed = array();
|
||||
|
||||
if (self::$canGetVendors) {
|
||||
foreach (ClassLoader::getRegisteredLoaders() as $vendorDir => $loader) {
|
||||
if (isset(self::$installedByVendor[$vendorDir])) {
|
||||
$installed[] = self::$installedByVendor[$vendorDir];
|
||||
} elseif (is_file($vendorDir.'/composer/installed.php')) {
|
||||
/** @var array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>} $required */
|
||||
$required = require $vendorDir.'/composer/installed.php';
|
||||
$installed[] = self::$installedByVendor[$vendorDir] = $required;
|
||||
if (null === self::$installed && strtr($vendorDir.'/composer', '\\', '/') === strtr(__DIR__, '\\', '/')) {
|
||||
self::$installed = $installed[count($installed) - 1];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (null === self::$installed) {
|
||||
// only require the installed.php file if this file is loaded from its dumped location,
|
||||
// and not from its source location in the composer/composer package, see https://github.com/composer/composer/issues/9937
|
||||
if (substr(__DIR__, -8, 1) !== 'C') {
|
||||
/** @var array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>} $required */
|
||||
$required = require __DIR__ . '/installed.php';
|
||||
self::$installed = $required;
|
||||
} else {
|
||||
self::$installed = array();
|
||||
}
|
||||
}
|
||||
|
||||
if (self::$installed !== array()) {
|
||||
$installed[] = self::$installed;
|
||||
}
|
||||
|
||||
return $installed;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
Copyright (c) Nils Adermann, Jordi Boggiano
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is furnished
|
||||
to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
@@ -0,0 +1,15 @@
|
||||
<?php
|
||||
|
||||
// autoload_classmap.php @generated by Composer
|
||||
|
||||
$vendorDir = dirname(__DIR__);
|
||||
$baseDir = dirname($vendorDir);
|
||||
|
||||
return array(
|
||||
'Attribute' => $vendorDir . '/symfony/polyfill-php80/Resources/stubs/Attribute.php',
|
||||
'Composer\\InstalledVersions' => $vendorDir . '/composer/InstalledVersions.php',
|
||||
'PhpToken' => $vendorDir . '/symfony/polyfill-php80/Resources/stubs/PhpToken.php',
|
||||
'Stringable' => $vendorDir . '/symfony/polyfill-php80/Resources/stubs/Stringable.php',
|
||||
'UnhandledMatchError' => $vendorDir . '/symfony/polyfill-php80/Resources/stubs/UnhandledMatchError.php',
|
||||
'ValueError' => $vendorDir . '/symfony/polyfill-php80/Resources/stubs/ValueError.php',
|
||||
);
|
||||
@@ -0,0 +1,12 @@
|
||||
<?php
|
||||
|
||||
// autoload_files.php @generated by Composer
|
||||
|
||||
$vendorDir = dirname(__DIR__);
|
||||
$baseDir = dirname($vendorDir);
|
||||
|
||||
return array(
|
||||
'320cde22f66dd4f5d3fd621d3e88b98f' => $vendorDir . '/symfony/polyfill-ctype/bootstrap.php',
|
||||
'0e6d7bf4a5811bfa5cf40c5ccd6fae6a' => $vendorDir . '/symfony/polyfill-mbstring/bootstrap.php',
|
||||
'a4a119a56e50fbb293281d9a48007e0e' => $vendorDir . '/symfony/polyfill-php80/bootstrap.php',
|
||||
);
|
||||
@@ -0,0 +1,9 @@
|
||||
<?php
|
||||
|
||||
// autoload_namespaces.php @generated by Composer
|
||||
|
||||
$vendorDir = dirname(__DIR__);
|
||||
$baseDir = dirname($vendorDir);
|
||||
|
||||
return array(
|
||||
);
|
||||
@@ -0,0 +1,18 @@
|
||||
<?php
|
||||
|
||||
// autoload_psr4.php @generated by Composer
|
||||
|
||||
$vendorDir = dirname(__DIR__);
|
||||
$baseDir = dirname($vendorDir);
|
||||
|
||||
return array(
|
||||
'Symfony\\Polyfill\\Php80\\' => array($vendorDir . '/symfony/polyfill-php80'),
|
||||
'Symfony\\Polyfill\\Mbstring\\' => array($vendorDir . '/symfony/polyfill-mbstring'),
|
||||
'Symfony\\Polyfill\\Ctype\\' => array($vendorDir . '/symfony/polyfill-ctype'),
|
||||
'Psr\\Http\\Message\\' => array($vendorDir . '/psr/http-message/src'),
|
||||
'Predis\\' => array($vendorDir . '/predis/predis/src'),
|
||||
'PhpOption\\' => array($vendorDir . '/phpoption/phpoption/src/PhpOption'),
|
||||
'KrothiumAPI\\' => array($baseDir . '/src'),
|
||||
'GrahamCampbell\\ResultType\\' => array($vendorDir . '/graham-campbell/result-type/src'),
|
||||
'Dotenv\\' => array($vendorDir . '/vlucas/phpdotenv/src'),
|
||||
);
|
||||
@@ -0,0 +1,50 @@
|
||||
<?php
|
||||
|
||||
// autoload_real.php @generated by Composer
|
||||
|
||||
class ComposerAutoloaderInit5598d6c42b75c33b245408cd5e519a5c
|
||||
{
|
||||
private static $loader;
|
||||
|
||||
public static function loadClassLoader($class)
|
||||
{
|
||||
if ('Composer\Autoload\ClassLoader' === $class) {
|
||||
require __DIR__ . '/ClassLoader.php';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return \Composer\Autoload\ClassLoader
|
||||
*/
|
||||
public static function getLoader()
|
||||
{
|
||||
if (null !== self::$loader) {
|
||||
return self::$loader;
|
||||
}
|
||||
|
||||
require __DIR__ . '/platform_check.php';
|
||||
|
||||
spl_autoload_register(array('ComposerAutoloaderInit5598d6c42b75c33b245408cd5e519a5c', 'loadClassLoader'), true, true);
|
||||
self::$loader = $loader = new \Composer\Autoload\ClassLoader(\dirname(__DIR__));
|
||||
spl_autoload_unregister(array('ComposerAutoloaderInit5598d6c42b75c33b245408cd5e519a5c', 'loadClassLoader'));
|
||||
|
||||
require __DIR__ . '/autoload_static.php';
|
||||
call_user_func(\Composer\Autoload\ComposerStaticInit5598d6c42b75c33b245408cd5e519a5c::getInitializer($loader));
|
||||
|
||||
$loader->register(true);
|
||||
|
||||
$filesToLoad = \Composer\Autoload\ComposerStaticInit5598d6c42b75c33b245408cd5e519a5c::$files;
|
||||
$requireFile = \Closure::bind(static function ($fileIdentifier, $file) {
|
||||
if (empty($GLOBALS['__composer_autoload_files'][$fileIdentifier])) {
|
||||
$GLOBALS['__composer_autoload_files'][$fileIdentifier] = true;
|
||||
|
||||
require $file;
|
||||
}
|
||||
}, null, null);
|
||||
foreach ($filesToLoad as $fileIdentifier => $file) {
|
||||
$requireFile($fileIdentifier, $file);
|
||||
}
|
||||
|
||||
return $loader;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,99 @@
|
||||
<?php
|
||||
|
||||
// autoload_static.php @generated by Composer
|
||||
|
||||
namespace Composer\Autoload;
|
||||
|
||||
class ComposerStaticInit5598d6c42b75c33b245408cd5e519a5c
|
||||
{
|
||||
public static $files = array (
|
||||
'320cde22f66dd4f5d3fd621d3e88b98f' => __DIR__ . '/..' . '/symfony/polyfill-ctype/bootstrap.php',
|
||||
'0e6d7bf4a5811bfa5cf40c5ccd6fae6a' => __DIR__ . '/..' . '/symfony/polyfill-mbstring/bootstrap.php',
|
||||
'a4a119a56e50fbb293281d9a48007e0e' => __DIR__ . '/..' . '/symfony/polyfill-php80/bootstrap.php',
|
||||
);
|
||||
|
||||
public static $prefixLengthsPsr4 = array (
|
||||
'S' =>
|
||||
array (
|
||||
'Symfony\\Polyfill\\Php80\\' => 23,
|
||||
'Symfony\\Polyfill\\Mbstring\\' => 26,
|
||||
'Symfony\\Polyfill\\Ctype\\' => 23,
|
||||
),
|
||||
'P' =>
|
||||
array (
|
||||
'Psr\\Http\\Message\\' => 17,
|
||||
'Predis\\' => 7,
|
||||
'PhpOption\\' => 10,
|
||||
),
|
||||
'K' =>
|
||||
array (
|
||||
'KrothiumAPI\\' => 12,
|
||||
),
|
||||
'G' =>
|
||||
array (
|
||||
'GrahamCampbell\\ResultType\\' => 26,
|
||||
),
|
||||
'D' =>
|
||||
array (
|
||||
'Dotenv\\' => 7,
|
||||
),
|
||||
);
|
||||
|
||||
public static $prefixDirsPsr4 = array (
|
||||
'Symfony\\Polyfill\\Php80\\' =>
|
||||
array (
|
||||
0 => __DIR__ . '/..' . '/symfony/polyfill-php80',
|
||||
),
|
||||
'Symfony\\Polyfill\\Mbstring\\' =>
|
||||
array (
|
||||
0 => __DIR__ . '/..' . '/symfony/polyfill-mbstring',
|
||||
),
|
||||
'Symfony\\Polyfill\\Ctype\\' =>
|
||||
array (
|
||||
0 => __DIR__ . '/..' . '/symfony/polyfill-ctype',
|
||||
),
|
||||
'Psr\\Http\\Message\\' =>
|
||||
array (
|
||||
0 => __DIR__ . '/..' . '/psr/http-message/src',
|
||||
),
|
||||
'Predis\\' =>
|
||||
array (
|
||||
0 => __DIR__ . '/..' . '/predis/predis/src',
|
||||
),
|
||||
'PhpOption\\' =>
|
||||
array (
|
||||
0 => __DIR__ . '/..' . '/phpoption/phpoption/src/PhpOption',
|
||||
),
|
||||
'KrothiumAPI\\' =>
|
||||
array (
|
||||
0 => __DIR__ . '/../..' . '/src',
|
||||
),
|
||||
'GrahamCampbell\\ResultType\\' =>
|
||||
array (
|
||||
0 => __DIR__ . '/..' . '/graham-campbell/result-type/src',
|
||||
),
|
||||
'Dotenv\\' =>
|
||||
array (
|
||||
0 => __DIR__ . '/..' . '/vlucas/phpdotenv/src',
|
||||
),
|
||||
);
|
||||
|
||||
public static $classMap = array (
|
||||
'Attribute' => __DIR__ . '/..' . '/symfony/polyfill-php80/Resources/stubs/Attribute.php',
|
||||
'Composer\\InstalledVersions' => __DIR__ . '/..' . '/composer/InstalledVersions.php',
|
||||
'PhpToken' => __DIR__ . '/..' . '/symfony/polyfill-php80/Resources/stubs/PhpToken.php',
|
||||
'Stringable' => __DIR__ . '/..' . '/symfony/polyfill-php80/Resources/stubs/Stringable.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)
|
||||
{
|
||||
return \Closure::bind(function () use ($loader) {
|
||||
$loader->prefixLengthsPsr4 = ComposerStaticInit5598d6c42b75c33b245408cd5e519a5c::$prefixLengthsPsr4;
|
||||
$loader->prefixDirsPsr4 = ComposerStaticInit5598d6c42b75c33b245408cd5e519a5c::$prefixDirsPsr4;
|
||||
$loader->classMap = ComposerStaticInit5598d6c42b75c33b245408cd5e519a5c::$classMap;
|
||||
|
||||
}, null, ClassLoader::class);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,619 @@
|
||||
{
|
||||
"packages": [
|
||||
{
|
||||
"name": "graham-campbell/result-type",
|
||||
"version": "v1.1.4",
|
||||
"version_normalized": "1.1.4.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/GrahamCampbell/Result-Type.git",
|
||||
"reference": "e01f4a821471308ba86aa202fed6698b6b695e3b"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/GrahamCampbell/Result-Type/zipball/e01f4a821471308ba86aa202fed6698b6b695e3b",
|
||||
"reference": "e01f4a821471308ba86aa202fed6698b6b695e3b",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": "^7.2.5 || ^8.0",
|
||||
"phpoption/phpoption": "^1.9.5"
|
||||
},
|
||||
"require-dev": {
|
||||
"phpunit/phpunit": "^8.5.41 || ^9.6.22 || ^10.5.45 || ^11.5.7"
|
||||
},
|
||||
"time": "2025-12-27T19:43:20+00:00",
|
||||
"type": "library",
|
||||
"installation-source": "dist",
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"GrahamCampbell\\ResultType\\": "src/"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Graham Campbell",
|
||||
"email": "hello@gjcampbell.co.uk",
|
||||
"homepage": "https://github.com/GrahamCampbell"
|
||||
}
|
||||
],
|
||||
"description": "An Implementation Of The Result Type",
|
||||
"keywords": [
|
||||
"Graham Campbell",
|
||||
"GrahamCampbell",
|
||||
"Result Type",
|
||||
"Result-Type",
|
||||
"result"
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/GrahamCampbell/Result-Type/issues",
|
||||
"source": "https://github.com/GrahamCampbell/Result-Type/tree/v1.1.4"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
"url": "https://github.com/GrahamCampbell",
|
||||
"type": "github"
|
||||
},
|
||||
{
|
||||
"url": "https://tidelift.com/funding/github/packagist/graham-campbell/result-type",
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"install-path": "../graham-campbell/result-type"
|
||||
},
|
||||
{
|
||||
"name": "phpoption/phpoption",
|
||||
"version": "1.9.5",
|
||||
"version_normalized": "1.9.5.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/schmittjoh/php-option.git",
|
||||
"reference": "75365b91986c2405cf5e1e012c5595cd487a98be"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/schmittjoh/php-option/zipball/75365b91986c2405cf5e1e012c5595cd487a98be",
|
||||
"reference": "75365b91986c2405cf5e1e012c5595cd487a98be",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": "^7.2.5 || ^8.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"bamarni/composer-bin-plugin": "^1.8.2",
|
||||
"phpunit/phpunit": "^8.5.44 || ^9.6.25 || ^10.5.53 || ^11.5.34"
|
||||
},
|
||||
"time": "2025-12-27T19:41:33+00:00",
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"bamarni-bin": {
|
||||
"bin-links": true,
|
||||
"forward-command": false
|
||||
},
|
||||
"branch-alias": {
|
||||
"dev-master": "1.9-dev"
|
||||
}
|
||||
},
|
||||
"installation-source": "dist",
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"PhpOption\\": "src/PhpOption/"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"Apache-2.0"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Johannes M. Schmitt",
|
||||
"email": "schmittjoh@gmail.com",
|
||||
"homepage": "https://github.com/schmittjoh"
|
||||
},
|
||||
{
|
||||
"name": "Graham Campbell",
|
||||
"email": "hello@gjcampbell.co.uk",
|
||||
"homepage": "https://github.com/GrahamCampbell"
|
||||
}
|
||||
],
|
||||
"description": "Option Type for PHP",
|
||||
"keywords": [
|
||||
"language",
|
||||
"option",
|
||||
"php",
|
||||
"type"
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/schmittjoh/php-option/issues",
|
||||
"source": "https://github.com/schmittjoh/php-option/tree/1.9.5"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
"url": "https://github.com/GrahamCampbell",
|
||||
"type": "github"
|
||||
},
|
||||
{
|
||||
"url": "https://tidelift.com/funding/github/packagist/phpoption/phpoption",
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"install-path": "../phpoption/phpoption"
|
||||
},
|
||||
{
|
||||
"name": "predis/predis",
|
||||
"version": "v3.4.0",
|
||||
"version_normalized": "3.4.0.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/predis/predis.git",
|
||||
"reference": "1183f5732e6b10efd33f64984a96726eaecb59aa"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/predis/predis/zipball/1183f5732e6b10efd33f64984a96726eaecb59aa",
|
||||
"reference": "1183f5732e6b10efd33f64984a96726eaecb59aa",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": "^7.2 || ^8.0",
|
||||
"psr/http-message": "^1.0|^2.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"friendsofphp/php-cs-fixer": "^3.3",
|
||||
"phpstan/phpstan": "^1.9",
|
||||
"phpunit/phpcov": "^6.0 || ^8.0",
|
||||
"phpunit/phpunit": "^8.0 || ~9.4.4"
|
||||
},
|
||||
"suggest": {
|
||||
"ext-relay": "Faster connection with in-memory caching (>=0.6.2)"
|
||||
},
|
||||
"time": "2026-02-11T17:30:28+00:00",
|
||||
"type": "library",
|
||||
"installation-source": "dist",
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Predis\\": "src/"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Till Krüss",
|
||||
"homepage": "https://till.im",
|
||||
"role": "Maintainer"
|
||||
}
|
||||
],
|
||||
"description": "A flexible and feature-complete Redis/Valkey client for PHP.",
|
||||
"homepage": "http://github.com/predis/predis",
|
||||
"keywords": [
|
||||
"nosql",
|
||||
"predis",
|
||||
"redis"
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/predis/predis/issues",
|
||||
"source": "https://github.com/predis/predis/tree/v3.4.0"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
"url": "https://github.com/sponsors/tillkruss",
|
||||
"type": "github"
|
||||
}
|
||||
],
|
||||
"install-path": "../predis/predis"
|
||||
},
|
||||
{
|
||||
"name": "psr/http-message",
|
||||
"version": "2.0",
|
||||
"version_normalized": "2.0.0.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/php-fig/http-message.git",
|
||||
"reference": "402d35bcb92c70c026d1a6a9883f06b2ead23d71"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"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:54:51+00:00",
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-master": "2.0.x-dev"
|
||||
}
|
||||
},
|
||||
"installation-source": "dist",
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Psr\\Http\\Message\\": "src/"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "PHP-FIG",
|
||||
"homepage": "https://www.php-fig.org/"
|
||||
}
|
||||
],
|
||||
"description": "Common interface for HTTP messages",
|
||||
"homepage": "https://github.com/php-fig/http-message",
|
||||
"keywords": [
|
||||
"http",
|
||||
"http-message",
|
||||
"psr",
|
||||
"psr-7",
|
||||
"request",
|
||||
"response"
|
||||
],
|
||||
"support": {
|
||||
"source": "https://github.com/php-fig/http-message/tree/2.0"
|
||||
},
|
||||
"install-path": "../psr/http-message"
|
||||
},
|
||||
{
|
||||
"name": "symfony/polyfill-ctype",
|
||||
"version": "v1.37.0",
|
||||
"version_normalized": "1.37.0.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/polyfill-ctype.git",
|
||||
"reference": "141046a8f9477948ff284fa65be2095baafb94f2"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/141046a8f9477948ff284fa65be2095baafb94f2",
|
||||
"reference": "141046a8f9477948ff284fa65be2095baafb94f2",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": ">=7.2"
|
||||
},
|
||||
"provide": {
|
||||
"ext-ctype": "*"
|
||||
},
|
||||
"suggest": {
|
||||
"ext-ctype": "For best performance"
|
||||
},
|
||||
"time": "2026-04-10T16:19:22+00:00",
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"thanks": {
|
||||
"url": "https://github.com/symfony/polyfill",
|
||||
"name": "symfony/polyfill"
|
||||
}
|
||||
},
|
||||
"installation-source": "dist",
|
||||
"autoload": {
|
||||
"files": [
|
||||
"bootstrap.php"
|
||||
],
|
||||
"psr-4": {
|
||||
"Symfony\\Polyfill\\Ctype\\": ""
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Gert de Pagter",
|
||||
"email": "BackEndTea@gmail.com"
|
||||
},
|
||||
{
|
||||
"name": "Symfony Community",
|
||||
"homepage": "https://symfony.com/contributors"
|
||||
}
|
||||
],
|
||||
"description": "Symfony polyfill for ctype functions",
|
||||
"homepage": "https://symfony.com",
|
||||
"keywords": [
|
||||
"compatibility",
|
||||
"ctype",
|
||||
"polyfill",
|
||||
"portable"
|
||||
],
|
||||
"support": {
|
||||
"source": "https://github.com/symfony/polyfill-ctype/tree/v1.37.0"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
"url": "https://symfony.com/sponsor",
|
||||
"type": "custom"
|
||||
},
|
||||
{
|
||||
"url": "https://github.com/fabpot",
|
||||
"type": "github"
|
||||
},
|
||||
{
|
||||
"url": "https://github.com/nicolas-grekas",
|
||||
"type": "github"
|
||||
},
|
||||
{
|
||||
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"install-path": "../symfony/polyfill-ctype"
|
||||
},
|
||||
{
|
||||
"name": "symfony/polyfill-mbstring",
|
||||
"version": "v1.37.0",
|
||||
"version_normalized": "1.37.0.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/polyfill-mbstring.git",
|
||||
"reference": "6a21eb99c6973357967f6ce3708cd55a6bec6315"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/6a21eb99c6973357967f6ce3708cd55a6bec6315",
|
||||
"reference": "6a21eb99c6973357967f6ce3708cd55a6bec6315",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"ext-iconv": "*",
|
||||
"php": ">=7.2"
|
||||
},
|
||||
"provide": {
|
||||
"ext-mbstring": "*"
|
||||
},
|
||||
"suggest": {
|
||||
"ext-mbstring": "For best performance"
|
||||
},
|
||||
"time": "2026-04-10T17:25:58+00:00",
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"thanks": {
|
||||
"url": "https://github.com/symfony/polyfill",
|
||||
"name": "symfony/polyfill"
|
||||
}
|
||||
},
|
||||
"installation-source": "dist",
|
||||
"autoload": {
|
||||
"files": [
|
||||
"bootstrap.php"
|
||||
],
|
||||
"psr-4": {
|
||||
"Symfony\\Polyfill\\Mbstring\\": ""
|
||||
}
|
||||
},
|
||||
"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 for the Mbstring extension",
|
||||
"homepage": "https://symfony.com",
|
||||
"keywords": [
|
||||
"compatibility",
|
||||
"mbstring",
|
||||
"polyfill",
|
||||
"portable",
|
||||
"shim"
|
||||
],
|
||||
"support": {
|
||||
"source": "https://github.com/symfony/polyfill-mbstring/tree/v1.37.0"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
"url": "https://symfony.com/sponsor",
|
||||
"type": "custom"
|
||||
},
|
||||
{
|
||||
"url": "https://github.com/fabpot",
|
||||
"type": "github"
|
||||
},
|
||||
{
|
||||
"url": "https://github.com/nicolas-grekas",
|
||||
"type": "github"
|
||||
},
|
||||
{
|
||||
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"install-path": "../symfony/polyfill-mbstring"
|
||||
},
|
||||
{
|
||||
"name": "symfony/polyfill-php80",
|
||||
"version": "v1.37.0",
|
||||
"version_normalized": "1.37.0.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/polyfill-php80.git",
|
||||
"reference": "dfb55726c3a76ea3b6459fcfda1ec2d80a682411"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/dfb55726c3a76ea3b6459fcfda1ec2d80a682411",
|
||||
"reference": "dfb55726c3a76ea3b6459fcfda1ec2d80a682411",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": ">=7.2"
|
||||
},
|
||||
"time": "2026-04-10T16:19:22+00:00",
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"thanks": {
|
||||
"url": "https://github.com/symfony/polyfill",
|
||||
"name": "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"
|
||||
],
|
||||
"support": {
|
||||
"source": "https://github.com/symfony/polyfill-php80/tree/v1.37.0"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
"url": "https://symfony.com/sponsor",
|
||||
"type": "custom"
|
||||
},
|
||||
{
|
||||
"url": "https://github.com/fabpot",
|
||||
"type": "github"
|
||||
},
|
||||
{
|
||||
"url": "https://github.com/nicolas-grekas",
|
||||
"type": "github"
|
||||
},
|
||||
{
|
||||
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"install-path": "../symfony/polyfill-php80"
|
||||
},
|
||||
{
|
||||
"name": "vlucas/phpdotenv",
|
||||
"version": "v5.6.2",
|
||||
"version_normalized": "5.6.2.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/vlucas/phpdotenv.git",
|
||||
"reference": "24ac4c74f91ee2c193fa1aaa5c249cb0822809af"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/vlucas/phpdotenv/zipball/24ac4c74f91ee2c193fa1aaa5c249cb0822809af",
|
||||
"reference": "24ac4c74f91ee2c193fa1aaa5c249cb0822809af",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"ext-pcre": "*",
|
||||
"graham-campbell/result-type": "^1.1.3",
|
||||
"php": "^7.2.5 || ^8.0",
|
||||
"phpoption/phpoption": "^1.9.3",
|
||||
"symfony/polyfill-ctype": "^1.24",
|
||||
"symfony/polyfill-mbstring": "^1.24",
|
||||
"symfony/polyfill-php80": "^1.24"
|
||||
},
|
||||
"require-dev": {
|
||||
"bamarni/composer-bin-plugin": "^1.8.2",
|
||||
"ext-filter": "*",
|
||||
"phpunit/phpunit": "^8.5.34 || ^9.6.13 || ^10.4.2"
|
||||
},
|
||||
"suggest": {
|
||||
"ext-filter": "Required to use the boolean validator."
|
||||
},
|
||||
"time": "2025-04-30T23:37:27+00:00",
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"bamarni-bin": {
|
||||
"bin-links": true,
|
||||
"forward-command": false
|
||||
},
|
||||
"branch-alias": {
|
||||
"dev-master": "5.6-dev"
|
||||
}
|
||||
},
|
||||
"installation-source": "dist",
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Dotenv\\": "src/"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"BSD-3-Clause"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Graham Campbell",
|
||||
"email": "hello@gjcampbell.co.uk",
|
||||
"homepage": "https://github.com/GrahamCampbell"
|
||||
},
|
||||
{
|
||||
"name": "Vance Lucas",
|
||||
"email": "vance@vancelucas.com",
|
||||
"homepage": "https://github.com/vlucas"
|
||||
}
|
||||
],
|
||||
"description": "Loads environment variables from `.env` to `getenv()`, `$_ENV` and `$_SERVER` automagically.",
|
||||
"keywords": [
|
||||
"dotenv",
|
||||
"env",
|
||||
"environment"
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/vlucas/phpdotenv/issues",
|
||||
"source": "https://github.com/vlucas/phpdotenv/tree/v5.6.2"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
"url": "https://github.com/GrahamCampbell",
|
||||
"type": "github"
|
||||
},
|
||||
{
|
||||
"url": "https://tidelift.com/funding/github/packagist/vlucas/phpdotenv",
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"install-path": "../vlucas/phpdotenv"
|
||||
}
|
||||
],
|
||||
"dev": true,
|
||||
"dev-package-names": []
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
<?php return array(
|
||||
'root' => array(
|
||||
'name' => 'claudecio/krothiumapi',
|
||||
'pretty_version' => 'dev-main',
|
||||
'version' => 'dev-main',
|
||||
'reference' => 'b738b2f594cf8825ed0637eb6d4e315380afa0d6',
|
||||
'type' => 'library',
|
||||
'install_path' => __DIR__ . '/../../',
|
||||
'aliases' => array(),
|
||||
'dev' => true,
|
||||
),
|
||||
'versions' => array(
|
||||
'claudecio/krothiumapi' => array(
|
||||
'pretty_version' => 'dev-main',
|
||||
'version' => 'dev-main',
|
||||
'reference' => 'b738b2f594cf8825ed0637eb6d4e315380afa0d6',
|
||||
'type' => 'library',
|
||||
'install_path' => __DIR__ . '/../../',
|
||||
'aliases' => array(),
|
||||
'dev_requirement' => false,
|
||||
),
|
||||
'graham-campbell/result-type' => array(
|
||||
'pretty_version' => 'v1.1.4',
|
||||
'version' => '1.1.4.0',
|
||||
'reference' => 'e01f4a821471308ba86aa202fed6698b6b695e3b',
|
||||
'type' => 'library',
|
||||
'install_path' => __DIR__ . '/../graham-campbell/result-type',
|
||||
'aliases' => array(),
|
||||
'dev_requirement' => false,
|
||||
),
|
||||
'phpoption/phpoption' => array(
|
||||
'pretty_version' => '1.9.5',
|
||||
'version' => '1.9.5.0',
|
||||
'reference' => '75365b91986c2405cf5e1e012c5595cd487a98be',
|
||||
'type' => 'library',
|
||||
'install_path' => __DIR__ . '/../phpoption/phpoption',
|
||||
'aliases' => array(),
|
||||
'dev_requirement' => false,
|
||||
),
|
||||
'predis/predis' => array(
|
||||
'pretty_version' => 'v3.4.0',
|
||||
'version' => '3.4.0.0',
|
||||
'reference' => '1183f5732e6b10efd33f64984a96726eaecb59aa',
|
||||
'type' => 'library',
|
||||
'install_path' => __DIR__ . '/../predis/predis',
|
||||
'aliases' => array(),
|
||||
'dev_requirement' => false,
|
||||
),
|
||||
'psr/http-message' => array(
|
||||
'pretty_version' => '2.0',
|
||||
'version' => '2.0.0.0',
|
||||
'reference' => '402d35bcb92c70c026d1a6a9883f06b2ead23d71',
|
||||
'type' => 'library',
|
||||
'install_path' => __DIR__ . '/../psr/http-message',
|
||||
'aliases' => array(),
|
||||
'dev_requirement' => false,
|
||||
),
|
||||
'symfony/polyfill-ctype' => array(
|
||||
'pretty_version' => 'v1.37.0',
|
||||
'version' => '1.37.0.0',
|
||||
'reference' => '141046a8f9477948ff284fa65be2095baafb94f2',
|
||||
'type' => 'library',
|
||||
'install_path' => __DIR__ . '/../symfony/polyfill-ctype',
|
||||
'aliases' => array(),
|
||||
'dev_requirement' => false,
|
||||
),
|
||||
'symfony/polyfill-mbstring' => array(
|
||||
'pretty_version' => 'v1.37.0',
|
||||
'version' => '1.37.0.0',
|
||||
'reference' => '6a21eb99c6973357967f6ce3708cd55a6bec6315',
|
||||
'type' => 'library',
|
||||
'install_path' => __DIR__ . '/../symfony/polyfill-mbstring',
|
||||
'aliases' => array(),
|
||||
'dev_requirement' => false,
|
||||
),
|
||||
'symfony/polyfill-php80' => array(
|
||||
'pretty_version' => 'v1.37.0',
|
||||
'version' => '1.37.0.0',
|
||||
'reference' => 'dfb55726c3a76ea3b6459fcfda1ec2d80a682411',
|
||||
'type' => 'library',
|
||||
'install_path' => __DIR__ . '/../symfony/polyfill-php80',
|
||||
'aliases' => array(),
|
||||
'dev_requirement' => false,
|
||||
),
|
||||
'vlucas/phpdotenv' => array(
|
||||
'pretty_version' => 'v5.6.2',
|
||||
'version' => '5.6.2.0',
|
||||
'reference' => '24ac4c74f91ee2c193fa1aaa5c249cb0822809af',
|
||||
'type' => 'library',
|
||||
'install_path' => __DIR__ . '/../vlucas/phpdotenv',
|
||||
'aliases' => array(),
|
||||
'dev_requirement' => false,
|
||||
),
|
||||
),
|
||||
);
|
||||
@@ -0,0 +1,26 @@
|
||||
<?php
|
||||
|
||||
// platform_check.php @generated by Composer
|
||||
|
||||
$issues = array();
|
||||
|
||||
if (!(PHP_VERSION_ID >= 80200)) {
|
||||
$issues[] = 'Your Composer dependencies require a PHP version ">= 8.2.0". You are running ' . PHP_VERSION . '.';
|
||||
}
|
||||
|
||||
if ($issues) {
|
||||
if (!headers_sent()) {
|
||||
header('HTTP/1.1 500 Internal Server Error');
|
||||
}
|
||||
if (!ini_get('display_errors')) {
|
||||
if (PHP_SAPI === 'cli' || PHP_SAPI === 'phpdbg') {
|
||||
fwrite(STDERR, 'Composer detected issues in your platform:' . PHP_EOL.PHP_EOL . implode(PHP_EOL, $issues) . PHP_EOL.PHP_EOL);
|
||||
} elseif (!headers_sent()) {
|
||||
echo 'Composer detected issues in your platform:' . PHP_EOL.PHP_EOL . str_replace('You are running '.PHP_VERSION.'.', '', implode(PHP_EOL, $issues)) . PHP_EOL.PHP_EOL;
|
||||
}
|
||||
}
|
||||
trigger_error(
|
||||
'Composer detected issues in your platform: ' . implode(' ', $issues),
|
||||
E_USER_ERROR
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2020-2024 Graham Campbell <hello@gjcampbell.co.uk>
|
||||
|
||||
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.
|
||||
+33
@@ -0,0 +1,33 @@
|
||||
{
|
||||
"name": "graham-campbell/result-type",
|
||||
"description": "An Implementation Of The Result Type",
|
||||
"keywords": ["result", "result-type", "Result", "Result Type", "Result-Type", "Graham Campbell", "GrahamCampbell"],
|
||||
"license": "MIT",
|
||||
"authors": [
|
||||
{
|
||||
"name": "Graham Campbell",
|
||||
"email": "hello@gjcampbell.co.uk",
|
||||
"homepage": "https://github.com/GrahamCampbell"
|
||||
}
|
||||
],
|
||||
"require": {
|
||||
"php": "^7.2.5 || ^8.0",
|
||||
"phpoption/phpoption": "^1.9.5"
|
||||
},
|
||||
"require-dev": {
|
||||
"phpunit/phpunit": "^8.5.41 || ^9.6.22 || ^10.5.45 || ^11.5.7"
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"GrahamCampbell\\ResultType\\": "src/"
|
||||
}
|
||||
},
|
||||
"autoload-dev": {
|
||||
"psr-4": {
|
||||
"GrahamCampbell\\Tests\\ResultType\\": "tests/"
|
||||
}
|
||||
},
|
||||
"config": {
|
||||
"preferred-install": "dist"
|
||||
}
|
||||
}
|
||||
+121
@@ -0,0 +1,121 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* This file is part of Result Type.
|
||||
*
|
||||
* (c) Graham Campbell <hello@gjcampbell.co.uk>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace GrahamCampbell\ResultType;
|
||||
|
||||
use PhpOption\None;
|
||||
use PhpOption\Some;
|
||||
|
||||
/**
|
||||
* @template T
|
||||
* @template E
|
||||
*
|
||||
* @extends \GrahamCampbell\ResultType\Result<T,E>
|
||||
*/
|
||||
final class Error extends Result
|
||||
{
|
||||
/**
|
||||
* @var E
|
||||
*/
|
||||
private $value;
|
||||
|
||||
/**
|
||||
* Internal constructor for an error value.
|
||||
*
|
||||
* @param E $value
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private function __construct($value)
|
||||
{
|
||||
$this->value = $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new error value.
|
||||
*
|
||||
* @template F
|
||||
*
|
||||
* @param F $value
|
||||
*
|
||||
* @return \GrahamCampbell\ResultType\Result<T,F>
|
||||
*/
|
||||
public static function create($value)
|
||||
{
|
||||
return new self($value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the success option value.
|
||||
*
|
||||
* @return \PhpOption\Option<T>
|
||||
*/
|
||||
public function success()
|
||||
{
|
||||
return None::create();
|
||||
}
|
||||
|
||||
/**
|
||||
* Map over the success value.
|
||||
*
|
||||
* @template S
|
||||
*
|
||||
* @param callable(T):S $f
|
||||
*
|
||||
* @return \GrahamCampbell\ResultType\Result<S,E>
|
||||
*/
|
||||
public function map(callable $f)
|
||||
{
|
||||
return self::create($this->value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Flat map over the success value.
|
||||
*
|
||||
* @template S
|
||||
* @template F
|
||||
*
|
||||
* @param callable(T):\GrahamCampbell\ResultType\Result<S,F> $f
|
||||
*
|
||||
* @return \GrahamCampbell\ResultType\Result<S,F>
|
||||
*/
|
||||
public function flatMap(callable $f)
|
||||
{
|
||||
/** @var \GrahamCampbell\ResultType\Result<S,F> */
|
||||
return self::create($this->value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the error option value.
|
||||
*
|
||||
* @return \PhpOption\Option<E>
|
||||
*/
|
||||
public function error()
|
||||
{
|
||||
return Some::create($this->value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Map over the error value.
|
||||
*
|
||||
* @template F
|
||||
*
|
||||
* @param callable(E):F $f
|
||||
*
|
||||
* @return \GrahamCampbell\ResultType\Result<T,F>
|
||||
*/
|
||||
public function mapError(callable $f)
|
||||
{
|
||||
return self::create($f($this->value));
|
||||
}
|
||||
}
|
||||
+69
@@ -0,0 +1,69 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* This file is part of Result Type.
|
||||
*
|
||||
* (c) Graham Campbell <hello@gjcampbell.co.uk>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace GrahamCampbell\ResultType;
|
||||
|
||||
/**
|
||||
* @template T
|
||||
* @template E
|
||||
*/
|
||||
abstract class Result
|
||||
{
|
||||
/**
|
||||
* Get the success option value.
|
||||
*
|
||||
* @return \PhpOption\Option<T>
|
||||
*/
|
||||
abstract public function success();
|
||||
|
||||
/**
|
||||
* Map over the success value.
|
||||
*
|
||||
* @template S
|
||||
*
|
||||
* @param callable(T):S $f
|
||||
*
|
||||
* @return \GrahamCampbell\ResultType\Result<S,E>
|
||||
*/
|
||||
abstract public function map(callable $f);
|
||||
|
||||
/**
|
||||
* Flat map over the success value.
|
||||
*
|
||||
* @template S
|
||||
* @template F
|
||||
*
|
||||
* @param callable(T):\GrahamCampbell\ResultType\Result<S,F> $f
|
||||
*
|
||||
* @return \GrahamCampbell\ResultType\Result<S,F>
|
||||
*/
|
||||
abstract public function flatMap(callable $f);
|
||||
|
||||
/**
|
||||
* Get the error option value.
|
||||
*
|
||||
* @return \PhpOption\Option<E>
|
||||
*/
|
||||
abstract public function error();
|
||||
|
||||
/**
|
||||
* Map over the error value.
|
||||
*
|
||||
* @template F
|
||||
*
|
||||
* @param callable(E):F $f
|
||||
*
|
||||
* @return \GrahamCampbell\ResultType\Result<T,F>
|
||||
*/
|
||||
abstract public function mapError(callable $f);
|
||||
}
|
||||
+120
@@ -0,0 +1,120 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* This file is part of Result Type.
|
||||
*
|
||||
* (c) Graham Campbell <hello@gjcampbell.co.uk>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace GrahamCampbell\ResultType;
|
||||
|
||||
use PhpOption\None;
|
||||
use PhpOption\Some;
|
||||
|
||||
/**
|
||||
* @template T
|
||||
* @template E
|
||||
*
|
||||
* @extends \GrahamCampbell\ResultType\Result<T,E>
|
||||
*/
|
||||
final class Success extends Result
|
||||
{
|
||||
/**
|
||||
* @var T
|
||||
*/
|
||||
private $value;
|
||||
|
||||
/**
|
||||
* Internal constructor for a success value.
|
||||
*
|
||||
* @param T $value
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private function __construct($value)
|
||||
{
|
||||
$this->value = $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new error value.
|
||||
*
|
||||
* @template S
|
||||
*
|
||||
* @param S $value
|
||||
*
|
||||
* @return \GrahamCampbell\ResultType\Result<S,E>
|
||||
*/
|
||||
public static function create($value)
|
||||
{
|
||||
return new self($value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the success option value.
|
||||
*
|
||||
* @return \PhpOption\Option<T>
|
||||
*/
|
||||
public function success()
|
||||
{
|
||||
return Some::create($this->value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Map over the success value.
|
||||
*
|
||||
* @template S
|
||||
*
|
||||
* @param callable(T):S $f
|
||||
*
|
||||
* @return \GrahamCampbell\ResultType\Result<S,E>
|
||||
*/
|
||||
public function map(callable $f)
|
||||
{
|
||||
return self::create($f($this->value));
|
||||
}
|
||||
|
||||
/**
|
||||
* Flat map over the success value.
|
||||
*
|
||||
* @template S
|
||||
* @template F
|
||||
*
|
||||
* @param callable(T):\GrahamCampbell\ResultType\Result<S,F> $f
|
||||
*
|
||||
* @return \GrahamCampbell\ResultType\Result<S,F>
|
||||
*/
|
||||
public function flatMap(callable $f)
|
||||
{
|
||||
return $f($this->value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the error option value.
|
||||
*
|
||||
* @return \PhpOption\Option<E>
|
||||
*/
|
||||
public function error()
|
||||
{
|
||||
return None::create();
|
||||
}
|
||||
|
||||
/**
|
||||
* Map over the error value.
|
||||
*
|
||||
* @template F
|
||||
*
|
||||
* @param callable(E):F $f
|
||||
*
|
||||
* @return \GrahamCampbell\ResultType\Result<T,F>
|
||||
*/
|
||||
public function mapError(callable $f)
|
||||
{
|
||||
return self::create($this->value);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,201 @@
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
@@ -0,0 +1,50 @@
|
||||
{
|
||||
"name": "phpoption/phpoption",
|
||||
"description": "Option Type for PHP",
|
||||
"keywords": ["php", "option", "language", "type"],
|
||||
"license": "Apache-2.0",
|
||||
"authors": [
|
||||
{
|
||||
"name": "Johannes M. Schmitt",
|
||||
"email": "schmittjoh@gmail.com",
|
||||
"homepage": "https://github.com/schmittjoh"
|
||||
},
|
||||
{
|
||||
"name": "Graham Campbell",
|
||||
"email": "hello@gjcampbell.co.uk",
|
||||
"homepage": "https://github.com/GrahamCampbell"
|
||||
}
|
||||
],
|
||||
"require": {
|
||||
"php": "^7.2.5 || ^8.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"bamarni/composer-bin-plugin": "^1.8.2",
|
||||
"phpunit/phpunit": "^8.5.44 || ^9.6.25 || ^10.5.53 || ^11.5.34"
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"PhpOption\\": "src/PhpOption/"
|
||||
}
|
||||
},
|
||||
"autoload-dev": {
|
||||
"psr-4": {
|
||||
"PhpOption\\Tests\\": "tests/PhpOption/Tests/"
|
||||
}
|
||||
},
|
||||
"config": {
|
||||
"allow-plugins": {
|
||||
"bamarni/composer-bin-plugin": true
|
||||
},
|
||||
"preferred-install": "dist"
|
||||
},
|
||||
"extra": {
|
||||
"bamarni-bin": {
|
||||
"bin-links": true,
|
||||
"forward-command": false
|
||||
},
|
||||
"branch-alias": {
|
||||
"dev-master": "1.9-dev"
|
||||
}
|
||||
}
|
||||
}
|
||||
+175
@@ -0,0 +1,175 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* Copyright 2012 Johannes M. Schmitt <schmittjoh@gmail.com>
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
namespace PhpOption;
|
||||
|
||||
use Traversable;
|
||||
|
||||
/**
|
||||
* @template T
|
||||
*
|
||||
* @extends Option<T>
|
||||
*/
|
||||
final class LazyOption extends Option
|
||||
{
|
||||
/** @var callable(mixed...):(Option<T>) */
|
||||
private $callback;
|
||||
|
||||
/** @var array<int, mixed> */
|
||||
private $arguments;
|
||||
|
||||
/** @var Option<T>|null */
|
||||
private $option;
|
||||
|
||||
/**
|
||||
* @template S
|
||||
* @param callable(mixed...):(Option<S>) $callback
|
||||
* @param array<int, mixed> $arguments
|
||||
*
|
||||
* @return LazyOption<S>
|
||||
*/
|
||||
public static function create($callback, array $arguments = []): self
|
||||
{
|
||||
return new self($callback, $arguments);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param callable(mixed...):(Option<T>) $callback
|
||||
* @param array<int, mixed> $arguments
|
||||
*/
|
||||
public function __construct($callback, array $arguments = [])
|
||||
{
|
||||
if (!is_callable($callback)) {
|
||||
throw new \InvalidArgumentException('Invalid callback given');
|
||||
}
|
||||
|
||||
$this->callback = $callback;
|
||||
$this->arguments = $arguments;
|
||||
}
|
||||
|
||||
public function isDefined(): bool
|
||||
{
|
||||
return $this->option()->isDefined();
|
||||
}
|
||||
|
||||
public function isEmpty(): bool
|
||||
{
|
||||
return $this->option()->isEmpty();
|
||||
}
|
||||
|
||||
public function get()
|
||||
{
|
||||
return $this->option()->get();
|
||||
}
|
||||
|
||||
public function getOrElse($default)
|
||||
{
|
||||
return $this->option()->getOrElse($default);
|
||||
}
|
||||
|
||||
public function getOrCall($callable)
|
||||
{
|
||||
return $this->option()->getOrCall($callable);
|
||||
}
|
||||
|
||||
public function getOrThrow(\Exception $ex)
|
||||
{
|
||||
return $this->option()->getOrThrow($ex);
|
||||
}
|
||||
|
||||
public function orElse(Option $else)
|
||||
{
|
||||
return $this->option()->orElse($else);
|
||||
}
|
||||
|
||||
public function ifDefined($callable)
|
||||
{
|
||||
$this->option()->forAll($callable);
|
||||
}
|
||||
|
||||
public function forAll($callable)
|
||||
{
|
||||
return $this->option()->forAll($callable);
|
||||
}
|
||||
|
||||
public function map($callable)
|
||||
{
|
||||
return $this->option()->map($callable);
|
||||
}
|
||||
|
||||
public function flatMap($callable)
|
||||
{
|
||||
return $this->option()->flatMap($callable);
|
||||
}
|
||||
|
||||
public function filter($callable)
|
||||
{
|
||||
return $this->option()->filter($callable);
|
||||
}
|
||||
|
||||
public function filterNot($callable)
|
||||
{
|
||||
return $this->option()->filterNot($callable);
|
||||
}
|
||||
|
||||
public function select($value)
|
||||
{
|
||||
return $this->option()->select($value);
|
||||
}
|
||||
|
||||
public function reject($value)
|
||||
{
|
||||
return $this->option()->reject($value);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Traversable<T>
|
||||
*/
|
||||
public function getIterator(): Traversable
|
||||
{
|
||||
return $this->option()->getIterator();
|
||||
}
|
||||
|
||||
public function foldLeft($initialValue, $callable)
|
||||
{
|
||||
return $this->option()->foldLeft($initialValue, $callable);
|
||||
}
|
||||
|
||||
public function foldRight($initialValue, $callable)
|
||||
{
|
||||
return $this->option()->foldRight($initialValue, $callable);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Option<T>
|
||||
*/
|
||||
private function option(): Option
|
||||
{
|
||||
if (null === $this->option) {
|
||||
/** @var mixed */
|
||||
$option = call_user_func_array($this->callback, $this->arguments);
|
||||
if ($option instanceof Option) {
|
||||
$this->option = $option;
|
||||
} else {
|
||||
throw new \RuntimeException(sprintf('Expected instance of %s', Option::class));
|
||||
}
|
||||
}
|
||||
|
||||
return $this->option;
|
||||
}
|
||||
}
|
||||
+136
@@ -0,0 +1,136 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* Copyright 2012 Johannes M. Schmitt <schmittjoh@gmail.com>
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
namespace PhpOption;
|
||||
|
||||
use EmptyIterator;
|
||||
|
||||
/**
|
||||
* @extends Option<mixed>
|
||||
*/
|
||||
final class None extends Option
|
||||
{
|
||||
/** @var None|null */
|
||||
private static $instance;
|
||||
|
||||
/**
|
||||
* @return None
|
||||
*/
|
||||
public static function create(): self
|
||||
{
|
||||
if (null === self::$instance) {
|
||||
self::$instance = new self();
|
||||
}
|
||||
|
||||
return self::$instance;
|
||||
}
|
||||
|
||||
public function get()
|
||||
{
|
||||
throw new \RuntimeException('None has no value.');
|
||||
}
|
||||
|
||||
public function getOrCall($callable)
|
||||
{
|
||||
return $callable();
|
||||
}
|
||||
|
||||
public function getOrElse($default)
|
||||
{
|
||||
return $default;
|
||||
}
|
||||
|
||||
public function getOrThrow(\Exception $ex)
|
||||
{
|
||||
throw $ex;
|
||||
}
|
||||
|
||||
public function isEmpty(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public function isDefined(): bool
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
public function orElse(Option $else)
|
||||
{
|
||||
return $else;
|
||||
}
|
||||
|
||||
public function ifDefined($callable)
|
||||
{
|
||||
// Just do nothing in that case.
|
||||
}
|
||||
|
||||
public function forAll($callable)
|
||||
{
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function map($callable)
|
||||
{
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function flatMap($callable)
|
||||
{
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function filter($callable)
|
||||
{
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function filterNot($callable)
|
||||
{
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function select($value)
|
||||
{
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function reject($value)
|
||||
{
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getIterator(): EmptyIterator
|
||||
{
|
||||
return new EmptyIterator();
|
||||
}
|
||||
|
||||
public function foldLeft($initialValue, $callable)
|
||||
{
|
||||
return $initialValue;
|
||||
}
|
||||
|
||||
public function foldRight($initialValue, $callable)
|
||||
{
|
||||
return $initialValue;
|
||||
}
|
||||
|
||||
private function __construct()
|
||||
{
|
||||
}
|
||||
}
|
||||
+434
@@ -0,0 +1,434 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* Copyright 2012 Johannes M. Schmitt <schmittjoh@gmail.com>
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
namespace PhpOption;
|
||||
|
||||
use ArrayAccess;
|
||||
use IteratorAggregate;
|
||||
|
||||
/**
|
||||
* @template T
|
||||
*
|
||||
* @implements IteratorAggregate<T>
|
||||
*/
|
||||
abstract class Option implements IteratorAggregate
|
||||
{
|
||||
/**
|
||||
* Creates an option given a return value.
|
||||
*
|
||||
* This is intended for consuming existing APIs and allows you to easily
|
||||
* convert them to an option. By default, we treat ``null`` as the None
|
||||
* case, and everything else as Some.
|
||||
*
|
||||
* @template S
|
||||
*
|
||||
* @param S $value The actual return value.
|
||||
* @param S $noneValue The value which should be considered "None"; null by
|
||||
* default.
|
||||
*
|
||||
* @return Option<S>
|
||||
*/
|
||||
public static function fromValue($value, $noneValue = null)
|
||||
{
|
||||
if ($value === $noneValue) {
|
||||
return None::create();
|
||||
}
|
||||
|
||||
return new Some($value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an option from an array's value.
|
||||
*
|
||||
* If the key does not exist in the array, the array is not actually an
|
||||
* array, or the array's value at the given key is null, None is returned.
|
||||
* Otherwise, Some is returned wrapping the value at the given key.
|
||||
*
|
||||
* @template S
|
||||
*
|
||||
* @param array<string|int,S>|ArrayAccess<string|int,S>|null $array A potential array or \ArrayAccess value.
|
||||
* @param string|int|null $key The key to check.
|
||||
*
|
||||
* @return Option<S>
|
||||
*/
|
||||
public static function fromArraysValue($array, $key)
|
||||
{
|
||||
if ($key === null || !(is_array($array) || $array instanceof ArrayAccess) || !isset($array[$key])) {
|
||||
return None::create();
|
||||
}
|
||||
|
||||
return new Some($array[$key]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a lazy-option with the given callback.
|
||||
*
|
||||
* This is also a helper constructor for lazy-consuming existing APIs where
|
||||
* the return value is not yet an option. By default, we treat ``null`` as
|
||||
* None case, and everything else as Some.
|
||||
*
|
||||
* @template S
|
||||
*
|
||||
* @param callable $callback The callback to evaluate.
|
||||
* @param array $arguments The arguments for the callback.
|
||||
* @param S $noneValue The value which should be considered "None";
|
||||
* null by default.
|
||||
*
|
||||
* @return LazyOption<S>
|
||||
*/
|
||||
public static function fromReturn($callback, array $arguments = [], $noneValue = null)
|
||||
{
|
||||
return new LazyOption(static function () use ($callback, $arguments, $noneValue) {
|
||||
/** @var mixed */
|
||||
$return = call_user_func_array($callback, $arguments);
|
||||
|
||||
if ($return === $noneValue) {
|
||||
return None::create();
|
||||
}
|
||||
|
||||
return new Some($return);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Option factory, which creates new option based on passed value.
|
||||
*
|
||||
* If value is already an option, it simply returns. If value is callable,
|
||||
* LazyOption with passed callback created and returned. If Option
|
||||
* returned from callback, it returns directly. On other case value passed
|
||||
* to Option::fromValue() method.
|
||||
*
|
||||
* @template S
|
||||
*
|
||||
* @param Option<S>|callable|S $value
|
||||
* @param S $noneValue Used when $value is mixed or
|
||||
* callable, for None-check.
|
||||
*
|
||||
* @return Option<S>|LazyOption<S>
|
||||
*/
|
||||
public static function ensure($value, $noneValue = null)
|
||||
{
|
||||
if ($value instanceof self) {
|
||||
return $value;
|
||||
} elseif (is_callable($value)) {
|
||||
return new LazyOption(static function () use ($value, $noneValue) {
|
||||
/** @var mixed */
|
||||
$return = $value();
|
||||
|
||||
if ($return instanceof self) {
|
||||
return $return;
|
||||
} else {
|
||||
return self::fromValue($return, $noneValue);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
return self::fromValue($value, $noneValue);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Lift a function so that it accepts Option as parameters.
|
||||
*
|
||||
* We return a new closure that wraps the original callback. If any of the
|
||||
* parameters passed to the lifted function is empty, the function will
|
||||
* return a value of None. Otherwise, we will pass all parameters to the
|
||||
* original callback and return the value inside a new Option, unless an
|
||||
* Option is returned from the function, in which case, we use that.
|
||||
*
|
||||
* @template S
|
||||
*
|
||||
* @param callable $callback
|
||||
* @param mixed $noneValue
|
||||
*
|
||||
* @return callable
|
||||
*/
|
||||
public static function lift($callback, $noneValue = null)
|
||||
{
|
||||
return static function () use ($callback, $noneValue) {
|
||||
/** @var array<int, mixed> */
|
||||
$args = func_get_args();
|
||||
|
||||
$reduced_args = array_reduce(
|
||||
$args,
|
||||
/** @param bool $status */
|
||||
static function ($status, self $o) {
|
||||
return $o->isEmpty() ? true : $status;
|
||||
},
|
||||
false
|
||||
);
|
||||
// if at least one parameter is empty, return None
|
||||
if ($reduced_args) {
|
||||
return None::create();
|
||||
}
|
||||
|
||||
$args = array_map(
|
||||
/** @return T */
|
||||
static function (self $o) {
|
||||
// it is safe to do so because the fold above checked
|
||||
// that all arguments are of type Some
|
||||
/** @var T */
|
||||
return $o->get();
|
||||
},
|
||||
$args
|
||||
);
|
||||
|
||||
return self::ensure(call_user_func_array($callback, $args), $noneValue);
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the value if available, or throws an exception otherwise.
|
||||
*
|
||||
* @throws \RuntimeException If value is not available.
|
||||
*
|
||||
* @return T
|
||||
*/
|
||||
abstract public function get();
|
||||
|
||||
/**
|
||||
* Returns the value if available, or the default value if not.
|
||||
*
|
||||
* @template S
|
||||
*
|
||||
* @param S $default
|
||||
*
|
||||
* @return T|S
|
||||
*/
|
||||
abstract public function getOrElse($default);
|
||||
|
||||
/**
|
||||
* Returns the value if available, or the results of the callable.
|
||||
*
|
||||
* This is preferable over ``getOrElse`` if the computation of the default
|
||||
* value is expensive.
|
||||
*
|
||||
* @template S
|
||||
*
|
||||
* @param callable():S $callable
|
||||
*
|
||||
* @return T|S
|
||||
*/
|
||||
abstract public function getOrCall($callable);
|
||||
|
||||
/**
|
||||
* Returns the value if available, or throws the passed exception.
|
||||
*
|
||||
* @param \Exception $ex
|
||||
*
|
||||
* @return T
|
||||
*/
|
||||
abstract public function getOrThrow(\Exception $ex);
|
||||
|
||||
/**
|
||||
* Returns true if no value is available, false otherwise.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
abstract public function isEmpty();
|
||||
|
||||
/**
|
||||
* Returns true if a value is available, false otherwise.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
abstract public function isDefined();
|
||||
|
||||
/**
|
||||
* Returns this option if non-empty, or the passed option otherwise.
|
||||
*
|
||||
* This can be used to try multiple alternatives, and is especially useful
|
||||
* with lazy evaluating options:
|
||||
*
|
||||
* ```php
|
||||
* $repo->findSomething()
|
||||
* ->orElse(new LazyOption(array($repo, 'findSomethingElse')))
|
||||
* ->orElse(new LazyOption(array($repo, 'createSomething')));
|
||||
* ```
|
||||
*
|
||||
* @param Option<T> $else
|
||||
*
|
||||
* @return Option<T>
|
||||
*/
|
||||
abstract public function orElse(self $else);
|
||||
|
||||
/**
|
||||
* This is similar to map() below except that the return value has no meaning;
|
||||
* the passed callable is simply executed if the option is non-empty, and
|
||||
* ignored if the option is empty.
|
||||
*
|
||||
* In all cases, the return value of the callable is discarded.
|
||||
*
|
||||
* ```php
|
||||
* $comment->getMaybeFile()->ifDefined(function($file) {
|
||||
* // Do something with $file here.
|
||||
* });
|
||||
* ```
|
||||
*
|
||||
* If you're looking for something like ``ifEmpty``, you can use ``getOrCall``
|
||||
* and ``getOrElse`` in these cases.
|
||||
*
|
||||
* @deprecated Use forAll() instead.
|
||||
*
|
||||
* @param callable(T):mixed $callable
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
abstract public function ifDefined($callable);
|
||||
|
||||
/**
|
||||
* This is similar to map() except that the return value of the callable has no meaning.
|
||||
*
|
||||
* The passed callable is simply executed if the option is non-empty, and ignored if the
|
||||
* option is empty. This method is preferred for callables with side-effects, while map()
|
||||
* is intended for callables without side-effects.
|
||||
*
|
||||
* @param callable(T):mixed $callable
|
||||
*
|
||||
* @return Option<T>
|
||||
*/
|
||||
abstract public function forAll($callable);
|
||||
|
||||
/**
|
||||
* Applies the callable to the value of the option if it is non-empty,
|
||||
* and returns the return value of the callable wrapped in Some().
|
||||
*
|
||||
* If the option is empty, then the callable is not applied.
|
||||
*
|
||||
* ```php
|
||||
* (new Some("foo"))->map('strtoupper')->get(); // "FOO"
|
||||
* ```
|
||||
*
|
||||
* @template S
|
||||
*
|
||||
* @param callable(T):S $callable
|
||||
*
|
||||
* @return Option<S>
|
||||
*/
|
||||
abstract public function map($callable);
|
||||
|
||||
/**
|
||||
* Applies the callable to the value of the option if it is non-empty, and
|
||||
* returns the return value of the callable directly.
|
||||
*
|
||||
* In contrast to ``map``, the return value of the callable is expected to
|
||||
* be an Option itself; it is not automatically wrapped in Some().
|
||||
*
|
||||
* @template S
|
||||
*
|
||||
* @param callable(T):Option<S> $callable must return an Option
|
||||
*
|
||||
* @return Option<S>
|
||||
*/
|
||||
abstract public function flatMap($callable);
|
||||
|
||||
/**
|
||||
* If the option is empty, it is returned immediately without applying the callable.
|
||||
*
|
||||
* If the option is non-empty, the callable is applied, and if it returns true,
|
||||
* the option itself is returned; otherwise, None is returned.
|
||||
*
|
||||
* @param callable(T):bool $callable
|
||||
*
|
||||
* @return Option<T>
|
||||
*/
|
||||
abstract public function filter($callable);
|
||||
|
||||
/**
|
||||
* If the option is empty, it is returned immediately without applying the callable.
|
||||
*
|
||||
* If the option is non-empty, the callable is applied, and if it returns false,
|
||||
* the option itself is returned; otherwise, None is returned.
|
||||
*
|
||||
* @param callable(T):bool $callable
|
||||
*
|
||||
* @return Option<T>
|
||||
*/
|
||||
abstract public function filterNot($callable);
|
||||
|
||||
/**
|
||||
* If the option is empty, it is returned immediately.
|
||||
*
|
||||
* If the option is non-empty, and its value does not equal the passed value
|
||||
* (via a shallow comparison ===), then None is returned. Otherwise, the
|
||||
* Option is returned.
|
||||
*
|
||||
* In other words, this will filter all but the passed value.
|
||||
*
|
||||
* @param T $value
|
||||
*
|
||||
* @return Option<T>
|
||||
*/
|
||||
abstract public function select($value);
|
||||
|
||||
/**
|
||||
* If the option is empty, it is returned immediately.
|
||||
*
|
||||
* If the option is non-empty, and its value does equal the passed value (via
|
||||
* a shallow comparison ===), then None is returned; otherwise, the Option is
|
||||
* returned.
|
||||
*
|
||||
* In other words, this will let all values through except the passed value.
|
||||
*
|
||||
* @param T $value
|
||||
*
|
||||
* @return Option<T>
|
||||
*/
|
||||
abstract public function reject($value);
|
||||
|
||||
/**
|
||||
* Binary operator for the initial value and the option's value.
|
||||
*
|
||||
* If empty, the initial value is returned. If non-empty, the callable
|
||||
* receives the initial value and the option's value as arguments.
|
||||
*
|
||||
* ```php
|
||||
*
|
||||
* $some = new Some(5);
|
||||
* $none = None::create();
|
||||
* $result = $some->foldLeft(1, function($a, $b) { return $a + $b; }); // int(6)
|
||||
* $result = $none->foldLeft(1, function($a, $b) { return $a + $b; }); // int(1)
|
||||
*
|
||||
* // This can be used instead of something like the following:
|
||||
* $option = Option::fromValue($integerOrNull);
|
||||
* $result = 1;
|
||||
* if ( ! $option->isEmpty()) {
|
||||
* $result += $option->get();
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* @template S
|
||||
*
|
||||
* @param S $initialValue
|
||||
* @param callable(S, T):S $callable
|
||||
*
|
||||
* @return S
|
||||
*/
|
||||
abstract public function foldLeft($initialValue, $callable);
|
||||
|
||||
/**
|
||||
* foldLeft() but with reversed arguments for the callable.
|
||||
*
|
||||
* @template S
|
||||
*
|
||||
* @param S $initialValue
|
||||
* @param callable(T, S):S $callable
|
||||
*
|
||||
* @return S
|
||||
*/
|
||||
abstract public function foldRight($initialValue, $callable);
|
||||
}
|
||||
+169
@@ -0,0 +1,169 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* Copyright 2012 Johannes M. Schmitt <schmittjoh@gmail.com>
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
namespace PhpOption;
|
||||
|
||||
use ArrayIterator;
|
||||
|
||||
/**
|
||||
* @template T
|
||||
*
|
||||
* @extends Option<T>
|
||||
*/
|
||||
final class Some extends Option
|
||||
{
|
||||
/** @var T */
|
||||
private $value;
|
||||
|
||||
/**
|
||||
* @param T $value
|
||||
*/
|
||||
public function __construct($value)
|
||||
{
|
||||
$this->value = $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* @template U
|
||||
*
|
||||
* @param U $value
|
||||
*
|
||||
* @return Some<U>
|
||||
*/
|
||||
public static function create($value): self
|
||||
{
|
||||
return new self($value);
|
||||
}
|
||||
|
||||
public function isDefined(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public function isEmpty(): bool
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
public function get()
|
||||
{
|
||||
return $this->value;
|
||||
}
|
||||
|
||||
public function getOrElse($default)
|
||||
{
|
||||
return $this->value;
|
||||
}
|
||||
|
||||
public function getOrCall($callable)
|
||||
{
|
||||
return $this->value;
|
||||
}
|
||||
|
||||
public function getOrThrow(\Exception $ex)
|
||||
{
|
||||
return $this->value;
|
||||
}
|
||||
|
||||
public function orElse(Option $else)
|
||||
{
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function ifDefined($callable)
|
||||
{
|
||||
$this->forAll($callable);
|
||||
}
|
||||
|
||||
public function forAll($callable)
|
||||
{
|
||||
$callable($this->value);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function map($callable)
|
||||
{
|
||||
return new self($callable($this->value));
|
||||
}
|
||||
|
||||
public function flatMap($callable)
|
||||
{
|
||||
/** @var mixed */
|
||||
$rs = $callable($this->value);
|
||||
if (!$rs instanceof Option) {
|
||||
throw new \RuntimeException('Callables passed to flatMap() must return an Option. Maybe you should use map() instead?');
|
||||
}
|
||||
|
||||
return $rs;
|
||||
}
|
||||
|
||||
public function filter($callable)
|
||||
{
|
||||
if (true === $callable($this->value)) {
|
||||
return $this;
|
||||
}
|
||||
|
||||
return None::create();
|
||||
}
|
||||
|
||||
public function filterNot($callable)
|
||||
{
|
||||
if (false === $callable($this->value)) {
|
||||
return $this;
|
||||
}
|
||||
|
||||
return None::create();
|
||||
}
|
||||
|
||||
public function select($value)
|
||||
{
|
||||
if ($this->value === $value) {
|
||||
return $this;
|
||||
}
|
||||
|
||||
return None::create();
|
||||
}
|
||||
|
||||
public function reject($value)
|
||||
{
|
||||
if ($this->value === $value) {
|
||||
return None::create();
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return ArrayIterator<int, T>
|
||||
*/
|
||||
public function getIterator(): ArrayIterator
|
||||
{
|
||||
return new ArrayIterator([$this->value]);
|
||||
}
|
||||
|
||||
public function foldLeft($initialValue, $callable)
|
||||
{
|
||||
return $callable($initialValue, $this->value);
|
||||
}
|
||||
|
||||
public function foldRight($initialValue, $callable)
|
||||
{
|
||||
return $callable($this->value, $initialValue);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2009-2020 Daniele Alessandri (original work)
|
||||
Copyright (c) 2021-2024 Till Krüss (modified work)
|
||||
|
||||
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.
|
||||
@@ -0,0 +1,763 @@
|
||||
# Predis #
|
||||
|
||||
[![Software license][ico-license]](LICENSE)
|
||||
[![Latest stable][ico-version-stable]][link-releases]
|
||||
[![Latest development][ico-version-dev]][link-releases]
|
||||
[![Monthly installs][ico-downloads-monthly]][link-downloads]
|
||||
[![Build status][ico-build]][link-actions]
|
||||
[![Coverage Status][ico-coverage]][link-coverage]
|
||||
|
||||
A flexible and feature-complete [Redis](http://redis.io) / [Valkey](https://github.com/valkey-io/valkey) client for PHP 7.2 and newer.
|
||||
|
||||
More details about this project can be found on the [frequently asked questions](FAQ.md).
|
||||
|
||||
|
||||
## Main features ##
|
||||
|
||||
- Support for Redis from __3.0__ to __8.0__.
|
||||
- Support for clustering using client-side sharding and pluggable keyspace distributors.
|
||||
- Support for [redis-cluster](http://redis.io/topics/cluster-tutorial) (Redis >= 3.0).
|
||||
- Support for master-slave replication setups and [redis-sentinel](http://redis.io/topics/sentinel).
|
||||
- Transparent key prefixing of keys using a customizable prefix strategy.
|
||||
- Command pipelining on both single nodes and clusters (client-side sharding only).
|
||||
- Abstraction for Redis transactions (Redis >= 2.0) and CAS operations (Redis >= 2.2).
|
||||
- Abstraction for Lua scripting (Redis >= 2.6) and automatic switching between `EVALSHA` or `EVAL`.
|
||||
- Abstraction for `SCAN`, `SSCAN`, `ZSCAN` and `HSCAN` (Redis >= 2.8) based on PHP iterators.
|
||||
- Connections are established lazily by the client upon the first command and can be persisted.
|
||||
- Connections can be established via TCP/IP (also TLS/SSL-encrypted) or UNIX domain sockets.
|
||||
- Support for custom connection classes for providing different network or protocol backends.
|
||||
- Flexible system for defining custom commands and override the default ones.
|
||||
|
||||
|
||||
## How to _install_ and use Predis ##
|
||||
|
||||
This library can be found on [Packagist](http://packagist.org/packages/predis/predis) for an easier
|
||||
management of projects dependencies using [Composer](http://packagist.org/about-composer).
|
||||
Compressed archives of each release are [available on GitHub](https://github.com/predis/predis/releases).
|
||||
|
||||
```shell
|
||||
composer require predis/predis
|
||||
```
|
||||
|
||||
|
||||
### Loading the library ###
|
||||
|
||||
Predis relies on the autoloading features of PHP to load its files when needed and complies with the
|
||||
[PSR-4 standard](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-4-autoloader.md).
|
||||
Autoloading is handled automatically when dependencies are managed through Composer, but it is also
|
||||
possible to leverage its own autoloader in projects or scripts lacking any autoload facility:
|
||||
|
||||
```php
|
||||
// Prepend a base path if Predis is not available in your "include_path".
|
||||
require 'Predis/Autoloader.php';
|
||||
|
||||
Predis\Autoloader::register();
|
||||
```
|
||||
|
||||
|
||||
### Connecting to Redis ###
|
||||
|
||||
When creating a client instance without passing any connection parameter, Predis assumes `127.0.0.1`
|
||||
and `6379` as default host and port. The default timeout for the `connect()` operation is 5 seconds:
|
||||
|
||||
```php
|
||||
$client = new Predis\Client();
|
||||
$client->set('foo', 'bar');
|
||||
$value = $client->get('foo');
|
||||
```
|
||||
|
||||
Connection parameters can be supplied either in the form of URI strings or named arrays. The latter
|
||||
is the preferred way to supply parameters, but URI strings can be useful when parameters are read
|
||||
from non-structured or partially-structured sources:
|
||||
|
||||
```php
|
||||
// Parameters passed using a named array:
|
||||
$client = new Predis\Client([
|
||||
'scheme' => 'tcp',
|
||||
'host' => '10.0.0.1',
|
||||
'port' => 6379,
|
||||
]);
|
||||
|
||||
// Same set of parameters, passed using an URI string:
|
||||
$client = new Predis\Client('tcp://10.0.0.1:6379');
|
||||
```
|
||||
|
||||
Password protected servers can be accessed by adding `password` to the parameters set. When ACLs are
|
||||
enabled on Redis >= 6.0, both `username` and `password` are required for user authentication.
|
||||
|
||||
It is also possible to connect to local instances of Redis using UNIX domain sockets, in this case
|
||||
the parameters must use the `unix` scheme and specify a path for the socket file:
|
||||
|
||||
```php
|
||||
$client = new Predis\Client(['scheme' => 'unix', 'path' => '/path/to/redis.sock']);
|
||||
$client = new Predis\Client('unix:/path/to/redis.sock');
|
||||
```
|
||||
|
||||
The client can leverage TLS/SSL encryption to connect to secured remote Redis instances without the
|
||||
need to configure an SSL proxy like stunnel. This can be useful when connecting to nodes running on
|
||||
various cloud hosting providers. Encryption can be enabled with using the `tls` scheme and an array
|
||||
of suitable [options](http://php.net/manual/context.ssl.php) passed via the `ssl` parameter:
|
||||
|
||||
```php
|
||||
// Named array of connection parameters:
|
||||
$client = new Predis\Client([
|
||||
'scheme' => 'tls',
|
||||
'ssl' => ['cafile' => 'private.pem', 'verify_peer' => true],
|
||||
]);
|
||||
|
||||
// Same set of parameters, but using an URI string:
|
||||
$client = new Predis\Client('tls://127.0.0.1?ssl[cafile]=private.pem&ssl[verify_peer]=1');
|
||||
```
|
||||
|
||||
The connection schemes [`redis`](http://www.iana.org/assignments/uri-schemes/prov/redis) (alias of
|
||||
`tcp`) and [`rediss`](http://www.iana.org/assignments/uri-schemes/prov/rediss) (alias of `tls`) are
|
||||
also supported, with the difference that URI strings containing these schemes are parsed following
|
||||
the rules described on their respective IANA provisional registration documents.
|
||||
|
||||
Since Redis 8.6, you can authenticate a client using the Subject CN from its TLS client certificate (mTLS).
|
||||
When this is enabled on the server, the client is authenticated during the TLS handshake, so you don’t need
|
||||
to send an AUTH command.
|
||||
|
||||
To use this, configure:
|
||||
|
||||
- a CA certificate used to verify the server certificate (cafile),
|
||||
- a client certificate (local_cert) signed by a CA trusted by the Redis server for client authentication,
|
||||
- the corresponding private key (local_pk).
|
||||
|
||||
Make sure:
|
||||
|
||||
- the Redis server certificate is signed by a CA trusted by the client, and
|
||||
- the client certificate is signed by a CA trusted by the Redis server (mTLS).
|
||||
|
||||
```php
|
||||
// Named array of connection parameters:
|
||||
$client = new Predis\Client([
|
||||
'scheme' => 'tls',
|
||||
'ssl' => [
|
||||
'cafile' => 'ca.pem', // CA used to verify the server certificate
|
||||
'local_cert' => 'client.crt', // client certificate (Subject CN maps to ACL user)
|
||||
'local_pk' => 'client.key', // client private key
|
||||
'verify_peer' => true,
|
||||
],
|
||||
]);
|
||||
|
||||
// ACL user must exist and match the certificate Subject CN (example: CN=CN_NAME).
|
||||
// Enable the user and grant permissions as needed:
|
||||
$client->acl->setUser('CN_NAME', 'on', '>clientpass', 'allcommands', 'allkeys')
|
||||
|
||||
echo $client->acl->whoami() // CN_NAME
|
||||
```
|
||||
|
||||
The actual list of supported connection parameters can vary depending on each connection backend so
|
||||
it is recommended to refer to their specific documentation or implementation for details.
|
||||
|
||||
Predis can aggregate multiple connections when providing an array of connection parameters and the
|
||||
appropriate option to instruct the client about how to aggregate them (clustering, replication or a
|
||||
custom aggregation logic). Named arrays and URI strings can be mixed when providing configurations
|
||||
for each node:
|
||||
|
||||
```php
|
||||
$client = new Predis\Client([
|
||||
'tcp://10.0.0.1?alias=first-node', ['host' => '10.0.0.2', 'alias' => 'second-node'],
|
||||
], [
|
||||
'cluster' => 'predis',
|
||||
]);
|
||||
```
|
||||
|
||||
See the [aggregate connections](#aggregate-connections) section of this document for more details.
|
||||
|
||||
Connections to Redis are lazy meaning that the client connects to a server only if and when needed.
|
||||
While it is recommended to let the client do its own stuff under the hood, there may be times when
|
||||
it is still desired to have control of when the connection is opened or closed: this can easily be
|
||||
achieved by invoking `$client->connect()` and `$client->disconnect()`. Please note that the effect
|
||||
of these methods on aggregate connections may differ depending on each specific implementation.
|
||||
|
||||
#### Persistent connections ####
|
||||
|
||||
To increase a performance of your application you may set up a client to use persistent TCP connection, this way
|
||||
client saves a time on socket creation and connection handshake. By default, connection is created on first-command
|
||||
execution and will be automatically closed by GC before the process is being killed.
|
||||
However, if your application is backed by PHP-FPM the processes are idle, and you may set up it to be persistent and
|
||||
reusable across multiple script execution within the same process.
|
||||
|
||||
To enable the persistent connection mode you should provide following configuration:
|
||||
|
||||
```php
|
||||
// Standalone
|
||||
$client = new Predis\Client(['persistent' => true]);
|
||||
|
||||
// Cluster
|
||||
$client = new Predis\Client(
|
||||
['tcp://host:port', 'tcp://host:port', 'tcp://host:port'],
|
||||
['cluster' => 'redis', 'parameters' => ['persistent' => true]]
|
||||
);
|
||||
```
|
||||
|
||||
**Important**
|
||||
|
||||
If you operate on multiple clients within the same application, and they communicate with the same resource, by default
|
||||
they will share the same socket (that's the default behaviour of persistent sockets). So in this case you would need
|
||||
to additionally provide a `conn_uid` identifier for each client, this way each client will create its own socket so
|
||||
the connection context won't be shared across clients. This socket behaviour explained
|
||||
[here](https://www.php.net/manual/en/function.stream-socket-client.php#105393)
|
||||
|
||||
```php
|
||||
// Standalone
|
||||
$client1 = new Predis\Client(['persistent' => true, 'conn_uid' => 'id_1']);
|
||||
$client2 = new Predis\Client(['persistent' => true, 'conn_uid' => 'id_2']);
|
||||
|
||||
// Cluster
|
||||
$client1 = new Predis\Client(
|
||||
['tcp://host:port', 'tcp://host:port', 'tcp://host:port'],
|
||||
['cluster' => 'redis', 'parameters' => ['persistent' => true, 'conn_uid' => 'id_1']]
|
||||
);
|
||||
$client2 = new Predis\Client(
|
||||
['tcp://host:port', 'tcp://host:port', 'tcp://host:port'],
|
||||
['cluster' => 'redis', 'parameters' => ['persistent' => true, 'conn_uid' => 'id_2']]
|
||||
);
|
||||
```
|
||||
|
||||
### Client configuration ###
|
||||
|
||||
Many aspects and behaviors of the client can be configured by passing specific client options to the
|
||||
second argument of `Predis\Client::__construct()`:
|
||||
|
||||
```php
|
||||
$client = new Predis\Client($parameters, ['prefix' => 'sample:']);
|
||||
```
|
||||
|
||||
Options are managed using a mini DI-alike container and their values can be lazily initialized only
|
||||
when needed. The client options supported by default in Predis are:
|
||||
|
||||
- `prefix`: prefix string applied to every key found in commands.
|
||||
- `exceptions`: whether the client should throw or return responses upon Redis errors.
|
||||
- `connections`: list of connection backends or a connection factory instance.
|
||||
- `cluster`: specifies a cluster backend (`predis`, `redis` or callable).
|
||||
- `replication`: specifies a replication backend (`predis`, `sentinel` or callable).
|
||||
- `aggregate`: configures the client with a custom aggregate connection (callable).
|
||||
- `parameters`: list of default connection parameters for aggregate connections.
|
||||
- `commands`: specifies a command factory instance to use through the library.
|
||||
- `readTimeout`: (cluster only) Timeout between read operations while loop over connections.
|
||||
|
||||
Users can also provide custom options with values or callable objects (for lazy initialization) that
|
||||
are stored in the options container for later use through the library.
|
||||
|
||||
|
||||
### Aggregate connections ###
|
||||
|
||||
Aggregate connections are the foundation upon which Predis implements clustering and replication and
|
||||
they are used to group multiple connections to single Redis nodes and hide the specific logic needed
|
||||
to handle them properly depending on the context. Aggregate connections usually require an array of
|
||||
connection parameters along with the appropriate client option when creating a new client instance.
|
||||
|
||||
#### Cluster ####
|
||||
|
||||
Predis can be configured to work in clustering mode with a traditional client-side sharding approach
|
||||
to create a cluster of independent nodes and distribute the keyspace among them. This approach needs
|
||||
some sort of external health monitoring of nodes and requires the keyspace to be rebalanced manually
|
||||
when nodes are added or removed:
|
||||
|
||||
```php
|
||||
$parameters = ['tcp://10.0.0.1', 'tcp://10.0.0.2', 'tcp://10.0.0.3'];
|
||||
$options = ['cluster' => 'predis'];
|
||||
|
||||
$client = new Predis\Client($parameters);
|
||||
```
|
||||
|
||||
Along with Redis 3.0, a new supervised and coordinated type of clustering was introduced in the form
|
||||
of [redis-cluster](http://redis.io/topics/cluster-tutorial). This kind of approach uses a different
|
||||
algorithm to distribute the keyspaces, with Redis nodes coordinating themselves by communicating via
|
||||
a gossip protocol to handle health status, rebalancing, nodes discovery and request redirection. In
|
||||
order to connect to a cluster managed by redis-cluster, the client requires a list of its nodes (not
|
||||
necessarily complete since it will automatically discover new nodes if necessary) and the `cluster`
|
||||
client options set to `redis`:
|
||||
|
||||
```php
|
||||
$parameters = ['tcp://10.0.0.1', 'tcp://10.0.0.2', 'tcp://10.0.0.3'];
|
||||
$options = ['cluster' => 'redis'];
|
||||
|
||||
$client = new Predis\Client($parameters, $options);
|
||||
```
|
||||
|
||||
#### Redis Gears with cluster ####
|
||||
|
||||
Since Redis v7.2, Redis Gears module is a part of Redis Stack bundle. Client supports a variety of
|
||||
Redis Gears commands that can be used with OSS cluster API. Currently, before using any Redis
|
||||
Gears commands against OSS cluster Redis server needs to be aware of cluster topology.
|
||||
|
||||
`REDISGEARS_2.REFRESHCLUSTER` command should be called against **each master node** (read replicas
|
||||
should be ignored) **on cluster creation and each time cluster topology changes**.
|
||||
|
||||
In most cases this actions should be performed from the CLI interface by the administrator, DevOPS
|
||||
or even Kubernetes, depends on your infrastructure managing process. However, client provides an API
|
||||
to do this programmatically.
|
||||
|
||||
```php
|
||||
/** @var \Predis\Connection\Cluster\ClusterInterface $connection */
|
||||
$connection->executeCommandOnEachNode(
|
||||
new \Predis\Command\RawCommand('REDISGEARS_2.REFRESHCLUSTER')
|
||||
);
|
||||
```
|
||||
|
||||
#### Replication ####
|
||||
|
||||
The client can be configured to operate in a single master / multiple slaves setup to provide better
|
||||
service availability. When using replication, Predis recognizes read-only commands and sends them to
|
||||
a random slave in order to provide some sort of load-balancing and switches to the master as soon as
|
||||
it detects a command that performs any kind of operation that would end up modifying the keyspace or
|
||||
the value of a key. Instead of raising a connection error when a slave fails, the client attempts to
|
||||
fall back to a different slave among the ones provided in the configuration.
|
||||
|
||||
The basic configuration needed to use the client in replication mode requires one Redis server to be
|
||||
identified as the master (this can be done via connection parameters by setting the `role` parameter
|
||||
to `master`) and one or more slaves (in this case setting `role` to `slave` for slaves is optional):
|
||||
|
||||
```php
|
||||
$parameters = ['tcp://10.0.0.1?role=master', 'tcp://10.0.0.2', 'tcp://10.0.0.3'];
|
||||
$options = ['replication' => 'predis'];
|
||||
|
||||
$client = new Predis\Client($parameters, $options);
|
||||
```
|
||||
|
||||
The above configuration has a static list of servers and relies entirely on the client's logic, but
|
||||
it is possible to rely on [`redis-sentinel`](http://redis.io/topics/sentinel) for a more robust HA
|
||||
environment with sentinel servers acting as a source of authority for clients for service discovery.
|
||||
The minimum configuration required by the client to work with redis-sentinel is a list of connection
|
||||
parameters pointing to a bunch of sentinel instances, the `replication` option set to `sentinel` and
|
||||
the `service` option set to the name of the service:
|
||||
|
||||
```php
|
||||
$sentinels = ['tcp://10.0.0.1', 'tcp://10.0.0.2', 'tcp://10.0.0.3'];
|
||||
$options = ['replication' => 'sentinel', 'service' => 'mymaster'];
|
||||
|
||||
$client = new Predis\Client($sentinels, $options);
|
||||
```
|
||||
|
||||
If the master and slave nodes are configured to require an authentication from clients, a password
|
||||
must be provided via the global `parameters` client option. This option can also be used to specify
|
||||
a different database index. The client options array would then look like this:
|
||||
|
||||
```php
|
||||
$options = [
|
||||
'replication' => 'sentinel',
|
||||
'service' => 'mymaster',
|
||||
'parameters' => [
|
||||
'password' => $secretpassword,
|
||||
'database' => 10,
|
||||
],
|
||||
];
|
||||
```
|
||||
|
||||
While Predis is able to distinguish commands performing write and read-only operations, `EVAL` and
|
||||
`EVALSHA` represent a corner case in which the client switches to the master node because it cannot
|
||||
tell when a Lua script is safe to be executed on slaves. While this is indeed the default behavior,
|
||||
when certain Lua scripts do not perform write operations it is possible to provide an hint to tell
|
||||
the client to stick with slaves for their execution:
|
||||
|
||||
```php
|
||||
$parameters = ['tcp://10.0.0.1?role=master', 'tcp://10.0.0.2', 'tcp://10.0.0.3'];
|
||||
$options = ['replication' => function () {
|
||||
// Set scripts that won't trigger a switch from a slave to the master node.
|
||||
$strategy = new Predis\Replication\ReplicationStrategy();
|
||||
$strategy->setScriptReadOnly($LUA_SCRIPT);
|
||||
|
||||
return new Predis\Connection\Replication\MasterSlaveReplication($strategy);
|
||||
}];
|
||||
|
||||
$client = new Predis\Client($parameters, $options);
|
||||
$client->eval($LUA_SCRIPT, 0); // Sticks to slave using `eval`...
|
||||
$client->evalsha(sha1($LUA_SCRIPT), 0); // ... and `evalsha`, too.
|
||||
```
|
||||
|
||||
The [`examples`](examples/) directory contains a few scripts that demonstrate how the client can be
|
||||
configured and used to leverage replication in both basic and complex scenarios.
|
||||
|
||||
|
||||
### Command pipelines ###
|
||||
|
||||
Pipelining can help with performances when many commands need to be sent to a server by reducing the
|
||||
latency introduced by network round-trip timings. Pipelining also works with aggregate connections.
|
||||
The client can execute the pipeline inside a callable block or return a pipeline instance with the
|
||||
ability to chain commands thanks to its fluent interface:
|
||||
|
||||
```php
|
||||
// Executes a pipeline inside the given callable block:
|
||||
$responses = $client->pipeline(function ($pipe) {
|
||||
for ($i = 0; $i < 1000; $i++) {
|
||||
$pipe->set("key:$i", str_pad($i, 4, '0', 0));
|
||||
$pipe->get("key:$i");
|
||||
}
|
||||
});
|
||||
|
||||
// Returns a pipeline that can be chained thanks to its fluent interface:
|
||||
$responses = $client->pipeline()->set('foo', 'bar')->get('foo')->execute();
|
||||
```
|
||||
|
||||
|
||||
### Transactions ###
|
||||
|
||||
The client provides an abstraction for Redis transactions based on `MULTI` and `EXEC` with a similar
|
||||
interface to command pipelines:
|
||||
|
||||
```php
|
||||
// Executes a transaction inside the given callable block:
|
||||
$responses = $client->transaction(function ($tx) {
|
||||
$tx->set('foo', 'bar');
|
||||
$tx->get('foo');
|
||||
});
|
||||
|
||||
// Returns a transaction that can be chained thanks to its fluent interface:
|
||||
$responses = $client->transaction()->set('foo', 'bar')->get('foo')->execute();
|
||||
```
|
||||
|
||||
This abstraction can perform check-and-set operations thanks to `WATCH` and `UNWATCH` and provides
|
||||
automatic retries of transactions aborted by Redis when `WATCH`ed keys are touched. For an example
|
||||
of a transaction using CAS you can see [the following example](examples/transaction_using_cas.php).
|
||||
|
||||
#### Support for clustered connections ####
|
||||
|
||||
Since Predis v3.0 transactions could be used with clustered connections. However, it has some limitations due to the
|
||||
fact that Redis doesn't support distributed transactions. All keys in the transaction context should operate on the same
|
||||
hash slot, due to this limitation it's recommended to use `{}` syntax to make sure that all keys will be mapped to the same hash
|
||||
slot. Apart from it no additional configuration needed on a client side.
|
||||
|
||||
```php
|
||||
$redis = $this->getClient();
|
||||
|
||||
$response = $redis->transaction(function (MultiExec $tx) {
|
||||
$tx->set('{foo}foo', 'value');
|
||||
$tx->set('{foo}bar', 'value');
|
||||
$tx->set('{foo}baz', 'value');
|
||||
});
|
||||
|
||||
// ['OK', 'OK', 'OK']
|
||||
```
|
||||
|
||||
|
||||
### Adding new commands ###
|
||||
|
||||
While we try to update Predis to stay up to date with all the commands available in Redis, you might
|
||||
prefer to stick with an old version of the library or provide a different way to filter arguments or
|
||||
parse responses for specific commands. To achieve that, Predis provides the ability to implement new
|
||||
command classes to define or override commands in the default command factory used by the client:
|
||||
|
||||
```php
|
||||
// Define a new command by extending Predis\Command\Command:
|
||||
class BrandNewRedisCommand extends Predis\Command\Command
|
||||
{
|
||||
public function getId()
|
||||
{
|
||||
return 'NEWCMD';
|
||||
}
|
||||
}
|
||||
|
||||
// Inject your command in the current command factory:
|
||||
$client = new Predis\Client($parameters, [
|
||||
'commands' => [
|
||||
'newcmd' => 'BrandNewRedisCommand',
|
||||
],
|
||||
]);
|
||||
|
||||
$response = $client->newcmd();
|
||||
```
|
||||
|
||||
There is also a method to send raw commands without filtering their arguments or parsing responses.
|
||||
Users must provide the list of arguments for the command as an array, following the signatures as
|
||||
defined by the [Redis documentation for commands](http://redis.io/commands):
|
||||
|
||||
```php
|
||||
$response = $client->executeRaw(['SET', 'foo', 'bar']);
|
||||
```
|
||||
|
||||
|
||||
### Script commands ###
|
||||
|
||||
While it is possible to leverage [Lua scripting](http://redis.io/commands/eval) on Redis 2.6+ using
|
||||
directly [`EVAL`](http://redis.io/commands/eval) and [`EVALSHA`](http://redis.io/commands/evalsha),
|
||||
Predis offers script commands as an higher level abstraction built upon them to make things simple.
|
||||
Script commands can be registered in the command factory used by the client and are accessible as if
|
||||
they were plain Redis commands, but they define Lua scripts that get transmitted to the server for
|
||||
remote execution. Internally they use [`EVALSHA`](http://redis.io/commands/evalsha) by default and
|
||||
identify a script by its SHA1 hash to save bandwidth, but [`EVAL`](http://redis.io/commands/eval)
|
||||
is used as a fall back when needed:
|
||||
|
||||
```php
|
||||
// Define a new script command by extending Predis\Command\ScriptCommand:
|
||||
class ListPushRandomValue extends Predis\Command\ScriptCommand
|
||||
{
|
||||
public function getKeysCount()
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
|
||||
public function getScript()
|
||||
{
|
||||
return <<<LUA
|
||||
math.randomseed(ARGV[1])
|
||||
local rnd = tostring(math.random())
|
||||
redis.call('lpush', KEYS[1], rnd)
|
||||
return rnd
|
||||
LUA;
|
||||
}
|
||||
}
|
||||
|
||||
// Inject the script command in the current command factory:
|
||||
$client = new Predis\Client($parameters, [
|
||||
'commands' => [
|
||||
'lpushrand' => 'ListPushRandomValue',
|
||||
],
|
||||
]);
|
||||
|
||||
$response = $client->lpushrand('random_values', $seed = mt_rand());
|
||||
```
|
||||
|
||||
|
||||
### Customizable connection backends ###
|
||||
|
||||
Predis can use different connection backends to connect to Redis. The builtin Relay integration
|
||||
leverages the [Relay](https://github.com/cachewerk/relay) extension for PHP for major performance
|
||||
gains, by caching a partial replica of the Redis dataset in PHP shared runtime memory.
|
||||
|
||||
```php
|
||||
$client = new Predis\Client('tcp://127.0.0.1', [
|
||||
'connections' => 'relay',
|
||||
]);
|
||||
```
|
||||
|
||||
Developers can create their own connection classes to support whole new network backends, extend
|
||||
existing classes or provide completely different implementations. Connection classes must implement
|
||||
`Predis\Connection\NodeConnectionInterface` or extend `Predis\Connection\AbstractConnection`:
|
||||
|
||||
```php
|
||||
class MyConnectionClass implements Predis\Connection\NodeConnectionInterface
|
||||
{
|
||||
// Implementation goes here...
|
||||
}
|
||||
|
||||
// Use MyConnectionClass to handle connections for the `tcp` scheme:
|
||||
$client = new Predis\Client('tcp://127.0.0.1', [
|
||||
'connections' => ['tcp' => 'MyConnectionClass'],
|
||||
]);
|
||||
```
|
||||
|
||||
For a more in-depth insight on how to create new connection backends you can refer to the actual
|
||||
implementation of the standard connection classes available in the `Predis\Connection` namespace.
|
||||
|
||||
### Retry exceptions
|
||||
|
||||
You can enable automatic retry that is disabled by default, to be able to reduce the amount of
|
||||
false-positives in case of network issues. By default, we're retrying on any connection,
|
||||
timeout or socket initialization exception, but you can update the list of retry
|
||||
exceptions. For now `EqualBackoff` and `ExponentialBackoff` strategies are available,
|
||||
but you may provide your custom one. Retry may be configured with any type of communication
|
||||
(standalone node, cluster, pipeline, transaction, replication). Here's an example of
|
||||
configuration:
|
||||
|
||||
```php
|
||||
// Standalone client
|
||||
$client = new Predis\Client([
|
||||
'retry' => new \Predis\Retry\Retry(
|
||||
new \Predis\Retry\Strategy\ExponentialBackoff(1000, 10000), // Base and cap configuration in microseconds
|
||||
3 // Number of retries
|
||||
),
|
||||
]);
|
||||
|
||||
// Cluster configuration
|
||||
$options = [
|
||||
'parameters' => [
|
||||
'retry' => new \Predis\Retry\Retry(new \Predis\Retry\Strategy\ExponentialBackoff(1000, 10000), 3),
|
||||
],
|
||||
];
|
||||
|
||||
$client = new Predis\Client(['tcp://host:port', 'tcp://host:port', 'tcp://host:port'], $options);
|
||||
|
||||
$retry = new \Predis\Retry\Retry(
|
||||
new \Predis\Retry\Strategy\ExponentialBackoff(1000, 10000),
|
||||
3
|
||||
);
|
||||
|
||||
// Update a list of exceptions to catch
|
||||
$retry->updateCatchableExceptions([Exception::class]);
|
||||
```
|
||||
|
||||
## RESP3 ##
|
||||
|
||||
### Connection ###
|
||||
To establish the connection using the [RESP3](https://github.com/redis/redis-specifications/blob/master/protocol/RESP3.md) protocol, you need to set parameter `protocol => 3`. The default protocol is RESP2.
|
||||
|
||||
You can pass parameter as configuration option in array or as a query parameter in `redis_url`
|
||||
|
||||
```php
|
||||
// Configuration option
|
||||
$client = new \Predis\Client(['protocol' => 3]);
|
||||
|
||||
// Redis URL
|
||||
$client = new \Predis\Client('redis://localhost:6379?protocol=3');
|
||||
|
||||
// ["proto" => "3"]
|
||||
$client->executeRaw(['HELLO']);
|
||||
```
|
||||
|
||||
### Command responses ###
|
||||
RESP3 protocol introduce a variety of new [response types](https://github.com/redis/redis-specifications/blob/master/protocol/RESP3.md#resp3-types),
|
||||
so on the client-side we have more explicit understanding on data types we retrieve from server. Here's some examples to show the difference
|
||||
between RESP2 and RESP3 responses.
|
||||
|
||||
#### Float responses ####
|
||||
``` php
|
||||
// RESP2 connection
|
||||
$client = new \Predis\Client();
|
||||
|
||||
$client->geoadd('my_geo', 11.111, 22.222, 'member1');
|
||||
|
||||
// [[0 => string(20) "11.11099988222122192", 1 => string(20) "22.22200052541037252"]]
|
||||
// RESP2 returns float values as simple strings.
|
||||
var_dump($client->geopos('my_geo', ['member1']));
|
||||
|
||||
// RESP3 connection
|
||||
$client = new \Predis\Client(['protocol' => 3]);
|
||||
|
||||
// [[0 => float(11.110999882221222), 1 => float(22.222000525410373)]]
|
||||
// RESP3 introduces new double type, that corresponds to PHP float.
|
||||
var_dump($client->geopos('my_geo', ['member1']));
|
||||
```
|
||||
|
||||
#### Aggregate types ####
|
||||
In RESP3 new aggregate type [Map](https://github.com/redis/redis-specifications/blob/master/protocol/RESP3.md#map-type)
|
||||
was introduced, that represents the sequence of field-value pairs. So it simplifies parsing, since we don't need to specify
|
||||
parsing strategy per command (RESP2) and instead relies on the type defined by protocol (RESP3).
|
||||
|
||||
In most cases RESP2 responses shouldn't differ from RESP3, since we added additional parsing for those
|
||||
command that return field-value pairs. However, since RESP2 requires additional parsing, it could be that some commands
|
||||
had lack of it and return unhandled responses. In this case there would be difference like this:
|
||||
|
||||
```php
|
||||
$client = new \Predis\Client();
|
||||
|
||||
// RESP2: ['field', 'value]
|
||||
$client->commandThatReturnsFieldValuePair('key');
|
||||
|
||||
$client = new \Predis\Client(['protocol' => 3]);
|
||||
|
||||
// RESP3: ['field' => 'value]
|
||||
$client->commandThatReturnsFieldValuePair('key');
|
||||
```
|
||||
|
||||
Feel free to open PR or GitHub issue if you face those protocol mismatching.
|
||||
|
||||
### Push notifications ###
|
||||
RESP3 introduce a concept of [push connection](https://github.com/redis/redis-specifications/blob/master/protocol/RESP3.md#push-type),
|
||||
is the one where server could send asynchronous data to client which was not explicitly requested. Predis 3.0 provides
|
||||
an API to establish this kind of connection as separate blocking process (worker) and invoke callbacks depends on push
|
||||
notification message type.
|
||||
|
||||
#### Consumer ####
|
||||
First of all, you need to set up a consumer connection and provide an optional callback that will be executed before
|
||||
event loop will be started. It allows you to subscribe on channels, enable keys invalidations tracking or enable monitor
|
||||
connection, any Redis command to let server know that you want to receive push notification within this connection.
|
||||
|
||||
```php
|
||||
// Make sure that RESP3 protocol enabled and read_write_timeout set 0,
|
||||
// so connection won't be killed by timeout.
|
||||
$client = new Predis\Client(['read_write_timeout' => 0, 'protocol' => 3]);
|
||||
|
||||
// Create push notifications consumer.
|
||||
// Provides callback where current consumer subscribes to few channels before
|
||||
// enter the loop.
|
||||
$push = $client->push(static function (ClientInterface $client) {
|
||||
$response = $client->subscribe('channel', 'control');
|
||||
$status = ($response[2] === 1) ? 'OK' : 'FAILED';
|
||||
echo "Channel subscription status: {$status}\n";
|
||||
});
|
||||
```
|
||||
|
||||
#### Dispatcher loop ####
|
||||
Dispatcher object allows you to attach a callback to given push notification type and run the actual worker process that
|
||||
listen for incoming push notifications. To be able to stop blocking process in runtime you can specify a condition and
|
||||
call `$dispatcher->stop()` method from given callback. In this example we're waiting for specific message `terminate`
|
||||
within `control` channel that we subscribed to before entering the loop.
|
||||
|
||||
```php
|
||||
// Storage for incoming notifications.
|
||||
$messages = [];
|
||||
|
||||
// Create dispatcher for push notifications.
|
||||
$dispatcher = new Predis\Consumer\Push\DispatcherLoop($push);
|
||||
|
||||
$dispatcher->attachCallback(
|
||||
PushResponseInterface::MESSAGE_DATA_TYPE,
|
||||
static function (array $payload, DispatcherLoopInterface $dispatcher) {
|
||||
global $messages;
|
||||
[$channel, $message] = $payload;
|
||||
|
||||
if ($channel === 'control' && $message === 'terminate') {
|
||||
echo "Terminating notification consumer.\n";
|
||||
$dispatcher->stop();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$messages[] = $message;
|
||||
echo "Received message: {$message}\n";
|
||||
}
|
||||
);
|
||||
|
||||
// Run consumer loop with attached callbacks.
|
||||
$dispatcher->run();
|
||||
|
||||
// Count all messages that were received during consumer loop.
|
||||
$messagesCount = count($messages);
|
||||
echo "We received: {$messagesCount} messages\n";
|
||||
```
|
||||
|
||||
This example shows a simple script to count all incoming messages from push notifications that we receive from
|
||||
subscribed channels until stop condition will be met. Examples available in `examples/` folder.
|
||||
|
||||
### Sharded pub/sub ###
|
||||
From Redis 7.0, sharded Pub/Sub is introduced in which shard channels are assigned to slots by the same algorithm used
|
||||
to assign keys to slots.
|
||||
|
||||
Predis 3.0 provides an API that allows to use pub/sub for Cluster connections using sharded pub/sub from Redis.
|
||||
You don't need to specify any additional configuration to enable sharded pub/sub, it will be automatically enabled if
|
||||
Cluster connection is using.
|
||||
|
||||
Implementation looks pretty much the same as Push notification, so you need to set up consumer
|
||||
and run it over Dispatcher loop object. All examples available in `examples/` folder.
|
||||
## Development ##
|
||||
|
||||
|
||||
### Reporting bugs and contributing code ###
|
||||
|
||||
Contributions to Predis are highly appreciated either in the form of pull requests for new features,
|
||||
bug fixes, or just bug reports. We only ask you to adhere to issue and pull request templates.
|
||||
|
||||
|
||||
### Test suite ###
|
||||
|
||||
__ATTENTION__: Do not ever run the test suite shipped with Predis against instances of Redis running
|
||||
in production environments or containing data you are interested in!
|
||||
|
||||
Predis has a comprehensive test suite covering every aspect of the library and that can optionally
|
||||
perform integration tests against a running instance of Redis (required >= 2.4.0 in order to verify
|
||||
the correct behavior of the implementation of each command. Integration tests for unsupported Redis
|
||||
commands are automatically skipped. If you do not have Redis up and running, integration tests can
|
||||
be disabled. See [the tests README](tests/README.md) for more details about testing this library.
|
||||
|
||||
Predis uses GitHub Actions for continuous integration and the history for past and current builds can be
|
||||
found [on its actions page](https://github.com/predis/predis/actions).
|
||||
|
||||
### License ###
|
||||
|
||||
The code for Predis is distributed under the terms of the MIT license (see [LICENSE](LICENSE)).
|
||||
|
||||
[ico-license]: https://img.shields.io/github/license/predis/predis.svg?style=flat-square
|
||||
[ico-version-stable]: https://img.shields.io/github/v/tag/predis/predis?label=stable&style=flat-square
|
||||
[ico-version-dev]: https://img.shields.io/github/v/tag/predis/predis?include_prereleases&label=pre-release&style=flat-square
|
||||
[ico-downloads-monthly]: https://img.shields.io/packagist/dm/predis/predis.svg?style=flat-square
|
||||
[ico-build]: https://img.shields.io/github/actions/workflow/status/predis/predis/tests.yml?branch=main&style=flat-square
|
||||
[ico-coverage]: https://img.shields.io/coverallsCoverage/github/predis/predis?style=flat-square
|
||||
|
||||
[link-releases]: https://github.com/predis/predis/releases
|
||||
[link-actions]: https://github.com/predis/predis/actions
|
||||
[link-downloads]: https://packagist.org/packages/predis/predis/stats
|
||||
[link-coverage]: https://coveralls.io/github/predis/predis
|
||||
@@ -0,0 +1,12 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Predis package.
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
require __DIR__.'/src/Autoloader.php';
|
||||
|
||||
Predis\Autoloader::register();
|
||||
@@ -0,0 +1,59 @@
|
||||
{
|
||||
"name": "predis/predis",
|
||||
"type": "library",
|
||||
"description": "A flexible and feature-complete Redis/Valkey client for PHP.",
|
||||
"keywords": ["nosql", "redis", "predis"],
|
||||
"homepage": "http://github.com/predis/predis",
|
||||
"license": "MIT",
|
||||
"support": {
|
||||
"issues": "https://github.com/predis/predis/issues"
|
||||
},
|
||||
"authors": [
|
||||
{
|
||||
"name": "Till Krüss",
|
||||
"homepage": "https://till.im",
|
||||
"role": "Maintainer"
|
||||
}
|
||||
],
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/tillkruss"
|
||||
}
|
||||
],
|
||||
"require": {
|
||||
"php": "^7.2 || ^8.0",
|
||||
"psr/http-message": "^1.0|^2.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"friendsofphp/php-cs-fixer": "^3.3",
|
||||
"phpstan/phpstan": "^1.9",
|
||||
"phpunit/phpunit": "^8.0 || ~9.4.4",
|
||||
"phpunit/phpcov": "^6.0 || ^8.0"
|
||||
},
|
||||
"suggest": {
|
||||
"ext-relay": "Faster connection with in-memory caching (>=0.6.2)"
|
||||
},
|
||||
"scripts": {
|
||||
"phpstan": "phpstan analyse",
|
||||
"style": "php-cs-fixer fix --diff --dry-run",
|
||||
"style:fix": "php-cs-fixer fix"
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Predis\\": "src/"
|
||||
}
|
||||
},
|
||||
"config": {
|
||||
"sort-packages": true,
|
||||
"preferred-install": "dist",
|
||||
"audit": {
|
||||
"ignore": [
|
||||
"GHSA-vvj3-c3rp-c85p",
|
||||
"PKSA-z3gr-8qht-p93v"
|
||||
]
|
||||
}
|
||||
},
|
||||
"minimum-stability": "dev",
|
||||
"prefer-stable": true
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Predis package.
|
||||
*
|
||||
* (c) 2009-2020 Daniele Alessandri
|
||||
* (c) 2021-2025 Till Krüss
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Predis;
|
||||
|
||||
/**
|
||||
* Implements a lightweight PSR-0 compliant autoloader for Predis.
|
||||
*
|
||||
* @author Eric Naeseth <eric@thumbtack.com>
|
||||
* @author Daniele Alessandri <suppakilla@gmail.com>
|
||||
* @codeCoverageIgnore
|
||||
*/
|
||||
class Autoloader
|
||||
{
|
||||
private $directory;
|
||||
private $prefix;
|
||||
private $prefixLength;
|
||||
|
||||
/**
|
||||
* @param string $baseDirectory Base directory where the source files are located.
|
||||
*/
|
||||
public function __construct($baseDirectory = __DIR__)
|
||||
{
|
||||
$this->directory = $baseDirectory;
|
||||
$this->prefix = __NAMESPACE__ . '\\';
|
||||
$this->prefixLength = strlen($this->prefix);
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers the autoloader class with the PHP SPL autoloader.
|
||||
*
|
||||
* @param bool $prepend Prepend the autoloader on the stack instead of appending it.
|
||||
*/
|
||||
public static function register($prepend = false)
|
||||
{
|
||||
spl_autoload_register([new self(), 'autoload'], true, $prepend);
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads a class from a file using its fully qualified name.
|
||||
*
|
||||
* @param string $className Fully qualified name of a class.
|
||||
*/
|
||||
public function autoload($className)
|
||||
{
|
||||
if (0 === strpos($className, $this->prefix)) {
|
||||
$parts = explode('\\', substr($className, $this->prefixLength));
|
||||
$filepath = $this->directory . DIRECTORY_SEPARATOR . implode(DIRECTORY_SEPARATOR, $parts) . '.php';
|
||||
|
||||
if (is_file($filepath)) {
|
||||
require $filepath;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,642 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Predis package.
|
||||
*
|
||||
* (c) 2009-2020 Daniele Alessandri
|
||||
* (c) 2021-2025 Till Krüss
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Predis;
|
||||
|
||||
use ArrayIterator;
|
||||
use InvalidArgumentException;
|
||||
use IteratorAggregate;
|
||||
use Predis\Command\CommandInterface;
|
||||
use Predis\Command\Container\ContainerFactory;
|
||||
use Predis\Command\Container\ContainerInterface;
|
||||
use Predis\Command\RawCommand;
|
||||
use Predis\Command\ScriptCommand;
|
||||
use Predis\Configuration\Options;
|
||||
use Predis\Configuration\OptionsInterface;
|
||||
use Predis\Connection\AggregateConnectionInterface;
|
||||
use Predis\Connection\ConnectionInterface;
|
||||
use Predis\Connection\Parameters;
|
||||
use Predis\Connection\ParametersInterface;
|
||||
use Predis\Connection\RelayConnection;
|
||||
use Predis\Consumer\PubSub\Consumer as PubSubConsumer;
|
||||
use Predis\Consumer\PubSub\RelayConsumer as RelayPubSubConsumer;
|
||||
use Predis\Consumer\Push\Consumer as PushConsumer;
|
||||
use Predis\Monitor\Consumer as MonitorConsumer;
|
||||
use Predis\Pipeline\Atomic;
|
||||
use Predis\Pipeline\FireAndForget;
|
||||
use Predis\Pipeline\Pipeline;
|
||||
use Predis\Pipeline\RelayAtomic;
|
||||
use Predis\Pipeline\RelayPipeline;
|
||||
use Predis\Response\ErrorInterface as ErrorResponseInterface;
|
||||
use Predis\Response\ResponseInterface;
|
||||
use Predis\Response\ServerException;
|
||||
use Predis\Transaction\MultiExec as MultiExecTransaction;
|
||||
use ReturnTypeWillChange;
|
||||
use RuntimeException;
|
||||
use Throwable;
|
||||
use Traversable;
|
||||
|
||||
/**
|
||||
* Client class used for connecting and executing commands on Redis.
|
||||
*
|
||||
* This is the main high-level abstraction of Predis upon which various other
|
||||
* abstractions are built. Internally it aggregates various other classes each
|
||||
* one with its own responsibility and scope.
|
||||
*
|
||||
* @template-implements \IteratorAggregate<string, static>
|
||||
*/
|
||||
class Client implements ClientInterface, IteratorAggregate
|
||||
{
|
||||
public const VERSION = '3.4.0';
|
||||
|
||||
/** @var OptionsInterface */
|
||||
private $options;
|
||||
|
||||
/** @var ConnectionInterface */
|
||||
private $connection;
|
||||
|
||||
/** @var Command\FactoryInterface */
|
||||
private $commands;
|
||||
|
||||
/**
|
||||
* @param mixed $parameters Connection parameters for one or more servers.
|
||||
* @param mixed $options Options to configure some behaviours of the client.
|
||||
*/
|
||||
public function __construct($parameters = null, $options = null)
|
||||
{
|
||||
$this->options = static::createOptions($options ?? new Options());
|
||||
$this->connection = static::createConnection($this->options, $parameters ?? new Parameters());
|
||||
$this->commands = $this->options->commands;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new set of client options for the client.
|
||||
*
|
||||
* @param array|OptionsInterface $options Set of client options
|
||||
*
|
||||
* @return OptionsInterface
|
||||
* @throws InvalidArgumentException
|
||||
*/
|
||||
protected static function createOptions($options)
|
||||
{
|
||||
if (is_array($options)) {
|
||||
return new Options($options);
|
||||
} elseif ($options instanceof OptionsInterface) {
|
||||
return $options;
|
||||
}
|
||||
throw new InvalidArgumentException('Invalid type for client options');
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates single or aggregate connections from supplied arguments.
|
||||
*
|
||||
* This method accepts the following types to create a connection instance:
|
||||
*
|
||||
* - Array (dictionary: single connection, indexed: aggregate connections)
|
||||
* - String (URI for a single connection)
|
||||
* - Callable (connection initializer callback)
|
||||
* - Instance of Predis\Connection\ParametersInterface (used as-is)
|
||||
* - Instance of Predis\Connection\ConnectionInterface (returned as-is)
|
||||
*
|
||||
* When a callable is passed, it receives the original set of client options
|
||||
* and must return an instance of Predis\Connection\ConnectionInterface.
|
||||
*
|
||||
* Connections are created using the connection factory (in case of single
|
||||
* connections) or a specialized aggregate connection initializer (in case
|
||||
* of cluster and replication) retrieved from the supplied client options.
|
||||
*
|
||||
* @param OptionsInterface $options Client options container
|
||||
* @param mixed $parameters Connection parameters
|
||||
*
|
||||
* @return ConnectionInterface
|
||||
* @throws InvalidArgumentException
|
||||
*/
|
||||
protected static function createConnection(OptionsInterface $options, $parameters)
|
||||
{
|
||||
if ($parameters instanceof ConnectionInterface) {
|
||||
return $parameters;
|
||||
}
|
||||
|
||||
if ($parameters instanceof ParametersInterface || is_string($parameters)) {
|
||||
return $options->connections->create($parameters);
|
||||
}
|
||||
|
||||
if (is_array($parameters)) {
|
||||
if (!isset($parameters[0])) {
|
||||
return $options->connections->create($parameters);
|
||||
} elseif ($options->defined('cluster') && $initializer = $options->cluster) {
|
||||
return $initializer($parameters, true);
|
||||
} elseif ($options->defined('replication') && $initializer = $options->replication) {
|
||||
return $initializer($parameters, true);
|
||||
} elseif ($options->defined('aggregate') && $initializer = $options->aggregate) {
|
||||
return $initializer($parameters, false);
|
||||
}
|
||||
throw new InvalidArgumentException(
|
||||
'Array of connection parameters requires `cluster`, `replication` or `aggregate` client option'
|
||||
);
|
||||
}
|
||||
|
||||
if (is_callable($parameters)) {
|
||||
$connection = call_user_func($parameters, $options);
|
||||
|
||||
if (!$connection instanceof ConnectionInterface) {
|
||||
throw new InvalidArgumentException('Callable parameters must return a valid connection');
|
||||
}
|
||||
|
||||
return $connection;
|
||||
}
|
||||
|
||||
throw new InvalidArgumentException('Invalid type for connection parameters');
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getCommandFactory()
|
||||
{
|
||||
return $this->commands;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getOptions()
|
||||
{
|
||||
return $this->options;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new client using a specific underlying connection.
|
||||
*
|
||||
* This method allows to create a new client instance by picking a specific
|
||||
* connection out of an aggregate one, with the same options of the original
|
||||
* client instance.
|
||||
*
|
||||
* The specified selector defines which logic to use to look for a suitable
|
||||
* connection by the specified value. Supported selectors are:
|
||||
*
|
||||
* - `id`
|
||||
* - `key`
|
||||
* - `slot`
|
||||
* - `command`
|
||||
* - `alias`
|
||||
* - `role`
|
||||
*
|
||||
* Internally the client relies on duck-typing and follows this convention:
|
||||
*
|
||||
* $selector string => getConnectionBy$selector($value) method
|
||||
*
|
||||
* This means that support for specific selectors may vary depending on the
|
||||
* actual logic implemented by connection classes and there is no interface
|
||||
* binding a connection class to implement any of these.
|
||||
*
|
||||
* @param string $selector Type of selector.
|
||||
* @param mixed $value Value to be used by the selector.
|
||||
*
|
||||
* @return ClientInterface
|
||||
*/
|
||||
public function getClientBy($selector, $value)
|
||||
{
|
||||
$selector = strtolower($selector);
|
||||
|
||||
if (!in_array($selector, ['id', 'key', 'slot', 'role', 'alias', 'command'])) {
|
||||
throw new InvalidArgumentException("Invalid selector type: `$selector`");
|
||||
}
|
||||
|
||||
if (!method_exists($this->connection, $method = "getConnectionBy$selector")) {
|
||||
$class = get_class($this->connection);
|
||||
throw new InvalidArgumentException("Selecting connection by $selector is not supported by $class");
|
||||
}
|
||||
|
||||
if (!$connection = $this->connection->$method($value)) {
|
||||
throw new InvalidArgumentException("Cannot find a connection by $selector matching `$value`");
|
||||
}
|
||||
|
||||
return new static($connection, $this->getOptions());
|
||||
}
|
||||
|
||||
/**
|
||||
* Opens the underlying connection and connects to the server.
|
||||
*/
|
||||
public function connect()
|
||||
{
|
||||
$this->connection->connect();
|
||||
}
|
||||
|
||||
/**
|
||||
* Closes the underlying connection and disconnects from the server.
|
||||
*/
|
||||
public function disconnect()
|
||||
{
|
||||
$this->connection->disconnect();
|
||||
}
|
||||
|
||||
/**
|
||||
* Closes the underlying connection and disconnects from the server.
|
||||
*
|
||||
* This is the same as `Client::disconnect()` as it does not actually send
|
||||
* the `QUIT` command to Redis, but simply closes the connection.
|
||||
*/
|
||||
public function quit()
|
||||
{
|
||||
$this->disconnect();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the current state of the underlying connection.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isConnected()
|
||||
{
|
||||
return $this->connection->isConnected();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getConnection()
|
||||
{
|
||||
return $this->connection;
|
||||
}
|
||||
|
||||
/**
|
||||
* Applies the configured serializer and compression to given value.
|
||||
*
|
||||
* @param mixed $value
|
||||
* @return string
|
||||
*/
|
||||
public function pack($value)
|
||||
{
|
||||
return $this->connection instanceof RelayConnection
|
||||
? $this->connection->pack($value)
|
||||
: $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Deserializes and decompresses to given value.
|
||||
*
|
||||
* @param mixed $value
|
||||
* @return string
|
||||
*/
|
||||
public function unpack($value)
|
||||
{
|
||||
return $this->connection instanceof RelayConnection
|
||||
? $this->connection->unpack($value)
|
||||
: $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes a command without filtering its arguments, parsing the response,
|
||||
* applying any prefix to keys or throwing exceptions on Redis errors even
|
||||
* regardless of client options.
|
||||
*
|
||||
* It is possible to identify Redis error responses from normal responses
|
||||
* using the second optional argument which is populated by reference.
|
||||
*
|
||||
* @param array $arguments Command arguments as defined by the command signature.
|
||||
* @param bool $error Set to TRUE when Redis returned an error response.
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function executeRaw(array $arguments, &$error = null)
|
||||
{
|
||||
$error = false;
|
||||
$commandID = array_shift($arguments);
|
||||
|
||||
$response = $this->connection->executeCommand(
|
||||
new RawCommand($commandID, $arguments)
|
||||
);
|
||||
|
||||
if ($response instanceof ResponseInterface) {
|
||||
if ($response instanceof ErrorResponseInterface) {
|
||||
$error = true;
|
||||
}
|
||||
|
||||
return (string) $response;
|
||||
}
|
||||
|
||||
return $response;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function __call($commandID, $arguments)
|
||||
{
|
||||
return $this->executeCommand(
|
||||
$this->createCommand($commandID, $arguments)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function createCommand($commandID, $arguments = [])
|
||||
{
|
||||
return $this->commands->create($commandID, $arguments);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $name
|
||||
* @return ContainerInterface
|
||||
*/
|
||||
public function __get(string $name)
|
||||
{
|
||||
return ContainerFactory::create($this, $name);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $name
|
||||
* @param mixed $value
|
||||
* @return mixed
|
||||
*/
|
||||
public function __set(string $name, $value)
|
||||
{
|
||||
throw new RuntimeException('Not allowed');
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $name
|
||||
* @return mixed
|
||||
*/
|
||||
public function __isset(string $name)
|
||||
{
|
||||
throw new RuntimeException('Not allowed');
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
* @throws Throwable
|
||||
*/
|
||||
public function executeCommand(CommandInterface $command)
|
||||
{
|
||||
$parameters = $this->connection->getParameters();
|
||||
|
||||
if ($this->connection instanceof AggregateConnectionInterface || $this->connection instanceof RelayConnection) {
|
||||
$response = $this->connection->executeCommand($command);
|
||||
} else {
|
||||
$response = $parameters->retry->callWithRetry(
|
||||
function () use ($command) {
|
||||
return $this->connection->executeCommand($command);
|
||||
},
|
||||
function () {
|
||||
$this->connection->disconnect();
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
if ($response instanceof ResponseInterface) {
|
||||
if ($response instanceof ErrorResponseInterface) {
|
||||
$response = $this->onErrorResponse($command, $response);
|
||||
}
|
||||
|
||||
return $response;
|
||||
}
|
||||
|
||||
if ($parameters->protocol === 2) {
|
||||
return $command->parseResponse($response);
|
||||
}
|
||||
|
||||
return $command->parseResp3Response($response);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles -ERR responses returned by Redis.
|
||||
*
|
||||
* @param CommandInterface $command Redis command that generated the error.
|
||||
* @param ErrorResponseInterface $response Instance of the error response.
|
||||
*
|
||||
* @return mixed
|
||||
* @throws ServerException
|
||||
*/
|
||||
protected function onErrorResponse(CommandInterface $command, ErrorResponseInterface $response)
|
||||
{
|
||||
if ($command instanceof ScriptCommand && $response->getErrorType() === 'NOSCRIPT') {
|
||||
$response = $this->executeCommand($command->getEvalCommand());
|
||||
|
||||
if (!$response instanceof ResponseInterface) {
|
||||
$response = $command->parseResponse($response);
|
||||
}
|
||||
|
||||
return $response;
|
||||
}
|
||||
|
||||
if ($this->options->exceptions) {
|
||||
throw new ServerException($response->getMessage());
|
||||
}
|
||||
|
||||
return $response;
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes the specified initializer method on `$this` by adjusting the
|
||||
* actual invocation depending on the arity (0, 1 or 2 arguments). This is
|
||||
* simply an utility method to create Redis contexts instances since they
|
||||
* follow a common initialization path.
|
||||
*
|
||||
* @param string $initializer Method name.
|
||||
* @param array $argv Arguments for the method.
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
private function sharedContextFactory($initializer, $argv = null)
|
||||
{
|
||||
switch (count($argv)) {
|
||||
case 0:
|
||||
return $this->$initializer();
|
||||
|
||||
case 1:
|
||||
return is_array($argv[0])
|
||||
? $this->$initializer($argv[0])
|
||||
: $this->$initializer(null, $argv[0]);
|
||||
|
||||
case 2:
|
||||
[$arg0, $arg1] = $argv;
|
||||
|
||||
return $this->$initializer($arg0, $arg1);
|
||||
|
||||
default:
|
||||
return $this->$initializer($this, $argv);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new pipeline context and returns it, or returns the results of
|
||||
* a pipeline executed inside the optionally provided callable object.
|
||||
*
|
||||
* @param mixed ...$arguments Array of options, a callable for execution, or both.
|
||||
*
|
||||
* @return Pipeline|array
|
||||
*/
|
||||
public function pipeline(...$arguments)
|
||||
{
|
||||
return $this->sharedContextFactory('createPipeline', func_get_args());
|
||||
}
|
||||
|
||||
/**
|
||||
* Actual pipeline context initializer method.
|
||||
*
|
||||
* @param array|null $options Options for the context.
|
||||
* @param mixed $callable Optional callable used to execute the context.
|
||||
*
|
||||
* @return Pipeline|array
|
||||
*/
|
||||
protected function createPipeline(?array $options = null, $callable = null)
|
||||
{
|
||||
if (isset($options['atomic']) && $options['atomic']) {
|
||||
$class = Atomic::class;
|
||||
} elseif (isset($options['fire-and-forget']) && $options['fire-and-forget']) {
|
||||
$class = FireAndForget::class;
|
||||
} else {
|
||||
$class = Pipeline::class;
|
||||
}
|
||||
|
||||
if ($this->connection instanceof RelayConnection) {
|
||||
if (isset($options['atomic']) && $options['atomic']) {
|
||||
$class = RelayAtomic::class;
|
||||
} elseif (isset($options['fire-and-forget']) && $options['fire-and-forget']) {
|
||||
throw new NotSupportedException('The "relay" extension does not support fire-and-forget pipelines.');
|
||||
} else {
|
||||
$class = RelayPipeline::class;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* @var ClientContextInterface
|
||||
*/
|
||||
$pipeline = new $class($this);
|
||||
|
||||
if (isset($callable)) {
|
||||
return $pipeline->execute($callable);
|
||||
}
|
||||
|
||||
return $pipeline;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new transaction context and returns it, or returns the results
|
||||
* of a transaction executed inside the optionally provided callable object.
|
||||
*
|
||||
* @param mixed ...$arguments Array of options, a callable for execution, or both.
|
||||
*
|
||||
* @return MultiExecTransaction|array
|
||||
*/
|
||||
public function transaction(...$arguments)
|
||||
{
|
||||
return $this->sharedContextFactory('createTransaction', func_get_args());
|
||||
}
|
||||
|
||||
/**
|
||||
* Actual transaction context initializer method.
|
||||
*
|
||||
* @param array|null $options Options for the context.
|
||||
* @param mixed $callable Optional callable used to execute the context.
|
||||
*
|
||||
* @return MultiExecTransaction|array
|
||||
*/
|
||||
protected function createTransaction(?array $options = null, $callable = null)
|
||||
{
|
||||
$transaction = new MultiExecTransaction($this, $options);
|
||||
|
||||
if (isset($callable)) {
|
||||
return $transaction->execute($callable);
|
||||
}
|
||||
|
||||
return $transaction;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new publish/subscribe context and returns it, or starts its loop
|
||||
* inside the optionally provided callable object.
|
||||
*
|
||||
* @param mixed ...$arguments Array of options, a callable for execution, or both.
|
||||
*
|
||||
* @return PubSubConsumer|null
|
||||
*/
|
||||
public function pubSubLoop(...$arguments)
|
||||
{
|
||||
return $this->sharedContextFactory('createPubSub', func_get_args());
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates new push notifications consumer.
|
||||
*
|
||||
* @param callable|null $preLoopCallback Callback that should be called on client before enter a loop.
|
||||
* @return PushConsumer
|
||||
*/
|
||||
public function push(?callable $preLoopCallback = null): PushConsumer
|
||||
{
|
||||
return new PushConsumer($this, $preLoopCallback);
|
||||
}
|
||||
|
||||
/**
|
||||
* Actual publish/subscribe context initializer method.
|
||||
*
|
||||
* @param array|null $options Options for the context.
|
||||
* @param mixed $callable Optional callable used to execute the context.
|
||||
*
|
||||
* @return PubSubConsumer|null
|
||||
*/
|
||||
protected function createPubSub(?array $options = null, $callable = null)
|
||||
{
|
||||
if ($this->connection instanceof RelayConnection) {
|
||||
$pubsub = new RelayPubSubConsumer($this, $options);
|
||||
} else {
|
||||
$pubsub = new PubSubConsumer($this, $options);
|
||||
}
|
||||
|
||||
if (!isset($callable)) {
|
||||
return $pubsub;
|
||||
}
|
||||
|
||||
foreach ($pubsub as $message) {
|
||||
if (call_user_func($callable, $pubsub, $message) === false) {
|
||||
$pubsub->stop();
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new monitor consumer and returns it.
|
||||
*
|
||||
* @return MonitorConsumer
|
||||
*/
|
||||
public function monitor()
|
||||
{
|
||||
return new MonitorConsumer($this);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Traversable<string, static>
|
||||
*/
|
||||
#[ReturnTypeWillChange]
|
||||
public function getIterator()
|
||||
{
|
||||
$clients = [];
|
||||
$connection = $this->getConnection();
|
||||
|
||||
if (!$connection instanceof Traversable) {
|
||||
return new ArrayIterator([
|
||||
(string) $connection => new static($connection, $this->getOptions()),
|
||||
]);
|
||||
}
|
||||
|
||||
foreach ($connection as $node) {
|
||||
$clients[(string) $node] = new static($node, $this->getOptions());
|
||||
}
|
||||
|
||||
return new ArrayIterator($clients);
|
||||
}
|
||||
}
|
||||
+42
@@ -0,0 +1,42 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Predis package.
|
||||
*
|
||||
* (c) 2009-2020 Daniele Alessandri
|
||||
* (c) 2021-2025 Till Krüss
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Predis;
|
||||
|
||||
class ClientConfiguration
|
||||
{
|
||||
/**
|
||||
* @var array{modules: array}|string[][]
|
||||
*/
|
||||
private static $config = [
|
||||
'modules' => [
|
||||
['name' => 'Json', 'commandPrefix' => 'JSON'],
|
||||
['name' => 'BloomFilter', 'commandPrefix' => 'BF'],
|
||||
['name' => 'CuckooFilter', 'commandPrefix' => 'CF'],
|
||||
['name' => 'CountMinSketch', 'commandPrefix' => 'CMS'],
|
||||
['name' => 'TDigest', 'commandPrefix' => 'TDIGEST'],
|
||||
['name' => 'TopK', 'commandPrefix' => 'TOPK'],
|
||||
['name' => 'Search', 'commandPrefix' => 'FT'],
|
||||
['name' => 'TimeSeries', 'commandPrefix' => 'TS'],
|
||||
],
|
||||
];
|
||||
|
||||
/**
|
||||
* Returns available modules with configuration.
|
||||
*
|
||||
* @return array|string[][]
|
||||
*/
|
||||
public static function getModules(): array
|
||||
{
|
||||
return self::$config['modules'];
|
||||
}
|
||||
}
|
||||
+437
@@ -0,0 +1,437 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Predis package.
|
||||
*
|
||||
* (c) 2009-2020 Daniele Alessandri
|
||||
* (c) 2021-2025 Till Krüss
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Predis;
|
||||
|
||||
use Predis\Command\Argument\Geospatial\ByInterface;
|
||||
use Predis\Command\Argument\Geospatial\FromInterface;
|
||||
use Predis\Command\Argument\Search\AggregateArguments;
|
||||
use Predis\Command\Argument\Search\AlterArguments;
|
||||
use Predis\Command\Argument\Search\CreateArguments;
|
||||
use Predis\Command\Argument\Search\DropArguments;
|
||||
use Predis\Command\Argument\Search\ExplainArguments;
|
||||
use Predis\Command\Argument\Search\HybridSearch\HybridSearchQuery;
|
||||
use Predis\Command\Argument\Search\ProfileArguments;
|
||||
use Predis\Command\Argument\Search\SchemaFields\FieldInterface;
|
||||
use Predis\Command\Argument\Search\SearchArguments;
|
||||
use Predis\Command\Argument\Search\SugAddArguments;
|
||||
use Predis\Command\Argument\Search\SugGetArguments;
|
||||
use Predis\Command\Argument\Search\SynUpdateArguments;
|
||||
use Predis\Command\Argument\Server\LimitOffsetCount;
|
||||
use Predis\Command\Argument\Server\To;
|
||||
use Predis\Command\Argument\TimeSeries\AddArguments;
|
||||
use Predis\Command\Argument\TimeSeries\AlterArguments as TSAlterArguments;
|
||||
use Predis\Command\Argument\TimeSeries\CreateArguments as TSCreateArguments;
|
||||
use Predis\Command\Argument\TimeSeries\DecrByArguments;
|
||||
use Predis\Command\Argument\TimeSeries\GetArguments;
|
||||
use Predis\Command\Argument\TimeSeries\IncrByArguments;
|
||||
use Predis\Command\Argument\TimeSeries\InfoArguments;
|
||||
use Predis\Command\Argument\TimeSeries\MGetArguments;
|
||||
use Predis\Command\Argument\TimeSeries\MRangeArguments;
|
||||
use Predis\Command\Argument\TimeSeries\RangeArguments;
|
||||
use Predis\Command\CommandInterface;
|
||||
use Predis\Command\Container\ACL;
|
||||
use Predis\Command\Container\CLIENT;
|
||||
use Predis\Command\Container\FUNCTIONS;
|
||||
use Predis\Command\Container\HOTKEYS;
|
||||
use Predis\Command\Container\Json\JSONDEBUG;
|
||||
use Predis\Command\Container\Search\FTCONFIG;
|
||||
use Predis\Command\Container\Search\FTCURSOR;
|
||||
use Predis\Command\Container\XGROUP;
|
||||
use Predis\Command\Redis\VADD;
|
||||
|
||||
/**
|
||||
* Interface defining a client-side context such as a pipeline or transaction.
|
||||
*
|
||||
* @method $this copy(string $source, string $destination, int $db = -1, bool $replace = false)
|
||||
* @method $this del(array|string $keys)
|
||||
* @method $this delex(string $key, string $flag, $flagValue)
|
||||
* @method $this digest(string $key)
|
||||
* @method $this dump($key)
|
||||
* @method $this exists($key)
|
||||
* @method $this expire($key, $seconds, string $expireOption = '')
|
||||
* @method $this expireat($key, $timestamp, string $expireOption = '')
|
||||
* @method $this expiretime(string $key)
|
||||
* @method $this keys($pattern)
|
||||
* @method $this move($key, $db)
|
||||
* @method $this object($subcommand, $key)
|
||||
* @method $this persist($key)
|
||||
* @method $this pexpire($key, $milliseconds, string $option = null)
|
||||
* @method $this pexpireat($key, $timestamp, string $option = null)
|
||||
* @method $this pttl($key)
|
||||
* @method $this randomkey()
|
||||
* @method $this rename($key, $target)
|
||||
* @method $this renamenx($key, $target)
|
||||
* @method $this scan($cursor, ?array $options = null)
|
||||
* @method $this sort($key, ?array $options = null)
|
||||
* @method $this sort_ro(string $key, ?string $byPattern = null, ?LimitOffsetCount $limit = null, array $getPatterns = [], ?string $sorting = null, bool $alpha = false)
|
||||
* @method $this ttl($key)
|
||||
* @method $this type($key)
|
||||
* @method $this append($key, $value)
|
||||
* @method $this bfadd(string $key, $item)
|
||||
* @method $this bfexists(string $key, $item)
|
||||
* @method $this bfinfo(string $key, string $modifier = '')
|
||||
* @method $this bfinsert(string $key, int $capacity = -1, float $error = -1, int $expansion = -1, bool $noCreate = false, bool $nonScaling = false, string ...$item)
|
||||
* @method $this bfloadchunk(string $key, int $iterator, $data)
|
||||
* @method $this bfmadd(string $key, ...$item)
|
||||
* @method $this bfmexists(string $key, ...$item)
|
||||
* @method $this bfreserve(string $key, float $errorRate, int $capacity, int $expansion = -1, bool $nonScaling = false)
|
||||
* @method $this bfscandump(string $key, int $iterator)
|
||||
* @method $this bitcount(string $key, $start = null, $end = null, string $index = 'byte')
|
||||
* @method $this bitop($operation, $destkey, $key)
|
||||
* @method $this bitfield($key, $subcommand, ...$subcommandArg)
|
||||
* @method $this bitfield_ro(string $key, ?array $encodingOffsetMap = null)
|
||||
* @method $this bitpos($key, $bit, $start = null, $end = null, string $index = 'byte')
|
||||
* @method $this blmpop(int $timeout, array $keys, string $modifier = 'left', int $count = 1)
|
||||
* @method $this bzpopmax(array $keys, int $timeout)
|
||||
* @method $this bzpopmin(array $keys, int $timeout)
|
||||
* @method $this bzmpop(int $timeout, array $keys, string $modifier = 'min', int $count = 1)
|
||||
* @method $this cfadd(string $key, $item)
|
||||
* @method $this cfaddnx(string $key, $item)
|
||||
* @method $this cfcount(string $key, $item)
|
||||
* @method $this cfdel(string $key, $item)
|
||||
* @method $this cfexists(string $key, $item)
|
||||
* @method $this cfloadchunk(string $key, int $iterator, $data)
|
||||
* @method $this cfmexists(string $key, ...$item)
|
||||
* @method $this cfinfo(string $key)
|
||||
* @method $this cfinsert(string $key, int $capacity = -1, bool $noCreate = false, string ...$item)
|
||||
* @method $this cfinsertnx(string $key, int $capacity = -1, bool $noCreate = false, string ...$item)
|
||||
* @method $this cfreserve(string $key, int $capacity, int $bucketSize = -1, int $maxIterations = -1, int $expansion = -1)
|
||||
* @method $this cfscandump(string $key, int $iterator)
|
||||
* @method $this cmsincrby(string $key, string|int...$itemIncrementDictionary)
|
||||
* @method $this cmsinfo(string $key)
|
||||
* @method $this cmsinitbydim(string $key, int $width, int $depth)
|
||||
* @method $this cmsinitbyprob(string $key, float $errorRate, float $probability)
|
||||
* @method $this cmsmerge(string $destination, array $sources, array $weights = [])
|
||||
* @method $this cmsquery(string $key, string ...$item)
|
||||
* @method $this decr($key)
|
||||
* @method $this decrby($key, $decrement)
|
||||
* @method $this failover(?To $to = null, bool $abort = false, int $timeout = -1)
|
||||
* @method $this fcall(string $function, array $keys, ...$args)
|
||||
* @method $this fcall_ro(string $function, array $keys, ...$args)
|
||||
* @method $this ft_list()
|
||||
* @method $this ftaggregate(string $index, string $query, ?AggregateArguments $arguments = null)
|
||||
* @method $this ftaliasadd(string $alias, string $index)
|
||||
* @method $this ftaliasdel(string $alias)
|
||||
* @method $this ftaliasupdate(string $alias, string $index)
|
||||
* @method $this ftalter(string $index, FieldInterface[] $schema, ?AlterArguments $arguments = null)
|
||||
* @method $this ftcreate(string $index, FieldInterface[] $schema, ?CreateArguments $arguments = null)
|
||||
* @method $this ftdictadd(string $dict, ...$term)
|
||||
* @method $this ftdictdel(string $dict, ...$term)
|
||||
* @method $this ftdictdump(string $dict)
|
||||
* @method $this ftdropindex(string $index, ?DropArguments $arguments = null)
|
||||
* @method $this ftexplain(string $index, string $query, ?ExplainArguments $arguments = null)
|
||||
* @method $this fthybrid(string $index, HybridSearchQuery $query)
|
||||
* @method $this ftinfo(string $index)
|
||||
* @method $this ftprofile(string $index, ProfileArguments $arguments)
|
||||
* @method $this ftsearch(string $index, string $query, ?SearchArguments $arguments = null)
|
||||
* @method $this ftspellcheck(string $index, string $query, ?SearchArguments $arguments = null)
|
||||
* @method $this ftsugadd(string $key, string $string, float $score, ?SugAddArguments $arguments = null)
|
||||
* @method $this ftsugdel(string $key, string $string)
|
||||
* @method $this ftsugget(string $key, string $prefix, ?SugGetArguments $arguments = null)
|
||||
* @method $this ftsuglen(string $key)
|
||||
* @method $this ftsyndump(string $index)
|
||||
* @method $this ftsynupdate(string $index, string $synonymGroupId, ?SynUpdateArguments $arguments = null, string ...$terms)
|
||||
* @method $this fttagvals(string $index, string $fieldName)
|
||||
* @method $this get($key)
|
||||
* @method $this getbit($key, $offset)
|
||||
* @method $this getex(string $key, $modifier = '', $value = false)
|
||||
* @method $this getrange($key, $start, $end)
|
||||
* @method $this getdel(string $key)
|
||||
* @method $this getset($key, $value)
|
||||
* @method $this incr($key)
|
||||
* @method $this incrby($key, $increment)
|
||||
* @method $this incrbyfloat($key, $increment)
|
||||
* @method $this mget(array $keys)
|
||||
* @method $this mset(array $dictionary)
|
||||
* @method $this msetex(array $dictionary, ?string $existModifier = null, ?string $expireResolution = null, ?int $expireTTL = null)
|
||||
* @method $this msetnx(array $dictionary)
|
||||
* @method $this psetex($key, $milliseconds, $value)
|
||||
* @method $this set($key, $value, $expireResolution = null, $expireTTL = null, $flag = null, $flagValue = null)
|
||||
* @method $this setbit($key, $offset, $value)
|
||||
* @method $this setex($key, $seconds, $value)
|
||||
* @method $this setnx($key, $value)
|
||||
* @method $this setrange($key, $offset, $value)
|
||||
* @method $this strlen($key)
|
||||
* @method $this hdel($key, array $fields)
|
||||
* @method $this hexists($key, $field)
|
||||
* @method $this hexpire(string $key, int $seconds, array $fields, string $flag = null)
|
||||
* @method $this hexpireat(string $key, int $unixTimeSeconds, array $fields, string $flag = null)
|
||||
* @method $this hexpiretime(string $key, array $fields)
|
||||
* @method $this hpersist(string $key, array $fields)
|
||||
* @method $this hpexpire(string $key, int $milliseconds, array $fields, string $flag = null)
|
||||
* @method $this hpexpireat(string $key, int $unixTimeMilliseconds, array $fields, string $flag = null)
|
||||
* @method $this hpexpiretime(string $key, array $fields)
|
||||
* @method $this hget($key, $field)
|
||||
* @method $this hgetex(string $key, array $fields, string $modifier = HGETEX::NULL)
|
||||
* @method $this hgetall($key)
|
||||
* @method $this hgetdel(string $key, array $fields)
|
||||
* @method $this hincrby($key, $field, $increment)
|
||||
* @method $this hincrbyfloat($key, $field, $increment)
|
||||
* @method $this hkeys($key)
|
||||
* @method $this hlen($key)
|
||||
* @method $this hmget($key, array $fields)
|
||||
* @method $this hmset($key, array $dictionary)
|
||||
* @method $this hrandfield(string $key, int $count = 1, bool $withValues = false)
|
||||
* @method $this hscan($key, $cursor, ?array $options = null)
|
||||
* @method $this hset($key, $field, $value)
|
||||
* @method $this hsetex(string $key, array $fieldValueMap, string $setModifier = HSETEX::SET_NULL, string $ttlModifier = HSETEX::TTL_NULL, int|bool $ttlModifierValue = false)
|
||||
* @method $this hsetnx($key, $field, $value)
|
||||
* @method $this httl(string $key, array $fields)
|
||||
* @method $this hpttl(string $key, array $fields)
|
||||
* @method $this hvals($key)
|
||||
* @method $this hstrlen($key, $field)
|
||||
* @method $this jsonarrappend(string $key, string $path = '$', ...$value)
|
||||
* @method $this jsonarrindex(string $key, string $path, string $value, int $start = 0, int $stop = 0)
|
||||
* @method $this jsonarrinsert(string $key, string $path, int $index, string ...$value)
|
||||
* @method $this jsonarrlen(string $key, string $path = '$')
|
||||
* @method $this jsonarrpop(string $key, string $path = '$', int $index = -1)
|
||||
* @method $this jsonarrtrim(string $key, string $path, int $start, int $stop)
|
||||
* @method $this jsonclear(string $key, string $path = '$')
|
||||
* @method $this jsondel(string $key, string $path = '$')
|
||||
* @method $this jsonforget(string $key, string $path = '$')
|
||||
* @method $this jsonget(string $key, string $indent = '', string $newline = '', string $space = '', string ...$paths)
|
||||
* @method $this jsonnumincrby(string $key, string $path, int $value)
|
||||
* @method $this jsonmerge(string $key, string $path, string $value)
|
||||
* @method $this jsonmget(array $keys, string $path)
|
||||
* @method $this jsonmset(string ...$keyPathValue)
|
||||
* @method $this jsonobjkeys(string $key, string $path = '$')
|
||||
* @method $this jsonobjlen(string $key, string $path = '$')
|
||||
* @method $this jsonresp(string $key, string $path = '$')
|
||||
* @method $this jsonset(string $key, string $path, string $value, ?string $subcommand = null)
|
||||
* @method $this jsonstrappend(string $key, string $path, string $value)
|
||||
* @method $this jsonstrlen(string $key, string $path = '$')
|
||||
* @method $this jsontoggle(string $key, string $path)
|
||||
* @method $this jsontype(string $key, string $path = '$')
|
||||
* @method $this blmove(string $source, string $destination, string $where, string $to, int $timeout)
|
||||
* @method $this blpop(array|string $keys, $timeout)
|
||||
* @method $this brpop(array|string $keys, $timeout)
|
||||
* @method $this brpoplpush($source, $destination, $timeout)
|
||||
* @method $this lcs(string $key1, string $key2, bool $len = false, bool $idx = false, int $minMatchLen = 0, bool $withMatchLen = false)
|
||||
* @method $this lindex($key, $index)
|
||||
* @method $this linsert($key, $whence, $pivot, $value)
|
||||
* @method $this llen($key)
|
||||
* @method $this lmove(string $source, string $destination, string $where, string $to)
|
||||
* @method $this lmpop(array $keys, string $modifier = 'left', int $count = 1)
|
||||
* @method $this lpop($key)
|
||||
* @method $this lpush($key, array $values)
|
||||
* @method $this lpushx($key, array $values)
|
||||
* @method $this lrange($key, $start, $stop)
|
||||
* @method $this lrem($key, $count, $value)
|
||||
* @method $this lset($key, $index, $value)
|
||||
* @method $this ltrim($key, $start, $stop)
|
||||
* @method $this rpop($key)
|
||||
* @method $this rpoplpush($source, $destination)
|
||||
* @method $this rpush($key, array $values)
|
||||
* @method $this rpushx($key, array $values)
|
||||
* @method $this sadd($key, array $members)
|
||||
* @method $this scard($key)
|
||||
* @method $this sdiff(array|string $keys)
|
||||
* @method $this sdiffstore($destination, array|string $keys)
|
||||
* @method $this sinter(array|string $keys)
|
||||
* @method $this sintercard(array $keys, int $limit = 0)
|
||||
* @method $this sinterstore($destination, array|string $keys)
|
||||
* @method $this sismember($key, $member)
|
||||
* @method $this smembers($key)
|
||||
* @method $this smismember(string $key, string ...$members)
|
||||
* @method $this smove($source, $destination, $member)
|
||||
* @method $this spop($key, $count = null)
|
||||
* @method $this srandmember($key, $count = null)
|
||||
* @method $this srem($key, $member)
|
||||
* @method $this sscan($key, $cursor, ?array $options = null)
|
||||
* @method $this ssubscribe(string ...$shardChannels)
|
||||
* @method $this subscribe(string ...$channels)
|
||||
* @method $this sunsubscribe(?string ...$shardChannels = null)
|
||||
* @method $this sunion(array|string $keys)
|
||||
* @method $this sunionstore($destination, array|string $keys)
|
||||
* @method $this tdigestadd(string $key, float ...$value)
|
||||
* @method $this tdigestbyrank(string $key, int ...$rank)
|
||||
* @method $this tdigestbyrevrank(string $key, int ...$reverseRank)
|
||||
* @method $this tdigestcdf(string $key, int ...$value)
|
||||
* @method $this tdigestcreate(string $key, int $compression = 0)
|
||||
* @method $this tdigestinfo(string $key)
|
||||
* @method $this tdigestmax(string $key)
|
||||
* @method $this tdigestmerge(string $destinationKey, array $sourceKeys, int $compression = 0, bool $override = false)
|
||||
* @method $this tdigestquantile(string $key, float ...$quantile)
|
||||
* @method $this tdigestmin(string $key)
|
||||
* @method $this tdigestrank(string $key, ...$value)
|
||||
* @method $this tdigestreset(string $key)
|
||||
* @method $this tdigestrevrank(string $key, float ...$value)
|
||||
* @method $this tdigesttrimmed_mean(string $key, float $lowCutQuantile, float $highCutQuantile)
|
||||
* @method $this topkadd(string $key, ...$items)
|
||||
* @method $this topkincrby(string $key, ...$itemIncrement)
|
||||
* @method $this topkinfo(string $key)
|
||||
* @method $this topklist(string $key, bool $withCount = false)
|
||||
* @method $this topkquery(string $key, ...$items)
|
||||
* @method $this topkreserve(string $key, int $topK, int $width = 8, int $depth = 7, float $decay = 0.9)
|
||||
* @method $this tsadd(string $key, int $timestamp, string|float $value, ?AddArguments $arguments = null)
|
||||
* @method $this tsalter(string $key, ?TSAlterArguments $arguments = null)
|
||||
* @method $this tscreate(string $key, ?TSCreateArguments $arguments = null)
|
||||
* @method $this tscreaterule(string $sourceKey, string $destKey, string $aggregator, int $bucketDuration, int $alignTimestamp = 0)
|
||||
* @method $this tsdecrby(string $key, float $value, ?DecrByArguments $arguments = null)
|
||||
* @method $this tsdel(string $key, int $fromTimestamp, int $toTimestamp)
|
||||
* @method $this tsdeleterule(string $sourceKey, string $destKey)
|
||||
* @method $this tsget(string $key, ?GetArguments $arguments = null)
|
||||
* @method $this tsincrby(string $key, float $value, ?IncrByArguments $arguments = null)
|
||||
* @method $this tsinfo(string $key, ?InfoArguments $arguments = null)
|
||||
* @method $this tsmadd(mixed ...$keyTimestampValue)
|
||||
* @method $this tsmget(MGetArguments $arguments, string ...$filterExpression)
|
||||
* @method $this tsmrange($fromTimestamp, $toTimestamp, MRangeArguments $arguments)
|
||||
* @method $this tsmrevrange($fromTimestamp, $toTimestamp, MRangeArguments $arguments)
|
||||
* @method $this tsqueryindex(string ...$filterExpression)
|
||||
* @method $this tsrange(string $key, $fromTimestamp, $toTimestamp, ?RangeArguments $arguments = null)
|
||||
* @method $this tsrevrange(string $key, $fromTimestamp, $toTimestamp, ?RangeArguments $arguments = null)
|
||||
* @method $this xack(string $key, string $group, string ...$id)
|
||||
* @method $this xackdel(string $key, string $group, string $mode, array $ids)
|
||||
* @method $this xadd(string $key, array $dictionary, string $id = '*', array $options = null)
|
||||
* @method $this xautoclaim(string $key, string $group, string $consumer, int $minIdleTime, string $start, ?int $count = null, bool $justId = false)
|
||||
* @method $this xclaim(string $key, string $group, string $consumer, int $minIdleTime, string|array $ids, ?int $idle = null, ?int $time = null, ?int $retryCount = null, bool $force = false, bool $justId = false, ?string $lastId = null)
|
||||
* @method $this xcfgset(string $key, ?int $duration = null, ?int $maxsize = null)
|
||||
* @method $this xdel(string $key, string ...$id)
|
||||
* @method $this xdelex(string $key, string $mode, array $ids)
|
||||
* @method $this xlen(string $key)
|
||||
* @method $this xpending(string $key, string $group, ?int $minIdleTime = null, ?string $start = null, ?string $end = null, ?int $count = null, ?string $consumer = null)
|
||||
* @method $this xrevrange(string $key, string $end, string $start, ?int $count = null)
|
||||
* @method $this xrange(string $key, string $start, string $end, ?int $count = null)
|
||||
* @method $this xread(int $count = null, int $block = null, array $streams = null, string ...$id)
|
||||
* @method $this xreadgroup(string $group, string $consumer, ?int $count = null, ?int $blockMs = null, bool $noAck = false, string ...$keyOrId)
|
||||
* @method $this xreadgroup_claim(string $group, string $consumer, array $keyIdDict, ?int $count = null, ?int $blockMs = null, bool $noAck = false, ?int $claim = null)
|
||||
* @method $this xsetid(string $key, string $lastId, ?int $entriesAdded = null, ?string $maxDeleteId = null)
|
||||
* @method $this xtrim(string $key, array|string $strategy, string $threshold, array $options = null)
|
||||
* @method $this zadd($key, array $membersAndScoresDictionary)
|
||||
* @method $this zcard($key)
|
||||
* @method $this zcount($key, $min, $max)
|
||||
* @method $this zdiff(array $keys, bool $withScores = false)
|
||||
* @method $this zdiffstore(string $destination, array $keys)
|
||||
* @method $this zincrby($key, $increment, $member)
|
||||
* @method $this zintercard(array $keys, int $limit = 0)
|
||||
* @method $this zinterstore(string $destination, array $keys, int[] $weights = [], string $aggregate = 'sum')
|
||||
* @method $this zinter(array $keys, int[] $weights = [], string $aggregate = 'sum', bool $withScores = false)
|
||||
* @method $this zmpop(array $keys, string $modifier = 'min', int $count = 1)
|
||||
* @method $this zmscore(string $key, string ...$member)
|
||||
* @method $this zrandmember(string $key, int $count = 1, bool $withScores = false)
|
||||
* @method $this zrange($key, $start, $stop, ?array $options = null)
|
||||
* @method $this zrangebyscore($key, $min, $max, ?array $options = null)
|
||||
* @method $this zrangestore(string $destination, string $source, int|string $min, string|int $max, string|bool $by = false, bool $reversed = false, bool $limit = false, int $offset = 0, int $count = 0)
|
||||
* @method $this zrank($key, $member)
|
||||
* @method $this zrem($key, $member)
|
||||
* @method $this zremrangebyrank($key, $start, $stop)
|
||||
* @method $this zremrangebyscore($key, $min, $max)
|
||||
* @method $this zrevrange($key, $start, $stop, ?array $options = null)
|
||||
* @method $this zrevrangebyscore($key, $max, $min, ?array $options = null)
|
||||
* @method $this zrevrank($key, $member)
|
||||
* @method $this zunion(array $keys, int[] $weights = [], string $aggregate = 'sum', bool $withScores = false)
|
||||
* @method $this zunionstore(string $destination, array $keys, int[] $weights = [], string $aggregate = 'sum')
|
||||
* @method $this zscore($key, $member)
|
||||
* @method $this zscan($key, $cursor, ?array $options = null)
|
||||
* @method $this zrangebylex($key, $start, $stop, ?array $options = null)
|
||||
* @method $this zrevrangebylex($key, $start, $stop, ?array $options = null)
|
||||
* @method $this zremrangebylex($key, $min, $max)
|
||||
* @method $this zlexcount($key, $min, $max)
|
||||
* @method $this pexpiretime(string $key)
|
||||
* @method $this pfadd($key, array $elements)
|
||||
* @method $this pfmerge($destinationKey, array|string $sourceKeys)
|
||||
* @method $this pfcount(array|string $keys)
|
||||
* @method $this pubsub($subcommand, $argument)
|
||||
* @method $this publish($channel, $message)
|
||||
* @method $this discard()
|
||||
* @method $this exec()
|
||||
* @method $this multi()
|
||||
* @method $this unwatch()
|
||||
* @method $this waitaof(int $numLocal, int $numReplicas, int $timeout)
|
||||
* @method $this unsubscribe(string ...$channels)
|
||||
* @method $this vadd(string $key, string|array $vector, string $elem, int $dim = null, bool $cas = false, string $quant = VADD::QUANT_DEFAULT, ?int $BEF = null, string|array $attributes = null, int $numlinks = null)
|
||||
* @method $this vcard(string $key)
|
||||
* @method $this vdim(int $key)
|
||||
* @method $this vemb(string $key, string $elem, bool $raw = false)
|
||||
* @method $this vgetattr(string $key, string $elem, bool $asJson = false)
|
||||
* @method $this vinfo(string $key)
|
||||
* @method $this vlinks(string $key, string $elem, bool $withScores = false)
|
||||
* @method $this vrandmember(string $key, int $count = null)
|
||||
* @method $this vrange(string $key, string $start, string $end, int $count = null)
|
||||
* @method $this vrem(string $key, string $elem)
|
||||
* @method $this vsetattr(string $key, string $elem, string|array $attributes)
|
||||
* @method $this vsim(string $key, string|array $vectorOrElem, bool $isElem = false, bool $withScores = false, int $count = null, float $epsilon = null, int $ef = null, string $filter = null, int $filterEf = null, bool $truth = false, bool $noThread = false)
|
||||
* @method $this watch($key)
|
||||
* @method $this eval($script, $numkeys, $keyOrArg1 = null, $keyOrArgN = null)
|
||||
* @method $this eval_ro(string $script, array $keys, ...$argument)
|
||||
* @method $this evalsha($script, $numkeys, $keyOrArg1 = null, $keyOrArgN = null)
|
||||
* @method $this evalsha_ro(string $sha1, array $keys, ...$argument)
|
||||
* @method $this script($subcommand, $argument = null)
|
||||
* @method $this shutdown(?bool $noSave = null, bool $now = false, bool $force = false, bool $abort = false)
|
||||
* @method $this auth($password)
|
||||
* @method $this echo($message)
|
||||
* @method $this ping($message = null)
|
||||
* @method $this select($database)
|
||||
* @method $this bgrewriteaof()
|
||||
* @method $this bgsave()
|
||||
* @method $this config($subcommand, $argument = null)
|
||||
* @method $this dbsize()
|
||||
* @method $this flushall()
|
||||
* @method $this flushdb()
|
||||
* @method $this info(string ...$section = null)
|
||||
* @method $this lastsave()
|
||||
* @method $this save()
|
||||
* @method $this slaveof($host, $port)
|
||||
* @method $this slowlog($subcommand, $argument = null)
|
||||
* @method $this spublish(string $shardChannel, string $message)
|
||||
* @method $this time()
|
||||
* @method $this command($subcommand, $argument = null)
|
||||
* @method $this geoadd($key, $longitude, $latitude, $member)
|
||||
* @method $this geohash($key, array $members)
|
||||
* @method $this geopos($key, array $members)
|
||||
* @method $this geodist($key, $member1, $member2, $unit = null)
|
||||
* @method $this georadius($key, $longitude, $latitude, $radius, $unit, ?array $options = null)
|
||||
* @method $this georadiusbymember($key, $member, $radius, $unit, ?array $options = null)
|
||||
* @method $this geosearch(string $key, FromInterface $from, ByInterface $by, ?string $sorting = null, int $count = -1, bool $any = false, bool $withCoord = false, bool $withDist = false, bool $withHash = false)
|
||||
* @method $this geosearchstore(string $destination, string $source, FromInterface $from, ByInterface $by, ?string $sorting = null, int $count = -1, bool $any = false, bool $storeDist = false)
|
||||
*
|
||||
* Container commands
|
||||
* @property CLIENT $client
|
||||
* @property HOTKEYS $hotkeys
|
||||
* @property FUNCTIONS $function
|
||||
* @property FTCONFIG $ftconfig
|
||||
* @property FTCURSOR $ftcursor
|
||||
* @property JSONDEBUG $jsondebug
|
||||
* @property ACL $acl
|
||||
* @property XGROUP $xgroup
|
||||
*/
|
||||
interface ClientContextInterface
|
||||
{
|
||||
/**
|
||||
* Sends the specified command instance to Redis.
|
||||
*
|
||||
* @param CommandInterface $command Command instance.
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function executeCommand(CommandInterface $command);
|
||||
|
||||
/**
|
||||
* Sends the specified command with its arguments to Redis.
|
||||
*
|
||||
* @param string $method Command ID.
|
||||
* @param array $arguments Arguments for the command.
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function __call($method, $arguments);
|
||||
|
||||
/**
|
||||
* Starts the execution of the context.
|
||||
*
|
||||
* @param mixed $callable Optional callback for execution.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function execute($callable = null);
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Predis package.
|
||||
*
|
||||
* (c) 2009-2020 Daniele Alessandri
|
||||
* (c) 2021-2025 Till Krüss
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Predis;
|
||||
|
||||
/**
|
||||
* Exception class that identifies client-side errors.
|
||||
*/
|
||||
class ClientException extends PredisException
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,484 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Predis package.
|
||||
*
|
||||
* (c) 2009-2020 Daniele Alessandri
|
||||
* (c) 2021-2025 Till Krüss
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Predis;
|
||||
|
||||
use Predis\Command\Argument\Geospatial\ByInterface;
|
||||
use Predis\Command\Argument\Geospatial\FromInterface;
|
||||
use Predis\Command\Argument\Search\AggregateArguments;
|
||||
use Predis\Command\Argument\Search\AlterArguments;
|
||||
use Predis\Command\Argument\Search\CreateArguments;
|
||||
use Predis\Command\Argument\Search\DropArguments;
|
||||
use Predis\Command\Argument\Search\ExplainArguments;
|
||||
use Predis\Command\Argument\Search\HybridSearch\HybridSearchQuery;
|
||||
use Predis\Command\Argument\Search\ProfileArguments;
|
||||
use Predis\Command\Argument\Search\SchemaFields\FieldInterface;
|
||||
use Predis\Command\Argument\Search\SearchArguments;
|
||||
use Predis\Command\Argument\Search\SugAddArguments;
|
||||
use Predis\Command\Argument\Search\SugGetArguments;
|
||||
use Predis\Command\Argument\Search\SynUpdateArguments;
|
||||
use Predis\Command\Argument\Server\LimitOffsetCount;
|
||||
use Predis\Command\Argument\Server\To;
|
||||
use Predis\Command\Argument\TimeSeries\AddArguments;
|
||||
use Predis\Command\Argument\TimeSeries\AlterArguments as TSAlterArguments;
|
||||
use Predis\Command\Argument\TimeSeries\CreateArguments as TSCreateArguments;
|
||||
use Predis\Command\Argument\TimeSeries\DecrByArguments;
|
||||
use Predis\Command\Argument\TimeSeries\GetArguments;
|
||||
use Predis\Command\Argument\TimeSeries\IncrByArguments;
|
||||
use Predis\Command\Argument\TimeSeries\InfoArguments;
|
||||
use Predis\Command\Argument\TimeSeries\MGetArguments;
|
||||
use Predis\Command\Argument\TimeSeries\MRangeArguments;
|
||||
use Predis\Command\Argument\TimeSeries\RangeArguments;
|
||||
use Predis\Command\CommandInterface;
|
||||
use Predis\Command\Container\ACL;
|
||||
use Predis\Command\Container\CLIENT;
|
||||
use Predis\Command\Container\FUNCTIONS;
|
||||
use Predis\Command\Container\HOTKEYS;
|
||||
use Predis\Command\Container\Json\JSONDEBUG;
|
||||
use Predis\Command\Container\Search\FTCONFIG;
|
||||
use Predis\Command\Container\Search\FTCURSOR;
|
||||
use Predis\Command\Container\XGROUP;
|
||||
use Predis\Command\Container\XINFO;
|
||||
use Predis\Command\FactoryInterface;
|
||||
use Predis\Command\Redis\VADD;
|
||||
use Predis\Configuration\OptionsInterface;
|
||||
use Predis\Connection\ConnectionInterface;
|
||||
use Predis\Response\Status;
|
||||
|
||||
/**
|
||||
* Interface defining a client able to execute commands against Redis.
|
||||
*
|
||||
* All the commands exposed by the client generally have the same signature as
|
||||
* described by the Redis documentation, but some of them offer an additional
|
||||
* and more friendly interface to ease programming which is described in the
|
||||
* following list of methods:
|
||||
*
|
||||
* @method int copy(string $source, string $destination, int $db = -1, bool $replace = false)
|
||||
* @method int del(string[]|string $keyOrKeys, string ...$keys = null)
|
||||
* @method int delex(string $key, string $flag, $flagValue)
|
||||
* @method string digest(string $key)
|
||||
* @method string|null dump(string $key)
|
||||
* @method int exists(string $key)
|
||||
* @method int expire(string $key, int $seconds, string $expireOption = '')
|
||||
* @method int expireat(string $key, int $timestamp, string $expireOption = '')
|
||||
* @method int expiretime(string $key)
|
||||
* @method array keys(string $pattern)
|
||||
* @method int move(string $key, int $db)
|
||||
* @method mixed object($subcommand, string $key)
|
||||
* @method int persist(string $key)
|
||||
* @method int pexpire(string $key, int $milliseconds, string $option = null)
|
||||
* @method int pexpireat(string $key, int $timestamp, string $option = null)
|
||||
* @method int pttl(string $key)
|
||||
* @method string|null randomkey()
|
||||
* @method mixed rename(string $key, string $target)
|
||||
* @method int renamenx(string $key, string $target)
|
||||
* @method array scan($cursor, ?array $options = null)
|
||||
* @method array sort(string $key, ?array $options = null)
|
||||
* @method array sort_ro(string $key, ?string $byPattern = null, ?LimitOffsetCount $limit = null, array $getPatterns = [], ?string $sorting = null, bool $alpha = false)
|
||||
* @method int ttl(string $key)
|
||||
* @method mixed type(string $key)
|
||||
* @method int append(string $key, $value)
|
||||
* @method mixed bfadd(string $key, $item)
|
||||
* @method mixed bfexists(string $key, $item)
|
||||
* @method array bfinfo(string $key, string $modifier = '')
|
||||
* @method array bfinsert(string $key, int $capacity = -1, float $error = -1, int $expansion = -1, bool $noCreate = false, bool $nonScaling = false, string ...$item)
|
||||
* @method Status bfloadchunk(string $key, int $iterator, $data)
|
||||
* @method array bfmadd(string $key, ...$item)
|
||||
* @method array bfmexists(string $key, ...$item)
|
||||
* @method Status bfreserve(string $key, float $errorRate, int $capacity, int $expansion = -1, bool $nonScaling = false)
|
||||
* @method array bfscandump(string $key, int $iterator)
|
||||
* @method int bitcount(string $key, $start = null, $end = null, string $index = 'byte')
|
||||
* @method int bitop($operation, $destkey, $key)
|
||||
* @method array|null bitfield(string $key, $subcommand, ...$subcommandArg)
|
||||
* @method array|null bitfield_ro(string $key, ?array $encodingOffsetMap = null)
|
||||
* @method int bitpos(string $key, $bit, $start = null, $end = null, string $index = 'byte')
|
||||
* @method array blmpop(int $timeout, array $keys, string $modifier = 'left', int $count = 1)
|
||||
* @method array bzpopmax(array $keys, int $timeout)
|
||||
* @method array bzpopmin(array $keys, int $timeout)
|
||||
* @method array bzmpop(int $timeout, array $keys, string $modifier = 'min', int $count = 1)
|
||||
* @method mixed cfadd(string $key, $item)
|
||||
* @method mixed cfaddnx(string $key, $item)
|
||||
* @method int cfcount(string $key, $item)
|
||||
* @method mixed cfdel(string $key, $item)
|
||||
* @method mixed cfexists(string $key, $item)
|
||||
* @method Status cfloadchunk(string $key, int $iterator, $data)
|
||||
* @method int cfmexists(string $key, ...$item)
|
||||
* @method array cfinfo(string $key)
|
||||
* @method array cfinsert(string $key, int $capacity = -1, bool $noCreate = false, string ...$item)
|
||||
* @method array cfinsertnx(string $key, int $capacity = -1, bool $noCreate = false, string ...$item)
|
||||
* @method Status cfreserve(string $key, int $capacity, int $bucketSize = -1, int $maxIterations = -1, int $expansion = -1)
|
||||
* @method array cfscandump(string $key, int $iterator)
|
||||
* @method array cmsincrby(string $key, string|int ...$itemIncrementDictionary)
|
||||
* @method array cmsinfo(string $key)
|
||||
* @method Status cmsinitbydim(string $key, int $width, int $depth)
|
||||
* @method Status cmsinitbyprob(string $key, float $errorRate, float $probability)
|
||||
* @method Status cmsmerge(string $destination, array $sources, array $weights = [])
|
||||
* @method array cmsquery(string $key, string ...$item)
|
||||
* @method int decr(string $key)
|
||||
* @method int decrby(string $key, int $decrement)
|
||||
* @method Status failover(?To $to = null, bool $abort = false, int $timeout = -1)
|
||||
* @method mixed fcall(string $function, array $keys, ...$args)
|
||||
* @method mixed fcall_ro(string $function, array $keys, ...$args)
|
||||
* @method array ft_list()
|
||||
* @method array ftaggregate(string $index, string $query, ?AggregateArguments $arguments = null)
|
||||
* @method Status ftaliasadd(string $alias, string $index)
|
||||
* @method Status ftaliasdel(string $alias)
|
||||
* @method Status ftaliasupdate(string $alias, string $index)
|
||||
* @method Status ftalter(string $index, FieldInterface[] $schema, ?AlterArguments $arguments = null)
|
||||
* @method Status ftcreate(string $index, FieldInterface[] $schema, ?CreateArguments $arguments = null)
|
||||
* @method int ftdictadd(string $dict, ...$term)
|
||||
* @method int ftdictdel(string $dict, ...$term)
|
||||
* @method array ftdictdump(string $dict)
|
||||
* @method Status ftdropindex(string $index, ?DropArguments $arguments = null)
|
||||
* @method string ftexplain(string $index, string $query, ?ExplainArguments $arguments = null)
|
||||
* @method array fthybrid(string $index, HybridSearchQuery $query)
|
||||
* @method array ftinfo(string $index)
|
||||
* @method array ftprofile(string $index, ProfileArguments $arguments)
|
||||
* @method array ftsearch(string $index, string $query, ?SearchArguments $arguments = null)
|
||||
* @method array ftspellcheck(string $index, string $query, ?SearchArguments $arguments = null)
|
||||
* @method int ftsugadd(string $key, string $string, float $score, ?SugAddArguments $arguments = null)
|
||||
* @method int ftsugdel(string $key, string $string)
|
||||
* @method array ftsugget(string $key, string $prefix, ?SugGetArguments $arguments = null)
|
||||
* @method int ftsuglen(string $key)
|
||||
* @method array ftsyndump(string $index)
|
||||
* @method Status ftsynupdate(string $index, string $synonymGroupId, ?SynUpdateArguments $arguments = null, string ...$terms)
|
||||
* @method array fttagvals(string $index, string $fieldName)
|
||||
* @method string|null get(string $key)
|
||||
* @method int getbit(string $key, $offset)
|
||||
* @method int|null getex(string $key, $modifier = '', $value = false)
|
||||
* @method string getrange(string $key, $start, $end)
|
||||
* @method string getdel(string $key)
|
||||
* @method string|null getset(string $key, $value)
|
||||
* @method int incr(string $key)
|
||||
* @method int incrby(string $key, int $increment)
|
||||
* @method string incrbyfloat(string $key, int|float $increment)
|
||||
* @method array mget(string[]|string $keyOrKeys, string ...$keys = null)
|
||||
* @method mixed mset(array $dictionary)
|
||||
* @method array msetex(array $dictionary, ?string $existModifier = null, ?string $expireResolution = null, ?int $expireTTL = null)
|
||||
* @method int msetnx(array $dictionary)
|
||||
* @method Status psetex(string $key, $milliseconds, $value)
|
||||
* @method Status|null set(string $key, $value, $expireResolution = null, $expireTTL = null, $flag = null, $flagValue = null)
|
||||
* @method int setbit(string $key, $offset, $value)
|
||||
* @method Status setex(string $key, $seconds, $value)
|
||||
* @method int setnx(string $key, $value)
|
||||
* @method int setrange(string $key, $offset, $value)
|
||||
* @method int strlen(string $key)
|
||||
* @method int hdel(string $key, array $fields)
|
||||
* @method int hexists(string $key, string $field)
|
||||
* @method array|null hexpire(string $key, int $seconds, array $fields, string $flag = null)
|
||||
* @method array|null hexpireat(string $key, int $unixTimeSeconds, array $fields, string $flag = null)
|
||||
* @method array|null hexpiretime(string $key, array $fields)
|
||||
* @method array|null hpersist(string $key, array $fields)
|
||||
* @method array|null hpexpire(string $key, int $milliseconds, array $fields, string $flag = null)
|
||||
* @method array|null hpexpireat(string $key, int $unixTimeMilliseconds, array $fields, string $flag = null)
|
||||
* @method array|null hpexpiretime(string $key, array $fields)
|
||||
* @method string|null hget(string $key, string $field)
|
||||
* @method array|null hgetex(string $key, array $fields, string $modifier = HGETEX::NULL, int|bool $modifierValue = false)
|
||||
* @method array hgetall(string $key)
|
||||
* @method array hgetdel(string $key, array $fields)
|
||||
* @method int hincrby(string $key, string $field, int $increment)
|
||||
* @method string hincrbyfloat(string $key, string $field, int|float $increment)
|
||||
* @method array hkeys(string $key)
|
||||
* @method int hlen(string $key)
|
||||
* @method array hmget(string $key, array $fields)
|
||||
* @method mixed hmset(string $key, array $dictionary)
|
||||
* @method array hrandfield(string $key, int $count = 1, bool $withValues = false)
|
||||
* @method array hscan(string $key, $cursor, ?array $options = null)
|
||||
* @method int hset(string $key, string $field, string $value)
|
||||
* @method int hsetex(string $key, array $fieldValueMap, string $setModifier = HSETEX::SET_NULL, string $ttlModifier = HSETEX::TTL_NULL, int|bool $ttlModifierValue = false)
|
||||
* @method int hsetnx(string $key, string $field, string $value)
|
||||
* @method array|null httl(string $key, array $fields)
|
||||
* @method array|null hpttl(string $key, array $fields)
|
||||
* @method array hvals(string $key)
|
||||
* @method int hstrlen(string $key, string $field)
|
||||
* @method array jsonarrappend(string $key, string $path = '$', ...$value)
|
||||
* @method array jsonarrindex(string $key, string $path, string $value, int $start = 0, int $stop = 0)
|
||||
* @method array jsonarrinsert(string $key, string $path, int $index, string ...$value)
|
||||
* @method array jsonarrlen(string $key, string $path = '$')
|
||||
* @method array jsonarrpop(string $key, string $path = '$', int $index = -1)
|
||||
* @method int jsonclear(string $key, string $path = '$')
|
||||
* @method array jsonarrtrim(string $key, string $path, int $start, int $stop)
|
||||
* @method int jsondel(string $key, string $path = '$')
|
||||
* @method int jsonforget(string $key, string $path = '$')
|
||||
* @method mixed jsonget(string $key, string $indent = '', string $newline = '', string $space = '', string ...$paths)
|
||||
* @method mixed jsonnumincrby(string $key, string $path, int $value)
|
||||
* @method Status jsonmerge(string $key, string $path, string $value)
|
||||
* @method array jsonmget(array $keys, string $path)
|
||||
* @method Status jsonmset(string ...$keyPathValue)
|
||||
* @method array jsonobjkeys(string $key, string $path = '$')
|
||||
* @method array jsonobjlen(string $key, string $path = '$')
|
||||
* @method array jsonresp(string $key, string $path = '$')
|
||||
* @method string jsonset(string $key, string $path, string $value, ?string $subcommand = null)
|
||||
* @method array jsonstrappend(string $key, string $path, string $value)
|
||||
* @method array jsonstrlen(string $key, string $path = '$')
|
||||
* @method array jsontoggle(string $key, string $path)
|
||||
* @method array jsontype(string $key, string $path = '$')
|
||||
* @method string blmove(string $source, string $destination, string $where, string $to, int $timeout)
|
||||
* @method array|null blpop(array|string $keys, int|float $timeout)
|
||||
* @method array|null brpop(array|string $keys, int|float $timeout)
|
||||
* @method string|null brpoplpush(string $source, string $destination, int|float $timeout)
|
||||
* @method mixed lcs(string $key1, string $key2, bool $len = false, bool $idx = false, int $minMatchLen = 0, bool $withMatchLen = false)
|
||||
* @method string|null lindex(string $key, int $index)
|
||||
* @method int linsert(string $key, $whence, $pivot, $value)
|
||||
* @method int llen(string $key)
|
||||
* @method string lmove(string $source, string $destination, string $where, string $to)
|
||||
* @method array|null lmpop(array $keys, string $modifier = 'left', int $count = 1)
|
||||
* @method string|null lpop(string $key)
|
||||
* @method int lpush(string $key, array $values)
|
||||
* @method int lpushx(string $key, array $values)
|
||||
* @method string[] lrange(string $key, int $start, int $stop)
|
||||
* @method int lrem(string $key, int $count, string $value)
|
||||
* @method mixed lset(string $key, int $index, string $value)
|
||||
* @method mixed ltrim(string $key, int $start, int $stop)
|
||||
* @method string|null rpop(string $key)
|
||||
* @method string|null rpoplpush(string $source, string $destination)
|
||||
* @method int rpush(string $key, array $values)
|
||||
* @method int rpushx(string $key, array $values)
|
||||
* @method int sadd(string $key, array $members)
|
||||
* @method int scard(string $key)
|
||||
* @method string[] sdiff(array|string $keys)
|
||||
* @method int sdiffstore(string $destination, array|string $keys)
|
||||
* @method string[] sinter(array|string $keys)
|
||||
* @method int sintercard(array $keys, int $limit = 0)
|
||||
* @method int sinterstore(string $destination, array|string $keys)
|
||||
* @method int sismember(string $key, string $member)
|
||||
* @method string[] smembers(string $key)
|
||||
* @method array smismember(string $key, string ...$members)
|
||||
* @method int smove(string $source, string $destination, string $member)
|
||||
* @method string|array|null spop(string $key, ?int $count = null)
|
||||
* @method string|null srandmember(string $key, ?int $count = null)
|
||||
* @method int srem(string $key, array|string $member)
|
||||
* @method array sscan(string $key, int $cursor, array $options = null)
|
||||
* @method array ssubscribe(string ...$shardChannels)
|
||||
* @method array subscribe(string ...$channels)
|
||||
* @method string[] sunion(array|string $keys)
|
||||
* @method int sunionstore(string $destination, array|string $keys)
|
||||
* @method array sunsubscribe(?string ...$shardChannels = null)
|
||||
* @method int touch(string[]|string $keyOrKeys, string ...$keys = null)
|
||||
* @method Status tdigestadd(string $key, float ...$value)
|
||||
* @method array tdigestbyrank(string $key, int ...$rank)
|
||||
* @method array tdigestbyrevrank(string $key, int ...$reverseRank)
|
||||
* @method array tdigestcdf(string $key, int ...$value)
|
||||
* @method Status tdigestcreate(string $key, int $compression = 0)
|
||||
* @method array tdigestinfo(string $key)
|
||||
* @method mixed tdigestmax(string $key)
|
||||
* @method Status tdigestmerge(string $destinationKey, array $sourceKeys, int $compression = 0, bool $override = false)
|
||||
* @method string[] tdigestquantile(string $key, float ...$quantile)
|
||||
* @method mixed tdigestmin(string $key)
|
||||
* @method array tdigestrank(string $key, float ...$value)
|
||||
* @method Status tdigestreset(string $key)
|
||||
* @method array tdigestrevrank(string $key, float ...$value)
|
||||
* @method string tdigesttrimmed_mean(string $key, float $lowCutQuantile, float $highCutQuantile)
|
||||
* @method array topkadd(string $key, ...$items)
|
||||
* @method array topkincrby(string $key, ...$itemIncrement)
|
||||
* @method array topkinfo(string $key)
|
||||
* @method array topklist(string $key, bool $withCount = false)
|
||||
* @method array topkquery(string $key, ...$items)
|
||||
* @method Status topkreserve(string $key, int $topK, int $width = 8, int $depth = 7, float $decay = 0.9)
|
||||
* @method int tsadd(string $key, int $timestamp, string|float $value, ?AddArguments $arguments = null)
|
||||
* @method Status tsalter(string $key, ?TSAlterArguments $arguments = null)
|
||||
* @method Status tscreate(string $key, ?TSCreateArguments $arguments = null)
|
||||
* @method Status tscreaterule(string $sourceKey, string $destKey, string $aggregator, int $bucketDuration, int $alignTimestamp = 0)
|
||||
* @method int tsdecrby(string $key, float $value, ?DecrByArguments $arguments = null)
|
||||
* @method int tsdel(string $key, int $fromTimestamp, int $toTimestamp)
|
||||
* @method Status tsdeleterule(string $sourceKey, string $destKey)
|
||||
* @method array tsget(string $key, ?GetArguments $arguments = null)
|
||||
* @method int tsincrby(string $key, float $value, ?IncrByArguments $arguments = null)
|
||||
* @method array tsinfo(string $key, ?InfoArguments $arguments = null)
|
||||
* @method array tsmadd(mixed ...$keyTimestampValue)
|
||||
* @method array tsmget(MGetArguments $arguments, string ...$filterExpression)
|
||||
* @method array tsmrange($fromTimestamp, $toTimestamp, MRangeArguments $arguments)
|
||||
* @method array tsmrevrange($fromTimestamp, $toTimestamp, MRangeArguments $arguments)
|
||||
* @method array tsqueryindex(string ...$filterExpression)
|
||||
* @method array tsrange(string $key, $fromTimestamp, $toTimestamp, ?RangeArguments $arguments = null)
|
||||
* @method array tsrevrange(string $key, $fromTimestamp, $toTimestamp, ?RangeArguments $arguments = null)
|
||||
* @method int xack(string $key, string $group, string ...$id)
|
||||
* @method array xackdel(string $key, string $group, string $mode, array $ids)
|
||||
* @method string xadd(string $key, array $dictionary, string $id = '*', array $options = null)
|
||||
* @method array xautoclaim(string $key, string $group, string $consumer, int $minIdleTime, string $start, ?int $count = null, bool $justId = false)
|
||||
* @method array xclaim(string $key, string $group, string $consumer, int $minIdleTime, string|array $ids, ?int $idle = null, ?int $time = null, ?int $retryCount = null, bool $force = false, bool $justId = false, ?string $lastId = null)
|
||||
* @method Status xcfgset(string $key, ?int $duration = null, ?int $maxsize = null)
|
||||
* @method int xdel(string $key, string ...$id)
|
||||
* @method array xdelex(string $key, string $mode, array $ids)
|
||||
* @method int xlen(string $key)
|
||||
* @method array xpending(string $key, string $group, ?int $minIdleTime = null, ?string $start = null, ?string $end = null, ?int $count = null, ?string $consumer = null)
|
||||
* @method array xrevrange(string $key, string $end, string $start, ?int $count = null)
|
||||
* @method array xrange(string $key, string $start, string $end, ?int $count = null)
|
||||
* @method array|null xread(int $count = null, int $block = null, array $streams = null, string ...$id)
|
||||
* @method array xreadgroup(string $group, string $consumer, ?int $count = null, ?int $blockMs = null, bool $noAck = false, string ...$keyOrId)
|
||||
* @method array xreadgroup_claim(string $group, string $consumer, array $keyIdDict, ?int $count = null, ?int $blockMs = null, bool $noAck = false, ?int $claim = null)
|
||||
* @method Status xsetid(string $key, string $lastId, ?int $entriesAdded = null, ?string $maxDeleteId = null)
|
||||
* @method string xtrim(string $key, array|string $strategy, string $threshold, array $options = null)
|
||||
* @method int zadd(string $key, array $membersAndScoresDictionary)
|
||||
* @method int zcard(string $key)
|
||||
* @method int zcount(string $key, int|string $min, int|string $max)
|
||||
* @method array zdiff(array $keys, bool $withScores = false)
|
||||
* @method int zdiffstore(string $destination, array $keys)
|
||||
* @method string zincrby(string $key, int $increment, string $member)
|
||||
* @method int zintercard(array $keys, int $limit = 0)
|
||||
* @method int zinterstore(string $destination, array $keys, int[] $weights = [], string $aggregate = 'sum')
|
||||
* @method array zinter(array $keys, int[] $weights = [], string $aggregate = 'sum', bool $withScores = false)
|
||||
* @method array zmpop(array $keys, string $modifier = 'min', int $count = 1)
|
||||
* @method array zmscore(string $key, string ...$member)
|
||||
* @method array zpopmin(string $key, int $count = 1)
|
||||
* @method array zpopmax(string $key, int $count = 1)
|
||||
* @method mixed zrandmember(string $key, int $count = 1, bool $withScores = false)
|
||||
* @method array zrange(string $key, int|string $start, int|string $stop, ?array $options = null)
|
||||
* @method array zrangebyscore(string $key, int|string $min, int|string $max, ?array $options = null)
|
||||
* @method int zrangestore(string $destination, string $source, int|string $min, int|string $max, string|bool $by = false, bool $reversed = false, bool $limit = false, int $offset = 0, int $count = 0)
|
||||
* @method int|null zrank(string $key, string $member)
|
||||
* @method int zrem(string $key, string ...$member)
|
||||
* @method int zremrangebyrank(string $key, int|string $start, int|string $stop)
|
||||
* @method int zremrangebyscore(string $key, int|string $min, int|string $max)
|
||||
* @method array zrevrange(string $key, int|string $start, int|string $stop, ?array $options = null)
|
||||
* @method array zrevrangebyscore(string $key, int|string $max, int|string $min, ?array $options = null)
|
||||
* @method int|null zrevrank(string $key, string $member)
|
||||
* @method array zunion(array $keys, int[] $weights = [], string $aggregate = 'sum', bool $withScores = false)
|
||||
* @method int zunionstore(string $destination, array $keys, int[] $weights = [], string $aggregate = 'sum')
|
||||
* @method string|null zscore(string $key, string $member)
|
||||
* @method array zscan(string $key, int $cursor, ?array $options = null)
|
||||
* @method array zrangebylex(string $key, string $start, string $stop, ?array $options = null)
|
||||
* @method array zrevrangebylex(string $key, string $start, string $stop, ?array $options = null)
|
||||
* @method int zremrangebylex(string $key, string $min, string $max)
|
||||
* @method int zlexcount(string $key, string $min, string $max)
|
||||
* @method int pexpiretime(string $key)
|
||||
* @method int pfadd(string $key, array $elements)
|
||||
* @method mixed pfmerge(string $destinationKey, array|string $sourceKeys)
|
||||
* @method int pfcount(string[]|string $keyOrKeys, string ...$keys = null)
|
||||
* @method mixed pubsub($subcommand, $argument)
|
||||
* @method int publish($channel, $message)
|
||||
* @method mixed discard()
|
||||
* @method array|null exec()
|
||||
* @method mixed multi()
|
||||
* @method mixed unwatch()
|
||||
* @method array unsubscribe(string ...$channels)
|
||||
* @method bool vadd(string $key, string|array $vector, string $elem, int $dim = null, bool $cas = false, string $quant = VADD::QUANT_DEFAULT, int $bef = null, string|array $attributes = null, int $numlinks = null)
|
||||
* @method int vcard(string $key)
|
||||
* @method int vdim(string $key)
|
||||
* @method array vemb(string $key, string $elem, bool $raw = false)
|
||||
* @method string|array|null vgetattr(string $key, string $elem, bool $asJson = false)
|
||||
* @method array|null vinfo(string $key)
|
||||
* @method array|null vlinks(string $key, string $elem, bool $withScores = false)
|
||||
* @method string|array|null vrandmember(string $key, int $count = null)
|
||||
* @method array vrange(string $key, string $start, string $end, int $count = null)
|
||||
* @method bool vrem(string $key, string $elem)
|
||||
* @method array vsim(string $key, string|array $vectorOrElem, bool $isElem = false, bool $withScores = false, int $count = null, float $epsilon = null, int $ef = null, string $filter = null, int $filterEf = null, bool $truth = false, bool $noThread = false)
|
||||
* @method bool vsetattr(string $key, string $elem, string|array $attributes)
|
||||
* @method array waitaof(int $numLocal, int $numReplicas, int $timeout)
|
||||
* @method mixed watch(string[]|string $keyOrKeys)
|
||||
* @method mixed eval(string $script, int $numkeys, string ...$keyOrArg = null)
|
||||
* @method mixed eval_ro(string $script, array $keys, ...$argument)
|
||||
* @method mixed evalsha(string $script, int $numkeys, string ...$keyOrArg = null)
|
||||
* @method mixed evalsha_ro(string $sha1, array $keys, ...$argument)
|
||||
* @method mixed script($subcommand, $argument = null)
|
||||
* @method Status shutdown(?bool $noSave = null, bool $now = false, bool $force = false, bool $abort = false)
|
||||
* @method mixed auth(string $password)
|
||||
* @method string echo(string $message)
|
||||
* @method mixed ping(?string $message = null)
|
||||
* @method mixed select(int $database)
|
||||
* @method mixed bgrewriteaof()
|
||||
* @method mixed bgsave()
|
||||
* @method mixed config($subcommand, $argument = null)
|
||||
* @method int dbsize()
|
||||
* @method mixed flushall()
|
||||
* @method mixed flushdb()
|
||||
* @method array info(string ...$section = null)
|
||||
* @method int lastsave()
|
||||
* @method mixed save()
|
||||
* @method mixed slaveof(string $host, int $port)
|
||||
* @method mixed slowlog($subcommand, $argument = null)
|
||||
* @method int spublish(string $shardChannel, string $message)
|
||||
* @method array time()
|
||||
* @method array command($subcommand, $argument = null)
|
||||
* @method int geoadd(string $key, $longitude, $latitude, $member)
|
||||
* @method array geohash(string $key, array $members)
|
||||
* @method array geopos(string $key, array $members)
|
||||
* @method string|null geodist(string $key, $member1, $member2, $unit = null)
|
||||
* @method array georadius(string $key, $longitude, $latitude, $radius, $unit, ?array $options = null)
|
||||
* @method array georadiusbymember(string $key, $member, $radius, $unit, ?array $options = null)
|
||||
* @method array geosearch(string $key, FromInterface $from, ByInterface $by, ?string $sorting = null, int $count = -1, bool $any = false, bool $withCoord = false, bool $withDist = false, bool $withHash = false)
|
||||
* @method int geosearchstore(string $destination, string $source, FromInterface $from, ByInterface $by, ?string $sorting = null, int $count = -1, bool $any = false, bool $storeDist = false)
|
||||
*
|
||||
* Container commands
|
||||
* @property CLIENT $client
|
||||
* @property HOTKEYS $hotkeys
|
||||
* @property FUNCTIONS $function
|
||||
* @property FTCONFIG $ftconfig
|
||||
* @property FTCURSOR $ftcursor
|
||||
* @property JSONDEBUG $jsondebug
|
||||
* @property ACL $acl
|
||||
* @property XGROUP $xgroup
|
||||
* @property XINFO $xinfo
|
||||
*/
|
||||
interface ClientInterface
|
||||
{
|
||||
/**
|
||||
* Returns the command factory used by the client.
|
||||
*
|
||||
* @return FactoryInterface
|
||||
*/
|
||||
public function getCommandFactory();
|
||||
|
||||
/**
|
||||
* Returns the client options specified upon initialization.
|
||||
*
|
||||
* @return OptionsInterface
|
||||
*/
|
||||
public function getOptions();
|
||||
|
||||
/**
|
||||
* Opens the underlying connection to the server.
|
||||
*/
|
||||
public function connect();
|
||||
|
||||
/**
|
||||
* Closes the underlying connection from the server.
|
||||
*/
|
||||
public function disconnect();
|
||||
|
||||
/**
|
||||
* Returns the underlying connection instance.
|
||||
*
|
||||
* @return ConnectionInterface
|
||||
*/
|
||||
public function getConnection();
|
||||
|
||||
/**
|
||||
* Creates a new instance of the specified Redis command.
|
||||
*
|
||||
* @param string $method Command ID.
|
||||
* @param array $arguments Arguments for the command.
|
||||
*
|
||||
* @return CommandInterface
|
||||
*/
|
||||
public function createCommand($method, $arguments = []);
|
||||
|
||||
/**
|
||||
* Executes the specified Redis command.
|
||||
*
|
||||
* @param CommandInterface $command Command instance.
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function executeCommand(CommandInterface $command);
|
||||
|
||||
/**
|
||||
* Creates a Redis command with the specified arguments and sends a request
|
||||
* to the server.
|
||||
*
|
||||
* @param string $method Command ID.
|
||||
* @param array $arguments Arguments for the command.
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function __call($method, $arguments);
|
||||
}
|
||||
+520
@@ -0,0 +1,520 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Predis package.
|
||||
*
|
||||
* (c) 2009-2020 Daniele Alessandri
|
||||
* (c) 2021-2025 Till Krüss
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Predis\Cluster;
|
||||
|
||||
use InvalidArgumentException;
|
||||
use Predis\Command\CommandInterface;
|
||||
use Predis\Command\ScriptCommand;
|
||||
|
||||
/**
|
||||
* Common class implementing the logic needed to support clustering strategies.
|
||||
*/
|
||||
abstract class ClusterStrategy implements StrategyInterface
|
||||
{
|
||||
protected $commands;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->commands = $this->getDefaultCommands();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the default map of supported commands with their handlers.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function getDefaultCommands()
|
||||
{
|
||||
$getKeyFromFirstArgument = [$this, 'getKeyFromFirstArgument'];
|
||||
$getKeyFromAllArguments = [$this, 'getKeyFromAllArguments'];
|
||||
|
||||
return [
|
||||
/* commands operating on the key space */
|
||||
'EXISTS' => $getKeyFromAllArguments,
|
||||
'DEL' => $getKeyFromAllArguments,
|
||||
'TYPE' => $getKeyFromFirstArgument,
|
||||
'EXPIRE' => $getKeyFromFirstArgument,
|
||||
'EXPIREAT' => $getKeyFromFirstArgument,
|
||||
'PERSIST' => $getKeyFromFirstArgument,
|
||||
'PEXPIRE' => $getKeyFromFirstArgument,
|
||||
'PEXPIREAT' => $getKeyFromFirstArgument,
|
||||
'TTL' => $getKeyFromFirstArgument,
|
||||
'PTTL' => $getKeyFromFirstArgument,
|
||||
'SORT' => [$this, 'getKeyFromSortCommand'],
|
||||
'DUMP' => $getKeyFromFirstArgument,
|
||||
'RESTORE' => $getKeyFromFirstArgument,
|
||||
'FLUSHDB' => [$this, 'getFakeKey'],
|
||||
|
||||
/* commands operating on string values */
|
||||
'APPEND' => $getKeyFromFirstArgument,
|
||||
'DECR' => $getKeyFromFirstArgument,
|
||||
'DECRBY' => $getKeyFromFirstArgument,
|
||||
'GET' => $getKeyFromFirstArgument,
|
||||
'GETBIT' => $getKeyFromFirstArgument,
|
||||
'MGET' => $getKeyFromAllArguments,
|
||||
'SET' => $getKeyFromFirstArgument,
|
||||
'GETRANGE' => $getKeyFromFirstArgument,
|
||||
'GETSET' => $getKeyFromFirstArgument,
|
||||
'INCR' => $getKeyFromFirstArgument,
|
||||
'INCRBY' => $getKeyFromFirstArgument,
|
||||
'INCRBYFLOAT' => $getKeyFromFirstArgument,
|
||||
'SETBIT' => $getKeyFromFirstArgument,
|
||||
'SETEX' => $getKeyFromFirstArgument,
|
||||
'MSET' => [$this, 'getKeyFromInterleavedArguments'],
|
||||
'MSETNX' => [$this, 'getKeyFromInterleavedArguments'],
|
||||
'SETNX' => $getKeyFromFirstArgument,
|
||||
'SETRANGE' => $getKeyFromFirstArgument,
|
||||
'STRLEN' => $getKeyFromFirstArgument,
|
||||
'SUBSTR' => $getKeyFromFirstArgument,
|
||||
'BITOP' => [$this, 'getKeyFromBitOp'],
|
||||
'BITCOUNT' => $getKeyFromFirstArgument,
|
||||
'BITFIELD' => $getKeyFromFirstArgument,
|
||||
|
||||
/* commands operating on lists */
|
||||
'LINSERT' => $getKeyFromFirstArgument,
|
||||
'LINDEX' => $getKeyFromFirstArgument,
|
||||
'LLEN' => $getKeyFromFirstArgument,
|
||||
'LPOP' => $getKeyFromFirstArgument,
|
||||
'RPOP' => $getKeyFromFirstArgument,
|
||||
'RPOPLPUSH' => $getKeyFromAllArguments,
|
||||
'BLPOP' => [$this, 'getKeyFromBlockingListCommands'],
|
||||
'BRPOP' => [$this, 'getKeyFromBlockingListCommands'],
|
||||
'BRPOPLPUSH' => [$this, 'getKeyFromBlockingListCommands'],
|
||||
'LPUSH' => $getKeyFromFirstArgument,
|
||||
'LPUSHX' => $getKeyFromFirstArgument,
|
||||
'RPUSH' => $getKeyFromFirstArgument,
|
||||
'RPUSHX' => $getKeyFromFirstArgument,
|
||||
'LRANGE' => $getKeyFromFirstArgument,
|
||||
'LREM' => $getKeyFromFirstArgument,
|
||||
'LSET' => $getKeyFromFirstArgument,
|
||||
'LTRIM' => $getKeyFromFirstArgument,
|
||||
|
||||
/* commands operating on sets */
|
||||
'SADD' => $getKeyFromFirstArgument,
|
||||
'SCARD' => $getKeyFromFirstArgument,
|
||||
'SDIFF' => $getKeyFromAllArguments,
|
||||
'SDIFFSTORE' => $getKeyFromAllArguments,
|
||||
'SINTER' => $getKeyFromAllArguments,
|
||||
'SINTERSTORE' => $getKeyFromAllArguments,
|
||||
'SUNION' => $getKeyFromAllArguments,
|
||||
'SUNIONSTORE' => $getKeyFromAllArguments,
|
||||
'SISMEMBER' => $getKeyFromFirstArgument,
|
||||
'SMEMBERS' => $getKeyFromFirstArgument,
|
||||
'SSCAN' => $getKeyFromFirstArgument,
|
||||
'SPOP' => $getKeyFromFirstArgument,
|
||||
'SRANDMEMBER' => $getKeyFromFirstArgument,
|
||||
'SREM' => $getKeyFromFirstArgument,
|
||||
|
||||
/* commands operating on sorted sets */
|
||||
'ZADD' => $getKeyFromFirstArgument,
|
||||
'ZCARD' => $getKeyFromFirstArgument,
|
||||
'ZCOUNT' => $getKeyFromFirstArgument,
|
||||
'ZINCRBY' => $getKeyFromFirstArgument,
|
||||
'ZINTERSTORE' => [$this, 'getKeyFromZsetAggregationCommands'],
|
||||
'ZRANGE' => $getKeyFromFirstArgument,
|
||||
'ZRANGEBYSCORE' => $getKeyFromFirstArgument,
|
||||
'ZRANK' => $getKeyFromFirstArgument,
|
||||
'ZREM' => $getKeyFromFirstArgument,
|
||||
'ZREMRANGEBYRANK' => $getKeyFromFirstArgument,
|
||||
'ZREMRANGEBYSCORE' => $getKeyFromFirstArgument,
|
||||
'ZREVRANGE' => $getKeyFromFirstArgument,
|
||||
'ZREVRANGEBYSCORE' => $getKeyFromFirstArgument,
|
||||
'ZREVRANK' => $getKeyFromFirstArgument,
|
||||
'ZSCORE' => $getKeyFromFirstArgument,
|
||||
'ZUNIONSTORE' => [$this, 'getKeyFromZsetAggregationCommands'],
|
||||
'ZSCAN' => $getKeyFromFirstArgument,
|
||||
'ZLEXCOUNT' => $getKeyFromFirstArgument,
|
||||
'ZRANGEBYLEX' => $getKeyFromFirstArgument,
|
||||
'ZREMRANGEBYLEX' => $getKeyFromFirstArgument,
|
||||
'ZREVRANGEBYLEX' => $getKeyFromFirstArgument,
|
||||
|
||||
/* commands operating on hashes */
|
||||
'HDEL' => $getKeyFromFirstArgument,
|
||||
'HEXISTS' => $getKeyFromFirstArgument,
|
||||
'HGET' => $getKeyFromFirstArgument,
|
||||
'HGETALL' => $getKeyFromFirstArgument,
|
||||
'HMGET' => $getKeyFromFirstArgument,
|
||||
'HMSET' => $getKeyFromFirstArgument,
|
||||
'HINCRBY' => $getKeyFromFirstArgument,
|
||||
'HINCRBYFLOAT' => $getKeyFromFirstArgument,
|
||||
'HKEYS' => $getKeyFromFirstArgument,
|
||||
'HLEN' => $getKeyFromFirstArgument,
|
||||
'HSET' => $getKeyFromFirstArgument,
|
||||
'HSETNX' => $getKeyFromFirstArgument,
|
||||
'HVALS' => $getKeyFromFirstArgument,
|
||||
'HSCAN' => $getKeyFromFirstArgument,
|
||||
'HSTRLEN' => $getKeyFromFirstArgument,
|
||||
|
||||
/* commands operating on streams */
|
||||
'XADD' => $getKeyFromFirstArgument,
|
||||
'XDEL' => $getKeyFromFirstArgument,
|
||||
'XRANGE' => $getKeyFromFirstArgument,
|
||||
|
||||
/* commands operating on HyperLogLog */
|
||||
'PFADD' => $getKeyFromFirstArgument,
|
||||
'PFCOUNT' => $getKeyFromAllArguments,
|
||||
'PFMERGE' => $getKeyFromAllArguments,
|
||||
|
||||
/* scripting */
|
||||
'EVAL' => [$this, 'getKeyFromScriptingCommands'],
|
||||
'EVALSHA' => [$this, 'getKeyFromScriptingCommands'],
|
||||
'EVAL_RO' => [$this, 'getKeyFromScriptingCommands'],
|
||||
'EVALSHA_RO' => [$this, 'getKeyFromScriptingCommands'],
|
||||
|
||||
/* server */
|
||||
'INFO' => [$this, 'getFakeKey'],
|
||||
|
||||
/* commands performing geospatial operations */
|
||||
'GEOADD' => $getKeyFromFirstArgument,
|
||||
'GEOHASH' => $getKeyFromFirstArgument,
|
||||
'GEOPOS' => $getKeyFromFirstArgument,
|
||||
'GEODIST' => $getKeyFromFirstArgument,
|
||||
'GEORADIUS' => [$this, 'getKeyFromGeoradiusCommands'],
|
||||
'GEORADIUSBYMEMBER' => [$this, 'getKeyFromGeoradiusCommands'],
|
||||
|
||||
/* sharded pubsub */
|
||||
'SSUBSCRIBE' => $getKeyFromAllArguments,
|
||||
'SUNSUBSCRIBE' => [$this, 'getKeyFromSUnsubscribeCommand'],
|
||||
'SPUBLISH' => $getKeyFromFirstArgument,
|
||||
|
||||
/* cluster */
|
||||
'CLUSTER' => [$this, 'getFakeKey'],
|
||||
|
||||
/* control */
|
||||
'ACL' => [$this, 'getFakeKey'],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the list of IDs for the supported commands.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getSupportedCommands()
|
||||
{
|
||||
return array_keys($this->commands);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets an handler for the specified command ID.
|
||||
*
|
||||
* The signature of the callback must have a single parameter of type
|
||||
* Predis\Command\CommandInterface.
|
||||
*
|
||||
* When the callback argument is omitted or NULL, the previously associated
|
||||
* handler for the specified command ID is removed.
|
||||
*
|
||||
* @param string $commandID Command ID.
|
||||
* @param mixed $callback A valid callable object, or NULL to unset the handler.
|
||||
*
|
||||
* @throws InvalidArgumentException
|
||||
*/
|
||||
public function setCommandHandler($commandID, $callback = null)
|
||||
{
|
||||
$commandID = strtoupper($commandID);
|
||||
|
||||
if (!isset($callback)) {
|
||||
unset($this->commands[$commandID]);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (!is_callable($callback)) {
|
||||
throw new InvalidArgumentException(
|
||||
'The argument must be a callable object or NULL.'
|
||||
);
|
||||
}
|
||||
|
||||
$this->commands[$commandID] = $callback;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get fake key for commands with no key argument.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function getFakeKey(): string
|
||||
{
|
||||
return 'key';
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts the key from the first argument of a command instance.
|
||||
*
|
||||
* @param CommandInterface $command Command instance.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function getKeyFromFirstArgument(CommandInterface $command)
|
||||
{
|
||||
return $command->getArgument(0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts the key from a command with multiple keys only when all keys in
|
||||
* the arguments array produce the same hash.
|
||||
*
|
||||
* @param CommandInterface $command Command instance.
|
||||
*
|
||||
* @return string|null
|
||||
*/
|
||||
protected function getKeyFromAllArguments(CommandInterface $command)
|
||||
{
|
||||
$arguments = $command->getArguments();
|
||||
|
||||
if (!$this->checkSameSlotForKeys($arguments)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $arguments[0];
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts the key from a command with multiple keys only when all keys in
|
||||
* the arguments array produce the same hash.
|
||||
*
|
||||
* @param CommandInterface $command Command instance.
|
||||
*
|
||||
* @return string|null
|
||||
*/
|
||||
protected function getKeyFromInterleavedArguments(CommandInterface $command)
|
||||
{
|
||||
$arguments = $command->getArguments();
|
||||
$keys = [];
|
||||
|
||||
for ($i = 0; $i < count($arguments); $i += 2) {
|
||||
$keys[] = $arguments[$i];
|
||||
}
|
||||
|
||||
if (!$this->checkSameSlotForKeys($keys)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $arguments[0];
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts the key from SORT command.
|
||||
*
|
||||
* @param CommandInterface $command Command instance.
|
||||
*
|
||||
* @return string|null
|
||||
*/
|
||||
protected function getKeyFromSortCommand(CommandInterface $command)
|
||||
{
|
||||
$arguments = $command->getArguments();
|
||||
$firstKey = $arguments[0];
|
||||
|
||||
if (1 === $argc = count($arguments)) {
|
||||
return $firstKey;
|
||||
}
|
||||
|
||||
$keys = [$firstKey];
|
||||
|
||||
for ($i = 1; $i < $argc; ++$i) {
|
||||
if (strtoupper($arguments[$i]) === 'STORE') {
|
||||
$keys[] = $arguments[++$i];
|
||||
}
|
||||
}
|
||||
|
||||
if (!$this->checkSameSlotForKeys($keys)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $firstKey;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts the key from BLPOP and BRPOP commands.
|
||||
*
|
||||
* @param CommandInterface $command Command instance.
|
||||
*
|
||||
* @return string|null
|
||||
*/
|
||||
protected function getKeyFromBlockingListCommands(CommandInterface $command)
|
||||
{
|
||||
$arguments = $command->getArguments();
|
||||
|
||||
if (!$this->checkSameSlotForKeys(array_slice($arguments, 0, count($arguments) - 1))) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $arguments[0];
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts the key from BITOP command.
|
||||
*
|
||||
* @param CommandInterface $command Command instance.
|
||||
*
|
||||
* @return string|null
|
||||
*/
|
||||
protected function getKeyFromBitOp(CommandInterface $command)
|
||||
{
|
||||
$arguments = $command->getArguments();
|
||||
|
||||
if (!$this->checkSameSlotForKeys(array_slice($arguments, 1, count($arguments)))) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $arguments[1];
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts the key from GEORADIUS and GEORADIUSBYMEMBER commands.
|
||||
*
|
||||
* @param CommandInterface $command Command instance.
|
||||
*
|
||||
* @return string|null
|
||||
*/
|
||||
protected function getKeyFromGeoradiusCommands(CommandInterface $command)
|
||||
{
|
||||
$arguments = $command->getArguments();
|
||||
$argc = count($arguments);
|
||||
$startIndex = $command->getId() === 'GEORADIUS' ? 5 : 4;
|
||||
|
||||
if ($argc > $startIndex) {
|
||||
$keys = [$arguments[0]];
|
||||
|
||||
for ($i = $startIndex; $i < $argc; ++$i) {
|
||||
$argument = strtoupper($arguments[$i]);
|
||||
if ($argument === 'STORE' || $argument === 'STOREDIST') {
|
||||
$keys[] = $arguments[++$i];
|
||||
}
|
||||
}
|
||||
|
||||
if (!$this->checkSameSlotForKeys($keys)) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
return $arguments[0];
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts the key from ZINTERSTORE and ZUNIONSTORE commands.
|
||||
*
|
||||
* @param CommandInterface $command Command instance.
|
||||
*
|
||||
* @return string|null
|
||||
*/
|
||||
protected function getKeyFromZsetAggregationCommands(CommandInterface $command)
|
||||
{
|
||||
$arguments = $command->getArguments();
|
||||
$keys = array_merge([$arguments[0]], array_slice($arguments, 2, $arguments[1]));
|
||||
|
||||
if (!$this->checkSameSlotForKeys($keys)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $arguments[0];
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts key from SUNSUBSCRIBE command if it's given.
|
||||
*
|
||||
* @param CommandInterface $command
|
||||
* @return string
|
||||
*/
|
||||
protected function getKeyFromSUnsubscribeCommand(CommandInterface $command): ?string
|
||||
{
|
||||
$arguments = $command->getArguments();
|
||||
|
||||
// SUNSUBSCRIBE command could be called without arguments, so it doesn't matter on each node it will be called.
|
||||
if (empty($arguments)) {
|
||||
return 'fake';
|
||||
}
|
||||
|
||||
return $this->getKeyFromAllArguments($command);
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts the key from EVAL and EVALSHA commands.
|
||||
*
|
||||
* @param CommandInterface $command Command instance.
|
||||
*
|
||||
* @return string|null
|
||||
*/
|
||||
protected function getKeyFromScriptingCommands(CommandInterface $command)
|
||||
{
|
||||
$keys = $command instanceof ScriptCommand
|
||||
? $command->getKeys()
|
||||
: array_slice($args = $command->getArguments(), 2, $args[1]);
|
||||
|
||||
if (!$keys || !$this->checkSameSlotForKeys($keys)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $keys[0];
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getSlot(CommandInterface $command)
|
||||
{
|
||||
$slot = $command->getSlot();
|
||||
|
||||
if (!isset($slot) && isset($this->commands[$cmdID = $command->getId()])) {
|
||||
$key = call_user_func($this->commands[$cmdID], $command);
|
||||
|
||||
if (isset($key)) {
|
||||
$slot = $this->getSlotByKey($key);
|
||||
$command->setSlot($slot);
|
||||
}
|
||||
}
|
||||
|
||||
return $slot;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function checkSameSlotForKeys(array $keys): bool
|
||||
{
|
||||
if (!$count = count($keys)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$currentSlot = $this->getSlotByKey($keys[0]);
|
||||
|
||||
for ($i = 1; $i < $count; ++$i) {
|
||||
$nextSlot = $this->getSlotByKey($keys[$i]);
|
||||
|
||||
if ($currentSlot !== $nextSlot) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns only the hashable part of a key (delimited by "{...}"), or the
|
||||
* whole key if a key tag is not found in the string.
|
||||
*
|
||||
* @param string $key A key.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function extractKeyTag($key)
|
||||
{
|
||||
if (false !== $start = strpos($key, '{')) {
|
||||
if (false !== ($end = strpos($key, '}', $start)) && $end !== ++$start) {
|
||||
$key = substr($key, $start, $end - $start);
|
||||
}
|
||||
}
|
||||
|
||||
return $key;
|
||||
}
|
||||
}
|
||||
Vendored
+81
@@ -0,0 +1,81 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Predis package.
|
||||
*
|
||||
* (c) 2009-2020 Daniele Alessandri
|
||||
* (c) 2021-2025 Till Krüss
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Predis\Cluster\Distributor;
|
||||
|
||||
use Predis\Cluster\Hash\HashGeneratorInterface;
|
||||
|
||||
/**
|
||||
* A distributor implements the logic to automatically distribute keys among
|
||||
* several nodes for client-side sharding.
|
||||
*/
|
||||
interface DistributorInterface
|
||||
{
|
||||
/**
|
||||
* Adds a node to the distributor with an optional weight.
|
||||
*
|
||||
* @param mixed $node Node object.
|
||||
* @param int $weight Weight for the node.
|
||||
*/
|
||||
public function add($node, $weight = null);
|
||||
|
||||
/**
|
||||
* Removes a node from the distributor.
|
||||
*
|
||||
* @param mixed $node Node object.
|
||||
*/
|
||||
public function remove($node);
|
||||
|
||||
/**
|
||||
* Returns the corresponding slot of a node from the distributor using the
|
||||
* computed hash of a key.
|
||||
*
|
||||
* @param mixed $hash
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function getSlot($hash);
|
||||
|
||||
/**
|
||||
* Returns a node from the distributor using its assigned slot ID.
|
||||
*
|
||||
* @param mixed $slot
|
||||
*
|
||||
* @return mixed|null
|
||||
*/
|
||||
public function getBySlot($slot);
|
||||
|
||||
/**
|
||||
* Returns a node from the distributor using the computed hash of a key.
|
||||
*
|
||||
* @param mixed $hash
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function getByHash($hash);
|
||||
|
||||
/**
|
||||
* Returns a node from the distributor mapping to the specified value.
|
||||
*
|
||||
* @param string $value
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function get($value);
|
||||
|
||||
/**
|
||||
* Returns the underlying hash generator instance.
|
||||
*
|
||||
* @return HashGeneratorInterface
|
||||
*/
|
||||
public function getHashGenerator();
|
||||
}
|
||||
Vendored
+22
@@ -0,0 +1,22 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Predis package.
|
||||
*
|
||||
* (c) 2009-2020 Daniele Alessandri
|
||||
* (c) 2021-2025 Till Krüss
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Predis\Cluster\Distributor;
|
||||
|
||||
use Exception;
|
||||
|
||||
/**
|
||||
* Exception class that identifies empty rings.
|
||||
*/
|
||||
class EmptyRingException extends Exception
|
||||
{
|
||||
}
|
||||
+268
@@ -0,0 +1,268 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Predis package.
|
||||
*
|
||||
* (c) 2009-2020 Daniele Alessandri
|
||||
* (c) 2021-2025 Till Krüss
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Predis\Cluster\Distributor;
|
||||
|
||||
use Predis\Cluster\Hash\HashGeneratorInterface;
|
||||
|
||||
/**
|
||||
* This class implements an hashring-based distributor that uses the same
|
||||
* algorithm of memcache to distribute keys in a cluster using client-side
|
||||
* sharding.
|
||||
* @author Lorenzo Castelli <lcastelli@gmail.com>
|
||||
*/
|
||||
class HashRing implements DistributorInterface, HashGeneratorInterface
|
||||
{
|
||||
public const DEFAULT_REPLICAS = 128;
|
||||
public const DEFAULT_WEIGHT = 100;
|
||||
|
||||
private $ring;
|
||||
private $ringKeys;
|
||||
private $ringKeysCount;
|
||||
private $replicas;
|
||||
private $nodeHashCallback;
|
||||
private $nodes = [];
|
||||
|
||||
/**
|
||||
* @param int $replicas Number of replicas in the ring.
|
||||
* @param mixed $nodeHashCallback Callback returning a string used to calculate the hash of nodes.
|
||||
*/
|
||||
public function __construct($replicas = self::DEFAULT_REPLICAS, $nodeHashCallback = null)
|
||||
{
|
||||
$this->replicas = $replicas;
|
||||
$this->nodeHashCallback = $nodeHashCallback;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a node to the ring with an optional weight.
|
||||
*
|
||||
* @param mixed $node Node object.
|
||||
* @param int $weight Weight for the node.
|
||||
*/
|
||||
public function add($node, $weight = null)
|
||||
{
|
||||
// In case of collisions in the hashes of the nodes, the node added
|
||||
// last wins, thus the order in which nodes are added is significant.
|
||||
$this->nodes[] = [
|
||||
'object' => $node,
|
||||
'weight' => (int) $weight ?: $this::DEFAULT_WEIGHT,
|
||||
];
|
||||
|
||||
$this->reset();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function remove($node)
|
||||
{
|
||||
// A node is removed by resetting the ring so that it's recreated from
|
||||
// scratch, in order to reassign possible hashes with collisions to the
|
||||
// right node according to the order in which they were added in the
|
||||
// first place.
|
||||
for ($i = 0; $i < count($this->nodes); ++$i) {
|
||||
if ($this->nodes[$i]['object'] === $node) {
|
||||
array_splice($this->nodes, $i, 1);
|
||||
$this->reset();
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Resets the distributor.
|
||||
*/
|
||||
private function reset()
|
||||
{
|
||||
unset(
|
||||
$this->ring,
|
||||
$this->ringKeys,
|
||||
$this->ringKeysCount
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the initialization status of the distributor.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
private function isInitialized()
|
||||
{
|
||||
return isset($this->ringKeys);
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates the total weight of all the nodes in the distributor.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
private function computeTotalWeight()
|
||||
{
|
||||
$totalWeight = 0;
|
||||
|
||||
foreach ($this->nodes as $node) {
|
||||
$totalWeight += $node['weight'];
|
||||
}
|
||||
|
||||
return $totalWeight;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes the distributor.
|
||||
*/
|
||||
private function initialize()
|
||||
{
|
||||
if ($this->isInitialized()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!$this->nodes) {
|
||||
throw new EmptyRingException('Cannot initialize an empty hashring.');
|
||||
}
|
||||
|
||||
$this->ring = [];
|
||||
$totalWeight = $this->computeTotalWeight();
|
||||
$nodesCount = count($this->nodes);
|
||||
|
||||
foreach ($this->nodes as $node) {
|
||||
$weightRatio = $node['weight'] / $totalWeight;
|
||||
$this->addNodeToRing($this->ring, $node, $nodesCount, $this->replicas, $weightRatio);
|
||||
}
|
||||
|
||||
ksort($this->ring, SORT_NUMERIC);
|
||||
$this->ringKeys = array_keys($this->ring);
|
||||
$this->ringKeysCount = count($this->ringKeys);
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements the logic needed to add a node to the hashring.
|
||||
*
|
||||
* @param array $ring Source hashring.
|
||||
* @param mixed $node Node object to be added.
|
||||
* @param int $totalNodes Total number of nodes.
|
||||
* @param int $replicas Number of replicas in the ring.
|
||||
* @param float $weightRatio Weight ratio for the node.
|
||||
*/
|
||||
protected function addNodeToRing(&$ring, $node, $totalNodes, $replicas, $weightRatio)
|
||||
{
|
||||
$nodeObject = $node['object'];
|
||||
$nodeHash = $this->getNodeHash($nodeObject);
|
||||
$replicas = (int) round($weightRatio * $totalNodes * $replicas);
|
||||
|
||||
for ($i = 0; $i < $replicas; ++$i) {
|
||||
$key = $this->hash("$nodeHash:$i");
|
||||
$ring[$key] = $nodeObject;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function getNodeHash($nodeObject)
|
||||
{
|
||||
if (!isset($this->nodeHashCallback)) {
|
||||
return (string) $nodeObject;
|
||||
}
|
||||
|
||||
return call_user_func($this->nodeHashCallback, $nodeObject);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function hash($value)
|
||||
{
|
||||
return crc32($value);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getByHash($hash)
|
||||
{
|
||||
return $this->ring[$this->getSlot($hash)];
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getBySlot($slot)
|
||||
{
|
||||
$this->initialize();
|
||||
|
||||
if (isset($this->ring[$slot])) {
|
||||
return $this->ring[$slot];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getSlot($hash)
|
||||
{
|
||||
$this->initialize();
|
||||
|
||||
$ringKeys = $this->ringKeys;
|
||||
$upper = $this->ringKeysCount - 1;
|
||||
$lower = 0;
|
||||
|
||||
while ($lower <= $upper) {
|
||||
$index = ($lower + $upper) >> 1;
|
||||
$item = $ringKeys[$index];
|
||||
|
||||
if ($item > $hash) {
|
||||
$upper = $index - 1;
|
||||
} elseif ($item < $hash) {
|
||||
$lower = $index + 1;
|
||||
} else {
|
||||
return $item;
|
||||
}
|
||||
}
|
||||
|
||||
return $ringKeys[$this->wrapAroundStrategy($upper, $lower, $this->ringKeysCount)];
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function get($value)
|
||||
{
|
||||
$hash = $this->hash($value);
|
||||
|
||||
return $this->getByHash($hash);
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements a strategy to deal with wrap-around errors during binary searches.
|
||||
*
|
||||
* @param int $upper
|
||||
* @param int $lower
|
||||
* @param int $ringKeysCount
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
protected function wrapAroundStrategy($upper, $lower, $ringKeysCount)
|
||||
{
|
||||
// Binary search for the last item in ringkeys with a value less or
|
||||
// equal to the key. If no such item exists, return the last item.
|
||||
return $upper >= 0 ? $upper : $ringKeysCount - 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getHashGenerator()
|
||||
{
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
+70
@@ -0,0 +1,70 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Predis package.
|
||||
*
|
||||
* (c) 2009-2020 Daniele Alessandri
|
||||
* (c) 2021-2025 Till Krüss
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Predis\Cluster\Distributor;
|
||||
|
||||
/**
|
||||
* This class implements an hashring-based distributor that uses the same
|
||||
* algorithm of libketama to distribute keys in a cluster using client-side
|
||||
* sharding.
|
||||
* @author Lorenzo Castelli <lcastelli@gmail.com>
|
||||
*/
|
||||
class KetamaRing extends HashRing
|
||||
{
|
||||
public const DEFAULT_REPLICAS = 160;
|
||||
|
||||
/**
|
||||
* @param mixed $nodeHashCallback Callback returning a string used to calculate the hash of nodes.
|
||||
*/
|
||||
public function __construct($nodeHashCallback = null)
|
||||
{
|
||||
parent::__construct($this::DEFAULT_REPLICAS, $nodeHashCallback);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function addNodeToRing(&$ring, $node, $totalNodes, $replicas, $weightRatio)
|
||||
{
|
||||
$nodeObject = $node['object'];
|
||||
$nodeHash = $this->getNodeHash($nodeObject);
|
||||
$replicas = (int) floor($weightRatio * $totalNodes * ($replicas / 4));
|
||||
|
||||
for ($i = 0; $i < $replicas; ++$i) {
|
||||
$unpackedDigest = unpack('V4', md5("$nodeHash-$i", true));
|
||||
|
||||
foreach ($unpackedDigest as $key) {
|
||||
$ring[$key] = $nodeObject;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function hash($value)
|
||||
{
|
||||
$hash = unpack('V', md5($value, true));
|
||||
|
||||
return $hash[1];
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function wrapAroundStrategy($upper, $lower, $ringKeysCount)
|
||||
{
|
||||
// Binary search for the first item in ringkeys with a value greater
|
||||
// or equal to the key. If no such item exists, return the first item.
|
||||
return $lower < $ringKeysCount ? $lower : 0;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Predis package.
|
||||
*
|
||||
* (c) 2009-2020 Daniele Alessandri
|
||||
* (c) 2021-2025 Till Krüss
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Predis\Cluster\Hash;
|
||||
|
||||
/**
|
||||
* Hash generator implementing the CRC-CCITT-16 algorithm used by redis-cluster.
|
||||
*/
|
||||
class CRC16 implements HashGeneratorInterface
|
||||
{
|
||||
private static $CCITT_16 = [
|
||||
0x0000, 0x1021, 0x2042, 0x3063, 0x4084, 0x50A5, 0x60C6, 0x70E7,
|
||||
0x8108, 0x9129, 0xA14A, 0xB16B, 0xC18C, 0xD1AD, 0xE1CE, 0xF1EF,
|
||||
0x1231, 0x0210, 0x3273, 0x2252, 0x52B5, 0x4294, 0x72F7, 0x62D6,
|
||||
0x9339, 0x8318, 0xB37B, 0xA35A, 0xD3BD, 0xC39C, 0xF3FF, 0xE3DE,
|
||||
0x2462, 0x3443, 0x0420, 0x1401, 0x64E6, 0x74C7, 0x44A4, 0x5485,
|
||||
0xA56A, 0xB54B, 0x8528, 0x9509, 0xE5EE, 0xF5CF, 0xC5AC, 0xD58D,
|
||||
0x3653, 0x2672, 0x1611, 0x0630, 0x76D7, 0x66F6, 0x5695, 0x46B4,
|
||||
0xB75B, 0xA77A, 0x9719, 0x8738, 0xF7DF, 0xE7FE, 0xD79D, 0xC7BC,
|
||||
0x48C4, 0x58E5, 0x6886, 0x78A7, 0x0840, 0x1861, 0x2802, 0x3823,
|
||||
0xC9CC, 0xD9ED, 0xE98E, 0xF9AF, 0x8948, 0x9969, 0xA90A, 0xB92B,
|
||||
0x5AF5, 0x4AD4, 0x7AB7, 0x6A96, 0x1A71, 0x0A50, 0x3A33, 0x2A12,
|
||||
0xDBFD, 0xCBDC, 0xFBBF, 0xEB9E, 0x9B79, 0x8B58, 0xBB3B, 0xAB1A,
|
||||
0x6CA6, 0x7C87, 0x4CE4, 0x5CC5, 0x2C22, 0x3C03, 0x0C60, 0x1C41,
|
||||
0xEDAE, 0xFD8F, 0xCDEC, 0xDDCD, 0xAD2A, 0xBD0B, 0x8D68, 0x9D49,
|
||||
0x7E97, 0x6EB6, 0x5ED5, 0x4EF4, 0x3E13, 0x2E32, 0x1E51, 0x0E70,
|
||||
0xFF9F, 0xEFBE, 0xDFDD, 0xCFFC, 0xBF1B, 0xAF3A, 0x9F59, 0x8F78,
|
||||
0x9188, 0x81A9, 0xB1CA, 0xA1EB, 0xD10C, 0xC12D, 0xF14E, 0xE16F,
|
||||
0x1080, 0x00A1, 0x30C2, 0x20E3, 0x5004, 0x4025, 0x7046, 0x6067,
|
||||
0x83B9, 0x9398, 0xA3FB, 0xB3DA, 0xC33D, 0xD31C, 0xE37F, 0xF35E,
|
||||
0x02B1, 0x1290, 0x22F3, 0x32D2, 0x4235, 0x5214, 0x6277, 0x7256,
|
||||
0xB5EA, 0xA5CB, 0x95A8, 0x8589, 0xF56E, 0xE54F, 0xD52C, 0xC50D,
|
||||
0x34E2, 0x24C3, 0x14A0, 0x0481, 0x7466, 0x6447, 0x5424, 0x4405,
|
||||
0xA7DB, 0xB7FA, 0x8799, 0x97B8, 0xE75F, 0xF77E, 0xC71D, 0xD73C,
|
||||
0x26D3, 0x36F2, 0x0691, 0x16B0, 0x6657, 0x7676, 0x4615, 0x5634,
|
||||
0xD94C, 0xC96D, 0xF90E, 0xE92F, 0x99C8, 0x89E9, 0xB98A, 0xA9AB,
|
||||
0x5844, 0x4865, 0x7806, 0x6827, 0x18C0, 0x08E1, 0x3882, 0x28A3,
|
||||
0xCB7D, 0xDB5C, 0xEB3F, 0xFB1E, 0x8BF9, 0x9BD8, 0xABBB, 0xBB9A,
|
||||
0x4A75, 0x5A54, 0x6A37, 0x7A16, 0x0AF1, 0x1AD0, 0x2AB3, 0x3A92,
|
||||
0xFD2E, 0xED0F, 0xDD6C, 0xCD4D, 0xBDAA, 0xAD8B, 0x9DE8, 0x8DC9,
|
||||
0x7C26, 0x6C07, 0x5C64, 0x4C45, 0x3CA2, 0x2C83, 0x1CE0, 0x0CC1,
|
||||
0xEF1F, 0xFF3E, 0xCF5D, 0xDF7C, 0xAF9B, 0xBFBA, 0x8FD9, 0x9FF8,
|
||||
0x6E17, 0x7E36, 0x4E55, 0x5E74, 0x2E93, 0x3EB2, 0x0ED1, 0x1EF0,
|
||||
];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function hash($value)
|
||||
{
|
||||
// CRC-CCITT-16 algorithm
|
||||
$crc = 0;
|
||||
$CCITT_16 = self::$CCITT_16;
|
||||
|
||||
$value = (string) $value;
|
||||
$strlen = strlen($value);
|
||||
|
||||
for ($i = 0; $i < $strlen; ++$i) {
|
||||
$crc = (($crc << 8) ^ $CCITT_16[($crc >> 8) ^ ord($value[$i])]) & 0xFFFF;
|
||||
}
|
||||
|
||||
return $crc;
|
||||
}
|
||||
}
|
||||
Vendored
+29
@@ -0,0 +1,29 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Predis package.
|
||||
*
|
||||
* (c) 2009-2020 Daniele Alessandri
|
||||
* (c) 2021-2025 Till Krüss
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Predis\Cluster\Hash;
|
||||
|
||||
/**
|
||||
* An hash generator implements the logic used to calculate the hash of a key to
|
||||
* distribute operations among Redis nodes.
|
||||
*/
|
||||
interface HashGeneratorInterface
|
||||
{
|
||||
/**
|
||||
* Generates an hash from a string to be used for distribution.
|
||||
*
|
||||
* @param string $value String value.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function hash($value);
|
||||
}
|
||||
+40
@@ -0,0 +1,40 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Predis package.
|
||||
*
|
||||
* (c) 2009-2020 Daniele Alessandri
|
||||
* (c) 2021-2025 Till Krüss
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Predis\Cluster;
|
||||
|
||||
/**
|
||||
* Represents the gap between slot ranges.
|
||||
*/
|
||||
class NullSlotRange extends SlotRange
|
||||
{
|
||||
public function __construct(int $start, int $end)
|
||||
{
|
||||
parent::__construct($start, $end, '');
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function toArray(): array
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function count(): int
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
+75
@@ -0,0 +1,75 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Predis package.
|
||||
*
|
||||
* (c) 2009-2020 Daniele Alessandri
|
||||
* (c) 2021-2025 Till Krüss
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Predis\Cluster;
|
||||
|
||||
use Predis\Cluster\Distributor\DistributorInterface;
|
||||
use Predis\Cluster\Distributor\HashRing;
|
||||
|
||||
/**
|
||||
* Default cluster strategy used by Predis to handle client-side sharding.
|
||||
*/
|
||||
class PredisStrategy extends ClusterStrategy
|
||||
{
|
||||
protected $distributor;
|
||||
|
||||
/**
|
||||
* @param DistributorInterface|null $distributor Optional distributor instance.
|
||||
*/
|
||||
public function __construct(?DistributorInterface $distributor = null)
|
||||
{
|
||||
parent::__construct();
|
||||
|
||||
$this->distributor = $distributor ?: new HashRing();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getSlotByKey($key)
|
||||
{
|
||||
$key = $this->extractKeyTag($key);
|
||||
$hash = $this->distributor->hash($key);
|
||||
|
||||
return $this->distributor->getSlot($hash);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function checkSameSlotForKeys(array $keys): bool
|
||||
{
|
||||
if (!$count = count($keys)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$currentKey = $this->extractKeyTag($keys[0]);
|
||||
|
||||
for ($i = 1; $i < $count; ++$i) {
|
||||
$nextKey = $this->extractKeyTag($keys[$i]);
|
||||
|
||||
if ($currentKey !== $nextKey) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getDistributor()
|
||||
{
|
||||
return $this->distributor;
|
||||
}
|
||||
}
|
||||
+55
@@ -0,0 +1,55 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Predis package.
|
||||
*
|
||||
* (c) 2009-2020 Daniele Alessandri
|
||||
* (c) 2021-2025 Till Krüss
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Predis\Cluster;
|
||||
|
||||
use Predis\Cluster\Hash\CRC16;
|
||||
use Predis\Cluster\Hash\HashGeneratorInterface;
|
||||
use Predis\NotSupportedException;
|
||||
|
||||
/**
|
||||
* Default class used by Predis to calculate hashes out of keys of
|
||||
* commands supported by redis-cluster.
|
||||
*/
|
||||
class RedisStrategy extends ClusterStrategy
|
||||
{
|
||||
protected $hashGenerator;
|
||||
|
||||
/**
|
||||
* @param HashGeneratorInterface|null $hashGenerator Hash generator instance.
|
||||
*/
|
||||
public function __construct(?HashGeneratorInterface $hashGenerator = null)
|
||||
{
|
||||
parent::__construct();
|
||||
|
||||
$this->hashGenerator = $hashGenerator ?: new CRC16();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getSlotByKey($key)
|
||||
{
|
||||
$key = $this->extractKeyTag($key);
|
||||
|
||||
return $this->hashGenerator->hash($key) & 0x3FFF;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getDistributor()
|
||||
{
|
||||
$class = get_class($this);
|
||||
throw new NotSupportedException("$class does not provide an external distributor");
|
||||
}
|
||||
}
|
||||
+209
@@ -0,0 +1,209 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Predis package.
|
||||
*
|
||||
* (c) 2009-2020 Daniele Alessandri
|
||||
* (c) 2021-2025 Till Krüss
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Predis\Cluster;
|
||||
|
||||
use ArrayAccess;
|
||||
use ArrayIterator;
|
||||
use Countable;
|
||||
use IteratorAggregate;
|
||||
use OutOfBoundsException;
|
||||
use Predis\Connection\NodeConnectionInterface;
|
||||
use ReturnTypeWillChange;
|
||||
use Traversable;
|
||||
|
||||
/**
|
||||
* Slot map for redis-cluster.
|
||||
*/
|
||||
class SimpleSlotMap implements ArrayAccess, IteratorAggregate, Countable
|
||||
{
|
||||
private $slots = [];
|
||||
|
||||
/**
|
||||
* Checks if the given slot is valid.
|
||||
*
|
||||
* @param int $slot Slot index.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public static function isValid($slot)
|
||||
{
|
||||
return $slot >= 0x0000 && $slot <= 0x3FFF;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the given slot range is valid.
|
||||
*
|
||||
* @param int $first Initial slot of the range.
|
||||
* @param int $last Last slot of the range.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public static function isValidRange($first, $last)
|
||||
{
|
||||
return $first >= 0x0000 && $first <= 0x3FFF && $last >= 0x0000 && $last <= 0x3FFF && $first <= $last;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resets the slot map.
|
||||
*/
|
||||
public function reset()
|
||||
{
|
||||
$this->slots = [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the slot map is empty.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isEmpty()
|
||||
{
|
||||
return empty($this->slots);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the current slot map as a dictionary of $slot => $node.
|
||||
*
|
||||
* The order of the slots in the dictionary is not guaranteed.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function toArray()
|
||||
{
|
||||
return $this->slots;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the list of unique nodes in the slot map.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getNodes()
|
||||
{
|
||||
return array_keys(array_flip($this->slots));
|
||||
}
|
||||
|
||||
/**
|
||||
* Assigns the specified slot range to a node.
|
||||
*
|
||||
* @param int $first Initial slot of the range.
|
||||
* @param int $last Last slot of the range.
|
||||
* @param NodeConnectionInterface|string $connection ID or connection instance.
|
||||
*
|
||||
* @throws OutOfBoundsException
|
||||
*/
|
||||
public function setSlots($first, $last, $connection)
|
||||
{
|
||||
if (!static::isValidRange($first, $last)) {
|
||||
throw new OutOfBoundsException("Invalid slot range $first-$last for `$connection`");
|
||||
}
|
||||
|
||||
$this->slots += array_fill($first, $last - $first + 1, (string) $connection);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the specified slot range.
|
||||
*
|
||||
* @param int $first Initial slot of the range.
|
||||
* @param int $last Last slot of the range.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getSlots($first, $last)
|
||||
{
|
||||
if (!static::isValidRange($first, $last)) {
|
||||
throw new OutOfBoundsException("Invalid slot range $first-$last");
|
||||
}
|
||||
|
||||
return array_intersect_key($this->slots, array_fill($first, $last - $first + 1, null));
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the specified slot is assigned.
|
||||
*
|
||||
* @param int $slot Slot index.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
#[ReturnTypeWillChange]
|
||||
public function offsetExists($slot)
|
||||
{
|
||||
return isset($this->slots[$slot]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the node assigned to the specified slot.
|
||||
*
|
||||
* @param int $slot Slot index.
|
||||
*
|
||||
* @return string|null
|
||||
*/
|
||||
#[ReturnTypeWillChange]
|
||||
public function offsetGet($slot)
|
||||
{
|
||||
return $this->slots[$slot] ?? null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Assigns the specified slot to a node.
|
||||
*
|
||||
* @param int $slot Slot index.
|
||||
* @param NodeConnectionInterface|string $connection ID or connection instance.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
#[ReturnTypeWillChange]
|
||||
public function offsetSet($slot, $connection)
|
||||
{
|
||||
if (!static::isValid($slot)) {
|
||||
throw new OutOfBoundsException("Invalid slot $slot for `$connection`");
|
||||
}
|
||||
|
||||
$this->slots[(int) $slot] = (string) $connection;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the node assigned to the specified slot.
|
||||
*
|
||||
* @param int $slot Slot index.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
#[ReturnTypeWillChange]
|
||||
public function offsetUnset($slot)
|
||||
{
|
||||
unset($this->slots[$slot]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the current number of assigned slots.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
#[ReturnTypeWillChange]
|
||||
public function count()
|
||||
{
|
||||
return count($this->slots);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an iterator over the slot map.
|
||||
*
|
||||
* @return Traversable<int, string>
|
||||
*/
|
||||
#[ReturnTypeWillChange]
|
||||
public function getIterator()
|
||||
{
|
||||
return new ArrayIterator($this->slots);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,417 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Predis package.
|
||||
*
|
||||
* (c) 2009-2020 Daniele Alessandri
|
||||
* (c) 2021-2025 Till Krüss
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Predis\Cluster;
|
||||
|
||||
use ArrayAccess;
|
||||
use ArrayIterator;
|
||||
use Countable;
|
||||
use IteratorAggregate;
|
||||
use OutOfBoundsException;
|
||||
use Predis\Connection\NodeConnectionInterface;
|
||||
use ReturnTypeWillChange;
|
||||
use Traversable;
|
||||
|
||||
/**
|
||||
* Compact slot map for redis-cluster.
|
||||
*/
|
||||
class SlotMap implements ArrayAccess, IteratorAggregate, Countable
|
||||
{
|
||||
/**
|
||||
* Slot ranges list.
|
||||
*
|
||||
* @var SlotRange[]
|
||||
*/
|
||||
private $slotRanges = [];
|
||||
|
||||
/**
|
||||
* Checks if the given slot is valid.
|
||||
*
|
||||
* @param int $slot Slot index.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public static function isValid($slot)
|
||||
{
|
||||
return $slot >= 0 && $slot <= SlotRange::MAX_SLOTS;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the given slot range is valid.
|
||||
*
|
||||
* @param int $first Initial slot of the range.
|
||||
* @param int $last Last slot of the range.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public static function isValidRange($first, $last)
|
||||
{
|
||||
return SlotRange::isValidRange($first, $last);
|
||||
}
|
||||
|
||||
/**
|
||||
* Resets the slot map.
|
||||
*/
|
||||
public function reset()
|
||||
{
|
||||
$this->slotRanges = [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the slot map is empty.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isEmpty()
|
||||
{
|
||||
return empty($this->slotRanges);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the current slot map as a dictionary of $slot => $node.
|
||||
*
|
||||
* The order of the slots in the dictionary is not guaranteed.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function toArray()
|
||||
{
|
||||
return array_reduce(
|
||||
$this->slotRanges,
|
||||
function ($carry, $slotRange) {
|
||||
return $carry + $slotRange->toArray();
|
||||
},
|
||||
[]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the list of unique nodes in the slot map.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getNodes()
|
||||
{
|
||||
return array_unique(array_map(
|
||||
function ($slotRange) {
|
||||
return $slotRange->getConnection();
|
||||
},
|
||||
$this->slotRanges
|
||||
));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the list of slot ranges.
|
||||
*
|
||||
* @return SlotRange[]
|
||||
*/
|
||||
public function getSlotRanges()
|
||||
{
|
||||
return $this->slotRanges;
|
||||
}
|
||||
|
||||
/**
|
||||
* Assigns the specified slot range to a node.
|
||||
*
|
||||
* @param int $first Initial slot of the range.
|
||||
* @param int $last Last slot of the range.
|
||||
* @param NodeConnectionInterface|string $connection ID or connection instance.
|
||||
*
|
||||
* @throws OutOfBoundsException
|
||||
*/
|
||||
public function setSlots($first, $last, $connection)
|
||||
{
|
||||
if (!static::isValidRange($first, $last)) {
|
||||
throw new OutOfBoundsException("Invalid slot range $first-$last for `$connection`");
|
||||
}
|
||||
|
||||
$targetSlotRange = new SlotRange($first, $last, (string) $connection);
|
||||
|
||||
// Get gaps of slot ranges list.
|
||||
$gaps = $this->getGaps($this->slotRanges);
|
||||
|
||||
$results = $this->slotRanges;
|
||||
|
||||
foreach ($gaps as $gap) {
|
||||
if (!$gap->hasIntersectionWith($targetSlotRange)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Get intersection of the gap and target slot range.
|
||||
$results[] = new SlotRange(
|
||||
max($gap->getStart(), $targetSlotRange->getStart()),
|
||||
min($gap->getEnd(), $targetSlotRange->getEnd()),
|
||||
$targetSlotRange->getConnection()
|
||||
);
|
||||
}
|
||||
|
||||
$this->sortSlotRanges($results);
|
||||
|
||||
$results = $this->compactSlotRanges($results);
|
||||
|
||||
$this->slotRanges = $results;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the specified slot range.
|
||||
*
|
||||
* @param int $first Initial slot of the range.
|
||||
* @param int $last Last slot of the range.
|
||||
*
|
||||
* @return array<int, string>
|
||||
*/
|
||||
public function getSlots($first, $last)
|
||||
{
|
||||
if (!static::isValidRange($first, $last)) {
|
||||
throw new OutOfBoundsException("Invalid slot range $first-$last");
|
||||
}
|
||||
|
||||
$placeHolder = new NullSlotRange($first, $last);
|
||||
|
||||
$intersections = [];
|
||||
foreach ($this->slotRanges as $slotRange) {
|
||||
if (!$placeHolder->hasIntersectionWith($slotRange)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$intersections[] = new SlotRange(
|
||||
max($placeHolder->getStart(), $slotRange->getStart()),
|
||||
min($placeHolder->getEnd(), $slotRange->getEnd()),
|
||||
$slotRange->getConnection()
|
||||
);
|
||||
}
|
||||
|
||||
return array_reduce(
|
||||
$intersections,
|
||||
function ($carry, $slotRange) {
|
||||
return $carry + $slotRange->toArray();
|
||||
},
|
||||
[]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the specified slot is assigned.
|
||||
*
|
||||
* @param int $slot Slot index.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
#[ReturnTypeWillChange]
|
||||
public function offsetExists($slot)
|
||||
{
|
||||
return $this->findRangeBySlot($slot) !== false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the node assigned to the specified slot.
|
||||
*
|
||||
* @param int $slot Slot index.
|
||||
*
|
||||
* @return string|null
|
||||
*/
|
||||
#[ReturnTypeWillChange]
|
||||
public function offsetGet($slot)
|
||||
{
|
||||
$found = $this->findRangeBySlot($slot);
|
||||
|
||||
return $found ? $found->getConnection() : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Assigns the specified slot to a node.
|
||||
*
|
||||
* @param int $slot Slot index.
|
||||
* @param NodeConnectionInterface|string $connection ID or connection instance.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
#[ReturnTypeWillChange]
|
||||
public function offsetSet($slot, $connection)
|
||||
{
|
||||
if (!static::isValid($slot)) {
|
||||
throw new OutOfBoundsException("Invalid slot $slot for `$connection`");
|
||||
}
|
||||
|
||||
$this->offsetUnset($slot);
|
||||
$this->setSlots($slot, $slot, $connection);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the node assigned to the specified slot.
|
||||
*
|
||||
* @param int $slot Slot index.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
#[ReturnTypeWillChange]
|
||||
public function offsetUnset($slot)
|
||||
{
|
||||
if (!static::isValid($slot)) {
|
||||
throw new OutOfBoundsException("Invalid slot $slot");
|
||||
}
|
||||
|
||||
$results = [];
|
||||
foreach ($this->slotRanges as $slotRange) {
|
||||
if (!$slotRange->hasSlot($slot)) {
|
||||
$results[] = $slotRange;
|
||||
}
|
||||
|
||||
if (static::isValidRange($slotRange->getStart(), $slot - 1)) {
|
||||
$results[] = new SlotRange($slotRange->getStart(), $slot - 1, $slotRange->getConnection());
|
||||
}
|
||||
|
||||
if (static::isValidRange($slot + 1, $slotRange->getEnd())) {
|
||||
$results[] = new SlotRange($slot + 1, $slotRange->getEnd(), $slotRange->getConnection());
|
||||
}
|
||||
}
|
||||
|
||||
$this->slotRanges = $results;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the current number of assigned slots.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
#[ReturnTypeWillChange]
|
||||
public function count()
|
||||
{
|
||||
return array_sum(array_map(
|
||||
function ($slotRange) {
|
||||
return $slotRange->count();
|
||||
},
|
||||
$this->slotRanges
|
||||
));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an iterator over the slot map.
|
||||
*
|
||||
* @return Traversable<int, string>
|
||||
*/
|
||||
#[ReturnTypeWillChange]
|
||||
public function getIterator()
|
||||
{
|
||||
return new ArrayIterator($this->toArray());
|
||||
}
|
||||
|
||||
/**
|
||||
* Find the slot range which contains the specific slot index.
|
||||
*
|
||||
* @param int $slot Slot index.
|
||||
*
|
||||
* @return SlotRange|false The slot range object or false if not found.
|
||||
*/
|
||||
protected function findRangeBySlot(int $slot)
|
||||
{
|
||||
foreach ($this->slotRanges as $slotRange) {
|
||||
if ($slotRange->hasSlot($slot)) {
|
||||
return $slotRange;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get gaps between sorted slot ranges with NullSlotRange object.
|
||||
*
|
||||
* @param SlotRange[] $slotRanges
|
||||
*
|
||||
* @return SlotRange[]
|
||||
*/
|
||||
protected function getGaps(array $slotRanges)
|
||||
{
|
||||
if (empty($slotRanges)) {
|
||||
return [
|
||||
new NullSlotRange(0, SlotRange::MAX_SLOTS),
|
||||
];
|
||||
}
|
||||
$gaps = [];
|
||||
$count = count($slotRanges);
|
||||
$i = 0;
|
||||
foreach ($slotRanges as $key => $slotRange) {
|
||||
$start = $slotRange->getStart();
|
||||
$end = $slotRange->getEnd();
|
||||
if (static::isValidRange($i, $start - 1)) {
|
||||
$gaps[] = new NullSlotRange($i, $start - 1);
|
||||
}
|
||||
|
||||
$i = $end + 1;
|
||||
|
||||
if ($key === $count - 1) {
|
||||
if (static::isValidRange($i, SlotRange::MAX_SLOTS)) {
|
||||
$gaps[] = new NullSlotRange($i, SlotRange::MAX_SLOTS);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $gaps;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sort slot ranges by start index.
|
||||
*
|
||||
* @param SlotRange[] $slotRanges
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function sortSlotRanges(array &$slotRanges)
|
||||
{
|
||||
usort(
|
||||
$slotRanges,
|
||||
function (SlotRange $a, SlotRange $b) {
|
||||
if ($a->getStart() == $b->getStart()) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return $a->getStart() < $b->getStart() ? -1 : 1;
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Compact adjacent slot ranges with the same connection.
|
||||
*
|
||||
* @param SlotRange[] $slotRanges
|
||||
*
|
||||
* @return SlotRange[]
|
||||
*/
|
||||
protected function compactSlotRanges(array $slotRanges)
|
||||
{
|
||||
if (empty($slotRanges)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$compacted = [];
|
||||
$count = count($slotRanges);
|
||||
$i = 0;
|
||||
$carry = $slotRanges[0];
|
||||
while ($i < $count) {
|
||||
$next = $slotRanges[$i + 1] ?? null;
|
||||
if (
|
||||
!is_null($next)
|
||||
&& ($carry->getEnd() + 1) === $next->getStart()
|
||||
&& $carry->getConnection() === $next->getConnection()
|
||||
) {
|
||||
$carry = new SlotRange($carry->getStart(), $next->getEnd(), $carry->getConnection());
|
||||
} else {
|
||||
$compacted[] = $carry;
|
||||
$carry = $next;
|
||||
}
|
||||
$i++;
|
||||
}
|
||||
|
||||
return array_values($compacted);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,145 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Predis package.
|
||||
*
|
||||
* (c) 2009-2020 Daniele Alessandri
|
||||
* (c) 2021-2025 Till Krüss
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Predis\Cluster;
|
||||
|
||||
use Countable;
|
||||
use OutOfBoundsException;
|
||||
|
||||
/**
|
||||
* Represents a range of slots in a Redis cluster.
|
||||
*/
|
||||
class SlotRange implements Countable
|
||||
{
|
||||
/**
|
||||
* Maximum number of slots in a Redis cluster is 16384.
|
||||
*/
|
||||
public const MAX_SLOTS = 0x3FFF;
|
||||
|
||||
/**
|
||||
* Starting slot of the range.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
protected $start;
|
||||
|
||||
/**
|
||||
* Ending slot of the range.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
protected $end;
|
||||
|
||||
/**
|
||||
* Connection to the server hosting this slot range.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $connection;
|
||||
|
||||
public function __construct(int $start, int $end, string $connection)
|
||||
{
|
||||
if (!static::isValidRange($start, $end)) {
|
||||
throw new OutOfBoundsException("Invalid slot range $start-$end for `$connection`");
|
||||
}
|
||||
$this->start = $start;
|
||||
$this->end = $end;
|
||||
$this->connection = $connection;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a slot range is valid.
|
||||
*
|
||||
* @param int $first
|
||||
* @param int $last
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public static function isValidRange($first, $last)
|
||||
{
|
||||
return $first >= 0x0000 && $first <= self::MAX_SLOTS && $last >= 0x0000 && $last <= self::MAX_SLOTS && $first <= $last;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the start slot index of this range.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function getStart()
|
||||
{
|
||||
return $this->start;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the end slot index of this range.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function getEnd()
|
||||
{
|
||||
return $this->end;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the connection to the server hosting this slot range.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getConnection()
|
||||
{
|
||||
return $this->connection;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the specific slot is contained in this range.
|
||||
*
|
||||
* @param int $slot
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function hasSlot(int $slot)
|
||||
{
|
||||
return $this->start <= $slot && $this->end >= $slot;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an array of connection strings for each slot in this range.
|
||||
*
|
||||
* @return string[]
|
||||
*/
|
||||
public function toArray(): array
|
||||
{
|
||||
return array_fill($this->start, $this->end - $this->start + 1, $this->connection);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the number of slots in this range.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function count(): int
|
||||
{
|
||||
return $this->end - $this->start + 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if this range has an intersection with the given slot range.
|
||||
*
|
||||
* @param SlotRange $slotRange
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function hasIntersectionWith(SlotRange $slotRange): bool
|
||||
{
|
||||
return $this->start <= $slotRange->getEnd() && $this->end >= $slotRange->getStart();
|
||||
}
|
||||
}
|
||||
+60
@@ -0,0 +1,60 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Predis package.
|
||||
*
|
||||
* (c) 2009-2020 Daniele Alessandri
|
||||
* (c) 2021-2025 Till Krüss
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Predis\Cluster;
|
||||
|
||||
use Predis\Cluster\Distributor\DistributorInterface;
|
||||
use Predis\Command\CommandInterface;
|
||||
|
||||
/**
|
||||
* Interface for classes defining the strategy used to calculate an hash out of
|
||||
* keys extracted from supported commands.
|
||||
*
|
||||
* This is mostly useful to support clustering via client-side sharding.
|
||||
*/
|
||||
interface StrategyInterface
|
||||
{
|
||||
/**
|
||||
* Returns a slot for the given command used for clustering distribution or
|
||||
* NULL when this is not possible.
|
||||
*
|
||||
* @param CommandInterface $command Command instance.
|
||||
*
|
||||
* @return int|null
|
||||
*/
|
||||
public function getSlot(CommandInterface $command);
|
||||
|
||||
/**
|
||||
* Returns a slot for the given key used for clustering distribution or NULL
|
||||
* when this is not possible.
|
||||
*
|
||||
* @param string $key Key string.
|
||||
*
|
||||
* @return int|null
|
||||
*/
|
||||
public function getSlotByKey($key);
|
||||
|
||||
/**
|
||||
* Returns a distributor instance to be used by the cluster.
|
||||
*
|
||||
* @return DistributorInterface
|
||||
*/
|
||||
public function getDistributor();
|
||||
|
||||
/**
|
||||
* Checks if the specified array of keys will generate the same hash.
|
||||
*
|
||||
* @param array $keys
|
||||
* @return bool
|
||||
*/
|
||||
public function checkSameSlotForKeys(array $keys): bool;
|
||||
}
|
||||
Vendored
+196
@@ -0,0 +1,196 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Predis package.
|
||||
*
|
||||
* (c) 2009-2020 Daniele Alessandri
|
||||
* (c) 2021-2025 Till Krüss
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Predis\Collection\Iterator;
|
||||
|
||||
use Iterator;
|
||||
use Predis\ClientInterface;
|
||||
use Predis\NotSupportedException;
|
||||
use ReturnTypeWillChange;
|
||||
|
||||
/**
|
||||
* Provides the base implementation for a fully-rewindable PHP iterator that can
|
||||
* incrementally iterate over cursor-based collections stored on Redis using the
|
||||
* commands in the `SCAN` family.
|
||||
*
|
||||
* Given their incremental nature with multiple fetches, these kind of iterators
|
||||
* offer limited guarantees about the returned elements because the collection
|
||||
* can change several times during the iteration process.
|
||||
*
|
||||
* @see http://redis.io/commands/scan
|
||||
*/
|
||||
abstract class CursorBasedIterator implements Iterator
|
||||
{
|
||||
protected $client;
|
||||
protected $match;
|
||||
protected $count;
|
||||
|
||||
protected $valid;
|
||||
protected $fetchmore;
|
||||
protected $elements;
|
||||
protected $cursor;
|
||||
protected $position;
|
||||
protected $current;
|
||||
|
||||
/**
|
||||
* @param ClientInterface $client Client connected to Redis.
|
||||
* @param string $match Pattern to match during the server-side iteration.
|
||||
* @param int $count Hint used by Redis to compute the number of results per iteration.
|
||||
*/
|
||||
public function __construct(ClientInterface $client, $match = null, $count = null)
|
||||
{
|
||||
$this->client = $client;
|
||||
$this->match = $match;
|
||||
$this->count = $count;
|
||||
|
||||
$this->reset();
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensures that the client supports the specified Redis command required to
|
||||
* fetch elements from the server to perform the iteration.
|
||||
*
|
||||
* @param ClientInterface $client Client connected to Redis.
|
||||
* @param string $commandID Command ID.
|
||||
*
|
||||
* @throws NotSupportedException
|
||||
*/
|
||||
protected function requiredCommand(ClientInterface $client, $commandID)
|
||||
{
|
||||
if (!$client->getCommandFactory()->supports($commandID)) {
|
||||
throw new NotSupportedException("'$commandID' is not supported by the current command factory.");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Resets the inner state of the iterator.
|
||||
*/
|
||||
protected function reset()
|
||||
{
|
||||
$this->valid = true;
|
||||
$this->fetchmore = true;
|
||||
$this->elements = [];
|
||||
$this->cursor = 0;
|
||||
$this->position = -1;
|
||||
$this->current = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an array of options for the `SCAN` command.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function getScanOptions()
|
||||
{
|
||||
$options = [];
|
||||
|
||||
if (strlen(strval($this->match)) > 0) {
|
||||
$options['MATCH'] = $this->match;
|
||||
}
|
||||
|
||||
if ($this->count > 0) {
|
||||
$options['COUNT'] = $this->count;
|
||||
}
|
||||
|
||||
return $options;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches a new set of elements from the remote collection, effectively
|
||||
* advancing the iteration process.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
abstract protected function executeCommand();
|
||||
|
||||
/**
|
||||
* Populates the local buffer of elements fetched from the server during
|
||||
* the iteration.
|
||||
*/
|
||||
protected function fetch()
|
||||
{
|
||||
[$cursor, $elements] = $this->executeCommand();
|
||||
|
||||
if (!$cursor) {
|
||||
$this->fetchmore = false;
|
||||
}
|
||||
|
||||
$this->cursor = $cursor;
|
||||
$this->elements = $elements;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts next values for key() and current().
|
||||
*/
|
||||
protected function extractNext()
|
||||
{
|
||||
++$this->position;
|
||||
$this->current = array_shift($this->elements);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return void
|
||||
*/
|
||||
#[ReturnTypeWillChange]
|
||||
public function rewind()
|
||||
{
|
||||
$this->reset();
|
||||
$this->next();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return mixed
|
||||
*/
|
||||
#[ReturnTypeWillChange]
|
||||
public function current()
|
||||
{
|
||||
return $this->current;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int|null
|
||||
*/
|
||||
#[ReturnTypeWillChange]
|
||||
public function key()
|
||||
{
|
||||
return $this->position;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return void
|
||||
*/
|
||||
#[ReturnTypeWillChange]
|
||||
public function next()
|
||||
{
|
||||
tryFetch:
|
||||
if (!$this->elements && $this->fetchmore) {
|
||||
$this->fetch();
|
||||
}
|
||||
|
||||
if ($this->elements) {
|
||||
$this->extractNext();
|
||||
} elseif ($this->cursor) {
|
||||
goto tryFetch;
|
||||
} else {
|
||||
$this->valid = false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
#[ReturnTypeWillChange]
|
||||
public function valid()
|
||||
{
|
||||
return $this->valid;
|
||||
}
|
||||
}
|
||||
+57
@@ -0,0 +1,57 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Predis package.
|
||||
*
|
||||
* (c) 2009-2020 Daniele Alessandri
|
||||
* (c) 2021-2025 Till Krüss
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Predis\Collection\Iterator;
|
||||
|
||||
use Predis\ClientInterface;
|
||||
|
||||
/**
|
||||
* Abstracts the iteration of fields and values of an hash by leveraging the
|
||||
* HSCAN command (Redis >= 2.8) wrapped in a fully-rewindable PHP iterator.
|
||||
*
|
||||
* @see http://redis.io/commands/scan
|
||||
*/
|
||||
class HashKey extends CursorBasedIterator
|
||||
{
|
||||
protected $key;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function __construct(ClientInterface $client, $key, $match = null, $count = null)
|
||||
{
|
||||
$this->requiredCommand($client, 'HSCAN');
|
||||
|
||||
parent::__construct($client, $match, $count);
|
||||
|
||||
$this->key = $key;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function executeCommand()
|
||||
{
|
||||
return $this->client->hscan($this->key, $this->cursor, $this->getScanOptions());
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function extractNext()
|
||||
{
|
||||
$this->position = key($this->elements);
|
||||
$this->current = current($this->elements);
|
||||
|
||||
unset($this->elements[$this->position]);
|
||||
}
|
||||
}
|
||||
+42
@@ -0,0 +1,42 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Predis package.
|
||||
*
|
||||
* (c) 2009-2020 Daniele Alessandri
|
||||
* (c) 2021-2025 Till Krüss
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Predis\Collection\Iterator;
|
||||
|
||||
use Predis\ClientInterface;
|
||||
|
||||
/**
|
||||
* Abstracts the iteration of the keyspace on a Redis instance by leveraging the
|
||||
* SCAN command (Redis >= 2.8) wrapped in a fully-rewindable PHP iterator.
|
||||
*
|
||||
* @see http://redis.io/commands/scan
|
||||
*/
|
||||
class Keyspace extends CursorBasedIterator
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function __construct(ClientInterface $client, $match = null, $count = null)
|
||||
{
|
||||
$this->requiredCommand($client, 'SCAN');
|
||||
|
||||
parent::__construct($client, $match, $count);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function executeCommand()
|
||||
{
|
||||
return $this->client->scan($this->cursor, $this->getScanOptions());
|
||||
}
|
||||
}
|
||||
+183
@@ -0,0 +1,183 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Predis package.
|
||||
*
|
||||
* (c) 2009-2020 Daniele Alessandri
|
||||
* (c) 2021-2025 Till Krüss
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Predis\Collection\Iterator;
|
||||
|
||||
use InvalidArgumentException;
|
||||
use Iterator;
|
||||
use Predis\ClientInterface;
|
||||
use Predis\NotSupportedException;
|
||||
use ReturnTypeWillChange;
|
||||
|
||||
/**
|
||||
* Abstracts the iteration of items stored in a list by leveraging the LRANGE
|
||||
* command wrapped in a fully-rewindable PHP iterator.
|
||||
*
|
||||
* This iterator tries to emulate the behaviour of cursor-based iterators based
|
||||
* on the SCAN-family of commands introduced in Redis <= 2.8, meaning that due
|
||||
* to its incremental nature with multiple fetches it can only offer limited
|
||||
* guarantees on the returned elements because the collection can change several
|
||||
* times (trimmed, deleted, overwritten) during the iteration process.
|
||||
*
|
||||
* @see http://redis.io/commands/lrange
|
||||
*/
|
||||
class ListKey implements Iterator
|
||||
{
|
||||
protected $client;
|
||||
protected $count;
|
||||
protected $key;
|
||||
|
||||
protected $valid;
|
||||
protected $fetchmore;
|
||||
protected $elements;
|
||||
protected $position;
|
||||
protected $current;
|
||||
|
||||
/**
|
||||
* @param ClientInterface $client Client connected to Redis.
|
||||
* @param string $key Redis list key.
|
||||
* @param int $count Number of items retrieved on each fetch operation.
|
||||
*
|
||||
* @throws InvalidArgumentException
|
||||
*/
|
||||
public function __construct(ClientInterface $client, $key, $count = 10)
|
||||
{
|
||||
$this->requiredCommand($client, 'LRANGE');
|
||||
|
||||
if ((false === $count = filter_var($count, FILTER_VALIDATE_INT)) || $count < 0) {
|
||||
throw new InvalidArgumentException('The $count argument must be a positive integer.');
|
||||
}
|
||||
|
||||
$this->client = $client;
|
||||
$this->key = $key;
|
||||
$this->count = $count;
|
||||
|
||||
$this->reset();
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensures that the client instance supports the specified Redis command
|
||||
* required to fetch elements from the server to perform the iteration.
|
||||
*
|
||||
* @param ClientInterface $client Client connected to Redis.
|
||||
* @param string $commandID Command ID.
|
||||
*
|
||||
* @throws NotSupportedException
|
||||
*/
|
||||
protected function requiredCommand(ClientInterface $client, $commandID)
|
||||
{
|
||||
if (!$client->getCommandFactory()->supports($commandID)) {
|
||||
throw new NotSupportedException("'$commandID' is not supported by the current command factory.");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Resets the inner state of the iterator.
|
||||
*/
|
||||
protected function reset()
|
||||
{
|
||||
$this->valid = true;
|
||||
$this->fetchmore = true;
|
||||
$this->elements = [];
|
||||
$this->position = -1;
|
||||
$this->current = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches a new set of elements from the remote collection, effectively
|
||||
* advancing the iteration process.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function executeCommand()
|
||||
{
|
||||
return $this->client->lrange($this->key, $this->position + 1, $this->position + $this->count);
|
||||
}
|
||||
|
||||
/**
|
||||
* Populates the local buffer of elements fetched from the server during the
|
||||
* iteration.
|
||||
*/
|
||||
protected function fetch()
|
||||
{
|
||||
$elements = $this->executeCommand();
|
||||
|
||||
if (count($elements) < $this->count) {
|
||||
$this->fetchmore = false;
|
||||
}
|
||||
|
||||
$this->elements = $elements;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts next values for key() and current().
|
||||
*/
|
||||
protected function extractNext()
|
||||
{
|
||||
++$this->position;
|
||||
$this->current = array_shift($this->elements);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return void
|
||||
*/
|
||||
#[ReturnTypeWillChange]
|
||||
public function rewind()
|
||||
{
|
||||
$this->reset();
|
||||
$this->next();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return mixed
|
||||
*/
|
||||
#[ReturnTypeWillChange]
|
||||
public function current()
|
||||
{
|
||||
return $this->current;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int|null
|
||||
*/
|
||||
#[ReturnTypeWillChange]
|
||||
public function key()
|
||||
{
|
||||
return $this->position;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return void
|
||||
*/
|
||||
#[ReturnTypeWillChange]
|
||||
public function next()
|
||||
{
|
||||
if (!$this->elements && $this->fetchmore) {
|
||||
$this->fetch();
|
||||
}
|
||||
|
||||
if ($this->elements) {
|
||||
$this->extractNext();
|
||||
} else {
|
||||
$this->valid = false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
#[ReturnTypeWillChange]
|
||||
public function valid()
|
||||
{
|
||||
return $this->valid;
|
||||
}
|
||||
}
|
||||
+46
@@ -0,0 +1,46 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Predis package.
|
||||
*
|
||||
* (c) 2009-2020 Daniele Alessandri
|
||||
* (c) 2021-2025 Till Krüss
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Predis\Collection\Iterator;
|
||||
|
||||
use Predis\ClientInterface;
|
||||
|
||||
/**
|
||||
* Abstracts the iteration of members stored in a set by leveraging the SSCAN
|
||||
* command (Redis >= 2.8) wrapped in a fully-rewindable PHP iterator.
|
||||
*
|
||||
* @see http://redis.io/commands/scan
|
||||
*/
|
||||
class SetKey extends CursorBasedIterator
|
||||
{
|
||||
protected $key;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function __construct(ClientInterface $client, $key, $match = null, $count = null)
|
||||
{
|
||||
$this->requiredCommand($client, 'SSCAN');
|
||||
|
||||
parent::__construct($client, $match, $count);
|
||||
|
||||
$this->key = $key;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function executeCommand()
|
||||
{
|
||||
return $this->client->sscan($this->key, $this->cursor, $this->getScanOptions());
|
||||
}
|
||||
}
|
||||
+57
@@ -0,0 +1,57 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Predis package.
|
||||
*
|
||||
* (c) 2009-2020 Daniele Alessandri
|
||||
* (c) 2021-2025 Till Krüss
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Predis\Collection\Iterator;
|
||||
|
||||
use Predis\ClientInterface;
|
||||
|
||||
/**
|
||||
* Abstracts the iteration of members stored in a sorted set by leveraging the
|
||||
* ZSCAN command (Redis >= 2.8) wrapped in a fully-rewindable PHP iterator.
|
||||
*
|
||||
* @see http://redis.io/commands/scan
|
||||
*/
|
||||
class SortedSetKey extends CursorBasedIterator
|
||||
{
|
||||
protected $key;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function __construct(ClientInterface $client, $key, $match = null, $count = null)
|
||||
{
|
||||
$this->requiredCommand($client, 'ZSCAN');
|
||||
|
||||
parent::__construct($client, $match, $count);
|
||||
|
||||
$this->key = $key;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function executeCommand()
|
||||
{
|
||||
return $this->client->zscan($this->key, $this->cursor, $this->getScanOptions());
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function extractNext()
|
||||
{
|
||||
$this->position = key($this->elements);
|
||||
$this->current = current($this->elements);
|
||||
|
||||
unset($this->elements[$this->position]);
|
||||
}
|
||||
}
|
||||
Vendored
+26
@@ -0,0 +1,26 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Predis package.
|
||||
*
|
||||
* (c) 2009-2020 Daniele Alessandri
|
||||
* (c) 2021-2025 Till Krüss
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Predis\Command\Argument;
|
||||
|
||||
/**
|
||||
* Allows to use object-oriented approach to handle complex conditional arguments.
|
||||
*/
|
||||
interface ArrayableArgument
|
||||
{
|
||||
/**
|
||||
* Get the instance as an array.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function toArray(): array;
|
||||
}
|
||||
Vendored
+46
@@ -0,0 +1,46 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Predis package.
|
||||
*
|
||||
* (c) 2009-2020 Daniele Alessandri
|
||||
* (c) 2021-2025 Till Krüss
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Predis\Command\Argument\Geospatial;
|
||||
|
||||
use UnexpectedValueException;
|
||||
|
||||
abstract class AbstractBy implements ByInterface
|
||||
{
|
||||
/**
|
||||
* @var string[]
|
||||
*/
|
||||
private static $unitEnum = ['m', 'km', 'ft', 'mi'];
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $unit;
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
abstract public function toArray(): array;
|
||||
|
||||
/**
|
||||
* @param string $unit
|
||||
* @return void
|
||||
*/
|
||||
protected function setUnit(string $unit): void
|
||||
{
|
||||
if (!in_array($unit, self::$unitEnum, true)) {
|
||||
throw new UnexpectedValueException('Wrong value given for unit');
|
||||
}
|
||||
|
||||
$this->unit = $unit;
|
||||
}
|
||||
}
|
||||
Vendored
+43
@@ -0,0 +1,43 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Predis package.
|
||||
*
|
||||
* (c) 2009-2020 Daniele Alessandri
|
||||
* (c) 2021-2025 Till Krüss
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Predis\Command\Argument\Geospatial;
|
||||
|
||||
class ByBox extends AbstractBy
|
||||
{
|
||||
private const KEYWORD = 'BYBOX';
|
||||
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
private $width;
|
||||
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
private $height;
|
||||
|
||||
public function __construct(int $width, int $height, string $unit)
|
||||
{
|
||||
$this->width = $width;
|
||||
$this->height = $height;
|
||||
$this->setUnit($unit);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function toArray(): array
|
||||
{
|
||||
return [self::KEYWORD, $this->width, $this->height, $this->unit];
|
||||
}
|
||||
}
|
||||
Vendored
+19
@@ -0,0 +1,19 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Predis package.
|
||||
*
|
||||
* (c) 2009-2020 Daniele Alessandri
|
||||
* (c) 2021-2025 Till Krüss
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Predis\Command\Argument\Geospatial;
|
||||
|
||||
use Predis\Command\Argument\ArrayableArgument;
|
||||
|
||||
interface ByInterface extends ArrayableArgument
|
||||
{
|
||||
}
|
||||
Vendored
+37
@@ -0,0 +1,37 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Predis package.
|
||||
*
|
||||
* (c) 2009-2020 Daniele Alessandri
|
||||
* (c) 2021-2025 Till Krüss
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Predis\Command\Argument\Geospatial;
|
||||
|
||||
class ByRadius extends AbstractBy
|
||||
{
|
||||
private const KEYWORD = 'BYRADIUS';
|
||||
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
private $radius;
|
||||
|
||||
public function __construct(int $radius, string $unit)
|
||||
{
|
||||
$this->radius = $radius;
|
||||
$this->setUnit($unit);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function toArray(): array
|
||||
{
|
||||
return [self::KEYWORD, $this->radius, $this->unit];
|
||||
}
|
||||
}
|
||||
Vendored
+19
@@ -0,0 +1,19 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Predis package.
|
||||
*
|
||||
* (c) 2009-2020 Daniele Alessandri
|
||||
* (c) 2021-2025 Till Krüss
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Predis\Command\Argument\Geospatial;
|
||||
|
||||
use Predis\Command\Argument\ArrayableArgument;
|
||||
|
||||
interface FromInterface extends ArrayableArgument
|
||||
{
|
||||
}
|
||||
Vendored
+42
@@ -0,0 +1,42 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Predis package.
|
||||
*
|
||||
* (c) 2009-2020 Daniele Alessandri
|
||||
* (c) 2021-2025 Till Krüss
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Predis\Command\Argument\Geospatial;
|
||||
|
||||
class FromLonLat implements FromInterface
|
||||
{
|
||||
private const KEYWORD = 'FROMLONLAT';
|
||||
|
||||
/**
|
||||
* @var float
|
||||
*/
|
||||
private $longitude;
|
||||
|
||||
/**
|
||||
* @var float
|
||||
*/
|
||||
private $latitude;
|
||||
|
||||
public function __construct(float $longitude, float $latitude)
|
||||
{
|
||||
$this->longitude = $longitude;
|
||||
$this->latitude = $latitude;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function toArray(): array
|
||||
{
|
||||
return [self::KEYWORD, $this->longitude, $this->latitude];
|
||||
}
|
||||
}
|
||||
Vendored
+36
@@ -0,0 +1,36 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Predis package.
|
||||
*
|
||||
* (c) 2009-2020 Daniele Alessandri
|
||||
* (c) 2021-2025 Till Krüss
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Predis\Command\Argument\Geospatial;
|
||||
|
||||
class FromMember implements FromInterface
|
||||
{
|
||||
private const KEYWORD = 'FROMMEMBER';
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $member;
|
||||
|
||||
public function __construct(string $member)
|
||||
{
|
||||
$this->member = $member;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function toArray(): array
|
||||
{
|
||||
return [self::KEYWORD, $this->member];
|
||||
}
|
||||
}
|
||||
vendor/claudecio/krothiumapi/vendor/predis/predis/src/Command/Argument/Search/AggregateArguments.php
Vendored
+161
@@ -0,0 +1,161 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Predis package.
|
||||
*
|
||||
* (c) 2009-2020 Daniele Alessandri
|
||||
* (c) 2021-2025 Till Krüss
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Predis\Command\Argument\Search;
|
||||
|
||||
class AggregateArguments extends CommonArguments
|
||||
{
|
||||
/**
|
||||
* @var string[]
|
||||
*/
|
||||
private $sortingEnum = [
|
||||
'asc' => 'ASC',
|
||||
'desc' => 'DESC',
|
||||
];
|
||||
|
||||
/**
|
||||
* Loads document attributes from the source document.
|
||||
*
|
||||
* @param string ...$fields Could be just '*' to load all fields
|
||||
* @return $this
|
||||
*/
|
||||
public function load(string ...$fields): self
|
||||
{
|
||||
$arguments = func_get_args();
|
||||
|
||||
$this->arguments[] = 'LOAD';
|
||||
|
||||
if ($arguments[0] === '*') {
|
||||
$this->arguments[] = '*';
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
$this->arguments[] = count($arguments);
|
||||
$this->arguments = array_merge($this->arguments, $arguments);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads document attributes from the source document.
|
||||
*
|
||||
* @param string ...$properties
|
||||
* @return $this
|
||||
*/
|
||||
public function groupBy(string ...$properties): self
|
||||
{
|
||||
$arguments = func_get_args();
|
||||
|
||||
array_push($this->arguments, 'GROUPBY', count($arguments));
|
||||
$this->arguments = array_merge($this->arguments, $arguments);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Groups the results in the pipeline based on one or more properties.
|
||||
*
|
||||
* If you want to add alias property to your argument just add "true" value in arguments enumeration,
|
||||
* next value will be considered as alias to previous one.
|
||||
*
|
||||
* Example: 'argument', true, 'name' => 'argument' AS 'name'
|
||||
*
|
||||
* @param string $function
|
||||
* @param string|bool ...$argument
|
||||
* @return $this
|
||||
*/
|
||||
public function reduce(string $function, ...$argument): self
|
||||
{
|
||||
$arguments = func_get_args();
|
||||
$functionValue = array_shift($arguments);
|
||||
$argumentsCounter = 0;
|
||||
|
||||
for ($i = 0, $iMax = count($arguments); $i < $iMax; $i++) {
|
||||
if (true === $arguments[$i]) {
|
||||
$arguments[$i] = 'AS';
|
||||
$i++;
|
||||
continue;
|
||||
}
|
||||
|
||||
$argumentsCounter++;
|
||||
}
|
||||
|
||||
array_push($this->arguments, 'REDUCE', $functionValue);
|
||||
$this->arguments = array_merge($this->arguments, [$argumentsCounter], $arguments);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sorts the pipeline up until the point of SORTBY, using a list of properties.
|
||||
*
|
||||
* @param int $max
|
||||
* @param string ...$properties Enumeration of properties, including sorting direction (ASC, DESC)
|
||||
* @return $this
|
||||
*/
|
||||
public function sortBy(int $max = 0, ...$properties): self
|
||||
{
|
||||
$arguments = func_get_args();
|
||||
$maxValue = array_shift($arguments);
|
||||
|
||||
$this->arguments[] = 'SORTBY';
|
||||
$this->arguments = array_merge($this->arguments, [count($arguments)], $arguments);
|
||||
|
||||
if ($maxValue !== 0) {
|
||||
array_push($this->arguments, 'MAX', $maxValue);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Applies a 1-to-1 transformation on one or more properties and either stores the result
|
||||
* as a new property down the pipeline or replaces any property using this transformation.
|
||||
*
|
||||
* @param string $expression
|
||||
* @param string $as
|
||||
* @return $this
|
||||
*/
|
||||
public function apply(string $expression, string $as = ''): self
|
||||
{
|
||||
array_push($this->arguments, 'APPLY', $expression);
|
||||
|
||||
if ($as !== '') {
|
||||
array_push($this->arguments, 'AS', $as);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Scan part of the results with a quicker alternative than LIMIT.
|
||||
*
|
||||
* @param int $readSize
|
||||
* @param int $idleTime
|
||||
* @return $this
|
||||
*/
|
||||
public function withCursor(int $readSize = 0, int $idleTime = 0): self
|
||||
{
|
||||
$this->arguments[] = 'WITHCURSOR';
|
||||
|
||||
if ($readSize !== 0) {
|
||||
array_push($this->arguments, 'COUNT', $readSize);
|
||||
}
|
||||
|
||||
if ($idleTime !== 0) {
|
||||
array_push($this->arguments, 'MAXIDLE', $idleTime);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
Vendored
+17
@@ -0,0 +1,17 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Predis package.
|
||||
*
|
||||
* (c) 2009-2020 Daniele Alessandri
|
||||
* (c) 2021-2025 Till Krüss
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Predis\Command\Argument\Search;
|
||||
|
||||
class AlterArguments extends CommonArguments
|
||||
{
|
||||
}
|
||||
Vendored
+182
@@ -0,0 +1,182 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Predis package.
|
||||
*
|
||||
* (c) 2009-2020 Daniele Alessandri
|
||||
* (c) 2021-2025 Till Krüss
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Predis\Command\Argument\Search;
|
||||
|
||||
use Predis\Command\Argument\ArrayableArgument;
|
||||
|
||||
class CommonArguments implements ArrayableArgument
|
||||
{
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $arguments = [];
|
||||
|
||||
/**
|
||||
* Adds default language for documents within an index.
|
||||
*
|
||||
* @param string $defaultLanguage
|
||||
* @return $this
|
||||
*/
|
||||
public function language(string $defaultLanguage = 'english'): self
|
||||
{
|
||||
$this->arguments[] = 'LANGUAGE';
|
||||
$this->arguments[] = $defaultLanguage;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Selects the dialect version under which to execute the query.
|
||||
* If not specified, the query will execute under the default dialect version
|
||||
* set during module initial loading or via FT.CONFIG SET command.
|
||||
*
|
||||
* @param string $dialect
|
||||
* @return $this
|
||||
*/
|
||||
public function dialect(string $dialect): self
|
||||
{
|
||||
$this->arguments[] = 'DIALECT';
|
||||
$this->arguments[] = $dialect;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* If set, does not scan and index.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function skipInitialScan(): self
|
||||
{
|
||||
$this->arguments[] = 'SKIPINITIALSCAN';
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds an arbitrary, binary safe payload that is exposed to custom scoring functions.
|
||||
*
|
||||
* @param string $payload
|
||||
* @return $this
|
||||
*/
|
||||
public function payload(string $payload): self
|
||||
{
|
||||
$this->arguments[] = 'PAYLOAD';
|
||||
$this->arguments[] = $payload;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Also returns the relative internal score of each document.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function withScores(): self
|
||||
{
|
||||
$this->arguments[] = 'WITHSCORES';
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves optional document payloads.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function withPayloads(): self
|
||||
{
|
||||
$this->arguments[] = 'WITHPAYLOADS';
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Does not try to use stemming for query expansion but searches the query terms verbatim.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function verbatim(): self
|
||||
{
|
||||
$this->arguments[] = 'VERBATIM';
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Overrides the timeout parameter of the module.
|
||||
*
|
||||
* @param int $timeout
|
||||
* @return $this
|
||||
*/
|
||||
public function timeout(int $timeout): self
|
||||
{
|
||||
$this->arguments[] = 'TIMEOUT';
|
||||
$this->arguments[] = $timeout;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds an arbitrary, binary safe payload that is exposed to custom scoring functions.
|
||||
*
|
||||
* @param int $offset
|
||||
* @param int $num
|
||||
* @return $this
|
||||
*/
|
||||
public function limit(int $offset, int $num): self
|
||||
{
|
||||
array_push($this->arguments, 'LIMIT', $offset, $num);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds filter expression into index.
|
||||
*
|
||||
* @param string $filter
|
||||
* @return $this
|
||||
*/
|
||||
public function filter(string $filter): self
|
||||
{
|
||||
$this->arguments[] = 'FILTER';
|
||||
$this->arguments[] = $filter;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Defines one or more value parameters. Each parameter has a name and a value.
|
||||
*
|
||||
* Example: ['name1', 'value1', 'name2', 'value2'...]
|
||||
*
|
||||
* @param array $nameValuesDictionary
|
||||
* @return $this
|
||||
*/
|
||||
public function params(array $nameValuesDictionary): self
|
||||
{
|
||||
$this->arguments[] = 'PARAMS';
|
||||
$this->arguments[] = count($nameValuesDictionary);
|
||||
$this->arguments = array_merge($this->arguments, $nameValuesDictionary);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function toArray(): array
|
||||
{
|
||||
return $this->arguments;
|
||||
}
|
||||
}
|
||||
Vendored
+191
@@ -0,0 +1,191 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Predis package.
|
||||
*
|
||||
* (c) 2009-2020 Daniele Alessandri
|
||||
* (c) 2021-2025 Till Krüss
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Predis\Command\Argument\Search;
|
||||
|
||||
use InvalidArgumentException;
|
||||
|
||||
class CreateArguments extends CommonArguments
|
||||
{
|
||||
/**
|
||||
* @var string[]
|
||||
*/
|
||||
private $supportedDataTypesEnum = [
|
||||
'hash' => 'HASH',
|
||||
'json' => 'JSON',
|
||||
];
|
||||
|
||||
/**
|
||||
* Specify data type for given index. To index JSON you must have the RedisJSON module to be installed.
|
||||
*
|
||||
* @param string $modifier
|
||||
* @return $this
|
||||
*/
|
||||
public function on(string $modifier = 'HASH'): self
|
||||
{
|
||||
if (in_array(strtoupper($modifier), $this->supportedDataTypesEnum)) {
|
||||
$this->arguments[] = 'ON';
|
||||
$this->arguments[] = $this->supportedDataTypesEnum[strtolower($modifier)];
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
$enumValues = implode(', ', array_values($this->supportedDataTypesEnum));
|
||||
throw new InvalidArgumentException("Wrong modifier value given. Currently supports: {$enumValues}");
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds one or more prefixes into index.
|
||||
*
|
||||
* @param array $prefixes
|
||||
* @return $this
|
||||
*/
|
||||
public function prefix(array $prefixes): self
|
||||
{
|
||||
$this->arguments[] = 'PREFIX';
|
||||
$this->arguments[] = count($prefixes);
|
||||
$this->arguments = array_merge($this->arguments, $prefixes);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Document attribute set as document language.
|
||||
*
|
||||
* @param string $languageAttribute
|
||||
* @return $this
|
||||
*/
|
||||
public function languageField(string $languageAttribute): self
|
||||
{
|
||||
$this->arguments[] = 'LANGUAGE_FIELD';
|
||||
$this->arguments[] = $languageAttribute;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Default score for documents in the index.
|
||||
*
|
||||
* @param float $defaultScore
|
||||
* @return $this
|
||||
*/
|
||||
public function score(float $defaultScore = 1.0): self
|
||||
{
|
||||
$this->arguments[] = 'SCORE';
|
||||
$this->arguments[] = $defaultScore;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Document attribute that used as the document rank based on the user ranking.
|
||||
*
|
||||
* @param string $scoreAttribute
|
||||
* @return $this
|
||||
*/
|
||||
public function scoreField(string $scoreAttribute): self
|
||||
{
|
||||
$this->arguments[] = 'SCORE_FIELD';
|
||||
$this->arguments[] = $scoreAttribute;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Forces RediSearch to encode indexes as if there were more than 32 text attributes.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function maxTextFields(): self
|
||||
{
|
||||
$this->arguments[] = 'MAXTEXTFIELDS';
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Does not store term offsets for documents.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function noOffsets(): self
|
||||
{
|
||||
$this->arguments[] = 'NOOFFSETS';
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a lightweight temporary index that expires after a specified period of inactivity, in seconds.
|
||||
*
|
||||
* @param int $seconds
|
||||
* @return $this
|
||||
*/
|
||||
public function temporary(int $seconds): self
|
||||
{
|
||||
$this->arguments[] = 'TEMPORARY';
|
||||
$this->arguments[] = $seconds;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Conserves storage space and memory by disabling highlighting support.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function noHl(): self
|
||||
{
|
||||
$this->arguments[] = 'NOHL';
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Does not store attribute bits for each term.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function noFields(): self
|
||||
{
|
||||
$this->arguments[] = 'NOFIELDS';
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Avoids saving the term frequencies in the index.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function noFreqs(): self
|
||||
{
|
||||
$this->arguments[] = 'NOFREQS';
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the index with a custom stopword list, to be ignored during indexing and search time.
|
||||
*
|
||||
* @param array $stopWords
|
||||
* @return $this
|
||||
*/
|
||||
public function stopWords(array $stopWords): self
|
||||
{
|
||||
$this->arguments[] = 'STOPWORDS';
|
||||
$this->arguments[] = count($stopWords);
|
||||
$this->arguments = array_merge($this->arguments, $stopWords);
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
Vendored
+44
@@ -0,0 +1,44 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Predis package.
|
||||
*
|
||||
* (c) 2009-2020 Daniele Alessandri
|
||||
* (c) 2021-2025 Till Krüss
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Predis\Command\Argument\Search;
|
||||
|
||||
use Predis\Command\Argument\ArrayableArgument;
|
||||
|
||||
class CursorArguments implements ArrayableArgument
|
||||
{
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $arguments = [];
|
||||
|
||||
/**
|
||||
* Is number of results to read. This parameter overrides COUNT specified in FT.AGGREGATE.
|
||||
*
|
||||
* @param int $readSize
|
||||
* @return $this
|
||||
*/
|
||||
public function count(int $readSize): self
|
||||
{
|
||||
array_push($this->arguments, 'COUNT', $readSize);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function toArray(): array
|
||||
{
|
||||
return $this->arguments;
|
||||
}
|
||||
}
|
||||
Vendored
+43
@@ -0,0 +1,43 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Predis package.
|
||||
*
|
||||
* (c) 2009-2020 Daniele Alessandri
|
||||
* (c) 2021-2025 Till Krüss
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Predis\Command\Argument\Search;
|
||||
|
||||
use Predis\Command\Argument\ArrayableArgument;
|
||||
|
||||
class DropArguments implements ArrayableArgument
|
||||
{
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $arguments = [];
|
||||
|
||||
/**
|
||||
* Drop operation that, if set, deletes the actual document hashes.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function dd(): self
|
||||
{
|
||||
$this->arguments[] = 'DD';
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function toArray(): array
|
||||
{
|
||||
return $this->arguments;
|
||||
}
|
||||
}
|
||||
Vendored
+17
@@ -0,0 +1,17 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Predis package.
|
||||
*
|
||||
* (c) 2009-2020 Daniele Alessandri
|
||||
* (c) 2021-2025 Till Krüss
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Predis\Command\Argument\Search;
|
||||
|
||||
class ExplainArguments extends CommonArguments
|
||||
{
|
||||
}
|
||||
+44
@@ -0,0 +1,44 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Predis package.
|
||||
*
|
||||
* (c) 2009-2020 Daniele Alessandri
|
||||
* (c) 2021-2025 Till Krüss
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Predis\Command\Argument\Search\HybridSearch\Combine;
|
||||
|
||||
use Predis\Command\Argument\ArrayableArgument;
|
||||
|
||||
abstract class BaseCombine implements ArrayableArgument
|
||||
{
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $arguments = ['COMBINE'];
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $as = [];
|
||||
|
||||
/**
|
||||
* @param string $alias
|
||||
* @return $this
|
||||
*/
|
||||
public function as(string $alias): self
|
||||
{
|
||||
array_push($this->as, 'YIELD_SCORE_AS', $alias);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
abstract public function toArray(): array;
|
||||
}
|
||||
+79
@@ -0,0 +1,79 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Predis package.
|
||||
*
|
||||
* (c) 2009-2020 Daniele Alessandri
|
||||
* (c) 2021-2025 Till Krüss
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Predis\Command\Argument\Search\HybridSearch\Combine;
|
||||
|
||||
class LinearCombineConfig extends BaseCombine
|
||||
{
|
||||
/**
|
||||
* @var float
|
||||
*/
|
||||
protected $alpha;
|
||||
|
||||
/**
|
||||
* @var float
|
||||
*/
|
||||
protected $beta;
|
||||
|
||||
/**
|
||||
* The weight for the text score (a value between 0 and 1).
|
||||
*
|
||||
* @param float $alpha
|
||||
* @return $this
|
||||
*/
|
||||
public function alpha(float $alpha): self
|
||||
{
|
||||
$this->alpha = $alpha;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* The weight for the vector score (a value between 0 and 1).
|
||||
*
|
||||
* @param float $beta
|
||||
* @return $this
|
||||
*/
|
||||
public function beta(float $beta): self
|
||||
{
|
||||
$this->beta = $beta;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function toArray(): array
|
||||
{
|
||||
$this->arguments[] = 'LINEAR';
|
||||
$tokens = [];
|
||||
|
||||
if ($this->alpha !== null) {
|
||||
array_push($tokens, 'ALPHA', $this->alpha);
|
||||
}
|
||||
|
||||
if ($this->beta !== null) {
|
||||
array_push($tokens, 'BETA', $this->beta);
|
||||
}
|
||||
|
||||
if ($this->as) {
|
||||
array_push($tokens, ...$this->as);
|
||||
}
|
||||
|
||||
if (!empty($tokens)) {
|
||||
array_push($this->arguments, count($tokens), ...$tokens);
|
||||
}
|
||||
|
||||
return $this->arguments;
|
||||
}
|
||||
}
|
||||
+79
@@ -0,0 +1,79 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Predis package.
|
||||
*
|
||||
* (c) 2009-2020 Daniele Alessandri
|
||||
* (c) 2021-2025 Till Krüss
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Predis\Command\Argument\Search\HybridSearch\Combine;
|
||||
|
||||
class RRFCombineConfig extends BaseCombine
|
||||
{
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
protected $window;
|
||||
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
protected $rrfConstant;
|
||||
|
||||
/**
|
||||
* The number of top results from each search type to consider for fusion. Defaults to 50.
|
||||
*
|
||||
* @param int $window
|
||||
* @return $this
|
||||
*/
|
||||
public function window(int $window): self
|
||||
{
|
||||
$this->window = $window;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* The RRF ranking constant. A smaller value gives more weight to top-ranked items. Defaults to 60.
|
||||
*
|
||||
* @param int $constant
|
||||
* @return $this
|
||||
*/
|
||||
public function rrfConstant(int $constant): self
|
||||
{
|
||||
$this->rrfConstant = $constant;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function toArray(): array
|
||||
{
|
||||
$this->arguments[] = 'RRF';
|
||||
$tokens = [];
|
||||
|
||||
if ($this->window !== null) {
|
||||
array_push($tokens, 'WINDOW', $this->window);
|
||||
}
|
||||
|
||||
if ($this->rrfConstant !== null) {
|
||||
array_push($tokens, 'CONSTANT', $this->rrfConstant);
|
||||
}
|
||||
|
||||
if ($this->as) {
|
||||
array_push($tokens, ...$this->as);
|
||||
}
|
||||
|
||||
if (!empty($tokens)) {
|
||||
array_push($this->arguments, count($tokens), ...$tokens);
|
||||
}
|
||||
|
||||
return $this->arguments;
|
||||
}
|
||||
}
|
||||
+352
@@ -0,0 +1,352 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Predis package.
|
||||
*
|
||||
* (c) 2009-2020 Daniele Alessandri
|
||||
* (c) 2021-2025 Till Krüss
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Predis\Command\Argument\Search\HybridSearch;
|
||||
|
||||
use Predis\Command\Argument\ArrayableArgument;
|
||||
use Predis\Command\Argument\Search\HybridSearch\Combine\LinearCombineConfig;
|
||||
use Predis\Command\Argument\Search\HybridSearch\Combine\RRFCombineConfig;
|
||||
use Predis\Command\Argument\Search\HybridSearch\VectorSearch\KNNVectorSearchConfig;
|
||||
use Predis\Command\Argument\Search\HybridSearch\VectorSearch\RangeVectorSearchConfig;
|
||||
use Predis\Command\Redis\Utils\CommandUtility;
|
||||
use ValueError;
|
||||
|
||||
class HybridSearchQuery implements ArrayableArgument
|
||||
{
|
||||
public const SORT_ASC = 'ASC';
|
||||
public const SORT_DESC = 'DESC';
|
||||
|
||||
/**
|
||||
* @var SearchConfig
|
||||
*/
|
||||
protected $searchConfig;
|
||||
|
||||
/**
|
||||
* The vector search portion of the query.
|
||||
*
|
||||
* @var KNNVectorSearchConfig|RangeVectorSearchConfig
|
||||
*/
|
||||
protected $vectorSearchConfig;
|
||||
|
||||
/**
|
||||
* Configuration for the score fusion method (optional).
|
||||
* If not provided, Reciprocal Rank Fusion (RRF) is used with server-side default parameters.
|
||||
*
|
||||
* @var RRFCombineConfig|LinearCombineConfig
|
||||
*/
|
||||
protected $combineConfig;
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $load = [];
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $groupBy = [];
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $apply = [];
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $sortBy = [];
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $filter;
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $limit = [];
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $params = [];
|
||||
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
protected $explainScore = false;
|
||||
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
protected $timeout = false;
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $withCursor = [];
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $arguments = [];
|
||||
|
||||
/**
|
||||
* @param string $vectorSearchMethod Class type of desired vector search method
|
||||
* @param string $combineMethod Class type of desired combine method
|
||||
*/
|
||||
public function __construct(
|
||||
string $vectorSearchMethod = KNNVectorSearchConfig::class,
|
||||
string $combineMethod = RRFCombineConfig::class
|
||||
) {
|
||||
$this->searchConfig = new SearchConfig();
|
||||
$this->vectorSearchConfig = new $vectorSearchMethod();
|
||||
$this->combineConfig = new $combineMethod();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param callable(SearchConfig): void $callable
|
||||
* @return $this
|
||||
*/
|
||||
public function buildSearchConfig(callable $callable): self
|
||||
{
|
||||
$callable($this->searchConfig);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param callable(KNNVectorSearchConfig|RangeVectorSearchConfig): void $callable
|
||||
* @return $this
|
||||
*/
|
||||
public function buildVectorSearchConfig(callable $callable): self
|
||||
{
|
||||
$callable($this->vectorSearchConfig);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param callable(RRFCombineConfig|LinearCombineConfig): void $callable
|
||||
* @return $this
|
||||
*/
|
||||
public function buildCombineConfig(callable $callable): self
|
||||
{
|
||||
$callable($this->combineConfig);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* The list of fields to return in the results.
|
||||
*
|
||||
* @param array $fields
|
||||
* @return $this
|
||||
*/
|
||||
public function load(array $fields): self
|
||||
{
|
||||
array_push($this->load, 'LOAD', count($fields), ...$fields);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $fields
|
||||
* @param Reducer[] $reducers
|
||||
* @return $this
|
||||
*/
|
||||
public function groupBy(array $fields, array $reducers): self
|
||||
{
|
||||
array_push($this->groupBy, 'GROUPBY', count($fields), ...$fields);
|
||||
|
||||
foreach ($reducers as $reducer) {
|
||||
array_push($this->groupBy, 'REDUCE', ...$reducer->toArray());
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $expressionFieldDict field => function dictionary
|
||||
* @return $this
|
||||
*/
|
||||
public function apply(array $expressionFieldDict): self
|
||||
{
|
||||
foreach ($expressionFieldDict as $field => $function) {
|
||||
array_push($this->apply, 'APPLY', $function, 'AS', $field);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sorts the final results by a specific field.
|
||||
*
|
||||
* @param array<string, string> $fields Dictionary with fields and sort direction. Check class constants.
|
||||
* @return $this
|
||||
*/
|
||||
public function sortBy(array $fields): self
|
||||
{
|
||||
$fieldsArray = [];
|
||||
foreach ($fields as $field => $direction) {
|
||||
if (!in_array(strtoupper($direction), [self::SORT_ASC, self::SORT_DESC])) {
|
||||
throw new ValueError('Sort direction must be one of "ASC" or "DESC".');
|
||||
}
|
||||
|
||||
array_push($fieldsArray, $field, $direction);
|
||||
}
|
||||
|
||||
array_push($this->sortBy, 'SORTBY', count($fieldsArray), ...$fieldsArray);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Final result filtering.
|
||||
*
|
||||
* @param string $expression
|
||||
* @return $this
|
||||
*/
|
||||
public function filter(string $expression): self
|
||||
{
|
||||
$this->filter = $expression;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $offset
|
||||
* @param int $num
|
||||
* @return $this
|
||||
*/
|
||||
public function limit(int $offset, int $num): self
|
||||
{
|
||||
array_push($this->limit, 'LIMIT', $offset, $num);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Binds values to named parameters in the query string.
|
||||
*
|
||||
* @param array $params
|
||||
* @return $this
|
||||
*/
|
||||
public function params(array $params): self
|
||||
{
|
||||
$arrayParams = CommandUtility::dictionaryToArray($params);
|
||||
array_push($this->params, 'PARAMS', count($arrayParams), ...$arrayParams);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return $this
|
||||
*/
|
||||
public function explainScore(): self
|
||||
{
|
||||
$this->explainScore = true;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return $this
|
||||
*/
|
||||
public function timeout(): self
|
||||
{
|
||||
$this->timeout = true;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int|null $readSize
|
||||
* @param int|null $idleTime
|
||||
* @return $this
|
||||
*/
|
||||
public function withCursor(?int $readSize = null, ?int $idleTime = null): self
|
||||
{
|
||||
$this->withCursor[] = 'WITHCURSOR';
|
||||
|
||||
if ($readSize) {
|
||||
array_push($this->withCursor, 'COUNT', $readSize);
|
||||
}
|
||||
|
||||
if ($idleTime) {
|
||||
array_push($this->withCursor, 'MAXIDLE', $idleTime);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function toArray(): array
|
||||
{
|
||||
$this->arguments = array_merge(
|
||||
$this->arguments,
|
||||
$this->searchConfig->toArray(),
|
||||
$this->vectorSearchConfig->toArray()
|
||||
);
|
||||
|
||||
$combineConfig = $this->combineConfig->toArray();
|
||||
|
||||
// Only add if any configuration was applied
|
||||
if (count($combineConfig) > 2) {
|
||||
$this->arguments = array_merge($this->arguments, $combineConfig);
|
||||
}
|
||||
|
||||
if ($this->load) {
|
||||
$this->arguments = array_merge($this->arguments, $this->load);
|
||||
}
|
||||
|
||||
if ($this->groupBy) {
|
||||
$this->arguments = array_merge($this->arguments, $this->groupBy);
|
||||
}
|
||||
|
||||
if ($this->apply) {
|
||||
$this->arguments = array_merge($this->arguments, $this->apply);
|
||||
}
|
||||
|
||||
if ($this->sortBy) {
|
||||
$this->arguments = array_merge($this->arguments, $this->sortBy);
|
||||
}
|
||||
|
||||
if ($this->filter) {
|
||||
array_push($this->arguments, 'FILTER', $this->filter);
|
||||
}
|
||||
|
||||
if ($this->limit) {
|
||||
$this->arguments = array_merge($this->arguments, $this->limit);
|
||||
}
|
||||
|
||||
if ($this->params) {
|
||||
$this->arguments = array_merge($this->arguments, $this->params);
|
||||
}
|
||||
|
||||
if ($this->explainScore) {
|
||||
$this->arguments[] = 'EXPLAINSCORE';
|
||||
}
|
||||
|
||||
if ($this->timeout) {
|
||||
$this->arguments[] = 'TIMEOUT';
|
||||
}
|
||||
|
||||
if ($this->withCursor) {
|
||||
$this->arguments = array_merge($this->arguments, $this->withCursor);
|
||||
}
|
||||
|
||||
return $this->arguments;
|
||||
}
|
||||
}
|
||||
+51
@@ -0,0 +1,51 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Predis package.
|
||||
*
|
||||
* (c) 2009-2020 Daniele Alessandri
|
||||
* (c) 2021-2025 Till Krüss
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Predis\Command\Argument\Search\HybridSearch;
|
||||
|
||||
use Predis\Command\Argument\ArrayableArgument;
|
||||
|
||||
class Reducer implements ArrayableArgument
|
||||
{
|
||||
public const REDUCE_COUNT = 'COUNT';
|
||||
public const REDUCE_COUNT_DISTINCT = 'COUNT_DISTINCT';
|
||||
public const REDUCE_COUNT_DISTINCTISH = 'COUNT_DISTINCTISH';
|
||||
public const REDUCE_SUM = 'SUM';
|
||||
public const REDUCE_MIN = 'MIN';
|
||||
public const REDUCE_MAX = 'MAX';
|
||||
public const REDUCE_AVG = 'AVG';
|
||||
public const REDUCE_STDDEV = 'STDDEV';
|
||||
public const REDUCE_QUANTILE = 'QUANTILE';
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $arguments = [];
|
||||
|
||||
/**
|
||||
* @param string $function One of the available functions. Check class constants.
|
||||
* @param array $arguments List of properties
|
||||
*/
|
||||
public function __construct(string $function = self::REDUCE_COUNT, array $arguments = [], ?string $alias = null)
|
||||
{
|
||||
array_push($this->arguments, $function, count($arguments), ...$arguments);
|
||||
|
||||
if ($alias) {
|
||||
array_push($this->arguments, 'AS', $alias);
|
||||
}
|
||||
}
|
||||
|
||||
public function toArray(): array
|
||||
{
|
||||
return $this->arguments;
|
||||
}
|
||||
}
|
||||
+60
@@ -0,0 +1,60 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Predis package.
|
||||
*
|
||||
* (c) 2009-2020 Daniele Alessandri
|
||||
* (c) 2021-2025 Till Krüss
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Predis\Command\Argument\Search\HybridSearch;
|
||||
|
||||
use Predis\Command\Argument\ArrayableArgument;
|
||||
|
||||
class ScorerConfig implements ArrayableArgument
|
||||
{
|
||||
public const TYPE_BM25 = 'BM25';
|
||||
public const TYPE_TFIDF = 'TFIDF';
|
||||
public const TYPE_DISMAX = 'DISMAX';
|
||||
public const TYPE_DOCSCORE = 'DOCSCORE';
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $arguments = [];
|
||||
|
||||
/**
|
||||
* The text scoring algorithm. Defaults to BM25.
|
||||
*
|
||||
* @param string $type
|
||||
* @return $this
|
||||
*/
|
||||
public function type(string $type = self::TYPE_BM25): self
|
||||
{
|
||||
$this->arguments[] = $type;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* An alias for the text score field in the results.
|
||||
* The aliased field will be included in the `value` object of each returned document.
|
||||
*
|
||||
* @param string $alias
|
||||
* @return $this
|
||||
*/
|
||||
public function as(string $alias): self
|
||||
{
|
||||
array_push($this->arguments, 'YIELD_SCORE_AS', $alias);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function toArray(): array
|
||||
{
|
||||
return $this->arguments;
|
||||
}
|
||||
}
|
||||
+80
@@ -0,0 +1,80 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Predis package.
|
||||
*
|
||||
* (c) 2009-2020 Daniele Alessandri
|
||||
* (c) 2021-2025 Till Krüss
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Predis\Command\Argument\Search\HybridSearch;
|
||||
|
||||
use Predis\Command\Argument\ArrayableArgument;
|
||||
|
||||
class SearchConfig implements ArrayableArgument
|
||||
{
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $arguments = ['SEARCH'];
|
||||
|
||||
/**
|
||||
* @var ScorerConfig
|
||||
*/
|
||||
protected $scorerConfig;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->scorerConfig = new ScorerConfig();
|
||||
}
|
||||
|
||||
/**
|
||||
* Search query.
|
||||
*
|
||||
* @param string $query
|
||||
* @return $this
|
||||
*/
|
||||
public function query(string $query): self
|
||||
{
|
||||
$this->arguments[] = $query;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $alias
|
||||
* @return $this
|
||||
*/
|
||||
public function as(string $alias): self
|
||||
{
|
||||
array_push($this->arguments, 'YIELD_SCORE_AS', $alias);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param callable(ScorerConfig): void $callable
|
||||
* @return $this
|
||||
*/
|
||||
public function buildScorerConfig(callable $callable): self
|
||||
{
|
||||
$callable($this->scorerConfig);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function toArray(): array
|
||||
{
|
||||
$scorerConfig = $this->scorerConfig->toArray();
|
||||
|
||||
if (!empty($scorerConfig)) {
|
||||
$this->arguments[] = 'SCORER';
|
||||
$this->arguments = array_merge($this->arguments, $scorerConfig);
|
||||
}
|
||||
|
||||
return $this->arguments;
|
||||
}
|
||||
}
|
||||
+80
@@ -0,0 +1,80 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Predis package.
|
||||
*
|
||||
* (c) 2009-2020 Daniele Alessandri
|
||||
* (c) 2021-2025 Till Krüss
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Predis\Command\Argument\Search\HybridSearch\VectorSearch;
|
||||
|
||||
use Predis\Command\Argument\ArrayableArgument;
|
||||
|
||||
abstract class BaseVectorSearchConfig implements ArrayableArgument
|
||||
{
|
||||
public const POLICY_ADHOC = 'ADHOC';
|
||||
public const POLICY_BATCHES = 'BATCHES';
|
||||
public const POLICY_ACORN = 'ACORN';
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $vector = [];
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $filter = [];
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $as = [];
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $arguments = ['VSIM'];
|
||||
|
||||
/**
|
||||
* Vector to perform search against.
|
||||
*
|
||||
* @param string $field The vector field name to search against. Must start with "@".
|
||||
* @param string $value Name of the parameter to use in the query. Must start with "$".
|
||||
* @return self
|
||||
*/
|
||||
public function vector(string $field, string $value): self
|
||||
{
|
||||
array_push($this->vector, $field, $value);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $expression
|
||||
* @return $this
|
||||
*/
|
||||
public function filter(string $expression): self
|
||||
{
|
||||
array_push($this->filter, 'FILTER', $expression);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $alias
|
||||
* @return $this
|
||||
*/
|
||||
public function as(string $alias): self
|
||||
{
|
||||
array_push($this->as, 'YIELD_SCORE_AS', $alias);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
abstract public function toArray(): array;
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user