This commit is contained in:
ywnsya 2026-05-04 13:15:16 +08:00
parent b8f599a617
commit 093ce2e8bc
776 changed files with 70357 additions and 16 deletions

View File

@ -29,6 +29,7 @@ class AiMetadata
$this->queue->releaseDueDelayed(); $this->queue->releaseDueDelayed();
$archiveUid = $this->queue->pop($this->queue->blockTimeout()); $archiveUid = $this->queue->pop($this->queue->blockTimeout());
if ($archiveUid === null) { if ($archiveUid === null) {
sleep($this->queue->idleSleepSeconds());
continue; continue;
} }

View File

@ -62,6 +62,11 @@ class AiMetadataQueue
return (int) config('queue.ai_metadata.block_timeout', 5); return (int) config('queue.ai_metadata.block_timeout', 5);
} }
public function idleSleepSeconds(): int
{
return max(1, (int) config('queue.ai_metadata.idle_sleep_seconds', 1));
}
private function pendingKey(): string private function pendingKey(): string
{ {
return config('queue.ai_metadata.pending', 'proofdb:ai:metadata:pending'); return config('queue.ai_metadata.pending', 'proofdb:ai:metadata:pending');

View File

@ -36,7 +36,8 @@
"webman/validation": "^2.2", "webman/validation": "^2.2",
"symfony/uid": "^8.0", "symfony/uid": "^8.0",
"vlucas/phpdotenv": "^5.6", "vlucas/phpdotenv": "^5.6",
"guzzlehttp/guzzle": "^7.10" "guzzlehttp/guzzle": "^7.10",
"opensearch-project/opensearch-php": "^2.6"
}, },
"suggest": { "suggest": {
"ext-event": "For better performance. " "ext-event": "For better performance. "
@ -67,5 +68,10 @@
] ]
}, },
"minimum-stability": "dev", "minimum-stability": "dev",
"prefer-stable": true "prefer-stable": true,
"config": {
"allow-plugins": {
"php-http/discovery": true
}
}
} }

427
composer.lock generated
View File

@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically" "This file is @generated automatically"
], ],
"content-hash": "f1556df2c64ede0da8f43d75af9461fe", "content-hash": "74649fce03b1c3319cd73b61cc7c202f",
"packages": [ "packages": [
{ {
"name": "brick/math", "name": "brick/math",
@ -368,6 +368,117 @@
], ],
"time": "2025-03-06T22:45:56+00:00" "time": "2025-03-06T22:45:56+00:00"
}, },
{
"name": "ezimuel/guzzlestreams",
"version": "4.1.0",
"source": {
"type": "git",
"url": "https://github.com/ezimuel/guzzlestreams.git",
"reference": "903161be81e9f497cc42fb7db982404a4e6441b0"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/ezimuel/guzzlestreams/zipball/903161be81e9f497cc42fb7db982404a4e6441b0",
"reference": "903161be81e9f497cc42fb7db982404a4e6441b0",
"shasum": ""
},
"require": {
"php": ">=7.4.0"
},
"require-dev": {
"phpstan/phpstan": "^2.1",
"phpunit/phpunit": "~9.0"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "3.0-dev"
}
},
"autoload": {
"psr-4": {
"GuzzleHttp\\Stream\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Michael Dowling",
"email": "mtdowling@gmail.com",
"homepage": "https://github.com/mtdowling"
}
],
"description": "Fork of guzzle/streams (abandoned) to be used with elasticsearch-php",
"homepage": "http://guzzlephp.org/",
"keywords": [
"Guzzle",
"stream"
],
"support": {
"source": "https://github.com/ezimuel/guzzlestreams/tree/4.1.0"
},
"time": "2025-08-05T06:44:46+00:00"
},
{
"name": "ezimuel/ringphp",
"version": "1.4.1",
"source": {
"type": "git",
"url": "https://github.com/ezimuel/ringphp.git",
"reference": "b97f46088940671100012482577eeb59f26a13b5"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/ezimuel/ringphp/zipball/b97f46088940671100012482577eeb59f26a13b5",
"reference": "b97f46088940671100012482577eeb59f26a13b5",
"shasum": ""
},
"require": {
"ezimuel/guzzlestreams": "^3.0.1 || ^4.0.0",
"php": ">=5.4.0",
"react/promise": "^2.0 || ^3.0"
},
"replace": {
"guzzlehttp/ringphp": "self.version"
},
"require-dev": {
"ext-curl": "*",
"phpunit/phpunit": "~9.0"
},
"suggest": {
"ext-curl": "Guzzle will use specific adapters if cURL is present"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "1.1-dev"
}
},
"autoload": {
"psr-4": {
"GuzzleHttp\\Ring\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Michael Dowling",
"email": "mtdowling@gmail.com",
"homepage": "https://github.com/mtdowling"
}
],
"description": "Fork of guzzle/RingPHP (abandoned) to be used with elasticsearch-php",
"support": {
"source": "https://github.com/ezimuel/ringphp/tree/1.4.1"
},
"time": "2026-02-11T16:42:15+00:00"
},
{ {
"name": "fruitcake/php-cors", "name": "fruitcake/php-cors",
"version": "v1.4.0", "version": "v1.4.0",
@ -2255,6 +2366,172 @@
}, },
"time": "2018-02-13T20:26:39+00:00" "time": "2018-02-13T20:26:39+00:00"
}, },
{
"name": "opensearch-project/opensearch-php",
"version": "2.6.0",
"source": {
"type": "git",
"url": "https://github.com/opensearch-project/opensearch-php.git",
"reference": "6bcff126ffd8f54ca33a0dd659128cbc5d3b2bcd"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/opensearch-project/opensearch-php/zipball/6bcff126ffd8f54ca33a0dd659128cbc5d3b2bcd",
"reference": "6bcff126ffd8f54ca33a0dd659128cbc5d3b2bcd",
"shasum": ""
},
"require": {
"ext-curl": "*",
"ext-json": ">=1.3.7",
"ezimuel/ringphp": "^1.4.0",
"php": "^8.2",
"php-http/discovery": "^1.20",
"psr/http-client": "^1.0.3",
"psr/http-client-implementation": "*",
"psr/http-factory": "^1.1.0",
"psr/http-factory-implementation": "*",
"psr/http-message": "^2.0",
"psr/http-message-implementation": "*",
"psr/log": "^3.0.2",
"symfony/yaml": "^v6.4.26|^7.3.5|^v8.0.0"
},
"require-dev": {
"aws/aws-sdk-php": "^3.359.8",
"colinodell/psr-testlogger": "^1.3.1",
"ext-zip": "*",
"friendsofphp/php-cs-fixer": "^v3.89.1",
"guzzlehttp/psr7": "^2.8.0",
"mockery/mockery": "^1.6.12",
"phpstan/extension-installer": "^1.4.3",
"phpstan/phpstan": "^1.12.32",
"phpstan/phpstan-deprecation-rules": "^1.2.1",
"phpstan/phpstan-mockery": "^1.1.3",
"phpstan/phpstan-phpunit": "^1.4.2",
"phpunit/phpunit": "^10.5.58",
"react/promise": "^v3.3",
"symfony/console": "v6.4.31|^7.4.3|^8.0.3",
"symfony/finder": "^v6.4.31|^7.4.3|^v8.0.3",
"symfony/http-client": "^6.4.31|^7.4.3|^v8.0.3",
"symfony/http-client-contracts": "^v3.6.0|^v8.0.3",
"twig/twig": "^3.0"
},
"suggest": {
"aws/aws-sdk-php": "Required (^3.0.0) in order to use the AWS Signing Client Decorator",
"guzzlehttp/psr7": "Required (^2.7) in order to use the Guzzle HTTP client",
"monolog/monolog": "Allows for client-level logging and tracing",
"symfony/http-client": "Required (^6.4|^7.0|^8.0) in order to use the Symfony HTTP client"
},
"type": "library",
"autoload": {
"psr-4": {
"OpenSearch\\": "src/OpenSearch/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"Apache-2.0",
"LGPL-2.1-only"
],
"authors": [
{
"name": "Elastic"
},
{
"name": "OpenSearch Contributors"
}
],
"description": "PHP Client for OpenSearch",
"keywords": [
"client",
"elasticsearch",
"opensearch",
"search"
],
"support": {
"issues": "https://github.com/opensearch-project/opensearch-php/issues",
"source": "https://github.com/opensearch-project/opensearch-php/tree/2.6.0"
},
"time": "2026-03-24T03:15:15+00:00"
},
{
"name": "php-http/discovery",
"version": "1.20.0",
"source": {
"type": "git",
"url": "https://github.com/php-http/discovery.git",
"reference": "82fe4c73ef3363caed49ff8dd1539ba06044910d"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/php-http/discovery/zipball/82fe4c73ef3363caed49ff8dd1539ba06044910d",
"reference": "82fe4c73ef3363caed49ff8dd1539ba06044910d",
"shasum": ""
},
"require": {
"composer-plugin-api": "^1.0|^2.0",
"php": "^7.1 || ^8.0"
},
"conflict": {
"nyholm/psr7": "<1.0",
"zendframework/zend-diactoros": "*"
},
"provide": {
"php-http/async-client-implementation": "*",
"php-http/client-implementation": "*",
"psr/http-client-implementation": "*",
"psr/http-factory-implementation": "*",
"psr/http-message-implementation": "*"
},
"require-dev": {
"composer/composer": "^1.0.2|^2.0",
"graham-campbell/phpspec-skip-example-extension": "^5.0",
"php-http/httplug": "^1.0 || ^2.0",
"php-http/message-factory": "^1.0",
"phpspec/phpspec": "^5.1 || ^6.1 || ^7.3",
"sebastian/comparator": "^3.0.5 || ^4.0.8",
"symfony/phpunit-bridge": "^6.4.4 || ^7.0.1"
},
"type": "composer-plugin",
"extra": {
"class": "Http\\Discovery\\Composer\\Plugin",
"plugin-optional": true
},
"autoload": {
"psr-4": {
"Http\\Discovery\\": "src/"
},
"exclude-from-classmap": [
"src/Composer/Plugin.php"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Márk Sági-Kazár",
"email": "mark.sagikazar@gmail.com"
}
],
"description": "Finds and installs PSR-7, PSR-17, PSR-18 and HTTPlug implementations",
"homepage": "http://php-http.org",
"keywords": [
"adapter",
"client",
"discovery",
"factory",
"http",
"message",
"psr17",
"psr7"
],
"support": {
"issues": "https://github.com/php-http/discovery/issues",
"source": "https://github.com/php-http/discovery/tree/1.20.0"
},
"time": "2024-10-02T11:20:13+00:00"
},
{ {
"name": "phpoption/phpoption", "name": "phpoption/phpoption",
"version": "1.9.5", "version": "1.9.5",
@ -2786,6 +3063,79 @@
}, },
"time": "2019-03-08T08:55:37+00:00" "time": "2019-03-08T08:55:37+00:00"
}, },
{
"name": "react/promise",
"version": "v3.3.0",
"source": {
"type": "git",
"url": "https://github.com/reactphp/promise.git",
"reference": "23444f53a813a3296c1368bb104793ce8d88f04a"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/reactphp/promise/zipball/23444f53a813a3296c1368bb104793ce8d88f04a",
"reference": "23444f53a813a3296c1368bb104793ce8d88f04a",
"shasum": ""
},
"require": {
"php": ">=7.1.0"
},
"require-dev": {
"phpstan/phpstan": "1.12.28 || 1.4.10",
"phpunit/phpunit": "^9.6 || ^7.5"
},
"type": "library",
"autoload": {
"files": [
"src/functions_include.php"
],
"psr-4": {
"React\\Promise\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Jan Sorgalla",
"email": "jsorgalla@gmail.com",
"homepage": "https://sorgalla.com/"
},
{
"name": "Christian Lück",
"email": "christian@clue.engineering",
"homepage": "https://clue.engineering/"
},
{
"name": "Cees-Jan Kiewiet",
"email": "reactphp@ceesjankiewiet.nl",
"homepage": "https://wyrihaximus.net/"
},
{
"name": "Chris Boden",
"email": "cboden@gmail.com",
"homepage": "https://cboden.dev/"
}
],
"description": "A lightweight implementation of CommonJS Promises/A for PHP",
"keywords": [
"promise",
"promises"
],
"support": {
"issues": "https://github.com/reactphp/promise/issues",
"source": "https://github.com/reactphp/promise/tree/v3.3.0"
},
"funding": [
{
"url": "https://opencollective.com/reactphp",
"type": "open_collective"
}
],
"time": "2025-08-19T18:57:03+00:00"
},
{ {
"name": "symfony/clock", "name": "symfony/clock",
"version": "v8.0.8", "version": "v8.0.8",
@ -4874,6 +5224,81 @@
], ],
"time": "2026-03-31T07:15:36+00:00" "time": "2026-03-31T07:15:36+00:00"
}, },
{
"name": "symfony/yaml",
"version": "v8.0.8",
"source": {
"type": "git",
"url": "https://github.com/symfony/yaml.git",
"reference": "54174ab48c0c0f9e21512b304be17f8150ccf8f1"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/yaml/zipball/54174ab48c0c0f9e21512b304be17f8150ccf8f1",
"reference": "54174ab48c0c0f9e21512b304be17f8150ccf8f1",
"shasum": ""
},
"require": {
"php": ">=8.4",
"symfony/polyfill-ctype": "^1.8"
},
"conflict": {
"symfony/console": "<7.4"
},
"require-dev": {
"symfony/console": "^7.4|^8.0"
},
"bin": [
"Resources/bin/yaml-lint"
],
"type": "library",
"autoload": {
"psr-4": {
"Symfony\\Component\\Yaml\\": ""
},
"exclude-from-classmap": [
"/Tests/"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Fabien Potencier",
"email": "fabien@symfony.com"
},
{
"name": "Symfony Community",
"homepage": "https://symfony.com/contributors"
}
],
"description": "Loads and dumps YAML files",
"homepage": "https://symfony.com",
"support": {
"source": "https://github.com/symfony/yaml/tree/v8.0.8"
},
"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-03-30T15:14:47+00:00"
},
{ {
"name": "vlucas/phpdotenv", "name": "vlucas/phpdotenv",
"version": "v5.6.3", "version": "v5.6.3",

43
config/opensearch.php Normal file
View File

@ -0,0 +1,43 @@
<?php
$bool = static function (string $name, bool $default): bool {
$value = getenv($name);
if ($value === false || $value === '') {
return $default;
}
return in_array(strtolower((string) $value), ['1', 'true', 'yes', 'on'], true);
};
$hosts = getenv('OPENSEARCH_HOSTS') ?: getenv('OPENSEARCH_HOST') ?: 'http://127.0.0.1:9200';
$hosts = array_values(array_filter(array_map('trim', explode(',', $hosts))));
return [
/*
* OpenSearch connection configuration.
*
* Recommended environment variables:
* - OPENSEARCH_HOSTS=http://127.0.0.1:9200
* - OPENSEARCH_USERNAME=admin
* - OPENSEARCH_PASSWORD=...
* - OPENSEARCH_SSL_VERIFY=true
* - OPENSEARCH_INDEX_CHUNKS=proofdb_chunks
*/
'default' => [
'hosts' => $hosts,
'username' => getenv('OPENSEARCH_USERNAME') ?: null,
'password' => getenv('OPENSEARCH_PASSWORD') ?: null,
'ssl_verify' => $bool('OPENSEARCH_SSL_VERIFY', true),
'timeout' => (float) (getenv('OPENSEARCH_TIMEOUT') ?: 30),
'connect_timeout' => (float) (getenv('OPENSEARCH_CONNECT_TIMEOUT') ?: 5),
],
'indices' => [
'chunks' => getenv('OPENSEARCH_INDEX_CHUNKS') ?: 'proofdb_chunks',
],
'bulk' => [
'refresh' => getenv('OPENSEARCH_BULK_REFRESH') ?: 'false',
'chunk_size' => (int) (getenv('OPENSEARCH_BULK_CHUNK_SIZE') ?: 500),
],
];

View File

@ -10,5 +10,6 @@ return [
'max_retries' => (int) (getenv('AI_METADATA_QUEUE_MAX_RETRIES') ?: 5), 'max_retries' => (int) (getenv('AI_METADATA_QUEUE_MAX_RETRIES') ?: 5),
'base_delay_seconds' => (int) (getenv('AI_METADATA_QUEUE_BASE_DELAY_SECONDS') ?: 60), 'base_delay_seconds' => (int) (getenv('AI_METADATA_QUEUE_BASE_DELAY_SECONDS') ?: 60),
'block_timeout' => (int) (getenv('AI_METADATA_QUEUE_BLOCK_TIMEOUT') ?: 5), 'block_timeout' => (int) (getenv('AI_METADATA_QUEUE_BLOCK_TIMEOUT') ?: 5),
'idle_sleep_seconds' => (int) (getenv('AI_METADATA_QUEUE_IDLE_SLEEP_SECONDS') ?: 1),
], ],
]; ];

View File

