update 1.0.8.0

Commits for version update
This commit is contained in:
Manish Verma
2016-10-17 12:02:27 +05:30
parent dec927987b
commit 76e85db070
9674 changed files with 495757 additions and 58922 deletions

View File

@@ -1,5 +1,22 @@
# CHANGELOG
## 1.3.1 - 2016-06-25
* Fix `Uri::__toString` for network path references, e.g. `//example.org`.
* Fix missing lowercase normalization for host.
* Fix handling of URI components in case they are `'0'` in a lot of places,
e.g. as a user info password.
* Fix `Uri::withAddedHeader` to correctly merge headers with different case.
* Fix trimming of header values in `Uri::withAddedHeader`. Header values may
be surrounded by whitespace which should be ignored according to RFC 7230
Section 3.2.4. This does not apply to header names.
* Fix `Uri::withAddedHeader` with an array of header values.
* Fix `Uri::resolve` when base path has no slash and handling of fragment.
* Fix handling of encoding in `Uri::with(out)QueryValue` so one can pass the
key/value both in encoded as well as decoded form to those methods. This is
consistent with withPath, withQuery etc.
* Fix `ServerRequest::withoutAttribute` when attribute value is null.
## 1.3.0 - 2016-04-13
* Added remaining interfaces needed for full PSR7 compatibility

View File

