From 6a31c2723c713dc63ffbb26e7a2e1b34ee00aade Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Thu, 20 Apr 2023 08:58:27 +0200 Subject: [PATCH] Allow psr/http-message v2 (#234) --- .github/workflows/static.yml | 2 +- composer.json | 4 +-- phpstan-baseline.neon | 5 ---- src/MessageTrait.php | 26 ++++++++++++---- src/RequestTrait.php | 16 ++++++++-- src/Response.php | 5 +++- src/ServerRequest.php | 18 ++++++++---- src/Stream.php | 33 ++------------------- src/StreamTrait.php | 57 ++++++++++++++++++++++++++++++++++++ src/Uri.php | 35 +++++++++++++++++----- tests/StreamTest.php | 3 +- 11 files changed, 142 insertions(+), 62 deletions(-) create mode 100644 src/StreamTrait.php diff --git a/.github/workflows/static.yml b/.github/workflows/static.yml index 1af3110..f3c88b7 100644 --- a/.github/workflows/static.yml +++ b/.github/workflows/static.yml @@ -61,7 +61,7 @@ jobs: php-version: 8.1 extensions: apcu, redis coverage: none - tools: vimeo/psalm:4.29.0 + tools: vimeo/psalm:5.9 - name: Download dependencies uses: ramsey/composer-install@v2 diff --git a/composer.json b/composer.json index 0f45a24..a676df5 100644 --- a/composer.json +++ b/composer.json @@ -16,13 +16,13 @@ ], "require": { "php": ">=7.2", - "psr/http-message": "^1.0", + "psr/http-message": "^1.1 || ^2.0", "php-http/message-factory": "^1.0", "psr/http-factory": "^1.0" }, "require-dev": { "phpunit/phpunit": "^7.5 || 8.5 || 9.4", - "php-http/psr7-integration-tests": "^1.0", + "php-http/psr7-integration-tests": "^1.0@dev", "http-interop/http-factory-tests": "^0.9", "symfony/error-handler": "^4.4" }, diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index 80991e8..9a0cf11 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -20,11 +20,6 @@ parameters: count: 1 path: src/ServerRequest.php - - - message: "#^Parameter \\#1 \\$callback of function set_error_handler expects \\(callable\\(int, string, string, int\\)\\: bool\\)\\|null, 'var_dump' given\\.$#" - count: 1 - path: src/Stream.php - - message: "#^Result of && is always false\\.$#" count: 1 diff --git a/src/MessageTrait.php b/src/MessageTrait.php index da34e58..7d02383 100644 --- a/src/MessageTrait.php +++ b/src/MessageTrait.php @@ -4,6 +4,7 @@ namespace Nyholm\Psr7; +use Psr\Http\Message\MessageInterface; use Psr\Http\Message\StreamInterface; /** @@ -34,7 +35,10 @@ public function getProtocolVersion(): string return $this->protocol; } - public function withProtocolVersion($version): self + /** + * @return static + */ + public function withProtocolVersion($version): MessageInterface { if (!\is_scalar($version)) { throw new \InvalidArgumentException('Protocol version must be a string'); @@ -81,7 +85,10 @@ public function getHeaderLine($header): string return \implode(', ', $this->getHeader($header)); } - public function withHeader($header, $value): self + /** + * @return static + */ + public function withHeader($header, $value): MessageInterface { $value = $this->validateAndTrimHeader($header, $value); $normalized = \strtr($header, 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', 'abcdefghijklmnopqrstuvwxyz'); @@ -96,7 +103,10 @@ public function withHeader($header, $value): self return $new; } - public function withAddedHeader($header, $value): self + /** + * @return static + */ + public function withAddedHeader($header, $value): MessageInterface { if (!\is_string($header) || '' === $header) { throw new \InvalidArgumentException('Header name must be an RFC 7230 compatible string'); @@ -108,7 +118,10 @@ public function withAddedHeader($header, $value): self return $new; } - public function withoutHeader($header): self + /** + * @return static + */ + public function withoutHeader($header): MessageInterface { if (!\is_string($header)) { throw new \InvalidArgumentException('Header name must be an RFC 7230 compatible string'); @@ -135,7 +148,10 @@ public function getBody(): StreamInterface return $this->stream; } - public function withBody(StreamInterface $body): self + /** + * @return static + */ + public function withBody(StreamInterface $body): MessageInterface { if ($body === $this->stream) { return $this; diff --git a/src/RequestTrait.php b/src/RequestTrait.php index 7c39bbb..2dbb3ab 100644 --- a/src/RequestTrait.php +++ b/src/RequestTrait.php @@ -4,6 +4,7 @@ namespace Nyholm\Psr7; +use Psr\Http\Message\RequestInterface; use Psr\Http\Message\UriInterface; /** @@ -40,7 +41,10 @@ public function getRequestTarget(): string return $target; } - public function withRequestTarget($requestTarget): self + /** + * @return static + */ + public function withRequestTarget($requestTarget): RequestInterface { if (!\is_string($requestTarget)) { throw new \InvalidArgumentException('Request target must be a string'); @@ -61,7 +65,10 @@ public function getMethod(): string return $this->method; } - public function withMethod($method): self + /** + * @return static + */ + public function withMethod($method): RequestInterface { if (!\is_string($method)) { throw new \InvalidArgumentException('Method must be a string'); @@ -78,7 +85,10 @@ public function getUri(): UriInterface return $this->uri; } - public function withUri(UriInterface $uri, $preserveHost = false): self + /** + * @return static + */ + public function withUri(UriInterface $uri, $preserveHost = false): RequestInterface { if ($uri === $this->uri) { return $this; diff --git a/src/Response.php b/src/Response.php index 9a26d2c..f3e2097 100644 --- a/src/Response.php +++ b/src/Response.php @@ -67,7 +67,10 @@ public function getReasonPhrase(): string return $this->reasonPhrase; } - public function withStatus($code, $reasonPhrase = ''): self + /** + * @return static + */ + public function withStatus($code, $reasonPhrase = ''): ResponseInterface { if (!\is_int($code) && !\is_string($code)) { throw new \InvalidArgumentException('Status code has to be an integer'); diff --git a/src/ServerRequest.php b/src/ServerRequest.php index 7f5022e..a3c5ba9 100644 --- a/src/ServerRequest.php +++ b/src/ServerRequest.php @@ -81,7 +81,7 @@ public function getUploadedFiles(): array /** * @return static */ - public function withUploadedFiles(array $uploadedFiles) + public function withUploadedFiles(array $uploadedFiles): ServerRequestInterface { $new = clone $this; $new->uploadedFiles = $uploadedFiles; @@ -97,7 +97,7 @@ public function getCookieParams(): array /** * @return static */ - public function withCookieParams(array $cookies) + public function withCookieParams(array $cookies): ServerRequestInterface { $new = clone $this; $new->cookieParams = $cookies; @@ -113,7 +113,7 @@ public function getQueryParams(): array /** * @return static */ - public function withQueryParams(array $query) + public function withQueryParams(array $query): ServerRequestInterface { $new = clone $this; $new->queryParams = $query; @@ -132,7 +132,7 @@ public function getParsedBody() /** * @return static */ - public function withParsedBody($data) + public function withParsedBody($data): ServerRequestInterface { if (!\is_array($data) && !\is_object($data) && null !== $data) { throw new \InvalidArgumentException('First parameter to withParsedBody MUST be object, array or null'); @@ -165,7 +165,10 @@ public function getAttribute($attribute, $default = null) return $this->attributes[$attribute]; } - public function withAttribute($attribute, $value): self + /** + * @return static + */ + public function withAttribute($attribute, $value): ServerRequestInterface { if (!\is_string($attribute)) { throw new \InvalidArgumentException('Attribute name must be a string'); @@ -177,7 +180,10 @@ public function withAttribute($attribute, $value): self return $new; } - public function withoutAttribute($attribute): self + /** + * @return static + */ + public function withoutAttribute($attribute): ServerRequestInterface { if (!\is_string($attribute)) { throw new \InvalidArgumentException('Attribute name must be a string'); diff --git a/src/Stream.php b/src/Stream.php index d173f35..9ed9bec 100644 --- a/src/Stream.php +++ b/src/Stream.php @@ -5,8 +5,6 @@ namespace Nyholm\Psr7; use Psr\Http\Message\StreamInterface; -use Symfony\Component\Debug\ErrorHandler as SymfonyLegacyErrorHandler; -use Symfony\Component\ErrorHandler\ErrorHandler as SymfonyErrorHandler; /** * @author Michael Dowling and contributors to guzzlehttp/psr7 @@ -17,6 +15,8 @@ */ class Stream implements StreamInterface { + use StreamTrait; + /** @var resource|null A resource reference */ private $stream; @@ -102,35 +102,6 @@ public function __destruct() $this->close(); } - /** - * @return string - */ - public function __toString() - { - try { - if ($this->isSeekable()) { - $this->seek(0); - } - - return $this->getContents(); - } catch (\Throwable $e) { - if (\PHP_VERSION_ID >= 70400) { - throw $e; - } - - if (\is_array($errorHandler = \set_error_handler('var_dump'))) { - $errorHandler = $errorHandler[0] ?? null; - } - \restore_error_handler(); - - if ($e instanceof \Error || $errorHandler instanceof SymfonyErrorHandler || $errorHandler instanceof SymfonyLegacyErrorHandler) { - return \trigger_error((string) $e, \E_USER_ERROR); - } - - return ''; - } - } - public function close(): void { if (isset($this->stream)) { diff --git a/src/StreamTrait.php b/src/StreamTrait.php new file mode 100644 index 0000000..41a3f9d --- /dev/null +++ b/src/StreamTrait.php @@ -0,0 +1,57 @@ += 70400 || (new \ReflectionMethod(StreamInterface::class, '__toString'))->hasReturnType()) { + /** + * @internal + */ + trait StreamTrait + { + public function __toString(): string + { + if ($this->isSeekable()) { + $this->seek(0); + } + + return $this->getContents(); + } + } +} else { + /** + * @internal + */ + trait StreamTrait + { + /** + * @return string + */ + public function __toString() + { + try { + if ($this->isSeekable()) { + $this->seek(0); + } + + return $this->getContents(); + } catch (\Throwable $e) { + if (\is_array($errorHandler = \set_error_handler('var_dump'))) { + $errorHandler = $errorHandler[0] ?? null; + } + \restore_error_handler(); + + if ($e instanceof \Error || $errorHandler instanceof SymfonyErrorHandler || $errorHandler instanceof SymfonyLegacyErrorHandler) { + return \trigger_error((string) $e, \E_USER_ERROR); + } + + return ''; + } + } + } +} diff --git a/src/Uri.php b/src/Uri.php index 0d2c975..621e2e7 100644 --- a/src/Uri.php +++ b/src/Uri.php @@ -140,7 +140,10 @@ public function getFragment(): string return $this->fragment; } - public function withScheme($scheme): self + /** + * @return static + */ + public function withScheme($scheme): UriInterface { if (!\is_string($scheme)) { throw new \InvalidArgumentException('Scheme must be a string'); @@ -157,7 +160,10 @@ public function withScheme($scheme): self return $new; } - public function withUserInfo($user, $password = null): self + /** + * @return static + */ + public function withUserInfo($user, $password = null): UriInterface { if (!\is_string($user)) { throw new \InvalidArgumentException('User must be a string'); @@ -182,7 +188,10 @@ public function withUserInfo($user, $password = null): self return $new; } - public function withHost($host): self + /** + * @return static + */ + public function withHost($host): UriInterface { if (!\is_string($host)) { throw new \InvalidArgumentException('Host must be a string'); @@ -198,7 +207,10 @@ public function withHost($host): self return $new; } - public function withPort($port): self + /** + * @return static + */ + public function withPort($port): UriInterface { if ($this->port === $port = $this->filterPort($port)) { return $this; @@ -210,7 +222,10 @@ public function withPort($port): self return $new; } - public function withPath($path): self + /** + * @return static + */ + public function withPath($path): UriInterface { if ($this->path === $path = $this->filterPath($path)) { return $this; @@ -222,7 +237,10 @@ public function withPath($path): self return $new; } - public function withQuery($query): self + /** + * @return static + */ + public function withQuery($query): UriInterface { if ($this->query === $query = $this->filterQueryAndFragment($query)) { return $this; @@ -234,7 +252,10 @@ public function withQuery($query): self return $new; } - public function withFragment($fragment): self + /** + * @return static + */ + public function withFragment($fragment): UriInterface { if ($this->fragment === $fragment = $this->filterQueryAndFragment($fragment)) { return $this; diff --git a/tests/StreamTest.php b/tests/StreamTest.php index a77d5c4..c9973da 100644 --- a/tests/StreamTest.php +++ b/tests/StreamTest.php @@ -4,6 +4,7 @@ use Nyholm\Psr7\Stream; use PHPUnit\Framework\TestCase; +use Psr\Http\Message\StreamInterface; use Symfony\Component\ErrorHandler\ErrorHandler as SymfonyErrorHandler; /** @@ -171,7 +172,7 @@ public function testCanDetachStream() $throws(function ($stream) { (string) $stream; }); - } else { + } elseif (!(new \ReflectionMethod(StreamInterface::class, '__toString'))->hasReturnType()) { $this->assertSame('', (string) $stream); SymfonyErrorHandler::register();