@ -18,7 +18,7 @@ Unlike generic RAG systems, this project treats **"evidence" as first-class stru
The system is divided into three conceptual layers: The system is divided into three conceptual layers:
* **Proof DB** → Data layer (MySQL + OpenSearch + Vector) * **Proof DB** → Data layer (PostgreSQL + OpenSearch + Vector)
* **Archive Cask** → Frontend interface (not part of this task) * **Archive Cask** → Frontend interface (not part of this task)
* **Few-shot Engine** → OCR (external, not part of this task) * **Few-shot Engine** → OCR (external, not part of this task)
@ -43,7 +43,7 @@ The backend follows a **modular service architecture** (not microservices yet, b
2. **Storage Layer** 2. **Storage Layer**
* MySQL → metadata, relations * PostgreSQL → metadata, relations
* OpenSearch → full-text index * OpenSearch → full-text index
* Vector DB → embeddings (can be OpenSearch kNN or Qdrant) * Vector DB → embeddings (can be OpenSearch kNN or Qdrant)
@ -72,7 +72,7 @@ The backend follows a **modular service architecture** (not microservices yet, b
### Database ### Database
* MySQL (relational metadata) * PostgreSQL (relational metadata)
### Search Engine ### Search Engine
@ -130,7 +130,7 @@ Chunk
* **archive_uid 是档案级核心 ID使用 ULID** * **archive_uid 是档案级核心 ID使用 ULID**
* **chunk_uid 是 chunk 级核心 ID格式为 `{archive_uid}_{chunk_index}_{short_uid}`** * **chunk_uid 是 chunk 级核心 ID格式为 `{archive_uid}_{chunk_index}_{short_uid}`**
* MySQL / OpenSearch / Vector DB 全部围绕 `archive_uid``chunk_uid` * PostgreSQL / OpenSearch / Vector DB 全部围绕 `archive_uid``chunk_uid`
* **page_number 是证据定位的关键字段** * **page_number 是证据定位的关键字段**
* Chunk 是向量化和检索召回单位,不是精确 citation 单位 * Chunk 是向量化和检索召回单位,不是精确 citation 单位
* 证据定位只需要定位到页码,因此 chunk 可以跨段落合并,但不能跨页 * 证据定位只需要定位到页码,因此 chunk 可以跨段落合并,但不能跨页
@ -212,3 +212,72 @@ GET /api/evidence/{chunk_uid}
[1]: https://dev.to/tomjohnson3/understanding-backend-architecture-ljb?utm_source=chatgpt.com "Understanding Backend Architecture" [1]: https://dev.to/tomjohnson3/understanding-backend-architecture-ljb?utm_source=chatgpt.com "Understanding Backend Architecture"
[2]: https://exodata.io/what-is-a-tech-stack-how-to-architect-a-modern-scalable-technology-stack/?utm_source=chatgpt.com "How to Build a Tech Stack That Scales [2026] | Exodata" [2]: https://exodata.io/what-is-a-tech-stack-how-to-architect-a-modern-scalable-technology-stack/?utm_source=chatgpt.com "How to Build a Tech Stack That Scales [2026] | Exodata"
[3]: https://medium.com/%40hanxuyang0826/roadmap-to-backend-programming-master-architectural-patterns-c763c9194414?utm_source=chatgpt.com "Roadmap to Backend Programming Master: Architectural ..." [3]: https://medium.com/%40hanxuyang0826/roadmap-to-backend-programming-master-architectural-patterns-c763c9194414?utm_source=chatgpt.com "Roadmap to Backend Programming Master: Architectural ..."
---
## 10. StepMapToDo
> Code review date: 2026-05-03
> Scope reviewed: `app/`, `config/`, `scripts/`, `apidoc/`, project root runtime/deploy files. `vendor/` is treated as third-party dependency code and not counted as project implementation.
> Database decision: PostgreSQL is the project database contract. The default Webman Dockerfile is out of scope for this StepMap.
### Done
- [x] Webman backend skeleton is present and listens on `0.0.0.0:8787`.
- [x] Import API route is registered: `POST /api/articles/import`.
- [x] Import controller supports multipart Markdown upload, raw Markdown body, and JSON body.
- [x] Archive import service can normalize payloads, infer fallback title/source, validate inputs, parse DOCMASTER Markdown page markers, and fall back to single-page Markdown when markers are absent.
- [x] Page-level parsing and chunking are implemented with page-bounded chunks. Current chunking does not intentionally cross page boundaries.
- [x] Noise filtering exists for common archive/OCR boilerplate such as page numbers, classification headers, and declassification footer lines.
- [x] List-style policy records and following `COMMENT` blocks are kept together where the local pattern matches.
- [x] `archive_uid` uses ULID and `chunk_uid` follows `{archive_uid}_{chunk_index}_{short_uid}`.
- [x] Runtime import snapshot writing is implemented under `runtime/proofdb/imports/{import_uid}.json`.
- [x] Relational persistence is implemented through `ArchiveRepository::saveImport()`, including `archives` and `chunks` writes.
- [x] PostgreSQL is the selected relational database, matching current `pgsql`, JSONB, `BIGSERIAL`, and `TIMESTAMPTZ` implementation.
- [x] PostgreSQL setup script exists for creating `archives` and `chunks` tables plus indexes.
- [x] Async AI metadata queue exists on Redis with pending, delayed, failed, retry, and error keys.
- [x] `ai_metadata` Workerman process is registered and can consume Redis jobs.
- [x] OpenAI-compatible chat client exists for metadata enrichment.
- [x] Metadata enrichment service can request/fill `title`, `year`, `author`, `tags`, and `summary` when LLM config is available.
- [x] LLM retry helper exists for retryable HTTP/provider errors.
- [x] Import API documentation exists in `apidoc/importapi.md`.
### Partially Done
- [ ] Archive/Page/Chunk model is partly persisted: `archives` and `chunks` tables exist, but pages/page blocks are only summarized in import output and snapshots, not stored as first-class relational tables.
- [ ] `embedding_status`, `embedding_ref`, and `embedding_model` fields exist, but no embedding generation or vector index write path exists yet.
- [ ] Import response exposes page summaries and chunk IDs, but there is no read API yet to fetch archive, page, or chunk records after import.
- [ ] AI metadata enrichment updates the archive row, but import-time response only reports the queue state; clients need a follow-up API or polling path to observe completed enrichment.
- [ ] API documentation still contains an old "后续接入 MySQL" phrase; update it to PostgreSQL to match the database decision.
- [ ] Database and Redis credentials are hard-coded in config files; move them to environment variables before production use.
### Not Done
- [ ] OpenSearch integration is not implemented.
- [ ] Full-text indexing of chunks is not implemented.
- [ ] Full-text search API is not implemented: `POST /api/search/fulltext`.
- [ ] Embedding API/client for vector generation is not implemented.
- [ ] Vector database integration is not implemented, neither OpenSearch kNN nor Qdrant.
- [ ] Vector search API is not implemented: `POST /api/search/vector`.
- [ ] Hybrid search fusion/rerank is not implemented: `POST /api/search/hybrid`.
- [ ] Evidence reconstruction API is not implemented: `GET /api/evidence/{chunk_uid}`.
- [ ] Chunk detail API is not implemented: `GET /api/chunks/{chunk_uid}`.
- [ ] Page-level citation reconstruction is not implemented beyond storing `page_start` and `page_end` on chunks.
- [ ] OpenSearch/Vector schema, index mappings, and migration/setup scripts are not present.
- [ ] Background worker for embedding pending chunks is not present.
- [ ] Reindex/re-embed maintenance commands are not present.
- [ ] Request validation is handwritten in the service; no dedicated validator classes or reusable validation layer are present.
- [ ] Automated tests for Markdown parsing, chunking, import persistence, queue behavior, and metadata enrichment are not present.
- [ ] API authentication, rate limiting, and admin controls are not present.
- [ ] Observability for import/search/enrichment jobs is minimal; no structured job metrics or admin status endpoints are present.
- [ ] Default index page/view still uses Webman starter content and is not Proof DB specific.
### Next Build Order
1. Normalize remaining API documentation wording from MySQL to PostgreSQL.
2. Add read APIs for archives/chunks/evidence so imported data can be verified without reading snapshots or the database directly.
3. Add focused tests for DOCMASTER page parsing, noise filtering, comment coalescing, chunk UID stability, and repository persistence.
4. Implement embedding generation worker and persist `embedding_ref`/`embedding_model`.
5. Add OpenSearch full-text indexing and `POST /api/search/fulltext`.
6. Add vector backend choice and `POST /api/search/vector`.
7. Implement hybrid fusion/rerank and citation-oriented evidence reconstruction.

119
vendor/bin/yaml-lint vendored Executable file
View File

@ -0,0 +1,119 @@
#!/usr/bin/env php
<?php
/**
* Proxy PHP file generated by Composer
*
* This file includes the referenced bin path (../symfony/yaml/Resources/bin/yaml-lint)
* using a stream wrapper to prevent the shebang from being output on PHP<8
*
* @generated
*/
namespace Composer;
$GLOBALS['_composer_bin_dir'] = __DIR__;
$GLOBALS['_composer_autoload_path'] = __DIR__ . '/..'.'/autoload.php';
if (PHP_VERSION_ID < 80000) {
if (!class_exists('Composer\BinProxyWrapper')) {
/**
* @internal
*/
final class BinProxyWrapper
{
private $handle;
private $position;
private $realpath;
public function stream_open($path, $mode, $options, &$opened_path)
{
// get rid of phpvfscomposer:// prefix for __FILE__ & __DIR__ resolution
$opened_path = substr($path, 17);
$this->realpath = realpath($opened_path) ?: $opened_path;
$opened_path = $this->realpath;
$this->handle = fopen($this->realpath, $mode);
$this->position = 0;
return (bool) $this->handle;
}
public function stream_read($count)
{
$data = fread($this->handle, $count);
if ($this->position === 0) {
$data = preg_replace('{^#!.*\r?\n}', '', $data);
}
$this->position += strlen($data);
return $data;
}
public function stream_cast($castAs)
{
return $this->handle;
}
public function stream_close()
{
fclose($this->handle);
}
public function stream_lock($operation)
{
return $operation ? flock($this->handle, $operation) : true;
}
public function stream_seek($offset, $whence)
{
if (0 === fseek($this->handle, $offset, $whence)) {
$this->position = ftell($this->handle);
return true;
}
return false;
}
public function stream_tell()
{
return $this->position;
}
public function stream_eof()
{
return feof($this->handle);
}
public function stream_stat()
{
return array();
}
public function stream_set_option($option, $arg1, $arg2)
{
return true;
}
public function url_stat($path, $flags)
{
$path = substr($path, 17);
if (file_exists($path)) {
return stat($path);
}
return false;
}
}
}
if (
(function_exists('stream_get_wrappers') && in_array('phpvfscomposer', stream_get_wrappers(), true))
|| (function_exists('stream_wrapper_register') && stream_wrapper_register('phpvfscomposer', 'Composer\BinProxyWrapper'))
) {
return include("phpvfscomposer://" . __DIR__ . '/..'.'/symfony/yaml/Resources/bin/yaml-lint');
}
}
return include __DIR__ . '/..'.'/symfony/yaml/Resources/bin/yaml-lint';

View File

@ -29,6 +29,7 @@ return array(
'b6b991a57620e2fb6b2f66f03fe9ddc2' => $vendorDir . '/symfony/string/Resources/functions.php', 'b6b991a57620e2fb6b2f66f03fe9ddc2' => $vendorDir . '/symfony/string/Resources/functions.php',
'd2136ff22b54ac75cd96a40e0022218e' => $vendorDir . '/workerman/webman-framework/src/support/helpers.php', 'd2136ff22b54ac75cd96a40e0022218e' => $vendorDir . '/workerman/webman-framework/src/support/helpers.php',
'37a3dc5111fe8f707ab4c132ef1dbc62' => $vendorDir . '/guzzlehttp/guzzle/src/functions_include.php', '37a3dc5111fe8f707ab4c132ef1dbc62' => $vendorDir . '/guzzlehttp/guzzle/src/functions_include.php',
'ad155f8f1cf0d418fe49e248db8c661b' => $vendorDir . '/react/promise/src/functions_include.php',
'09f6b20656683369174dd6fa83b7e5fb' => $vendorDir . '/symfony/polyfill-uuid/bootstrap.php', '09f6b20656683369174dd6fa83b7e5fb' => $vendorDir . '/symfony/polyfill-uuid/bootstrap.php',
'ef65a1626449d89d0811cf9befce46f0' => $vendorDir . '/illuminate/events/functions.php', 'ef65a1626449d89d0811cf9befce46f0' => $vendorDir . '/illuminate/events/functions.php',
); );

View File

@ -30,6 +30,7 @@ return array(
'Symfony\\Contracts\\Translation\\' => array($vendorDir . '/symfony/translation-contracts'), 'Symfony\\Contracts\\Translation\\' => array($vendorDir . '/symfony/translation-contracts'),
'Symfony\\Contracts\\Service\\' => array($vendorDir . '/symfony/service-contracts'), 'Symfony\\Contracts\\Service\\' => array($vendorDir . '/symfony/service-contracts'),
'Symfony\\Contracts\\EventDispatcher\\' => array($vendorDir . '/symfony/event-dispatcher-contracts'), 'Symfony\\Contracts\\EventDispatcher\\' => array($vendorDir . '/symfony/event-dispatcher-contracts'),
'Symfony\\Component\\Yaml\\' => array($vendorDir . '/symfony/yaml'),
'Symfony\\Component\\VarDumper\\' => array($vendorDir . '/symfony/var-dumper'), 'Symfony\\Component\\VarDumper\\' => array($vendorDir . '/symfony/var-dumper'),
'Symfony\\Component\\Uid\\' => array($vendorDir . '/symfony/uid'), 'Symfony\\Component\\Uid\\' => array($vendorDir . '/symfony/uid'),
'Symfony\\Component\\Translation\\' => array($vendorDir . '/symfony/translation'), 'Symfony\\Component\\Translation\\' => array($vendorDir . '/symfony/translation'),
@ -46,6 +47,7 @@ return array(
'Support\\Exception\\' => array($vendorDir . '/workerman/webman-framework/src/support/exception'), 'Support\\Exception\\' => array($vendorDir . '/workerman/webman-framework/src/support/exception'),
'Support\\Bootstrap\\' => array($vendorDir . '/workerman/webman-framework/src/support/bootstrap'), 'Support\\Bootstrap\\' => array($vendorDir . '/workerman/webman-framework/src/support/bootstrap'),
'Support\\' => array($vendorDir . '/workerman/webman-framework/src/support'), 'Support\\' => array($vendorDir . '/workerman/webman-framework/src/support'),
'React\\Promise\\' => array($vendorDir . '/react/promise/src'),
'Psr\\SimpleCache\\' => array($vendorDir . '/psr/simple-cache/src'), 'Psr\\SimpleCache\\' => array($vendorDir . '/psr/simple-cache/src'),
'Psr\\Log\\' => array($vendorDir . '/psr/log/src'), 'Psr\\Log\\' => array($vendorDir . '/psr/log/src'),
'Psr\\Http\\Message\\' => array($vendorDir . '/psr/http-factory/src', $vendorDir . '/psr/http-message/src'), 'Psr\\Http\\Message\\' => array($vendorDir . '/psr/http-factory/src', $vendorDir . '/psr/http-message/src'),
@ -54,6 +56,7 @@ return array(
'Psr\\Container\\' => array($vendorDir . '/psr/container/src'), 'Psr\\Container\\' => array($vendorDir . '/psr/container/src'),
'Psr\\Clock\\' => array($vendorDir . '/psr/clock/src'), 'Psr\\Clock\\' => array($vendorDir . '/psr/clock/src'),
'PhpOption\\' => array($vendorDir . '/phpoption/phpoption/src/PhpOption'), 'PhpOption\\' => array($vendorDir . '/phpoption/phpoption/src/PhpOption'),
'OpenSearch\\' => array($vendorDir . '/opensearch-project/opensearch-php/src/OpenSearch'),
'Monolog\\' => array($vendorDir . '/monolog/monolog/src/Monolog'), 'Monolog\\' => array($vendorDir . '/monolog/monolog/src/Monolog'),
'Laravel\\SerializableClosure\\' => array($vendorDir . '/laravel/serializable-closure/src'), 'Laravel\\SerializableClosure\\' => array($vendorDir . '/laravel/serializable-closure/src'),
'Illuminate\\Validation\\' => array($vendorDir . '/illuminate/validation'), 'Illuminate\\Validation\\' => array($vendorDir . '/illuminate/validation'),
@ -70,7 +73,10 @@ return array(
'Illuminate\\Contracts\\' => array($vendorDir . '/illuminate/contracts'), 'Illuminate\\Contracts\\' => array($vendorDir . '/illuminate/contracts'),
'Illuminate\\Container\\' => array($vendorDir . '/illuminate/container'), 'Illuminate\\Container\\' => array($vendorDir . '/illuminate/container'),
'Illuminate\\Bus\\' => array($vendorDir . '/illuminate/bus'), 'Illuminate\\Bus\\' => array($vendorDir . '/illuminate/bus'),
'Http\\Discovery\\' => array($vendorDir . '/php-http/discovery/src'),
'GuzzleHttp\\UriTemplate\\' => array($vendorDir . '/guzzlehttp/uri-template/src'), 'GuzzleHttp\\UriTemplate\\' => array($vendorDir . '/guzzlehttp/uri-template/src'),
'GuzzleHttp\\Stream\\' => array($vendorDir . '/ezimuel/guzzlestreams/src'),
'GuzzleHttp\\Ring\\' => array($vendorDir . '/ezimuel/ringphp/src'),
'GuzzleHttp\\Psr7\\' => array($vendorDir . '/guzzlehttp/psr7/src'), 'GuzzleHttp\\Psr7\\' => array($vendorDir . '/guzzlehttp/psr7/src'),
'GuzzleHttp\\Promise\\' => array($vendorDir . '/guzzlehttp/promises/src'), 'GuzzleHttp\\Promise\\' => array($vendorDir . '/guzzlehttp/promises/src'),
'GuzzleHttp\\' => array($vendorDir . '/guzzlehttp/guzzle/src'), 'GuzzleHttp\\' => array($vendorDir . '/guzzlehttp/guzzle/src'),

View File

@ -30,6 +30,7 @@ class ComposerStaticInit691f538563ac6695008ddc51b7722c80
'b6b991a57620e2fb6b2f66f03fe9ddc2' => __DIR__ . '/..' . '/symfony/string/Resources/functions.php', 'b6b991a57620e2fb6b2f66f03fe9ddc2' => __DIR__ . '/..' . '/symfony/string/Resources/functions.php',
'd2136ff22b54ac75cd96a40e0022218e' => __DIR__ . '/..' . '/workerman/webman-framework/src/support/helpers.php', 'd2136ff22b54ac75cd96a40e0022218e' => __DIR__ . '/..' . '/workerman/webman-framework/src/support/helpers.php',
'37a3dc5111fe8f707ab4c132ef1dbc62' => __DIR__ . '/..' . '/guzzlehttp/guzzle/src/functions_include.php', '37a3dc5111fe8f707ab4c132ef1dbc62' => __DIR__ . '/..' . '/guzzlehttp/guzzle/src/functions_include.php',
'ad155f8f1cf0d418fe49e248db8c661b' => __DIR__ . '/..' . '/react/promise/src/functions_include.php',
'09f6b20656683369174dd6fa83b7e5fb' => __DIR__ . '/..' . '/symfony/polyfill-uuid/bootstrap.php', '09f6b20656683369174dd6fa83b7e5fb' => __DIR__ . '/..' . '/symfony/polyfill-uuid/bootstrap.php',
'ef65a1626449d89d0811cf9befce46f0' => __DIR__ . '/..' . '/illuminate/events/functions.php', 'ef65a1626449d89d0811cf9befce46f0' => __DIR__ . '/..' . '/illuminate/events/functions.php',
); );
@ -73,6 +74,7 @@ class ComposerStaticInit691f538563ac6695008ddc51b7722c80
'Symfony\\Contracts\\Translation\\' => 30, 'Symfony\\Contracts\\Translation\\' => 30,
'Symfony\\Contracts\\Service\\' => 26, 'Symfony\\Contracts\\Service\\' => 26,
'Symfony\\Contracts\\EventDispatcher\\' => 34, 'Symfony\\Contracts\\EventDispatcher\\' => 34,
'Symfony\\Component\\Yaml\\' => 23,
'Symfony\\Component\\VarDumper\\' => 28, 'Symfony\\Component\\VarDumper\\' => 28,
'Symfony\\Component\\Uid\\' => 22, 'Symfony\\Component\\Uid\\' => 22,
'Symfony\\Component\\Translation\\' => 30, 'Symfony\\Component\\Translation\\' => 30,
@ -90,6 +92,10 @@ class ComposerStaticInit691f538563ac6695008ddc51b7722c80
'Support\\Bootstrap\\' => 18, 'Support\\Bootstrap\\' => 18,
'Support\\' => 8, 'Support\\' => 8,
), ),
'R' =>
array (
'React\\Promise\\' => 14,
),
'P' => 'P' =>
array ( array (
'Psr\\SimpleCache\\' => 16, 'Psr\\SimpleCache\\' => 16,
@ -101,6 +107,10 @@ class ComposerStaticInit691f538563ac6695008ddc51b7722c80
'Psr\\Clock\\' => 10, 'Psr\\Clock\\' => 10,
'PhpOption\\' => 10, 'PhpOption\\' => 10,
), ),
'O' =>
array (
'OpenSearch\\' => 11,
),
'M' => 'M' =>
array ( array (
'Monolog\\' => 8, 'Monolog\\' => 8,
@ -126,9 +136,15 @@ class ComposerStaticInit691f538563ac6695008ddc51b7722c80
'Illuminate\\Container\\' => 21, 'Illuminate\\Container\\' => 21,
'Illuminate\\Bus\\' => 15, 'Illuminate\\Bus\\' => 15,
), ),
'H' =>
array (
'Http\\Discovery\\' => 15,
),
'G' => 'G' =>
array ( array (
'GuzzleHttp\\UriTemplate\\' => 23, 'GuzzleHttp\\UriTemplate\\' => 23,
'GuzzleHttp\\Stream\\' => 18,
'GuzzleHttp\\Ring\\' => 16,
'GuzzleHttp\\Psr7\\' => 16, 'GuzzleHttp\\Psr7\\' => 16,
'GuzzleHttp\\Promise\\' => 19, 'GuzzleHttp\\Promise\\' => 19,
'GuzzleHttp\\' => 11, 'GuzzleHttp\\' => 11,
@ -264,6 +280,10 @@ class ComposerStaticInit691f538563ac6695008ddc51b7722c80
array ( array (
0 => __DIR__ . '/..' . '/symfony/event-dispatcher-contracts', 0 => __DIR__ . '/..' . '/symfony/event-dispatcher-contracts',
), ),
'Symfony\\Component\\Yaml\\' =>
array (
0 => __DIR__ . '/..' . '/symfony/yaml',
),
'Symfony\\Component\\VarDumper\\' => 'Symfony\\Component\\VarDumper\\' =>
array ( array (
0 => __DIR__ . '/..' . '/symfony/var-dumper', 0 => __DIR__ . '/..' . '/symfony/var-dumper',
@ -328,6 +348,10 @@ class ComposerStaticInit691f538563ac6695008ddc51b7722c80
array ( array (
0 => __DIR__ . '/..' . '/workerman/webman-framework/src/support', 0 => __DIR__ . '/..' . '/workerman/webman-framework/src/support',
), ),
'React\\Promise\\' =>
array (
0 => __DIR__ . '/..' . '/react/promise/src',
),
'Psr\\SimpleCache\\' => 'Psr\\SimpleCache\\' =>
array ( array (
0 => __DIR__ . '/..' . '/psr/simple-cache/src', 0 => __DIR__ . '/..' . '/psr/simple-cache/src',
@ -361,6 +385,10 @@ class ComposerStaticInit691f538563ac6695008ddc51b7722c80
array ( array (
0 => __DIR__ . '/..' . '/phpoption/phpoption/src/PhpOption', 0 => __DIR__ . '/..' . '/phpoption/phpoption/src/PhpOption',
), ),
'OpenSearch\\' =>
array (
0 => __DIR__ . '/..' . '/opensearch-project/opensearch-php/src/OpenSearch',
),
'Monolog\\' => 'Monolog\\' =>
array ( array (
0 => __DIR__ . '/..' . '/monolog/monolog/src/Monolog', 0 => __DIR__ . '/..' . '/monolog/monolog/src/Monolog',
@ -429,10 +457,22 @@ class ComposerStaticInit691f538563ac6695008ddc51b7722c80
array ( array (
0 => __DIR__ . '/..' . '/illuminate/bus', 0 => __DIR__ . '/..' . '/illuminate/bus',
), ),
'Http\\Discovery\\' =>
array (
0 => __DIR__ . '/..' . '/php-http/discovery/src',
),
'GuzzleHttp\\UriTemplate\\' => 'GuzzleHttp\\UriTemplate\\' =>
array ( array (
0 => __DIR__ . '/..' . '/guzzlehttp/uri-template/src', 0 => __DIR__ . '/..' . '/guzzlehttp/uri-template/src',
), ),
'GuzzleHttp\\Stream\\' =>
array (
0 => __DIR__ . '/..' . '/ezimuel/guzzlestreams/src',
),
'GuzzleHttp\\Ring\\' =>
array (
0 => __DIR__ . '/..' . '/ezimuel/ringphp/src',
),
'GuzzleHttp\\Psr7\\' => 'GuzzleHttp\\Psr7\\' =>
array ( array (
0 => __DIR__ . '/..' . '/guzzlehttp/psr7/src', 0 => __DIR__ . '/..' . '/guzzlehttp/psr7/src',

View File

@ -377,6 +377,123 @@
], ],
"install-path": "../egulias/email-validator" "install-path": "../egulias/email-validator"
}, },
{
"name": "ezimuel/guzzlestreams",
"version": "4.1.0",
"version_normalized": "4.1.0.0",
"source": {
"type": "git",
"url": "https://github.com/ezimuel/guzzlestreams.git",
"reference": "903161be81e9f497cc42fb7db982404a4e6441b0"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/ezimuel/guzzlestreams/zipball/903161be81e9f497cc42fb7db982404a4e6441b0",
"reference": "903161be81e9f497cc42fb7db982404a4e6441b0",
"shasum": ""
},
"require": {
"php": ">=7.4.0"
},
"require-dev": {
"phpstan/phpstan": "^2.1",
"phpunit/phpunit": "~9.0"
},
"time": "2025-08-05T06:44:46+00:00",
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "3.0-dev"
}
},
"installation-source": "dist",
"autoload": {
"psr-4": {
"GuzzleHttp\\Stream\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Michael Dowling",
"email": "mtdowling@gmail.com",
"homepage": "https://github.com/mtdowling"
}
],
"description": "Fork of guzzle/streams (abandoned) to be used with elasticsearch-php",
"homepage": "http://guzzlephp.org/",
"keywords": [
"Guzzle",
"stream"
],
"support": {
"source": "https://github.com/ezimuel/guzzlestreams/tree/4.1.0"
},
"install-path": "../ezimuel/guzzlestreams"
},
{
"name": "ezimuel/ringphp",
"version": "1.4.1",
"version_normalized": "1.4.1.0",
"source": {
"type": "git",
"url": "https://github.com/ezimuel/ringphp.git",
"reference": "b97f46088940671100012482577eeb59f26a13b5"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/ezimuel/ringphp/zipball/b97f46088940671100012482577eeb59f26a13b5",
"reference": "b97f46088940671100012482577eeb59f26a13b5",
"shasum": ""
},
"require": {
"ezimuel/guzzlestreams": "^3.0.1 || ^4.0.0",
"php": ">=5.4.0",
"react/promise": "^2.0 || ^3.0"
},
"replace": {
"guzzlehttp/ringphp": "self.version"
},
"require-dev": {
"ext-curl": "*",
"phpunit/phpunit": "~9.0"
},
"suggest": {
"ext-curl": "Guzzle will use specific adapters if cURL is present"
},
"time": "2026-02-11T16:42:15+00:00",
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "1.1-dev"
}
},
"installation-source": "dist",
"autoload": {
"psr-4": {
"GuzzleHttp\\Ring\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Michael Dowling",
"email": "mtdowling@gmail.com",
"homepage": "https://github.com/mtdowling"
}
],
"description": "Fork of guzzle/RingPHP (abandoned) to be used with elasticsearch-php",
"support": {
"source": "https://github.com/ezimuel/ringphp/tree/1.4.1"
},
"install-path": "../ezimuel/ringphp"
},
{ {
"name": "fruitcake/php-cors", "name": "fruitcake/php-cors",
"version": "v1.4.0", "version": "v1.4.0",
@ -2348,6 +2465,178 @@
}, },
"install-path": "../nikic/fast-route" "install-path": "../nikic/fast-route"
}, },
{
"name": "opensearch-project/opensearch-php",
"version": "2.6.0",
"version_normalized": "2.6.0.0",
"source": {
"type": "git",
"url": "https://github.com/opensearch-project/opensearch-php.git",
"reference": "6bcff126ffd8f54ca33a0dd659128cbc5d3b2bcd"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/opensearch-project/opensearch-php/zipball/6bcff126ffd8f54ca33a0dd659128cbc5d3b2bcd",
"reference": "6bcff126ffd8f54ca33a0dd659128cbc5d3b2bcd",
"shasum": ""
},
"require": {
"ext-curl": "*",
"ext-json": ">=1.3.7",
"ezimuel/ringphp": "^1.4.0",
"php": "^8.2",
"php-http/discovery": "^1.20",
"psr/http-client": "^1.0.3",
"psr/http-client-implementation": "*",
"psr/http-factory": "^1.1.0",
"psr/http-factory-implementation": "*",
"psr/http-message": "^2.0",
"psr/http-message-implementation": "*",
"psr/log": "^3.0.2",
"symfony/yaml": "^v6.4.26|^7.3.5|^v8.0.0"
},
"require-dev": {
"aws/aws-sdk-php": "^3.359.8",
"colinodell/psr-testlogger": "^1.3.1",
"ext-zip": "*",
"friendsofphp/php-cs-fixer": "^v3.89.1",
"guzzlehttp/psr7": "^2.8.0",
"mockery/mockery": "^1.6.12",
"phpstan/extension-installer": "^1.4.3",
"phpstan/phpstan": "^1.12.32",
"phpstan/phpstan-deprecation-rules": "^1.2.1",
"phpstan/phpstan-mockery": "^1.1.3",
"phpstan/phpstan-phpunit": "^1.4.2",
"phpunit/phpunit": "^10.5.58",
"react/promise": "^v3.3",
"symfony/console": "v6.4.31|^7.4.3|^8.0.3",
"symfony/finder": "^v6.4.31|^7.4.3|^v8.0.3",
"symfony/http-client": "^6.4.31|^7.4.3|^v8.0.3",
"symfony/http-client-contracts": "^v3.6.0|^v8.0.3",
"twig/twig": "^3.0"
},
"suggest": {
"aws/aws-sdk-php": "Required (^3.0.0) in order to use the AWS Signing Client Decorator",
"guzzlehttp/psr7": "Required (^2.7) in order to use the Guzzle HTTP client",
"monolog/monolog": "Allows for client-level logging and tracing",
"symfony/http-client": "Required (^6.4|^7.0|^8.0) in order to use the Symfony HTTP client"
},
"time": "2026-03-24T03:15:15+00:00",
"type": "library",
"installation-source": "dist",
"autoload": {
"psr-4": {
"OpenSearch\\": "src/OpenSearch/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"Apache-2.0",
"LGPL-2.1-only"
],
"authors": [
{
"name": "Elastic"
},
{
"name": "OpenSearch Contributors"
}
],
"description": "PHP Client for OpenSearch",
"keywords": [
"client",
"elasticsearch",
"opensearch",
"search"
],
"support": {
"issues": "https://github.com/opensearch-project/opensearch-php/issues",
"source": "https://github.com/opensearch-project/opensearch-php/tree/2.6.0"
},
"install-path": "../opensearch-project/opensearch-php"
},
{
"name": "php-http/discovery",
"version": "1.20.0",
"version_normalized": "1.20.0.0",
"source": {
"type": "git",
"url": "https://github.com/php-http/discovery.git",
"reference": "82fe4c73ef3363caed49ff8dd1539ba06044910d"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/php-http/discovery/zipball/82fe4c73ef3363caed49ff8dd1539ba06044910d",
"reference": "82fe4c73ef3363caed49ff8dd1539ba06044910d",
"shasum": ""
},
"require": {
"composer-plugin-api": "^1.0|^2.0",
"php": "^7.1 || ^8.0"
},
"conflict": {
"nyholm/psr7": "<1.0",
"zendframework/zend-diactoros": "*"
},
"provide": {
"php-http/async-client-implementation": "*",
"php-http/client-implementation": "*",
"psr/http-client-implementation": "*",
"psr/http-factory-implementation": "*",
"psr/http-message-implementation": "*"
},
"require-dev": {
"composer/composer": "^1.0.2|^2.0",
"graham-campbell/phpspec-skip-example-extension": "^5.0",
"php-http/httplug": "^1.0 || ^2.0",
"php-http/message-factory": "^1.0",
"phpspec/phpspec": "^5.1 || ^6.1 || ^7.3",
"sebastian/comparator": "^3.0.5 || ^4.0.8",
"symfony/phpunit-bridge": "^6.4.4 || ^7.0.1"
},
"time": "2024-10-02T11:20:13+00:00",
"type": "composer-plugin",
"extra": {
"class": "Http\\Discovery\\Composer\\Plugin",
"plugin-optional": true
},
"installation-source": "dist",
"autoload": {
"psr-4": {
"Http\\Discovery\\": "src/"
},
"exclude-from-classmap": [
"src/Composer/Plugin.php"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Márk Sági-Kazár",
"email": "mark.sagikazar@gmail.com"
}
],
"description": "Finds and installs PSR-7, PSR-17, PSR-18 and HTTPlug implementations",
"homepage": "http://php-http.org",
"keywords": [
"adapter",
"client",
"discovery",
"factory",
"http",
"message",
"psr17",
"psr7"
],
"support": {
"issues": "https://github.com/php-http/discovery/issues",
"source": "https://github.com/php-http/discovery/tree/1.20.0"
},
"install-path": "../php-http/discovery"
},
{ {
"name": "phpoption/phpoption", "name": "phpoption/phpoption",
"version": "1.9.5", "version": "1.9.5",
@ -2909,6 +3198,82 @@
}, },
"install-path": "../ralouphie/getallheaders" "install-path": "../ralouphie/getallheaders"
}, },
{
"name": "react/promise",
"version": "v3.3.0",
"version_normalized": "3.3.0.0",
"source": {
"type": "git",
"url": "https://github.com/reactphp/promise.git",
"reference": "23444f53a813a3296c1368bb104793ce8d88f04a"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/reactphp/promise/zipball/23444f53a813a3296c1368bb104793ce8d88f04a",
"reference": "23444f53a813a3296c1368bb104793ce8d88f04a",
"shasum": ""
},
"require": {
"php": ">=7.1.0"
},
"require-dev": {
"phpstan/phpstan": "1.12.28 || 1.4.10",
"phpunit/phpunit": "^9.6 || ^7.5"
},
"time": "2025-08-19T18:57:03+00:00",
"type": "library",
"installation-source": "dist",
"autoload": {
"files": [
"src/functions_include.php"
],
"psr-4": {
"React\\Promise\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Jan Sorgalla",
"email": "jsorgalla@gmail.com",
"homepage": "https://sorgalla.com/"
},
{
"name": "Christian Lück",
"email": "christian@clue.engineering",
"homepage": "https://clue.engineering/"
},
{
"name": "Cees-Jan Kiewiet",
"email": "reactphp@ceesjankiewiet.nl",
"homepage": "https://wyrihaximus.net/"
},
{
"name": "Chris Boden",
"email": "cboden@gmail.com",
"homepage": "https://cboden.dev/"
}
],
"description": "A lightweight implementation of CommonJS Promises/A for PHP",
"keywords": [
"promise",
"promises"
],
"support": {
"issues": "https://github.com/reactphp/promise/issues",
"source": "https://github.com/reactphp/promise/tree/v3.3.0"
},
"funding": [
{
"url": "https://opencollective.com/reactphp",
"type": "open_collective"
}
],
"install-path": "../react/promise"
},
{ {
"name": "symfony/clock", "name": "symfony/clock",
"version": "v8.0.8", "version": "v8.0.8",
@ -5072,6 +5437,84 @@
], ],
"install-path": "../symfony/var-dumper" "install-path": "../symfony/var-dumper"
}, },
{
"name": "symfony/yaml",
"version": "v8.0.8",
"version_normalized": "8.0.8.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/yaml.git",
"reference": "54174ab48c0c0f9e21512b304be17f8150ccf8f1"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/yaml/zipball/54174ab48c0c0f9e21512b304be17f8150ccf8f1",
"reference": "54174ab48c0c0f9e21512b304be17f8150ccf8f1",
"shasum": ""
},
"require": {
"php": ">=8.4",
"symfony/polyfill-ctype": "^1.8"
},
"conflict": {
"symfony/console": "<7.4"
},
"require-dev": {
"symfony/console": "^7.4|^8.0"
},
"time": "2026-03-30T15:14:47+00:00",
"bin": [
"Resources/bin/yaml-lint"
],
"type": "library",
"installation-source": "dist",
"autoload": {
"psr-4": {
"Symfony\\Component\\Yaml\\": ""
},
"exclude-from-classmap": [
"/Tests/"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Fabien Potencier",
"email": "fabien@symfony.com"
},
{
"name": "Symfony Community",
"homepage": "https://symfony.com/contributors"
}
],
"description": "Loads and dumps YAML files",
"homepage": "https://symfony.com",
"support": {
"source": "https://github.com/symfony/yaml/tree/v8.0.8"
},
"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/yaml"
},
{ {
"name": "vlucas/phpdotenv", "name": "vlucas/phpdotenv",
"version": "v5.6.3", "version": "v5.6.3",

View File

@ -1,9 +1,9 @@
<?php return array( <?php return array(
'root' => array( 'root' => array(
'name' => 'workerman/webman', 'name' => 'workerman/webman',
'pretty_version' => '1.0.0+no-version-set', 'pretty_version' => 'dev-main',
'version' => '1.0.0.0', 'version' => 'dev-main',
'reference' => null, 'reference' => 'b8f599a6177d1d8c523473a30f8c357bc9b7bf66',
'type' => 'project', 'type' => 'project',
'install_path' => __DIR__ . '/../../', 'install_path' => __DIR__ . '/../../',
'aliases' => array(), 'aliases' => array(),
@ -55,6 +55,24 @@
'aliases' => array(), 'aliases' => array(),
'dev_requirement' => false, 'dev_requirement' => false,
), ),
'ezimuel/guzzlestreams' => array(
'pretty_version' => '4.1.0',
'version' => '4.1.0.0',
'reference' => '903161be81e9f497cc42fb7db982404a4e6441b0',
'type' => 'library',
'install_path' => __DIR__ . '/../ezimuel/guzzlestreams',
'aliases' => array(),
'dev_requirement' => false,
),
'ezimuel/ringphp' => array(
'pretty_version' => '1.4.1',
'version' => '1.4.1.0',
'reference' => 'b97f46088940671100012482577eeb59f26a13b5',
'type' => 'library',
'install_path' => __DIR__ . '/../ezimuel/ringphp',
'aliases' => array(),
'dev_requirement' => false,
),
'fruitcake/php-cors' => array( 'fruitcake/php-cors' => array(
'pretty_version' => 'v1.4.0', 'pretty_version' => 'v1.4.0',
'version' => '1.4.0.0', 'version' => '1.4.0.0',
@ -100,6 +118,12 @@
'aliases' => array(), 'aliases' => array(),
'dev_requirement' => false, 'dev_requirement' => false,
), ),
'guzzlehttp/ringphp' => array(
'dev_requirement' => false,
'replaced' => array(
0 => '1.4.1',
),
),
'guzzlehttp/uri-template' => array( 'guzzlehttp/uri-template' => array(
'pretty_version' => 'v1.0.5', 'pretty_version' => 'v1.0.5',
'version' => '1.0.5.0', 'version' => '1.0.5.0',
@ -307,6 +331,36 @@
'aliases' => array(), 'aliases' => array(),
'dev_requirement' => false, 'dev_requirement' => false,
), ),
'opensearch-project/opensearch-php' => array(
'pretty_version' => '2.6.0',
'version' => '2.6.0.0',
'reference' => '6bcff126ffd8f54ca33a0dd659128cbc5d3b2bcd',
'type' => 'library',
'install_path' => __DIR__ . '/../opensearch-project/opensearch-php',
'aliases' => array(),
'dev_requirement' => false,
),
'php-http/async-client-implementation' => array(
'dev_requirement' => false,
'provided' => array(
0 => '*',
),
),
'php-http/client-implementation' => array(
'dev_requirement' => false,
'provided' => array(
0 => '*',
),
),
'php-http/discovery' => array(
'pretty_version' => '1.20.0',
'version' => '1.20.0.0',
'reference' => '82fe4c73ef3363caed49ff8dd1539ba06044910d',
'type' => 'composer-plugin',
'install_path' => __DIR__ . '/../php-http/discovery',
'aliases' => array(),
'dev_requirement' => false,
),
'phpoption/phpoption' => array( 'phpoption/phpoption' => array(
'pretty_version' => '1.9.5', 'pretty_version' => '1.9.5',
'version' => '1.9.5.0', 'version' => '1.9.5.0',
@ -373,7 +427,8 @@
'psr/http-client-implementation' => array( 'psr/http-client-implementation' => array(
'dev_requirement' => false, 'dev_requirement' => false,
'provided' => array( 'provided' => array(
0 => '1.0', 0 => '*',
1 => '1.0',
), ),
), ),
'psr/http-factory' => array( 'psr/http-factory' => array(
@ -388,7 +443,8 @@
'psr/http-factory-implementation' => array( 'psr/http-factory-implementation' => array(
'dev_requirement' => false, 'dev_requirement' => false,
'provided' => array( 'provided' => array(
0 => '1.0', 0 => '*',
1 => '1.0',
), ),
), ),
'psr/http-message' => array( 'psr/http-message' => array(
@ -403,7 +459,8 @@
'psr/http-message-implementation' => array( 'psr/http-message-implementation' => array(
'dev_requirement' => false, 'dev_requirement' => false,
'provided' => array( 'provided' => array(
0 => '1.0', 0 => '*',
1 => '1.0',
), ),
), ),
'psr/log' => array( 'psr/log' => array(
@ -440,6 +497,15 @@
'aliases' => array(), 'aliases' => array(),
'dev_requirement' => false, 'dev_requirement' => false,
), ),
'react/promise' => array(
'pretty_version' => 'v3.3.0',
'version' => '3.3.0.0',
'reference' => '23444f53a813a3296c1368bb104793ce8d88f04a',
'type' => 'library',
'install_path' => __DIR__ . '/../react/promise',
'aliases' => array(),
'dev_requirement' => false,
),
'spatie/once' => array( 'spatie/once' => array(
'dev_requirement' => false, 'dev_requirement' => false,
'replaced' => array( 'replaced' => array(
@ -683,6 +749,15 @@
'aliases' => array(), 'aliases' => array(),
'dev_requirement' => false, 'dev_requirement' => false,
), ),
'symfony/yaml' => array(
'pretty_version' => 'v8.0.8',
'version' => '8.0.8.0',
'reference' => '54174ab48c0c0f9e21512b304be17f8150ccf8f1',
'type' => 'library',
'install_path' => __DIR__ . '/../symfony/yaml',
'aliases' => array(),
'dev_requirement' => false,
),
'vlucas/phpdotenv' => array( 'vlucas/phpdotenv' => array(
'pretty_version' => 'v5.6.3', 'pretty_version' => 'v5.6.3',
'version' => '5.6.3.0', 'version' => '5.6.3.0',
@ -747,9 +822,9 @@
'dev_requirement' => false, 'dev_requirement' => false,
), ),
'workerman/webman' => array( 'workerman/webman' => array(
'pretty_version' => '1.0.0+no-version-set', 'pretty_version' => 'dev-main',
'version' => '1.0.0.0', 'version' => 'dev-main',
'reference' => null, 'reference' => 'b8f599a6177d1d8c523473a30f8c357bc9b7bf66',
'type' => 'project', 'type' => 'project',
'install_path' => __DIR__ . '/../../', 'install_path' => __DIR__ . '/../../',
'aliases' => array(), 'aliases' => array(),

View File

@ -0,0 +1,101 @@
=========
Changelog
=========
4.0.0 (2024-06-19)
------------------
* Changed the minimum PHP version to 7.4+
* Created .gitattributes to avoid installing test folder
https://github.com/ezimuel/guzzlestreams/pull/2
3.0.0 (2014-10-12)
------------------
* Now supports creating streams from functions and iterators.
* Supports creating buffered streams and asynchronous streams.
* Removed ``functions.php``. Use the corresponding functions provided by
``GuzzleHttp\Streams\Utils`` instead.
* Moved ``GuzzleHttp\Stream\MetadataStreamInterface::getMetadata`` to
``GuzzleHttp\Stream\StreamInterface``. MetadataStreamInterface is no longer
used and is marked as deprecated.
* Added ``attach()`` to ``GuzzleHttp\Stream\StreamInterface`` for PSR-7
compatibility.
* Removed ``flush()`` from StreamInterface.
* Removed the ``$maxLength`` parameter from
``GuzzleHttp\Stream\StreamInterface::getContents()``. This function now
returns the entire remainder of the stream. If you want to limit the maximum
amount of data read from the stream, use the
``GuzzleHttp\Stream\Utils::copyToString()`` function.
* Streams that return an empty string, ``''``, are no longer considered a
failure. You MUST return ``false`` to mark the read as a failure, and ensure
that any decorators you create properly return ``true`` in response to the
``eof()`` method when the stream is consumed.
* ``GuzzleHttp\Stream\Stream::__construct``,
``GuzzleHttp\Stream\Stream::factory``, and
``GuzzleHttp\Stream\Utils::create`` no longer accept a size in the second
argument. They now accept an associative array of options, including the
"size" key and "metadata" key which can be used to provide custom metadata.
* Added ``GuzzleHttp\Stream\BufferStream`` to add support for buffering data,
and when read, shifting data off of the buffer.
* Added ``GuzzleHttp\Stream\NullBuffer`` which can be used as a buffer that
does not actually store any data.
* Added ``GuzzleHttp\Stream\AsyncStream`` to provide support for non-blocking
streams that can be filled by a remote source (e.g., an event-loop). If a
``drain`` option is provided, the stream can also act as if it is a blocking
stream.
2.1.0 (2014-08-17)
------------------
* Added an InflateStream to inflate gzipped or deflated content.
* Added ``flush`` to stream wrapper.
* Added the ability to easily register the GuzzleStreamWrapper if needed.
2.0.0 (2014-08-16)
------------------
* Deprecated functions.php and moved all of those methods to
``GuzzleHttp\Streams\Utils``. Use ``GuzzleHttp\Stream\Stream::factory()``
instead of ``GuzzleHttp\Stream\create()`` to create new streams.
* Added ``flush()`` to ``StreamInterface``. This method is used to flush any
underlying stream write buffers.
* Added ``FnStream`` to easily decorate stream behavior with callables.
* ``Utils::hash`` now throws an exception when the stream cannot seek to 0.
1.5.1 (2014-09-10)
------------------
* Stream metadata is grabbed from the underlying stream each time
``getMetadata`` is called rather than returning a value from a cache.
* Properly closing all underlying streams when AppendStream is closed.
* Seek functions no longer throw exceptions.
* LazyOpenStream now correctly returns the underlying stream resource when
detached.
1.5.0 (2014-08-07)
------------------
* Added ``Stream\safe_open`` to open stream resources and throw exceptions
instead of raising errors.
1.4.0 (2014-07-19)
------------------
* Added a LazyOpenStream
1.3.0 (2014-07-15)
------------------
* Added an AppendStream to stream over multiple stream one after the other.
1.2.0 (2014-07-15)
------------------
* Updated the ``detach()`` method to return the underlying stream resource or
``null`` if it does not wrap a resource.
* Multiple fixes for how streams behave when the underlying resource is
detached
* Do not clear statcache when a stream does not have a 'uri'
* Added a fix to LimitStream
* Added a condition to ensure that functions.php can be required multiple times

19
vendor/ezimuel/guzzlestreams/LICENSE vendored Normal file
View File

@ -0,0 +1,19 @@
Copyright (c) 2014 Michael Dowling, https://github.com/mtdowling <mtdowling@gmail.com>
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.

19
vendor/ezimuel/guzzlestreams/Makefile vendored Normal file
View File

@ -0,0 +1,19 @@
all: clean coverage
release: tag
git push origin --tags
tag:
chag tag --sign --debug CHANGELOG.rst
test:
vendor/bin/phpunit
coverage:
vendor/bin/phpunit --coverage-html=artifacts/coverage
view-coverage:
open artifacts/coverage/index.html
clean:
rm -rf artifacts/*

43
vendor/ezimuel/guzzlestreams/README.rst vendored Normal file
View File

@ -0,0 +1,43 @@
==============
Guzzle Streams
==============
**Note:** this is a fork of the original project since it was abandoned.
The main goal of this fork is to offer support for `elastic/elasticsearch-php <https://github.com/elastic/elasticsearch-php>`_
version 7.x.
## Here the original README
Provides a simple abstraction over streams of data.
This library is used in `Guzzle 5 <https://github.com/guzzle/guzzle>`_, and is
(currently) compatible with the WIP PSR-7.
Installation
============
This package can be installed easily using `Composer <http://getcomposer.org>`_.
Simply add the following to the composer.json file at the root of your project:
.. code-block:: javascript
{
"require": {
"guzzlehttp/streams": "~3.0"
}
}
Then install your dependencies using ``composer.phar install``.
Documentation
=============
The documentation for this package can be found on the main Guzzle website at
http://docs.guzzlephp.org/en/guzzle4/streams.html.
Testing
=======
This library is tested using PHPUnit. You'll need to install the dependencies
using `Composer <http://getcomposer.org>`_ then run ``make test``.

View File

@ -0,0 +1,33 @@
{
"name": "ezimuel/guzzlestreams",
"description": "Fork of guzzle/streams (abandoned) to be used with elasticsearch-php",
"homepage": "http://guzzlephp.org/",
"keywords": ["stream", "guzzle"],
"license": "MIT",
"authors": [
{
"name": "Michael Dowling",
"email": "mtdowling@gmail.com",
"homepage": "https://github.com/mtdowling"
}
],
"require": {
"php": ">=7.4.0"
},
"require-dev": {
"phpunit/phpunit": "~9.0",
"phpstan/phpstan": "^2.1"
},
"autoload": {
"psr-4": { "GuzzleHttp\\Stream\\": "src/" }
},
"scripts": {
"test": "make test",
"phpstan": "vendor/bin/phpstan analyse src"
},
"extra": {
"branch-alias": {
"dev-master": "3.0-dev"
}
}
}

View File

@ -0,0 +1,220 @@
<?php
namespace GuzzleHttp\Stream;
use GuzzleHttp\Stream\Exception\CannotAttachException;
/**
* Reads from multiple streams, one after the other.
*
* This is a read-only stream decorator.
*/
class AppendStream implements StreamInterface
{
/** @var StreamInterface[] Streams being decorated */
private $streams = [];
private $seekable = true;
private $current = 0;
private $pos = 0;
private $detached = false;
/**
* @param StreamInterface[] $streams Streams to decorate. Each stream must
* be readable.
*/
public function __construct(array $streams = [])
{
foreach ($streams as $stream) {
$this->addStream($stream);
}
}
public function __toString()
{
try {
$this->seek(0);
return $this->getContents();
} catch (\Exception $e) {
return '';
}
}
/**
* Add a stream to the AppendStream
*
* @param StreamInterface $stream Stream to append. Must be readable.
*
* @throws \InvalidArgumentException if the stream is not readable
*/
public function addStream(StreamInterface $stream)
{
if (!$stream->isReadable()) {
throw new \InvalidArgumentException('Each stream must be readable');
}
// The stream is only seekable if all streams are seekable
if (!$stream->isSeekable()) {
$this->seekable = false;
}
$this->streams[] = $stream;
}
public function getContents()
{
return Utils::copyToString($this);
}
/**
* Closes each attached stream.
*
* {@inheritdoc}
*/
public function close()
{
$this->pos = $this->current = 0;
foreach ($this->streams as $stream) {
$stream->close();
}
$this->streams = [];
}
/**
* Detaches each attached stream
*
* {@inheritdoc}
*/
public function detach()
{
$this->close();
$this->detached = true;
}
public function attach($stream)
{
throw new CannotAttachException();
}
public function tell()
{
return $this->pos;
}
/**
* Tries to calculate the size by adding the size of each stream.
*
* If any of the streams do not return a valid number, then the size of the
* append stream cannot be determined and null is returned.
*
* {@inheritdoc}
*/
public function getSize()
{
$size = 0;
foreach ($this->streams as $stream) {
$s = $stream->getSize();
if ($s === null) {
return null;
}
$size += $s;
}
return $size;
}
public function eof()
{
return !$this->streams ||
($this->current >= count($this->streams) - 1 &&
$this->streams[$this->current]->eof());
}
/**
* Attempts to seek to the given position. Only supports SEEK_SET.
*
* {@inheritdoc}
*/
public function seek($offset, $whence = SEEK_SET)
{
if (!$this->seekable || $whence !== SEEK_SET) {
return false;
}
$success = true;
$this->pos = $this->current = 0;
// Rewind each stream
foreach ($this->streams as $stream) {
if (!$stream->seek(0)) {
$success = false;
}
}
if (!$success) {
return false;
}
// Seek to the actual position by reading from each stream
while ($this->pos < $offset && !$this->eof()) {
$this->read(min(8096, $offset - $this->pos));
}
return $this->pos == $offset;
}
/**
* Reads from all of the appended streams until the length is met or EOF.
*
* {@inheritdoc}
*/
public function read($length)
{
$buffer = '';
$total = count($this->streams) - 1;
$remaining = $length;
while ($remaining > 0) {
// Progress to the next stream if needed.
if ($this->streams[$this->current]->eof()) {
if ($this->current == $total) {
break;
}
$this->current++;
}
$buffer .= $this->streams[$this->current]->read($remaining);
$remaining = $length - strlen($buffer);
}
$this->pos += strlen($buffer);
return $buffer;
}
public function isReadable()
{
return true;
}
public function isWritable()
{
return false;
}
public function isSeekable()
{
return $this->seekable;
}
public function write($string)
{
return false;
}
public function getMetadata($key = null)
{
return $key ? null : [];
}
}

View File

@ -0,0 +1,207 @@
<?php
namespace GuzzleHttp\Stream;
/**
* Represents an asynchronous read-only stream that supports a drain event and
* pumping data from a source stream.
*
* The AsyncReadStream can be used as a completely asynchronous stream, meaning
* the data you can read from the stream will immediately return only
* the data that is currently buffered.
*
* AsyncReadStream can also be used in a "blocking" manner if a "pump" function
* is provided. When a caller requests more bytes than are available in the
* buffer, then the pump function is used to block until the requested number
* of bytes are available or the remote source stream has errored, closed, or
* timed-out. This behavior isn't strictly "blocking" because the pump function
* can send other transfers while waiting on the desired buffer size to be
* ready for reading (e.g., continue to tick an event loop).
*
* @unstable This class is subject to change.
*/
class AsyncReadStream implements StreamInterface
{
use StreamDecoratorTrait;
/** @var callable|null Fn used to notify writers the buffer has drained */
private $drain;
/** @var callable|null Fn used to block for more data */
private $pump;
/** @var int|null Highwater mark of the underlying buffer */
private $hwm;
/** @var bool Whether or not drain needs to be called at some point */
private $needsDrain;
/** @var int The expected size of the remote source */
private $size;
/**
* In order to utilize high water marks to tell writers to slow down, the
* provided stream must answer to the "hwm" stream metadata variable,
* providing the high water mark. If no "hwm" metadata value is available,
* then the "drain" functionality is not utilized.
*
* This class accepts an associative array of configuration options.
*
* - drain: (callable) Function to invoke when the stream has drained,
* meaning the buffer is now writable again because the size of the
* buffer is at an acceptable level (e.g., below the high water mark).
* The function accepts a single argument, the buffer stream object that
* has drained.
* - pump: (callable) A function that accepts the number of bytes to read
* from the source stream. This function will block until all of the data
* that was requested has been read, EOF of the source stream, or the
* source stream is closed.
* - size: (int) The expected size in bytes of the data that will be read
* (if known up-front).
*
* @param StreamInterface $buffer Buffer that contains the data that has
* been read by the event loop.
* @param array $config Associative array of options.
*
* @throws \InvalidArgumentException if the buffer is not readable and
* writable.
*/
public function __construct(
StreamInterface $buffer,
array $config = []
) {
if (!$buffer->isReadable() || !$buffer->isWritable()) {
throw new \InvalidArgumentException(
'Buffer must be readable and writable'
);
}
if (isset($config['size'])) {
$this->size = $config['size'];
}
static $callables = ['pump', 'drain'];
foreach ($callables as $check) {
if (isset($config[$check])) {
if (!is_callable($config[$check])) {
throw new \InvalidArgumentException(
$check . ' must be callable'
);
}
$this->{$check} = $config[$check];
}
}
$this->hwm = $buffer->getMetadata('hwm');
// Cannot drain when there's no high water mark.
if ($this->hwm === null) {
$this->drain = null;
}
$this->stream = $buffer;
}
/**
* Factory method used to create new async stream and an underlying buffer
* if no buffer is provided.
*
* This function accepts the same options as AsyncReadStream::__construct,
* but added the following key value pairs:
*
* - buffer: (StreamInterface) Buffer used to buffer data. If none is
* provided, a default buffer is created.
* - hwm: (int) High water mark to use if a buffer is created on your
* behalf.
* - max_buffer: (int) If provided, wraps the utilized buffer in a
* DroppingStream decorator to ensure that buffer does not exceed a given
* length. When exceeded, the stream will begin dropping data. Set the
* max_buffer to 0, to use a NullStream which does not store data.
* - write: (callable) A function that is invoked when data is written
* to the underlying buffer. The function accepts the buffer as the first
* argument, and the data being written as the second. The function MUST
* return the number of bytes that were written or false to let writers
* know to slow down.
* - drain: (callable) See constructor documentation.
* - pump: (callable) See constructor documentation.
*
* @param array $options Associative array of options.
*
* @return array Returns an array containing the buffer used to buffer
* data, followed by the ready to use AsyncReadStream object.
*/
public static function create(array $options = [])
{
$maxBuffer = isset($options['max_buffer'])
? $options['max_buffer']
: null;
if ($maxBuffer === 0) {
$buffer = new NullStream();
} elseif (isset($options['buffer'])) {
$buffer = $options['buffer'];
} else {
$hwm = isset($options['hwm']) ? $options['hwm'] : 16384;
$buffer = new BufferStream($hwm);
}
if ($maxBuffer > 0) {
$buffer = new DroppingStream($buffer, $options['max_buffer']);
}
// Call the on_write callback if an on_write function was provided.
if (isset($options['write'])) {
$onWrite = $options['write'];
$buffer = FnStream::decorate($buffer, [
'write' => function ($string) use ($buffer, $onWrite) {
$result = $buffer->write($string);
$onWrite($buffer, $string);
return $result;
}
]);
}
return [$buffer, new self($buffer, $options)];
}
public function getSize()
{
return $this->size;
}
public function isWritable()
{
return false;
}
public function write($string)
{
return false;
}
public function read($length)
{
if (!$this->needsDrain && $this->drain) {
$this->needsDrain = $this->stream->getSize() >= $this->hwm;
}
$result = $this->stream->read($length);
// If we need to drain, then drain when the buffer is empty.
if ($this->needsDrain && $this->stream->getSize() === 0) {
$this->needsDrain = false;
$drainFn = $this->drain;
$drainFn($this->stream);
}
$resultLen = strlen($result);
// If a pump was provided, the buffer is still open, and not enough
// data was given, then block until the data is provided.
if ($this->pump && $resultLen < $length) {
$pumpFn = $this->pump;
$result .= $pumpFn($length - $resultLen);
}
return $result;
}
}

View File

@ -0,0 +1,138 @@
<?php
namespace GuzzleHttp\Stream;
use GuzzleHttp\Stream\Exception\CannotAttachException;
/**
* Provides a buffer stream that can be written to to fill a buffer, and read
* from to remove bytes from the buffer.
*
* This stream returns a "hwm" metadata value that tells upstream consumers
* what the configured high water mark of the stream is, or the maximum
* preferred size of the buffer.
*
* @package GuzzleHttp\Stream
*/
class BufferStream implements StreamInterface
{
private $hwm;
private $buffer = '';
/**
* @param int $hwm High water mark, representing the preferred maximum
* buffer size. If the size of the buffer exceeds the high
* water mark, then calls to write will continue to succeed
* but will return false to inform writers to slow down
* until the buffer has been drained by reading from it.
*/
public function __construct($hwm = 16384)
{
$this->hwm = $hwm;
}
public function __toString()
{
return $this->getContents();
}
public function getContents()
{
$buffer = $this->buffer;
$this->buffer = '';
return $buffer;
}
public function close()
{
$this->buffer = '';
}
public function detach()
{
$this->close();
}
public function attach($stream)
{
throw new CannotAttachException();
}
public function getSize()
{
return strlen($this->buffer);
}
public function isReadable()
{
return true;
}
public function isWritable()
{
return true;
}
public function isSeekable()
{
return false;
}
public function seek($offset, $whence = SEEK_SET)
{
return false;
}
public function eof()
{
return strlen($this->buffer) === 0;
}
public function tell()
{
return false;
}
/**
* Reads data from the buffer.
*/
public function read($length)
{
$currentLength = strlen($this->buffer);
if ($length >= $currentLength) {
// No need to slice the buffer because we don't have enough data.
$result = $this->buffer;
$this->buffer = '';
} else {
// Slice up the result to provide a subset of the buffer.
$result = substr($this->buffer, 0, $length);
$this->buffer = substr($this->buffer, $length);
}
return $result;
}
/**
* Writes data to the buffer.
*/
public function write($string)
{
$this->buffer .= $string;
if (strlen($this->buffer) >= $this->hwm) {
return false;
}
return strlen($string);
}
public function getMetadata($key = null)
{
if ($key == 'hwm') {
return $this->hwm;
}
return $key ? null : [];
}
}

View File

@ -0,0 +1,122 @@
<?php
namespace GuzzleHttp\Stream;
use GuzzleHttp\Stream\Exception\SeekException;
/**
* Stream decorator that can cache previously read bytes from a sequentially
* read stream.
*/
class CachingStream implements StreamInterface
{
use StreamDecoratorTrait;
/** @var StreamInterface Stream being wrapped */
private $remoteStream;
/** @var int Number of bytes to skip reading due to a write on the buffer */
private $skipReadBytes = 0;
/**
* We will treat the buffer object as the body of the stream
*
* @param StreamInterface $stream Stream to cache
* @param StreamInterface $target Optionally specify where data is cached
*/
public function __construct(
StreamInterface $stream,
?StreamInterface $target = null
) {
$this->remoteStream = $stream;
$this->stream = $target ?: new Stream(fopen('php://temp', 'r+'));
}
public function getSize()
{
return max($this->stream->getSize(), $this->remoteStream->getSize());
}
/**
* {@inheritdoc}
* @throws SeekException When seeking with SEEK_END or when seeking
* past the total size of the buffer stream
*/
public function seek($offset, $whence = SEEK_SET)
{
if ($whence == SEEK_SET) {
$byte = $offset;
} elseif ($whence == SEEK_CUR) {
$byte = $offset + $this->tell();
} else {
return false;
}
// You cannot skip ahead past where you've read from the remote stream
if ($byte > $this->stream->getSize()) {
throw new SeekException(
$this,
$byte,
sprintf('Cannot seek to byte %d when the buffered stream only'
. ' contains %d bytes', $byte, $this->stream->getSize())
);
}
return $this->stream->seek($byte);
}
public function read($length)
{
// Perform a regular read on any previously read data from the buffer
$data = $this->stream->read($length);
$remaining = $length - strlen($data);
// More data was requested so read from the remote stream
if ($remaining) {
// If data was written to the buffer in a position that would have
// been filled from the remote stream, then we must skip bytes on
// the remote stream to emulate overwriting bytes from that
// position. This mimics the behavior of other PHP stream wrappers.
$remoteData = $this->remoteStream->read(
$remaining + $this->skipReadBytes
);
if ($this->skipReadBytes) {
$len = strlen($remoteData);
$remoteData = substr($remoteData, $this->skipReadBytes);
$this->skipReadBytes = max(0, $this->skipReadBytes - $len);
}
$data .= $remoteData;
$this->stream->write($remoteData);
}
return $data;
}
public function write($string)
{
// When appending to the end of the currently read stream, you'll want
// to skip bytes from being read from the remote stream to emulate
// other stream wrappers. Basically replacing bytes of data of a fixed
// length.
$overflow = (strlen($string) + $this->tell()) - $this->remoteStream->tell();
if ($overflow > 0) {
$this->skipReadBytes += $overflow;
}
return $this->stream->write($string);
}
public function eof()
{
return $this->stream->eof() && $this->remoteStream->eof();
}
/**
* Close both the remote stream and buffer stream
*/
public function close()
{
$this->remoteStream->close() && $this->stream->close();
}
}

View File

@ -0,0 +1,42 @@
<?php
namespace GuzzleHttp\Stream;
/**
* Stream decorator that begins dropping data once the size of the underlying
* stream becomes too full.
*/
class DroppingStream implements StreamInterface
{
use StreamDecoratorTrait;
private $maxLength;
/**
* @param StreamInterface $stream Underlying stream to decorate.
* @param int $maxLength Maximum size before dropping data.
*/
public function __construct(StreamInterface $stream, $maxLength)
{
$this->stream = $stream;
$this->maxLength = $maxLength;
}
public function write($string)
{
$diff = $this->maxLength - $this->stream->getSize();
// Begin returning false when the underlying stream is too large.
if ($diff <= 0) {
return false;
}
// Write the stream or a subset of the stream if needed.
if (strlen($string) < $diff) {
return $this->stream->write($string);
}
$this->stream->write(substr($string, 0, $diff));
return false;
}
}

View File

@ -0,0 +1,4 @@
<?php
namespace GuzzleHttp\Stream\Exception;
class CannotAttachException extends \RuntimeException {}

View File

@ -0,0 +1,27 @@
<?php
namespace GuzzleHttp\Stream\Exception;
use GuzzleHttp\Stream\StreamInterface;
/**
* Exception thrown when a seek fails on a stream.
*/
class SeekException extends \RuntimeException
{
private $stream;
public function __construct(StreamInterface $stream, $pos = 0, $msg = '')
{
$this->stream = $stream;
$msg = $msg ?: 'Could not seek the stream to position ' . $pos;
parent::__construct($msg);
}
/**
* @return StreamInterface
*/
public function getStream()
{
return $this->stream;
}
}

View File

@ -0,0 +1,147 @@
<?php
namespace GuzzleHttp\Stream;
/**
* Compose stream implementations based on a hash of functions.
*
* Allows for easy testing and extension of a provided stream without needing
* to create a concrete class for a simple extension point.
*/
class FnStream implements StreamInterface
{
/** @var array */
private $methods;
/** @var array Methods that must be implemented in the given array */
private static $slots = ['__toString', 'close', 'detach', 'attach',
'getSize', 'tell', 'eof', 'isSeekable', 'seek', 'isWritable', 'write',
'isReadable', 'read', 'getContents', 'getMetadata'];
/**
* @param array $methods Hash of method name to a callable.
*/
public function __construct(array $methods)
{
$this->methods = $methods;
// Create the functions on the class
foreach ($methods as $name => $fn) {
$this->{'_fn_' . $name} = $fn;
}
}
/**
* Lazily determine which methods are not implemented.
* @throws \BadMethodCallException
*/
public function __get($name)
{
throw new \BadMethodCallException(str_replace('_fn_', '', $name)
. '() is not implemented in the FnStream');
}
/**
* The close method is called on the underlying stream only if possible.
*/
public function __destruct()
{
if (isset($this->_fn_close)) {
call_user_func($this->_fn_close);
}
}
/**
* Adds custom functionality to an underlying stream by intercepting
* specific method calls.
*
* @param StreamInterface $stream Stream to decorate
* @param array $methods Hash of method name to a closure
*
* @return FnStream
*/
public static function decorate(StreamInterface $stream, array $methods)
{
// If any of the required methods were not provided, then simply
// proxy to the decorated stream.
foreach (array_diff(self::$slots, array_keys($methods)) as $diff) {
$methods[$diff] = [$stream, $diff];
}
return new self($methods);
}
public function __toString()
{
return call_user_func($this->_fn___toString);
}
public function close()
{
return call_user_func($this->_fn_close);
}
public function detach()
{
return call_user_func($this->_fn_detach);
}
public function attach($stream)
{
return call_user_func($this->_fn_attach, $stream);
}
public function getSize()
{
return call_user_func($this->_fn_getSize);
}
public function tell()
{
return call_user_func($this->_fn_tell);
}
public function eof()
{
return call_user_func($this->_fn_eof);
}
public function isSeekable()
{
return call_user_func($this->_fn_isSeekable);
}
public function seek($offset, $whence = SEEK_SET)
{
return call_user_func($this->_fn_seek, $offset, $whence);
}
public function isWritable()
{
return call_user_func($this->_fn_isWritable);
}
public function write($string)
{
return call_user_func($this->_fn_write, $string);
}
public function isReadable()
{
return call_user_func($this->_fn_isReadable);
}
public function read($length)
{
return call_user_func($this->_fn_read, $length);
}
public function getContents()
{
return call_user_func($this->_fn_getContents);
}
public function getMetadata($key = null)
{
return call_user_func($this->_fn_getMetadata, $key);
}
}

View File

@ -0,0 +1,117 @@
<?php
namespace GuzzleHttp\Stream;
/**
* Converts Guzzle streams into PHP stream resources.
*/
class GuzzleStreamWrapper
{
/** @var resource */
public $context;
/** @var StreamInterface */
private $stream;
/** @var string r, r+, or w */
private $mode;
/**
* Returns a resource representing the stream.
*
* @param StreamInterface $stream The stream to get a resource for
*
* @return resource
* @throws \InvalidArgumentException if stream is not readable or writable
*/
public static function getResource(StreamInterface $stream)
{
self::register();
if ($stream->isReadable()) {
$mode = $stream->isWritable() ? 'r+' : 'r';
} elseif ($stream->isWritable()) {
$mode = 'w';
} else {
throw new \InvalidArgumentException('The stream must be readable, '
. 'writable, or both.');
}
return fopen('guzzle://stream', $mode, false, stream_context_create([
'guzzle' => ['stream' => $stream],
]));
}
/**
* Registers the stream wrapper if needed
*/
public static function register()
{
if (!in_array('guzzle', stream_get_wrappers())) {
stream_wrapper_register('guzzle', __CLASS__);
}
}
public function stream_open($path, $mode, $options, &$opened_path)
{
$options = stream_context_get_options($this->context);
if (!isset($options['guzzle']['stream'])) {
return false;
}
$this->mode = $mode;
$this->stream = $options['guzzle']['stream'];
return true;
}
public function stream_read($count)
{
return $this->stream->read($count);
}
public function stream_write($data)
{
return (int) $this->stream->write($data);
}
public function stream_tell()
{
return $this->stream->tell();
}
public function stream_eof()
{
return $this->stream->eof();
}
public function stream_seek($offset, $whence)
{
return $this->stream->seek($offset, $whence);
}
public function stream_stat()
{
static $modeMap = [
'r' => 33060,
'r+' => 33206,
'w' => 33188,
];
return [
'dev' => 0,
'ino' => 0,
'mode' => $modeMap[$this->mode],
'nlink' => 0,
'uid' => 0,
'gid' => 0,
'rdev' => 0,
'size' => $this->stream->getSize() ?: 0,
'atime' => 0,
'mtime' => 0,
'ctime' => 0,
'blksize' => 0,
'blocks' => 0
];
}
}

View File

@ -0,0 +1,27 @@
<?php
namespace GuzzleHttp\Stream;
/**
* Uses PHP's zlib.inflate filter to inflate deflate or gzipped content.
*
* This stream decorator skips the first 10 bytes of the given stream to remove
* the gzip header, converts the provided stream to a PHP stream resource,
* then appends the zlib.inflate filter. The stream is then converted back
* to a Guzzle stream resource to be used as a Guzzle stream.
*
* @link http://tools.ietf.org/html/rfc1952
* @link http://php.net/manual/en/filters.compression.php
*/
class InflateStream implements StreamInterface
{
use StreamDecoratorTrait;
public function __construct(StreamInterface $stream)
{
// Skip the first 10 bytes
$stream = new LimitStream($stream, -1, 10);
$resource = GuzzleStreamWrapper::getResource($stream);
stream_filter_append($resource, 'zlib.inflate', STREAM_FILTER_READ);
$this->stream = new Stream($resource);
}
}

View File

@ -0,0 +1,37 @@
<?php
namespace GuzzleHttp\Stream;
/**
* Lazily reads or writes to a file that is opened only after an IO operation
* take place on the stream.
*/
class LazyOpenStream implements StreamInterface
{
use StreamDecoratorTrait;
/** @var string File to open */
private $filename;
/** @var string $mode */
private $mode;
/**
* @param string $filename File to lazily open
* @param string $mode fopen mode to use when opening the stream
*/
public function __construct($filename, $mode)
{
$this->filename = $filename;
$this->mode = $mode;
}
/**
* Creates the underlying stream lazily when required.
*
* @return StreamInterface
*/
protected function createStream()
{
return Stream::factory(Utils::open($this->filename, $this->mode));
}
}

View File

@ -0,0 +1,161 @@
<?php
namespace GuzzleHttp\Stream;
use GuzzleHttp\Stream\Exception\SeekException;
/**
* Decorator used to return only a subset of a stream
*/
class LimitStream implements StreamInterface
{
use StreamDecoratorTrait;
/** @var int Offset to start reading from */
private $offset;
/** @var int Limit the number of bytes that can be read */
private $limit;
/**
* @param StreamInterface $stream Stream to wrap
* @param int $limit Total number of bytes to allow to be read
* from the stream. Pass -1 for no limit.
* @param int|null $offset Position to seek to before reading (only
* works on seekable streams).
*/
public function __construct(
StreamInterface $stream,
$limit = -1,
$offset = 0
) {
$this->stream = $stream;
$this->setLimit($limit);
$this->setOffset($offset);
}
public function eof()
{
// Always return true if the underlying stream is EOF
if ($this->stream->eof()) {
return true;
}
// No limit and the underlying stream is not at EOF
if ($this->limit == -1) {
return false;
}
$tell = $this->stream->tell();
if ($tell === false) {
return false;
}
return $tell >= $this->offset + $this->limit;
}
/**
* Returns the size of the limited subset of data
* {@inheritdoc}
*/
public function getSize()
{
if (null === ($length = $this->stream->getSize())) {
return null;
} elseif ($this->limit == -1) {
return $length - $this->offset;
} else {
return min($this->limit, $length - $this->offset);
}
}
/**
* Allow for a bounded seek on the read limited stream
* {@inheritdoc}
*/
public function seek($offset, $whence = SEEK_SET)
{
if ($whence !== SEEK_SET || $offset < 0) {
return false;
}
$offset += $this->offset;
if ($this->limit !== -1) {
if ($offset > $this->offset + $this->limit) {
$offset = $this->offset + $this->limit;
}
}
return $this->stream->seek($offset);
}
/**
* Give a relative tell()
* {@inheritdoc}
*/
public function tell()
{
return $this->stream->tell() - $this->offset;
}
/**
* Set the offset to start limiting from
*
* @param int $offset Offset to seek to and begin byte limiting from
*
* @return self
* @throws SeekException
*/
public function setOffset($offset)
{
$current = $this->stream->tell();
if ($current !== $offset) {
// If the stream cannot seek to the offset position, then read to it
if (!$this->stream->seek($offset)) {
if ($current > $offset) {
throw new SeekException($this, $offset);
} else {
$this->stream->read($offset - $current);
}
}
}
$this->offset = $offset;
return $this;
}
/**
* Set the limit of bytes that the decorator allows to be read from the
* stream.
*
* @param int $limit Number of bytes to allow to be read from the stream.
* Use -1 for no limit.
* @return self
*/
public function setLimit($limit)
{
$this->limit = $limit;
return $this;
}
public function read($length)
{
if ($this->limit == -1) {
return $this->stream->read($length);
}
// Check if the current position is less than the total allowed
// bytes + original offset
$remaining = ($this->offset + $this->limit) - $this->stream->tell();
if ($remaining > 0) {
// Only return the amount of requested data, ensuring that the byte
// limit is not exceeded
return $this->stream->read(min($remaining, $length));
} else {
return false;
}
}
}

View File

@ -0,0 +1,11 @@
<?php
namespace GuzzleHttp\Stream;
/**
* This interface is deprecated and should no longer be used. Just use
* StreamInterface now that the getMetadata method has been added to
* StreamInterface.
*
* @deprecated
*/
interface MetadataStreamInterface extends StreamInterface {}

View File

@ -0,0 +1,25 @@
<?php
namespace GuzzleHttp\Stream;
/**
* Stream decorator that prevents a stream from being seeked
*/
class NoSeekStream implements StreamInterface
{
use StreamDecoratorTrait;
public function seek($offset, $whence = SEEK_SET)
{
return false;
}
public function isSeekable()
{
return false;
}
public function attach($stream)
{
$this->stream->attach($stream);
}
}

View File

@ -0,0 +1,79 @@
<?php
namespace GuzzleHttp\Stream;
use GuzzleHttp\Stream\Exception\CannotAttachException;
/**
* Does not store any data written to it.
*/
class NullStream implements StreamInterface
{
public function __toString()
{
return '';
}
public function getContents()
{
return '';
}
public function close() {}
public function detach() {}
public function attach($stream)
{
throw new CannotAttachException();
}
public function getSize()
{
return 0;
}
public function isReadable()
{
return true;
}
public function isWritable()
{
return true;
}
public function isSeekable()
{
return true;
}
public function eof()
{
return true;
}
public function tell()
{
return 0;
}
public function seek($offset, $whence = SEEK_SET)
{
return false;
}
public function read($length)
{
return false;
}
public function write($string)
{
return strlen($string);
}
public function getMetadata($key = null)
{
return $key ? null : [];
}
}

View File

@ -0,0 +1,161 @@
<?php
namespace GuzzleHttp\Stream;
use GuzzleHttp\Stream\Exception\CannotAttachException;
/**
* Provides a read only stream that pumps data from a PHP callable.
*
* When invoking the provided callable, the PumpStream will pass the amount of
* data requested to read to the callable. The callable can choose to ignore
* this value and return fewer or more bytes than requested. Any extra data
* returned by the provided callable is buffered internally until drained using
* the read() function of the PumpStream. The provided callable MUST return
* false when there is no more data to read.
*/
class PumpStream implements StreamInterface
{
/** @var callable */
private $source;
/** @var int */
private $size;
/** @var int */
private $tellPos = 0;
/** @var array */
private $metadata;
/** @var BufferStream */
private $buffer;
/**
* @param callable $source Source of the stream data. The callable MAY
* accept an integer argument used to control the
* amount of data to return. The callable MUST
* return a string when called, or false on error
* or EOF.
* @param array $options Stream options:
* - metadata: Hash of metadata to use with stream.
* - size: Size of the stream, if known.
*/
public function __construct(callable $source, array $options = [])
{
$this->source = $source;
$this->size = isset($options['size']) ? $options['size'] : null;
$this->metadata = isset($options['metadata']) ? $options['metadata'] : [];
$this->buffer = new BufferStream();
}
public function __toString()
{
return Utils::copyToString($this);
}
public function close()
{
$this->detach();
}
public function detach()
{
$this->tellPos = false;
$this->source = null;
}
public function attach($stream)
{
throw new CannotAttachException();
}
public function getSize()
{
return $this->size;
}
public function tell()
{
return $this->tellPos;
}
public function eof()
{
return !$this->source;
}
public function isSeekable()
{
return false;
}
public function seek($offset, $whence = SEEK_SET)
{
return false;
}
public function isWritable()
{
return false;
}
public function write($string)
{
return false;
}
public function isReadable()
{
return true;
}
public function read($length)
{
$data = $this->buffer->read($length);
$readLen = strlen($data);
$this->tellPos += $readLen;
$remaining = $length - $readLen;
if ($remaining) {
$this->pump($remaining);
$data .= $this->buffer->read($remaining);
$this->tellPos += strlen($data) - $readLen;
}
return $data;
}
public function getContents()
{
$result = '';
while (!$this->eof()) {
$result .= $this->read(1000000);
}
return $result;
}
public function getMetadata($key = null)
{
if (!$key) {
return $this->metadata;
}
return isset($this->metadata[$key]) ? $this->metadata[$key] : null;
}
private function pump($length)
{
if ($this->source) {
do {
$data = call_user_func($this->source, $length);
if ($data === false || $data === null) {
$this->source = null;
return;
}
$this->buffer->write($data);
$length -= strlen($data);
} while ($length > 0);
}
}
}

View File

@ -0,0 +1,261 @@
<?php
namespace GuzzleHttp\Stream;
/**
* PHP stream implementation
*/
class Stream implements StreamInterface
{
private $stream;
private $size;
private $seekable;
private $readable;
private $writable;
private $uri;
private $customMetadata;
/** @var array Hash of readable and writable stream types */
private static $readWriteHash = [
'read' => [
'r' => true, 'w+' => true, 'r+' => true, 'x+' => true, 'c+' => true,
'rb' => true, 'w+b' => true, 'r+b' => true, 'x+b' => true,
'c+b' => true, 'rt' => true, 'w+t' => true, 'r+t' => true,
'x+t' => true, 'c+t' => true, 'a+' => true,
],
'write' => [
'w' => true, 'w+' => true, 'rw' => true, 'r+' => true, 'x+' => true,
'c+' => true, 'wb' => true, 'w+b' => true, 'r+b' => true,
'x+b' => true, 'c+b' => true, 'w+t' => true, 'r+t' => true,
'x+t' => true, 'c+t' => true, 'a' => true, 'a+' => true,
],
];
/**
* Create a new stream based on the input type.
*
* This factory accepts the same associative array of options as described
* in the constructor.
*
* @param resource|string|StreamInterface $resource Entity body data
* @param array $options Additional options
*
* @return Stream
* @throws \InvalidArgumentException if the $resource arg is not valid.
*/
public static function factory($resource = '', array $options = [])
{
$type = gettype($resource);
if ($type == 'string') {
$stream = fopen('php://temp', 'r+');
if ($resource !== '') {
fwrite($stream, $resource);
fseek($stream, 0);
}
return new self($stream, $options);
}
if ($type == 'resource') {
return new self($resource, $options);
}
if ($resource instanceof StreamInterface) {
return $resource;
}
if ($type == 'object' && method_exists($resource, '__toString')) {
return self::factory((string) $resource, $options);
}
if (is_callable($resource)) {
return new PumpStream($resource, $options);
}
if ($resource instanceof \Iterator) {
return new PumpStream(function () use ($resource) {
if (!$resource->valid()) {
return false;
}
$result = $resource->current();
$resource->next();
return $result;
}, $options);
}
throw new \InvalidArgumentException('Invalid resource type: ' . $type);
}
/**
* This constructor accepts an associative array of options.
*
* - size: (int) If a read stream would otherwise have an indeterminate
* size, but the size is known due to foreknownledge, then you can
* provide that size, in bytes.
* - metadata: (array) Any additional metadata to return when the metadata
* of the stream is accessed.
*
* @param resource $stream Stream resource to wrap.
* @param array $options Associative array of options.
*
* @throws \InvalidArgumentException if the stream is not a stream resource
*/
public function __construct($stream, $options = [])
{
if (!is_resource($stream)) {
throw new \InvalidArgumentException('Stream must be a resource');
}
if (isset($options['size'])) {
$this->size = $options['size'];
}
$this->customMetadata = isset($options['metadata'])
? $options['metadata']
: [];
$this->attach($stream);
}
/**
* Closes the stream when the destructed
*/
public function __destruct()
{
$this->close();
}
public function __toString()
{
if (!$this->stream) {
return '';
}
$this->seek(0);
return (string) stream_get_contents($this->stream);
}
public function getContents()
{
return $this->stream ? stream_get_contents($this->stream) : '';
}
public function close()
{
if (is_resource($this->stream)) {
fclose($this->stream);
}
$this->detach();
}
public function detach()
{
$result = $this->stream;
$this->stream = $this->size = $this->uri = null;
$this->readable = $this->writable = $this->seekable = false;
return $result;
}
public function attach($stream)
{
$this->stream = $stream;
$meta = stream_get_meta_data($this->stream);
$this->seekable = $meta['seekable'];
$this->readable = isset(self::$readWriteHash['read'][$meta['mode']]);
$this->writable = isset(self::$readWriteHash['write'][$meta['mode']]);
$this->uri = $this->getMetadata('uri');
}
public function getSize()
{
if ($this->size !== null) {
return $this->size;
}
if (!$this->stream) {
return null;
}
// Clear the stat cache if the stream has a URI
if ($this->uri) {
clearstatcache(true, $this->uri);
}
$stats = fstat($this->stream);
if (isset($stats['size'])) {
$this->size = $stats['size'];
return $this->size;
}
return null;
}
public function isReadable()
{
return $this->readable;
}
public function isWritable()
{
return $this->writable;
}
public function isSeekable()
{
return $this->seekable;
}
public function eof()
{
return !$this->stream || feof($this->stream);
}
public function tell()
{
return $this->stream ? ftell($this->stream) : false;
}
public function setSize($size)
{
$this->size = $size;
return $this;
}
public function seek($offset, $whence = SEEK_SET)
{
return $this->seekable
? fseek($this->stream, $offset, $whence) === 0
: false;
}
public function read($length)
{
return $this->readable ? fread($this->stream, $length) : false;
}
public function write($string)
{
// We can't know the size after writing anything
$this->size = null;
return $this->writable ? fwrite($this->stream, $string) : false;
}
public function getMetadata($key = null)
{
if (!$this->stream) {
return $key ? null : [];
} elseif (!$key) {
return $this->customMetadata + stream_get_meta_data($this->stream);
} elseif (isset($this->customMetadata[$key])) {
return $this->customMetadata[$key];
}
$meta = stream_get_meta_data($this->stream);
return isset($meta[$key]) ? $meta[$key] : null;
}
}

View File

@ -0,0 +1,144 @@
<?php
namespace GuzzleHttp\Stream;
use GuzzleHttp\Stream\Exception\CannotAttachException;
/**
* Stream decorator trait
* @property StreamInterface stream
*/
trait StreamDecoratorTrait
{
/**
* @param StreamInterface $stream Stream to decorate
*/
public function __construct(StreamInterface $stream)
{
$this->stream = $stream;
}
/**
* Magic method used to create a new stream if streams are not added in
* the constructor of a decorator (e.g., LazyOpenStream).
*/
public function __get($name)
{
if ($name == 'stream') {
$this->stream = $this->createStream();
return $this->stream;
}
throw new \UnexpectedValueException("$name not found on class");
}
public function __toString()
{
try {
$this->seek(0);
return $this->getContents();
} catch (\Exception $e) {
// Really, PHP? https://bugs.php.net/bug.php?id=53648
trigger_error('StreamDecorator::__toString exception: '
. (string) $e, E_USER_ERROR);
return '';
}
}
public function getContents()
{
return Utils::copyToString($this);
}
/**
* Allow decorators to implement custom methods
*
* @param string $method Missing method name
* @param array $args Method arguments
*
* @return mixed
*/
public function __call($method, array $args)
{
$result = call_user_func_array(array($this->stream, $method), $args);
// Always return the wrapped object if the result is a return $this
return $result === $this->stream ? $this : $result;
}
public function close()
{
$this->stream->close();
}
public function getMetadata($key = null)
{
return $this->stream->getMetadata($key);
}
public function detach()
{
return $this->stream->detach();
}
public function attach($stream)
{
throw new CannotAttachException();
}
public function getSize()
{
return $this->stream->getSize();
}
public function eof()
{
return $this->stream->eof();
}
public function tell()
{
return $this->stream->tell();
}
public function isReadable()
{
return $this->stream->isReadable();
}
public function isWritable()
{
return $this->stream->isWritable();
}
public function isSeekable()
{
return $this->stream->isSeekable();
}
public function seek($offset, $whence = SEEK_SET)
{
return $this->stream->seek($offset, $whence);
}
public function read($length)
{
return $this->stream->read($length);
}
public function write($string)
{
return $this->stream->write($string);
}
/**
* Implement in subclasses to dynamically create streams when requested.
*
* @return StreamInterface
* @throws \BadMethodCallException
*/
protected function createStream()
{
throw new \BadMethodCallException('createStream() not implemented in '
. get_class($this));
}
}

View File

@ -0,0 +1,159 @@
<?php
namespace GuzzleHttp\Stream;
/**
* Describes a stream instance.
*/
interface StreamInterface
{
/**
* Attempts to seek to the beginning of the stream and reads all data into
* a string until the end of the stream is reached.
*
* Warning: This could attempt to load a large amount of data into memory.
*
* @return string
*/
public function __toString();
/**
* Closes the stream and any underlying resources.
*/
public function close();
/**
* Separates any underlying resources from the stream.
*
* After the underlying resource has been detached, the stream object is in
* an unusable state. If you wish to use a Stream object as a PHP stream
* but keep the Stream object in a consistent state, use
* {@see GuzzleHttp\Stream\GuzzleStreamWrapper::getResource}.
*
* @return resource|null Returns the underlying PHP stream resource or null
* if the Stream object did not utilize an underlying
* stream resource.
*/
public function detach();
/**
* Replaces the underlying stream resource with the provided stream.
*
* Use this method to replace the underlying stream with another; as an
* example, in server-side code, if you decide to return a file, you
* would replace the original content-oriented stream with the file
* stream.
*
* Any internal state such as caching of cursor position should be reset
* when attach() is called, as the stream has changed.
*
* @param resource $stream
*
* @return void
*/
public function attach($stream);
/**
* Get the size of the stream if known
*
* @return int|null Returns the size in bytes if known, or null if unknown
*/
public function getSize();
/**
* Returns the current position of the file read/write pointer
*
* @return int|bool Returns the position of the file pointer or false on error
*/
public function tell();
/**
* Returns true if the stream is at the end of the stream.
*
* @return bool
*/
public function eof();
/**
* Returns whether or not the stream is seekable
*
* @return bool
*/
public function isSeekable();
/**
* Seek to a position in the stream
*
* @param int $offset Stream offset
* @param int $whence Specifies how the cursor position will be calculated
* based on the seek offset. Valid values are identical
* to the built-in PHP $whence values for `fseek()`.
* SEEK_SET: Set position equal to offset bytes
* SEEK_CUR: Set position to current location plus offset
* SEEK_END: Set position to end-of-stream plus offset
*
* @return bool Returns true on success or false on failure
* @link http://www.php.net/manual/en/function.fseek.php
*/
public function seek($offset, $whence = SEEK_SET);
/**
* Returns whether or not the stream is writable
*
* @return bool
*/
public function isWritable();
/**
* Write data to the stream
*
* @param string $string The string that is to be written.
*
* @return int|bool Returns the number of bytes written to the stream on
* success returns false on failure (e.g., broken pipe,
* writer needs to slow down, buffer is full, etc.)
*/
public function write($string);
/**
* Returns whether or not the stream is readable
*
* @return bool
*/
public function isReadable();
/**
* Read data from the stream
*
* @param int $length Read up to $length bytes from the object and return
* them. Fewer than $length bytes may be returned if
* underlying stream call returns fewer bytes.
*
* @return string Returns the data read from the stream.
*/
public function read($length);
/**
* Returns the remaining contents of the stream as a string.
*
* Note: this could potentially load a large amount of data into memory.
*
* @return string
*/
public function getContents();
/**
* Get stream metadata as an associative array or retrieve a specific key.
*
* The keys returned are identical to the keys returned from PHP's
* stream_get_meta_data() function.
*
* @param string $key Specific metadata to retrieve.
*
* @return array|mixed|null Returns an associative array if no key is
* no key is provided. Returns a specific key
* value if a key is provided and the value is
* found, or null if the key is not found.
* @see http://php.net/manual/en/function.stream-get-meta-data.php
*/
public function getMetadata($key = null);
}

View File

@ -0,0 +1,198 @@
<?php
namespace GuzzleHttp\Stream;
use GuzzleHttp\Stream\Exception\SeekException;
/**
* Static utility class because PHP's autoloaders don't support the concept
* of namespaced function autoloading.
*/
class Utils
{
/**
* Safely opens a PHP stream resource using a filename.
*
* When fopen fails, PHP normally raises a warning. This function adds an
* error handler that checks for errors and throws an exception instead.
*
* @param string $filename File to open
* @param string $mode Mode used to open the file
*
* @return resource
* @throws \RuntimeException if the file cannot be opened
*/
public static function open($filename, $mode)
{
$ex = null;
set_error_handler(function () use ($filename, $mode, &$ex) {
$ex = new \RuntimeException(sprintf(
'Unable to open %s using mode %s: %s',
$filename,
$mode,
func_get_args()[1]
));
});
$handle = fopen($filename, $mode);
restore_error_handler();
if ($ex) {
/** @var $ex \RuntimeException */
throw $ex;
}
return $handle;
}
/**
* Copy the contents of a stream into a string until the given number of
* bytes have been read.
*
* @param StreamInterface $stream Stream to read
* @param int $maxLen Maximum number of bytes to read. Pass -1
* to read the entire stream.
* @return string
*/
public static function copyToString(StreamInterface $stream, $maxLen = -1)
{
$buffer = '';
if ($maxLen === -1) {
while (!$stream->eof()) {
$buf = $stream->read(1048576);
if ($buf === false) {
break;
}
$buffer .= $buf;
}
return $buffer;
}
$len = 0;
while (!$stream->eof() && $len < $maxLen) {
$buf = $stream->read($maxLen - $len);
if ($buf === false) {
break;
}
$buffer .= $buf;
$len = strlen($buffer);
}
return $buffer;
}
/**
* Copy the contents of a stream into another stream until the given number
* of bytes have been read.
*
* @param StreamInterface $source Stream to read from
* @param StreamInterface $dest Stream to write to
* @param int $maxLen Maximum number of bytes to read. Pass -1
* to read the entire stream.
*/
public static function copyToStream(
StreamInterface $source,
StreamInterface $dest,
$maxLen = -1
) {
if ($maxLen === -1) {
while (!$source->eof()) {
if (!$dest->write($source->read(1048576))) {
break;
}
}
return;
}
$bytes = 0;
while (!$source->eof()) {
$buf = $source->read($maxLen - $bytes);
if (!($len = strlen($buf))) {
break;
}
$bytes += $len;
$dest->write($buf);
if ($bytes == $maxLen) {
break;
}
}
}
/**
* Calculate a hash of a Stream
*
* @param StreamInterface $stream Stream to calculate the hash for
* @param string $algo Hash algorithm (e.g. md5, crc32, etc)
* @param bool $rawOutput Whether or not to use raw output
*
* @return string Returns the hash of the stream
* @throws SeekException
*/
public static function hash(
StreamInterface $stream,
$algo,
$rawOutput = false
) {
$pos = $stream->tell();
if ($pos > 0 && !$stream->seek(0)) {
throw new SeekException($stream);
}
$ctx = hash_init($algo);
while (!$stream->eof()) {
hash_update($ctx, $stream->read(1048576));
}
$out = hash_final($ctx, (bool) $rawOutput);
$stream->seek($pos);
return $out;
}
/**
* Read a line from the stream up to the maximum allowed buffer length
*
* @param StreamInterface $stream Stream to read from
* @param int $maxLength Maximum buffer length
* @param string $eol Line ending
*
* @return string|bool
*/
public static function readline(StreamInterface $stream, $maxLength = null, $eol = PHP_EOL)
{
$buffer = '';
$size = 0;
$negEolLen = -strlen($eol);
while (!$stream->eof()) {
if (false === ($byte = $stream->read(1))) {
return $buffer;
}
$buffer .= $byte;
// Break when a new line is found or the max length - 1 is reached
if (++$size == $maxLength || substr($buffer, $negEolLen) === $eol) {
break;
}
}
return $buffer;
}
/**
* Alias of GuzzleHttp\Stream\Stream::factory.
*
* @param mixed $resource Resource to create
* @param array $options Associative array of stream options defined in
* {@see \GuzzleHttp\Stream\Stream::__construct}
*
* @return StreamInterface
*
* @see GuzzleHttp\Stream\Stream::factory
* @see GuzzleHttp\Stream\Stream::__construct
*/
public static function create($resource, array $options = [])
{
return Stream::factory($resource, $options);
}
}

12
vendor/ezimuel/ringphp/.editorconfig vendored Normal file
View File

@ -0,0 +1,12 @@
root = true
[*]
charset = utf-8
end_of_line = lf
indent_size = 4
indent_style = space
insert_final_newline = true
trim_trailing_whitespace = true
[{Makefile,*.mk}]
indent_style = tab

View File

@ -0,0 +1,44 @@
name: PHP test
on: [push, pull_request]
jobs:
test:
name: Test
runs-on: ubuntu-latest
strategy:
matrix:
php-version: [7.4, 8.0, 8.1, 8.2, 8.3, 8.4, 8.5]
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Use PHP ${{ matrix.php-version }}
uses: shivammathur/setup-php@v2
with:
php-version: ${{ matrix.php-version }}
extensions: zip, curl
env:
COMPOSER_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Get composer cache directory
id: composercache
run: echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT
- name: Cache dependencies
uses: actions/cache@v4
with:
path: ${{ steps.composercache.outputs.dir }}
key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.json') }}
restore-keys: ${{ runner.os }}-composer-
- name: Install dependencies
run: |
composer install --prefer-dist
- name: Unit tests
run: |
composer run-script test

139
vendor/ezimuel/ringphp/CHANGELOG.md vendored Normal file
View File

@ -0,0 +1,139 @@
# Changelog
All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/)
and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html).
## [1.2.1] - 2022-10-25
- Fixed the support of PHP 8.1 with [#7](https://github.com/ezimuel/ringphp/pull/7) and [#8](https://github.com/ezimuel/ringphp/pull/8)
- Fixed the integration with Symfony, adding explicit @return annotations to suppress User Deprecated notices [#5](https://github.com/ezimuel/ringphp/pull/5)
## [1.2.0] - 2021-11-16
### Added
- Add attribute to avoid Deprecated notice for PHP 8.1 [#4](https://github.com/ezimuel/ringphp/pull/4)
- Add replace guzzlehttp/ringphp in composer [#3](https://github.com/ezimuel/ringphp/pull/3)
- Add .gitattributes file [#2](https://github.com/ezimuel/ringphp/pull/2)
### Changed
- Updated to PHPUnit 9 and fixed the unit tests for PHP 8
[d386b25](https://github.com/ezimuel/ringphp/commit/d386b2597389dafe3b437ab90d115eb092fff109)
## [1.1.2] - 2020-02-15
### Changed
- Required guzzlestreams 3.0.1+ [0b78f89](https://github.com/ezimuel/ringphp/commit/0b78f89d8e0bb9e380046c31adfa40347e9f663b)
## [1.1.1] - 2018-07-31
### Fixed
- `continue` keyword usage on PHP 7.3
## [1.1.0] - 2015-05-19
### Added
- Added `CURL_HTTP_VERSION_2_0`
### Changed
- The PHP stream wrapper handler now sets `allow_self_signed` to `false` to
match the cURL handler when `verify` is set to `true` or a certificate file.
- Ensuring that a directory exists before using the `save_to` option.
- Response protocol version is now correctly extracted from a response.
### Fixed
- Fixed a bug in which the result of `CurlFactory::retryFailedRewind` did not
return an array.
## [1.0.7] - 2015-03-29
### Fixed
- PHP 7 fixes.
## [1.0.6] - 2015-02-26
### Changed
- The multi handle of the CurlMultiHandler is now created lazily.
### Fixed
- Bug fix: futures now extend from React's PromiseInterface to ensure that they
are properly forwarded down the promise chain.
## [1.0.5] - 2014-12-10
### Added
- Adding more error information to PHP stream wrapper exceptions.
- Added digest auth integration test support to test server.
## [1.0.4] - 2014-12-01
### Added
- Added support for older versions of cURL that do not have CURLOPT_TIMEOUT_MS.
- Added a fix to the StreamHandler to return a `FutureArrayInterface` when an
### Changed
- Setting debug to `false` does not enable debug output. error occurs.
## [1.0.3] - 2014-11-03
### Fixed
- Setting the `header` stream option as a string to be compatible with GAE.
- Header parsing now ensures that header order is maintained in the parsed
message.
## [1.0.2] - 2014-10-28
### Fixed
- Now correctly honoring a `version` option is supplied in a request.
See https://github.com/guzzle/RingPHP/pull/8
## [1.0.1] - 2014-10-26
### Fixed
- Fixed a header parsing issue with the `CurlHandler` and `CurlMultiHandler`
that caused cURL requests with multiple responses to merge repsonses together
(e.g., requests with digest authentication).
## 1.0.0 - 2014-10-12
- Initial release
[Unreleased]: https://github.com/guzzle/RingPHP/compare/1.1.1...HEAD
[1.1.1]: https://github.com/guzzle/RingPHP/compare/1.1.0...1.1.1
[1.1.0]: https://github.com/guzzle/RingPHP/compare/1.0.7...1.1.0
[1.0.7]: https://github.com/guzzle/RingPHP/compare/1.0.6...1.0.7
[1.0.6]: https://github.com/guzzle/RingPHP/compare/1.0.5...1.0.6
[1.0.5]: https://github.com/guzzle/RingPHP/compare/1.0.4...1.0.5
[1.0.4]: https://github.com/guzzle/RingPHP/compare/1.0.3...1.0.4
[1.0.3]: https://github.com/guzzle/RingPHP/compare/1.0.2...1.0.3
[1.0.2]: https://github.com/guzzle/RingPHP/compare/1.0.1...1.0.2
[1.0.1]: https://github.com/guzzle/RingPHP/compare/1.0.0...1.0.1

19
vendor/ezimuel/ringphp/LICENSE vendored Normal file
View File

@ -0,0 +1,19 @@
Copyright (c) 2014 Michael Dowling, https://github.com/mtdowling <mtdowling@gmail.com>
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.

46
vendor/ezimuel/ringphp/Makefile vendored Normal file
View File

@ -0,0 +1,46 @@
all: clean coverage docs
docs:
cd docs && make html
view-docs:
open docs/_build/html/index.html
start-server: stop-server
node tests/Client/server.js &> /dev/null &
stop-server:
@PID=$(shell ps axo pid,command \
| grep 'tests/Client/server.js' \
| grep -v grep \
| cut -f 1 -d " "\
) && [ -n "$$PID" ] && kill $$PID || true
test: start-server
vendor/bin/phpunit $(TEST)
$(MAKE) stop-server
coverage: start-server
vendor/bin/phpunit --coverage-html=build/artifacts/coverage $(TEST)
$(MAKE) stop-server
view-coverage:
open build/artifacts/coverage/index.html
clean:
rm -rf build/artifacts/*
cd docs && make clean
tag:
$(if $(TAG),,$(error TAG is not defined. Pass via "make tag TAG=4.2.1"))
@echo Tagging $(TAG)
chag update -m '$(TAG) ()'
git add -A
git commit -m '$(TAG) release'
chag tag
perf: start-server
php tests/perf.php
$(MAKE) stop-server
.PHONY: docs

48
vendor/ezimuel/ringphp/README.md vendored Normal file
View File

@ -0,0 +1,48 @@
RingPHP
=======
[![Build status](https://github.com/ezimuel/ringphp/workflows/PHP%20test/badge.svg)](https://github.com/ezimuel/ringphp/actions) [![Latest Stable Version](https://poser.pugx.org/ezimuel/ringphp/v/stable)](https://packagist.org/packages/ezimuel/ringphp)
**Note:** this is a fork of the original project since it was abandoned.
Provides a simple API and specification that abstracts away the details of HTTP
into a single PHP function. RingPHP be used to power HTTP clients and servers
through a PHP function that accepts a request hash and returns a response hash
that is fulfilled using a [promise](https://github.com/reactphp/promise),
allowing RingPHP to support both synchronous and asynchronous workflows.
By abstracting the implementation details of different HTTP clients and
servers, RingPHP allows you to utilize pluggable HTTP clients and servers
without tying your application to a specific implementation.
```php
require 'vendor/autoload.php';
use GuzzleHttp\Ring\Client\CurlHandler;
$handler = new CurlHandler();
$response = $handler([
'http_method' => 'GET',
'uri' => '/',
'headers' => [
'host' => ['www.google.com'],
'x-foo' => ['baz']
]
]);
$response->then(function (array $response) {
echo $response['status'];
});
$response->wait();
```
RingPHP is inspired by Clojure's [Ring](https://github.com/ring-clojure/ring),
which, in turn, was inspired by Python's WSGI and Ruby's Rack. RingPHP is
utilized as the handler layer in [Guzzle](https://guzzlephp.org) 5.0+ to send
HTTP requests.
Documentation
-------------
See https://ringphp.readthedocs.io/en/latest/ for the full online documentation.

46
vendor/ezimuel/ringphp/composer.json vendored Normal file
View File

@ -0,0 +1,46 @@
{
"name": "ezimuel/ringphp",
"description": "Fork of guzzle/RingPHP (abandoned) to be used with elasticsearch-php",
"license": "MIT",
"authors": [
{
"name": "Michael Dowling",
"email": "mtdowling@gmail.com",
"homepage": "https://github.com/mtdowling"
}
],
"require": {
"php": ">=5.4.0",
"ezimuel/guzzlestreams": "^3.0.1 || ^4.0.0",
"react/promise": "^2.0 || ^3.0"
},
"require-dev": {
"ext-curl": "*",
"phpunit/phpunit": "~9.0"
},
"replace": {
"guzzlehttp/ringphp": "self.version"
},
"suggest": {
"ext-curl": "Guzzle will use specific adapters if cURL is present"
},
"autoload": {
"psr-4": {
"GuzzleHttp\\Ring\\": "src/"
}
},
"autoload-dev": {
"psr-4": {
"GuzzleHttp\\Tests\\Ring\\": "tests/"
}
},
"scripts": {
"test": "make test",
"test-ci": "make coverage"
},
"extra": {
"branch-alias": {
"dev-master": "1.1-dev"
}
}
}

View File

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<phpunit bootstrap="./tests/bootstrap.php"
colors="true">
<testsuites>
<testsuite name="Unit tests">
<directory>tests</directory>
</testsuite>
</testsuites>
</phpunit>

View File

@ -0,0 +1,74 @@
<?php
namespace GuzzleHttp\Ring\Client;
/**
* Client specific utility functions.
*/
class ClientUtils
{
/**
* Returns the default cacert bundle for the current system.
*
* First, the openssl.cafile and curl.cainfo php.ini settings are checked.
* If those settings are not configured, then the common locations for
* bundles found on Red Hat, CentOS, Fedora, Ubuntu, Debian, FreeBSD, OS X
* and Windows are checked. If any of these file locations are found on
* disk, they will be utilized.
*
* Note: the result of this function is cached for subsequent calls.
*
* @return string
* @throws \RuntimeException if no bundle can be found.
*/
public static function getDefaultCaBundle()
{
static $cached = null;
static $cafiles = [
// Red Hat, CentOS, Fedora (provided by the ca-certificates package)
'/etc/pki/tls/certs/ca-bundle.crt',
// Ubuntu, Debian (provided by the ca-certificates package)
'/etc/ssl/certs/ca-certificates.crt',
// FreeBSD (provided by the ca_root_nss package)
'/usr/local/share/certs/ca-root-nss.crt',
// OS X provided by homebrew (using the default path)
'/usr/local/etc/openssl/cert.pem',
// Windows?
'C:\\windows\\system32\\curl-ca-bundle.crt',
'C:\\windows\\curl-ca-bundle.crt',
];
if ($cached) {
return $cached;
}
if ($ca = ini_get('openssl.cafile')) {
return $cached = $ca;
}
if ($ca = ini_get('curl.cainfo')) {
return $cached = $ca;
}
foreach ($cafiles as $filename) {
if (file_exists($filename)) {
return $cached = $filename;
}
}
throw new \RuntimeException(self::CA_ERR);
}
const CA_ERR = "
No system CA bundle could be found in any of the the common system locations.
PHP versions earlier than 5.6 are not properly configured to use the system's
CA bundle by default. In order to verify peer certificates, you will need to
supply the path on disk to a certificate bundle to the 'verify' request
option: http://docs.guzzlephp.org/en/5.3/clients.html#verify. If you do not
need a specific certificate bundle, then Mozilla provides a commonly used CA
bundle which can be downloaded here (provided by the maintainer of cURL):
https://raw.githubusercontent.com/bagder/ca-bundle/master/ca-bundle.crt. Once
you have a CA bundle available on disk, you can set the 'openssl.cafile' PHP
ini setting to point to the path to the file, allowing you to omit the 'verify'
request option. See http://curl.haxx.se/docs/sslcerts.html for more
information.";
}

View File

@ -0,0 +1,560 @@
<?php
namespace GuzzleHttp\Ring\Client;
use GuzzleHttp\Ring\Core;
use GuzzleHttp\Ring\Exception\ConnectException;
use GuzzleHttp\Ring\Exception\RingException;
use GuzzleHttp\Stream\LazyOpenStream;
use GuzzleHttp\Stream\StreamInterface;
/**
* Creates curl resources from a request
*/
class CurlFactory
{
/**
* Creates a cURL handle, header resource, and body resource based on a
* transaction.
*
* @param array $request Request hash
* @param null|resource $handle Optionally provide a curl handle to modify
*
* @return array Returns an array of the curl handle, headers array, and
* response body handle.
* @throws \RuntimeException when an option cannot be applied
*/
public function __invoke(array $request, $handle = null)
{
$headers = [];
$options = $this->getDefaultOptions($request, $headers);
$this->applyMethod($request, $options);
if (isset($request['client'])) {
$this->applyHandlerOptions($request, $options);
}
$this->applyHeaders($request, $options);
unset($options['_headers']);
// Add handler options from the request's configuration options
if (isset($request['client']['curl'])) {
$options = $this->applyCustomCurlOptions(
$request['client']['curl'],
$options
);
}
if (!$handle) {
$handle = curl_init();
}
$body = $this->getOutputBody($request, $options);
curl_setopt_array($handle, $options);
return [$handle, &$headers, $body];
}
/**
* Creates a response hash from a cURL result.
*
* @param callable $handler Handler that was used.
* @param array $request Request that sent.
* @param array $response Response hash to update.
* @param array $headers Headers received during transfer.
* @param resource $body Body fopen response.
*
* @return array
*/
public static function createResponse(
callable $handler,
array $request,
array $response,
array $headers,
$body
) {
if (isset($response['transfer_stats']['url'])) {
$response['effective_url'] = $response['transfer_stats']['url'];
}
if (!empty($headers)) {
$startLine = explode(' ', array_shift($headers), 3);
$headerList = Core::headersFromLines($headers);
$response['headers'] = $headerList;
$response['version'] = isset($startLine[0]) ? substr($startLine[0], 5) : null;
$response['status'] = isset($startLine[1]) ? (int) $startLine[1] : null;
$response['reason'] = isset($startLine[2]) ? $startLine[2] : null;
$response['body'] = $body;
Core::rewindBody($response);
}
return !empty($response['curl']['errno']) || !isset($response['status'])
? self::createErrorResponse($handler, $request, $response)
: $response;
}
private static function createErrorResponse(
callable $handler,
array $request,
array $response
) {
static $connectionErrors = [
CURLE_OPERATION_TIMEOUTED => true,
CURLE_COULDNT_RESOLVE_HOST => true,
CURLE_COULDNT_CONNECT => true,
CURLE_SSL_CONNECT_ERROR => true,
CURLE_GOT_NOTHING => true,
];
// Retry when nothing is present or when curl failed to rewind.
if (!isset($response['err_message'])
&& (empty($response['curl']['errno'])
|| $response['curl']['errno'] == 65)
) {
return self::retryFailedRewind($handler, $request, $response);
}
$message = isset($response['err_message'])
? $response['err_message']
: sprintf('cURL error %s: %s',
$response['curl']['errno'],
isset($response['curl']['error'])
? $response['curl']['error']
: 'See http://curl.haxx.se/libcurl/c/libcurl-errors.html');
$error = isset($response['curl']['errno'])
&& isset($connectionErrors[$response['curl']['errno']])
? new ConnectException($message)
: new RingException($message);
return $response + [
'status' => null,
'reason' => null,
'body' => null,
'headers' => [],
'error' => $error,
];
}
private function getOutputBody(array $request, array &$options)
{
// Determine where the body of the response (if any) will be streamed.
if (isset($options[CURLOPT_WRITEFUNCTION])) {
return $request['client']['save_to'];
}
if (isset($options[CURLOPT_FILE])) {
return $options[CURLOPT_FILE];
}
if ($request['http_method'] != 'HEAD') {
// Create a default body if one was not provided
return $options[CURLOPT_FILE] = fopen('php://temp', 'w+');
}
return null;
}
private function getDefaultOptions(array $request, array &$headers)
{
$url = Core::url($request);
$startingResponse = false;
$options = [
'_headers' => $request['headers'],
CURLOPT_CUSTOMREQUEST => $request['http_method'],
CURLOPT_URL => $url,
CURLOPT_RETURNTRANSFER => false,
CURLOPT_HEADER => false,
CURLOPT_CONNECTTIMEOUT => 150,
CURLOPT_HEADERFUNCTION => function ($ch, $h) use (&$headers, &$startingResponse) {
$value = trim($h);
if ($value === '') {
$startingResponse = true;
} elseif ($startingResponse) {
$startingResponse = false;
$headers = [$value];
} else {
$headers[] = $value;
}
return strlen($h);
},
];
if (isset($request['version'])) {
if ($request['version'] == 2.0) {
$options[CURLOPT_HTTP_VERSION] = CURL_HTTP_VERSION_2_0;
} else if ($request['version'] == 1.1) {
$options[CURLOPT_HTTP_VERSION] = CURL_HTTP_VERSION_1_1;
} else {
$options[CURLOPT_HTTP_VERSION] = CURL_HTTP_VERSION_1_0;
}
}
if (defined('CURLOPT_PROTOCOLS')) {
$options[CURLOPT_PROTOCOLS] = CURLPROTO_HTTP | CURLPROTO_HTTPS;
}
return $options;
}
private function applyMethod(array $request, array &$options)
{
if (isset($request['body'])) {
$this->applyBody($request, $options);
return;
}
switch ($request['http_method']) {
case 'PUT':
case 'POST':
// See http://tools.ietf.org/html/rfc7230#section-3.3.2
if (!Core::hasHeader($request, 'Content-Length')) {
$options[CURLOPT_HTTPHEADER][] = 'Content-Length: 0';
}
break;
case 'HEAD':
$options[CURLOPT_NOBODY] = true;
unset(
$options[CURLOPT_WRITEFUNCTION],
$options[CURLOPT_READFUNCTION],
$options[CURLOPT_FILE],
$options[CURLOPT_INFILE]
);
}
}
private function applyBody(array $request, array &$options)
{
$contentLength = Core::firstHeader($request, 'Content-Length');
$size = $contentLength !== null ? (int) $contentLength : null;
// Send the body as a string if the size is less than 1MB OR if the
// [client][curl][body_as_string] request value is set.
if (($size !== null && $size < 1000000) ||
isset($request['client']['curl']['body_as_string']) ||
is_string($request['body'])
) {
$options[CURLOPT_POSTFIELDS] = Core::body($request);
// Don't duplicate the Content-Length header
$this->removeHeader('Content-Length', $options);
$this->removeHeader('Transfer-Encoding', $options);
} else {
$options[CURLOPT_UPLOAD] = true;
if ($size !== null) {
// Let cURL handle setting the Content-Length header
$options[CURLOPT_INFILESIZE] = $size;
$this->removeHeader('Content-Length', $options);
}
$this->addStreamingBody($request, $options);
}
// If the Expect header is not present, prevent curl from adding it
if (!Core::hasHeader($request, 'Expect')) {
$options[CURLOPT_HTTPHEADER][] = 'Expect:';
}
// cURL sometimes adds a content-type by default. Prevent this.
if (!Core::hasHeader($request, 'Content-Type')) {
$options[CURLOPT_HTTPHEADER][] = 'Content-Type:';
}
}
private function addStreamingBody(array $request, array &$options)
{
$body = $request['body'];
if ($body instanceof StreamInterface) {
$options[CURLOPT_READFUNCTION] = function ($ch, $fd, $length) use ($body) {
return (string) $body->read($length);
};
if (!isset($options[CURLOPT_INFILESIZE])) {
if ($size = $body->getSize()) {
$options[CURLOPT_INFILESIZE] = $size;
}
}
} elseif (is_resource($body)) {
$options[CURLOPT_INFILE] = $body;
} elseif ($body instanceof \Iterator) {
$buf = '';
$options[CURLOPT_READFUNCTION] = function ($ch, $fd, $length) use ($body, &$buf) {
if ($body->valid()) {
$buf .= $body->current();
$body->next();
}
$result = (string) substr($buf, 0, $length);
$buf = substr($buf, $length);
return $result;
};
} else {
throw new \InvalidArgumentException('Invalid request body provided');
}
}
private function applyHeaders(array $request, array &$options)
{
foreach ($options['_headers'] as $name => $values) {
foreach ($values as $value) {
$options[CURLOPT_HTTPHEADER][] = "$name: $value";
}
}
// Remove the Accept header if one was not set
if (!Core::hasHeader($request, 'Accept')) {
$options[CURLOPT_HTTPHEADER][] = 'Accept:';
}
}
/**
* Takes an array of curl options specified in the 'curl' option of a
* request's configuration array and maps them to CURLOPT_* options.
*
* This method is only called when a request has a 'curl' config setting.
*
* @param array $config Configuration array of custom curl option
* @param array $options Array of existing curl options
*
* @return array Returns a new array of curl options
*/
private function applyCustomCurlOptions(array $config, array $options)
{
$curlOptions = [];
foreach ($config as $key => $value) {
if (is_int($key)) {
$curlOptions[$key] = $value;
}
}
return $curlOptions + $options;
}
/**
* Remove a header from the options array.
*
* @param string $name Case-insensitive header to remove
* @param array $options Array of options to modify
*/
private function removeHeader($name, array &$options)
{
foreach (array_keys($options['_headers']) as $key) {
if (!strcasecmp($key, $name)) {
unset($options['_headers'][$key]);
return;
}
}
}
/**
* Applies an array of request client options to a the options array.
*
* This method uses a large switch rather than double-dispatch to save on
* high overhead of calling functions in PHP.
*/
private function applyHandlerOptions(array $request, array &$options)
{
foreach ($request['client'] as $key => $value) {
switch ($key) {
// Violating PSR-4 to provide more room.
case 'verify':
if ($value === false) {
unset($options[CURLOPT_CAINFO]);
$options[CURLOPT_SSL_VERIFYHOST] = 0;
$options[CURLOPT_SSL_VERIFYPEER] = false;
continue 2;
}
$options[CURLOPT_SSL_VERIFYHOST] = 2;
$options[CURLOPT_SSL_VERIFYPEER] = true;
if (is_string($value)) {
$options[CURLOPT_CAINFO] = $value;
if (!file_exists($value)) {
throw new \InvalidArgumentException(
"SSL CA bundle not found: $value"
);
}
}
break;
case 'decode_content':
if ($value === false) {
continue 2;
}
$accept = Core::firstHeader($request, 'Accept-Encoding');
if ($accept) {
$options[CURLOPT_ENCODING] = $accept;
} else {
$options[CURLOPT_ENCODING] = '';
// Don't let curl send the header over the wire
$options[CURLOPT_HTTPHEADER][] = 'Accept-Encoding:';
}
break;
case 'save_to':
if (is_string($value)) {
if (!is_dir(dirname($value))) {
throw new \RuntimeException(sprintf(
'Directory %s does not exist for save_to value of %s',
dirname($value),
$value
));
}
$value = new LazyOpenStream($value, 'w+');
}
if ($value instanceof StreamInterface) {
$options[CURLOPT_WRITEFUNCTION] =
function ($ch, $write) use ($value) {
return $value->write($write);
};
} elseif (is_resource($value)) {
$options[CURLOPT_FILE] = $value;
} else {
throw new \InvalidArgumentException('save_to must be a '
. 'GuzzleHttp\Stream\StreamInterface or resource');
}
break;
case 'timeout':
if (defined('CURLOPT_TIMEOUT_MS')) {
$options[CURLOPT_TIMEOUT_MS] = $value * 1000;
} else {
$options[CURLOPT_TIMEOUT] = $value;
}
break;
case 'connect_timeout':
if (defined('CURLOPT_CONNECTTIMEOUT_MS')) {
$options[CURLOPT_CONNECTTIMEOUT_MS] = $value * 1000;
} else {
$options[CURLOPT_CONNECTTIMEOUT] = $value;
}
break;
case 'proxy':
if (!is_array($value)) {
$options[CURLOPT_PROXY] = $value;
} elseif (isset($request['scheme'])) {
$scheme = $request['scheme'];
if (isset($value[$scheme])) {
$options[CURLOPT_PROXY] = $value[$scheme];
}
}
break;
case 'cert':
if (is_array($value)) {
$options[CURLOPT_SSLCERTPASSWD] = $value[1];
$value = $value[0];
}
if (!file_exists($value)) {
throw new \InvalidArgumentException(
"SSL certificate not found: {$value}"
);
}
$options[CURLOPT_SSLCERT] = $value;
break;
case 'ssl_key':
if (is_array($value)) {
$options[CURLOPT_SSLKEYPASSWD] = $value[1];
$value = $value[0];
}
if (!file_exists($value)) {
throw new \InvalidArgumentException(
"SSL private key not found: {$value}"
);
}
$options[CURLOPT_SSLKEY] = $value;
break;
case 'progress':
if (!is_callable($value)) {
throw new \InvalidArgumentException(
'progress client option must be callable'
);
}
$options[CURLOPT_NOPROGRESS] = false;
$options[CURLOPT_PROGRESSFUNCTION] =
function () use ($value) {
$args = func_get_args();
// PHP 5.5 pushed the handle onto the start of the args
if (is_resource($args[0])) {
array_shift($args);
}
call_user_func_array($value, $args);
};
break;
case 'debug':
if ($value) {
$options[CURLOPT_STDERR] = Core::getDebugResource($value);
$options[CURLOPT_VERBOSE] = true;
}
break;
}
}
}
/**
* This function ensures that a response was set on a transaction. If one
* was not set, then the request is retried if possible. This error
* typically means you are sending a payload, curl encountered a
* "Connection died, retrying a fresh connect" error, tried to rewind the
* stream, and then encountered a "necessary data rewind wasn't possible"
* error, causing the request to be sent through curl_multi_info_read()
* without an error status.
*/
private static function retryFailedRewind(
callable $handler,
array $request,
array $response
) {
// If there is no body, then there is some other kind of issue. This
// is weird and should probably never happen.
if (!isset($request['body'])) {
$response['err_message'] = 'No response was received for a request '
. 'with no body. This could mean that you are saturating your '
. 'network.';
return self::createErrorResponse($handler, $request, $response);
}
if (!Core::rewindBody($request)) {
$response['err_message'] = 'The connection unexpectedly failed '
. 'without providing an error. The request would have been '
. 'retried, but attempting to rewind the request body failed.';
return self::createErrorResponse($handler, $request, $response);
}
// Retry no more than 3 times before giving up.
if (!isset($request['curl']['retries'])) {
$request['curl']['retries'] = 1;
} elseif ($request['curl']['retries'] == 2) {
$response['err_message'] = 'The cURL request was retried 3 times '
. 'and did no succeed. cURL was unable to rewind the body of '
. 'the request and subsequent retries resulted in the same '
. 'error. Turn on the debug option to see what went wrong. '
. 'See https://bugs.php.net/bug.php?id=47204 for more information.';
return self::createErrorResponse($handler, $request, $response);
} else {
$request['curl']['retries']++;
}
return $handler($request);
}
}

View File

@ -0,0 +1,140 @@
<?php
namespace GuzzleHttp\Ring\Client;
use GuzzleHttp\Ring\Future\CompletedFutureArray;
use GuzzleHttp\Ring\Core;
/**
* HTTP handler that uses cURL easy handles as a transport layer.
*
* Requires PHP 5.5+
*
* When using the CurlHandler, custom curl options can be specified as an
* associative array of curl option constants mapping to values in the
* **curl** key of the "client" key of the request.
*/
class CurlHandler
{
/** @var callable */
private $factory;
/** @var array Array of curl easy handles */
private $handles = [];
/** @var array Array of owned curl easy handles */
private $ownedHandles = [];
/** @var int Total number of idle handles to keep in cache */
private $maxHandles;
/**
* Accepts an associative array of options:
*
* - factory: Optional callable factory used to create cURL handles.
* The callable is passed a request hash when invoked, and returns an
* array of the curl handle, headers resource, and body resource.
* - max_handles: Maximum number of idle handles (defaults to 5).
*
* @param array $options Array of options to use with the handler
*/
public function __construct(array $options = [])
{
$this->handles = $this->ownedHandles = [];
$this->factory = isset($options['handle_factory'])
? $options['handle_factory']
: new CurlFactory();
$this->maxHandles = isset($options['max_handles'])
? $options['max_handles']
: 5;
}
public function __destruct()
{
if (PHP_VERSION_ID >= 80000) {
return;
}
foreach ($this->handles as $handle) {
if (is_resource($handle)) {
curl_close($handle);
}
}
}
/**
* @param array $request
*
* @return CompletedFutureArray
*/
public function __invoke(array $request)
{
return new CompletedFutureArray(
$this->_invokeAsArray($request)
);
}
/**
* @internal
*
* @param array $request
*
* @return array
*/
public function _invokeAsArray(array $request)
{
$factory = $this->factory;
// Ensure headers are by reference. They're updated elsewhere.
$result = $factory($request, $this->checkoutEasyHandle());
$h = $result[0];
$hd =& $result[1];
$bd = $result[2];
Core::doSleep($request);
curl_exec($h);
$response = ['transfer_stats' => curl_getinfo($h)];
$response['curl']['error'] = curl_error($h);
$response['curl']['errno'] = curl_errno($h);
$response['transfer_stats'] = array_merge($response['transfer_stats'], $response['curl']);
$this->releaseEasyHandle($h);
return CurlFactory::createResponse([$this, '_invokeAsArray'], $request, $response, $hd, $bd);
}
private function checkoutEasyHandle()
{
// Find an unused handle in the cache
if (false !== ($key = array_search(false, $this->ownedHandles, true))) {
$this->ownedHandles[$key] = true;
return $this->handles[$key];
}
// Add a new handle
$handle = curl_init();
$id = (int) $handle;
$this->handles[$id] = $handle;
$this->ownedHandles[$id] = true;
return $handle;
}
private function releaseEasyHandle($handle)
{
$id = (int) $handle;
if (count($this->ownedHandles) > $this->maxHandles) {
if (PHP_VERSION_ID < 80000) {
curl_close($this->handles[$id]);
}
unset($this->handles[$id], $this->ownedHandles[$id]);
} else {
// curl_reset doesn't clear these out for some reason
static $unsetValues = [
CURLOPT_HEADERFUNCTION => null,
CURLOPT_WRITEFUNCTION => null,
CURLOPT_READFUNCTION => null,
CURLOPT_PROGRESSFUNCTION => null,
];
curl_setopt_array($handle, $unsetValues);
curl_reset($handle);
$this->ownedHandles[$id] = false;
}
}
}

View File

@ -0,0 +1,252 @@
<?php
namespace GuzzleHttp\Ring\Client;
use GuzzleHttp\Ring\Future\FutureArray;
use React\Promise\Deferred;
/**
* Returns an asynchronous response using curl_multi_* functions.
*
* This handler supports future responses and the "delay" request client
* option that can be used to delay before sending a request.
*
* When using the CurlMultiHandler, custom curl options can be specified as an
* associative array of curl option constants mapping to values in the
* **curl** key of the "client" key of the request.
*
* @property resource $_mh Internal use only. Lazy loaded multi-handle.
*/
#[\AllowDynamicProperties]
class CurlMultiHandler
{
/** @var callable */
private $factory;
private $selectTimeout;
private $active = 0;
private $handles = [];
private $delays = [];
private $maxHandles;
/**
* This handler accepts the following options:
*
* - mh: An optional curl_multi resource
* - handle_factory: An optional callable used to generate curl handle
* resources. the callable accepts a request hash and returns an array
* of the handle, headers file resource, and the body resource.
* - select_timeout: Optional timeout (in seconds) to block before timing
* out while selecting curl handles. Defaults to 1 second.
* - max_handles: Optional integer representing the maximum number of
* open requests. When this number is reached, the queued futures are
* flushed.
*
* @param array $options
*/
public function __construct(array $options = [])
{
if (isset($options['mh'])) {
$this->_mh = $options['mh'];
}
$this->factory = isset($options['handle_factory'])
? $options['handle_factory'] : new CurlFactory();
$this->selectTimeout = isset($options['select_timeout'])
? $options['select_timeout'] : 1;
$this->maxHandles = isset($options['max_handles'])
? $options['max_handles'] : 100;
}
public function __get($name)
{
if ($name === '_mh') {
return $this->_mh = curl_multi_init();
}
throw new \BadMethodCallException();
}
public function __destruct()
{
// Finish any open connections before terminating the script.
if ($this->handles) {
$this->execute();
}
if (isset($this->_mh)) {
curl_multi_close($this->_mh);
unset($this->_mh);
}
}
public function __invoke(array $request)
{
$factory = $this->factory;
$result = $factory($request);
$entry = [
'request' => $request,
'response' => [],
'handle' => $result[0],
'headers' => &$result[1],
'body' => $result[2],
'deferred' => new Deferred(),
];
$id = (int) $result[0];
$future = new FutureArray(
$entry['deferred']->promise(),
[$this, 'execute'],
function () use ($id) {
return $this->cancel($id);
}
);
$this->addRequest($entry);
// Transfer outstanding requests if there are too many open handles.
if (count($this->handles) >= $this->maxHandles) {
$this->execute();
}
return $future;
}
/**
* Runs until all outstanding connections have completed.
*/
public function execute()
{
do {
if ($this->active &&
curl_multi_select($this->_mh, $this->selectTimeout) === -1
) {
// Perform a usleep if a select returns -1.
// See: https://bugs.php.net/bug.php?id=61141
usleep(250);
}
// Add any delayed futures if needed.
if ($this->delays) {
$this->addDelays();
}
do {
$mrc = curl_multi_exec($this->_mh, $this->active);
} while ($mrc === CURLM_CALL_MULTI_PERFORM);
$this->processMessages();
// If there are delays but no transfers, then sleep for a bit.
if (!$this->active && $this->delays) {
usleep(500);
}
} while ($this->active || $this->handles);
}
private function addRequest(array &$entry)
{
$id = (int) $entry['handle'];
$this->handles[$id] = $entry;
// If the request is a delay, then add the reques to the curl multi
// pool only after the specified delay.
if (isset($entry['request']['client']['delay'])) {
$this->delays[$id] = microtime(true) + ($entry['request']['client']['delay'] / 1000);
} elseif (empty($entry['request']['future'])) {
curl_multi_add_handle($this->_mh, $entry['handle']);
} else {
curl_multi_add_handle($this->_mh, $entry['handle']);
// "lazy" futures are only sent once the pool has many requests.
if ($entry['request']['future'] !== 'lazy') {
do {
$mrc = curl_multi_exec($this->_mh, $this->active);
} while ($mrc === CURLM_CALL_MULTI_PERFORM);
$this->processMessages();
}
}
}
private function removeProcessed($id)
{
if (isset($this->handles[$id])) {
curl_multi_remove_handle(
$this->_mh,
$this->handles[$id]['handle']
);
if (PHP_VERSION_ID < 80000) {
curl_close($this->handles[$id]['handle']);
}
unset($this->handles[$id], $this->delays[$id]);
}
}
/**
* Cancels a handle from sending and removes references to it.
*
* @param int $id Handle ID to cancel and remove.
*
* @return bool True on success, false on failure.
*/
private function cancel($id)
{
// Cannot cancel if it has been processed.
if (!isset($this->handles[$id])) {
return false;
}
$handle = $this->handles[$id]['handle'];
unset($this->delays[$id], $this->handles[$id]);
curl_multi_remove_handle($this->_mh, $handle);
if (PHP_VERSION_ID < 80000) {
curl_close($handle);
}
return true;
}
private function addDelays()
{
$currentTime = microtime(true);
foreach ($this->delays as $id => $delay) {
if ($currentTime >= $delay) {
unset($this->delays[$id]);
curl_multi_add_handle(
$this->_mh,
$this->handles[$id]['handle']
);
}
}
}
private function processMessages()
{
while ($done = curl_multi_info_read($this->_mh)) {
$id = (int) $done['handle'];
if (!isset($this->handles[$id])) {
// Probably was cancelled.
continue;
}
$entry = $this->handles[$id];
$entry['response']['transfer_stats'] = curl_getinfo($done['handle']);
if ($done['result'] !== CURLM_OK) {
$entry['response']['curl']['errno'] = $done['result'];
$entry['response']['curl']['error'] = curl_error($done['handle']);
}
$result = CurlFactory::createResponse(
$this,
$entry['request'],
$entry['response'],
$entry['headers'],
$entry['body']
);
$this->removeProcessed($id);
$entry['deferred']->resolve($result);
}
}
}

View File

@ -0,0 +1,58 @@
<?php
namespace GuzzleHttp\Ring\Client;
/**
* Provides basic middleware wrappers.
*
* If a middleware is more complex than a few lines of code, then it should
* be implemented in a class rather than a static method.
*/
class Middleware
{
/**
* Sends future requests to a future compatible handler while sending all
* other requests to a default handler.
*
* When the "future" option is not provided on a request, any future responses
* are automatically converted to synchronous responses and block.
*
* @param callable $default Handler used for non-streaming responses
* @param callable $future Handler used for future responses
*
* @return callable Returns the composed handler.
*/
public static function wrapFuture(
callable $default,
callable $future
) {
return function (array $request) use ($default, $future) {
return empty($request['client']['future'])
? $default($request)
: $future($request);
};
}
/**
* Sends streaming requests to a streaming compatible handler while sendin
* all other requests to a default handler.
*
* This, for example, could be useful for taking advantage of the
* performance benefits of curl while still supporting true streaming
* through the StreamHandler.
*
* @param callable $default Handler used for non-streaming responses
* @param callable $streaming Handler used for streaming responses
*
* @return callable Returns the composed handler.
*/
public static function wrapStreaming(
callable $default,
callable $streaming
) {
return function (array $request) use ($default, $streaming) {
return empty($request['client']['stream'])
? $default($request)
: $streaming($request);
};
}
}

View File

@ -0,0 +1,52 @@
<?php
namespace GuzzleHttp\Ring\Client;
use GuzzleHttp\Ring\Core;
use GuzzleHttp\Ring\Future\CompletedFutureArray;
use GuzzleHttp\Ring\Future\FutureArrayInterface;
/**
* Ring handler that returns a canned response or evaluated function result.
*/
class MockHandler
{
/** @var callable|array|FutureArrayInterface */
private $result;
/**
* Provide an array or future to always return the same value. Provide a
* callable that accepts a request object and returns an array or future
* to dynamically create a response.
*
* @param array|FutureArrayInterface|callable $result Mock return value.
*/
public function __construct($result)
{
$this->result = $result;
}
public function __invoke(array $request)
{
Core::doSleep($request);
$response = is_callable($this->result)
? call_user_func($this->result, $request)
: $this->result;
if (is_array($response)) {
$response = new CompletedFutureArray($response + [
'status' => null,
'body' => null,
'headers' => [],
'reason' => null,
'effective_url' => null,
]);
} elseif (!$response instanceof FutureArrayInterface) {
throw new \InvalidArgumentException(
'Response must be an array or FutureArrayInterface. Found '
. Core::describeType($request)
);
}
return $response;
}
}

View File

@ -0,0 +1,414 @@
<?php
namespace GuzzleHttp\Ring\Client;
use GuzzleHttp\Ring\Core;
use GuzzleHttp\Ring\Exception\ConnectException;
use GuzzleHttp\Ring\Exception\RingException;
use GuzzleHttp\Ring\Future\CompletedFutureArray;
use GuzzleHttp\Stream\InflateStream;
use GuzzleHttp\Stream\StreamInterface;
use GuzzleHttp\Stream\Stream;
use GuzzleHttp\Stream\Utils;
/**
* RingPHP client handler that uses PHP's HTTP stream wrapper.
*/
class StreamHandler
{
private $options;
private $lastHeaders;
public function __construct(array $options = [])
{
$this->options = $options;
}
public function __invoke(array $request)
{
$url = Core::url($request);
Core::doSleep($request);
try {
// Does not support the expect header.
$request = Core::removeHeader($request, 'Expect');
$stream = $this->createStream($url, $request);
return $this->createResponse($request, $url, $stream);
} catch (RingException $e) {
return $this->createErrorResponse($url, $e);
}
}
private function createResponse(array $request, $url, $stream)
{
$hdrs = $this->lastHeaders;
$this->lastHeaders = null;
$parts = explode(' ', array_shift($hdrs), 3);
$response = [
'version' => substr($parts[0], 5),
'status' => $parts[1],
'reason' => isset($parts[2]) ? $parts[2] : null,
'headers' => Core::headersFromLines($hdrs),
'effective_url' => $url,
];
$stream = $this->checkDecode($request, $response, $stream);
// If not streaming, then drain the response into a stream.
if (empty($request['client']['stream'])) {
$dest = isset($request['client']['save_to'])
? $request['client']['save_to']
: fopen('php://temp', 'r+');
$stream = $this->drain($stream, $dest);
}
$response['body'] = $stream;
return new CompletedFutureArray($response);
}
private function checkDecode(array $request, array $response, $stream)
{
// Automatically decode responses when instructed.
if (!empty($request['client']['decode_content'])) {
switch (Core::firstHeader($response, 'Content-Encoding', true)) {
case 'gzip':
case 'deflate':
$stream = new InflateStream(Stream::factory($stream));
break;
}
}
return $stream;
}
/**
* Drains the stream into the "save_to" client option.
*
* @param resource $stream
* @param string|resource|StreamInterface $dest
*
* @return Stream
* @throws \RuntimeException when the save_to option is invalid.
*/
private function drain($stream, $dest)
{
if (is_resource($stream)) {
if (!is_resource($dest)) {
$stream = Stream::factory($stream);
} else {
stream_copy_to_stream($stream, $dest);
fclose($stream);
rewind($dest);
return $dest;
}
}
// Stream the response into the destination stream
$dest = is_string($dest)
? new Stream(Utils::open($dest, 'r+'))
: Stream::factory($dest);
Utils::copyToStream($stream, $dest);
$dest->seek(0);
$stream->close();
return $dest;
}
/**
* Creates an error response for the given stream.
*
* @param string $url
* @param RingException $e
*
* @return array
*/
private function createErrorResponse($url, RingException $e)
{
// Determine if the error was a networking error.
$message = $e->getMessage();
// This list can probably get more comprehensive.
if (strpos($message, 'getaddrinfo') // DNS lookup failed
|| strpos($message, 'Connection refused')
) {
$e = new ConnectException($e->getMessage(), 0, $e);
}
return new CompletedFutureArray([
'status' => null,
'body' => null,
'headers' => [],
'effective_url' => $url,
'error' => $e
]);
}
/**
* Create a resource and check to ensure it was created successfully
*
* @param callable $callback Callable that returns stream resource
*
* @return resource
* @throws \RuntimeException on error
*/
private function createResource(callable $callback)
{
$errors = null;
set_error_handler(function ($_, $msg, $file, $line) use (&$errors) {
$errors[] = [
'message' => $msg,
'file' => $file,
'line' => $line
];
return true;
});
$resource = $callback();
restore_error_handler();
if (!$resource) {
$message = 'Error creating resource: ';
foreach ($errors as $err) {
foreach ($err as $key => $value) {
$message .= "[$key] $value" . PHP_EOL;
}
}
throw new RingException(trim($message));
}
return $resource;
}
private function createStream($url, array $request)
{
static $methods;
if (!$methods) {
$methods = array_flip(get_class_methods(__CLASS__));
}
// HTTP/1.1 streams using the PHP stream wrapper require a
// Connection: close header
if ((!isset($request['version']) || $request['version'] == '1.1')
&& !Core::hasHeader($request, 'Connection')
) {
$request['headers']['Connection'] = ['close'];
}
// Ensure SSL is verified by default
if (!isset($request['client']['verify'])) {
$request['client']['verify'] = true;
}
$params = [];
$options = $this->getDefaultOptions($request);
if (isset($request['client'])) {
foreach ($request['client'] as $key => $value) {
$method = "add_{$key}";
if (isset($methods[$method])) {
$this->{$method}($request, $options, $value, $params);
}
}
}
return $this->createStreamResource(
$url,
$request,
$options,
$this->createContext($request, $options, $params)
);
}
private function getDefaultOptions(array $request)
{
$headers = "";
foreach ($request['headers'] as $name => $value) {
foreach ((array) $value as $val) {
$headers .= "$name: $val\r\n";
}
}
$context = [
'http' => [
'method' => $request['http_method'],
'header' => $headers,
'protocol_version' => isset($request['version']) ? $request['version'] : 1.1,
'ignore_errors' => true,
'follow_location' => 0,
],
];
$body = Core::body($request);
if (isset($body)) {
$context['http']['content'] = $body;
// Prevent the HTTP handler from adding a Content-Type header.
if (!Core::hasHeader($request, 'Content-Type')) {
$context['http']['header'] .= "Content-Type:\r\n";
}
}
$context['http']['header'] = rtrim($context['http']['header']);
return $context;
}
private function add_proxy(array $request, &$options, $value, &$params)
{
if (!is_array($value)) {
$options['http']['proxy'] = $value;
} else {
$scheme = isset($request['scheme']) ? $request['scheme'] : 'http';
if (isset($value[$scheme])) {
$options['http']['proxy'] = $value[$scheme];
}
}
}
private function add_timeout(array $request, &$options, $value, &$params)
{
$options['http']['timeout'] = $value;
}
private function add_verify(array $request, &$options, $value, &$params)
{
if ($value === true) {
// PHP 5.6 or greater will find the system cert by default. When
// < 5.6, use the Guzzle bundled cacert.
if (PHP_VERSION_ID < 50600) {
$options['ssl']['cafile'] = ClientUtils::getDefaultCaBundle();
}
} elseif (is_string($value)) {
$options['ssl']['cafile'] = $value;
if (!file_exists($value)) {
throw new RingException("SSL CA bundle not found: $value");
}
} elseif ($value === false) {
$options['ssl']['verify_peer'] = false;
$options['ssl']['allow_self_signed'] = true;
return;
} else {
throw new RingException('Invalid verify request option');
}
$options['ssl']['verify_peer'] = true;
$options['ssl']['allow_self_signed'] = false;
}
private function add_cert(array $request, &$options, $value, &$params)
{
if (is_array($value)) {
$options['ssl']['passphrase'] = $value[1];
$value = $value[0];
}
if (!file_exists($value)) {
throw new RingException("SSL certificate not found: {$value}");
}
$options['ssl']['local_cert'] = $value;
}
private function add_progress(array $request, &$options, $value, &$params)
{
$fn = function ($code, $_1, $_2, $_3, $transferred, $total) use ($value) {
if ($code == STREAM_NOTIFY_PROGRESS) {
$value($total, $transferred, null, null);
}
};
// Wrap the existing function if needed.
$params['notification'] = isset($params['notification'])
? Core::callArray([$params['notification'], $fn])
: $fn;
}
private function add_debug(array $request, &$options, $value, &$params)
{
if ($value === false) {
return;
}
static $map = [
STREAM_NOTIFY_CONNECT => 'CONNECT',
STREAM_NOTIFY_AUTH_REQUIRED => 'AUTH_REQUIRED',
STREAM_NOTIFY_AUTH_RESULT => 'AUTH_RESULT',
STREAM_NOTIFY_MIME_TYPE_IS => 'MIME_TYPE_IS',
STREAM_NOTIFY_FILE_SIZE_IS => 'FILE_SIZE_IS',
STREAM_NOTIFY_REDIRECTED => 'REDIRECTED',
STREAM_NOTIFY_PROGRESS => 'PROGRESS',
STREAM_NOTIFY_FAILURE => 'FAILURE',
STREAM_NOTIFY_COMPLETED => 'COMPLETED',
STREAM_NOTIFY_RESOLVE => 'RESOLVE',
];
static $args = ['severity', 'message', 'message_code',
'bytes_transferred', 'bytes_max'];
$value = Core::getDebugResource($value);
$ident = $request['http_method'] . ' ' . Core::url($request);
$fn = function () use ($ident, $value, $map, $args) {
$passed = func_get_args();
$code = array_shift($passed);
fprintf($value, '<%s> [%s] ', $ident, $map[$code]);
foreach (array_filter($passed) as $i => $v) {
fwrite($value, $args[$i] . ': "' . $v . '" ');
}
fwrite($value, "\n");
};
// Wrap the existing function if needed.
$params['notification'] = isset($params['notification'])
? Core::callArray([$params['notification'], $fn])
: $fn;
}
private function applyCustomOptions(array $request, array &$options)
{
if (!isset($request['client']['stream_context'])) {
return;
}
if (!is_array($request['client']['stream_context'])) {
throw new RingException('stream_context must be an array');
}
$options = array_replace_recursive(
$options,
$request['client']['stream_context']
);
}
private function createContext(array $request, array $options, array $params)
{
$this->applyCustomOptions($request, $options);
return $this->createResource(
function () use ($request, $options, $params) {
return stream_context_create($options, $params);
},
$request,
$options
);
}
private function createStreamResource(
$url,
array $request,
array $options,
$context
) {
return $this->createResource(
function () use ($url, $context) {
if (false === strpos($url, 'http')) {
trigger_error("URL is invalid: {$url}", E_USER_WARNING);
return null;
}
$resource = fopen($url, 'r', null, $context);
$this->lastHeaders = $http_response_header;
return $resource;
},
$request,
$options
);
}
}

364
vendor/ezimuel/ringphp/src/Core.php vendored Normal file
View File

@ -0,0 +1,364 @@
<?php
namespace GuzzleHttp\Ring;
use GuzzleHttp\Stream\StreamInterface;
use GuzzleHttp\Ring\Future\FutureArrayInterface;
use GuzzleHttp\Ring\Future\FutureArray;
/**
* Provides core functionality of Ring handlers and middleware.
*/
class Core
{
/**
* Returns a function that calls all of the provided functions, in order,
* passing the arguments provided to the composed function to each function.
*
* @param callable[] $functions Array of functions to proxy to.
*
* @return callable
*/
public static function callArray(array $functions)
{
return function () use ($functions) {
$args = func_get_args();
foreach ($functions as $fn) {
call_user_func_array($fn, $args);
}
};
}
/**
* Gets an array of header line values from a message for a specific header
*
* This method searches through the "headers" key of a message for a header
* using a case-insensitive search.
*
* @param array $message Request or response hash.
* @param string $header Header to retrieve
*
* @return array
*/
public static function headerLines($message, $header)
{
$result = [];
if (!empty($message['headers'])) {
foreach ($message['headers'] as $name => $value) {
if (!strcasecmp($name, $header)) {
$result = array_merge($result, $value);
}
}
}
return $result;
}
/**
* Gets a header value from a message as a string or null
*
* This method searches through the "headers" key of a message for a header
* using a case-insensitive search. The lines of the header are imploded
* using commas into a single string return value.
*
* @param array $message Request or response hash.
* @param string $header Header to retrieve
*
* @return string|null Returns the header string if found, or null if not.
*/
public static function header($message, $header)
{
$match = self::headerLines($message, $header);
return $match ? implode(', ', $match) : null;
}
/**
* Returns the first header value from a message as a string or null. If
* a header line contains multiple values separated by a comma, then this
* function will return the first value in the list.
*
* @param array $message Request or response hash.
* @param string $header Header to retrieve
*
* @return string|null Returns the value as a string if found.
*/
public static function firstHeader($message, $header)
{
if (!empty($message['headers'])) {
foreach ($message['headers'] as $name => $value) {
if (!strcasecmp($name, $header)) {
// Return the match itself if it is a single value.
$pos = strpos($value[0], ',');
return $pos ? substr($value[0], 0, $pos) : $value[0];
}
}
}
return null;
}
/**
* Returns true if a message has the provided case-insensitive header.
*
* @param array $message Request or response hash.
* @param string $header Header to check
*
* @return bool
*/
public static function hasHeader($message, $header)
{
if (!empty($message['headers'])) {
foreach ($message['headers'] as $name => $value) {
if (!strcasecmp($name, $header)) {
return true;
}
}
}
return false;
}
/**
* Parses an array of header lines into an associative array of headers.
*
* @param array $lines Header lines array of strings in the following
* format: "Name: Value"
* @return array
*/
public static function headersFromLines($lines)
{
$headers = [];
foreach ($lines as $line) {
$parts = explode(':', $line, 2);
$headers[trim($parts[0])][] = isset($parts[1])
? trim($parts[1])
: null;
}
return $headers;
}
/**
* Removes a header from a message using a case-insensitive comparison.
*
* @param array $message Message that contains 'headers'
* @param string $header Header to remove
*
* @return array
*/
public static function removeHeader(array $message, $header)
{
if (isset($message['headers'])) {
foreach (array_keys($message['headers']) as $key) {
if (!strcasecmp($header, $key)) {
unset($message['headers'][$key]);
}
}
}
return $message;
}
/**
* Replaces any existing case insensitive headers with the given value.
*
* @param array $message Message that contains 'headers'
* @param string $header Header to set.
* @param array $value Value to set.
*
* @return array
*/
public static function setHeader(array $message, $header, array $value)
{
$message = self::removeHeader($message, $header);
$message['headers'][$header] = $value;
return $message;
}
/**
* Creates a URL string from a request.
*
* If the "url" key is present on the request, it is returned, otherwise
* the url is built up based on the scheme, host, uri, and query_string
* request values.
*
* @param array $request Request to get the URL from
*
* @return string Returns the request URL as a string.
* @throws \InvalidArgumentException if no Host header is present.
*/
public static function url(array $request)
{
if (isset($request['url'])) {
return $request['url'];
}
$uri = (isset($request['scheme'])
? $request['scheme'] : 'http') . '://';
if ($host = self::header($request, 'host')) {
$uri .= $host;
} else {
throw new \InvalidArgumentException('No Host header was provided');
}
if (isset($request['uri'])) {
$uri .= $request['uri'];
}
if (isset($request['query_string'])) {
$uri .= '?' . $request['query_string'];
}
return $uri;
}
/**
* Reads the body of a message into a string.
*
* @param array|FutureArrayInterface $message Array containing a "body" key
*
* @return null|string Returns the body as a string or null if not set.
* @throws \InvalidArgumentException if a request body is invalid.
*/
public static function body($message)
{
if (!isset($message['body'])) {
return null;
}
if ($message['body'] instanceof StreamInterface) {
return (string) $message['body'];
}
switch (gettype($message['body'])) {
case 'string':
return $message['body'];
case 'resource':
return stream_get_contents($message['body']);
case 'object':
if ($message['body'] instanceof \Iterator) {
return implode('', iterator_to_array($message['body']));
} elseif (method_exists($message['body'], '__toString')) {
return (string) $message['body'];
}
default:
throw new \InvalidArgumentException('Invalid request body: '
. self::describeType($message['body']));
}
}
/**
* Rewind the body of the provided message if possible.
*
* @param array $message Message that contains a 'body' field.
*
* @return bool Returns true on success, false on failure
*/
public static function rewindBody($message)
{
if ($message['body'] instanceof StreamInterface) {
return $message['body']->seek(0);
}
if ($message['body'] instanceof \Generator) {
return false;
}
if ($message['body'] instanceof \Iterator) {
$message['body']->rewind();
return true;
}
if (is_resource($message['body'])) {
return rewind($message['body']);
}
return is_string($message['body'])
|| (is_object($message['body'])
&& method_exists($message['body'], '__toString'));
}
/**
* Debug function used to describe the provided value type and class.
*
* @param mixed $input
*
* @return string Returns a string containing the type of the variable and
* if a class is provided, the class name.
*/
public static function describeType($input)
{
switch (gettype($input)) {
case 'object':
return 'object(' . get_class($input) . ')';
case 'array':
return 'array(' . count($input) . ')';
default:
ob_start();
var_dump($input);
// normalize float vs double
return str_replace('double(', 'float(', rtrim(ob_get_clean()));
}
}
/**
* Sleep for the specified amount of time specified in the request's
* ['client']['delay'] option if present.
*
* This function should only be used when a non-blocking sleep is not
* possible.
*
* @param array $request Request to sleep
*/
public static function doSleep(array $request)
{
if (isset($request['client']['delay'])) {
usleep($request['client']['delay'] * 1000);
}
}
/**
* Returns a proxied future that modifies the dereferenced value of another
* future using a promise.
*
* @param FutureArrayInterface $future Future to wrap with a new future
* @param callable $onFulfilled Invoked when the future fulfilled
* @param callable $onRejected Invoked when the future rejected
* @param callable $onProgress Invoked when the future progresses
*
* @return FutureArray
*/
public static function proxy(
FutureArrayInterface $future,
?callable $onFulfilled = null,
?callable $onRejected = null,
?callable $onProgress = null
) {
return new FutureArray(
$future->then($onFulfilled, $onRejected, $onProgress),
[$future, 'wait'],
[$future, 'cancel']
);
}
/**
* Returns a debug stream based on the provided variable.
*
* @param mixed $value Optional value
*
* @return resource
*/
public static function getDebugResource($value = null)
{
if (is_resource($value)) {
return $value;
} elseif (defined('STDOUT')) {
return STDOUT;
} else {
return fopen('php://output', 'w');
}
}
}

View File

@ -0,0 +1,7 @@
<?php
namespace GuzzleHttp\Ring\Exception;
/**
* Marker interface for cancelled exceptions.
*/
interface CancelledException {}

View File

@ -0,0 +1,4 @@
<?php
namespace GuzzleHttp\Ring\Exception;
class CancelledFutureAccessException extends RingException implements CancelledException {}

View File

@ -0,0 +1,7 @@
<?php
namespace GuzzleHttp\Ring\Exception;
/**
* Occurs when the connection failed.
*/
class ConnectException extends RingException {}

View File

@ -0,0 +1,4 @@
<?php
namespace GuzzleHttp\Ring\Exception;
class RingException extends \RuntimeException {};

View File

@ -0,0 +1,134 @@
<?php
namespace GuzzleHttp\Ring\Future;
use GuzzleHttp\Ring\Exception\CancelledFutureAccessException;
use GuzzleHttp\Ring\Exception\RingException;
use React\Promise\PromiseInterface;
/**
* Implements common future functionality built on top of promises.
*/
trait BaseFutureTrait
{
/** @var callable */
private $waitfn;
/** @var callable */
private $cancelfn;
/** @var PromiseInterface */
private $wrappedPromise;
/** @var \Exception Error encountered. */
private $error;
/** @var mixed Result of the future */
private $result;
private $isRealized = false;
/**
* @param PromiseInterface $promise Promise to shadow with the future.
* @param callable $wait Function that blocks until the deferred
* computation has been resolved. This
* function MUST resolve the deferred value
* associated with the supplied promise.
* @param callable $cancel If possible and reasonable, provide a
* function that can be used to cancel the
* future from completing.
*/
public function __construct(
PromiseInterface $promise,
?callable $wait = null,
?callable $cancel = null
) {
$this->wrappedPromise = $promise;
$this->waitfn = $wait;
$this->cancelfn = $cancel;
}
/**
* @return mixed
*/
public function wait()
{
if (!$this->isRealized) {
$this->addShadow();
if (!$this->isRealized && $this->waitfn) {
$this->invokeWait();
}
if (!$this->isRealized) {
$this->error = new RingException('Waiting did not resolve future');
}
}
if ($this->error) {
throw $this->error;
}
return $this->result;
}
/**
* @return PromiseInterface
*/
public function promise()
{
return $this->wrappedPromise;
}
/**
* @return PromiseInterface
*/
public function then(
?callable $onFulfilled = null,
?callable $onRejected = null,
?callable $onProgress = null
) {
return $this->wrappedPromise->then($onFulfilled, $onRejected, $onProgress);
}
public function cancel(): void
{
if (!$this->isRealized) {
$cancelfn = $this->cancelfn;
$this->waitfn = $this->cancelfn = null;
$this->isRealized = true;
$this->error = new CancelledFutureAccessException();
if ($cancelfn) {
$cancelfn($this);
}
}
}
private function addShadow()
{
// Get the result and error when the promise is resolved. Note that
// calling this function might trigger the resolution immediately.
$this->wrappedPromise->then(
function ($value) {
$this->isRealized = true;
$this->result = $value;
$this->waitfn = $this->cancelfn = null;
},
function ($error) {
$this->isRealized = true;
$this->error = $error;
$this->waitfn = $this->cancelfn = null;
}
);
}
private function invokeWait()
{
try {
$wait = $this->waitfn;
$this->waitfn = null;
$wait();
} catch (\Exception $e) {
// Defer can throw to reject.
$this->error = $e;
$this->isRealized = true;
}
}
}

View File

@ -0,0 +1,67 @@
<?php
namespace GuzzleHttp\Ring\Future;
/**
* Represents a future array that has been completed successfully.
*/
class CompletedFutureArray extends CompletedFutureValue implements FutureArrayInterface
{
public function __construct(array $result)
{
parent::__construct($result);
}
#[\ReturnTypeWillChange]
/**
* @return bool
*/
public function offsetExists($offset)
{
return isset($this->result[$offset]);
}
#[\ReturnTypeWillChange]
/**
* @return mixed
*/
public function offsetGet($offset)
{
return $this->result[$offset];
}
#[\ReturnTypeWillChange]
/**
* @return void
*/
public function offsetSet($offset, $value)
{
$this->result[$offset] = $value;
}
#[\ReturnTypeWillChange]
/**
* @return void
*/
public function offsetUnset($offset)
{
unset($this->result[$offset]);
}
#[\ReturnTypeWillChange]
/**
* @return int
*/
public function count()
{
return count($this->result);
}
#[\ReturnTypeWillChange]
/**
* @return \ArrayIterator
*/
public function getIterator()
{
return new \ArrayIterator($this->result);
}
}

View File

@ -0,0 +1,68 @@
<?php
namespace GuzzleHttp\Ring\Future;
use React\Promise\PromiseInterface;
use function React\Promise\reject;
use function React\Promise\resolve;
/**
* Represents a future value that has been resolved or rejected.
*/
class CompletedFutureValue implements FutureInterface
{
protected $result;
protected $error;
private $cachedPromise;
/**
* @param mixed $result Resolved result
* @param \Exception $e Error. Pass a GuzzleHttp\Ring\Exception\CancelledFutureAccessException
* to mark the future as cancelled.
*/
public function __construct($result, ?\Exception $e = null)
{
$this->result = $result;
$this->error = $e;
}
/**
* @return mixed
*/
public function wait()
{
if ($this->error) {
throw $this->error;
}
return $this->result;
}
public function cancel(): void
{}
/**
* @return PromiseInterface
*/
public function promise()
{
if (!$this->cachedPromise) {
$this->cachedPromise = $this->error
? reject($this->error)
: resolve($this->result);
}
return $this->cachedPromise;
}
/**
* @return PromiseInterface
*/
public function then(
?callable $onFulfilled = null,
?callable $onRejected = null
) {
return $this->promise()->then($onFulfilled, $onRejected);
}
}

View File

@ -0,0 +1,65 @@
<?php
namespace GuzzleHttp\Ring\Future;
/**
* Represents a future array value that when dereferenced returns an array.
*/
#[\AllowDynamicProperties]
class FutureArray implements FutureArrayInterface
{
use MagicFutureTrait;
#[\ReturnTypeWillChange]
/**
* @return bool
*/
public function offsetExists($offset)
{
return isset($this->_value[$offset]);
}
#[\ReturnTypeWillChange]
/**
* @return mixed
*/
public function offsetGet($offset)
{
return $this->_value[$offset];
}
#[\ReturnTypeWillChange]
/**
* @return void
*/
public function offsetSet($offset, $value)
{
$this->_value[$offset] = $value;
}
#[\ReturnTypeWillChange]
/**
* @return void
*/
public function offsetUnset($offset)
{
unset($this->_value[$offset]);
}
#[\ReturnTypeWillChange]
/**
* @return int
*/
public function count()
{
return count($this->_value);
}
#[\ReturnTypeWillChange]
/**
* @return \ArrayIterator
*/
public function getIterator()
{
return new \ArrayIterator($this->_value);
}
}

View File

@ -0,0 +1,11 @@
<?php
namespace GuzzleHttp\Ring\Future;
/**
* Future that provides array-like access.
*/
interface FutureArrayInterface extends
FutureInterface,
\ArrayAccess,
\Countable,
\IteratorAggregate {};

View File

@ -0,0 +1,80 @@
<?php
namespace GuzzleHttp\Ring\Future;
use React\Promise\PromiseInterface;
/**
* Represents the result of a computation that may not have completed yet.
*
* You can use the future in a blocking manner using the wait() function, or
* you can use a promise from the future to receive the result when the future
* has been resolved.
*
* When the future is dereferenced using wait(), the result of the computation
* is cached and returned for subsequent calls to wait(). If the result of the
* computation has not yet completed when wait() is called, the call to wait()
* will block until the future has completed.
*/
interface FutureInterface
{
/**
* Returns the result of the future either from cache or by blocking until
* it is complete.
*
* This method must block until the future has a result or is cancelled.
* Throwing an exception in the wait() method will mark the future as
* realized and will throw the exception each time wait() is called.
* Throwing an instance of GuzzleHttp\Ring\CancelledException will mark
* the future as realized, will not throw immediately, but will throw the
* exception if the future's wait() method is called again.
*
* @return mixed
*/
public function wait();
/**
* Cancels the future, if possible.
*/
public function cancel();
/**
* Transforms a promise's value by applying a function to the promise's fulfillment
* or rejection value. Returns a new promise for the transformed result.
*
* The `then()` method registers new fulfilled and rejection handlers with a promise
* (all parameters are optional):
*
* * `$onFulfilled` will be invoked once the promise is fulfilled and passed
* the result as the first argument.
* * `$onRejected` will be invoked once the promise is rejected and passed the
* reason as the first argument.
* * `$onProgress` (deprecated) will be invoked whenever the producer of the promise
* triggers progress notifications and passed a single argument (whatever it
* wants) to indicate progress.
*
* It returns a new promise that will fulfill with the return value of either
* `$onFulfilled` or `$onRejected`, whichever is called, or will reject with
* the thrown exception if either throws.
*
* A promise makes the following guarantees about handlers registered in
* the same call to `then()`:
*
* 1. Only one of `$onFulfilled` or `$onRejected` will be called,
* never both.
* 2. `$onFulfilled` and `$onRejected` will never be called more
* than once.
* 3. `$onProgress` (deprecated) may be called multiple times.
*
* @param callable|null $onFulfilled
* @param callable|null $onRejected
* @return PromiseInterface
*/
public function then(?callable $onFulfilled = null, ?callable $onRejected = null);
/**
* Returns the promise of the deferred.
*
* @return PromiseInterface
*/
public function promise();
}

View File

@ -0,0 +1,12 @@
<?php
namespace GuzzleHttp\Ring\Future;
/**
* Represents a future value that responds to wait() to retrieve the promised
* value, but can also return promises that are delivered the value when it is
* available.
*/
class FutureValue implements FutureInterface
{
use BaseFutureTrait;
}

View File

@ -0,0 +1,32 @@
<?php
namespace GuzzleHttp\Ring\Future;
/**
* Implements common future functionality that is triggered when the result
* property is accessed via a magic __get method.
*
* @property mixed $_value Actual data used by the future. Accessing this
* property will cause the future to block if needed.
*/
trait MagicFutureTrait
{
use BaseFutureTrait;
/**
* This function handles retrieving the dereferenced result when requested.
*
* @param string $name Should always be "data" or an exception is thrown.
*
* @return mixed Returns the dereferenced data.
* @throws \RuntimeException
* @throws \GuzzleHttp\Ring\Exception\CancelledException
*/
public function __get($name)
{
if ($name !== '_value') {
throw new \RuntimeException("Class has no {$name} property");
}
return $this->_value = $this->wait();
}
}

View File

@ -0,0 +1,22 @@
{
"scanSettings": {
"configMode": "AUTO",
"configExternalURL": "",
"projectToken": "",
"baseBranches": []
},
"checkRunSettings": {
"vulnerableCheckRunConclusionLevel": "failure",
"displayMode": "diff",
"useMendCheckNames": true
},
"issueSettings": {
"minSeverityLevel": "LOW",
"issueType": "DEPENDENCY"
},
"remediateSettings": {
"workflowRules": {
"enabled": true
}
}
}

View File

@ -0,0 +1,13 @@
This software is licensed under the Apache License, version 2.0 by the OpenSearch Contributors.
This software also includes certain Elastic software that can be used under the Apache License, Version 2.0
(<LICENSE-APACHE> or <http://www.apache.org/licenses/LICENSE-2.0> (http://www.apache.org/licenses/LICENSE-2.0))
or the GNU Lesser General Public License, Version 2.1
(<LICENSE-LGPL> or <https://www.gnu.org/licenses/lgpl-2.1.html> (https://www.gnu.org/licenses/lgpl-2.1.html))
Election of Apache 2.0.
For the avoidance of doubt, the OpenSearch Contributors (i) elect to use only the Apache License, Version 2.0 for all
software where a choice of Apache License, Version 2.0 and GNU Lesser General Public License, Version 2.1 is
made available and (ii) elect to use only the Apache License, Version 2.0 on modifications of the software
by OpenSearch Contributors.

View File

@ -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.

View File

@ -0,0 +1,459 @@
GNU LESSER GENERAL PUBLIC LICENSE
Version 2.1, February 1999
Copyright (C) 1991, 1999 Free Software Foundation, Inc.
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
[This is the first released version of the Lesser GPL. It also counts
as the successor of the GNU Library Public License, version 2, hence
the version number 2.1.]
Preamble
The licenses for most software are designed to take away your
freedom to share and change it. By contrast, the GNU General Public
Licenses are intended to guarantee your freedom to share and change
free software--to make sure the software is free for all its users.
This license, the Lesser General Public License, applies to some
specially designated software packages--typically libraries--of the
Free Software Foundation and other authors who decide to use it. You
can use it too, but we suggest you first think carefully about whether
this license or the ordinary General Public License is the better
strategy to use in any particular case, based on the explanations below.
When we speak of free software, we are referring to freedom of use,
not price. Our General Public Licenses are designed to make sure that
you have the freedom to distribute copies of free software (and charge
for this service if you wish); that you receive source code or can get
it if you want it; that you can change the software and use pieces of
it in new free programs; and that you are informed that you can do
these things.
To protect your rights, we need to make restrictions that forbid
distributors to deny you these rights or to ask you to surrender these
rights. These restrictions translate to certain responsibilities for
you if you distribute copies of the library or if you modify it.
For example, if you distribute copies of the library, whether gratis
or for a fee, you must give the recipients all the rights that we gave
you. You must make sure that they, too, receive or can get the source
code. If you link other code with the library, you must provide
complete object files to the recipients, so that they can relink them
with the library after making changes to the library and recompiling
it. And you must show them these terms so they know their rights.
We protect your rights with a two-step method: (1) we copyright the
library, and (2) we offer you this license, which gives you legal
permission to copy, distribute and/or modify the library.
To protect each distributor, we want to make it very clear that
there is no warranty for the free library. Also, if the library is
modified by someone else and passed on, the recipients should know
that what they have is not the original version, so that the original
author's reputation will not be affected by problems that might be
introduced by others.
Finally, software patents pose a constant threat to the existence of
any free program. We wish to make sure that a company cannot
effectively restrict the users of a free program by obtaining a
restrictive license from a patent holder. Therefore, we insist that
any patent license obtained for a version of the library must be
consistent with the full freedom of use specified in this license.
Most GNU software, including some libraries, is covered by the
ordinary GNU General Public License. This license, the GNU Lesser
General Public License, applies to certain designated libraries, and
is quite different from the ordinary General Public License. We use
this license for certain libraries in order to permit linking those
libraries into non-free programs.
When a program is linked with a library, whether statically or using
a shared library, the combination of the two is legally speaking a
combined work, a derivative of the original library. The ordinary
General Public License therefore permits such linking only if the
entire combination fits its criteria of freedom. The Lesser General
Public License permits more lax criteria for linking other code with
the library.
We call this license the "Lesser" General Public License because it
does Less to protect the user's freedom than the ordinary General
Public License. It also provides other free software developers Less
of an advantage over competing non-free programs. These disadvantages
are the reason we use the ordinary General Public License for many
libraries. However, the Lesser license provides advantages in certain
special circumstances.
For example, on rare occasions, there may be a special need to
encourage the widest possible use of a certain library, so that it becomes
a de-facto standard. To achieve this, non-free programs must be
allowed to use the library. A more frequent case is that a free
library does the same job as widely used non-free libraries. In this
case, there is little to gain by limiting the free library to free
software only, so we use the Lesser General Public License.
In other cases, permission to use a particular library in non-free
programs enables a greater number of people to use a large body of
free software. For example, permission to use the GNU C Library in
non-free programs enables many more people to use the whole GNU
operating system, as well as its variant, the GNU/Linux operating
system.
Although the Lesser General Public License is Less protective of the
users' freedom, it does ensure that the user of a program that is
linked with the Library has the freedom and the wherewithal to run
that program using a modified version of the Library.
The precise terms and conditions for copying, distribution and
modification follow. Pay close attention to the difference between a
"work based on the library" and a "work that uses the library". The
former contains code derived from the library, whereas the latter must
be combined with the library in order to run.
GNU LESSER GENERAL PUBLIC LICENSE
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
0. This License Agreement applies to any software library or other
program which contains a notice placed by the copyright holder or
other authorized party saying it may be distributed under the terms of
this Lesser General Public License (also called "this License").
Each licensee is addressed as "you".
A "library" means a collection of software functions and/or data
prepared so as to be conveniently linked with application programs
(which use some of those functions and data) to form executables.
The "Library", below, refers to any such software library or work
which has been distributed under these terms. A "work based on the
Library" means either the Library or any derivative work under
copyright law: that is to say, a work containing the Library or a
portion of it, either verbatim or with modifications and/or translated
straightforwardly into another language. (Hereinafter, translation is
included without limitation in the term "modification".)
"Source code" for a work means the preferred form of the work for
making modifications to it. For a library, complete source code means
all the source code for all modules it contains, plus any associated
interface definition files, plus the scripts used to control compilation
and installation of the library.
Activities other than copying, distribution and modification are not
covered by this License; they are outside its scope. The act of
running a program using the Library is not restricted, and output from
such a program is covered only if its contents constitute a work based
on the Library (independent of the use of the Library in a tool for
writing it). Whether that is true depends on what the Library does
and what the program that uses the Library does.
1. You may copy and distribute verbatim copies of the Library's
complete source code as you receive it, in any medium, provided that
you conspicuously and appropriately publish on each copy an
appropriate copyright notice and disclaimer of warranty; keep intact
all the notices that refer to this License and to the absence of any
warranty; and distribute a copy of this License along with the
Library.
You may charge a fee for the physical act of transferring a copy,
and you may at your option offer warranty protection in exchange for a
fee.
2. You may modify your copy or copies of the Library or any portion
of it, thus forming a work based on the Library, and copy and
distribute such modifications or work under the terms of Section 1
above, provided that you also meet all of these conditions:
a) The modified work must itself be a software library.
b) You must cause the files modified to carry prominent notices
stating that you changed the files and the date of any change.
c) You must cause the whole of the work to be licensed at no
charge to all third parties under the terms of this License.
d) If a facility in the modified Library refers to a function or a
table of data to be supplied by an application program that uses
the facility, other than as an argument passed when the facility
is invoked, then you must make a good faith effort to ensure that,
in the event an application does not supply such function or
table, the facility still operates, and performs whatever part of
its purpose remains meaningful.
(For example, a function in a library to compute square roots has
a purpose that is entirely well-defined independent of the
application. Therefore, Subsection 2d requires that any
application-supplied function or table used by this function must
be optional: if the application does not supply it, the square
root function must still compute square roots.)
These requirements apply to the modified work as a whole. If
identifiable sections of that work are not derived from the Library,
and can be reasonably considered independent and separate works in
themselves, then this License, and its terms, do not apply to those
sections when you distribute them as separate works. But when you
distribute the same sections as part of a whole which is a work based
on the Library, the distribution of the whole must be on the terms of
this License, whose permissions for other licensees extend to the
entire whole, and thus to each and every part regardless of who wrote
it.
Thus, it is not the intent of this section to claim rights or contest
your rights to work written entirely by you; rather, the intent is to
exercise the right to control the distribution of derivative or
collective works based on the Library.
In addition, mere aggregation of another work not based on the Library
with the Library (or with a work based on the Library) on a volume of
a storage or distribution medium does not bring the other work under
the scope of this License.
3. You may opt to apply the terms of the ordinary GNU General Public
License instead of this License to a given copy of the Library. To do
this, you must alter all the notices that refer to this License, so
that they refer to the ordinary GNU General Public License, version 2,
instead of to this License. (If a newer version than version 2 of the
ordinary GNU General Public License has appeared, then you can specify
that version instead if you wish.) Do not make any other change in
these notices.
Once this change is made in a given copy, it is irreversible for
that copy, so the ordinary GNU General Public License applies to all
subsequent copies and derivative works made from that copy.
This option is useful when you wish to copy part of the code of
the Library into a program that is not a library.
4. You may copy and distribute the Library (or a portion or
derivative of it, under Section 2) in object code or executable form
under the terms of Sections 1 and 2 above provided that you accompany
it with the complete corresponding machine-readable source code, which
must be distributed under the terms of Sections 1 and 2 above on a
medium customarily used for software interchange.
If distribution of object code is made by offering access to copy
from a designated place, then offering equivalent access to copy the
source code from the same place satisfies the requirement to
distribute the source code, even though third parties are not
compelled to copy the source along with the object code.
5. A program that contains no derivative of any portion of the
Library, but is designed to work with the Library by being compiled or
linked with it, is called a "work that uses the Library". Such a
work, in isolation, is not a derivative work of the Library, and
therefore falls outside the scope of this License.
However, linking a "work that uses the Library" with the Library
creates an executable that is a derivative of the Library (because it
contains portions of the Library), rather than a "work that uses the
library". The executable is therefore covered by this License.
Section 6 states terms for distribution of such executables.
When a "work that uses the Library" uses material from a header file
that is part of the Library, the object code for the work may be a
derivative work of the Library even though the source code is not.
Whether this is true is especially significant if the work can be
linked without the Library, or if the work is itself a library. The
threshold for this to be true is not precisely defined by law.
If such an object file uses only numerical parameters, data
structure layouts and accessors, and small macros and small inline
functions (ten lines or less in length), then the use of the object
file is unrestricted, regardless of whether it is legally a derivative
work. (Executables containing this object code plus portions of the
Library will still fall under Section 6.)
Otherwise, if the work is a derivative of the Library, you may
distribute the object code for the work under the terms of Section 6.
Any executables containing that work also fall under Section 6,
whether or not they are linked directly with the Library itself.
6. As an exception to the Sections above, you may also combine or
link a "work that uses the Library" with the Library to produce a
work containing portions of the Library, and distribute that work
under terms of your choice, provided that the terms permit
modification of the work for the customer's own use and reverse
engineering for debugging such modifications.
You must give prominent notice with each copy of the work that the
Library is used in it and that the Library and its use are covered by
this License. You must supply a copy of this License. If the work
during execution displays copyright notices, you must include the
copyright notice for the Library among them, as well as a reference
directing the user to the copy of this License. Also, you must do one
of these things:
a) Accompany the work with the complete corresponding
machine-readable source code for the Library including whatever
changes were used in the work (which must be distributed under
Sections 1 and 2 above); and, if the work is an executable linked
with the Library, with the complete machine-readable "work that
uses the Library", as object code and/or source code, so that the
user can modify the Library and then relink to produce a modified
executable containing the modified Library. (It is understood
that the user who changes the contents of definitions files in the
Library will not necessarily be able to recompile the application
to use the modified definitions.)
b) Use a suitable shared library mechanism for linking with the
Library. A suitable mechanism is one that (1) uses at run time a
copy of the library already present on the user's computer system,
rather than copying library functions into the executable, and (2)
will operate properly with a modified version of the library, if
the user installs one, as long as the modified version is
interface-compatible with the version that the work was made with.
c) Accompany the work with a written offer, valid for at
least three years, to give the same user the materials
specified in Subsection 6a, above, for a charge no more
than the cost of performing this distribution.
d) If distribution of the work is made by offering access to copy
from a designated place, offer equivalent access to copy the above
specified materials from the same place.
e) Verify that the user has already received a copy of these
materials or that you have already sent this user a copy.
For an executable, the required form of the "work that uses the
Library" must include any data and utility programs needed for
reproducing the executable from it. However, as a special exception,
the materials to be distributed need not include anything that is
normally distributed (in either source or binary form) with the major
components (compiler, kernel, and so on) of the operating system on
which the executable runs, unless that component itself accompanies
the executable.
It may happen that this requirement contradicts the license
restrictions of other proprietary libraries that do not normally
accompany the operating system. Such a contradiction means you cannot
use both them and the Library together in an executable that you
distribute.
7. You may place library facilities that are a work based on the
Library side-by-side in a single library together with other library
facilities not covered by this License, and distribute such a combined
library, provided that the separate distribution of the work based on
the Library and of the other library facilities is otherwise
permitted, and provided that you do these two things:
a) Accompany the combined library with a copy of the same work
based on the Library, uncombined with any other library
facilities. This must be distributed under the terms of the
Sections above.
b) Give prominent notice with the combined library of the fact
that part of it is a work based on the Library, and explaining
where to find the accompanying uncombined form of the same work.
8. You may not copy, modify, sublicense, link with, or distribute
the Library except as expressly provided under this License. Any
attempt otherwise to copy, modify, sublicense, link with, or
distribute the Library is void, and will automatically terminate your
rights under this License. However, parties who have received copies,
or rights, from you under this License will not have their licenses
terminated so long as such parties remain in full compliance.
9. You are not required to accept this License, since you have not
signed it. However, nothing else grants you permission to modify or
distribute the Library or its derivative works. These actions are
prohibited by law if you do not accept this License. Therefore, by
modifying or distributing the Library (or any work based on the
Library), you indicate your acceptance of this License to do so, and
all its terms and conditions for copying, distributing or modifying
the Library or works based on it.
10. Each time you redistribute the Library (or any work based on the
Library), the recipient automatically receives a license from the
original licensor to copy, distribute, link with or modify the Library
subject to these terms and conditions. You may not impose any further
restrictions on the recipients' exercise of the rights granted herein.
You are not responsible for enforcing compliance by third parties with
this License.
11. If, as a consequence of a court judgment or allegation of patent
infringement or for any other reason (not limited to patent issues),
conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License. If you cannot
distribute so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you
may not distribute the Library at all. For example, if a patent
license would not permit royalty-free redistribution of the Library by
all those who receive copies directly or indirectly through you, then
the only way you could satisfy both it and this License would be to
refrain entirely from distribution of the Library.
If any portion of this section is held invalid or unenforceable under any
particular circumstance, the balance of the section is intended to apply,
and the section as a whole is intended to apply in other circumstances.
It is not the purpose of this section to induce you to infringe any
patents or other property right claims or to contest validity of any
such claims; this section has the sole purpose of protecting the
integrity of the free software distribution system which is
implemented by public license practices. Many people have made
generous contributions to the wide range of software distributed
through that system in reliance on consistent application of that
system; it is up to the author/donor to decide if he or she is willing
to distribute software through any other system and a licensee cannot
impose that choice.
This section is intended to make thoroughly clear what is believed to
be a consequence of the rest of this License.
12. If the distribution and/or use of the Library is restricted in
certain countries either by patents or by copyrighted interfaces, the
original copyright holder who places the Library under this License may add
an explicit geographical distribution limitation excluding those countries,
so that distribution is permitted only in or among countries not thus
excluded. In such case, this License incorporates the limitation as if
written in the body of this License.
13. The Free Software Foundation may publish revised and/or new
versions of the Lesser General Public License from time to time.
Such new versions will be similar in spirit to the present version,
but may differ in detail to address new problems or concerns.
Each version is given a distinguishing version number. If the Library
specifies a version number of this License which applies to it and
"any later version", you have the option of following the terms and
conditions either of that version or of any later version published by
the Free Software Foundation. If the Library does not specify a
license version number, you may choose any version ever published by
the Free Software Foundation.
14. If you wish to incorporate parts of the Library into other free
programs whose distribution conditions are incompatible with these,
write to the author to ask for permission. For software which is
copyrighted by the Free Software Foundation, write to the Free
Software Foundation; we sometimes make exceptions for this. Our
decision will be guided by the two goals of preserving the free status
of all derivatives of our free software and of promoting the sharing
and reuse of software generally.
NO WARRANTY
15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO
WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW.
EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR
OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY
KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE
LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME
THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN
WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY
AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU
FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR
CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE
LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING
RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A
FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF
SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH
DAMAGES.
END OF TERMS AND CONDITIONS

View File

@ -0,0 +1,44 @@
This software is licensed under the Apache 2.0 by the OpenSearch Contributors.
Election of Apache 2.0.
For the avoidance of doubt, the OpenSearch Contributors
(i) elect to use only the Apache License, Version 2.0 for all software where a
choice of Apache License, Version 2.0 and GNU Lesser General Public License, Version 2.1 is made available and
(ii) elect to use only the Apache License, Version 2.0 on modifications of the software by OpenSearch Contributors.
This software includes certain Elastic software that included the following in the NOTICE file
located here: https://github.com/elastic/elasticsearch-php/blob/master/NOTICE
Apache v2.0 Notice:
Copyright 2013-2014 Elasticsearch
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.
LGPL v2.1 Notice:
Copyright (C) 2013-2014 Elasticsearch
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA

View File

@ -0,0 +1 @@
<svg viewBox="0 0 372 72" height="64" fill="none" xmlns="http://www.w3.org/2000/svg"><style>.a{fill:#005EB8}.b{fill:#003B5C}@media(prefers-color-scheme:dark){.a{fill:#00A3E0}.b{fill:#B9D9EB}}</style><g class="a"><path d="M61.74 26.5a2.26 2.26 0 00-2.27 2.26 33.71 33.71 0 01-33.7 33.71 2.26 2.26 0 100 4.53A38.24 38.24 0 0064 28.76a2.26 2.26 0 00-2.26-2.26zM3.92 17A24.43 24.43 0 00.05 31.9c.86 13.73 13.3 24.14 25.03 23.02 4.6-.45 9.31-4.2 8.9-10.9-.19-2.92-1.62-4.64-3.93-5.96C27.84 36.8 25 36 21.79 35.1c-3.89-1.1-8.4-2.32-11.85-4.87-4.15-3.06-6.99-6.6-6.02-13.23z"/><path fill-rule="evenodd" clip-rule="evenodd" d="M107.78 48.26c3.15-4.49 4.72-10.92 4.72-19.28 0-8.36-1.56-14.77-4.69-19.24C104.7 5.24 100.2 3 94.34 3 88.4 3 83.87 5.23 80.72 9.7 77.57 14.15 76 20.55 76 28.91c0 8.43 1.57 14.9 4.72 19.39 3.15 4.47 7.67 6.7 13.55 6.7 5.86 0 10.36-2.25 13.5-6.74zm-19.94-6.11c-1.46-3.02-2.19-7.4-2.19-13.17 0-5.78.73-10.17 2.2-13.16 1.45-3.02 3.62-4.53 6.49-4.53 5.65 0 8.47 5.9 8.47 17.7 0 11.79-2.85 17.68-8.54 17.68-2.83 0-4.97-1.5-6.43-4.52zM128.2 54c1.28.74 2.66 1 4.31 1 3.53 0 6.4-1.67 8.44-5.24C142.98 46.2 144 41.28 144 35c0-6.36-.99-11.28-2.96-14.76-1.97-3.5-4.7-5.24-8.18-5.24-3.62 0-6.46 2.16-8.36 6h-.5l-1.5-5h-7v55.5h9V55c0-.65-.13-2.85-.5-6h.5a10.06 10.06 0 003.69 5zm-2.3-28.62c.8-1.69 2.09-2.53 3.88-2.53 1.67 0 2.9 1 3.68 2.98.8 2 1.2 5 1.2 9.03 0 8.2-1.6 12.3-4.81 12.3-1.86 0-3.19-.98-4-2.92-.8-1.95-1.2-5.05-1.2-9.3v-1.22c.05-3.9.46-6.67 1.24-8.34zM161.64 55c-4.84 0-8.67-1.7-11.48-5.13-2.78-3.44-4.17-8.3-4.17-14.58 0-6.37 1.26-11.34 3.8-14.92 2.52-3.58 6.04-5.37 10.56-5.37 4.23 0 7.55 1.54 9.99 4.6 2.43 3.05 3.65 7.34 3.65 12.85v5.05h-18.5c.07 3.44.67 5.88 2.01 7.56a6.77 6.77 0 005.57 2.5c3.01 0 6.1-.94 9.25-2.81v7.58c-2.97 1.78-6.53 2.67-10.68 2.67zm-1.35-32.9c-1.33 0-2.42.7-3.27 2.11-.86 1.39-1.4 3.86-1.53 6.79h9.5c-.05-2.82-.55-5.26-1.37-6.72-.8-1.45-1.92-2.18-3.33-2.18zm36.2 8.9v23h9V29.2c0-4.64-.88-8.17-2.63-10.58C201.14 16.2 198.52 15 195 15c-2.08 0-3.9.51-5.44 1.53-1.55 1-2.74 2.68-3.57 4.47h-.5l-1.25-5H177v38h9.5V35.75c0-4.71.17-7.92 1.1-9.9.92-1.99 2.37-2.98 4.36-2.98 1.5 0 2.59.71 3.25 2.15A13.12 13.12 0 01196.5 31z"/></g><g class="b"><path d="M48.08 41a24.43 24.43 0 003.87-14.9c-.86-13.73-13.3-24.14-25.03-23.02-4.6.45-9.31 4.2-8.9 10.9.19 2.92 1.62 4.64 3.93 5.96C24.16 21.2 27 22 30.21 22.9c3.89 1.1 8.4 2.32 11.85 4.87 4.15 3.06 6.99 6.6 6.02 13.23z"/><path fill-rule="evenodd" clip-rule="evenodd" d="M362.5 31v23h9V29c0-4.6-.9-8.09-2.7-10.45-1.8-2.38-4.52-3.55-8.05-3.55-3.83 0-6.9 2.24-8.75 6h-.5c.14-1.94.26-3.05.35-3.86.1-.84.15-1.36.15-2.14V.5h-9V54h9.5V35.5c0-4.15.14-7.22 1-9.42.85-2.22 2.34-3.33 4.46-3.33 2.84 0 4.54 2.63 4.54 8.25zM231.85 51.23c2.43-2.52 3.65-6.14 3.65-10.86 0-2.96-.67-5.59-2-7.9-1.3-2.3-3.63-4.56-6.98-6.77-2.48-1.62-4.22-3.06-5.23-4.33a7.08 7.08 0 01-1.47-4.46c0-1.73.41-3.1 1.23-4.08a4.39 4.39 0 013.58-1.53c1.4 0 2.7.26 3.93.76 1.23.51 2.41 1.09 3.54 1.73l3.15-7.54A21.76 21.76 0 00223.93 3c-4.12 0-7.4 1.27-9.86 3.8-2.43 2.54-3.65 5.98-3.65 10.32 0 2.26.3 4.24.91 5.95.63 1.7 1.51 3.25 2.63 4.63a24.91 24.91 0 005.02 4.3c2.53 1.7 4.34 3.26 5.44 4.66a7.22 7.22 0 011.65 4.6c0 1.71-.47 3.06-1.4 4.05-.92 1-2.29 1.49-4.11 1.49-3.2 0-6.72-1.23-10.56-3.7v9.3c3.13 1.74 6.93 2.6 11.4 2.6 4.56 0 8.04-1.26 10.45-3.77zm9.82-1.36c2.81 3.42 6.64 5.13 11.48 5.13 4.15 0 7.71-.89 10.68-2.67v-7.58c-3.15 1.87-6.24 2.8-9.25 2.8a6.77 6.77 0 01-5.57-2.49c-1.34-1.68-1.94-4.12-2.01-7.56h18.5v-5.05c0-5.51-1.22-9.8-3.65-12.84-2.44-3.07-5.76-4.61-9.99-4.61-4.52 0-8.04 1.79-10.57 5.37-2.53 3.58-3.79 8.55-3.79 14.92 0 6.28 1.4 11.14 4.17 14.58zm6.86-25.66c.85-1.4 1.94-2.11 3.27-2.11 1.41 0 2.52.73 3.33 2.18.82 1.46 1.32 3.9 1.37 6.72H247c.14-2.93.67-5.4 1.53-6.79zM288 54l-1.5-5h-.5c-1.38 2.26-2.7 3.87-4.18 4.72a10.99 10.99 0 01-5.57 1.28 8.17 8.17 0 01-6.8-3.18c-1.63-2.12-2.45-5.07-2.45-8.85 0-4.06 1.12-7.07 3.36-9.02 2.27-1.99 5.65-3.08 10.13-3.29l5.19-.2v-2.77c0-3.6-1.58-5.4-4.73-5.4-2.34 0-5.03.9-8.06 2.7l-3.23-6.36A23.23 23.23 0 01282.25 15c4.13 0 7.34 1.18 9.5 3.53 2.16 2.32 3.25 5.63 3.25 9.92V54h-7zm-7.93-6.2c1.7 0 3.06-.74 4.07-2.24 1.02-1.52 1.54-3.54 1.54-6.05v-3.25l-2.88.14c-2.12.12-3.69.71-4.7 1.8-.97 1.08-1.46 2.7-1.46 4.84 0 3.18 1.14 4.77 3.43 4.77zM318 15.76a14.31 14.31 0 00-3.78-.75 6.1 6.1 0 00-4.13 1.55A10.4 10.4 0 00307 21h-.5l-1.5-5h-7v38h9.46V34c0-3.36.22-5.52 1.4-7.27a5.76 5.76 0 015.09-2.66c1.02 0 1.91.2 2.55.43l1.5-8.75zM332 55c-4.56 0-8.05-1.52-10.43-4.87-2.38-3.35-3.57-8.27-3.57-14.75 0-6.8 1.12-11.86 3.37-15.2 2.26-3.35 5.65-5.18 10.37-5.18 1.41 0 3.01.36 4.57.78 1.56.41 3.45.94 4.69 1.72l-3.11 7.24c-1.9-1.13-3.58-1.7-5.05-1.7-1.95 0-3.35 1.04-4.23 3.09-.84 2.03-1.27 5.1-1.27 9.18 0 4 .43 6.98 1.27 8.97.85 1.96 2.24 2.94 4.16 2.94 2.3 0 4.68-.8 7.18-2.42v8.1c-2.4 1.5-5.04 2.1-7.95 2.1z"/></g></svg>

After

Width:  |  Height:  |  Size: 4.7 KiB

View File

@ -0,0 +1,49 @@
![OpenSearch logo](OpenSearch.svg)
- [Welcome!](#welcome)
- [Project Resources](#project-resources)
- [Code of Conduct](#code-of-conduct)
- [Sample code](#sample-code)
- [Compatibility with OpenSearch](#compatibility-with-opensearch)
- [License](#license)
- [Copyright](#copyright)
## Welcome!
**opensearch-php** is [a community-driven, open source fork](https://aws.amazon.com/blogs/opensource/introducing-opensearch/) of elasticsearch-php licensed under the [Apache v2.0 License](LICENSE). For more information, see [opensearch.org](https://opensearch.org/).
## Project Resources
* [Project Website](https://opensearch.org/)
* [User Guide](USER_GUIDE.md)
* [Samples](samples)
* [Developer Guide](DEVELOPER_GUIDE.md)
* [Downloads](https://opensearch.org/downloads/).
* [Documentation](https://docs.opensearch.org/latest/)
* Need help? Try [Forums](https://forum.opensearch.org/)
* [Project Principles](https://opensearch.org/#principles)
* [Contributing to OpenSearch](CONTRIBUTING.md)
* [Maintainer Responsibilities](MAINTAINERS.md)
* [Release Management](RELEASING.md)
* [Admin Responsibilities](ADMINS.md)
* [Security](SECURITY.md)
## Code of Conduct
This project has adopted the [Amazon Open Source Code of Conduct](CODE_OF_CONDUCT.md). For more information see the [Code of Conduct FAQ](https://aws.github.io/code-of-conduct-faq), or contact [opensource-codeofconduct@amazon.com](mailto:opensource-codeofconduct@amazon.com) with any additional questions or comments.
## Sample code
See [Sample Code](USER_GUIDE.md).
## Compatibility with OpenSearch
See [Compatibility](COMPATIBILITY.md).
## License
This project is licensed under the [Apache v2.0 License](LICENSE).
## Copyright
Copyright OpenSearch Contributors. See [NOTICE](NOTICE) for details.

View File

@ -0,0 +1,3 @@
## Reporting a Vulnerability
If you discover a potential security issue in this project we ask that you notify AWS/Amazon Security via our [vulnerability reporting page](https://aws.amazon.com/security/vulnerability-reporting/) or directly via email to aws-security@amazon.com. Please do **not** create a public GitHub issue.

View File

@ -0,0 +1,63 @@
- [Upgrading OpenSearch PHP Client](#upgrading-opensearch-php-client)
- [Upgrading to \>= 2.4.0](#upgrading-to--240)
- [PSR-18 HTTP Client Interfaces](#psr-18-http-client-interfaces)
- [Configuring Guzzle HTTP Client in 2.4.x](#configuring-guzzle-http-client-in-24x)
- [Configuring Symfony HTTP Client in 2.4.x](#configuring-symfony-http-client-in-24x)
# Upgrading OpenSearch PHP Client
## Upgrading to >= 2.4.0
The openseach-php library removes the hard-coded dependency on the [Guzzle HTTP client](https://docs.guzzlephp.org/en/stable/#) and switches to the following PSR interfaces:
- [PSR-7 HTTP message interfaces](https://www.php-fig.org/psr/psr-7/)
- [PSR-17 HTTP Factories](https://www.php-fig.org/psr/psr-17/)
- [PSR-18 HTTP Client](https://www.php-fig.org/psr/psr-18/)
You can continue to use Guzzle, but will need to configure it as a PSR-18 HTTP Client.
### PSR-18 HTTP Client Interfaces
Starting with opensearch-php 2.4.0 you can use any PSR-18 compatible HTTP client.
To simplify creating a Client, we provide two factories to create PSR-18 HTTP clients for Guzzle and Symfony HTTP clients since opensearch-php 2.5.0.
### Configuring Guzzle HTTP Client in 2.4.x
To configure Guzzle as a PSR HTTP Client with the similar configuration to opensearch-php 1.x you can use the following example:
Ensure the Guzzle packages are installed via composer:
```php
composer require guzzlehttp/guzzle
```
```php
$client = (new \OpenSearch\GuzzleClientFactory())->create([
'base_uri' => 'https://localhost:9200',
'auth' => ['admin', getenv('OPENSEARCH_PASSWORD')],
'verify' => false,
]);
// Send a request to the 'info' endpoint.
$info = $client->info();
```
### Configuring Symfony HTTP Client in 2.4.x
You can configure [Symfony HTTP Client](https://symfony.com/doc/current/http_client.html) as a PSR HTTP Client using the following example:
```php
composer require symfony/http-client
```
```php
$client = (new \OpenSearch\SymfonyClientFactory())->create([
'base_uri' => 'https://localhost:9200',
'auth_basic' => ['admin', getenv('OPENSEARCH_PASSWORD')],
'verify_peer' => false,
]);
// Send a request to the 'info' endpoint.
$info = $client->info();
```

View File

@ -0,0 +1,13 @@
#!/usr/bin/env php
<?php
require __DIR__.'/../vendor/autoload.php';
use OpenSearch\Util\Command\GenerateEndpointsCommand;
use OpenSearch\Util\Command\UpdateChangelogCommand;
use Symfony\Component\Console\Application;
$app = new Application();
$app->addCommand(new GenerateEndpointsCommand());
$app->addCommand(new UpdateChangelogCommand());
$app->run();

View File

@ -0,0 +1,114 @@
{
"name": "opensearch-project/opensearch-php",
"description": "PHP Client for OpenSearch",
"keywords": [
"search",
"client",
"opensearch",
"elasticsearch"
],
"type": "library",
"license": [
"Apache-2.0",
"LGPL-2.1-only"
],
"authors": [
{
"name": "Elastic"
},
{
"name": "OpenSearch Contributors"
}
],
"require": {
"php": "^8.2",
"ext-json": ">=1.3.7",
"ext-curl": "*",
"ezimuel/ringphp": "^1.4.0",
"php-http/discovery": "^1.20",
"psr/http-client": "^1.0.3",
"psr/http-client-implementation": "*",
"psr/http-factory": "^1.1.0",
"psr/http-factory-implementation": "*",
"psr/http-message": "^2.0",
"psr/http-message-implementation": "*",
"psr/log": "^3.0.2",
"symfony/yaml": "^v6.4.26|^7.3.5|^v8.0.0"
},
"require-dev": {
"ext-zip": "*",
"aws/aws-sdk-php": "^3.359.8",
"colinodell/psr-testlogger": "^1.3.1",
"friendsofphp/php-cs-fixer": "^v3.89.1",
"guzzlehttp/psr7": "^2.8.0",
"mockery/mockery": "^1.6.12",
"phpstan/extension-installer": "^1.4.3",
"phpstan/phpstan": "^1.12.32",
"phpstan/phpstan-deprecation-rules": "^1.2.1",
"phpstan/phpstan-mockery": "^1.1.3",
"phpstan/phpstan-phpunit": "^1.4.2",
"phpunit/phpunit": "^10.5.58",
"react/promise": "^v3.3",
"symfony/console": "v6.4.31|^7.4.3|^8.0.3",
"symfony/finder": "^v6.4.31|^7.4.3|^v8.0.3",
"symfony/http-client": "^6.4.31|^7.4.3|^v8.0.3",
"symfony/http-client-contracts": "^v3.6.0|^v8.0.3",
"twig/twig": "^3.0"
},
"suggest": {
"monolog/monolog": "Allows for client-level logging and tracing",
"aws/aws-sdk-php": "Required (^3.0.0) in order to use the AWS Signing Client Decorator",
"guzzlehttp/psr7": "Required (^2.7) in order to use the Guzzle HTTP client",
"symfony/http-client": "Required (^6.4|^7.0|^8.0) in order to use the Symfony HTTP client"
},
"autoload": {
"psr-4": {
"OpenSearch\\": "src/OpenSearch/"
}
},
"autoload-dev": {
"psr-4": {
"OpenSearch\\Tests\\": "tests/",
"OpenSearch\\Util\\": "util/"
}
},
"config": {
"sort-packages": true,
"allow-plugins": {
"php-http/discovery": true,
"phpstan/extension-installer": true
}
},
"scripts": {
"php-cs": [
"php-cs-fixer fix"
],
"phpstan": [
"phpstan analyse --no-progress"
],
"unit": [
"phpunit --exclude-group integration"
],
"integration": [
"phpunit --group integration"
],
"integration-min": [
"phpunit --group integration-min"
],
"phpunit": [
"phpunit"
],
"apigen": [
"rm -f apigen",
"curl -L https://github.com/ApiGen/ApiGen/releases/download/v7.0.0-alpha.4/apigen.phar -o apigen",
"chmod +x apigen",
"./apigen src --output docs",
"rm -f apigen"
],
"generate-api": [
"php bin/console app:generate-endpoints",
"composer run php-cs",
"php bin/console app:update-changelog"
]
}
}

View File

@ -0,0 +1,6 @@
exclude_path = ["vendor"]
accept = ["200","403","429"]
exclude = [
"https://localhost:9200",
"^mailto:",
]

View File

@ -0,0 +1,45 @@
<?php
namespace OpenSearch\Aws;
use Aws\Credentials\CredentialsInterface;
use Aws\Signature\SignatureInterface;
use Psr\Http\Client\ClientInterface;
use Psr\Http\Message\RequestInterface;
use Psr\Http\Message\ResponseInterface;
/**
* A decorator client that signs requests using the provided AWS credentials and signer.
*/
class SigningClientDecorator implements ClientInterface
{
/**
* @param ClientInterface $inner The client to decorate.
* @param CredentialsInterface $credentials The AWS credentials to use for signing requests.
* @param SignatureInterface $signer The AWS signer to use for signing requests.
* @param array $headers Additional headers to add to the request. `Host` is required.
* @return void
*/
public function __construct(
protected ClientInterface $inner,
protected CredentialsInterface $credentials,
protected SignatureInterface $signer,
protected array $headers = []
) {
}
public function sendRequest(RequestInterface $request): ResponseInterface
{
foreach ($this->headers as $name => $value) {
$request = $request->withHeader($name, $value);
}
if (empty($request->getHeaderLine('Host'))) {
throw new \RuntimeException('Missing Host header.');
}
$request = $request->withHeader('x-amz-content-sha256', hash('sha256', (string) $request->getBody()));
$request = $this->signer->signRequest($request, $this->credentials);
return $this->inner->sendRequest($request);
}
}

View File

@ -0,0 +1,115 @@
<?php
declare(strict_types=1);
namespace OpenSearch\Aws;
use Aws\Credentials\CredentialProvider;
use Aws\Credentials\Credentials;
use Aws\Exception\CredentialsException;
use Aws\Signature\SignatureInterface;
use Aws\Signature\SignatureV4;
use Psr\Http\Client\ClientInterface;
use Psr\Log\LoggerInterface;
/**
* A factory for creating an HTTP Client that signs requests using AWS credentials.
*/
class SigningClientFactory
{
/**
* The allowed AWS services.
*/
public const ALLOWED_SERVICES = ['es', 'aoss'];
public function __construct(
protected ?SignatureInterface $signer = null,
protected ?CredentialProvider $provider = null,
protected ?LoggerInterface $logger = null,
) {
}
/**
* Creates a new signing client.
*
* @param ClientInterface $innerClient
* The decorated inner HTTP client.
* @param array<string,string> $options
* The AWS auth options.
*/
public function create(ClientInterface $innerClient, array $options): ClientInterface
{
if (!isset($options['host'])) {
throw new \InvalidArgumentException('The host option is required.');
}
// Get the credentials.
$provider = $this->getCredentialProvider($options);
$promise = $provider();
try {
$credentials = $promise->wait();
} catch (CredentialsException $e) {
$this->logger?->error('Failed to get AWS credentials: @message', ['@message' => $e->getMessage()]);
$credentials = new Credentials('', '');
}
// Get the signer.
$signer = $this->getSigner($options);
return new SigningClientDecorator($innerClient, $credentials, $signer, ['host' => $options['host']]);
}
/**
* Gets the credential provider.
*
* @param array<string,mixed> $options
* The options array.
*/
protected function getCredentialProvider(array $options): CredentialProvider|\Closure|null|callable
{
// Check for a provided credential provider.
if ($this->provider) {
return $this->provider;
}
// Check for provided access key and secret.
if (isset($options['credentials'])) {
return CredentialProvider::fromCredentials(
new Credentials(
$options['credentials']['access_key'] ?? '',
$options['credentials']['secret_key'] ?? '',
$options['credentials']['session_token'] ?? null,
)
);
}
// Fallback to the default provider.
return CredentialProvider::defaultProvider();
}
/**
* Gets the request signer.
*
* @param array<string,string> $options
* The options.
*/
protected function getSigner(array $options): SignatureInterface
{
if ($this->signer) {
return $this->signer;
}
if (!isset($options['region'])) {
throw new \InvalidArgumentException('The region option is required.');
}
$service = $options['service'] ?? 'es';
if (!in_array($service, self::ALLOWED_SERVICES, true)) {
throw new \InvalidArgumentException('The service option must be either "es" or "aoss".');
}
return new SignatureV4($service, $options['region'], $options);
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,861 @@
<?php
declare(strict_types=1);
/**
* Copyright OpenSearch Contributors
* SPDX-License-Identifier: Apache-2.0
*
* OpenSearch PHP client
*
* @link https://github.com/opensearch-project/opensearch-php/
* @copyright Copyright (c) Elasticsearch B.V (https://www.elastic.co)
* @license http://www.apache.org/licenses/LICENSE-2.0 Apache License, Version 2.0
* @license https://www.gnu.org/licenses/lgpl-2.1.html GNU Lesser General Public License, Version 2.1
*
* Licensed to Elasticsearch B.V under one or more agreements.
* Elasticsearch B.V licenses this file to you under the Apache 2.0 License or
* the GNU Lesser General Public License, Version 2.1, at your option.
* See the LICENSE file in the project root for more information.
*/
namespace OpenSearch;
use Aws\Credentials\CredentialProvider;
use Aws\Credentials\Credentials;
use Aws\Credentials\CredentialsInterface;
use GuzzleHttp\Ring\Client\CurlHandler;
use GuzzleHttp\Ring\Client\CurlMultiHandler;
use GuzzleHttp\Ring\Client\Middleware;
use OpenSearch\Common\Exceptions\AuthenticationConfigException;
use OpenSearch\Common\Exceptions\InvalidArgumentException;
use OpenSearch\Common\Exceptions\RuntimeException;
use OpenSearch\ConnectionPool\AbstractConnectionPool;
use OpenSearch\ConnectionPool\Selectors\RoundRobinSelector;
use OpenSearch\ConnectionPool\Selectors\SelectorInterface;
use OpenSearch\ConnectionPool\StaticNoPingConnectionPool;
use OpenSearch\Connections\ConnectionFactory;
use OpenSearch\Connections\ConnectionFactoryInterface;
use OpenSearch\Connections\ConnectionInterface;
use OpenSearch\Handlers\SigV4Handler;
use OpenSearch\Namespaces\NamespaceBuilderInterface;
use OpenSearch\Serializers\SerializerInterface;
use OpenSearch\Serializers\SmartSerializer;
use Psr\Log\LoggerInterface;
use Psr\Log\NullLogger;
use ReflectionClass;
// @phpstan-ignore classConstant.deprecatedClass
@trigger_error(ClientBuilder::class . ' is deprecated in 2.4.0 and will be removed in 3.0.0.', E_USER_DEPRECATED);
/**
* @deprecated in 2.4.0 and will be removed in 3.0.0.
*/
class ClientBuilder
{
public const ALLOWED_METHODS_FROM_CONFIG = ['includePortInHostHeader'];
/**
* @var Transport|null
*/
private $transport;
private ?EndpointFactoryInterface $endpointFactory = null;
/**
* @var NamespaceBuilderInterface[]
*/
private $registeredNamespacesBuilders = [];
/**
* @var ConnectionFactoryInterface|null
*/
private $connectionFactory;
/**
* @var callable|null
*/
private $handler;
/**
* @var LoggerInterface|null
*/
private $logger;
/**
* @var LoggerInterface|null
*/
private $tracer;
/**
* @var string|AbstractConnectionPool
*/
private $connectionPool = StaticNoPingConnectionPool::class;
/**
* @var string|SerializerInterface|null
*/
private $serializer = SmartSerializer::class;
/**
* @var string|SelectorInterface|null
*/
private $selector = RoundRobinSelector::class;
/**
* @var array
*/
private $connectionPoolArgs = [
'randomizeHosts' => true
];
/**
* @var array|null
*/
private $hosts;
/**
* @var array
*/
private $connectionParams;
/**
* @var int|null
*/
private $retries;
/**
* @var null|callable
*/
private $sigV4CredentialProvider;
/**
* @var null|string
*/
private $sigV4Region;
/**
* @var null|string
*/
private $sigV4Service;
/**
* @var bool
*/
private $sniffOnStart = false;
/**
* @var null|array
*/
private $sslCert;
/**
* @var null|array
*/
private $sslKey;
/**
* @var null|bool|string
*/
private $sslVerification;
/**
* @var bool
*/
private $includePortInHostHeader = false;
/**
* @var string|null
*/
private $basicAuthentication = null;
/**
* Create an instance of ClientBuilder
*/
public static function create(): ClientBuilder
{
return new self();
}
/**
* Can supply first param to Client::__construct() when invoking manually or with dependency injection
*/
public function getTransport(): Transport
{
return $this->transport;
}
/**
* Can supply second param to Client::__construct() when invoking manually or with dependency injection
*
* @deprecated in 2.4.0 and will be removed in 3.0.0. Use \OpenSearch\ClientBuilder::getEndpointFactory() instead.
*/
public function getEndpoint(): callable
{
@trigger_error(__METHOD__ . '() is deprecated in 2.4.0 and will be removed in 3.0.0. Use \OpenSearch\ClientBuilder::getEndpointFactory() instead.', E_USER_DEPRECATED);
return fn ($c) => $this->endpointFactory->getEndpoint('OpenSearch\\Endpoints\\' . $c);
}
/**
* Can supply third param to Client::__construct() when invoking manually or with dependency injection
*
* @return NamespaceBuilderInterface[]
*/
public function getRegisteredNamespacesBuilders(): array
{
return $this->registeredNamespacesBuilders;
}
/**
* Build a new client from the provided config. Hash keys
* should correspond to the method name e.g. ['connectionPool']
* corresponds to setConnectionPool().
*
* Missing keys will use the default for that setting if applicable
*
* Unknown keys will throw an exception by default, but this can be silenced
* by setting `quiet` to true
*
* @param array $config
* @param bool $quiet False if unknown settings throw exception, true to silently
* ignore unknown settings
* @throws Common\Exceptions\RuntimeException
*/
public static function fromConfig(array $config, bool $quiet = false): Client
{
$builder = new self();
foreach ($config as $key => $value) {
$method = in_array($key, self::ALLOWED_METHODS_FROM_CONFIG, true) ? $key : "set$key";
$reflection = new ReflectionClass($builder);
if ($reflection->hasMethod($method)) {
$func = $reflection->getMethod($method);
if ($func->getNumberOfParameters() > 1) {
$builder->$method(...$value);
} else {
$builder->$method($value);
}
unset($config[$key]);
}
}
if ($quiet === false && count($config) > 0) {
$unknown = implode(array_keys($config));
throw new RuntimeException("Unknown parameters provided: $unknown");
}
return $builder->build();
}
/**
* Get the default handler
*
* @param array $multiParams
* @param array $singleParams
* @throws \RuntimeException
*/
public static function defaultHandler(array $multiParams = [], array $singleParams = []): callable
{
$future = null;
if (extension_loaded('curl')) {
$config = array_merge([ 'mh' => curl_multi_init() ], $multiParams);
if (function_exists('curl_reset')) {
$default = new CurlHandler($singleParams);
$future = new CurlMultiHandler($config);
} else {
$default = new CurlMultiHandler($config);
}
} else {
throw new \RuntimeException('OpenSearch-PHP requires cURL, or a custom HTTP handler.');
}
return $future ? Middleware::wrapFuture($default, $future) : $default;
}
/**
* Get the multi handler for async (CurlMultiHandler)
*
* @throws \RuntimeException
*/
public static function multiHandler(array $params = []): CurlMultiHandler
{
if (function_exists('curl_multi_init')) {
return new CurlMultiHandler(array_merge([ 'mh' => curl_multi_init() ], $params));
}
throw new \RuntimeException('CurlMulti handler requires cURL.');
}
/**
* Get the handler instance (CurlHandler)
*
* @throws \RuntimeException
*/
public static function singleHandler(): CurlHandler
{
if (function_exists('curl_reset')) {
return new CurlHandler();
}
throw new \RuntimeException('CurlSingle handler requires cURL.');
}
/**
* Set connection Factory
*
* @param ConnectionFactoryInterface $connectionFactory
*/
public function setConnectionFactory(ConnectionFactoryInterface $connectionFactory): ClientBuilder
{
$this->connectionFactory = $connectionFactory;
return $this;
}
/**
* Set the connection pool (default is StaticNoPingConnectionPool)
*
* @param AbstractConnectionPool|string $connectionPool
* @param array $args
* @throws \InvalidArgumentException
*/
public function setConnectionPool($connectionPool, array $args = []): ClientBuilder
{
if (is_string($connectionPool)) {
$this->connectionPool = $connectionPool;
$this->connectionPoolArgs = $args;
} elseif (is_object($connectionPool)) {
$this->connectionPool = $connectionPool;
} else {
throw new InvalidArgumentException("Serializer must be a class path or instantiated object extending AbstractConnectionPool");
}
return $this;
}
/**
* Set the endpoint
*
* @param callable $endpoint
*
* @deprecated in 2.4.0 and will be removed in 3.0.0. Use \OpenSearch\ClientBuilder::setEndpointFactory() instead.
*/
public function setEndpoint(callable $endpoint): ClientBuilder
{
@trigger_error(__METHOD__ . '() is deprecated in 2.4.0 and will be removed in 3.0.0. Use \OpenSearch\ClientBuilder::setEndpointFactory() instead.', E_USER_DEPRECATED);
$this->endpointFactory = new LegacyEndpointFactory($endpoint);
return $this;
}
public function setEndpointFactory(EndpointFactoryInterface $endpointFactory): ClientBuilder
{
$this->endpointFactory = $endpointFactory;
return $this;
}
/**
* Register namespace
*
* @param NamespaceBuilderInterface $namespaceBuilder
*/
public function registerNamespace(NamespaceBuilderInterface $namespaceBuilder): ClientBuilder
{
$this->registeredNamespacesBuilders[] = $namespaceBuilder;
return $this;
}
/**
* Set the transport
*
* @param Transport $transport
*/
public function setTransport(Transport $transport): ClientBuilder
{
$this->transport = $transport;
return $this;
}
/**
* Set the HTTP handler (cURL is default)
*
* @param mixed $handler
*/
public function setHandler($handler): ClientBuilder
{
$this->handler = $handler;
return $this;
}
/**
* Set the PSR-3 Logger
*
* @param LoggerInterface $logger
*/
public function setLogger(LoggerInterface $logger): ClientBuilder
{
$this->logger = $logger;
return $this;
}
/**
* Set the PSR-3 tracer
*
* @param LoggerInterface $tracer
*/
public function setTracer(LoggerInterface $tracer): ClientBuilder
{
$this->tracer = $tracer;
return $this;
}
/**
* Set the serializer
*
* @param \OpenSearch\Serializers\SerializerInterface|string $serializer
*/
public function setSerializer($serializer): ClientBuilder
{
$this->parseStringOrObject($serializer, $this->serializer, 'SerializerInterface');
return $this;
}
/**
* Set the hosts (nodes)
*
* @param array $hosts
*/
public function setHosts(array $hosts): ClientBuilder
{
$this->hosts = $hosts;
return $this;
}
/**
* Set Basic access authentication
*
* @see https://en.wikipedia.org/wiki/Basic_access_authentication
* @param string $username
* @param string $password
*
* @throws AuthenticationConfigException
*/
public function setBasicAuthentication(string $username, string $password): ClientBuilder
{
$this->basicAuthentication = $username.':'.$password;
return $this;
}
/**
* Set connection parameters
*
* @param array $params
*/
public function setConnectionParams(array $params): ClientBuilder
{
$this->connectionParams = $params;
return $this;
}
/**
* Set number or retries (default is equal to number of nodes)
*
* @param int $retries
*/
public function setRetries(int $retries): ClientBuilder
{
$this->retries = $retries;
return $this;
}
/**
* Set the selector algorithm
*
* @param \OpenSearch\ConnectionPool\Selectors\SelectorInterface|string $selector
*/
public function setSelector($selector): ClientBuilder
{
$this->parseStringOrObject($selector, $this->selector, 'SelectorInterface');
return $this;
}
/**
* Set the credential provider for SigV4 request signing. The value provider should be a
* callable object that will return
*
* @param callable|bool|array|CredentialsInterface|null $credentialProvider
*/
public function setSigV4CredentialProvider($credentialProvider): ClientBuilder
{
if ($credentialProvider !== null && $credentialProvider !== false) {
$this->sigV4CredentialProvider = $this->normalizeCredentialProvider($credentialProvider);
}
return $this;
}
/**
* Set the region for SigV4 signing.
*
* @param string|null $region
*/
public function setSigV4Region($region): ClientBuilder
{
$this->sigV4Region = $region;
return $this;
}
/**
* Set the service for SigV4 signing.
*
* @param string|null $service
*/
public function setSigV4Service($service): ClientBuilder
{
$this->sigV4Service = $service;
return $this;
}
/**
* Set sniff on start
*
* @param bool $sniffOnStart enable or disable sniff on start
*/
public function setSniffOnStart(bool $sniffOnStart): ClientBuilder
{
$this->sniffOnStart = $sniffOnStart;
return $this;
}
/**
* Set SSL certificate
*
* @param string $cert The name of a file containing a PEM formatted certificate.
* @param string $password if the certificate requires a password
*/
public function setSSLCert(string $cert, ?string $password = null): ClientBuilder
{
$this->sslCert = [$cert, $password];
return $this;
}
/**
* Set SSL key
*
* @param string $key The name of a file containing a private SSL key
* @param string $password if the private key requires a password
*/
public function setSSLKey(string $key, ?string $password = null): ClientBuilder
{
$this->sslKey = [$key, $password];
return $this;
}
/**
* Set SSL verification
*
* @param bool|string $value
*/
public function setSSLVerification($value = true): ClientBuilder
{
$this->sslVerification = $value;
return $this;
}
/**
* Include the port in Host header
*
* @see https://github.com/elastic/elasticsearch-php/issues/993
*/
public function includePortInHostHeader(bool $enable): ClientBuilder
{
$this->includePortInHostHeader = $enable;
return $this;
}
/**
* Build and returns the Client object
*/
public function build(): Client
{
$this->buildLoggers();
if (is_null($this->handler)) {
$this->handler = ClientBuilder::defaultHandler();
}
if (!is_null($this->sigV4CredentialProvider)) {
if (is_null($this->sigV4Region)) {
throw new RuntimeException("A region must be supplied for SigV4 request signing.");
}
if (is_null($this->sigV4Service)) {
$this->setSigV4Service("es");
}
$this->handler = new SigV4Handler($this->sigV4Region, $this->sigV4Service, $this->sigV4CredentialProvider, $this->handler);
}
$sslOptions = null;
if (isset($this->sslKey)) {
$sslOptions['ssl_key'] = $this->sslKey;
}
if (isset($this->sslCert)) {
$sslOptions['cert'] = $this->sslCert;
}
if (isset($this->sslVerification)) {
$sslOptions['verify'] = $this->sslVerification;
}
if (!is_null($sslOptions)) {
$sslHandler = function (callable $handler, array $sslOptions) {
return function (array $request) use ($handler, $sslOptions) {
// Add our custom headers
foreach ($sslOptions as $key => $value) {
$request['client'][$key] = $value;
}
// Send the request using the handler and return the response.
return $handler($request);
};
};
$this->handler = $sslHandler($this->handler, $sslOptions);
}
if (is_null($this->serializer)) {
$this->serializer = new SmartSerializer();
} elseif (is_string($this->serializer)) {
$this->serializer = new $this->serializer();
}
$this->connectionParams['client']['port_in_header'] = $this->includePortInHostHeader;
if (! is_null($this->basicAuthentication)) {
if (isset($this->connectionParams['client']['curl']) === false) {
$this->connectionParams['client']['curl'] = [];
}
$this->connectionParams['client']['curl'] += [
CURLOPT_HTTPAUTH => CURLAUTH_BASIC,
CURLOPT_USERPWD => $this->basicAuthentication
];
}
if (is_null($this->connectionFactory)) {
// Make sure we are setting Content-Type and Accept (unless the user has explicitly
// overridden it
if (! isset($this->connectionParams['client']['headers'])) {
$this->connectionParams['client']['headers'] = [];
}
if (! isset($this->connectionParams['client']['headers']['Content-Type'])) {
$this->connectionParams['client']['headers']['Content-Type'] = ['application/json'];
}
if (! isset($this->connectionParams['client']['headers']['Accept'])) {
$this->connectionParams['client']['headers']['Accept'] = ['application/json'];
}
$this->connectionFactory = new ConnectionFactory($this->handler, $this->connectionParams, $this->serializer, $this->logger, $this->tracer);
}
if (is_null($this->hosts)) {
$this->hosts = $this->getDefaultHost();
}
if (is_null($this->selector)) {
$this->selector = new RoundRobinSelector();
} elseif (is_string($this->selector)) {
$this->selector = new $this->selector();
}
$this->buildTransport();
if (is_null($this->endpointFactory)) {
$this->endpointFactory = new EndpointFactory($this->serializer);
}
$registeredNamespaces = [];
foreach ($this->registeredNamespacesBuilders as $builder) {
/**
* @var NamespaceBuilderInterface $builder
*/
$registeredNamespaces[$builder->getName()] = $builder->getObject($this->transport, $this->serializer);
}
return $this->instantiate($this->transport, $this->endpointFactory, $registeredNamespaces);
}
protected function instantiate(Transport $transport, EndpointFactoryInterface $endpointFactory, array $registeredNamespaces): Client
{
return new Client($transport, $endpointFactory, $registeredNamespaces);
}
private function buildLoggers(): void
{
if (is_null($this->logger)) {
$this->logger = new NullLogger();
}
if (is_null($this->tracer)) {
$this->tracer = new NullLogger();
}
}
private function buildTransport(): void
{
$connections = $this->buildConnectionsFromHosts($this->hosts);
if (is_string($this->connectionPool)) {
$this->connectionPool = new $this->connectionPool(
$connections,
$this->selector,
$this->connectionFactory,
$this->connectionPoolArgs
);
}
if (is_null($this->retries)) {
$this->retries = count($connections);
}
if (is_null($this->transport)) {
$this->transport = new Transport($this->retries, $this->connectionPool, $this->logger, $this->sniffOnStart);
}
}
private function parseStringOrObject($arg, &$destination, $interface): void
{
if (is_string($arg)) {
$destination = new $arg();
} elseif (is_object($arg)) {
$destination = $arg;
} else {
throw new InvalidArgumentException("Serializer must be a class path or instantiated object implementing $interface");
}
}
private function getDefaultHost(): array
{
return ['localhost:9200'];
}
/**
* @return ConnectionInterface[]
* @throws RuntimeException
*/
private function buildConnectionsFromHosts(array $hosts): array
{
$connections = [];
foreach ($hosts as $host) {
if (is_string($host)) {
$host = $this->prependMissingScheme($host);
$host = $this->extractURIParts($host);
} elseif (is_array($host)) {
$host = $this->normalizeExtendedHost($host);
} else {
$this->logger->error("Could not parse host: ".print_r($host, true));
throw new RuntimeException("Could not parse host: ".print_r($host, true));
}
$connections[] = $this->connectionFactory->create($host);
}
return $connections;
}
/**
* @throws RuntimeException
*/
private function normalizeExtendedHost(array $host): array
{
if (isset($host['host']) === false) {
$this->logger->error("Required 'host' was not defined in extended format: ".print_r($host, true));
throw new RuntimeException("Required 'host' was not defined in extended format: ".print_r($host, true));
}
if (isset($host['scheme']) === false) {
$host['scheme'] = 'http';
}
if (isset($host['port']) === false) {
$host['port'] = 9200;
}
return $host;
}
/**
* @throws InvalidArgumentException
*/
private function extractURIParts(string $host): array
{
$parts = parse_url($host);
if ($parts === false) {
throw new InvalidArgumentException(sprintf('Could not parse URI: "%s"', $host));
}
if (isset($parts['port']) !== true) {
$parts['port'] = 9200;
}
return $parts;
}
private function prependMissingScheme(string $host): string
{
if (!preg_match("/^https?:\/\//", $host)) {
$host = 'http://' . $host;
}
return $host;
}
private function normalizeCredentialProvider($provider): ?callable
{
if ($provider === null || $provider === false) {
return null;
}
if (is_callable($provider)) {
return $provider;
}
SigV4Handler::assertDependenciesInstalled();
if ($provider === true) {
return CredentialProvider::defaultProvider();
}
if ($provider instanceof CredentialsInterface) {
return CredentialProvider::fromCredentials($provider);
} elseif (is_array($provider) && isset($provider['key']) && isset($provider['secret'])) {
return CredentialProvider::fromCredentials(
new Credentials(
$provider['key'],
$provider['secret'],
isset($provider['token']) ? $provider['token'] : null,
isset($provider['expires']) ? $provider['expires'] : null
)
);
}
throw new InvalidArgumentException('Credentials must be an instance of Aws\Credentials\CredentialsInterface, an'
. ' associative array that contains "key", "secret", and an optional "token" key-value pairs, a credentials'
. ' provider function, or true.');
}
}

View File

@ -0,0 +1,17 @@
<?php
namespace OpenSearch;
/**
* Creates an OpenSearch client.
*/
interface ClientFactoryInterface
{
/**
* Creates a new OpenSearch client.
*
* @param array<string,mixed> $options
* The options to use when creating the client. The options are specific to the HTTP client implementation.
*/
public function create(array $options): Client;
}

View File

@ -0,0 +1,47 @@
<?php
declare(strict_types=1);
/**
* Copyright OpenSearch Contributors
* SPDX-License-Identifier: Apache-2.0
*
* OpenSearch PHP client
*
* @link https://github.com/opensearch-project/opensearch-php/
* @copyright Copyright (c) Elasticsearch B.V (https://www.elastic.co)
* @license http://www.apache.org/licenses/LICENSE-2.0 Apache License, Version 2.0
* @license https://www.gnu.org/licenses/lgpl-2.1.html GNU Lesser General Public License, Version 2.1
*
* Licensed to Elasticsearch B.V under one or more agreements.
* Elasticsearch B.V licenses this file to you under the Apache 2.0 License or
* the GNU Lesser General Public License, Version 2.1, at your option.
* See the LICENSE file in the project root for more information.
*/
namespace OpenSearch\Common;
use Psr\Log\AbstractLogger;
use Psr\Log\LoggerInterface;
// @phpstan-ignore classConstant.deprecatedClass
@trigger_error(EmptyLogger::class . ' is deprecated in 2.4.0 and will be removed in 3.0.0. Use Psr\Log\NullLogger instead', E_USER_DEPRECATED);
/**
* Class EmptyLogger
*
* Logger that doesn't do anything. Similar to Monolog's NullHandler,
* but avoids the overhead of partially loading Monolog
*
* @deprecated in 2.4.0 and will be removed in 3.0.0. Use Psr\Log\NullLogger instead.
*/
class EmptyLogger extends AbstractLogger implements LoggerInterface
{
/**
* {@inheritDoc}
*/
public function log($level, $message, array $context = []): void
{
return;
}
}

View File

@ -0,0 +1,32 @@
<?php
declare(strict_types=1);
/**
* Copyright OpenSearch Contributors
* SPDX-License-Identifier: Apache-2.0
*
* OpenSearch PHP client
*
* @link https://github.com/opensearch-project/opensearch-php/
* @copyright Copyright (c) Elasticsearch B.V (https://www.elastic.co)
* @license http://www.apache.org/licenses/LICENSE-2.0 Apache License, Version 2.0
* @license https://www.gnu.org/licenses/lgpl-2.1.html GNU Lesser General Public License, Version 2.1
*
* Licensed to Elasticsearch B.V under one or more agreements.
* Elasticsearch B.V licenses this file to you under the Apache 2.0 License or
* the GNU Lesser General Public License, Version 2.1, at your option.
* See the LICENSE file in the project root for more information.
*/
namespace OpenSearch\Common\Exceptions;
// @phpstan-ignore classConstant.deprecatedClass
@trigger_error(AuthenticationConfigException::class . ' is deprecated in 2.4.0 and will be removed in 3.0.0.', E_USER_DEPRECATED);
/**
* @deprecated in 2.4.0 and will be removed in 3.0.0.
*/
class AuthenticationConfigException extends \RuntimeException implements OpenSearchException
{
}

View File

@ -0,0 +1,36 @@
<?php
declare(strict_types=1);
/**
* Copyright OpenSearch Contributors
* SPDX-License-Identifier: Apache-2.0
*
* OpenSearch PHP client
*
* @link https://github.com/opensearch-project/opensearch-php/
* @copyright Copyright (c) Elasticsearch B.V (https://www.elastic.co)
* @license http://www.apache.org/licenses/LICENSE-2.0 Apache License, Version 2.0
* @license https://www.gnu.org/licenses/lgpl-2.1.html GNU Lesser General Public License, Version 2.1
*
* Licensed to Elasticsearch B.V under one or more agreements.
* Elasticsearch B.V licenses this file to you under the Apache 2.0 License or
* the GNU Lesser General Public License, Version 2.1, at your option.
* See the LICENSE file in the project root for more information.
*/
namespace OpenSearch\Common\Exceptions;
// @phpstan-ignore classConstant.deprecatedClass
@trigger_error(BadMethodCallException::class . ' is deprecated in 2.4.0 and will be removed in 3.0.0.', E_USER_DEPRECATED);
/**
* BadMethodCallException
*
* Denote problems with a method call (e.g. incorrect number of arguments)
*
* @deprecated in 2.4.0 and will be removed in 3.0.0.
*/
class BadMethodCallException extends \OpenSearch\Exception\BadMethodCallException
{
}

View File

@ -0,0 +1,32 @@
<?php
declare(strict_types=1);
/**
* Copyright OpenSearch Contributors
* SPDX-License-Identifier: Apache-2.0
*
* OpenSearch PHP client
*
* @link https://github.com/opensearch-project/opensearch-php/
* @copyright Copyright (c) Elasticsearch B.V (https://www.elastic.co)
* @license http://www.apache.org/licenses/LICENSE-2.0 Apache License, Version 2.0
* @license https://www.gnu.org/licenses/lgpl-2.1.html GNU Lesser General Public License, Version 2.1
*
* Licensed to Elasticsearch B.V under one or more agreements.
* Elasticsearch B.V licenses this file to you under the Apache 2.0 License or
* the GNU Lesser General Public License, Version 2.1, at your option.
* See the LICENSE file in the project root for more information.
*/
namespace OpenSearch\Common\Exceptions;
// @phpstan-ignore classConstant.deprecatedClass
@trigger_error(BadRequest400Exception::class . ' is deprecated in 2.4.0 and will be removed in 3.0.0.', E_USER_DEPRECATED);
/**
* @deprecated in 2.4.0 and will be removed in 3.0.0.
*/
class BadRequest400Exception extends \Exception implements OpenSearchException
{
}

View File

@ -0,0 +1,34 @@
<?php
declare(strict_types=1);
/**
* Copyright OpenSearch Contributors
* SPDX-License-Identifier: Apache-2.0
*
* OpenSearch PHP client
*
* @link https://github.com/opensearch-project/opensearch-php/
* @copyright Copyright (c) Elasticsearch B.V (https://www.elastic.co)
* @license http://www.apache.org/licenses/LICENSE-2.0 Apache License, Version 2.0
* @license https://www.gnu.org/licenses/lgpl-2.1.html GNU Lesser General Public License, Version 2.1
*
* Licensed to Elasticsearch B.V under one or more agreements.
* Elasticsearch B.V licenses this file to you under the Apache 2.0 License or
* the GNU Lesser General Public License, Version 2.1, at your option.
* See the LICENSE file in the project root for more information.
*/
namespace OpenSearch\Common\Exceptions;
// @phpstan-ignore classConstant.deprecatedClass
@trigger_error(ClientErrorResponseException::class . ' is deprecated in 2.4.0 and will be removed in 3.0.0.', E_USER_DEPRECATED);
/**
* @deprecated in 2.4.0 and will be removed in 3.0.0.
*
* @phpstan-ignore class.extendsDeprecatedClass
*/
class ClientErrorResponseException extends TransportException implements OpenSearchException
{
}

View File

@ -0,0 +1,32 @@
<?php
declare(strict_types=1);
/**
* Copyright OpenSearch Contributors
* SPDX-License-Identifier: Apache-2.0
*
* OpenSearch PHP client
*
* @link https://github.com/opensearch-project/opensearch-php/
* @copyright Copyright (c) Elasticsearch B.V (https://www.elastic.co)
* @license http://www.apache.org/licenses/LICENSE-2.0 Apache License, Version 2.0
* @license https://www.gnu.org/licenses/lgpl-2.1.html GNU Lesser General Public License, Version 2.1
*
* Licensed to Elasticsearch B.V under one or more agreements.
* Elasticsearch B.V licenses this file to you under the Apache 2.0 License or
* the GNU Lesser General Public License, Version 2.1, at your option.
* See the LICENSE file in the project root for more information.
*/
namespace OpenSearch\Common\Exceptions;
// @phpstan-ignore classConstant.deprecatedClass
@trigger_error(Conflict409Exception::class . ' is deprecated in 2.4.0 and will be removed in 3.0.0.', E_USER_DEPRECATED);
/**
* @deprecated in 2.4.0 and will be removed in 3.0.0.
*/
class Conflict409Exception extends \Exception implements OpenSearchException
{
}

View File

@ -0,0 +1,37 @@
<?php
declare(strict_types=1);
/**
* Copyright OpenSearch Contributors
* SPDX-License-Identifier: Apache-2.0
*
* OpenSearch PHP client
*
* @link https://github.com/opensearch-project/opensearch-php/
* @copyright Copyright (c) Elasticsearch B.V (https://www.elastic.co)
* @license http://www.apache.org/licenses/LICENSE-2.0 Apache License, Version 2.0
* @license https://www.gnu.org/licenses/lgpl-2.1.html GNU Lesser General Public License, Version 2.1
*
* Licensed to Elasticsearch B.V under one or more agreements.
* Elasticsearch B.V licenses this file to you under the Apache 2.0 License or
* the GNU Lesser General Public License, Version 2.1, at your option.
* See the LICENSE file in the project root for more information.
*/
namespace OpenSearch\Common\Exceptions\Curl;
use OpenSearch\Common\Exceptions\OpenSearchException;
use OpenSearch\Common\Exceptions\TransportException;
// @phpstan-ignore classConstant.deprecatedClass
@trigger_error(CouldNotConnectToHost::class . ' is deprecated in 2.4.0 and will be removed in 3.0.0.', E_USER_DEPRECATED);
/**
* @deprecated in 2.4.0 and will be removed in 3.0.0.
*
* @phpstan-ignore class.extendsDeprecatedClass
*/
class CouldNotConnectToHost extends TransportException implements OpenSearchException
{
}

View File

@ -0,0 +1,37 @@
<?php
declare(strict_types=1);
/**
* Copyright OpenSearch Contributors
* SPDX-License-Identifier: Apache-2.0
*
* OpenSearch PHP client
*
* @link https://github.com/opensearch-project/opensearch-php/
* @copyright Copyright (c) Elasticsearch B.V (https://www.elastic.co)
* @license http://www.apache.org/licenses/LICENSE-2.0 Apache License, Version 2.0
* @license https://www.gnu.org/licenses/lgpl-2.1.html GNU Lesser General Public License, Version 2.1
*
* Licensed to Elasticsearch B.V under one or more agreements.
* Elasticsearch B.V licenses this file to you under the Apache 2.0 License or
* the GNU Lesser General Public License, Version 2.1, at your option.
* See the LICENSE file in the project root for more information.
*/
namespace OpenSearch\Common\Exceptions\Curl;
use OpenSearch\Common\Exceptions\OpenSearchException;
use OpenSearch\Common\Exceptions\TransportException;
// @phpstan-ignore classConstant.deprecatedClass
@trigger_error(CouldNotResolveHostException::class . ' is deprecated in 2.4.0 and will be removed in 3.0.0.', E_USER_DEPRECATED);
/**
* @deprecated in 2.4.0 and will be removed in 3.0.0.
*
* @phpstan-ignore class.extendsDeprecatedClass
*/
class CouldNotResolveHostException extends TransportException implements OpenSearchException
{
}

View File

@ -0,0 +1,37 @@
<?php
declare(strict_types=1);
/**
* Copyright OpenSearch Contributors
* SPDX-License-Identifier: Apache-2.0
*
* OpenSearch PHP client
*
* @link https://github.com/opensearch-project/opensearch-php/
* @copyright Copyright (c) Elasticsearch B.V (https://www.elastic.co)
* @license http://www.apache.org/licenses/LICENSE-2.0 Apache License, Version 2.0
* @license https://www.gnu.org/licenses/lgpl-2.1.html GNU Lesser General Public License, Version 2.1
*
* Licensed to Elasticsearch B.V under one or more agreements.
* Elasticsearch B.V licenses this file to you under the Apache 2.0 License or
* the GNU Lesser General Public License, Version 2.1, at your option.
* See the LICENSE file in the project root for more information.
*/
namespace OpenSearch\Common\Exceptions\Curl;
use OpenSearch\Common\Exceptions\OpenSearchException;
use OpenSearch\Common\Exceptions\TransportException;
// @phpstan-ignore classConstant.deprecatedClass
@trigger_error(OperationTimeoutException::class . ' is deprecated in 2.4.0 and will be removed in 3.0.0.', E_USER_DEPRECATED);
/**
* @deprecated in 2.4.0 and will be removed in 3.0.0.
*
* @phpstan-ignore class.extendsDeprecatedClass
*/
class OperationTimeoutException extends TransportException implements OpenSearchException
{
}

View File

@ -0,0 +1,36 @@
<?php
declare(strict_types=1);
/**
* Copyright OpenSearch Contributors
* SPDX-License-Identifier: Apache-2.0
*
* OpenSearch PHP client
*
* @link https://github.com/opensearch-project/opensearch-php/
* @copyright Copyright (c) Elasticsearch B.V (https://www.elastic.co)
* @license http://www.apache.org/licenses/LICENSE-2.0 Apache License, Version 2.0
* @license https://www.gnu.org/licenses/lgpl-2.1.html GNU Lesser General Public License, Version 2.1
*
* Licensed to Elasticsearch B.V under one or more agreements.
* Elasticsearch B.V licenses this file to you under the Apache 2.0 License or
* the GNU Lesser General Public License, Version 2.1, at your option.
* See the LICENSE file in the project root for more information.
*/
namespace OpenSearch\Common\Exceptions;
use OpenSearch\Exception\ForbiddenHttpException;
// @phpstan-ignore classConstant.deprecatedClass
@trigger_error(Forbidden403Exception::class . ' is deprecated in 2.4.0 and will be removed in 3.0.0. Use \OpenSearch\Exception\ForbiddenHttpException instead', E_USER_DEPRECATED);
/**
* @deprecated in 2.4.0 and will be removed in 3.0.0.
*
* @see \OpenSearch\Exception\ForbiddenHttpException
*/
class Forbidden403Exception extends ForbiddenHttpException
{
}

View File

@ -0,0 +1,32 @@
<?php
declare(strict_types=1);
/**
* Copyright OpenSearch Contributors
* SPDX-License-Identifier: Apache-2.0
*
* OpenSearch PHP client
*
* @link https://github.com/opensearch-project/opensearch-php/
* @copyright Copyright (c) Elasticsearch B.V (https://www.elastic.co)
* @license http://www.apache.org/licenses/LICENSE-2.0 Apache License, Version 2.0
* @license https://www.gnu.org/licenses/lgpl-2.1.html GNU Lesser General Public License, Version 2.1
*
* Licensed to Elasticsearch B.V under one or more agreements.
* Elasticsearch B.V licenses this file to you under the Apache 2.0 License or
* the GNU Lesser General Public License, Version 2.1, at your option.
* See the LICENSE file in the project root for more information.
*/
namespace OpenSearch\Common\Exceptions;
// @phpstan-ignore classConstant.deprecatedClass
@trigger_error(InvalidArgumentException::class . ' is deprecated in 2.4.0 and will be removed in 3.0.0.', E_USER_DEPRECATED);
/**
* @deprecated in 2.4.0 and will be removed in 3.0.0.
*/
class InvalidArgumentException extends \InvalidArgumentException implements OpenSearchException
{
}

View File

@ -0,0 +1,34 @@
<?php
declare(strict_types=1);
/**
* Copyright OpenSearch Contributors
* SPDX-License-Identifier: Apache-2.0
*
* OpenSearch PHP client
*
* @link https://github.com/opensearch-project/opensearch-php/
* @copyright Copyright (c) Elasticsearch B.V (https://www.elastic.co)
* @license http://www.apache.org/licenses/LICENSE-2.0 Apache License, Version 2.0
* @license https://www.gnu.org/licenses/lgpl-2.1.html GNU Lesser General Public License, Version 2.1
*
* Licensed to Elasticsearch B.V under one or more agreements.
* Elasticsearch B.V licenses this file to you under the Apache 2.0 License or
* the GNU Lesser General Public License, Version 2.1, at your option.
* See the LICENSE file in the project root for more information.
*/
namespace OpenSearch\Common\Exceptions;
// @phpstan-ignore classConstant.deprecatedClass
@trigger_error(MaxRetriesException::class . ' is deprecated in 2.4.0 and will be removed in 3.0.0.', E_USER_DEPRECATED);
/**
* @deprecated in 2.4.0 and will be removed in 3.0.0.
*
* @phpstan-ignore class.extendsDeprecatedClass
*/
class MaxRetriesException extends TransportException implements OpenSearchException
{
}

View File

@ -0,0 +1,39 @@
<?php
declare(strict_types=1);
/**
* Copyright OpenSearch Contributors
* SPDX-License-Identifier: Apache-2.0
*
* OpenSearch PHP client
*
* @link https://github.com/opensearch-project/opensearch-php/
* @copyright Copyright (c) Elasticsearch B.V (https://www.elastic.co)
* @license http://www.apache.org/licenses/LICENSE-2.0 Apache License, Version 2.0
* @license https://www.gnu.org/licenses/lgpl-2.1.html GNU Lesser General Public License, Version 2.1
*
* Licensed to Elasticsearch B.V under one or more agreements.
* Elasticsearch B.V licenses this file to you under the Apache 2.0 License or
* the GNU Lesser General Public License, Version 2.1, at your option.
* See the LICENSE file in the project root for more information.
*/
namespace OpenSearch\Common\Exceptions;
use OpenSearch\Exception\NotFoundHttpException;
@trigger_error(
// @phpstan-ignore classConstant.deprecatedClass
Missing404Exception::class . ' is deprecated in 2.4.0 and will be removed in 3.0.0. Use \OpenSearch\Exception\NotFoundHttpException instead.',
E_USER_DEPRECATED
);
/**
* @deprecated in 2.4.0 and will be removed in 3.0.0.
*
* @see \OpenSearch\Exception\NotFoundHttpException
*/
class Missing404Exception extends NotFoundHttpException
{
}

View File

@ -0,0 +1,37 @@
<?php
declare(strict_types=1);
/**
* Copyright OpenSearch Contributors
* SPDX-License-Identifier: Apache-2.0
*
* OpenSearch PHP client
*
* @link https://github.com/opensearch-project/opensearch-php/
* @copyright Copyright (c) Elasticsearch B.V (https://www.elastic.co)
* @license http://www.apache.org/licenses/LICENSE-2.0 Apache License, Version 2.0
* @license https://www.gnu.org/licenses/lgpl-2.1.html GNU Lesser General Public License, Version 2.1
*
* Licensed to Elasticsearch B.V under one or more agreements.
* Elasticsearch B.V licenses this file to you under the Apache 2.0 License or
* the GNU Lesser General Public License, Version 2.1, at your option.
* See the LICENSE file in the project root for more information.
*/
namespace OpenSearch\Common\Exceptions;
@trigger_error(
// @phpstan-ignore classConstant.deprecatedClass
NoDocumentsToGetException::class . ' is deprecated in 2.4.0 and will be removed in 3.0.0. Use \OpenSearch\Exception\NoDocumentsToGetException instead.',
E_USER_DEPRECATED
);
/**
* @deprecated in 2.4.0 and will be removed in 3.0.0. Use \OpenSearch\Exception\NoDocumentsToGetException instead.
*
* @see \OpenSearch\Exception\ScriptLangNotSupportedException
*/
class NoDocumentsToGetException extends \OpenSearch\Exception\NoDocumentsToGetException
{
}

View File

@ -0,0 +1,34 @@
<?php
declare(strict_types=1);
/**
* Copyright OpenSearch Contributors
* SPDX-License-Identifier: Apache-2.0
*
* OpenSearch PHP client
*
* @link https://github.com/opensearch-project/opensearch-php/
* @copyright Copyright (c) Elasticsearch B.V (https://www.elastic.co)
* @license http://www.apache.org/licenses/LICENSE-2.0 Apache License, Version 2.0
* @license https://www.gnu.org/licenses/lgpl-2.1.html GNU Lesser General Public License, Version 2.1
*
* Licensed to Elasticsearch B.V under one or more agreements.
* Elasticsearch B.V licenses this file to you under the Apache 2.0 License or
* the GNU Lesser General Public License, Version 2.1, at your option.
* See the LICENSE file in the project root for more information.
*/
namespace OpenSearch\Common\Exceptions;
// @phpstan-ignore classConstant.deprecatedClass
@trigger_error(NoNodesAvailableException::class . ' is deprecated in 2.4.0 and will be removed in 3.0.0.', E_USER_DEPRECATED);
/**
* @deprecated in 2.4.0 and will be removed in 3.0.0.
*
* @phpstan-ignore class.extendsDeprecatedClass
*/
class NoNodesAvailableException extends ServerErrorResponseException implements OpenSearchException
{
}

View File

@ -0,0 +1,34 @@
<?php
declare(strict_types=1);
/**
* Copyright OpenSearch Contributors
* SPDX-License-Identifier: Apache-2.0
*
* OpenSearch PHP client
*
* @link https://github.com/opensearch-project/opensearch-php/
* @copyright Copyright (c) Elasticsearch B.V (https://www.elastic.co)
* @license http://www.apache.org/licenses/LICENSE-2.0 Apache License, Version 2.0
* @license https://www.gnu.org/licenses/lgpl-2.1.html GNU Lesser General Public License, Version 2.1
*
* Licensed to Elasticsearch B.V under one or more agreements.
* Elasticsearch B.V licenses this file to you under the Apache 2.0 License or
* the GNU Lesser General Public License, Version 2.1, at your option.
* See the LICENSE file in the project root for more information.
*/
namespace OpenSearch\Common\Exceptions;
// @phpstan-ignore classConstant.deprecatedClass
@trigger_error(NoShardAvailableException::class . ' is deprecated in 2.4.0 and will be removed in 3.0.0. Use \OpenSearch\Exception\NoShardAvailableException instead.', E_USER_DEPRECATED);
/**
* @deprecated in 2.4.0 and will be removed in 3.0.0. Use \OpenSearch\Exception\NoShardAvailableException instead.
*
* @see \OpenSearch\Exception\NoShardAvailableException
*/
class NoShardAvailableException extends \OpenSearch\Exception\NoShardAvailableException
{
}

View File

@ -0,0 +1,34 @@
<?php
declare(strict_types=1);
/**
* Copyright OpenSearch Contributors
* SPDX-License-Identifier: Apache-2.0
*
* OpenSearch PHP client
*
* @link https://github.com/opensearch-project/opensearch-php/
* @copyright Copyright (c) Elasticsearch B.V (https://www.elastic.co)
* @license http://www.apache.org/licenses/LICENSE-2.0 Apache License, Version 2.0
* @license https://www.gnu.org/licenses/lgpl-2.1.html GNU Lesser General Public License, Version 2.1
*
* Licensed to Elasticsearch B.V under one or more agreements.
* Elasticsearch B.V licenses this file to you under the Apache 2.0 License or
* the GNU Lesser General Public License, Version 2.1, at your option.
* See the LICENSE file in the project root for more information.
*/
namespace OpenSearch\Common\Exceptions;
use OpenSearch\Exception\OpenSearchExceptionInterface;
// @phpstan-ignore classConstant.deprecatedClass
@trigger_error(NoNodesAvailableException::class . ' is deprecated in 2.4.0 and will be removed in 3.0.0.', E_USER_DEPRECATED);
/**
* @deprecated in 2.4.0 and will be removed in 3.0.0.
*/
interface OpenSearchException extends OpenSearchExceptionInterface
{
}

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