@@ -1,9 +1,8 @@
# PSR-7 Message Implementation
This repository contains a partial [PSR-7](http://www.php-fig.org/psr/psr-7/)
This repository contains a full [PSR-7](http://www.php-fig.org/psr/psr-7/)
message implementation, several stream decorators, and some helpful
functionality like query string parsing. Currently missing
ServerRequestInterface and UploadedFileInterface; a pull request for these features is welcome.
functionality like query string parsing.
[![Build Status](https://travis-ci.org/guzzle/psr7.svg?branch=master)](https://travis-ci.org/guzzle/psr7)
@@ -30,7 +29,7 @@ $composed = new Psr7\AppendStream([$a, $b]);
$composed->addStream(Psr7\stream_for(' Above all listen to me'));
echo $composed(); // abc, 123. Above all listen to me.
echo $composed; // abc, 123. Above all listen to me.
```
@@ -106,7 +105,7 @@ echo $stream; // 0123456789
Compose stream implementations based on a hash of functions.
Allows for easy testing and extension of a provided stream without needing
Allows for easy testing and extension of a provided stream without needing
to create a concrete class for a simple extension point.
```php
@@ -501,7 +500,7 @@ an associative array (e.g., `foo[a]=1&foo[b]=2` will be parsed into
Build a query string from an array of key value pairs.
This function can use the return value of parseQuery() to build a query string.
This function can use the return value of parse_query() to build a query string.
This function does not modify the provided keys when an array is encountered
(like http_build_query would).
@@ -527,7 +526,7 @@ The `GuzzleHttp\Psr7\Uri` class has several static methods to manipulate URIs.
## `GuzzleHttp\Psr7\Uri::removeDotSegments`
`public static function removeDotSegments($path) -> UriInterface`
`public static function removeDotSegments(string $path): string`
Removes dot segments from a path and returns the new path.
@@ -536,7 +535,7 @@ See http://tools.ietf.org/html/rfc3986#section-5.2.4
## `GuzzleHttp\Psr7\Uri::resolve`
`public static function resolve(UriInterface $base, $rel) -> UriInterface`
`public static function resolve(UriInterface $base, $rel): UriInterface`
Resolve a base URI with a relative URI and return a new URI.
@@ -545,39 +544,26 @@ See http://tools.ietf.org/html/rfc3986#section-5
## `GuzzleHttp\Psr7\Uri::withQueryValue`
`public static function withQueryValue(UriInterface $uri, $key, $value) -> UriInterface`
`public static function withQueryValue(UriInterface $uri, $key, $value): UriInterface`
Create a new URI with a specific query string value.
Any existing query string values that exactly match the provided key are
removed and replaced with the given key value pair.
Note: this function will convert "=" to "%3D" and "&" to "%26".
## `GuzzleHttp\Psr7\Uri::withoutQueryValue`
`public static function withoutQueryValue(UriInterface $uri, $key, $value) -> UriInterface`
`public static function withoutQueryValue(UriInterface $uri, $key): UriInterface`
Create a new URI with a specific query string value removed.
Any existing query string values that exactly match the provided key are
removed.
Note: this function will convert "=" to "%3D" and "&" to "%26".
## `GuzzleHttp\Psr7\Uri::fromParts`
`public static function fromParts(array $parts) -> UriInterface`
`public static function fromParts(array $parts): UriInterface`
Create a `GuzzleHttp\Psr7\Uri` object from a hash of `parse_url` parts.
# Not Implemented
A few aspects of PSR-7 are not implemented in this project. A pull request for
any of these features is welcome:
- `Psr\Http\Message\ServerRequestInterface`
- `Psr\Http\Message\UploadedFileInterface`

View File

@@ -29,7 +29,7 @@
},
"extra": {
"branch-alias": {
"dev-master": "1.0-dev"
"dev-master": "1.4-dev"
}
}
}

View File

@@ -8,11 +8,11 @@ use Psr\Http\Message\StreamInterface;
*/
trait MessageTrait
{
/** @var array Cached HTTP header collection with lowercase key to values */
/** @var array Map of all registered headers, as original name => array of values */
private $headers = [];
/** @var array Actual key to list of values per header. */
private $headerLines = [];
/** @var array Map of lowercase header name => original name at registration */
private $headerNames = [];
/** @var string */
private $protocol = '1.1';
@@ -38,18 +38,25 @@ trait MessageTrait
public function getHeaders()
{
return $this->headerLines;
return $this->headers;
}
public function hasHeader($header)
{
return isset($this->headers[strtolower($header)]);
return isset($this->headerNames[strtolower($header)]);
}
public function getHeader($header)
{
$name = strtolower($header);
return isset($this->headers[$name]) ? $this->headers[$name] : [];
$header = strtolower($header);
if (!isset($this->headerNames[$header])) {
return [];
}
$header = $this->headerNames[$header];
return $this->headers[$header];
}
public function getHeaderLine($header)
@@ -59,59 +66,56 @@ trait MessageTrait
public function withHeader($header, $value)
{
$new = clone $this;
$header = trim($header);
$name = strtolower($header);
if (!is_array($value)) {
$new->headers[$name] = [trim($value)];
} else {
$new->headers[$name] = $value;
foreach ($new->headers[$name] as &$v) {
$v = trim($v);
}
$value = [$value];
}
// Remove the header lines.
foreach (array_keys($new->headerLines) as $key) {
if (strtolower($key) === $name) {
unset($new->headerLines[$key]);
}
}
$value = $this->trimHeaderValues($value);
$normalized = strtolower($header);
// Add the header line.
$new->headerLines[$header] = $new->headers[$name];
$new = clone $this;
if (isset($new->headerNames[$normalized])) {
unset($new->headers[$new->headerNames[$normalized]]);
}
$new->headerNames[$normalized] = $header;
$new->headers[$header] = $value;
return $new;
}
public function withAddedHeader($header, $value)
{
if (!$this->hasHeader($header)) {
return $this->withHeader($header, $value);
if (!is_array($value)) {
$value = [$value];
}
$value = $this->trimHeaderValues($value);
$normalized = strtolower($header);
$new = clone $this;
$new->headers[strtolower($header)][] = $value;
$new->headerLines[$header][] = $value;
if (isset($new->headerNames[$normalized])) {
$header = $this->headerNames[$normalized];
$new->headers[$header] = array_merge($this->headers[$header], $value);
} else {
$new->headerNames[$normalized] = $header;
$new->headers[$header] = $value;
}
return $new;
}
public function withoutHeader($header)
{
if (!$this->hasHeader($header)) {
$normalized = strtolower($header);
if (!isset($this->headerNames[$normalized])) {
return $this;
}
$new = clone $this;
$name = strtolower($header);
unset($new->headers[$name]);
$header = $this->headerNames[$normalized];
foreach (array_keys($new->headerLines) as $key) {
if (strtolower($key) === $name) {
unset($new->headerLines[$key]);
}
}
$new = clone $this;
unset($new->headers[$header], $new->headerNames[$normalized]);
return $new;
}
@@ -138,21 +142,42 @@ trait MessageTrait
private function setHeaders(array $headers)
{
$this->headerLines = $this->headers = [];
$this->headerNames = $this->headers = [];
foreach ($headers as $header => $value) {
$header = trim($header);
$name = strtolower($header);
if (!is_array($value)) {
$value = trim($value);
$this->headers[$name][] = $value;
$this->headerLines[$header][] = $value;
$value = [$value];
}
$value = $this->trimHeaderValues($value);
$normalized = strtolower($header);
if (isset($this->headerNames[$normalized])) {
$header = $this->headerNames[$normalized];
$this->headers[$header] = array_merge($this->headers[$header], $value);
} else {
foreach ($value as $v) {
$v = trim($v);
$this->headers[$name][] = $v;
$this->headerLines[$header][] = $v;
}
$this->headerNames[$normalized] = $header;
$this->headers[$header] = $value;
}
}
}
/**
* Trims whitespace from the header values.
*
* Spaces and tabs ought to be excluded by parsers when extracting the field value from a header field.
*
* header-field = field-name ":" OWS field-value OWS
* OWS = *( SP / HTAB )
*
* @param string[] $values Header values
*
* @return string[] Trimmed header values
*
* @see https://tools.ietf.org/html/rfc7230#section-3.2.4
*/
private function trimHeaderValues(array $values)
{
return array_map(function ($value) {
return trim($value, " \t");
}, $values);
}
}

View File

@@ -11,9 +11,7 @@ use Psr\Http\Message\UriInterface;
*/
class Request implements RequestInterface
{
use MessageTrait {
withHeader as protected withParentHeader;
}
use MessageTrait;
/** @var string */
private $method;
@@ -25,40 +23,33 @@ class Request implements RequestInterface
private $uri;
/**
* @param null|string $method HTTP method for the request.
* @param null|string|UriInterface $uri URI for the request.
* @param array $headers Headers for the message.
* @param string|resource|StreamInterface $body Message body.
* @param string $protocolVersion HTTP protocol version.
*
* @throws InvalidArgumentException for an invalid URI
* @param string $method HTTP method
* @param string|UriInterface $uri URI
* @param array $headers Request headers
* @param string|null|resource|StreamInterface $body Request body
* @param string $version Protocol version
*/
public function __construct(
$method,
$uri,
array $headers = [],
$body = null,
$protocolVersion = '1.1'
$version = '1.1'
) {
if (is_string($uri)) {
if (!($uri instanceof UriInterface)) {
$uri = new Uri($uri);
} elseif (!($uri instanceof UriInterface)) {
throw new \InvalidArgumentException(
'URI must be a string or Psr\Http\Message\UriInterface'
);
}
$this->method = strtoupper($method);
$this->uri = $uri;
$this->setHeaders($headers);
$this->protocol = $protocolVersion;
$this->protocol = $version;
$host = $uri->getHost();
if ($host && !$this->hasHeader('Host')) {
$this->updateHostFromUri($host);
if (!$this->hasHeader('Host')) {
$this->updateHostFromUri();
}
if ($body) {
if ($body !== '' && $body !== null) {
$this->stream = stream_for($body);
}
}
@@ -70,10 +61,10 @@ class Request implements RequestInterface
}
$target = $this->uri->getPath();
if ($target == null) {
if ($target == '') {
$target = '/';
}
if ($this->uri->getQuery()) {
if ($this->uri->getQuery() != '') {
$target .= '?' . $this->uri->getQuery();
}
@@ -120,30 +111,32 @@ class Request implements RequestInterface
$new->uri = $uri;
if (!$preserveHost) {
if ($host = $uri->getHost()) {
$new->updateHostFromUri($host);
}
$new->updateHostFromUri();
}
return $new;
}
public function withHeader($header, $value)
private function updateHostFromUri()
{
/** @var Request $newInstance */
$newInstance = $this->withParentHeader($header, $value);
return $newInstance;
}
$host = $this->uri->getHost();
private function updateHostFromUri($host)
{
// Ensure Host is the first header.
// See: http://tools.ietf.org/html/rfc7230#section-5.4
if ($port = $this->uri->getPort()) {
if ($host == '') {
return;
}
if (($port = $this->uri->getPort()) !== null) {
$host .= ':' . $port;
}
$this->headerLines = ['Host' => [$host]] + $this->headerLines;
$this->headers = ['host' => [$host]] + $this->headers;
if (isset($this->headerNames['host'])) {
$header = $this->headerNames['host'];
} else {
$header = 'Host';
$this->headerNames['host'] = 'Host';
}
// Ensure Host is the first header.
// See: http://tools.ietf.org/html/rfc7230#section-5.4
$this->headers = [$header => [$host]] + $this->headers;
}
}

View File

@@ -72,18 +72,18 @@ class Response implements ResponseInterface
511 => 'Network Authentication Required',
];
/** @var null|string */
/** @var string */
private $reasonPhrase = '';
/** @var int */
private $statusCode = 200;
/**
* @param int $status Status code for the response, if any.
* @param array $headers Headers for the response, if any.
* @param mixed $body Stream body.
* @param string $version Protocol version.
* @param string $reason Reason phrase (a default will be used if possible).
* @param int $status Status code
* @param array $headers Response headers
* @param string|null|resource|StreamInterface $body Response body
* @param string $version Protocol version
* @param string|null $reason Reason phrase (when empty a default will be used based on the status code)
*/
public function __construct(
$status = 200,
@@ -94,12 +94,12 @@ class Response implements ResponseInterface
) {
$this->statusCode = (int) $status;
if ($body !== null) {
if ($body !== '' && $body !== null) {
$this->stream = stream_for($body);
}
$this->setHeaders($headers);
if (!$reason && isset(self::$phrases[$this->statusCode])) {
if ($reason == '' && isset(self::$phrases[$this->statusCode])) {
$this->reasonPhrase = self::$phrases[$status];
} else {
$this->reasonPhrase = (string) $reason;
@@ -122,7 +122,7 @@ class Response implements ResponseInterface
{
$new = clone $this;
$new->statusCode = (int) $code;
if (!$reasonPhrase && isset(self::$phrases[$new->statusCode])) {
if ($reasonPhrase == '' && isset(self::$phrases[$new->statusCode])) {
$reasonPhrase = self::$phrases[$new->statusCode];
}
$new->reasonPhrase = $reasonPhrase;

View File

@@ -55,26 +55,24 @@ class ServerRequest extends Request implements ServerRequestInterface
private $uploadedFiles = [];
/**
* @param null|string $method HTTP method for the request
* @param null|string|UriInterface $uri URI for the request
* @param array $headers Headers for the message
* @param string|resource|StreamInterface $body Message body
* @param string $protocolVersion HTTP protocol version
* @param array $serverParams the value of $_SERVER superglobal
*
* @throws InvalidArgumentException for an invalid URI
* @param string $method HTTP method
* @param string|UriInterface $uri URI
* @param array $headers Request headers
* @param string|null|resource|StreamInterface $body Request body
* @param string $version Protocol version
* @param array $serverParams Typically the $_SERVER superglobal
*/
public function __construct(
$method,
$uri,
array $headers = [],
$body = null,
$protocolVersion = '1.1',
$version = '1.1',
array $serverParams = []
) {
$this->serverParams = $serverParams;
parent::__construct($method, $uri, $headers, $body, $protocolVersion);
parent::__construct($method, $uri, $headers, $body, $version);
}
/**
@@ -336,8 +334,8 @@ class ServerRequest extends Request implements ServerRequestInterface
*/
public function withoutAttribute($attribute)
{
if (false === isset($this->attributes[$attribute])) {
return clone $this;
if (false === array_key_exists($attribute, $this->attributes)) {
return $this;
}
$new = clone $this;

View File

@@ -4,10 +4,11 @@ namespace GuzzleHttp\Psr7;
use Psr\Http\Message\UriInterface;
/**
* Basic PSR-7 URI implementation.
* PSR-7 URI implementation.
*
* @link https://github.com/phly/http This class is based upon
* Matthew Weier O'Phinney's URI implementation in phly/http.
* @author Michael Dowling
* @author Tobias Schultze
* @author Matthew Weier O'Phinney
*/
class Uri implements UriInterface
{
@@ -42,11 +43,11 @@ class Uri implements UriInterface
private $fragment = '';
/**
* @param string $uri URI to parse and wrap.
* @param string $uri URI to parse
*/
public function __construct($uri = '')
{
if ($uri != null) {
if ($uri != '') {
$parts = parse_url($uri);
if ($parts === false) {
throw new \InvalidArgumentException("Unable to parse URI: $uri");
@@ -60,7 +61,7 @@ class Uri implements UriInterface
return self::createUriString(
$this->scheme,
$this->getAuthority(),
$this->getPath(),
$this->path,
$this->query,
$this->fragment
);
@@ -86,7 +87,7 @@ class Uri implements UriInterface
$results = [];
$segments = explode('/', $path);
foreach ($segments as $segment) {
if ($segment == '..') {
if ($segment === '..') {
array_pop($results);
} elseif (!isset($ignoreSegments[$segment])) {
$results[] = $segment;
@@ -102,7 +103,7 @@ class Uri implements UriInterface
}
// Add the trailing slash if necessary
if ($newPath != '/' && isset($ignoreSegments[end($segments)])) {
if ($newPath !== '/' && isset($ignoreSegments[end($segments)])) {
$newPath .= '/';
}
@@ -112,74 +113,62 @@ class Uri implements UriInterface
/**
* Resolve a base URI with a relative URI and return a new URI.
*
* @param UriInterface $base Base URI
* @param string $rel Relative URI
* @param UriInterface $base Base URI
* @param string|UriInterface $rel Relative URI
*
* @return UriInterface
* @link http://tools.ietf.org/html/rfc3986#section-5.2
*/
public static function resolve(UriInterface $base, $rel)
{
if ($rel === null || $rel === '') {
return $base;
}
if (!($rel instanceof UriInterface)) {
$rel = new self($rel);
}
// Return the relative uri as-is if it has a scheme.
if ($rel->getScheme()) {
return $rel->withPath(static::removeDotSegments($rel->getPath()));
if ((string) $rel === '') {
// we can simply return the same base URI instance for this same-document reference
return $base;
}
$relParts = [
'scheme' => $rel->getScheme(),
'authority' => $rel->getAuthority(),
'path' => $rel->getPath(),
'query' => $rel->getQuery(),
'fragment' => $rel->getFragment()
];
if ($rel->getScheme() != '') {
return $rel->withPath(self::removeDotSegments($rel->getPath()));
}
$parts = [
'scheme' => $base->getScheme(),
'authority' => $base->getAuthority(),
'path' => $base->getPath(),
'query' => $base->getQuery(),
'fragment' => $base->getFragment()
];
if (!empty($relParts['authority'])) {
$parts['authority'] = $relParts['authority'];
$parts['path'] = self::removeDotSegments($relParts['path']);
$parts['query'] = $relParts['query'];
$parts['fragment'] = $relParts['fragment'];
} elseif (!empty($relParts['path'])) {
if (substr($relParts['path'], 0, 1) == '/') {
$parts['path'] = self::removeDotSegments($relParts['path']);
$parts['query'] = $relParts['query'];
$parts['fragment'] = $relParts['fragment'];
if ($rel->getAuthority() != '') {
$targetAuthority = $rel->getAuthority();
$targetPath = self::removeDotSegments($rel->getPath());
$targetQuery = $rel->getQuery();
} else {
$targetAuthority = $base->getAuthority();
if ($rel->getPath() === '') {
$targetPath = $base->getPath();
$targetQuery = $rel->getQuery() != '' ? $rel->getQuery() : $base->getQuery();
} else {
if (!empty($parts['authority']) && empty($parts['path'])) {
$mergedPath = '/';
if ($rel->getPath()[0] === '/') {
$targetPath = $rel->getPath();
} else {
$mergedPath = substr($parts['path'], 0, strrpos($parts['path'], '/') + 1);
if ($targetAuthority != '' && $base->getPath() === '') {
$targetPath = '/' . $rel->getPath();
} else {
$lastSlashPos = strrpos($base->getPath(), '/');
if ($lastSlashPos === false) {
$targetPath = $rel->getPath();
} else {
$targetPath = substr($base->getPath(), 0, $lastSlashPos + 1) . $rel->getPath();
}
}
}
$parts['path'] = self::removeDotSegments($mergedPath . $relParts['path']);
$parts['query'] = $relParts['query'];
$parts['fragment'] = $relParts['fragment'];
$targetPath = self::removeDotSegments($targetPath);
$targetQuery = $rel->getQuery();
}
} elseif (!empty($relParts['query'])) {
$parts['query'] = $relParts['query'];
} elseif ($relParts['fragment'] != null) {
$parts['fragment'] = $relParts['fragment'];
}
return new self(self::createUriString(
$parts['scheme'],
$parts['authority'],
$parts['path'],
$parts['query'],
$parts['fragment']
$base->getScheme(),
$targetAuthority,
$targetPath,
$targetQuery,
$rel->getFragment()
));
}
@@ -189,26 +178,22 @@ class Uri implements UriInterface
* Any existing query string values that exactly match the provided key are
* removed.
*
* Note: this function will convert "=" to "%3D" and "&" to "%26".
*
* @param UriInterface $uri URI to use as a base.
* @param string $key Query string key value pair to remove.
* @param string $key Query string key to remove.
*
* @return UriInterface
*/
public static function withoutQueryValue(UriInterface $uri, $key)
{
$current = $uri->getQuery();
if (!$current) {
if ($current == '') {
return $uri;
}
$result = [];
foreach (explode('&', $current) as $part) {
if (explode('=', $part)[0] !== $key) {
$result[] = $part;
};
}
$decodedKey = rawurldecode($key);
$result = array_filter(explode('&', $current), function ($part) use ($decodedKey) {
return rawurldecode(explode('=', $part)[0]) !== $decodedKey;
});
return $uri->withQuery(implode('&', $result));
}
@@ -219,30 +204,33 @@ class Uri implements UriInterface
* Any existing query string values that exactly match the provided key are
* removed and replaced with the given key value pair.
*
* Note: this function will convert "=" to "%3D" and "&" to "%26".
* A value of null will set the query string key without a value, e.g. "key"
* instead of "key=value".
*
* @param UriInterface $uri URI to use as a base.
* @param string $key Key to set.
* @param string $value Value to set.
* @param UriInterface $uri URI to use as a base.
* @param string $key Key to set.
* @param string|null $value Value to set
*
* @return UriInterface
*/
public static function withQueryValue(UriInterface $uri, $key, $value)
{
$current = $uri->getQuery();
$key = strtr($key, self::$replaceQuery);
if (!$current) {
if ($current == '') {
$result = [];
} else {
$result = [];
foreach (explode('&', $current) as $part) {
if (explode('=', $part)[0] !== $key) {
$result[] = $part;
};
}
$decodedKey = rawurldecode($key);
$result = array_filter(explode('&', $current), function ($part) use ($decodedKey) {
return rawurldecode(explode('=', $part)[0]) !== $decodedKey;
});
}
// Query string separators ("=", "&") within the key or value need to be encoded
// (while preventing double-encoding) before setting the query string. All other
// chars that need percent-encoding will be encoded by withQuery().
$key = strtr($key, self::$replaceQuery);
if ($value !== null) {
$result[] = $key . '=' . strtr($value, self::$replaceQuery);
} else {
@@ -273,16 +261,16 @@ class Uri implements UriInterface
public function getAuthority()
{
if (empty($this->host)) {
if ($this->host == '') {
return '';
}
$authority = $this->host;
if (!empty($this->userInfo)) {
if ($this->userInfo != '') {
$authority = $this->userInfo . '@' . $authority;
}
if ($this->isNonStandardPort($this->scheme, $this->host, $this->port)) {
if ($this->port !== null) {
$authority .= ':' . $this->port;
}
@@ -306,7 +294,7 @@ class Uri implements UriInterface
public function getPath()
{
return $this->path == null ? '' : $this->path;
return $this->path;
}
public function getQuery()
@@ -329,14 +317,14 @@ class Uri implements UriInterface
$new = clone $this;
$new->scheme = $scheme;
$new->port = $new->filterPort($new->scheme, $new->host, $new->port);
$new->port = $new->filterPort($new->port);
return $new;
}
public function withUserInfo($user, $password = null)
{
$info = $user;
if ($password) {
if ($password != '') {
$info .= ':' . $password;
}
@@ -351,6 +339,8 @@ class Uri implements UriInterface
public function withHost($host)
{
$host = $this->filterHost($host);
if ($this->host === $host) {
return $this;
}
@@ -362,7 +352,7 @@ class Uri implements UriInterface
public function withPort($port)
{
$port = $this->filterPort($this->scheme, $this->host, $port);
$port = $this->filterPort($port);
if ($this->port === $port) {
return $this;
@@ -375,12 +365,6 @@ class Uri implements UriInterface
public function withPath($path)
{
if (!is_string($path)) {
throw new \InvalidArgumentException(
'Invalid path provided; must be a string'
);
}
$path = $this->filterPath($path);
if ($this->path === $path) {
@@ -394,17 +378,6 @@ class Uri implements UriInterface
public function withQuery($query)
{
if (!is_string($query) && !method_exists($query, '__toString')) {
throw new \InvalidArgumentException(
'Query string must be a string'
);
}
$query = (string) $query;
if (substr($query, 0, 1) === '?') {
$query = substr($query, 1);
}
$query = $this->filterQueryAndFragment($query);
if ($this->query === $query) {
@@ -418,10 +391,6 @@ class Uri implements UriInterface
public function withFragment($fragment)
{
if (substr($fragment, 0, 1) === '#') {
$fragment = substr($fragment, 1);
}
$fragment = $this->filterQueryAndFragment($fragment);
if ($this->fragment === $fragment) {
@@ -436,7 +405,7 @@ class Uri implements UriInterface
/**
* Apply parse_url parts to a URI.
*
* @param $parts Array of parse_url parts to apply.
* @param array $parts Array of parse_url parts to apply.
*/
private function applyParts(array $parts)
{
@@ -444,9 +413,11 @@ class Uri implements UriInterface
? $this->filterScheme($parts['scheme'])
: '';
$this->userInfo = isset($parts['user']) ? $parts['user'] : '';
$this->host = isset($parts['host']) ? $parts['host'] : '';
$this->port = !empty($parts['port'])
? $this->filterPort($this->scheme, $this->host, $parts['port'])
$this->host = isset($parts['host'])
? $this->filterHost($parts['host'])
: '';
$this->port = isset($parts['port'])
? $this->filterPort($parts['port'])
: null;
$this->path = isset($parts['path'])
? $this->filterPath($parts['path'])
@@ -476,34 +447,36 @@ class Uri implements UriInterface
{
$uri = '';
if (!empty($scheme)) {
if ($scheme != '') {
$uri .= $scheme . ':';
}
$hierPart = '';
if (!empty($authority)) {
if (!empty($scheme)) {
$hierPart .= '//';
}
$hierPart .= $authority;
if ($authority != '') {
$uri .= '//' . $authority;
}
if ($path != null) {
// Add a leading slash if necessary.
if ($hierPart && substr($path, 0, 1) !== '/') {
$hierPart .= '/';
if ($path != '') {
if ($path[0] !== '/') {
if ($authority != '') {
// If the path is rootless and an authority is present, the path MUST be prefixed by "/"
$path = '/' . $path;
}
} elseif (isset($path[1]) && $path[1] === '/') {
if ($authority == '') {
// If the path is starting with more than one "/" and no authority is present, the
// starting slashes MUST be reduced to one.
$path = '/' . ltrim($path, '/');
}
}
$hierPart .= $path;
$uri .= $path;
}
$uri .= $hierPart;
if ($query != null) {
if ($query != '') {
$uri .= '?' . $query;
}
if ($fragment != null) {
if ($fragment != '') {
$uri .= '#' . $fragment;
}
@@ -514,20 +487,12 @@ class Uri implements UriInterface
* Is a given port non-standard for the current scheme?
*
* @param string $scheme
* @param string $host
* @param int $port
* @param int $port
*
* @return bool
*/
private static function isNonStandardPort($scheme, $host, $port)
private static function isNonStandardPort($scheme, $port)
{
if (!$scheme && $port) {
return true;
}
if (!$host || !$port) {
return false;
}
return !isset(self::$schemes[$scheme]) || $port !== self::$schemes[$scheme];
}
@@ -535,49 +500,74 @@ class Uri implements UriInterface
* @param string $scheme
*
* @return string
*
* @throws \InvalidArgumentException If the scheme is invalid.
*/
private function filterScheme($scheme)
{
$scheme = strtolower($scheme);
$scheme = rtrim($scheme, ':/');
if (!is_string($scheme)) {
throw new \InvalidArgumentException('Scheme must be a string');
}
return $scheme;
return strtolower($scheme);
}
/**
* @param string $scheme
* @param string $host
* @param int $port
*
* @return string
*
* @throws \InvalidArgumentException If the host is invalid.
*/
private function filterHost($host)
{
if (!is_string($host)) {
throw new \InvalidArgumentException('Host must be a string');
}
return strtolower($host);
}
/**
* @param int|null $port
*
* @return int|null
*
* @throws \InvalidArgumentException If the port is invalid.
*/
private function filterPort($scheme, $host, $port)
private function filterPort($port)
{
if (null !== $port) {
$port = (int) $port;
if (1 > $port || 0xffff < $port) {
throw new \InvalidArgumentException(
sprintf('Invalid port: %d. Must be between 1 and 65535', $port)
);
}
if ($port === null) {
return null;
}
return $this->isNonStandardPort($scheme, $host, $port) ? $port : null;
$port = (int) $port;
if (1 > $port || 0xffff < $port) {
throw new \InvalidArgumentException(
sprintf('Invalid port: %d. Must be between 1 and 65535', $port)
);
}
return self::isNonStandardPort($this->scheme, $port) ? $port : null;
}
/**
* Filters the path of a URI
*
* @param $path
* @param string $path
*
* @return string
*
* @throws \InvalidArgumentException If the path is invalid.
*/
private function filterPath($path)
{
if (!is_string($path)) {
throw new \InvalidArgumentException('Path must be a string');
}
return preg_replace_callback(
'/(?:[^' . self::$charUnreserved . self::$charSubDelims . ':@\/%]+|%(?![A-Fa-f0-9]{2}))/',
'/(?:[^' . self::$charUnreserved . self::$charSubDelims . '%:@\/]++|%(?![A-Fa-f0-9]{2}))/',
[$this, 'rawurlencodeMatchZero'],
$path
);
@@ -586,14 +576,20 @@ class Uri implements UriInterface
/**
* Filters the query string or fragment of a URI.
*
* @param $str
* @param string $str
*
* @return string
*
* @throws \InvalidArgumentException If the query or fragment is invalid.
*/
private function filterQueryAndFragment($str)
{
if (!is_string($str)) {
throw new \InvalidArgumentException('Query and fragment must be a string');
}
return preg_replace_callback(
'/(?:[^' . self::$charUnreserved . self::$charSubDelims . '%:@\/\?]+|%(?![A-Fa-f0-9]{2}))/',
'/(?:[^' . self::$charUnreserved . self::$charSubDelims . '%:@\/\?]++|%(?![A-Fa-f0-9]{2}))/',
[$this, 'rawurlencodeMatchZero'],
$str
);

View File

@@ -4,6 +4,7 @@ namespace GuzzleHttp\Psr7;
use Psr\Http\Message\MessageInterface;
use Psr\Http\Message\RequestInterface;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Message\StreamInterface;
use Psr\Http\Message\UriInterface;
@@ -68,8 +69,8 @@ function uri_for($uri)
* - metadata: Array of custom metadata.
* - size: Size of the stream.
*
* @param resource|int|string|float|bool|StreamInterface $resource Entity body data
* @param array $options Additional options
* @param resource|string|null|int|float|bool|StreamInterface|callable $resource Entity body data
* @param array $options Additional options
*
* @return Stream
* @throws \InvalidArgumentException if the $resource arg is not valid.
@@ -236,6 +237,19 @@ function modify_request(RequestInterface $request, array $changes)
$uri = $uri->withQuery($changes['query']);
}
if ($request instanceof ServerRequestInterface) {
return new ServerRequest(
isset($changes['method']) ? $changes['method'] : $request->getMethod(),
$uri,
$headers,
isset($changes['body']) ? $changes['body'] : $request->getBody(),
isset($changes['version'])
? $changes['version']
: $request->getProtocolVersion(),
$request->getServerParams()
);
}
return new Request(
isset($changes['method']) ? $changes['method'] : $request->getMethod(),
$uri,
@@ -432,7 +446,7 @@ function readline(StreamInterface $stream, $maxLength = null)
}
$buffer .= $byte;
// Break when a new line is found or the max length - 1 is reached
if ($byte == PHP_EOL || ++$size == $maxLength - 1) {
if ($byte === "\n" || ++$size === $maxLength - 1) {
break;
}
}
@@ -545,7 +559,7 @@ function parse_query($str, $urlEncoding = true)
/**
* Build a query string from an array of key value pairs.
*
* This function can use the return value of parseQuery() to build a query
* This function can use the return value of parse_query() to build a query
* string. This function does not modify the provided keys when an array is
* encountered (like http_build_query would).
*
@@ -563,9 +577,9 @@ function build_query(array $params, $encoding = PHP_QUERY_RFC3986)
if ($encoding === false) {
$encoder = function ($str) { return $str; };
} elseif ($encoding == PHP_QUERY_RFC3986) {
} elseif ($encoding === PHP_QUERY_RFC3986) {
$encoder = 'rawurlencode';
} elseif ($encoding == PHP_QUERY_RFC1738) {
} elseif ($encoding === PHP_QUERY_RFC1738) {
$encoder = 'urlencode';
} else {
throw new \InvalidArgumentException('Invalid type');

View File

@@ -439,7 +439,7 @@ class FunctionsTest extends \PHPUnit_Framework_TestCase
{
$request = new Psr7\Request('PUT', 'http://foo.com/hi?123', [
'Baz' => 'bar',
'Qux' => ' ipsum'
'Qux' => 'ipsum'
], 'hello', '1.0');
$this->assertEquals(
"PUT /hi?123 HTTP/1.0\r\nHost: foo.com\r\nBaz: bar\r\nQux: ipsum\r\n\r\nhello",
@@ -451,7 +451,7 @@ class FunctionsTest extends \PHPUnit_Framework_TestCase
{
$response = new Psr7\Response(200, [
'Baz' => 'bar',
'Qux' => ' ipsum'
'Qux' => 'ipsum'
], 'hello', '1.0', 'FOO');
$this->assertEquals(
"HTTP/1.0 200 FOO\r\nBaz: bar\r\nQux: ipsum\r\n\r\nhello",
@@ -573,8 +573,13 @@ class FunctionsTest extends \PHPUnit_Framework_TestCase
public function testReturnsAsIsWhenNoChanges()
{
$request = new Psr7\Request('GET', 'http://foo.com');
$this->assertSame($request, Psr7\modify_request($request, []));
$r1 = new Psr7\Request('GET', 'http://foo.com');
$r2 = Psr7\modify_request($r1, []);
$this->assertTrue($r2 instanceof Psr7\Request);
$r1 = new Psr7\ServerRequest('GET', 'http://foo.com');
$r2 = Psr7\modify_request($r1, []);
$this->assertTrue($r2 instanceof \Psr\Http\Message\ServerRequestInterface);
}
public function testReturnsUriAsIsWhenNoChanges()
@@ -600,4 +605,15 @@ class FunctionsTest extends \PHPUnit_Framework_TestCase
$this->assertNotSame($r1, $r2);
$this->assertEquals('foo=bar', $r2->getUri()->getQuery());
}
public function testModifyRequestKeepInstanceOfRequest()
{
$r1 = new Psr7\Request('GET', 'http://foo.com');
$r2 = Psr7\modify_request($r1, ['remove_headers' => ['non-existent']]);
$this->assertTrue($r2 instanceof Psr7\Request);
$r1 = new Psr7\ServerRequest('GET', 'http://foo.com');
$r2 = Psr7\modify_request($r1, ['remove_headers' => ['non-existent']]);
$this->assertTrue($r2 instanceof \Psr\Http\Message\ServerRequestInterface);
}
}

View File

@@ -1,6 +1,7 @@
<?php
namespace GuzzleHttp\Tests\Psr7;
use GuzzleHttp\Psr7;
use GuzzleHttp\Psr7\Request;
use GuzzleHttp\Psr7\Uri;
@@ -27,15 +28,45 @@ class RequestTest extends \PHPUnit_Framework_TestCase
*/
public function testValidateRequestUri()
{
new Request('GET', true);
new Request('GET', '///');
}
public function testCanConstructWithBody()
{
$r = new Request('GET', '/', [], 'baz');
$this->assertInstanceOf('Psr\Http\Message\StreamInterface', $r->getBody());
$this->assertEquals('baz', (string) $r->getBody());
}
public function testNullBody()
{
$r = new Request('GET', '/', [], null);
$this->assertInstanceOf('Psr\Http\Message\StreamInterface', $r->getBody());
$this->assertSame('', (string) $r->getBody());
}
public function testFalseyBody()
{
$r = new Request('GET', '/', [], '0');
$this->assertInstanceOf('Psr\Http\Message\StreamInterface', $r->getBody());
$this->assertSame('0', (string) $r->getBody());
}
public function testConstructorDoesNotReadStreamBody()
{
$streamIsRead = false;
$body = Psr7\FnStream::decorate(Psr7\stream_for(''), [
'__toString' => function () use (&$streamIsRead) {
$streamIsRead = true;
return '';
}
]);
$r = new Request('GET', '/', [], $body);
$this->assertFalse($streamIsRead);
$this->assertSame($body, $r->getBody());
}
public function testCapitalizesMethod()
{
$r = new Request('get', '/');
@@ -99,6 +130,12 @@ class RequestTest extends \PHPUnit_Framework_TestCase
$this->assertEquals('/baz?bar=bam', $r1->getRequestTarget());
}
public function testBuildsRequestTargetWithFalseyQuery()
{
$r1 = new Request('GET', 'http://foo.com/baz?0');
$this->assertEquals('/baz?0', $r1->getRequestTarget());
}
public function testHostIsAddedFirst()
{
$r = new Request('GET', 'http://foo.com/baz?bar=bam', ['Foo' => 'Bar']);
@@ -135,10 +172,11 @@ class RequestTest extends \PHPUnit_Framework_TestCase
public function testAggregatesHeaders()
{
$r = new Request('GET', 'http://foo.com', [
$r = new Request('GET', '', [
'ZOO' => 'zoobar',
'zoo' => ['foobar', 'zoobar']
]);
$this->assertEquals(['ZOO' => ['zoobar', 'foobar', 'zoobar']], $r->getHeaders());
$this->assertEquals('zoobar, foobar, zoobar', $r->getHeaderLine('zoo'));
}

View File

@@ -1,8 +1,8 @@
<?php
namespace GuzzleHttp\Tests\Psr7;
use GuzzleHttp\Psr7\Response;
use GuzzleHttp\Psr7;
use GuzzleHttp\Psr7\Response;
/**
* @covers GuzzleHttp\Psr7\MessageTrait
@@ -10,137 +10,243 @@ use GuzzleHttp\Psr7;
*/
class ResponseTest extends \PHPUnit_Framework_TestCase
{
public function testAddsDefaultReason()
{
$r = new Response('200');
$this->assertSame(200, $r->getStatusCode());
$this->assertEquals('OK', $r->getReasonPhrase());
}
public function testCanGiveCustomReason()
{
$r = new Response(200, [], null, '1.1', 'bar');
$this->assertEquals('bar', $r->getReasonPhrase());
}
public function testCanGiveCustomProtocolVersion()
{
$r = new Response(200, [], null, '1000');
$this->assertEquals('1000', $r->getProtocolVersion());
}
public function testCanCreateNewResponseWithStatusAndNoReason()
{
$r = new Response(200);
$r2 = $r->withStatus(201);
$this->assertEquals(200, $r->getStatusCode());
$this->assertEquals('OK', $r->getReasonPhrase());
$this->assertEquals(201, $r2->getStatusCode());
$this->assertEquals('Created', $r2->getReasonPhrase());
}
public function testCanCreateNewResponseWithStatusAndReason()
{
$r = new Response(200);
$r2 = $r->withStatus(201, 'Foo');
$this->assertEquals(200, $r->getStatusCode());
$this->assertEquals('OK', $r->getReasonPhrase());
$this->assertEquals(201, $r2->getStatusCode());
$this->assertEquals('Foo', $r2->getReasonPhrase());
}
public function testCreatesResponseWithAddedHeaderArray()
public function testDefaultConstructor()
{
$r = new Response();
$r2 = $r->withAddedHeader('foo', ['baz', 'bar']);
$this->assertFalse($r->hasHeader('foo'));
$this->assertEquals('baz, bar', $r2->getHeaderLine('foo'));
$this->assertSame(200, $r->getStatusCode());
$this->assertSame('1.1', $r->getProtocolVersion());
$this->assertSame('OK', $r->getReasonPhrase());
$this->assertSame([], $r->getHeaders());
$this->assertInstanceOf('Psr\Http\Message\StreamInterface', $r->getBody());
$this->assertSame('', (string) $r->getBody());
}
public function testReturnsIdentityWhenRemovingMissingHeader()
public function testCanConstructWithStatusCode()
{
$r = new Response(404);
$this->assertSame(404, $r->getStatusCode());
$this->assertSame('Not Found', $r->getReasonPhrase());
}
public function testConstructorDoesNotReadStreamBody()
{
$streamIsRead = false;
$body = Psr7\FnStream::decorate(Psr7\stream_for(''), [
'__toString' => function () use (&$streamIsRead) {
$streamIsRead = true;
return '';
}
]);
$r = new Response(200, [], $body);
$this->assertFalse($streamIsRead);
$this->assertSame($body, $r->getBody());
}
public function testStatusCanBeNumericString()
{
$r = new Response('404');
$r2 = $r->withStatus('201');
$this->assertSame(404, $r->getStatusCode());
$this->assertSame('Not Found', $r->getReasonPhrase());
$this->assertSame(201, $r2->getStatusCode());
$this->assertSame('Created', $r2->getReasonPhrase());
}
public function testCanConstructWithHeaders()
{
$r = new Response(200, ['Foo' => 'Bar']);
$this->assertSame(['Foo' => ['Bar']], $r->getHeaders());
$this->assertSame('Bar', $r->getHeaderLine('Foo'));
$this->assertSame(['Bar'], $r->getHeader('Foo'));
}
public function testCanConstructWithHeadersAsArray()
{
$r = new Response(200, [
'Foo' => ['baz', 'bar']
]);
$this->assertSame(['Foo' => ['baz', 'bar']], $r->getHeaders());
$this->assertSame('baz, bar', $r->getHeaderLine('Foo'));
$this->assertSame(['baz', 'bar'], $r->getHeader('Foo'));
}
public function testCanConstructWithBody()
{
$r = new Response(200, [], 'baz');
$this->assertInstanceOf('Psr\Http\Message\StreamInterface', $r->getBody());
$this->assertSame('baz', (string) $r->getBody());
}
public function testNullBody()
{
$r = new Response(200, [], null);
$this->assertInstanceOf('Psr\Http\Message\StreamInterface', $r->getBody());
$this->assertSame('', (string) $r->getBody());
}
public function testFalseyBody()
{
$r = new Response(200, [], '0');
$this->assertInstanceOf('Psr\Http\Message\StreamInterface', $r->getBody());
$this->assertSame('0', (string) $r->getBody());
}
public function testCanConstructWithReason()
{
$r = new Response(200, [], null, '1.1', 'bar');
$this->assertSame('bar', $r->getReasonPhrase());
$r = new Response(200, [], null, '1.1', '0');
$this->assertSame('0', $r->getReasonPhrase(), 'Falsey reason works');
}
public function testCanConstructWithProtocolVersion()
{
$r = new Response(200, [], null, '1000');
$this->assertSame('1000', $r->getProtocolVersion());
}
public function testWithStatusCodeAndNoReason()
{
$r = (new Response())->withStatus(201);
$this->assertSame(201, $r->getStatusCode());
$this->assertSame('Created', $r->getReasonPhrase());
}
public function testWithStatusCodeAndReason()
{
$r = (new Response())->withStatus(201, 'Foo');
$this->assertSame(201, $r->getStatusCode());
$this->assertSame('Foo', $r->getReasonPhrase());
$r = (new Response())->withStatus(201, '0');
$this->assertSame(201, $r->getStatusCode());
$this->assertSame('0', $r->getReasonPhrase(), 'Falsey reason works');
}
public function testWithProtocolVersion()
{
$r = (new Response())->withProtocolVersion('1000');
$this->assertSame('1000', $r->getProtocolVersion());
}
public function testSameInstanceWhenSameProtocol()
{
$r = new Response();
$this->assertSame($r, $r->withProtocolVersion('1.1'));
}
public function testWithBody()
{
$b = Psr7\stream_for('0');
$r = (new Response())->withBody($b);
$this->assertInstanceOf('Psr\Http\Message\StreamInterface', $r->getBody());
$this->assertSame('0', (string) $r->getBody());
}
public function testSameInstanceWhenSameBody()
{
$r = new Response();
$b = $r->getBody();
$this->assertSame($r, $r->withBody($b));
}
public function testWithHeader()
{
$r = new Response(200, ['Foo' => 'Bar']);
$r2 = $r->withHeader('baZ', 'Bam');
$this->assertSame(['Foo' => ['Bar']], $r->getHeaders());
$this->assertSame(['Foo' => ['Bar'], 'baZ' => ['Bam']], $r2->getHeaders());
$this->assertSame('Bam', $r2->getHeaderLine('baz'));
$this->assertSame(['Bam'], $r2->getHeader('baz'));
}
public function testWithHeaderAsArray()
{
$r = new Response(200, ['Foo' => 'Bar']);
$r2 = $r->withHeader('baZ', ['Bam', 'Bar']);
$this->assertSame(['Foo' => ['Bar']], $r->getHeaders());
$this->assertSame(['Foo' => ['Bar'], 'baZ' => ['Bam', 'Bar']], $r2->getHeaders());
$this->assertSame('Bam, Bar', $r2->getHeaderLine('baz'));
$this->assertSame(['Bam', 'Bar'], $r2->getHeader('baz'));
}
public function testWithHeaderReplacesDifferentCase()
{
$r = new Response(200, ['Foo' => 'Bar']);
$r2 = $r->withHeader('foO', 'Bam');
$this->assertSame(['Foo' => ['Bar']], $r->getHeaders());
$this->assertSame(['foO' => ['Bam']], $r2->getHeaders());
$this->assertSame('Bam', $r2->getHeaderLine('foo'));
$this->assertSame(['Bam'], $r2->getHeader('foo'));
}
public function testWithAddedHeader()
{
$r = new Response(200, ['Foo' => 'Bar']);
$r2 = $r->withAddedHeader('foO', 'Baz');
$this->assertSame(['Foo' => ['Bar']], $r->getHeaders());
$this->assertSame(['Foo' => ['Bar', 'Baz']], $r2->getHeaders());
$this->assertSame('Bar, Baz', $r2->getHeaderLine('foo'));
$this->assertSame(['Bar', 'Baz'], $r2->getHeader('foo'));
}
public function testWithAddedHeaderAsArray()
{
$r = new Response(200, ['Foo' => 'Bar']);
$r2 = $r->withAddedHeader('foO', ['Baz', 'Bam']);
$this->assertSame(['Foo' => ['Bar']], $r->getHeaders());
$this->assertSame(['Foo' => ['Bar', 'Baz', 'Bam']], $r2->getHeaders());
$this->assertSame('Bar, Baz, Bam', $r2->getHeaderLine('foo'));
$this->assertSame(['Bar', 'Baz', 'Bam'], $r2->getHeader('foo'));
}
public function testWithAddedHeaderThatDoesNotExist()
{
$r = new Response(200, ['Foo' => 'Bar']);
$r2 = $r->withAddedHeader('nEw', 'Baz');
$this->assertSame(['Foo' => ['Bar']], $r->getHeaders());
$this->assertSame(['Foo' => ['Bar'], 'nEw' => ['Baz']], $r2->getHeaders());
$this->assertSame('Baz', $r2->getHeaderLine('new'));
$this->assertSame(['Baz'], $r2->getHeader('new'));
}
public function testWithoutHeaderThatExists()
{
$r = new Response(200, ['Foo' => 'Bar', 'Baz' => 'Bam']);
$r2 = $r->withoutHeader('foO');
$this->assertTrue($r->hasHeader('foo'));
$this->assertSame(['Foo' => ['Bar'], 'Baz' => ['Bam']], $r->getHeaders());
$this->assertFalse($r2->hasHeader('foo'));
$this->assertSame(['Baz' => ['Bam']], $r2->getHeaders());
}
public function testWithoutHeaderThatDoesNotExist()
{
$r = new Response(200, ['Baz' => 'Bam']);
$r2 = $r->withoutHeader('foO');
$this->assertSame($r, $r2);
$this->assertFalse($r2->hasHeader('foo'));
$this->assertSame(['Baz' => ['Bam']], $r2->getHeaders());
}
public function testSameInstanceWhenRemovingMissingHeader()
{
$r = new Response();
$this->assertSame($r, $r->withoutHeader('foo'));
}
public function testAlwaysReturnsBody()
public function testHeaderValuesAreTrimmed()
{
$r = new Response();
$this->assertInstanceOf('Psr\Http\Message\StreamInterface', $r->getBody());
}
$r1 = new Response(200, ['OWS' => " \t \tFoo\t \t "]);
$r2 = (new Response())->withHeader('OWS', " \t \tFoo\t \t ");
$r3 = (new Response())->withAddedHeader('OWS', " \t \tFoo\t \t ");;
public function testCanSetHeaderAsArray()
{
$r = new Response(200, [
'foo' => ['baz ', ' bar ']
]);
$this->assertEquals('baz, bar', $r->getHeaderLine('foo'));
$this->assertEquals(['baz', 'bar'], $r->getHeader('foo'));
foreach ([$r1, $r2, $r3] as $r) {
$this->assertSame(['OWS' => ['Foo']], $r->getHeaders());
$this->assertSame('Foo', $r->getHeaderLine('OWS'));
$this->assertSame(['Foo'], $r->getHeader('OWS'));
}
}
public function testSameInstanceWhenSameBody()
{
$r = new Response(200, [], 'foo');
$b = $r->getBody();
$this->assertSame($r, $r->withBody($b));
}
public function testNewInstanceWhenNewBody()
{
$r = new Response(200, [], 'foo');
$b2 = Psr7\stream_for('abc');
$this->assertNotSame($r, $r->withBody($b2));
}
public function testSameInstanceWhenSameProtocol()
{
$r = new Response(200);
$this->assertSame($r, $r->withProtocolVersion('1.1'));
}
public function testNewInstanceWhenNewProtocol()
{
$r = new Response(200);
$this->assertNotSame($r, $r->withProtocolVersion('1.0'));
}
public function testNewInstanceWhenRemovingHeader()
{
$r = new Response(200, ['Foo' => 'Bar']);
$r2 = $r->withoutHeader('Foo');
$this->assertNotSame($r, $r2);
$this->assertFalse($r2->hasHeader('foo'));
}
public function testNewInstanceWhenAddingHeader()
{
$r = new Response(200, ['Foo' => 'Bar']);
$r2 = $r->withAddedHeader('Foo', 'Baz');
$this->assertNotSame($r, $r2);
$this->assertEquals('Bar, Baz', $r2->getHeaderLine('foo'));
}
public function testNewInstanceWhenAddingHeaderThatWasNotThereBefore()
{
$r = new Response(200, ['Foo' => 'Bar']);
$r2 = $r->withAddedHeader('Baz', 'Bam');
$this->assertNotSame($r, $r2);
$this->assertEquals('Bam', $r2->getHeaderLine('Baz'));
$this->assertEquals('Bar', $r2->getHeaderLine('Foo'));
}
public function testRemovesPreviouslyAddedHeaderOfDifferentCase()
{
$r = new Response(200, ['Foo' => 'Bar']);
$r2 = $r->withHeader('foo', 'Bam');
$this->assertNotSame($r, $r2);
$this->assertEquals('Bam', $r2->getHeaderLine('Foo'));
}
public function testBodyConsistent()
{
$r = new Response(200, [], '0');
$this->assertEquals('0', (string)$r->getBody());
}
}

View File

@@ -516,4 +516,17 @@ class ServerRequestTest extends \PHPUnit_Framework_TestCase
$this->assertEquals(['name' => 'value', 'other' => 'otherValue'], $request3->getAttributes());
$this->assertEquals(['name' => 'value'], $request4->getAttributes());
}
public function testNullAttribute()
{
$request = (new ServerRequest('GET', '/'))->withAttribute('name', null);
$this->assertSame(['name' => null], $request->getAttributes());
$this->assertNull($request->getAttribute('name', 'different-default'));
$requestWithoutAttribute = $request->withoutAttribute('name');
$this->assertSame([], $requestWithoutAttribute->getAttributes());
$this->assertSame('different-default', $requestWithoutAttribute->getAttribute('name', 'different-default'));
}
}

View File

@@ -21,6 +21,8 @@ class StreamWrapperTest extends \PHPUnit_Framework_TestCase
$this->assertSame('', fread($handle, 1));
$this->assertTrue(feof($handle));
$stBlksize = defined('PHP_WINDOWS_VERSION_BUILD') ? -1 : 0;
// This fails on HHVM for some reason
if (!defined('HHVM_VERSION')) {
$this->assertEquals([
@@ -35,8 +37,8 @@ class StreamWrapperTest extends \PHPUnit_Framework_TestCase
'atime' => 0,
'mtime' => 0,
'ctime' => 0,
'blksize' => 0,
'blocks' => 0,
'blksize' => $stBlksize,
'blocks' => $stBlksize,
0 => 0,
1 => 0,
2 => 33206,
@@ -48,8 +50,8 @@ class StreamWrapperTest extends \PHPUnit_Framework_TestCase
8 => 0,
9 => 0,
10 => 0,
11 => 0,
12 => 0,
11 => $stBlksize,
12 => $stBlksize,
], fstat($handle));
}

View File

@@ -8,97 +8,214 @@ use GuzzleHttp\Psr7\Uri;
*/
class UriTest extends \PHPUnit_Framework_TestCase
{
const RFC3986_BASE = "http://a/b/c/d;p?q";
const RFC3986_BASE = 'http://a/b/c/d;p?q';
public function testParsesProvidedUrl()
public function testParsesProvidedUri()
{
$uri = new Uri('https://michael:test@test.com:443/path/123?q=abc#test');
$uri = new Uri('https://user:pass@example.com:8080/path/123?q=abc#test');
// Standard port 443 for https gets ignored.
$this->assertEquals(
'https://michael:test@test.com/path/123?q=abc#test',
(string) $uri
);
$this->assertSame('https', $uri->getScheme());
$this->assertSame('user:pass@example.com:8080', $uri->getAuthority());
$this->assertSame('user:pass', $uri->getUserInfo());
$this->assertSame('example.com', $uri->getHost());
$this->assertSame(8080, $uri->getPort());
$this->assertSame('/path/123', $uri->getPath());
$this->assertSame('q=abc', $uri->getQuery());
$this->assertSame('test', $uri->getFragment());
$this->assertSame('https://user:pass@example.com:8080/path/123?q=abc#test', (string) $uri);
}
$this->assertEquals('test', $uri->getFragment());
$this->assertEquals('test.com', $uri->getHost());
$this->assertEquals('/path/123', $uri->getPath());
$this->assertEquals(null, $uri->getPort());
$this->assertEquals('q=abc', $uri->getQuery());
$this->assertEquals('https', $uri->getScheme());
$this->assertEquals('michael:test', $uri->getUserInfo());
public function testCanTransformAndRetrievePartsIndividually()
{
$uri = (new Uri())
->withScheme('https')
->withUserInfo('user', 'pass')
->withHost('example.com')
->withPort(8080)
->withPath('/path/123')
->withQuery('q=abc')
->withFragment('test');
$this->assertSame('https', $uri->getScheme());
$this->assertSame('user:pass@example.com:8080', $uri->getAuthority());
$this->assertSame('user:pass', $uri->getUserInfo());
$this->assertSame('example.com', $uri->getHost());
$this->assertSame(8080, $uri->getPort());
$this->assertSame('/path/123', $uri->getPath());
$this->assertSame('q=abc', $uri->getQuery());
$this->assertSame('test', $uri->getFragment());
$this->assertSame('https://user:pass@example.com:8080/path/123?q=abc#test', (string) $uri);
}
/**
* @dataProvider getValidUris
*/
public function testValidUrisStayValid($input)
{
$uri = new Uri($input);
$this->assertSame($input, (string) $uri);
}
/**
* @dataProvider getValidUris
*/
public function testFromParts($input)
{
$uri = Uri::fromParts(parse_url($input));
$this->assertSame($input, (string) $uri);
}
public function getValidUris()
{
return [
['urn:path-rootless'],
['urn:path:with:colon'],
['urn:/path-absolute'],
['urn:/'],
// only scheme with empty path
['urn:'],
// only path
['/'],
['relative/'],
['0'],
// same document reference
[''],
// network path without scheme
['//example.org'],
['//example.org/'],
['//example.org?q#h'],
// only query
['?q'],
['?q=abc&foo=bar'],
// only fragment
['#fragment'],
// dot segments are not removed automatically
['./foo/../bar'],
];
}
/**
* @expectedException \InvalidArgumentException
* @expectedExceptionMessage Unable to parse URI
* @dataProvider getInvalidUris
*/
public function testInvalidUrisThrowException($invalidUri)
{
new Uri($invalidUri);
}
public function getInvalidUris()
{
return [
// parse_url() requires the host component which makes sense for http(s)
// but not when the scheme is not known or different. So '//' or '///' is
// currently invalid as well but should not according to RFC 3986.
['http://'],
['urn://host:with:colon'], // host cannot contain ":"
];
}
/**
* @expectedException \InvalidArgumentException
* @expectedExceptionMessage Invalid port: 100000. Must be between 1 and 65535
*/
public function testPortMustBeValid()
{
(new Uri())->withPort(100000);
}
/**
* @expectedException \InvalidArgumentException
* @expectedExceptionMessage Invalid port: 0. Must be between 1 and 65535
*/
public function testWithPortCannotBeZero()
{
(new Uri())->withPort(0);
}
/**
* @expectedException \InvalidArgumentException
* @expectedExceptionMessage Unable to parse URI
*/
public function testValidatesUriCanBeParsed()
public function testParseUriPortCannotBeZero()
{
new Uri('///');
}
public function testCanTransformAndRetrievePartsIndividually()
{
$uri = (new Uri(''))
->withFragment('#test')
->withHost('example.com')
->withPath('path/123')
->withPort(8080)
->withQuery('?q=abc')
->withScheme('http')
->withUserInfo('user', 'pass');
// Test getters.
$this->assertEquals('user:pass@example.com:8080', $uri->getAuthority());
$this->assertEquals('test', $uri->getFragment());
$this->assertEquals('example.com', $uri->getHost());
$this->assertEquals('path/123', $uri->getPath());
$this->assertEquals(8080, $uri->getPort());
$this->assertEquals('q=abc', $uri->getQuery());
$this->assertEquals('http', $uri->getScheme());
$this->assertEquals('user:pass', $uri->getUserInfo());
new Uri('//example.com:0');
}
/**
* @expectedException \InvalidArgumentException
*/
public function testPortMustBeValid()
public function testSchemeMustHaveCorrectType()
{
(new Uri(''))->withPort(100000);
(new Uri())->withScheme([]);
}
/**
* @expectedException \InvalidArgumentException
*/
public function testPathMustBeValid()
public function testHostMustHaveCorrectType()
{
(new Uri(''))->withPath([]);
(new Uri())->withHost([]);
}
/**
* @expectedException \InvalidArgumentException
*/
public function testQueryMustBeValid()
public function testPathMustHaveCorrectType()
{
(new Uri(''))->withQuery(new \stdClass);
(new Uri())->withPath([]);
}
public function testAllowsFalseyUrlParts()
/**
* @expectedException \InvalidArgumentException
*/
public function testQueryMustHaveCorrectType()
{
$url = new Uri('http://a:1/0?0#0');
$this->assertSame('a', $url->getHost());
$this->assertEquals(1, $url->getPort());
$this->assertSame('/0', $url->getPath());
$this->assertEquals('0', (string) $url->getQuery());
$this->assertSame('0', $url->getFragment());
$this->assertEquals('http://a:1/0?0#0', (string) $url);
$url = new Uri('');
$this->assertSame('', (string) $url);
$url = new Uri('0');
$this->assertSame('0', (string) $url);
$url = new Uri('/');
$this->assertSame('/', (string) $url);
(new Uri())->withQuery([]);
}
/**
* @expectedException \InvalidArgumentException
*/
public function testFragmentMustHaveCorrectType()
{
(new Uri())->withFragment([]);
}
public function testCanParseFalseyUriParts()
{
$uri = new Uri('0://0:0@0/0?0#0');
$this->assertSame('0', $uri->getScheme());
$this->assertSame('0:0@0', $uri->getAuthority());
$this->assertSame('0:0', $uri->getUserInfo());
$this->assertSame('0', $uri->getHost());
$this->assertSame('/0', $uri->getPath());
$this->assertSame('0', $uri->getQuery());
$this->assertSame('0', $uri->getFragment());
$this->assertSame('0://0:0@0/0?0#0', (string) $uri);
}
public function testCanConstructFalseyUriParts()
{
$uri = (new Uri())
->withScheme('0')
->withUserInfo('0', '0')
->withHost('0')
->withPath('/0')
->withQuery('0')
->withFragment('0');
$this->assertSame('0', $uri->getScheme());
$this->assertSame('0:0@0', $uri->getAuthority());
$this->assertSame('0:0', $uri->getUserInfo());
$this->assertSame('0', $uri->getHost());
$this->assertSame('/0', $uri->getPath());
$this->assertSame('0', $uri->getQuery());
$this->assertSame('0', $uri->getFragment());
$this->assertSame('0://0:0@0/0?0#0', (string) $uri);
}
/**
@@ -108,13 +225,13 @@ class UriTest extends \PHPUnit_Framework_TestCase
{
$uri = new Uri($base);
$actual = Uri::resolve($uri, $rel);
$this->assertEquals($expected, (string) $actual);
$this->assertSame($expected, (string) $actual);
}
public function getResolveTestCases()
{
return [
//[self::RFC3986_BASE, 'g:h', 'g:h'],
[self::RFC3986_BASE, 'g:h', 'g:h'],
[self::RFC3986_BASE, 'g', 'http://a/b/c/g'],
[self::RFC3986_BASE, './g', 'http://a/b/c/g'],
[self::RFC3986_BASE, 'g/', 'http://a/b/c/g/'],
@@ -152,131 +269,291 @@ class UriTest extends \PHPUnit_Framework_TestCase
[self::RFC3986_BASE, 'g/../h', 'http://a/b/c/h'],
[self::RFC3986_BASE, 'g;x=1/./y', 'http://a/b/c/g;x=1/y'],
[self::RFC3986_BASE, 'g;x=1/../y', 'http://a/b/c/y'],
// dot-segments in the query or fragment
[self::RFC3986_BASE, 'g?y/./x', 'http://a/b/c/g?y/./x'],
[self::RFC3986_BASE, 'g?y/../x', 'http://a/b/c/g?y/../x'],
[self::RFC3986_BASE, 'g#s/./x', 'http://a/b/c/g#s/./x'],
[self::RFC3986_BASE, 'g#s/../x', 'http://a/b/c/g#s/../x'],
[self::RFC3986_BASE, 'g#s/../x', 'http://a/b/c/g#s/../x'],
[self::RFC3986_BASE, '?y#s', 'http://a/b/c/d;p?y#s'],
['http://a/b/c/d;p?q#s', '?y', 'http://a/b/c/d;p?y'],
['http://u@a/b/c/d;p?q', '.', 'http://u@a/b/c/'],
['http://u:p@a/b/c/d;p?q', '.', 'http://u:p@a/b/c/'],
['http://a/b/c/d/', 'e', 'http://a/b/c/d/e'],
['urn:no-slash', 'e', 'urn:e'],
// falsey relative parts
[self::RFC3986_BASE, '//0', 'http://0'],
[self::RFC3986_BASE, '0', 'http://a/b/c/0'],
[self::RFC3986_BASE, '?0', 'http://a/b/c/d;p?0'],
[self::RFC3986_BASE, '#0', 'http://a/b/c/d;p?q#0'],
];
}
public function testAddAndRemoveQueryValues()
{
$uri = new Uri('http://foo.com/bar');
$uri = new Uri();
$uri = Uri::withQueryValue($uri, 'a', 'b');
$uri = Uri::withQueryValue($uri, 'c', 'd');
$uri = Uri::withQueryValue($uri, 'e', null);
$this->assertEquals('a=b&c=d&e', $uri->getQuery());
$this->assertSame('a=b&c=d&e', $uri->getQuery());
$uri = Uri::withoutQueryValue($uri, 'c');
$this->assertSame('a=b&e', $uri->getQuery());
$uri = Uri::withoutQueryValue($uri, 'e');
$this->assertEquals('a=b', $uri->getQuery());
$this->assertSame('a=b', $uri->getQuery());
$uri = Uri::withoutQueryValue($uri, 'a');
$uri = Uri::withoutQueryValue($uri, 'a');
$this->assertEquals('', $uri->getQuery());
$this->assertSame('', $uri->getQuery());
}
public function testGetAuthorityReturnsCorrectPort()
public function testWithQueryValueReplacesSameKeys()
{
// HTTPS non-standard port
$uri = new Uri('https://foo.co:99');
$this->assertEquals('foo.co:99', $uri->getAuthority());
// HTTP non-standard port
$uri = new Uri('http://foo.co:99');
$this->assertEquals('foo.co:99', $uri->getAuthority());
// No scheme
$uri = new Uri('foo.co:99');
$this->assertEquals('foo.co:99', $uri->getAuthority());
// No host or port
$uri = new Uri('http:');
$this->assertEquals('', $uri->getAuthority());
// No host or port
$uri = new Uri('http://foo.co');
$this->assertEquals('foo.co', $uri->getAuthority());
$uri = new Uri();
$uri = Uri::withQueryValue($uri, 'a', 'b');
$uri = Uri::withQueryValue($uri, 'c', 'd');
$uri = Uri::withQueryValue($uri, 'a', 'e');
$this->assertSame('c=d&a=e', $uri->getQuery());
}
public function pathTestProvider()
public function testWithoutQueryValueRemovesAllSameKeys()
{
$uri = (new Uri())->withQuery('a=b&c=d&a=e');
$uri = Uri::withoutQueryValue($uri, 'a');
$this->assertSame('c=d', $uri->getQuery());
}
public function testRemoveNonExistingQueryValue()
{
$uri = new Uri();
$uri = Uri::withQueryValue($uri, 'a', 'b');
$uri = Uri::withoutQueryValue($uri, 'c');
$this->assertSame('a=b', $uri->getQuery());
}
public function testWithQueryValueHandlesEncoding()
{
$uri = new Uri();
$uri = Uri::withQueryValue($uri, 'E=mc^2', 'ein&stein');
$this->assertSame('E%3Dmc%5E2=ein%26stein', $uri->getQuery(), 'Decoded key/value get encoded');
$uri = new Uri();
$uri = Uri::withQueryValue($uri, 'E%3Dmc%5e2', 'ein%26stein');
$this->assertSame('E%3Dmc%5e2=ein%26stein', $uri->getQuery(), 'Encoded key/value do not get double-encoded');
}
public function testWithoutQueryValueHandlesEncoding()
{
// It also tests that the case of the percent-encoding does not matter,
// i.e. both lowercase "%3d" and uppercase "%5E" can be removed.
$uri = (new Uri())->withQuery('E%3dmc%5E2=einstein&foo=bar');
$uri = Uri::withoutQueryValue($uri, 'E=mc^2');
$this->assertSame('foo=bar', $uri->getQuery(), 'Handles key in decoded form');
$uri = (new Uri())->withQuery('E%3dmc%5E2=einstein&foo=bar');
$uri = Uri::withoutQueryValue($uri, 'E%3Dmc%5e2');
$this->assertSame('foo=bar', $uri->getQuery(), 'Handles key in encoded form');
}
public function testSchemeIsNormalizedToLowercase()
{
$uri = new Uri('HTTP://example.com');
$this->assertSame('http', $uri->getScheme());
$this->assertSame('http://example.com', (string) $uri);
$uri = (new Uri('//example.com'))->withScheme('HTTP');
$this->assertSame('http', $uri->getScheme());
$this->assertSame('http://example.com', (string) $uri);
}
public function testHostIsNormalizedToLowercase()
{
$uri = new Uri('//eXaMpLe.CoM');
$this->assertSame('example.com', $uri->getHost());
$this->assertSame('//example.com', (string) $uri);
$uri = (new Uri())->withHost('eXaMpLe.CoM');
$this->assertSame('example.com', $uri->getHost());
$this->assertSame('//example.com', (string) $uri);
}
public function testPortIsNullIfStandardPortForScheme()
{
// HTTPS standard port
$uri = new Uri('https://example.com:443');
$this->assertNull($uri->getPort());
$this->assertSame('example.com', $uri->getAuthority());
$uri = (new Uri('https://example.com'))->withPort(443);
$this->assertNull($uri->getPort());
$this->assertSame('example.com', $uri->getAuthority());
// HTTP standard port
$uri = new Uri('http://example.com:80');
$this->assertNull($uri->getPort());
$this->assertSame('example.com', $uri->getAuthority());
$uri = (new Uri('http://example.com'))->withPort(80);
$this->assertNull($uri->getPort());
$this->assertSame('example.com', $uri->getAuthority());
}
public function testPortIsReturnedIfSchemeUnknown()
{
$uri = (new Uri('//example.com'))->withPort(80);
$this->assertSame(80, $uri->getPort());
$this->assertSame('example.com:80', $uri->getAuthority());
}
public function testStandardPortIsNullIfSchemeChanges()
{
$uri = new Uri('http://example.com:443');
$this->assertSame('http', $uri->getScheme());
$this->assertSame(443, $uri->getPort());
$uri = $uri->withScheme('https');
$this->assertNull($uri->getPort());
}
public function testPortPassedAsStringIsCastedToInt()
{
$uri = (new Uri('//example.com'))->withPort('8080');
$this->assertSame(8080, $uri->getPort(), 'Port is returned as integer');
$this->assertSame('example.com:8080', $uri->getAuthority());
}
public function testPortCanBeRemoved()
{
$uri = (new Uri('http://example.com:8080'))->withPort(null);
$this->assertNull($uri->getPort());
$this->assertSame('http://example.com', (string) $uri);
}
public function testAuthorityWithUserInfoButWithoutHost()
{
$uri = (new Uri())->withUserInfo('user', 'pass');
$this->assertSame('user:pass', $uri->getUserInfo());
$this->assertSame('', $uri->getAuthority());
}
public function uriComponentsEncodingProvider()
{
$unreserved = 'a-zA-Z0-9.-_~!$&\'()*+,;=:@';
return [
// Percent encode spaces.
['http://foo.com/baz bar', 'http://foo.com/baz%20bar'],
// Don't encoding something that's already encoded.
['http://foo.com/baz%20bar', 'http://foo.com/baz%20bar'],
// Percent encode spaces
['/pa th?q=va lue#frag ment', '/pa%20th', 'q=va%20lue', 'frag%20ment', '/pa%20th?q=va%20lue#frag%20ment'],
// Percent encode multibyte
['/€?€#€', '/%E2%82%AC', '%E2%82%AC', '%E2%82%AC', '/%E2%82%AC?%E2%82%AC#%E2%82%AC'],
// Don't encode something that's already encoded
['/pa%20th?q=va%20lue#frag%20ment', '/pa%20th', 'q=va%20lue', 'frag%20ment', '/pa%20th?q=va%20lue#frag%20ment'],
// Percent encode invalid percent encodings
['http://foo.com/baz%2-bar', 'http://foo.com/baz%252-bar'],
['/pa%2-th?q=va%2-lue#frag%2-ment', '/pa%252-th', 'q=va%252-lue', 'frag%252-ment', '/pa%252-th?q=va%252-lue#frag%252-ment'],
// Don't encode path segments
['http://foo.com/baz/bar/bam?a', 'http://foo.com/baz/bar/bam?a'],
['http://foo.com/baz+bar', 'http://foo.com/baz+bar'],
['http://foo.com/baz:bar', 'http://foo.com/baz:bar'],
['http://foo.com/baz@bar', 'http://foo.com/baz@bar'],
['http://foo.com/baz(bar);bam/', 'http://foo.com/baz(bar);bam/'],
['http://foo.com/a-zA-Z0-9.-_~!$&\'()*+,;=:@', 'http://foo.com/a-zA-Z0-9.-_~!$&\'()*+,;=:@'],
['/pa/th//two?q=va/lue#frag/ment', '/pa/th//two', 'q=va/lue', 'frag/ment', '/pa/th//two?q=va/lue#frag/ment'],
// Don't encode unreserved chars or sub-delimiters
["/$unreserved?$unreserved#$unreserved", "/$unreserved", $unreserved, $unreserved, "/$unreserved?$unreserved#$unreserved"],
// Encoded unreserved chars are not decoded
['/p%61th?q=v%61lue#fr%61gment', '/p%61th', 'q=v%61lue', 'fr%61gment', '/p%61th?q=v%61lue#fr%61gment'],
];
}
/**
* @dataProvider pathTestProvider
* @dataProvider uriComponentsEncodingProvider
*/
public function testUriEncodesPathProperly($input, $output)
public function testUriComponentsGetEncodedProperly($input, $path, $query, $fragment, $output)
{
$uri = new Uri($input);
$this->assertEquals((string) $uri, $output);
$this->assertSame($path, $uri->getPath());
$this->assertSame($query, $uri->getQuery());
$this->assertSame($fragment, $uri->getFragment());
$this->assertSame($output, (string) $uri);
}
public function testDoesNotAddPortWhenNoPort()
public function testWithPathEncodesProperly()
{
$this->assertEquals('bar', new Uri('//bar'));
$this->assertEquals('bar', (new Uri('//bar'))->getHost());
$uri = (new Uri())->withPath('/baz?#€/b%61r');
// Query and fragment delimiters and multibyte chars are encoded.
$this->assertSame('/baz%3F%23%E2%82%AC/b%61r', $uri->getPath());
$this->assertSame('/baz%3F%23%E2%82%AC/b%61r', (string) $uri);
}
public function testWithQueryEncodesProperly()
{
$uri = (new Uri())->withQuery('?=#&€=/&b%61r');
// A query starting with a "?" is valid and must not be magically removed. Otherwise it would be impossible to
// construct such an URI. Also the "?" and "/" does not need to be encoded in the query.
$this->assertSame('?=%23&%E2%82%AC=/&b%61r', $uri->getQuery());
$this->assertSame('??=%23&%E2%82%AC=/&b%61r', (string) $uri);
}
public function testWithFragmentEncodesProperly()
{
$uri = (new Uri())->withFragment('#€?/b%61r');
// A fragment starting with a "#" is valid and must not be magically removed. Otherwise it would be impossible to
// construct such an URI. Also the "?" and "/" does not need to be encoded in the fragment.
$this->assertSame('%23%E2%82%AC?/b%61r', $uri->getFragment());
$this->assertSame('#%23%E2%82%AC?/b%61r', (string) $uri);
}
public function testAllowsForRelativeUri()
{
$uri = (new Uri)->withPath('foo');
$this->assertEquals('foo', $uri->getPath());
$this->assertEquals('foo', (string) $uri);
$this->assertSame('foo', $uri->getPath());
$this->assertSame('foo', (string) $uri);
}
public function testAddsSlashForRelativeUriStringWithHost()
{
$uri = (new Uri)->withPath('foo')->withHost('bar.com');
$this->assertEquals('foo', $uri->getPath());
$this->assertEquals('bar.com/foo', (string) $uri);
// If the path is rootless and an authority is present, the path MUST
// be prefixed by "/".
$uri = (new Uri)->withPath('foo')->withHost('example.com');
$this->assertSame('foo', $uri->getPath());
// concatenating a relative path with a host doesn't work: "//example.comfoo" would be wrong
$this->assertSame('//example.com/foo', (string) $uri);
}
/**
* @dataProvider pathTestNoAuthority
*/
public function testNoAuthority($input)
public function testRemoveExtraSlashesWihoutHost()
{
$uri = new Uri($input);
$this->assertEquals($input, (string) $uri);
// If the path is starting with more than one "/" and no authority is
// present, the starting slashes MUST be reduced to one.
$uri = (new Uri)->withPath('//foo');
$this->assertSame('//foo', $uri->getPath());
// URI "//foo" would be interpreted as network reference and thus change the original path to the host
$this->assertSame('/foo', (string) $uri);
}
public function pathTestNoAuthority()
public function testDefaultReturnValuesOfGetters()
{
return [
// path-rootless
['urn:example:animal:ferret:nose'],
// path-absolute
['urn:/example:animal:ferret:nose'],
['urn:/'],
// path-empty
['urn:'],
['urn'],
];
$uri = new Uri();
$this->assertSame('', $uri->getScheme());
$this->assertSame('', $uri->getAuthority());
$this->assertSame('', $uri->getUserInfo());
$this->assertSame('', $uri->getHost());
$this->assertNull($uri->getPort());
$this->assertSame('', $uri->getPath());
$this->assertSame('', $uri->getQuery());
$this->assertSame('', $uri->getFragment());
}
/**
* @expectedException \InvalidArgumentException
* @expectedExceptionMessage Unable to parse URI
*/
public function testNoAuthorityWithInvalidPath()
public function testImmutability()
{
$input = 'urn://example:animal:ferret:nose';
$uri = new Uri($input);
$uri = new Uri();
$this->assertNotSame($uri, $uri->withScheme('https'));
$this->assertNotSame($uri, $uri->withUserInfo('user', 'pass'));
$this->assertNotSame($uri, $uri->withHost('example.com'));
$this->assertNotSame($uri, $uri->withPort(8080));
$this->assertNotSame($uri, $uri->withPath('/path/123'));
$this->assertNotSame($uri, $uri->withQuery('q=abc'));
$this->assertNotSame($uri, $uri->withFragment('test'));
}
public function testExtendingClassesInstantiates()