src/Request.php

Summary

Maintainability
A
3 hrs
Test Coverage
<?php
namespace Pluf\Http;

use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Message\StreamInterface;
use Psr\Http\Message\UriInterface;
use InvalidArgumentException;

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 \Psr\Http\Message\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;
    }
}