Laravel version update

Laravel version update
This commit is contained in:
Manish Verma
2018-08-06 18:48:58 +05:30
parent d143048413
commit 126fbb0255
13678 changed files with 1031482 additions and 778530 deletions

View File

@@ -1,41 +0,0 @@
language: php
sudo: false
php:
- 5.5
- 5.6
- 7.0
- 7.1
- hhvm
before_script:
- curl --version
- composer install --no-interaction --prefer-source --dev
- ~/.nvm/nvm.sh install v0.6.14
- ~/.nvm/nvm.sh run v0.6.14
- '[ "$TRAVIS_PHP_VERSION" != "7.0" ] || echo "xdebug.overload_var_dump = 1" >> ~/.phpenv/versions/$(phpenv version-name)/etc/php.ini'
script: make test
matrix:
allow_failures:
- php: hhvm
fast_finish: true
before_deploy:
- rvm 1.9.3 do gem install mime-types -v 2.6.2
- make package
deploy:
provider: releases
api_key:
secure: UpypqlYgsU68QT/x40YzhHXvzWjFwCNo9d+G8KAdm7U9+blFfcWhV1aMdzugvPMl6woXgvJj7qHq5tAL4v6oswCORhpSBfLgOQVFaica5LiHsvWlAedOhxGmnJqMTwuepjBCxXhs3+I8Kof1n4oUL9gKytXjOVCX/f7XU1HiinU=
file:
- build/artifacts/guzzle.phar
- build/artifacts/guzzle.zip
on:
repo: guzzle/guzzle
tags: true
all_branches: true
php: 5.5

View File

