This commit is contained in:
2026-02-02 15:18:51 +01:00
parent 7a26dd69a5
commit ae0ee002ec
170 changed files with 7446 additions and 1519 deletions

View File

@@ -15,13 +15,13 @@
}
],
"require": {
"php": ">=7.1",
"psr/http-message": "^1.0",
"php-http/message-factory": "^1.0",
"php": ">=7.2",
"psr/http-message": "^1.1 || ^2.0",
"psr/http-factory": "^1.0"
},
"require-dev": {
"phpunit/phpunit": "^7.5 || 8.5 || 9.4",
"phpunit/phpunit": "^7.5 || ^8.5 || ^9.4",
"php-http/message-factory": "^1.0",
"php-http/psr7-integration-tests": "^1.0",
"http-interop/http-factory-tests": "^0.9",
"symfony/error-handler": "^4.4"
@@ -43,7 +43,7 @@
},
"extra": {
"branch-alias": {
"dev-master": "1.6-dev"
"dev-master": "1.8-dev"
}
}
}

View File

@@ -11,11 +11,19 @@ use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\StreamInterface;
use Psr\Http\Message\UriInterface;
if (!\interface_exists(MessageFactory::class)) {
throw new \LogicException('You cannot use "Nyholm\Psr7\Factory\HttplugFactory" as the "php-http/message-factory" package is not installed. Try running "composer require php-http/message-factory". Note that this package is deprecated, use "psr/http-factory" instead');
}
@\trigger_error('Class "Nyholm\Psr7\Factory\HttplugFactory" is deprecated since version 1.8, use "Nyholm\Psr7\Factory\Psr17Factory" instead.', \E_USER_DEPRECATED);
/**
* @author Tobias Nyholm <tobias.nyholm@gmail.com>
* @author Martijn van der Ven <martijn@vanderven.se>
*
* @final This class should never be extended. See https://github.com/Nyholm/psr7/blob/master/doc/final.md
*
* @deprecated since version 1.8, use Psr17Factory instead
*/
class HttplugFactory implements MessageFactory, StreamFactory, UriFactory
{

View File

@@ -57,7 +57,7 @@ class Psr17Factory implements RequestFactoryInterface, ResponseFactoryInterface,
return Stream::create($resource);
}
public function createUploadedFile(StreamInterface $stream, int $size = null, int $error = \UPLOAD_ERR_OK, string $clientFilename = null, string $clientMediaType = null): UploadedFileInterface
public function createUploadedFile(StreamInterface $stream, ?int $size = null, int $error = \UPLOAD_ERR_OK, ?string $clientFilename = null, ?string $clientMediaType = null): UploadedFileInterface
{
if (null === $size) {
$size = $stream->getSize();

View File

@@ -4,6 +4,7 @@ declare(strict_types=1);
namespace Nyholm\Psr7;
use Psr\Http\Message\MessageInterface;
use Psr\Http\Message\StreamInterface;
/**
@@ -34,7 +35,10 @@ trait MessageTrait
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 @@ trait MessageTrait
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 @@ trait MessageTrait
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 @@ trait MessageTrait
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 @@ trait MessageTrait
return $this->stream;
}
public function withBody(StreamInterface $body): self
/**
* @return static
*/
public function withBody(StreamInterface $body): MessageInterface
{
if ($body === $this->stream) {
return $this;

View File

@@ -4,6 +4,7 @@ declare(strict_types=1);
namespace Nyholm\Psr7;
use Psr\Http\Message\RequestInterface;
use Psr\Http\Message\UriInterface;
/**
@@ -40,7 +41,10 @@ trait RequestTrait
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 @@ trait RequestTrait
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 @@ trait RequestTrait
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;

View File

@@ -39,7 +39,7 @@ class Response implements ResponseInterface
* @param string $version Protocol version
* @param string|null $reason Reason phrase (when empty a default will be used based on the status code)
*/
public function __construct(int $status = 200, array $headers = [], $body = null, string $version = '1.1', string $reason = null)
public function __construct(int $status = 200, array $headers = [], $body = null, string $version = '1.1', ?string $reason = null)
{
// If we got no body, defer initialization of the stream until Response::getBody()
if ('' !== $body && null !== $body) {
@@ -67,7 +67,10 @@ class Response implements ResponseInterface
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');

View File

@@ -81,7 +81,7 @@ class ServerRequest implements ServerRequestInterface
/**
* @return static
*/
public function withUploadedFiles(array $uploadedFiles)
public function withUploadedFiles(array $uploadedFiles): ServerRequestInterface
{
$new = clone $this;
$new->uploadedFiles = $uploadedFiles;
@@ -97,7 +97,7 @@ class ServerRequest implements ServerRequestInterface
/**
* @return static
*/
public function withCookieParams(array $cookies)
public function withCookieParams(array $cookies): ServerRequestInterface
{
$new = clone $this;
$new->cookieParams = $cookies;
@@ -113,7 +113,7 @@ class ServerRequest implements ServerRequestInterface
/**
* @return static
*/
public function withQueryParams(array $query)
public function withQueryParams(array $query): ServerRequestInterface
{
$new = clone $this;
$new->queryParams = $query;
@@ -132,7 +132,7 @@ class ServerRequest implements ServerRequestInterface
/**
* @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 @@ class ServerRequest implements ServerRequestInterface
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 @@ class ServerRequest implements ServerRequestInterface
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');

View File

@@ -5,8 +5,6 @@ declare(strict_types=1);
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 @@ use Symfony\Component\ErrorHandler\ErrorHandler as SymfonyErrorHandler;
*/
class Stream implements StreamInterface
{
use StreamTrait;
/** @var resource|null A resource reference */
private $stream;
@@ -81,10 +81,14 @@ class Stream implements StreamInterface
}
if (\is_string($body)) {
$resource = \fopen('php://temp', 'rw+');
\fwrite($resource, $body);
\fseek($resource, 0);
$body = $resource;
if (200000 <= \strlen($body)) {
$body = self::openZvalStream($body);
} else {
$resource = \fopen('php://memory', 'r+');
\fwrite($resource, $body);
\fseek($resource, 0);
$body = $resource;
}
}
if (!\is_resource($body)) {
@@ -102,35 +106,6 @@ class Stream implements StreamInterface
$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)) {
@@ -285,11 +260,19 @@ class Stream implements StreamInterface
throw new \RuntimeException('Stream is detached');
}
if (false === $contents = @\stream_get_contents($this->stream)) {
throw new \RuntimeException('Unable to read stream contents: ' . (\error_get_last()['message'] ?? ''));
}
$exception = null;
return $contents;
\set_error_handler(static function ($type, $message) use (&$exception) {
throw $exception = new \RuntimeException('Unable to read stream contents: ' . $message);
});
try {
return \stream_get_contents($this->stream);
} catch (\Throwable $e) {
throw $e === $exception ? $e : new \RuntimeException('Unable to read stream contents: ' . $e->getMessage(), 0, $e);
} finally {
\restore_error_handler();
}
}
/**
@@ -313,4 +296,104 @@ class Stream implements StreamInterface
return $meta[$key] ?? null;
}
private static function openZvalStream(string $body)
{
static $wrapper;
$wrapper ?? \stream_wrapper_register('Nyholm-Psr7-Zval', $wrapper = \get_class(new class() {
public $context;
private $data;
private $position = 0;
public function stream_open(): bool
{
$this->data = \stream_context_get_options($this->context)['Nyholm-Psr7-Zval']['data'];
\stream_context_set_option($this->context, 'Nyholm-Psr7-Zval', 'data', null);
return true;
}
public function stream_read(int $count): string
{
$result = \substr($this->data, $this->position, $count);
$this->position += \strlen($result);
return $result;
}
public function stream_write(string $data): int
{
$this->data = \substr_replace($this->data, $data, $this->position, \strlen($data));
$this->position += \strlen($data);
return \strlen($data);
}
public function stream_tell(): int
{
return $this->position;
}
public function stream_eof(): bool
{
return \strlen($this->data) <= $this->position;
}
public function stream_stat(): array
{
return [
'mode' => 33206, // POSIX_S_IFREG | 0666
'nlink' => 1,
'rdev' => -1,
'size' => \strlen($this->data),
'blksize' => -1,
'blocks' => -1,
];
}
public function stream_seek(int $offset, int $whence): bool
{
if (\SEEK_SET === $whence && (0 <= $offset && \strlen($this->data) >= $offset)) {
$this->position = $offset;
} elseif (\SEEK_CUR === $whence && 0 <= $offset) {
$this->position += $offset;
} elseif (\SEEK_END === $whence && (0 > $offset && 0 <= $offset = \strlen($this->data) + $offset)) {
$this->position = $offset;
} else {
return false;
}
return true;
}
public function stream_set_option(): bool
{
return true;
}
public function stream_truncate(int $new_size): bool
{
if ($new_size) {
$this->data = \substr($this->data, 0, $new_size);
$this->position = \min($this->position, $new_size);
} else {
$this->data = '';
$this->position = 0;
}
return true;
}
}));
$context = \stream_context_create(['Nyholm-Psr7-Zval' => ['data' => $body]]);
if (!$stream = @\fopen('Nyholm-Psr7-Zval://', 'r+', false, $context)) {
\stream_wrapper_register('Nyholm-Psr7-Zval', $wrapper);
$stream = \fopen('Nyholm-Psr7-Zval://', 'r+', false, $context);
}
return $stream;
}
}

View File

@@ -0,0 +1,57 @@
<?php
declare(strict_types=1);
namespace Nyholm\Psr7;
use Psr\Http\Message\StreamInterface;
use Symfony\Component\Debug\ErrorHandler as SymfonyLegacyErrorHandler;
use Symfony\Component\ErrorHandler\ErrorHandler as SymfonyErrorHandler;
if (\PHP_VERSION_ID >= 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 '';
}
}
}
}

View File

@@ -140,7 +140,10 @@ class Uri implements UriInterface
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 @@ class Uri implements UriInterface
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 @@ class Uri implements UriInterface
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 @@ class Uri implements UriInterface
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 @@ class Uri implements UriInterface
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 @@ class Uri implements UriInterface
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 @@ class Uri implements UriInterface
return $new;
}
public function withFragment($fragment): self
/**
* @return static
*/
public function withFragment($fragment): UriInterface
{
if ($this->fragment === $fragment = $this->filterQueryAndFragment($fragment)) {
return $this;