From 936f79c962c3953ffc0ba7a475c1c8de3171a0c4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E0=B2=A0=5F=E0=B2=A0?= Date: Wed, 20 Nov 2024 21:13:10 +0100 Subject: [PATCH 1/7] implment psr7/uri --- _/HttpClient/CurlHttpClient.php | 94 +++ _/HttpClient/DumpingHttpClient.php | 44 ++ _/HttpClient/HttpClientInterface.php | 20 + _/HttpClient/HttpRequest.php | 18 + _/HttpClient/HttpRequestException.php | 9 + _/HttpClient/HttpRequestRetryException.php | 7 + _/HttpClient/HttpRequestTimeoutException.php | 7 + _/HttpClient/HttpResponse.php | 29 + _/HttpClient/MockHttpClient.php | 55 ++ _/HttpClient/Psr7/Uri.php | 323 +++++++++ _/HttpClient/RetryableHttpClient.php | 78 +++ _/HttpClient/UriParser.php | 119 ++++ composer.json | 15 +- composer.lock | 666 ++++++++++++------- phpunit.xml | 3 + tests/Psr7/UriTest.php | 35 + tests/Unit/HttpClient/UriParserTest.php | 154 +++++ 17 files changed, 1441 insertions(+), 235 deletions(-) create mode 100644 _/HttpClient/CurlHttpClient.php create mode 100644 _/HttpClient/DumpingHttpClient.php create mode 100644 _/HttpClient/HttpClientInterface.php create mode 100644 _/HttpClient/HttpRequest.php create mode 100644 _/HttpClient/HttpRequestException.php create mode 100644 _/HttpClient/HttpRequestRetryException.php create mode 100644 _/HttpClient/HttpRequestTimeoutException.php create mode 100644 _/HttpClient/HttpResponse.php create mode 100644 _/HttpClient/MockHttpClient.php create mode 100644 _/HttpClient/Psr7/Uri.php create mode 100644 _/HttpClient/RetryableHttpClient.php create mode 100644 _/HttpClient/UriParser.php create mode 100644 tests/Psr7/UriTest.php create mode 100644 tests/Unit/HttpClient/UriParserTest.php diff --git a/_/HttpClient/CurlHttpClient.php b/_/HttpClient/CurlHttpClient.php new file mode 100644 index 0000000..e132bbd --- /dev/null +++ b/_/HttpClient/CurlHttpClient.php @@ -0,0 +1,94 @@ +client->request($uri, $method, $header, $payload); + + ++$this->requestNumber; + + \mkdir("{$this->path}/{$this->requestNumber}", recursive: true); + + \file_put_contents("{$this->path}/{$this->requestNumber}/request.json", \json_encode([ + 'method' => $method, + 'path' => $uri, + 'header' => $header, + 'payload' => $payload, + ], JSON_THROW_ON_ERROR)); + + \file_put_contents("{$this->path}/{$this->requestNumber}/payload.json", $response->response); + \file_put_contents("{$this->path}/{$this->requestNumber}/header.json", \json_encode($response->header, \JSON_THROW_ON_ERROR)); + \file_put_contents("{$this->path}/{$this->requestNumber}/statuscode.json", $response->statusCode); + + return $response; + } +} diff --git a/_/HttpClient/HttpClientInterface.php b/_/HttpClient/HttpClientInterface.php new file mode 100644 index 0000000..4192455 --- /dev/null +++ b/_/HttpClient/HttpClientInterface.php @@ -0,0 +1,20 @@ + $header + * + * @throws HttpRequestTimeoutException + */ + public function request( + string $uri, + string $method = 'get', + array $header = [], + ?string $payload = null, + ): HttpResponse; +} diff --git a/_/HttpClient/HttpRequest.php b/_/HttpClient/HttpRequest.php new file mode 100644 index 0000000..201fe6a --- /dev/null +++ b/_/HttpClient/HttpRequest.php @@ -0,0 +1,18 @@ + $header + */ + public function __construct( + public string $uri, + public string $method = 'get', + public array $header = [], + public ?string $payload = null, + ) {} +} diff --git a/_/HttpClient/HttpRequestException.php b/_/HttpClient/HttpRequestException.php new file mode 100644 index 0000000..d68c4e5 --- /dev/null +++ b/_/HttpClient/HttpRequestException.php @@ -0,0 +1,9 @@ + $header + */ + public function __construct( + public int $statusCode, + public string $response = '', + public array $header = [], + ) {} + + /** + * @return array + */ + public function __debugInfo(): array + { + return [ + 'statusCode' => $this->statusCode, + 'header' => \json_encode($this->header, JSON_THROW_ON_ERROR), + 'response' => $this->response, + ]; + } +} diff --git a/_/HttpClient/MockHttpClient.php b/_/HttpClient/MockHttpClient.php new file mode 100644 index 0000000..0c1eb88 --- /dev/null +++ b/_/HttpClient/MockHttpClient.php @@ -0,0 +1,55 @@ +responses = $responses; + } + + public function addResponse(HttpResponse ... $responses): static + { + foreach ($responses as $response) { + $this->responses[] = $response; + } + return $this; + } + + /** + * @return HttpRequest[] + */ + public function getRequests(): array + { + return $this->requests; + } + + #[Override] + public function request( + string $uri, + string $method = 'get', + array $header = [], + ?string $payload = null, + ): HttpResponse { + $this->requests[] = new HttpRequest($uri, $method, $header, $payload); + + if (count($this->responses) === 0) { + throw new RuntimeException("no responses left for serving: {$method} {$uri}"); + } + + return \array_shift($this->responses); + } +} diff --git a/_/HttpClient/Psr7/Uri.php b/_/HttpClient/Psr7/Uri.php new file mode 100644 index 0000000..8516654 --- /dev/null +++ b/_/HttpClient/Psr7/Uri.php @@ -0,0 +1,323 @@ +parse(); + } + + private function parse(): void + { + $parser = UriParser::parser(); + $state = $parser->run(new ParserInput($this->uri)); + + if ($state->isError()) { + throw new InvalidArgumentException($state->getError() ?? 'unknown error'); + } + + $results = $state->getResult(); + + assert(\is_array($results)); + $assertString = function (mixed $input): string { + assert(is_string($input)); + return $input; + }; + + $assertInt = function (mixed $input): ?int { + assert(\is_int($input) || \is_null($input)); + return $input; + }; + + $this->scheme = $assertString($results[0]); + $this->userInfo = $assertString($results[1]); + $this->host = $assertString($results[2]); + $this->port = $assertInt($results[3]); + $this->path = $assertString($results[4]); + $this->query = $assertString($results[5]); + $this->fragment = $assertString($results[6]); + + // fix path + $this->path = \str_replace(' ', '%20', $this->path); + + $this->authority = ''; + + if ($this->userInfo !== '') { + $this->authority .= $this->userInfo . '@'; + } + + if ($this->host !== '') { + $this->authority .= $this->host; + } + + $known = [ + ['http', 80], + ['https', 443], + ]; + + if (\in_array([$this->scheme, $this->port], $known, true)) { + $this->port = null; + } + + if ($this->port !== null) { + $this->authority .= ':' . ((string) $this->port); + } + } + + #[Override] + public function getScheme(): string + { + return $this->scheme; + } + + #[Override] + public function getAuthority(): string + { + return $this->authority; + } + + #[Override] + public function getUserInfo(): string + { + return $this->userInfo; + } + + #[Override] + public function getHost(): string + { + return $this->host; + } + + #[Override] + public function getPort(): ?int + { + return $this->port; + } + + #[Override] + public function getPath(): string + { + return $this->path; + } + + #[Override] + public function getQuery(): string + { + return $this->query; + } + + #[Override] + public function getFragment(): string + { + return $this->fragment; + } + + #[Override] + public function withScheme(string $scheme): UriInterface + { + return new self( + $this->print( + $scheme, + $this->authority, + $this->userInfo, + $this->host, + $this->port, + $this->path, + $this->query, + $this->fragment, + ), + ); + } + + #[Override] + public function withUserInfo(string $user, ?string $password = null): UriInterface + { + if (\preg_match('/[^a-zA-Z0-9%\-_\.]/', $user) === 1) { + $user = \urlencode($user); + } + + if ($password !== null && \preg_match('/[^a-zA-Z0-9%\-_\.]/', $password) === 1) { + $password = \urlencode($password); + } + + $userinfo = $user; + + if ($password !== null) { + $userinfo .= ':' . $password; + } + + return new self( + $this->print( + $this->scheme, + $this->authority, + $userinfo, + $this->host, + $this->port, + $this->path, + $this->query, + $this->fragment, + ), + ); + } + + #[Override] + public function withHost(string $host): UriInterface + { + return new self( + $this->print( + $this->scheme, + $this->authority, + $this->userInfo, + $host, + $this->port, + $this->path, + $this->query, + $this->fragment, + ), + ); + } + + #[Override] + public function withPort(?int $port): UriInterface + { + return new self( + $this->print( + $this->scheme, + $this->authority, + $this->userInfo, + $this->host, + $port, + $this->path, + $this->query, + $this->fragment, + ), + ); + } + + #[Override] + public function withPath(string $path): UriInterface + { + $path = \str_replace(' ', '%20', $path); + + return new self( + $this->print( + $this->scheme, + $this->authority, + $this->userInfo, + $this->host, + $this->port, + $path, + $this->query, + $this->fragment, + ), + ); + } + + #[Override] + public function withQuery(string $query): UriInterface + { + return new self( + $this->print( + $this->scheme, + $this->authority, + $this->userInfo, + $this->host, + $this->port, + $this->path, + $query, + $this->fragment, + ), + ); + } + + #[Override] + public function withFragment(string $fragment): UriInterface + { + return new self( + $this->print( + $this->scheme, + $this->authority, + $this->userInfo, + $this->host, + $this->port, + $this->path, + $this->query, + $fragment, + ), + ); + } + + private function print( + string $scheme, + string $authority, + string $userInfo, + string $host, + ?int $port, + string $path, + string $query, + string $fragment, + ): string { + $uri = ''; + + if ($scheme !== '') { + $uri .= $scheme . '://'; + } + + if ($authority !== '') { + throw new RuntimeException('not implemented'); + } + + if ($userInfo !== '') { + $uri .= $userInfo . '@'; + } + + if ($host !== '') { + $uri .= $host; + } + + if ($port !== null) { + $uri .= ':' . $port; + } + + if ($path !== '') { + $uri .= $path; + } + + if ($query !== '') { + $uri .= '?' . $query; + } + + if ($fragment !== '') { + $uri .= '#' . $fragment; + } + + return $uri; + } + + #[Override] + public function __toString(): string + { + return $this->uri; + } +} diff --git a/_/HttpClient/RetryableHttpClient.php b/_/HttpClient/RetryableHttpClient.php new file mode 100644 index 0000000..28c22c4 --- /dev/null +++ b/_/HttpClient/RetryableHttpClient.php @@ -0,0 +1,78 @@ +retriedStatusCodes = $statusCodes; + return $this; + } + + /** + * @param Closure(): void $closure + * + * @return $this + */ + public function retryCallable(Closure $closure): static + { + $this->callback = $closure; + return $this; + } + + #[Override] + public function request( + string $uri, + string $method = 'get', + array $header = [], + ?string $payload = null, + ): HttpResponse { + $counter = 0; + + request: + try { + $response = $this->client->request($uri, $method, $header, $payload); + + if (\in_array($response->statusCode, $this->retriedStatusCodes, true)) { + throw new HttpRequestRetryException("unexpected code http code: {$response->statusCode}, retrying \n {$response->response}"); + } + + return $response; + } catch (HttpRequestTimeoutException|HttpRequestRetryException $e) { + if (++$counter >= $this->maxRetries) { + throw new HttpRequestRetryException("failed after {$this->maxRetries} retries: {$e->getMessage()}"); + } + \usleep((int) ($counter * $this->retryIntervalSeconds * 1_000_000)); + + if (isset($this->callback)) { + ($this->callback)(); + } + + goto request; + } + + throw new RuntimeException('unreacheable'); + } +} diff --git a/_/HttpClient/UriParser.php b/_/HttpClient/UriParser.php new file mode 100644 index 0000000..d73d7c6 --- /dev/null +++ b/_/HttpClient/UriParser.php @@ -0,0 +1,119 @@ +map(fn (?array $result) => $result[0] ?? ''); + } + + public static function userinfo(): Parser + { + return optional( + sequenceOf( + regexp('[^:@]+'), + optional( + sequenceOf( + char(':'), + regexp('[^:@]+'), + )->map(fn (array $result) => \implode('', $result)), + ), + char('@')->map(fn () => ''), + ), + )->map(fn (?array $result, ParserState $state) => \is_array($result) ? \implode('', $result) : ''); + } + + public static function host(): Parser + { + return optional(regexp('[a-zA-Z0-9\-\.]+'))->map(fn (?string $result) => \strtolower($result ?? '')); + } + + public static function port(): Parser + { + return optional( + char(':'), + )->chain(function ($result) { + + if ($result !== ':') { + return succeed(null); + } + + return numbers(); + })->map(function (?string $result): ?int { + + if ($result === null) { + return null; + } + + return (int) $result; + }); + } + + public static function path(): Parser + { + return optional( + sequenceOf( + manyOne( + char('/'), + )->map(fn (mixed $result) => '/'), + regexp('[^\?#]*'), + )->map(fn (?array $result) => \implode('', $result)), + )->map(fn (?string $result) => ($result ?? '')); + } + + public static function query(): Parser + { + return optional( + sequenceOf( + char('?'), + regexp('[^#]+'), + ), + )->map(fn (?array $result) => ($result[1] ?? '')); + } + + public static function fragment(): Parser + { + return optional( + sequenceOf( + char('#'), + regexp('.*'), + ), + )->map(fn (?array $result) => ($result[1] ?? '')); + } +} diff --git a/composer.json b/composer.json index 4816e57..6959e8a 100644 --- a/composer.json +++ b/composer.json @@ -5,21 +5,28 @@ "require": { "php" : "^8.3", "ext-pdo": "*", - "psr/clock": "^1.0" + "psr/clock": "^1.0", + "psr/http-factory": "^1.1", + "psr/http-message": "^2.0", + "ext-curl": "*", + "verfriemelt/pp": "^1.0" }, "require-dev": { "phpstan/phpstan": "^1.12.11", "phpstan/phpstan-strict-rules": "^1.6.1", "phpstan/phpstan-phpunit": "^1.4.1", - "phpunit/phpunit": "^11.4.3", + "phpunit/phpunit": "^10", "rector/rector": "^1.2.10", "friendsofphp/php-cs-fixer": "^3.64.0", "tomasvotruba/type-coverage": "^1.0", "tomasvotruba/cognitive-complexity": "^0.2.3", - "infection/infection": "^0.29.8" + "infection/infection": "^0.29.8", + "php-http/psr7-integration-tests": "^1.4" }, "provide": { - "psr/clock-implementation": "1.0" + "psr/clock-implementation": "1.0", + "psr/http-factory-implementation": "1.0", + "psr/http-message-implementation": "1.0" }, "autoload": { "psr-4": { diff --git a/composer.lock b/composer.lock index b8096da..7d83788 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "8398476628d32238665d5d7ca475e472", + "content-hash": "e4d318f64c3f82a1fa680ff80ae91af0", "packages": [ { "name": "psr/clock", @@ -53,6 +53,162 @@ "source": "https://github.com/php-fig/clock/tree/1.0.0" }, "time": "2022-11-25T14:36:26+00:00" + }, + { + "name": "psr/http-factory", + "version": "1.1.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-factory.git", + "reference": "2b4765fddfe3b508ac62f829e852b1501d3f6e8a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-factory/zipball/2b4765fddfe3b508ac62f829e852b1501d3f6e8a", + "reference": "2b4765fddfe3b508ac62f829e852b1501d3f6e8a", + "shasum": "" + }, + "require": { + "php": ">=7.1", + "psr/http-message": "^1.0 || ^2.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Message\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "PSR-17: Common interfaces for PSR-7 HTTP message factories", + "keywords": [ + "factory", + "http", + "message", + "psr", + "psr-17", + "psr-7", + "request", + "response" + ], + "support": { + "source": "https://github.com/php-fig/http-factory" + }, + "time": "2024-04-15T12:06:14+00:00" + }, + { + "name": "psr/http-message", + "version": "2.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-message.git", + "reference": "402d35bcb92c70c026d1a6a9883f06b2ead23d71" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-message/zipball/402d35bcb92c70c026d1a6a9883f06b2ead23d71", + "reference": "402d35bcb92c70c026d1a6a9883f06b2ead23d71", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Message\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interface for HTTP messages", + "homepage": "https://github.com/php-fig/http-message", + "keywords": [ + "http", + "http-message", + "psr", + "psr-7", + "request", + "response" + ], + "support": { + "source": "https://github.com/php-fig/http-message/tree/2.0" + }, + "time": "2023-04-04T09:54:51+00:00" + }, + { + "name": "verfriemelt/pp", + "version": "1.0.0", + "source": { + "type": "git", + "url": "https://github.com/verfriemelt-dot-org/pp.git", + "reference": "8ea53d791872de6a8cb35b2b15dfe5dcb20aff70" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/verfriemelt-dot-org/pp/zipball/8ea53d791872de6a8cb35b2b15dfe5dcb20aff70", + "reference": "8ea53d791872de6a8cb35b2b15dfe5dcb20aff70", + "shasum": "" + }, + "require": { + "ext-intl": "*", + "php": "^8.2" + }, + "require-dev": { + "friendsofphp/php-cs-fixer": "^3.64", + "phpbench/phpbench": "^1.3.1", + "phpstan/phpstan": "^1.12.11", + "phpstan/phpstan-phpunit": "^1.4.1", + "phpstan/phpstan-strict-rules": "^1.6.1", + "phpunit/phpunit": "^10.5.38" + }, + "type": "library", + "autoload": { + "files": [ + "src/Parser/functions/binary.php", + "src/Parser/functions/characters.php", + "src/Parser/functions/combinator.php" + ], + "psr-4": { + "verfriemelt\\pp\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "for educational purposes only", + "support": { + "issues": "https://github.com/verfriemelt-dot-org/pp/issues", + "source": "https://github.com/verfriemelt-dot-org/pp/tree/1.0.0" + }, + "time": "2024-11-20T16:58:30+00:00" } ], "packages-dev": [ @@ -1469,6 +1625,61 @@ }, "time": "2022-02-21T01:04:05+00:00" }, + { + "name": "php-http/psr7-integration-tests", + "version": "1.4.0", + "source": { + "type": "git", + "url": "https://github.com/php-http/psr7-integration-tests.git", + "reference": "7e7bc95a414ce1c23d62fc50b444ca849a75101e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-http/psr7-integration-tests/zipball/7e7bc95a414ce1c23d62fc50b444ca849a75101e", + "reference": "7e7bc95a414ce1c23d62fc50b444ca849a75101e", + "shasum": "" + }, + "require": { + "php": "^7.3 || ^8.0", + "phpunit/phpunit": "^9.3 || ^10.0", + "psr/http-message": "^1.0 || ^2.0" + }, + "require-dev": { + "guzzlehttp/psr7": "^1.7 || ^2.0", + "httpsoft/http-message": "^1.1", + "laminas/laminas-diactoros": "^2.1", + "nyholm/psr7": "^1.0", + "ringcentral/psr7": "^1.2", + "slim/psr7": "^1.4" + }, + "type": "library", + "autoload": { + "psr-4": { + "Http\\Psr7Test\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Tobias Nyholm", + "email": "tobias.nyholm@gmail.com" + } + ], + "description": "Test suite for PSR7", + "homepage": "http://php-http.org", + "keywords": [ + "psr-7", + "test" + ], + "support": { + "issues": "https://github.com/php-http/psr7-integration-tests/issues", + "source": "https://github.com/php-http/psr7-integration-tests/tree/1.4.0" + }, + "time": "2024-08-02T11:48:41+00:00" + }, { "name": "phpstan/phpstan", "version": "1.12.11", @@ -1630,35 +1841,35 @@ }, { "name": "phpunit/php-code-coverage", - "version": "11.0.7", + "version": "10.1.16", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-code-coverage.git", - "reference": "f7f08030e8811582cc459871d28d6f5a1a4d35ca" + "reference": "7e308268858ed6baedc8704a304727d20bc07c77" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/f7f08030e8811582cc459871d28d6f5a1a4d35ca", - "reference": "f7f08030e8811582cc459871d28d6f5a1a4d35ca", + "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/7e308268858ed6baedc8704a304727d20bc07c77", + "reference": "7e308268858ed6baedc8704a304727d20bc07c77", "shasum": "" }, "require": { "ext-dom": "*", "ext-libxml": "*", "ext-xmlwriter": "*", - "nikic/php-parser": "^5.3.1", - "php": ">=8.2", - "phpunit/php-file-iterator": "^5.1.0", - "phpunit/php-text-template": "^4.0.1", - "sebastian/code-unit-reverse-lookup": "^4.0.1", - "sebastian/complexity": "^4.0.1", - "sebastian/environment": "^7.2.0", - "sebastian/lines-of-code": "^3.0.1", - "sebastian/version": "^5.0.2", + "nikic/php-parser": "^4.19.1 || ^5.1.0", + "php": ">=8.1", + "phpunit/php-file-iterator": "^4.1.0", + "phpunit/php-text-template": "^3.0.1", + "sebastian/code-unit-reverse-lookup": "^3.0.0", + "sebastian/complexity": "^3.2.0", + "sebastian/environment": "^6.1.0", + "sebastian/lines-of-code": "^2.0.2", + "sebastian/version": "^4.0.1", "theseer/tokenizer": "^1.2.3" }, "require-dev": { - "phpunit/phpunit": "^11.4.1" + "phpunit/phpunit": "^10.1" }, "suggest": { "ext-pcov": "PHP extension that provides line coverage", @@ -1667,7 +1878,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "11.0.x-dev" + "dev-main": "10.1.x-dev" } }, "autoload": { @@ -1696,7 +1907,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/php-code-coverage/issues", "security": "https://github.com/sebastianbergmann/php-code-coverage/security/policy", - "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/11.0.7" + "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/10.1.16" }, "funding": [ { @@ -1704,32 +1915,32 @@ "type": "github" } ], - "time": "2024-10-09T06:21:38+00:00" + "time": "2024-08-22T04:31:57+00:00" }, { "name": "phpunit/php-file-iterator", - "version": "5.1.0", + "version": "4.1.0", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-file-iterator.git", - "reference": "118cfaaa8bc5aef3287bf315b6060b1174754af6" + "reference": "a95037b6d9e608ba092da1b23931e537cadc3c3c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/118cfaaa8bc5aef3287bf315b6060b1174754af6", - "reference": "118cfaaa8bc5aef3287bf315b6060b1174754af6", + "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/a95037b6d9e608ba092da1b23931e537cadc3c3c", + "reference": "a95037b6d9e608ba092da1b23931e537cadc3c3c", "shasum": "" }, "require": { - "php": ">=8.2" + "php": ">=8.1" }, "require-dev": { - "phpunit/phpunit": "^11.0" + "phpunit/phpunit": "^10.0" }, "type": "library", "extra": { "branch-alias": { - "dev-main": "5.0-dev" + "dev-main": "4.0-dev" } }, "autoload": { @@ -1757,7 +1968,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/php-file-iterator/issues", "security": "https://github.com/sebastianbergmann/php-file-iterator/security/policy", - "source": "https://github.com/sebastianbergmann/php-file-iterator/tree/5.1.0" + "source": "https://github.com/sebastianbergmann/php-file-iterator/tree/4.1.0" }, "funding": [ { @@ -1765,28 +1976,28 @@ "type": "github" } ], - "time": "2024-08-27T05:02:59+00:00" + "time": "2023-08-31T06:24:48+00:00" }, { "name": "phpunit/php-invoker", - "version": "5.0.1", + "version": "4.0.0", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-invoker.git", - "reference": "c1ca3814734c07492b3d4c5f794f4b0995333da2" + "reference": "f5e568ba02fa5ba0ddd0f618391d5a9ea50b06d7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-invoker/zipball/c1ca3814734c07492b3d4c5f794f4b0995333da2", - "reference": "c1ca3814734c07492b3d4c5f794f4b0995333da2", + "url": "https://api.github.com/repos/sebastianbergmann/php-invoker/zipball/f5e568ba02fa5ba0ddd0f618391d5a9ea50b06d7", + "reference": "f5e568ba02fa5ba0ddd0f618391d5a9ea50b06d7", "shasum": "" }, "require": { - "php": ">=8.2" + "php": ">=8.1" }, "require-dev": { "ext-pcntl": "*", - "phpunit/phpunit": "^11.0" + "phpunit/phpunit": "^10.0" }, "suggest": { "ext-pcntl": "*" @@ -1794,7 +2005,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "5.0-dev" + "dev-main": "4.0-dev" } }, "autoload": { @@ -1820,8 +2031,7 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/php-invoker/issues", - "security": "https://github.com/sebastianbergmann/php-invoker/security/policy", - "source": "https://github.com/sebastianbergmann/php-invoker/tree/5.0.1" + "source": "https://github.com/sebastianbergmann/php-invoker/tree/4.0.0" }, "funding": [ { @@ -1829,32 +2039,32 @@ "type": "github" } ], - "time": "2024-07-03T05:07:44+00:00" + "time": "2023-02-03T06:56:09+00:00" }, { "name": "phpunit/php-text-template", - "version": "4.0.1", + "version": "3.0.1", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-text-template.git", - "reference": "3e0404dc6b300e6bf56415467ebcb3fe4f33e964" + "reference": "0c7b06ff49e3d5072f057eb1fa59258bf287a748" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-text-template/zipball/3e0404dc6b300e6bf56415467ebcb3fe4f33e964", - "reference": "3e0404dc6b300e6bf56415467ebcb3fe4f33e964", + "url": "https://api.github.com/repos/sebastianbergmann/php-text-template/zipball/0c7b06ff49e3d5072f057eb1fa59258bf287a748", + "reference": "0c7b06ff49e3d5072f057eb1fa59258bf287a748", "shasum": "" }, "require": { - "php": ">=8.2" + "php": ">=8.1" }, "require-dev": { - "phpunit/phpunit": "^11.0" + "phpunit/phpunit": "^10.0" }, "type": "library", "extra": { "branch-alias": { - "dev-main": "4.0-dev" + "dev-main": "3.0-dev" } }, "autoload": { @@ -1881,7 +2091,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/php-text-template/issues", "security": "https://github.com/sebastianbergmann/php-text-template/security/policy", - "source": "https://github.com/sebastianbergmann/php-text-template/tree/4.0.1" + "source": "https://github.com/sebastianbergmann/php-text-template/tree/3.0.1" }, "funding": [ { @@ -1889,32 +2099,32 @@ "type": "github" } ], - "time": "2024-07-03T05:08:43+00:00" + "time": "2023-08-31T14:07:24+00:00" }, { "name": "phpunit/php-timer", - "version": "7.0.1", + "version": "6.0.0", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-timer.git", - "reference": "3b415def83fbcb41f991d9ebf16ae4ad8b7837b3" + "reference": "e2a2d67966e740530f4a3343fe2e030ffdc1161d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/3b415def83fbcb41f991d9ebf16ae4ad8b7837b3", - "reference": "3b415def83fbcb41f991d9ebf16ae4ad8b7837b3", + "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/e2a2d67966e740530f4a3343fe2e030ffdc1161d", + "reference": "e2a2d67966e740530f4a3343fe2e030ffdc1161d", "shasum": "" }, "require": { - "php": ">=8.2" + "php": ">=8.1" }, "require-dev": { - "phpunit/phpunit": "^11.0" + "phpunit/phpunit": "^10.0" }, "type": "library", "extra": { "branch-alias": { - "dev-main": "7.0-dev" + "dev-main": "6.0-dev" } }, "autoload": { @@ -1940,8 +2150,7 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/php-timer/issues", - "security": "https://github.com/sebastianbergmann/php-timer/security/policy", - "source": "https://github.com/sebastianbergmann/php-timer/tree/7.0.1" + "source": "https://github.com/sebastianbergmann/php-timer/tree/6.0.0" }, "funding": [ { @@ -1949,20 +2158,20 @@ "type": "github" } ], - "time": "2024-07-03T05:09:35+00:00" + "time": "2023-02-03T06:57:52+00:00" }, { "name": "phpunit/phpunit", - "version": "11.4.3", + "version": "10.5.38", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "e8e8ed1854de5d36c088ec1833beae40d2dedd76" + "reference": "a86773b9e887a67bc53efa9da9ad6e3f2498c132" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/e8e8ed1854de5d36c088ec1833beae40d2dedd76", - "reference": "e8e8ed1854de5d36c088ec1833beae40d2dedd76", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/a86773b9e887a67bc53efa9da9ad6e3f2498c132", + "reference": "a86773b9e887a67bc53efa9da9ad6e3f2498c132", "shasum": "" }, "require": { @@ -1975,22 +2184,23 @@ "myclabs/deep-copy": "^1.12.0", "phar-io/manifest": "^2.0.4", "phar-io/version": "^3.2.1", - "php": ">=8.2", - "phpunit/php-code-coverage": "^11.0.7", - "phpunit/php-file-iterator": "^5.1.0", - "phpunit/php-invoker": "^5.0.1", - "phpunit/php-text-template": "^4.0.1", - "phpunit/php-timer": "^7.0.1", - "sebastian/cli-parser": "^3.0.2", - "sebastian/code-unit": "^3.0.1", - "sebastian/comparator": "^6.1.1", - "sebastian/diff": "^6.0.2", - "sebastian/environment": "^7.2.0", - "sebastian/exporter": "^6.1.3", - "sebastian/global-state": "^7.0.2", - "sebastian/object-enumerator": "^6.0.1", - "sebastian/type": "^5.1.0", - "sebastian/version": "^5.0.2" + "php": ">=8.1", + "phpunit/php-code-coverage": "^10.1.16", + "phpunit/php-file-iterator": "^4.1.0", + "phpunit/php-invoker": "^4.0.0", + "phpunit/php-text-template": "^3.0.1", + "phpunit/php-timer": "^6.0.0", + "sebastian/cli-parser": "^2.0.1", + "sebastian/code-unit": "^2.0.0", + "sebastian/comparator": "^5.0.3", + "sebastian/diff": "^5.1.1", + "sebastian/environment": "^6.1.0", + "sebastian/exporter": "^5.1.2", + "sebastian/global-state": "^6.0.2", + "sebastian/object-enumerator": "^5.0.0", + "sebastian/recursion-context": "^5.0.0", + "sebastian/type": "^4.0.0", + "sebastian/version": "^4.0.1" }, "suggest": { "ext-soap": "To be able to generate mocks based on WSDL files" @@ -2001,7 +2211,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "11.4-dev" + "dev-main": "10.5-dev" } }, "autoload": { @@ -2033,7 +2243,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/phpunit/issues", "security": "https://github.com/sebastianbergmann/phpunit/security/policy", - "source": "https://github.com/sebastianbergmann/phpunit/tree/11.4.3" + "source": "https://github.com/sebastianbergmann/phpunit/tree/10.5.38" }, "funding": [ { @@ -2049,7 +2259,7 @@ "type": "tidelift" } ], - "time": "2024-10-28T13:07:50+00:00" + "time": "2024-10-28T13:06:21+00:00" }, { "name": "psr/container", @@ -2924,28 +3134,28 @@ }, { "name": "sebastian/cli-parser", - "version": "3.0.2", + "version": "2.0.1", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/cli-parser.git", - "reference": "15c5dd40dc4f38794d383bb95465193f5e0ae180" + "reference": "c34583b87e7b7a8055bf6c450c2c77ce32a24084" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/cli-parser/zipball/15c5dd40dc4f38794d383bb95465193f5e0ae180", - "reference": "15c5dd40dc4f38794d383bb95465193f5e0ae180", + "url": "https://api.github.com/repos/sebastianbergmann/cli-parser/zipball/c34583b87e7b7a8055bf6c450c2c77ce32a24084", + "reference": "c34583b87e7b7a8055bf6c450c2c77ce32a24084", "shasum": "" }, "require": { - "php": ">=8.2" + "php": ">=8.1" }, "require-dev": { - "phpunit/phpunit": "^11.0" + "phpunit/phpunit": "^10.0" }, "type": "library", "extra": { "branch-alias": { - "dev-main": "3.0-dev" + "dev-main": "2.0-dev" } }, "autoload": { @@ -2969,7 +3179,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/cli-parser/issues", "security": "https://github.com/sebastianbergmann/cli-parser/security/policy", - "source": "https://github.com/sebastianbergmann/cli-parser/tree/3.0.2" + "source": "https://github.com/sebastianbergmann/cli-parser/tree/2.0.1" }, "funding": [ { @@ -2977,32 +3187,32 @@ "type": "github" } ], - "time": "2024-07-03T04:41:36+00:00" + "time": "2024-03-02T07:12:49+00:00" }, { "name": "sebastian/code-unit", - "version": "3.0.1", + "version": "2.0.0", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/code-unit.git", - "reference": "6bb7d09d6623567178cf54126afa9c2310114268" + "reference": "a81fee9eef0b7a76af11d121767abc44c104e503" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/code-unit/zipball/6bb7d09d6623567178cf54126afa9c2310114268", - "reference": "6bb7d09d6623567178cf54126afa9c2310114268", + "url": "https://api.github.com/repos/sebastianbergmann/code-unit/zipball/a81fee9eef0b7a76af11d121767abc44c104e503", + "reference": "a81fee9eef0b7a76af11d121767abc44c104e503", "shasum": "" }, "require": { - "php": ">=8.2" + "php": ">=8.1" }, "require-dev": { - "phpunit/phpunit": "^11.0" + "phpunit/phpunit": "^10.0" }, "type": "library", "extra": { "branch-alias": { - "dev-main": "3.0-dev" + "dev-main": "2.0-dev" } }, "autoload": { @@ -3025,8 +3235,7 @@ "homepage": "https://github.com/sebastianbergmann/code-unit", "support": { "issues": "https://github.com/sebastianbergmann/code-unit/issues", - "security": "https://github.com/sebastianbergmann/code-unit/security/policy", - "source": "https://github.com/sebastianbergmann/code-unit/tree/3.0.1" + "source": "https://github.com/sebastianbergmann/code-unit/tree/2.0.0" }, "funding": [ { @@ -3034,32 +3243,32 @@ "type": "github" } ], - "time": "2024-07-03T04:44:28+00:00" + "time": "2023-02-03T06:58:43+00:00" }, { "name": "sebastian/code-unit-reverse-lookup", - "version": "4.0.1", + "version": "3.0.0", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/code-unit-reverse-lookup.git", - "reference": "183a9b2632194febd219bb9246eee421dad8d45e" + "reference": "5e3a687f7d8ae33fb362c5c0743794bbb2420a1d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/code-unit-reverse-lookup/zipball/183a9b2632194febd219bb9246eee421dad8d45e", - "reference": "183a9b2632194febd219bb9246eee421dad8d45e", + "url": "https://api.github.com/repos/sebastianbergmann/code-unit-reverse-lookup/zipball/5e3a687f7d8ae33fb362c5c0743794bbb2420a1d", + "reference": "5e3a687f7d8ae33fb362c5c0743794bbb2420a1d", "shasum": "" }, "require": { - "php": ">=8.2" + "php": ">=8.1" }, "require-dev": { - "phpunit/phpunit": "^11.0" + "phpunit/phpunit": "^10.0" }, "type": "library", "extra": { "branch-alias": { - "dev-main": "4.0-dev" + "dev-main": "3.0-dev" } }, "autoload": { @@ -3081,8 +3290,7 @@ "homepage": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/", "support": { "issues": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/issues", - "security": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/security/policy", - "source": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/tree/4.0.1" + "source": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/tree/3.0.0" }, "funding": [ { @@ -3090,36 +3298,36 @@ "type": "github" } ], - "time": "2024-07-03T04:45:54+00:00" + "time": "2023-02-03T06:59:15+00:00" }, { "name": "sebastian/comparator", - "version": "6.2.1", + "version": "5.0.3", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/comparator.git", - "reference": "43d129d6a0f81c78bee378b46688293eb7ea3739" + "reference": "a18251eb0b7a2dcd2f7aa3d6078b18545ef0558e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/43d129d6a0f81c78bee378b46688293eb7ea3739", - "reference": "43d129d6a0f81c78bee378b46688293eb7ea3739", + "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/a18251eb0b7a2dcd2f7aa3d6078b18545ef0558e", + "reference": "a18251eb0b7a2dcd2f7aa3d6078b18545ef0558e", "shasum": "" }, "require": { "ext-dom": "*", "ext-mbstring": "*", - "php": ">=8.2", - "sebastian/diff": "^6.0", - "sebastian/exporter": "^6.0" + "php": ">=8.1", + "sebastian/diff": "^5.0", + "sebastian/exporter": "^5.0" }, "require-dev": { - "phpunit/phpunit": "^11.4" + "phpunit/phpunit": "^10.5" }, "type": "library", "extra": { "branch-alias": { - "dev-main": "6.2-dev" + "dev-main": "5.0-dev" } }, "autoload": { @@ -3159,7 +3367,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/comparator/issues", "security": "https://github.com/sebastianbergmann/comparator/security/policy", - "source": "https://github.com/sebastianbergmann/comparator/tree/6.2.1" + "source": "https://github.com/sebastianbergmann/comparator/tree/5.0.3" }, "funding": [ { @@ -3167,33 +3375,33 @@ "type": "github" } ], - "time": "2024-10-31T05:30:08+00:00" + "time": "2024-10-18T14:56:07+00:00" }, { "name": "sebastian/complexity", - "version": "4.0.1", + "version": "3.2.0", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/complexity.git", - "reference": "ee41d384ab1906c68852636b6de493846e13e5a0" + "reference": "68ff824baeae169ec9f2137158ee529584553799" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/complexity/zipball/ee41d384ab1906c68852636b6de493846e13e5a0", - "reference": "ee41d384ab1906c68852636b6de493846e13e5a0", + "url": "https://api.github.com/repos/sebastianbergmann/complexity/zipball/68ff824baeae169ec9f2137158ee529584553799", + "reference": "68ff824baeae169ec9f2137158ee529584553799", "shasum": "" }, "require": { - "nikic/php-parser": "^5.0", - "php": ">=8.2" + "nikic/php-parser": "^4.18 || ^5.0", + "php": ">=8.1" }, "require-dev": { - "phpunit/phpunit": "^11.0" + "phpunit/phpunit": "^10.0" }, "type": "library", "extra": { "branch-alias": { - "dev-main": "4.0-dev" + "dev-main": "3.2-dev" } }, "autoload": { @@ -3217,7 +3425,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/complexity/issues", "security": "https://github.com/sebastianbergmann/complexity/security/policy", - "source": "https://github.com/sebastianbergmann/complexity/tree/4.0.1" + "source": "https://github.com/sebastianbergmann/complexity/tree/3.2.0" }, "funding": [ { @@ -3225,33 +3433,33 @@ "type": "github" } ], - "time": "2024-07-03T04:49:50+00:00" + "time": "2023-12-21T08:37:17+00:00" }, { "name": "sebastian/diff", - "version": "6.0.2", + "version": "5.1.1", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/diff.git", - "reference": "b4ccd857127db5d41a5b676f24b51371d76d8544" + "reference": "c41e007b4b62af48218231d6c2275e4c9b975b2e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/b4ccd857127db5d41a5b676f24b51371d76d8544", - "reference": "b4ccd857127db5d41a5b676f24b51371d76d8544", + "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/c41e007b4b62af48218231d6c2275e4c9b975b2e", + "reference": "c41e007b4b62af48218231d6c2275e4c9b975b2e", "shasum": "" }, "require": { - "php": ">=8.2" + "php": ">=8.1" }, "require-dev": { - "phpunit/phpunit": "^11.0", - "symfony/process": "^4.2 || ^5" + "phpunit/phpunit": "^10.0", + "symfony/process": "^6.4" }, "type": "library", "extra": { "branch-alias": { - "dev-main": "6.0-dev" + "dev-main": "5.1-dev" } }, "autoload": { @@ -3284,7 +3492,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/diff/issues", "security": "https://github.com/sebastianbergmann/diff/security/policy", - "source": "https://github.com/sebastianbergmann/diff/tree/6.0.2" + "source": "https://github.com/sebastianbergmann/diff/tree/5.1.1" }, "funding": [ { @@ -3292,27 +3500,27 @@ "type": "github" } ], - "time": "2024-07-03T04:53:05+00:00" + "time": "2024-03-02T07:15:17+00:00" }, { "name": "sebastian/environment", - "version": "7.2.0", + "version": "6.1.0", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/environment.git", - "reference": "855f3ae0ab316bbafe1ba4e16e9f3c078d24a0c5" + "reference": "8074dbcd93529b357029f5cc5058fd3e43666984" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/855f3ae0ab316bbafe1ba4e16e9f3c078d24a0c5", - "reference": "855f3ae0ab316bbafe1ba4e16e9f3c078d24a0c5", + "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/8074dbcd93529b357029f5cc5058fd3e43666984", + "reference": "8074dbcd93529b357029f5cc5058fd3e43666984", "shasum": "" }, "require": { - "php": ">=8.2" + "php": ">=8.1" }, "require-dev": { - "phpunit/phpunit": "^11.0" + "phpunit/phpunit": "^10.0" }, "suggest": { "ext-posix": "*" @@ -3320,7 +3528,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "7.2-dev" + "dev-main": "6.1-dev" } }, "autoload": { @@ -3348,7 +3556,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/environment/issues", "security": "https://github.com/sebastianbergmann/environment/security/policy", - "source": "https://github.com/sebastianbergmann/environment/tree/7.2.0" + "source": "https://github.com/sebastianbergmann/environment/tree/6.1.0" }, "funding": [ { @@ -3356,34 +3564,34 @@ "type": "github" } ], - "time": "2024-07-03T04:54:44+00:00" + "time": "2024-03-23T08:47:14+00:00" }, { "name": "sebastian/exporter", - "version": "6.1.3", + "version": "5.1.2", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/exporter.git", - "reference": "c414673eee9a8f9d51bbf8d61fc9e3ef1e85b20e" + "reference": "955288482d97c19a372d3f31006ab3f37da47adf" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/c414673eee9a8f9d51bbf8d61fc9e3ef1e85b20e", - "reference": "c414673eee9a8f9d51bbf8d61fc9e3ef1e85b20e", + "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/955288482d97c19a372d3f31006ab3f37da47adf", + "reference": "955288482d97c19a372d3f31006ab3f37da47adf", "shasum": "" }, "require": { "ext-mbstring": "*", - "php": ">=8.2", - "sebastian/recursion-context": "^6.0" + "php": ">=8.1", + "sebastian/recursion-context": "^5.0" }, "require-dev": { - "phpunit/phpunit": "^11.2" + "phpunit/phpunit": "^10.0" }, "type": "library", "extra": { "branch-alias": { - "dev-main": "6.1-dev" + "dev-main": "5.1-dev" } }, "autoload": { @@ -3426,7 +3634,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/exporter/issues", "security": "https://github.com/sebastianbergmann/exporter/security/policy", - "source": "https://github.com/sebastianbergmann/exporter/tree/6.1.3" + "source": "https://github.com/sebastianbergmann/exporter/tree/5.1.2" }, "funding": [ { @@ -3434,35 +3642,35 @@ "type": "github" } ], - "time": "2024-07-03T04:56:19+00:00" + "time": "2024-03-02T07:17:12+00:00" }, { "name": "sebastian/global-state", - "version": "7.0.2", + "version": "6.0.2", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/global-state.git", - "reference": "3be331570a721f9a4b5917f4209773de17f747d7" + "reference": "987bafff24ecc4c9ac418cab1145b96dd6e9cbd9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/3be331570a721f9a4b5917f4209773de17f747d7", - "reference": "3be331570a721f9a4b5917f4209773de17f747d7", + "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/987bafff24ecc4c9ac418cab1145b96dd6e9cbd9", + "reference": "987bafff24ecc4c9ac418cab1145b96dd6e9cbd9", "shasum": "" }, "require": { - "php": ">=8.2", - "sebastian/object-reflector": "^4.0", - "sebastian/recursion-context": "^6.0" + "php": ">=8.1", + "sebastian/object-reflector": "^3.0", + "sebastian/recursion-context": "^5.0" }, "require-dev": { "ext-dom": "*", - "phpunit/phpunit": "^11.0" + "phpunit/phpunit": "^10.0" }, "type": "library", "extra": { "branch-alias": { - "dev-main": "7.0-dev" + "dev-main": "6.0-dev" } }, "autoload": { @@ -3488,7 +3696,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/global-state/issues", "security": "https://github.com/sebastianbergmann/global-state/security/policy", - "source": "https://github.com/sebastianbergmann/global-state/tree/7.0.2" + "source": "https://github.com/sebastianbergmann/global-state/tree/6.0.2" }, "funding": [ { @@ -3496,33 +3704,33 @@ "type": "github" } ], - "time": "2024-07-03T04:57:36+00:00" + "time": "2024-03-02T07:19:19+00:00" }, { "name": "sebastian/lines-of-code", - "version": "3.0.1", + "version": "2.0.2", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/lines-of-code.git", - "reference": "d36ad0d782e5756913e42ad87cb2890f4ffe467a" + "reference": "856e7f6a75a84e339195d48c556f23be2ebf75d0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/lines-of-code/zipball/d36ad0d782e5756913e42ad87cb2890f4ffe467a", - "reference": "d36ad0d782e5756913e42ad87cb2890f4ffe467a", + "url": "https://api.github.com/repos/sebastianbergmann/lines-of-code/zipball/856e7f6a75a84e339195d48c556f23be2ebf75d0", + "reference": "856e7f6a75a84e339195d48c556f23be2ebf75d0", "shasum": "" }, "require": { - "nikic/php-parser": "^5.0", - "php": ">=8.2" + "nikic/php-parser": "^4.18 || ^5.0", + "php": ">=8.1" }, "require-dev": { - "phpunit/phpunit": "^11.0" + "phpunit/phpunit": "^10.0" }, "type": "library", "extra": { "branch-alias": { - "dev-main": "3.0-dev" + "dev-main": "2.0-dev" } }, "autoload": { @@ -3546,7 +3754,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/lines-of-code/issues", "security": "https://github.com/sebastianbergmann/lines-of-code/security/policy", - "source": "https://github.com/sebastianbergmann/lines-of-code/tree/3.0.1" + "source": "https://github.com/sebastianbergmann/lines-of-code/tree/2.0.2" }, "funding": [ { @@ -3554,34 +3762,34 @@ "type": "github" } ], - "time": "2024-07-03T04:58:38+00:00" + "time": "2023-12-21T08:38:20+00:00" }, { "name": "sebastian/object-enumerator", - "version": "6.0.1", + "version": "5.0.0", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/object-enumerator.git", - "reference": "f5b498e631a74204185071eb41f33f38d64608aa" + "reference": "202d0e344a580d7f7d04b3fafce6933e59dae906" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/object-enumerator/zipball/f5b498e631a74204185071eb41f33f38d64608aa", - "reference": "f5b498e631a74204185071eb41f33f38d64608aa", + "url": "https://api.github.com/repos/sebastianbergmann/object-enumerator/zipball/202d0e344a580d7f7d04b3fafce6933e59dae906", + "reference": "202d0e344a580d7f7d04b3fafce6933e59dae906", "shasum": "" }, "require": { - "php": ">=8.2", - "sebastian/object-reflector": "^4.0", - "sebastian/recursion-context": "^6.0" + "php": ">=8.1", + "sebastian/object-reflector": "^3.0", + "sebastian/recursion-context": "^5.0" }, "require-dev": { - "phpunit/phpunit": "^11.0" + "phpunit/phpunit": "^10.0" }, "type": "library", "extra": { "branch-alias": { - "dev-main": "6.0-dev" + "dev-main": "5.0-dev" } }, "autoload": { @@ -3603,8 +3811,7 @@ "homepage": "https://github.com/sebastianbergmann/object-enumerator/", "support": { "issues": "https://github.com/sebastianbergmann/object-enumerator/issues", - "security": "https://github.com/sebastianbergmann/object-enumerator/security/policy", - "source": "https://github.com/sebastianbergmann/object-enumerator/tree/6.0.1" + "source": "https://github.com/sebastianbergmann/object-enumerator/tree/5.0.0" }, "funding": [ { @@ -3612,32 +3819,32 @@ "type": "github" } ], - "time": "2024-07-03T05:00:13+00:00" + "time": "2023-02-03T07:08:32+00:00" }, { "name": "sebastian/object-reflector", - "version": "4.0.1", + "version": "3.0.0", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/object-reflector.git", - "reference": "6e1a43b411b2ad34146dee7524cb13a068bb35f9" + "reference": "24ed13d98130f0e7122df55d06c5c4942a577957" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/object-reflector/zipball/6e1a43b411b2ad34146dee7524cb13a068bb35f9", - "reference": "6e1a43b411b2ad34146dee7524cb13a068bb35f9", + "url": "https://api.github.com/repos/sebastianbergmann/object-reflector/zipball/24ed13d98130f0e7122df55d06c5c4942a577957", + "reference": "24ed13d98130f0e7122df55d06c5c4942a577957", "shasum": "" }, "require": { - "php": ">=8.2" + "php": ">=8.1" }, "require-dev": { - "phpunit/phpunit": "^11.0" + "phpunit/phpunit": "^10.0" }, "type": "library", "extra": { "branch-alias": { - "dev-main": "4.0-dev" + "dev-main": "3.0-dev" } }, "autoload": { @@ -3659,8 +3866,7 @@ "homepage": "https://github.com/sebastianbergmann/object-reflector/", "support": { "issues": "https://github.com/sebastianbergmann/object-reflector/issues", - "security": "https://github.com/sebastianbergmann/object-reflector/security/policy", - "source": "https://github.com/sebastianbergmann/object-reflector/tree/4.0.1" + "source": "https://github.com/sebastianbergmann/object-reflector/tree/3.0.0" }, "funding": [ { @@ -3668,32 +3874,32 @@ "type": "github" } ], - "time": "2024-07-03T05:01:32+00:00" + "time": "2023-02-03T07:06:18+00:00" }, { "name": "sebastian/recursion-context", - "version": "6.0.2", + "version": "5.0.0", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/recursion-context.git", - "reference": "694d156164372abbd149a4b85ccda2e4670c0e16" + "reference": "05909fb5bc7df4c52992396d0116aed689f93712" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/694d156164372abbd149a4b85ccda2e4670c0e16", - "reference": "694d156164372abbd149a4b85ccda2e4670c0e16", + "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/05909fb5bc7df4c52992396d0116aed689f93712", + "reference": "05909fb5bc7df4c52992396d0116aed689f93712", "shasum": "" }, "require": { - "php": ">=8.2" + "php": ">=8.1" }, "require-dev": { - "phpunit/phpunit": "^11.0" + "phpunit/phpunit": "^10.0" }, "type": "library", "extra": { "branch-alias": { - "dev-main": "6.0-dev" + "dev-main": "5.0-dev" } }, "autoload": { @@ -3723,8 +3929,7 @@ "homepage": "https://github.com/sebastianbergmann/recursion-context", "support": { "issues": "https://github.com/sebastianbergmann/recursion-context/issues", - "security": "https://github.com/sebastianbergmann/recursion-context/security/policy", - "source": "https://github.com/sebastianbergmann/recursion-context/tree/6.0.2" + "source": "https://github.com/sebastianbergmann/recursion-context/tree/5.0.0" }, "funding": [ { @@ -3732,32 +3937,32 @@ "type": "github" } ], - "time": "2024-07-03T05:10:34+00:00" + "time": "2023-02-03T07:05:40+00:00" }, { "name": "sebastian/type", - "version": "5.1.0", + "version": "4.0.0", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/type.git", - "reference": "461b9c5da241511a2a0e8f240814fb23ce5c0aac" + "reference": "462699a16464c3944eefc02ebdd77882bd3925bf" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/type/zipball/461b9c5da241511a2a0e8f240814fb23ce5c0aac", - "reference": "461b9c5da241511a2a0e8f240814fb23ce5c0aac", + "url": "https://api.github.com/repos/sebastianbergmann/type/zipball/462699a16464c3944eefc02ebdd77882bd3925bf", + "reference": "462699a16464c3944eefc02ebdd77882bd3925bf", "shasum": "" }, "require": { - "php": ">=8.2" + "php": ">=8.1" }, "require-dev": { - "phpunit/phpunit": "^11.3" + "phpunit/phpunit": "^10.0" }, "type": "library", "extra": { "branch-alias": { - "dev-main": "5.1-dev" + "dev-main": "4.0-dev" } }, "autoload": { @@ -3780,8 +3985,7 @@ "homepage": "https://github.com/sebastianbergmann/type", "support": { "issues": "https://github.com/sebastianbergmann/type/issues", - "security": "https://github.com/sebastianbergmann/type/security/policy", - "source": "https://github.com/sebastianbergmann/type/tree/5.1.0" + "source": "https://github.com/sebastianbergmann/type/tree/4.0.0" }, "funding": [ { @@ -3789,29 +3993,29 @@ "type": "github" } ], - "time": "2024-09-17T13:12:04+00:00" + "time": "2023-02-03T07:10:45+00:00" }, { "name": "sebastian/version", - "version": "5.0.2", + "version": "4.0.1", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/version.git", - "reference": "c687e3387b99f5b03b6caa64c74b63e2936ff874" + "reference": "c51fa83a5d8f43f1402e3f32a005e6262244ef17" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/version/zipball/c687e3387b99f5b03b6caa64c74b63e2936ff874", - "reference": "c687e3387b99f5b03b6caa64c74b63e2936ff874", + "url": "https://api.github.com/repos/sebastianbergmann/version/zipball/c51fa83a5d8f43f1402e3f32a005e6262244ef17", + "reference": "c51fa83a5d8f43f1402e3f32a005e6262244ef17", "shasum": "" }, "require": { - "php": ">=8.2" + "php": ">=8.1" }, "type": "library", "extra": { "branch-alias": { - "dev-main": "5.0-dev" + "dev-main": "4.0-dev" } }, "autoload": { @@ -3834,8 +4038,7 @@ "homepage": "https://github.com/sebastianbergmann/version", "support": { "issues": "https://github.com/sebastianbergmann/version/issues", - "security": "https://github.com/sebastianbergmann/version/security/policy", - "source": "https://github.com/sebastianbergmann/version/tree/5.0.2" + "source": "https://github.com/sebastianbergmann/version/tree/4.0.1" }, "funding": [ { @@ -3843,7 +4046,7 @@ "type": "github" } ], - "time": "2024-10-09T05:16:32+00:00" + "time": "2023-02-07T11:34:05+00:00" }, { "name": "symfony/console", @@ -5489,7 +5692,8 @@ "prefer-lowest": false, "platform": { "php": "^8.3", - "ext-pdo": "*" + "ext-pdo": "*", + "ext-curl": "*" }, "platform-dev": {}, "plugin-api-version": "2.6.0" diff --git a/phpunit.xml b/phpunit.xml index 471f9dc..2826115 100644 --- a/phpunit.xml +++ b/phpunit.xml @@ -27,6 +27,9 @@ tests/Integration + + tests/Psr7 + diff --git a/tests/Psr7/UriTest.php b/tests/Psr7/UriTest.php new file mode 100644 index 0000000..5dcc06a --- /dev/null +++ b/tests/Psr7/UriTest.php @@ -0,0 +1,35 @@ + + */ + #[Override] + public static function getPaths(): iterable + { + $data = parent::getPaths(); + + // skip out [$test->createUri('foo/bar'), 'foo/bar'], + unset($data[2]); + + return $data; + } +} diff --git a/tests/Unit/HttpClient/UriParserTest.php b/tests/Unit/HttpClient/UriParserTest.php new file mode 100644 index 0000000..04d3027 --- /dev/null +++ b/tests/Unit/HttpClient/UriParserTest.php @@ -0,0 +1,154 @@ + + */ + public static function scheme(): iterable + { + yield 'none' => ['google.de', '']; + yield 'ftp' => ['ftp://google.de', 'ftp']; + yield 'https' => ['https://google.de', 'https']; + } + + #[DataProvider('scheme')] + public function test_scheme(string $input, string $expected): void + { + $parser = UriParser::scheme(); + $result = $parser->run(new ParserInput($input)); + + static::assertSame($expected, $result->getResult()); + } + + /** + * @return iterable + */ + public static function userinfo(): iterable + { + yield 'none' => ['', '']; + yield 'username' => ['user@', 'user']; + yield 'password' => ['user:password@', 'user:password']; + yield 'encoded' => ['foo%40bar.com:pass%23word@', 'foo%40bar.com:pass%23word']; + } + + #[DataProvider('userinfo')] + public function test_userinfo(string $input, string $expected): void + { + $parser = UriParser::userinfo(); + $result = $parser->run(new ParserInput($input)); + + static::assertSame($expected, $result->getResult()); + } + + /** + * @return iterable + */ + public static function port(): iterable + { + yield 'none' => ['', null]; + yield '80' => [':80', 80]; + } + + #[DataProvider('port')] + public function test_port(string $input, ?int $expected): void + { + $parser = UriParser::port(); + $result = $parser->run(new ParserInput($input)); + + static::assertSame($expected, $result->getResult()); + } + + /** + * @return iterable + */ + public static function host(): iterable + { + yield 'none' => ['', '']; + yield 'foo.bar' => ['foo.bar', 'foo.bar']; + } + + #[DataProvider('host')] + public function test_host(string $input, ?string $expected): void + { + $parser = UriParser::host(); + $result = $parser->run(new ParserInput($input)); + + static::assertSame($expected, $result->getResult()); + } + + /** + * @return iterable + */ + public static function path(): iterable + { + yield 'none' => ['', '']; + yield '/foo/bar.html' => ['/foo/bar.html', '/foo/bar.html']; + yield '//valid///path' => ['//valid///path', '/valid///path']; + } + + #[DataProvider('path')] + public function test_path(string $input, ?string $expected): void + { + $parser = UriParser::path(); + $result = $parser->run(new ParserInput($input)); + + static::assertSame($expected, $result->getResult()); + } + + /** + * @return iterable + */ + public static function query(): iterable + { + yield 'none' => ['', '']; + yield '?foo=bar' => ['?foo=bar', 'foo=bar']; + yield '?foo=bar&bar=foo' => ['?foo=bar&bar=foo', 'foo=bar&bar=foo']; + } + + #[DataProvider('query')] + public function test_query(string $input, ?string $expected): void + { + $parser = UriParser::query(); + $result = $parser->run(new ParserInput($input)); + + static::assertSame($expected, $result->getResult()); + } + + /** + * @return iterable + */ + public static function fragment(): iterable + { + yield 'none' => ['', '']; + yield '#foo=bar' => ['#foo=bar', 'foo=bar']; + yield '#foo=bar&bar=foo' => ['#foo=bar&bar=foo', 'foo=bar&bar=foo']; + } + + #[DataProvider('fragment')] + public function test_fragment(string $input, ?string $expected): void + { + $parser = UriParser::fragment(); + $result = $parser->run(new ParserInput($input)); + + static::assertSame($expected, $result->getResult()); + } + + public function test(): void + { + $parser = UriParser::port(); + $result = $parser->run(new ParserInput(':')); + + static::assertTrue($result->isError()); + + } +} From a8b2accf1e166b1a957458c2a0bc2dcc0f1aa0c5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E0=B2=A0=5F=E0=B2=A0?= Date: Wed, 20 Nov 2024 23:19:10 +0100 Subject: [PATCH 2/7] implment psr7/request & psr7/response --- _/HttpClient/Psr7/Message.php | 147 +++++++++++++++++++++ _/HttpClient/Psr7/Request.php | 86 +++++++++++++ _/HttpClient/Psr7/Response.php | 39 ++++++ _/HttpClient/Psr7/Stream.php | 163 ++++++++++++++++++++++++ _/HttpClient/Psr7/StreamFactory.php | 39 ++++++ _/HttpClient/Psr7/Uri.php | 5 - _/HttpClient/Psr7/UriFactory.php | 18 +++ _/HttpClient/UriParser.php | 2 +- tests/Psr7/RequestTest.php | 18 +++ tests/Psr7/ResponseTest.php | 18 +++ tests/Unit/HttpClient/UriParserTest.php | 12 +- tests/bootstrap.php | 6 +- 12 files changed, 545 insertions(+), 8 deletions(-) create mode 100644 _/HttpClient/Psr7/Message.php create mode 100644 _/HttpClient/Psr7/Request.php create mode 100644 _/HttpClient/Psr7/Response.php create mode 100644 _/HttpClient/Psr7/Stream.php create mode 100644 _/HttpClient/Psr7/StreamFactory.php create mode 100644 _/HttpClient/Psr7/UriFactory.php create mode 100644 tests/Psr7/RequestTest.php create mode 100644 tests/Psr7/ResponseTest.php diff --git a/_/HttpClient/Psr7/Message.php b/_/HttpClient/Psr7/Message.php new file mode 100644 index 0000000..20da9bb --- /dev/null +++ b/_/HttpClient/Psr7/Message.php @@ -0,0 +1,147 @@ + */ + private array $headers = []; + + private string $protocolVersion; + + /** @var array */ + private array $normalizedHeaderNames = []; + + #[Override] + public function getProtocolVersion(): string + { + return $this->protocolVersion; + } + + #[Override] + public function withProtocolVersion(string $version): MessageInterface + { + $instance = clone $this; + $instance->protocolVersion = $version; + + return $instance; + } + + #[Override] + public function getHeaders(): array + { + return $this->headers; + } + + #[Override] + public function hasHeader(string $name): bool + { + return \array_key_exists($this->translateHeaderName($name), $this->headers); + } + + #[Override] + public function getHeader(string $name): array + { + return $this->headers[$this->translateHeaderName($name)] ?? []; + } + + #[Override] + public function getHeaderLine(string $name): string + { + return implode(',', $this->headers[$this->translateHeaderName($name)] ?? []); + } + + #[Override] + public function withHeader(string $name, $value): MessageInterface + { + $instance = clone $this; + + if (\mb_strlen($name) === 0) { + throw new InvalidArgumentException(); + } elseif (!\is_array($value) && !\is_string($value) || $value === []) { + throw new InvalidArgumentException(); + } + + if (!\is_array($value)) { + $instance->headers[$this->translateHeaderName($name)] = [$value]; + } else { + $instance->headers[$this->translateHeaderName($name)] = $value; + } + + $instance->normalizedHeaderNames[$this->normalizeHeaderName($name)] ??= $name; + + + return $instance; + } + + #[Override] + public function withAddedHeader(string $name, $value): MessageInterface + { + $instance = clone $this; + + if (\mb_strlen($name) === 0) { + throw new InvalidArgumentException(); + } elseif (!\is_array($value) && !\is_string($value) || $value === []) { + throw new InvalidArgumentException(); + } + + $instance->headers[$this->translateHeaderName($name)] = [ + ... $this->headers[$this->translateHeaderName($name)] ?? [], + ... (\is_array($value) ? array_values($value) : [$value]), + ]; + + $instance->normalizedHeaderNames[$this->normalizeHeaderName($name)] ??= $name; + + return $instance; + + } + + #[Override] + public function withoutHeader(string $name): MessageInterface + { + $instance = clone $this; + + if (!isset($this->headers[$this->translateHeaderName($name)])) { + return $instance; + } + + unset($instance->headers[$this->translateHeaderName($name)]); + unset($instance->normalizedHeaderNames[$this->normalizeHeaderName($name)]); + + return $instance; + } + + #[Override] + public function getBody(): StreamInterface + { + return $this->body; + } + + #[Override] + public function withBody(StreamInterface $body): MessageInterface + { + $instance = clone $this; + $instance->body = $body; + + return $instance; + } + + protected function translateHeaderName(string $name): string + { + return $this->normalizedHeaderNames[$this->normalizeHeaderName($name)] ?? $name; + } + + protected function normalizeHeaderName(string $name): string + { + return strtolower($name); + } +} diff --git a/_/HttpClient/Psr7/Request.php b/_/HttpClient/Psr7/Request.php new file mode 100644 index 0000000..380e308 --- /dev/null +++ b/_/HttpClient/Psr7/Request.php @@ -0,0 +1,86 @@ +requestMethod = $method; + + if (is_string($uri)) { + $this->uri = new Uri($uri); + } + + } + + #[Override] + public function getRequestTarget(): string + { + return $this->requestTarget; + } + + #[Override] + public function withRequestTarget(string $requestTarget): RequestInterface + { + $instance = clone $this; + $instance->requestTarget = $requestTarget; + + return $instance; + } + + #[Override] + public function getMethod(): string + { + return $this->requestMethod; + } + + #[Override] + public function withMethod(string $method): RequestInterface + { + if (\mb_strlen($method) === 0) { + throw new InvalidArgumentException(); + } + + $instance = clone $this; + $instance->requestMethod = $method; + + return $instance; + } + + #[Override] + public function getUri(): UriInterface + { + return $this->uri; + } + + #[Override] + public function withUri(UriInterface $uri, bool $preserveHost = false): RequestInterface + { + $instance = clone $this; + $instance->uri = $uri; + $instance->requestTarget = $uri->getPath(); + + if ($preserveHost === true && ($this->hasHeader('Host') && $this->getHeaderLine('Host') !== '')) { + return $instance; + } + + if ($uri->getHost() === '') { + return $instance; + } + + return $instance->withHeader('Host', $uri->getHost()); + } +} diff --git a/_/HttpClient/Psr7/Response.php b/_/HttpClient/Psr7/Response.php new file mode 100644 index 0000000..03c580f --- /dev/null +++ b/_/HttpClient/Psr7/Response.php @@ -0,0 +1,39 @@ +statusCode >= 600 || $this->statusCode < 200) { + throw new InvalidArgumentException('invalid status code'); + } + } + + #[Override] + public function getStatusCode(): int + { + return $this->statusCode; + } + + #[Override] + public function withStatus(int $code, string $reasonPhrase = ''): ResponseInterface + { + return new self($code, $reasonPhrase); + } + + #[Override] + public function getReasonPhrase(): string + { + return $this->reasonPhrase; + } +} diff --git a/_/HttpClient/Psr7/Stream.php b/_/HttpClient/Psr7/Stream.php new file mode 100644 index 0000000..d482f93 --- /dev/null +++ b/_/HttpClient/Psr7/Stream.php @@ -0,0 +1,163 @@ +stream)); + + $this->meta = \stream_get_meta_data($this->stream); + } + + #[Override] + public function __toString(): string + { + $string = \stream_get_contents($this->stream, null, 0); + if ($string === false) { + throw new \RuntimeException('Unable to read stream'); + } + + return $string; + } + + #[Override] + public function close(): void + { + fclose($this->stream); + } + + /** + * @return resource + */ + #[Override] + public function detach() + { + $result = $this->stream; + unset($this->stream); + return $result; + } + + #[Override] + public function getSize(): ?int + { + return null; + } + + #[Override] + public function tell(): int + { + $pos = \ftell($this->stream); + if ($pos === false) { + throw new \RuntimeException('unable to determine stream position'); + } + + return $pos; + } + + #[Override] + public function eof(): bool + { + return \feof($this->stream); + } + + #[Override] + public function isSeekable(): bool + { + return $this->meta['seekable']; + } + + #[Override] + public function seek(int $offset, int $whence = SEEK_SET): void + { + \fseek($this->stream, $offset, $whence); + } + + #[Override] + public function rewind(): void + { + \fseek($this->stream, 0); + } + + #[Override] + public function isWritable(): bool + { + return false; + } + + #[Override] + public function write(string $string): int + { + throw new RuntimeException('not implemented'); + } + + #[Override] + public function isReadable(): bool + { + return true; + } + + #[Override] + public function read(int $length): string + { + \assert($length > 0); + + try { + $string = \fread($this->stream, $length); + } catch (Exception $e) { + throw new \RuntimeException('unable to read from stream', 0, $e); + } + + if (false === $string) { + throw new \RuntimeException('unable to read from stream'); + } + + return $string; + } + + #[Override] + public function getContents(): string + { + $string = \stream_get_contents($this->stream); + if ($string === false) { + throw new \RuntimeException('Unable to read stream'); + } + + return $string; + + } + + #[Override] + public function getMetadata(?string $key = null) + { + return $this->meta[$key] ?? null; + } +} diff --git a/_/HttpClient/Psr7/StreamFactory.php b/_/HttpClient/Psr7/StreamFactory.php new file mode 100644 index 0000000..dbcf481 --- /dev/null +++ b/_/HttpClient/Psr7/StreamFactory.php @@ -0,0 +1,39 @@ +map(fn (?string $result) => \strtolower($result ?? '')); + return optional(regexp('^[a-zA-Z0-9\-\.]+'))->map(fn (?string $result) => \strtolower($result ?? '')); } public static function port(): Parser diff --git a/tests/Psr7/RequestTest.php b/tests/Psr7/RequestTest.php new file mode 100644 index 0000000..28bc1e4 --- /dev/null +++ b/tests/Psr7/RequestTest.php @@ -0,0 +1,18 @@ +getResult()); } - public function test(): void + public function test_missing_number_after_port(): void { $parser = UriParser::port(); $result = $parser->run(new ParserInput(':')); static::assertTrue($result->isError()); + } + + public function test_regression_with_only_path(): void + { + $result = UriParser::parser()->run(new ParserInput('/foobar'))->getResult(); + + static::assertIsList($result); + static::assertCount(7, $result); + static::assertSame('', $result[2], 'host part empty'); + static::assertSame('/foobar', $result[4], 'uri part populated'); } } diff --git a/tests/bootstrap.php b/tests/bootstrap.php index 9df7ff4..543fccb 100644 --- a/tests/bootstrap.php +++ b/tests/bootstrap.php @@ -6,10 +6,14 @@ use verfriemelt\wrapped\_\Database\Driver\Postgres; use verfriemelt\wrapped\_\Database\Driver\SQLite; use verfriemelt\wrapped\_\DotEnv\DotEnv; +use verfriemelt\wrapped\_\HttpClient\Psr7\StreamFactory; +use verfriemelt\wrapped\_\HttpClient\Psr7\UriFactory; require_once __DIR__ . '/../vendor/autoload.php'; -define('TEST_ROOT', __DIR__); +const STREAM_FACTORY = StreamFactory::class; +const URI_FACTORY = UriFactory::class; +const TEST_ROOT = __DIR__; $dotenv = new DotEnv(); $dotenv->load( From 0ac1bee586007ea077258c1cd76430499fc123e0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E0=B2=A0=5F=E0=B2=A0?= Date: Thu, 21 Nov 2024 09:27:51 +0100 Subject: [PATCH 3/7] implment psr7/stream and psr7/streamfactory --- _/HttpClient/Psr7/Stream.php | 41 ++++++++++++++++++++++------- _/HttpClient/Psr7/StreamFactory.php | 2 +- tests/Psr7/StreamTest.php | 19 +++++++++++++ 3 files changed, 51 insertions(+), 11 deletions(-) create mode 100644 tests/Psr7/StreamTest.php diff --git a/_/HttpClient/Psr7/Stream.php b/_/HttpClient/Psr7/Stream.php index d482f93..c9acc8e 100644 --- a/_/HttpClient/Psr7/Stream.php +++ b/_/HttpClient/Psr7/Stream.php @@ -5,9 +5,9 @@ namespace verfriemelt\wrapped\_\HttpClient\Psr7; use Psr\Http\Message\StreamInterface; -use SebastianBergmann\Template\RuntimeException; use Exception; use Override; +use RuntimeException; final class Stream implements StreamInterface { @@ -43,7 +43,7 @@ public function __toString(): string { $string = \stream_get_contents($this->stream, null, 0); if ($string === false) { - throw new \RuntimeException('Unable to read stream'); + throw new RuntimeException('Unable to read stream'); } return $string; @@ -69,7 +69,14 @@ public function detach() #[Override] public function getSize(): ?int { - return null; + $stats = fstat($this->stream); + if ($stats === false) { + return null; + } + + assert(\array_key_exists('size', $stats)); + + return $stats['size']; } #[Override] @@ -77,7 +84,7 @@ public function tell(): int { $pos = \ftell($this->stream); if ($pos === false) { - throw new \RuntimeException('unable to determine stream position'); + throw new RuntimeException('unable to determine stream position'); } return $pos; @@ -98,25 +105,39 @@ public function isSeekable(): bool #[Override] public function seek(int $offset, int $whence = SEEK_SET): void { + if (!$this->meta['seekable']) { + throw new RuntimeException('stream not seekable'); + } + \fseek($this->stream, $offset, $whence); } #[Override] public function rewind(): void { - \fseek($this->stream, 0); + if (!$this->meta['seekable']) { + throw new RuntimeException('stream not seekable'); + } + + \rewind($this->stream); } #[Override] public function isWritable(): bool { - return false; + return \str_contains($this->meta['mode'], 'w'); } #[Override] public function write(string $string): int { - throw new RuntimeException('not implemented'); + $bytes = \fwrite($this->stream, $string); + + if ($bytes === false) { + throw new RuntimeException('write failed'); + } + + return $bytes; } #[Override] @@ -133,11 +154,11 @@ public function read(int $length): string try { $string = \fread($this->stream, $length); } catch (Exception $e) { - throw new \RuntimeException('unable to read from stream', 0, $e); + throw new RuntimeException('unable to read from stream', 0, $e); } if (false === $string) { - throw new \RuntimeException('unable to read from stream'); + throw new RuntimeException('unable to read from stream'); } return $string; @@ -148,7 +169,7 @@ public function getContents(): string { $string = \stream_get_contents($this->stream); if ($string === false) { - throw new \RuntimeException('Unable to read stream'); + throw new RuntimeException('Unable to read stream'); } return $string; diff --git a/_/HttpClient/Psr7/StreamFactory.php b/_/HttpClient/Psr7/StreamFactory.php index dbcf481..d1858fb 100644 --- a/_/HttpClient/Psr7/StreamFactory.php +++ b/_/HttpClient/Psr7/StreamFactory.php @@ -12,7 +12,7 @@ final class StreamFactory implements \Psr\Http\Message\StreamFactoryInterface #[Override] public function createStream(string $content = ''): StreamInterface { - $stream = \fopen('php://memory', 'r+'); + $stream = \fopen('php://temp', 'r+'); \assert(\is_resource($stream)); fwrite($stream, $content); \rewind($stream); diff --git a/tests/Psr7/StreamTest.php b/tests/Psr7/StreamTest.php new file mode 100644 index 0000000..a03f3c7 --- /dev/null +++ b/tests/Psr7/StreamTest.php @@ -0,0 +1,19 @@ +createStreamFromResource($data); + } +} From 9748700abcdab2d8427c2de20b54761d831c7694 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E0=B2=A0=5F=E0=B2=A0?= Date: Thu, 21 Nov 2024 12:23:48 +0100 Subject: [PATCH 4/7] implment psr7/serverrequest --- _/Http/Request/Request.php | 8 -- _/HttpClient/Psr7/Message.php | 23 +++- _/HttpClient/Psr7/Request.php | 21 ++- _/HttpClient/Psr7/Response.php | 6 + _/HttpClient/Psr7/ServerRequest.php | 197 ++++++++++++++++++++++++++++ tests/Psr7/ServerRequestTest.php | 18 +++ 6 files changed, 253 insertions(+), 20 deletions(-) create mode 100644 _/HttpClient/Psr7/ServerRequest.php create mode 100644 tests/Psr7/ServerRequestTest.php diff --git a/_/Http/Request/Request.php b/_/Http/Request/Request.php index 012196f..adc281c 100644 --- a/_/Http/Request/Request.php +++ b/_/Http/Request/Request.php @@ -11,21 +11,13 @@ class Request { private ParameterBag $request; - private ParameterBag $query; - private ParameterBag $attributes; - private ParameterBag $cookies; - private ParameterBag $server; - private ParameterBag $files; - private ParameterBag $content; - private ParameterBag $header; - private Session $session; public function __construct( diff --git a/_/HttpClient/Psr7/Message.php b/_/HttpClient/Psr7/Message.php index 20da9bb..1f73f68 100644 --- a/_/HttpClient/Psr7/Message.php +++ b/_/HttpClient/Psr7/Message.php @@ -12,14 +12,28 @@ abstract class Message implements MessageInterface { protected StreamInterface $body; + protected string $protocolVersion; /** @var array */ - private array $headers = []; - - private string $protocolVersion; + protected array $headers = []; /** @var array */ - private array $normalizedHeaderNames = []; + protected array $normalizedHeaderNames = []; + + /** + * @param array $headers + */ + public function __construct(array $headers = []) + { + foreach ($headers as $name => $value) { + if (!\is_array($value)) { + $value = [$value]; + } + + $this->normalizedHeaderNames[$this->normalizeHeaderName($name)] ??= $name; + $this->headers[$this->normalizeHeaderName($name)] = $value; + } + } #[Override] public function getProtocolVersion(): string @@ -102,7 +116,6 @@ public function withAddedHeader(string $name, $value): MessageInterface $instance->normalizedHeaderNames[$this->normalizeHeaderName($name)] ??= $name; return $instance; - } #[Override] diff --git a/_/HttpClient/Psr7/Request.php b/_/HttpClient/Psr7/Request.php index 380e308..e715367 100644 --- a/_/HttpClient/Psr7/Request.php +++ b/_/HttpClient/Psr7/Request.php @@ -11,19 +11,26 @@ class Request extends Message implements RequestInterface { - private UriInterface $uri; - - private string $requestMethod; - private string $requestTarget = '/'; - - public function __construct(string $method = 'GET', string|UriInterface $uri = '/') - { + protected UriInterface $uri; + + protected string $requestMethod; + protected string $requestTarget = '/'; + + /** + * @param array $headers + */ + public function __construct( + string $method = 'GET', + string|UriInterface $uri = '/', + array $headers = [], + ) { $this->requestMethod = $method; if (is_string($uri)) { $this->uri = new Uri($uri); } + parent::__construct($headers); } #[Override] diff --git a/_/HttpClient/Psr7/Response.php b/_/HttpClient/Psr7/Response.php index 03c580f..e89f9f7 100644 --- a/_/HttpClient/Psr7/Response.php +++ b/_/HttpClient/Psr7/Response.php @@ -10,13 +10,19 @@ final class Response extends Message implements ResponseInterface { + /** + * @param array $headers + */ public function __construct( public readonly int $statusCode = 200, public readonly string $reasonPhrase = '', + public array $headers = [], ) { if ($this->statusCode >= 600 || $this->statusCode < 200) { throw new InvalidArgumentException('invalid status code'); } + + parent::__construct($this->headers); } #[Override] diff --git a/_/HttpClient/Psr7/ServerRequest.php b/_/HttpClient/Psr7/ServerRequest.php new file mode 100644 index 0000000..8ff6418 --- /dev/null +++ b/_/HttpClient/Psr7/ServerRequest.php @@ -0,0 +1,197 @@ + $query + * @param array $attributes + * @param array $cookies + * @param array $server + * @param array $files + * @param array $headers + */ + public function __construct( + string $method = 'GET', + string|UriInterface $uri = '/', + array $query = [], + array $attributes = [], + array $cookies = [], + array $server = [], + array $files = [], + ?string $content = null, + array $headers = [], + ) { + parent::__construct($method, $uri, $headers); + + $this->query = new ParameterBag($query); + $this->attributes = new ParameterBag($attributes); + $this->cookies = new ParameterBag($cookies); + $this->server = new ParameterBag($server); + $this->files = new ParameterBag($files); + + if ($content !== null) { + parse_str($content, $payload); + $this->content = new ParameterBag($payload); + } else { + $this->content = new ParameterBag([]); + } + } + + /** + * @return array + */ + #[Override] + public function getServerParams(): array + { + return $this->server->all(); + } + + /** + * @return array + */ + #[Override] + public function getCookieParams(): array + { + return $this->cookies->all(); + } + + /** + * @param array $cookies + */ + #[Override] + public function withCookieParams(array $cookies): ServerRequestInterface + { + $instance = clone $this; + $instance->cookies = new ParameterBag($cookies); + return $instance; + } + + /** + * @return array + */ + #[Override] + public function getQueryParams(): array + { + return $this->query->all(); + } + + /** + * @param array $query + */ + #[Override] + public function withQueryParams(array $query): ServerRequestInterface + { + $instance = clone $this; + $instance->query = new ParameterBag($query); + return $instance; + } + + /** + * @return array + */ + #[Override] + public function getUploadedFiles(): array + { + return $this->files->all(); + } + + /** + * @param array $uploadedFiles + */ + #[Override] + public function withUploadedFiles(array $uploadedFiles): ServerRequestInterface + { + $instance = clone $this; + $instance->files = new ParameterBag($uploadedFiles); + return $instance; + } + + /** + * @return array + */ + #[Override] + public function getParsedBody() + { + return $this->content->all(); + } + + /** + * @param object|array|null $data + */ + #[Override] + public function withParsedBody($data): ServerRequestInterface + { + assert(is_array($data)); + + $instance = clone $this; + $instance->content = new ParameterBag($data); + return $instance; + } + + /** + * @return array + */ + #[Override] + public function getAttributes(): array + { + return $this->attributes->all(); + } + + #[Override] + public function getAttribute(string $name, $default = null) + { + \assert(\is_string($default) || \is_null($default)); + return $this->attributes->get($name, $default); + } + + #[Override] + public function withAttribute(string $name, $value): ServerRequestInterface + { + \assert(\is_string($value) || \is_null($value)); + $instance = clone $this; + + $data = $this->attributes->all(); + $data[$name] = $value; + + $instance->attributes = new ParameterBag($data); + return $instance; + } + + #[Override] + public function withoutAttribute(string $name): ServerRequestInterface + { + $instance = clone $this; + + $data = $this->attributes->all(); + unset($data[$name]); + + $instance->attributes = new ParameterBag($data); + return $instance; + } + + public function __clone(): void + { + $this->query = clone $this->query; + $this->attributes = clone $this->attributes; + $this->cookies = clone $this->cookies; + $this->server = clone $this->server; + $this->files = clone $this->files; + $this->content = clone $this->content; + } +} diff --git a/tests/Psr7/ServerRequestTest.php b/tests/Psr7/ServerRequestTest.php new file mode 100644 index 0000000..0a675d0 --- /dev/null +++ b/tests/Psr7/ServerRequestTest.php @@ -0,0 +1,18 @@ + Date: Thu, 21 Nov 2024 12:34:59 +0100 Subject: [PATCH 5/7] implment psr7/uploadedfile --- _/HttpClient/Psr7/UploadedFile.php | 82 ++++++++++++++++++++++++++++++ tests/Psr7/UploadedFileTest.php | 21 ++++++++ 2 files changed, 103 insertions(+) create mode 100644 _/HttpClient/Psr7/UploadedFile.php create mode 100644 tests/Psr7/UploadedFileTest.php diff --git a/_/HttpClient/Psr7/UploadedFile.php b/_/HttpClient/Psr7/UploadedFile.php new file mode 100644 index 0000000..1c933f9 --- /dev/null +++ b/_/HttpClient/Psr7/UploadedFile.php @@ -0,0 +1,82 @@ +errorCode, self::UPLOAD_ERRORS, true)); + } + + #[Override] + public function getStream(): StreamInterface + { + if ($this->moved) { + throw new RuntimeException('stream has moved to path'); + } + + return $this->stream; + } + + #[Override] + public function moveTo(string $targetPath): void + { + if ($this->moved) { + throw new RuntimeException('stream has moved to path'); + } + + $this->stream->rewind(); + \file_put_contents($targetPath, $this->stream->getContents()); + + $this->moved = true; + } + + #[Override] + public function getSize(): ?int + { + return $this->stream->getSize(); + } + + #[Override] + public function getError(): int + { + return $this->errorCode; + } + + #[Override] + public function getClientFilename(): ?string + { + return $this->filename; + } + + #[Override] + public function getClientMediaType(): ?string + { + return $this->mediaType; + } +} diff --git a/tests/Psr7/UploadedFileTest.php b/tests/Psr7/UploadedFileTest.php new file mode 100644 index 0000000..ef7361e --- /dev/null +++ b/tests/Psr7/UploadedFileTest.php @@ -0,0 +1,21 @@ +createStream('testing'); + + return new UploadedFile($stream, UPLOAD_ERR_OK, 'filename.txt', 'text/plain'); + } +} From 9c2f7b447960ff9554fc4e31a4af351d62d4821a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E0=B2=A0=5F=E0=B2=A0?= Date: Thu, 21 Nov 2024 12:44:30 +0100 Subject: [PATCH 6/7] add psr7 testsuite to ci --- .github/workflows/actions.yml | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/.github/workflows/actions.yml b/.github/workflows/actions.yml index 10426ac..c3c69dc 100644 --- a/.github/workflows/actions.yml +++ b/.github/workflows/actions.yml @@ -21,6 +21,27 @@ jobs: run: vendor/bin/phpunit env: DATABASE_DRIVER: none + + psr-7: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Setup PHP 8.3 + uses: shivammathur/setup-php@v2 + with: + php-version: 8.3 + + - name: composer install + run: composer install + + - name: run tests + run: vendor/bin/phpunit --testsuite=psr7 + env: + DATABASE_DRIVER: none int-psql: runs-on: ubuntu-latest From c009e650ad3c21982c03514ff070e9ab11ec152c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E0=B2=A0=5F=E0=B2=A0?= Date: Thu, 21 Nov 2024 12:50:11 +0100 Subject: [PATCH 7/7] cleanup --- phpstan.neon | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/phpstan.neon b/phpstan.neon index aa479f3..d6cf60a 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -32,8 +32,11 @@ parameters: - tests excludePaths: - - tests/integration/* - _/Formular/Template/* + bootstrapFiles: + - tests/bootstrap.php + + parallel: processTimeout: 30.0