From 74e2a84352351f25c094f856017657dd46ec76b5 Mon Sep 17 00:00:00 2001 From: Alexander Nortung Date: Tue, 27 May 2025 17:02:21 +0200 Subject: [PATCH 1/5] feat!: update psr7 http message to version 2 --- composer.json | 58 ++++++++--------- src/BodyStream.php | 100 ++++++++++++++++++++++------- src/RequestUri.php | 73 +++++++++------------ src/WP_REST_PSR7_Request.php | 46 ++++++------- src/WP_REST_PSR7_Response.php | 35 +++++----- src/WP_REST_PSR7_ServerRequest.php | 25 ++++---- 6 files changed, 190 insertions(+), 147 deletions(-) diff --git a/composer.json b/composer.json index 313427a..cc43b8f 100644 --- a/composer.json +++ b/composer.json @@ -1,31 +1,31 @@ { - "name": "papertower/wp-rest-api-psr7", - "description": "Provides PSR-7 and WP REST API Response and Request classes", - "type": "library", - "license": "MIT", - "require": { - "psr/http-message": "^1.0.1" - }, - "repositories": [ - { - "type": "git", - "url": "git@github.com:papertower/WP-REST-API-PSR7.git" - } - ], - "keywords": [ - "wp rest api", - "wordpress", - "psr-7" - ], - "authors": [ - { - "name": "Jason Adams", - "email": "jason@papertower.com" - } - ], - "autoload": { - "psr-4": { - "WPRestApi\\PSR7\\": "src/" - } - } + "name": "papertower/wp-rest-api-psr7", + "description": "Provides PSR-7 and WP REST API Response and Request classes", + "type": "library", + "license": "MIT", + "require": { + "psr/http-message": "^2.0", + "league/uri": "^7.5" + }, + "repositories": [ + { + "type": "git", + "url": "git@github.com:papertower/WP-REST-API-PSR7.git" + } + ], + "keywords": ["wp rest api", "wordpress", "psr-7"], + "authors": [ + { + "name": "Jason Adams", + "email": "jason@papertower.com" + } + ], + "autoload": { + "psr-4": { + "WPRestApi\\PSR7\\": "src/" + } + }, + "require-dev": { + "php-stubs/wordpress-stubs": "^6.8" + } } diff --git a/src/BodyStream.php b/src/BodyStream.php index ddc7ed0..4501fa0 100644 --- a/src/BodyStream.php +++ b/src/BodyStream.php @@ -18,16 +18,41 @@ class BodyStream implements StreamInterface /** @var array Hash of readable and writable stream types */ private static $readWriteHash = [ 'read' => [ - 'r' => true, 'w+' => true, 'r+' => true, 'x+' => true, 'c+' => true, - 'rb' => true, 'w+b' => true, 'r+b' => true, 'x+b' => true, - 'c+b' => true, 'rt' => true, 'w+t' => true, 'r+t' => true, - 'x+t' => true, 'c+t' => true, 'a+' => true + 'r' => true, + 'w+' => true, + 'r+' => true, + 'x+' => true, + 'c+' => true, + 'rb' => true, + 'w+b' => true, + 'r+b' => true, + 'x+b' => true, + 'c+b' => true, + 'rt' => true, + 'w+t' => true, + 'r+t' => true, + 'x+t' => true, + 'c+t' => true, + 'a+' => true ], 'write' => [ - 'w' => true, 'w+' => true, 'rw' => true, 'r+' => true, 'x+' => true, - 'c+' => true, 'wb' => true, 'w+b' => true, 'r+b' => true, - 'x+b' => true, 'c+b' => true, 'w+t' => true, 'r+t' => true, - 'x+t' => true, 'c+t' => true, 'a' => true, 'a+' => true + 'w' => true, + 'w+' => true, + 'rw' => true, + 'r+' => true, + 'x+' => true, + 'c+' => true, + 'wb' => true, + 'w+b' => true, + 'r+b' => true, + 'x+b' => true, + 'c+b' => true, + 'w+t' => true, + 'r+t' => true, + 'x+t' => true, + 'c+t' => true, + 'a' => true, + 'a+' => true ] ]; @@ -95,7 +120,7 @@ public function __toString() } } - public function getContents() + public function getContents(): string { $contents = stream_get_contents($this->stream); @@ -106,7 +131,7 @@ public function getContents() return $contents; } - public function close() + public function close(): void { if (isset($this->stream)) { if (is_resource($this->stream)) { @@ -130,7 +155,10 @@ public function detach() return $result; } - public function getSize() + /** + * {@inheritDoc} + */ + public function getSize(): ?int { if ($this->size !== null) { return $this->size; @@ -154,27 +182,42 @@ public function getSize() return null; } - public function isReadable() + /** + * {@inheritDoc} + */ + public function isReadable(): bool { return $this->readable; } - public function isWritable() + /** + * {@inheritDoc} + */ + public function isWritable(): bool { return $this->writable; } - public function isSeekable() + /** + * {@inheritDoc} + */ + public function isSeekable(): bool { return $this->seekable; } - public function eof() + /** + * {@inheritDoc} + */ + public function eof(): bool { return !$this->stream || feof($this->stream); } - public function tell() + /** + * {@inheritDoc} + */ + public function tell(): int { $result = ftell($this->stream); @@ -185,22 +228,31 @@ public function tell() return $result; } - public function rewind() + /** + * {@inheritDoc} + */ + public function rewind(): void { $this->seek(0); } - public function seek($offset, $whence = SEEK_SET) + /** + * {@inheritDoc} + */ + public function seek($offset, $whence = SEEK_SET): void { if (!$this->seekable) { throw new \RuntimeException('Stream is not seekable'); } elseif (fseek($this->stream, $offset, $whence) === -1) { throw new \RuntimeException('Unable to seek to stream position ' - . $offset . ' with whence ' . var_export($whence, true)); + . $offset . ' with whence ' . var_export($whence, true)); } } - public function read($length) + /** + * {@inheritDoc} + */ + public function read($length): string { if (!$this->readable) { throw new \RuntimeException('Cannot read from non-readable stream'); @@ -221,7 +273,10 @@ public function read($length) return $string; } - public function write($string) + /** + * {@inheritDoc} + */ + public function write($string): int { if (!$this->writable) { throw new \RuntimeException('Cannot write to a non-writable stream'); @@ -252,4 +307,5 @@ public function getMetadata($key = null) return isset($meta[$key]) ? $meta[$key] : null; } -} \ No newline at end of file +} + diff --git a/src/RequestUri.php b/src/RequestUri.php index 4e915f4..3739812 100644 --- a/src/RequestUri.php +++ b/src/RequestUri.php @@ -3,7 +3,7 @@ namespace WPRestApi\PSR7; - +use League\Uri\UriResolver; use Psr\Http\Message\UriInterface; class RequestUri implements UriInterface @@ -116,7 +116,7 @@ public static function composeComponents($scheme, $authority, $path, $query, $fr $uri .= $scheme . ':'; } - if ($authority != ''|| $scheme === 'file') { + if ($authority != '' || $scheme === 'file') { $uri .= '//' . $authority; } @@ -146,7 +146,7 @@ public static function composeComponents($scheme, $authority, $path, $query, $fr public static function isDefaultPort(UriInterface $uri) { return $uri->getPort() === null - || (isset(self::$defaultPorts[$uri->getScheme()]) && $uri->getPort() === self::$defaultPorts[$uri->getScheme()]); + || (isset(self::$defaultPorts[$uri->getScheme()]) && $uri->getPort() === self::$defaultPorts[$uri->getScheme()]); } /** @@ -200,9 +200,9 @@ public static function isNetworkPathReference(UriInterface $uri) public static function isAbsolutePathReference(UriInterface $uri) { return $uri->getScheme() === '' - && $uri->getAuthority() === '' - && isset($uri->getPath()[0]) - && $uri->getPath()[0] === '/'; + && $uri->getAuthority() === '' + && isset($uri->getPath()[0]) + && $uri->getPath()[0] === '/'; } /** @@ -218,8 +218,8 @@ public static function isAbsolutePathReference(UriInterface $uri) public static function isRelativePathReference(UriInterface $uri) { return $uri->getScheme() === '' - && $uri->getAuthority() === '' - && (!isset($uri->getPath()[0]) || $uri->getPath()[0] !== '/'); + && $uri->getAuthority() === '' + && (!isset($uri->getPath()[0]) || $uri->getPath()[0] !== '/'); } /** @@ -241,29 +241,14 @@ public static function isSameDocumentReference(UriInterface $uri, UriInterface $ $uri = UriResolver::resolve($base, $uri); return ($uri->getScheme() === $base->getScheme()) - && ($uri->getAuthority() === $base->getAuthority()) - && ($uri->getPath() === $base->getPath()) - && ($uri->getQuery() === $base->getQuery()); + && ($uri->getAuthority() === $base->getAuthority()) + && ($uri->getPath() === $base->getPath()) + && ($uri->getQuery() === $base->getQuery()); } return $uri->getScheme() === '' && $uri->getAuthority() === '' && $uri->getPath() === '' && $uri->getQuery() === ''; } - /** - * Removes dot segments from a path and returns the new path. - * - * @param string $path - * - * @return string - * - * @deprecated since version 1.4. Use UriResolver::removeDotSegments instead. - * @see UriResolver::removeDotSegments - */ - public static function removeDotSegments($path) - { - return UriResolver::removeDotSegments($path); - } - /** * Converts the relative URI into a new URI that is resolved against the base URI. * @@ -371,12 +356,12 @@ public static function fromParts(array $parts) return $uri; } - public function getScheme() + public function getScheme(): string { return $this->scheme; } - public function getAuthority() + public function getAuthority(): string { $authority = $this->host; if ($this->userInfo !== '') { @@ -390,37 +375,37 @@ public function getAuthority() return $authority; } - public function getUserInfo() + public function getUserInfo(): string { return $this->userInfo; } - public function getHost() + public function getHost(): string { return $this->host; } - public function getPort() + public function getPort(): int|null { return $this->port; } - public function getPath() + public function getPath(): string { return $this->path; } - public function getQuery() + public function getQuery(): string { return $this->query; } - public function getFragment() + public function getFragment(): string { return $this->fragment; } - public function withScheme($scheme) + public function withScheme($scheme): static { $scheme = $this->filterScheme($scheme); @@ -436,7 +421,7 @@ public function withScheme($scheme) return $new; } - public function withUserInfo($user, $password = null) + public function withUserInfo($user, $password = null): static { $info = $user; if ($password != '') { @@ -454,7 +439,7 @@ public function withUserInfo($user, $password = null) return $new; } - public function withHost($host) + public function withHost($host): static { $host = $this->filterHost($host); @@ -469,7 +454,7 @@ public function withHost($host) return $new; } - public function withPort($port) + public function withPort($port): static { $port = $this->filterPort($port); @@ -485,7 +470,7 @@ public function withPort($port) return $new; } - public function withPath($path) + public function withPath($path): static { $path = $this->filterPath($path); @@ -500,7 +485,7 @@ public function withPath($path) return $new; } - public function withQuery($query) + public function withQuery($query): static { $query = $this->filterQueryAndFragment($query); @@ -514,7 +499,7 @@ public function withQuery($query) return $new; } - public function withFragment($fragment) + public function withFragment($fragment): static { $fragment = $this->filterQueryAndFragment($fragment); @@ -688,11 +673,11 @@ private function validateState() } elseif (isset($this->path[0]) && $this->path[0] !== '/') { @trigger_error( 'The path of a URI with an authority must start with a slash "/" or be empty. Automagically fixing the URI ' . - 'by adding a leading slash to the path is deprecated since version 1.4 and will throw an exception instead.', + 'by adding a leading slash to the path is deprecated since version 1.4 and will throw an exception instead.', E_USER_DEPRECATED ); - $this->path = '/'. $this->path; + $this->path = '/' . $this->path; //throw new \InvalidArgumentException('The path of a URI with an authority must start with a slash "/" or be empty'); } } -} \ No newline at end of file +} diff --git a/src/WP_REST_PSR7_Request.php b/src/WP_REST_PSR7_Request.php index cefb714..ce794e3 100644 --- a/src/WP_REST_PSR7_Request.php +++ b/src/WP_REST_PSR7_Request.php @@ -44,7 +44,7 @@ public function __construct($method = '', $route = '', array $attributes = []) * * @return string HTTP protocol version. */ - public function getProtocolVersion() + public function getProtocolVersion(): string { return $this->protocol; } @@ -63,7 +63,7 @@ public function getProtocolVersion() * * @return static */ - public function withProtocolVersion($version) + public function withProtocolVersion($version): static { $clone = clone $this; $clone->protocol = $version; @@ -96,7 +96,7 @@ public function withProtocolVersion($version) * key MUST be a header name, and each value MUST be an array of strings * for that header. */ - public function getHeaders() + public function getHeaders(): array { return $this->headers; } @@ -110,7 +110,7 @@ public function getHeaders() * name using a case-insensitive string comparison. Returns false if * no matching header name is found in the message. */ - public function hasHeader($name) + public function hasHeader($name): bool { return isset($this->headers[$name]); } @@ -130,7 +130,7 @@ public function hasHeader($name) * header. If the header does not appear in the message, this method MUST * return an empty array. */ - public function getHeader($name) + public function getHeader($name): array { $header = $this->get_header_as_array($name); @@ -157,7 +157,7 @@ public function getHeader($name) * concatenated together using a comma. If the header does not appear in * the message, this method MUST return an empty string. */ - public function getHeaderLine($name) + public function getHeaderLine($name): string { $line = $this->get_header($name); @@ -180,7 +180,7 @@ public function getHeaderLine($name) * @return static * @throws \InvalidArgumentException for invalid header names or values. */ - public function withHeader($name, $value) + public function withHeader($name, $value): static { $clone = clone $this; $clone->set_header($name, $value); @@ -205,7 +205,7 @@ public function withHeader($name, $value) * @return static * @throws \InvalidArgumentException for invalid header names or values. */ - public function withAddedHeader($name, $value) + public function withAddedHeader($name, $value): static { $clone = clone $this; $clone->add_header($name, $value); @@ -226,7 +226,7 @@ public function withAddedHeader($name, $value) * * @return static */ - public function withoutHeader($name) + public function withoutHeader($name): static { $clone = clone $this; $clone->remove_header($name); @@ -239,9 +239,9 @@ public function withoutHeader($name) * * @return StreamInterface Returns the body as a stream. */ - public function getBody() + public function getBody(): StreamInterface { - if ( ! isset($this->bodyStream)) { + if (! isset($this->bodyStream)) { $stream = fopen('php://temp', 'r+'); if ($this->body !== '') { fwrite($stream, $this->body); @@ -268,7 +268,7 @@ public function getBody() * @return static * @throws \InvalidArgumentException When the body is not valid. */ - public function withBody(StreamInterface $body) + public function withBody(StreamInterface $body): static { if ($body === $this->bodyStream) { return $this; @@ -297,7 +297,7 @@ public function withBody(StreamInterface $body) * * @return string */ - public function getRequestTarget() + public function getRequestTarget(): string { if (null !== $this->requestTarget) { return $this->requestTarget; @@ -309,7 +309,7 @@ public function getRequestTarget() } $query = $this->uri->getQuery(); - if ( ! empty($query)) { + if (! empty($query)) { $target .= "?{$query}"; } @@ -335,10 +335,10 @@ public function getRequestTarget() * * @return static */ - public function withRequestTarget($requestTarget) + public function withRequestTarget($requestTarget): static { if (strpos($requestTarget, ' ')) { - throw new InvalidArgumentException( + throw new \InvalidArgumentException( 'Invalid request target provided; cannot contain whitespace' ); } @@ -354,7 +354,7 @@ public function withRequestTarget($requestTarget) * * @return string Returns the request method. */ - public function getMethod() + public function getMethod(): string { return $this->method; } @@ -375,7 +375,7 @@ public function getMethod() * @return static * @throws \InvalidArgumentException for invalid HTTP methods. */ - public function withMethod($method) + public function withMethod($method): static { $clone = clone $this; $clone->method = $method; @@ -392,7 +392,7 @@ public function withMethod($method) * @return UriInterface Returns a UriInterface instance * representing the URI of the request. */ - public function getUri() + public function getUri(): UriInterface { return $this->uri; } @@ -429,7 +429,7 @@ public function getUri() * * @return static */ - public function withUri(UriInterface $uri, $preserveHost = false) + public function withUri(UriInterface $uri, $preserveHost = false): static { if ($uri === $this->uri) { return $this; @@ -438,8 +438,8 @@ public function withUri(UriInterface $uri, $preserveHost = false) $clone = clone $this; $clone->uri = $uri; - if ( ! $preserveHost) { - $clone->updateHostFromUri(); + if (! $preserveHost) { + $clone->updateHostFromUri(); } return $clone; @@ -467,4 +467,4 @@ private function updateHostFromUri() // See: http://tools.ietf.org/html/rfc7230#section-5.4 $this->headers = [$header => [$host]] + $this->headers; } -} \ No newline at end of file +} diff --git a/src/WP_REST_PSR7_Response.php b/src/WP_REST_PSR7_Response.php index 2439d62..d7b2a77 100644 --- a/src/WP_REST_PSR7_Response.php +++ b/src/WP_REST_PSR7_Response.php @@ -125,7 +125,7 @@ public function __construct($data = null, $status = 200, array $headers = []) * * @return string HTTP protocol version. */ - public function getProtocolVersion() + public function getProtocolVersion(): string { return $this->protocol; } @@ -144,7 +144,7 @@ public function getProtocolVersion() * * @return static */ - public function withProtocolVersion($version) + public function withProtocolVersion($version): static { $clone = clone $this; $clone->protocol = $version; @@ -177,7 +177,7 @@ public function withProtocolVersion($version) * key MUST be a header name, and each value MUST be an array of strings * for that header. */ - public function getHeaders() + public function getHeaders(): array { return $this->get_headers(); } @@ -191,7 +191,7 @@ public function getHeaders() * name using a case-insensitive string comparison. Returns false if * no matching header name is found in the message. */ - public function hasHeader($name) + public function hasHeader($name): bool { return isset($this->headers[$name]); } @@ -211,7 +211,7 @@ public function hasHeader($name) * header. If the header does not appear in the message, this method MUST * return an empty array. */ - public function getHeader($name) + public function getHeader($name): array { return isset($this->headers[$name]) ? $this->headers[$name] : []; } @@ -236,7 +236,7 @@ public function getHeader($name) * concatenated together using a comma. If the header does not appear in * the message, this method MUST return an empty string. */ - public function getHeaderLine($name) + public function getHeaderLine($name): string { return implode(',', $this->getHeader($name)); } @@ -257,7 +257,7 @@ public function getHeaderLine($name) * @return static * @throws \InvalidArgumentException for invalid header names or values. */ - public function withHeader($name, $value) + public function withHeader($name, $value): static { $clone = clone $this; $clone->header($name, $value); @@ -282,7 +282,7 @@ public function withHeader($name, $value) * @return static * @throws \InvalidArgumentException for invalid header names or values. */ - public function withAddedHeader($name, $value) + public function withAddedHeader($name, $value): static { $clone = clone $this; $clone->header($name, $value, false); @@ -303,7 +303,7 @@ public function withAddedHeader($name, $value) * * @return static */ - public function withoutHeader($name) + public function withoutHeader($name): static { $clone = clone $this; unset($clone->headers[$name]); @@ -316,9 +316,9 @@ public function withoutHeader($name) * * @return StreamInterface Returns the body as a stream. */ - public function getBody() + public function getBody(): StreamInterface { - if ( ! isset($this->bodyStream)) { + if (! isset($this->bodyStream)) { $stream = fopen('php://temp', 'r+'); if ($this->data !== '') { fwrite($stream, $this->data); @@ -345,7 +345,7 @@ public function getBody() * @return static * @throws \InvalidArgumentException When the body is not valid. */ - public function withBody(StreamInterface $body) + public function withBody(StreamInterface $body): static { if ($body === $this->bodyStream) { return $this; @@ -366,7 +366,7 @@ public function withBody(StreamInterface $body) * * @return int Status code. */ - public function getStatusCode() + public function getStatusCode(): int { return $this->get_status(); } @@ -393,7 +393,7 @@ public function getStatusCode() * @return static * @throws \InvalidArgumentException For invalid status code arguments. */ - public function withStatus($code, $reasonPhrase = '') + public function withStatus($code, $reasonPhrase = ''): static { $clone = clone $this; $clone->status = (int)$code; @@ -420,17 +420,18 @@ public function withStatus($code, $reasonPhrase = '') * @link http://www.iana.org/assignments/http-status-codes/http-status-codes.xhtml * @return string Reason phrase; must return an empty string if none present. */ - public function getReasonPhrase() + public function getReasonPhrase(): string { return $this->reasonPhrase; } public function get_data() { - if ( isset($this->bodyStream) ) { + if (isset($this->bodyStream)) { return json_decode($this->bodyStream); } else { return parent::get_data(); } } -} \ No newline at end of file +} + diff --git a/src/WP_REST_PSR7_ServerRequest.php b/src/WP_REST_PSR7_ServerRequest.php index bfbe68a..4d4422d 100644 --- a/src/WP_REST_PSR7_ServerRequest.php +++ b/src/WP_REST_PSR7_ServerRequest.php @@ -27,7 +27,7 @@ public function __construct($method = '', $route = '', array $attributes = []) * * @return array */ - public function getServerParams() + public function getServerParams(): array { return $this->serverParams; } @@ -42,7 +42,7 @@ public function getServerParams() * * @return array */ - public function getCookieParams() + public function getCookieParams(): array { return $this->cookieParams; } @@ -65,7 +65,7 @@ public function getCookieParams() * * @return static */ - public function withCookieParams(array $cookies) + public function withCookieParams(array $cookies): static { $clone = clone $this; $clone->cookieParams = $cookies; @@ -85,7 +85,7 @@ public function withCookieParams(array $cookies) * * @return array */ - public function getQueryParams() + public function getQueryParams(): array { return $this->get_query_params(); } @@ -113,7 +113,7 @@ public function getQueryParams() * * @return static */ - public function withQueryParams(array $query) + public function withQueryParams(array $query): static { $clone = clone $this; $clone->params['GET'] = $query; @@ -133,7 +133,7 @@ public function withQueryParams(array $query) * @return array An array tree of UploadedFileInterface instances; an empty * array MUST be returned if no data is present. */ - public function getUploadedFiles() + public function getUploadedFiles(): array { return $this->get_file_params(); } @@ -150,7 +150,7 @@ public function getUploadedFiles() * @return static * @throws \InvalidArgumentException if an invalid structure is provided. */ - public function withUploadedFiles(array $uploadedFiles) + public function withUploadedFiles(array $uploadedFiles): static { $clone = clone $this; $clone->params['FILES'] = $uploadedFiles; @@ -206,7 +206,7 @@ public function getParsedBody() * @throws \InvalidArgumentException if an unsupported argument type is * provided. */ - public function withParsedBody($data) + public function withParsedBody($data): static { $clone = clone $this; $clone->params['POST'] = $data; @@ -225,7 +225,7 @@ public function withParsedBody($data) * * @return array Attributes derived from the request. */ - public function getAttributes() + public function getAttributes(): array { return $this->get_attributes(); } @@ -269,7 +269,7 @@ public function getAttribute($name, $default = null) * * @return static */ - public function withAttribute($name, $value) + public function withAttribute($name, $value): static { $clone = clone $this; $clone->attributes[$name] = $value; @@ -293,11 +293,12 @@ public function withAttribute($name, $value) * * @return static */ - public function withoutAttribute($name) + public function withoutAttribute($name): static { $clone = clone $this; unset($clone->attributes[$name]); return $clone; } -} \ No newline at end of file +} + From b3286b2872c4e8c707037b97a9a3cf532bee08ce Mon Sep 17 00:00:00 2001 From: Alexander Nortung Date: Wed, 28 May 2025 09:56:40 +0200 Subject: [PATCH 2/5] chore: Added phpstan and resolved errors --- .editorconfig | 10 +++++++ composer.json | 10 ++++++- phpstan.neon | 8 ++++++ src/BodyStream.php | 17 ++++++----- src/RequestUri.php | 46 +++++++----------------------- src/WP_REST_PSR7_Request.php | 13 +++++---- src/WP_REST_PSR7_Response.php | 5 ++-- src/WP_REST_PSR7_ServerRequest.php | 8 ++---- 8 files changed, 58 insertions(+), 59 deletions(-) create mode 100644 .editorconfig create mode 100644 phpstan.neon diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..b602ea2 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,10 @@ +root = true + +# Unix-style newlines with a newline ending every file +[*] +end_of_line = lf +insert_final_newline = true + +[*.neon] +indent_style = space +indent_size = 4 diff --git a/composer.json b/composer.json index cc43b8f..524e8ae 100644 --- a/composer.json +++ b/composer.json @@ -26,6 +26,14 @@ } }, "require-dev": { - "php-stubs/wordpress-stubs": "^6.8" + "php-stubs/wordpress-stubs": "^6.8", + "phpstan/phpstan": "^2.1", + "szepeviktor/phpstan-wordpress": "^2.0", + "phpstan/extension-installer": "^1.4" + }, + "config": { + "allow-plugins": { + "phpstan/extension-installer": true + } } } diff --git a/phpstan.neon b/phpstan.neon new file mode 100644 index 0000000..c59622a --- /dev/null +++ b/phpstan.neon @@ -0,0 +1,8 @@ +parameters: + level: 6 + paths: + - src + ignoreErrors: + - identifier: missingType.iterableValue + - identifier: throws.unusedType + - identifier: missingType.generics diff --git a/src/BodyStream.php b/src/BodyStream.php index 4501fa0..e6db162 100644 --- a/src/BodyStream.php +++ b/src/BodyStream.php @@ -7,13 +7,13 @@ class BodyStream implements StreamInterface { - private $stream; - private $size; - private $seekable; - private $readable; - private $writable; - private $uri; - private $customMetadata; + private mixed $stream; + private mixed $size; + private bool $seekable; + private bool $readable; + private bool $writable; + private mixed $uri; + private mixed $customMetadata; /** @var array Hash of readable and writable stream types */ private static $readWriteHash = [ @@ -93,7 +93,7 @@ public function __construct($stream, $options = []) $this->uri = $this->getMetadata('uri'); } - public function __get($name) + public function __get(string $name): void { if ($name == 'stream') { throw new \RuntimeException('The stream is detached'); @@ -308,4 +308,3 @@ public function getMetadata($key = null) return isset($meta[$key]) ? $meta[$key] : null; } } - diff --git a/src/RequestUri.php b/src/RequestUri.php index 3739812..57a27f3 100644 --- a/src/RequestUri.php +++ b/src/RequestUri.php @@ -16,7 +16,7 @@ class RequestUri implements UriInterface */ const HTTP_DEFAULT_HOST = 'localhost'; - private static $defaultPorts = [ + private static array $defaultPorts = [ 'http' => 80, 'https' => 443, 'ftp' => 21, @@ -30,9 +30,9 @@ class RequestUri implements UriInterface 'ldap' => 389, ]; - private static $charUnreserved = 'a-zA-Z0-9_\-\.~'; - private static $charSubDelims = '!\$&\'\(\)\*\+,;='; - private static $replaceQuery = ['=' => '%3D', '&' => '%26']; + private static string $charUnreserved = 'a-zA-Z0-9_\-\.~'; + private static string $charSubDelims = '!\$&\'\(\)\*\+,;='; + private static array $replaceQuery = ['=' => '%3D', '&' => '%26']; /** @var string Uri scheme. */ private $scheme = ''; @@ -518,7 +518,7 @@ public function withFragment($fragment): static * * @param array $parts Array of parse_url parts to apply. */ - private function applyParts(array $parts) + private function applyParts(array $parts): void { $this->scheme = isset($parts['scheme']) ? $this->filterScheme($parts['scheme']) @@ -550,15 +550,9 @@ private function applyParts(array $parts) * @param string $scheme * * @return string - * - * @throws \InvalidArgumentException If the scheme is invalid. */ - private function filterScheme($scheme) + private function filterScheme(string $scheme) { - if (!is_string($scheme)) { - throw new \InvalidArgumentException('Scheme must be a string'); - } - return strtolower($scheme); } @@ -566,15 +560,9 @@ private function filterScheme($scheme) * @param string $host * * @return string - * - * @throws \InvalidArgumentException If the host is invalid. */ - private function filterHost($host) + private function filterHost(string $host) { - if (!is_string($host)) { - throw new \InvalidArgumentException('Host must be a string'); - } - return strtolower($host); } @@ -601,7 +589,7 @@ private function filterPort($port) return $port; } - private function removeDefaultPort() + private function removeDefaultPort(): void { if ($this->port !== null && self::isDefaultPort($this)) { $this->port = null; @@ -614,15 +602,9 @@ private function removeDefaultPort() * @param string $path * * @return string - * - * @throws \InvalidArgumentException If the path is invalid. */ - private function filterPath($path) + private function filterPath(string $path) { - if (!is_string($path)) { - throw new \InvalidArgumentException('Path must be a string'); - } - return preg_replace_callback( '/(?:[^' . self::$charUnreserved . self::$charSubDelims . '%:@\/]++|%(?![A-Fa-f0-9]{2}))/', [$this, 'rawurlencodeMatchZero'], @@ -636,15 +618,9 @@ private function filterPath($path) * @param string $str * * @return string - * - * @throws \InvalidArgumentException If the query or fragment is invalid. */ private function filterQueryAndFragment($str) { - if (!is_string($str)) { - throw new \InvalidArgumentException('Query and fragment must be a string'); - } - return preg_replace_callback( '/(?:[^' . self::$charUnreserved . self::$charSubDelims . '%:@\/\?]++|%(?![A-Fa-f0-9]{2}))/', [$this, 'rawurlencodeMatchZero'], @@ -652,12 +628,12 @@ private function filterQueryAndFragment($str) ); } - private function rawurlencodeMatchZero(array $match) + private function rawurlencodeMatchZero(array $match): string { return rawurlencode($match[0]); } - private function validateState() + private function validateState(): void { if ($this->host === '' && ($this->scheme === 'http' || $this->scheme === 'https')) { $this->host = self::HTTP_DEFAULT_HOST; diff --git a/src/WP_REST_PSR7_Request.php b/src/WP_REST_PSR7_Request.php index ce794e3..79200db 100644 --- a/src/WP_REST_PSR7_Request.php +++ b/src/WP_REST_PSR7_Request.php @@ -10,13 +10,14 @@ class WP_REST_PSR7_Request extends \WP_REST_Request implements RequestInterface { - protected $protocol = '1.1'; - protected $bodyStream; - protected $uri; - protected $requestTarget; + protected string $protocol = '1.1'; + protected BodyStream|StreamInterface|null $bodyStream; + protected RequestUri|UriInterface $uri; + protected ?string $requestTarget; - public static function fromRequest(\WP_REST_Request $request) + public static function fromRequest(\WP_REST_Request $request): static { + /** @phpstan-ignore new.static */ $psr7_request = new static( $request->get_method(), $request->get_route(), @@ -445,7 +446,7 @@ public function withUri(UriInterface $uri, $preserveHost = false): static return $clone; } - private function updateHostFromUri() + private function updateHostFromUri(): void { $host = $this->uri->getHost(); diff --git a/src/WP_REST_PSR7_Response.php b/src/WP_REST_PSR7_Response.php index d7b2a77..47aa49d 100644 --- a/src/WP_REST_PSR7_Response.php +++ b/src/WP_REST_PSR7_Response.php @@ -77,7 +77,7 @@ class WP_REST_PSR7_Response extends \WP_REST_Response implements ResponseInterfa protected $protocol = '1.1'; /** - * @var BodyStream Stream for the data + * @var BodyStream|StreamInterface|null Stream for the data */ protected $bodyStream; @@ -105,7 +105,7 @@ public static function fromPSR7Response(ResponseInterface $response) /** * Adds the reasonPhrase property to the parent constructor. * - * @param null $data + * @param string|null $data * @param int $status * @param array $headers */ @@ -434,4 +434,3 @@ public function get_data() } } } - diff --git a/src/WP_REST_PSR7_ServerRequest.php b/src/WP_REST_PSR7_ServerRequest.php index 4d4422d..550e4ba 100644 --- a/src/WP_REST_PSR7_ServerRequest.php +++ b/src/WP_REST_PSR7_ServerRequest.php @@ -8,8 +8,8 @@ class WP_REST_PSR7_ServerRequest extends WP_REST_PSR7_Request implements ServerRequestInterface { - protected $serverParams; - protected $cookieParams; + protected array $serverParams; + protected array $cookieParams; public function __construct($method = '', $route = '', array $attributes = []) { @@ -203,8 +203,7 @@ public function getParsedBody() * typically be in an array or object. * * @return static - * @throws \InvalidArgumentException if an unsupported argument type is - * provided. + * @throws \InvalidArgumentException if an unsupported argument type is provided. */ public function withParsedBody($data): static { @@ -301,4 +300,3 @@ public function withoutAttribute($name): static return $clone; } } - From 304cfcb5973e7cfed723e5d27faa6421f46a81e3 Mon Sep 17 00:00:00 2001 From: Alexander Nortung Date: Wed, 28 May 2025 10:02:48 +0200 Subject: [PATCH 3/5] feat: Added ci workflow ci: change from phpstan action to simple run --- .github/workflows/ci.yml | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 .github/workflows/ci.yml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..81bf77c --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,21 @@ +name: CI + +on: + push: + pull_request: + types: + - synchronize + - opened + +jobs: + build: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v3 + - name: Fix git issue + run: git config --global --add safe.directory /app + - name: Composer install + uses: php-actions/composer@v6 + - name: Check code with phpstan + run: vendor/bin/phpstan analyze From 38ec9d6fb3162bb8bc0e2d685f54dba12b1ee223 Mon Sep 17 00:00:00 2001 From: Alexander Nortung Date: Wed, 28 May 2025 10:23:32 +0200 Subject: [PATCH 4/5] chore: removed repositories from composer.json --- composer.json | 6 ------ 1 file changed, 6 deletions(-) diff --git a/composer.json b/composer.json index 524e8ae..8832d72 100644 --- a/composer.json +++ b/composer.json @@ -7,12 +7,6 @@ "psr/http-message": "^2.0", "league/uri": "^7.5" }, - "repositories": [ - { - "type": "git", - "url": "git@github.com:papertower/WP-REST-API-PSR7.git" - } - ], "keywords": ["wp rest api", "wordpress", "psr-7"], "authors": [ { From dbd4942d066aea5bcf334513c8b5c107eaf0967c Mon Sep 17 00:00:00 2001 From: Alexander Nortung Date: Tue, 10 Jun 2025 10:26:16 +0200 Subject: [PATCH 5/5] fix(response): convert headers correctly --- src/WP_REST_PSR7_Response.php | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/src/WP_REST_PSR7_Response.php b/src/WP_REST_PSR7_Response.php index 47aa49d..3c723ab 100644 --- a/src/WP_REST_PSR7_Response.php +++ b/src/WP_REST_PSR7_Response.php @@ -95,10 +95,24 @@ class WP_REST_PSR7_Response extends \WP_REST_Response implements ResponseInterfa */ public static function fromPSR7Response(ResponseInterface $response) { + // Since PSR7 response has the headers as an array of arrays, + // we need to convert it to a simple associative array + $headers = []; + $responseHeaders = $response->getHeaders(); + foreach ($responseHeaders as $name => $values) { + // If for some reason the values is a string, we set it directly + if (is_string($values)) { + $headers[$name] = $values; + } + // Set the first value as the header value + if (isset($values[0])) { + $headers[$name] = $values[0]; + } + } return new self( (string)$response->getBody(), $response->getStatusCode(), - $response->getHeaders() + $headers, ); }