Chris@0: validateMethod($method); Chris@0: Chris@0: $this->method = $method ?: ''; Chris@0: $this->uri = $this->createUri($uri); Chris@0: $this->stream = $this->getStream($body, 'wb+'); Chris@0: Chris@0: $this->setHeaders($headers); Chris@0: Chris@0: // per PSR-7: attempt to set the Host header from a provided URI if no Chris@0: // Host header is provided Chris@0: if (! $this->hasHeader('Host') && $this->uri->getHost()) { Chris@0: $this->headerNames['host'] = 'Host'; Chris@0: $this->headers['Host'] = [$this->getHostFromUri()]; Chris@0: } Chris@0: } Chris@0: Chris@0: /** Chris@0: * Create and return a URI instance. Chris@0: * Chris@0: * If `$uri` is a already a `UriInterface` instance, returns it. Chris@0: * Chris@0: * If `$uri` is a string, passes it to the `Uri` constructor to return an Chris@0: * instance. Chris@0: * Chris@0: * If `$uri is null, creates and returns an empty `Uri` instance. Chris@0: * Chris@0: * Otherwise, it raises an exception. Chris@0: * Chris@0: * @param null|string|UriInterface $uri Chris@0: * @return UriInterface Chris@0: * @throws InvalidArgumentException Chris@0: */ Chris@0: private function createUri($uri) Chris@0: { Chris@0: if ($uri instanceof UriInterface) { Chris@0: return $uri; Chris@0: } Chris@0: if (is_string($uri)) { Chris@0: return new Uri($uri); Chris@0: } Chris@0: if ($uri === null) { Chris@0: return new Uri(); Chris@0: } Chris@0: throw new InvalidArgumentException( Chris@0: 'Invalid URI provided; must be null, a string, or a Psr\Http\Message\UriInterface instance' Chris@0: ); Chris@0: } Chris@0: Chris@0: /** Chris@0: * Retrieves the message's request target. Chris@0: * Chris@0: * Retrieves the message's request-target either as it will appear (for Chris@0: * clients), as it appeared at request (for servers), or as it was Chris@0: * specified for the instance (see withRequestTarget()). Chris@0: * Chris@0: * In most cases, this will be the origin-form of the composed URI, Chris@0: * unless a value was provided to the concrete implementation (see Chris@0: * withRequestTarget() below). Chris@0: * Chris@0: * If no URI is available, and no request-target has been specifically Chris@0: * provided, this method MUST return the string "/". Chris@0: * Chris@0: * @return string Chris@0: */ Chris@0: public function getRequestTarget() Chris@0: { Chris@0: if (null !== $this->requestTarget) { Chris@0: return $this->requestTarget; Chris@0: } Chris@0: Chris@0: $target = $this->uri->getPath(); Chris@0: if ($this->uri->getQuery()) { Chris@0: $target .= '?' . $this->uri->getQuery(); Chris@0: } Chris@0: Chris@0: if (empty($target)) { Chris@0: $target = '/'; Chris@0: } Chris@0: Chris@0: return $target; Chris@0: } Chris@0: Chris@0: /** Chris@0: * Create a new instance with a specific request-target. Chris@0: * Chris@0: * If the request needs a non-origin-form request-target — e.g., for Chris@0: * specifying an absolute-form, authority-form, or asterisk-form — Chris@0: * this method may be used to create an instance with the specified Chris@0: * request-target, verbatim. Chris@0: * Chris@0: * This method MUST be implemented in such a way as to retain the Chris@0: * immutability of the message, and MUST return a new instance that has the Chris@0: * changed request target. Chris@0: * Chris@0: * @link http://tools.ietf.org/html/rfc7230#section-2.7 (for the various Chris@0: * request-target forms allowed in request messages) Chris@0: * @param mixed $requestTarget Chris@0: * @return static Chris@0: * @throws InvalidArgumentException if the request target is invalid. Chris@0: */ Chris@0: public function withRequestTarget($requestTarget) Chris@0: { Chris@0: if (preg_match('#\s#', $requestTarget)) { Chris@0: throw new InvalidArgumentException( Chris@0: 'Invalid request target provided; cannot contain whitespace' Chris@0: ); Chris@0: } Chris@0: Chris@0: $new = clone $this; Chris@0: $new->requestTarget = $requestTarget; Chris@0: return $new; Chris@0: } Chris@0: Chris@0: /** Chris@0: * Retrieves the HTTP method of the request. Chris@0: * Chris@0: * @return string Returns the request method. Chris@0: */ Chris@0: public function getMethod() Chris@0: { Chris@0: return $this->method; Chris@0: } Chris@0: Chris@0: /** Chris@0: * Return an instance with the provided HTTP method. Chris@0: * Chris@0: * While HTTP method names are typically all uppercase characters, HTTP Chris@0: * method names are case-sensitive and thus implementations SHOULD NOT Chris@0: * modify the given string. Chris@0: * Chris@0: * This method MUST be implemented in such a way as to retain the Chris@0: * immutability of the message, and MUST return an instance that has the Chris@0: * changed request method. Chris@0: * Chris@0: * @param string $method Case-insensitive method. Chris@0: * @return static Chris@0: * @throws InvalidArgumentException for invalid HTTP methods. Chris@0: */ Chris@0: public function withMethod($method) Chris@0: { Chris@0: $this->validateMethod($method); Chris@0: $new = clone $this; Chris@0: $new->method = $method; Chris@0: return $new; Chris@0: } Chris@0: Chris@0: /** Chris@0: * Retrieves the URI instance. Chris@0: * Chris@0: * This method MUST return a UriInterface instance. Chris@0: * Chris@0: * @link http://tools.ietf.org/html/rfc3986#section-4.3 Chris@0: * @return UriInterface Returns a UriInterface instance Chris@0: * representing the URI of the request, if any. Chris@0: */ Chris@0: public function getUri() Chris@0: { Chris@0: return $this->uri; Chris@0: } Chris@0: Chris@0: /** Chris@0: * Returns an instance with the provided URI. Chris@0: * Chris@0: * This method will update the Host header of the returned request by Chris@0: * default if the URI contains a host component. If the URI does not Chris@0: * contain a host component, any pre-existing Host header will be carried Chris@0: * over to the returned request. Chris@0: * Chris@0: * You can opt-in to preserving the original state of the Host header by Chris@0: * setting `$preserveHost` to `true`. When `$preserveHost` is set to Chris@0: * `true`, the returned request will not update the Host header of the Chris@0: * returned message -- even if the message contains no Host header. This Chris@0: * means that a call to `getHeader('Host')` on the original request MUST Chris@0: * equal the return value of a call to `getHeader('Host')` on the returned Chris@0: * request. Chris@0: * Chris@0: * This method MUST be implemented in such a way as to retain the Chris@0: * immutability of the message, and MUST return an instance that has the Chris@0: * new UriInterface instance. Chris@0: * Chris@0: * @link http://tools.ietf.org/html/rfc3986#section-4.3 Chris@0: * @param UriInterface $uri New request URI to use. Chris@0: * @param bool $preserveHost Preserve the original state of the Host header. Chris@0: * @return static Chris@0: */ Chris@0: public function withUri(UriInterface $uri, $preserveHost = false) Chris@0: { Chris@0: $new = clone $this; Chris@0: $new->uri = $uri; Chris@0: Chris@0: if ($preserveHost && $this->hasHeader('Host')) { Chris@0: return $new; Chris@0: } Chris@0: Chris@0: if (! $uri->getHost()) { Chris@0: return $new; Chris@0: } Chris@0: Chris@0: $host = $uri->getHost(); Chris@0: if ($uri->getPort()) { Chris@0: $host .= ':' . $uri->getPort(); Chris@0: } Chris@0: Chris@0: $new->headerNames['host'] = 'Host'; Chris@0: Chris@0: // Remove an existing host header if present, regardless of current Chris@0: // de-normalization of the header name. Chris@0: // @see https://github.com/zendframework/zend-diactoros/issues/91 Chris@0: foreach (array_keys($new->headers) as $header) { Chris@0: if (strtolower($header) === 'host') { Chris@0: unset($new->headers[$header]); Chris@0: } Chris@0: } Chris@0: Chris@0: $new->headers['Host'] = [$host]; Chris@0: Chris@0: return $new; Chris@0: } Chris@0: Chris@0: /** Chris@0: * Validate the HTTP method Chris@0: * Chris@0: * @param null|string $method Chris@0: * @throws InvalidArgumentException on invalid HTTP method. Chris@0: */ Chris@0: private function validateMethod($method) Chris@0: { Chris@0: if (null === $method) { Chris@0: return; Chris@0: } Chris@0: Chris@0: if (! is_string($method)) { Chris@0: throw new InvalidArgumentException(sprintf( Chris@0: 'Unsupported HTTP method; must be a string, received %s', Chris@0: (is_object($method) ? get_class($method) : gettype($method)) Chris@0: )); Chris@0: } Chris@0: Chris@0: if (! preg_match('/^[!#$%&\'*+.^_`\|~0-9a-z-]+$/i', $method)) { Chris@0: throw new InvalidArgumentException(sprintf( Chris@0: 'Unsupported HTTP method "%s" provided', Chris@0: $method Chris@0: )); Chris@0: } Chris@0: } Chris@0: Chris@0: /** Chris@0: * Retrieve the host from the URI instance Chris@0: * Chris@0: * @return string Chris@0: */ Chris@0: private function getHostFromUri() Chris@0: { Chris@0: $host = $this->uri->getHost(); Chris@0: $host .= $this->uri->getPort() ? ':' . $this->uri->getPort() : ''; Chris@0: return $host; Chris@0: } Chris@0: }