@@ -1,4 +1,52 @@
# CHANGELOG
# Change Log
## 6.3.3 - 2018-04-22
* Fix: Default headers when decode_content is specified
## 6.3.2 - 2018-03-26
* Fix: Release process
## 6.3.1 - 2018-03-26
* Bug fix: Parsing 0 epoch expiry times in cookies [#2014](https://github.com/guzzle/guzzle/pull/2014)
* Improvement: Better ConnectException detection [#2012](https://github.com/guzzle/guzzle/pull/2012)
* Bug fix: Malformed domain that contains a "/" [#1999](https://github.com/guzzle/guzzle/pull/1999)
* Bug fix: Undefined offset when a cookie has no first key-value pair [#1998](https://github.com/guzzle/guzzle/pull/1998)
* Improvement: Support PHPUnit 6 [#1953](https://github.com/guzzle/guzzle/pull/1953)
* Bug fix: Support empty headers [#1915](https://github.com/guzzle/guzzle/pull/1915)
* Bug fix: Ignore case during header modifications [#1916](https://github.com/guzzle/guzzle/pull/1916)
+ Minor code cleanups, documentation fixes and clarifications.
## 6.3.0 - 2017-06-22
* Feature: force IP resolution (ipv4 or ipv6) [#1608](https://github.com/guzzle/guzzle/pull/1608), [#1659](https://github.com/guzzle/guzzle/pull/1659)
* Improvement: Don't include summary in exception message when body is empty [#1621](https://github.com/guzzle/guzzle/pull/1621)
* Improvement: Handle `on_headers` option in MockHandler [#1580](https://github.com/guzzle/guzzle/pull/1580)
* Improvement: Added SUSE Linux CA path [#1609](https://github.com/guzzle/guzzle/issues/1609)
* Improvement: Use class reference for getting the name of the class instead of using hardcoded strings [#1641](https://github.com/guzzle/guzzle/pull/1641)
* Feature: Added `read_timeout` option [#1611](https://github.com/guzzle/guzzle/pull/1611)
* Bug fix: PHP 7.x fixes [#1685](https://github.com/guzzle/guzzle/pull/1685), [#1686](https://github.com/guzzle/guzzle/pull/1686), [#1811](https://github.com/guzzle/guzzle/pull/1811)
* Deprecation: BadResponseException instantiation without a response [#1642](https://github.com/guzzle/guzzle/pull/1642)
* Feature: Added NTLM auth [#1569](https://github.com/guzzle/guzzle/pull/1569)
* Feature: Track redirect HTTP status codes [#1711](https://github.com/guzzle/guzzle/pull/1711)
* Improvement: Check handler type during construction [#1745](https://github.com/guzzle/guzzle/pull/1745)
* Improvement: Always include the Content-Length if there's a body [#1721](https://github.com/guzzle/guzzle/pull/1721)
* Feature: Added convenience method to access a cookie by name [#1318](https://github.com/guzzle/guzzle/pull/1318)
* Bug fix: Fill `CURLOPT_CAPATH` and `CURLOPT_CAINFO` properly [#1684](https://github.com/guzzle/guzzle/pull/1684)
* Improvement: Use `\GuzzleHttp\Promise\rejection_for` function instead of object init [#1827](https://github.com/guzzle/guzzle/pull/1827)
+ Minor code cleanups, documentation fixes and clarifications.
## 6.2.3 - 2017-02-28
* Fix deprecations with guzzle/psr7 version 1.4
## 6.2.2 - 2016-10-08

View File

@@ -1,4 +1,4 @@
Copyright (c) 2011-2016 Michael Dowling, https://github.com/mtdowling <mtdowling@gmail.com>
Copyright (c) 2011-2018 Michael Dowling, https://github.com/mtdowling <mtdowling@gmail.com>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal

View File

@@ -1,7 +1,9 @@
Guzzle, PHP HTTP client
=======================
[![Build Status](https://travis-ci.org/guzzle/guzzle.svg?branch=master)](https://travis-ci.org/guzzle/guzzle)
[![Latest Version](https://img.shields.io/github/release/guzzle/guzzle.svg?style=flat-square)](https://github.com/guzzle/guzzle/releases)
[![Build Status](https://img.shields.io/travis/guzzle/guzzle.svg?style=flat-square)](https://travis-ci.org/guzzle/guzzle)
[![Total Downloads](https://img.shields.io/packagist/dt/guzzlehttp/guzzle.svg?style=flat-square)](https://packagist.org/packages/guzzlehttp/guzzle)
Guzzle is a PHP HTTP client that makes it easy to send HTTP requests and
trivial to integrate with web services.
@@ -19,15 +21,13 @@ trivial to integrate with web services.
```php
$client = new \GuzzleHttp\Client();
$res = $client->request('GET', 'https://api.github.com/user', [
'auth' => ['user', 'pass']
]);
$res = $client->request('GET', 'https://api.github.com/repos/guzzle/guzzle');
echo $res->getStatusCode();
// 200
echo $res->getHeaderLine('content-type');
// 'application/json; charset=utf8'
echo $res->getBody();
// {"type":"User"...'
// '{"id": 1420053, "name": "guzzle", ...}'
// Send an asynchronous request.
$request = new \GuzzleHttp\Psr7\Request('GET', 'http://httpbin.org');
@@ -40,7 +40,7 @@ $promise->wait();
## Help and docs
- [Documentation](http://guzzlephp.org/)
- [stackoverflow](http://stackoverflow.com/questions/tagged/guzzle)
- [Stack Overflow](http://stackoverflow.com/questions/tagged/guzzle)
- [Gitter](https://gitter.im/guzzle/guzzle)
@@ -75,14 +75,15 @@ composer.phar update
## Version Guidance
| Version | Status | Packagist | Namespace | Repo | Docs | PSR-7 |
|---------|-------------|---------------------|--------------|---------------------|---------------------|-------|
| 3.x | EOL | `guzzle/guzzle` | `Guzzle` | [v3][guzzle-3-repo] | [v3][guzzle-3-docs] | No |
| 4.x | EOL | `guzzlehttp/guzzle` | `GuzzleHttp` | N/A | N/A | No |
| 5.x | Maintained | `guzzlehttp/guzzle` | `GuzzleHttp` | [v5][guzzle-5-repo] | [v5][guzzle-5-docs] | No |
| 6.x | Latest | `guzzlehttp/guzzle` | `GuzzleHttp` | [v6][guzzle-6-repo] | [v6][guzzle-6-docs] | Yes |
| Version | Status | Packagist | Namespace | Repo | Docs | PSR-7 | PHP Version |
|---------|------------|---------------------|--------------|---------------------|---------------------|-------|-------------|
| 3.x | EOL | `guzzle/guzzle` | `Guzzle` | [v3][guzzle-3-repo] | [v3][guzzle-3-docs] | No | >= 5.3.3 |
| 4.x | EOL | `guzzlehttp/guzzle` | `GuzzleHttp` | [v4][guzzle-4-repo] | N/A | No | >= 5.4 |
| 5.x | Maintained | `guzzlehttp/guzzle` | `GuzzleHttp` | [v5][guzzle-5-repo] | [v5][guzzle-5-docs] | No | >= 5.4 |
| 6.x | Latest | `guzzlehttp/guzzle` | `GuzzleHttp` | [v6][guzzle-6-repo] | [v6][guzzle-6-docs] | Yes | >= 5.5 |
[guzzle-3-repo]: https://github.com/guzzle/guzzle3
[guzzle-4-repo]: https://github.com/guzzle/guzzle/tree/4.x
[guzzle-5-repo]: https://github.com/guzzle/guzzle/tree/5.3
[guzzle-6-repo]: https://github.com/guzzle/guzzle
[guzzle-3-docs]: http://guzzle3.readthedocs.org/en/latest/

View File

@@ -14,12 +14,12 @@
],
"require": {
"php": ">=5.5",
"guzzlehttp/psr7": "^1.3.1",
"guzzlehttp/psr7": "^1.4",
"guzzlehttp/promises": "^1.0"
},
"require-dev": {
"ext-curl": "*",
"phpunit/phpunit": "^4.0",
"phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.4 || ^7.0",
"psr/log": "^1.0"
},
"autoload": {
@@ -33,9 +33,12 @@
"GuzzleHttp\\Tests\\": "tests/"
}
},
"suggest": {
"psr/log": "Required for using the Log middleware"
},
"extra": {
"branch-alias": {
"dev-master": "6.2-dev"
"dev-master": "6.3-dev"
}
}
}

View File

@@ -63,6 +63,8 @@ class Client implements ClientInterface
{
if (!isset($config['handler'])) {
$config['handler'] = HandlerStack::create();
} elseif (!is_callable($config['handler'])) {
throw new \InvalidArgumentException('handler must be a callable');
}
// Convert the base_uri to a UriInterface
@@ -142,7 +144,7 @@ class Client implements ClientInterface
$uri = Psr7\uri_for($uri === null ? '' : $uri);
if (isset($config['base_uri'])) {
$uri = Psr7\Uri::resolve(Psr7\uri_for($config['base_uri']), $uri);
$uri = Psr7\UriResolver::resolve(Psr7\uri_for($config['base_uri']), $uri);
}
return $uri->getScheme() === '' && $uri->getHost() !== '' ? $uri->withScheme('http') : $uri;
@@ -288,7 +290,14 @@ class Client implements ClientInterface
*/
private function applyOptions(RequestInterface $request, array &$options)
{
$modify = [];
$modify = [
'set_headers' => [],
];
if (isset($options['headers'])) {
$modify['set_headers'] = $options['headers'];
unset($options['headers']);
}
if (isset($options['form_params'])) {
if (isset($options['multipart'])) {
@@ -300,6 +309,8 @@ class Client implements ClientInterface
}
$options['body'] = http_build_query($options['form_params'], '', '&');
unset($options['form_params']);
// Ensure that we don't have the header in different case and set the new value.
$options['_conditional'] = Psr7\_caseless_remove(['Content-Type'], $options['_conditional']);
$options['_conditional']['Content-Type'] = 'application/x-www-form-urlencoded';
}
@@ -311,24 +322,19 @@ class Client implements ClientInterface
if (isset($options['json'])) {
$options['body'] = \GuzzleHttp\json_encode($options['json']);
unset($options['json']);
// Ensure that we don't have the header in different case and set the new value.
$options['_conditional'] = Psr7\_caseless_remove(['Content-Type'], $options['_conditional']);
$options['_conditional']['Content-Type'] = 'application/json';
}
if (!empty($options['decode_content'])
&& $options['decode_content'] !== true
) {
// Ensure that we don't have the header in different case and set the new value.
$options['_conditional'] = Psr7\_caseless_remove(['Accept-Encoding'], $options['_conditional']);
$modify['set_headers']['Accept-Encoding'] = $options['decode_content'];
}
if (isset($options['headers'])) {
if (isset($modify['set_headers'])) {
$modify['set_headers'] = $options['headers'] + $modify['set_headers'];
} else {
$modify['set_headers'] = $options['headers'];
}
unset($options['headers']);
}
if (isset($options['body'])) {
if (is_array($options['body'])) {
$this->invalidBody();
@@ -342,6 +348,8 @@ class Client implements ClientInterface
$type = isset($value[2]) ? strtolower($value[2]) : 'basic';
switch ($type) {
case 'basic':
// Ensure that we don't have the header in different case and set the new value.
$modify['set_headers'] = Psr7\_caseless_remove(['Authorization'], $modify['set_headers']);
$modify['set_headers']['Authorization'] = 'Basic '
. base64_encode("$value[0]:$value[1]");
break;
@@ -350,6 +358,10 @@ class Client implements ClientInterface
$options['curl'][CURLOPT_HTTPAUTH] = CURLAUTH_DIGEST;
$options['curl'][CURLOPT_USERPWD] = "$value[0]:$value[1]";
break;
case 'ntlm':
$options['curl'][CURLOPT_HTTPAUTH] = CURLAUTH_NTLM;
$options['curl'][CURLOPT_USERPWD] = "$value[0]:$value[1]";
break;
}
}
@@ -376,6 +388,8 @@ class Client implements ClientInterface
$request = Psr7\modify_request($request, $modify);
if ($request->getBody() instanceof Psr7\MultipartStream) {
// Use a multipart/form-data POST if a Content-Type is not set.
// Ensure that we don't have the header in different case and set the new value.
$options['_conditional'] = Psr7\_caseless_remove(['Content-Type'], $options['_conditional']);
$options['_conditional']['Content-Type'] = 'multipart/form-data; boundary='
. $request->getBody()->getBoundary();
}
@@ -402,7 +416,7 @@ class Client implements ClientInterface
throw new \InvalidArgumentException('Passing in the "body" request '
. 'option as an array to send a POST request has been deprecated. '
. 'Please use the "form_params" request option to send a '
. 'application/x-www-form-urlencoded request, or a the "multipart" '
. 'application/x-www-form-urlencoded request, or the "multipart" '
. 'request option to send a multipart/form-data request.');
}
}

View File

@@ -12,7 +12,7 @@ use Psr\Http\Message\UriInterface;
*/
interface ClientInterface
{
const VERSION = '6.2.1';
const VERSION = '6.3.3';
/**
* Send an HTTP request.

View File

@@ -86,6 +86,25 @@ class CookieJar implements CookieJarInterface
return false;
}
/**
* Finds and returns the cookie based on the name
*
* @param string $name cookie name to search for
* @return SetCookie|null cookie that was found or null if not found
*/
public function getCookieByName($name)
{
// don't allow a null name
if ($name === null) {
return null;
}
foreach ($this->cookies as $cookie) {
if ($cookie->getName() !== null && strcasecmp($cookie->getName(), $name) === 0) {
return $cookie;
}
}
}
public function toArray()
{
return array_map(function (SetCookie $cookie) {
@@ -216,11 +235,41 @@ class CookieJar implements CookieJarInterface
if (!$sc->getDomain()) {
$sc->setDomain($request->getUri()->getHost());
}
if (0 !== strpos($sc->getPath(), '/')) {
$sc->setPath($this->getCookiePathFromRequest($request));
}
$this->setCookie($sc);
}
}
}
/**
* Computes cookie path following RFC 6265 section 5.1.4
*
* @link https://tools.ietf.org/html/rfc6265#section-5.1.4
*
* @param RequestInterface $request
* @return string
*/
private function getCookiePathFromRequest(RequestInterface $request)
{
$uriPath = $request->getUri()->getPath();
if ('' === $uriPath) {
return '/';
}
if (0 !== strpos($uriPath, '/')) {
return '/';
}
if ('/' === $uriPath) {
return '/';
}
if (0 === $lastSlashPos = strrpos($uriPath, '/')) {
return '/';
}
return substr($uriPath, 0, $lastSlashPos);
}
public function withCookieHeader(RequestInterface $request)
{
$values = [];

View File

@@ -15,7 +15,7 @@ class SessionCookieJar extends CookieJar
/**
* Create a new SessionCookieJar object
*
* @param string $sessionKey Session key name to store the cookie
* @param string $sessionKey Session key name to store the cookie
* data in session
* @param bool $storeSessionCookies Set to true to store session cookies
* in the cookie jar.

View File

@@ -35,14 +35,13 @@ class SetCookie
$data = self::$defaults;
// Explode the cookie string using a series of semicolons
$pieces = array_filter(array_map('trim', explode(';', $cookie)));
// The name of the cookie (first kvp) must include an equal sign.
if (empty($pieces) || !strpos($pieces[0], '=')) {
// The name of the cookie (first kvp) must exist and include an equal sign.
if (empty($pieces[0]) || !strpos($pieces[0], '=')) {
return new self($data);
}
// Add the cookie pieces into the parsed data array
foreach ($pieces as $part) {
$cookieParts = explode('=', $part, 2);
$key = trim($cookieParts[0]);
$value = isset($cookieParts[1])
@@ -349,7 +348,7 @@ class SetCookie
return false;
}
return (bool) preg_match('/\.' . preg_quote($cookieDomain) . '$/', $domain);
return (bool) preg_match('/\.' . preg_quote($cookieDomain, '/') . '$/', $domain);
}
/**
@@ -359,7 +358,7 @@ class SetCookie
*/
public function isExpired()
{
return $this->getExpires() && time() > $this->getExpires();
return $this->getExpires() !== null && time() > $this->getExpires();
}
/**
@@ -378,8 +377,8 @@ class SetCookie
// Check if any of the invalid characters are present in the cookie name
if (preg_match(
'/[\x00-\x20\x22\x28-\x29\x2c\x2f\x3a-\x40\x5c\x7b\x7d\x7f]/',
$name)
) {
$name
)) {
return 'Cookie name must not contain invalid characters: ASCII '
. 'Control characters (0-31;127), space, tab and the '
. 'following characters: ()<>@,;:\"/?={}';

View File

@@ -1,7 +1,27 @@
<?php
namespace GuzzleHttp\Exception;
use Psr\Http\Message\RequestInterface;
use Psr\Http\Message\ResponseInterface;
/**
* Exception when an HTTP error occurs (4xx or 5xx error)
*/
class BadResponseException extends RequestException {}
class BadResponseException extends RequestException
{
public function __construct(
$message,
RequestInterface $request,
ResponseInterface $response = null,
\Exception $previous = null,
array $handlerContext = []
) {
if (null === $response) {
@trigger_error(
'Instantiating the ' . __CLASS__ . ' class without a Response is deprecated since version 6.3 and will be removed in 7.0.',
E_USER_DEPRECATED
);
}
parent::__construct($message, $request, $response, $previous, $handlerContext);
}
}

View File

@@ -1,4 +1,13 @@
<?php
namespace GuzzleHttp\Exception;
/**
* @method string getMessage()
* @method \Throwable|null getPrevious()
* @method mixed getCode()
* @method string getFile()
* @method int getLine()
* @method array getTrace()
* @method string getTraceAsString()
*/
interface GuzzleException {}

View File

@@ -81,10 +81,10 @@ class RequestException extends TransferException
$level = (int) floor($response->getStatusCode() / 100);
if ($level === 4) {
$label = 'Client error';
$className = __NAMESPACE__ . '\\ClientException';
$className = ClientException::class;
} elseif ($level === 5) {
$label = 'Server error';
$className = __NAMESPACE__ . '\\ServerException';
$className = ServerException::class;
} else {
$label = 'Unsuccessful request';
$className = __CLASS__;
@@ -93,13 +93,15 @@ class RequestException extends TransferException
$uri = $request->getUri();
$uri = static::obfuscateUri($uri);
// Server Error: `GET /` resulted in a `404 Not Found` response:
// Client Error: `GET /` resulted in a `404 Not Found` response:
// <html> ... (truncated)
$message = sprintf(
'%s: `%s` resulted in a `%s` response',
'%s: `%s %s` resulted in a `%s %s` response',
$label,
$request->getMethod() . ' ' . $uri,
$response->getStatusCode() . ' ' . $response->getReasonPhrase()
$request->getMethod(),
$uri,
$response->getStatusCode(),
$response->getReasonPhrase()
);
$summary = static::getResponseBodySummary($response);
@@ -129,6 +131,11 @@ class RequestException extends TransferException
}
$size = $body->getSize();
if ($size === 0) {
return null;
}
$summary = $body->read(120);
$body->rewind();

View File

@@ -4,7 +4,6 @@ namespace GuzzleHttp\Handler;
use GuzzleHttp\Exception\RequestException;
use GuzzleHttp\Exception\ConnectException;
use GuzzleHttp\Promise\FulfilledPromise;
use GuzzleHttp\Promise\RejectedPromise;
use GuzzleHttp\Psr7;
use GuzzleHttp\Psr7\LazyOpenStream;
use GuzzleHttp\TransferStats;
@@ -16,7 +15,7 @@ use Psr\Http\Message\RequestInterface;
class CurlFactory implements CurlFactoryInterface
{
/** @var array */
private $handles;
private $handles = [];
/** @var int Total number of idle handles to keep in cache */
private $maxHandles;
@@ -163,7 +162,7 @@ class CurlFactory implements CurlFactoryInterface
// If an exception was encountered during the onHeaders event, then
// return a rejected promise that wraps that exception.
if ($easy->onHeadersException) {
return new RejectedPromise(
return \GuzzleHttp\Promise\rejection_for(
new RequestException(
'An error was encountered during the on_headers event',
$easy->request,
@@ -186,7 +185,7 @@ class CurlFactory implements CurlFactoryInterface
? new ConnectException($message, $easy->request, null, $ctx)
: new RequestException($message, $easy->request, $easy->response, null, $ctx);
return new RejectedPromise($error);
return \GuzzleHttp\Promise\rejection_for($error);
}
private function getDefaultConf(EasyHandle $easy)
@@ -288,7 +287,14 @@ class CurlFactory implements CurlFactoryInterface
{
foreach ($conf['_headers'] as $name => $values) {
foreach ($values as $value) {
$conf[CURLOPT_HTTPHEADER][] = "$name: $value";
$value = (string) $value;
if ($value === '') {
// cURL requires a special format for empty headers.
// See https://github.com/guzzle/guzzle/issues/1882 for more details.
$conf[CURLOPT_HTTPHEADER][] = "$name;";
} else {
$conf[CURLOPT_HTTPHEADER][] = "$name: $value";
}
}
}
@@ -326,12 +332,20 @@ class CurlFactory implements CurlFactoryInterface
$conf[CURLOPT_SSL_VERIFYHOST] = 2;
$conf[CURLOPT_SSL_VERIFYPEER] = true;
if (is_string($options['verify'])) {
$conf[CURLOPT_CAINFO] = $options['verify'];
// Throw an error if the file/folder/link path is not valid or doesn't exist.
if (!file_exists($options['verify'])) {
throw new \InvalidArgumentException(
"SSL CA bundle not found: {$options['verify']}"
);
}
// If it's a directory or a link to a directory use CURLOPT_CAPATH.
// If not, it's probably a file, or a link to a file, so use CURLOPT_CAINFO.
if (is_dir($options['verify']) ||
(is_link($options['verify']) && is_dir(readlink($options['verify'])))) {
$conf[CURLOPT_CAPATH] = $options['verify'];
} else {
$conf[CURLOPT_CAINFO] = $options['verify'];
}
}
}
}
@@ -370,15 +384,30 @@ class CurlFactory implements CurlFactoryInterface
$conf[CURLOPT_FILE] = fopen('php://temp', 'w+');
$easy->sink = Psr7\stream_for($conf[CURLOPT_FILE]);
}
$timeoutRequiresNoSignal = false;
if (isset($options['timeout'])) {
$timeoutRequiresNoSignal |= $options['timeout'] < 1;
$conf[CURLOPT_TIMEOUT_MS] = $options['timeout'] * 1000;
}
// CURL default value is CURL_IPRESOLVE_WHATEVER
if (isset($options['force_ip_resolve'])) {
if ('v4' === $options['force_ip_resolve']) {
$conf[CURLOPT_IPRESOLVE] = CURL_IPRESOLVE_V4;
} elseif ('v6' === $options['force_ip_resolve']) {
$conf[CURLOPT_IPRESOLVE] = CURL_IPRESOLVE_V6;
}
}
if (isset($options['connect_timeout'])) {
$timeoutRequiresNoSignal |= $options['connect_timeout'] < 1;
$conf[CURLOPT_CONNECTTIMEOUT_MS] = $options['connect_timeout'] * 1000;
}
if ($timeoutRequiresNoSignal && strtoupper(substr(PHP_OS, 0, 3)) !== 'WIN') {
$conf[CURLOPT_NOSIGNAL] = true;
}
if (isset($options['proxy'])) {
if (!is_array($options['proxy'])) {
$conf[CURLOPT_PROXY] = $options['proxy'];

View File

@@ -65,7 +65,9 @@ class CurlMultiHandler
$promise = new Promise(
[$this, 'execute'],
function () use ($id) { return $this->cancel($id); }
function () use ($id) {
return $this->cancel($id);
}
);
$this->addRequest(['easy' => $easy, 'deferred' => $promise]);

View File

@@ -1,6 +1,7 @@
<?php
namespace GuzzleHttp\Handler;
use GuzzleHttp\Exception\RequestException;
use GuzzleHttp\HandlerStack;
use GuzzleHttp\Promise\PromiseInterface;
use GuzzleHttp\Promise\RejectedPromise;
@@ -13,7 +14,7 @@ use Psr\Http\Message\ResponseInterface;
*/
class MockHandler implements \Countable
{
private $queue;
private $queue = [];
private $lastRequest;
private $lastOptions;
private $onFulfilled;
@@ -73,12 +74,24 @@ class MockHandler implements \Countable
$this->lastOptions = $options;
$response = array_shift($this->queue);
if (isset($options['on_headers'])) {
if (!is_callable($options['on_headers'])) {
throw new \InvalidArgumentException('on_headers must be callable');
}
try {
$options['on_headers']($response);
} catch (\Exception $e) {
$msg = 'An error was encountered during the on_headers event';
$response = new RequestException($msg, $request, $response, $e);
}
}
if (is_callable($response)) {
$response = call_user_func($response, $request, $options);
}
$response = $response instanceof \Exception
? new RejectedPromise($response)
? \GuzzleHttp\Promise\rejection_for($response)
: \GuzzleHttp\Promise\promise_for($response);
return $response->then(
@@ -107,7 +120,7 @@ class MockHandler implements \Countable
if ($this->onRejected) {
call_user_func($this->onRejected, $reason);
}
return new RejectedPromise($reason);
return \GuzzleHttp\Promise\rejection_for($reason);
}
);
}
@@ -145,7 +158,7 @@ class MockHandler implements \Countable
/**
* Get the last received request options.
*
* @return RequestInterface
* @return array
*/
public function getLastOptions()
{

View File

@@ -4,7 +4,6 @@ namespace GuzzleHttp\Handler;
use GuzzleHttp\Exception\RequestException;
use GuzzleHttp\Exception\ConnectException;
use GuzzleHttp\Promise\FulfilledPromise;
use GuzzleHttp\Promise\RejectedPromise;
use GuzzleHttp\Promise\PromiseInterface;
use GuzzleHttp\Psr7;
use GuzzleHttp\TransferStats;
@@ -61,13 +60,14 @@ class StreamHandler
if (strpos($message, 'getaddrinfo') // DNS lookup failed
|| strpos($message, 'Connection refused')
|| strpos($message, "couldn't connect to host") // error on HHVM
|| strpos($message, "connection attempt failed")
) {
$e = new ConnectException($e->getMessage(), $request, $e);
}
$e = RequestException::wrapException($request, $e);
$this->invokeStats($options, $request, $startTime, null, $e);
return new RejectedPromise($e);
return \GuzzleHttp\Promise\rejection_for($e);
}
}
@@ -103,7 +103,7 @@ class StreamHandler
$status = $parts[1];
$reason = isset($parts[2]) ? $parts[2] : null;
$headers = \GuzzleHttp\headers_from_lines($hdrs);
list ($stream, $headers) = $this->checkDecode($options, $headers, $stream);
list($stream, $headers) = $this->checkDecode($options, $headers, $stream);
$stream = Psr7\stream_for($stream);
$sink = $stream;
@@ -119,7 +119,7 @@ class StreamHandler
} catch (\Exception $e) {
$msg = 'An error was encountered during the on_headers event';
$ex = new RequestException($msg, $request, $response, $e);
return new RejectedPromise($ex);
return \GuzzleHttp\Promise\rejection_for($ex);
}
}
@@ -276,7 +276,7 @@ class StreamHandler
}
$params = [];
$context = $this->getDefaultContext($request, $options);
$context = $this->getDefaultContext($request);
if (isset($options['on_headers']) && !is_callable($options['on_headers'])) {
throw new \InvalidArgumentException('on_headers must be callable');
@@ -301,6 +301,17 @@ class StreamHandler
);
}
// Microsoft NTLM authentication only supported with curl handler
if (isset($options['auth'])
&& is_array($options['auth'])
&& isset($options['auth'][2])
&& 'ntlm' == $options['auth'][2]
) {
throw new \InvalidArgumentException('Microsoft NTLM authentication only supported with curl handler');
}
$uri = $this->resolveHost($request, $options);
$context = $this->createResource(
function () use ($context, $params) {
return stream_context_create($context, $params);
@@ -308,14 +319,45 @@ class StreamHandler
);
return $this->createResource(
function () use ($request, &$http_response_header, $context) {
$resource = fopen((string) $request->getUri()->withFragment(''), 'r', null, $context);
function () use ($uri, &$http_response_header, $context, $options) {
$resource = fopen((string) $uri, 'r', null, $context);
$this->lastHeaders = $http_response_header;
if (isset($options['read_timeout'])) {
$readTimeout = $options['read_timeout'];
$sec = (int) $readTimeout;
$usec = ($readTimeout - $sec) * 100000;
stream_set_timeout($resource, $sec, $usec);
}
return $resource;
}
);
}
private function resolveHost(RequestInterface $request, array $options)
{
$uri = $request->getUri();
if (isset($options['force_ip_resolve']) && !filter_var($uri->getHost(), FILTER_VALIDATE_IP)) {
if ('v4' === $options['force_ip_resolve']) {
$records = dns_get_record($uri->getHost(), DNS_A);
if (!isset($records[0]['ip'])) {
throw new ConnectException(sprintf("Could not resolve IPv4 address for host '%s'", $uri->getHost()), $request);
}
$uri = $uri->withHost($records[0]['ip']);
} elseif ('v6' === $options['force_ip_resolve']) {
$records = dns_get_record($uri->getHost(), DNS_AAAA);
if (!isset($records[0]['ipv6'])) {
throw new ConnectException(sprintf("Could not resolve IPv6 address for host '%s'", $uri->getHost()), $request);
}
$uri = $uri->withHost('[' . $records[0]['ipv6'] . ']');
}
}
return $uri;
}
private function getDefaultContext(RequestInterface $request)
{
$headers = '';

View File

@@ -22,7 +22,7 @@ class HandlerStack
* Creates a default handler stack that can be used by clients.
*
* The returned handler will wrap the provided handler or use the most
* appropriate default handler for you system. The returned HandlerStack has
* appropriate default handler for your system. The returned HandlerStack has
* support for cookies, redirects, HTTP error exceptions, and preparing a body
* before sending.
*

View File

@@ -19,7 +19,6 @@ use Psr\Http\Message\ResponseInterface;
* - {host}: Host of the request
* - {method}: Method of the request
* - {uri}: URI of the request
* - {host}: Host of the request
* - {version}: Protocol version
* - {target}: Request target of the request (path + query + fragment)
* - {hostname}: Hostname of the machine that sent the request
@@ -74,7 +73,6 @@ class MessageFormatter
return preg_replace_callback(
'/{\s*([A-Za-z_\-\.0-9]+)\s*}/',
function (array $matches) use ($request, $response, $error, &$cache) {
if (isset($cache[$matches[1]])) {
return $cache[$matches[1]];
}

View File

@@ -34,10 +34,11 @@ final class Middleware
$cookieJar = $options['cookies'];
$request = $cookieJar->withCookieHeader($request);
return $handler($request, $options)
->then(function ($response) use ($cookieJar, $request) {
$cookieJar->extractCookies($request, $response);
return $response;
}
->then(
function ($response) use ($cookieJar, $request) {
$cookieJar->extractCookies($request, $response);
return $response;
}
);
};
};
@@ -72,7 +73,7 @@ final class Middleware
/**
* Middleware that pushes history data to an ArrayAccess container.
*
* @param array $container Container to hold the history (by reference).
* @param array|\ArrayAccess $container Container to hold the history (by reference).
*
* @return callable Returns a function that accepts the next handler.
* @throws \InvalidArgumentException if container is not an array or ArrayAccess.
@@ -102,7 +103,7 @@ final class Middleware
'error' => $reason,
'options' => $options
];
return new RejectedPromise($reason);
return \GuzzleHttp\Promise\rejection_for($reason);
}
);
};

View File

@@ -14,9 +14,6 @@ class PrepareBodyMiddleware
/** @var callable */
private $nextHandler;
/** @var array */
private static $skipMethods = ['GET' => true, 'HEAD' => true];
/**
* @param callable $nextHandler Next handler to invoke.
*/
@@ -36,9 +33,7 @@ class PrepareBodyMiddleware
$fn = $this->nextHandler;
// Don't do anything if the request has no body.
if (isset(self::$skipMethods[$request->getMethod()])
|| $request->getBody()->getSize() === 0
) {
if ($request->getBody()->getSize() === 0) {
return $fn($request, $options);
}
@@ -54,8 +49,7 @@ class PrepareBodyMiddleware
}
// Add a default content-length or transfer-encoding header.
if (!isset(self::$skipMethods[$request->getMethod()])
&& !$request->hasHeader('Content-Length')
if (!$request->hasHeader('Content-Length')
&& !$request->hasHeader('Transfer-Encoding')
) {
$size = $request->getBody()->getSize();

View File

@@ -19,6 +19,8 @@ class RedirectMiddleware
{
const HISTORY_HEADER = 'X-Guzzle-Redirect-History';
const STATUS_HISTORY_HEADER = 'X-Guzzle-Redirect-Status-History';
public static $defaultSettings = [
'max' => 5,
'protocols' => ['http', 'https'],
@@ -108,23 +110,27 @@ class RedirectMiddleware
if (!empty($options['allow_redirects']['track_redirects'])) {
return $this->withTracking(
$promise,
(string) $nextRequest->getUri()
(string) $nextRequest->getUri(),
$response->getStatusCode()
);
}
return $promise;
}
private function withTracking(PromiseInterface $promise, $uri)
private function withTracking(PromiseInterface $promise, $uri, $statusCode)
{
return $promise->then(
function (ResponseInterface $response) use ($uri) {
function (ResponseInterface $response) use ($uri, $statusCode) {
// Note that we are pushing to the front of the list as this
// would be an earlier response than what is currently present
// in the history header.
$header = $response->getHeader(self::HISTORY_HEADER);
array_unshift($header, $uri);
return $response->withHeader(self::HISTORY_HEADER, $header);
$historyHeader = $response->getHeader(self::HISTORY_HEADER);
$statusHeader = $response->getHeader(self::STATUS_HISTORY_HEADER);
array_unshift($historyHeader, $uri);
array_unshift($statusHeader, $statusCode);
return $response->withHeader(self::HISTORY_HEADER, $historyHeader)
->withHeader(self::STATUS_HISTORY_HEADER, $statusHeader);
}
);
}
@@ -208,9 +214,9 @@ class RedirectMiddleware
ResponseInterface $response,
array $protocols
) {
$location = Psr7\Uri::resolve(
$location = Psr7\UriResolver::resolve(
$request->getUri(),
$response->getHeaderLine('Location')
new Psr7\Uri($response->getHeaderLine('Location'))
);
// Ensure that the redirect URI is allowed based on the protocols.

View File

@@ -43,8 +43,8 @@ final class RequestOptions
const AUTH = 'auth';
/**
* body: (string|null|callable|iterator|object) Body to send in the
* request.
* body: (resource|string|null|int|float|StreamInterface|callable|\Iterator)
* Body to send in the request.
*/
const BODY = 'body';
@@ -237,8 +237,19 @@ final class RequestOptions
*/
const TIMEOUT = 'timeout';
/**
* read_timeout: (float, default=default_socket_timeout ini setting) Float describing
* the body read timeout, for stream requests.
*/
const READ_TIMEOUT = 'read_timeout';
/**
* version: (float) Specifies the HTTP protocol version to attempt to use.
*/
const VERSION = 'version';
/**
* force_ip_resolve: (bool) Force client to use only ipv4 or ipv6 protocol
*/
const FORCE_IP_RESOLVE = 'force_ip_resolve';
}

View File

@@ -97,7 +97,7 @@ class RetryMiddleware
null,
$reason
)) {
return new RejectedPromise($reason);
return \GuzzleHttp\Promise\rejection_for($reason);
}
return $this->doRetry($req, $options);
};

View File

@@ -107,7 +107,6 @@ class UriTemplate
$useQuery = self::$operatorHash[$parsed['operator']]['query'];
foreach ($parsed['values'] as $value) {
if (!isset($this->variables[$value['value']])) {
continue;
}
@@ -117,11 +116,9 @@ class UriTemplate
$expanded = '';
if (is_array($variable)) {
$isAssoc = $this->isAssoc($variable);
$kvp = [];
foreach ($variable as $key => $var) {
if ($isAssoc) {
$key = rawurlencode($key);
$isNestedArray = is_array($var);
@@ -179,7 +176,6 @@ class UriTemplate
}
$expanded = implode(',', $kvp);
}
} else {
if ($value['modifier'] === ':') {
$variable = substr($variable, 0, $value['position']);

View File

@@ -167,6 +167,8 @@ function default_ca_bundle()
'/etc/ssl/certs/ca-certificates.crt',
// FreeBSD (provided by the ca_root_nss package)
'/usr/local/share/certs/ca-root-nss.crt',
// SLES 12 (provided by the ca-certificates package)
'/var/lib/ca-certificates/ca-bundle.pem',
// OS X provided by homebrew (using the default path)
'/usr/local/etc/openssl/cert.pem',
// Google app engine
@@ -300,7 +302,8 @@ function json_decode($json, $assoc = false, $depth = 512, $options = 0)
$data = \json_decode($json, $assoc, $depth, $options);
if (JSON_ERROR_NONE !== json_last_error()) {
throw new \InvalidArgumentException(
'json_decode error: ' . json_last_error_msg());
'json_decode error: ' . json_last_error_msg()
);
}
return $data;
@@ -322,7 +325,8 @@ function json_encode($value, $options = 0, $depth = 512)
$json = \json_encode($value, $options, $depth);
if (JSON_ERROR_NONE !== json_last_error()) {
throw new \InvalidArgumentException(
'json_encode error: ' . json_last_error_msg());
'json_encode error: ' . json_last_error_msg()
);
}
return $json;

View File

@@ -1,11 +0,0 @@
phpunit.xml
composer.phar
composer.lock
composer-test.lock
vendor/
build/artifacts/
artifacts/
docs/_build
docs/*.pyc
.idea
.DS_STORE

View File

@@ -1,19 +0,0 @@
language: php
php:
- 5.5
- 5.6
- 7.0
- hhvm
sudo: false
install:
- travis_retry composer install --no-interaction --prefer-source
script: make test
matrix:
allow_failures:
- php: hhvm
fast_finish: true

View File

@@ -1,31 +1,65 @@
# CHANGELOG
## 1.3.1 - 2016-12-20
### Fixed
- `wait()` foreign promise compatibility
## 1.3.0 - 2016-11-18
### Added
- Adds support for custom task queues.
### Fixed
- Fixed coroutine promise memory leak.
## 1.2.0 - 2016-05-18
* Update to now catch `\Throwable` on PHP 7+
### Changed
- Update to now catch `\Throwable` on PHP 7+
## 1.1.0 - 2016-03-07
* Update EachPromise to prevent recurring on a iterator when advancing, as this
### Changed
- Update EachPromise to prevent recurring on a iterator when advancing, as this
could trigger fatal generator errors.
* Update Promise to allow recursive waiting without unwrapping exceptions.
- Update Promise to allow recursive waiting without unwrapping exceptions.
## 1.0.3 - 2015-10-15
* Update EachPromise to immediately resolve when the underlying promise iterator
### Changed
- Update EachPromise to immediately resolve when the underlying promise iterator
is empty. Previously, such a promise would throw an exception when its `wait`
function was called.
## 1.0.2 - 2015-05-15
* Conditionally require functions.php.
### Changed
- Conditionally require functions.php.
## 1.0.1 - 2015-06-24
* Updating EachPromise to call next on the underlying promise iterator as late
### Changed
- Updating EachPromise to call next on the underlying promise iterator as late
as possible to ensure that generators that generate new requests based on
callbacks are not iterated until after callbacks are invoked.
## 1.0.0 - 2015-05-12
* Initial release
- Initial release

View File

@@ -1,4 +1,4 @@
Copyright (c) 2015 Michael Dowling, https://github.com/mtdowling <mtdowling@gmail.com>
Copyright (c) 2015-2016 Michael Dowling, https://github.com/mtdowling <mtdowling@gmail.com>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal

View File

@@ -96,7 +96,7 @@ $promise->resolve('reader.');
## Promise forwarding
Promises can be chained one after the other. Each then in the chain is a new
promise. The return value of of a promise is what's forwarded to the next
promise. The return value of a promise is what's forwarded to the next
promise in the chain. Returning a promise in a `then` callback will cause the
subsequent promises in the chain to only be fulfilled when the returned promise
has been fulfilled. The next promise in the chain will be invoked with the
@@ -315,8 +315,11 @@ A promise has the following methods:
- `then(callable $onFulfilled, callable $onRejected) : PromiseInterface`
Creates a new promise that is fulfilled or rejected when the promise is
resolved.
Appends fulfillment and rejection handlers to the promise, and returns a new promise resolving to the return value of the called handler.
- `otherwise(callable $onRejected) : PromiseInterface`
Appends a rejection handler callback to the promise, and returns a new promise resolving to the return value of the callback if it is called, or to its original fulfillment value if the promise is instead fulfilled.
- `wait($unwrap = true) : mixed`

View File

@@ -1,6 +1,5 @@
{
"name": "guzzlehttp/promises",
"type": "library",
"description": "Guzzle promises library",
"keywords": ["promise"],
"license": "MIT",
@@ -15,7 +14,7 @@
"php": ">=5.5.0"
},
"require-dev": {
"phpunit/phpunit": "~4.0"
"phpunit/phpunit": "^4.0"
},
"autoload": {
"psr-4": {
@@ -23,9 +22,13 @@
},
"files": ["src/functions_include.php"]
},
"scripts": {
"test": "vendor/bin/phpunit",
"test-ci": "vendor/bin/phpunit --coverage-text"
},
"extra": {
"branch-alias": {
"dev-master": "1.0-dev"
"dev-master": "1.4-dev"
}
}
}

View File

@@ -1,17 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<phpunit bootstrap="./tests/bootstrap.php"
colors="true">
<testsuites>
<testsuite>
<directory>tests</directory>
</testsuite>
</testsuites>
<filter>
<whitelist>
<directory suffix=".php">src</directory>
<exclude>
<directory suffix="Interface.php">src/</directory>
</exclude>
</whitelist>
</filter>
</phpunit>

View File

@@ -0,0 +1,151 @@
<?php
namespace GuzzleHttp\Promise;
use Exception;
use Generator;
use Throwable;
/**
* Creates a promise that is resolved using a generator that yields values or
* promises (somewhat similar to C#'s async keyword).
*
* When called, the coroutine function will start an instance of the generator
* and returns a promise that is fulfilled with its final yielded value.
*
* Control is returned back to the generator when the yielded promise settles.
* This can lead to less verbose code when doing lots of sequential async calls
* with minimal processing in between.
*
* use GuzzleHttp\Promise;
*
* function createPromise($value) {
* return new Promise\FulfilledPromise($value);
* }
*
* $promise = Promise\coroutine(function () {
* $value = (yield createPromise('a'));
* try {
* $value = (yield createPromise($value . 'b'));
* } catch (\Exception $e) {
* // The promise was rejected.
* }
* yield $value . 'c';
* });
*
* // Outputs "abc"
* $promise->then(function ($v) { echo $v; });
*
* @param callable $generatorFn Generator function to wrap into a promise.
*
* @return Promise
* @link https://github.com/petkaantonov/bluebird/blob/master/API.md#generators inspiration
*/
final class Coroutine implements PromiseInterface
{
/**
* @var PromiseInterface|null
*/
private $currentPromise;
/**
* @var Generator
*/
private $generator;
/**
* @var Promise
*/
private $result;
public function __construct(callable $generatorFn)
{
$this->generator = $generatorFn();
$this->result = new Promise(function () {
while (isset($this->currentPromise)) {
$this->currentPromise->wait();
}
});
$this->nextCoroutine($this->generator->current());
}
public function then(
callable $onFulfilled = null,
callable $onRejected = null
) {
return $this->result->then($onFulfilled, $onRejected);
}
public function otherwise(callable $onRejected)
{
return $this->result->otherwise($onRejected);
}
public function wait($unwrap = true)
{
return $this->result->wait($unwrap);
}
public function getState()
{
return $this->result->getState();
}
public function resolve($value)
{
$this->result->resolve($value);
}
public function reject($reason)
{
$this->result->reject($reason);
}
public function cancel()
{
$this->currentPromise->cancel();
$this->result->cancel();
}
private function nextCoroutine($yielded)
{
$this->currentPromise = promise_for($yielded)
->then([$this, '_handleSuccess'], [$this, '_handleFailure']);
}
/**
* @internal
*/
public function _handleSuccess($value)
{
unset($this->currentPromise);
try {
$next = $this->generator->send($value);
if ($this->generator->valid()) {
$this->nextCoroutine($next);
} else {
$this->result->resolve($value);
}
} catch (Exception $exception) {
$this->result->reject($exception);
} catch (Throwable $throwable) {
$this->result->reject($throwable);
}
}
/**
* @internal
*/
public function _handleFailure($reason)
{
unset($this->currentPromise);
try {
$nextYield = $this->generator->throw(exception_for($reason));
// The throw was caught, so keep iterating on the coroutine
$this->nextCoroutine($nextYield);
} catch (Exception $exception) {
$this->result->reject($exception);
} catch (Throwable $throwable) {
$this->result->reject($throwable);
}
}
}

View File

@@ -263,10 +263,17 @@ class Promise implements PromiseInterface
$this->waitList = null;
foreach ($waitList as $result) {
$result->waitIfPending();
while ($result->result instanceof Promise) {
$result = $result->result;
while (true) {
$result->waitIfPending();
if ($result->result instanceof Promise) {
$result = $result->result;
} else {
if ($result->result instanceof PromiseInterface) {
$result->result->wait(false);
}
break;
}
}
}
}

View File

@@ -10,7 +10,7 @@ namespace GuzzleHttp\Promise;
*
* GuzzleHttp\Promise\queue()->run();
*/
class TaskQueue
class TaskQueue implements TaskQueueInterface
{
private $enableShutdown = true;
private $queue = [];
@@ -30,30 +30,16 @@ class TaskQueue
}
}
/**
* Returns true if the queue is empty.
*
* @return bool
*/
public function isEmpty()
{
return !$this->queue;
}
/**
* Adds a task to the queue that will be executed the next time run is
* called.
*
* @param callable $task
*/
public function add(callable $task)
{
$this->queue[] = $task;
}
/**
* Execute all of the pending task in the queue.
*/
public function run()
{
/** @var callable $task */

View File

@@ -0,0 +1,25 @@
<?php
namespace GuzzleHttp\Promise;
interface TaskQueueInterface
{
/**
* Returns true if the queue is empty.
*
* @return bool
*/
public function isEmpty();
/**
* Adds a task to the queue that will be executed the next time run is
* called.
*
* @param callable $task
*/
public function add(callable $task);
/**
* Execute all of the pending task in the queue.
*/
public function run();
}

View File

@@ -14,13 +14,17 @@ namespace GuzzleHttp\Promise;
* }
* </code>
*
* @return TaskQueue
* @param TaskQueueInterface $assign Optionally specify a new queue instance.
*
* @return TaskQueueInterface
*/
function queue()
function queue(TaskQueueInterface $assign = null)
{
static $queue;
if (!$queue) {
if ($assign) {
$queue = $assign;
} elseif (!$queue) {
$queue = new TaskQueue();
}
@@ -210,7 +214,7 @@ function unwrap($promises)
*
* @param mixed $promises Promises or values.
*
* @return Promise
* @return PromiseInterface
*/
function all($promises)
{
@@ -243,7 +247,7 @@ function all($promises)
* @param int $count Total number of promises.
* @param mixed $promises Promises or values.
*
* @return Promise
* @return PromiseInterface
*/
function some($count, $promises)
{
@@ -299,7 +303,7 @@ function any($promises)
*
* @param mixed $promises Promises or values.
*
* @return Promise
* @return PromiseInterface
* @see GuzzleHttp\Promise\inspect for the inspection state array format.
*/
function settle($promises)
@@ -337,7 +341,7 @@ function settle($promises)
* @param callable $onFulfilled
* @param callable $onRejected
*
* @return Promise
* @return PromiseInterface
*/
function each(
$iterable,
@@ -363,7 +367,7 @@ function each(
* @param callable $onFulfilled
* @param callable $onRejected
*
* @return mixed
* @return PromiseInterface
*/
function each_limit(
$iterable,
@@ -387,7 +391,7 @@ function each_limit(
* @param int|callable $concurrency
* @param callable $onFulfilled
*
* @return mixed
* @return PromiseInterface
*/
function each_limit_all(
$iterable,
@@ -441,60 +445,13 @@ function is_settled(PromiseInterface $promise)
}
/**
* Creates a promise that is resolved using a generator that yields values or
* promises (somewhat similar to C#'s async keyword).
* @see Coroutine
*
* When called, the coroutine function will start an instance of the generator
* and returns a promise that is fulfilled with its final yielded value.
* @param callable $generatorFn
*
* Control is returned back to the generator when the yielded promise settles.
* This can lead to less verbose code when doing lots of sequential async calls
* with minimal processing in between.
*
* use GuzzleHttp\Promise;
*
* function createPromise($value) {
* return new Promise\FulfilledPromise($value);
* }
*
* $promise = Promise\coroutine(function () {
* $value = (yield createPromise('a'));
* try {
* $value = (yield createPromise($value . 'b'));
* } catch (\Exception $e) {
* // The promise was rejected.
* }
* yield $value . 'c';
* });
*
* // Outputs "abc"
* $promise->then(function ($v) { echo $v; });
*
* @param callable $generatorFn Generator function to wrap into a promise.
*
* @return Promise
* @link https://github.com/petkaantonov/bluebird/blob/master/API.md#generators inspiration
* @return PromiseInterface
*/
function coroutine(callable $generatorFn)
{
$generator = $generatorFn();
return __next_coroutine($generator->current(), $generator)->then();
}
/** @internal */
function __next_coroutine($yielded, \Generator $generator)
{
return promise_for($yielded)->then(
function ($value) use ($generator) {
$nextYield = $generator->send($value);
return $generator->valid()
? __next_coroutine($nextYield, $generator)
: $value;
},
function ($reason) use ($generator) {
$nextYield = $generator->throw(exception_for($reason));
// The throw was caught, so keep iterating on the coroutine
return __next_coroutine($nextYield, $generator);
}
);
return new Coroutine($generatorFn);
}

View File

@@ -1,14 +0,0 @@
<?php
namespace GuzzleHttp\Promise\Tests;
use GuzzleHttp\Promise\AggregateException;
class AggregateExceptionTest extends \PHPUnit_Framework_TestCase
{
public function testHasReason()
{
$e = new AggregateException('foo', ['baz', 'bar']);
$this->assertContains('foo', $e->getMessage());
$this->assertEquals(['baz', 'bar'], $e->getReason());
}
}

View File

@@ -1,336 +0,0 @@
<?php
namespace GuzzleHttp\Promise\Tests;
use GuzzleHttp\Promise\RejectedPromise;
use GuzzleHttp\Promise\FulfilledPromise;
use GuzzleHttp\Promise\Promise;
use GuzzleHttp\Promise\PromiseInterface;
use GuzzleHttp\Promise\EachPromise;
use GuzzleHttp\Promise as P;
/**
* @covers GuzzleHttp\Promise\EachPromise
*/
class EachPromiseTest extends \PHPUnit_Framework_TestCase
{
public function testReturnsSameInstance()
{
$each = new EachPromise([], ['concurrency' => 100]);
$this->assertSame($each->promise(), $each->promise());
}
public function testInvokesAllPromises()
{
$promises = [new Promise(), new Promise(), new Promise()];
$called = [];
$each = new EachPromise($promises, [
'fulfilled' => function ($value) use (&$called) {
$called[] = $value;
}
]);
$p = $each->promise();
$promises[0]->resolve('a');
$promises[1]->resolve('c');
$promises[2]->resolve('b');
P\queue()->run();
$this->assertEquals(['a', 'c', 'b'], $called);
$this->assertEquals(PromiseInterface::FULFILLED, $p->getState());
}
public function testIsWaitable()
{
$a = $this->createSelfResolvingPromise('a');
$b = $this->createSelfResolvingPromise('b');
$called = [];
$each = new EachPromise([$a, $b], [
'fulfilled' => function ($value) use (&$called) { $called[] = $value; }
]);
$p = $each->promise();
$this->assertNull($p->wait());
$this->assertEquals(PromiseInterface::FULFILLED, $p->getState());
$this->assertEquals(['a', 'b'], $called);
}
public function testCanResolveBeforeConsumingAll()
{
$called = 0;
$a = $this->createSelfResolvingPromise('a');
$b = new Promise(function () { $this->fail(); });
$each = new EachPromise([$a, $b], [
'fulfilled' => function ($value, $idx, Promise $aggregate) use (&$called) {
$this->assertSame($idx, 0);
$this->assertEquals('a', $value);
$aggregate->resolve(null);
$called++;
},
'rejected' => function (\Exception $reason) {
$this->fail($reason->getMessage());
}
]);
$p = $each->promise();
$p->wait();
$this->assertNull($p->wait());
$this->assertEquals(1, $called);
$this->assertEquals(PromiseInterface::FULFILLED, $a->getState());
$this->assertEquals(PromiseInterface::PENDING, $b->getState());
// Resolving $b has no effect on the aggregate promise.
$b->resolve('foo');
$this->assertEquals(1, $called);
}
public function testLimitsPendingPromises()
{
$pending = [new Promise(), new Promise(), new Promise(), new Promise()];
$promises = new \ArrayIterator($pending);
$each = new EachPromise($promises, ['concurrency' => 2]);
$p = $each->promise();
$this->assertCount(2, $this->readAttribute($each, 'pending'));
$pending[0]->resolve('a');
$this->assertCount(2, $this->readAttribute($each, 'pending'));
$this->assertTrue($promises->valid());
$pending[1]->resolve('b');
P\queue()->run();
$this->assertCount(2, $this->readAttribute($each, 'pending'));
$this->assertTrue($promises->valid());
$promises[2]->resolve('c');
P\queue()->run();
$this->assertCount(1, $this->readAttribute($each, 'pending'));
$this->assertEquals(PromiseInterface::PENDING, $p->getState());
$promises[3]->resolve('d');
P\queue()->run();
$this->assertNull($this->readAttribute($each, 'pending'));
$this->assertEquals(PromiseInterface::FULFILLED, $p->getState());
$this->assertFalse($promises->valid());
}
public function testDynamicallyLimitsPendingPromises()
{
$calls = [];
$pendingFn = function ($count) use (&$calls) {
$calls[] = $count;
return 2;
};
$pending = [new Promise(), new Promise(), new Promise(), new Promise()];
$promises = new \ArrayIterator($pending);
$each = new EachPromise($promises, ['concurrency' => $pendingFn]);
$p = $each->promise();
$this->assertCount(2, $this->readAttribute($each, 'pending'));
$pending[0]->resolve('a');
$this->assertCount(2, $this->readAttribute($each, 'pending'));
$this->assertTrue($promises->valid());
$pending[1]->resolve('b');
$this->assertCount(2, $this->readAttribute($each, 'pending'));
P\queue()->run();
$this->assertTrue($promises->valid());
$promises[2]->resolve('c');
P\queue()->run();
$this->assertCount(1, $this->readAttribute($each, 'pending'));
$this->assertEquals(PromiseInterface::PENDING, $p->getState());
$promises[3]->resolve('d');
P\queue()->run();
$this->assertNull($this->readAttribute($each, 'pending'));
$this->assertEquals(PromiseInterface::FULFILLED, $p->getState());
$this->assertEquals([0, 1, 1, 1], $calls);
$this->assertFalse($promises->valid());
}
public function testClearsReferencesWhenResolved()
{
$called = false;
$a = new Promise(function () use (&$a, &$called) {
$a->resolve('a');
$called = true;
});
$each = new EachPromise([$a], [
'concurrency' => function () { return 1; },
'fulfilled' => function () {},
'rejected' => function () {}
]);
$each->promise()->wait();
$this->assertNull($this->readAttribute($each, 'onFulfilled'));
$this->assertNull($this->readAttribute($each, 'onRejected'));
$this->assertNull($this->readAttribute($each, 'iterable'));
$this->assertNull($this->readAttribute($each, 'pending'));
$this->assertNull($this->readAttribute($each, 'concurrency'));
$this->assertTrue($called);
}
public function testCanBeCancelled()
{
$this->markTestIncomplete();
}
public function testFulfillsImmediatelyWhenGivenAnEmptyIterator()
{
$each = new EachPromise(new \ArrayIterator([]));
$result = $each->promise()->wait();
}
public function testDoesNotBlowStackWithFulfilledPromises()
{
$pending = [];
for ($i = 0; $i < 100; $i++) {
$pending[] = new FulfilledPromise($i);
}
$values = [];
$each = new EachPromise($pending, [
'fulfilled' => function ($value) use (&$values) {
$values[] = $value;
}
]);
$called = false;
$each->promise()->then(function () use (&$called) {
$called = true;
});
$this->assertFalse($called);
P\queue()->run();
$this->assertTrue($called);
$this->assertEquals(range(0, 99), $values);
}
public function testDoesNotBlowStackWithRejectedPromises()
{
$pending = [];
for ($i = 0; $i < 100; $i++) {
$pending[] = new RejectedPromise($i);
}
$values = [];
$each = new EachPromise($pending, [
'rejected' => function ($value) use (&$values) {
$values[] = $value;
}
]);
$called = false;
$each->promise()->then(
function () use (&$called) { $called = true; },
function () { $this->fail('Should not have rejected.'); }
);
$this->assertFalse($called);
P\queue()->run();
$this->assertTrue($called);
$this->assertEquals(range(0, 99), $values);
}
public function testReturnsPromiseForWhatever()
{
$called = [];
$arr = ['a', 'b'];
$each = new EachPromise($arr, [
'fulfilled' => function ($v) use (&$called) { $called[] = $v; }
]);
$p = $each->promise();
$this->assertNull($p->wait());
$this->assertEquals(['a', 'b'], $called);
}
public function testRejectsAggregateWhenNextThrows()
{
$iter = function () {
yield 'a';
throw new \Exception('Failure');
};
$each = new EachPromise($iter());
$p = $each->promise();
$e = null;
$received = null;
$p->then(null, function ($reason) use (&$e) { $e = $reason; });
P\queue()->run();
$this->assertInstanceOf('Exception', $e);
$this->assertEquals('Failure', $e->getMessage());
}
public function testDoesNotCallNextOnIteratorUntilNeededWhenWaiting()
{
$results = [];
$values = [10];
$remaining = 9;
$iter = function () use (&$values) {
while ($value = array_pop($values)) {
yield $value;
}
};
$each = new EachPromise($iter(), [
'concurrency' => 1,
'fulfilled' => function ($r) use (&$results, &$values, &$remaining) {
$results[] = $r;
if ($remaining > 0) {
$values[] = $remaining--;
}
}
]);
$each->promise()->wait();
$this->assertEquals(range(10, 1), $results);
}
public function testDoesNotCallNextOnIteratorUntilNeededWhenAsync()
{
$firstPromise = new Promise();
$pending = [$firstPromise];
$values = [$firstPromise];
$results = [];
$remaining = 9;
$iter = function () use (&$values) {
while ($value = array_pop($values)) {
yield $value;
}
};
$each = new EachPromise($iter(), [
'concurrency' => 1,
'fulfilled' => function ($r) use (&$results, &$values, &$remaining, &$pending) {
$results[] = $r;
if ($remaining-- > 0) {
$pending[] = $values[] = new Promise();
}
}
]);
$i = 0;
$each->promise();
while ($promise = array_pop($pending)) {
$promise->resolve($i++);
P\queue()->run();
}
$this->assertEquals(range(0, 9), $results);
}
private function createSelfResolvingPromise($value)
{
$p = new Promise(function () use (&$p, $value) {
$p->resolve($value);
});
return $p;
}
public function testMutexPreventsGeneratorRecursion()
{
$results = $promises = [];
for ($i = 0; $i < 20; $i++) {
$p = $this->createSelfResolvingPromise($i);
$pending[] = $p;
$promises[] = $p;
}
$iter = function () use (&$promises, &$pending) {
foreach ($promises as $promise) {
// Resolve a promises, which will trigger the then() function,
// which would cause the EachPromise to try to add more
// promises to the queue. Without a lock, this would trigger
// a "Cannot resume an already running generator" fatal error.
if ($p = array_pop($pending)) {
$p->wait();
}
yield $promise;
}
};
$each = new EachPromise($iter(), [
'concurrency' => 5,
'fulfilled' => function ($r) use (&$results, &$pending) {
$results[] = $r;
}
]);
$each->promise()->wait();
$this->assertCount(20, $results);
}
}

View File

@@ -1,108 +0,0 @@
<?php
namespace GuzzleHttp\Tests\Promise;
use GuzzleHttp\Promise\Promise;
use GuzzleHttp\Promise\FulfilledPromise;
/**
* @covers GuzzleHttp\Promise\FulfilledPromise
*/
class FulfilledPromiseTest extends \PHPUnit_Framework_TestCase
{
public function testReturnsValueWhenWaitedUpon()
{
$p = new FulfilledPromise('foo');
$this->assertEquals('fulfilled', $p->getState());
$this->assertEquals('foo', $p->wait(true));
}
public function testCannotCancel()
{
$p = new FulfilledPromise('foo');
$this->assertEquals('fulfilled', $p->getState());
$p->cancel();
$this->assertEquals('foo', $p->wait());
}
/**
* @expectedException \LogicException
* @exepctedExceptionMessage Cannot resolve a fulfilled promise
*/
public function testCannotResolve()
{
$p = new FulfilledPromise('foo');
$p->resolve('bar');
}
/**
* @expectedException \LogicException
* @exepctedExceptionMessage Cannot reject a fulfilled promise
*/
public function testCannotReject()
{
$p = new FulfilledPromise('foo');
$p->reject('bar');
}
public function testCanResolveWithSameValue()
{
$p = new FulfilledPromise('foo');
$p->resolve('foo');
}
/**
* @expectedException \InvalidArgumentException
*/
public function testCannotResolveWithPromise()
{
new FulfilledPromise(new Promise());
}
public function testReturnsSelfWhenNoOnFulfilled()
{
$p = new FulfilledPromise('a');
$this->assertSame($p, $p->then());
}
public function testAsynchronouslyInvokesOnFulfilled()
{
$p = new FulfilledPromise('a');
$r = null;
$f = function ($d) use (&$r) { $r = $d; };
$p2 = $p->then($f);
$this->assertNotSame($p, $p2);
$this->assertNull($r);
\GuzzleHttp\Promise\queue()->run();
$this->assertEquals('a', $r);
}
public function testReturnsNewRejectedWhenOnFulfilledFails()
{
$p = new FulfilledPromise('a');
$f = function () { throw new \Exception('b'); };
$p2 = $p->then($f);
$this->assertNotSame($p, $p2);
try {
$p2->wait();
$this->fail();
} catch (\Exception $e) {
$this->assertEquals('b', $e->getMessage());
}
}
public function testOtherwiseIsSugarForRejections()
{
$c = null;
$p = new FulfilledPromise('foo');
$p->otherwise(function ($v) use (&$c) { $c = $v; });
$this->assertNull($c);
}
public function testDoesNotTryToFulfillTwiceDuringTrampoline()
{
$fp = new FulfilledPromise('a');
$t1 = $fp->then(function ($v) { return $v . ' b'; });
$t1->resolve('why!');
$this->assertEquals('why!', $t1->wait());
}
}

View File

@@ -1,50 +0,0 @@
<?php
namespace GuzzleHttp\Promise\Tests;
use GuzzleHttp\Promise\Promise;
use GuzzleHttp\Promise\PromiseInterface;
class NotPromiseInstance extends Thennable implements PromiseInterface
{
private $nextPromise = null;
public function __construct()
{
$this->nextPromise = new Promise();
}
public function then(callable $res = null, callable $rej = null)
{
return $this->nextPromise->then($res, $rej);
}
public function otherwise(callable $onRejected)
{
return $this->then($onRejected);
}
public function resolve($value)
{
$this->nextPromise->resolve($value);
}
public function reject($reason)
{
$this->nextPromise->reject($reason);
}
public function wait($unwrap = true, $defaultResolution = null)
{
}
public function cancel()
{
}
public function getState()
{
return $this->nextPromise->getState();
}
}

View File

@@ -1,591 +0,0 @@
<?php
namespace GuzzleHttp\Promise\Tests;
use GuzzleHttp\Promise\CancellationException;
use GuzzleHttp\Promise as P;
use GuzzleHttp\Promise\Promise;
use GuzzleHttp\Promise\RejectedPromise;
use GuzzleHttp\Promise\RejectionException;
/**
* @covers GuzzleHttp\Promise\Promise
*/
class PromiseTest extends \PHPUnit_Framework_TestCase
{
/**
* @expectedException \LogicException
* @expectedExceptionMessage The promise is already fulfilled
*/
public function testCannotResolveNonPendingPromise()
{
$p = new Promise();
$p->resolve('foo');
$p->resolve('bar');
$this->assertEquals('foo', $p->wait());
}
public function testCanResolveWithSameValue()
{
$p = new Promise();
$p->resolve('foo');
$p->resolve('foo');
}
/**
* @expectedException \LogicException
* @expectedExceptionMessage Cannot change a fulfilled promise to rejected
*/
public function testCannotRejectNonPendingPromise()
{
$p = new Promise();
$p->resolve('foo');
$p->reject('bar');
$this->assertEquals('foo', $p->wait());
}
public function testCanRejectWithSameValue()
{
$p = new Promise();
$p->reject('foo');
$p->reject('foo');
}
/**
* @expectedException \LogicException
* @expectedExceptionMessage Cannot change a fulfilled promise to rejected
*/
public function testCannotRejectResolveWithSameValue()
{
$p = new Promise();
$p->resolve('foo');
$p->reject('foo');
}
public function testInvokesWaitFunction()
{
$p = new Promise(function () use (&$p) { $p->resolve('10'); });
$this->assertEquals('10', $p->wait());
}
/**
* @expectedException \GuzzleHttp\Promise\RejectionException
*/
public function testRejectsAndThrowsWhenWaitFailsToResolve()
{
$p = new Promise(function () {});
$p->wait();
}
/**
* @expectedException \GuzzleHttp\Promise\RejectionException
* @expectedExceptionMessage The promise was rejected with reason: foo
*/
public function testThrowsWhenUnwrapIsRejectedWithNonException()
{
$p = new Promise(function () use (&$p) { $p->reject('foo'); });
$p->wait();
}
/**
* @expectedException \UnexpectedValueException
* @expectedExceptionMessage foo
*/
public function testThrowsWhenUnwrapIsRejectedWithException()
{
$e = new \UnexpectedValueException('foo');
$p = new Promise(function () use (&$p, $e) { $p->reject($e); });
$p->wait();
}
public function testDoesNotUnwrapExceptionsWhenDisabled()
{
$p = new Promise(function () use (&$p) { $p->reject('foo'); });
$this->assertEquals('pending', $p->getState());
$p->wait(false);
$this->assertEquals('rejected', $p->getState());
}
public function testRejectsSelfWhenWaitThrows()
{
$e = new \UnexpectedValueException('foo');
$p = new Promise(function () use ($e) { throw $e; });
try {
$p->wait();
$this->fail();
} catch (\UnexpectedValueException $e) {
$this->assertEquals('rejected', $p->getState());
}
}
public function testWaitsOnNestedPromises()
{
$p = new Promise(function () use (&$p) { $p->resolve('_'); });
$p2 = new Promise(function () use (&$p2) { $p2->resolve('foo'); });
$p3 = $p->then(function () use ($p2) { return $p2; });
$this->assertSame('foo', $p3->wait());
}
/**
* @expectedException \GuzzleHttp\Promise\RejectionException
*/
public function testThrowsWhenWaitingOnPromiseWithNoWaitFunction()
{
$p = new Promise();
$p->wait();
}
public function testThrowsWaitExceptionAfterPromiseIsResolved()
{
$p = new Promise(function () use (&$p) {
$p->reject('Foo!');
throw new \Exception('Bar?');
});
try {
$p->wait();
$this->fail();
} catch (\Exception $e) {
$this->assertEquals('Bar?', $e->getMessage());
}
}
public function testGetsActualWaitValueFromThen()
{
$p = new Promise(function () use (&$p) { $p->reject('Foo!'); });
$p2 = $p->then(null, function ($reason) {
return new RejectedPromise([$reason]);
});
try {
$p2->wait();
$this->fail('Should have thrown');
} catch (RejectionException $e) {
$this->assertEquals(['Foo!'], $e->getReason());
}
}
public function testWaitBehaviorIsBasedOnLastPromiseInChain()
{
$p3 = new Promise(function () use (&$p3) { $p3->resolve('Whoop'); });
$p2 = new Promise(function () use (&$p2, $p3) { $p2->reject($p3); });
$p = new Promise(function () use (&$p, $p2) { $p->reject($p2); });
$this->assertEquals('Whoop', $p->wait());
}
public function testWaitsOnAPromiseChainEvenWhenNotUnwrapped()
{
$p2 = new Promise(function () use (&$p2) {
$p2->reject('Fail');
});
$p = new Promise(function () use ($p2, &$p) {
$p->resolve($p2);
});
$p->wait(false);
$this->assertSame(Promise::REJECTED, $p2->getState());
}
public function testCannotCancelNonPending()
{
$p = new Promise();
$p->resolve('foo');
$p->cancel();
$this->assertEquals('fulfilled', $p->getState());
}
/**
* @expectedException \GuzzleHttp\Promise\CancellationException
*/
public function testCancelsPromiseWhenNoCancelFunction()
{
$p = new Promise();
$p->cancel();
$this->assertEquals('rejected', $p->getState());
$p->wait();
}
public function testCancelsPromiseWithCancelFunction()
{
$called = false;
$p = new Promise(null, function () use (&$called) { $called = true; });
$p->cancel();
$this->assertEquals('rejected', $p->getState());
$this->assertTrue($called);
}
public function testCancelsUppermostPendingPromise()
{
$called = false;
$p1 = new Promise(null, function () use (&$called) { $called = true; });
$p2 = $p1->then(function () {});
$p3 = $p2->then(function () {});
$p4 = $p3->then(function () {});
$p3->cancel();
$this->assertEquals('rejected', $p1->getState());
$this->assertEquals('rejected', $p2->getState());
$this->assertEquals('rejected', $p3->getState());
$this->assertEquals('pending', $p4->getState());
$this->assertTrue($called);
try {
$p3->wait();
$this->fail();
} catch (CancellationException $e) {
$this->assertContains('cancelled', $e->getMessage());
}
try {
$p4->wait();
$this->fail();
} catch (CancellationException $e) {
$this->assertContains('cancelled', $e->getMessage());
}
$this->assertEquals('rejected', $p4->getState());
}
public function testCancelsChildPromises()
{
$called1 = $called2 = $called3 = false;
$p1 = new Promise(null, function () use (&$called1) { $called1 = true; });
$p2 = new Promise(null, function () use (&$called2) { $called2 = true; });
$p3 = new Promise(null, function () use (&$called3) { $called3 = true; });
$p4 = $p2->then(function () use ($p3) { return $p3; });
$p5 = $p4->then(function () { $this->fail(); });
$p4->cancel();
$this->assertEquals('pending', $p1->getState());
$this->assertEquals('rejected', $p2->getState());
$this->assertEquals('rejected', $p4->getState());
$this->assertEquals('pending', $p5->getState());
$this->assertFalse($called1);
$this->assertTrue($called2);
$this->assertFalse($called3);
}
public function testRejectsPromiseWhenCancelFails()
{
$called = false;
$p = new Promise(null, function () use (&$called) {
$called = true;
throw new \Exception('e');
});
$p->cancel();
$this->assertEquals('rejected', $p->getState());
$this->assertTrue($called);
try {
$p->wait();
$this->fail();
} catch (\Exception $e) {
$this->assertEquals('e', $e->getMessage());
}
}
public function testCreatesPromiseWhenFulfilledAfterThen()
{
$p = new Promise();
$carry = null;
$p2 = $p->then(function ($v) use (&$carry) { $carry = $v; });
$this->assertNotSame($p, $p2);
$p->resolve('foo');
P\queue()->run();
$this->assertEquals('foo', $carry);
}
public function testCreatesPromiseWhenFulfilledBeforeThen()
{
$p = new Promise();
$p->resolve('foo');
$carry = null;
$p2 = $p->then(function ($v) use (&$carry) { $carry = $v; });
$this->assertNotSame($p, $p2);
$this->assertNull($carry);
\GuzzleHttp\Promise\queue()->run();
$this->assertEquals('foo', $carry);
}
public function testCreatesPromiseWhenFulfilledWithNoCallback()
{
$p = new Promise();
$p->resolve('foo');
$p2 = $p->then();
$this->assertNotSame($p, $p2);
$this->assertInstanceOf('GuzzleHttp\Promise\FulfilledPromise', $p2);
}
public function testCreatesPromiseWhenRejectedAfterThen()
{
$p = new Promise();
$carry = null;
$p2 = $p->then(null, function ($v) use (&$carry) { $carry = $v; });
$this->assertNotSame($p, $p2);
$p->reject('foo');
P\queue()->run();
$this->assertEquals('foo', $carry);
}
public function testCreatesPromiseWhenRejectedBeforeThen()
{
$p = new Promise();
$p->reject('foo');
$carry = null;
$p2 = $p->then(null, function ($v) use (&$carry) { $carry = $v; });
$this->assertNotSame($p, $p2);
$this->assertNull($carry);
P\queue()->run();
$this->assertEquals('foo', $carry);
}
public function testCreatesPromiseWhenRejectedWithNoCallback()
{
$p = new Promise();
$p->reject('foo');
$p2 = $p->then();
$this->assertNotSame($p, $p2);
$this->assertInstanceOf('GuzzleHttp\Promise\RejectedPromise', $p2);
}
public function testInvokesWaitFnsForThens()
{
$p = new Promise(function () use (&$p) { $p->resolve('a'); });
$p2 = $p
->then(function ($v) { return $v . '-1-'; })
->then(function ($v) { return $v . '2'; });
$this->assertEquals('a-1-2', $p2->wait());
}
public function testStacksThenWaitFunctions()
{
$p1 = new Promise(function () use (&$p1) { $p1->resolve('a'); });
$p2 = new Promise(function () use (&$p2) { $p2->resolve('b'); });
$p3 = new Promise(function () use (&$p3) { $p3->resolve('c'); });
$p4 = $p1
->then(function () use ($p2) { return $p2; })
->then(function () use ($p3) { return $p3; });
$this->assertEquals('c', $p4->wait());
}
public function testForwardsFulfilledDownChainBetweenGaps()
{
$p = new Promise();
$r = $r2 = null;
$p->then(null, null)
->then(function ($v) use (&$r) { $r = $v; return $v . '2'; })
->then(function ($v) use (&$r2) { $r2 = $v; });
$p->resolve('foo');
P\queue()->run();
$this->assertEquals('foo', $r);
$this->assertEquals('foo2', $r2);
}
public function testForwardsRejectedPromisesDownChainBetweenGaps()
{
$p = new Promise();
$r = $r2 = null;
$p->then(null, null)
->then(null, function ($v) use (&$r) { $r = $v; return $v . '2'; })
->then(function ($v) use (&$r2) { $r2 = $v; });
$p->reject('foo');
P\queue()->run();
$this->assertEquals('foo', $r);
$this->assertEquals('foo2', $r2);
}
public function testForwardsThrownPromisesDownChainBetweenGaps()
{
$e = new \Exception();
$p = new Promise();
$r = $r2 = null;
$p->then(null, null)
->then(null, function ($v) use (&$r, $e) {
$r = $v;
throw $e;
})
->then(
null,
function ($v) use (&$r2) { $r2 = $v; }
);
$p->reject('foo');
P\queue()->run();
$this->assertEquals('foo', $r);
$this->assertSame($e, $r2);
}
public function testForwardsReturnedRejectedPromisesDownChainBetweenGaps()
{
$p = new Promise();
$rejected = new RejectedPromise('bar');
$r = $r2 = null;
$p->then(null, null)
->then(null, function ($v) use (&$r, $rejected) {
$r = $v;
return $rejected;
})
->then(
null,
function ($v) use (&$r2) { $r2 = $v; }
);
$p->reject('foo');
P\queue()->run();
$this->assertEquals('foo', $r);
$this->assertEquals('bar', $r2);
try {
$p->wait();
} catch (RejectionException $e) {
$this->assertEquals('foo', $e->getReason());
}
}
public function testForwardsHandlersToNextPromise()
{
$p = new Promise();
$p2 = new Promise();
$resolved = null;
$p
->then(function ($v) use ($p2) { return $p2; })
->then(function ($value) use (&$resolved) { $resolved = $value; });
$p->resolve('a');
$p2->resolve('b');
P\queue()->run();
$this->assertEquals('b', $resolved);
}
public function testRemovesReferenceFromChildWhenParentWaitedUpon()
{
$r = null;
$p = new Promise(function () use (&$p) { $p->resolve('a'); });
$p2 = new Promise(function () use (&$p2) { $p2->resolve('b'); });
$pb = $p->then(
function ($v) use ($p2, &$r) {
$r = $v;
return $p2;
})
->then(function ($v) { return $v . '.'; });
$this->assertEquals('a', $p->wait());
$this->assertEquals('b', $p2->wait());
$this->assertEquals('b.', $pb->wait());
$this->assertEquals('a', $r);
}
public function testForwardsHandlersWhenFulfilledPromiseIsReturned()
{
$res = [];
$p = new Promise();
$p2 = new Promise();
$p2->resolve('foo');
$p2->then(function ($v) use (&$res) { $res[] = 'A:' . $v; });
// $res is A:foo
$p
->then(function () use ($p2, &$res) { $res[] = 'B'; return $p2; })
->then(function ($v) use (&$res) { $res[] = 'C:' . $v; });
$p->resolve('a');
$p->then(function ($v) use (&$res) { $res[] = 'D:' . $v; });
P\queue()->run();
$this->assertEquals(['A:foo', 'B', 'D:a', 'C:foo'], $res);
}
public function testForwardsHandlersWhenRejectedPromiseIsReturned()
{
$res = [];
$p = new Promise();
$p2 = new Promise();
$p2->reject('foo');
$p2->then(null, function ($v) use (&$res) { $res[] = 'A:' . $v; });
$p->then(null, function () use ($p2, &$res) { $res[] = 'B'; return $p2; })
->then(null, function ($v) use (&$res) { $res[] = 'C:' . $v; });
$p->reject('a');
$p->then(null, function ($v) use (&$res) { $res[] = 'D:' . $v; });
P\queue()->run();
$this->assertEquals(['A:foo', 'B', 'D:a', 'C:foo'], $res);
}
public function testDoesNotForwardRejectedPromise()
{
$res = [];
$p = new Promise();
$p2 = new Promise();
$p2->cancel();
$p2->then(function ($v) use (&$res) { $res[] = "B:$v"; return $v; });
$p->then(function ($v) use ($p2, &$res) { $res[] = "B:$v"; return $p2; })
->then(function ($v) use (&$res) { $res[] = 'C:' . $v; });
$p->resolve('a');
$p->then(function ($v) use (&$res) { $res[] = 'D:' . $v; });
P\queue()->run();
$this->assertEquals(['B:a', 'D:a'], $res);
}
public function testRecursivelyForwardsWhenOnlyThennable()
{
$res = [];
$p = new Promise();
$p2 = new Thennable();
$p2->resolve('foo');
$p2->then(function ($v) use (&$res) { $res[] = 'A:' . $v; });
$p->then(function () use ($p2, &$res) { $res[] = 'B'; return $p2; })
->then(function ($v) use (&$res) { $res[] = 'C:' . $v; });
$p->resolve('a');
$p->then(function ($v) use (&$res) { $res[] = 'D:' . $v; });
P\queue()->run();
$this->assertEquals(['A:foo', 'B', 'D:a', 'C:foo'], $res);
}
public function testRecursivelyForwardsWhenNotInstanceOfPromise()
{
$res = [];
$p = new Promise();
$p2 = new NotPromiseInstance();
$p2->then(function ($v) use (&$res) { $res[] = 'A:' . $v; });
$p->then(function () use ($p2, &$res) { $res[] = 'B'; return $p2; })
->then(function ($v) use (&$res) { $res[] = 'C:' . $v; });
$p->resolve('a');
$p->then(function ($v) use (&$res) { $res[] = 'D:' . $v; });
P\queue()->run();
$this->assertEquals(['B', 'D:a'], $res);
$p2->resolve('foo');
P\queue()->run();
$this->assertEquals(['B', 'D:a', 'A:foo', 'C:foo'], $res);
}
/**
* @expectedException \LogicException
* @expectedExceptionMessage Cannot fulfill or reject a promise with itself
*/
public function testCannotResolveWithSelf()
{
$p = new Promise();
$p->resolve($p);
}
/**
* @expectedException \LogicException
* @expectedExceptionMessage Cannot fulfill or reject a promise with itself
*/
public function testCannotRejectWithSelf()
{
$p = new Promise();
$p->reject($p);
}
public function testDoesNotBlowStackWhenWaitingOnNestedThens()
{
$inner = new Promise(function () use (&$inner) { $inner->resolve(0); });
$prev = $inner;
for ($i = 1; $i < 100; $i++) {
$prev = $prev->then(function ($i) { return $i + 1; });
}
$parent = new Promise(function () use (&$parent, $prev) {
$parent->resolve($prev);
});
$this->assertEquals(99, $parent->wait());
}
public function testOtherwiseIsSugarForRejections()
{
$p = new Promise();
$p->reject('foo');
$p->otherwise(function ($v) use (&$c) { $c = $v; });
P\queue()->run();
$this->assertEquals($c, 'foo');
}
}

View File

@@ -1,143 +0,0 @@
<?php
namespace GuzzleHttp\Promise\Tests;
use GuzzleHttp\Promise\Promise;
use GuzzleHttp\Promise\RejectedPromise;
/**
* @covers GuzzleHttp\Promise\RejectedPromise
*/
class RejectedPromiseTest extends \PHPUnit_Framework_TestCase
{
public function testThrowsReasonWhenWaitedUpon()
{
$p = new RejectedPromise('foo');
$this->assertEquals('rejected', $p->getState());
try {
$p->wait(true);
$this->fail();
} catch (\Exception $e) {
$this->assertEquals('rejected', $p->getState());
$this->assertContains('foo', $e->getMessage());
}
}
public function testCannotCancel()
{
$p = new RejectedPromise('foo');
$p->cancel();
$this->assertEquals('rejected', $p->getState());
}
/**
* @expectedException \LogicException
* @exepctedExceptionMessage Cannot resolve a rejected promise
*/
public function testCannotResolve()
{
$p = new RejectedPromise('foo');
$p->resolve('bar');
}
/**
* @expectedException \LogicException
* @exepctedExceptionMessage Cannot reject a rejected promise
*/
public function testCannotReject()
{
$p = new RejectedPromise('foo');
$p->reject('bar');
}
public function testCanRejectWithSameValue()
{
$p = new RejectedPromise('foo');
$p->reject('foo');
}
public function testThrowsSpecificException()
{
$e = new \Exception();
$p = new RejectedPromise($e);
try {
$p->wait(true);
$this->fail();
} catch (\Exception $e2) {
$this->assertSame($e, $e2);
}
}
/**
* @expectedException \InvalidArgumentException
*/
public function testCannotResolveWithPromise()
{
new RejectedPromise(new Promise());
}
public function testReturnsSelfWhenNoOnReject()
{
$p = new RejectedPromise('a');
$this->assertSame($p, $p->then());
}
public function testInvokesOnRejectedAsynchronously()
{
$p = new RejectedPromise('a');
$r = null;
$f = function ($reason) use (&$r) { $r = $reason; };
$p->then(null, $f);
$this->assertNull($r);
\GuzzleHttp\Promise\queue()->run();
$this->assertEquals('a', $r);
}
public function testReturnsNewRejectedWhenOnRejectedFails()
{
$p = new RejectedPromise('a');
$f = function () { throw new \Exception('b'); };
$p2 = $p->then(null, $f);
$this->assertNotSame($p, $p2);
try {
$p2->wait();
$this->fail();
} catch (\Exception $e) {
$this->assertEquals('b', $e->getMessage());
}
}
public function testWaitingIsNoOp()
{
$p = new RejectedPromise('a');
$p->wait(false);
}
public function testOtherwiseIsSugarForRejections()
{
$p = new RejectedPromise('foo');
$p->otherwise(function ($v) use (&$c) { $c = $v; });
\GuzzleHttp\Promise\queue()->run();
$this->assertSame('foo', $c);
}
public function testCanResolveThenWithSuccess()
{
$actual = null;
$p = new RejectedPromise('foo');
$p->otherwise(function ($v) {
return $v . ' bar';
})->then(function ($v) use (&$actual) {
$actual = $v;
});
\GuzzleHttp\Promise\queue()->run();
$this->assertEquals('foo bar', $actual);
}
public function testDoesNotTryToRejectTwiceDuringTrampoline()
{
$fp = new RejectedPromise('a');
$t1 = $fp->then(null, function ($v) { return $v . ' b'; });
$t1->resolve('why!');
$this->assertEquals('why!', $t1->wait());
}
}

View File

@@ -1,47 +0,0 @@
<?php
namespace GuzzleHttp\Promise\Tests;
use GuzzleHttp\Promise\RejectionException;
class Thing1
{
public function __construct($message)
{
$this->message = $message;
}
public function __toString()
{
return $this->message;
}
}
class Thing2 implements \JsonSerializable
{
public function jsonSerialize()
{
return '{}';
}
}
/**
* @covers GuzzleHttp\Promise\RejectionException
*/
class RejectionExceptionTest extends \PHPUnit_Framework_TestCase
{
public function testCanGetReasonFromException()
{
$thing = new Thing1('foo');
$e = new RejectionException($thing);
$this->assertSame($thing, $e->getReason());
$this->assertEquals('The promise was rejected with reason: foo', $e->getMessage());
}
public function testCanGetReasonMessageFromJson()
{
$reason = new Thing2();
$e = new RejectionException($reason);
$this->assertContains("{}", $e->getMessage());
}
}

View File

@@ -1,31 +0,0 @@
<?php
namespace GuzzleHttp\Promise\Test;
use GuzzleHttp\Promise\TaskQueue;
class TaskQueueTest extends \PHPUnit_Framework_TestCase
{
public function testKnowsIfEmpty()
{
$tq = new TaskQueue(false);
$this->assertTrue($tq->isEmpty());
}
public function testKnowsIfFull()
{
$tq = new TaskQueue(false);
$tq->add(function () {});
$this->assertFalse($tq->isEmpty());
}
public function testExecutesTasksInOrder()
{
$tq = new TaskQueue(false);
$called = [];
$tq->add(function () use (&$called) { $called[] = 'a'; });
$tq->add(function () use (&$called) { $called[] = 'b'; });
$tq->add(function () use (&$called) { $called[] = 'c'; });
$tq->run();
$this->assertEquals(['a', 'b', 'c'], $called);
}
}

View File

@@ -1,24 +0,0 @@
<?php
namespace GuzzleHttp\Promise\Tests;
use GuzzleHttp\Promise\Promise;
class Thennable
{
private $nextPromise = null;
public function __construct()
{
$this->nextPromise = new Promise();
}
public function then(callable $res = null, callable $rej = null)
{
return $this->nextPromise->then($res, $rej);
}
public function resolve($value)
{
$this->nextPromise->resolve($value);
}
}

View File

@@ -1,4 +0,0 @@
<?php
require __DIR__ . '/../vendor/autoload.php';
require __DIR__ . '/Thennable.php';
require __DIR__ . '/NotPromiseInstance.php';

View File

@@ -1,694 +0,0 @@
<?php
namespace GuzzleHttp\Promise\Tests;
use GuzzleHttp\Promise as P;
use GuzzleHttp\Promise\FulfilledPromise;
use GuzzleHttp\Promise\Promise;
use GuzzleHttp\Promise\RejectedPromise;
class FunctionsTest extends \PHPUnit_Framework_TestCase
{
public function testCreatesPromiseForValue()
{
$p = \GuzzleHttp\Promise\promise_for('foo');
$this->assertInstanceOf('GuzzleHttp\Promise\FulfilledPromise', $p);
}
public function testReturnsPromiseForPromise()
{
$p = new Promise();
$this->assertSame($p, \GuzzleHttp\Promise\promise_for($p));
}
public function testReturnsPromiseForThennable()
{
$p = new Thennable();
$wrapped = \GuzzleHttp\Promise\promise_for($p);
$this->assertNotSame($p, $wrapped);
$this->assertInstanceOf('GuzzleHttp\Promise\PromiseInterface', $wrapped);
$p->resolve('foo');
P\queue()->run();
$this->assertEquals('foo', $wrapped->wait());
}
public function testReturnsRejection()
{
$p = \GuzzleHttp\Promise\rejection_for('fail');
$this->assertInstanceOf('GuzzleHttp\Promise\RejectedPromise', $p);
$this->assertEquals('fail', $this->readAttribute($p, 'reason'));
}
public function testReturnsPromisesAsIsInRejectionFor()
{
$a = new Promise();
$b = \GuzzleHttp\Promise\rejection_for($a);
$this->assertSame($a, $b);
}
public function testWaitsOnAllPromisesIntoArray()
{
$e = new \Exception();
$a = new Promise(function () use (&$a) { $a->resolve('a'); });
$b = new Promise(function () use (&$b) { $b->reject('b'); });
$c = new Promise(function () use (&$c, $e) { $c->reject($e); });
$results = \GuzzleHttp\Promise\inspect_all([$a, $b, $c]);
$this->assertEquals([
['state' => 'fulfilled', 'value' => 'a'],
['state' => 'rejected', 'reason' => 'b'],
['state' => 'rejected', 'reason' => $e]
], $results);
}
/**
* @expectedException \GuzzleHttp\Promise\RejectionException
*/
public function testUnwrapsPromisesWithNoDefaultAndFailure()
{
$promises = [new FulfilledPromise('a'), new Promise()];
\GuzzleHttp\Promise\unwrap($promises);
}
public function testUnwrapsPromisesWithNoDefault()
{
$promises = [new FulfilledPromise('a')];
$this->assertEquals(['a'], \GuzzleHttp\Promise\unwrap($promises));
}
public function testUnwrapsPromisesWithKeys()
{
$promises = [
'foo' => new FulfilledPromise('a'),
'bar' => new FulfilledPromise('b'),
];
$this->assertEquals([
'foo' => 'a',
'bar' => 'b'
], \GuzzleHttp\Promise\unwrap($promises));
}
public function testAllAggregatesSortedArray()
{
$a = new Promise();
$b = new Promise();
$c = new Promise();
$d = \GuzzleHttp\Promise\all([$a, $b, $c]);
$b->resolve('b');
$a->resolve('a');
$c->resolve('c');
$d->then(
function ($value) use (&$result) { $result = $value; },
function ($reason) use (&$result) { $result = $reason; }
);
P\queue()->run();
$this->assertEquals(['a', 'b', 'c'], $result);
}
public function testAllThrowsWhenAnyRejected()
{
$a = new Promise();
$b = new Promise();
$c = new Promise();
$d = \GuzzleHttp\Promise\all([$a, $b, $c]);
$b->resolve('b');
$a->reject('fail');
$c->resolve('c');
$d->then(
function ($value) use (&$result) { $result = $value; },
function ($reason) use (&$result) { $result = $reason; }
);
P\queue()->run();
$this->assertEquals('fail', $result);
}
public function testSomeAggregatesSortedArrayWithMax()
{
$a = new Promise();
$b = new Promise();
$c = new Promise();
$d = \GuzzleHttp\Promise\some(2, [$a, $b, $c]);
$b->resolve('b');
$c->resolve('c');
$a->resolve('a');
$d->then(function ($value) use (&$result) { $result = $value; });
P\queue()->run();
$this->assertEquals(['b', 'c'], $result);
}
public function testSomeRejectsWhenTooManyRejections()
{
$a = new Promise();
$b = new Promise();
$d = \GuzzleHttp\Promise\some(2, [$a, $b]);
$a->reject('bad');
$b->resolve('good');
P\queue()->run();
$this->assertEquals($a::REJECTED, $d->getState());
$d->then(null, function ($reason) use (&$called) {
$called = $reason;
});
P\queue()->run();
$this->assertInstanceOf('GuzzleHttp\Promise\AggregateException', $called);
$this->assertContains('bad', $called->getReason());
}
public function testCanWaitUntilSomeCountIsSatisfied()
{
$a = new Promise(function () use (&$a) { $a->resolve('a'); });
$b = new Promise(function () use (&$b) { $b->resolve('b'); });
$c = new Promise(function () use (&$c) { $c->resolve('c'); });
$d = \GuzzleHttp\Promise\some(2, [$a, $b, $c]);
$this->assertEquals(['a', 'b'], $d->wait());
}
/**
* @expectedException \GuzzleHttp\Promise\AggregateException
* @expectedExceptionMessage Not enough promises to fulfill count
*/
public function testThrowsIfImpossibleToWaitForSomeCount()
{
$a = new Promise(function () use (&$a) { $a->resolve('a'); });
$d = \GuzzleHttp\Promise\some(2, [$a]);
$d->wait();
}
/**
* @expectedException \GuzzleHttp\Promise\AggregateException
* @expectedExceptionMessage Not enough promises to fulfill count
*/
public function testThrowsIfResolvedWithoutCountTotalResults()
{
$a = new Promise();
$b = new Promise();
$d = \GuzzleHttp\Promise\some(3, [$a, $b]);
$a->resolve('a');
$b->resolve('b');
$d->wait();
}
public function testAnyReturnsFirstMatch()
{
$a = new Promise();
$b = new Promise();
$c = \GuzzleHttp\Promise\any([$a, $b]);
$b->resolve('b');
$a->resolve('a');
//P\queue()->run();
//$this->assertEquals('fulfilled', $c->getState());
$c->then(function ($value) use (&$result) { $result = $value; });
P\queue()->run();
$this->assertEquals('b', $result);
}
public function testSettleFulfillsWithFulfilledAndRejected()
{
$a = new Promise();
$b = new Promise();
$c = new Promise();
$d = \GuzzleHttp\Promise\settle([$a, $b, $c]);
$b->resolve('b');
$c->resolve('c');
$a->reject('a');
P\queue()->run();
$this->assertEquals('fulfilled', $d->getState());
$d->then(function ($value) use (&$result) { $result = $value; });
P\queue()->run();
$this->assertEquals([
['state' => 'rejected', 'reason' => 'a'],
['state' => 'fulfilled', 'value' => 'b'],
['state' => 'fulfilled', 'value' => 'c']
], $result);
}
public function testCanInspectFulfilledPromise()
{
$p = new FulfilledPromise('foo');
$this->assertEquals([
'state' => 'fulfilled',
'value' => 'foo'
], \GuzzleHttp\Promise\inspect($p));
}
public function testCanInspectRejectedPromise()
{
$p = new RejectedPromise('foo');
$this->assertEquals([
'state' => 'rejected',
'reason' => 'foo'
], \GuzzleHttp\Promise\inspect($p));
}
public function testCanInspectRejectedPromiseWithNormalException()
{
$e = new \Exception('foo');
$p = new RejectedPromise($e);
$this->assertEquals([
'state' => 'rejected',
'reason' => $e
], \GuzzleHttp\Promise\inspect($p));
}
public function testCallsEachLimit()
{
$p = new Promise();
$aggregate = \GuzzleHttp\Promise\each_limit($p, 2);
$p->resolve('a');
P\queue()->run();
$this->assertEquals($p::FULFILLED, $aggregate->getState());
}
public function testEachLimitAllRejectsOnFailure()
{
$p = [new FulfilledPromise('a'), new RejectedPromise('b')];
$aggregate = \GuzzleHttp\Promise\each_limit_all($p, 2);
P\queue()->run();
$this->assertEquals(P\PromiseInterface::REJECTED, $aggregate->getState());
$result = \GuzzleHttp\Promise\inspect($aggregate);
$this->assertEquals('b', $result['reason']);
}
public function testIterForReturnsIterator()
{
$iter = new \ArrayIterator();
$this->assertSame($iter, \GuzzleHttp\Promise\iter_for($iter));
}
public function testKnowsIfFulfilled()
{
$p = new FulfilledPromise(null);
$this->assertTrue(P\is_fulfilled($p));
$this->assertFalse(P\is_rejected($p));
}
public function testKnowsIfRejected()
{
$p = new RejectedPromise(null);
$this->assertTrue(P\is_rejected($p));
$this->assertFalse(P\is_fulfilled($p));
}
public function testKnowsIfSettled()
{
$p = new RejectedPromise(null);
$this->assertTrue(P\is_settled($p));
$p = new Promise();
$this->assertFalse(P\is_settled($p));
}
public function testReturnsTrampoline()
{
$this->assertInstanceOf('GuzzleHttp\Promise\TaskQueue', P\queue());
$this->assertSame(P\queue(), P\queue());
}
public function testCanScheduleThunk()
{
$tramp = P\queue();
$promise = P\task(function () { return 'Hi!'; });
$c = null;
$promise->then(function ($v) use (&$c) { $c = $v; });
$this->assertNull($c);
$tramp->run();
$this->assertEquals('Hi!', $c);
}
public function testCanScheduleThunkWithRejection()
{
$tramp = P\queue();
$promise = P\task(function () { throw new \Exception('Hi!'); });
$c = null;
$promise->otherwise(function ($v) use (&$c) { $c = $v; });
$this->assertNull($c);
$tramp->run();
$this->assertEquals('Hi!', $c->getMessage());
}
public function testCanScheduleThunkWithWait()
{
$tramp = P\queue();
$promise = P\task(function () { return 'a'; });
$this->assertEquals('a', $promise->wait());
$tramp->run();
}
public function testYieldsFromCoroutine()
{
$promise = P\coroutine(function () {
$value = (yield new P\FulfilledPromise('a'));
yield $value . 'b';
});
$promise->then(function ($value) use (&$result) { $result = $value; });
P\queue()->run();
$this->assertEquals('ab', $result);
}
public function testCanCatchExceptionsInCoroutine()
{
$promise = P\coroutine(function () {
try {
yield new P\RejectedPromise('a');
$this->fail('Should have thrown into the coroutine!');
} catch (P\RejectionException $e) {
$value = (yield new P\FulfilledPromise($e->getReason()));
yield $value . 'b';
}
});
$promise->then(function ($value) use (&$result) { $result = $value; });
P\queue()->run();
$this->assertEquals(P\PromiseInterface::FULFILLED, $promise->getState());
$this->assertEquals('ab', $result);
}
public function testRejectsParentExceptionWhenException()
{
$promise = P\coroutine(function () {
yield new P\FulfilledPromise(0);
throw new \Exception('a');
});
$promise->then(
function () { $this->fail(); },
function ($reason) use (&$result) { $result = $reason; }
);
P\queue()->run();
$this->assertInstanceOf('Exception', $result);
$this->assertEquals('a', $result->getMessage());
}
public function testCanRejectFromRejectionCallback()
{
$promise = P\coroutine(function () {
yield new P\FulfilledPromise(0);
yield new P\RejectedPromise('no!');
});
$promise->then(
function () { $this->fail(); },
function ($reason) use (&$result) { $result = $reason; }
);
P\queue()->run();
$this->assertInstanceOf('GuzzleHttp\Promise\RejectionException', $result);
$this->assertEquals('no!', $result->getReason());
}
public function testCanAsyncReject()
{
$rej = new P\Promise();
$promise = P\coroutine(function () use ($rej) {
yield new P\FulfilledPromise(0);
yield $rej;
});
$promise->then(
function () { $this->fail(); },
function ($reason) use (&$result) { $result = $reason; }
);
$rej->reject('no!');
P\queue()->run();
$this->assertInstanceOf('GuzzleHttp\Promise\RejectionException', $result);
$this->assertEquals('no!', $result->getReason());
}
public function testCanCatchAndThrowOtherException()
{
$promise = P\coroutine(function () {
try {
yield new P\RejectedPromise('a');
$this->fail('Should have thrown into the coroutine!');
} catch (P\RejectionException $e) {
throw new \Exception('foo');
}
});
$promise->otherwise(function ($value) use (&$result) { $result = $value; });
P\queue()->run();
$this->assertEquals(P\PromiseInterface::REJECTED, $promise->getState());
$this->assertContains('foo', $result->getMessage());
}
public function testCanCatchAndYieldOtherException()
{
$promise = P\coroutine(function () {
try {
yield new P\RejectedPromise('a');
$this->fail('Should have thrown into the coroutine!');
} catch (P\RejectionException $e) {
yield new P\RejectedPromise('foo');
}
});
$promise->otherwise(function ($value) use (&$result) { $result = $value; });
P\queue()->run();
$this->assertEquals(P\PromiseInterface::REJECTED, $promise->getState());
$this->assertContains('foo', $result->getMessage());
}
public function createLotsOfSynchronousPromise()
{
return P\coroutine(function () {
$value = 0;
for ($i = 0; $i < 1000; $i++) {
$value = (yield new P\FulfilledPromise($i));
}
yield $value;
});
}
public function testLotsOfSynchronousDoesNotBlowStack()
{
$promise = $this->createLotsOfSynchronousPromise();
$promise->then(function ($v) use (&$r) { $r = $v; });
P\queue()->run();
$this->assertEquals(999, $r);
}
public function testLotsOfSynchronousWaitDoesNotBlowStack()
{
$promise = $this->createLotsOfSynchronousPromise();
$promise->then(function ($v) use (&$r) { $r = $v; });
$this->assertEquals(999, $promise->wait());
$this->assertEquals(999, $r);
}
private function createLotsOfFlappingPromise()
{
return P\coroutine(function () {
$value = 0;
for ($i = 0; $i < 1000; $i++) {
try {
if ($i % 2) {
$value = (yield new P\FulfilledPromise($i));
} else {
$value = (yield new P\RejectedPromise($i));
}
} catch (\Exception $e) {
$value = (yield new P\FulfilledPromise($i));
}
}
yield $value;
});
}
public function testLotsOfTryCatchingDoesNotBlowStack()
{
$promise = $this->createLotsOfFlappingPromise();
$promise->then(function ($v) use (&$r) { $r = $v; });
P\queue()->run();
$this->assertEquals(999, $r);
}
public function testLotsOfTryCatchingWaitingDoesNotBlowStack()
{
$promise = $this->createLotsOfFlappingPromise();
$promise->then(function ($v) use (&$r) { $r = $v; });
$this->assertEquals(999, $promise->wait());
$this->assertEquals(999, $r);
}
public function testAsyncPromisesWithCorrectlyYieldedValues()
{
$promises = [
new P\Promise(),
new P\Promise(),
new P\Promise()
];
$promise = P\coroutine(function () use ($promises) {
$value = null;
$this->assertEquals('skip', (yield new P\FulfilledPromise('skip')));
foreach ($promises as $idx => $p) {
$value = (yield $p);
$this->assertEquals($value, $idx);
$this->assertEquals('skip', (yield new P\FulfilledPromise('skip')));
}
$this->assertEquals('skip', (yield new P\FulfilledPromise('skip')));
yield $value;
});
$promises[0]->resolve(0);
$promises[1]->resolve(1);
$promises[2]->resolve(2);
$promise->then(function ($v) use (&$r) { $r = $v; });
P\queue()->run();
$this->assertEquals(2, $r);
}
public function testYieldFinalWaitablePromise()
{
$p1 = new P\Promise(function () use (&$p1) {
$p1->resolve('skip me');
});
$p2 = new P\Promise(function () use (&$p2) {
$p2->resolve('hello!');
});
$co = P\coroutine(function() use ($p1, $p2) {
yield $p1;
yield $p2;
});
P\queue()->run();
$this->assertEquals('hello!', $co->wait());
}
public function testCanYieldFinalPendingPromise()
{
$p1 = new P\Promise();
$p2 = new P\Promise();
$co = P\coroutine(function() use ($p1, $p2) {
yield $p1;
yield $p2;
});
$p1->resolve('a');
$p2->resolve('b');
$co->then(function ($value) use (&$result) { $result = $value; });
P\queue()->run();
$this->assertEquals('b', $result);
}
public function testCanNestYieldsAndFailures()
{
$p1 = new P\Promise();
$p2 = new P\Promise();
$p3 = new P\Promise();
$p4 = new P\Promise();
$p5 = new P\Promise();
$co = P\coroutine(function() use ($p1, $p2, $p3, $p4, $p5) {
try {
yield $p1;
} catch (\Exception $e) {
yield $p2;
try {
yield $p3;
yield $p4;
} catch (\Exception $e) {
yield $p5;
}
}
});
$p1->reject('a');
$p2->resolve('b');
$p3->resolve('c');
$p4->reject('d');
$p5->resolve('e');
$co->then(function ($value) use (&$result) { $result = $value; });
P\queue()->run();
$this->assertEquals('e', $result);
}
public function testCanYieldErrorsAndSuccessesWithoutRecursion()
{
$promises = [];
for ($i = 0; $i < 20; $i++) {
$promises[] = new P\Promise();
}
$co = P\coroutine(function() use ($promises) {
for ($i = 0; $i < 20; $i += 4) {
try {
yield $promises[$i];
yield $promises[$i + 1];
} catch (\Exception $e) {
yield $promises[$i + 2];
yield $promises[$i + 3];
}
}
});
for ($i = 0; $i < 20; $i += 4) {
$promises[$i]->resolve($i);
$promises[$i + 1]->reject($i + 1);
$promises[$i + 2]->resolve($i + 2);
$promises[$i + 3]->resolve($i + 3);
}
$co->then(function ($value) use (&$result) { $result = $value; });
P\queue()->run();
$this->assertEquals('19', $result);
}
public function testCanWaitOnPromiseAfterFulfilled()
{
$f = function () {
static $i = 0;
$i++;
return $p = new P\Promise(function () use (&$p, $i) {
$p->resolve($i . '-bar');
});
};
$promises = [];
for ($i = 0; $i < 20; $i++) {
$promises[] = $f();
}
$p = P\coroutine(function () use ($promises) {
yield new P\FulfilledPromise('foo!');
foreach ($promises as $promise) {
yield $promise;
}
});
$this->assertEquals('20-bar', $p->wait());
}
public function testCanWaitOnErroredPromises()
{
$p1 = new P\Promise(function () use (&$p1) { $p1->reject('a'); });
$p2 = new P\Promise(function () use (&$p2) { $p2->resolve('b'); });
$p3 = new P\Promise(function () use (&$p3) { $p3->resolve('c'); });
$p4 = new P\Promise(function () use (&$p4) { $p4->reject('d'); });
$p5 = new P\Promise(function () use (&$p5) { $p5->resolve('e'); });
$p6 = new P\Promise(function () use (&$p6) { $p6->reject('f'); });
$co = P\coroutine(function() use ($p1, $p2, $p3, $p4, $p5, $p6) {
try {
yield $p1;
} catch (\Exception $e) {
yield $p2;
try {
yield $p3;
yield $p4;
} catch (\Exception $e) {
yield $p5;
yield $p6;
}
}
});
$res = P\inspect($co);
$this->assertEquals('f', $res['reason']);
}
public function testCoroutineOtherwiseIntegrationTest()
{
$a = new P\Promise();
$b = new P\Promise();
$promise = P\coroutine(function () use ($a, $b) {
// Execute the pool of commands concurrently, and process errors.
yield $a;
yield $b;
})->otherwise(function (\Exception $e) {
// Throw errors from the operations as a specific Multipart error.
throw new \OutOfBoundsException('a', 0, $e);
});
$a->resolve('a');
$b->reject('b');
$reason = P\inspect($promise)['reason'];
$this->assertInstanceOf('OutOfBoundsException', $reason);
$this->assertInstanceOf('GuzzleHttp\Promise\RejectionException', $reason->getPrevious());
}
}

View File

@@ -1,11 +0,0 @@
phpunit.xml
composer.phar
composer.lock
composer-test.lock
vendor/
build/artifacts/
artifacts/
docs/_build
docs/*.pyc
.idea
.DS_STORE

View File

@@ -1,20 +0,0 @@
language: php
php:
- 5.4
- 5.5
- 5.6
- 7.0
- hhvm
sudo: false
install:
- travis_retry composer install --no-interaction --prefer-source
script: make test
matrix:
allow_failures:
- php: hhvm
fast_finish: true

View File

@@ -1,5 +1,45 @@
# CHANGELOG
## 1.4.2 - 2017-03-20
* Reverted BC break to `Uri::resolve` and `Uri::removeDotSegments` by removing
calls to `trigger_error` when deprecated methods are invoked.
## 1.4.1 - 2017-02-27
* Reverted BC break by reintroducing behavior to automagically fix a URI with a
relative path and an authority by adding a leading slash to the path. It's only
deprecated now.
* Added triggering of silenced deprecation warnings.
## 1.4.0 - 2017-02-21
* Fix `Stream::read` when length parameter <= 0.
* `copy_to_stream` reads bytes in chunks instead of `maxLen` into memory.
* Fix `ServerRequest::getUriFromGlobals` when `Host` header contains port.
* Ensure `ServerRequest::getUriFromGlobals` returns a URI in absolute form.
* Allow `parse_response` to parse a response without delimiting space and reason.
* Ensure each URI modification results in a valid URI according to PSR-7 discussions.
Invalid modifications will throw an exception instead of returning a wrong URI or
doing some magic.
- `(new Uri)->withPath('foo')->withHost('example.com')` will throw an exception
because the path of a URI with an authority must start with a slash "/" or be empty
- `(new Uri())->withScheme('http')` will return `'http://localhost'`
* Fix compatibility of URIs with `file` scheme and empty host.
* Added common URI utility methods based on RFC 3986 (see documentation in the readme):
- `Uri::isDefaultPort`
- `Uri::isAbsolute`
- `Uri::isNetworkPathReference`
- `Uri::isAbsolutePathReference`
- `Uri::isRelativePathReference`
- `Uri::isSameDocumentReference`
- `Uri::composeComponents`
- `UriNormalizer::normalize`
- `UriNormalizer::isEquivalent`
- `UriResolver::relativize`
* Deprecated `Uri::resolve` in favor of `UriResolver::resolve`
* Deprecated `Uri::removeDotSegments` in favor of `UriResolver::removeDotSegments`
## 1.3.1 - 2016-06-25
* Fix `Uri::__toString` for network path references, e.g. `//example.org`.

View File

@@ -1,29 +0,0 @@
all: clean test
test:
vendor/bin/phpunit $(TEST)
coverage:
vendor/bin/phpunit --coverage-html=artifacts/coverage $(TEST)
view-coverage:
open artifacts/coverage/index.html
check-tag:
$(if $(TAG),,$(error TAG is not defined. Pass via "make tag TAG=4.2.1"))
tag: check-tag
@echo Tagging $(TAG)
chag update $(TAG)
git commit -a -m '$(TAG) release'
chag tag
@echo "Release has been created. Push using 'make release'"
@echo "Changes made in the release commit"
git diff HEAD~1 HEAD
release: check-tag
git push origin master
git push origin $(TAG)
clean:
rm -rf artifacts/*

View File

@@ -519,51 +519,221 @@ Determines the mimetype of a file by looking at its extension.
Maps a file extensions to a mimetype.
# Static URI methods
# Additional URI Methods
The `GuzzleHttp\Psr7\Uri` class has several static methods to manipulate URIs.
Aside from the standard `Psr\Http\Message\UriInterface` implementation in form of the `GuzzleHttp\Psr7\Uri` class,
this library also provides additional functionality when working with URIs as static methods.
## URI Types
## `GuzzleHttp\Psr7\Uri::removeDotSegments`
An instance of `Psr\Http\Message\UriInterface` can either be an absolute URI or a relative reference.
An absolute URI has a scheme. A relative reference is used to express a URI relative to another URI,
the base URI. Relative references can be divided into several forms according to
[RFC 3986 Section 4.2](https://tools.ietf.org/html/rfc3986#section-4.2):
`public static function removeDotSegments(string $path): string`
- network-path references, e.g. `//example.com/path`
- absolute-path references, e.g. `/path`
- relative-path references, e.g. `subpath`
Removes dot segments from a path and returns the new path.
The following methods can be used to identify the type of the URI.
See http://tools.ietf.org/html/rfc3986#section-5.2.4
### `GuzzleHttp\Psr7\Uri::isAbsolute`
`public static function isAbsolute(UriInterface $uri): bool`
## `GuzzleHttp\Psr7\Uri::resolve`
Whether the URI is absolute, i.e. it has a scheme.
`public static function resolve(UriInterface $base, $rel): UriInterface`
### `GuzzleHttp\Psr7\Uri::isNetworkPathReference`
Resolve a base URI with a relative URI and return a new URI.
`public static function isNetworkPathReference(UriInterface $uri): bool`
See http://tools.ietf.org/html/rfc3986#section-5
Whether the URI is a network-path reference. A relative reference that begins with two slash characters is
termed an network-path reference.
### `GuzzleHttp\Psr7\Uri::isAbsolutePathReference`
## `GuzzleHttp\Psr7\Uri::withQueryValue`
`public static function isAbsolutePathReference(UriInterface $uri): bool`
`public static function withQueryValue(UriInterface $uri, $key, $value): UriInterface`
Whether the URI is a absolute-path reference. A relative reference that begins with a single slash character is
termed an absolute-path reference.
Create a new URI with a specific query string value.
### `GuzzleHttp\Psr7\Uri::isRelativePathReference`
Any existing query string values that exactly match the provided key are
removed and replaced with the given key value pair.
`public static function isRelativePathReference(UriInterface $uri): bool`
Whether the URI is a relative-path reference. A relative reference that does not begin with a slash character is
termed a relative-path reference.
## `GuzzleHttp\Psr7\Uri::withoutQueryValue`
### `GuzzleHttp\Psr7\Uri::isSameDocumentReference`
`public static function withoutQueryValue(UriInterface $uri, $key): UriInterface`
`public static function isSameDocumentReference(UriInterface $uri, UriInterface $base = null): bool`
Create a new URI with a specific query string value removed.
Whether the URI is a same-document reference. A same-document reference refers to a URI that is, aside from its
fragment component, identical to the base URI. When no base URI is given, only an empty URI reference
(apart from its fragment) is considered a same-document reference.
Any existing query string values that exactly match the provided key are
removed.
## URI Components
Additional methods to work with URI components.
## `GuzzleHttp\Psr7\Uri::fromParts`
### `GuzzleHttp\Psr7\Uri::isDefaultPort`
`public static function isDefaultPort(UriInterface $uri): bool`
Whether the URI has the default port of the current scheme. `Psr\Http\Message\UriInterface::getPort` may return null
or the standard port. This method can be used independently of the implementation.
### `GuzzleHttp\Psr7\Uri::composeComponents`
`public static function composeComponents($scheme, $authority, $path, $query, $fragment): string`
Composes a URI reference string from its various components according to
[RFC 3986 Section 5.3](https://tools.ietf.org/html/rfc3986#section-5.3). Usually this method does not need to be called
manually but instead is used indirectly via `Psr\Http\Message\UriInterface::__toString`.
### `GuzzleHttp\Psr7\Uri::fromParts`
`public static function fromParts(array $parts): UriInterface`
Create a `GuzzleHttp\Psr7\Uri` object from a hash of `parse_url` parts.
Creates a URI from a hash of [`parse_url`](http://php.net/manual/en/function.parse-url.php) components.
### `GuzzleHttp\Psr7\Uri::withQueryValue`
`public static function withQueryValue(UriInterface $uri, $key, $value): UriInterface`
Creates 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. A value of null will set the query string
key without a value, e.g. "key" instead of "key=value".
### `GuzzleHttp\Psr7\Uri::withoutQueryValue`
`public static function withoutQueryValue(UriInterface $uri, $key): UriInterface`
Creates a new URI with a specific query string value removed. Any existing query string values that exactly match the
provided key are removed.
## Reference Resolution
`GuzzleHttp\Psr7\UriResolver` provides methods to resolve a URI reference in the context of a base URI according
to [RFC 3986 Section 5](https://tools.ietf.org/html/rfc3986#section-5). This is for example also what web browsers
do when resolving a link in a website based on the current request URI.
### `GuzzleHttp\Psr7\UriResolver::resolve`
`public static function resolve(UriInterface $base, UriInterface $rel): UriInterface`
Converts the relative URI into a new URI that is resolved against the base URI.
### `GuzzleHttp\Psr7\UriResolver::removeDotSegments`
`public static function removeDotSegments(string $path): string`
Removes dot segments from a path and returns the new path according to
[RFC 3986 Section 5.2.4](https://tools.ietf.org/html/rfc3986#section-5.2.4).
### `GuzzleHttp\Psr7\UriResolver::relativize`
`public static function relativize(UriInterface $base, UriInterface $target): UriInterface`
Returns the target URI as a relative reference from the base URI. This method is the counterpart to resolve():
```php
(string) $target === (string) UriResolver::resolve($base, UriResolver::relativize($base, $target))
```
One use-case is to use the current request URI as base URI and then generate relative links in your documents
to reduce the document size or offer self-contained downloadable document archives.
```php
$base = new Uri('http://example.com/a/b/');
echo UriResolver::relativize($base, new Uri('http://example.com/a/b/c')); // prints 'c'.
echo UriResolver::relativize($base, new Uri('http://example.com/a/x/y')); // prints '../x/y'.
echo UriResolver::relativize($base, new Uri('http://example.com/a/b/?q')); // prints '?q'.
echo UriResolver::relativize($base, new Uri('http://example.org/a/b/')); // prints '//example.org/a/b/'.
```
## Normalization and Comparison
`GuzzleHttp\Psr7\UriNormalizer` provides methods to normalize and compare URIs according to
[RFC 3986 Section 6](https://tools.ietf.org/html/rfc3986#section-6).
### `GuzzleHttp\Psr7\UriNormalizer::normalize`
`public static function normalize(UriInterface $uri, $flags = self::PRESERVING_NORMALIZATIONS): UriInterface`
Returns a normalized URI. The scheme and host component are already normalized to lowercase per PSR-7 UriInterface.
This methods adds additional normalizations that can be configured with the `$flags` parameter which is a bitmask
of normalizations to apply. The following normalizations are available:
- `UriNormalizer::PRESERVING_NORMALIZATIONS`
Default normalizations which only include the ones that preserve semantics.
- `UriNormalizer::CAPITALIZE_PERCENT_ENCODING`
All letters within a percent-encoding triplet (e.g., "%3A") are case-insensitive, and should be capitalized.
Example: `http://example.org/a%c2%b1b``http://example.org/a%C2%B1b`
- `UriNormalizer::DECODE_UNRESERVED_CHARACTERS`
Decodes percent-encoded octets of unreserved characters. For consistency, percent-encoded octets in the ranges of
ALPHA (%41%5A and %61%7A), DIGIT (%30%39), hyphen (%2D), period (%2E), underscore (%5F), or tilde (%7E) should
not be created by URI producers and, when found in a URI, should be decoded to their corresponding unreserved
characters by URI normalizers.
Example: `http://example.org/%7Eusern%61me/``http://example.org/~username/`
- `UriNormalizer::CONVERT_EMPTY_PATH`
Converts the empty path to "/" for http and https URIs.
Example: `http://example.org``http://example.org/`
- `UriNormalizer::REMOVE_DEFAULT_HOST`
Removes the default host of the given URI scheme from the URI. Only the "file" scheme defines the default host
"localhost". All of `file:/myfile`, `file:///myfile`, and `file://localhost/myfile` are equivalent according to
RFC 3986.
Example: `file://localhost/myfile``file:///myfile`
- `UriNormalizer::REMOVE_DEFAULT_PORT`
Removes the default port of the given URI scheme from the URI.
Example: `http://example.org:80/``http://example.org/`
- `UriNormalizer::REMOVE_DOT_SEGMENTS`
Removes unnecessary dot-segments. Dot-segments in relative-path references are not removed as it would
change the semantics of the URI reference.
Example: `http://example.org/../a/b/../c/./d.html``http://example.org/a/c/d.html`
- `UriNormalizer::REMOVE_DUPLICATE_SLASHES`
Paths which include two or more adjacent slashes are converted to one. Webservers usually ignore duplicate slashes
and treat those URIs equivalent. But in theory those URIs do not need to be equivalent. So this normalization
may change the semantics. Encoded slashes (%2F) are not removed.
Example: `http://example.org//foo///bar.html``http://example.org/foo/bar.html`
- `UriNormalizer::SORT_QUERY_PARAMETERS`
Sort query parameters with their values in alphabetical order. However, the order of parameters in a URI may be
significant (this is not defined by the standard). So this normalization is not safe and may change the semantics
of the URI.
Example: `?lang=en&article=fred``?article=fred&lang=en`
### `GuzzleHttp\Psr7\UriNormalizer::isEquivalent`
`public static function isEquivalent(UriInterface $uri1, UriInterface $uri2, $normalizations = self::PRESERVING_NORMALIZATIONS): bool`
Whether two URIs can be considered equivalent. Both URIs are normalized automatically before comparison with the given
`$normalizations` bitmask. The method also accepts relative URI references and returns true when they are equivalent.
This of course assumes they will be resolved against the same base URI. If this is not the case, determination of
equivalence or difference of relative references does not mean anything.

View File

@@ -1,14 +1,18 @@
{
"name": "guzzlehttp/psr7",
"type": "library",
"description": "PSR-7 message implementation",
"keywords": ["message", "stream", "http", "uri"],
"description": "PSR-7 message implementation that also provides common utility methods",
"keywords": ["request", "response", "message", "stream", "http", "uri", "url"],
"license": "MIT",
"authors": [
{
"name": "Michael Dowling",
"email": "mtdowling@gmail.com",
"homepage": "https://github.com/mtdowling"
},
{
"name": "Tobias Schultze",
"homepage": "https://github.com/Tobion"
}
],
"require": {

View File

@@ -1,17 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<phpunit bootstrap="./tests/bootstrap.php"
colors="true">
<testsuites>
<testsuite>
<directory>tests</directory>
</testsuite>
</testsuites>
<filter>
<whitelist>
<directory suffix=".php">src</directory>
<exclude>
<directory suffix="Interface.php">src/</directory>
</exclude>
</whitelist>
</filter>
</phpunit>

View File

@@ -21,7 +21,7 @@ class LimitStream implements StreamInterface
* @param StreamInterface $stream Stream to wrap
* @param int $limit Total number of bytes to allow to be read
* from the stream. Pass -1 for no limit.
* @param int|null $offset Position to seek to before reading (only
* @param int $offset Position to seek to before reading (only
* works on seekable streams).
*/
public function __construct(

View File

@@ -27,7 +27,7 @@ class MultipartStream implements StreamInterface
*/
public function __construct(array $elements = [], $boundary = null)
{
$this->boundary = $boundary ?: uniqid();
$this->boundary = $boundary ?: sha1(uniqid('', true));
$this->stream = $this->createStream($elements);
}
@@ -108,7 +108,7 @@ class MultipartStream implements StreamInterface
/**
* @return array
*/
private function createElement($name, $stream, $filename, array $headers)
private function createElement($name, StreamInterface $stream, $filename, array $headers)
{
// Set a default content-disposition header if one was no provided
$disposition = $this->getHeader($headers, 'content-disposition');

View File

@@ -19,7 +19,7 @@ class Request implements RequestInterface
/** @var null|string */
private $requestTarget;
/** @var null|UriInterface */
/** @var UriInterface */
private $uri;
/**

View File

@@ -2,6 +2,7 @@
namespace GuzzleHttp\Psr7;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\StreamInterface;
/**
* PSR-7 response implementation.
@@ -100,7 +101,7 @@ class Response implements ResponseInterface
$this->setHeaders($headers);
if ($reason == '' && isset(self::$phrases[$this->statusCode])) {
$this->reasonPhrase = self::$phrases[$status];
$this->reasonPhrase = self::$phrases[$this->statusCode];
} else {
$this->reasonPhrase = (string) $reason;
}

View File

@@ -188,25 +188,37 @@ class ServerRequest extends Request implements ServerRequestInterface
public static function getUriFromGlobals() {
$uri = new Uri('');
if (isset($_SERVER['HTTPS'])) {
$uri = $uri->withScheme($_SERVER['HTTPS'] == 'on' ? 'https' : 'http');
}
$uri = $uri->withScheme(!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off' ? 'https' : 'http');
$hasPort = false;
if (isset($_SERVER['HTTP_HOST'])) {
$uri = $uri->withHost($_SERVER['HTTP_HOST']);
$hostHeaderParts = explode(':', $_SERVER['HTTP_HOST']);
$uri = $uri->withHost($hostHeaderParts[0]);
if (isset($hostHeaderParts[1])) {
$hasPort = true;
$uri = $uri->withPort($hostHeaderParts[1]);
}
} elseif (isset($_SERVER['SERVER_NAME'])) {
$uri = $uri->withHost($_SERVER['SERVER_NAME']);
} elseif (isset($_SERVER['SERVER_ADDR'])) {
$uri = $uri->withHost($_SERVER['SERVER_ADDR']);
}
if (isset($_SERVER['SERVER_PORT'])) {
if (!$hasPort && isset($_SERVER['SERVER_PORT'])) {
$uri = $uri->withPort($_SERVER['SERVER_PORT']);
}
$hasQuery = false;
if (isset($_SERVER['REQUEST_URI'])) {
$uri = $uri->withPath(current(explode('?', $_SERVER['REQUEST_URI'])));
$requestUriParts = explode('?', $_SERVER['REQUEST_URI']);
$uri = $uri->withPath($requestUriParts[0]);
if (isset($requestUriParts[1])) {
$hasQuery = true;
$uri = $uri->withQuery($requestUriParts[1]);
}
}
if (isset($_SERVER['QUERY_STRING'])) {
if (!$hasQuery && isset($_SERVER['QUERY_STRING'])) {
$uri = $uri->withQuery($_SERVER['QUERY_STRING']);
}

View File

@@ -207,8 +207,20 @@ class Stream implements StreamInterface
if (!$this->readable) {
throw new \RuntimeException('Cannot read from non-readable stream');
}
if ($length < 0) {
throw new \RuntimeException('Length parameter cannot be negative');
}
return fread($this->stream, $length);
if (0 === $length) {
return '';
}
$string = fread($this->stream, $length);
if (false === $string) {
throw new \RuntimeException('Unable to read from stream');
}
return $string;
}
public function write($string)

View File

@@ -12,9 +12,26 @@ use Psr\Http\Message\UriInterface;
*/
class Uri implements UriInterface
{
private static $schemes = [
/**
* Absolute http and https URIs require a host per RFC 7230 Section 2.7
* but in generic URIs the host can be empty. So for http(s) URIs
* we apply this default host when no host is given yet to form a
* valid URI.
*/
const HTTP_DEFAULT_HOST = 'localhost';
private static $defaultPorts = [
'http' => 80,
'https' => 443,
'ftp' => 21,
'gopher' => 70,
'nntp' => 119,
'news' => 119,
'telnet' => 23,
'tn3270' => 23,
'imap' => 143,
'pop' => 110,
'ldap' => 389,
];
private static $charUnreserved = 'a-zA-Z0-9_\-\.~';
@@ -47,6 +64,7 @@ class Uri implements UriInterface
*/
public function __construct($uri = '')
{
// weak type check to also accept null until we can add scalar type hints
if ($uri != '') {
$parts = parse_url($uri);
if ($parts === false) {
@@ -58,7 +76,7 @@ class Uri implements UriInterface
public function __toString()
{
return self::createUriString(
return self::composeComponents(
$this->scheme,
$this->getAuthority(),
$this->path,
@@ -67,57 +85,199 @@ class Uri implements UriInterface
);
}
/**
* Composes a URI reference string from its various components.
*
* Usually this method does not need to be called manually but instead is used indirectly via
* `Psr\Http\Message\UriInterface::__toString`.
*
* PSR-7 UriInterface treats an empty component the same as a missing component as
* getQuery(), getFragment() etc. always return a string. This explains the slight
* difference to RFC 3986 Section 5.3.
*
* Another adjustment is that the authority separator is added even when the authority is missing/empty
* for the "file" scheme. This is because PHP stream functions like `file_get_contents` only work with
* `file:///myfile` but not with `file:/myfile` although they are equivalent according to RFC 3986. But
* `file:///` is the more common syntax for the file scheme anyway (Chrome for example redirects to
* that format).
*
* @param string $scheme
* @param string $authority
* @param string $path
* @param string $query
* @param string $fragment
*
* @return string
*
* @link https://tools.ietf.org/html/rfc3986#section-5.3
*/
public static function composeComponents($scheme, $authority, $path, $query, $fragment)
{
$uri = '';
// weak type checks to also accept null until we can add scalar type hints
if ($scheme != '') {
$uri .= $scheme . ':';
}
if ($authority != ''|| $scheme === 'file') {
$uri .= '//' . $authority;
}
$uri .= $path;
if ($query != '') {
$uri .= '?' . $query;
}
if ($fragment != '') {
$uri .= '#' . $fragment;
}
return $uri;
}
/**
* Whether the URI has the default port of the current scheme.
*
* `Psr\Http\Message\UriInterface::getPort` may return null or the standard port. This method can be used
* independently of the implementation.
*
* @param UriInterface $uri
*
* @return bool
*/
public static function isDefaultPort(UriInterface $uri)
{
return $uri->getPort() === null
|| (isset(self::$defaultPorts[$uri->getScheme()]) && $uri->getPort() === self::$defaultPorts[$uri->getScheme()]);
}
/**
* Whether the URI is absolute, i.e. it has a scheme.
*
* An instance of UriInterface can either be an absolute URI or a relative reference. This method returns true
* if it is the former. An absolute URI has a scheme. A relative reference is used to express a URI relative
* to another URI, the base URI. Relative references can be divided into several forms:
* - network-path references, e.g. '//example.com/path'
* - absolute-path references, e.g. '/path'
* - relative-path references, e.g. 'subpath'
*
* @param UriInterface $uri
*
* @return bool
* @see Uri::isNetworkPathReference
* @see Uri::isAbsolutePathReference
* @see Uri::isRelativePathReference
* @link https://tools.ietf.org/html/rfc3986#section-4
*/
public static function isAbsolute(UriInterface $uri)
{
return $uri->getScheme() !== '';
}
/**
* Whether the URI is a network-path reference.
*
* A relative reference that begins with two slash characters is termed an network-path reference.
*
* @param UriInterface $uri
*
* @return bool
* @link https://tools.ietf.org/html/rfc3986#section-4.2
*/
public static function isNetworkPathReference(UriInterface $uri)
{
return $uri->getScheme() === '' && $uri->getAuthority() !== '';
}
/**
* Whether the URI is a absolute-path reference.
*
* A relative reference that begins with a single slash character is termed an absolute-path reference.
*
* @param UriInterface $uri
*
* @return bool
* @link https://tools.ietf.org/html/rfc3986#section-4.2
*/
public static function isAbsolutePathReference(UriInterface $uri)
{
return $uri->getScheme() === ''
&& $uri->getAuthority() === ''
&& isset($uri->getPath()[0])
&& $uri->getPath()[0] === '/';
}
/**
* Whether the URI is a relative-path reference.
*
* A relative reference that does not begin with a slash character is termed a relative-path reference.
*
* @param UriInterface $uri
*
* @return bool
* @link https://tools.ietf.org/html/rfc3986#section-4.2
*/
public static function isRelativePathReference(UriInterface $uri)
{
return $uri->getScheme() === ''
&& $uri->getAuthority() === ''
&& (!isset($uri->getPath()[0]) || $uri->getPath()[0] !== '/');
}
/**
* Whether the URI is a same-document reference.
*
* A same-document reference refers to a URI that is, aside from its fragment
* component, identical to the base URI. When no base URI is given, only an empty
* URI reference (apart from its fragment) is considered a same-document reference.
*
* @param UriInterface $uri The URI to check
* @param UriInterface|null $base An optional base URI to compare against
*
* @return bool
* @link https://tools.ietf.org/html/rfc3986#section-4.4
*/
public static function isSameDocumentReference(UriInterface $uri, UriInterface $base = null)
{
if ($base !== null) {
$uri = UriResolver::resolve($base, $uri);
return ($uri->getScheme() === $base->getScheme())
&& ($uri->getAuthority() === $base->getAuthority())
&& ($uri->getPath() === $base->getPath())
&& ($uri->getQuery() === $base->getQuery());
}
return $uri->getScheme() === '' && $uri->getAuthority() === '' && $uri->getPath() === '' && $uri->getQuery() === '';
}
/**
* Removes dot segments from a path and returns the new path.
*
* @param string $path
*
* @return string
* @link http://tools.ietf.org/html/rfc3986#section-5.2.4
*
* @deprecated since version 1.4. Use UriResolver::removeDotSegments instead.
* @see UriResolver::removeDotSegments
*/
public static function removeDotSegments($path)
{
static $noopPaths = ['' => true, '/' => true, '*' => true];
static $ignoreSegments = ['.' => true, '..' => true];
if (isset($noopPaths[$path])) {
return $path;
}
$results = [];
$segments = explode('/', $path);
foreach ($segments as $segment) {
if ($segment === '..') {
array_pop($results);
} elseif (!isset($ignoreSegments[$segment])) {
$results[] = $segment;
}
}
$newPath = implode('/', $results);
// Add the leading slash if necessary
if (substr($path, 0, 1) === '/' &&
substr($newPath, 0, 1) !== '/'
) {
$newPath = '/' . $newPath;
}
// Add the trailing slash if necessary
if ($newPath !== '/' && isset($ignoreSegments[end($segments)])) {
$newPath .= '/';
}
return $newPath;
return UriResolver::removeDotSegments($path);
}
/**
* Resolve a base URI with a relative URI and return a new URI.
* Converts the relative URI into a new URI that is resolved against the base URI.
*
* @param UriInterface $base Base URI
* @param string|UriInterface $rel Relative URI
*
* @return UriInterface
* @link http://tools.ietf.org/html/rfc3986#section-5.2
*
* @deprecated since version 1.4. Use UriResolver::resolve instead.
* @see UriResolver::resolve
*/
public static function resolve(UriInterface $base, $rel)
{
@@ -125,55 +285,11 @@ class Uri implements UriInterface
$rel = new self($rel);
}
if ((string) $rel === '') {
// we can simply return the same base URI instance for this same-document reference
return $base;
}
if ($rel->getScheme() != '') {
return $rel->withPath(self::removeDotSegments($rel->getPath()));
}
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 ($rel->getPath()[0] === '/') {
$targetPath = $rel->getPath();
} else {
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();
}
}
}
$targetPath = self::removeDotSegments($targetPath);
$targetQuery = $rel->getQuery();
}
}
return new self(self::createUriString(
$base->getScheme(),
$targetAuthority,
$targetPath,
$targetQuery,
$rel->getFragment()
));
return UriResolver::resolve($base, $rel);
}
/**
* Create a new URI with a specific query string value removed.
* Creates a new URI with a specific query string value removed.
*
* Any existing query string values that exactly match the provided key are
* removed.
@@ -186,7 +302,7 @@ class Uri implements UriInterface
public static function withoutQueryValue(UriInterface $uri, $key)
{
$current = $uri->getQuery();
if ($current == '') {
if ($current === '') {
return $uri;
}
@@ -199,7 +315,7 @@ class Uri implements UriInterface
}
/**
* Create a new URI with a specific query string value.
* Creates 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.
@@ -217,7 +333,7 @@ class Uri implements UriInterface
{
$current = $uri->getQuery();
if ($current == '') {
if ($current === '') {
$result = [];
} else {
$decodedKey = rawurldecode($key);
@@ -241,16 +357,21 @@ class Uri implements UriInterface
}
/**
* Create a URI from a hash of parse_url parts.
* Creates a URI from a hash of `parse_url` components.
*
* @param array $parts
*
* @return self
* @return UriInterface
* @link http://php.net/manual/en/function.parse-url.php
*
* @throws \InvalidArgumentException If the components do not form a valid URI.
*/
public static function fromParts(array $parts)
{
$uri = new self();
$uri->applyParts($parts);
$uri->validateState();
return $uri;
}
@@ -261,12 +382,8 @@ class Uri implements UriInterface
public function getAuthority()
{
if ($this->host == '') {
return '';
}
$authority = $this->host;
if ($this->userInfo != '') {
if ($this->userInfo !== '') {
$authority = $this->userInfo . '@' . $authority;
}
@@ -317,7 +434,9 @@ class Uri implements UriInterface
$new = clone $this;
$new->scheme = $scheme;
$new->port = $new->filterPort($new->port);
$new->removeDefaultPort();
$new->validateState();
return $new;
}
@@ -334,6 +453,8 @@ class Uri implements UriInterface
$new = clone $this;
$new->userInfo = $info;
$new->validateState();
return $new;
}
@@ -347,6 +468,8 @@ class Uri implements UriInterface
$new = clone $this;
$new->host = $host;
$new->validateState();
return $new;
}
@@ -360,6 +483,9 @@ class Uri implements UriInterface
$new = clone $this;
$new->port = $port;
$new->removeDefaultPort();
$new->validateState();
return $new;
}
@@ -373,6 +499,8 @@ class Uri implements UriInterface
$new = clone $this;
$new->path = $path;
$new->validateState();
return $new;
}
@@ -386,6 +514,7 @@ class Uri implements UriInterface
$new = clone $this;
$new->query = $query;
return $new;
}
@@ -399,6 +528,7 @@ class Uri implements UriInterface
$new = clone $this;
$new->fragment = $fragment;
return $new;
}
@@ -431,69 +561,8 @@ class Uri implements UriInterface
if (isset($parts['pass'])) {
$this->userInfo .= ':' . $parts['pass'];
}
}
/**
* Create a URI string from its various parts
*
* @param string $scheme
* @param string $authority
* @param string $path
* @param string $query
* @param string $fragment
* @return string
*/
private static function createUriString($scheme, $authority, $path, $query, $fragment)
{
$uri = '';
if ($scheme != '') {
$uri .= $scheme . ':';
}
if ($authority != '') {
$uri .= '//' . $authority;
}
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, '/');
}
}
$uri .= $path;
}
if ($query != '') {
$uri .= '?' . $query;
}
if ($fragment != '') {
$uri .= '#' . $fragment;
}
return $uri;
}
/**
* Is a given port non-standard for the current scheme?
*
* @param string $scheme
* @param int $port
*
* @return bool
*/
private static function isNonStandardPort($scheme, $port)
{
return !isset(self::$schemes[$scheme]) || $port !== self::$schemes[$scheme];
$this->removeDefaultPort();
}
/**
@@ -548,7 +617,14 @@ class Uri implements UriInterface
);
}
return self::isNonStandardPort($this->scheme, $port) ? $port : null;
return $port;
}
private function removeDefaultPort()
{
if ($this->port !== null && self::isDefaultPort($this)) {
$this->port = null;
}
}
/**
@@ -599,4 +675,28 @@ class Uri implements UriInterface
{
return rawurlencode($match[0]);
}
private function validateState()
{
if ($this->host === '' && ($this->scheme === 'http' || $this->scheme === 'https')) {
$this->host = self::HTTP_DEFAULT_HOST;
}
if ($this->getAuthority() === '') {
if (0 === strpos($this->path, '//')) {
throw new \InvalidArgumentException('The path of a URI without an authority must not start with two slashes "//"');
}
if ($this->scheme === '' && false !== strpos(explode('/', $this->path, 2)[0], ':')) {
throw new \InvalidArgumentException('A relative URI must not have a path beginning with a segment containing a colon');
}
} elseif (isset($this->path[0]) && $this->path[0] !== '/') {
@trigger_error(
'The path of a URI with an authority must start with a slash "/" or be empty. Automagically fixing the URI ' .
'by adding a leading slash to the path is deprecated since version 1.4 and will throw an exception instead.',
E_USER_DEPRECATED
);
$this->path = '/'. $this->path;
//throw new \InvalidArgumentException('The path of a URI with an authority must start with a slash "/" or be empty');
}
}
}

View File

@@ -0,0 +1,216 @@
<?php
namespace GuzzleHttp\Psr7;
use Psr\Http\Message\UriInterface;
/**
* Provides methods to normalize and compare URIs.
*
* @author Tobias Schultze
*
* @link https://tools.ietf.org/html/rfc3986#section-6
*/
final class UriNormalizer
{
/**
* Default normalizations which only include the ones that preserve semantics.
*
* self::CAPITALIZE_PERCENT_ENCODING | self::DECODE_UNRESERVED_CHARACTERS | self::CONVERT_EMPTY_PATH |
* self::REMOVE_DEFAULT_HOST | self::REMOVE_DEFAULT_PORT | self::REMOVE_DOT_SEGMENTS
*/
const PRESERVING_NORMALIZATIONS = 63;
/**
* All letters within a percent-encoding triplet (e.g., "%3A") are case-insensitive, and should be capitalized.
*
* Example: http://example.org/a%c2%b1b → http://example.org/a%C2%B1b
*/
const CAPITALIZE_PERCENT_ENCODING = 1;
/**
* Decodes percent-encoded octets of unreserved characters.
*
* For consistency, percent-encoded octets in the ranges of ALPHA (%41%5A and %61%7A), DIGIT (%30%39),
* hyphen (%2D), period (%2E), underscore (%5F), or tilde (%7E) should not be created by URI producers and,
* when found in a URI, should be decoded to their corresponding unreserved characters by URI normalizers.
*
* Example: http://example.org/%7Eusern%61me/ → http://example.org/~username/
*/
const DECODE_UNRESERVED_CHARACTERS = 2;
/**
* Converts the empty path to "/" for http and https URIs.
*
* Example: http://example.org → http://example.org/
*/
const CONVERT_EMPTY_PATH = 4;
/**
* Removes the default host of the given URI scheme from the URI.
*
* Only the "file" scheme defines the default host "localhost".
* All of `file:/myfile`, `file:///myfile`, and `file://localhost/myfile`
* are equivalent according to RFC 3986. The first format is not accepted
* by PHPs stream functions and thus already normalized implicitly to the
* second format in the Uri class. See `GuzzleHttp\Psr7\Uri::composeComponents`.
*
* Example: file://localhost/myfile → file:///myfile
*/
const REMOVE_DEFAULT_HOST = 8;
/**
* Removes the default port of the given URI scheme from the URI.
*
* Example: http://example.org:80/ → http://example.org/
*/
const REMOVE_DEFAULT_PORT = 16;
/**
* Removes unnecessary dot-segments.
*
* Dot-segments in relative-path references are not removed as it would
* change the semantics of the URI reference.
*
* Example: http://example.org/../a/b/../c/./d.html → http://example.org/a/c/d.html
*/
const REMOVE_DOT_SEGMENTS = 32;
/**
* Paths which include two or more adjacent slashes are converted to one.
*
* Webservers usually ignore duplicate slashes and treat those URIs equivalent.
* But in theory those URIs do not need to be equivalent. So this normalization
* may change the semantics. Encoded slashes (%2F) are not removed.
*
* Example: http://example.org//foo///bar.html → http://example.org/foo/bar.html
*/
const REMOVE_DUPLICATE_SLASHES = 64;
/**
* Sort query parameters with their values in alphabetical order.
*
* However, the order of parameters in a URI may be significant (this is not defined by the standard).
* So this normalization is not safe and may change the semantics of the URI.
*
* Example: ?lang=en&article=fred → ?article=fred&lang=en
*
* Note: The sorting is neither locale nor Unicode aware (the URI query does not get decoded at all) as the
* purpose is to be able to compare URIs in a reproducible way, not to have the params sorted perfectly.
*/
const SORT_QUERY_PARAMETERS = 128;
/**
* Returns a normalized URI.
*
* The scheme and host component are already normalized to lowercase per PSR-7 UriInterface.
* This methods adds additional normalizations that can be configured with the $flags parameter.
*
* PSR-7 UriInterface cannot distinguish between an empty component and a missing component as
* getQuery(), getFragment() etc. always return a string. This means the URIs "/?#" and "/" are
* treated equivalent which is not necessarily true according to RFC 3986. But that difference
* is highly uncommon in reality. So this potential normalization is implied in PSR-7 as well.
*
* @param UriInterface $uri The URI to normalize
* @param int $flags A bitmask of normalizations to apply, see constants
*
* @return UriInterface The normalized URI
* @link https://tools.ietf.org/html/rfc3986#section-6.2
*/
public static function normalize(UriInterface $uri, $flags = self::PRESERVING_NORMALIZATIONS)
{
if ($flags & self::CAPITALIZE_PERCENT_ENCODING) {
$uri = self::capitalizePercentEncoding($uri);
}
if ($flags & self::DECODE_UNRESERVED_CHARACTERS) {
$uri = self::decodeUnreservedCharacters($uri);
}
if ($flags & self::CONVERT_EMPTY_PATH && $uri->getPath() === '' &&
($uri->getScheme() === 'http' || $uri->getScheme() === 'https')
) {
$uri = $uri->withPath('/');
}
if ($flags & self::REMOVE_DEFAULT_HOST && $uri->getScheme() === 'file' && $uri->getHost() === 'localhost') {
$uri = $uri->withHost('');
}
if ($flags & self::REMOVE_DEFAULT_PORT && $uri->getPort() !== null && Uri::isDefaultPort($uri)) {
$uri = $uri->withPort(null);
}
if ($flags & self::REMOVE_DOT_SEGMENTS && !Uri::isRelativePathReference($uri)) {
$uri = $uri->withPath(UriResolver::removeDotSegments($uri->getPath()));
}
if ($flags & self::REMOVE_DUPLICATE_SLASHES) {
$uri = $uri->withPath(preg_replace('#//++#', '/', $uri->getPath()));
}
if ($flags & self::SORT_QUERY_PARAMETERS && $uri->getQuery() !== '') {
$queryKeyValues = explode('&', $uri->getQuery());
sort($queryKeyValues);
$uri = $uri->withQuery(implode('&', $queryKeyValues));
}
return $uri;
}
/**
* Whether two URIs can be considered equivalent.
*
* Both URIs are normalized automatically before comparison with the given $normalizations bitmask. The method also
* accepts relative URI references and returns true when they are equivalent. This of course assumes they will be
* resolved against the same base URI. If this is not the case, determination of equivalence or difference of
* relative references does not mean anything.
*
* @param UriInterface $uri1 An URI to compare
* @param UriInterface $uri2 An URI to compare
* @param int $normalizations A bitmask of normalizations to apply, see constants
*
* @return bool
* @link https://tools.ietf.org/html/rfc3986#section-6.1
*/
public static function isEquivalent(UriInterface $uri1, UriInterface $uri2, $normalizations = self::PRESERVING_NORMALIZATIONS)
{
return (string) self::normalize($uri1, $normalizations) === (string) self::normalize($uri2, $normalizations);
}
private static function capitalizePercentEncoding(UriInterface $uri)
{
$regex = '/(?:%[A-Fa-f0-9]{2})++/';
$callback = function (array $match) {
return strtoupper($match[0]);
};
return
$uri->withPath(
preg_replace_callback($regex, $callback, $uri->getPath())
)->withQuery(
preg_replace_callback($regex, $callback, $uri->getQuery())
);
}
private static function decodeUnreservedCharacters(UriInterface $uri)
{
$regex = '/%(?:2D|2E|5F|7E|3[0-9]|[46][1-9A-F]|[57][0-9A])/i';
$callback = function (array $match) {
return rawurldecode($match[0]);
};
return
$uri->withPath(
preg_replace_callback($regex, $callback, $uri->getPath())
)->withQuery(
preg_replace_callback($regex, $callback, $uri->getQuery())
);
}
private function __construct()
{
// cannot be instantiated
}
}

View File

@@ -0,0 +1,219 @@
<?php
namespace GuzzleHttp\Psr7;
use Psr\Http\Message\UriInterface;
/**
* Resolves a URI reference in the context of a base URI and the opposite way.
*
* @author Tobias Schultze
*
* @link https://tools.ietf.org/html/rfc3986#section-5
*/
final class UriResolver
{
/**
* Removes dot segments from a path and returns the new path.
*
* @param string $path
*
* @return string
* @link http://tools.ietf.org/html/rfc3986#section-5.2.4
*/
public static function removeDotSegments($path)
{
if ($path === '' || $path === '/') {
return $path;
}
$results = [];
$segments = explode('/', $path);
foreach ($segments as $segment) {
if ($segment === '..') {
array_pop($results);
} elseif ($segment !== '.') {
$results[] = $segment;
}
}
$newPath = implode('/', $results);
if ($path[0] === '/' && (!isset($newPath[0]) || $newPath[0] !== '/')) {
// Re-add the leading slash if necessary for cases like "/.."
$newPath = '/' . $newPath;
} elseif ($newPath !== '' && ($segment === '.' || $segment === '..')) {
// Add the trailing slash if necessary
// If newPath is not empty, then $segment must be set and is the last segment from the foreach
$newPath .= '/';
}
return $newPath;
}
/**
* Converts the relative URI into a new URI that is resolved against the base URI.
*
* @param UriInterface $base Base URI
* @param UriInterface $rel Relative URI
*
* @return UriInterface
* @link http://tools.ietf.org/html/rfc3986#section-5.2
*/
public static function resolve(UriInterface $base, UriInterface $rel)
{
if ((string) $rel === '') {
// we can simply return the same base URI instance for this same-document reference
return $base;
}
if ($rel->getScheme() != '') {
return $rel->withPath(self::removeDotSegments($rel->getPath()));
}
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 ($rel->getPath()[0] === '/') {
$targetPath = $rel->getPath();
} else {
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();
}
}
}
$targetPath = self::removeDotSegments($targetPath);
$targetQuery = $rel->getQuery();
}
}
return new Uri(Uri::composeComponents(
$base->getScheme(),
$targetAuthority,
$targetPath,
$targetQuery,
$rel->getFragment()
));
}
/**
* Returns the target URI as a relative reference from the base URI.
*
* This method is the counterpart to resolve():
*
* (string) $target === (string) UriResolver::resolve($base, UriResolver::relativize($base, $target))
*
* One use-case is to use the current request URI as base URI and then generate relative links in your documents
* to reduce the document size or offer self-contained downloadable document archives.
*
* $base = new Uri('http://example.com/a/b/');
* echo UriResolver::relativize($base, new Uri('http://example.com/a/b/c')); // prints 'c'.
* echo UriResolver::relativize($base, new Uri('http://example.com/a/x/y')); // prints '../x/y'.
* echo UriResolver::relativize($base, new Uri('http://example.com/a/b/?q')); // prints '?q'.
* echo UriResolver::relativize($base, new Uri('http://example.org/a/b/')); // prints '//example.org/a/b/'.
*
* This method also accepts a target that is already relative and will try to relativize it further. Only a
* relative-path reference will be returned as-is.
*
* echo UriResolver::relativize($base, new Uri('/a/b/c')); // prints 'c' as well
*
* @param UriInterface $base Base URI
* @param UriInterface $target Target URI
*
* @return UriInterface The relative URI reference
*/
public static function relativize(UriInterface $base, UriInterface $target)
{
if ($target->getScheme() !== '' &&
($base->getScheme() !== $target->getScheme() || $target->getAuthority() === '' && $base->getAuthority() !== '')
) {
return $target;
}
if (Uri::isRelativePathReference($target)) {
// As the target is already highly relative we return it as-is. It would be possible to resolve
// the target with `$target = self::resolve($base, $target);` and then try make it more relative
// by removing a duplicate query. But let's not do that automatically.
return $target;
}
if ($target->getAuthority() !== '' && $base->getAuthority() !== $target->getAuthority()) {
return $target->withScheme('');
}
// We must remove the path before removing the authority because if the path starts with two slashes, the URI
// would turn invalid. And we also cannot set a relative path before removing the authority, as that is also
// invalid.
$emptyPathUri = $target->withScheme('')->withPath('')->withUserInfo('')->withPort(null)->withHost('');
if ($base->getPath() !== $target->getPath()) {
return $emptyPathUri->withPath(self::getRelativePath($base, $target));
}
if ($base->getQuery() === $target->getQuery()) {
// Only the target fragment is left. And it must be returned even if base and target fragment are the same.
return $emptyPathUri->withQuery('');
}
// If the base URI has a query but the target has none, we cannot return an empty path reference as it would
// inherit the base query component when resolving.
if ($target->getQuery() === '') {
$segments = explode('/', $target->getPath());
$lastSegment = end($segments);
return $emptyPathUri->withPath($lastSegment === '' ? './' : $lastSegment);
}
return $emptyPathUri;
}
private static function getRelativePath(UriInterface $base, UriInterface $target)
{
$sourceSegments = explode('/', $base->getPath());
$targetSegments = explode('/', $target->getPath());
array_pop($sourceSegments);
$targetLastSegment = array_pop($targetSegments);
foreach ($sourceSegments as $i => $segment) {
if (isset($targetSegments[$i]) && $segment === $targetSegments[$i]) {
unset($sourceSegments[$i], $targetSegments[$i]);
} else {
break;
}
}
$targetSegments[] = $targetLastSegment;
$relativePath = str_repeat('../', count($sourceSegments)) . implode('/', $targetSegments);
// A reference to am empty last segment or an empty first sub-segment must be prefixed with "./".
// This also applies to a segment with a colon character (e.g., "file:colon") that cannot be used
// as the first segment of a relative-path reference, as it would be mistaken for a scheme name.
if ('' === $relativePath || false !== strpos(explode('/', $relativePath, 2)[0], ':')) {
$relativePath = "./$relativePath";
} elseif ('/' === $relativePath[0]) {
if ($base->getAuthority() != '' && $base->getPath() === '') {
// In this case an extra slash is added by resolve() automatically. So we must not add one here.
$relativePath = ".$relativePath";
} else {
$relativePath = "./$relativePath";
}
}
return $relativePath;
}
private function __construct()
{
// cannot be instantiated
}
}

View File

@@ -371,25 +371,24 @@ function copy_to_stream(
StreamInterface $dest,
$maxLen = -1
) {
$bufferSize = 8192;
if ($maxLen === -1) {
while (!$source->eof()) {
if (!$dest->write($source->read(1048576))) {
if (!$dest->write($source->read($bufferSize))) {
break;
}
}
return;
}
$bytes = 0;
while (!$source->eof()) {
$buf = $source->read($maxLen - $bytes);
if (!($len = strlen($buf))) {
break;
}
$bytes += $len;
$dest->write($buf);
if ($bytes == $maxLen) {
break;
} else {
$remaining = $maxLen;
while ($remaining > 0 && !$source->eof()) {
$buf = $source->read(min($bufferSize, $remaining));
$len = strlen($buf);
if (!$len) {
break;
}
$remaining -= $len;
$dest->write($buf);
}
}
}
@@ -492,7 +491,10 @@ function parse_request($message)
function parse_response($message)
{
$data = _parse_message($message);
if (!preg_match('/^HTTP\/.* [0-9]{3} .*/', $data['start-line'])) {
// According to https://tools.ietf.org/html/rfc7230#section-3.1.2 the space
// between status-code and reason-phrase is required. But browsers accept
// responses without space and reason as well.
if (!preg_match('/^HTTP\/.* [0-9]{3}( .*|$)/', $data['start-line'])) {
throw new \InvalidArgumentException('Invalid response string');
}
$parts = explode(' ', $data['start-line'], 3);

View File

@@ -1,186 +0,0 @@
<?php
namespace GuzzleHttp\Tests\Psr7;
use GuzzleHttp\Psr7\AppendStream;
use GuzzleHttp\Psr7;
class AppendStreamTest extends \PHPUnit_Framework_TestCase
{
/**
* @expectedException \InvalidArgumentException
* @expectedExceptionMessage Each stream must be readable
*/
public function testValidatesStreamsAreReadable()
{
$a = new AppendStream();
$s = $this->getMockBuilder('Psr\Http\Message\StreamInterface')
->setMethods(['isReadable'])
->getMockForAbstractClass();
$s->expects($this->once())
->method('isReadable')
->will($this->returnValue(false));
$a->addStream($s);
}
/**
* @expectedException \RuntimeException
* @expectedExceptionMessage The AppendStream can only seek with SEEK_SET
*/
public function testValidatesSeekType()
{
$a = new AppendStream();
$a->seek(100, SEEK_CUR);
}
/**
* @expectedException \RuntimeException
* @expectedExceptionMessage Unable to seek stream 0 of the AppendStream
*/
public function testTriesToRewindOnSeek()
{
$a = new AppendStream();
$s = $this->getMockBuilder('Psr\Http\Message\StreamInterface')
->setMethods(['isReadable', 'rewind', 'isSeekable'])
->getMockForAbstractClass();
$s->expects($this->once())
->method('isReadable')
->will($this->returnValue(true));
$s->expects($this->once())
->method('isSeekable')
->will($this->returnValue(true));
$s->expects($this->once())
->method('rewind')
->will($this->throwException(new \RuntimeException()));
$a->addStream($s);
$a->seek(10);
}
public function testSeeksToPositionByReading()
{
$a = new AppendStream([
Psr7\stream_for('foo'),
Psr7\stream_for('bar'),
Psr7\stream_for('baz'),
]);
$a->seek(3);
$this->assertEquals(3, $a->tell());
$this->assertEquals('bar', $a->read(3));
$a->seek(6);
$this->assertEquals(6, $a->tell());
$this->assertEquals('baz', $a->read(3));
}
public function testDetachesEachStream()
{
$s1 = Psr7\stream_for('foo');
$s2 = Psr7\stream_for('bar');
$a = new AppendStream([$s1, $s2]);
$this->assertSame('foobar', (string) $a);
$a->detach();
$this->assertSame('', (string) $a);
$this->assertSame(0, $a->getSize());
}
public function testClosesEachStream()
{
$s1 = Psr7\stream_for('foo');
$a = new AppendStream([$s1]);
$a->close();
$this->assertSame('', (string) $a);
}
/**
* @expectedExceptionMessage Cannot write to an AppendStream
* @expectedException \RuntimeException
*/
public function testIsNotWritable()
{
$a = new AppendStream([Psr7\stream_for('foo')]);
$this->assertFalse($a->isWritable());
$this->assertTrue($a->isSeekable());
$this->assertTrue($a->isReadable());
$a->write('foo');
}
public function testDoesNotNeedStreams()
{
$a = new AppendStream();
$this->assertEquals('', (string) $a);
}
public function testCanReadFromMultipleStreams()
{
$a = new AppendStream([
Psr7\stream_for('foo'),
Psr7\stream_for('bar'),
Psr7\stream_for('baz'),
]);
$this->assertFalse($a->eof());
$this->assertSame(0, $a->tell());
$this->assertEquals('foo', $a->read(3));
$this->assertEquals('bar', $a->read(3));
$this->assertEquals('baz', $a->read(3));
$this->assertSame('', $a->read(1));
$this->assertTrue($a->eof());
$this->assertSame(9, $a->tell());
$this->assertEquals('foobarbaz', (string) $a);
}
public function testCanDetermineSizeFromMultipleStreams()
{
$a = new AppendStream([
Psr7\stream_for('foo'),
Psr7\stream_for('bar')
]);
$this->assertEquals(6, $a->getSize());
$s = $this->getMockBuilder('Psr\Http\Message\StreamInterface')
->setMethods(['isSeekable', 'isReadable'])
->getMockForAbstractClass();
$s->expects($this->once())
->method('isSeekable')
->will($this->returnValue(null));
$s->expects($this->once())
->method('isReadable')
->will($this->returnValue(true));
$a->addStream($s);
$this->assertNull($a->getSize());
}
public function testCatchesExceptionsWhenCastingToString()
{
$s = $this->getMockBuilder('Psr\Http\Message\StreamInterface')
->setMethods(['isSeekable', 'read', 'isReadable', 'eof'])
->getMockForAbstractClass();
$s->expects($this->once())
->method('isSeekable')
->will($this->returnValue(true));
$s->expects($this->once())
->method('read')
->will($this->throwException(new \RuntimeException('foo')));
$s->expects($this->once())
->method('isReadable')
->will($this->returnValue(true));
$s->expects($this->any())
->method('eof')
->will($this->returnValue(false));
$a = new AppendStream([$s]);
$this->assertFalse($a->eof());
$this->assertSame('', (string) $a);
}
public function testCanDetach()
{
$s = new AppendStream();
$s->detach();
}
public function testReturnsEmptyMetadata()
{
$s = new AppendStream();
$this->assertEquals([], $s->getMetadata());
$this->assertNull($s->getMetadata('foo'));
}
}

View File

@@ -1,63 +0,0 @@
<?php
namespace GuzzleHttp\Tests\Psr7;
use GuzzleHttp\Psr7\BufferStream;
class BufferStreamTest extends \PHPUnit_Framework_TestCase
{
public function testHasMetadata()
{
$b = new BufferStream(10);
$this->assertTrue($b->isReadable());
$this->assertTrue($b->isWritable());
$this->assertFalse($b->isSeekable());
$this->assertEquals(null, $b->getMetadata('foo'));
$this->assertEquals(10, $b->getMetadata('hwm'));
$this->assertEquals([], $b->getMetadata());
}
public function testRemovesReadDataFromBuffer()
{
$b = new BufferStream();
$this->assertEquals(3, $b->write('foo'));
$this->assertEquals(3, $b->getSize());
$this->assertFalse($b->eof());
$this->assertEquals('foo', $b->read(10));
$this->assertTrue($b->eof());
$this->assertEquals('', $b->read(10));
}
/**
* @expectedException \RuntimeException
* @expectedExceptionMessage Cannot determine the position of a BufferStream
*/
public function testCanCastToStringOrGetContents()
{
$b = new BufferStream();
$b->write('foo');
$b->write('baz');
$this->assertEquals('foo', $b->read(3));
$b->write('bar');
$this->assertEquals('bazbar', (string) $b);
$b->tell();
}
public function testDetachClearsBuffer()
{
$b = new BufferStream();
$b->write('foo');
$b->detach();
$this->assertTrue($b->eof());
$this->assertEquals(3, $b->write('abc'));
$this->assertEquals('abc', $b->read(10));
}
public function testExceedingHighwaterMarkReturnsFalseButStillBuffers()
{
$b = new BufferStream(5);
$this->assertEquals(3, $b->write('hi '));
$this->assertFalse($b->write('hello'));
$this->assertEquals('hi hello', (string) $b);
$this->assertEquals(4, $b->write('test'));
}
}

View File

@@ -1,193 +0,0 @@
<?php
namespace GuzzleHttp\Tests\Psr7;
use GuzzleHttp\Psr7;
use GuzzleHttp\Psr7\CachingStream;
/**
* @covers GuzzleHttp\Psr7\CachingStream
*/
class CachingStreamTest extends \PHPUnit_Framework_TestCase
{
/** @var CachingStream */
protected $body;
protected $decorated;
public function setUp()
{
$this->decorated = Psr7\stream_for('testing');
$this->body = new CachingStream($this->decorated);
}
public function tearDown()
{
$this->decorated->close();
$this->body->close();
}
public function testUsesRemoteSizeIfPossible()
{
$body = Psr7\stream_for('test');
$caching = new CachingStream($body);
$this->assertEquals(4, $caching->getSize());
}
public function testReadsUntilCachedToByte()
{
$this->body->seek(5);
$this->assertEquals('n', $this->body->read(1));
$this->body->seek(0);
$this->assertEquals('t', $this->body->read(1));
}
public function testCanSeekNearEndWithSeekEnd()
{
$baseStream = Psr7\stream_for(implode('', range('a', 'z')));
$cached = new CachingStream($baseStream);
$cached->seek(-1, SEEK_END);
$this->assertEquals(25, $baseStream->tell());
$this->assertEquals('z', $cached->read(1));
$this->assertEquals(26, $cached->getSize());
}
public function testCanSeekToEndWithSeekEnd()
{
$baseStream = Psr7\stream_for(implode('', range('a', 'z')));
$cached = new CachingStream($baseStream);
$cached->seek(0, SEEK_END);
$this->assertEquals(26, $baseStream->tell());
$this->assertEquals('', $cached->read(1));
$this->assertEquals(26, $cached->getSize());
}
public function testCanUseSeekEndWithUnknownSize()
{
$baseStream = Psr7\stream_for('testing');
$decorated = Psr7\FnStream::decorate($baseStream, [
'getSize' => function () { return null; }
]);
$cached = new CachingStream($decorated);
$cached->seek(-1, SEEK_END);
$this->assertEquals('g', $cached->read(1));
}
public function testRewindUsesSeek()
{
$a = Psr7\stream_for('foo');
$d = $this->getMockBuilder('GuzzleHttp\Psr7\CachingStream')
->setMethods(array('seek'))
->setConstructorArgs(array($a))
->getMock();
$d->expects($this->once())
->method('seek')
->with(0)
->will($this->returnValue(true));
$d->seek(0);
}
public function testCanSeekToReadBytes()
{
$this->assertEquals('te', $this->body->read(2));
$this->body->seek(0);
$this->assertEquals('test', $this->body->read(4));
$this->assertEquals(4, $this->body->tell());
$this->body->seek(2);
$this->assertEquals(2, $this->body->tell());
$this->body->seek(2, SEEK_CUR);
$this->assertEquals(4, $this->body->tell());
$this->assertEquals('ing', $this->body->read(3));
}
public function testCanSeekToReadBytesWithPartialBodyReturned()
{
$stream = fopen('php://temp', 'r+');
fwrite($stream, 'testing');
fseek($stream, 0);
$this->decorated = $this->getMockBuilder('\GuzzleHttp\Psr7\Stream')
->setConstructorArgs([$stream])
->setMethods(['read'])
->getMock();
$this->decorated->expects($this->exactly(2))
->method('read')
->willReturnCallback(function($length) use ($stream){
return fread($stream, 2);
});
$this->body = new CachingStream($this->decorated);
$this->assertEquals(0, $this->body->tell());
$this->body->seek(4, SEEK_SET);
$this->assertEquals(4, $this->body->tell());
$this->body->seek(0);
$this->assertEquals('test', $this->body->read(4));
}
public function testWritesToBufferStream()
{
$this->body->read(2);
$this->body->write('hi');
$this->body->seek(0);
$this->assertEquals('tehiing', (string) $this->body);
}
public function testSkipsOverwrittenBytes()
{
$decorated = Psr7\stream_for(
implode("\n", array_map(function ($n) {
return str_pad($n, 4, '0', STR_PAD_LEFT);
}, range(0, 25)))
);
$body = new CachingStream($decorated);
$this->assertEquals("0000\n", Psr7\readline($body));
$this->assertEquals("0001\n", Psr7\readline($body));
// Write over part of the body yet to be read, so skip some bytes
$this->assertEquals(5, $body->write("TEST\n"));
$this->assertEquals(5, $this->readAttribute($body, 'skipReadBytes'));
// Read, which skips bytes, then reads
$this->assertEquals("0003\n", Psr7\readline($body));
$this->assertEquals(0, $this->readAttribute($body, 'skipReadBytes'));
$this->assertEquals("0004\n", Psr7\readline($body));
$this->assertEquals("0005\n", Psr7\readline($body));
// Overwrite part of the cached body (so don't skip any bytes)
$body->seek(5);
$this->assertEquals(5, $body->write("ABCD\n"));
$this->assertEquals(0, $this->readAttribute($body, 'skipReadBytes'));
$this->assertEquals("TEST\n", Psr7\readline($body));
$this->assertEquals("0003\n", Psr7\readline($body));
$this->assertEquals("0004\n", Psr7\readline($body));
$this->assertEquals("0005\n", Psr7\readline($body));
$this->assertEquals("0006\n", Psr7\readline($body));
$this->assertEquals(5, $body->write("1234\n"));
$this->assertEquals(5, $this->readAttribute($body, 'skipReadBytes'));
// Seek to 0 and ensure the overwritten bit is replaced
$body->seek(0);
$this->assertEquals("0000\nABCD\nTEST\n0003\n0004\n0005\n0006\n1234\n0008\n0009\n", $body->read(50));
// Ensure that casting it to a string does not include the bit that was overwritten
$this->assertContains("0000\nABCD\nTEST\n0003\n0004\n0005\n0006\n1234\n0008\n0009\n", (string) $body);
}
public function testClosesBothStreams()
{
$s = fopen('php://temp', 'r');
$a = Psr7\stream_for($s);
$d = new CachingStream($a);
$d->close();
$this->assertFalse(is_resource($s));
}
/**
* @expectedException \InvalidArgumentException
*/
public function testEnsuresValidWhence()
{
$this->body->seek(10, -123456);
}
}

View File

@@ -1,26 +0,0 @@
<?php
namespace GuzzleHttp\Tests\Psr7;
use GuzzleHttp\Psr7\BufferStream;
use GuzzleHttp\Psr7\DroppingStream;
class DroppingStreamTest extends \PHPUnit_Framework_TestCase
{
public function testBeginsDroppingWhenSizeExceeded()
{
$stream = new BufferStream();
$drop = new DroppingStream($stream, 5);
$this->assertEquals(3, $drop->write('hel'));
$this->assertEquals(2, $drop->write('lo'));
$this->assertEquals(5, $drop->getSize());
$this->assertEquals('hello', $drop->read(5));
$this->assertEquals(0, $drop->getSize());
$drop->write('12345678910');
$this->assertEquals(5, $stream->getSize());
$this->assertEquals(5, $drop->getSize());
$this->assertEquals('12345', (string) $drop);
$this->assertEquals(0, $drop->getSize());
$drop->write('hello');
$this->assertSame(0, $drop->write('test'));
}
}

View File

@@ -1,90 +0,0 @@
<?php
namespace GuzzleHttp\Tests\Psr7;
use GuzzleHttp\Psr7;
use GuzzleHttp\Psr7\FnStream;
/**
* @covers GuzzleHttp\Psr7\FnStream
*/
class FnStreamTest extends \PHPUnit_Framework_TestCase
{
/**
* @expectedException \BadMethodCallException
* @expectedExceptionMessage seek() is not implemented in the FnStream
*/
public function testThrowsWhenNotImplemented()
{
(new FnStream([]))->seek(1);
}
public function testProxiesToFunction()
{
$s = new FnStream([
'read' => function ($len) {
$this->assertEquals(3, $len);
return 'foo';
}
]);
$this->assertEquals('foo', $s->read(3));
}
public function testCanCloseOnDestruct()
{
$called = false;
$s = new FnStream([
'close' => function () use (&$called) {
$called = true;
}
]);
unset($s);
$this->assertTrue($called);
}
public function testDoesNotRequireClose()
{
$s = new FnStream([]);
unset($s);
}
public function testDecoratesStream()
{
$a = Psr7\stream_for('foo');
$b = FnStream::decorate($a, []);
$this->assertEquals(3, $b->getSize());
$this->assertEquals($b->isWritable(), true);
$this->assertEquals($b->isReadable(), true);
$this->assertEquals($b->isSeekable(), true);
$this->assertEquals($b->read(3), 'foo');
$this->assertEquals($b->tell(), 3);
$this->assertEquals($a->tell(), 3);
$this->assertSame('', $a->read(1));
$this->assertEquals($b->eof(), true);
$this->assertEquals($a->eof(), true);
$b->seek(0);
$this->assertEquals('foo', (string) $b);
$b->seek(0);
$this->assertEquals('foo', $b->getContents());
$this->assertEquals($a->getMetadata(), $b->getMetadata());
$b->seek(0, SEEK_END);
$b->write('bar');
$this->assertEquals('foobar', (string) $b);
$this->assertInternalType('resource', $b->detach());
$b->close();
}
public function testDecoratesWithCustomizations()
{
$called = false;
$a = Psr7\stream_for('foo');
$b = FnStream::decorate($a, [
'read' => function ($len) use (&$called, $a) {
$called = true;
return $a->read($len);
}
]);
$this->assertEquals('foo', $b->read(3));
$this->assertTrue($called);
}
}

View File

@@ -1,619 +0,0 @@
<?php
namespace GuzzleHttp\Tests\Psr7;
use GuzzleHttp\Psr7;
use GuzzleHttp\Psr7\FnStream;
use GuzzleHttp\Psr7\NoSeekStream;
class FunctionsTest extends \PHPUnit_Framework_TestCase
{
public function testCopiesToString()
{
$s = Psr7\stream_for('foobaz');
$this->assertEquals('foobaz', Psr7\copy_to_string($s));
$s->seek(0);
$this->assertEquals('foo', Psr7\copy_to_string($s, 3));
$this->assertEquals('baz', Psr7\copy_to_string($s, 3));
$this->assertEquals('', Psr7\copy_to_string($s));
}
public function testCopiesToStringStopsWhenReadFails()
{
$s1 = Psr7\stream_for('foobaz');
$s1 = FnStream::decorate($s1, [
'read' => function () { return ''; }
]);
$result = Psr7\copy_to_string($s1);
$this->assertEquals('', $result);
}
public function testCopiesToStream()
{
$s1 = Psr7\stream_for('foobaz');
$s2 = Psr7\stream_for('');
Psr7\copy_to_stream($s1, $s2);
$this->assertEquals('foobaz', (string) $s2);
$s2 = Psr7\stream_for('');
$s1->seek(0);
Psr7\copy_to_stream($s1, $s2, 3);
$this->assertEquals('foo', (string) $s2);
Psr7\copy_to_stream($s1, $s2, 3);
$this->assertEquals('foobaz', (string) $s2);
}
public function testStopsCopyToStreamWhenWriteFails()
{
$s1 = Psr7\stream_for('foobaz');
$s2 = Psr7\stream_for('');
$s2 = FnStream::decorate($s2, ['write' => function () { return 0; }]);
Psr7\copy_to_stream($s1, $s2);
$this->assertEquals('', (string) $s2);
}
public function testStopsCopyToSteamWhenWriteFailsWithMaxLen()
{
$s1 = Psr7\stream_for('foobaz');
$s2 = Psr7\stream_for('');
$s2 = FnStream::decorate($s2, ['write' => function () { return 0; }]);
Psr7\copy_to_stream($s1, $s2, 10);
$this->assertEquals('', (string) $s2);
}
public function testStopsCopyToSteamWhenReadFailsWithMaxLen()
{
$s1 = Psr7\stream_for('foobaz');
$s1 = FnStream::decorate($s1, ['read' => function () { return ''; }]);
$s2 = Psr7\stream_for('');
Psr7\copy_to_stream($s1, $s2, 10);
$this->assertEquals('', (string) $s2);
}
public function testReadsLines()
{
$s = Psr7\stream_for("foo\nbaz\nbar");
$this->assertEquals("foo\n", Psr7\readline($s));
$this->assertEquals("baz\n", Psr7\readline($s));
$this->assertEquals("bar", Psr7\readline($s));
}
public function testReadsLinesUpToMaxLength()
{
$s = Psr7\stream_for("12345\n");
$this->assertEquals("123", Psr7\readline($s, 4));
$this->assertEquals("45\n", Psr7\readline($s));
}
public function testReadsLineUntilFalseReturnedFromRead()
{
$s = $this->getMockBuilder('GuzzleHttp\Psr7\Stream')
->setMethods(['read', 'eof'])
->disableOriginalConstructor()
->getMock();
$s->expects($this->exactly(2))
->method('read')
->will($this->returnCallback(function () {
static $c = false;
if ($c) {
return false;
}
$c = true;
return 'h';
}));
$s->expects($this->exactly(2))
->method('eof')
->will($this->returnValue(false));
$this->assertEquals("h", Psr7\readline($s));
}
public function testCalculatesHash()
{
$s = Psr7\stream_for('foobazbar');
$this->assertEquals(md5('foobazbar'), Psr7\hash($s, 'md5'));
}
/**
* @expectedException \RuntimeException
*/
public function testCalculatesHashThrowsWhenSeekFails()
{
$s = new NoSeekStream(Psr7\stream_for('foobazbar'));
$s->read(2);
Psr7\hash($s, 'md5');
}
public function testCalculatesHashSeeksToOriginalPosition()
{
$s = Psr7\stream_for('foobazbar');
$s->seek(4);
$this->assertEquals(md5('foobazbar'), Psr7\hash($s, 'md5'));
$this->assertEquals(4, $s->tell());
}
public function testOpensFilesSuccessfully()
{
$r = Psr7\try_fopen(__FILE__, 'r');
$this->assertInternalType('resource', $r);
fclose($r);
}
/**
* @expectedException \RuntimeException
* @expectedExceptionMessage Unable to open /path/to/does/not/exist using mode r
*/
public function testThrowsExceptionNotWarning()
{
Psr7\try_fopen('/path/to/does/not/exist', 'r');
}
public function parseQueryProvider()
{
return [
// Does not need to parse when the string is empty
['', []],
// Can parse mult-values items
['q=a&q=b', ['q' => ['a', 'b']]],
// Can parse multi-valued items that use numeric indices
['q[0]=a&q[1]=b', ['q[0]' => 'a', 'q[1]' => 'b']],
// Can parse duplicates and does not include numeric indices
['q[]=a&q[]=b', ['q[]' => ['a', 'b']]],
// Ensures that the value of "q" is an array even though one value
['q[]=a', ['q[]' => 'a']],
// Does not modify "." to "_" like PHP's parse_str()
['q.a=a&q.b=b', ['q.a' => 'a', 'q.b' => 'b']],
// Can decode %20 to " "
['q%20a=a%20b', ['q a' => 'a b']],
// Can parse funky strings with no values by assigning each to null
['q&a', ['q' => null, 'a' => null]],
// Does not strip trailing equal signs
['data=abc=', ['data' => 'abc=']],
// Can store duplicates without affecting other values
['foo=a&foo=b&?µ=c', ['foo' => ['a', 'b'], '?µ' => 'c']],
// Sets value to null when no "=" is present
['foo', ['foo' => null]],
// Preserves "0" keys.
['0', ['0' => null]],
// Sets the value to an empty string when "=" is present
['0=', ['0' => '']],
// Preserves falsey keys
['var=0', ['var' => '0']],
['a[b][c]=1&a[b][c]=2', ['a[b][c]' => ['1', '2']]],
['a[b]=c&a[d]=e', ['a[b]' => 'c', 'a[d]' => 'e']],
// Ensure it doesn't leave things behind with repeated values
// Can parse mult-values items
['q=a&q=b&q=c', ['q' => ['a', 'b', 'c']]],
];
}
/**
* @dataProvider parseQueryProvider
*/
public function testParsesQueries($input, $output)
{
$result = Psr7\parse_query($input);
$this->assertSame($output, $result);
}
public function testDoesNotDecode()
{
$str = 'foo%20=bar';
$data = Psr7\parse_query($str, false);
$this->assertEquals(['foo%20' => 'bar'], $data);
}
/**
* @dataProvider parseQueryProvider
*/
public function testParsesAndBuildsQueries($input, $output)
{
$result = Psr7\parse_query($input, false);
$this->assertSame($input, Psr7\build_query($result, false));
}
public function testEncodesWithRfc1738()
{
$str = Psr7\build_query(['foo bar' => 'baz+'], PHP_QUERY_RFC1738);
$this->assertEquals('foo+bar=baz%2B', $str);
}
public function testEncodesWithRfc3986()
{
$str = Psr7\build_query(['foo bar' => 'baz+'], PHP_QUERY_RFC3986);
$this->assertEquals('foo%20bar=baz%2B', $str);
}
public function testDoesNotEncode()
{
$str = Psr7\build_query(['foo bar' => 'baz+'], false);
$this->assertEquals('foo bar=baz+', $str);
}
public function testCanControlDecodingType()
{
$result = Psr7\parse_query('var=foo+bar', PHP_QUERY_RFC3986);
$this->assertEquals('foo+bar', $result['var']);
$result = Psr7\parse_query('var=foo+bar', PHP_QUERY_RFC1738);
$this->assertEquals('foo bar', $result['var']);
}
public function testParsesRequestMessages()
{
$req = "GET /abc HTTP/1.0\r\nHost: foo.com\r\nFoo: Bar\r\nBaz: Bam\r\nBaz: Qux\r\n\r\nTest";
$request = Psr7\parse_request($req);
$this->assertEquals('GET', $request->getMethod());
$this->assertEquals('/abc', $request->getRequestTarget());
$this->assertEquals('1.0', $request->getProtocolVersion());
$this->assertEquals('foo.com', $request->getHeaderLine('Host'));
$this->assertEquals('Bar', $request->getHeaderLine('Foo'));
$this->assertEquals('Bam, Qux', $request->getHeaderLine('Baz'));
$this->assertEquals('Test', (string) $request->getBody());
$this->assertEquals('http://foo.com/abc', (string) $request->getUri());
}
public function testParsesRequestMessagesWithHttpsScheme()
{
$req = "PUT /abc?baz=bar HTTP/1.1\r\nHost: foo.com:443\r\n\r\n";
$request = Psr7\parse_request($req);
$this->assertEquals('PUT', $request->getMethod());
$this->assertEquals('/abc?baz=bar', $request->getRequestTarget());
$this->assertEquals('1.1', $request->getProtocolVersion());
$this->assertEquals('foo.com:443', $request->getHeaderLine('Host'));
$this->assertEquals('', (string) $request->getBody());
$this->assertEquals('https://foo.com/abc?baz=bar', (string) $request->getUri());
}
public function testParsesRequestMessagesWithUriWhenHostIsNotFirst()
{
$req = "PUT / HTTP/1.1\r\nFoo: Bar\r\nHost: foo.com\r\n\r\n";
$request = Psr7\parse_request($req);
$this->assertEquals('PUT', $request->getMethod());
$this->assertEquals('/', $request->getRequestTarget());
$this->assertEquals('http://foo.com/', (string) $request->getUri());
}
public function testParsesRequestMessagesWithFullUri()
{
$req = "GET https://www.google.com:443/search?q=foobar HTTP/1.1\r\nHost: www.google.com\r\n\r\n";
$request = Psr7\parse_request($req);
$this->assertEquals('GET', $request->getMethod());
$this->assertEquals('https://www.google.com:443/search?q=foobar', $request->getRequestTarget());
$this->assertEquals('1.1', $request->getProtocolVersion());
$this->assertEquals('www.google.com', $request->getHeaderLine('Host'));
$this->assertEquals('', (string) $request->getBody());
$this->assertEquals('https://www.google.com/search?q=foobar', (string) $request->getUri());
}
public function testParsesRequestMessagesWithCustomMethod()
{
$req = "GET_DATA / HTTP/1.1\r\nFoo: Bar\r\nHost: foo.com\r\n\r\n";
$request = Psr7\parse_request($req);
$this->assertEquals('GET_DATA', $request->getMethod());
}
/**
* @expectedException \InvalidArgumentException
*/
public function testValidatesRequestMessages()
{
Psr7\parse_request("HTTP/1.1 200 OK\r\n\r\n");
}
public function testParsesResponseMessages()
{
$res = "HTTP/1.0 200 OK\r\nFoo: Bar\r\nBaz: Bam\r\nBaz: Qux\r\n\r\nTest";
$response = Psr7\parse_response($res);
$this->assertEquals(200, $response->getStatusCode());
$this->assertEquals('OK', $response->getReasonPhrase());
$this->assertEquals('1.0', $response->getProtocolVersion());
$this->assertEquals('Bar', $response->getHeaderLine('Foo'));
$this->assertEquals('Bam, Qux', $response->getHeaderLine('Baz'));
$this->assertEquals('Test', (string) $response->getBody());
}
/**
* @expectedException \InvalidArgumentException
*/
public function testValidatesResponseMessages()
{
Psr7\parse_response("GET / HTTP/1.1\r\n\r\n");
}
public function testDetermineMimetype()
{
$this->assertNull(Psr7\mimetype_from_extension('not-a-real-extension'));
$this->assertEquals(
'application/json',
Psr7\mimetype_from_extension('json')
);
$this->assertEquals(
'image/jpeg',
Psr7\mimetype_from_filename('/tmp/images/IMG034821.JPEG')
);
}
public function testCreatesUriForValue()
{
$this->assertInstanceOf('GuzzleHttp\Psr7\Uri', Psr7\uri_for('/foo'));
$this->assertInstanceOf(
'GuzzleHttp\Psr7\Uri',
Psr7\uri_for(new Psr7\Uri('/foo'))
);
}
/**
* @expectedException \InvalidArgumentException
*/
public function testValidatesUri()
{
Psr7\uri_for([]);
}
public function testKeepsPositionOfResource()
{
$h = fopen(__FILE__, 'r');
fseek($h, 10);
$stream = Psr7\stream_for($h);
$this->assertEquals(10, $stream->tell());
$stream->close();
}
public function testCreatesWithFactory()
{
$stream = Psr7\stream_for('foo');
$this->assertInstanceOf('GuzzleHttp\Psr7\Stream', $stream);
$this->assertEquals('foo', $stream->getContents());
$stream->close();
}
public function testFactoryCreatesFromEmptyString()
{
$s = Psr7\stream_for();
$this->assertInstanceOf('GuzzleHttp\Psr7\Stream', $s);
}
public function testFactoryCreatesFromNull()
{
$s = Psr7\stream_for(null);
$this->assertInstanceOf('GuzzleHttp\Psr7\Stream', $s);
}
public function testFactoryCreatesFromResource()
{
$r = fopen(__FILE__, 'r');
$s = Psr7\stream_for($r);
$this->assertInstanceOf('GuzzleHttp\Psr7\Stream', $s);
$this->assertSame(file_get_contents(__FILE__), (string) $s);
}
public function testFactoryCreatesFromObjectWithToString()
{
$r = new HasToString();
$s = Psr7\stream_for($r);
$this->assertInstanceOf('GuzzleHttp\Psr7\Stream', $s);
$this->assertEquals('foo', (string) $s);
}
public function testCreatePassesThrough()
{
$s = Psr7\stream_for('foo');
$this->assertSame($s, Psr7\stream_for($s));
}
/**
* @expectedException \InvalidArgumentException
*/
public function testThrowsExceptionForUnknown()
{
Psr7\stream_for(new \stdClass());
}
public function testReturnsCustomMetadata()
{
$s = Psr7\stream_for('foo', ['metadata' => ['hwm' => 3]]);
$this->assertEquals(3, $s->getMetadata('hwm'));
$this->assertArrayHasKey('hwm', $s->getMetadata());
}
public function testCanSetSize()
{
$s = Psr7\stream_for('', ['size' => 10]);
$this->assertEquals(10, $s->getSize());
}
public function testCanCreateIteratorBasedStream()
{
$a = new \ArrayIterator(['foo', 'bar', '123']);
$p = Psr7\stream_for($a);
$this->assertInstanceOf('GuzzleHttp\Psr7\PumpStream', $p);
$this->assertEquals('foo', $p->read(3));
$this->assertFalse($p->eof());
$this->assertEquals('b', $p->read(1));
$this->assertEquals('a', $p->read(1));
$this->assertEquals('r12', $p->read(3));
$this->assertFalse($p->eof());
$this->assertEquals('3', $p->getContents());
$this->assertTrue($p->eof());
$this->assertEquals(9, $p->tell());
}
public function testConvertsRequestsToStrings()
{
$request = new Psr7\Request('PUT', 'http://foo.com/hi?123', [
'Baz' => 'bar',
'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",
Psr7\str($request)
);
}
public function testConvertsResponsesToStrings()
{
$response = new Psr7\Response(200, [
'Baz' => 'bar',
'Qux' => 'ipsum'
], 'hello', '1.0', 'FOO');
$this->assertEquals(
"HTTP/1.0 200 FOO\r\nBaz: bar\r\nQux: ipsum\r\n\r\nhello",
Psr7\str($response)
);
}
public function parseParamsProvider()
{
$res1 = array(
array(
'<http:/.../front.jpeg>',
'rel' => 'front',
'type' => 'image/jpeg',
),
array(
'<http://.../back.jpeg>',
'rel' => 'back',
'type' => 'image/jpeg',
),
);
return array(
array(
'<http:/.../front.jpeg>; rel="front"; type="image/jpeg", <http://.../back.jpeg>; rel=back; type="image/jpeg"',
$res1
),
array(
'<http:/.../front.jpeg>; rel="front"; type="image/jpeg",<http://.../back.jpeg>; rel=back; type="image/jpeg"',
$res1
),
array(
'foo="baz"; bar=123, boo, test="123", foobar="foo;bar"',
array(
array('foo' => 'baz', 'bar' => '123'),
array('boo'),
array('test' => '123'),
array('foobar' => 'foo;bar')
)
),
array(
'<http://.../side.jpeg?test=1>; rel="side"; type="image/jpeg",<http://.../side.jpeg?test=2>; rel=side; type="image/jpeg"',
array(
array('<http://.../side.jpeg?test=1>', 'rel' => 'side', 'type' => 'image/jpeg'),
array('<http://.../side.jpeg?test=2>', 'rel' => 'side', 'type' => 'image/jpeg')
)
),
array(
'',
array()
)
);
}
/**
* @dataProvider parseParamsProvider
*/
public function testParseParams($header, $result)
{
$this->assertEquals($result, Psr7\parse_header($header));
}
public function testParsesArrayHeaders()
{
$header = ['a, b', 'c', 'd, e'];
$this->assertEquals(['a', 'b', 'c', 'd', 'e'], Psr7\normalize_header($header));
}
public function testRewindsBody()
{
$body = Psr7\stream_for('abc');
$res = new Psr7\Response(200, [], $body);
Psr7\rewind_body($res);
$this->assertEquals(0, $body->tell());
$body->rewind(1);
Psr7\rewind_body($res);
$this->assertEquals(0, $body->tell());
}
/**
* @expectedException \RuntimeException
*/
public function testThrowsWhenBodyCannotBeRewound()
{
$body = Psr7\stream_for('abc');
$body->read(1);
$body = FnStream::decorate($body, [
'rewind' => function () { throw new \RuntimeException('a'); }
]);
$res = new Psr7\Response(200, [], $body);
Psr7\rewind_body($res);
}
public function testCanModifyRequestWithUri()
{
$r1 = new Psr7\Request('GET', 'http://foo.com');
$r2 = Psr7\modify_request($r1, [
'uri' => new Psr7\Uri('http://www.foo.com')
]);
$this->assertEquals('http://www.foo.com', (string) $r2->getUri());
$this->assertEquals('www.foo.com', (string) $r2->getHeaderLine('host'));
}
public function testCanModifyRequestWithUriAndPort()
{
$r1 = new Psr7\Request('GET', 'http://foo.com:8000');
$r2 = Psr7\modify_request($r1, [
'uri' => new Psr7\Uri('http://www.foo.com:8000')
]);
$this->assertEquals('http://www.foo.com:8000', (string) $r2->getUri());
$this->assertEquals('www.foo.com:8000', (string) $r2->getHeaderLine('host'));
}
public function testCanModifyRequestWithCaseInsensitiveHeader()
{
$r1 = new Psr7\Request('GET', 'http://foo.com', ['User-Agent' => 'foo']);
$r2 = Psr7\modify_request($r1, ['set_headers' => ['User-agent' => 'bar']]);
$this->assertEquals('bar', $r2->getHeaderLine('User-Agent'));
$this->assertEquals('bar', $r2->getHeaderLine('User-agent'));
}
public function testReturnsAsIsWhenNoChanges()
{
$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()
{
$r1 = new Psr7\Request('GET', 'http://foo.com');
$r2 = Psr7\modify_request($r1, ['set_headers' => ['foo' => 'bar']]);
$this->assertNotSame($r1, $r2);
$this->assertEquals('bar', $r2->getHeaderLine('foo'));
}
public function testRemovesHeadersFromMessage()
{
$r1 = new Psr7\Request('GET', 'http://foo.com', ['foo' => 'bar']);
$r2 = Psr7\modify_request($r1, ['remove_headers' => ['foo']]);
$this->assertNotSame($r1, $r2);
$this->assertFalse($r2->hasHeader('foo'));
}
public function testAddsQueryToUri()
{
$r1 = new Psr7\Request('GET', 'http://foo.com');
$r2 = Psr7\modify_request($r1, ['query' => 'foo=bar']);
$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,39 +0,0 @@
<?php
namespace GuzzleHttp\Tests\Psr7;
use GuzzleHttp\Psr7;
use GuzzleHttp\Psr7\InflateStream;
class InflateStreamtest extends \PHPUnit_Framework_TestCase
{
public function testInflatesStreams()
{
$content = gzencode('test');
$a = Psr7\stream_for($content);
$b = new InflateStream($a);
$this->assertEquals('test', (string) $b);
}
public function testInflatesStreamsWithFilename()
{
$content = $this->getGzipStringWithFilename('test');
$a = Psr7\stream_for($content);
$b = new InflateStream($a);
$this->assertEquals('test', (string) $b);
}
private function getGzipStringWithFilename($original_string)
{
$gzipped = bin2hex(gzencode($original_string));
$header = substr($gzipped, 0, 20);
// set FNAME flag
$header[6]=0;
$header[7]=8;
// make a dummy filename
$filename = "64756d6d7900";
$rest = substr($gzipped, 20);
return hex2bin($header . $filename . $rest);
}
}

View File

@@ -1,64 +0,0 @@
<?php
namespace GuzzleHttp\Tests\Psr7;
use GuzzleHttp\Psr7\LazyOpenStream;
class LazyOpenStreamTest extends \PHPUnit_Framework_TestCase
{
private $fname;
public function setup()
{
$this->fname = tempnam('/tmp', 'tfile');
if (file_exists($this->fname)) {
unlink($this->fname);
}
}
public function tearDown()
{
if (file_exists($this->fname)) {
unlink($this->fname);
}
}
public function testOpensLazily()
{
$l = new LazyOpenStream($this->fname, 'w+');
$l->write('foo');
$this->assertInternalType('array', $l->getMetadata());
$this->assertFileExists($this->fname);
$this->assertEquals('foo', file_get_contents($this->fname));
$this->assertEquals('foo', (string) $l);
}
public function testProxiesToFile()
{
file_put_contents($this->fname, 'foo');
$l = new LazyOpenStream($this->fname, 'r');
$this->assertEquals('foo', $l->read(4));
$this->assertTrue($l->eof());
$this->assertEquals(3, $l->tell());
$this->assertTrue($l->isReadable());
$this->assertTrue($l->isSeekable());
$this->assertFalse($l->isWritable());
$l->seek(1);
$this->assertEquals('oo', $l->getContents());
$this->assertEquals('foo', (string) $l);
$this->assertEquals(3, $l->getSize());
$this->assertInternalType('array', $l->getMetadata());
$l->close();
}
public function testDetachesUnderlyingStream()
{
file_put_contents($this->fname, 'foo');
$l = new LazyOpenStream($this->fname, 'r');
$r = $l->detach();
$this->assertInternalType('resource', $r);
fseek($r, 0);
$this->assertEquals('foo', stream_get_contents($r));
fclose($r);
}
}

View File

@@ -1,166 +0,0 @@
<?php
namespace GuzzleHttp\Tests\Psr7;
use GuzzleHttp\Psr7;
use GuzzleHttp\Psr7\FnStream;
use GuzzleHttp\Psr7\Stream;
use GuzzleHttp\Psr7\LimitStream;
use GuzzleHttp\Psr7\NoSeekStream;
/**
* @covers GuzzleHttp\Psr7\LimitStream
*/
class LimitStreamTest extends \PHPUnit_Framework_TestCase
{
/** @var LimitStream */
protected $body;
/** @var Stream */
protected $decorated;
public function setUp()
{
$this->decorated = Psr7\stream_for(fopen(__FILE__, 'r'));
$this->body = new LimitStream($this->decorated, 10, 3);
}
public function testReturnsSubset()
{
$body = new LimitStream(Psr7\stream_for('foo'), -1, 1);
$this->assertEquals('oo', (string) $body);
$this->assertTrue($body->eof());
$body->seek(0);
$this->assertFalse($body->eof());
$this->assertEquals('oo', $body->read(100));
$this->assertSame('', $body->read(1));
$this->assertTrue($body->eof());
}
public function testReturnsSubsetWhenCastToString()
{
$body = Psr7\stream_for('foo_baz_bar');
$limited = new LimitStream($body, 3, 4);
$this->assertEquals('baz', (string) $limited);
}
/**
* @expectedException \RuntimeException
* @expectedExceptionMessage Unable to seek to stream position 10 with whence 0
*/
public function testEnsuresPositionCanBeekSeekedTo()
{
new LimitStream(Psr7\stream_for(''), 0, 10);
}
public function testReturnsSubsetOfEmptyBodyWhenCastToString()
{
$body = Psr7\stream_for('01234567891234');
$limited = new LimitStream($body, 0, 10);
$this->assertEquals('', (string) $limited);
}
public function testReturnsSpecificSubsetOBodyWhenCastToString()
{
$body = Psr7\stream_for('0123456789abcdef');
$limited = new LimitStream($body, 3, 10);
$this->assertEquals('abc', (string) $limited);
}
public function testSeeksWhenConstructed()
{
$this->assertEquals(0, $this->body->tell());
$this->assertEquals(3, $this->decorated->tell());
}
public function testAllowsBoundedSeek()
{
$this->body->seek(100);
$this->assertEquals(10, $this->body->tell());
$this->assertEquals(13, $this->decorated->tell());
$this->body->seek(0);
$this->assertEquals(0, $this->body->tell());
$this->assertEquals(3, $this->decorated->tell());
try {
$this->body->seek(-10);
$this->fail();
} catch (\RuntimeException $e) {}
$this->assertEquals(0, $this->body->tell());
$this->assertEquals(3, $this->decorated->tell());
$this->body->seek(5);
$this->assertEquals(5, $this->body->tell());
$this->assertEquals(8, $this->decorated->tell());
// Fail
try {
$this->body->seek(1000, SEEK_END);
$this->fail();
} catch (\RuntimeException $e) {}
}
public function testReadsOnlySubsetOfData()
{
$data = $this->body->read(100);
$this->assertEquals(10, strlen($data));
$this->assertSame('', $this->body->read(1000));
$this->body->setOffset(10);
$newData = $this->body->read(100);
$this->assertEquals(10, strlen($newData));
$this->assertNotSame($data, $newData);
}
/**
* @expectedException \RuntimeException
* @expectedExceptionMessage Could not seek to stream offset 2
*/
public function testThrowsWhenCurrentGreaterThanOffsetSeek()
{
$a = Psr7\stream_for('foo_bar');
$b = new NoSeekStream($a);
$c = new LimitStream($b);
$a->getContents();
$c->setOffset(2);
}
public function testCanGetContentsWithoutSeeking()
{
$a = Psr7\stream_for('foo_bar');
$b = new NoSeekStream($a);
$c = new LimitStream($b);
$this->assertEquals('foo_bar', $c->getContents());
}
public function testClaimsConsumedWhenReadLimitIsReached()
{
$this->assertFalse($this->body->eof());
$this->body->read(1000);
$this->assertTrue($this->body->eof());
}
public function testContentLengthIsBounded()
{
$this->assertEquals(10, $this->body->getSize());
}
public function testGetContentsIsBasedOnSubset()
{
$body = new LimitStream(Psr7\stream_for('foobazbar'), 3, 3);
$this->assertEquals('baz', $body->getContents());
}
public function testReturnsNullIfSizeCannotBeDetermined()
{
$a = new FnStream([
'getSize' => function () { return null; },
'tell' => function () { return 0; },
]);
$b = new LimitStream($a);
$this->assertNull($b->getSize());
}
public function testLengthLessOffsetWhenNoLimitSize()
{
$a = Psr7\stream_for('foo_bar');
$b = new LimitStream($a, -1, 4);
$this->assertEquals(3, $b->getSize());
}
}

View File

@@ -1,242 +0,0 @@
<?php
namespace GuzzleHttp\Tests;
use GuzzleHttp\Psr7;
use GuzzleHttp\Psr7\MultipartStream;
class MultipartStreamTest extends \PHPUnit_Framework_TestCase
{
public function testCreatesDefaultBoundary()
{
$b = new MultipartStream();
$this->assertNotEmpty($b->getBoundary());
}
public function testCanProvideBoundary()
{
$b = new MultipartStream([], 'foo');
$this->assertEquals('foo', $b->getBoundary());
}
public function testIsNotWritable()
{
$b = new MultipartStream();
$this->assertFalse($b->isWritable());
}
public function testCanCreateEmptyStream()
{
$b = new MultipartStream();
$boundary = $b->getBoundary();
$this->assertSame("--{$boundary}--\r\n", $b->getContents());
$this->assertSame(strlen($boundary) + 6, $b->getSize());
}
/**
* @expectedException \InvalidArgumentException
*/
public function testValidatesFilesArrayElement()
{
new MultipartStream([['foo' => 'bar']]);
}
/**
* @expectedException \InvalidArgumentException
*/
public function testEnsuresFileHasName()
{
new MultipartStream([['contents' => 'bar']]);
}
public function testSerializesFields()
{
$b = new MultipartStream([
[
'name' => 'foo',
'contents' => 'bar'
],
[
'name' => 'baz',
'contents' => 'bam'
]
], 'boundary');
$this->assertEquals(
"--boundary\r\nContent-Disposition: form-data; name=\"foo\"\r\nContent-Length: 3\r\n\r\n"
. "bar\r\n--boundary\r\nContent-Disposition: form-data; name=\"baz\"\r\nContent-Length: 3"
. "\r\n\r\nbam\r\n--boundary--\r\n", (string) $b);
}
public function testSerializesNonStringFields()
{
$b = new MultipartStream([
[
'name' => 'int',
'contents' => (int) 1
],
[
'name' => 'bool',
'contents' => (boolean) false
],
[
'name' => 'bool2',
'contents' => (boolean) true
],
[
'name' => 'float',
'contents' => (float) 1.1
]
], 'boundary');
$this->assertEquals(
"--boundary\r\nContent-Disposition: form-data; name=\"int\"\r\nContent-Length: 1\r\n\r\n"
. "1\r\n--boundary\r\nContent-Disposition: form-data; name=\"bool\"\r\n\r\n\r\n--boundary"
. "\r\nContent-Disposition: form-data; name=\"bool2\"\r\nContent-Length: 1\r\n\r\n"
. "1\r\n--boundary\r\nContent-Disposition: form-data; name=\"float\"\r\nContent-Length: 3"
. "\r\n\r\n1.1\r\n--boundary--\r\n", (string) $b);
}
public function testSerializesFiles()
{
$f1 = Psr7\FnStream::decorate(Psr7\stream_for('foo'), [
'getMetadata' => function () {
return '/foo/bar.txt';
}
]);
$f2 = Psr7\FnStream::decorate(Psr7\stream_for('baz'), [
'getMetadata' => function () {
return '/foo/baz.jpg';
}
]);
$f3 = Psr7\FnStream::decorate(Psr7\stream_for('bar'), [
'getMetadata' => function () {
return '/foo/bar.gif';
}
]);
$b = new MultipartStream([
[
'name' => 'foo',
'contents' => $f1
],
[
'name' => 'qux',
'contents' => $f2
],
[
'name' => 'qux',
'contents' => $f3
],
], 'boundary');
$expected = <<<EOT
--boundary
Content-Disposition: form-data; name="foo"; filename="bar.txt"
Content-Length: 3
Content-Type: text/plain
foo
--boundary
Content-Disposition: form-data; name="qux"; filename="baz.jpg"
Content-Length: 3
Content-Type: image/jpeg
baz
--boundary
Content-Disposition: form-data; name="qux"; filename="bar.gif"
Content-Length: 3
Content-Type: image/gif
bar
--boundary--
EOT;
$this->assertEquals($expected, str_replace("\r", '', $b));
}
public function testSerializesFilesWithCustomHeaders()
{
$f1 = Psr7\FnStream::decorate(Psr7\stream_for('foo'), [
'getMetadata' => function () {
return '/foo/bar.txt';
}
]);
$b = new MultipartStream([
[
'name' => 'foo',
'contents' => $f1,
'headers' => [
'x-foo' => 'bar',
'content-disposition' => 'custom'
]
]
], 'boundary');
$expected = <<<EOT
--boundary
x-foo: bar
content-disposition: custom
Content-Length: 3
Content-Type: text/plain
foo
--boundary--
EOT;
$this->assertEquals($expected, str_replace("\r", '', $b));
}
public function testSerializesFilesWithCustomHeadersAndMultipleValues()
{
$f1 = Psr7\FnStream::decorate(Psr7\stream_for('foo'), [
'getMetadata' => function () {
return '/foo/bar.txt';
}
]);
$f2 = Psr7\FnStream::decorate(Psr7\stream_for('baz'), [
'getMetadata' => function () {
return '/foo/baz.jpg';
}
]);
$b = new MultipartStream([
[
'name' => 'foo',
'contents' => $f1,
'headers' => [
'x-foo' => 'bar',
'content-disposition' => 'custom'
]
],
[
'name' => 'foo',
'contents' => $f2,
'headers' => ['cOntenT-Type' => 'custom'],
]
], 'boundary');
$expected = <<<EOT
--boundary
x-foo: bar
content-disposition: custom
Content-Length: 3
Content-Type: text/plain
foo
--boundary
cOntenT-Type: custom
Content-Disposition: form-data; name="foo"; filename="baz.jpg"
Content-Length: 3
baz
--boundary--
EOT;
$this->assertEquals($expected, str_replace("\r", '', $b));
}
}

View File

@@ -1,40 +0,0 @@
<?php
namespace GuzzleHttp\Tests\Psr7;
use GuzzleHttp\Psr7;
use GuzzleHttp\Psr7\NoSeekStream;
/**
* @covers GuzzleHttp\Psr7\NoSeekStream
* @covers GuzzleHttp\Psr7\StreamDecoratorTrait
*/
class NoSeekStreamTest extends \PHPUnit_Framework_TestCase
{
/**
* @expectedException \RuntimeException
* @expectedExceptionMessage Cannot seek a NoSeekStream
*/
public function testCannotSeek()
{
$s = $this->getMockBuilder('Psr\Http\Message\StreamInterface')
->setMethods(['isSeekable', 'seek'])
->getMockForAbstractClass();
$s->expects($this->never())->method('seek');
$s->expects($this->never())->method('isSeekable');
$wrapped = new NoSeekStream($s);
$this->assertFalse($wrapped->isSeekable());
$wrapped->seek(2);
}
/**
* @expectedException \RuntimeException
* @expectedExceptionMessage Cannot write to a non-writable stream
*/
public function testHandlesClose()
{
$s = Psr7\stream_for('foo');
$wrapped = new NoSeekStream($s);
$wrapped->close();
$wrapped->write('foo');
}
}

View File

@@ -1,72 +0,0 @@
<?php
namespace GuzzleHttp\Tests\Psr7;
use GuzzleHttp\Psr7\LimitStream;
use GuzzleHttp\Psr7\PumpStream;
use GuzzleHttp\Psr7;
class PumpStreamTest extends \PHPUnit_Framework_TestCase
{
public function testHasMetadataAndSize()
{
$p = new PumpStream(function () {}, [
'metadata' => ['foo' => 'bar'],
'size' => 100
]);
$this->assertEquals('bar', $p->getMetadata('foo'));
$this->assertEquals(['foo' => 'bar'], $p->getMetadata());
$this->assertEquals(100, $p->getSize());
}
public function testCanReadFromCallable()
{
$p = Psr7\stream_for(function ($size) {
return 'a';
});
$this->assertEquals('a', $p->read(1));
$this->assertEquals(1, $p->tell());
$this->assertEquals('aaaaa', $p->read(5));
$this->assertEquals(6, $p->tell());
}
public function testStoresExcessDataInBuffer()
{
$called = [];
$p = Psr7\stream_for(function ($size) use (&$called) {
$called[] = $size;
return 'abcdef';
});
$this->assertEquals('a', $p->read(1));
$this->assertEquals('b', $p->read(1));
$this->assertEquals('cdef', $p->read(4));
$this->assertEquals('abcdefabc', $p->read(9));
$this->assertEquals([1, 9, 3], $called);
}
public function testInifiniteStreamWrappedInLimitStream()
{
$p = Psr7\stream_for(function () { return 'a'; });
$s = new LimitStream($p, 5);
$this->assertEquals('aaaaa', (string) $s);
}
public function testDescribesCapabilities()
{
$p = Psr7\stream_for(function () {});
$this->assertTrue($p->isReadable());
$this->assertFalse($p->isSeekable());
$this->assertFalse($p->isWritable());
$this->assertNull($p->getSize());
$this->assertEquals('', $p->getContents());
$this->assertEquals('', (string) $p);
$p->close();
$this->assertEquals('', $p->read(10));
$this->assertTrue($p->eof());
try {
$this->assertFalse($p->write('aa'));
$this->fail();
} catch (\RuntimeException $e) {}
}
}

View File

@@ -1,195 +0,0 @@
<?php
namespace GuzzleHttp\Tests\Psr7;
use GuzzleHttp\Psr7;
use GuzzleHttp\Psr7\Request;
use GuzzleHttp\Psr7\Uri;
/**
* @covers GuzzleHttp\Psr7\Request
*/
class RequestTest extends \PHPUnit_Framework_TestCase
{
public function testRequestUriMayBeString()
{
$r = new Request('GET', '/');
$this->assertEquals('/', (string) $r->getUri());
}
public function testRequestUriMayBeUri()
{
$uri = new Uri('/');
$r = new Request('GET', $uri);
$this->assertSame($uri, $r->getUri());
}
/**
* @expectedException \InvalidArgumentException
*/
public function testValidateRequestUri()
{
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', '/');
$this->assertEquals('GET', $r->getMethod());
}
public function testCapitalizesWithMethod()
{
$r = new Request('GET', '/');
$this->assertEquals('PUT', $r->withMethod('put')->getMethod());
}
public function testWithUri()
{
$r1 = new Request('GET', '/');
$u1 = $r1->getUri();
$u2 = new Uri('http://www.example.com');
$r2 = $r1->withUri($u2);
$this->assertNotSame($r1, $r2);
$this->assertSame($u2, $r2->getUri());
$this->assertSame($u1, $r1->getUri());
}
public function testSameInstanceWhenSameUri()
{
$r1 = new Request('GET', 'http://foo.com');
$r2 = $r1->withUri($r1->getUri());
$this->assertSame($r1, $r2);
}
public function testWithRequestTarget()
{
$r1 = new Request('GET', '/');
$r2 = $r1->withRequestTarget('*');
$this->assertEquals('*', $r2->getRequestTarget());
$this->assertEquals('/', $r1->getRequestTarget());
}
/**
* @expectedException \InvalidArgumentException
*/
public function testRequestTargetDoesNotAllowSpaces()
{
$r1 = new Request('GET', '/');
$r1->withRequestTarget('/foo bar');
}
public function testRequestTargetDefaultsToSlash()
{
$r1 = new Request('GET', '');
$this->assertEquals('/', $r1->getRequestTarget());
$r2 = new Request('GET', '*');
$this->assertEquals('*', $r2->getRequestTarget());
$r3 = new Request('GET', 'http://foo.com/bar baz/');
$this->assertEquals('/bar%20baz/', $r3->getRequestTarget());
}
public function testBuildsRequestTarget()
{
$r1 = new Request('GET', 'http://foo.com/baz?bar=bam');
$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']);
$this->assertEquals([
'Host' => ['foo.com'],
'Foo' => ['Bar']
], $r->getHeaders());
}
public function testCanGetHeaderAsCsv()
{
$r = new Request('GET', 'http://foo.com/baz?bar=bam', [
'Foo' => ['a', 'b', 'c']
]);
$this->assertEquals('a, b, c', $r->getHeaderLine('Foo'));
$this->assertEquals('', $r->getHeaderLine('Bar'));
}
public function testHostIsNotOverwrittenWhenPreservingHost()
{
$r = new Request('GET', 'http://foo.com/baz?bar=bam', ['Host' => 'a.com']);
$this->assertEquals(['Host' => ['a.com']], $r->getHeaders());
$r2 = $r->withUri(new Uri('http://www.foo.com/bar'), true);
$this->assertEquals('a.com', $r2->getHeaderLine('Host'));
}
public function testOverridesHostWithUri()
{
$r = new Request('GET', 'http://foo.com/baz?bar=bam');
$this->assertEquals(['Host' => ['foo.com']], $r->getHeaders());
$r2 = $r->withUri(new Uri('http://www.baz.com/bar'));
$this->assertEquals('www.baz.com', $r2->getHeaderLine('Host'));
}
public function testAggregatesHeaders()
{
$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'));
}
public function testAddsPortToHeader()
{
$r = new Request('GET', 'http://foo.com:8124/bar');
$this->assertEquals('foo.com:8124', $r->getHeaderLine('host'));
}
public function testAddsPortToHeaderAndReplacePreviousPort()
{
$r = new Request('GET', 'http://foo.com:8124/bar');
$r = $r->withUri(new Uri('http://foo.com:8125/bar'));
$this->assertEquals('foo.com:8125', $r->getHeaderLine('host'));
}
}

View File

@@ -1,252 +0,0 @@
<?php
namespace GuzzleHttp\Tests\Psr7;
use GuzzleHttp\Psr7;
use GuzzleHttp\Psr7\Response;
/**
* @covers GuzzleHttp\Psr7\MessageTrait
* @covers GuzzleHttp\Psr7\Response
*/
class ResponseTest extends \PHPUnit_Framework_TestCase
{
public function testDefaultConstructor()
{
$r = new Response();
$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 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 testHeaderValuesAreTrimmed()
{
$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 ");;
foreach ([$r1, $r2, $r3] as $r) {
$this->assertSame(['OWS' => ['Foo']], $r->getHeaders());
$this->assertSame('Foo', $r->getHeaderLine('OWS'));
$this->assertSame(['Foo'], $r->getHeader('OWS'));
}
}
}

View File

@@ -1,532 +0,0 @@
<?php
namespace GuzzleHttp\Tests\Psr7;
use GuzzleHttp\Psr7\ServerRequest;
use GuzzleHttp\Psr7\UploadedFile;
use GuzzleHttp\Psr7\Uri;
/**
* @covers GuzzleHttp\Psr7\ServerRequest
*/
class ServerRequestTest extends \PHPUnit_Framework_TestCase
{
public function dataNormalizeFiles()
{
return [
'Single file' => [
[
'file' => [
'name' => 'MyFile.txt',
'type' => 'text/plain',
'tmp_name' => '/tmp/php/php1h4j1o',
'error' => '0',
'size' => '123'
]
],
[
'file' => new UploadedFile(
'/tmp/php/php1h4j1o',
123,
UPLOAD_ERR_OK,
'MyFile.txt',
'text/plain'
)
]
],
'Empty file' => [
[
'image_file' => [
'name' => '',
'type' => '',
'tmp_name' => '',
'error' => '4',
'size' => '0'
]
],
[
'image_file' => new UploadedFile(
'',
0,
UPLOAD_ERR_NO_FILE,
'',
''
)
]
],
'Already Converted' => [
[
'file' => new UploadedFile(
'/tmp/php/php1h4j1o',
123,
UPLOAD_ERR_OK,
'MyFile.txt',
'text/plain'
)
],
[
'file' => new UploadedFile(
'/tmp/php/php1h4j1o',
123,
UPLOAD_ERR_OK,
'MyFile.txt',
'text/plain'
)
]
],
'Already Converted array' => [
[
'file' => [
new UploadedFile(
'/tmp/php/php1h4j1o',
123,
UPLOAD_ERR_OK,
'MyFile.txt',
'text/plain'
),
new UploadedFile(
'',
0,
UPLOAD_ERR_NO_FILE,
'',
''
)
],
],
[
'file' => [
new UploadedFile(
'/tmp/php/php1h4j1o',
123,
UPLOAD_ERR_OK,
'MyFile.txt',
'text/plain'
),
new UploadedFile(
'',
0,
UPLOAD_ERR_NO_FILE,
'',
''
)
],
]
],
'Multiple files' => [
[
'text_file' => [
'name' => 'MyFile.txt',
'type' => 'text/plain',
'tmp_name' => '/tmp/php/php1h4j1o',
'error' => '0',
'size' => '123'
],
'image_file' => [
'name' => '',
'type' => '',
'tmp_name' => '',
'error' => '4',
'size' => '0'
]
],
[
'text_file' => new UploadedFile(
'/tmp/php/php1h4j1o',
123,
UPLOAD_ERR_OK,
'MyFile.txt',
'text/plain'
),
'image_file' => new UploadedFile(
'',
0,
UPLOAD_ERR_NO_FILE,
'',
''
)
]
],
'Nested files' => [
[
'file' => [
'name' => [
0 => 'MyFile.txt',
1 => 'Image.png',
],
'type' => [
0 => 'text/plain',
1 => 'image/png',
],
'tmp_name' => [
0 => '/tmp/php/hp9hskjhf',
1 => '/tmp/php/php1h4j1o',
],
'error' => [
0 => '0',
1 => '0',
],
'size' => [
0 => '123',
1 => '7349',
],
],
'nested' => [
'name' => [
'other' => 'Flag.txt',
'test' => [
0 => 'Stuff.txt',
1 => '',
],
],
'type' => [
'other' => 'text/plain',
'test' => [
0 => 'text/plain',
1 => '',
],
],
'tmp_name' => [
'other' => '/tmp/php/hp9hskjhf',
'test' => [
0 => '/tmp/php/asifu2gp3',
1 => '',
],
],
'error' => [
'other' => '0',
'test' => [
0 => '0',
1 => '4',
],
],
'size' => [
'other' => '421',
'test' => [
0 => '32',
1 => '0',
]
]
],
],
[
'file' => [
0 => new UploadedFile(
'/tmp/php/hp9hskjhf',
123,
UPLOAD_ERR_OK,
'MyFile.txt',
'text/plain'
),
1 => new UploadedFile(
'/tmp/php/php1h4j1o',
7349,
UPLOAD_ERR_OK,
'Image.png',
'image/png'
),
],
'nested' => [
'other' => new UploadedFile(
'/tmp/php/hp9hskjhf',
421,
UPLOAD_ERR_OK,
'Flag.txt',
'text/plain'
),
'test' => [
0 => new UploadedFile(
'/tmp/php/asifu2gp3',
32,
UPLOAD_ERR_OK,
'Stuff.txt',
'text/plain'
),
1 => new UploadedFile(
'',
0,
UPLOAD_ERR_NO_FILE,
'',
''
),
]
]
]
]
];
}
/**
* @dataProvider dataNormalizeFiles
*/
public function testNormalizeFiles($files, $expected)
{
$result = ServerRequest::normalizeFiles($files);
$this->assertEquals($expected, $result);
}
public function testNormalizeFilesRaisesException()
{
$this->setExpectedException('InvalidArgumentException', 'Invalid value in files specification');
ServerRequest::normalizeFiles(['test' => 'something']);
}
public function dataGetUriFromGlobals()
{
$server = [
'PHP_SELF' => '/blog/article.php',
'GATEWAY_INTERFACE' => 'CGI/1.1',
'SERVER_ADDR' => 'Server IP: 217.112.82.20',
'SERVER_NAME' => 'www.blakesimpson.co.uk',
'SERVER_SOFTWARE' => 'Apache/2.2.15 (Win32) JRun/4.0 PHP/5.2.13',
'SERVER_PROTOCOL' => 'HTTP/1.0',
'REQUEST_METHOD' => 'POST',
'REQUEST_TIME' => 'Request start time: 1280149029',
'QUERY_STRING' => 'id=10&user=foo',
'DOCUMENT_ROOT' => '/path/to/your/server/root/',
'HTTP_ACCEPT' => 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
'HTTP_ACCEPT_CHARSET' => 'ISO-8859-1,utf-8;q=0.7,*;q=0.7',
'HTTP_ACCEPT_ENCODING' => 'gzip,deflate',
'HTTP_ACCEPT_LANGUAGE' => 'en-gb,en;q=0.5',
'HTTP_CONNECTION' => 'keep-alive',
'HTTP_HOST' => 'www.blakesimpson.co.uk',
'HTTP_REFERER' => 'http://previous.url.com',
'HTTP_USER_AGENT' => 'Mozilla/5.0 (Windows; U; Windows NT 6.0; en-GB; rv:1.9.2.6) Gecko/20100625 Firefox/3.6.6 ( .NET CLR 3.5.30729)',
'HTTPS' => '1',
'REMOTE_ADDR' => '193.60.168.69',
'REMOTE_HOST' => 'Client server\'s host name',
'REMOTE_PORT' => '5390',
'SCRIPT_FILENAME' => '/path/to/this/script.php',
'SERVER_ADMIN' => 'webmaster@blakesimpson.co.uk',
'SERVER_PORT' => '80',
'SERVER_SIGNATURE' => 'Version signature: 5.123',
'SCRIPT_NAME' => '/blog/article.php',
'REQUEST_URI' => '/blog/article.php?id=10&user=foo',
];
return [
'Normal request' => [
'http://www.blakesimpson.co.uk/blog/article.php?id=10&user=foo',
$server,
],
'Secure request' => [
'https://www.blakesimpson.co.uk/blog/article.php?id=10&user=foo',
array_merge($server, ['HTTPS' => 'on', 'SERVER_PORT' => '443']),
],
'HTTP_HOST missing' => [
'http://www.blakesimpson.co.uk/blog/article.php?id=10&user=foo',
array_merge($server, ['HTTP_HOST' => null]),
],
'No query String' => [
'http://www.blakesimpson.co.uk/blog/article.php',
array_merge($server, ['REQUEST_URI' => '/blog/article.php', 'QUERY_STRING' => '']),
],
'Different port' => [
'http://www.blakesimpson.co.uk:8324/blog/article.php?id=10&user=foo',
array_merge($server, ['SERVER_PORT' => '8324']),
],
'Empty server variable' => [
'',
[],
],
];
}
/**
* @dataProvider dataGetUriFromGlobals
*/
public function testGetUriFromGlobals($expected, $serverParams)
{
$_SERVER = $serverParams;
$this->assertEquals(new Uri($expected), ServerRequest::getUriFromGlobals());
}
public function testFromGlobals()
{
$_SERVER = [
'PHP_SELF' => '/blog/article.php',
'GATEWAY_INTERFACE' => 'CGI/1.1',
'SERVER_ADDR' => 'Server IP: 217.112.82.20',
'SERVER_NAME' => 'www.blakesimpson.co.uk',
'SERVER_SOFTWARE' => 'Apache/2.2.15 (Win32) JRun/4.0 PHP/5.2.13',
'SERVER_PROTOCOL' => 'HTTP/1.0',
'REQUEST_METHOD' => 'POST',
'REQUEST_TIME' => 'Request start time: 1280149029',
'QUERY_STRING' => 'id=10&user=foo',
'DOCUMENT_ROOT' => '/path/to/your/server/root/',
'HTTP_ACCEPT' => 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
'HTTP_ACCEPT_CHARSET' => 'ISO-8859-1,utf-8;q=0.7,*;q=0.7',
'HTTP_ACCEPT_ENCODING' => 'gzip,deflate',
'HTTP_ACCEPT_LANGUAGE' => 'en-gb,en;q=0.5',
'HTTP_CONNECTION' => 'keep-alive',
'HTTP_HOST' => 'www.blakesimpson.co.uk',
'HTTP_REFERER' => 'http://previous.url.com',
'HTTP_USER_AGENT' => 'Mozilla/5.0 (Windows; U; Windows NT 6.0; en-GB; rv:1.9.2.6) Gecko/20100625 Firefox/3.6.6 ( .NET CLR 3.5.30729)',
'HTTPS' => '1',
'REMOTE_ADDR' => '193.60.168.69',
'REMOTE_HOST' => 'Client server\'s host name',
'REMOTE_PORT' => '5390',
'SCRIPT_FILENAME' => '/path/to/this/script.php',
'SERVER_ADMIN' => 'webmaster@blakesimpson.co.uk',
'SERVER_PORT' => '80',
'SERVER_SIGNATURE' => 'Version signature: 5.123',
'SCRIPT_NAME' => '/blog/article.php',
'REQUEST_URI' => '/blog/article.php?id=10&user=foo',
];
$_COOKIE = [
'logged-in' => 'yes!'
];
$_POST = [
'name' => 'Pesho',
'email' => 'pesho@example.com',
];
$_GET = [
'id' => 10,
'user' => 'foo',
];
$_FILES = [
'file' => [
'name' => 'MyFile.txt',
'type' => 'text/plain',
'tmp_name' => '/tmp/php/php1h4j1o',
'error' => UPLOAD_ERR_OK,
'size' => 123,
]
];
$server = ServerRequest::fromGlobals();
$this->assertEquals('POST', $server->getMethod());
$this->assertEquals(['Host' => ['www.blakesimpson.co.uk']], $server->getHeaders());
$this->assertEquals('', (string) $server->getBody());
$this->assertEquals('1.0', $server->getProtocolVersion());
$this->assertEquals($_COOKIE, $server->getCookieParams());
$this->assertEquals($_POST, $server->getParsedBody());
$this->assertEquals($_GET, $server->getQueryParams());
$this->assertEquals(
new Uri('http://www.blakesimpson.co.uk/blog/article.php?id=10&user=foo'),
$server->getUri()
);
$expectedFiles = [
'file' => new UploadedFile(
'/tmp/php/php1h4j1o',
123,
UPLOAD_ERR_OK,
'MyFile.txt',
'text/plain'
),
];
$this->assertEquals($expectedFiles, $server->getUploadedFiles());
}
public function testUploadedFiles()
{
$request1 = new ServerRequest('GET', '/');
$files = [
'file' => new UploadedFile('test', 123, UPLOAD_ERR_OK)
];
$request2 = $request1->withUploadedFiles($files);
$this->assertNotSame($request2, $request1);
$this->assertSame([], $request1->getUploadedFiles());
$this->assertSame($files, $request2->getUploadedFiles());
}
public function testServerParams()
{
$params = ['name' => 'value'];
$request = new ServerRequest('GET', '/', [], null, '1.1', $params);
$this->assertSame($params, $request->getServerParams());
}
public function testCookieParams()
{
$request1 = new ServerRequest('GET', '/');
$params = ['name' => 'value'];
$request2 = $request1->withCookieParams($params);
$this->assertNotSame($request2, $request1);
$this->assertEmpty($request1->getCookieParams());
$this->assertSame($params, $request2->getCookieParams());
}
public function testQueryParams()
{
$request1 = new ServerRequest('GET', '/');
$params = ['name' => 'value'];
$request2 = $request1->withQueryParams($params);
$this->assertNotSame($request2, $request1);
$this->assertEmpty($request1->getQueryParams());
$this->assertSame($params, $request2->getQueryParams());
}
public function testParsedBody()
{
$request1 = new ServerRequest('GET', '/');
$params = ['name' => 'value'];
$request2 = $request1->withParsedBody($params);
$this->assertNotSame($request2, $request1);
$this->assertEmpty($request1->getParsedBody());
$this->assertSame($params, $request2->getParsedBody());
}
public function testAttributes()
{
$request1 = new ServerRequest('GET', '/');
$request2 = $request1->withAttribute('name', 'value');
$request3 = $request2->withAttribute('other', 'otherValue');
$request4 = $request3->withoutAttribute('other');
$request5 = $request3->withoutAttribute('unknown');
$this->assertNotSame($request2, $request1);
$this->assertNotSame($request3, $request2);
$this->assertNotSame($request4, $request3);
$this->assertNotSame($request5, $request4);
$this->assertEmpty($request1->getAttributes());
$this->assertEmpty($request1->getAttribute('name'));
$this->assertEquals(
'something',
$request1->getAttribute('name', 'something'),
'Should return the default value'
);
$this->assertEquals('value', $request2->getAttribute('name'));
$this->assertEquals(['name' => 'value'], $request2->getAttributes());
$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

@@ -1,137 +0,0 @@
<?php
namespace GuzzleHttp\Tests\Psr7;
use Psr\Http\Message\StreamInterface;
use GuzzleHttp\Psr7;
use GuzzleHttp\Psr7\StreamDecoratorTrait;
class Str implements StreamInterface
{
use StreamDecoratorTrait;
}
/**
* @covers GuzzleHttp\Psr7\StreamDecoratorTrait
*/
class StreamDecoratorTraitTest extends \PHPUnit_Framework_TestCase
{
private $a;
private $b;
private $c;
public function setUp()
{
$this->c = fopen('php://temp', 'r+');
fwrite($this->c, 'foo');
fseek($this->c, 0);
$this->a = Psr7\stream_for($this->c);
$this->b = new Str($this->a);
}
public function testCatchesExceptionsWhenCastingToString()
{
$s = $this->getMockBuilder('Psr\Http\Message\StreamInterface')
->setMethods(['read'])
->getMockForAbstractClass();
$s->expects($this->once())
->method('read')
->will($this->throwException(new \Exception('foo')));
$msg = '';
set_error_handler(function ($errNo, $str) use (&$msg) { $msg = $str; });
echo new Str($s);
restore_error_handler();
$this->assertContains('foo', $msg);
}
public function testToString()
{
$this->assertEquals('foo', (string) $this->b);
}
public function testHasSize()
{
$this->assertEquals(3, $this->b->getSize());
}
public function testReads()
{
$this->assertEquals('foo', $this->b->read(10));
}
public function testCheckMethods()
{
$this->assertEquals($this->a->isReadable(), $this->b->isReadable());
$this->assertEquals($this->a->isWritable(), $this->b->isWritable());
$this->assertEquals($this->a->isSeekable(), $this->b->isSeekable());
}
public function testSeeksAndTells()
{
$this->b->seek(1);
$this->assertEquals(1, $this->a->tell());
$this->assertEquals(1, $this->b->tell());
$this->b->seek(0);
$this->assertEquals(0, $this->a->tell());
$this->assertEquals(0, $this->b->tell());
$this->b->seek(0, SEEK_END);
$this->assertEquals(3, $this->a->tell());
$this->assertEquals(3, $this->b->tell());
}
public function testGetsContents()
{
$this->assertEquals('foo', $this->b->getContents());
$this->assertEquals('', $this->b->getContents());
$this->b->seek(1);
$this->assertEquals('oo', $this->b->getContents(1));
}
public function testCloses()
{
$this->b->close();
$this->assertFalse(is_resource($this->c));
}
public function testDetaches()
{
$this->b->detach();
$this->assertFalse($this->b->isReadable());
}
public function testWrapsMetadata()
{
$this->assertSame($this->b->getMetadata(), $this->a->getMetadata());
$this->assertSame($this->b->getMetadata('uri'), $this->a->getMetadata('uri'));
}
public function testWrapsWrites()
{
$this->b->seek(0, SEEK_END);
$this->b->write('foo');
$this->assertEquals('foofoo', (string) $this->a);
}
/**
* @expectedException \UnexpectedValueException
*/
public function testThrowsWithInvalidGetter()
{
$this->b->foo;
}
/**
* @expectedException \BadMethodCallException
*/
public function testThrowsWhenGetterNotImplemented()
{
$s = new BadStream();
$s->stream;
}
}
class BadStream
{
use StreamDecoratorTrait;
public function __construct() {}
}

View File

@@ -1,161 +0,0 @@
<?php
namespace GuzzleHttp\Tests\Psr7;
use GuzzleHttp\Psr7\NoSeekStream;
use GuzzleHttp\Psr7\Stream;
/**
* @covers GuzzleHttp\Psr7\Stream
*/
class StreamTest extends \PHPUnit_Framework_TestCase
{
/**
* @expectedException \InvalidArgumentException
*/
public function testConstructorThrowsExceptionOnInvalidArgument()
{
new Stream(true);
}
public function testConstructorInitializesProperties()
{
$handle = fopen('php://temp', 'r+');
fwrite($handle, 'data');
$stream = new Stream($handle);
$this->assertTrue($stream->isReadable());
$this->assertTrue($stream->isWritable());
$this->assertTrue($stream->isSeekable());
$this->assertEquals('php://temp', $stream->getMetadata('uri'));
$this->assertInternalType('array', $stream->getMetadata());
$this->assertEquals(4, $stream->getSize());
$this->assertFalse($stream->eof());
$stream->close();
}
public function testStreamClosesHandleOnDestruct()
{
$handle = fopen('php://temp', 'r');
$stream = new Stream($handle);
unset($stream);
$this->assertFalse(is_resource($handle));
}
public function testConvertsToString()
{
$handle = fopen('php://temp', 'w+');
fwrite($handle, 'data');
$stream = new Stream($handle);
$this->assertEquals('data', (string) $stream);
$this->assertEquals('data', (string) $stream);
$stream->close();
}
public function testGetsContents()
{
$handle = fopen('php://temp', 'w+');
fwrite($handle, 'data');
$stream = new Stream($handle);
$this->assertEquals('', $stream->getContents());
$stream->seek(0);
$this->assertEquals('data', $stream->getContents());
$this->assertEquals('', $stream->getContents());
}
public function testChecksEof()
{
$handle = fopen('php://temp', 'w+');
fwrite($handle, 'data');
$stream = new Stream($handle);
$this->assertFalse($stream->eof());
$stream->read(4);
$this->assertTrue($stream->eof());
$stream->close();
}
public function testGetSize()
{
$size = filesize(__FILE__);
$handle = fopen(__FILE__, 'r');
$stream = new Stream($handle);
$this->assertEquals($size, $stream->getSize());
// Load from cache
$this->assertEquals($size, $stream->getSize());
$stream->close();
}
public function testEnsuresSizeIsConsistent()
{
$h = fopen('php://temp', 'w+');
$this->assertEquals(3, fwrite($h, 'foo'));
$stream = new Stream($h);
$this->assertEquals(3, $stream->getSize());
$this->assertEquals(4, $stream->write('test'));
$this->assertEquals(7, $stream->getSize());
$this->assertEquals(7, $stream->getSize());
$stream->close();
}
public function testProvidesStreamPosition()
{
$handle = fopen('php://temp', 'w+');
$stream = new Stream($handle);
$this->assertEquals(0, $stream->tell());
$stream->write('foo');
$this->assertEquals(3, $stream->tell());
$stream->seek(1);
$this->assertEquals(1, $stream->tell());
$this->assertSame(ftell($handle), $stream->tell());
$stream->close();
}
public function testCanDetachStream()
{
$r = fopen('php://temp', 'w+');
$stream = new Stream($r);
$stream->write('foo');
$this->assertTrue($stream->isReadable());
$this->assertSame($r, $stream->detach());
$stream->detach();
$this->assertFalse($stream->isReadable());
$this->assertFalse($stream->isWritable());
$this->assertFalse($stream->isSeekable());
$throws = function (callable $fn) use ($stream) {
try {
$fn($stream);
$this->fail();
} catch (\Exception $e) {}
};
$throws(function ($stream) { $stream->read(10); });
$throws(function ($stream) { $stream->write('bar'); });
$throws(function ($stream) { $stream->seek(10); });
$throws(function ($stream) { $stream->tell(); });
$throws(function ($stream) { $stream->eof(); });
$throws(function ($stream) { $stream->getSize(); });
$throws(function ($stream) { $stream->getContents(); });
$this->assertSame('', (string) $stream);
$stream->close();
}
public function testCloseClearProperties()
{
$handle = fopen('php://temp', 'r+');
$stream = new Stream($handle);
$stream->close();
$this->assertFalse($stream->isSeekable());
$this->assertFalse($stream->isReadable());
$this->assertFalse($stream->isWritable());
$this->assertNull($stream->getSize());
$this->assertEmpty($stream->getMetadata());
}
public function testDoesNotThrowInToString()
{
$s = \GuzzleHttp\Psr7\stream_for('foo');
$s = new NoSeekStream($s);
$this->assertEquals('foo', (string) $s);
}
}

View File

@@ -1,102 +0,0 @@
<?php
namespace GuzzleHttp\Tests\Psr7;
use GuzzleHttp\Psr7\StreamWrapper;
use GuzzleHttp\Psr7;
/**
* @covers GuzzleHttp\Psr7\StreamWrapper
*/
class StreamWrapperTest extends \PHPUnit_Framework_TestCase
{
public function testResource()
{
$stream = Psr7\stream_for('foo');
$handle = StreamWrapper::getResource($stream);
$this->assertSame('foo', fread($handle, 3));
$this->assertSame(3, ftell($handle));
$this->assertSame(3, fwrite($handle, 'bar'));
$this->assertSame(0, fseek($handle, 0));
$this->assertSame('foobar', fread($handle, 6));
$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([
'dev' => 0,
'ino' => 0,
'mode' => 33206,
'nlink' => 0,
'uid' => 0,
'gid' => 0,
'rdev' => 0,
'size' => 6,
'atime' => 0,
'mtime' => 0,
'ctime' => 0,
'blksize' => $stBlksize,
'blocks' => $stBlksize,
0 => 0,
1 => 0,
2 => 33206,
3 => 0,
4 => 0,
5 => 0,
6 => 0,
7 => 6,
8 => 0,
9 => 0,
10 => 0,
11 => $stBlksize,
12 => $stBlksize,
], fstat($handle));
}
$this->assertTrue(fclose($handle));
$this->assertSame('foobar', (string) $stream);
}
/**
* @expectedException \InvalidArgumentException
*/
public function testValidatesStream()
{
$stream = $this->getMockBuilder('Psr\Http\Message\StreamInterface')
->setMethods(['isReadable', 'isWritable'])
->getMockForAbstractClass();
$stream->expects($this->once())
->method('isReadable')
->will($this->returnValue(false));
$stream->expects($this->once())
->method('isWritable')
->will($this->returnValue(false));
StreamWrapper::getResource($stream);
}
/**
* @expectedException \PHPUnit_Framework_Error_Warning
*/
public function testReturnsFalseWhenStreamDoesNotExist()
{
fopen('guzzle://foo', 'r');
}
public function testCanOpenReadonlyStream()
{
$stream = $this->getMockBuilder('Psr\Http\Message\StreamInterface')
->setMethods(['isReadable', 'isWritable'])
->getMockForAbstractClass();
$stream->expects($this->once())
->method('isReadable')
->will($this->returnValue(false));
$stream->expects($this->once())
->method('isWritable')
->will($this->returnValue(true));
$r = StreamWrapper::getResource($stream);
$this->assertInternalType('resource', $r);
fclose($r);
}
}

View File

@@ -1,280 +0,0 @@
<?php
namespace GuzzleHttp\Tests\Psr7;
use ReflectionProperty;
use GuzzleHttp\Psr7\Stream;
use GuzzleHttp\Psr7\UploadedFile;
/**
* @covers GuzzleHttp\Psr7\UploadedFile
*/
class UploadedFileTest extends \PHPUnit_Framework_TestCase
{
protected $cleanup;
public function setUp()
{
$this->cleanup = [];
}
public function tearDown()
{
foreach ($this->cleanup as $file) {
if (is_scalar($file) && file_exists($file)) {
unlink($file);
}
}
}
public function invalidStreams()
{
return [
'null' => [null],
'true' => [true],
'false' => [false],
'int' => [1],
'float' => [1.1],
'array' => [['filename']],
'object' => [(object) ['filename']],
];
}
/**
* @dataProvider invalidStreams
*/
public function testRaisesExceptionOnInvalidStreamOrFile($streamOrFile)
{
$this->setExpectedException('InvalidArgumentException');
new UploadedFile($streamOrFile, 0, UPLOAD_ERR_OK);
}
public function invalidSizes()
{
return [
'null' => [null],
'float' => [1.1],
'array' => [[1]],
'object' => [(object) [1]],
];
}
/**
* @dataProvider invalidSizes
*/
public function testRaisesExceptionOnInvalidSize($size)
{
$this->setExpectedException('InvalidArgumentException', 'size');
new UploadedFile(fopen('php://temp', 'wb+'), $size, UPLOAD_ERR_OK);
}
public function invalidErrorStatuses()
{
return [
'null' => [null],
'true' => [true],
'false' => [false],
'float' => [1.1],
'string' => ['1'],
'array' => [[1]],
'object' => [(object) [1]],
'negative' => [-1],
'too-big' => [9],
];
}
/**
* @dataProvider invalidErrorStatuses
*/
public function testRaisesExceptionOnInvalidErrorStatus($status)
{
$this->setExpectedException('InvalidArgumentException', 'status');
new UploadedFile(fopen('php://temp', 'wb+'), 0, $status);
}
public function invalidFilenamesAndMediaTypes()
{
return [
'true' => [true],
'false' => [false],
'int' => [1],
'float' => [1.1],
'array' => [['string']],
'object' => [(object) ['string']],
];
}
/**
* @dataProvider invalidFilenamesAndMediaTypes
*/
public function testRaisesExceptionOnInvalidClientFilename($filename)
{
$this->setExpectedException('InvalidArgumentException', 'filename');
new UploadedFile(fopen('php://temp', 'wb+'), 0, UPLOAD_ERR_OK, $filename);
}
/**
* @dataProvider invalidFilenamesAndMediaTypes
*/
public function testRaisesExceptionOnInvalidClientMediaType($mediaType)
{
$this->setExpectedException('InvalidArgumentException', 'media type');
new UploadedFile(fopen('php://temp', 'wb+'), 0, UPLOAD_ERR_OK, 'foobar.baz', $mediaType);
}
public function testGetStreamReturnsOriginalStreamObject()
{
$stream = new Stream(fopen('php://temp', 'r'));
$upload = new UploadedFile($stream, 0, UPLOAD_ERR_OK);
$this->assertSame($stream, $upload->getStream());
}
public function testGetStreamReturnsWrappedPhpStream()
{
$stream = fopen('php://temp', 'wb+');
$upload = new UploadedFile($stream, 0, UPLOAD_ERR_OK);
$uploadStream = $upload->getStream()->detach();
$this->assertSame($stream, $uploadStream);
}
public function testGetStreamReturnsStreamForFile()
{
$this->cleanup[] = $stream = tempnam(sys_get_temp_dir(), 'stream_file');
$upload = new UploadedFile($stream, 0, UPLOAD_ERR_OK);
$uploadStream = $upload->getStream();
$r = new ReflectionProperty($uploadStream, 'filename');
$r->setAccessible(true);
$this->assertSame($stream, $r->getValue($uploadStream));
}
public function testSuccessful()
{
$stream = \GuzzleHttp\Psr7\stream_for('Foo bar!');
$upload = new UploadedFile($stream, $stream->getSize(), UPLOAD_ERR_OK, 'filename.txt', 'text/plain');
$this->assertEquals($stream->getSize(), $upload->getSize());
$this->assertEquals('filename.txt', $upload->getClientFilename());
$this->assertEquals('text/plain', $upload->getClientMediaType());
$this->cleanup[] = $to = tempnam(sys_get_temp_dir(), 'successful');
$upload->moveTo($to);
$this->assertFileExists($to);
$this->assertEquals($stream->__toString(), file_get_contents($to));
}
public function invalidMovePaths()
{
return [
'null' => [null],
'true' => [true],
'false' => [false],
'int' => [1],
'float' => [1.1],
'empty' => [''],
'array' => [['filename']],
'object' => [(object) ['filename']],
];
}
/**
* @dataProvider invalidMovePaths
*/
public function testMoveRaisesExceptionForInvalidPath($path)
{
$stream = \GuzzleHttp\Psr7\stream_for('Foo bar!');
$upload = new UploadedFile($stream, 0, UPLOAD_ERR_OK);
$this->cleanup[] = $path;
$this->setExpectedException('InvalidArgumentException', 'path');
$upload->moveTo($path);
}
public function testMoveCannotBeCalledMoreThanOnce()
{
$stream = \GuzzleHttp\Psr7\stream_for('Foo bar!');
$upload = new UploadedFile($stream, 0, UPLOAD_ERR_OK);
$this->cleanup[] = $to = tempnam(sys_get_temp_dir(), 'diac');
$upload->moveTo($to);
$this->assertTrue(file_exists($to));
$this->setExpectedException('RuntimeException', 'moved');
$upload->moveTo($to);
}
public function testCannotRetrieveStreamAfterMove()
{
$stream = \GuzzleHttp\Psr7\stream_for('Foo bar!');
$upload = new UploadedFile($stream, 0, UPLOAD_ERR_OK);
$this->cleanup[] = $to = tempnam(sys_get_temp_dir(), 'diac');
$upload->moveTo($to);
$this->assertFileExists($to);
$this->setExpectedException('RuntimeException', 'moved');
$upload->getStream();
}
public function nonOkErrorStatus()
{
return [
'UPLOAD_ERR_INI_SIZE' => [ UPLOAD_ERR_INI_SIZE ],
'UPLOAD_ERR_FORM_SIZE' => [ UPLOAD_ERR_FORM_SIZE ],
'UPLOAD_ERR_PARTIAL' => [ UPLOAD_ERR_PARTIAL ],
'UPLOAD_ERR_NO_FILE' => [ UPLOAD_ERR_NO_FILE ],
'UPLOAD_ERR_NO_TMP_DIR' => [ UPLOAD_ERR_NO_TMP_DIR ],
'UPLOAD_ERR_CANT_WRITE' => [ UPLOAD_ERR_CANT_WRITE ],
'UPLOAD_ERR_EXTENSION' => [ UPLOAD_ERR_EXTENSION ],
];
}
/**
* @dataProvider nonOkErrorStatus
*/
public function testConstructorDoesNotRaiseExceptionForInvalidStreamWhenErrorStatusPresent($status)
{
$uploadedFile = new UploadedFile('not ok', 0, $status);
$this->assertSame($status, $uploadedFile->getError());
}
/**
* @dataProvider nonOkErrorStatus
*/
public function testMoveToRaisesExceptionWhenErrorStatusPresent($status)
{
$uploadedFile = new UploadedFile('not ok', 0, $status);
$this->setExpectedException('RuntimeException', 'upload error');
$uploadedFile->moveTo(__DIR__ . '/' . uniqid());
}
/**
* @dataProvider nonOkErrorStatus
*/
public function testGetStreamRaisesExceptionWhenErrorStatusPresent($status)
{
$uploadedFile = new UploadedFile('not ok', 0, $status);
$this->setExpectedException('RuntimeException', 'upload error');
$stream = $uploadedFile->getStream();
}
public function testMoveToCreatesStreamIfOnlyAFilenameWasProvided()
{
$this->cleanup[] = $from = tempnam(sys_get_temp_dir(), 'copy_from');
$this->cleanup[] = $to = tempnam(sys_get_temp_dir(), 'copy_to');
copy(__FILE__, $from);
$uploadedFile = new UploadedFile($from, 100, UPLOAD_ERR_OK, basename($from), 'text/plain');
$uploadedFile->moveTo($to);
$this->assertFileEquals(__FILE__, $to);
}
}

View File

@@ -1,573 +0,0 @@
<?php
namespace GuzzleHttp\Tests\Psr7;
use GuzzleHttp\Psr7\Uri;
/**
* @covers GuzzleHttp\Psr7\Uri
*/
class UriTest extends \PHPUnit_Framework_TestCase
{
const RFC3986_BASE = 'http://a/b/c/d;p?q';
public function testParsesProvidedUri()
{
$uri = new Uri('https://user:pass@example.com:8080/path/123?q=abc#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);
}
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 testParseUriPortCannotBeZero()
{
new Uri('//example.com:0');
}
/**
* @expectedException \InvalidArgumentException
*/
public function testSchemeMustHaveCorrectType()
{
(new Uri())->withScheme([]);
}
/**
* @expectedException \InvalidArgumentException
*/
public function testHostMustHaveCorrectType()
{
(new Uri())->withHost([]);
}
/**
* @expectedException \InvalidArgumentException
*/
public function testPathMustHaveCorrectType()
{
(new Uri())->withPath([]);
}
/**
* @expectedException \InvalidArgumentException
*/
public function testQueryMustHaveCorrectType()
{
(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);
}
/**
* @dataProvider getResolveTestCases
*/
public function testResolvesUris($base, $rel, $expected)
{
$uri = new Uri($base);
$actual = Uri::resolve($uri, $rel);
$this->assertSame($expected, (string) $actual);
}
public function getResolveTestCases()
{
return [
[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/'],
[self::RFC3986_BASE, '/g', 'http://a/g'],
[self::RFC3986_BASE, '//g', 'http://g'],
[self::RFC3986_BASE, '?y', 'http://a/b/c/d;p?y'],
[self::RFC3986_BASE, 'g?y', 'http://a/b/c/g?y'],
[self::RFC3986_BASE, '#s', 'http://a/b/c/d;p?q#s'],
[self::RFC3986_BASE, 'g#s', 'http://a/b/c/g#s'],
[self::RFC3986_BASE, 'g?y#s', 'http://a/b/c/g?y#s'],
[self::RFC3986_BASE, ';x', 'http://a/b/c/;x'],
[self::RFC3986_BASE, 'g;x', 'http://a/b/c/g;x'],
[self::RFC3986_BASE, 'g;x?y#s', 'http://a/b/c/g;x?y#s'],
[self::RFC3986_BASE, '', self::RFC3986_BASE],
[self::RFC3986_BASE, '.', 'http://a/b/c/'],
[self::RFC3986_BASE, './', 'http://a/b/c/'],
[self::RFC3986_BASE, '..', 'http://a/b/'],
[self::RFC3986_BASE, '../', 'http://a/b/'],
[self::RFC3986_BASE, '../g', 'http://a/b/g'],
[self::RFC3986_BASE, '../..', 'http://a/'],
[self::RFC3986_BASE, '../../', 'http://a/'],
[self::RFC3986_BASE, '../../g', 'http://a/g'],
[self::RFC3986_BASE, '../../../g', 'http://a/g'],
[self::RFC3986_BASE, '../../../../g', 'http://a/g'],
[self::RFC3986_BASE, '/./g', 'http://a/g'],
[self::RFC3986_BASE, '/../g', 'http://a/g'],
[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..'],
[self::RFC3986_BASE, '..g', 'http://a/b/c/..g'],
[self::RFC3986_BASE, './../g', 'http://a/b/g'],
[self::RFC3986_BASE, 'foo////g', 'http://a/b/c/foo////g'],
[self::RFC3986_BASE, './g/.', 'http://a/b/c/g/'],
[self::RFC3986_BASE, 'g/./h', 'http://a/b/c/g/h'],
[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();
$uri = Uri::withQueryValue($uri, 'a', 'b');
$uri = Uri::withQueryValue($uri, 'c', 'd');
$uri = Uri::withQueryValue($uri, 'e', null);
$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->assertSame('a=b', $uri->getQuery());
$uri = Uri::withoutQueryValue($uri, 'a');
$this->assertSame('', $uri->getQuery());
}
public function testWithQueryValueReplacesSameKeys()
{
$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 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
['/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
['/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
['/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 uriComponentsEncodingProvider
*/
public function testUriComponentsGetEncodedProperly($input, $path, $query, $fragment, $output)
{
$uri = new Uri($input);
$this->assertSame($path, $uri->getPath());
$this->assertSame($query, $uri->getQuery());
$this->assertSame($fragment, $uri->getFragment());
$this->assertSame($output, (string) $uri);
}
public function testWithPathEncodesProperly()
{
$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->assertSame('foo', $uri->getPath());
$this->assertSame('foo', (string) $uri);
}
public function testAddsSlashForRelativeUriStringWithHost()
{
// 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);
}
public function testRemoveExtraSlashesWihoutHost()
{
// 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 testDefaultReturnValuesOfGetters()
{
$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());
}
public function testImmutability()
{
$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()
{
// The non-standard port triggers a cascade of private methods which
// should not use late static binding to access private static members.
// If they do, this will fatal.
$this->assertInstanceOf(
'\GuzzleHttp\Tests\Psr7\ExtendingClassTest',
new ExtendingClassTest('http://h:9/')
);
}
}
class ExtendingClassTest extends \GuzzleHttp\Psr7\Uri
{
}

View File

@@ -1,11 +0,0 @@
<?php
namespace GuzzleHttp\Tests\Psr7;
require __DIR__ . '/../vendor/autoload.php';
class HasToString
{
public function __toString() {
return 'foo';
}
}