diff --git a/.php_cs b/.php_cs
index 5ebdd8e..d3f1b34 100644
--- a/.php_cs
+++ b/.php_cs
@@ -25,6 +25,7 @@ return PhpCsFixer\Config::create()
'php_unit_strict' => true,
'phpdoc_order' => true,
'semicolon_after_instruction' => true,
+ 'single_import_per_statement' => false,
'strict_comparison' => true,
'strict_param' => true,
'concat_space' => ['spacing' => 'one'],
diff --git a/LICENSE b/LICENSE
index 09407b4..3423f5d 100644
--- a/LICENSE
+++ b/LICENSE
@@ -1,21 +1,127 @@
-MIT License
-
-Copyright (c) 2017 Rancoud
-
-Permission is hereby granted, free of charge, to any person obtaining a copy
-of this software and associated documentation files (the "Software"), to deal
-in the Software without restriction, including without limitation the rights
-to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-copies of the Software, and to permit persons to whom the Software is
-furnished to do so, subject to the following conditions:
-
-The above copyright notice and this permission notice shall be included in all
-copies or substantial portions of the Software.
-
-THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+Copyright (c) 2014 PHP Framework Interoperability Group
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
+
+MIT License
+
+Copyright (c) 2018 PHP-FIG
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+
+The MIT License (MIT)
+
+Copyright (c) 2016 PHP Framework Interoperability Group
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+
+Copyright (c) 2017 PHP Framework Interoperability Group
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
+
+MIT License
+
+Copyright (c) 2016 Tobias Nyholm
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+
+MIT License
+
+Copyright (c) 2017 Rancoud
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
\ No newline at end of file
diff --git a/README.md b/README.md
index d44e6c8..b9714ac 100644
--- a/README.md
+++ b/README.md
@@ -22,6 +22,8 @@ $stream = (new Rancoud\Http\Message\Factory())->createStream('foobar');
### Methods
* createRequest(method: string, uri: mixed): RequestInterface
* createResponse([code: int = 200], [reasonPhrase: string = '']): ResponseInterface
+* createResponseBody([code: int = 200], [body: mixed = null]): ResponseInterface
+* createRedirection(location: string): ResponseInterface
* createStream([content: string = '']): StreamInterface
* createStreamFromFile(filename: string, [mode: string = 'r']): StreamInterface
* createStreamFromResource(resource: mixed): StreamInterface
@@ -141,7 +143,7 @@ $stream = (new Rancoud\Http\Message\Factory())->createStream('foobar');
* getReasonPhrase(): string
* getStatusCode(): int
* hasHeader(name: string): bool
-* send(): void
+* send([bodyChunkSize: int = 8192]): void
* withAddedHeader(name: string, value: mixed): self
* withBody(body: StreamInterface): self
* withHeader(name: string, value: mixed): self
diff --git a/phpcs.xml b/phpcs.xml
index 083c4d4..9c50928 100644
--- a/phpcs.xml
+++ b/phpcs.xml
@@ -8,7 +8,7 @@
src
*/src/Psr/*
-
+
diff --git a/src/Message/Factory/Factory.php b/src/Message/Factory/Factory.php
index 75919a6..6047c1d 100644
--- a/src/Message/Factory/Factory.php
+++ b/src/Message/Factory/Factory.php
@@ -4,26 +4,19 @@
namespace Rancoud\Http\Message\Factory;
-use InvalidArgumentException;
-use Psr\Http\Message\RequestFactoryInterface;
-use Psr\Http\Message\RequestInterface;
-use Psr\Http\Message\ResponseFactoryInterface;
-use Psr\Http\Message\ResponseInterface;
-use Psr\Http\Message\ServerRequestFactoryInterface;
-use Psr\Http\Message\ServerRequestInterface;
-use Psr\Http\Message\StreamFactoryInterface;
-use Psr\Http\Message\StreamInterface;
-use Psr\Http\Message\UploadedFileFactoryInterface;
-use Psr\Http\Message\UploadedFileInterface;
-use Psr\Http\Message\UriFactoryInterface;
-use Psr\Http\Message\UriInterface;
-use Rancoud\Http\Message\Request;
-use Rancoud\Http\Message\Response;
-use Rancoud\Http\Message\ServerRequest;
-use Rancoud\Http\Message\Stream;
-use Rancoud\Http\Message\UploadedFile;
-use Rancoud\Http\Message\Uri;
-use RuntimeException;
+use Psr\Http\Message\{RequestFactoryInterface,
+ RequestInterface,
+ ResponseFactoryInterface,
+ ResponseInterface,
+ ServerRequestFactoryInterface,
+ ServerRequestInterface,
+ StreamFactoryInterface,
+ StreamInterface,
+ UploadedFileFactoryInterface,
+ UploadedFileInterface,
+ UriFactoryInterface,
+ UriInterface};
+use Rancoud\Http\Message\{Request, Response, ServerRequest, Stream, UploadedFile, Uri};
/**
* Class Factory.
@@ -31,11 +24,10 @@
class Factory implements RequestFactoryInterface, ResponseFactoryInterface, ServerRequestFactoryInterface, StreamFactoryInterface, UploadedFileFactoryInterface, UriFactoryInterface
{
/**
- * @param string $method
- * @param $uri
+ * @param string $method
+ * @param string|UriInterface $uri
*
- * @throws InvalidArgumentException
- * @throws RuntimeException
+ * @throws \InvalidArgumentException
*
* @return RequestInterface
*/
@@ -48,8 +40,7 @@ public function createRequest(string $method, $uri): RequestInterface
* @param int $code
* @param string $reasonPhrase
*
- * @throws InvalidArgumentException
- * @throws RuntimeException
+ * @throws \InvalidArgumentException
*
* @return ResponseInterface
*/
@@ -58,10 +49,35 @@ public function createResponse(int $code = 200, string $reasonPhrase = ''): Resp
return new Response($code, [], null, '1.1', $reasonPhrase);
}
+ /**
+ * @param int $code
+ * @param string|resource|StreamInterface|null $body
+ *
+ * @throws \InvalidArgumentException
+ *
+ * @return Response
+ */
+ public function createResponseBody(int $code = 200, $body = null): Response
+ {
+ return new Response($code, [], $body, '1.1');
+ }
+
+ /**
+ * @param string $location
+ *
+ * @throws \InvalidArgumentException
+ *
+ * @return Response
+ */
+ public function createRedirection(string $location): Response
+ {
+ return new Response(301, ['Location' => $location], null, '1.1');
+ }
+
/**
* @param string $content
*
- * @throws InvalidArgumentException
+ * @throws \InvalidArgumentException
*
* @return StreamInterface
*/
@@ -74,33 +90,33 @@ public function createStream(string $content = ''): StreamInterface
* @param string $filename
* @param string $mode
*
- * @throws InvalidArgumentException
- * @throws RuntimeException
+ * @throws \InvalidArgumentException
+ * @throws \RuntimeException
*
* @return StreamInterface
*/
public function createStreamFromFile(string $filename, string $mode = 'r'): StreamInterface
{
if (!\file_exists($filename)) {
- throw new RuntimeException(\sprintf('The file %s doesn\'t exist.', $filename));
+ throw new \RuntimeException(\sprintf('The file %s doesn\'t exist.', $filename));
}
if (!\in_array($mode[0], ['r', 'w', 'a', 'x', 'c'], true)) {
- throw new InvalidArgumentException(\sprintf('The mode %s is invalid.', $mode));
+ throw new \InvalidArgumentException(\sprintf('The mode %s is invalid.', $mode));
}
$resource = \fopen($filename, $mode);
if ($resource === false) {
- throw new RuntimeException(\sprintf('The file %s cannot be opened.', $filename));
+ throw new \RuntimeException(\sprintf('The file %s cannot be opened.', $filename));
}
return Stream::create($resource);
}
/**
- * @param $resource
+ * @param resource $resource
*
- * @throws InvalidArgumentException
+ * @throws \InvalidArgumentException
*
* @return StreamInterface
*/
@@ -116,7 +132,7 @@ public function createStreamFromResource($resource): StreamInterface
* @param string|null $clientFilename
* @param string|null $clientMediaType
*
- * @throws InvalidArgumentException
+ * @throws \InvalidArgumentException
*
* @return UploadedFileInterface
*/
@@ -137,7 +153,7 @@ public function createUploadedFile(
/**
* @param string $uri
*
- * @throws InvalidArgumentException
+ * @throws \InvalidArgumentException
*
* @return UriInterface
*/
@@ -149,11 +165,11 @@ public function createUri(string $uri = ''): UriInterface
/**
* @param array $server
*
- * @throws InvalidArgumentException
+ * @throws \InvalidArgumentException
*
- * @return UriInterface
+ * @return Uri
*/
- public function createUriFromArray(array $server): UriInterface
+ public function createUriFromArray(array $server): Uri
{
$uri = new Uri('');
@@ -190,12 +206,11 @@ public function createUriFromArray(array $server): UriInterface
}
/**
- * @param string $method
- * @param $uri
- * @param array $serverParams
+ * @param string $method
+ * @param string|UriInterface $uri
+ * @param array $serverParams
*
- * @throws InvalidArgumentException
- * @throws \RuntimeException
+ * @throws \InvalidArgumentException
*
* @return ServerRequestInterface
*/
@@ -207,12 +222,11 @@ public function createServerRequest(string $method, $uri, array $serverParams =
/**
* @param array $server
*
- * @throws InvalidArgumentException
- * @throws RuntimeException
+ * @throws \InvalidArgumentException
*
- * @return ServerRequestInterface
+ * @return ServerRequest
*/
- public function createServerRequestFromArray(array $server): ServerRequestInterface
+ public function createServerRequestFromArray(array $server): ServerRequest
{
return new ServerRequest(
$this->getMethodFromEnvironment($server),
@@ -232,10 +246,9 @@ public function createServerRequestFromArray(array $server): ServerRequestInterf
* @param array $post
* @param array $files
*
- * @throws InvalidArgumentException
- * @throws RuntimeException
+ * @throws \InvalidArgumentException
*
- * @return ServerRequestInterface
+ * @return ServerRequest
*/
public function createServerRequestFromArrays(
array $server,
@@ -244,7 +257,7 @@ public function createServerRequestFromArrays(
array $get,
array $post,
array $files
- ): ServerRequestInterface {
+ ): ServerRequest {
$method = $this->getMethodFromEnvironment($server);
$uri = $this->getUriFromEnvironmentWithHTTP($server);
@@ -259,23 +272,21 @@ public function createServerRequestFromArrays(
->withCookieParams($cookie)
->withQueryParams($get)
->withParsedBody($post)
- ->withUploadedFiles(self::normalizeFiles($files));
+ ->withUploadedFiles(static::normalizeFiles($files));
}
/**
- * @throws InvalidArgumentException
- * @throws RuntimeException
+ * @throws \InvalidArgumentException
*
- * @return ServerRequestInterface
+ * @return ServerRequest
*/
- public function createServerRequestFromGlobals(): ServerRequestInterface
+ public function createServerRequestFromGlobals(): ServerRequest
{
$server = $_SERVER;
if (!\array_key_exists('REQUEST_METHOD', $server)) {
$server['REQUEST_METHOD'] = 'GET';
}
- $headers = [];
if (\function_exists('\getallheaders')) {
$headers = \getallheaders();
} else {
@@ -288,14 +299,14 @@ public function createServerRequestFromGlobals(): ServerRequestInterface
/**
* @param array $environment
*
- * @throws InvalidArgumentException
+ * @throws \InvalidArgumentException
*
* @return string
*/
protected function getMethodFromEnvironment(array $environment): string
{
if (!isset($environment['REQUEST_METHOD'])) {
- throw new InvalidArgumentException('Cannot determine HTTP method');
+ throw new \InvalidArgumentException('Cannot determine HTTP method');
}
return $environment['REQUEST_METHOD'];
@@ -304,7 +315,7 @@ protected function getMethodFromEnvironment(array $environment): string
/**
* @param array $environment
*
- * @throws InvalidArgumentException
+ * @throws \InvalidArgumentException
*
* @return UriInterface
*/
@@ -321,7 +332,7 @@ protected function getUriFromEnvironmentWithHTTP(array $environment): UriInterfa
/**
* @param array $files
*
- * @throws InvalidArgumentException
+ * @throws \InvalidArgumentException
*
* @return array
*/
@@ -333,13 +344,13 @@ protected static function normalizeFiles(array $files): array
if ($value instanceof UploadedFileInterface) {
$normalized[$key] = $value;
} elseif (\is_array($value) && isset($value['tmp_name'])) {
- $normalized[$key] = self::createUploadedFileFromSpec($value);
+ $normalized[$key] = static::createUploadedFileFromSpec($value);
} elseif (\is_array($value)) {
- $normalized[$key] = self::normalizeFiles($value);
+ $normalized[$key] = static::normalizeFiles($value);
continue;
} else {
- throw new InvalidArgumentException('Invalid value in files specification');
+ throw new \InvalidArgumentException('Invalid value in files specification');
}
}
@@ -349,14 +360,14 @@ protected static function normalizeFiles(array $files): array
/**
* @param array $value
*
- * @throws InvalidArgumentException
+ * @throws \InvalidArgumentException
*
* @return array|UploadedFile
*/
protected static function createUploadedFileFromSpec(array $value)
{
if (\is_array($value['tmp_name'])) {
- return self::normalizeNestedFileSpec($value);
+ return static::normalizeNestedFileSpec($value);
}
return new UploadedFile(
@@ -371,7 +382,7 @@ protected static function createUploadedFileFromSpec(array $value)
/**
* @param array $files
*
- * @throws InvalidArgumentException
+ * @throws \InvalidArgumentException
*
* @return array
*/
@@ -387,7 +398,7 @@ protected static function normalizeNestedFileSpec(array $files = []): array
'name' => $files['name'][$key],
'type' => $files['type'][$key],
];
- $normalizedFiles[$key] = self::createUploadedFileFromSpec($spec);
+ $normalizedFiles[$key] = static::createUploadedFileFromSpec($spec);
}
return $normalizedFiles;
diff --git a/src/Message/MessageTrait.php b/src/Message/MessageTrait.php
index bdb8389..73ce233 100644
--- a/src/Message/MessageTrait.php
+++ b/src/Message/MessageTrait.php
@@ -4,9 +4,7 @@
namespace Rancoud\Http\Message;
-use InvalidArgumentException;
use Psr\Http\Message\StreamInterface;
-use Rancoud\Http\Message\Factory\Factory;
/**
* Trait MessageTrait.
@@ -19,6 +17,9 @@ trait MessageTrait
/** @var string */
protected static $patternHeaderValue = "@^[ \t\x21-\x7E\x80-\xFF]*$@";
+ /** @var array */
+ protected static $validProtocols = ['0.9', '1.0', '1.1', '2'];
+
/** @var array */
protected $headers = [];
@@ -28,7 +29,7 @@ trait MessageTrait
/** @var string */
protected $protocol = '1.1';
- /** @var StreamInterface */
+ /** @var StreamInterface|null */
protected $stream;
/**
@@ -42,7 +43,7 @@ public function getProtocolVersion(): string
/**
* @param string $version
*
- * @throws InvalidArgumentException
+ * @throws \InvalidArgumentException
*
* @return self
*/
@@ -71,14 +72,14 @@ public function getHeaders(): array
/**
* @param string $name
*
- * @throws InvalidArgumentException
+ * @throws \InvalidArgumentException
*
* @return bool
*/
public function hasHeader($name): bool
{
if (!\is_string($name)) {
- throw new InvalidArgumentException('Header name must be a string');
+ throw new \InvalidArgumentException('Header name must be a string');
}
return isset($this->headerNames[\mb_strtolower($name)]);
@@ -87,19 +88,19 @@ public function hasHeader($name): bool
/**
* @param string $name
*
- * @throws InvalidArgumentException
+ * @throws \InvalidArgumentException
*
* @return array
*/
public function getHeader($name): array
{
if (!\is_string($name)) {
- throw new InvalidArgumentException('Header name must be a string');
+ throw new \InvalidArgumentException('Header name must be a string');
}
$name = \mb_strtolower($name);
- if (!isset($this->headerNames[\mb_strtolower($name)])) {
+ if (!isset($this->headerNames[$name])) {
return [];
}
@@ -111,31 +112,31 @@ public function getHeader($name): array
/**
* @param string $name
*
- * @throws InvalidArgumentException
+ * @throws \InvalidArgumentException
*
* @return string
*/
public function getHeaderLine($name): string
{
if (!\is_string($name)) {
- throw new InvalidArgumentException('Header name must be a string');
+ throw new \InvalidArgumentException('Header name must be a string');
}
return \implode(', ', $this->getHeader($name));
}
/**
- * @param string $name
- * @param mixed $value
+ * @param string $name
+ * @param string|int|float|array $value
*
- * @throws InvalidArgumentException
+ * @throws \InvalidArgumentException
*
* @return self
*/
public function withHeader($name, $value): self
{
if (!\is_string($name) || $name === '') {
- throw new InvalidArgumentException('Header name must be non-empty string');
+ throw new \InvalidArgumentException('Header name must be non-empty string');
}
$value = $this->validateAndTrimHeader($name, $value);
@@ -152,17 +153,17 @@ public function withHeader($name, $value): self
}
/**
- * @param string $name
- * @param mixed $value
+ * @param string $name
+ * @param string|int|float|array $value
*
- * @throws InvalidArgumentException
+ * @throws \InvalidArgumentException
*
* @return self
*/
public function withAddedHeader($name, $value): self
{
if (!\is_string($name) || $name === '') {
- throw new InvalidArgumentException('Header name must be non-empty string');
+ throw new \InvalidArgumentException('Header name must be non-empty string');
}
$new = clone $this;
@@ -174,14 +175,14 @@ public function withAddedHeader($name, $value): self
/**
* @param string $name
*
- * @throws InvalidArgumentException
+ * @throws \InvalidArgumentException
*
* @return self
*/
public function withoutHeader($name): self
{
if (!\is_string($name) || $name === '') {
- throw new InvalidArgumentException('Header name must be non-empty string');
+ throw new \InvalidArgumentException('Header name must be non-empty string');
}
$normalized = \mb_strtolower($name);
@@ -199,14 +200,14 @@ public function withoutHeader($name): self
}
/**
- * @throws InvalidArgumentException
+ * @throws \InvalidArgumentException
*
* @return StreamInterface
*/
public function getBody(): StreamInterface
{
if (!$this->stream) {
- $this->stream = (new Factory())->createStream('');
+ $this->stream = Stream::create('');
}
return $this->stream;
@@ -232,7 +233,7 @@ public function withBody(StreamInterface $body): self
/**
* @param array $headers
*
- * @throws InvalidArgumentException
+ * @throws \InvalidArgumentException
*/
protected function setHeaders(array $headers): void
{
@@ -253,32 +254,32 @@ protected function setHeaders(array $headers): void
* @param string $header
* @param mixed $values
*
- * @throws InvalidArgumentException
+ * @throws \InvalidArgumentException
*
* @return array
*/
protected function validateAndTrimHeader($header, $values): array
{
- if (!\is_string($header) || \preg_match(self::$patternHeaderName, $header) !== 1) {
- throw new InvalidArgumentException('Header name must be RFC 7230 compatible string.');
+ if (!\is_string($header) || \preg_match(static::$patternHeaderName, $header) !== 1) {
+ throw new \InvalidArgumentException('Header name must be RFC 7230 compatible string.');
}
if (!\is_array($values)) {
- if ((!\is_numeric($values) && !\is_string($values)) || \preg_match(self::$patternHeaderValue, (string) $values) !== 1) {
- throw new InvalidArgumentException('Header value must be RFC 7230 compatible string.');
+ if ((!\is_numeric($values) && !\is_string($values)) || \preg_match(static::$patternHeaderValue, (string) $values) !== 1) {
+ throw new \InvalidArgumentException('Header value must be RFC 7230 compatible string.');
}
return [\trim((string) $values, " \t")];
}
if (empty($values)) {
- throw new InvalidArgumentException('Header values must be a string or an array of strings, empty array given.');
+ throw new \InvalidArgumentException('Header values must be a string or an array of strings, empty array given.');
}
$returnValues = [];
foreach ($values as $v) {
- if ((!\is_numeric($v) && !\is_string($v)) || \preg_match(self::$patternHeaderValue, (string) $v) !== 1) {
- throw new InvalidArgumentException('Header values must be RFC 7230 compatible strings.');
+ if ((!\is_numeric($v) && !\is_string($v)) || \preg_match(static::$patternHeaderValue, (string) $v) !== 1) {
+ throw new \InvalidArgumentException('Header values must be RFC 7230 compatible strings.');
}
$returnValues[] = \trim((string) $v, " \t");
@@ -290,14 +291,14 @@ protected function validateAndTrimHeader($header, $values): array
/**
* @param string $protocolVersion
*
- * @throws InvalidArgumentException
+ * @throws \InvalidArgumentException
*
* @return string
*/
protected function validateProtocolVersion(string $protocolVersion): string
{
- if (!\in_array($protocolVersion, ['0.9', '1.0', '1.1', '2'], true)) {
- throw new InvalidArgumentException('Protocol Version must be 0.9 or 1.0 or 1.1 or 2');
+ if (!\in_array($protocolVersion, static::$validProtocols, true)) {
+ throw new \InvalidArgumentException('Protocol Version must be 0.9 or 1.0 or 1.1 or 2');
}
return $protocolVersion;
diff --git a/src/Message/Request.php b/src/Message/Request.php
index bb815bf..b5a51af 100644
--- a/src/Message/Request.php
+++ b/src/Message/Request.php
@@ -4,8 +4,7 @@
namespace Rancoud\Http\Message;
-use Psr\Http\Message\RequestInterface;
-use Psr\Http\Message\UriInterface;
+use Psr\Http\Message\{RequestInterface, StreamInterface, UriInterface};
/**
* Class Request.
@@ -16,13 +15,11 @@ class Request implements RequestInterface
use RequestTrait;
/**
- * Request constructor.
- *
- * @param string $method
- * @param mixed $uri
- * @param array $headers
- * @param mixed $body
- * @param string $version
+ * @param string $method HTTP method
+ * @param string|UriInterface $uri URI
+ * @param array $headers Request headers
+ * @param string|resource|StreamInterface|null $body Request body
+ * @param string $version Protocol version
*
* @throws \InvalidArgumentException
*/
@@ -37,7 +34,7 @@ public function __construct(
$uri = new Uri($uri);
}
- $this->method = $method;
+ $this->method = $this->filterMethod($method);
$this->uri = $uri;
$this->setHeaders($headers);
$this->protocol = $this->validateProtocolVersion($version);
diff --git a/src/Message/RequestTrait.php b/src/Message/RequestTrait.php
index d6807d8..c0a80a7 100644
--- a/src/Message/RequestTrait.php
+++ b/src/Message/RequestTrait.php
@@ -4,7 +4,6 @@
namespace Rancoud\Http\Message;
-use InvalidArgumentException;
use Psr\Http\Message\UriInterface;
/**
@@ -70,10 +69,10 @@ trait RequestTrait
/** @var string */
protected $method;
- /** @var null|string */
+ /** @var string|null */
protected $requestTarget;
- /** @var null|UriInterface */
+ /** @var UriInterface|null */
protected $uri;
/**
@@ -86,11 +85,14 @@ public function getRequestTarget(): string
}
$target = $this->uri->getPath();
+ $query = $this->uri->getQuery();
+
if ($target === '') {
$target = '/';
}
- if ($this->uri->getQuery() !== '') {
- $target .= '?' . $this->uri->getQuery();
+
+ if ($query !== '') {
+ $target .= '?' . $query;
}
return $target;
@@ -99,14 +101,14 @@ public function getRequestTarget(): string
/**
* @param $requestTarget
*
- * @throws InvalidArgumentException
+ * @throws \InvalidArgumentException
*
* @return self
*/
public function withRequestTarget($requestTarget): self
{
if (\preg_match('#\s#', $requestTarget)) {
- throw new InvalidArgumentException('Invalid request target provided; cannot contain whitespace');
+ throw new \InvalidArgumentException('Invalid request target provided; cannot contain whitespace');
}
$new = clone $this;
@@ -126,13 +128,13 @@ public function getMethod(): string
/**
* @param string $method
*
- * @throws InvalidArgumentException
+ * @throws \InvalidArgumentException
*
* @return self
*/
public function withMethod($method): self
{
- $this->filterMethod($method);
+ $method = $this->filterMethod($method);
$new = clone $this;
$new->method = $method;
@@ -152,14 +154,14 @@ public function getUri(): UriInterface
* @param UriInterface $uri
* @param bool $preserveHost
*
- * @throws InvalidArgumentException
+ * @throws \InvalidArgumentException
*
* @return self
*/
public function withUri(UriInterface $uri, $preserveHost = false): self
{
if (!\is_bool($preserveHost)) {
- throw new InvalidArgumentException('Preserve Host must be a boolean');
+ throw new \InvalidArgumentException('Preserve Host must be a boolean');
}
if ($uri === $this->uri) {
@@ -179,23 +181,26 @@ public function withUri(UriInterface $uri, $preserveHost = false): self
/**
* @param string $method
*
- * @throws InvalidArgumentException
+ * @throws \InvalidArgumentException
+ *
+ * @return string
*/
- protected function filterMethod($method): void
+ protected function filterMethod($method): string
{
if (!\is_string($method)) {
- throw new InvalidArgumentException('Method must be a string');
+ throw new \InvalidArgumentException('Method must be a string');
}
- if (!\in_array($method, self::$methods, true)) {
- throw new InvalidArgumentException(\sprintf('Method %s is invalid', $method));
+ if (!\in_array($method, static::$methods, true)) {
+ throw new \InvalidArgumentException(\sprintf('Method %s is invalid', $method));
}
+
+ return $method;
}
protected function updateHostFromUri(): void
{
$host = $this->uri->getHost();
-
if ($host === '') {
return;
}
diff --git a/src/Message/Response.php b/src/Message/Response.php
index f99e931..c8687ac 100644
--- a/src/Message/Response.php
+++ b/src/Message/Response.php
@@ -4,8 +4,7 @@
namespace Rancoud\Http\Message;
-use InvalidArgumentException;
-use Psr\Http\Message\ResponseInterface;
+use Psr\Http\Message\{ResponseInterface, StreamInterface};
/**
* Class Response.
@@ -15,7 +14,7 @@ class Response implements ResponseInterface
use MessageTrait;
/** @var array */
- public static $phrases = [
+ public const PHRASES = [
100 => 'Continue',
101 => 'Switching Protocols',
102 => 'Processing',
@@ -182,15 +181,13 @@ class Response implements ResponseInterface
protected $statusCode = 200;
/**
- * Response constructor.
+ * @param int $status
+ * @param array $headers
+ * @param string|resource|StreamInterface|null $body
+ * @param string $version
+ * @param string|null $reason
*
- * @param int $status
- * @param array $headers
- * @param mixed $body
- * @param string $version
- * @param string $reason
- *
- * @throws InvalidArgumentException
+ * @throws \InvalidArgumentException
*/
public function __construct(
int $status = 200,
@@ -199,6 +196,11 @@ public function __construct(
string $version = '1.1',
string $reason = null
) {
+ $isStatusExist = isset(static::PHRASES[$status]);
+ if (!$isStatusExist) {
+ throw new \InvalidArgumentException('Status code has to be an integer between 100 and 799');
+ }
+
$this->statusCode = $status;
if ($body !== '' && $body !== null) {
@@ -206,8 +208,8 @@ public function __construct(
}
$this->setHeaders($headers);
- if (($reason === null || $reason === '') && isset(self::$phrases[$this->statusCode])) {
- $this->reasonPhrase = self::$phrases[$status];
+ if (($reason === null || $reason === '') && $isStatusExist) {
+ $this->reasonPhrase = static::PHRASES[$status];
} else {
$this->reasonPhrase = $reason;
}
@@ -227,25 +229,24 @@ public function getStatusCode(): int
* @param int $code
* @param string $reasonPhrase
*
- * @throws InvalidArgumentException
+ * @throws \InvalidArgumentException
*
* @return Response
*/
public function withStatus($code, $reasonPhrase = ''): self
{
- if (!\is_int($code) && !\is_string($code)) {
- throw new InvalidArgumentException('Status code has to be an integer');
+ if (!\is_int($code)) {
+ throw new \InvalidArgumentException('Status code has to be an integer');
}
- $code = (int) $code;
- if (!isset(self::$phrases[$code])) {
- throw new InvalidArgumentException('Status code has to be an integer between 100 and 599');
+ if (!isset(static::PHRASES[$code])) {
+ throw new \InvalidArgumentException('Status code has to be an integer between 100 and 799');
}
$new = clone $this;
$new->statusCode = $code;
if (($reasonPhrase === null || $reasonPhrase === '')) {
- $reasonPhrase = self::$phrases[$new->statusCode];
+ $reasonPhrase = static::PHRASES[$new->statusCode];
}
$new->reasonPhrase = $reasonPhrase;
@@ -261,18 +262,23 @@ public function getReasonPhrase(): string
}
/**
- * @throws InvalidArgumentException
+ * @param int $bodyChunkSize
+ *
+ * @throws \InvalidArgumentException
+ * @throws \RuntimeException
*/
- public function send()
+ public function send(int $bodyChunkSize = 8192): void
{
+ $statusCode = $this->getStatusCode();
+
$httpLine = \sprintf(
'HTTP/%s %s %s',
$this->getProtocolVersion(),
- $this->getStatusCode(),
+ $statusCode,
$this->getReasonPhrase()
);
- \header($httpLine, true, $this->getStatusCode());
+ \header($httpLine, true, $statusCode);
foreach ($this->getHeaders() as $name => $values) {
foreach ($values as $value) {
@@ -287,7 +293,7 @@ public function send()
}
while (!$stream->eof()) {
- echo $stream->read(1024 * 8);
+ echo $stream->read($bodyChunkSize);
}
}
}
diff --git a/src/Message/ServerRequest.php b/src/Message/ServerRequest.php
index 0db5ea5..5963d01 100644
--- a/src/Message/ServerRequest.php
+++ b/src/Message/ServerRequest.php
@@ -4,10 +4,7 @@
namespace Rancoud\Http\Message;
-use InvalidArgumentException;
-use Psr\Http\Message\ServerRequestInterface;
-use Psr\Http\Message\UploadedFileInterface;
-use Psr\Http\Message\UriInterface;
+use Psr\Http\Message\{ServerRequestInterface, StreamInterface, UploadedFileInterface, UriInterface};
/**
* Class ServerRequest.
@@ -23,7 +20,7 @@ class ServerRequest implements ServerRequestInterface
/** @var array */
protected $cookieParams = [];
- /** @var null|array|object */
+ /** @var array|object|null */
protected $parsedBody;
/** @var array */
@@ -36,16 +33,14 @@ class ServerRequest implements ServerRequestInterface
protected $uploadedFiles = [];
/**
- * ServerRequest constructor.
+ * @param string $method HTTP method
+ * @param string|UriInterface $uri URI
+ * @param array $headers Request headers
+ * @param string|resource|StreamInterface|null $body Request body
+ * @param string $version Protocol version
+ * @param array $serverParams Typically the $_SERVER superglobal
*
- * @param string $method
- * @param mixed $uri
- * @param array $headers
- * @param mixed $body
- * @param string $version
- * @param array $serverParams
- *
- * @throws InvalidArgumentException
+ * @throws \InvalidArgumentException
*/
public function __construct(
string $method,
@@ -61,7 +56,7 @@ public function __construct(
$uri = new Uri($uri);
}
- $this->method = $method;
+ $this->method = $this->filterMethod($method);
$this->uri = $uri;
$this->setHeaders($headers);
$this->protocol = $this->validateProtocolVersion($version);
@@ -147,7 +142,7 @@ public function withUploadedFiles(array $uploadedFiles): self
}
/**
- * @return array|null|object
+ * @return array|object|null
*/
public function getParsedBody()
{
@@ -155,9 +150,9 @@ public function getParsedBody()
}
/**
- * @param array|null|object $data
+ * @param array|object|null $data
*
- * @throws InvalidArgumentException
+ * @throws \InvalidArgumentException
*
* @return self
*/
@@ -183,14 +178,14 @@ public function getAttributes(): array
* @param string $name
* @param mixed $default
*
- * @throws InvalidArgumentException
+ * @throws \InvalidArgumentException
*
* @return mixed|null
*/
public function getAttribute($name, $default = null)
{
if (!\is_string($name)) {
- throw new InvalidArgumentException('Name must be a string');
+ throw new \InvalidArgumentException('Name must be a string');
}
if (!\array_key_exists($name, $this->attributes)) {
@@ -204,14 +199,14 @@ public function getAttribute($name, $default = null)
* @param string $name
* @param mixed $value
*
- * @throws InvalidArgumentException
+ * @throws \InvalidArgumentException
*
* @return self
*/
public function withAttribute($name, $value): self
{
if (!\is_string($name)) {
- throw new InvalidArgumentException('Name must be a string');
+ throw new \InvalidArgumentException('Name must be a string');
}
$new = clone $this;
@@ -223,14 +218,14 @@ public function withAttribute($name, $value): self
/**
* @param string $name
*
- * @throws InvalidArgumentException
+ * @throws \InvalidArgumentException
*
* @return self
*/
public function withoutAttribute($name): self
{
if (!\is_string($name)) {
- throw new InvalidArgumentException('Name must be a string');
+ throw new \InvalidArgumentException('Name must be a string');
}
if (!\array_key_exists($name, $this->attributes)) {
@@ -246,12 +241,12 @@ public function withoutAttribute($name): self
/**
* @param mixed $data
*
- * @throws InvalidArgumentException
+ * @throws \InvalidArgumentException
*/
protected function validateData($data): void
{
if (!\is_array($data) && !\is_object($data) && $data !== null) {
- throw new InvalidArgumentException('First parameter to withParsedBody MUST be object, array or null');
+ throw new \InvalidArgumentException('First parameter to withParsedBody MUST be object, array or null');
}
}
}
diff --git a/src/Message/Stream.php b/src/Message/Stream.php
index 48386d5..bbfd422 100644
--- a/src/Message/Stream.php
+++ b/src/Message/Stream.php
@@ -4,10 +4,7 @@
namespace Rancoud\Http\Message;
-use Exception;
-use InvalidArgumentException;
use Psr\Http\Message\StreamInterface;
-use RuntimeException;
/**
* Class Stream.
@@ -26,14 +23,14 @@ class Stream implements StreamInterface
/** @var bool */
protected $writable;
- /** @var array|mixed|null|void */
+ /** @var array|mixed|void|null */
protected $uri;
- /** @var int */
+ /** @var int|null */
protected $size;
/** @var array Hash of readable and writable stream types */
- protected static $readWriteHash = [
+ protected const READ_WRITE_HASH = [
'read' => [
'r' => true, 'w+' => true, 'r+' => true, 'x+' => true, 'c+' => true,
'rb' => true, 'w+b' => true, 'r+b' => true, 'x+b' => true,
@@ -63,7 +60,7 @@ public function __toString(): string
}
return $this->getContents();
- } catch (Exception $e) {
+ } catch (\Exception $e) {
return '';
}
}
@@ -79,7 +76,7 @@ public function close(): void
}
/**
- * @return null|resource
+ * @return resource|null
*/
public function detach()
{
@@ -123,7 +120,7 @@ public function getSize(): ?int
}
/**
- * @throws RuntimeException
+ * @throws \RuntimeException
*
* @return int
*/
@@ -132,7 +129,7 @@ public function tell(): int
$result = \ftell($this->stream);
if ($result === false) {
- throw new RuntimeException('Unable to determine stream position');
+ throw new \RuntimeException('Unable to determine stream position');
}
return $result;
@@ -158,31 +155,31 @@ public function isSeekable(): bool
* @param int $offset
* @param int $whence
*
- * @throws InvalidArgumentException
- * @throws RuntimeException
+ * @throws \InvalidArgumentException
+ * @throws \RuntimeException
*/
public function seek($offset, $whence = \SEEK_SET): void
{
if (!\is_int($offset)) {
- throw new InvalidArgumentException('Offset must be a int');
+ throw new \InvalidArgumentException('Offset must be a int');
}
if (!\is_int($whence)) {
- throw new InvalidArgumentException('Whence must be a int');
+ throw new \InvalidArgumentException('Whence must be a int');
}
if (!$this->seekable) {
- throw new RuntimeException('Stream is not seekable');
+ throw new \RuntimeException('Stream is not seekable');
} elseif (\fseek($this->stream, $offset, $whence) === -1) {
$whenceStr = \var_export($whence, true);
$message = \sprintf('Unable to seek to stream position %d with whence %d', $offset, $whenceStr);
- throw new RuntimeException($message);
+ throw new \RuntimeException($message);
}
}
/**
- * @throws InvalidArgumentException
- * @throws RuntimeException
+ * @throws \InvalidArgumentException
+ * @throws \RuntimeException
*/
public function rewind(): void
{
@@ -200,26 +197,26 @@ public function isWritable(): bool
/**
* @param string $string
*
- * @throws InvalidArgumentException
- * @throws RuntimeException
+ * @throws \InvalidArgumentException
+ * @throws \RuntimeException
*
* @return bool|int
*/
public function write($string)
{
if (!\is_string($string)) {
- throw new InvalidArgumentException('Data must be a string');
+ throw new \InvalidArgumentException('Data must be a string');
}
if (!$this->writable) {
- throw new RuntimeException('Cannot write to a non-writable stream');
+ throw new \RuntimeException('Cannot write to a non-writable stream');
}
$this->size = null;
$result = \fwrite($this->stream, $string);
if ($result === false) {
- throw new RuntimeException('Unable to write to stream');
+ throw new \RuntimeException('Unable to write to stream');
}
return $result;
@@ -236,39 +233,39 @@ public function isReadable(): bool
/**
* @param $length
*
- * @throws InvalidArgumentException
- * @throws RuntimeException
+ * @throws \InvalidArgumentException
+ * @throws \RuntimeException
*
* @return string
*/
public function read($length): string
{
if (!\is_int($length)) {
- throw new InvalidArgumentException('Length must be a int');
+ throw new \InvalidArgumentException('Length must be a int');
}
if (!$this->readable) {
- throw new RuntimeException('Cannot read from non-readable stream');
+ throw new \RuntimeException('Cannot read from non-readable stream');
}
return \fread($this->stream, $length);
}
/**
- * @throws RuntimeException
+ * @throws \RuntimeException
*
* @return string
*/
public function getContents(): string
{
if (!isset($this->stream)) {
- throw new RuntimeException('Unable to read stream contents');
+ throw new \RuntimeException('Unable to read stream contents');
}
$contents = \stream_get_contents($this->stream);
if ($contents === false) {
- throw new RuntimeException('Unable to read stream contents');
+ throw new \RuntimeException('Unable to read stream contents');
}
return $contents;
@@ -277,14 +274,14 @@ public function getContents(): string
/**
* @param string|null $key
*
- * @throws InvalidArgumentException
+ * @throws \InvalidArgumentException
*
* @return array|null
*/
public function getMetadata($key = null)
{
if (!$this->isStringOrNull($key)) {
- throw new InvalidArgumentException('Key must be a string or NULL');
+ throw new \InvalidArgumentException('Key must be a string or NULL');
}
if (!isset($this->stream)) {
@@ -293,23 +290,21 @@ public function getMetadata($key = null)
}
return [];
- } elseif ($key === null) {
- return \stream_get_meta_data($this->stream);
}
$meta = \stream_get_meta_data($this->stream);
- if (!isset($meta[$key])) {
- return null;
+ if ($key === null) {
+ return $meta;
}
- return $meta[$key];
+ return $meta[$key] ?? null;
}
/**
- * @param string $content
+ * @param string|resource|StreamInterface $content
*
- * @throws InvalidArgumentException
+ * @throws \InvalidArgumentException
*
* @return StreamInterface
*/
@@ -320,24 +315,24 @@ public static function create($content = ''): StreamInterface
}
if (\is_string($content)) {
- $resource = \fopen('php://temp', 'rw+b');
+ $resource = \fopen('php://temp', 'rw+');
\fwrite($resource, $content);
$content = $resource;
}
- if ('resource' === \gettype($content)) {
+ if (\is_resource($content)) {
$obj = new self();
$obj->stream = $content;
$meta = \stream_get_meta_data($obj->stream);
$obj->seekable = $meta['seekable'];
- $obj->readable = isset(self::$readWriteHash['read'][$meta['mode']]);
- $obj->writable = isset(self::$readWriteHash['write'][$meta['mode']]);
+ $obj->readable = isset(static::READ_WRITE_HASH['read'][$meta['mode']]);
+ $obj->writable = isset(static::READ_WRITE_HASH['write'][$meta['mode']]);
$obj->uri = $obj->getMetadata('uri');
return $obj;
}
- throw new InvalidArgumentException('First argument to Stream::create() must be a string, resource or StreamInterface.');
+ throw new \InvalidArgumentException('First argument to Stream::create() must be a string, resource or StreamInterface.');
}
public function __destruct()
@@ -352,6 +347,6 @@ public function __destruct()
*/
protected function isStringOrNull($param): bool
{
- return \in_array(\gettype($param), ['string', 'NULL'], true);
+ return $param === null || \is_string($param);
}
}
diff --git a/src/Message/UploadedFile.php b/src/Message/UploadedFile.php
index 80855b8..22223db 100644
--- a/src/Message/UploadedFile.php
+++ b/src/Message/UploadedFile.php
@@ -4,28 +4,28 @@
namespace Rancoud\Http\Message;
-use InvalidArgumentException;
-use Psr\Http\Message\StreamInterface;
-use Psr\Http\Message\UploadedFileInterface;
-use RuntimeException;
+use Psr\Http\Message\{StreamInterface, UploadedFileInterface};
/**
* Class UploadedFile.
*/
class UploadedFile implements UploadedFileInterface
{
- /** @var int[] */
- protected static $errors = [
- \UPLOAD_ERR_OK,
- \UPLOAD_ERR_INI_SIZE,
- \UPLOAD_ERR_FORM_SIZE,
- \UPLOAD_ERR_PARTIAL,
- \UPLOAD_ERR_NO_FILE,
- \UPLOAD_ERR_NO_TMP_DIR,
- \UPLOAD_ERR_CANT_WRITE,
- \UPLOAD_ERR_EXTENSION
+ /** @var array */
+ protected const ERRORS = [
+ \UPLOAD_ERR_OK => 1,
+ \UPLOAD_ERR_INI_SIZE => 1,
+ \UPLOAD_ERR_FORM_SIZE => 1,
+ \UPLOAD_ERR_PARTIAL => 1,
+ \UPLOAD_ERR_NO_FILE => 1,
+ \UPLOAD_ERR_NO_TMP_DIR => 1,
+ \UPLOAD_ERR_CANT_WRITE => 1,
+ \UPLOAD_ERR_EXTENSION => 1,
];
+ /** @var int */
+ protected const DEFAULT_MAX_BYTES_LENGTH = 1048576;
+
/** @var string */
protected $clientFilename;
@@ -35,31 +35,26 @@ class UploadedFile implements UploadedFileInterface
/** @var int */
protected $error;
- /** @var null|string */
+ /** @var string|null */
protected $file;
/** @var bool */
protected $moved = false;
- /** @var null|int */
+ /** @var int|null */
protected $size;
- /** @var null|StreamInterface */
+ /** @var StreamInterface|null */
protected $stream;
- /** @var int */
- protected $defaultMaxBytesLength = 1048576;
-
/**
- * UploadedFile constructor.
+ * @param StreamInterface|string|resource $streamOrFile
+ * @param int $size
+ * @param int $errorStatus
+ * @param string|null $clientFilename
+ * @param string|null $clientMediaType
*
- * @param mixed $streamOrFile
- * @param int $size
- * @param int $errorStatus
- * @param string|null $clientFilename
- * @param string|null $clientMediaType
- *
- * @throws InvalidArgumentException
+ * @throws \InvalidArgumentException
*/
public function __construct(
$streamOrFile,
@@ -73,14 +68,14 @@ public function __construct(
$this->setClientFilename($clientFilename);
$this->setClientMediaType($clientMediaType);
- if ($this->isOk()) {
+ if ($this->isUploadSuccess()) {
$this->setStreamOrFile($streamOrFile);
}
}
/**
- * @throws InvalidArgumentException
- * @throws RuntimeException
+ * @throws \InvalidArgumentException
+ * @throws \RuntimeException
*
* @return StreamInterface
*/
@@ -92,7 +87,7 @@ public function getStream(): StreamInterface
return $this->stream;
}
- $resource = \fopen($this->file, 'rb');
+ $resource = \fopen($this->file, 'r');
return Stream::create($resource);
}
@@ -100,15 +95,15 @@ public function getStream(): StreamInterface
/**
* @param string $targetPath
*
- * @throws InvalidArgumentException
- * @throws RuntimeException
+ * @throws \InvalidArgumentException
+ * @throws \RuntimeException
*/
public function moveTo($targetPath): void
{
$this->validateActive();
if (!$this->isStringNotEmpty($targetPath)) {
- throw new InvalidArgumentException('Invalid path provided for move operation; must be a non-empty string');
+ throw new \InvalidArgumentException('Invalid path provided for move operation; must be a non-empty string');
}
if ($this->file !== null) {
@@ -119,16 +114,17 @@ public function moveTo($targetPath): void
}
} else {
$stream = $this->getStream();
- if ($stream->isSeekable() === true) {
+ if ($stream->isSeekable()) {
$stream->rewind();
}
- $this->copyToStream($stream, Stream::create(\fopen($targetPath, 'wb')));
+ $destination = Stream::create(\fopen($targetPath, 'w'));
+ $this->copyToStream($stream, $destination);
$this->moved = true;
}
if (!$this->moved) {
- throw new RuntimeException(\sprintf('Uploaded file could not be moved to %s', $targetPath));
+ throw new \RuntimeException(\sprintf('Uploaded file could not be moved to %s', $targetPath));
}
}
@@ -149,7 +145,7 @@ public function getError(): int
}
/**
- * @return null|string
+ * @return string|null
*/
public function getClientFilename(): ?string
{
@@ -157,7 +153,7 @@ public function getClientFilename(): ?string
}
/**
- * @return null|string
+ * @return string|null
*/
public function getClientMediaType(): ?string
{
@@ -167,7 +163,7 @@ public function getClientMediaType(): ?string
/**
* @param $streamOrFile
*
- * @throws InvalidArgumentException
+ * @throws \InvalidArgumentException
*/
protected function setStreamOrFile($streamOrFile): void
{
@@ -178,22 +174,22 @@ protected function setStreamOrFile($streamOrFile): void
} elseif ($streamOrFile instanceof StreamInterface) {
$this->stream = $streamOrFile;
} else {
- throw new InvalidArgumentException('Invalid stream or file provided for UploadedFile');
+ throw new \InvalidArgumentException('Invalid stream or file provided for UploadedFile');
}
}
/**
* @param int $error
*
- * @throws InvalidArgumentException
+ * @throws \InvalidArgumentException
*/
protected function setError($error): void
{
if (!\is_int($error)) {
- throw new InvalidArgumentException('Upload file error status must be an integer');
+ throw new \InvalidArgumentException('Upload file error status must be an integer');
}
- if (!\in_array($error, self::$errors, true)) {
+ if (!isset(static::ERRORS[$error])) {
throw new \InvalidArgumentException('Invalid error status for UploadedFile');
}
@@ -203,12 +199,12 @@ protected function setError($error): void
/**
* @param int $size
*
- * @throws InvalidArgumentException
+ * @throws \InvalidArgumentException
*/
protected function setSize($size): void
{
if (!\is_int($size)) {
- throw new InvalidArgumentException('Upload file size must be an integer');
+ throw new \InvalidArgumentException('Upload file size must be an integer');
}
$this->size = $size;
@@ -221,7 +217,7 @@ protected function setSize($size): void
*/
protected function isStringOrNull($param): bool
{
- return \in_array(\gettype($param), ['string', 'NULL'], true);
+ return $param === null || \is_string($param);
}
/**
@@ -237,12 +233,12 @@ protected function isStringNotEmpty($param): bool
/**
* @param string|null $clientFilename
*
- * @throws InvalidArgumentException
+ * @throws \InvalidArgumentException
*/
protected function setClientFilename($clientFilename): void
{
if (!$this->isStringOrNull($clientFilename)) {
- throw new InvalidArgumentException('Upload file client filename must be a string or null');
+ throw new \InvalidArgumentException('Upload file client filename must be a string or null');
}
$this->clientFilename = $clientFilename;
@@ -251,12 +247,12 @@ protected function setClientFilename($clientFilename): void
/**
* @param string|null $clientMediaType
*
- * @throws InvalidArgumentException
+ * @throws \InvalidArgumentException
*/
protected function setClientMediaType($clientMediaType): void
{
if (!$this->isStringOrNull($clientMediaType)) {
- throw new InvalidArgumentException('Upload file client media type must be a string or null');
+ throw new \InvalidArgumentException('Upload file client media type must be a string or null');
}
$this->clientMediaType = $clientMediaType;
@@ -265,33 +261,35 @@ protected function setClientMediaType($clientMediaType): void
/**
* @return bool
*/
- protected function isOk(): bool
+ protected function isUploadSuccess(): bool
{
- return $this->error === UPLOAD_ERR_OK;
+ return $this->error === \UPLOAD_ERR_OK;
}
/**
- * @throws RuntimeException
+ * @throws \RuntimeException
*/
protected function validateActive(): void
{
- if (!$this->isOk()) {
- throw new RuntimeException('Cannot retrieve stream due to upload error');
+ if (!$this->isUploadSuccess()) {
+ throw new \RuntimeException('Cannot retrieve stream due to upload error');
}
if ($this->moved) {
- throw new RuntimeException('Cannot retrieve stream after it has already been moved');
+ throw new \RuntimeException('Cannot retrieve stream after it has already been moved');
}
}
/**
* @param StreamInterface $source
* @param StreamInterface $dest
+ *
+ * @throws \RuntimeException
*/
protected function copyToStream(StreamInterface $source, StreamInterface $dest)
{
while (!$source->eof()) {
- if (!$dest->write($source->read($this->defaultMaxBytesLength))) {
+ if (!$dest->write($source->read(static::DEFAULT_MAX_BYTES_LENGTH))) {
break;
}
}
diff --git a/src/Message/Uri.php b/src/Message/Uri.php
index ba676bf..be0c391 100644
--- a/src/Message/Uri.php
+++ b/src/Message/Uri.php
@@ -4,7 +4,6 @@
namespace Rancoud\Http\Message;
-use InvalidArgumentException;
use Psr\Http\Message\UriInterface;
/**
@@ -13,16 +12,16 @@
class Uri implements UriInterface
{
/** @var array */
- protected static $schemes = [
+ protected const SCHEMES = [
'http' => 80,
'https' => 443,
];
/** @var string */
- protected static $charUnreserved = 'a-zA-Z0-9_\-\.~';
+ protected const CHAR_UNRESERVED = 'a-zA-Z0-9_\-\.~';
/** @var string */
- protected static $charSubDelims = '!\$&\'\(\)\*\+,;=';
+ protected const CHAR_SUB_DELIMS = '!\$&\'\(\)\*\+,;=';
/** @var string Uri scheme. */
protected $scheme = '';
@@ -46,18 +45,16 @@ class Uri implements UriInterface
protected $fragment = '';
/**
- * Uri constructor.
- *
* @param string $uri
*
- * @throws InvalidArgumentException
+ * @throws \InvalidArgumentException
*/
public function __construct(string $uri = '')
{
if ($uri !== '') {
$parts = \parse_url($uri);
if ($parts === false) {
- throw new InvalidArgumentException(\sprintf('Unable to parse URI: %s', $uri));
+ throw new \InvalidArgumentException(\sprintf('Unable to parse URI: %s', $uri));
}
$this->applyParts($parts);
@@ -145,7 +142,7 @@ public function getFragment(): string
/**
* @param string $scheme
*
- * @throws InvalidArgumentException
+ * @throws \InvalidArgumentException
*
* @return self
*/
@@ -168,21 +165,15 @@ public function withScheme($scheme): self
* @param string $user
* @param string|null $password
*
- * @throws InvalidArgumentException
+ * @throws \InvalidArgumentException
*
* @return self
*/
public function withUserInfo($user, $password = null): self
{
- if (!\is_string($user)) {
- throw new InvalidArgumentException('User must be a string');
- }
-
- if (!$this->isStringOrNull($password)) {
- throw new InvalidArgumentException('Password must be a string or NULL');
- }
+ $info = $this->filterUser($user);
+ $password = $this->filterPass($password);
- $info = $user;
if ($password !== null && $password !== '') {
$info .= ':' . $password;
}
@@ -200,7 +191,7 @@ public function withUserInfo($user, $password = null): self
/**
* @param string $host
*
- * @throws InvalidArgumentException
+ * @throws \InvalidArgumentException
*
* @return self
*/
@@ -221,7 +212,7 @@ public function withHost($host): self
/**
* @param int|null $port
*
- * @throws InvalidArgumentException
+ * @throws \InvalidArgumentException
*
* @return self
*/
@@ -242,7 +233,7 @@ public function withPort($port): self
/**
* @param string $path
*
- * @throws InvalidArgumentException
+ * @throws \InvalidArgumentException
*
* @return self
*/
@@ -263,7 +254,7 @@ public function withPath($path): self
/**
* @param string $query
*
- * @throws InvalidArgumentException
+ * @throws \InvalidArgumentException
*
* @return self
*/
@@ -284,7 +275,7 @@ public function withQuery($query): self
/**
* @param string $fragment
*
- * @throws InvalidArgumentException
+ * @throws \InvalidArgumentException
*
* @return self
*/
@@ -307,7 +298,7 @@ public function withFragment($fragment): self
*/
public function __toString(): string
{
- return self::createUriString(
+ return static::createUriString(
$this->scheme,
$this->getAuthority(),
$this->path,
@@ -319,7 +310,7 @@ public function __toString(): string
/**
* @param array $parts
*
- * @throws InvalidArgumentException
+ * @throws \InvalidArgumentException
*/
protected function applyParts(array $parts): void
{
@@ -336,7 +327,7 @@ protected function applyParts(array $parts): void
}
if (isset($parts['user'])) {
- $this->userInfo = $parts['user'];
+ $this->userInfo = $this->filterUser($parts['user']);
}
if (isset($parts['host'])) {
@@ -360,7 +351,7 @@ protected function applyParts(array $parts): void
}
if (isset($parts['pass'])) {
- $this->userInfo .= ':' . $parts['pass'];
+ $this->userInfo .= ':' . $this->filterPass($parts['pass']);
}
}
@@ -389,12 +380,15 @@ protected static function createUriString(
$uri .= '//' . $authority;
}
+ $charAtPosZero = \mb_substr($path, 0, 1);
+ $charAtPosOne = \mb_substr($path, 1, 1);
+
if ($path !== '') {
- if ($path[0] !== '/') {
+ if ($charAtPosZero !== '/') {
if ($authority !== '') {
$path = '/' . $path;
}
- } elseif (isset($path[1]) && $path[1] === '/') {
+ } elseif (isset($charAtPosOne) && $charAtPosOne === '/') {
if ($authority === '') {
$path = '/' . \ltrim($path, '/');
}
@@ -422,36 +416,68 @@ protected static function createUriString(
*/
protected static function isNonStandardPort(string $scheme, int $port): bool
{
- return !isset(self::$schemes[$scheme]) || $port !== self::$schemes[$scheme];
+ return !isset(static::SCHEMES[$scheme]) || $port !== static::SCHEMES[$scheme];
}
/**
* @param string $scheme
*
- * @throws InvalidArgumentException
+ * @throws \InvalidArgumentException
*
* @return string
*/
protected function filterScheme($scheme): string
{
if (!\is_string($scheme)) {
- throw new InvalidArgumentException('Scheme must be a string');
+ throw new \InvalidArgumentException('Scheme must be a string');
}
return \mb_strtolower($scheme);
}
+ /**
+ * @param string $user
+ *
+ * @throws \InvalidArgumentException
+ *
+ * @return string
+ */
+ protected function filterUser($user): string
+ {
+ if (!\is_string($user)) {
+ throw new \InvalidArgumentException('User must be a string');
+ }
+
+ return $user;
+ }
+
+ /**
+ * @param ?string $pass
+ *
+ * @throws \InvalidArgumentException
+ *
+ * @return string
+ */
+ protected function filterPass($pass): ?string
+ {
+ if ($pass !== null && !\is_string($pass)) {
+ throw new \InvalidArgumentException('Password must be a string or NULL');
+ }
+
+ return $pass;
+ }
+
/**
* @param string $host
*
- * @throws InvalidArgumentException
+ * @throws \InvalidArgumentException
*
* @return string
*/
protected function filterHost($host): string
{
if (!\is_string($host)) {
- throw new InvalidArgumentException('Host must be a string');
+ throw new \InvalidArgumentException('Host must be a string');
}
return \mb_strtolower($host);
@@ -460,7 +486,7 @@ protected function filterHost($host): string
/**
* @param int|null $port
*
- * @throws InvalidArgumentException
+ * @throws \InvalidArgumentException
*
* @return int|null
*/
@@ -472,10 +498,10 @@ protected function filterPort($port): ?int
$port = (int) $port;
if ($port < 1 || $port > 65535) {
- throw new InvalidArgumentException(\sprintf('Invalid port: %d. Must be between 1 and 65535', $port));
+ throw new \InvalidArgumentException(\sprintf('Invalid port: %d. Must be between 1 and 65535', $port));
}
- if (!self::isNonStandardPort($this->scheme, $port)) {
+ if (!static::isNonStandardPort($this->scheme, $port)) {
return null;
}
@@ -485,19 +511,19 @@ protected function filterPort($port): ?int
/**
* @param string $path
*
- * @throws InvalidArgumentException
+ * @throws \InvalidArgumentException
*
* @return string
*/
protected function filterPath($path): string
{
if (!\is_string($path)) {
- throw new InvalidArgumentException('Path must be a string');
+ throw new \InvalidArgumentException('Path must be a string');
}
return \preg_replace_callback(
- $this->getPatternForFilteringPath(),
- [$this, 'rawurlencodeMatchZero'],
+ static::getPatternForFilteringPath(),
+ [__CLASS__, 'rawurlencodeMatchZero'],
$path
);
}
@@ -505,19 +531,19 @@ protected function filterPath($path): string
/**
* @param string $str
*
- * @throws InvalidArgumentException
+ * @throws \InvalidArgumentException
*
* @return string
*/
protected function filterQueryAndFragment($str): string
{
if (!\is_string($str)) {
- throw new InvalidArgumentException('Query and fragment must be a string');
+ throw new \InvalidArgumentException('Query and fragment must be a string');
}
return \preg_replace_callback(
- $this->getPatternForFilteringQueryAndFragment(),
- [$this, 'rawurlencodeMatchZero'],
+ static::getPatternForFilteringQueryAndFragment(),
+ [__CLASS__, 'rawurlencodeMatchZero'],
$str
);
}
@@ -527,34 +553,24 @@ protected function filterQueryAndFragment($str): string
*
* @return string
*/
- protected function rawurlencodeMatchZero(array $match): string
+ protected static function rawurlencodeMatchZero(array $match): string
{
return \rawurlencode($match[0]);
}
- /**
- * @param $param
- *
- * @return bool
- */
- protected function isStringOrNull($param): bool
- {
- return \in_array(\gettype($param), ['string', 'NULL'], true);
- }
-
/**
* @return string
*/
- protected function getPatternForFilteringPath(): string
+ protected static function getPatternForFilteringPath(): string
{
- return '/(?:[^' . self::$charUnreserved . self::$charSubDelims . '%:@\/]++|%(?![A-Fa-f0-9]{2}))/';
+ return '/(?:[^' . static::CHAR_UNRESERVED . static::CHAR_SUB_DELIMS . '%:@\/]++|%(?![A-Fa-f0-9]{2}))/';
}
/**
* @return string
*/
- protected function getPatternForFilteringQueryAndFragment(): string
+ protected static function getPatternForFilteringQueryAndFragment(): string
{
- return '/(?:[^' . self::$charUnreserved . self::$charSubDelims . '%:@\/\?]++|%(?![A-Fa-f0-9]{2}))/';
+ return '/(?:[^' . static::CHAR_UNRESERVED . static::CHAR_SUB_DELIMS . '%:@\/\?]++|%(?![A-Fa-f0-9]{2}))/';
}
}
diff --git a/src/Psr/Client/ClientException.php b/src/Psr/Client/ClientException.php
deleted file mode 100644
index c7f47c2..0000000
--- a/src/Psr/Client/ClientException.php
+++ /dev/null
@@ -1,7 +0,0 @@
-getHeaders() as $name => $values) {
+ * echo $name . ": " . implode(", ", $values);
+ * }
+ *
+ * // Emit headers iteratively:
+ * foreach ($message->getHeaders() as $name => $values) {
+ * foreach ($values as $value) {
+ * header(sprintf('%s: %s', $name, $value), false);
+ * }
+ * }
+ *
+ * While header names are not case-sensitive, getHeaders() will preserve the
+ * exact case in which headers were originally specified.
+ *
+ * @return string[][] Returns an associative array of the message's headers. Each
+ * key MUST be a header name, and each value MUST be an array of strings
+ * for that header.
+ */
public function getHeaders();
+
+ /**
+ * Checks if a header exists by the given case-insensitive name.
+ *
+ * @param string $name Case-insensitive header field name.
+ * @return bool Returns true if any header names match the given header
+ * name using a case-insensitive string comparison. Returns false if
+ * no matching header name is found in the message.
+ */
public function hasHeader($name);
+
+ /**
+ * Retrieves a message header value by the given case-insensitive name.
+ *
+ * This method returns an array of all the header values of the given
+ * case-insensitive header name.
+ *
+ * If the header does not appear in the message, this method MUST return an
+ * empty array.
+ *
+ * @param string $name Case-insensitive header field name.
+ * @return string[] An array of string values as provided for the given
+ * header. If the header does not appear in the message, this method MUST
+ * return an empty array.
+ */
public function getHeader($name);
+
+ /**
+ * Retrieves a comma-separated string of the values for a single header.
+ *
+ * This method returns all of the header values of the given
+ * case-insensitive header name as a string concatenated together using
+ * a comma.
+ *
+ * NOTE: Not all header values may be appropriately represented using
+ * comma concatenation. For such headers, use getHeader() instead
+ * and supply your own delimiter when concatenating.
+ *
+ * If the header does not appear in the message, this method MUST return
+ * an empty string.
+ *
+ * @param string $name Case-insensitive header field name.
+ * @return string A string of values as provided for the given header
+ * 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);
+
+ /**
+ * Return an instance with the provided value replacing the specified header.
+ *
+ * While header names are case-insensitive, the casing of the header will
+ * be preserved by this function, and returned from getHeaders().
+ *
+ * This method MUST be implemented in such a way as to retain the
+ * immutability of the message, and MUST return an instance that has the
+ * new and/or updated header and value.
+ *
+ * @param string $name Case-insensitive header field name.
+ * @param string|string[] $value Header value(s).
+ * @return static
+ * @throws \InvalidArgumentException for invalid header names or values.
+ */
public function withHeader($name, $value);
+
+ /**
+ * Return an instance with the specified header appended with the given value.
+ *
+ * Existing values for the specified header will be maintained. The new
+ * value(s) will be appended to the existing list. If the header did not
+ * exist previously, it will be added.
+ *
+ * This method MUST be implemented in such a way as to retain the
+ * immutability of the message, and MUST return an instance that has the
+ * new header and/or value.
+ *
+ * @param string $name Case-insensitive header field name to add.
+ * @param string|string[] $value Header value(s).
+ * @return static
+ * @throws \InvalidArgumentException for invalid header names or values.
+ */
public function withAddedHeader($name, $value);
+
+ /**
+ * Return an instance without the specified header.
+ *
+ * Header resolution MUST be done without case-sensitivity.
+ *
+ * This method MUST be implemented in such a way as to retain the
+ * immutability of the message, and MUST return an instance that removes
+ * the named header.
+ *
+ * @param string $name Case-insensitive header field name to remove.
+ * @return static
+ */
public function withoutHeader($name);
+
+ /**
+ * Gets the body of the message.
+ *
+ * @return StreamInterface Returns the body as a stream.
+ */
public function getBody();
+
+ /**
+ * Return an instance with the specified message body.
+ *
+ * The body MUST be a StreamInterface object.
+ *
+ * This method MUST be implemented in such a way as to retain the
+ * immutability of the message, and MUST return a new instance that has the
+ * new body stream.
+ *
+ * @param StreamInterface $body Body.
+ * @return static
+ * @throws \InvalidArgumentException When the body is not valid.
+ */
public function withBody(StreamInterface $body);
-}
+}
\ No newline at end of file
diff --git a/src/Psr/Message/RequestFactoryInterface.php b/src/Psr/Message/RequestFactoryInterface.php
index d8ed797..cad6baa 100644
--- a/src/Psr/Message/RequestFactoryInterface.php
+++ b/src/Psr/Message/RequestFactoryInterface.php
@@ -4,5 +4,15 @@
interface RequestFactoryInterface
{
+ /**
+ * Create a new request.
+ *
+ * @param string $method The HTTP method associated with the request.
+ * @param UriInterface|string $uri The URI associated with the request. If
+ * the value is a string, the factory MUST create a UriInterface
+ * instance based on it.
+ *
+ * @return RequestInterface
+ */
public function createRequest(string $method, $uri): RequestInterface;
-}
+}
\ No newline at end of file
diff --git a/src/Psr/Message/RequestInterface.php b/src/Psr/Message/RequestInterface.php
index 0b5aaf1..5b1c9c0 100644
--- a/src/Psr/Message/RequestInterface.php
+++ b/src/Psr/Message/RequestInterface.php
@@ -2,12 +2,128 @@
namespace Psr\Http\Message;
+/**
+ * Representation of an outgoing, client-side request.
+ *
+ * Per the HTTP specification, this interface includes properties for
+ * each of the following:
+ *
+ * - Protocol version
+ * - HTTP method
+ * - URI
+ * - Headers
+ * - Message body
+ *
+ * During construction, implementations MUST attempt to set the Host header from
+ * a provided URI if no Host header is provided.
+ *
+ * Requests are considered immutable; all methods that might change state MUST
+ * be implemented such that they retain the internal state of the current
+ * message and return an instance that contains the changed state.
+ */
interface RequestInterface extends MessageInterface
{
+ /**
+ * Retrieves the message's request target.
+ *
+ * Retrieves the message's request-target either as it will appear (for
+ * clients), as it appeared at request (for servers), or as it was
+ * specified for the instance (see withRequestTarget()).
+ *
+ * In most cases, this will be the origin-form of the composed URI,
+ * unless a value was provided to the concrete implementation (see
+ * withRequestTarget() below).
+ *
+ * If no URI is available, and no request-target has been specifically
+ * provided, this method MUST return the string "/".
+ *
+ * @return string
+ */
public function getRequestTarget();
+
+ /**
+ * Return an instance with the specific request-target.
+ *
+ * If the request needs a non-origin-form request-target — e.g., for
+ * specifying an absolute-form, authority-form, or asterisk-form —
+ * this method may be used to create an instance with the specified
+ * request-target, verbatim.
+ *
+ * This method MUST be implemented in such a way as to retain the
+ * immutability of the message, and MUST return an instance that has the
+ * changed request target.
+ *
+ * @link http://tools.ietf.org/html/rfc7230#section-5.3 (for the various
+ * request-target forms allowed in request messages)
+ * @param mixed $requestTarget
+ * @return static
+ */
public function withRequestTarget($requestTarget);
+
+ /**
+ * Retrieves the HTTP method of the request.
+ *
+ * @return string Returns the request method.
+ */
public function getMethod();
+
+ /**
+ * Return an instance with the provided HTTP method.
+ *
+ * While HTTP method names are typically all uppercase characters, HTTP
+ * method names are case-sensitive and thus implementations SHOULD NOT
+ * modify the given string.
+ *
+ * This method MUST be implemented in such a way as to retain the
+ * immutability of the message, and MUST return an instance that has the
+ * changed request method.
+ *
+ * @param string $method Case-sensitive method.
+ * @return static
+ * @throws \InvalidArgumentException for invalid HTTP methods.
+ */
public function withMethod($method);
+
+ /**
+ * Retrieves the URI instance.
+ *
+ * This method MUST return a UriInterface instance.
+ *
+ * @link http://tools.ietf.org/html/rfc3986#section-4.3
+ * @return UriInterface Returns a UriInterface instance
+ * representing the URI of the request.
+ */
public function getUri();
+
+ /**
+ * Returns an instance with the provided URI.
+ *
+ * This method MUST update the Host header of the returned request by
+ * default if the URI contains a host component. If the URI does not
+ * contain a host component, any pre-existing Host header MUST be carried
+ * over to the returned request.
+ *
+ * You can opt-in to preserving the original state of the Host header by
+ * setting `$preserveHost` to `true`. When `$preserveHost` is set to
+ * `true`, this method interacts with the Host header in the following ways:
+ *
+ * - If the Host header is missing or empty, and the new URI contains
+ * a host component, this method MUST update the Host header in the returned
+ * request.
+ * - If the Host header is missing or empty, and the new URI does not contain a
+ * host component, this method MUST NOT update the Host header in the returned
+ * request.
+ * - If a Host header is present and non-empty, this method MUST NOT update
+ * the Host header in the returned request.
+ *
+ * This method MUST be implemented in such a way as to retain the
+ * immutability of the message, and MUST return an instance that has the
+ * new UriInterface instance.
+ *
+ * @link http://tools.ietf.org/html/rfc3986#section-4.3
+ * @param UriInterface $uri New request URI to use.
+ * @param bool $preserveHost Preserve the original state of the Host header.
+ * @return static
+ */
public function withUri(UriInterface $uri, $preserveHost = false);
-}
+}
\ No newline at end of file
diff --git a/src/Psr/Message/ResponseFactoryInterface.php b/src/Psr/Message/ResponseFactoryInterface.php
index e3ea778..c3a7bb1 100644
--- a/src/Psr/Message/ResponseFactoryInterface.php
+++ b/src/Psr/Message/ResponseFactoryInterface.php
@@ -4,5 +4,15 @@
interface ResponseFactoryInterface
{
+ /**
+ * Create a new response.
+ *
+ * @param int $code HTTP status code; defaults to 200
+ * @param string $reasonPhrase Reason phrase to associate with status code
+ * in generated response; if none is provided implementations MAY use
+ * the defaults as suggested in the HTTP specification.
+ *
+ * @return ResponseInterface
+ */
public function createResponse(int $code = 200, string $reasonPhrase = ''): ResponseInterface;
-}
+}
\ No newline at end of file
diff --git a/src/Psr/Message/ResponseInterface.php b/src/Psr/Message/ResponseInterface.php
index 3483410..5f7344b 100644
--- a/src/Psr/Message/ResponseInterface.php
+++ b/src/Psr/Message/ResponseInterface.php
@@ -2,9 +2,67 @@
namespace Psr\Http\Message;
+/**
+ * Representation of an outgoing, server-side response.
+ *
+ * Per the HTTP specification, this interface includes properties for
+ * each of the following:
+ *
+ * - Protocol version
+ * - Status code and reason phrase
+ * - Headers
+ * - Message body
+ *
+ * Responses are considered immutable; all methods that might change state MUST
+ * be implemented such that they retain the internal state of the current
+ * message and return an instance that contains the changed state.
+ */
interface ResponseInterface extends MessageInterface
{
+ /**
+ * Gets the response status code.
+ *
+ * The status code is a 3-digit integer result code of the server's attempt
+ * to understand and satisfy the request.
+ *
+ * @return int Status code.
+ */
public function getStatusCode();
+
+ /**
+ * Return an instance with the specified status code and, optionally, reason phrase.
+ *
+ * If no reason phrase is specified, implementations MAY choose to default
+ * to the RFC 7231 or IANA recommended reason phrase for the response's
+ * status code.
+ *
+ * This method MUST be implemented in such a way as to retain the
+ * immutability of the message, and MUST return an instance that has the
+ * updated status and reason phrase.
+ *
+ * @link http://tools.ietf.org/html/rfc7231#section-6
+ * @link http://www.iana.org/assignments/http-status-codes/http-status-codes.xhtml
+ * @param int $code The 3-digit integer result code to set.
+ * @param string $reasonPhrase The reason phrase to use with the
+ * provided status code; if none is provided, implementations MAY
+ * use the defaults as suggested in the HTTP specification.
+ * @return static
+ * @throws \InvalidArgumentException For invalid status code arguments.
+ */
public function withStatus($code, $reasonPhrase = '');
+
+ /**
+ * Gets the response reason phrase associated with the status code.
+ *
+ * Because a reason phrase is not a required element in a response
+ * status line, the reason phrase value MAY be null. Implementations MAY
+ * choose to return the default RFC 7231 recommended reason phrase (or those
+ * listed in the IANA HTTP Status Code Registry) for the response's
+ * status code.
+ *
+ * @link http://tools.ietf.org/html/rfc7231#section-6
+ * @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();
-}
+}
\ No newline at end of file
diff --git a/src/Psr/Message/ServerRequestFactoryInterface.php b/src/Psr/Message/ServerRequestFactoryInterface.php
index fbf8ddf..9ed626a 100644
--- a/src/Psr/Message/ServerRequestFactoryInterface.php
+++ b/src/Psr/Message/ServerRequestFactoryInterface.php
@@ -4,5 +4,21 @@
interface ServerRequestFactoryInterface
{
+ /**
+ * Create a new server request.
+ *
+ * Note that server-params are taken precisely as given - no parsing/processing
+ * of the given values is performed, and, in particular, no attempt is made to
+ * determine the HTTP method or URI, which must be provided explicitly.
+ *
+ * @param string $method The HTTP method associated with the request.
+ * @param UriInterface|string $uri The URI associated with the request. If
+ * the value is a string, the factory MUST create a UriInterface
+ * instance based on it.
+ * @param array $serverParams Array of SAPI parameters with which to seed
+ * the generated request instance.
+ *
+ * @return ServerRequestInterface
+ */
public function createServerRequest(string $method, $uri, array $serverParams = []): ServerRequestInterface;
-}
+}
\ No newline at end of file
diff --git a/src/Psr/Message/ServerRequestInterface.php b/src/Psr/Message/ServerRequestInterface.php
index 49bf4be..42baf11 100644
--- a/src/Psr/Message/ServerRequestInterface.php
+++ b/src/Psr/Message/ServerRequestInterface.php
@@ -2,19 +2,260 @@
namespace Psr\Http\Message;
+/**
+ * Representation of an incoming, server-side HTTP request.
+ *
+ * Per the HTTP specification, this interface includes properties for
+ * each of the following:
+ *
+ * - Protocol version
+ * - HTTP method
+ * - URI
+ * - Headers
+ * - Message body
+ *
+ * Additionally, it encapsulates all data as it has arrived to the
+ * application from the CGI and/or PHP environment, including:
+ *
+ * - The values represented in $_SERVER.
+ * - Any cookies provided (generally via $_COOKIE)
+ * - Query string arguments (generally via $_GET, or as parsed via parse_str())
+ * - Upload files, if any (as represented by $_FILES)
+ * - Deserialized body parameters (generally from $_POST)
+ *
+ * $_SERVER values MUST be treated as immutable, as they represent application
+ * state at the time of request; as such, no methods are provided to allow
+ * modification of those values. The other values provide such methods, as they
+ * can be restored from $_SERVER or the request body, and may need treatment
+ * during the application (e.g., body parameters may be deserialized based on
+ * content type).
+ *
+ * Additionally, this interface recognizes the utility of introspecting a
+ * request to derive and match additional parameters (e.g., via URI path
+ * matching, decrypting cookie values, deserializing non-form-encoded body
+ * content, matching authorization headers to users, etc). These parameters
+ * are stored in an "attributes" property.
+ *
+ * Requests are considered immutable; all methods that might change state MUST
+ * be implemented such that they retain the internal state of the current
+ * message and return an instance that contains the changed state.
+ */
interface ServerRequestInterface extends RequestInterface
{
+ /**
+ * Retrieve server parameters.
+ *
+ * Retrieves data related to the incoming request environment,
+ * typically derived from PHP's $_SERVER superglobal. The data IS NOT
+ * REQUIRED to originate from $_SERVER.
+ *
+ * @return array
+ */
public function getServerParams();
+
+ /**
+ * Retrieve cookies.
+ *
+ * Retrieves cookies sent by the client to the server.
+ *
+ * The data MUST be compatible with the structure of the $_COOKIE
+ * superglobal.
+ *
+ * @return array
+ */
public function getCookieParams();
+
+ /**
+ * Return an instance with the specified cookies.
+ *
+ * The data IS NOT REQUIRED to come from the $_COOKIE superglobal, but MUST
+ * be compatible with the structure of $_COOKIE. Typically, this data will
+ * be injected at instantiation.
+ *
+ * This method MUST NOT update the related Cookie header of the request
+ * instance, nor related values in the server params.
+ *
+ * This method MUST be implemented in such a way as to retain the
+ * immutability of the message, and MUST return an instance that has the
+ * updated cookie values.
+ *
+ * @param array $cookies Array of key/value pairs representing cookies.
+ * @return static
+ */
public function withCookieParams(array $cookies);
+
+ /**
+ * Retrieve query string arguments.
+ *
+ * Retrieves the deserialized query string arguments, if any.
+ *
+ * Note: the query params might not be in sync with the URI or server
+ * params. If you need to ensure you are only getting the original
+ * values, you may need to parse the query string from `getUri()->getQuery()`
+ * or from the `QUERY_STRING` server param.
+ *
+ * @return array
+ */
public function getQueryParams();
+
+ /**
+ * Return an instance with the specified query string arguments.
+ *
+ * These values SHOULD remain immutable over the course of the incoming
+ * request. They MAY be injected during instantiation, such as from PHP's
+ * $_GET superglobal, or MAY be derived from some other value such as the
+ * URI. In cases where the arguments are parsed from the URI, the data
+ * MUST be compatible with what PHP's parse_str() would return for
+ * purposes of how duplicate query parameters are handled, and how nested
+ * sets are handled.
+ *
+ * Setting query string arguments MUST NOT change the URI stored by the
+ * request, nor the values in the server params.
+ *
+ * This method MUST be implemented in such a way as to retain the
+ * immutability of the message, and MUST return an instance that has the
+ * updated query string arguments.
+ *
+ * @param array $query Array of query string arguments, typically from
+ * $_GET.
+ * @return static
+ */
public function withQueryParams(array $query);
+
+ /**
+ * Retrieve normalized file upload data.
+ *
+ * This method returns upload metadata in a normalized tree, with each leaf
+ * an instance of Psr\Http\Message\UploadedFileInterface.
+ *
+ * These values MAY be prepared from $_FILES or the message body during
+ * instantiation, or MAY be injected via withUploadedFiles().
+ *
+ * @return array An array tree of UploadedFileInterface instances; an empty
+ * array MUST be returned if no data is present.
+ */
public function getUploadedFiles();
+
+ /**
+ * Create a new instance with the specified uploaded files.
+ *
+ * This method MUST be implemented in such a way as to retain the
+ * immutability of the message, and MUST return an instance that has the
+ * updated body parameters.
+ *
+ * @param array $uploadedFiles An array tree of UploadedFileInterface instances.
+ * @return static
+ * @throws \InvalidArgumentException if an invalid structure is provided.
+ */
public function withUploadedFiles(array $uploadedFiles);
+
+ /**
+ * Retrieve any parameters provided in the request body.
+ *
+ * If the request Content-Type is either application/x-www-form-urlencoded
+ * or multipart/form-data, and the request method is POST, this method MUST
+ * return the contents of $_POST.
+ *
+ * Otherwise, this method may return any results of deserializing
+ * the request body content; as parsing returns structured content, the
+ * potential types MUST be arrays or objects only. A null value indicates
+ * the absence of body content.
+ *
+ * @return null|array|object The deserialized body parameters, if any.
+ * These will typically be an array or object.
+ */
public function getParsedBody();
+
+ /**
+ * Return an instance with the specified body parameters.
+ *
+ * These MAY be injected during instantiation.
+ *
+ * If the request Content-Type is either application/x-www-form-urlencoded
+ * or multipart/form-data, and the request method is POST, use this method
+ * ONLY to inject the contents of $_POST.
+ *
+ * The data IS NOT REQUIRED to come from $_POST, but MUST be the results of
+ * deserializing the request body content. Deserialization/parsing returns
+ * structured data, and, as such, this method ONLY accepts arrays or objects,
+ * or a null value if nothing was available to parse.
+ *
+ * As an example, if content negotiation determines that the request data
+ * is a JSON payload, this method could be used to create a request
+ * instance with the deserialized parameters.
+ *
+ * This method MUST be implemented in such a way as to retain the
+ * immutability of the message, and MUST return an instance that has the
+ * updated body parameters.
+ *
+ * @param null|array|object $data The deserialized body data. This will
+ * typically be in an array or object.
+ * @return static
+ * @throws \InvalidArgumentException if an unsupported argument type is
+ * provided.
+ */
public function withParsedBody($data);
+
+ /**
+ * Retrieve attributes derived from the request.
+ *
+ * The request "attributes" may be used to allow injection of any
+ * parameters derived from the request: e.g., the results of path
+ * match operations; the results of decrypting cookies; the results of
+ * deserializing non-form-encoded message bodies; etc. Attributes
+ * will be application and request specific, and CAN be mutable.
+ *
+ * @return array Attributes derived from the request.
+ */
public function getAttributes();
+
+ /**
+ * Retrieve a single derived request attribute.
+ *
+ * Retrieves a single derived request attribute as described in
+ * getAttributes(). If the attribute has not been previously set, returns
+ * the default value as provided.
+ *
+ * This method obviates the need for a hasAttribute() method, as it allows
+ * specifying a default value to return if the attribute is not found.
+ *
+ * @see getAttributes()
+ * @param string $name The attribute name.
+ * @param mixed $default Default value to return if the attribute does not exist.
+ * @return mixed
+ */
public function getAttribute($name, $default = null);
+
+ /**
+ * Return an instance with the specified derived request attribute.
+ *
+ * This method allows setting a single derived request attribute as
+ * described in getAttributes().
+ *
+ * This method MUST be implemented in such a way as to retain the
+ * immutability of the message, and MUST return an instance that has the
+ * updated attribute.
+ *
+ * @see getAttributes()
+ * @param string $name The attribute name.
+ * @param mixed $value The value of the attribute.
+ * @return static
+ */
public function withAttribute($name, $value);
+
+ /**
+ * Return an instance that removes the specified derived request attribute.
+ *
+ * This method allows removing a single derived request attribute as
+ * described in getAttributes().
+ *
+ * This method MUST be implemented in such a way as to retain the
+ * immutability of the message, and MUST return an instance that removes
+ * the attribute.
+ *
+ * @see getAttributes()
+ * @param string $name The attribute name.
+ * @return static
+ */
public function withoutAttribute($name);
-}
+}
\ No newline at end of file
diff --git a/src/Psr/Message/StreamFactoryInterface.php b/src/Psr/Message/StreamFactoryInterface.php
index a513ce6..74298fa 100644
--- a/src/Psr/Message/StreamFactoryInterface.php
+++ b/src/Psr/Message/StreamFactoryInterface.php
@@ -4,7 +4,40 @@
interface StreamFactoryInterface
{
+ /**
+ * Create a new stream from a string.
+ *
+ * The stream SHOULD be created with a temporary resource.
+ *
+ * @param string $content String content with which to populate the stream.
+ *
+ * @return StreamInterface
+ */
public function createStream(string $content = ''): StreamInterface;
+
+ /**
+ * Create a stream from an existing file.
+ *
+ * The file MUST be opened using the given mode, which may be any mode
+ * supported by the `fopen` function.
+ *
+ * The `$filename` MAY be any string supported by `fopen()`.
+ *
+ * @param string $filename Filename or stream URI to use as basis of stream.
+ * @param string $mode Mode with which to open the underlying filename/stream.
+ *
+ * @return StreamInterface
+ */
public function createStreamFromFile(string $filename, string $mode = 'r'): StreamInterface;
+
+ /**
+ * Create a new stream from an existing resource.
+ *
+ * The stream MUST be readable and may be writable.
+ *
+ * @param resource $resource PHP resource to use as basis of stream.
+ *
+ * @return StreamInterface
+ */
public function createStreamFromResource($resource): StreamInterface;
-}
+}
\ No newline at end of file
diff --git a/src/Psr/Message/StreamInterface.php b/src/Psr/Message/StreamInterface.php
index a9cced5..c7b3368 100644
--- a/src/Psr/Message/StreamInterface.php
+++ b/src/Psr/Message/StreamInterface.php
@@ -2,21 +2,157 @@
namespace Psr\Http\Message;
+/**
+ * Describes a data stream.
+ *
+ * Typically, an instance will wrap a PHP stream; this interface provides
+ * a wrapper around the most common operations, including serialization of
+ * the entire stream to a string.
+ */
interface StreamInterface
{
+ /**
+ * Reads all data from the stream into a string, from the beginning to end.
+ *
+ * This method MUST attempt to seek to the beginning of the stream before
+ * reading data and read the stream until the end is reached.
+ *
+ * Warning: This could attempt to load a large amount of data into memory.
+ *
+ * This method MUST NOT raise an exception in order to conform with PHP's
+ * string casting operations.
+ *
+ * @see http://php.net/manual/en/language.oop5.magic.php#object.tostring
+ * @return string
+ */
public function __toString();
+
+ /**
+ * Closes the stream and any underlying resources.
+ *
+ * @return void
+ */
public function close();
+
+ /**
+ * Separates any underlying resources from the stream.
+ *
+ * After the stream has been detached, the stream is in an unusable state.
+ *
+ * @return resource|null Underlying PHP stream, if any
+ */
public function detach();
+
+ /**
+ * Get the size of the stream if known.
+ *
+ * @return int|null Returns the size in bytes if known, or null if unknown.
+ */
public function getSize();
+
+ /**
+ * Returns the current position of the file read/write pointer
+ *
+ * @return int Position of the file pointer
+ * @throws \RuntimeException on error.
+ */
public function tell();
+
+ /**
+ * Returns true if the stream is at the end of the stream.
+ *
+ * @return bool
+ */
public function eof();
+
+ /**
+ * Returns whether or not the stream is seekable.
+ *
+ * @return bool
+ */
public function isSeekable();
+
+ /**
+ * Seek to a position in the stream.
+ *
+ * @link http://www.php.net/manual/en/function.fseek.php
+ * @param int $offset Stream offset
+ * @param int $whence Specifies how the cursor position will be calculated
+ * based on the seek offset. Valid values are identical to the built-in
+ * PHP $whence values for `fseek()`. SEEK_SET: Set position equal to
+ * offset bytes SEEK_CUR: Set position to current location plus offset
+ * SEEK_END: Set position to end-of-stream plus offset.
+ * @throws \RuntimeException on failure.
+ */
public function seek($offset, $whence = SEEK_SET);
+
+ /**
+ * Seek to the beginning of the stream.
+ *
+ * If the stream is not seekable, this method will raise an exception;
+ * otherwise, it will perform a seek(0).
+ *
+ * @see seek()
+ * @link http://www.php.net/manual/en/function.fseek.php
+ * @throws \RuntimeException on failure.
+ */
public function rewind();
+
+ /**
+ * Returns whether or not the stream is writable.
+ *
+ * @return bool
+ */
public function isWritable();
+
+ /**
+ * Write data to the stream.
+ *
+ * @param string $string The string that is to be written.
+ * @return int Returns the number of bytes written to the stream.
+ * @throws \RuntimeException on failure.
+ */
public function write($string);
+
+ /**
+ * Returns whether or not the stream is readable.
+ *
+ * @return bool
+ */
public function isReadable();
+
+ /**
+ * Read data from the stream.
+ *
+ * @param int $length Read up to $length bytes from the object and return
+ * them. Fewer than $length bytes may be returned if underlying stream
+ * call returns fewer bytes.
+ * @return string Returns the data read from the stream, or an empty string
+ * if no bytes are available.
+ * @throws \RuntimeException if an error occurs.
+ */
public function read($length);
+
+ /**
+ * Returns the remaining contents in a string
+ *
+ * @return string
+ * @throws \RuntimeException if unable to read or an error occurs while
+ * reading.
+ */
public function getContents();
+
+ /**
+ * Get stream metadata as an associative array or retrieve a specific key.
+ *
+ * The keys returned are identical to the keys returned from PHP's
+ * stream_get_meta_data() function.
+ *
+ * @link http://php.net/manual/en/function.stream-get-meta-data.php
+ * @param string $key Specific metadata to retrieve.
+ * @return array|mixed|null Returns an associative array if no key is
+ * provided. Returns a specific key value if a key is provided and the
+ * value is found, or null if the key is not found.
+ */
public function getMetadata($key = null);
-}
+}
\ No newline at end of file
diff --git a/src/Psr/Message/UploadedFileFactoryInterface.php b/src/Psr/Message/UploadedFileFactoryInterface.php
index f024451..e141121 100644
--- a/src/Psr/Message/UploadedFileFactoryInterface.php
+++ b/src/Psr/Message/UploadedFileFactoryInterface.php
@@ -4,6 +4,26 @@
interface UploadedFileFactoryInterface
{
+ /**
+ * Create a new uploaded file.
+ *
+ * If a size is not provided it will be determined by checking the size of
+ * the file.
+ *
+ * @see http://php.net/manual/features.file-upload.post-method.php
+ * @see http://php.net/manual/features.file-upload.errors.php
+ *
+ * @param StreamInterface $stream Underlying stream representing the
+ * uploaded file content.
+ * @param int $size in bytes
+ * @param int $error PHP file upload error
+ * @param string $clientFilename Filename as provided by the client, if any.
+ * @param string $clientMediaType Media type as provided by the client, if any.
+ *
+ * @return UploadedFileInterface
+ *
+ * @throws \InvalidArgumentException If the file resource is not readable.
+ */
public function createUploadedFile(
StreamInterface $stream,
int $size = null,
@@ -11,4 +31,4 @@ public function createUploadedFile(
string $clientFilename = null,
string $clientMediaType = null
): UploadedFileInterface;
-}
+}
\ No newline at end of file
diff --git a/src/Psr/Message/UploadedFileInterface.php b/src/Psr/Message/UploadedFileInterface.php
index 54c31d2..eba5756 100644
--- a/src/Psr/Message/UploadedFileInterface.php
+++ b/src/Psr/Message/UploadedFileInterface.php
@@ -2,12 +2,122 @@
namespace Psr\Http\Message;
+/**
+ * Value object representing a file uploaded through an HTTP request.
+ *
+ * Instances of this interface are considered immutable; all methods that
+ * might change state MUST be implemented such that they retain the internal
+ * state of the current instance and return an instance that contains the
+ * changed state.
+ */
interface UploadedFileInterface
{
+ /**
+ * Retrieve a stream representing the uploaded file.
+ *
+ * This method MUST return a StreamInterface instance, representing the
+ * uploaded file. The purpose of this method is to allow utilizing native PHP
+ * stream functionality to manipulate the file upload, such as
+ * stream_copy_to_stream() (though the result will need to be decorated in a
+ * native PHP stream wrapper to work with such functions).
+ *
+ * If the moveTo() method has been called previously, this method MUST raise
+ * an exception.
+ *
+ * @return StreamInterface Stream representation of the uploaded file.
+ * @throws \RuntimeException in cases when no stream is available or can be
+ * created.
+ */
public function getStream();
+
+ /**
+ * Move the uploaded file to a new location.
+ *
+ * Use this method as an alternative to move_uploaded_file(). This method is
+ * guaranteed to work in both SAPI and non-SAPI environments.
+ * Implementations must determine which environment they are in, and use the
+ * appropriate method (move_uploaded_file(), rename(), or a stream
+ * operation) to perform the operation.
+ *
+ * $targetPath may be an absolute path, or a relative path. If it is a
+ * relative path, resolution should be the same as used by PHP's rename()
+ * function.
+ *
+ * The original file or stream MUST be removed on completion.
+ *
+ * If this method is called more than once, any subsequent calls MUST raise
+ * an exception.
+ *
+ * When used in an SAPI environment where $_FILES is populated, when writing
+ * files via moveTo(), is_uploaded_file() and move_uploaded_file() SHOULD be
+ * used to ensure permissions and upload status are verified correctly.
+ *
+ * If you wish to move to a stream, use getStream(), as SAPI operations
+ * cannot guarantee writing to stream destinations.
+ *
+ * @see http://php.net/is_uploaded_file
+ * @see http://php.net/move_uploaded_file
+ * @param string $targetPath Path to which to move the uploaded file.
+ * @throws \InvalidArgumentException if the $targetPath specified is invalid.
+ * @throws \RuntimeException on any error during the move operation, or on
+ * the second or subsequent call to the method.
+ */
public function moveTo($targetPath);
+
+ /**
+ * Retrieve the file size.
+ *
+ * Implementations SHOULD return the value stored in the "size" key of
+ * the file in the $_FILES array if available, as PHP calculates this based
+ * on the actual size transmitted.
+ *
+ * @return int|null The file size in bytes or null if unknown.
+ */
public function getSize();
+
+ /**
+ * Retrieve the error associated with the uploaded file.
+ *
+ * The return value MUST be one of PHP's UPLOAD_ERR_XXX constants.
+ *
+ * If the file was uploaded successfully, this method MUST return
+ * UPLOAD_ERR_OK.
+ *
+ * Implementations SHOULD return the value stored in the "error" key of
+ * the file in the $_FILES array.
+ *
+ * @see http://php.net/manual/en/features.file-upload.errors.php
+ * @return int One of PHP's UPLOAD_ERR_XXX constants.
+ */
public function getError();
+
+ /**
+ * Retrieve the filename sent by the client.
+ *
+ * Do not trust the value returned by this method. A client could send
+ * a malicious filename with the intention to corrupt or hack your
+ * application.
+ *
+ * Implementations SHOULD return the value stored in the "name" key of
+ * the file in the $_FILES array.
+ *
+ * @return string|null The filename sent by the client or null if none
+ * was provided.
+ */
public function getClientFilename();
+
+ /**
+ * Retrieve the media type sent by the client.
+ *
+ * Do not trust the value returned by this method. A client could send
+ * a malicious media type with the intention to corrupt or hack your
+ * application.
+ *
+ * Implementations SHOULD return the value stored in the "type" key of
+ * the file in the $_FILES array.
+ *
+ * @return string|null The media type sent by the client or null if none
+ * was provided.
+ */
public function getClientMediaType();
-}
+}
\ No newline at end of file
diff --git a/src/Psr/Message/UriFactoryInterface.php b/src/Psr/Message/UriFactoryInterface.php
index 9a645bd..348f34a 100644
--- a/src/Psr/Message/UriFactoryInterface.php
+++ b/src/Psr/Message/UriFactoryInterface.php
@@ -4,5 +4,14 @@
interface UriFactoryInterface
{
- public function createUri(string $uri = '') : UriInterface;
-}
+ /**
+ * Create a new URI.
+ *
+ * @param string $uri
+ *
+ * @return UriInterface
+ *
+ * @throws \InvalidArgumentException If the given URI cannot be parsed.
+ */
+ public function createUri(string $uri = ''): UriInterface;
+}
\ No newline at end of file
diff --git a/src/Psr/Message/UriInterface.php b/src/Psr/Message/UriInterface.php
index 713773f..6adb8fc 100644
--- a/src/Psr/Message/UriInterface.php
+++ b/src/Psr/Message/UriInterface.php
@@ -1,23 +1,323 @@
+ * [user-info@]host[:port]
+ *
+ *
+ * If the port component is not set or is the standard port for the current
+ * scheme, it SHOULD NOT be included.
+ *
+ * @see https://tools.ietf.org/html/rfc3986#section-3.2
+ * @return string The URI authority, in "[user-info@]host[:port]" format.
+ */
public function getAuthority();
+
+ /**
+ * Retrieve the user information component of the URI.
+ *
+ * If no user information is present, this method MUST return an empty
+ * string.
+ *
+ * If a user is present in the URI, this will return that value;
+ * additionally, if the password is also present, it will be appended to the
+ * user value, with a colon (":") separating the values.
+ *
+ * The trailing "@" character is not part of the user information and MUST
+ * NOT be added.
+ *
+ * @return string The URI user information, in "username[:password]" format.
+ */
public function getUserInfo();
+
+ /**
+ * Retrieve the host component of the URI.
+ *
+ * If no host is present, this method MUST return an empty string.
+ *
+ * The value returned MUST be normalized to lowercase, per RFC 3986
+ * Section 3.2.2.
+ *
+ * @see http://tools.ietf.org/html/rfc3986#section-3.2.2
+ * @return string The URI host.
+ */
public function getHost();
+
+ /**
+ * Retrieve the port component of the URI.
+ *
+ * If a port is present, and it is non-standard for the current scheme,
+ * this method MUST return it as an integer. If the port is the standard port
+ * used with the current scheme, this method SHOULD return null.
+ *
+ * If no port is present, and no scheme is present, this method MUST return
+ * a null value.
+ *
+ * If no port is present, but a scheme is present, this method MAY return
+ * the standard port for that scheme, but SHOULD return null.
+ *
+ * @return null|int The URI port.
+ */
public function getPort();
+
+ /**
+ * Retrieve the path component of the URI.
+ *
+ * The path can either be empty or absolute (starting with a slash) or
+ * rootless (not starting with a slash). Implementations MUST support all
+ * three syntaxes.
+ *
+ * Normally, the empty path "" and absolute path "/" are considered equal as
+ * defined in RFC 7230 Section 2.7.3. But this method MUST NOT automatically
+ * do this normalization because in contexts with a trimmed base path, e.g.
+ * the front controller, this difference becomes significant. It's the task
+ * of the user to handle both "" and "/".
+ *
+ * The value returned MUST be percent-encoded, but MUST NOT double-encode
+ * any characters. To determine what characters to encode, please refer to
+ * RFC 3986, Sections 2 and 3.3.
+ *
+ * As an example, if the value should include a slash ("/") not intended as
+ * delimiter between path segments, that value MUST be passed in encoded
+ * form (e.g., "%2F") to the instance.
+ *
+ * @see https://tools.ietf.org/html/rfc3986#section-2
+ * @see https://tools.ietf.org/html/rfc3986#section-3.3
+ * @return string The URI path.
+ */
public function getPath();
+
+ /**
+ * Retrieve the query string of the URI.
+ *
+ * If no query string is present, this method MUST return an empty string.
+ *
+ * The leading "?" character is not part of the query and MUST NOT be
+ * added.
+ *
+ * The value returned MUST be percent-encoded, but MUST NOT double-encode
+ * any characters. To determine what characters to encode, please refer to
+ * RFC 3986, Sections 2 and 3.4.
+ *
+ * As an example, if a value in a key/value pair of the query string should
+ * include an ampersand ("&") not intended as a delimiter between values,
+ * that value MUST be passed in encoded form (e.g., "%26") to the instance.
+ *
+ * @see https://tools.ietf.org/html/rfc3986#section-2
+ * @see https://tools.ietf.org/html/rfc3986#section-3.4
+ * @return string The URI query string.
+ */
public function getQuery();
+
+ /**
+ * Retrieve the fragment component of the URI.
+ *
+ * If no fragment is present, this method MUST return an empty string.
+ *
+ * The leading "#" character is not part of the fragment and MUST NOT be
+ * added.
+ *
+ * The value returned MUST be percent-encoded, but MUST NOT double-encode
+ * any characters. To determine what characters to encode, please refer to
+ * RFC 3986, Sections 2 and 3.5.
+ *
+ * @see https://tools.ietf.org/html/rfc3986#section-2
+ * @see https://tools.ietf.org/html/rfc3986#section-3.5
+ * @return string The URI fragment.
+ */
public function getFragment();
+
+ /**
+ * Return an instance with the specified scheme.
+ *
+ * This method MUST retain the state of the current instance, and return
+ * an instance that contains the specified scheme.
+ *
+ * Implementations MUST support the schemes "http" and "https" case
+ * insensitively, and MAY accommodate other schemes if required.
+ *
+ * An empty scheme is equivalent to removing the scheme.
+ *
+ * @param string $scheme The scheme to use with the new instance.
+ * @return static A new instance with the specified scheme.
+ * @throws \InvalidArgumentException for invalid or unsupported schemes.
+ */
public function withScheme($scheme);
+
+ /**
+ * Return an instance with the specified user information.
+ *
+ * This method MUST retain the state of the current instance, and return
+ * an instance that contains the specified user information.
+ *
+ * Password is optional, but the user information MUST include the
+ * user; an empty string for the user is equivalent to removing user
+ * information.
+ *
+ * @param string $user The user name to use for authority.
+ * @param null|string $password The password associated with $user.
+ * @return static A new instance with the specified user information.
+ */
public function withUserInfo($user, $password = null);
+
+ /**
+ * Return an instance with the specified host.
+ *
+ * This method MUST retain the state of the current instance, and return
+ * an instance that contains the specified host.
+ *
+ * An empty host value is equivalent to removing the host.
+ *
+ * @param string $host The hostname to use with the new instance.
+ * @return static A new instance with the specified host.
+ * @throws \InvalidArgumentException for invalid hostnames.
+ */
public function withHost($host);
+
+ /**
+ * Return an instance with the specified port.
+ *
+ * This method MUST retain the state of the current instance, and return
+ * an instance that contains the specified port.
+ *
+ * Implementations MUST raise an exception for ports outside the
+ * established TCP and UDP port ranges.
+ *
+ * A null value provided for the port is equivalent to removing the port
+ * information.
+ *
+ * @param null|int $port The port to use with the new instance; a null value
+ * removes the port information.
+ * @return static A new instance with the specified port.
+ * @throws \InvalidArgumentException for invalid ports.
+ */
public function withPort($port);
+
+ /**
+ * Return an instance with the specified path.
+ *
+ * This method MUST retain the state of the current instance, and return
+ * an instance that contains the specified path.
+ *
+ * The path can either be empty or absolute (starting with a slash) or
+ * rootless (not starting with a slash). Implementations MUST support all
+ * three syntaxes.
+ *
+ * If the path is intended to be domain-relative rather than path relative then
+ * it must begin with a slash ("/"). Paths not starting with a slash ("/")
+ * are assumed to be relative to some base path known to the application or
+ * consumer.
+ *
+ * Users can provide both encoded and decoded path characters.
+ * Implementations ensure the correct encoding as outlined in getPath().
+ *
+ * @param string $path The path to use with the new instance.
+ * @return static A new instance with the specified path.
+ * @throws \InvalidArgumentException for invalid paths.
+ */
public function withPath($path);
+
+ /**
+ * Return an instance with the specified query string.
+ *
+ * This method MUST retain the state of the current instance, and return
+ * an instance that contains the specified query string.
+ *
+ * Users can provide both encoded and decoded query characters.
+ * Implementations ensure the correct encoding as outlined in getQuery().
+ *
+ * An empty query string value is equivalent to removing the query string.
+ *
+ * @param string $query The query string to use with the new instance.
+ * @return static A new instance with the specified query string.
+ * @throws \InvalidArgumentException for invalid query strings.
+ */
public function withQuery($query);
+
+ /**
+ * Return an instance with the specified URI fragment.
+ *
+ * This method MUST retain the state of the current instance, and return
+ * an instance that contains the specified URI fragment.
+ *
+ * Users can provide both encoded and decoded fragment characters.
+ * Implementations ensure the correct encoding as outlined in getFragment().
+ *
+ * An empty fragment value is equivalent to removing the fragment.
+ *
+ * @param string $fragment The fragment to use with the new instance.
+ * @return static A new instance with the specified fragment.
+ */
public function withFragment($fragment);
+
+ /**
+ * Return the string representation as a URI reference.
+ *
+ * Depending on which components of the URI are present, the resulting
+ * string is either a full URI or relative reference according to RFC 3986,
+ * Section 4.1. The method concatenates the various components of the URI,
+ * using the appropriate delimiters:
+ *
+ * - If a scheme is present, it MUST be suffixed by ":".
+ * - If an authority is present, it MUST be prefixed by "//".
+ * - The path can be concatenated without delimiters. But there are two
+ * cases where the path has to be adjusted to make the URI reference
+ * valid as PHP does not allow to throw an exception in __toString():
+ * - If the path is rootless and an authority is present, the path MUST
+ * be prefixed by "/".
+ * - If the path is starting with more than one "/" and no authority is
+ * present, the starting slashes MUST be reduced to one.
+ * - If a query is present, it MUST be prefixed by "?".
+ * - If a fragment is present, it MUST be prefixed by "#".
+ *
+ * @see http://tools.ietf.org/html/rfc3986#section-4.1
+ * @return string
+ */
public function __toString();
-}
+}
\ No newline at end of file
diff --git a/src/Psr/Server/MiddlewareInterface.php b/src/Psr/Server/MiddlewareInterface.php
index 6741d40..0393475 100644
--- a/src/Psr/Server/MiddlewareInterface.php
+++ b/src/Psr/Server/MiddlewareInterface.php
@@ -5,7 +5,26 @@
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
+/**
+ * Participant in processing a server request and response.
+ *
+ * An HTTP middleware component participates in processing an HTTP message:
+ * by acting on the request, generating the response, or forwarding the
+ * request to a subsequent middleware and possibly acting on its response.
+ */
interface MiddlewareInterface
{
+ /**
+ * Process an incoming server request.
+ *
+ * Processes an incoming server request in order to produce a response.
+ * If unable to produce the response itself, it may delegate to the provided
+ * request handler to do so.
+ *
+ * @param ServerRequestInterface $request
+ * @param RequestHandlerInterface $handler
+ *
+ * @return ResponseInterface
+ */
public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface;
-}
+}
\ No newline at end of file
diff --git a/src/Psr/Server/RequestHandlerInterface.php b/src/Psr/Server/RequestHandlerInterface.php
index cbda5e8..7e38f1f 100644
--- a/src/Psr/Server/RequestHandlerInterface.php
+++ b/src/Psr/Server/RequestHandlerInterface.php
@@ -5,7 +5,22 @@
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
+/**
+ * Handles a server request and produces a response.
+ *
+ * An HTTP request handler process an HTTP request in order to produce an
+ * HTTP response.
+ */
interface RequestHandlerInterface
{
+ /**
+ * Handles a request and produces a response.
+ *
+ * May call other collaborating code to generate the response.
+ *
+ * @param ServerRequestInterface $request
+ *
+ * @return ResponseInterface
+ */
public function handle(ServerRequestInterface $request): ResponseInterface;
-}
+}
\ No newline at end of file
diff --git a/tests/FactoryTest.php b/tests/FactoryTest.php
index 6209c13..87b8b3a 100644
--- a/tests/FactoryTest.php
+++ b/tests/FactoryTest.php
@@ -30,7 +30,29 @@ public function testCreateResponse()
$this->assertInstanceOf(StreamInterface::class, $r->getBody());
$this->assertSame('', (string) $r->getBody());
}
-
+
+ public function testCreateResponseBody()
+ {
+ $r = (new Factory())->createResponseBody(201, 'yolo');
+ $this->assertSame(201, $r->getStatusCode());
+ $this->assertSame('1.1', $r->getProtocolVersion());
+ $this->assertSame('Created', $r->getReasonPhrase());
+ $this->assertSame([], $r->getHeaders());
+ $this->assertInstanceOf(StreamInterface::class, $r->getBody());
+ $this->assertSame('yolo', (string) $r->getBody());
+ }
+
+ public function testCreateRedirection()
+ {
+ $r = (new Factory())->createRedirection('/blog/');
+ $this->assertSame(301, $r->getStatusCode());
+ $this->assertSame('1.1', $r->getProtocolVersion());
+ $this->assertSame('Moved Permanently', $r->getReasonPhrase());
+ $this->assertEquals(['Location' => ['/blog/']], $r->getHeaders());
+ $this->assertInstanceOf(StreamInterface::class, $r->getBody());
+ $this->assertSame('', (string) $r->getBody());
+ }
+
public function testCreateServerRequest()
{
$r = (new Factory())->createServerRequest('POST', '/');
@@ -83,7 +105,7 @@ public function testCreateUri()
$this->assertEquals('/aze/', $r->getPath());
}
- public function testCreateUriFromArray()
+ public function testCreateUriFromServer()
{
$server = [
'PHP_SELF' => '/blog/article.php',
diff --git a/tests/ResponseTest.php b/tests/ResponseTest.php
index 32c789d..acc6032 100644
--- a/tests/ResponseTest.php
+++ b/tests/ResponseTest.php
@@ -40,14 +40,23 @@ public function testConstructorDoesNotReadStreamBody()
$this->assertSame($body, $r->getBody());
}
- public function testStatusCanBeNumericString()
+ public function testConstructStatusCantBeNumericString()
{
- $r = new Response('404');
- $r2 = $r->withStatus('201');
+ $this->expectException(\InvalidArgumentException::class);
+ $this->expectExceptionMessage('Status code has to be an integer');
+
+ $r = new Response('-404.4');
+ }
+
+ public function testWithStatusCantBeNumericString()
+ {
+ $r = new Response(404);
$this->assertSame(404, $r->getStatusCode());
$this->assertSame('Not Found', $r->getReasonPhrase());
- $this->assertSame(201, $r2->getStatusCode());
- $this->assertSame('Created', $r2->getReasonPhrase());
+
+ $this->expectException(\InvalidArgumentException::class);
+ $this->expectExceptionMessage('Status code has to be an integer');
+ $r->withStatus('201');
}
public function testCanConstructWithHeaders()
@@ -381,7 +390,7 @@ public function testWithStatusCodeMustHaveCorrectType()
public function testWithStatusReasonPhraseMustHaveCorrectType()
{
$this->expectException(\InvalidArgumentException::class);
- $this->expectExceptionMessage('Status code has to be an integer between 100 and 599');
+ $this->expectExceptionMessage('Status code has to be an integer between 100 and 799');
$r = new Response();
$r->withStatus(9, []);