<?php /** * Slim Framework (https://slimframework.com) * * @license https://github.com/slimphp/Slim-Psr7/blob/master/LICENSE.md (MIT License) */ declare(strict_types=1); namespace Slim\Psr7; use InvalidArgumentException; use Psr\Http\Message\ServerRequestInterface; use Psr\Http\Message\StreamInterface; use Psr\Http\Message\UploadedFileInterface; use Psr\Http\Message\UriInterface; use Slim\Psr7\Interfaces\HeadersInterface; use function get_class; use function gettype; use function is_array; use function is_null; use function is_object; use function is_string; use function ltrim; use function parse_str; use function preg_match; use function sprintf; use function str_replace; class Request extends Message implements ServerRequestInterface { /** * @var string */ protected $method; /** * @var UriInterface */ protected $uri; /** * @var string */ protected $requestTarget; /** * @var ?array */ protected $queryParams; /** * @var array */ protected $cookies; /** * @var array */ protected $serverParams; /** * @var array */ protected $attributes; /** * @var null|array|object */ protected $parsedBody; /** * @var UploadedFileInterface[] */ protected $uploadedFiles; /** * @param string $method The request method * @param UriInterface $uri The request URI object * @param HeadersInterface $headers The request headers collection * @param array $cookies The request cookies collection * @param array $serverParams The server environment variables * @param StreamInterface $body The request body object * @param array $uploadedFiles The request uploadedFiles collection * @throws InvalidArgumentException on invalid HTTP method */ public function __construct( $method, UriInterface $uri, HeadersInterface $headers, array $cookies, array $serverParams, StreamInterface $body, array $uploadedFiles = [] ) { $this->method = $this->filterMethod($method); $this->uri = $uri; $this->headers = $headers; $this->cookies = $cookies; $this->serverParams = $serverParams; $this->attributes = []; $this->body = $body; $this->uploadedFiles = $uploadedFiles; if (isset($serverParams['SERVER_PROTOCOL'])) { $this->protocolVersion = str_replace('HTTP/', '', $serverParams['SERVER_PROTOCOL']); } if (!$this->headers->hasHeader('Host') || $this->uri->getHost() !== '') { $this->headers->setHeader('Host', $this->uri->getHost()); } } /** * This method is applied to the cloned object after PHP performs an initial shallow-copy. * This method completes a deep-copy by creating new objects for the cloned object's internal reference pointers. */ public function __clone() { $this->headers = clone $this->headers; $this->body = clone $this->body; } /** * {@inheritdoc} */ public function getMethod(): string { return $this->method; } /** * {@inheritdoc} */ public function withMethod($method) { $method = $this->filterMethod($method); $clone = clone $this; $clone->method = $method; return $clone; } /** * Validate the HTTP method * * @param string $method * * @return string * * @throws InvalidArgumentException on invalid HTTP method. */ protected function filterMethod($method): string { /** @var mixed $method */ if (!is_string($method)) { throw new InvalidArgumentException(sprintf( 'Unsupported HTTP method; must be a string, received %s', (is_object($method) ? get_class($method) : gettype($method)) )); } if (preg_match("/^[!#$%&'*+.^_`|~0-9a-z-]+$/i", $method) !== 1) { throw new InvalidArgumentException(sprintf( 'Unsupported HTTP method "%s" provided', $method )); } return $method; } /** * {@inheritdoc} */ public function getRequestTarget(): string { if ($this->requestTarget) { return $this->requestTarget; } if ($this->uri === null) { return '/'; } $path = $this->uri->getPath(); $path = '/' . ltrim($path, '/'); $query = $this->uri->getQuery(); if ($query) { $path .= '?' . $query; } return $path; } /** * {@inheritdoc} */ public function withRequestTarget($requestTarget) { if (preg_match('#\s#', $requestTarget)) { throw new InvalidArgumentException( 'Invalid request target provided; must be a string and cannot contain whitespace' ); } $clone = clone $this; $clone->requestTarget = $requestTarget; return $clone; } /** * {@inheritdoc} */ public function getUri(): UriInterface { return $this->uri; } /** * {@inheritdoc} */ public function withUri(UriInterface $uri, $preserveHost = false) { $clone = clone $this; $clone->uri = $uri; if (!$preserveHost && $uri->getHost() !== '') { $clone->headers->setHeader('Host', $uri->getHost()); return $clone; } if (($uri->getHost() !== '' && !$this->hasHeader('Host') || $this->getHeaderLine('Host') === '')) { $clone->headers->setHeader('Host', $uri->getHost()); return $clone; } return $clone; } /** * {@inheritdoc} */ public function getCookieParams(): array { return $this->cookies; } /** * {@inheritdoc} */ public function withCookieParams(array $cookies) { $clone = clone $this; $clone->cookies = $cookies; return $clone; } /** * {@inheritdoc} */ public function getQueryParams(): array { if (is_array($this->queryParams)) { return $this->queryParams; } if ($this->uri === null) { return []; } parse_str($this->uri->getQuery(), $this->queryParams); // <-- URL decodes data assert(is_array($this->queryParams)); return $this->queryParams; } /** * {@inheritdoc} */ public function withQueryParams(array $query) { $clone = clone $this; $clone->queryParams = $query; return $clone; } /** * {@inheritdoc} */ public function getUploadedFiles(): array { return $this->uploadedFiles; } /** * {@inheritdoc} */ public function withUploadedFiles(array $uploadedFiles) { $clone = clone $this; $clone->uploadedFiles = $uploadedFiles; return $clone; } /** * {@inheritdoc} */ public function getServerParams(): array { return $this->serverParams; } /** * {@inheritdoc} */ public function getAttributes(): array { return $this->attributes; } /** * {@inheritdoc} */ public function getAttribute($name, $default = null) { return isset($this->attributes[$name]) ? $this->attributes[$name] : $default; } /** * {@inheritdoc} */ public function withAttribute($name, $value) { $clone = clone $this; $clone->attributes[$name] = $value; return $clone; } /** * {@inheritdoc} */ public function withoutAttribute($name) { $clone = clone $this; unset($clone->attributes[$name]); return $clone; } /** * {@inheritdoc} */ public function getParsedBody() { return $this->parsedBody; } /** * {@inheritdoc} */ public function withParsedBody($data) { /** @var mixed $data */ if (!is_null($data) && !is_object($data) && !is_array($data)) { throw new InvalidArgumentException('Parsed body value must be an array, an object, or null'); } $clone = clone $this; $clone->parsedBody = $data; return $clone; } }