From ce99317ac12a7736bedb16e567c02f6e27f05bbe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=9C=D0=B8=D1=85=D0=B0=D0=B8=D0=BB=20=D0=9A=D1=80=D0=B0?= =?UTF-8?q?=D1=81=D0=B8=D0=BB=D1=8C=D0=BD=D0=B8=D0=BA=D0=BE=D0=B2?= Date: Thu, 28 Jan 2016 11:47:11 +0300 Subject: [PATCH 001/108] Move documentation to official site. --- README.md | 65 +------------------------------------------------------ 1 file changed, 1 insertion(+), 64 deletions(-) diff --git a/README.md b/README.md index 334c680..fc60b7e 100644 --- a/README.md +++ b/README.md @@ -18,72 +18,9 @@ Via Composer $ composer require php-http/curl-client ``` -## Usage - -### Using [php-http/message](https://packagist.org/packages/php-http/message): - -```php -use Http\Client\Curl\Client; -use Http\Message\MessageFactory\DiactorosMessageFactory; -use Http\Message\StreamFactory\DiactorosStreamFactory; - -$messageFactory = new DiactorosMessageFactory(); -$client = new Client($messageFactory, new DiactorosStreamFactory()); - -$request = $messageFactory->createRequest('GET', 'http://example.com/'); -$response = $client->sendRequest($request); -``` - -### Using [php-http/discovery](https://packagist.org/packages/php-http/discovery): - -```php -use Http\Client\Curl\Client; -use Http\Discovery\MessageFactoryDiscovery; -use Http\Discovery\StreamFactoryDiscovery; - -$messageFactory = MessageFactoryDiscovery::find(); -$streamFactory = StreamFactoryDiscovery::find(); -$client = new Client($messageFactory, $streamFactory); - -$request = $messageFactory->createRequest('GET', 'http://example.com/'); -$response = $client->sendRequest($request); -``` - -### Configuring client - -You can use [cURL options](http://php.net/curl_setopt) to configure Client: - -```php -use Http\Client\Curl\Client; -use Http\Discovery\MessageFactoryDiscovery; -use Http\Discovery\StreamFactoryDiscovery; - -$options = [ - CURLOPT_CONNECTTIMEOUT => 10, // The number of seconds to wait while trying to connect. - CURLOPT_SSL_VERIFYPEER => false // Stop cURL from verifying the peer's certificate -]; -$client = new Client(MessageFactoryDiscovery::find(), StreamFactoryDiscovery::find(), $options); -``` - -These options can not be used: - -* CURLOPT_CUSTOMREQUEST -* CURLOPT_FOLLOWLOCATION -* CURLOPT_HEADER -* CURLOPT_HTTP_VERSION -* CURLOPT_HTTPHEADER -* CURLOPT_NOBODY -* CURLOPT_POSTFIELDS -* CURLOPT_RETURNTRANSFER -* CURLOPT_URL - -These options can be overwritten by Client: - -* CURLOPT_USERPWD - ## Documentation -Please see the [official documentation](http://php-http.readthedocs.org/en/latest/). +Please see the [official documentation](http://docs.php-http.org/en/latest/clients/curl-client.html). ## Testing From 7e3661d74c82287e33259d5e41cc21bc5c6acfae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=9C=D0=B8=D1=85=D0=B0=D0=B8=D0=BB=20=D0=9A=D1=80=D0=B0?= =?UTF-8?q?=D1=81=D0=B8=D0=BB=D1=8C=D0=BD=D0=B8=D0=BA=D0=BE=D0=B2?= Date: Thu, 28 Jan 2016 11:57:44 +0300 Subject: [PATCH 002/108] First stable release. --- CHANGELOG.md | 4 ++++ composer.json | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b86bc21..e0a0ca9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ # Change Log +## 1.0.0 - 2016-01-28 + +First stable release. + ## 0.7.0 - 2016-01-26 ### Changed diff --git a/composer.json b/composer.json index 4bac1a2..349a653 100644 --- a/composer.json +++ b/composer.json @@ -22,7 +22,7 @@ "guzzlehttp/psr7": "^1.0", "php-http/adapter-integration-tests": "0.3", "php-http/message": "^0.2", - "phpunit/phpunit": "^4.8@stable", + "phpunit/phpunit": "^4.8", "zendframework/zend-diactoros": "^1.0" }, "autoload": { From cdf93a5c5c04141092748c56930e467872d8fde2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=9C=D0=B8=D1=85=D0=B0=D0=B8=D0=BB=20=D0=9A=D1=80=D0=B0?= =?UTF-8?q?=D1=81=D0=B8=D0=BB=D1=8C=D0=BD=D0=B8=D0=BA=D0=BE=D0=B2?= Date: Fri, 29 Jan 2016 12:53:04 +0300 Subject: [PATCH 003/108] Switch to stable php-http/message version. --- composer.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/composer.json b/composer.json index 349a653..b6897bf 100644 --- a/composer.json +++ b/composer.json @@ -20,8 +20,8 @@ }, "require-dev": { "guzzlehttp/psr7": "^1.0", - "php-http/adapter-integration-tests": "0.3", - "php-http/message": "^0.2", + "php-http/adapter-integration-tests": "dev-master#836cdff8294174cceeae54601ab4079c309227b7", + "php-http/message": "^1.0", "phpunit/phpunit": "^4.8", "zendframework/zend-diactoros": "^1.0" }, From 5fbbcb8254a981094eb0f84be7f1e2b157b76c86 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=9C=D0=B8=D1=85=D0=B0=D0=B8=D0=BB=20=D0=9A=D1=80=D0=B0?= =?UTF-8?q?=D1=81=D0=B8=D0=BB=D1=8C=D0=BD=D0=B8=D0=BA=D0=BE=D0=B2?= Date: Fri, 29 Jan 2016 13:06:46 +0300 Subject: [PATCH 004/108] Version 1.1 --- CHANGELOG.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index e0a0ca9..9b6a938 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,9 +1,17 @@ # Change Log +## 1.1.0 - 2016-01-29 + +### Changed + +- Switch to php-http/message 1.0. + + ## 1.0.0 - 2016-01-28 First stable release. + ## 0.7.0 - 2016-01-26 ### Changed From 33370293884b7f8f7b7e04751753f91834d47212 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=9C=D0=B8=D1=85=D0=B0=D0=B8=D0=BB=20=D0=9A=D1=80=D0=B0?= =?UTF-8?q?=D1=81=D0=B8=D0=BB=D1=8C=D0=BD=D0=B8=D0=BA=D0=BE=D0=B2?= Date: Thu, 11 Feb 2016 12:23:43 +0300 Subject: [PATCH 005/108] #13: Remove HeaderParser --- CHANGELOG.md | 7 ++ composer.json | 2 +- src/ResponseParser.php | 8 +- src/Tools/HeadersParser.php | 88 -------------------- tests/Tools/HeadersParserTest.php | 66 --------------- tests/Tools/data/headers_invalid_header.http | 2 - tests/Tools/data/headers_invalid_status.http | 4 - tests/Tools/data/headers_valid.http | 4 - 8 files changed, 13 insertions(+), 168 deletions(-) delete mode 100644 src/Tools/HeadersParser.php delete mode 100644 tests/Tools/HeadersParserTest.php delete mode 100644 tests/Tools/data/headers_invalid_header.http delete mode 100644 tests/Tools/data/headers_invalid_status.http delete mode 100644 tests/Tools/data/headers_valid.http diff --git a/CHANGELOG.md b/CHANGELOG.md index 9b6a938..d4e9916 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,12 @@ # Change Log +## Unreleased + +### Removed + +- #13: Remove HeaderParser. + + ## 1.1.0 - 2016-01-29 ### Changed diff --git a/composer.json b/composer.json index b6897bf..3c02267 100644 --- a/composer.json +++ b/composer.json @@ -21,7 +21,7 @@ "require-dev": { "guzzlehttp/psr7": "^1.0", "php-http/adapter-integration-tests": "dev-master#836cdff8294174cceeae54601ab4079c309227b7", - "php-http/message": "^1.0", + "php-http/message": "dev-master", "phpunit/phpunit": "^4.8", "zendframework/zend-diactoros": "^1.0" }, diff --git a/src/ResponseParser.php b/src/ResponseParser.php index 714f832..6b0fcf0 100644 --- a/src/ResponseParser.php +++ b/src/ResponseParser.php @@ -1,7 +1,7 @@ parseString($rawHeaders, $response); + $builder = new ResponseBuilder($response); + $builder->setHeadersFromString($rawHeaders); + $response = $builder->getResponse(); /* * substr can return boolean value for empty string. But createStream does not support diff --git a/src/Tools/HeadersParser.php b/src/Tools/HeadersParser.php deleted file mode 100644 index 8644570..0000000 --- a/src/Tools/HeadersParser.php +++ /dev/null @@ -1,88 +0,0 @@ - 2 ? $parts[2] : ''; - /** @var ResponseInterface $response */ - $response = $response - ->withStatus((int) $parts[1], $reasonPhrase) - ->withProtocolVersion(substr($parts[0], 5)); - - foreach ($headers as $headerLine) { - $headerLine = trim($headerLine); - if ('' === $headerLine) { - continue; - } - - $parts = explode(':', $headerLine, 2); - if (count($parts) !== 2) { - throw new \RuntimeException( - sprintf('"%s" is not a valid HTTP header line', $headerLine) - ); - } - $name = trim(urldecode($parts[0])); - $value = trim(urldecode($parts[1])); - if ($response->hasHeader($name)) { - $response = $response->withAddedHeader($name, $value); - } else { - $response = $response->withHeader($name, $value); - } - } - - return $response; - } - - /** - * Parse headers and write them to response object. - * - * @param string $headers Response headers as single string. - * @param ResponseInterface $response Response to write headers to. - * - * @return ResponseInterface - * - * @throws \InvalidArgumentException if $headers is not a string on object with __toString() - * @throws \RuntimeException - */ - public function parseString($headers, ResponseInterface $response) - { - if (!(is_string($headers) - || (is_object($headers) && method_exists($headers, '__toString'))) - ) { - throw new \InvalidArgumentException( - sprintf( - '%s expects parameter 1 to be a string, %s given', - __METHOD__, - is_object($headers) ? get_class($headers) : gettype($headers) - ) - ); - } - return $this->parseArray(explode("\r\n", $headers), $response); - } -} diff --git a/tests/Tools/HeadersParserTest.php b/tests/Tools/HeadersParserTest.php deleted file mode 100644 index bfdde12..0000000 --- a/tests/Tools/HeadersParserTest.php +++ /dev/null @@ -1,66 +0,0 @@ -createResponse(); - $parser = new HeadersParser(); - $response = $parser->parseString($headers, $response); - static::assertEquals(200, $response->getStatusCode()); - static::assertEquals('OK', $response->getReasonPhrase()); - static::assertEquals('text/html; charset=UTF-8', $response->getHeaderLine('Content-Type')); - static::assertEquals(['foo=1234', 'bar=4321'], $response->getHeader('Set-Cookie')); - } - - /** - * Test parsing headers with invalid status line - * - * @expectedException \RuntimeException - * @expectedExceptionMessage "HTTP/1.1" is not a valid HTTP status line - */ - public function testInvalidStatusLine() - { - $headers = file_get_contents(__DIR__ . '/data/headers_invalid_status.http'); - $response = MessageFactoryDiscovery::find()->createResponse(); - $parser = new HeadersParser(); - $parser->parseString($headers, $response); - } - - /** - * Test parsing headers with invalid header line - * - * @expectedException \RuntimeException - * @expectedExceptionMessage "Content-Type text/html" is not a valid HTTP header line - */ - public function testInvalidHeaderLine() - { - $headers = file_get_contents(__DIR__ . '/data/headers_invalid_header.http'); - $response = MessageFactoryDiscovery::find()->createResponse(); - $parser = new HeadersParser(); - $parser->parseString($headers, $response); - } - - - /** - * @expectedException \InvalidArgumentException - * @expectedExceptionMessage HeadersParser::parseString expects parameter 1 to be a string, array given - */ - public function testInvalidArgument() - { - $response = MessageFactoryDiscovery::find()->createResponse(); - $parser = new HeadersParser(); - $parser->parseString([], $response); - } -} diff --git a/tests/Tools/data/headers_invalid_header.http b/tests/Tools/data/headers_invalid_header.http deleted file mode 100644 index cbfa1e8..0000000 --- a/tests/Tools/data/headers_invalid_header.http +++ /dev/null @@ -1,2 +0,0 @@ -HTTP/1.1 200 OK -Content-Type text/html diff --git a/tests/Tools/data/headers_invalid_status.http b/tests/Tools/data/headers_invalid_status.http deleted file mode 100644 index 4bea2bb..0000000 --- a/tests/Tools/data/headers_invalid_status.http +++ /dev/null @@ -1,4 +0,0 @@ -HTTP/1.1 -Content-Type: text/html; charset=UTF-8 -Set-Cookie: foo=1234 -Set-Cookie: bar=4321 diff --git a/tests/Tools/data/headers_valid.http b/tests/Tools/data/headers_valid.http deleted file mode 100644 index b3b5536..0000000 --- a/tests/Tools/data/headers_valid.http +++ /dev/null @@ -1,4 +0,0 @@ -HTTP/1.1 200 OK -Content-Type: text/html; charset=UTF-8 -Set-Cookie: foo=1234 -Set-Cookie: bar=4321 From 24c641704141a004907b02e8371acc9019562ce3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=9C=D0=B8=D1=85=D0=B0=D0=B8=D0=BB=20=D0=9A=D1=80=D0=B0?= =?UTF-8?q?=D1=81=D0=B8=D0=BB=D1=8C=D0=BD=D0=B8=D0=BA=D0=BE=D0=B2?= Date: Thu, 11 Feb 2016 12:26:05 +0300 Subject: [PATCH 006/108] Fix @cover directives in tests. --- tests/CurlPromiseTest.php | 4 ++-- tests/HttpAsyncClientGuzzleTest.php | 2 +- tests/HttpClientDiactorosTest.php | 2 +- tests/HttpClientGuzzleTest.php | 2 +- tests/PromiseCoreTest.php | 4 ++-- tests/Tools/HeadersParserTest.php | 2 +- 6 files changed, 8 insertions(+), 8 deletions(-) diff --git a/tests/CurlPromiseTest.php b/tests/CurlPromiseTest.php index 7fcb528..ed422a9 100644 --- a/tests/CurlPromiseTest.php +++ b/tests/CurlPromiseTest.php @@ -7,9 +7,9 @@ use Http\Promise\Promise; /** - * Tests for Http\Curl\CurlPromise + * Tests for Http\Client\Curl\CurlPromise * - * @covers Http\Curl\CurlPromise + * @covers Http\Client\Curl\CurlPromise */ class CurlPromiseTest extends BaseUnitTestCase { diff --git a/tests/HttpAsyncClientGuzzleTest.php b/tests/HttpAsyncClientGuzzleTest.php index cca54e1..073fed5 100644 --- a/tests/HttpAsyncClientGuzzleTest.php +++ b/tests/HttpAsyncClientGuzzleTest.php @@ -7,7 +7,7 @@ use Http\Message\StreamFactory\GuzzleStreamFactory; /** - * Tests for Http\Curl\Client + * Tests for Http\Client\Curl\Client */ class HttpAsyncClientGuzzleTest extends HttpAsyncClientTestCase { diff --git a/tests/HttpClientDiactorosTest.php b/tests/HttpClientDiactorosTest.php index a4ddae4..5c7a1e7 100644 --- a/tests/HttpClientDiactorosTest.php +++ b/tests/HttpClientDiactorosTest.php @@ -9,7 +9,7 @@ use Zend\Diactoros\Response; /** - * Tests for Http\Curl\Client + * Tests for Http\Client\Curl\Client */ class HttpClientDiactorosTest extends HttpClientTestCase { diff --git a/tests/HttpClientGuzzleTest.php b/tests/HttpClientGuzzleTest.php index 961cb60..124bc05 100644 --- a/tests/HttpClientGuzzleTest.php +++ b/tests/HttpClientGuzzleTest.php @@ -7,7 +7,7 @@ use Http\Message\StreamFactory\GuzzleStreamFactory; /** - * Tests for Http\Curl\Client + * Tests for Http\Client\Curl\Client */ class HttpClientGuzzleTest extends HttpClientTestCase { diff --git a/tests/PromiseCoreTest.php b/tests/PromiseCoreTest.php index 4af6935..348105c 100644 --- a/tests/PromiseCoreTest.php +++ b/tests/PromiseCoreTest.php @@ -8,9 +8,9 @@ use Psr\Http\Message\ResponseInterface; /** - * Tests for Http\Curl\PromiseCore + * Tests for Http\Client\Curl\PromiseCore * - * @covers Http\Curl\PromiseCore + * @covers Http\Client\Curl\PromiseCore */ class PromiseCoreTest extends BaseUnitTestCase { diff --git a/tests/Tools/HeadersParserTest.php b/tests/Tools/HeadersParserTest.php index bfdde12..a9157d5 100644 --- a/tests/Tools/HeadersParserTest.php +++ b/tests/Tools/HeadersParserTest.php @@ -5,7 +5,7 @@ use Http\Discovery\MessageFactoryDiscovery; /** - * @covers Http\Curl\Tools\HeadersParser + * @covers Http\Client\Curl\Tools\HeadersParser */ class HeadersParserTest extends \PHPUnit_Framework_TestCase { From e3ae04c6146b0f45072648f25a328af5d20f3285 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=9C=D0=B8=D1=85=D0=B0=D0=B8=D0=BB=20=D0=9A=D1=80=D0=B0?= =?UTF-8?q?=D1=81=D0=B8=D0=BB=D1=8C=D0=BD=D0=B8=D0=BA=D0=BE=D0=B2?= Date: Fri, 4 Mar 2016 19:04:06 +0300 Subject: [PATCH 007/108] #15: "Out of memory" sending large files --- src/Client.php | 24 ++++++++-- tests/HttpClientDiactorosTest.php | 14 ++++++ tests/HttpClientGuzzleTest.php | 14 ++++++ tests/HttpClientTestCase.php | 73 +++++++++++++++++++++++++++++++ 4 files changed, 122 insertions(+), 3 deletions(-) diff --git a/src/Client.php b/src/Client.php index 444596a..0d77316 100644 --- a/src/Client.php +++ b/src/Client.php @@ -123,6 +123,7 @@ public function sendRequest(RequestInterface $request) } catch (\Exception $e) { throw new RequestException($e->getMessage(), $request, $e); } + return $response; } @@ -161,6 +162,7 @@ public function sendAsyncRequest(RequestInterface $request) * @param RequestInterface $request * * @throws \UnexpectedValueException if unsupported HTTP version requested + * @throws \RuntimeException if can not read body * * @return array */ @@ -177,9 +179,23 @@ private function createCurlOptions(RequestInterface $request) if (in_array($request->getMethod(), ['OPTIONS', 'POST', 'PUT'], true)) { // cURL allows request body only for these methods. - $body = (string) $request->getBody(); - if ('' !== $body) { - $options[CURLOPT_POSTFIELDS] = $body; + $body = $request->getBody(); + $bodySize = $body->getSize(); + if ($bodySize !== 0) { + // Message has non empty body. + if (null === $bodySize || $bodySize > 1024 * 1024) { + // Avoid full loading large or unknown size body into memory + $options[CURLOPT_UPLOAD] = true; + if (null !== $bodySize) { + $options[CURLOPT_INFILESIZE] = $bodySize; + } + $options[CURLOPT_READFUNCTION] = function ($ch, $fd, $length) use ($body) { + return $body->read($length); + }; + } else { + // Small body can be loaded into memory + $options[CURLOPT_POSTFIELDS] = (string) $body; + } } } @@ -221,6 +237,7 @@ private function getProtocolVersion($requestVersion) } throw new \UnexpectedValueException('libcurl 7.33 needed for HTTP 2.0 support'); } + return CURL_HTTP_VERSION_NONE; } @@ -249,6 +266,7 @@ private function createHeaders(RequestInterface $request, array $options) $curlHeaders[] = $name . ': ' . $value; } } + return $curlHeaders; } } diff --git a/tests/HttpClientDiactorosTest.php b/tests/HttpClientDiactorosTest.php index 5c7a1e7..954ec33 100644 --- a/tests/HttpClientDiactorosTest.php +++ b/tests/HttpClientDiactorosTest.php @@ -5,8 +5,10 @@ use Http\Client\HttpClient; use Http\Message\MessageFactory\DiactorosMessageFactory; use Http\Message\StreamFactory\DiactorosStreamFactory; +use Psr\Http\Message\StreamInterface; use Zend\Diactoros\Request; use Zend\Diactoros\Response; +use Zend\Diactoros\Stream; /** * Tests for Http\Client\Curl\Client @@ -20,4 +22,16 @@ protected function createHttpAdapter() { return new Client(new DiactorosMessageFactory(), new DiactorosStreamFactory()); } + + /** + * Create stream from file + * + * @param string $filename + * + * @return StreamInterface + */ + protected function createFileStream($filename) + { + return new Stream($filename); + } } diff --git a/tests/HttpClientGuzzleTest.php b/tests/HttpClientGuzzleTest.php index 124bc05..2955f6b 100644 --- a/tests/HttpClientGuzzleTest.php +++ b/tests/HttpClientGuzzleTest.php @@ -1,10 +1,12 @@ createTempFile(); + $fd = fopen($filename, 'a'); + $buffer = str_repeat('x', 1024); + for ($i = 0; $i < 2048; $i++) { + fwrite($fd, $buffer); + } + fclose($fd); + $body = $this->createFileStream($filename); + + $request = self::$messageFactory->createRequest( + 'POST', + PHPUnitUtility::getUri(), + [], + $body + ); + + $response = $this->httpAdapter->sendRequest($request); + $this->assertResponse( + $response, + [ + 'body' => 'Ok', + ] + ); + } + + /** + * Create temp file. + * + * @return string Filename + */ + protected function createTempFile() + { + $filename = tempnam(sys_get_temp_dir(), 'tests'); + $this->tmpFiles[] = $filename; + + return $filename; + } + + /** + * Create stream from file + * + * @param string $filename + * + * @return StreamInterface + */ + abstract protected function createFileStream($filename); + + /** + * Tears down the fixture + */ + protected function tearDown() + { + parent::tearDown(); + + foreach ($this->tmpFiles as $filename) { + @unlink($filename); + } + } } From 65a79115c29c2c6626b9b65e2bd7ef3e34538154 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=9C=D0=B8=D1=85=D0=B0=D0=B8=D0=BB=20=D0=9A=D1=80=D0=B0?= =?UTF-8?q?=D1=81=D0=B8=D0=BB=D1=8C=D0=BD=D0=B8=D0=BA=D0=BE=D0=B2?= Date: Fri, 4 Mar 2016 20:36:44 +0300 Subject: [PATCH 008/108] Requirements updated. --- composer.json | 2 ++ 1 file changed, 2 insertions(+) diff --git a/composer.json b/composer.json index b6897bf..f9a5d5a 100644 --- a/composer.json +++ b/composer.json @@ -22,7 +22,9 @@ "guzzlehttp/psr7": "^1.0", "php-http/adapter-integration-tests": "dev-master#836cdff8294174cceeae54601ab4079c309227b7", "php-http/message": "^1.0", + "php-http/discovery": "~0.8.0", "phpunit/phpunit": "^4.8", + "puli/composer-plugin": "^1.0", "zendframework/zend-diactoros": "^1.0" }, "autoload": { From 4f0f266ad3cc9d64e2241a625e9817facee7a273 Mon Sep 17 00:00:00 2001 From: Tobias Nyholm Date: Sun, 6 Mar 2016 18:10:22 +0100 Subject: [PATCH 009/108] Make sure discovery can find the curl client --- .gitignore | 1 - composer.json | 6 +++--- puli.json | 16 ++++++++++++++++ 3 files changed, 19 insertions(+), 4 deletions(-) create mode 100644 puli.json diff --git a/.gitignore b/.gitignore index df9e8fe..396c451 100644 --- a/.gitignore +++ b/.gitignore @@ -3,4 +3,3 @@ build/ vendor/ composer.lock phpunit.xml -puli.json diff --git a/composer.json b/composer.json index f9a5d5a..21afc9a 100644 --- a/composer.json +++ b/composer.json @@ -11,7 +11,7 @@ } ], "prefer-stable": true, - "minimum-stability": "dev", + "minimum-stability": "beta", "require": { "php": ">=5.5", "ext-curl": "*", @@ -21,10 +21,10 @@ "require-dev": { "guzzlehttp/psr7": "^1.0", "php-http/adapter-integration-tests": "dev-master#836cdff8294174cceeae54601ab4079c309227b7", - "php-http/message": "^1.0", + "php-http/message": "^1.0.2", "php-http/discovery": "~0.8.0", "phpunit/phpunit": "^4.8", - "puli/composer-plugin": "^1.0", + "puli/composer-plugin": "1.0.0-beta9", "zendframework/zend-diactoros": "^1.0" }, "autoload": { diff --git a/puli.json b/puli.json new file mode 100644 index 0000000..42d0a7e --- /dev/null +++ b/puli.json @@ -0,0 +1,16 @@ +{ + "version": "1.0", + "name": "php-http/curl-client", + "bindings": { + "98239b8b-103b-4f47-94c7-4cba49a05a1f": { + "_class": "Puli\\Discovery\\Binding\\ClassBinding", + "class": "Http\\Client\\Curl\\Client", + "type": "Http\\Client\\HttpAsyncClient" + }, + "a6a79968-2aa5-427c-bbe1-a581d9a48321": { + "_class": "Puli\\Discovery\\Binding\\ClassBinding", + "class": "Http\\Client\\Curl\\Client", + "type": "Http\\Client\\HttpClient" + } + } +} From 52d0c7c5a73da4f5ffe528185b4428e1ddd34622 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=9C=D0=B8=D1=85=D0=B0=D0=B8=D0=BB=20=D0=9A=D1=80=D0=B0?= =?UTF-8?q?=D1=81=D0=B8=D0=BB=D1=8C=D0=BD=D0=B8=D0=BA=D0=BE=D0=B2?= Date: Wed, 9 Mar 2016 10:42:18 +0300 Subject: [PATCH 010/108] Update CHANGELOG. --- CHANGELOG.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9b6a938..6ff6cb5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,12 @@ # Change Log +## Unreleased + +### Fixed + +- #15: "Out of memory" sending large files. + + ## 1.1.0 - 2016-01-29 ### Changed From b3c3798175867bb18ba479947201f5a4f888be46 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=9C=D0=B8=D1=85=D0=B0=D0=B8=D0=BB=20=D0=9A=D1=80=D0=B0?= =?UTF-8?q?=D1=81=D0=B8=D0=BB=D1=8C=D0=BD=D0=B8=D0=BA=D0=BE=D0=B2?= Date: Wed, 9 Mar 2016 10:46:04 +0300 Subject: [PATCH 011/108] Update CHANGELOG. --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6ff6cb5..204e375 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,10 @@ ## Unreleased +### Added + +- #16: Make sure discovery can find the curl client + ### Fixed - #15: "Out of memory" sending large files. From 10d2a02660fc0a16eeb913b842921bd9d8ef622a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=9C=D0=B8=D1=85=D0=B0=D0=B8=D0=BB=20=D0=9A=D1=80=D0=B0?= =?UTF-8?q?=D1=81=D0=B8=D0=BB=D1=8C=D0=BD=D0=B8=D0=BA=D0=BE=D0=B2?= Date: Wed, 9 Mar 2016 10:58:29 +0300 Subject: [PATCH 012/108] Version 1.2. --- CHANGELOG.md | 2 +- puli.json | 216 +++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 217 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 204e375..53268a4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,6 @@ # Change Log -## Unreleased +## 1.2 - 2016-03-09 ### Added diff --git a/puli.json b/puli.json index 42d0a7e..4a694d8 100644 --- a/puli.json +++ b/puli.json @@ -12,5 +12,221 @@ "class": "Http\\Client\\Curl\\Client", "type": "Http\\Client\\HttpClient" } + }, + "config": { + "bootstrap-file": "vendor/autoload.php" + }, + "packages": { + "clue/stream-filter": { + "install-path": "vendor/clue/stream-filter", + "installer": "composer", + "env": "dev" + }, + "doctrine/instantiator": { + "install-path": "vendor/doctrine/instantiator", + "installer": "composer", + "env": "dev" + }, + "guzzlehttp/psr7": { + "install-path": "vendor/guzzlehttp/psr7", + "installer": "composer", + "env": "dev" + }, + "justinrainbow/json-schema": { + "install-path": "vendor/justinrainbow/json-schema", + "installer": "composer", + "env": "dev" + }, + "php-http/adapter-integration-tests": { + "install-path": "vendor/php-http/adapter-integration-tests", + "installer": "composer", + "env": "dev" + }, + "php-http/discovery": { + "install-path": "vendor/php-http/discovery", + "installer": "composer", + "env": "dev" + }, + "php-http/httplug": { + "install-path": "vendor/php-http/httplug", + "installer": "composer" + }, + "php-http/message": { + "install-path": "vendor/php-http/message", + "installer": "composer", + "env": "dev" + }, + "php-http/message-factory": { + "install-path": "vendor/php-http/message-factory", + "installer": "composer" + }, + "php-http/promise": { + "install-path": "vendor/php-http/promise", + "installer": "composer" + }, + "phpdocumentor/reflection-docblock": { + "install-path": "vendor/phpdocumentor/reflection-docblock", + "installer": "composer", + "env": "dev" + }, + "phpspec/prophecy": { + "install-path": "vendor/phpspec/prophecy", + "installer": "composer", + "env": "dev" + }, + "phpunit/php-code-coverage": { + "install-path": "vendor/phpunit/php-code-coverage", + "installer": "composer", + "env": "dev" + }, + "phpunit/php-file-iterator": { + "install-path": "vendor/phpunit/php-file-iterator", + "installer": "composer", + "env": "dev" + }, + "phpunit/php-text-template": { + "install-path": "vendor/phpunit/php-text-template", + "installer": "composer", + "env": "dev" + }, + "phpunit/php-timer": { + "install-path": "vendor/phpunit/php-timer", + "installer": "composer", + "env": "dev" + }, + "phpunit/php-token-stream": { + "install-path": "vendor/phpunit/php-token-stream", + "installer": "composer", + "env": "dev" + }, + "phpunit/phpunit": { + "install-path": "vendor/phpunit/phpunit", + "installer": "composer", + "env": "dev" + }, + "phpunit/phpunit-mock-objects": { + "install-path": "vendor/phpunit/phpunit-mock-objects", + "installer": "composer", + "env": "dev" + }, + "psr/http-message": { + "install-path": "vendor/psr/http-message", + "installer": "composer" + }, + "psr/log": { + "install-path": "vendor/psr/log", + "installer": "composer", + "env": "dev" + }, + "puli/composer-plugin": { + "install-path": "vendor/puli/composer-plugin", + "installer": "composer", + "env": "dev" + }, + "puli/discovery": { + "install-path": "vendor/puli/discovery", + "installer": "composer", + "env": "dev" + }, + "puli/repository": { + "install-path": "vendor/puli/repository", + "installer": "composer", + "env": "dev" + }, + "puli/url-generator": { + "install-path": "vendor/puli/url-generator", + "installer": "composer", + "env": "dev" + }, + "ramsey/uuid": { + "install-path": "vendor/ramsey/uuid", + "installer": "composer", + "env": "dev" + }, + "sebastian/comparator": { + "install-path": "vendor/sebastian/comparator", + "installer": "composer", + "env": "dev" + }, + "sebastian/diff": { + "install-path": "vendor/sebastian/diff", + "installer": "composer", + "env": "dev" + }, + "sebastian/environment": { + "install-path": "vendor/sebastian/environment", + "installer": "composer", + "env": "dev" + }, + "sebastian/exporter": { + "install-path": "vendor/sebastian/exporter", + "installer": "composer", + "env": "dev" + }, + "sebastian/global-state": { + "install-path": "vendor/sebastian/global-state", + "installer": "composer", + "env": "dev" + }, + "sebastian/recursion-context": { + "install-path": "vendor/sebastian/recursion-context", + "installer": "composer", + "env": "dev" + }, + "sebastian/version": { + "install-path": "vendor/sebastian/version", + "installer": "composer", + "env": "dev" + }, + "seld/jsonlint": { + "install-path": "vendor/seld/jsonlint", + "installer": "composer", + "env": "dev" + }, + "symfony/process": { + "install-path": "vendor/symfony/process", + "installer": "composer", + "env": "dev" + }, + "symfony/yaml": { + "install-path": "vendor/symfony/yaml", + "installer": "composer", + "env": "dev" + }, + "th3n3rd/cartesian-product": { + "install-path": "vendor/th3n3rd/cartesian-product", + "installer": "composer", + "env": "dev" + }, + "webmozart/assert": { + "install-path": "vendor/webmozart/assert", + "installer": "composer", + "env": "dev" + }, + "webmozart/expression": { + "install-path": "vendor/webmozart/expression", + "installer": "composer", + "env": "dev" + }, + "webmozart/glob": { + "install-path": "vendor/webmozart/glob", + "installer": "composer", + "env": "dev" + }, + "webmozart/json": { + "install-path": "vendor/webmozart/json", + "installer": "composer", + "env": "dev" + }, + "webmozart/path-util": { + "install-path": "vendor/webmozart/path-util", + "installer": "composer", + "env": "dev" + }, + "zendframework/zend-diactoros": { + "install-path": "vendor/zendframework/zend-diactoros", + "installer": "composer", + "env": "dev" + } } } From 21885e9e1b8911ac07bd49e05fe3497966c37938 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=9C=D0=B8=D1=85=D0=B0=D0=B8=D0=BB=20=D0=9A=D1=80=D0=B0?= =?UTF-8?q?=D1=81=D0=B8=D0=BB=D1=8C=D0=BD=D0=B8=D0=BA=D0=BE=D0=B2?= Date: Wed, 9 Mar 2016 12:37:37 +0300 Subject: [PATCH 013/108] #18: Invalid "Expect" header. --- src/Client.php | 12 +++++++++++- tests/ClientTest.php | 33 +++++++++++++++++++++++++++++++++ 2 files changed, 44 insertions(+), 1 deletion(-) create mode 100644 tests/ClientTest.php diff --git a/src/Client.php b/src/Client.php index 0d77316..85a69eb 100644 --- a/src/Client.php +++ b/src/Client.php @@ -254,7 +254,12 @@ private function createHeaders(RequestInterface $request, array $options) $curlHeaders = []; $headers = array_keys($request->getHeaders()); foreach ($headers as $name) { - if (strtolower($name) === 'content-length') { + $header = strtolower($name); + if ('expect' === $header) { + // curl-client does not support "Expect-Continue", so dropping "expect" headers + continue; + } + if ('content-length' === $header) { $values = [0]; if (array_key_exists(CURLOPT_POSTFIELDS, $options)) { $values = [strlen($options[CURLOPT_POSTFIELDS])]; @@ -266,6 +271,11 @@ private function createHeaders(RequestInterface $request, array $options) $curlHeaders[] = $name . ': ' . $value; } } + /* + * curl-client does not support "Expect-Continue", but cURL adds "Expect" header by default. + * We can not suppress it, but we can set it to empty. + */ + $curlHeaders []= 'Expect:'; return $curlHeaders; } diff --git a/tests/ClientTest.php b/tests/ClientTest.php new file mode 100644 index 0000000..cdbd6e2 --- /dev/null +++ b/tests/ClientTest.php @@ -0,0 +1,33 @@ +getMockBuilder(Client::class)->disableOriginalConstructor() + ->setMethods(['__none__'])->getMock(); + + $createHeaders = new \ReflectionMethod(Client::class, 'createHeaders'); + $createHeaders->setAccessible(true); + + $request = new Request(); + + $headers = $createHeaders->invoke($client, $request, []); + + static::assertContains('Expect:', $headers); + } +} From a4b9143c2586d9255d39d9ae0a48feeb5f5d5d6c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=9C=D0=B8=D1=85=D0=B0=D0=B8=D0=BB=20=D0=9A=D1=80=D0=B0?= =?UTF-8?q?=D1=81=D0=B8=D0=BB=D1=8C=D0=BD=D0=B8=D0=BA=D0=BE=D0=B2?= Date: Mon, 14 Mar 2016 16:46:07 +0300 Subject: [PATCH 014/108] Version 1.3. --- CHANGELOG.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index fb17247..1fa6ace 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,10 @@ # Change Log -## Unreleased +## 1.3 - 2016-03-14 + +### Fixed + +- #18: Invalid "Expect" header. ### Removed From 23e65b9136c7f489a305cdf777d6814e4874e960 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=9C=D0=B8=D1=85=D0=B0=D0=B8=D0=BB=20=D0=9A=D1=80=D0=B0?= =?UTF-8?q?=D1=81=D0=B8=D0=BB=D1=8C=D0=BD=D0=B8=D0=BA=D0=BE=D0=B2?= Date: Thu, 17 Mar 2016 17:59:43 +0300 Subject: [PATCH 015/108] Remove old PHPDoc. --- src/Client.php | 7 ------- 1 file changed, 7 deletions(-) diff --git a/src/Client.php b/src/Client.php index 85a69eb..4a2a5e1 100644 --- a/src/Client.php +++ b/src/Client.php @@ -55,13 +55,6 @@ class Client implements HttpClient, HttpAsyncClient /** * Create new client * - * Available options: - * - * - connection_timeout : int — connection timeout in seconds; - * - curl_options: array — custom cURL options; - * - ssl_verify_peer : bool — verify peer when using SSL; - * - timeout : int — overall timeout in seconds. - * * @param MessageFactory $messageFactory HTTP Message factory * @param StreamFactory $streamFactory HTTP Stream factory * @param array $options cURL options (see http://php.net/curl_setopt) From c5b5c80be793c5b099196a45195a1e4972a7ea4b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=9C=D0=B8=D1=85=D0=B0=D0=B8=D0=BB=20=D0=9A=D1=80=D0=B0?= =?UTF-8?q?=D1=81=D0=B8=D0=BB=D1=8C=D0=BD=D0=B8=D0=BA=D0=BE=D0=B2?= Date: Wed, 23 Mar 2016 07:43:47 +0300 Subject: [PATCH 016/108] Fix PHPDoc. --- src/Client.php | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/Client.php b/src/Client.php index 4a2a5e1..d4fed98 100644 --- a/src/Client.php +++ b/src/Client.php @@ -87,8 +87,9 @@ public function __destruct() * * @return ResponseInterface * - * @throws \UnexpectedValueException if unsupported HTTP version requested * @throws RequestException + * @throws \UnexpectedValueException if unsupported HTTP version requested + * @throws \RuntimeException if can not read body * * @since 1.0 */ @@ -129,6 +130,7 @@ public function sendRequest(RequestInterface $request) * * @throws Exception * @throws \UnexpectedValueException if unsupported HTTP version requested + * @throws \RuntimeException if can not read body * * @since 1.0 */ @@ -268,7 +270,7 @@ private function createHeaders(RequestInterface $request, array $options) * curl-client does not support "Expect-Continue", but cURL adds "Expect" header by default. * We can not suppress it, but we can set it to empty. */ - $curlHeaders []= 'Expect:'; + $curlHeaders[] = 'Expect:'; return $curlHeaders; } From 5f7e83597777464de983a2a37048afcebf4690be Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=9C=D0=B8=D1=85=D0=B0=D0=B8=D0=BB=20=D0=9A=D1=80=D0=B0?= =?UTF-8?q?=D1=81=D0=B8=D0=BB=D1=8C=D0=BD=D0=B8=D0=BA=D0=BE=D0=B2?= Date: Wed, 30 Mar 2016 14:44:10 +0300 Subject: [PATCH 017/108] #20: Minimize memory usage when reading large response body --- composer.json | 4 +- puli.json | 5 +++ src/Client.php | 88 +++++++++++++++++++++++++++++---------- src/CurlPromise.php | 2 +- src/MultiRunner.php | 23 +--------- src/PromiseCore.php | 49 +++++++++++++--------- src/ResponseBuilder.php | 21 ++++++++++ src/ResponseParser.php | 77 ---------------------------------- tests/PromiseCoreTest.php | 33 ++++++++------- 9 files changed, 141 insertions(+), 161 deletions(-) create mode 100644 src/ResponseBuilder.php delete mode 100644 src/ResponseParser.php diff --git a/composer.json b/composer.json index 21afc9a..ed63d80 100644 --- a/composer.json +++ b/composer.json @@ -20,8 +20,8 @@ }, "require-dev": { "guzzlehttp/psr7": "^1.0", - "php-http/adapter-integration-tests": "dev-master#836cdff8294174cceeae54601ab4079c309227b7", - "php-http/message": "^1.0.2", + "php-http/adapter-integration-tests": "0.4.*", + "php-http/message": "^1.2", "php-http/discovery": "~0.8.0", "phpunit/phpunit": "^4.8", "puli/composer-plugin": "1.0.0-beta9", diff --git a/puli.json b/puli.json index 4a694d8..55a49a1 100644 --- a/puli.json +++ b/puli.json @@ -37,6 +37,11 @@ "installer": "composer", "env": "dev" }, + "paragonie/random_compat": { + "install-path": "vendor/paragonie/random_compat", + "installer": "composer", + "env": "dev" + }, "php-http/adapter-integration-tests": { "install-path": "vendor/php-http/adapter-integration-tests", "installer": "composer", diff --git a/src/Client.php b/src/Client.php index d4fed98..f485e7d 100644 --- a/src/Client.php +++ b/src/Client.php @@ -32,11 +32,18 @@ class Client implements HttpClient, HttpAsyncClient private $options; /** - * cURL response parser + * PSR-7 message factory * - * @var ResponseParser + * @var MessageFactory */ - private $responseParser; + private $messageFactory; + + /** + * PSR-7 stream factory + * + * @var StreamFactory + */ + private $streamFactory; /** * cURL synchronous requests handle @@ -66,7 +73,8 @@ public function __construct( StreamFactory $streamFactory, array $options = [] ) { - $this->responseParser = new ResponseParser($messageFactory, $streamFactory); + $this->messageFactory = $messageFactory; + $this->streamFactory = $streamFactory; $this->options = $options; } @@ -87,15 +95,16 @@ public function __destruct() * * @return ResponseInterface * - * @throws RequestException + * @throws \RuntimeException If creating the body stream fails. * @throws \UnexpectedValueException if unsupported HTTP version requested - * @throws \RuntimeException if can not read body + * @throws RequestException * * @since 1.0 */ public function sendRequest(RequestInterface $request) { - $options = $this->createCurlOptions($request); + $responseBuilder = $this->createResponseBuilder(); + $options = $this->createCurlOptions($request, $responseBuilder); if (is_resource($this->handle)) { curl_reset($this->handle); @@ -104,19 +113,14 @@ public function sendRequest(RequestInterface $request) } curl_setopt_array($this->handle, $options); - $raw = curl_exec($this->handle); + curl_exec($this->handle); if (curl_errno($this->handle) > 0) { throw new RequestException(curl_error($this->handle), $request); } - $info = curl_getinfo($this->handle); - - try { - $response = $this->responseParser->parse($raw, $info); - } catch (\Exception $e) { - throw new RequestException($e->getMessage(), $request, $e); - } + $response = $responseBuilder->getResponse(); + $response->getBody()->seek(0); return $response; } @@ -128,23 +132,24 @@ public function sendRequest(RequestInterface $request) * * @return Promise * + * @throws \RuntimeException If creating the body stream fails. + * @throws \UnexpectedValueException If unsupported HTTP version requested * @throws Exception - * @throws \UnexpectedValueException if unsupported HTTP version requested - * @throws \RuntimeException if can not read body * * @since 1.0 */ public function sendAsyncRequest(RequestInterface $request) { if (!$this->multiRunner instanceof MultiRunner) { - $this->multiRunner = new MultiRunner($this->responseParser); + $this->multiRunner = new MultiRunner(); } $handle = curl_init(); - $options = $this->createCurlOptions($request); + $responseBuilder = $this->createResponseBuilder(); + $options = $this->createCurlOptions($request, $responseBuilder); curl_setopt_array($handle, $options); - $core = new PromiseCore($request, $handle); + $core = new PromiseCore($request, $handle, $responseBuilder); $promise = new CurlPromise($core, $this->multiRunner); $this->multiRunner->add($core); @@ -155,18 +160,19 @@ public function sendAsyncRequest(RequestInterface $request) * Generates cURL options * * @param RequestInterface $request + * @param ResponseBuilder $responseBuilder * * @throws \UnexpectedValueException if unsupported HTTP version requested * @throws \RuntimeException if can not read body * * @return array */ - private function createCurlOptions(RequestInterface $request) + private function createCurlOptions(RequestInterface $request, ResponseBuilder $responseBuilder) { $options = $this->options; - $options[CURLOPT_HEADER] = true; - $options[CURLOPT_RETURNTRANSFER] = true; + $options[CURLOPT_HEADER] = false; + $options[CURLOPT_RETURNTRANSFER] = false; $options[CURLOPT_FOLLOWLOCATION] = false; $options[CURLOPT_HTTP_VERSION] = $this->getProtocolVersion($request->getProtocolVersion()); @@ -207,6 +213,23 @@ private function createCurlOptions(RequestInterface $request) $options[CURLOPT_USERPWD] = $request->getUri()->getUserInfo(); } + $options[CURLOPT_HEADERFUNCTION] = function ($ch, $data) use ($responseBuilder) { + $str = trim($data); + if ('' !== $str) { + if (strpos(strtolower($str), 'http/') === 0) { + $responseBuilder->setStatus($str)->getResponse(); + } else { + $responseBuilder->addHeader($str); + } + } + + return strlen($data); + }; + + $options[CURLOPT_WRITEFUNCTION] = function ($ch, $data) use ($responseBuilder) { + return $responseBuilder->getResponse()->getBody()->write($data); + }; + return $options; } @@ -274,4 +297,23 @@ private function createHeaders(RequestInterface $request, array $options) return $curlHeaders; } + + /** + * Create new ResponseBuilder instance + * + * @return ResponseBuilder + * + * @throws \RuntimeException If creating the stream from $body fails. + */ + private function createResponseBuilder() + { + try { + $body = $this->streamFactory->createStream(fopen('php://temp', 'w+')); + } catch (\InvalidArgumentException $e) { + throw new \RuntimeException('Can not create "php://temp" stream.'); + } + $response = $this->messageFactory->createResponse(200, null, [], $body); + + return new ResponseBuilder($response); + } } diff --git a/src/CurlPromise.php b/src/CurlPromise.php index 59aa81c..150c422 100644 --- a/src/CurlPromise.php +++ b/src/CurlPromise.php @@ -98,7 +98,7 @@ public function wait($unwrap = true) $this->runner->wait($this->core); if ($unwrap) { - if ($this->core->getState() == self::REJECTED) { + if ($this->core->getState() === self::REJECTED) { throw $this->core->getException(); } diff --git a/src/MultiRunner.php b/src/MultiRunner.php index ea7a378..58893f1 100644 --- a/src/MultiRunner.php +++ b/src/MultiRunner.php @@ -19,13 +19,6 @@ class MultiRunner */ private $multiHandle = null; - /** - * cURL response parser - * - * @var ResponseParser - */ - private $responseParser; - /** * Awaiting cores * @@ -33,16 +26,6 @@ class MultiRunner */ private $cores = []; - /** - * Construct new runner. - * - * @param ResponseParser $responseParser - */ - public function __construct(ResponseParser $responseParser) - { - $this->responseParser = $responseParser; - } - /** * Release resources if still active */ @@ -111,11 +94,7 @@ public function wait(PromiseCore $targetCore = null) if (CURLE_OK === $info['result']) { try { - $response = $this->responseParser->parse( - curl_multi_getcontent($core->getHandle()), - curl_getinfo($core->getHandle()) - ); - $core->fulfill($response); + $core->fulfill(); } catch (\Exception $e) { $core->reject( new RequestException($e->getMessage(), $core->getRequest(), $e) diff --git a/src/PromiseCore.php b/src/PromiseCore.php index 25e6801..721468c 100644 --- a/src/PromiseCore.php +++ b/src/PromiseCore.php @@ -29,6 +29,13 @@ class PromiseCore */ private $handle; + /** + * Response builder + * + * @var ResponseBuilder + */ + private $responseBuilder; + /** * Promise state * @@ -57,26 +64,24 @@ class PromiseCore */ private $onRejected = []; - /** - * Received response - * - * @var ResponseInterface|null - */ - private $response = null; - /** * Create shared core. * * @param RequestInterface $request HTTP request * @param resource $handle cURL handle + * @param ResponseBuilder $responseBuilder */ - public function __construct(RequestInterface $request, $handle) - { + public function __construct( + RequestInterface $request, + $handle, + ResponseBuilder $responseBuilder + ) { assert('is_resource($handle)'); assert('get_resource_type($handle) === "curl"'); $this->request = $request; $this->handle = $handle; + $this->responseBuilder = $responseBuilder; $this->state = Promise::PENDING; } @@ -90,7 +95,10 @@ public function addOnFulfilled(callable $callback) if ($this->getState() === Promise::PENDING) { $this->onFulfilled[] = $callback; } elseif ($this->getState() === Promise::FULFILLED) { - $this->response = call_user_func($callback, $this->response); + $response = call_user_func($callback, $this->responseBuilder->getResponse()); + if ($response instanceof ResponseInterface) { + $this->responseBuilder->setResponse($response); + } } } @@ -142,15 +150,10 @@ public function getRequest() * Return the value of the promise (fulfilled). * * @return ResponseInterface Response Object only when the Promise is fulfilled. - * - * @throws \LogicException When the promise is not fulfilled. */ public function getResponse() { - if (null === $this->response) { - throw new \LogicException('Promise is not fulfilled'); - } - return $this->response; + return $this->responseBuilder->getResponse(); } /** @@ -168,19 +171,24 @@ public function getException() if (null === $this->exception) { throw new \LogicException('Promise is not rejected'); } + return $this->exception; } /** * Fulfill promise. * - * @param ResponseInterface $response Received response + * @throws \Exception from on fulfill handler. */ - public function fulfill(ResponseInterface $response) + public function fulfill() { - $this->response = $response; $this->state = Promise::FULFILLED; - $this->response = $this->call($this->onFulfilled, $this->response); + $response = $this->responseBuilder->getResponse(); + $response->getBody()->seek(0); + $response = $this->call($this->onFulfilled, $response); + if ($response instanceof ResponseInterface) { + $this->responseBuilder->setResponse($response); + } } /** @@ -214,6 +222,7 @@ private function call(array &$callbacks, $argument) $callback = array_shift($callbacks); $argument = call_user_func($callback, $argument); } + return $argument; } } diff --git a/src/ResponseBuilder.php b/src/ResponseBuilder.php new file mode 100644 index 0000000..99e79db --- /dev/null +++ b/src/ResponseBuilder.php @@ -0,0 +1,21 @@ +response = $response; + } +} diff --git a/src/ResponseParser.php b/src/ResponseParser.php deleted file mode 100644 index 6b0fcf0..0000000 --- a/src/ResponseParser.php +++ /dev/null @@ -1,77 +0,0 @@ - -*/ -class ResponseParser -{ - /** - * PSR-7 message factory - * - * @var MessageFactory - */ - private $messageFactory; - - /** - * PSR-7 stream factory - * - * @var StreamFactory - */ - private $streamFactory; - - /** - * Create new parser. - * - * @param MessageFactory $messageFactory HTTP Message factory - * @param StreamFactory $streamFactory HTTP Stream factory - */ - public function __construct(MessageFactory $messageFactory, StreamFactory $streamFactory) - { - $this->messageFactory = $messageFactory; - $this->streamFactory = $streamFactory; - } - - /** - * Parse cURL response - * - * @param string $raw raw response - * @param array $info cURL response info - * - * @return ResponseInterface - * - * @throws \InvalidArgumentException - * @throws \UnexpectedValueException - * @throws \RuntimeException - */ - public function parse($raw, array $info) - { - $response = $this->messageFactory->createResponse(); - - $headerSize = $info['header_size']; - $rawHeaders = substr($raw, 0, $headerSize); - - $builder = new ResponseBuilder($response); - $builder->setHeadersFromString($rawHeaders); - $response = $builder->getResponse(); - - /* - * substr can return boolean value for empty string. But createStream does not support - * booleans. Converting to string. - */ - $content = (string) substr($raw, $headerSize); - $stream = $this->streamFactory->createStream($content); - $response = $response->withBody($stream); - - return $response; - } -} diff --git a/tests/PromiseCoreTest.php b/tests/PromiseCoreTest.php index 348105c..2c167dd 100644 --- a/tests/PromiseCoreTest.php +++ b/tests/PromiseCoreTest.php @@ -2,6 +2,7 @@ namespace Http\Client\Curl\Tests; use Http\Client\Curl\PromiseCore; +use Http\Client\Curl\ResponseBuilder; use Http\Client\Exception; use Http\Client\Exception\RequestException; use Http\Promise\Promise; @@ -22,7 +23,11 @@ public function testOnFulfill() $request = $this->createRequest('GET', '/'); $this->handle = curl_init(); - $core = new PromiseCore($request, $this->handle); + $core = new PromiseCore( + $request, + $this->handle, + new ResponseBuilder($this->createResponse()) + ); static::assertSame($request, $core->getRequest()); static::assertSame($this->handle, $core->getHandle()); @@ -32,7 +37,7 @@ function (ResponseInterface $response) { } ); - $core->fulfill($this->createResponse()); + $core->fulfill(); static::assertEquals(Promise::FULFILLED, $core->getState()); static::assertInstanceOf(ResponseInterface::class, $core->getResponse()); static::assertEquals('foo', $core->getResponse()->getHeaderLine('X-Test')); @@ -53,7 +58,11 @@ public function testOnReject() $request = $this->createRequest('GET', '/'); $this->handle = curl_init(); - $core = new PromiseCore($request, $this->handle); + $core = new PromiseCore( + $request, + $this->handle, + new ResponseBuilder($this->createResponse()) + ); $core->addOnRejected( function (RequestException $exception) { throw new RequestException('Foo', $exception->getRequest(), $exception); @@ -74,18 +83,6 @@ function (RequestException $exception) { static::assertEquals('Bar', $core->getException()->getMessage()); } - /** - * @expectedException \LogicException - */ - public function testNotFulfilled() - { - $request = $this->createRequest('GET', '/'); - $this->handle = curl_init(); - $core = new PromiseCore($request, $this->handle); - $core->getResponse(); - } - - /** * @expectedException \LogicException */ @@ -93,7 +90,11 @@ public function testNotRejected() { $request = $this->createRequest('GET', '/'); $this->handle = curl_init(); - $core = new PromiseCore($request, $this->handle); + $core = new PromiseCore( + $request, + $this->handle, + new ResponseBuilder($this->createResponse()) + ); $core->getException(); } } From a31fb783f90b06b3a0306477f00798b8b4fd7bba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=9C=D0=B8=D1=85=D0=B0=D0=B8=D0=BB=20=D0=9A=D1=80=D0=B0?= =?UTF-8?q?=D1=81=D0=B8=D0=BB=D1=8C=D0=BD=D0=B8=D0=BA=D0=BE=D0=B2?= Date: Wed, 30 Mar 2016 14:55:10 +0300 Subject: [PATCH 018/108] Version 1.4. --- CHANGELOG.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1fa6ace..99089e5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,12 @@ # Change Log +## 1.4 - 2016-03-30 + +### Changed + +- #20: Minimize memory usage when reading large response body. + + ## 1.3 - 2016-03-14 ### Fixed From d735f73d4a9219c1ffc14d3f17adb969cab060aa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=9C=D0=B8=D1=85=D0=B0=D0=B8=D0=BB=20=D0=9A=D1=80=D0=B0?= =?UTF-8?q?=D1=81=D0=B8=D0=BB=D1=8C=D0=BD=D0=B8=D0=BA=D0=BE=D0=B2?= Date: Mon, 30 May 2016 12:41:39 +0300 Subject: [PATCH 019/108] #22: Cannot create the client using `HttpClientDiscovery` --- composer.json | 5 ++++- puli.json | 5 +++++ src/Client.php | 33 ++++++++++++++++++++++++++++----- tests/ClientTest.php | 10 ++++++++++ 4 files changed, 47 insertions(+), 6 deletions(-) diff --git a/composer.json b/composer.json index ed63d80..cefccda 100644 --- a/composer.json +++ b/composer.json @@ -18,13 +18,16 @@ "php-http/httplug": "^1.0", "php-http/message-factory": "^1.0" }, + "suggest": { + "php-http/discovery": "Allow automatically discover needed HTTPlug implementations" + }, "require-dev": { "guzzlehttp/psr7": "^1.0", "php-http/adapter-integration-tests": "0.4.*", "php-http/message": "^1.2", "php-http/discovery": "~0.8.0", "phpunit/phpunit": "^4.8", - "puli/composer-plugin": "1.0.0-beta9", + "puli/composer-plugin": "^1.0@beta,>=1.0.0-beta9", "zendframework/zend-diactoros": "^1.0" }, "autoload": { diff --git a/puli.json b/puli.json index 55a49a1..b35768d 100644 --- a/puli.json +++ b/puli.json @@ -188,6 +188,11 @@ "installer": "composer", "env": "dev" }, + "symfony/filesystem": { + "install-path": "vendor/symfony/filesystem", + "installer": "composer", + "env": "dev" + }, "symfony/process": { "install-path": "vendor/symfony/process", "installer": "composer", diff --git a/src/Client.php b/src/Client.php index f485e7d..2ac31f2 100644 --- a/src/Client.php +++ b/src/Client.php @@ -5,6 +5,8 @@ use Http\Client\Exception\RequestException; use Http\Client\HttpAsyncClient; use Http\Client\HttpClient; +use Http\Discovery\MessageFactoryDiscovery; +use Http\Discovery\StreamFactoryDiscovery; use Http\Message\MessageFactory; use Http\Message\StreamFactory; use Http\Promise\Promise; @@ -24,6 +26,11 @@ */ class Client implements HttpClient, HttpAsyncClient { + /** + * @access private + */ + const DEPENDENCY_MSG = 'You should either provide $%s argument or install "php-http/discovery"'; + /** * cURL options * @@ -62,19 +69,35 @@ class Client implements HttpClient, HttpAsyncClient /** * Create new client * - * @param MessageFactory $messageFactory HTTP Message factory - * @param StreamFactory $streamFactory HTTP Stream factory - * @param array $options cURL options (see http://php.net/curl_setopt) + * @param MessageFactory|null $messageFactory HTTP Message factory + * @param StreamFactory|null $streamFactory HTTP Stream factory + * @param array $options cURL options (see http://php.net/curl_setopt) + * + * @throws \LogicException If some factory not provided and php-http/discovery not installed * * @since 1.0 */ public function __construct( - MessageFactory $messageFactory, - StreamFactory $streamFactory, + MessageFactory $messageFactory = null, + StreamFactory $streamFactory = null, array $options = [] ) { + if (null === $messageFactory) { + if (!class_exists(MessageFactoryDiscovery::class)) { + throw new \LogicException(sprintf(self::DEPENDENCY_MSG, 'messageFactory')); + } + $messageFactory = MessageFactoryDiscovery::find(); + } $this->messageFactory = $messageFactory; + + if (null === $streamFactory) { + if (!class_exists(StreamFactoryDiscovery::class)) { + throw new \LogicException(sprintf(self::DEPENDENCY_MSG, 'streamFactory')); + } + $streamFactory = StreamFactoryDiscovery::find(); + } $this->streamFactory = $streamFactory; + $this->options = $options; } diff --git a/tests/ClientTest.php b/tests/ClientTest.php index cdbd6e2..584d625 100644 --- a/tests/ClientTest.php +++ b/tests/ClientTest.php @@ -30,4 +30,14 @@ public function testExpectHeader() static::assertContains('Expect:', $headers); } + + /** + * Discovery should be used if no factory given. + */ + public function testFactoryDiscovery() + { + $client = new Client; + + static::assertInstanceOf(Client::class, $client); + } } From 535383bf09f7ed891ba6d064f98da4565d4046b9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=9C=D0=B8=D1=85=D0=B0=D0=B8=D0=BB=20=D0=9A=D1=80=D0=B0?= =?UTF-8?q?=D1=81=D0=B8=D0=BB=D1=8C=D0=BD=D0=B8=D0=BA=D0=BE=D0=B2?= Date: Mon, 30 May 2016 12:43:04 +0300 Subject: [PATCH 020/108] Version 1.4.1 --- CHANGELOG.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 99089e5..6db23bc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,12 @@ # Change Log +## 1.4.1 - 2016-05-30 + +### Fixed + +- #22: Cannot create the client using `HttpClientDiscovery`. + + ## 1.4 - 2016-03-30 ### Changed From 86bea524d3b91fb5b94e778fd80a04662c1aba44 Mon Sep 17 00:00:00 2001 From: Tobias Nyholm Date: Tue, 14 Jun 2016 09:18:32 +0200 Subject: [PATCH 021/108] We do also provide async client --- composer.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/composer.json b/composer.json index cefccda..2b4b185 100644 --- a/composer.json +++ b/composer.json @@ -41,7 +41,8 @@ } }, "provide": { - "php-http/client-implementation": "1.0" + "php-http/client-implementation": "1.0", + "php-http/async-client-implementation": "1.0" }, "scripts": { "test": "vendor/bin/phpunit", From 10d80303dc38cc2fe99f03f875a4c4f28f9e9d72 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=9C=D0=B8=D1=85=D0=B0=D0=B8=D0=BB=20=D0=9A=D1=80=D0=B0?= =?UTF-8?q?=D1=81=D0=B8=D0=BB=D1=8C=D0=BD=D0=B8=D0=BA=D0=BE=D0=B2?= Date: Tue, 14 Jun 2016 10:37:01 +0300 Subject: [PATCH 022/108] Workaround for https://github.com/puli/issues/issues/190 --- phpunit.xml.dist | 2 +- tests/bootstrap.php | 10 ++++++++++ 2 files changed, 11 insertions(+), 1 deletion(-) create mode 100644 tests/bootstrap.php diff --git a/phpunit.xml.dist b/phpunit.xml.dist index c2f6ec4..c6643dc 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -1,5 +1,5 @@ - + tests/ diff --git a/tests/bootstrap.php b/tests/bootstrap.php new file mode 100644 index 0000000..6a83c44 --- /dev/null +++ b/tests/bootstrap.php @@ -0,0 +1,10 @@ +addClassMap( + [ + 'Puli\\GeneratedPuliFactory' => __DIR__ . '/../.puli/GeneratedPuliFactory.php' + ] +); From 6d319ff8364b6edc3b40570f5e93f6ac23409b66 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=9C=D0=B8=D1=85=D0=B0=D0=B8=D0=BB=20=D0=9A=D1=80=D0=B0?= =?UTF-8?q?=D1=81=D0=B8=D0=BB=D1=8C=D0=BD=D0=B8=D0=BA=D0=BE=D0=B2?= Date: Tue, 14 Jun 2016 10:43:37 +0300 Subject: [PATCH 023/108] Version 1.4.2 --- CHANGELOG.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6db23bc..c32fd34 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,12 @@ # Change Log +## 1.4.2 - 2016-06-14 + +### Added + +- #23: "php-http/async-client-implementation" added to "provide" section. + + ## 1.4.1 - 2016-05-30 ### Fixed From af8026066236c77245d644ebbd90eecc8ccf7b1d Mon Sep 17 00:00:00 2001 From: Tobias Nyholm Date: Tue, 2 Aug 2016 12:49:05 +0200 Subject: [PATCH 024/108] Dev updated dependencies (#24) Updated dependencies --- composer.json | 11 +++++------ tests/PromiseCoreTest.php | 2 +- 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/composer.json b/composer.json index 2b4b185..464c88e 100644 --- a/composer.json +++ b/composer.json @@ -13,21 +13,20 @@ "prefer-stable": true, "minimum-stability": "beta", "require": { - "php": ">=5.5", + "php": "^5.5 || ^7.0", "ext-curl": "*", "php-http/httplug": "^1.0", - "php-http/message-factory": "^1.0" + "php-http/message-factory": "^1.0.2", + "php-http/message": "^1.2" }, "suggest": { "php-http/discovery": "Allow automatically discover needed HTTPlug implementations" }, "require-dev": { "guzzlehttp/psr7": "^1.0", - "php-http/adapter-integration-tests": "0.4.*", - "php-http/message": "^1.2", - "php-http/discovery": "~0.8.0", + "php-http/client-integration-tests": "^0.5.1", + "php-http/discovery": "^1.0", "phpunit/phpunit": "^4.8", - "puli/composer-plugin": "^1.0@beta,>=1.0.0-beta9", "zendframework/zend-diactoros": "^1.0" }, "autoload": { diff --git a/tests/PromiseCoreTest.php b/tests/PromiseCoreTest.php index 2c167dd..590b039 100644 --- a/tests/PromiseCoreTest.php +++ b/tests/PromiseCoreTest.php @@ -47,7 +47,7 @@ function (ResponseInterface $response) { return $response->withAddedHeader('X-Test', 'bar'); } ); - static::assertEquals('foo,bar', $core->getResponse()->getHeaderLine('X-Test')); + static::assertEquals('foo, bar', $core->getResponse()->getHeaderLine('X-Test')); } /** From 31a2de34a2393905101ae320e09533c6fd44bd94 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=9C=D0=B8=D1=85=D0=B0=D0=B8=D0=BB=20=D0=9A=D1=80=D0=B0?= =?UTF-8?q?=D1=81=D0=B8=D0=BB=D1=8C=D0=BD=D0=B8=D0=BA=D0=BE=D0=B2?= Date: Wed, 3 Aug 2016 09:47:07 +0300 Subject: [PATCH 025/108] Remove unused imports. --- src/CurlPromise.php | 1 - tests/HttpAsyncClientDiactorosTest.php | 2 -- tests/HttpClientDiactorosTest.php | 2 -- 3 files changed, 5 deletions(-) diff --git a/src/CurlPromise.php b/src/CurlPromise.php index 150c422..68a775c 100644 --- a/src/CurlPromise.php +++ b/src/CurlPromise.php @@ -1,7 +1,6 @@ Date: Wed, 3 Aug 2016 09:56:56 +0300 Subject: [PATCH 026/108] PHPDoc. --- src/Client.php | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Client.php b/src/Client.php index 2ac31f2..b5f0aeb 100644 --- a/src/Client.php +++ b/src/Client.php @@ -74,6 +74,7 @@ class Client implements HttpClient, HttpAsyncClient * @param array $options cURL options (see http://php.net/curl_setopt) * * @throws \LogicException If some factory not provided and php-http/discovery not installed + * @throws \Http\Discovery\Exception\NotFoundException If factory discovery failed. * * @since 1.0 */ From f06bc4732d4b2a40a5b63fa2785d6765b64db4aa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=9C=D0=B8=D1=85=D0=B0=D0=B8=D0=BB=20=D0=9A=D1=80=D0=B0?= =?UTF-8?q?=D1=81=D0=B8=D0=BB=D1=8C=D0=BD=D0=B8=D0=BA=D0=BE=D0=B2?= Date: Wed, 3 Aug 2016 11:32:31 +0300 Subject: [PATCH 027/108] Request body can be send with any method except GET, HEAD and TRACE. --- CHANGELOG.md | 6 ++++++ src/Client.php | 12 ++++++++++-- tests/HttpAsyncClientTestCase.php | 2 +- tests/HttpClientTestCase.php | 2 +- 4 files changed, 18 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c32fd34..0806dfc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Change Log +## Unreleased + +### Changed + +- Request body can be send with any method except GET, HEAD and TRACE. + ## 1.4.2 - 2016-06-14 ### Added diff --git a/src/Client.php b/src/Client.php index b5f0aeb..6d3672b 100644 --- a/src/Client.php +++ b/src/Client.php @@ -202,8 +202,15 @@ private function createCurlOptions(RequestInterface $request, ResponseBuilder $r $options[CURLOPT_HTTP_VERSION] = $this->getProtocolVersion($request->getProtocolVersion()); $options[CURLOPT_URL] = (string) $request->getUri(); - if (in_array($request->getMethod(), ['OPTIONS', 'POST', 'PUT'], true)) { - // cURL allows request body only for these methods. + /* + * Add body to request. Some HTTP methods can not have payload: + * + * - GET — cURL will automatically change method to PUT or POST if we set CURLOPT_UPLOAD or + * CURLOPT_POSTFIELDS. + * - HEAD — cURL treats HEAD as GET request with a same restrictions. + * - TRACE — According to RFC7231: a client MUST NOT send a message body in a TRACE request. + */ + if (!in_array($request->getMethod(), ['GET', 'HEAD', 'TRACE'], true)) { $body = $request->getBody(); $bodySize = $body->getSize(); if ($bodySize !== 0) { @@ -225,6 +232,7 @@ private function createCurlOptions(RequestInterface $request, ResponseBuilder $r } if ($request->getMethod() === 'HEAD') { + // This will set HTTP method to "HEAD". $options[CURLOPT_NOBODY] = true; } elseif ($request->getMethod() !== 'GET') { // GET is a default method. Other methods should be specified explicitly. diff --git a/tests/HttpAsyncClientTestCase.php b/tests/HttpAsyncClientTestCase.php index 08a29ea..f724601 100644 --- a/tests/HttpAsyncClientTestCase.php +++ b/tests/HttpAsyncClientTestCase.php @@ -17,7 +17,7 @@ public function testAsyncSendRequest($method, $uri, array $headers, $body) if (defined('HHVM_VERSION')) { static::markTestSkipped('This test can not run under HHVM'); } - if (null !== $body && !in_array($method, ['OPTIONS', 'POST', 'PUT'], true)) { + if (null !== $body && in_array($method, ['GET', 'HEAD', 'TRACE'], true)) { static::markTestSkipped('cURL can not send body using ' . $method); } parent::testAsyncSendRequest( diff --git a/tests/HttpClientTestCase.php b/tests/HttpClientTestCase.php index 8086879..d2566a2 100644 --- a/tests/HttpClientTestCase.php +++ b/tests/HttpClientTestCase.php @@ -26,7 +26,7 @@ public function testSendRequest($method, $uri, array $headers, $body) if (defined('HHVM_VERSION')) { static::markTestSkipped('This test can not run under HHVM'); } - if (null !== $body && !in_array($method, ['OPTIONS', 'POST', 'PUT'], true)) { + if (null !== $body && in_array($method, ['GET', 'HEAD', 'TRACE'], true)) { static::markTestSkipped('cURL can not send body using ' . $method); } parent::testSendRequest( From e564aee9d7c502908928a5cd8ca2e6e428d2115a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=9C=D0=B8=D1=85=D0=B0=D0=B8=D0=BB=20=D0=9A=D1=80=D0=B0?= =?UTF-8?q?=D1=81=D0=B8=D0=BB=D1=8C=D0=BD=D0=B8=D0=BA=D0=BE=D0=B2?= Date: Wed, 3 Aug 2016 11:39:15 +0300 Subject: [PATCH 028/108] #25: Make discovery a hard dependency --- composer.json | 7 ++----- src/Client.php | 24 ++---------------------- 2 files changed, 4 insertions(+), 27 deletions(-) diff --git a/composer.json b/composer.json index 464c88e..e46a5cf 100644 --- a/composer.json +++ b/composer.json @@ -17,15 +17,12 @@ "ext-curl": "*", "php-http/httplug": "^1.0", "php-http/message-factory": "^1.0.2", - "php-http/message": "^1.2" - }, - "suggest": { - "php-http/discovery": "Allow automatically discover needed HTTPlug implementations" + "php-http/message": "^1.2", + "php-http/discovery": "^1.0" }, "require-dev": { "guzzlehttp/psr7": "^1.0", "php-http/client-integration-tests": "^0.5.1", - "php-http/discovery": "^1.0", "phpunit/phpunit": "^4.8", "zendframework/zend-diactoros": "^1.0" }, diff --git a/src/Client.php b/src/Client.php index 6d3672b..afd40d7 100644 --- a/src/Client.php +++ b/src/Client.php @@ -26,11 +26,6 @@ */ class Client implements HttpClient, HttpAsyncClient { - /** - * @access private - */ - const DEPENDENCY_MSG = 'You should either provide $%s argument or install "php-http/discovery"'; - /** * cURL options * @@ -73,7 +68,6 @@ class Client implements HttpClient, HttpAsyncClient * @param StreamFactory|null $streamFactory HTTP Stream factory * @param array $options cURL options (see http://php.net/curl_setopt) * - * @throws \LogicException If some factory not provided and php-http/discovery not installed * @throws \Http\Discovery\Exception\NotFoundException If factory discovery failed. * * @since 1.0 @@ -83,22 +77,8 @@ public function __construct( StreamFactory $streamFactory = null, array $options = [] ) { - if (null === $messageFactory) { - if (!class_exists(MessageFactoryDiscovery::class)) { - throw new \LogicException(sprintf(self::DEPENDENCY_MSG, 'messageFactory')); - } - $messageFactory = MessageFactoryDiscovery::find(); - } - $this->messageFactory = $messageFactory; - - if (null === $streamFactory) { - if (!class_exists(StreamFactoryDiscovery::class)) { - throw new \LogicException(sprintf(self::DEPENDENCY_MSG, 'streamFactory')); - } - $streamFactory = StreamFactoryDiscovery::find(); - } - $this->streamFactory = $streamFactory; - + $this->messageFactory = $messageFactory ?: MessageFactoryDiscovery::find(); + $this->streamFactory = $streamFactory ?: StreamFactoryDiscovery::find(); $this->options = $options; } From a5dd7f640d233675273cf2cc99a6fb12d052b876 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=9C=D0=B8=D1=85=D0=B0=D0=B8=D0=BB=20=D0=9A=D1=80=D0=B0?= =?UTF-8?q?=D1=81=D0=B8=D0=BB=D1=8C=D0=BD=D0=B8=D0=BA=D0=BE=D0=B2?= Date: Wed, 3 Aug 2016 11:41:04 +0300 Subject: [PATCH 029/108] PHPDoc. --- src/Client.php | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/Client.php b/src/Client.php index afd40d7..f70713a 100644 --- a/src/Client.php +++ b/src/Client.php @@ -99,6 +99,7 @@ public function __destruct() * * @return ResponseInterface * + * @throws \InvalidArgumentException For invalid header names or values. * @throws \RuntimeException If creating the body stream fails. * @throws \UnexpectedValueException if unsupported HTTP version requested * @throws RequestException @@ -136,6 +137,7 @@ public function sendRequest(RequestInterface $request) * * @return Promise * + * @throws \InvalidArgumentException For invalid header names or values. * @throws \RuntimeException If creating the body stream fails. * @throws \UnexpectedValueException If unsupported HTTP version requested * @throws Exception @@ -166,8 +168,9 @@ public function sendAsyncRequest(RequestInterface $request) * @param RequestInterface $request * @param ResponseBuilder $responseBuilder * - * @throws \UnexpectedValueException if unsupported HTTP version requested + * @throws \InvalidArgumentException For invalid header names or values. * @throws \RuntimeException if can not read body + * @throws \UnexpectedValueException if unsupported HTTP version requested * * @return array */ From a1d33b1dd706b353e487f52cced7e3e76d73f4ee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=9C=D0=B8=D1=85=D0=B0=D0=B8=D0=BB=20=D0=9A=D1=80=D0=B0?= =?UTF-8?q?=D1=81=D0=B8=D0=BB=D1=8C=D0=BD=D0=B8=D0=BA=D0=BE=D0=B2?= Date: Wed, 3 Aug 2016 11:41:38 +0300 Subject: [PATCH 030/108] Update CHANGELOG.md --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0806dfc..91ba0c6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ ### Changed - Request body can be send with any method except GET, HEAD and TRACE. +- #25: Make discovery a hard dependency. ## 1.4.2 - 2016-06-14 From fc7209fd29858718d444d41eb88f42188aeb7af4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=9C=D0=B8=D1=85=D0=B0=D0=B8=D0=BB=20=D0=9A=D1=80=D0=B0?= =?UTF-8?q?=D1=81=D0=B8=D0=BB=D1=8C=D0=BD=D0=B8=D0=BA=D0=BE=D0=B2?= Date: Wed, 3 Aug 2016 11:46:57 +0300 Subject: [PATCH 031/108] Version 1.5 --- CHANGELOG.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 91ba0c6..a39e837 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,12 +1,13 @@ # Change Log -## Unreleased +## 1.5 - 2016-08-03 ### Changed - Request body can be send with any method except GET, HEAD and TRACE. - #25: Make discovery a hard dependency. + ## 1.4.2 - 2016-06-14 ### Added From 4a588d1779401587f1199a32a05168e6fce44b4e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=9C=D0=B8=D1=85=D0=B0=D0=B8=D0=BB=20=D0=9A=D1=80=D0=B0?= =?UTF-8?q?=D1=81=D0=B8=D0=BB=D1=8C=D0=BD=D0=B8=D0=BA=D0=BE=D0=B2?= Date: Mon, 29 Aug 2016 10:54:02 +0300 Subject: [PATCH 032/108] Fix #26: Combining CurlClient with StopwatchPlugin causes Promise onRejected handler to never be invoked. --- src/PromiseCore.php | 19 ++++++++++++++----- tests/PromiseCoreTest.php | 33 +++++++++++++++++++++++++++++++++ 2 files changed, 47 insertions(+), 5 deletions(-) diff --git a/src/PromiseCore.php b/src/PromiseCore.php index 721468c..1adea40 100644 --- a/src/PromiseCore.php +++ b/src/PromiseCore.php @@ -185,7 +185,12 @@ public function fulfill() $this->state = Promise::FULFILLED; $response = $this->responseBuilder->getResponse(); $response->getBody()->seek(0); - $response = $this->call($this->onFulfilled, $response); + + while (count($this->onFulfilled) > 0) { + $callback = array_shift($this->onFulfilled); + $response = call_user_func($callback, $response); + } + if ($response instanceof ResponseInterface) { $this->responseBuilder->setResponse($response); } @@ -201,10 +206,14 @@ public function reject(Exception $exception) $this->exception = $exception; $this->state = Promise::REJECTED; - try { - $this->call($this->onRejected, $this->exception); - } catch (Exception $exception) { - $this->exception = $exception; + while (count($this->onRejected) > 0) { + $callback = array_shift($this->onRejected); + try { + $exception = call_user_func($callback, $this->exception); + $this->exception = $exception; + } catch (Exception $exception) { + $this->exception = $exception; + } } } diff --git a/tests/PromiseCoreTest.php b/tests/PromiseCoreTest.php index 590b039..aeb30e2 100644 --- a/tests/PromiseCoreTest.php +++ b/tests/PromiseCoreTest.php @@ -83,6 +83,39 @@ function (RequestException $exception) { static::assertEquals('Bar', $core->getException()->getMessage()); } + /** + * «onReject» callback can throw exception. + * + * @see https://github.com/php-http/curl-client/issues/26 + */ + public function testIssue26() + { + $request = $this->createRequest('GET', '/'); + $this->handle = curl_init(); + + $core = new PromiseCore( + $request, + $this->handle, + new ResponseBuilder($this->createResponse()) + ); + $core->addOnRejected( + function (RequestException $exception) { + throw new RequestException('Foo', $exception->getRequest(), $exception); + } + ); + $core->addOnRejected( + function (RequestException $exception) { + return new RequestException('Bar', $exception->getRequest(), $exception); + } + ); + + $exception = new RequestException('Error', $request); + $core->reject($exception); + static::assertEquals(Promise::REJECTED, $core->getState()); + static::assertInstanceOf(Exception::class, $core->getException()); + static::assertEquals('Bar', $core->getException()->getMessage()); + } + /** * @expectedException \LogicException */ From 8603d1529163fe4f353c61e7abb4e4ec0630dab3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=9C=D0=B8=D1=85=D0=B0=D0=B8=D0=BB=20=D0=9A=D1=80=D0=B0?= =?UTF-8?q?=D1=81=D0=B8=D0=BB=D1=8C=D0=BD=D0=B8=D0=BA=D0=BE=D0=B2?= Date: Mon, 29 Aug 2016 10:54:59 +0300 Subject: [PATCH 033/108] Version 1.5.1 --- CHANGELOG.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index a39e837..9816c0d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,13 @@ # Change Log +## 1.5.1 - 2016-08-29 + +### Fixed + +- #26: Combining CurlClient with StopwatchPlugin causes Promise onRejected handler to never be + invoked. + + ## 1.5 - 2016-08-03 ### Changed From 4f33a3de90a65f54be0edeecef61591b4d09684f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=9C=D0=B8=D1=85=D0=B0=D0=B8=D0=BB=20=D0=9A=D1=80=D0=B0?= =?UTF-8?q?=D1=81=D0=B8=D0=BB=D1=8C=D0=BD=D0=B8=D0=BA=D0=BE=D0=B2?= Date: Mon, 12 Sep 2016 16:12:01 +0300 Subject: [PATCH 034/108] Set latest PHPUnit 4.8.x version, because we use "--prefer-lowest" in Travis config. --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index e46a5cf..b151033 100644 --- a/composer.json +++ b/composer.json @@ -23,7 +23,7 @@ "require-dev": { "guzzlehttp/psr7": "^1.0", "php-http/client-integration-tests": "^0.5.1", - "phpunit/phpunit": "^4.8", + "phpunit/phpunit": "^4.8.27", "zendframework/zend-diactoros": "^1.0" }, "autoload": { From 771ecd685fdbac4a08f63f3203fd4ee126a4bfab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=9C=D0=B8=D1=85=D0=B0=D0=B8=D0=BB=20=D0=9A=D1=80=D0=B0?= =?UTF-8?q?=D1=81=D0=B8=D0=BB=D1=8C=D0=BD=D0=B8=D0=BA=D0=BE=D0=B2?= Date: Mon, 12 Sep 2016 16:14:51 +0300 Subject: [PATCH 035/108] Throw NetworkException on network errors. --- CHANGELOG.md | 7 +++++++ src/Client.php | 20 ++++++++++++++++---- 2 files changed, 23 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9816c0d..fbbe04e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,12 @@ # Change Log +## Unreleased + +### Changed + +- `Client::sendRequest` now throws NetworkException on network errors. + + ## 1.5.1 - 2016-08-29 ### Fixed diff --git a/src/Client.php b/src/Client.php index f70713a..dbc214f 100644 --- a/src/Client.php +++ b/src/Client.php @@ -2,7 +2,6 @@ namespace Http\Client\Curl; use Http\Client\Exception; -use Http\Client\Exception\RequestException; use Http\Client\HttpAsyncClient; use Http\Client\HttpClient; use Http\Discovery\MessageFactoryDiscovery; @@ -99,11 +98,13 @@ public function __destruct() * * @return ResponseInterface * + * @throws \Http\Client\Exception\NetworkException In case of network problems. + * @throws \Http\Client\Exception\RequestException On invalid request. * @throws \InvalidArgumentException For invalid header names or values. * @throws \RuntimeException If creating the body stream fails. * @throws \UnexpectedValueException if unsupported HTTP version requested - * @throws RequestException * + * @since x.x Throw NetworkException on network errors. * @since 1.0 */ public function sendRequest(RequestInterface $request) @@ -120,8 +121,19 @@ public function sendRequest(RequestInterface $request) curl_setopt_array($this->handle, $options); curl_exec($this->handle); - if (curl_errno($this->handle) > 0) { - throw new RequestException(curl_error($this->handle), $request); + $errno = curl_errno($this->handle); + switch ($errno) { + case CURLE_OK: + // All OK, no actions needed. + break; + case CURLE_COULDNT_RESOLVE_PROXY: + case CURLE_COULDNT_RESOLVE_HOST: + case CURLE_COULDNT_CONNECT: + case CURLE_OPERATION_TIMEOUTED: + case CURLE_SSL_CONNECT_ERROR: + throw new Exception\NetworkException(curl_error($this->handle), $request); + default: + throw new Exception\RequestException(curl_error($this->handle), $request); } $response = $responseBuilder->getResponse(); From 550986190eaf02de556beaf8577bdff843cac7ee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=9C=D0=B8=D1=85=D0=B0=D0=B8=D0=BB=20=D0=9A=D1=80=D0=B0?= =?UTF-8?q?=D1=81=D0=B8=D0=BB=D1=8C=D0=BD=D0=B8=D0=BA=D0=BE=D0=B2?= Date: Mon, 12 Sep 2016 16:49:51 +0300 Subject: [PATCH 036/108] Replace \UnexpectedValueException with Http\Client\Exception\RequestException in Client::sendRequest and Client::sendAsyncRequest. --- CHANGELOG.md | 4 +++- src/Client.php | 17 +++++++++++------ 2 files changed, 14 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index fbbe04e..028862e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,7 +4,9 @@ ### Changed -- `Client::sendRequest` now throws NetworkException on network errors. +- `Client::sendRequest` now throws `Http\Client\Exception\NetworkException` on network errors. +- `\UnexpectedValueException` replaced with `Http\Client\Exception\RequestException` in + `Client::sendRequest` and `Client::sendAsyncRequest` ## 1.5.1 - 2016-08-29 diff --git a/src/Client.php b/src/Client.php index dbc214f..c85ab77 100644 --- a/src/Client.php +++ b/src/Client.php @@ -102,8 +102,8 @@ public function __destruct() * @throws \Http\Client\Exception\RequestException On invalid request. * @throws \InvalidArgumentException For invalid header names or values. * @throws \RuntimeException If creating the body stream fails. - * @throws \UnexpectedValueException if unsupported HTTP version requested * + * @since x.x \UnexpectedValueException replaced with RequestException. * @since x.x Throw NetworkException on network errors. * @since 1.0 */ @@ -149,11 +149,11 @@ public function sendRequest(RequestInterface $request) * * @return Promise * + * @throws \Http\Client\Exception\RequestException On invalid request. * @throws \InvalidArgumentException For invalid header names or values. - * @throws \RuntimeException If creating the body stream fails. - * @throws \UnexpectedValueException If unsupported HTTP version requested - * @throws Exception + * @throws \RuntimeException If creating the body stream fails. * + * @since x.x \UnexpectedValueException replaced with RequestException. * @since 1.0 */ public function sendAsyncRequest(RequestInterface $request) @@ -180,9 +180,9 @@ public function sendAsyncRequest(RequestInterface $request) * @param RequestInterface $request * @param ResponseBuilder $responseBuilder * + * @throws \Http\Client\Exception\RequestException On invalid request. * @throws \InvalidArgumentException For invalid header names or values. * @throws \RuntimeException if can not read body - * @throws \UnexpectedValueException if unsupported HTTP version requested * * @return array */ @@ -194,7 +194,12 @@ private function createCurlOptions(RequestInterface $request, ResponseBuilder $r $options[CURLOPT_RETURNTRANSFER] = false; $options[CURLOPT_FOLLOWLOCATION] = false; - $options[CURLOPT_HTTP_VERSION] = $this->getProtocolVersion($request->getProtocolVersion()); + try { + $options[CURLOPT_HTTP_VERSION] + = $this->getProtocolVersion($request->getProtocolVersion()); + } catch (\UnexpectedValueException $e) { + throw new Exception\RequestException($e->getMessage(), $request); + } $options[CURLOPT_URL] = (string) $request->getUri(); /* From bfc8157d0d3233e38ea6cc13796a77a6f8a601c3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=9C=D0=B8=D1=85=D0=B0=D0=B8=D0=BB=20=D0=9A=D1=80=D0=B0?= =?UTF-8?q?=D1=81=D0=B8=D0=BB=D1=8C=D0=BD=D0=B8=D0=BA=D0=BE=D0=B2?= Date: Mon, 12 Sep 2016 17:09:09 +0300 Subject: [PATCH 037/108] Refactoring. --- src/Client.php | 87 +++++++++++++++++++++++++++++--------------------- 1 file changed, 51 insertions(+), 36 deletions(-) diff --git a/src/Client.php b/src/Client.php index c85ab77..1c928a6 100644 --- a/src/Client.php +++ b/src/Client.php @@ -202,42 +202,7 @@ private function createCurlOptions(RequestInterface $request, ResponseBuilder $r } $options[CURLOPT_URL] = (string) $request->getUri(); - /* - * Add body to request. Some HTTP methods can not have payload: - * - * - GET — cURL will automatically change method to PUT or POST if we set CURLOPT_UPLOAD or - * CURLOPT_POSTFIELDS. - * - HEAD — cURL treats HEAD as GET request with a same restrictions. - * - TRACE — According to RFC7231: a client MUST NOT send a message body in a TRACE request. - */ - if (!in_array($request->getMethod(), ['GET', 'HEAD', 'TRACE'], true)) { - $body = $request->getBody(); - $bodySize = $body->getSize(); - if ($bodySize !== 0) { - // Message has non empty body. - if (null === $bodySize || $bodySize > 1024 * 1024) { - // Avoid full loading large or unknown size body into memory - $options[CURLOPT_UPLOAD] = true; - if (null !== $bodySize) { - $options[CURLOPT_INFILESIZE] = $bodySize; - } - $options[CURLOPT_READFUNCTION] = function ($ch, $fd, $length) use ($body) { - return $body->read($length); - }; - } else { - // Small body can be loaded into memory - $options[CURLOPT_POSTFIELDS] = (string) $body; - } - } - } - - if ($request->getMethod() === 'HEAD') { - // This will set HTTP method to "HEAD". - $options[CURLOPT_NOBODY] = true; - } elseif ($request->getMethod() !== 'GET') { - // GET is a default method. Other methods should be specified explicitly. - $options[CURLOPT_CUSTOMREQUEST] = $request->getMethod(); - } + $options = $this->addRequestBodyOptions($request, $options); $options[CURLOPT_HTTPHEADER] = $this->createHeaders($request, $options); @@ -291,6 +256,56 @@ private function getProtocolVersion($requestVersion) return CURL_HTTP_VERSION_NONE; } + /** + * Add request body related cURL options. + * + * @param RequestInterface $request + * @param array $options + * + * @return array + */ + private function addRequestBodyOptions(RequestInterface $request, array $options) + { + /* + * Some HTTP methods cannot have payload: + * + * - GET — cURL will automatically change method to PUT or POST if we set CURLOPT_UPLOAD or + * CURLOPT_POSTFIELDS. + * - HEAD — cURL treats HEAD as GET request with a same restrictions. + * - TRACE — According to RFC7231: a client MUST NOT send a message body in a TRACE request. + */ + if (!in_array($request->getMethod(), ['GET', 'HEAD', 'TRACE'], true)) { + $body = $request->getBody(); + $bodySize = $body->getSize(); + if ($bodySize !== 0) { + // Message has non empty body. + if (null === $bodySize || $bodySize > 1024 * 1024) { + // Avoid full loading large or unknown size body into memory + $options[CURLOPT_UPLOAD] = true; + if (null !== $bodySize) { + $options[CURLOPT_INFILESIZE] = $bodySize; + } + $options[CURLOPT_READFUNCTION] = function ($ch, $fd, $length) use ($body) { + return $body->read($length); + }; + } else { + // Small body can be loaded into memory + $options[CURLOPT_POSTFIELDS] = (string) $body; + } + } + } + + if ($request->getMethod() === 'HEAD') { + // This will set HTTP method to "HEAD". + $options[CURLOPT_NOBODY] = true; + } elseif ($request->getMethod() !== 'GET') { + // GET is a default method. Other methods should be specified explicitly. + $options[CURLOPT_CUSTOMREQUEST] = $request->getMethod(); + } + + return $options; + } + /** * Create headers array for CURLOPT_HTTPHEADER * From 984b3e6e9b7c56bb85f6db3725e5a74e83cd6257 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=9C=D0=B8=D1=85=D0=B0=D0=B8=D0=BB=20=D0=9A=D1=80=D0=B0?= =?UTF-8?q?=D1=81=D0=B8=D0=BB=D1=8C=D0=BD=D0=B8=D0=BA=D0=BE=D0=B2?= Date: Mon, 12 Sep 2016 17:10:21 +0300 Subject: [PATCH 038/108] Version 1.6 --- CHANGELOG.md | 2 +- src/Client.php | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 028862e..bf417df 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,6 @@ # Change Log -## Unreleased +## 1.6 - 2016-09-12 ### Changed diff --git a/src/Client.php b/src/Client.php index 1c928a6..dca129d 100644 --- a/src/Client.php +++ b/src/Client.php @@ -103,8 +103,8 @@ public function __destruct() * @throws \InvalidArgumentException For invalid header names or values. * @throws \RuntimeException If creating the body stream fails. * - * @since x.x \UnexpectedValueException replaced with RequestException. - * @since x.x Throw NetworkException on network errors. + * @since 1.6 \UnexpectedValueException replaced with RequestException. + * @since 1.6 Throw NetworkException on network errors. * @since 1.0 */ public function sendRequest(RequestInterface $request) @@ -153,7 +153,7 @@ public function sendRequest(RequestInterface $request) * @throws \InvalidArgumentException For invalid header names or values. * @throws \RuntimeException If creating the body stream fails. * - * @since x.x \UnexpectedValueException replaced with RequestException. + * @since 1.6 \UnexpectedValueException replaced with RequestException. * @since 1.0 */ public function sendAsyncRequest(RequestInterface $request) From 70f02e063cc8dda3094088e78f7b07d758b643ff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=9C=D0=B8=D1=85=D0=B0=D0=B8=D0=BB=20=D0=9A=D1=80=D0=B0?= =?UTF-8?q?=D1=81=D0=B8=D0=BB=D1=8C=D0=BD=D0=B8=D0=BA=D0=BE=D0=B2?= Date: Fri, 11 Nov 2016 12:36:03 +0300 Subject: [PATCH 039/108] Remove unused code. --- src/PromiseCore.php | 18 ------------------ 1 file changed, 18 deletions(-) diff --git a/src/PromiseCore.php b/src/PromiseCore.php index 1adea40..01b8f11 100644 --- a/src/PromiseCore.php +++ b/src/PromiseCore.php @@ -216,22 +216,4 @@ public function reject(Exception $exception) } } } - - /** - * Call functions. - * - * @param callable[] $callbacks on fulfill or on reject callback queue - * @param mixed $argument response or exception - * - * @return mixed response or exception - */ - private function call(array &$callbacks, $argument) - { - while (count($callbacks) > 0) { - $callback = array_shift($callbacks); - $argument = call_user_func($callback, $argument); - } - - return $argument; - } } From 55bdbe741301426bc71c8b9b62a085a9f8f57527 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=9C=D0=B8=D1=85=D0=B0=D0=B8=D0=BB=20=D0=9A=D1=80=D0=B0?= =?UTF-8?q?=D1=81=D0=B8=D0=BB=D1=8C=D0=BD=D0=B8=D0=BA=D0=BE=D0=B2?= Date: Fri, 11 Nov 2016 14:43:36 +0300 Subject: [PATCH 040/108] Fix #27: ErrorPlugin and sendAsyncRequest() incompatibility --- CHANGELOG.md | 7 +++++++ src/MultiRunner.php | 8 +------- src/PromiseCore.php | 11 ++++++++--- 3 files changed, 16 insertions(+), 10 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index bf417df..97c2d83 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,12 @@ # Change Log +## Unreleased + +### Fixed + +- #27: ErrorPlugin and sendAsyncRequest() incompatibility + + ## 1.6 - 2016-09-12 ### Changed diff --git a/src/MultiRunner.php b/src/MultiRunner.php index 58893f1..9094c0f 100644 --- a/src/MultiRunner.php +++ b/src/MultiRunner.php @@ -93,13 +93,7 @@ public function wait(PromiseCore $targetCore = null) } if (CURLE_OK === $info['result']) { - try { - $core->fulfill(); - } catch (\Exception $e) { - $core->reject( - new RequestException($e->getMessage(), $core->getRequest(), $e) - ); - } + $core->fulfill(); } else { $error = curl_error($core->getHandle()); $core->reject(new RequestException($error, $core->getRequest())); diff --git a/src/PromiseCore.php b/src/PromiseCore.php index 01b8f11..f1a3aa5 100644 --- a/src/PromiseCore.php +++ b/src/PromiseCore.php @@ -177,14 +177,19 @@ public function getException() /** * Fulfill promise. - * - * @throws \Exception from on fulfill handler. */ public function fulfill() { $this->state = Promise::FULFILLED; $response = $this->responseBuilder->getResponse(); - $response->getBody()->seek(0); + try { + $response->getBody()->seek(0); + } catch (\RuntimeException $e) { + $exception = new Exception\TransferException($e->getMessage(), $e->getCode(), $e); + $this->reject($exception); + + return; + } while (count($this->onFulfilled) > 0) { $callback = array_shift($this->onFulfilled); From 38c5e38311839eef4e5f68cb2a0c9c6a49fc1dc8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=9C=D0=B8=D1=85=D0=B0=D0=B8=D0=BB=20=D0=9A=D1=80=D0=B0?= =?UTF-8?q?=D1=81=D0=B8=D0=BB=D1=8C=D0=BD=D0=B8=D0=BA=D0=BE=D0=B2?= Date: Fri, 11 Nov 2016 15:43:03 +0300 Subject: [PATCH 041/108] Version 1.6.1 --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 97c2d83..38e699f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,6 @@ # Change Log -## Unreleased +## 1.6.1 - 2016-11-11 ### Fixed From 4ea6cd09bc80973296bdfff90fa29bdaf42671f6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=9C=D0=B8=D1=85=D0=B0=D0=B8=D0=BB=20=D0=9A=D1=80=D0=B0?= =?UTF-8?q?=D1=81=D0=B8=D0=BB=D1=8C=D0=BD=D0=B8=D0=BA=D0=BE=D0=B2?= Date: Thu, 29 Dec 2016 12:21:44 +0300 Subject: [PATCH 042/108] Use binary mode to create response body stream. --- CHANGELOG.md | 7 +++++++ src/Client.php | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 38e699f..abc1d4c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,12 @@ # Change Log +## Unreleased + +### Changed + +- Use binary mode to create response body stream. + + ## 1.6.1 - 2016-11-11 ### Fixed diff --git a/src/Client.php b/src/Client.php index dca129d..e866f19 100644 --- a/src/Client.php +++ b/src/Client.php @@ -355,7 +355,7 @@ private function createHeaders(RequestInterface $request, array $options) private function createResponseBuilder() { try { - $body = $this->streamFactory->createStream(fopen('php://temp', 'w+')); + $body = $this->streamFactory->createStream(fopen('php://temp', 'w+b')); } catch (\InvalidArgumentException $e) { throw new \RuntimeException('Can not create "php://temp" stream.'); } From 13a895a96fed8ae460c2c950fa0801fb6488deb7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=9C=D0=B8=D1=85=D0=B0=D0=B8=D0=BB=20=D0=9A=D1=80=D0=B0?= =?UTF-8?q?=D1=81=D0=B8=D0=BB=D1=8C=D0=BD=D0=B8=D0=BA=D0=BE=D0=B2?= Date: Thu, 29 Dec 2016 12:29:48 +0300 Subject: [PATCH 043/108] Explicitly set "bin-dir" to override possible user-defined value. --- composer.json | 3 +++ 1 file changed, 3 insertions(+) diff --git a/composer.json b/composer.json index b151033..5d16222 100644 --- a/composer.json +++ b/composer.json @@ -12,6 +12,9 @@ ], "prefer-stable": true, "minimum-stability": "beta", + "config": { + "bin-dir": "vendor/bin" + }, "require": { "php": "^5.5 || ^7.0", "ext-curl": "*", From 4d3a81f79c3542400ed2e677a9cd2360f4115b94 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=9C=D0=B8=D1=85=D0=B0=D0=B8=D0=BB=20=D0=9A=D1=80=D0=B0?= =?UTF-8?q?=D1=81=D0=B8=D0=BB=D1=8C=D0=BD=D0=B8=D0=BA=D0=BE=D0=B2?= Date: Thu, 29 Dec 2016 13:23:17 +0300 Subject: [PATCH 044/108] #29: Request not using CURLOPT_POSTFIELDS have content-length set to 0 --- src/Client.php | 11 ++++++----- tests/HttpClientTestCase.php | 8 ++++++-- 2 files changed, 12 insertions(+), 7 deletions(-) diff --git a/src/Client.php b/src/Client.php index e866f19..631428b 100644 --- a/src/Client.php +++ b/src/Client.php @@ -317,20 +317,21 @@ private function addRequestBodyOptions(RequestInterface $request, array $options private function createHeaders(RequestInterface $request, array $options) { $curlHeaders = []; - $headers = array_keys($request->getHeaders()); - foreach ($headers as $name) { + $headers = $request->getHeaders(); + foreach ($headers as $name => $values) { $header = strtolower($name); if ('expect' === $header) { // curl-client does not support "Expect-Continue", so dropping "expect" headers continue; } if ('content-length' === $header) { - $values = [0]; if (array_key_exists(CURLOPT_POSTFIELDS, $options)) { + // Small body content length can be calculated here. $values = [strlen($options[CURLOPT_POSTFIELDS])]; + } elseif (!array_key_exists(CURLOPT_READFUNCTION, $options)) { + // Else if there is no body, forcing "Content-length" to 0 + $values = [0]; } - } else { - $values = $request->getHeader($name); } foreach ($values as $value) { $curlHeaders[] = $name . ': ' . $value; diff --git a/tests/HttpClientTestCase.php b/tests/HttpClientTestCase.php index d2566a2..3fc6590 100644 --- a/tests/HttpClientTestCase.php +++ b/tests/HttpClientTestCase.php @@ -78,7 +78,7 @@ public function testSendLargeFile() $request = self::$messageFactory->createRequest( 'POST', PHPUnitUtility::getUri(), - [], + ['content-length' => 1024 * 2048], $body ); @@ -86,9 +86,13 @@ public function testSendLargeFile() $this->assertResponse( $response, [ - 'body' => 'Ok', + 'body' => 'Ok' ] ); + + $request = $this->getRequest(); + self::assertArrayHasKey('CONTENT_LENGTH', $request['SERVER']); + self::assertEquals($body->getSize(), $request['SERVER']['CONTENT_LENGTH']); } /** From 6bcbe79aebbb4c749c9329a42a4d61d86ad9bd8a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=9C=D0=B8=D1=85=D0=B0=D0=B8=D0=BB=20=D0=9A=D1=80=D0=B0?= =?UTF-8?q?=D1=81=D0=B8=D0=BB=D1=8C=D0=BD=D0=B8=D0=BA=D0=BE=D0=B2?= Date: Mon, 2 Jan 2017 18:34:26 +0300 Subject: [PATCH 045/108] #29: Request not using CURLOPT_POSTFIELDS have content-length set to 0 Content-length located in $_SERVER['HTTP_CONTENT_LENGTH']. --- tests/HttpClientTestCase.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/HttpClientTestCase.php b/tests/HttpClientTestCase.php index 3fc6590..c97a84b 100644 --- a/tests/HttpClientTestCase.php +++ b/tests/HttpClientTestCase.php @@ -67,7 +67,7 @@ public function testSendRequestWithOutcome( public function testSendLargeFile() { $filename = $this->createTempFile(); - $fd = fopen($filename, 'a'); + $fd = fopen($filename, 'ab'); $buffer = str_repeat('x', 1024); for ($i = 0; $i < 2048; $i++) { fwrite($fd, $buffer); @@ -91,8 +91,8 @@ public function testSendLargeFile() ); $request = $this->getRequest(); - self::assertArrayHasKey('CONTENT_LENGTH', $request['SERVER']); - self::assertEquals($body->getSize(), $request['SERVER']['CONTENT_LENGTH']); + self::assertArrayHasKey('HTTP_CONTENT_LENGTH', $request['SERVER']); + self::assertEquals($body->getSize(), $request['SERVER']['HTTP_CONTENT_LENGTH']); } /** From 3f9ff1ffbe2496ed793734a9ab92b784a05d277a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=9C=D0=B8=D1=85=D0=B0=D0=B8=D0=BB=20=D0=9A=D1=80=D0=B0?= =?UTF-8?q?=D1=81=D0=B8=D0=BB=D1=8C=D0=BD=D0=B8=D0=BA=D0=BE=D0=B2?= Date: Mon, 2 Jan 2017 18:39:30 +0300 Subject: [PATCH 046/108] Version 1.6.2 --- CHANGELOG.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index abc1d4c..2f29448 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,10 @@ # Change Log -## Unreleased +## 1.6.2 - 2017-01-02 + +### Fixed + +- #29: Request not using CURLOPT_POSTFIELDS have content-length set to ### Changed From 59ad15684839d39fe07701a3caf3d138d497656c Mon Sep 17 00:00:00 2001 From: Tobias Nyholm Date: Thu, 9 Feb 2017 14:26:04 +0100 Subject: [PATCH 047/108] Make sure we rewind streams --- src/Client.php | 4 ++++ tests/ClientTest.php | 41 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 45 insertions(+) diff --git a/src/Client.php b/src/Client.php index 631428b..5696ab3 100644 --- a/src/Client.php +++ b/src/Client.php @@ -278,6 +278,10 @@ private function addRequestBodyOptions(RequestInterface $request, array $options $body = $request->getBody(); $bodySize = $body->getSize(); if ($bodySize !== 0) { + if ($body->isSeekable()) { + $body->rewind(); + } + // Message has non empty body. if (null === $bodySize || $bodySize > 1024 * 1024) { // Avoid full loading large or unknown size body into memory diff --git a/tests/ClientTest.php b/tests/ClientTest.php index 584d625..6beed4e 100644 --- a/tests/ClientTest.php +++ b/tests/ClientTest.php @@ -1,6 +1,7 @@ getMockBuilder(Client::class)->disableOriginalConstructor() + ->setMethods(['__none__'])->getMock(); + + $bodyOptions = new \ReflectionMethod(Client::class, 'addRequestBodyOptions'); + $bodyOptions->setAccessible(true); + + $body = \GuzzleHttp\Psr7\stream_for('abcdef'); + $body->seek(3); + $request = new Request('http://foo.com', 'POST', $body); + $options = $bodyOptions->invoke($client, $request, []); + + static::assertEquals('abcdef', $options[CURLOPT_POSTFIELDS]); + } + + public function testRewindLargeStream() + { + $client = $this->getMockBuilder(Client::class)->disableOriginalConstructor() + ->setMethods(['__none__'])->getMock(); + + $bodyOptions = new \ReflectionMethod(Client::class, 'addRequestBodyOptions'); + $bodyOptions->setAccessible(true); + + $content = 'abcdef'; + while (strlen($content) < 1024*1024+100) { + $content .= '123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890'; + } + + $length = strlen($content); + $body = \GuzzleHttp\Psr7\stream_for($content); + $body->seek(40); + $request = new Request('http://foo.com', 'POST', $body); + $options = $bodyOptions->invoke($client, $request, []); + + static::assertTrue(false !== strstr($options[CURLOPT_READFUNCTION](null, null, $length), 'abcdef'), 'Steam was not rewinded'); + } + /** * Discovery should be used if no factory given. */ From 0972ad0d7d37032a52077a5cbe27cf370f2007d8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=9C=D0=B8=D1=85=D0=B0=D0=B8=D0=BB=20=D0=9A=D1=80=D0=B0?= =?UTF-8?q?=D1=81=D0=B8=D0=BB=D1=8C=D0=BD=D0=B8=D0=BA=D0=BE=D0=B2?= Date: Thu, 9 Feb 2017 18:18:33 +0300 Subject: [PATCH 048/108] Version 1.7 --- CHANGELOG.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2f29448..a3f52d1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Change Log +## 1.7 - 2017-02-09 + +### Changed + +- #30: Make sure we rewind streams + ## 1.6.2 - 2017-01-02 ### Fixed From e982c5627c88fd7a611ec0e8db9495919667cc94 Mon Sep 17 00:00:00 2001 From: Tobias Nyholm Date: Mon, 29 May 2017 17:07:01 +0200 Subject: [PATCH 049/108] PHPUnit 6 compatability (#31) * PHPUnit 6 compatability * Require latest (unreleased) version of the integration tests --- composer.json | 2 +- tests/BaseUnitTestCase.php | 3 ++- tests/ClientTest.php | 3 ++- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/composer.json b/composer.json index 5d16222..c72b7a7 100644 --- a/composer.json +++ b/composer.json @@ -25,7 +25,7 @@ }, "require-dev": { "guzzlehttp/psr7": "^1.0", - "php-http/client-integration-tests": "^0.5.1", + "php-http/client-integration-tests": "^0.6", "phpunit/phpunit": "^4.8.27", "zendframework/zend-diactoros": "^1.0" }, diff --git a/tests/BaseUnitTestCase.php b/tests/BaseUnitTestCase.php index d53d665..3f2f0fd 100644 --- a/tests/BaseUnitTestCase.php +++ b/tests/BaseUnitTestCase.php @@ -3,13 +3,14 @@ use Http\Client\Curl\PromiseCore; use Http\Discovery\MessageFactoryDiscovery; +use PHPUnit\Framework\TestCase; use Psr\Http\Message\RequestInterface; use Psr\Http\Message\ResponseInterface; /** * Base class for unit tests */ -abstract class BaseUnitTestCase extends \PHPUnit_Framework_TestCase +abstract class BaseUnitTestCase extends TestCase { /** * Test cURL handle diff --git a/tests/ClientTest.php b/tests/ClientTest.php index 6beed4e..a091b82 100644 --- a/tests/ClientTest.php +++ b/tests/ClientTest.php @@ -3,6 +3,7 @@ use GuzzleHttp\Psr7\Stream; use Http\Client\Curl\Client; +use PHPUnit\Framework\TestCase; use Zend\Diactoros\Request; /** @@ -10,7 +11,7 @@ * * @covers Http\Client\Curl\Client */ -class ClientTest extends \PHPUnit_Framework_TestCase +class ClientTest extends TestCase { /** * "Expect" header should be empty From 0af69c93f21cc19814db044b748de62e0384e203 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=9C=D0=B8=D1=85=D0=B0=D0=B8=D0=BB=20=D0=9A=D1=80=D0=B0?= =?UTF-8?q?=D1=81=D0=B8=D0=BB=D1=8C=D0=BD=D0=B8=D0=BA=D0=BE=D0=B2?= Date: Wed, 16 Aug 2017 15:33:08 +0300 Subject: [PATCH 050/108] Fix code style with PHP-CS-Fixer --- src/Client.php | 59 +++++++++++++------------- src/CurlPromise.php | 15 ++++--- src/MultiRunner.php | 16 ++++--- src/PromiseCore.php | 28 ++++++------ src/ResponseBuilder.php | 5 ++- tests/BaseUnitTestCase.php | 14 +++--- tests/ClientTest.php | 14 +++--- tests/CurlPromiseTest.php | 7 +-- tests/HttpAsyncClientDiactorosTest.php | 3 +- tests/HttpAsyncClientGuzzleTest.php | 3 +- tests/HttpAsyncClientTestCase.php | 5 ++- tests/HttpClientDiactorosTest.php | 5 ++- tests/HttpClientGuzzleTest.php | 5 ++- tests/HttpClientTestCase.php | 13 +++--- tests/PromiseCoreTest.php | 9 ++-- tests/bootstrap.php | 4 +- 16 files changed, 109 insertions(+), 96 deletions(-) diff --git a/src/Client.php b/src/Client.php index 5696ab3..015e7ed 100644 --- a/src/Client.php +++ b/src/Client.php @@ -1,4 +1,5 @@ * @author Blake Williams * * @api + * * @since 1.0 */ class Client implements HttpClient, HttpAsyncClient { /** - * cURL options + * cURL options. * * @var array */ private $options; /** - * PSR-7 message factory + * PSR-7 message factory. * * @var MessageFactory */ private $messageFactory; /** - * PSR-7 stream factory + * PSR-7 stream factory. * * @var StreamFactory */ private $streamFactory; /** - * cURL synchronous requests handle + * cURL synchronous requests handle. * * @var resource|null */ private $handle = null; /** - * Simultaneous requests runner + * Simultaneous requests runner. * * @var MultiRunner|null */ private $multiRunner = null; /** - * Create new client + * Create new client. * * @param MessageFactory|null $messageFactory HTTP Message factory * @param StreamFactory|null $streamFactory HTTP Stream factory * @param array $options cURL options (see http://php.net/curl_setopt) * - * @throws \Http\Discovery\Exception\NotFoundException If factory discovery failed. + * @throws \Http\Discovery\Exception\NotFoundException If factory discovery failed * * @since 1.0 */ @@ -82,7 +83,7 @@ public function __construct( } /** - * Release resources if still active + * Release resources if still active. */ public function __destruct() { @@ -98,13 +99,13 @@ public function __destruct() * * @return ResponseInterface * - * @throws \Http\Client\Exception\NetworkException In case of network problems. - * @throws \Http\Client\Exception\RequestException On invalid request. - * @throws \InvalidArgumentException For invalid header names or values. - * @throws \RuntimeException If creating the body stream fails. + * @throws \Http\Client\Exception\NetworkException In case of network problems + * @throws \Http\Client\Exception\RequestException On invalid request + * @throws \InvalidArgumentException For invalid header names or values + * @throws \RuntimeException If creating the body stream fails * - * @since 1.6 \UnexpectedValueException replaced with RequestException. - * @since 1.6 Throw NetworkException on network errors. + * @since 1.6 \UnexpectedValueException replaced with RequestException + * @since 1.6 Throw NetworkException on network errors * @since 1.0 */ public function sendRequest(RequestInterface $request) @@ -149,11 +150,11 @@ public function sendRequest(RequestInterface $request) * * @return Promise * - * @throws \Http\Client\Exception\RequestException On invalid request. - * @throws \InvalidArgumentException For invalid header names or values. - * @throws \RuntimeException If creating the body stream fails. + * @throws \Http\Client\Exception\RequestException On invalid request + * @throws \InvalidArgumentException For invalid header names or values + * @throws \RuntimeException If creating the body stream fails * - * @since 1.6 \UnexpectedValueException replaced with RequestException. + * @since 1.6 \UnexpectedValueException replaced with RequestException * @since 1.0 */ public function sendAsyncRequest(RequestInterface $request) @@ -175,14 +176,14 @@ public function sendAsyncRequest(RequestInterface $request) } /** - * Generates cURL options + * Generates cURL options. * * @param RequestInterface $request * @param ResponseBuilder $responseBuilder * - * @throws \Http\Client\Exception\RequestException On invalid request. - * @throws \InvalidArgumentException For invalid header names or values. - * @throws \RuntimeException if can not read body + * @throws \Http\Client\Exception\RequestException On invalid request + * @throws \InvalidArgumentException For invalid header names or values + * @throws \RuntimeException if can not read body * * @return array */ @@ -231,7 +232,7 @@ private function createCurlOptions(RequestInterface $request, ResponseBuilder $r } /** - * Return cURL constant for specified HTTP version + * Return cURL constant for specified HTTP version. * * @param string $requestVersion * @@ -311,7 +312,7 @@ private function addRequestBodyOptions(RequestInterface $request, array $options } /** - * Create headers array for CURLOPT_HTTPHEADER + * Create headers array for CURLOPT_HTTPHEADER. * * @param RequestInterface $request * @param array $options cURL options @@ -338,7 +339,7 @@ private function createHeaders(RequestInterface $request, array $options) } } foreach ($values as $value) { - $curlHeaders[] = $name . ': ' . $value; + $curlHeaders[] = $name.': '.$value; } } /* @@ -351,11 +352,11 @@ private function createHeaders(RequestInterface $request, array $options) } /** - * Create new ResponseBuilder instance + * Create new ResponseBuilder instance. * * @return ResponseBuilder * - * @throws \RuntimeException If creating the stream from $body fails. + * @throws \RuntimeException If creating the stream from $body fails */ private function createResponseBuilder() { diff --git a/src/CurlPromise.php b/src/CurlPromise.php index 68a775c..5061af5 100644 --- a/src/CurlPromise.php +++ b/src/CurlPromise.php @@ -1,4 +1,5 @@ */ class CurlPromise implements Promise { /** - * Shared promise core + * Shared promise core. * * @var PromiseCore */ private $core; /** - * Requests runner + * Requests runner. * * @var MultiRunner */ @@ -49,13 +49,13 @@ public function __construct(PromiseCore $core, MultiRunner $runner) * If you do not care about one of the cases, you can set the corresponding callable to null * The callback will be called when the response or exception arrived and never more than once. * - * @param callable $onFulfilled Called when a response will be available. + * @param callable $onFulfilled Called when a response will be available * @param callable $onRejected Called when an error happens. * - * You must always return the Response in the interface or throw an Exception. + * You must always return the Response in the interface or throw an Exception * * @return Promise Always returns a new promise which is resolved with value of the executed - * callback (onFulfilled / onRejected). + * callback (onFulfilled / onRejected) */ public function then(callable $onFulfilled = null, callable $onRejected = null) { @@ -90,7 +90,7 @@ public function getState() * * @return \Psr\Http\Message\ResponseInterface|null Resolved value, null if $unwrap is set to false * - * @throws \Http\Client\Exception The rejection reason. + * @throws \Http\Client\Exception The rejection reason */ public function wait($unwrap = true) { @@ -103,6 +103,7 @@ public function wait($unwrap = true) return $this->core->getResponse(); } + return null; } } diff --git a/src/MultiRunner.php b/src/MultiRunner.php index 9094c0f..545f39a 100644 --- a/src/MultiRunner.php +++ b/src/MultiRunner.php @@ -1,33 +1,33 @@ */ class MultiRunner { /** - * cURL multi handle + * cURL multi handle. * * @var resource|null */ private $multiHandle = null; /** - * Awaiting cores + * Awaiting cores. * * @var PromiseCore[] */ private $cores = []; /** - * Release resources if still active + * Release resources if still active. */ public function __destruct() { @@ -37,7 +37,7 @@ public function __destruct() } /** - * Add promise to runner + * Add promise to runner. * * @param PromiseCore $core */ @@ -58,7 +58,7 @@ public function add(PromiseCore $core) } /** - * Remove promise from runner + * Remove promise from runner. * * @param PromiseCore $core */ @@ -68,6 +68,7 @@ public function remove(PromiseCore $core) if ($existed === $core) { curl_multi_remove_handle($this->multiHandle, $core->getHandle()); unset($this->cores[$index]); + return; } } @@ -122,6 +123,7 @@ private function findCoreByHandle($handle) return $core; } } + return null; } } diff --git a/src/PromiseCore.php b/src/PromiseCore.php index f1a3aa5..dbc89f6 100644 --- a/src/PromiseCore.php +++ b/src/PromiseCore.php @@ -1,4 +1,5 @@ */ class PromiseCore { /** - * HTTP request + * HTTP request. * * @var RequestInterface */ private $request; /** - * cURL handle + * cURL handle. * * @var resource */ private $handle; /** - * Response builder + * Response builder. * * @var ResponseBuilder */ private $responseBuilder; /** - * Promise state + * Promise state. * * @var string */ private $state; /** - * Exception + * Exception. * * @var Exception|null */ @@ -67,8 +67,8 @@ class PromiseCore /** * Create shared core. * - * @param RequestInterface $request HTTP request - * @param resource $handle cURL handle + * @param RequestInterface $request HTTP request + * @param resource $handle cURL handle * @param ResponseBuilder $responseBuilder */ public function __construct( @@ -117,7 +117,7 @@ public function addOnRejected(callable $callback) } /** - * Return cURL handle + * Return cURL handle. * * @return resource */ @@ -137,7 +137,7 @@ public function getState() } /** - * Return request + * Return request. * * @return RequestInterface */ @@ -149,7 +149,7 @@ public function getRequest() /** * Return the value of the promise (fulfilled). * - * @return ResponseInterface Response Object only when the Promise is fulfilled. + * @return ResponseInterface Response Object only when the Promise is fulfilled */ public function getResponse() { @@ -162,9 +162,9 @@ public function getResponse() * If the exception is an instance of Http\Client\Exception\HttpException it will contain * the response object with the status code and the http reason. * - * @return Exception Exception Object only when the Promise is rejected. + * @return Exception Exception Object only when the Promise is rejected * - * @throws \LogicException When the promise is not rejected. + * @throws \LogicException When the promise is not rejected */ public function getException() { @@ -204,7 +204,7 @@ public function fulfill() /** * Reject promise. * - * @param Exception $exception Reject reason. + * @param Exception $exception Reject reason */ public function reject(Exception $exception) { diff --git a/src/ResponseBuilder.php b/src/ResponseBuilder.php index 99e79db..805b330 100644 --- a/src/ResponseBuilder.php +++ b/src/ResponseBuilder.php @@ -1,16 +1,17 @@ getMockBuilder(PromiseCore::class)->disableOriginalConstructor() ->setMethods($methods)->getMock(); + return $core; } } diff --git a/tests/ClientTest.php b/tests/ClientTest.php index a091b82..e21130f 100644 --- a/tests/ClientTest.php +++ b/tests/ClientTest.php @@ -1,20 +1,20 @@ getMockBuilder(Client::class)->disableOriginalConstructor() @@ -60,7 +58,7 @@ public function testRewindLargeStream() $bodyOptions->setAccessible(true); $content = 'abcdef'; - while (strlen($content) < 1024*1024+100) { + while (strlen($content) < 1024 * 1024 + 100) { $content .= '123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890'; } @@ -78,7 +76,7 @@ public function testRewindLargeStream() */ public function testFactoryDiscovery() { - $client = new Client; + $client = new Client(); static::assertInstanceOf(Client::class, $client); } diff --git a/tests/CurlPromiseTest.php b/tests/CurlPromiseTest.php index ed422a9..e128e3a 100644 --- a/tests/CurlPromiseTest.php +++ b/tests/CurlPromiseTest.php @@ -1,4 +1,5 @@ createTempFile(); $fd = fopen($filename, 'ab'); $buffer = str_repeat('x', 1024); - for ($i = 0; $i < 2048; $i++) { + for ($i = 0; $i < 2048; ++$i) { fwrite($fd, $buffer); } fclose($fd); @@ -86,7 +87,7 @@ public function testSendLargeFile() $this->assertResponse( $response, [ - 'body' => 'Ok' + 'body' => 'Ok', ] ); @@ -109,7 +110,7 @@ protected function createTempFile() } /** - * Create stream from file + * Create stream from file. * * @param string $filename * @@ -118,7 +119,7 @@ protected function createTempFile() abstract protected function createFileStream($filename); /** - * Tears down the fixture + * Tears down the fixture. */ protected function tearDown() { diff --git a/tests/PromiseCoreTest.php b/tests/PromiseCoreTest.php index aeb30e2..3b704c6 100644 --- a/tests/PromiseCoreTest.php +++ b/tests/PromiseCoreTest.php @@ -1,4 +1,5 @@ addClassMap( [ - 'Puli\\GeneratedPuliFactory' => __DIR__ . '/../.puli/GeneratedPuliFactory.php' + 'Puli\\GeneratedPuliFactory' => __DIR__.'/../.puli/GeneratedPuliFactory.php', ] ); From 2abc03f7fd14b79997dbb6f68d54e22d3c14879e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=9C=D0=B8=D1=85=D0=B0=D0=B8=D0=BB=20=D0=9A=D1=80=D0=B0?= =?UTF-8?q?=D1=81=D0=B8=D0=BB=D1=8C=D0=BD=D0=B8=D0=BA=D0=BE=D0=B2?= Date: Wed, 16 Aug 2017 15:54:48 +0300 Subject: [PATCH 051/108] Add PHP 7.1 to build environments --- .travis.yml | 1 + CHANGELOG.md | 3 +++ 2 files changed, 4 insertions(+) diff --git a/.travis.yml b/.travis.yml index abfe989..f9bc448 100644 --- a/.travis.yml +++ b/.travis.yml @@ -10,6 +10,7 @@ php: - 5.5 - 5.6 - 7.0 + - 7.1 - hhvm env: diff --git a/CHANGELOG.md b/CHANGELOG.md index a3f52d1..3719b8d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,8 @@ # Change Log +## Unreleased + + ## 1.7 - 2017-02-09 ### Changed From 9c1e6a0cfbf0877fcd279f4b6908b57f63917467 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=9C=D0=B8=D1=85=D0=B0=D0=B8=D0=BB=20=D0=9A=D1=80=D0=B0?= =?UTF-8?q?=D1=81=D0=B8=D0=BB=D1=8C=D0=BD=D0=B8=D0=BA=D0=BE=D0=B2?= Date: Wed, 16 Aug 2017 15:59:44 +0300 Subject: [PATCH 052/108] HHVM testing requires Trusty --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index f9bc448..928b418 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,6 +1,6 @@ language: php - sudo: false +dist: trusty cache: directories: From 5d9e01ebc5ebb565b1ed16bd7cc3e929ff1a2355 Mon Sep 17 00:00:00 2001 From: Samuel Nogueira Date: Mon, 26 Mar 2018 19:11:47 +0100 Subject: [PATCH 053/108] Fix #36: Calls to assert() now pass a boolean condition instead of string (PHP 7.2 compat) --- .travis.yml | 1 + src/PromiseCore.php | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index 928b418..fd4905e 100644 --- a/.travis.yml +++ b/.travis.yml @@ -11,6 +11,7 @@ php: - 5.6 - 7.0 - 7.1 + - 7.2 - hhvm env: diff --git a/src/PromiseCore.php b/src/PromiseCore.php index dbc89f6..4e6f571 100644 --- a/src/PromiseCore.php +++ b/src/PromiseCore.php @@ -76,8 +76,8 @@ public function __construct( $handle, ResponseBuilder $responseBuilder ) { - assert('is_resource($handle)'); - assert('get_resource_type($handle) === "curl"'); + assert(is_resource($handle)); + assert(get_resource_type($handle) === "curl"); $this->request = $request; $this->handle = $handle; From 82201483cbe9e28450aa9267c58de8e3e50e7d0e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=9C=D0=B8=D1=85=D0=B0=D0=B8=D0=BB=20=D0=9A=D1=80=D0=B0?= =?UTF-8?q?=D1=81=D0=B8=D0=BB=D1=8C=D0=BD=D0=B8=D0=BA=D0=BE=D0=B2?= Date: Mon, 26 Mar 2018 22:03:12 +0300 Subject: [PATCH 054/108] Throw exceptions instead of using assertions --- CHANGELOG.md | 3 +++ src/PromiseCore.php | 27 ++++++++++++++++++++++----- tests/PromiseCoreTest.php | 34 ++++++++++++++++++++++++++++++++++ 3 files changed, 59 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3719b8d..c32c272 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,9 @@ ## Unreleased +### Fixed + +- #36: Failure evaluating code: is_resource($handle) (string assertions are deprecated in PHP 7.2) ## 1.7 - 2017-02-09 diff --git a/src/PromiseCore.php b/src/PromiseCore.php index 4e6f571..09065c4 100644 --- a/src/PromiseCore.php +++ b/src/PromiseCore.php @@ -67,17 +67,34 @@ class PromiseCore /** * Create shared core. * - * @param RequestInterface $request HTTP request - * @param resource $handle cURL handle - * @param ResponseBuilder $responseBuilder + * @param RequestInterface $request HTTP request. + * @param resource $handle cURL handle. + * @param ResponseBuilder $responseBuilder Response builder. + * + * @throws \InvalidArgumentException If $handle is not a cURL resource. */ public function __construct( RequestInterface $request, $handle, ResponseBuilder $responseBuilder ) { - assert(is_resource($handle)); - assert(get_resource_type($handle) === "curl"); + if (!is_resource($handle)) { + throw new \InvalidArgumentException( + sprintf( + 'Parameter $handle expected to be a cURL resource, %s given', + gettype($handle) + ) + ); + } + + if (get_resource_type($handle) !== 'curl') { + throw new \InvalidArgumentException( + sprintf( + 'Parameter $handle expected to be a cURL resource, %s resource given', + get_resource_type($handle) + ) + ); + } $this->request = $request; $this->handle = $handle; diff --git a/tests/PromiseCoreTest.php b/tests/PromiseCoreTest.php index 3b704c6..081a9f9 100644 --- a/tests/PromiseCoreTest.php +++ b/tests/PromiseCoreTest.php @@ -16,6 +16,40 @@ */ class PromiseCoreTest extends BaseUnitTestCase { + /** + * Testing if handle is not a resource. + */ + public function testHandleIsNotAResource() + { + $this->setExpectedException( + \InvalidArgumentException::class, + 'Parameter $handle expected to be a cURL resource, NULL given' + ); + + new PromiseCore( + $this->createRequest('GET', '/'), + null, + new ResponseBuilder($this->createResponse()) + ); + } + + /** + * Testing if handle is not a cURL resource. + */ + public function testHandleIsNotACurlResource() + { + $this->setExpectedException( + \InvalidArgumentException::class, + 'Parameter $handle expected to be a cURL resource, stream resource given' + ); + + new PromiseCore( + $this->createRequest('GET', '/'), + fopen('php://memory', 'r+b'), + new ResponseBuilder($this->createResponse()) + ); + } + /** * Test on fulfill actions. */ From 6341a93d00e5d953fc868a3928b5167e6513f2b6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=9C=D0=B8=D1=85=D0=B0=D0=B8=D0=BB=20=D0=9A=D1=80=D0=B0?= =?UTF-8?q?=D1=81=D0=B8=D0=BB=D1=8C=D0=BD=D0=B8=D0=BA=D0=BE=D0=B2?= Date: Mon, 26 Mar 2018 22:21:48 +0300 Subject: [PATCH 055/108] Version 1.7.1 --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index c32c272..a338788 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,10 +2,14 @@ ## Unreleased + +## 1.7.1 - 2018-03-36 + ### Fixed - #36: Failure evaluating code: is_resource($handle) (string assertions are deprecated in PHP 7.2) + ## 1.7 - 2017-02-09 ### Changed From 4a818d3b7edf2406712b8004dcf1670d224cbcc4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=88=B9=E6=AD=8C?= Date: Sat, 6 Oct 2018 15:55:59 +0800 Subject: [PATCH 056/108] Fix phpdoc typo. (#42) * Update Client.php --- src/Client.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Client.php b/src/Client.php index 015e7ed..5e8c4a6 100644 --- a/src/Client.php +++ b/src/Client.php @@ -66,7 +66,7 @@ class Client implements HttpClient, HttpAsyncClient * * @param MessageFactory|null $messageFactory HTTP Message factory * @param StreamFactory|null $streamFactory HTTP Stream factory - * @param array $options cURL options (see http://php.net/curl_setopt) + * @param array $options cURL options {@link http://php.net/curl_setopt} * * @throws \Http\Discovery\Exception\NotFoundException If factory discovery failed * @@ -183,7 +183,7 @@ public function sendAsyncRequest(RequestInterface $request) * * @throws \Http\Client\Exception\RequestException On invalid request * @throws \InvalidArgumentException For invalid header names or values - * @throws \RuntimeException if can not read body + * @throws \RuntimeException If can not read body * * @return array */ @@ -236,7 +236,7 @@ private function createCurlOptions(RequestInterface $request, ResponseBuilder $r * * @param string $requestVersion * - * @throws \UnexpectedValueException if unsupported version requested + * @throws \UnexpectedValueException If unsupported version requested * * @return int */ From 668bc6f231530fc263fb2da36f223e0c67915bd7 Mon Sep 17 00:00:00 2001 From: Nyholm Date: Sat, 27 Oct 2018 11:23:41 +0200 Subject: [PATCH 057/108] Support PSR-18 --- composer.json | 9 +++++---- src/Client.php | 5 +++-- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/composer.json b/composer.json index c72b7a7..6d7f9d4 100644 --- a/composer.json +++ b/composer.json @@ -11,21 +11,22 @@ } ], "prefer-stable": true, - "minimum-stability": "beta", + "minimum-stability": "dev", "config": { "bin-dir": "vendor/bin" }, "require": { - "php": "^5.5 || ^7.0", + "php": "^7.1", "ext-curl": "*", - "php-http/httplug": "^1.0", + "psr/http-client": "^0.3", + "php-http/httplug": "^2.0", "php-http/message-factory": "^1.0.2", "php-http/message": "^1.2", "php-http/discovery": "^1.0" }, "require-dev": { "guzzlehttp/psr7": "^1.0", - "php-http/client-integration-tests": "^0.6", + "php-http/client-integration-tests": "dev-master", "phpunit/phpunit": "^4.8.27", "zendframework/zend-diactoros": "^1.0" }, diff --git a/src/Client.php b/src/Client.php index 5e8c4a6..63e5fa4 100644 --- a/src/Client.php +++ b/src/Client.php @@ -10,6 +10,7 @@ use Http\Message\MessageFactory; use Http\Message\StreamFactory; use Http\Promise\Promise; +use Psr\Http\Client\ClientInterface; use Psr\Http\Message\RequestInterface; use Psr\Http\Message\ResponseInterface; @@ -24,7 +25,7 @@ * * @since 1.0 */ -class Client implements HttpClient, HttpAsyncClient +class Client implements HttpClient, HttpAsyncClient, ClientInterface { /** * cURL options. @@ -108,7 +109,7 @@ public function __destruct() * @since 1.6 Throw NetworkException on network errors * @since 1.0 */ - public function sendRequest(RequestInterface $request) + public function sendRequest(RequestInterface $request): ResponseInterface { $responseBuilder = $this->createResponseBuilder(); $options = $this->createCurlOptions($request, $responseBuilder); From ec296799d0ac4681f3d9b8eb74bc233be1b73af8 Mon Sep 17 00:00:00 2001 From: Nyholm Date: Sat, 27 Oct 2018 12:19:31 +0200 Subject: [PATCH 058/108] Update PHP versions --- .travis.yml | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/.travis.yml b/.travis.yml index fd4905e..905e4cb 100644 --- a/.travis.yml +++ b/.travis.yml @@ -7,9 +7,6 @@ cache: - $HOME/.composer/cache php: - - 5.5 - - 5.6 - - 7.0 - 7.1 - 7.2 - hhvm @@ -21,10 +18,9 @@ env: matrix: fast_finish: true include: - - php: 5.5 + - php: 7.1 + name: "Lowest dependencies" env: COMPOSER_FLAGS="--prefer-stable --prefer-lowest" COVERAGE=true PHPUNIT_FLAGS="--coverage-clover build/coverage.xml" - allow_failures: - - php: hhvm before_install: - travis_retry composer self-update From 8ce55ee609af325796399b6f53521fdd0fd98300 Mon Sep 17 00:00:00 2001 From: Nyholm Date: Sat, 27 Oct 2018 12:20:25 +0200 Subject: [PATCH 059/108] Remove HHVM --- .travis.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 905e4cb..e91e651 100644 --- a/.travis.yml +++ b/.travis.yml @@ -9,7 +9,6 @@ cache: php: - 7.1 - 7.2 - - hhvm env: global: From c1e422b7958794ba9c6e4bf6808ce8a7164a754a Mon Sep 17 00:00:00 2001 From: Nyholm Date: Sat, 27 Oct 2018 12:25:39 +0200 Subject: [PATCH 060/108] Remove explicit declaration of PSR18 --- src/Client.php | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/Client.php b/src/Client.php index 63e5fa4..f2bb184 100644 --- a/src/Client.php +++ b/src/Client.php @@ -10,7 +10,6 @@ use Http\Message\MessageFactory; use Http\Message\StreamFactory; use Http\Promise\Promise; -use Psr\Http\Client\ClientInterface; use Psr\Http\Message\RequestInterface; use Psr\Http\Message\ResponseInterface; @@ -25,7 +24,7 @@ * * @since 1.0 */ -class Client implements HttpClient, HttpAsyncClient, ClientInterface +class Client implements HttpClient, HttpAsyncClient { /** * cURL options. From 7ea399cf5349c8f37fadf946a497e666515e8bbc Mon Sep 17 00:00:00 2001 From: Nyholm Date: Sat, 27 Oct 2018 12:29:26 +0200 Subject: [PATCH 061/108] Remove dependency to psr/http-client --- composer.json | 1 - 1 file changed, 1 deletion(-) diff --git a/composer.json b/composer.json index 6d7f9d4..6139cd7 100644 --- a/composer.json +++ b/composer.json @@ -18,7 +18,6 @@ "require": { "php": "^7.1", "ext-curl": "*", - "psr/http-client": "^0.3", "php-http/httplug": "^2.0", "php-http/message-factory": "^1.0.2", "php-http/message": "^1.2", From fda86d810ca50438544f47c33863b3e6134a1b3f Mon Sep 17 00:00:00 2001 From: Graham Campbell Date: Sat, 27 Oct 2018 12:34:30 +0100 Subject: [PATCH 062/108] Test on PHP 7.3 --- .travis.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.travis.yml b/.travis.yml index e91e651..64a5a82 100644 --- a/.travis.yml +++ b/.travis.yml @@ -9,6 +9,7 @@ cache: php: - 7.1 - 7.2 + - 7.3 env: global: From 19756501e53d4cc16d58df447cb98152d0746242 Mon Sep 17 00:00:00 2001 From: David Buchmann Date: Mon, 29 Oct 2018 13:19:49 +0100 Subject: [PATCH 063/108] add branch alias --- composer.json | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/composer.json b/composer.json index 6139cd7..795a50f 100644 --- a/composer.json +++ b/composer.json @@ -46,5 +46,10 @@ "scripts": { "test": "vendor/bin/phpunit", "test-ci": "vendor/bin/phpunit --coverage-clover build/coverage.xml" + }, + "extra": { + "branch-alias": { + "dev-master": "2.x-dev" + } } } From 16876e4b5524ae6c2c63b41985e383a0728e388b Mon Sep 17 00:00:00 2001 From: Mark Sagi-Kazar Date: Sat, 3 Nov 2018 12:47:08 +0100 Subject: [PATCH 064/108] Support PHPUnit 6.x --- composer.json | 2 +- tests/PromiseCoreTest.php | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/composer.json b/composer.json index 795a50f..c5db3bc 100644 --- a/composer.json +++ b/composer.json @@ -26,7 +26,7 @@ "require-dev": { "guzzlehttp/psr7": "^1.0", "php-http/client-integration-tests": "dev-master", - "phpunit/phpunit": "^4.8.27", + "phpunit/phpunit": "^4.8.35 || ^5.4.3 || ^6.0", "zendframework/zend-diactoros": "^1.0" }, "autoload": { diff --git a/tests/PromiseCoreTest.php b/tests/PromiseCoreTest.php index 081a9f9..9df678b 100644 --- a/tests/PromiseCoreTest.php +++ b/tests/PromiseCoreTest.php @@ -21,7 +21,7 @@ class PromiseCoreTest extends BaseUnitTestCase */ public function testHandleIsNotAResource() { - $this->setExpectedException( + $this->expectException( \InvalidArgumentException::class, 'Parameter $handle expected to be a cURL resource, NULL given' ); @@ -38,7 +38,7 @@ public function testHandleIsNotAResource() */ public function testHandleIsNotACurlResource() { - $this->setExpectedException( + $this->expectException( \InvalidArgumentException::class, 'Parameter $handle expected to be a cURL resource, stream resource given' ); From b5b44da789b2267cd59c4b9565b939e07c8497e1 Mon Sep 17 00:00:00 2001 From: Mark Sagi-Kazar Date: Sat, 3 Nov 2018 12:51:36 +0100 Subject: [PATCH 065/108] Support PHPUnit 7 --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index c5db3bc..092692f 100644 --- a/composer.json +++ b/composer.json @@ -26,7 +26,7 @@ "require-dev": { "guzzlehttp/psr7": "^1.0", "php-http/client-integration-tests": "dev-master", - "phpunit/phpunit": "^4.8.35 || ^5.4.3 || ^6.0", + "phpunit/phpunit": "^4.8.35 || ^5.4.3 || ^6.0 || ^7.0", "zendframework/zend-diactoros": "^1.0" }, "autoload": { From fbcd93c7c116127b22429f6592cf628c0c89b2be Mon Sep 17 00:00:00 2001 From: Mark Sagi-Kazar Date: Sat, 3 Nov 2018 12:56:54 +0100 Subject: [PATCH 066/108] Use latest PHPUnit --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 092692f..c545bc0 100644 --- a/composer.json +++ b/composer.json @@ -26,7 +26,7 @@ "require-dev": { "guzzlehttp/psr7": "^1.0", "php-http/client-integration-tests": "dev-master", - "phpunit/phpunit": "^4.8.35 || ^5.4.3 || ^6.0 || ^7.0", + "phpunit/phpunit": "^7.0", "zendframework/zend-diactoros": "^1.0" }, "autoload": { From 9bc6ecbe2a089a81ac7fcf0c95c0e630ca66b11a Mon Sep 17 00:00:00 2001 From: George Mponos Date: Tue, 27 Nov 2018 20:58:50 +0200 Subject: [PATCH 067/108] Declare strict types --- src/Client.php | 8 ++++---- src/CurlPromise.php | 2 ++ src/MultiRunner.php | 2 ++ src/PromiseCore.php | 2 ++ src/ResponseBuilder.php | 2 ++ tests/BaseUnitTestCase.php | 2 ++ tests/ClientTest.php | 2 ++ tests/CurlPromiseTest.php | 2 ++ tests/HttpAsyncClientDiactorosTest.php | 2 ++ tests/HttpAsyncClientGuzzleTest.php | 2 ++ tests/HttpAsyncClientTestCase.php | 2 ++ tests/HttpClientDiactorosTest.php | 2 ++ tests/HttpClientGuzzleTest.php | 2 ++ tests/HttpClientTestCase.php | 2 ++ tests/PromiseCoreTest.php | 2 ++ 15 files changed, 32 insertions(+), 4 deletions(-) diff --git a/src/Client.php b/src/Client.php index f2bb184..ae5cfe3 100644 --- a/src/Client.php +++ b/src/Client.php @@ -240,7 +240,7 @@ private function createCurlOptions(RequestInterface $request, ResponseBuilder $r * * @return int */ - private function getProtocolVersion($requestVersion) + private function getProtocolVersion(string $requestVersion): int { switch ($requestVersion) { case '1.0': @@ -265,7 +265,7 @@ private function getProtocolVersion($requestVersion) * * @return array */ - private function addRequestBodyOptions(RequestInterface $request, array $options) + private function addRequestBodyOptions(RequestInterface $request, array $options): array { /* * Some HTTP methods cannot have payload: @@ -319,7 +319,7 @@ private function addRequestBodyOptions(RequestInterface $request, array $options * * @return string[] */ - private function createHeaders(RequestInterface $request, array $options) + private function createHeaders(RequestInterface $request, array $options): array { $curlHeaders = []; $headers = $request->getHeaders(); @@ -358,7 +358,7 @@ private function createHeaders(RequestInterface $request, array $options) * * @throws \RuntimeException If creating the stream from $body fails */ - private function createResponseBuilder() + private function createResponseBuilder(): ResponseBuilder { try { $body = $this->streamFactory->createStream(fopen('php://temp', 'w+b')); diff --git a/src/CurlPromise.php b/src/CurlPromise.php index 5061af5..f04ccf6 100644 --- a/src/CurlPromise.php +++ b/src/CurlPromise.php @@ -1,5 +1,7 @@ Date: Thu, 29 Nov 2018 13:26:47 +0100 Subject: [PATCH 068/108] use options resolver to handle curl options and report problems --- CHANGELOG.md | 1 + composer.json | 3 ++- src/Client.php | 34 +++++++++++++++++++++------------- tests/ClientTest.php | 15 +++++++++++++++ 4 files changed, 39 insertions(+), 14 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a338788..112bfc3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,7 @@ ## Unreleased +- Allow cURL options to overwrite our default spec-compliant default configuration ## 1.7.1 - 2018-03-36 diff --git a/composer.json b/composer.json index c545bc0..7ddc323 100644 --- a/composer.json +++ b/composer.json @@ -21,7 +21,8 @@ "php-http/httplug": "^2.0", "php-http/message-factory": "^1.0.2", "php-http/message": "^1.2", - "php-http/discovery": "^1.0" + "php-http/discovery": "^1.0", + "symfony/options-resolver": "^3.4 || ^4.0" }, "require-dev": { "guzzlehttp/psr7": "^1.0", diff --git a/src/Client.php b/src/Client.php index ae5cfe3..2e8705d 100644 --- a/src/Client.php +++ b/src/Client.php @@ -12,6 +12,7 @@ use Http\Promise\Promise; use Psr\Http\Message\RequestInterface; use Psr\Http\Message\ResponseInterface; +use Symfony\Component\OptionsResolver\OptionsResolver; /** * PSR-7 compatible cURL based HTTP client. @@ -62,8 +63,6 @@ class Client implements HttpClient, HttpAsyncClient private $multiRunner = null; /** - * Create new client. - * * @param MessageFactory|null $messageFactory HTTP Message factory * @param StreamFactory|null $streamFactory HTTP Stream factory * @param array $options cURL options {@link http://php.net/curl_setopt} @@ -79,7 +78,20 @@ public function __construct( ) { $this->messageFactory = $messageFactory ?: MessageFactoryDiscovery::find(); $this->streamFactory = $streamFactory ?: StreamFactoryDiscovery::find(); - $this->options = $options; + $resolver = new OptionsResolver(); + $resolver->setDefaults([ + CURLOPT_HEADER => false, + CURLOPT_RETURNTRANSFER => false, + CURLOPT_FOLLOWLOCATION => false, + ]); + $resolver->setAllowedValues(CURLOPT_HEADER, [false]); // our parsing will fail if this is set to true + $resolver->setAllowedValues(CURLOPT_RETURNTRANSFER, [false]); // our parsing will fail if this is set to true + + // We do not know what everything curl supports and might support in the future. + // Make sure that we accept everything that is in the options. + $resolver->setDefined(array_keys($options)); + + $this->options = $resolver->resolve($options); } /** @@ -111,7 +123,7 @@ public function __destruct() public function sendRequest(RequestInterface $request): ResponseInterface { $responseBuilder = $this->createResponseBuilder(); - $options = $this->createCurlOptions($request, $responseBuilder); + $requestOptions = $this->prepareRequestOptions($request, $responseBuilder); if (is_resource($this->handle)) { curl_reset($this->handle); @@ -119,7 +131,7 @@ public function sendRequest(RequestInterface $request): ResponseInterface $this->handle = curl_init(); } - curl_setopt_array($this->handle, $options); + curl_setopt_array($this->handle, $requestOptions); curl_exec($this->handle); $errno = curl_errno($this->handle); @@ -165,8 +177,8 @@ public function sendAsyncRequest(RequestInterface $request) $handle = curl_init(); $responseBuilder = $this->createResponseBuilder(); - $options = $this->createCurlOptions($request, $responseBuilder); - curl_setopt_array($handle, $options); + $requestOptions = $this->prepareRequestOptions($request, $responseBuilder); + curl_setopt_array($handle, $requestOptions); $core = new PromiseCore($request, $handle, $responseBuilder); $promise = new CurlPromise($core, $this->multiRunner); @@ -176,7 +188,7 @@ public function sendAsyncRequest(RequestInterface $request) } /** - * Generates cURL options. + * Update cURL options for this request and hook in the response builder. * * @param RequestInterface $request * @param ResponseBuilder $responseBuilder @@ -187,14 +199,10 @@ public function sendAsyncRequest(RequestInterface $request) * * @return array */ - private function createCurlOptions(RequestInterface $request, ResponseBuilder $responseBuilder) + private function prepareRequestOptions(RequestInterface $request, ResponseBuilder $responseBuilder) { $options = $this->options; - $options[CURLOPT_HEADER] = false; - $options[CURLOPT_RETURNTRANSFER] = false; - $options[CURLOPT_FOLLOWLOCATION] = false; - try { $options[CURLOPT_HTTP_VERSION] = $this->getProtocolVersion($request->getProtocolVersion()); diff --git a/tests/ClientTest.php b/tests/ClientTest.php index ff6c10b..fd6331e 100644 --- a/tests/ClientTest.php +++ b/tests/ClientTest.php @@ -5,7 +5,10 @@ namespace Http\Client\Curl\Tests; use Http\Client\Curl\Client; +use Http\Message\MessageFactory; +use Http\Message\StreamFactory; use PHPUnit\Framework\TestCase; +use Symfony\Component\OptionsResolver\Exception\InvalidOptionsException; use Zend\Diactoros\Request; /** @@ -73,6 +76,18 @@ public function testRewindLargeStream() static::assertTrue(false !== strstr($options[CURLOPT_READFUNCTION](null, null, $length), 'abcdef'), 'Steam was not rewinded'); } + public function testInvalidCurlOptions() + { + $this->expectException(InvalidOptionsException::class); + new Client( + $this->createMock(MessageFactory::class), + $this->createMock(StreamFactory::class), + [ + CURLOPT_HEADER => true, // this won't work with our client + ] + ); + } + /** * Discovery should be used if no factory given. */ From c78f4f882fe2fff9ef89914593b3b81d8d9b1eb8 Mon Sep 17 00:00:00 2001 From: George Mponos Date: Sun, 23 Dec 2018 22:48:11 +0200 Subject: [PATCH 069/108] Remove set methods and lock phpunit --- composer.json | 2 +- tests/BaseUnitTestCase.php | 11 +---------- tests/ClientTest.php | 29 +++++++++++++++++++++++------ tests/CurlPromiseTest.php | 3 +-- tests/PromiseCoreTest.php | 12 ++++-------- 5 files changed, 30 insertions(+), 27 deletions(-) diff --git a/composer.json b/composer.json index 7ddc323..8c9b395 100644 --- a/composer.json +++ b/composer.json @@ -27,7 +27,7 @@ "require-dev": { "guzzlehttp/psr7": "^1.0", "php-http/client-integration-tests": "dev-master", - "phpunit/phpunit": "^7.0", + "phpunit/phpunit": "^7.5", "zendframework/zend-diactoros": "^1.0" }, "autoload": { diff --git a/tests/BaseUnitTestCase.php b/tests/BaseUnitTestCase.php index 540414c..1557431 100644 --- a/tests/BaseUnitTestCase.php +++ b/tests/BaseUnitTestCase.php @@ -63,15 +63,6 @@ protected function createResponse() */ protected function createPromiseCore() { - $class = new \ReflectionClass(PromiseCore::class); - $methods = $class->getMethods(\ReflectionMethod::IS_PUBLIC); - foreach ($methods as &$item) { - $item = $item->getName(); - } - unset($item); - $core = $this->getMockBuilder(PromiseCore::class)->disableOriginalConstructor() - ->setMethods($methods)->getMock(); - - return $core; + return $this->getMockBuilder(PromiseCore::class)->disableOriginalConstructor()->getMock(); } } diff --git a/tests/ClientTest.php b/tests/ClientTest.php index fd6331e..85ae402 100644 --- a/tests/ClientTest.php +++ b/tests/ClientTest.php @@ -25,8 +25,7 @@ class ClientTest extends TestCase */ public function testExpectHeader() { - $client = $this->getMockBuilder(Client::class)->disableOriginalConstructor() - ->setMethods(['__none__'])->getMock(); + $client = $this->getMockBuilder(Client::class)->disableOriginalConstructor()->getMock(); $createHeaders = new \ReflectionMethod(Client::class, 'createHeaders'); $createHeaders->setAccessible(true); @@ -38,10 +37,29 @@ public function testExpectHeader() static::assertContains('Expect:', $headers); } + /** + * "Expect" header should be empty. + * + * @link https://github.com/php-http/curl-client/issues/18 + */ + public function testWithNullPostFields() + { + $client = $this->getMockBuilder(Client::class)->disableOriginalConstructor()->getMock(); + + $createHeaders = new \ReflectionMethod(Client::class, 'createHeaders'); + $createHeaders->setAccessible(true); + + $request = new Request(); + $request = $request->withHeader('content-length', '0'); + + $headers = $createHeaders->invoke($client, $request, [CURLOPT_POSTFIELDS => null]); + + static::assertContains('content-length: 0', $headers); + } + public function testRewindStream() { - $client = $this->getMockBuilder(Client::class)->disableOriginalConstructor() - ->setMethods(['__none__'])->getMock(); + $client = $this->getMockBuilder(Client::class)->disableOriginalConstructor()->getMock(); $bodyOptions = new \ReflectionMethod(Client::class, 'addRequestBodyOptions'); $bodyOptions->setAccessible(true); @@ -56,8 +74,7 @@ public function testRewindStream() public function testRewindLargeStream() { - $client = $this->getMockBuilder(Client::class)->disableOriginalConstructor() - ->setMethods(['__none__'])->getMock(); + $client = $this->getMockBuilder(Client::class)->disableOriginalConstructor()->getMock(); $bodyOptions = new \ReflectionMethod(Client::class, 'addRequestBodyOptions'); $bodyOptions->setAccessible(true); diff --git a/tests/CurlPromiseTest.php b/tests/CurlPromiseTest.php index 2bab160..5c4ddf8 100644 --- a/tests/CurlPromiseTest.php +++ b/tests/CurlPromiseTest.php @@ -57,8 +57,7 @@ public function testCoreCallWaitFulfilled() public function testCoreCallWaitRejected() { $core = $this->createPromiseCore(); - $runner = $this->getMockBuilder(MultiRunner::class)->disableOriginalConstructor() - ->setMethods(['wait'])->getMock(); + $runner = $this->getMockBuilder(MultiRunner::class)->disableOriginalConstructor()->getMock(); /** @var MultiRunner|\PHPUnit_Framework_MockObject_MockObject $runner */ $promise = new CurlPromise($core, $runner); diff --git a/tests/PromiseCoreTest.php b/tests/PromiseCoreTest.php index e3d9d9d..78f1052 100644 --- a/tests/PromiseCoreTest.php +++ b/tests/PromiseCoreTest.php @@ -23,10 +23,8 @@ class PromiseCoreTest extends BaseUnitTestCase */ public function testHandleIsNotAResource() { - $this->expectException( - \InvalidArgumentException::class, - 'Parameter $handle expected to be a cURL resource, NULL given' - ); + $this->expectException(\InvalidArgumentException::class); + $this->expectExceptionMessage('Parameter $handle expected to be a cURL resource, NULL given'); new PromiseCore( $this->createRequest('GET', '/'), @@ -40,10 +38,8 @@ public function testHandleIsNotAResource() */ public function testHandleIsNotACurlResource() { - $this->expectException( - \InvalidArgumentException::class, - 'Parameter $handle expected to be a cURL resource, stream resource given' - ); + $this->expectException(\InvalidArgumentException::class); + $this->expectExceptionMessage('Parameter $handle expected to be a cURL resource, stream resource given'); new PromiseCore( $this->createRequest('GET', '/'), From 15d4ff920cca3049b4d1dc5961bd9236f60a1e5f Mon Sep 17 00:00:00 2001 From: George Mponos Date: Tue, 25 Dec 2018 17:08:18 +0200 Subject: [PATCH 070/108] Improve tests no2 --- .gitattributes | 2 ++ tests/BaseUnitTestCase.php | 17 +++-------------- tests/ClientTest.php | 8 ++++---- tests/CurlPromiseTest.php | 21 ++++++++++----------- tests/HttpAsyncClientTestCase.php | 16 +--------------- tests/HttpClientTestCase.php | 6 ------ 6 files changed, 20 insertions(+), 50 deletions(-) diff --git a/.gitattributes b/.gitattributes index 7c0fa03..7950037 100644 --- a/.gitattributes +++ b/.gitattributes @@ -3,6 +3,8 @@ .gitattributes export-ignore .gitignore export-ignore .scrutinizer.yml export-ignore +.styleci.yml export-ignore +.php_cs.yml export-ignore .travis.yml export-ignore CONDUCT.md export-ignore CONTRIBUTING.md export-ignore diff --git a/tests/BaseUnitTestCase.php b/tests/BaseUnitTestCase.php index 1557431..3a3c9e3 100644 --- a/tests/BaseUnitTestCase.php +++ b/tests/BaseUnitTestCase.php @@ -4,7 +4,6 @@ namespace Http\Client\Curl\Tests; -use Http\Client\Curl\PromiseCore; use Http\Discovery\MessageFactoryDiscovery; use PHPUnit\Framework\TestCase; use Psr\Http\Message\RequestInterface; @@ -37,11 +36,11 @@ protected function tearDown() * Create new request. * * @param string $method - * @param mixed $uri + * @param mixed $uri * * @return RequestInterface */ - protected function createRequest($method, $uri) + protected function createRequest(string $method, $uri): RequestInterface { return MessageFactoryDiscovery::find()->createRequest($method, $uri); } @@ -51,18 +50,8 @@ protected function createRequest($method, $uri) * * @return ResponseInterface */ - protected function createResponse() + protected function createResponse(): ResponseInterface { return MessageFactoryDiscovery::find()->createResponse(); } - - /** - * Create PromiseCore mock. - * - * @return PromiseCore|\PHPUnit_Framework_MockObject_MockObject - */ - protected function createPromiseCore() - { - return $this->getMockBuilder(PromiseCore::class)->disableOriginalConstructor()->getMock(); - } } diff --git a/tests/ClientTest.php b/tests/ClientTest.php index 85ae402..13c6dcd 100644 --- a/tests/ClientTest.php +++ b/tests/ClientTest.php @@ -25,7 +25,7 @@ class ClientTest extends TestCase */ public function testExpectHeader() { - $client = $this->getMockBuilder(Client::class)->disableOriginalConstructor()->getMock(); + $client = $this->createMock(Client::class); $createHeaders = new \ReflectionMethod(Client::class, 'createHeaders'); $createHeaders->setAccessible(true); @@ -44,7 +44,7 @@ public function testExpectHeader() */ public function testWithNullPostFields() { - $client = $this->getMockBuilder(Client::class)->disableOriginalConstructor()->getMock(); + $client = $this->createMock(Client::class); $createHeaders = new \ReflectionMethod(Client::class, 'createHeaders'); $createHeaders->setAccessible(true); @@ -59,7 +59,7 @@ public function testWithNullPostFields() public function testRewindStream() { - $client = $this->getMockBuilder(Client::class)->disableOriginalConstructor()->getMock(); + $client = $this->createMock(Client::class); $bodyOptions = new \ReflectionMethod(Client::class, 'addRequestBodyOptions'); $bodyOptions->setAccessible(true); @@ -74,7 +74,7 @@ public function testRewindStream() public function testRewindLargeStream() { - $client = $this->getMockBuilder(Client::class)->disableOriginalConstructor()->getMock(); + $client = $this->createMock(Client::class); $bodyOptions = new \ReflectionMethod(Client::class, 'addRequestBodyOptions'); $bodyOptions->setAccessible(true); diff --git a/tests/CurlPromiseTest.php b/tests/CurlPromiseTest.php index 5c4ddf8..389262e 100644 --- a/tests/CurlPromiseTest.php +++ b/tests/CurlPromiseTest.php @@ -6,6 +6,7 @@ use Http\Client\Curl\CurlPromise; use Http\Client\Curl\MultiRunner; +use Http\Client\Curl\PromiseCore; use Http\Client\Exception\TransferException; use Http\Promise\Promise; @@ -21,10 +22,9 @@ class CurlPromiseTest extends BaseUnitTestCase */ public function testCoreCalls() { - $core = $this->createPromiseCore(); - $runner = $this->getMockBuilder(MultiRunner::class)->disableOriginalConstructor() - ->setMethods(['wait'])->getMock(); - /** @var MultiRunner|\PHPUnit_Framework_MockObject_MockObject $runner */ + $core = $this->createMock(PromiseCore::class); + $runner = $this->createMock(MultiRunner::class); + $promise = new CurlPromise($core, $runner); $onFulfill = function () { @@ -42,10 +42,9 @@ public function testCoreCalls() public function testCoreCallWaitFulfilled() { - $core = $this->createPromiseCore(); - $runner = $this->getMockBuilder(MultiRunner::class)->disableOriginalConstructor() - ->setMethods(['wait'])->getMock(); - /** @var MultiRunner|\PHPUnit_Framework_MockObject_MockObject $runner */ + $core = $this->createMock(PromiseCore::class); + $runner = $this->createMock(MultiRunner::class); + $promise = new CurlPromise($core, $runner); $runner->expects(static::once())->method('wait')->with($core); @@ -56,9 +55,9 @@ public function testCoreCallWaitFulfilled() public function testCoreCallWaitRejected() { - $core = $this->createPromiseCore(); - $runner = $this->getMockBuilder(MultiRunner::class)->disableOriginalConstructor()->getMock(); - /** @var MultiRunner|\PHPUnit_Framework_MockObject_MockObject $runner */ + $core = $this->createMock(PromiseCore::class); + $runner = $this->createMock(MultiRunner::class); + $promise = new CurlPromise($core, $runner); $runner->expects(static::once())->method('wait')->with($core); diff --git a/tests/HttpAsyncClientTestCase.php b/tests/HttpAsyncClientTestCase.php index 8a6f7f0..0b3f839 100644 --- a/tests/HttpAsyncClientTestCase.php +++ b/tests/HttpAsyncClientTestCase.php @@ -17,11 +17,8 @@ abstract class HttpAsyncClientTestCase extends HttpAsyncClientTest */ public function testAsyncSendRequest($method, $uri, array $headers, $body) { - if (defined('HHVM_VERSION')) { - static::markTestSkipped('This test can not run under HHVM'); - } if (null !== $body && in_array($method, ['GET', 'HEAD', 'TRACE'], true)) { - static::markTestSkipped('cURL can not send body using '.$method); + static::markTestSkipped('cURL can not send body using ' . $method); } parent::testAsyncSendRequest( $method, @@ -41,9 +38,6 @@ public function testSendAsyncRequestWithOutcome( array $headers, $body ) { - if (defined('HHVM_VERSION')) { - static::markTestSkipped('This test can not run under HHVM'); - } if (null !== $body) { static::markTestSkipped('cURL can not send body using GET'); } @@ -54,12 +48,4 @@ public function testSendAsyncRequestWithOutcome( $body ); } - - public function testSuccessiveCallMustUseResponseInterface() - { - if (defined('HHVM_VERSION')) { - static::markTestSkipped('This test can not run under HHVM'); - } - parent::testSuccessiveCallMustUseResponseInterface(); - } } diff --git a/tests/HttpClientTestCase.php b/tests/HttpClientTestCase.php index cea9e90..25ee940 100644 --- a/tests/HttpClientTestCase.php +++ b/tests/HttpClientTestCase.php @@ -26,9 +26,6 @@ abstract class HttpClientTestCase extends HttpClientTest */ public function testSendRequest($method, $uri, array $headers, $body) { - if (defined('HHVM_VERSION')) { - static::markTestSkipped('This test can not run under HHVM'); - } if (null !== $body && in_array($method, ['GET', 'HEAD', 'TRACE'], true)) { static::markTestSkipped('cURL can not send body using '.$method); } @@ -50,9 +47,6 @@ public function testSendRequestWithOutcome( array $headers, $body ) { - if (defined('HHVM_VERSION')) { - static::markTestSkipped('This test can not run under HHVM'); - } if (null !== $body) { static::markTestSkipped('cURL can not send body using GET'); } From 84e7017543863a3e3d7b4c6f3f2ddc97f43222d4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=9C=D0=B8=D1=85=D0=B0=D0=B8=D0=BB=20=D0=9A=D1=80=D0=B0?= =?UTF-8?q?=D1=81=D0=B8=D0=BB=D1=8C=D0=BD=D0=B8=D0=BA=D0=BE=D0=B2?= Date: Wed, 26 Dec 2018 17:22:05 +0300 Subject: [PATCH 071/108] #41: Support PSR-18 --- composer.json | 19 +- phpunit.xml.dist | 39 +++- src/Client.php | 65 ++++--- src/MultiRunner.php | 10 +- src/PromiseCore.php | 20 +- src/ResponseBuilder.php | 2 +- tests/BaseUnitTestCase.php | 68 ------- tests/CurlPromiseTest.php | 74 -------- .../HttpAsyncClientDiactorosTest.php | 26 +++ .../HttpAsyncClientGuzzleTest.php | 10 +- tests/Functional/HttpAsyncClientTestCase.php | 63 +++++++ .../HttpClientDiactorosTest.php | 28 +-- .../{ => Functional}/HttpClientGuzzleTest.php | 2 +- tests/{ => Functional}/HttpClientTestCase.php | 116 ++++++------ tests/HttpAsyncClientDiactorosTest.php | 24 --- tests/HttpAsyncClientTestCase.php | 65 ------- tests/PromiseCoreTest.php | 166 ----------------- tests/{ => Unit}/ClientTest.php | 39 ++-- tests/Unit/CurlPromiseTest.php | 86 +++++++++ tests/Unit/PromiseCoreTest.php | 176 ++++++++++++++++++ 20 files changed, 543 insertions(+), 555 deletions(-) delete mode 100644 tests/BaseUnitTestCase.php delete mode 100644 tests/CurlPromiseTest.php create mode 100644 tests/Functional/HttpAsyncClientDiactorosTest.php rename tests/{ => Functional}/HttpAsyncClientGuzzleTest.php (63%) create mode 100644 tests/Functional/HttpAsyncClientTestCase.php rename tests/{ => Functional}/HttpClientDiactorosTest.php (52%) rename tests/{ => Functional}/HttpClientGuzzleTest.php (94%) rename tests/{ => Functional}/HttpClientTestCase.php (69%) delete mode 100644 tests/HttpAsyncClientDiactorosTest.php delete mode 100644 tests/HttpAsyncClientTestCase.php delete mode 100644 tests/PromiseCoreTest.php rename tests/{ => Unit}/ClientTest.php (77%) create mode 100644 tests/Unit/CurlPromiseTest.php create mode 100644 tests/Unit/PromiseCoreTest.php diff --git a/composer.json b/composer.json index 8c9b395..5b0a0e2 100644 --- a/composer.json +++ b/composer.json @@ -1,8 +1,12 @@ { "name": "php-http/curl-client", - "description": "cURL client for PHP-HTTP", + "description": "PSR-18 cURL client", "license": "MIT", - "keywords": ["http", "curl"], + "keywords": [ + "curl", + "http", + "psr-18" + ], "homepage": "http://php-http.org", "authors": [ { @@ -12,23 +16,20 @@ ], "prefer-stable": true, "minimum-stability": "dev", - "config": { - "bin-dir": "vendor/bin" - }, "require": { "php": "^7.1", "ext-curl": "*", - "php-http/httplug": "^2.0", - "php-http/message-factory": "^1.0.2", - "php-http/message": "^1.2", "php-http/discovery": "^1.0", + "php-http/httplug": "^2.0", + "psr/http-client": "^1.0", + "psr/http-factory": "^1.0", "symfony/options-resolver": "^3.4 || ^4.0" }, "require-dev": { "guzzlehttp/psr7": "^1.0", "php-http/client-integration-tests": "dev-master", "phpunit/phpunit": "^7.5", - "zendframework/zend-diactoros": "^1.0" + "zendframework/zend-diactoros": "^2.0" }, "autoload": { "psr-4": { diff --git a/phpunit.xml.dist b/phpunit.xml.dist index c6643dc..3f3b615 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -1,16 +1,41 @@ - + + + + + + + - - tests/ + + + tests + + tests/Functional/HttpAsyncClientGuzzleTest.php + tests/Functional/HttpClientGuzzleTest.php + + + + tests/Unit + + + tests/Functional + + tests/Functional/HttpAsyncClientGuzzleTest.php + tests/Functional/HttpClientGuzzleTest.php + + - - - + - src/ + src + diff --git a/src/Client.php b/src/Client.php index 2e8705d..5b19bd1 100644 --- a/src/Client.php +++ b/src/Client.php @@ -1,5 +1,7 @@ messageFactory = $messageFactory ?: MessageFactoryDiscovery::find(); - $this->streamFactory = $streamFactory ?: StreamFactoryDiscovery::find(); + $this->responseFactory = $responseFactory; // FIXME ?: MessageFactoryDiscovery::find(); + $this->streamFactory = $streamFactory; // FIXME ?: StreamFactoryDiscovery::find(); $resolver = new OptionsResolver(); - $resolver->setDefaults([ - CURLOPT_HEADER => false, - CURLOPT_RETURNTRANSFER => false, - CURLOPT_FOLLOWLOCATION => false, - ]); + $resolver->setDefaults( + [ + CURLOPT_HEADER => false, + CURLOPT_RETURNTRANSFER => false, + CURLOPT_FOLLOWLOCATION => false, + ] + ); $resolver->setAllowedValues(CURLOPT_HEADER, [false]); // our parsing will fail if this is set to true $resolver->setAllowedValues(CURLOPT_RETURNTRANSFER, [false]); // our parsing will fail if this is set to true @@ -105,7 +113,7 @@ public function __destruct() } /** - * Sends a PSR-7 request. + * Sends a PSR-7 request and returns a PSR-7 response. * * @param RequestInterface $request * @@ -363,17 +371,14 @@ private function createHeaders(RequestInterface $request, array $options): array * Create new ResponseBuilder instance. * * @return ResponseBuilder - * - * @throws \RuntimeException If creating the stream from $body fails */ private function createResponseBuilder(): ResponseBuilder { - try { - $body = $this->streamFactory->createStream(fopen('php://temp', 'w+b')); - } catch (\InvalidArgumentException $e) { - throw new \RuntimeException('Can not create "php://temp" stream.'); - } - $response = $this->messageFactory->createResponse(200, null, [], $body); + $body = $this->streamFactory->createStreamFromFile('php://temp', 'w+b'); + + $response = $this->responseFactory + ->createResponse(200) + ->withBody($body); return new ResponseBuilder($response); } diff --git a/src/MultiRunner.php b/src/MultiRunner.php index 4c60587..7a2c50c 100644 --- a/src/MultiRunner.php +++ b/src/MultiRunner.php @@ -19,7 +19,7 @@ class MultiRunner * * @var resource|null */ - private $multiHandle = null; + private $multiHandle; /** * Awaiting cores. @@ -43,7 +43,7 @@ public function __destruct() * * @param PromiseCore $core */ - public function add(PromiseCore $core) + public function add(PromiseCore $core): void { foreach ($this->cores as $existed) { if ($existed === $core) { @@ -64,7 +64,7 @@ public function add(PromiseCore $core) * * @param PromiseCore $core */ - public function remove(PromiseCore $core) + public function remove(PromiseCore $core): void { foreach ($this->cores as $index => $existed) { if ($existed === $core) { @@ -81,7 +81,7 @@ public function remove(PromiseCore $core) * * @param PromiseCore|null $targetCore */ - public function wait(PromiseCore $targetCore = null) + public function wait(PromiseCore $targetCore = null): void { do { $status = curl_multi_exec($this->multiHandle, $active); @@ -118,7 +118,7 @@ public function wait(PromiseCore $targetCore = null) * * @return PromiseCore|null */ - private function findCoreByHandle($handle) + private function findCoreByHandle($handle): ?PromiseCore { foreach ($this->cores as $core) { if ($core->getHandle() === $handle) { diff --git a/src/PromiseCore.php b/src/PromiseCore.php index 72a733e..2b0256d 100644 --- a/src/PromiseCore.php +++ b/src/PromiseCore.php @@ -109,7 +109,7 @@ public function __construct( * * @param callable $callback */ - public function addOnFulfilled(callable $callback) + public function addOnFulfilled(callable $callback): void { if ($this->getState() === Promise::PENDING) { $this->onFulfilled[] = $callback; @@ -126,7 +126,7 @@ public function addOnFulfilled(callable $callback) * * @param callable $callback */ - public function addOnRejected(callable $callback) + public function addOnRejected(callable $callback): void { if ($this->getState() === Promise::PENDING) { $this->onRejected[] = $callback; @@ -150,7 +150,7 @@ public function getHandle() * * @return string */ - public function getState() + public function getState(): string { return $this->state; } @@ -160,7 +160,7 @@ public function getState() * * @return RequestInterface */ - public function getRequest() + public function getRequest(): RequestInterface { return $this->request; } @@ -168,9 +168,9 @@ public function getRequest() /** * Return the value of the promise (fulfilled). * - * @return ResponseInterface Response Object only when the Promise is fulfilled + * @return ResponseInterface Response object only when the Promise is fulfilled */ - public function getResponse() + public function getResponse(): ResponseInterface { return $this->responseBuilder->getResponse(); } @@ -181,11 +181,11 @@ public function getResponse() * If the exception is an instance of Http\Client\Exception\HttpException it will contain * the response object with the status code and the http reason. * - * @return Exception Exception Object only when the Promise is rejected + * @return \Throwable Exception Object only when the Promise is rejected * * @throws \LogicException When the promise is not rejected */ - public function getException() + public function getException(): \Throwable { if (null === $this->exception) { throw new \LogicException('Promise is not rejected'); @@ -197,7 +197,7 @@ public function getException() /** * Fulfill promise. */ - public function fulfill() + public function fulfill(): void { $this->state = Promise::FULFILLED; $response = $this->responseBuilder->getResponse(); @@ -225,7 +225,7 @@ public function fulfill() * * @param Exception $exception Reject reason */ - public function reject(Exception $exception) + public function reject(Exception $exception): void { $this->exception = $exception; $this->state = Promise::REJECTED; diff --git a/src/ResponseBuilder.php b/src/ResponseBuilder.php index 8d98819..c7250e7 100644 --- a/src/ResponseBuilder.php +++ b/src/ResponseBuilder.php @@ -17,7 +17,7 @@ class ResponseBuilder extends OriginalResponseBuilder * * @param ResponseInterface $response */ - public function setResponse(ResponseInterface $response) + public function setResponse(ResponseInterface $response): void { $this->response = $response; } diff --git a/tests/BaseUnitTestCase.php b/tests/BaseUnitTestCase.php deleted file mode 100644 index 1557431..0000000 --- a/tests/BaseUnitTestCase.php +++ /dev/null @@ -1,68 +0,0 @@ -handle)) { - curl_close($this->handle); - } - } - - /** - * Create new request. - * - * @param string $method - * @param mixed $uri - * - * @return RequestInterface - */ - protected function createRequest($method, $uri) - { - return MessageFactoryDiscovery::find()->createRequest($method, $uri); - } - - /** - * Create new response. - * - * @return ResponseInterface - */ - protected function createResponse() - { - return MessageFactoryDiscovery::find()->createResponse(); - } - - /** - * Create PromiseCore mock. - * - * @return PromiseCore|\PHPUnit_Framework_MockObject_MockObject - */ - protected function createPromiseCore() - { - return $this->getMockBuilder(PromiseCore::class)->disableOriginalConstructor()->getMock(); - } -} diff --git a/tests/CurlPromiseTest.php b/tests/CurlPromiseTest.php deleted file mode 100644 index 5c4ddf8..0000000 --- a/tests/CurlPromiseTest.php +++ /dev/null @@ -1,74 +0,0 @@ -createPromiseCore(); - $runner = $this->getMockBuilder(MultiRunner::class)->disableOriginalConstructor() - ->setMethods(['wait'])->getMock(); - /** @var MultiRunner|\PHPUnit_Framework_MockObject_MockObject $runner */ - $promise = new CurlPromise($core, $runner); - - $onFulfill = function () { - }; - $core->expects(static::once())->method('addOnFulfilled')->with($onFulfill); - $onReject = function () { - }; - $core->expects(static::once())->method('addOnRejected')->with($onReject); - $value = $promise->then($onFulfill, $onReject); - static::assertInstanceOf(Promise::class, $value); - - $core->expects(static::once())->method('getState')->willReturn('STATE'); - static::assertEquals('STATE', $promise->getState()); - } - - public function testCoreCallWaitFulfilled() - { - $core = $this->createPromiseCore(); - $runner = $this->getMockBuilder(MultiRunner::class)->disableOriginalConstructor() - ->setMethods(['wait'])->getMock(); - /** @var MultiRunner|\PHPUnit_Framework_MockObject_MockObject $runner */ - $promise = new CurlPromise($core, $runner); - - $runner->expects(static::once())->method('wait')->with($core); - $core->expects(static::once())->method('getState')->willReturn(Promise::FULFILLED); - $core->expects(static::once())->method('getResponse')->willReturn('RESPONSE'); - static::assertEquals('RESPONSE', $promise->wait()); - } - - public function testCoreCallWaitRejected() - { - $core = $this->createPromiseCore(); - $runner = $this->getMockBuilder(MultiRunner::class)->disableOriginalConstructor()->getMock(); - /** @var MultiRunner|\PHPUnit_Framework_MockObject_MockObject $runner */ - $promise = new CurlPromise($core, $runner); - - $runner->expects(static::once())->method('wait')->with($core); - $core->expects(static::once())->method('getState')->willReturn(Promise::REJECTED); - $core->expects(static::once())->method('getException')->willReturn(new TransferException()); - - try { - $promise->wait(); - } catch (TransferException $exception) { - static::assertTrue(true); - } - } -} diff --git a/tests/Functional/HttpAsyncClientDiactorosTest.php b/tests/Functional/HttpAsyncClientDiactorosTest.php new file mode 100644 index 0000000..273ea0f --- /dev/null +++ b/tests/Functional/HttpAsyncClientDiactorosTest.php @@ -0,0 +1,26 @@ +createTempFile(); $fd = fopen($filename, 'ab'); @@ -99,16 +55,53 @@ public function testSendLargeFile() } /** - * Create temp file. + * TODO Summary. * - * @return string Filename + * @param string $method HTTP method. + * @param string $uri Request URI. + * @param array $headers HTTP headers. + * @param string $body Request body. + * + * @dataProvider requestProvider */ - protected function createTempFile() + public function testSendRequest($method, $uri, array $headers, $body): void { - $filename = tempnam(sys_get_temp_dir(), 'tests'); - $this->tmpFiles[] = $filename; + if ($body !== null && in_array($method, ['GET', 'HEAD', 'TRACE'], true)) { + self::markTestSkipped('cURL can not send body using '.$method); + } + parent::testSendRequest( + $method, + $uri, + $headers, + $body + ); + } - return $filename; + /** + * TODO Summary. + * + * @param array $uriAndOutcome TODO ??? + * @param string $protocolVersion HTTP version. + * @param array $headers HTTP headers. + * @param string $body Request body. + * + * @dataProvider requestWithOutcomeProvider + */ + public function testSendRequestWithOutcome( + $uriAndOutcome, + $protocolVersion, + array $headers, + $body + ): void { + if ($body !== null) { + self::markTestSkipped('cURL can not send body using GET'); + } + parent::testSendRequestWithOutcome( + $uriAndOutcome, + $protocolVersion, + $headers, + $body + ); } /** @@ -118,7 +111,20 @@ protected function createTempFile() * * @return StreamInterface */ - abstract protected function createFileStream($filename); + abstract protected function createFileStream($filename): StreamInterface; + + /** + * Create temporary file. + * + * @return string Filename + */ + protected function createTempFile(): string + { + $filename = tempnam(sys_get_temp_dir(), 'tests'); + $this->tmpFiles[] = $filename; + + return $filename; + } /** * Tears down the fixture. diff --git a/tests/HttpAsyncClientDiactorosTest.php b/tests/HttpAsyncClientDiactorosTest.php deleted file mode 100644 index e94cbb0..0000000 --- a/tests/HttpAsyncClientDiactorosTest.php +++ /dev/null @@ -1,24 +0,0 @@ -expectException(\InvalidArgumentException::class); - $this->expectExceptionMessage('Parameter $handle expected to be a cURL resource, NULL given'); - - new PromiseCore( - $this->createRequest('GET', '/'), - null, - new ResponseBuilder($this->createResponse()) - ); - } - - /** - * Testing if handle is not a cURL resource. - */ - public function testHandleIsNotACurlResource() - { - $this->expectException(\InvalidArgumentException::class); - $this->expectExceptionMessage('Parameter $handle expected to be a cURL resource, stream resource given'); - - new PromiseCore( - $this->createRequest('GET', '/'), - fopen('php://memory', 'r+b'), - new ResponseBuilder($this->createResponse()) - ); - } - - /** - * Test on fulfill actions. - */ - public function testOnFulfill() - { - $request = $this->createRequest('GET', '/'); - $this->handle = curl_init(); - - $core = new PromiseCore( - $request, - $this->handle, - new ResponseBuilder($this->createResponse()) - ); - static::assertSame($request, $core->getRequest()); - static::assertSame($this->handle, $core->getHandle()); - - $core->addOnFulfilled( - function (ResponseInterface $response) { - return $response->withAddedHeader('X-Test', 'foo'); - } - ); - - $core->fulfill(); - static::assertEquals(Promise::FULFILLED, $core->getState()); - static::assertInstanceOf(ResponseInterface::class, $core->getResponse()); - static::assertEquals('foo', $core->getResponse()->getHeaderLine('X-Test')); - - $core->addOnFulfilled( - function (ResponseInterface $response) { - return $response->withAddedHeader('X-Test', 'bar'); - } - ); - static::assertEquals('foo, bar', $core->getResponse()->getHeaderLine('X-Test')); - } - - /** - * Test on reject actions. - */ - public function testOnReject() - { - $request = $this->createRequest('GET', '/'); - $this->handle = curl_init(); - - $core = new PromiseCore( - $request, - $this->handle, - new ResponseBuilder($this->createResponse()) - ); - $core->addOnRejected( - function (RequestException $exception) { - throw new RequestException('Foo', $exception->getRequest(), $exception); - } - ); - - $exception = new RequestException('Error', $request); - $core->reject($exception); - static::assertEquals(Promise::REJECTED, $core->getState()); - static::assertInstanceOf(Exception::class, $core->getException()); - static::assertEquals('Foo', $core->getException()->getMessage()); - - $core->addOnRejected( - function (RequestException $exception) { - return new RequestException('Bar', $exception->getRequest(), $exception); - } - ); - static::assertEquals('Bar', $core->getException()->getMessage()); - } - - /** - * «onReject» callback can throw exception. - * - * @see https://github.com/php-http/curl-client/issues/26 - */ - public function testIssue26() - { - $request = $this->createRequest('GET', '/'); - $this->handle = curl_init(); - - $core = new PromiseCore( - $request, - $this->handle, - new ResponseBuilder($this->createResponse()) - ); - $core->addOnRejected( - function (RequestException $exception) { - throw new RequestException('Foo', $exception->getRequest(), $exception); - } - ); - $core->addOnRejected( - function (RequestException $exception) { - return new RequestException('Bar', $exception->getRequest(), $exception); - } - ); - - $exception = new RequestException('Error', $request); - $core->reject($exception); - static::assertEquals(Promise::REJECTED, $core->getState()); - static::assertInstanceOf(Exception::class, $core->getException()); - static::assertEquals('Bar', $core->getException()->getMessage()); - } - - /** - * @expectedException \LogicException - */ - public function testNotRejected() - { - $request = $this->createRequest('GET', '/'); - $this->handle = curl_init(); - $core = new PromiseCore( - $request, - $this->handle, - new ResponseBuilder($this->createResponse()) - ); - $core->getException(); - } -} diff --git a/tests/ClientTest.php b/tests/Unit/ClientTest.php similarity index 77% rename from tests/ClientTest.php rename to tests/Unit/ClientTest.php index 85ae402..0cc07d3 100644 --- a/tests/ClientTest.php +++ b/tests/Unit/ClientTest.php @@ -2,12 +2,12 @@ declare(strict_types=1); -namespace Http\Client\Curl\Tests; +namespace Http\Client\Curl\Tests\Unit; use Http\Client\Curl\Client; -use Http\Message\MessageFactory; -use Http\Message\StreamFactory; use PHPUnit\Framework\TestCase; +use Psr\Http\Message\ResponseFactoryInterface; +use Psr\Http\Message\StreamFactoryInterface; use Symfony\Component\OptionsResolver\Exception\InvalidOptionsException; use Zend\Diactoros\Request; @@ -23,7 +23,7 @@ class ClientTest extends TestCase * * @link https://github.com/php-http/curl-client/issues/18 */ - public function testExpectHeader() + public function testExpectHeader(): void { $client = $this->getMockBuilder(Client::class)->disableOriginalConstructor()->getMock(); @@ -42,9 +42,9 @@ public function testExpectHeader() * * @link https://github.com/php-http/curl-client/issues/18 */ - public function testWithNullPostFields() + public function testWithNullPostFields(): void { - $client = $this->getMockBuilder(Client::class)->disableOriginalConstructor()->getMock(); + $client = $this->createMock(Client::class); $createHeaders = new \ReflectionMethod(Client::class, 'createHeaders'); $createHeaders->setAccessible(true); @@ -52,12 +52,12 @@ public function testWithNullPostFields() $request = new Request(); $request = $request->withHeader('content-length', '0'); - $headers = $createHeaders->invoke($client, $request, [CURLOPT_POSTFIELDS => null]); + $headers = $createHeaders->invoke($client, $request, [CURLOPT_POSTFIELDS => '']); - static::assertContains('content-length: 0', $headers); + self::assertContains('content-length: 0', $headers); } - public function testRewindStream() + public function testRewindStream(): void { $client = $this->getMockBuilder(Client::class)->disableOriginalConstructor()->getMock(); @@ -72,7 +72,7 @@ public function testRewindStream() static::assertEquals('abcdef', $options[CURLOPT_POSTFIELDS]); } - public function testRewindLargeStream() + public function testRewindLargeStream(): void { $client = $this->getMockBuilder(Client::class)->disableOriginalConstructor()->getMock(); @@ -93,25 +93,18 @@ public function testRewindLargeStream() static::assertTrue(false !== strstr($options[CURLOPT_READFUNCTION](null, null, $length), 'abcdef'), 'Steam was not rewinded'); } - public function testInvalidCurlOptions() + /** + * Tests throwing InvalidArgumentException when invalid cURL options passed to constructor. + */ + public function testInvalidCurlOptions(): void { $this->expectException(InvalidOptionsException::class); new Client( - $this->createMock(MessageFactory::class), - $this->createMock(StreamFactory::class), + $this->createMock(ResponseFactoryInterface::class), + $this->createMock(StreamFactoryInterface::class), [ CURLOPT_HEADER => true, // this won't work with our client ] ); } - - /** - * Discovery should be used if no factory given. - */ - public function testFactoryDiscovery() - { - $client = new Client(); - - static::assertInstanceOf(Client::class, $client); - } } diff --git a/tests/Unit/CurlPromiseTest.php b/tests/Unit/CurlPromiseTest.php new file mode 100644 index 0000000..2cf42d8 --- /dev/null +++ b/tests/Unit/CurlPromiseTest.php @@ -0,0 +1,86 @@ +createMock(PromiseCore::class); + $runner = $this->getMockBuilder(MultiRunner::class)->disableOriginalConstructor() + ->setMethods(['wait'])->getMock(); + /** @var MultiRunner|\PHPUnit_Framework_MockObject_MockObject $runner */ + $promise = new CurlPromise($core, $runner); + + $runner->expects(self::once())->method('wait')->with($core); + $core->expects(self::once())->method('getState')->willReturn(Promise::FULFILLED); + + $response = $this->createMock(ResponseInterface::class); + $core->expects(self::once())->method('getResponse')->willReturn($response); + self::assertSame($response, $promise->wait()); + } + + /** + * TODO Summary + */ + public function testCoreCallWaitRejected(): void + { + $core = $this->createMock(PromiseCore::class); + $runner = $this->getMockBuilder(MultiRunner::class)->disableOriginalConstructor()->getMock(); + /** @var MultiRunner|\PHPUnit_Framework_MockObject_MockObject $runner */ + $promise = new CurlPromise($core, $runner); + + $runner->expects(self::once())->method('wait')->with($core); + $core->expects(self::once())->method('getState')->willReturn(Promise::REJECTED); + $core->expects(self::once())->method('getException')->willReturn(new TransferException()); + + try { + $promise->wait(); + } catch (TransferException $exception) { + self::assertTrue(true); + } + } + + /** + * Test that promise call core methods. + */ + public function testCoreCalls(): void + { + $core = $this->createMock(PromiseCore::class); + $runner = $this->getMockBuilder(MultiRunner::class)->disableOriginalConstructor() + ->setMethods(['wait'])->getMock(); + /** @var MultiRunner|\PHPUnit_Framework_MockObject_MockObject $runner */ + $promise = new CurlPromise($core, $runner); + + $onFulfill = function () { + }; + $core->expects(self::once())->method('addOnFulfilled')->with($onFulfill); + + $onReject = function () { + }; + $core->expects(self::once())->method('addOnRejected')->with($onReject); + + $promise->then($onFulfill, $onReject); + + $core->expects(self::once())->method('getState')->willReturn('STATE'); + self::assertEquals('STATE', $promise->getState()); + } +} diff --git a/tests/Unit/PromiseCoreTest.php b/tests/Unit/PromiseCoreTest.php new file mode 100644 index 0000000..489f834 --- /dev/null +++ b/tests/Unit/PromiseCoreTest.php @@ -0,0 +1,176 @@ +createMock(RequestInterface::class); + $responseBuilder = $this->createMock(ResponseBuilder::class); + + $this->expectException(\InvalidArgumentException::class); + $this->expectExceptionMessage('Parameter $handle expected to be a cURL resource, stream resource given'); + + new PromiseCore($request, fopen('php://memory', 'r+b'), $responseBuilder); + } + + /** + * Testing if handle is not a resource. + */ + public function testHandleIsNotAResource(): void + { + $request = $this->createMock(RequestInterface::class); + $responseBuilder = $this->createMock(ResponseBuilder::class); + + $this->expectException(\InvalidArgumentException::class); + $this->expectExceptionMessage('Parameter $handle expected to be a cURL resource, NULL given'); + + new PromiseCore($request, null, $responseBuilder); + } + + /** + * «onReject» callback can throw exception. + * + * @see https://github.com/php-http/curl-client/issues/26 + */ + public function testIssue26(): void + { + $request = $this->createMock(RequestInterface::class); + $responseBuilder = $this->createMock(ResponseBuilder::class); + + $this->handle = curl_init(); + + $core = new PromiseCore($request, $this->handle, $responseBuilder); + $core->addOnRejected( + function (RequestException $exception) { + throw new RequestException('Foo', $exception->getRequest(), $exception); + } + ); + $core->addOnRejected( + function (RequestException $exception) { + return new RequestException('Bar', $exception->getRequest(), $exception); + } + ); + + $exception = new RequestException('Error', $request); + $core->reject($exception); + self::assertEquals(Promise::REJECTED, $core->getState()); + self::assertEquals('Bar', $core->getException()->getMessage()); + } + + /** + * @expectedException \LogicException + */ + public function testNotRejected(): void + { + $request = $this->createMock(RequestInterface::class); + $responseBuilder = $this->createMock(ResponseBuilder::class); + + $this->handle = curl_init(); + + $core = new PromiseCore($request, $this->handle, $responseBuilder); + $core->getException(); + } + + /** + * Test on fulfill actions. + */ + public function testOnFulfill(): void + { + $request = $this->createMock(RequestInterface::class); + + $stream = $this->createMock(StreamInterface::class); + $response1 = $this->createConfiguredMock(ResponseInterface::class, ['getBody' => $stream]); + $responseBuilder = $this->createConfiguredMock(ResponseBuilder::class, ['getResponse' => $response1]); + $response2 = $this->createConfiguredMock(ResponseInterface::class, ['getBody' => $stream]); + + $this->handle = curl_init(); + + $core = new PromiseCore($request, $this->handle, $responseBuilder); + + self::assertSame($request, $core->getRequest()); + self::assertSame($this->handle, $core->getHandle()); + + $core->addOnFulfilled( + function (ResponseInterface $response) use ($response1, $response2) { + self::assertSame($response1, $response); + + return $response2; + } + ); + + $core->fulfill(); + self::assertEquals(Promise::FULFILLED, $core->getState()); + } + + /** + * Test on reject actions. + */ + public function testOnReject(): void + { + $request = $this->createMock(RequestInterface::class); + $responseBuilder = $this->createMock(ResponseBuilder::class); + + $this->handle = curl_init(); + + $core = new PromiseCore($request, $this->handle, $responseBuilder); + $core->addOnRejected( + function (RequestException $exception) { + throw new RequestException('Foo', $exception->getRequest(), $exception); + } + ); + + $exception = new RequestException('Error', $request); + $core->reject($exception); + self::assertEquals(Promise::REJECTED, $core->getState()); + self::assertEquals('Foo', $core->getException()->getMessage()); + + $core->addOnRejected( + function (RequestException $exception) { + return new RequestException('Bar', $exception->getRequest(), $exception); + } + ); + self::assertEquals('Bar', $core->getException()->getMessage()); + } + + /** + * Tears down the fixture. + */ + protected function tearDown() + { + if (is_resource($this->handle)) { + curl_close($this->handle); + } + + parent::tearDown(); + } +} From fa4feeac1267a01c22752a3461fe1652f2dee01b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=9C=D0=B8=D1=85=D0=B0=D0=B8=D0=BB=20=D0=9A=D1=80=D0=B0?= =?UTF-8?q?=D1=81=D0=B8=D0=BB=D1=8C=D0=BD=D0=B8=D0=BA=D0=BE=D0=B2?= Date: Wed, 26 Dec 2018 17:31:00 +0300 Subject: [PATCH 072/108] Restore php-http/message dependency --- composer.json | 1 + 1 file changed, 1 insertion(+) diff --git a/composer.json b/composer.json index 5b0a0e2..eba01d8 100644 --- a/composer.json +++ b/composer.json @@ -21,6 +21,7 @@ "ext-curl": "*", "php-http/discovery": "^1.0", "php-http/httplug": "^2.0", + "php-http/message": "^1.2", "psr/http-client": "^1.0", "psr/http-factory": "^1.0", "symfony/options-resolver": "^3.4 || ^4.0" From dd1384284b20f6a2cb78f5031370bc310066de1e Mon Sep 17 00:00:00 2001 From: David Buchmann Date: Thu, 24 Jan 2019 11:06:45 +0100 Subject: [PATCH 073/108] cleanup for PSR-17 --- CHANGELOG.md | 3 +- composer.json | 4 +- src/Client.php | 98 ++++++------------- .../HttpAsyncClientDiactorosTest.php | 6 +- .../Functional/HttpAsyncClientGuzzleTest.php | 6 +- tests/Functional/HttpAsyncClientTestCase.php | 40 +++----- tests/Functional/HttpClientDiactorosTest.php | 14 +-- tests/Functional/HttpClientGuzzleTest.php | 14 +-- tests/Functional/HttpClientTestCase.php | 54 ++++------ tests/Unit/ClientTest.php | 2 - tests/Unit/CurlPromiseTest.php | 11 --- tests/Unit/PromiseCoreTest.php | 15 +-- 12 files changed, 77 insertions(+), 190 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 112bfc3..a2faf1c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,7 +1,8 @@ # Change Log -## Unreleased +## 2.0.0 - Unreleased +- Client expects PSR-17 ResponseFactoryInterface and StreamFactoryInterface rather than Httplug factories. - Allow cURL options to overwrite our default spec-compliant default configuration ## 1.7.1 - 2018-03-36 diff --git a/composer.json b/composer.json index eba01d8..6b528f1 100644 --- a/composer.json +++ b/composer.json @@ -1,6 +1,6 @@ { "name": "php-http/curl-client", - "description": "PSR-18 cURL client", + "description": "PSR-18 and HTTPlug Async client with cURL", "license": "MIT", "keywords": [ "curl", @@ -19,7 +19,7 @@ "require": { "php": "^7.1", "ext-curl": "*", - "php-http/discovery": "^1.0", + "php-http/discovery": "^1.6", "php-http/httplug": "^2.0", "php-http/message": "^1.2", "psr/http-client": "^1.0", diff --git a/src/Client.php b/src/Client.php index 5b19bd1..7dc4262 100644 --- a/src/Client.php +++ b/src/Client.php @@ -7,9 +7,7 @@ use Http\Client\Exception; use Http\Client\HttpAsyncClient; use Http\Client\HttpClient; -use Http\Discovery\MessageFactoryDiscovery; -use Http\Discovery\StreamFactoryDiscovery; -use Http\Promise\Promise; +use Http\Discovery\Psr17FactoryDiscovery; use Psr\Http\Message\RequestInterface; use Psr\Http\Message\ResponseFactoryInterface; use Psr\Http\Message\ResponseInterface; @@ -17,7 +15,7 @@ use Symfony\Component\OptionsResolver\OptionsResolver; /** - * PSR-7 compatible cURL based HTTP client. + * PSR-18 and HTTPlug Async client based on lib-curl. * * @license http://opensource.org/licenses/MIT MIT * @author Михаил Красильников @@ -34,7 +32,7 @@ class Client implements HttpClient, HttpAsyncClient * * @var array */ - private $options; + private $curlOptions; /** * PSR-17 response factory. @@ -65,25 +63,19 @@ class Client implements HttpClient, HttpAsyncClient private $multiRunner; /** - * Construct client. - * * @param ResponseFactoryInterface|null $responseFactory PSR-17 HTTP response factory. * @param StreamFactoryInterface|null $streamFactory PSR-17 HTTP stream factory. * @param array $options cURL options {@link http://php.net/curl_setopt} * * @throws \Http\Discovery\Exception\NotFoundException If factory discovery failed - * - * @since x.x $messageFactory changed to PSR-17 ResponseFactoryInterface $responseFactory. - * @since x.x $streamFactory type changed to PSR-17 StreamFactoryInterface. - * @since 1.0 */ public function __construct( ResponseFactoryInterface $responseFactory = null, StreamFactoryInterface $streamFactory = null, array $options = [] ) { - $this->responseFactory = $responseFactory; // FIXME ?: MessageFactoryDiscovery::find(); - $this->streamFactory = $streamFactory; // FIXME ?: StreamFactoryDiscovery::find(); + $this->responseFactory = $responseFactory ?: Psr17FactoryDiscovery::findResponseFactory(); + $this->streamFactory = $streamFactory ?: Psr17FactoryDiscovery::findStreamFactory(); $resolver = new OptionsResolver(); $resolver->setDefaults( [ @@ -99,7 +91,7 @@ public function __construct( // Make sure that we accept everything that is in the options. $resolver->setDefined(array_keys($options)); - $this->options = $resolver->resolve($options); + $this->curlOptions = $resolver->resolve($options); } /** @@ -113,11 +105,7 @@ public function __destruct() } /** - * Sends a PSR-7 request and returns a PSR-7 response. - * - * @param RequestInterface $request - * - * @return ResponseInterface + * {@inheritdoc} * * @throws \Http\Client\Exception\NetworkException In case of network problems * @throws \Http\Client\Exception\RequestException On invalid request @@ -164,11 +152,7 @@ public function sendRequest(RequestInterface $request): ResponseInterface } /** - * Sends a PSR-7 request in an asynchronous way. - * - * @param RequestInterface $request - * - * @return Promise + * {@inheritdoc} * * @throws \Http\Client\Exception\RequestException On invalid request * @throws \InvalidArgumentException For invalid header names or values @@ -198,36 +182,31 @@ public function sendAsyncRequest(RequestInterface $request) /** * Update cURL options for this request and hook in the response builder. * - * @param RequestInterface $request - * @param ResponseBuilder $responseBuilder - * * @throws \Http\Client\Exception\RequestException On invalid request * @throws \InvalidArgumentException For invalid header names or values * @throws \RuntimeException If can not read body - * - * @return array */ - private function prepareRequestOptions(RequestInterface $request, ResponseBuilder $responseBuilder) + private function prepareRequestOptions(RequestInterface $request, ResponseBuilder $responseBuilder): array { - $options = $this->options; + $curlOptions = $this->curlOptions; try { - $options[CURLOPT_HTTP_VERSION] + $curlOptions[CURLOPT_HTTP_VERSION] = $this->getProtocolVersion($request->getProtocolVersion()); } catch (\UnexpectedValueException $e) { throw new Exception\RequestException($e->getMessage(), $request); } - $options[CURLOPT_URL] = (string) $request->getUri(); + $curlOptions[CURLOPT_URL] = (string) $request->getUri(); - $options = $this->addRequestBodyOptions($request, $options); + $curlOptions = $this->addRequestBodyOptions($request, $curlOptions); - $options[CURLOPT_HTTPHEADER] = $this->createHeaders($request, $options); + $curlOptions[CURLOPT_HTTPHEADER] = $this->createHeaders($request, $curlOptions); if ($request->getUri()->getUserInfo()) { - $options[CURLOPT_USERPWD] = $request->getUri()->getUserInfo(); + $curlOptions[CURLOPT_USERPWD] = $request->getUri()->getUserInfo(); } - $options[CURLOPT_HEADERFUNCTION] = function ($ch, $data) use ($responseBuilder) { + $curlOptions[CURLOPT_HEADERFUNCTION] = function ($ch, $data) use ($responseBuilder) { $str = trim($data); if ('' !== $str) { if (strpos(strtolower($str), 'http/') === 0) { @@ -240,21 +219,17 @@ private function prepareRequestOptions(RequestInterface $request, ResponseBuilde return strlen($data); }; - $options[CURLOPT_WRITEFUNCTION] = function ($ch, $data) use ($responseBuilder) { + $curlOptions[CURLOPT_WRITEFUNCTION] = function ($ch, $data) use ($responseBuilder) { return $responseBuilder->getResponse()->getBody()->write($data); }; - return $options; + return $curlOptions; } /** * Return cURL constant for specified HTTP version. * - * @param string $requestVersion - * * @throws \UnexpectedValueException If unsupported version requested - * - * @return int */ private function getProtocolVersion(string $requestVersion): int { @@ -275,13 +250,8 @@ private function getProtocolVersion(string $requestVersion): int /** * Add request body related cURL options. - * - * @param RequestInterface $request - * @param array $options - * - * @return array */ - private function addRequestBodyOptions(RequestInterface $request, array $options): array + private function addRequestBodyOptions(RequestInterface $request, array $curlOptions): array { /* * Some HTTP methods cannot have payload: @@ -302,40 +272,37 @@ private function addRequestBodyOptions(RequestInterface $request, array $options // Message has non empty body. if (null === $bodySize || $bodySize > 1024 * 1024) { // Avoid full loading large or unknown size body into memory - $options[CURLOPT_UPLOAD] = true; + $curlOptions[CURLOPT_UPLOAD] = true; if (null !== $bodySize) { - $options[CURLOPT_INFILESIZE] = $bodySize; + $curlOptions[CURLOPT_INFILESIZE] = $bodySize; } - $options[CURLOPT_READFUNCTION] = function ($ch, $fd, $length) use ($body) { + $curlOptions[CURLOPT_READFUNCTION] = function ($ch, $fd, $length) use ($body) { return $body->read($length); }; } else { // Small body can be loaded into memory - $options[CURLOPT_POSTFIELDS] = (string) $body; + $curlOptions[CURLOPT_POSTFIELDS] = (string) $body; } } } if ($request->getMethod() === 'HEAD') { // This will set HTTP method to "HEAD". - $options[CURLOPT_NOBODY] = true; + $curlOptions[CURLOPT_NOBODY] = true; } elseif ($request->getMethod() !== 'GET') { // GET is a default method. Other methods should be specified explicitly. - $options[CURLOPT_CUSTOMREQUEST] = $request->getMethod(); + $curlOptions[CURLOPT_CUSTOMREQUEST] = $request->getMethod(); } - return $options; + return $curlOptions; } /** * Create headers array for CURLOPT_HTTPHEADER. * - * @param RequestInterface $request - * @param array $options cURL options - * * @return string[] */ - private function createHeaders(RequestInterface $request, array $options): array + private function createHeaders(RequestInterface $request, array $curlOptions): array { $curlHeaders = []; $headers = $request->getHeaders(); @@ -346,10 +313,10 @@ private function createHeaders(RequestInterface $request, array $options): array continue; } if ('content-length' === $header) { - if (array_key_exists(CURLOPT_POSTFIELDS, $options)) { + if (array_key_exists(CURLOPT_POSTFIELDS, $curlOptions)) { // Small body content length can be calculated here. - $values = [strlen($options[CURLOPT_POSTFIELDS])]; - } elseif (!array_key_exists(CURLOPT_READFUNCTION, $options)) { + $values = [strlen($curlOptions[CURLOPT_POSTFIELDS])]; + } elseif (!array_key_exists(CURLOPT_READFUNCTION, $curlOptions)) { // Else if there is no body, forcing "Content-length" to 0 $values = [0]; } @@ -367,11 +334,6 @@ private function createHeaders(RequestInterface $request, array $options): array return $curlHeaders; } - /** - * Create new ResponseBuilder instance. - * - * @return ResponseBuilder - */ private function createResponseBuilder(): ResponseBuilder { $body = $this->streamFactory->createStreamFromFile('php://temp', 'w+b'); diff --git a/tests/Functional/HttpAsyncClientDiactorosTest.php b/tests/Functional/HttpAsyncClientDiactorosTest.php index 273ea0f..2ef7b6c 100644 --- a/tests/Functional/HttpAsyncClientDiactorosTest.php +++ b/tests/Functional/HttpAsyncClientDiactorosTest.php @@ -10,14 +10,12 @@ use Zend\Diactoros\StreamFactory; /** - * Testing asynchronous requests with Zend Diactoros factories. + * @covers \Http\Client\Curl\Client */ class HttpAsyncClientDiactorosTest extends HttpAsyncClientTestCase { /** - * Create asynchronous HTTP client for tests. - * - * @return HttpAsyncClient + * {@inheritdoc} */ protected function createHttpAsyncClient(): HttpAsyncClient { diff --git a/tests/Functional/HttpAsyncClientGuzzleTest.php b/tests/Functional/HttpAsyncClientGuzzleTest.php index 1b72d83..f1d2808 100644 --- a/tests/Functional/HttpAsyncClientGuzzleTest.php +++ b/tests/Functional/HttpAsyncClientGuzzleTest.php @@ -10,14 +10,12 @@ use Http\Message\StreamFactory\GuzzleStreamFactory; /** - * Tests for Http\Client\Curl\Client. + * @covers \Http\Client\Curl\Client */ class HttpAsyncClientGuzzleTest extends HttpAsyncClientTestCase { /** - * Create asynchronious HTTP client for tests. - * - * @return HttpAsyncClient + * {@inheritdoc} */ protected function createHttpAsyncClient(): HttpAsyncClient { diff --git a/tests/Functional/HttpAsyncClientTestCase.php b/tests/Functional/HttpAsyncClientTestCase.php index b940dc6..ded8f72 100644 --- a/tests/Functional/HttpAsyncClientTestCase.php +++ b/tests/Functional/HttpAsyncClientTestCase.php @@ -12,52 +12,42 @@ abstract class HttpAsyncClientTestCase extends HttpAsyncClientTest { /** - * TODO Summary. - * - * @param string $method HTTP method. - * @param string $uri Request URI. - * @param array $headers HTTP headers. - * @param string $body Request body. + * {@inheritdoc} * * @dataProvider requestProvider */ - public function testAsyncSendRequest($method, $uri, array $headers, $body): void + public function testAsyncSendRequest($httpMethod, $uri, array $httpHeaders, $requestBody): void { - if ($body !== null && in_array($method, ['GET', 'HEAD', 'TRACE'], true)) { - self::markTestSkipped('cURL can not send body using '.$method); + if ($requestBody !== null && in_array($httpMethod, ['GET', 'HEAD', 'TRACE'], true)) { + self::markTestSkipped('cURL can not send body using '.$httpMethod); } parent::testAsyncSendRequest( - $method, + $httpMethod, $uri, - $headers, - $body + $httpHeaders, + $requestBody ); } /** - * TODO Summary. - * - * @param array $uriAndOutcome TODO ??? - * @param string $protocolVersion HTTP version. - * @param array $headers HTTP headers. - * @param string $body Request body. + * {@inheritdoc} * * @dataProvider requestWithOutcomeProvider */ public function testSendAsyncRequestWithOutcome( $uriAndOutcome, - $protocolVersion, - array $headers, - $body + $httpVersion, + array $httpHeaders, + $requestBody ): void { - if ( $body !== null) { + if ( $requestBody !== null) { self::markTestSkipped('cURL can not send body using GET'); } parent::testSendAsyncRequestWithOutcome( $uriAndOutcome, - $protocolVersion, - $headers, - $body + $httpVersion, + $httpHeaders, + $requestBody ); } } diff --git a/tests/Functional/HttpClientDiactorosTest.php b/tests/Functional/HttpClientDiactorosTest.php index 5117f4b..854bcdc 100644 --- a/tests/Functional/HttpClientDiactorosTest.php +++ b/tests/Functional/HttpClientDiactorosTest.php @@ -16,23 +16,11 @@ */ class HttpClientDiactorosTest extends HttpClientTestCase { - /** - * Create stream from file. - * - * @param string $filename - * - * @return StreamInterface - */ - protected function createFileStream($filename): StreamInterface + protected function createFileStream(string $filename): StreamInterface { return new Stream($filename); } - /** - * Create HTTP client for tests. - * - * @return HttpClient - */ protected function createHttpAdapter(): HttpClient { return new Client(new ResponseFactory(), new StreamFactory()); diff --git a/tests/Functional/HttpClientGuzzleTest.php b/tests/Functional/HttpClientGuzzleTest.php index 02d1689..9cbe9da 100644 --- a/tests/Functional/HttpClientGuzzleTest.php +++ b/tests/Functional/HttpClientGuzzleTest.php @@ -12,26 +12,22 @@ use Psr\Http\Message\StreamInterface; /** - * Tests for Http\Client\Curl\Client. + * @covers \Http\Client\Curl\Client */ class HttpClientGuzzleTest extends HttpClientTestCase { /** - * @return HttpClient + * {@inheritdoc} */ - protected function createHttpAdapter() + protected function createHttpAdapter(): HttpClient { return new Client(new GuzzleMessageFactory(), new GuzzleStreamFactory()); } /** - * Create stream from file. - * - * @param string $filename - * - * @return StreamInterface + * {@inheritdoc} */ - protected function createFileStream($filename) + protected function createFileStream(string $filename): StreamInterface { return new Stream(fopen($filename, 'r')); } diff --git a/tests/Functional/HttpClientTestCase.php b/tests/Functional/HttpClientTestCase.php index e7cec47..6df8a8d 100644 --- a/tests/Functional/HttpClientTestCase.php +++ b/tests/Functional/HttpClientTestCase.php @@ -20,9 +20,6 @@ abstract class HttpClientTestCase extends HttpClientTest */ protected $tmpFiles = []; - /** - * Test sending large files. - */ public function testSendLargeFile(): void { $filename = $this->createTempFile(); @@ -55,63 +52,46 @@ public function testSendLargeFile(): void } /** - * TODO Summary. - * - * @param string $method HTTP method. - * @param string $uri Request URI. - * @param array $headers HTTP headers. - * @param string $body Request body. + * {@inheritdoc} * * @dataProvider requestProvider */ - public function testSendRequest($method, $uri, array $headers, $body): void + public function testSendRequest($httpMethod, $uri, array $httpHeaders, $requestBody): void { - if ($body !== null && in_array($method, ['GET', 'HEAD', 'TRACE'], true)) { - self::markTestSkipped('cURL can not send body using '.$method); + if ($requestBody !== null && in_array($httpMethod, ['GET', 'HEAD', 'TRACE'], true)) { + self::markTestSkipped('cURL can not send body using '.$httpMethod); } parent::testSendRequest( - $method, + $httpMethod, $uri, - $headers, - $body + $httpHeaders, + $requestBody ); } /** - * TODO Summary. - * - * @param array $uriAndOutcome TODO ??? - * @param string $protocolVersion HTTP version. - * @param array $headers HTTP headers. - * @param string $body Request body. + * {@inheritdoc} * * @dataProvider requestWithOutcomeProvider */ public function testSendRequestWithOutcome( $uriAndOutcome, - $protocolVersion, - array $headers, - $body + $httpVersion, + array $httpHeaders, + $requestBody ): void { - if ($body !== null) { + if ($requestBody !== null) { self::markTestSkipped('cURL can not send body using GET'); } parent::testSendRequestWithOutcome( $uriAndOutcome, - $protocolVersion, - $headers, - $body + $httpVersion, + $httpHeaders, + $requestBody ); } - /** - * Create stream from file. - * - * @param string $filename - * - * @return StreamInterface - */ - abstract protected function createFileStream($filename): StreamInterface; + abstract protected function createFileStream(string $filename): StreamInterface; /** * Create temporary file. @@ -127,7 +107,7 @@ protected function createTempFile(): string } /** - * Tears down the fixture. + * Delete files created with createTempFile */ protected function tearDown() { diff --git a/tests/Unit/ClientTest.php b/tests/Unit/ClientTest.php index d4274c1..264e977 100644 --- a/tests/Unit/ClientTest.php +++ b/tests/Unit/ClientTest.php @@ -12,8 +12,6 @@ use Zend\Diactoros\Request; /** - * Tests for Http\Client\Curl\Client. - * * @covers \Http\Client\Curl\Client */ class ClientTest extends TestCase diff --git a/tests/Unit/CurlPromiseTest.php b/tests/Unit/CurlPromiseTest.php index 2cf42d8..617b258 100644 --- a/tests/Unit/CurlPromiseTest.php +++ b/tests/Unit/CurlPromiseTest.php @@ -13,15 +13,10 @@ use Psr\Http\Message\ResponseInterface; /** - * Tests for Http\Client\Curl\CurlPromise. - * * @covers \Http\Client\Curl\CurlPromise */ class CurlPromiseTest extends TestCase { - /** - * TODO Summary - */ public function testCoreCallWaitFulfilled(): void { $core = $this->createMock(PromiseCore::class); @@ -38,9 +33,6 @@ public function testCoreCallWaitFulfilled(): void self::assertSame($response, $promise->wait()); } - /** - * TODO Summary - */ public function testCoreCallWaitRejected(): void { $core = $this->createMock(PromiseCore::class); @@ -59,9 +51,6 @@ public function testCoreCallWaitRejected(): void } } - /** - * Test that promise call core methods. - */ public function testCoreCalls(): void { $core = $this->createMock(PromiseCore::class); diff --git a/tests/Unit/PromiseCoreTest.php b/tests/Unit/PromiseCoreTest.php index 489f834..b52df28 100644 --- a/tests/Unit/PromiseCoreTest.php +++ b/tests/Unit/PromiseCoreTest.php @@ -16,8 +16,6 @@ use Psr\Http\Message\StreamInterface; /** - * Tests for Http\Client\Curl\PromiseCore. - * * @covers \Http\Client\Curl\PromiseCore */ class PromiseCoreTest extends TestCase @@ -87,9 +85,6 @@ function (RequestException $exception) { self::assertEquals('Bar', $core->getException()->getMessage()); } - /** - * @expectedException \LogicException - */ public function testNotRejected(): void { $request = $this->createMock(RequestInterface::class); @@ -98,12 +93,10 @@ public function testNotRejected(): void $this->handle = curl_init(); $core = new PromiseCore($request, $this->handle, $responseBuilder); + $this->expectException(\LogicException::class); $core->getException(); } - /** - * Test on fulfill actions. - */ public function testOnFulfill(): void { $request = $this->createMock(RequestInterface::class); @@ -132,9 +125,6 @@ function (ResponseInterface $response) use ($response1, $response2) { self::assertEquals(Promise::FULFILLED, $core->getState()); } - /** - * Test on reject actions. - */ public function testOnReject(): void { $request = $this->createMock(RequestInterface::class); @@ -162,9 +152,6 @@ function (RequestException $exception) { self::assertEquals('Bar', $core->getException()->getMessage()); } - /** - * Tears down the fixture. - */ protected function tearDown() { if (is_resource($this->handle)) { From 4c7769f268f652e62e7466d8cd3c785081e35ee1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=9C=D0=B8=D1=85=D0=B0=D0=B8=D0=BB=20=D0=9A=D1=80=D0=B0?= =?UTF-8?q?=D1=81=D0=B8=D0=BB=D1=8C=D0=BD=D0=B8=D0=BA=D0=BE=D0=B2?= Date: Tue, 29 Jan 2019 17:10:58 +0300 Subject: [PATCH 074/108] tests: ClientTest formatted and rearranged --- tests/Unit/ClientTest.php | 67 ++++++++++++++++++++------------------- 1 file changed, 35 insertions(+), 32 deletions(-) diff --git a/tests/Unit/ClientTest.php b/tests/Unit/ClientTest.php index 264e977..0922733 100644 --- a/tests/Unit/ClientTest.php +++ b/tests/Unit/ClientTest.php @@ -17,11 +17,26 @@ class ClientTest extends TestCase { /** - * "Expect" header should be empty. + * Tests throwing InvalidArgumentException when invalid cURL options passed to constructor. + */ + public function testExceptionThrownOnInvalidCurlOptions(): void + { + $this->expectException(InvalidOptionsException::class); + new Client( + $this->createMock(ResponseFactoryInterface::class), + $this->createMock(StreamFactoryInterface::class), + [ + CURLOPT_HEADER => true, // this won't work with our client + ] + ); + } + + /** + * "Expect" header should be empty by default. * * @link https://github.com/php-http/curl-client/issues/18 */ - public function testExpectHeader(): void + public function testExpectHeaderIsEmpty(): void { $client = $this->createMock(Client::class); @@ -36,11 +51,11 @@ public function testExpectHeader(): void } /** - * "Expect" header should be empty. + * "Expect" header should be empty when POST field is empty. * * @link https://github.com/php-http/curl-client/issues/18 */ - public function testWithNullPostFields(): void + public function testExpectHeaderIsEmpty2(): void { $client = $this->createMock(Client::class); @@ -55,21 +70,6 @@ public function testWithNullPostFields(): void self::assertContains('content-length: 0', $headers); } - public function testRewindStream(): void - { - $client = $this->createMock(Client::class); - - $bodyOptions = new \ReflectionMethod(Client::class, 'addRequestBodyOptions'); - $bodyOptions->setAccessible(true); - - $body = \GuzzleHttp\Psr7\stream_for('abcdef'); - $body->seek(3); - $request = new Request('http://foo.com', 'POST', $body); - $options = $bodyOptions->invoke($client, $request, []); - - static::assertEquals('abcdef', $options[CURLOPT_POSTFIELDS]); - } - public function testRewindLargeStream(): void { $client = $this->createMock(Client::class); @@ -88,21 +88,24 @@ public function testRewindLargeStream(): void $request = new Request('http://foo.com', 'POST', $body); $options = $bodyOptions->invoke($client, $request, []); - static::assertTrue(false !== strstr($options[CURLOPT_READFUNCTION](null, null, $length), 'abcdef'), 'Steam was not rewinded'); + static::assertTrue( + false !== strstr($options[CURLOPT_READFUNCTION](null, null, $length), 'abcdef'), + 'Steam was not rewinded' + ); } - /** - * Tests throwing InvalidArgumentException when invalid cURL options passed to constructor. - */ - public function testInvalidCurlOptions(): void + public function testRewindStream(): void { - $this->expectException(InvalidOptionsException::class); - new Client( - $this->createMock(ResponseFactoryInterface::class), - $this->createMock(StreamFactoryInterface::class), - [ - CURLOPT_HEADER => true, // this won't work with our client - ] - ); + $client = $this->createMock(Client::class); + + $bodyOptions = new \ReflectionMethod(Client::class, 'addRequestBodyOptions'); + $bodyOptions->setAccessible(true); + + $body = \GuzzleHttp\Psr7\stream_for('abcdef'); + $body->seek(3); + $request = new Request('http://foo.com', 'POST', $body); + $options = $bodyOptions->invoke($client, $request, []); + + static::assertEquals('abcdef', $options[CURLOPT_POSTFIELDS]); } } From 800134806b8523c4d029137e02b0081288444755 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=9C=D0=B8=D1=85=D0=B0=D0=B8=D0=BB=20=D0=9A=D1=80=D0=B0?= =?UTF-8?q?=D1=81=D0=B8=D0=BB=D1=8C=D0=BD=D0=B8=D0=BA=D0=BE=D0=B2?= Date: Tue, 5 Mar 2019 22:29:00 +0300 Subject: [PATCH 075/108] Switch php-http/client-integration-tests from master to 2.0 --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 6b528f1..7962557 100644 --- a/composer.json +++ b/composer.json @@ -28,7 +28,7 @@ }, "require-dev": { "guzzlehttp/psr7": "^1.0", - "php-http/client-integration-tests": "dev-master", + "php-http/client-integration-tests": "^2.0", "phpunit/phpunit": "^7.5", "zendframework/zend-diactoros": "^2.0" }, From 8f25c9f896ebdc2e8e21d632ab26da40c22576d5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=9C=D0=B8=D1=85=D0=B0=D0=B8=D0=BB=20=D0=9A=D1=80=D0=B0?= =?UTF-8?q?=D1=81=D0=B8=D0=BB=D1=8C=D0=BD=D0=B8=D0=BA=D0=BE=D0=B2?= Date: Tue, 5 Mar 2019 22:58:57 +0300 Subject: [PATCH 076/108] Small refactoring, PHPDoc and formatting --- src/Client.php | 145 +++++++++++++++++++++++++++++++++---------------- 1 file changed, 98 insertions(+), 47 deletions(-) diff --git a/src/Client.php b/src/Client.php index 7dc4262..6b00be9 100644 --- a/src/Client.php +++ b/src/Client.php @@ -7,7 +7,9 @@ use Http\Client\Exception; use Http\Client\HttpAsyncClient; use Http\Client\HttpClient; +use Http\Discovery\Exception\NotFoundException; use Http\Discovery\Psr17FactoryDiscovery; +use Http\Promise\Promise; use Psr\Http\Message\RequestInterface; use Psr\Http\Message\ResponseFactoryInterface; use Psr\Http\Message\ResponseInterface; @@ -63,11 +65,16 @@ class Client implements HttpClient, HttpAsyncClient private $multiRunner; /** + * Create HTTP client. + * * @param ResponseFactoryInterface|null $responseFactory PSR-17 HTTP response factory. * @param StreamFactoryInterface|null $streamFactory PSR-17 HTTP stream factory. - * @param array $options cURL options {@link http://php.net/curl_setopt} + * @param array $options cURL options + * {@link http://php.net/curl_setopt}. + * + * @throws NotFoundException If factory discovery failed. * - * @throws \Http\Discovery\Exception\NotFoundException If factory discovery failed + * @since 2.0 Accepts PSR-17 factories instead of HTTPlug ones. */ public function __construct( ResponseFactoryInterface $responseFactory = null, @@ -81,11 +88,21 @@ public function __construct( [ CURLOPT_HEADER => false, CURLOPT_RETURNTRANSFER => false, - CURLOPT_FOLLOWLOCATION => false, + CURLOPT_FOLLOWLOCATION => false ] ); - $resolver->setAllowedValues(CURLOPT_HEADER, [false]); // our parsing will fail if this is set to true - $resolver->setAllowedValues(CURLOPT_RETURNTRANSFER, [false]); // our parsing will fail if this is set to true + + // Our parsing will fail if this is set to true. + $resolver->setAllowedValues( + CURLOPT_HEADER, + [false] + ); + + // Our parsing will fail if this is set to true. + $resolver->setAllowedValues( + CURLOPT_RETURNTRANSFER, + [false] + ); // We do not know what everything curl supports and might support in the future. // Make sure that we accept everything that is in the options. @@ -105,12 +122,16 @@ public function __destruct() } /** - * {@inheritdoc} + * Sends a PSR-7 request and returns a PSR-7 response. + * + * @param RequestInterface $request * - * @throws \Http\Client\Exception\NetworkException In case of network problems - * @throws \Http\Client\Exception\RequestException On invalid request - * @throws \InvalidArgumentException For invalid header names or values - * @throws \RuntimeException If creating the body stream fails + * @return ResponseInterface + * + * @throws \InvalidArgumentException For invalid header names or values. + * @throws \RuntimeException If creating the body stream fails. + * @throws Exception\NetworkException In case of network problems. + * @throws Exception\RequestException On invalid request. * * @since 1.6 \UnexpectedValueException replaced with RequestException * @since 1.6 Throw NetworkException on network errors @@ -152,42 +173,37 @@ public function sendRequest(RequestInterface $request): ResponseInterface } /** - * {@inheritdoc} - * - * @throws \Http\Client\Exception\RequestException On invalid request - * @throws \InvalidArgumentException For invalid header names or values - * @throws \RuntimeException If creating the body stream fails + * Create builder to use for building response object. * - * @since 1.6 \UnexpectedValueException replaced with RequestException - * @since 1.0 + * @return ResponseBuilder */ - public function sendAsyncRequest(RequestInterface $request) + private function createResponseBuilder(): ResponseBuilder { - if (!$this->multiRunner instanceof MultiRunner) { - $this->multiRunner = new MultiRunner(); - } - - $handle = curl_init(); - $responseBuilder = $this->createResponseBuilder(); - $requestOptions = $this->prepareRequestOptions($request, $responseBuilder); - curl_setopt_array($handle, $requestOptions); + $body = $this->streamFactory->createStreamFromFile('php://temp', 'w+b'); - $core = new PromiseCore($request, $handle, $responseBuilder); - $promise = new CurlPromise($core, $this->multiRunner); - $this->multiRunner->add($core); + $response = $this->responseFactory + ->createResponse(200) + ->withBody($body); - return $promise; + return new ResponseBuilder($response); } /** - * Update cURL options for this request and hook in the response builder. + * Update cURL options for given request and hook in the response builder. + * + * @param RequestInterface $request Request on which to create options. + * @param ResponseBuilder $responseBuilder Builder to use for building response. * - * @throws \Http\Client\Exception\RequestException On invalid request - * @throws \InvalidArgumentException For invalid header names or values - * @throws \RuntimeException If can not read body + * @return array cURL options based on request. + * + * @throws \InvalidArgumentException For invalid header names or values. + * @throws \RuntimeException If can not read body. + * @throws Exception\RequestException On invalid request. */ - private function prepareRequestOptions(RequestInterface $request, ResponseBuilder $responseBuilder): array - { + private function prepareRequestOptions( + RequestInterface $request, + ResponseBuilder $responseBuilder + ): array { $curlOptions = $this->curlOptions; try { @@ -196,7 +212,7 @@ private function prepareRequestOptions(RequestInterface $request, ResponseBuilde } catch (\UnexpectedValueException $e) { throw new Exception\RequestException($e->getMessage(), $request); } - $curlOptions[CURLOPT_URL] = (string) $request->getUri(); + $curlOptions[CURLOPT_URL] = (string)$request->getUri(); $curlOptions = $this->addRequestBodyOptions($request, $curlOptions); @@ -209,7 +225,7 @@ private function prepareRequestOptions(RequestInterface $request, ResponseBuilde $curlOptions[CURLOPT_HEADERFUNCTION] = function ($ch, $data) use ($responseBuilder) { $str = trim($data); if ('' !== $str) { - if (strpos(strtolower($str), 'http/') === 0) { + if (stripos($str, 'http/') === 0) { $responseBuilder->setStatus($str)->getResponse(); } else { $responseBuilder->addHeader($str); @@ -229,7 +245,11 @@ private function prepareRequestOptions(RequestInterface $request, ResponseBuilde /** * Return cURL constant for specified HTTP version. * - * @throws \UnexpectedValueException If unsupported version requested + * @param string $requestVersion HTTP version ("1.0", "1.1" or "2.0"). + * + * @return int Respective CURL_HTTP_VERSION_x_x constant. + * + * @throws \UnexpectedValueException If unsupported version requested. */ private function getProtocolVersion(string $requestVersion): int { @@ -250,6 +270,11 @@ private function getProtocolVersion(string $requestVersion): int /** * Add request body related cURL options. + * + * @param RequestInterface $request Request on which to create options. + * @param array $curlOptions Options created by prepareRequestOptions(). + * + * @return array cURL options based on request. */ private function addRequestBodyOptions(RequestInterface $request, array $curlOptions): array { @@ -281,7 +306,7 @@ private function addRequestBodyOptions(RequestInterface $request, array $curlOpt }; } else { // Small body can be loaded into memory - $curlOptions[CURLOPT_POSTFIELDS] = (string) $body; + $curlOptions[CURLOPT_POSTFIELDS] = (string)$body; } } } @@ -300,6 +325,9 @@ private function addRequestBodyOptions(RequestInterface $request, array $curlOpt /** * Create headers array for CURLOPT_HTTPHEADER. * + * @param RequestInterface $request Request on which to create headers. + * @param array $curlOptions Options created by prepareRequestOptions(). + * * @return string[] */ private function createHeaders(RequestInterface $request, array $curlOptions): array @@ -322,7 +350,7 @@ private function createHeaders(RequestInterface $request, array $curlOptions): a } } foreach ($values as $value) { - $curlHeaders[] = $name.': '.$value; + $curlHeaders[] = $name . ': ' . $value; } } /* @@ -334,14 +362,37 @@ private function createHeaders(RequestInterface $request, array $curlOptions): a return $curlHeaders; } - private function createResponseBuilder(): ResponseBuilder + /** + * Sends a PSR-7 request in an asynchronous way. + * + * Exceptions related to processing the request are available from the returned Promise. + * + * @param RequestInterface $request + * + * @return Promise Resolves a PSR-7 Response or fails with an Http\Client\Exception. + * + * @throws \InvalidArgumentException For invalid header names or values. + * @throws \RuntimeException If creating the body stream fails. + * @throws Exception\RequestException On invalid request. + * + * @since 1.6 \UnexpectedValueException replaced with RequestException + * @since 1.0 + */ + public function sendAsyncRequest(RequestInterface $request) { - $body = $this->streamFactory->createStreamFromFile('php://temp', 'w+b'); + if (!$this->multiRunner instanceof MultiRunner) { + $this->multiRunner = new MultiRunner(); + } - $response = $this->responseFactory - ->createResponse(200) - ->withBody($body); + $handle = curl_init(); + $responseBuilder = $this->createResponseBuilder(); + $requestOptions = $this->prepareRequestOptions($request, $responseBuilder); + curl_setopt_array($handle, $requestOptions); - return new ResponseBuilder($response); + $core = new PromiseCore($request, $handle, $responseBuilder); + $promise = new CurlPromise($core, $this->multiRunner); + $this->multiRunner->add($core); + + return $promise; } } From e7a2a5ebcce1ff7d75eaf02b7c85634a6fac00da Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=9C=D0=B8=D1=85=D0=B0=D0=B8=D0=BB=20=D0=9A=D1=80=D0=B0?= =?UTF-8?q?=D1=81=D0=B8=D0=BB=D1=8C=D0=BD=D0=B8=D0=BA=D0=BE=D0=B2?= Date: Tue, 5 Mar 2019 22:59:23 +0300 Subject: [PATCH 077/108] Version 2.0.0 --- CHANGELOG.md | 30 ++++++++++++++++++++++++++---- 1 file changed, 26 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a2faf1c..61c63db 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,11 +1,33 @@ # Change Log -## 2.0.0 - Unreleased +All notable changes to this project will be documented in this file. -- Client expects PSR-17 ResponseFactoryInterface and StreamFactoryInterface rather than Httplug factories. -- Allow cURL options to overwrite our default spec-compliant default configuration +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). -## 1.7.1 - 2018-03-36 + +## 2.0.0 - 2019-03-05 + +- Client expects PSR-17 ResponseFactoryInterface and StreamFactoryInterface rather than Httplug + factories. +- Allow cURL options to overwrite our default spec-compliant default configuration. + +### Removed + +- HHVM support removed. + +### Changed + +- Minimal PHP version changed to 7.1. +- `Client::__construct` now expects PSR-17 factories instead of HTTPlug ones. + +### Added + +- #41: Support [PSR-17](https://www.php-fig.org/psr/psr-17/) and + [PSR-18](https://www.php-fig.org/psr/psr-18/). + + +## 1.7.1 - 2018-03-26 ### Fixed From d02343021dacbaf54cecd4e9a9181dbf6d9927f1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=9C=D0=B8=D1=85=D0=B0=D0=B8=D0=BB=20=D0=9A=D1=80=D0=B0?= =?UTF-8?q?=D1=81=D0=B8=D0=BB=D1=8C=D0=BD=D0=B8=D0=BA=D0=BE=D0=B2?= Date: Tue, 5 Mar 2019 23:03:46 +0300 Subject: [PATCH 078/108] CHANGELOG cleanup --- CHANGELOG.md | 4 ---- 1 file changed, 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 61c63db..26361ae 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,10 +8,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## 2.0.0 - 2019-03-05 -- Client expects PSR-17 ResponseFactoryInterface and StreamFactoryInterface rather than Httplug - factories. -- Allow cURL options to overwrite our default spec-compliant default configuration. - ### Removed - HHVM support removed. From a2385c03ff249c03f7bc0c94b440fd52d47d6e0e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Baptiste=20Clavi=C3=A9?= Date: Tue, 10 Sep 2019 17:26:26 +0200 Subject: [PATCH 079/108] State that the curl-client provides a http client --- composer.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 7962557..1e6d5ab 100644 --- a/composer.json +++ b/composer.json @@ -44,7 +44,8 @@ }, "provide": { "php-http/client-implementation": "1.0", - "php-http/async-client-implementation": "1.0" + "php-http/async-client-implementation": "1.0", + "psr/http-client-implementation": "1.0" }, "scripts": { "test": "vendor/bin/phpunit", From 4e4b42ba084c67be15632325bb92ff1bb8719599 Mon Sep 17 00:00:00 2001 From: Graham Campbell Date: Sun, 17 Nov 2019 15:28:36 +0000 Subject: [PATCH 080/108] Support Symfony 5 --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 1e6d5ab..e240cbc 100644 --- a/composer.json +++ b/composer.json @@ -24,7 +24,7 @@ "php-http/message": "^1.2", "psr/http-client": "^1.0", "psr/http-factory": "^1.0", - "symfony/options-resolver": "^3.4 || ^4.0" + "symfony/options-resolver": "^3.4 || ^4.0 || ^5.0" }, "require-dev": { "guzzlehttp/psr7": "^1.0", From 4c61021abbe75a560d96b9c39f606222d6982f9c Mon Sep 17 00:00:00 2001 From: Tim Goudriaan Date: Sat, 23 Nov 2019 12:05:47 +0100 Subject: [PATCH 081/108] Cast CURL constants to string that cause a InvalidArgumentException --- src/Client.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Client.php b/src/Client.php index 6b00be9..f6b844c 100644 --- a/src/Client.php +++ b/src/Client.php @@ -94,13 +94,13 @@ public function __construct( // Our parsing will fail if this is set to true. $resolver->setAllowedValues( - CURLOPT_HEADER, + (string)CURLOPT_HEADER, [false] ); // Our parsing will fail if this is set to true. $resolver->setAllowedValues( - CURLOPT_RETURNTRANSFER, + (string)CURLOPT_RETURNTRANSFER, [false] ); From 225f137829f0fdec4c9ede8e93715db7e24145b0 Mon Sep 17 00:00:00 2001 From: Tobias Nyholm Date: Fri, 27 Dec 2019 11:49:54 +0100 Subject: [PATCH 082/108] Prepare for release of 2.1.0 --- CHANGELOG.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 26361ae..5efce9d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,11 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## 2.1.0 - 2019-12-27 + +### Added + +- Symfony 5 support ## 2.0.0 - 2019-03-05 From b0d2accaccc2e5ef34d7b5cb23874ad7ec4b8675 Mon Sep 17 00:00:00 2001 From: jxlwqq Date: Fri, 3 Jan 2020 20:07:58 +0800 Subject: [PATCH 083/108] Update composer.json --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index e240cbc..94e7c3d 100644 --- a/composer.json +++ b/composer.json @@ -30,7 +30,7 @@ "guzzlehttp/psr7": "^1.0", "php-http/client-integration-tests": "^2.0", "phpunit/phpunit": "^7.5", - "zendframework/zend-diactoros": "^2.0" + "laminas/laminas-diactoros": "^2.0" }, "autoload": { "psr-4": { From 59ec574acf38ceed803c52fedb095ac00496da18 Mon Sep 17 00:00:00 2001 From: jxlwqq Date: Mon, 6 Jan 2020 17:47:22 +0800 Subject: [PATCH 084/108] Replace Zend\Diactoros namespace to Laminas\Diactoros in tests --- tests/Functional/HttpAsyncClientDiactorosTest.php | 4 ++-- tests/Functional/HttpClientDiactorosTest.php | 8 ++++---- tests/Unit/ClientTest.php | 2 +- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/tests/Functional/HttpAsyncClientDiactorosTest.php b/tests/Functional/HttpAsyncClientDiactorosTest.php index 2ef7b6c..c8dc1cf 100644 --- a/tests/Functional/HttpAsyncClientDiactorosTest.php +++ b/tests/Functional/HttpAsyncClientDiactorosTest.php @@ -6,8 +6,8 @@ use Http\Client\Curl\Client; use Http\Client\HttpAsyncClient; -use Zend\Diactoros\ResponseFactory; -use Zend\Diactoros\StreamFactory; +use Laminas\Diactoros\ResponseFactory; +use Laminas\Diactoros\StreamFactory; /** * @covers \Http\Client\Curl\Client diff --git a/tests/Functional/HttpClientDiactorosTest.php b/tests/Functional/HttpClientDiactorosTest.php index 854bcdc..6ce67ce 100644 --- a/tests/Functional/HttpClientDiactorosTest.php +++ b/tests/Functional/HttpClientDiactorosTest.php @@ -7,12 +7,12 @@ use Http\Client\Curl\Client; use Http\Client\HttpClient; use Psr\Http\Message\StreamInterface; -use Zend\Diactoros\ResponseFactory; -use Zend\Diactoros\Stream; -use Zend\Diactoros\StreamFactory; +use Laminas\Diactoros\ResponseFactory; +use Laminas\Diactoros\Stream; +use Laminas\Diactoros\StreamFactory; /** - * Testing synchronous requests with Zend Diactoros factories. + * Testing synchronous requests with Laminas Diactoros factories. */ class HttpClientDiactorosTest extends HttpClientTestCase { diff --git a/tests/Unit/ClientTest.php b/tests/Unit/ClientTest.php index 0922733..f198930 100644 --- a/tests/Unit/ClientTest.php +++ b/tests/Unit/ClientTest.php @@ -9,7 +9,7 @@ use Psr\Http\Message\ResponseFactoryInterface; use Psr\Http\Message\StreamFactoryInterface; use Symfony\Component\OptionsResolver\Exception\InvalidOptionsException; -use Zend\Diactoros\Request; +use Laminas\Diactoros\Request; /** * @covers \Http\Client\Curl\Client From 724859b61360e45bd4394a5af972d2e30a0d86a3 Mon Sep 17 00:00:00 2001 From: David Buchmann Date: Mon, 12 Oct 2020 08:52:54 +0200 Subject: [PATCH 085/108] adjust to newer phpunit versions --- tests/Functional/HttpClientTestCase.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/Functional/HttpClientTestCase.php b/tests/Functional/HttpClientTestCase.php index 6df8a8d..f74d6fd 100644 --- a/tests/Functional/HttpClientTestCase.php +++ b/tests/Functional/HttpClientTestCase.php @@ -109,7 +109,7 @@ protected function createTempFile(): string /** * Delete files created with createTempFile */ - protected function tearDown() + protected function tearDown(): void { parent::tearDown(); From dcb28fa28180a0962d5bbfc896ee16c56cda8109 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Beno=C3=AEt=20Viguier?= Date: Mon, 14 Dec 2020 09:00:26 +0100 Subject: [PATCH 086/108] full support to PHP 8.0 + add support for CurlHandle resource objects (#71) --- .github/workflows/Build-Test.yml | 90 ++++++++++++++++++++ .travis.yml | 38 --------- composer.json | 18 ++-- src/Client.php | 2 +- src/PromiseCore.php | 37 +++++--- tests/Functional/HttpClientDiactorosTest.php | 4 +- tests/Unit/PromiseCoreTest.php | 14 ++- 7 files changed, 136 insertions(+), 67 deletions(-) create mode 100644 .github/workflows/Build-Test.yml delete mode 100644 .travis.yml diff --git a/.github/workflows/Build-Test.yml b/.github/workflows/Build-Test.yml new file mode 100644 index 0000000..1c0dde3 --- /dev/null +++ b/.github/workflows/Build-Test.yml @@ -0,0 +1,90 @@ + +# Run this workflow every time a new commit pushed to your repository +on: + push: + +jobs: + tests: + if: "! contains(toJSON(github.event.commits.*.msg), 'skip') && ! contains(toJSON(github.event.commits.*.msg), 'ci')" #skip ci... + runs-on: ${{ matrix.operating-system }} + + strategy: + fail-fast: false + matrix: + operating-system: [ubuntu-20.04] + php-versions: ['7.2', '7.3', '7.4', '8.0'] + include: + - operating-system: ubuntu-16.04 + php-versions: '7.1' + COMPOSER_FLAGS: '--prefer-stable --prefer-lowest' + COVERAGE: 'true' + PHPUNIT_FLAGS: '--coverage-clover build/coverage.xml' + + name: PHP ${{ matrix.php-versions }} - ${{ matrix.operating-system }} + + env: + extensions: curl json libxml dom + key: cache-v1 # can be any string, change to clear the extension cache. + + steps: + # Checks out a copy of your repository on the ubuntu machine + - name: Checkout code + uses: actions/checkout@v2 + + - name: Setup cache environment + id: extcache + uses: shivammathur/cache-extensions@v1 + with: + php-version: ${{ matrix.php-versions }} + extensions: ${{ env.extensions }} + key: ${{ env.key }} + + - name: Cache PHP Extensions + uses: actions/cache@v2 + with: + path: ${{ steps.extcache.outputs.dir }} + key: ${{ steps.extcache.outputs.key }} + restore-keys: ${{ steps.extcache.outputs.key }} + + - name: Cache Composer Dependencies + uses: actions/cache@v1 + with: + path: ~/.composer/cache/files + key: dependencies-composer-${{ hashFiles('composer.json') }} + + - name: Setup PHP Action + uses: shivammathur/setup-php@2.8.0 + with: + php-version: ${{ matrix.php-versions }} + extensions: ${{ env.extensions }} + coverage: xdebug + tools: pecl, composer + + - name: Get composer cache directory + id: composer-cache + run: echo "::set-output name=dir::$(composer config cache-files-dir)" + + - name: Cache dependencies + uses: actions/cache@v2 + with: + path: ${{ steps.composer-cache.outputs.dir }} + key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }} + restore-keys: ${{ runner.os }}-composer- + + - name: Composer self update + run: composer self-update + + - name: Install Composer dependencies + run: composer update ${{ matrix.COMPOSER_FLAGS }} --prefer-source --no-interaction + + - name: boot test server + run: vendor/bin/http_test_server > /dev/null 2>&1 & + + - name: Apply tests + run: composer test + + - name: Apply coverage + if: ${{ matrix.COVERAGE == 'true' }} + run: | + wget https://scrutinizer-ci.com/ocular.phar + php ocular.phar code-coverage:upload --format=php-clover build/coverage.xml diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 64a5a82..0000000 --- a/.travis.yml +++ /dev/null @@ -1,38 +0,0 @@ -language: php -sudo: false -dist: trusty - -cache: - directories: - - $HOME/.composer/cache - -php: - - 7.1 - - 7.2 - - 7.3 - -env: - global: - - TEST_COMMAND="composer test" - -matrix: - fast_finish: true - include: - - php: 7.1 - name: "Lowest dependencies" - env: COMPOSER_FLAGS="--prefer-stable --prefer-lowest" COVERAGE=true PHPUNIT_FLAGS="--coverage-clover build/coverage.xml" - -before_install: - - travis_retry composer self-update - -install: - - travis_retry composer update ${COMPOSER_FLAGS} --prefer-source --no-interaction - -before_script: vendor/bin/http_test_server > /dev/null 2>&1 & - -script: - - $TEST_COMMAND - -after_success: - - if [[ "$COVERAGE" = true ]]; then wget https://scrutinizer-ci.com/ocular.phar; fi - - if [[ "$COVERAGE" = true ]]; then php ocular.phar code-coverage:upload --format=php-clover build/coverage.xml; fi diff --git a/composer.json b/composer.json index 94e7c3d..55f90d7 100644 --- a/composer.json +++ b/composer.json @@ -8,16 +8,14 @@ "psr-18" ], "homepage": "http://php-http.org", - "authors": [ - { - "name": "Михаил Красильников", - "email": "m.krasilnikov@yandex.ru" - } - ], + "authors": [{ + "name": "Михаил Красильников", + "email": "m.krasilnikov@yandex.ru" + }], "prefer-stable": true, "minimum-stability": "dev", "require": { - "php": "^7.1", + "php": "^7.1 || ^8.0", "ext-curl": "*", "php-http/discovery": "^1.6", "php-http/httplug": "^2.0", @@ -28,8 +26,8 @@ }, "require-dev": { "guzzlehttp/psr7": "^1.0", - "php-http/client-integration-tests": "^2.0", - "phpunit/phpunit": "^7.5", + "php-http/client-integration-tests": "^3.0", + "phpunit/phpunit": "^7.5 || ^9.4", "laminas/laminas-diactoros": "^2.0" }, "autoload": { @@ -56,4 +54,4 @@ "dev-master": "2.x-dev" } } -} +} \ No newline at end of file diff --git a/src/Client.php b/src/Client.php index f6b844c..739fdc2 100644 --- a/src/Client.php +++ b/src/Client.php @@ -53,7 +53,7 @@ class Client implements HttpClient, HttpAsyncClient /** * cURL synchronous requests handle. * - * @var resource|null + * @var resource|\CurlHandle|null */ private $handle; diff --git a/src/PromiseCore.php b/src/PromiseCore.php index 2b0256d..b58776f 100644 --- a/src/PromiseCore.php +++ b/src/PromiseCore.php @@ -69,9 +69,9 @@ class PromiseCore /** * Create shared core. * - * @param RequestInterface $request HTTP request. - * @param resource $handle cURL handle. - * @param ResponseBuilder $responseBuilder Response builder. + * @param RequestInterface $request HTTP request. + * @param resource|\CurlHandle $handle cURL handle. + * @param ResponseBuilder $responseBuilder Response builder. * * @throws \InvalidArgumentException If $handle is not a cURL resource. */ @@ -80,20 +80,29 @@ public function __construct( $handle, ResponseBuilder $responseBuilder ) { - if (!is_resource($handle)) { - throw new \InvalidArgumentException( - sprintf( - 'Parameter $handle expected to be a cURL resource, %s given', - gettype($handle) - ) - ); + if (PHP_MAJOR_VERSION === 7) { + if (!is_resource($handle)) { + throw new \InvalidArgumentException( + sprintf( + 'Parameter $handle expected to be a cURL resource, %s given', + gettype($handle) + ) + ); + } elseif (get_resource_type($handle) !== 'curl') { + throw new \InvalidArgumentException( + sprintf( + 'Parameter $handle expected to be a cURL resource, %s resource given', + get_resource_type($handle) + ) + ); + } } - if (get_resource_type($handle) !== 'curl') { + if (PHP_MAJOR_VERSION > 7 && !$handle instanceof \CurlHandle) { throw new \InvalidArgumentException( sprintf( - 'Parameter $handle expected to be a cURL resource, %s resource given', - get_resource_type($handle) + 'Parameter $handle expected to be a cURL resource, %s given', + get_debug_type($handle) ) ); } @@ -138,7 +147,7 @@ public function addOnRejected(callable $callback): void /** * Return cURL handle. * - * @return resource + * @return resource|\CurlHandle */ public function getHandle() { diff --git a/tests/Functional/HttpClientDiactorosTest.php b/tests/Functional/HttpClientDiactorosTest.php index 6ce67ce..a8fd42f 100644 --- a/tests/Functional/HttpClientDiactorosTest.php +++ b/tests/Functional/HttpClientDiactorosTest.php @@ -4,7 +4,9 @@ namespace Http\Client\Curl\Tests\Functional; + use Http\Client\Curl\Client; +use Psr\Http\Client\ClientInterface; use Http\Client\HttpClient; use Psr\Http\Message\StreamInterface; use Laminas\Diactoros\ResponseFactory; @@ -21,7 +23,7 @@ protected function createFileStream(string $filename): StreamInterface return new Stream($filename); } - protected function createHttpAdapter(): HttpClient + protected function createHttpAdapter(): ClientInterface { return new Client(new ResponseFactory(), new StreamFactory()); } diff --git a/tests/Unit/PromiseCoreTest.php b/tests/Unit/PromiseCoreTest.php index b52df28..7374640 100644 --- a/tests/Unit/PromiseCoreTest.php +++ b/tests/Unit/PromiseCoreTest.php @@ -36,7 +36,11 @@ public function testHandleIsNotACurlResource(): void $responseBuilder = $this->createMock(ResponseBuilder::class); $this->expectException(\InvalidArgumentException::class); - $this->expectExceptionMessage('Parameter $handle expected to be a cURL resource, stream resource given'); + if (PHP_MAJOR_VERSION > 7) { + $this->expectExceptionMessage('Parameter $handle expected to be a cURL resource, resource (stream) given'); + } else { + $this->expectExceptionMessage('Parameter $handle expected to be a cURL resource, stream resource given'); + } new PromiseCore($request, fopen('php://memory', 'r+b'), $responseBuilder); } @@ -50,7 +54,11 @@ public function testHandleIsNotAResource(): void $responseBuilder = $this->createMock(ResponseBuilder::class); $this->expectException(\InvalidArgumentException::class); - $this->expectExceptionMessage('Parameter $handle expected to be a cURL resource, NULL given'); + if (PHP_MAJOR_VERSION > 7) { + $this->expectExceptionMessage('Parameter $handle expected to be a cURL resource, null given'); + } else { + $this->expectExceptionMessage('Parameter $handle expected to be a cURL resource, NULL given'); + } new PromiseCore($request, null, $responseBuilder); } @@ -152,7 +160,7 @@ function (RequestException $exception) { self::assertEquals('Bar', $core->getException()->getMessage()); } - protected function tearDown() + protected function tearDown(): void { if (is_resource($this->handle)) { curl_close($this->handle); From 15b11b7c2f39fe61ef6a70e0c247b4a84e845cdb Mon Sep 17 00:00:00 2001 From: David Buchmann Date: Mon, 14 Dec 2020 09:36:51 +0100 Subject: [PATCH 087/108] prepare release --- CHANGELOG.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5efce9d..41294d9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,12 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## 2.2.0 - 2020-14-12 + +### Added + +- PHP 8.0 support + ## 2.1.0 - 2019-12-27 ### Added From fe1fa469f619f6702a72946d116598d27557fe15 Mon Sep 17 00:00:00 2001 From: David Buchmann Date: Mon, 14 Dec 2020 09:32:46 +0100 Subject: [PATCH 088/108] update build badge to look at github workflow --- .github/workflows/Build-Test.yml | 8 +------- README.md | 2 +- 2 files changed, 2 insertions(+), 8 deletions(-) diff --git a/.github/workflows/Build-Test.yml b/.github/workflows/Build-Test.yml index 1c0dde3..9a4ab1e 100644 --- a/.github/workflows/Build-Test.yml +++ b/.github/workflows/Build-Test.yml @@ -1,3 +1,4 @@ +name: Tests # Run this workflow every time a new commit pushed to your repository on: @@ -17,7 +18,6 @@ jobs: - operating-system: ubuntu-16.04 php-versions: '7.1' COMPOSER_FLAGS: '--prefer-stable --prefer-lowest' - COVERAGE: 'true' PHPUNIT_FLAGS: '--coverage-clover build/coverage.xml' name: PHP ${{ matrix.php-versions }} - ${{ matrix.operating-system }} @@ -82,9 +82,3 @@ jobs: - name: Apply tests run: composer test - - - name: Apply coverage - if: ${{ matrix.COVERAGE == 'true' }} - run: | - wget https://scrutinizer-ci.com/ocular.phar - php ocular.phar code-coverage:upload --format=php-clover build/coverage.xml diff --git a/README.md b/README.md index fc60b7e..3cebafd 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ [![Latest Version](https://img.shields.io/github/release/php-http/curl-client.svg?style=flat-square)](https://github.com/php-http/curl-client/releases) [![Software License](https://img.shields.io/badge/license-MIT-brightgreen.svg?style=flat-square)](LICENSE) -[![Build Status](https://img.shields.io/travis/php-http/curl-client.svg?style=flat-square)](https://travis-ci.org/php-http/curl-client) +[![Build Status](https://img.shields.io/github/workflow/status/php-http/curl-client/Tests.svg?style=flat-square)](https://github.com/php-http/curl-client/actions?query=workflow%3ATests) [![Code Coverage](https://img.shields.io/scrutinizer/coverage/g/php-http/curl-client.svg?style=flat-square)](https://scrutinizer-ci.com/g/php-http/curl-client) [![Quality Score](https://img.shields.io/scrutinizer/g/php-http/curl-client.svg?style=flat-square)](https://scrutinizer-ci.com/g/php-http/curl-client) [![Total Downloads](https://img.shields.io/packagist/dt/php-http/curl-client.svg?style=flat-square)](https://packagist.org/packages/php-http/curl-client) From a694b6813122c7c7b6cd10a36952e463519f52e3 Mon Sep 17 00:00:00 2001 From: David Buchmann Date: Mon, 14 Dec 2020 13:57:50 +0100 Subject: [PATCH 089/108] tweak some build steps --- .github/workflows/Build-Test.yml | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/.github/workflows/Build-Test.yml b/.github/workflows/Build-Test.yml index 9a4ab1e..95a00f8 100644 --- a/.github/workflows/Build-Test.yml +++ b/.github/workflows/Build-Test.yml @@ -71,14 +71,11 @@ jobs: key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }} restore-keys: ${{ runner.os }}-composer- - - name: Composer self update - run: composer self-update - - name: Install Composer dependencies - run: composer update ${{ matrix.COMPOSER_FLAGS }} --prefer-source --no-interaction + run: composer update ${{ matrix.COMPOSER_FLAGS }} --no-interaction - name: boot test server run: vendor/bin/http_test_server > /dev/null 2>&1 & - - name: Apply tests + - name: Run tests run: composer test From 555b1a9b42c77ef8ade6ea6cb4be7fa4977c86a9 Mon Sep 17 00:00:00 2001 From: David Buchmann Date: Wed, 16 Dec 2020 08:46:00 +0100 Subject: [PATCH 090/108] remove not working scrutinizer badges fix #72 --- README.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/README.md b/README.md index 3cebafd..c9d6b5c 100644 --- a/README.md +++ b/README.md @@ -3,8 +3,6 @@ [![Latest Version](https://img.shields.io/github/release/php-http/curl-client.svg?style=flat-square)](https://github.com/php-http/curl-client/releases) [![Software License](https://img.shields.io/badge/license-MIT-brightgreen.svg?style=flat-square)](LICENSE) [![Build Status](https://img.shields.io/github/workflow/status/php-http/curl-client/Tests.svg?style=flat-square)](https://github.com/php-http/curl-client/actions?query=workflow%3ATests) -[![Code Coverage](https://img.shields.io/scrutinizer/coverage/g/php-http/curl-client.svg?style=flat-square)](https://scrutinizer-ci.com/g/php-http/curl-client) -[![Quality Score](https://img.shields.io/scrutinizer/g/php-http/curl-client.svg?style=flat-square)](https://scrutinizer-ci.com/g/php-http/curl-client) [![Total Downloads](https://img.shields.io/packagist/dt/php-http/curl-client.svg?style=flat-square)](https://packagist.org/packages/php-http/curl-client) The cURL client use the cURL PHP extension which must be activated in your `php.ini`. From be69b8051d513f23456a0e5a1af62993fb95c3d8 Mon Sep 17 00:00:00 2001 From: Yosh de Vos Date: Wed, 8 Dec 2021 11:45:39 +0100 Subject: [PATCH 091/108] Added symfony 6 --- composer.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/composer.json b/composer.json index 55f90d7..3b71e7f 100644 --- a/composer.json +++ b/composer.json @@ -22,7 +22,7 @@ "php-http/message": "^1.2", "psr/http-client": "^1.0", "psr/http-factory": "^1.0", - "symfony/options-resolver": "^3.4 || ^4.0 || ^5.0" + "symfony/options-resolver": "^3.4 || ^4.0 || ^5.0 || ^6.0" }, "require-dev": { "guzzlehttp/psr7": "^1.0", @@ -54,4 +54,4 @@ "dev-master": "2.x-dev" } } -} \ No newline at end of file +} From e0dd606d557623e1bd24de4cc61ac16740bd2fc6 Mon Sep 17 00:00:00 2001 From: David Buchmann Date: Fri, 10 Dec 2021 18:35:47 +0100 Subject: [PATCH 092/108] build with php 8.1 --- .github/workflows/Build-Test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/Build-Test.yml b/.github/workflows/Build-Test.yml index 95a00f8..e527d1b 100644 --- a/.github/workflows/Build-Test.yml +++ b/.github/workflows/Build-Test.yml @@ -13,7 +13,7 @@ jobs: fail-fast: false matrix: operating-system: [ubuntu-20.04] - php-versions: ['7.2', '7.3', '7.4', '8.0'] + php-versions: ['7.2', '7.3', '7.4', '8.0', '8.1'] include: - operating-system: ubuntu-16.04 php-versions: '7.1' From 2ed4245a817d859dd0c1d51c7078cdb343cf5233 Mon Sep 17 00:00:00 2001 From: David Buchmann Date: Fri, 10 Dec 2021 19:02:07 +0100 Subject: [PATCH 093/108] prepare release --- CHANGELOG.md | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 41294d9..06a293b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,7 +5,14 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). -## 2.2.0 - 2020-14-12 +## 2.2.1 - 2021-12-10 + +### Added + +- Symfony 6 support +- Tested with PHP 8.1 + +## 2.2.0 - 2020-12-14 ### Added From aa7d3c1c7efe313f5d9e4ee63785b8c6ce007cf1 Mon Sep 17 00:00:00 2001 From: David Buchmann Date: Sat, 3 Apr 2021 11:22:26 +0200 Subject: [PATCH 094/108] we need a psr http factory implementation --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 3b71e7f..303f785 100644 --- a/composer.json +++ b/composer.json @@ -21,7 +21,7 @@ "php-http/httplug": "^2.0", "php-http/message": "^1.2", "psr/http-client": "^1.0", - "psr/http-factory": "^1.0", + "psr/http-factory-implementation": "^1.0", "symfony/options-resolver": "^3.4 || ^4.0 || ^5.0 || ^6.0" }, "require-dev": { From 2391d01fe24d6d89eee591c1284e3cb6ffdd1740 Mon Sep 17 00:00:00 2001 From: David Buchmann Date: Fri, 28 Apr 2023 15:32:33 +0200 Subject: [PATCH 095/108] semantic branch naming --- .github/workflows/Build-Test.yml | 7 +++++-- composer.json | 5 ----- 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/.github/workflows/Build-Test.yml b/.github/workflows/Build-Test.yml index e527d1b..9c1a2e6 100644 --- a/.github/workflows/Build-Test.yml +++ b/.github/workflows/Build-Test.yml @@ -3,6 +3,9 @@ name: Tests # Run this workflow every time a new commit pushed to your repository on: push: + branches: + - '*.x' + pull_request: jobs: tests: @@ -13,7 +16,7 @@ jobs: fail-fast: false matrix: operating-system: [ubuntu-20.04] - php-versions: ['7.2', '7.3', '7.4', '8.0', '8.1'] + php-versions: ['7.2', '7.3', '7.4', '8.0', '8.1', '8.2'] include: - operating-system: ubuntu-16.04 php-versions: '7.1' @@ -29,7 +32,7 @@ jobs: steps: # Checks out a copy of your repository on the ubuntu machine - name: Checkout code - uses: actions/checkout@v2 + uses: actions/checkout@v3 - name: Setup cache environment id: extcache diff --git a/composer.json b/composer.json index 303f785..78b4a46 100644 --- a/composer.json +++ b/composer.json @@ -48,10 +48,5 @@ "scripts": { "test": "vendor/bin/phpunit", "test-ci": "vendor/bin/phpunit --coverage-clover build/coverage.xml" - }, - "extra": { - "branch-alias": { - "dev-master": "2.x-dev" - } } } From f724c42c3b3f0016c77c7e0502988a4bfd918d9d Mon Sep 17 00:00:00 2001 From: David Buchmann Date: Fri, 28 Apr 2023 15:39:50 +0200 Subject: [PATCH 096/108] update changelog --- CHANGELOG.md | 24 ++++++++++++++++++------ 1 file changed, 18 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 06a293b..08dc177 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,18 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## 2.3.0 - 2023-04-28 + +### Added + +- Test with PHP 8.2 + +### Fixed + +- This client needs a PSR-17 factories implementation. Instead of requiring an implementation, + previous versions only required the interfaces which could lead to a non-functional installation. + Fixed by requiring `psr/http-factory-implementation`. + ## 2.2.1 - 2021-12-10 ### Added @@ -33,12 +45,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed - Minimal PHP version changed to 7.1. -- `Client::__construct` now expects PSR-17 factories instead of HTTPlug ones. +- `Client::__construct` now expects PSR-17 factories instead of HTTPlug ones. ### Added - #41: Support [PSR-17](https://www.php-fig.org/psr/psr-17/) and - [PSR-18](https://www.php-fig.org/psr/psr-18/). + [PSR-18](https://www.php-fig.org/psr/psr-18/). ## 1.7.1 - 2018-03-26 @@ -58,7 +70,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Fixed -- #29: Request not using CURLOPT_POSTFIELDS have content-length set to +- #29: Request not using CURLOPT_POSTFIELDS have content-length set to ### Changed @@ -94,7 +106,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed - Request body can be send with any method except GET, HEAD and TRACE. -- #25: Make discovery a hard dependency. +- #25: Make discovery a hard dependency. ## 1.4.2 - 2016-06-14 @@ -126,7 +138,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Removed -- #13: Remove HeaderParser. +- #13: Remove HeaderParser. ## 1.2 - 2016-03-09 @@ -163,7 +175,7 @@ First stable release. ### Changed - Root namespace changed from `Http\Curl` to `Http\Client\Curl`. -- Main client class name renamed from `CurlHttpClient` to `Client`. +- Main client class name renamed from `CurlHttpClient` to `Client`. - Minimum required [php-http/discovery](https://packagist.org/packages/php-http/discovery) version changed to 0.5. From 14649223ef8ee2ecdf610676bed5a187741aedb5 Mon Sep 17 00:00:00 2001 From: David Buchmann Date: Fri, 28 Apr 2023 15:39:58 +0200 Subject: [PATCH 097/108] upgrade build actions --- .github/workflows/Build-Test.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/Build-Test.yml b/.github/workflows/Build-Test.yml index 9c1a2e6..756a8fe 100644 --- a/.github/workflows/Build-Test.yml +++ b/.github/workflows/Build-Test.yml @@ -50,13 +50,13 @@ jobs: restore-keys: ${{ steps.extcache.outputs.key }} - name: Cache Composer Dependencies - uses: actions/cache@v1 + uses: actions/cache@v3 with: path: ~/.composer/cache/files key: dependencies-composer-${{ hashFiles('composer.json') }} - name: Setup PHP Action - uses: shivammathur/setup-php@2.8.0 + uses: shivammathur/setup-php@3 with: php-version: ${{ matrix.php-versions }} extensions: ${{ env.extensions }} @@ -68,7 +68,7 @@ jobs: run: echo "::set-output name=dir::$(composer config cache-files-dir)" - name: Cache dependencies - uses: actions/cache@v2 + uses: actions/cache@v3 with: path: ${{ steps.composer-cache.outputs.dir }} key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }} From 738aa88198d584db53d8c397cbd965b239c18f73 Mon Sep 17 00:00:00 2001 From: David Buchmann Date: Fri, 28 Apr 2023 15:42:11 +0200 Subject: [PATCH 098/108] setup ci --- .github/workflows/Build-Test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/Build-Test.yml b/.github/workflows/Build-Test.yml index 756a8fe..b8789a8 100644 --- a/.github/workflows/Build-Test.yml +++ b/.github/workflows/Build-Test.yml @@ -56,7 +56,7 @@ jobs: key: dependencies-composer-${{ hashFiles('composer.json') }} - name: Setup PHP Action - uses: shivammathur/setup-php@3 + uses: shivammathur/setup-php@v2 with: php-version: ${{ matrix.php-versions }} extensions: ${{ env.extensions }} From eeadd647947fb909c8c051e24ae96159caabbb45 Mon Sep 17 00:00:00 2001 From: David Buchmann Date: Fri, 28 Apr 2023 16:45:34 +0200 Subject: [PATCH 099/108] cleanup changelog --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 08dc177..29183b4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -57,7 +57,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Fixed -- #36: Failure evaluating code: is_resource($handle) (string assertions are deprecated in PHP 7.2) +- #36: Failure evaluating code: `is_resource($handle)` (string assertions are deprecated in PHP 7.2) ## 1.7 - 2017-02-09 From 03c63a4369f323066af7138e44714c600b8f6c10 Mon Sep 17 00:00:00 2001 From: David Buchmann Date: Fri, 28 Apr 2023 16:52:00 +0200 Subject: [PATCH 100/108] simplify ci --- .github/workflows/Build-Test.yml | 37 ++------------------------------ 1 file changed, 2 insertions(+), 35 deletions(-) diff --git a/.github/workflows/Build-Test.yml b/.github/workflows/Build-Test.yml index b8789a8..eb6545b 100644 --- a/.github/workflows/Build-Test.yml +++ b/.github/workflows/Build-Test.yml @@ -1,11 +1,10 @@ name: Tests -# Run this workflow every time a new commit pushed to your repository on: push: branches: - '*.x' - pull_request: + pull_request: jobs: tests: @@ -34,45 +33,13 @@ jobs: - name: Checkout code uses: actions/checkout@v3 - - name: Setup cache environment - id: extcache - uses: shivammathur/cache-extensions@v1 - with: - php-version: ${{ matrix.php-versions }} - extensions: ${{ env.extensions }} - key: ${{ env.key }} - - - name: Cache PHP Extensions - uses: actions/cache@v2 - with: - path: ${{ steps.extcache.outputs.dir }} - key: ${{ steps.extcache.outputs.key }} - restore-keys: ${{ steps.extcache.outputs.key }} - - - name: Cache Composer Dependencies - uses: actions/cache@v3 - with: - path: ~/.composer/cache/files - key: dependencies-composer-${{ hashFiles('composer.json') }} - - name: Setup PHP Action uses: shivammathur/setup-php@v2 with: php-version: ${{ matrix.php-versions }} extensions: ${{ env.extensions }} coverage: xdebug - tools: pecl, composer - - - name: Get composer cache directory - id: composer-cache - run: echo "::set-output name=dir::$(composer config cache-files-dir)" - - - name: Cache dependencies - uses: actions/cache@v3 - with: - path: ${{ steps.composer-cache.outputs.dir }} - key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }} - restore-keys: ${{ runner.os }}-composer- + tools: 'composer:v2, pecl' - name: Install Composer dependencies run: composer update ${{ matrix.COMPOSER_FLAGS }} --no-interaction From 422f6ddb93380903cdeee12841e0e6d867305c3a Mon Sep 17 00:00:00 2001 From: David Buchmann Date: Fri, 3 Nov 2023 16:05:07 +0100 Subject: [PATCH 101/108] fix build status badge --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index c9d6b5c..71ab2ea 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ [![Latest Version](https://img.shields.io/github/release/php-http/curl-client.svg?style=flat-square)](https://github.com/php-http/curl-client/releases) [![Software License](https://img.shields.io/badge/license-MIT-brightgreen.svg?style=flat-square)](LICENSE) -[![Build Status](https://img.shields.io/github/workflow/status/php-http/curl-client/Tests.svg?style=flat-square)](https://github.com/php-http/curl-client/actions?query=workflow%3ATests) +[![Tests](https://github.com/php-http/curl-client/actions/workflows/Build-Test.yml/badge.svg?branch=2.x)](https://github.com/php-http/curl-client/actions/workflows/Build-Test.yml) [![Total Downloads](https://img.shields.io/packagist/dt/php-http/curl-client.svg?style=flat-square)](https://packagist.org/packages/php-http/curl-client) The cURL client use the cURL PHP extension which must be activated in your `php.ini`. From 0f21d5de19c77310145fec276b0efb1054b87340 Mon Sep 17 00:00:00 2001 From: David Buchmann Date: Fri, 3 Nov 2023 16:23:41 +0100 Subject: [PATCH 102/108] install legacy factory to still test it --- .github/workflows/Build-Test.yml | 4 ++-- composer.json | 8 +++++++- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/.github/workflows/Build-Test.yml b/.github/workflows/Build-Test.yml index eb6545b..cc6d0e2 100644 --- a/.github/workflows/Build-Test.yml +++ b/.github/workflows/Build-Test.yml @@ -14,10 +14,10 @@ jobs: strategy: fail-fast: false matrix: - operating-system: [ubuntu-20.04] + operating-system: [ubuntu-22.04] php-versions: ['7.2', '7.3', '7.4', '8.0', '8.1', '8.2'] include: - - operating-system: ubuntu-16.04 + - operating-system: ubuntu-20.04 php-versions: '7.1' COMPOSER_FLAGS: '--prefer-stable --prefer-lowest' PHPUNIT_FLAGS: '--coverage-clover build/coverage.xml' diff --git a/composer.json b/composer.json index 78b4a46..f4ba129 100644 --- a/composer.json +++ b/composer.json @@ -28,7 +28,8 @@ "guzzlehttp/psr7": "^1.0", "php-http/client-integration-tests": "^3.0", "phpunit/phpunit": "^7.5 || ^9.4", - "laminas/laminas-diactoros": "^2.0" + "laminas/laminas-diactoros": "^2.0", + "php-http/message-factory": "^1.1" }, "autoload": { "psr-4": { @@ -48,5 +49,10 @@ "scripts": { "test": "vendor/bin/phpunit", "test-ci": "vendor/bin/phpunit --coverage-clover build/coverage.xml" + }, + "config": { + "allow-plugins": { + "php-http/discovery": false + } } } From 09994be2f6b2f3f423121c65835a368011f43787 Mon Sep 17 00:00:00 2001 From: David Buchmann Date: Fri, 3 Nov 2023 16:29:59 +0100 Subject: [PATCH 103/108] allow symfony 7 --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index f4ba129..f332007 100644 --- a/composer.json +++ b/composer.json @@ -22,7 +22,7 @@ "php-http/message": "^1.2", "psr/http-client": "^1.0", "psr/http-factory-implementation": "^1.0", - "symfony/options-resolver": "^3.4 || ^4.0 || ^5.0 || ^6.0" + "symfony/options-resolver": "^3.4 || ^4.0 || ^5.0 || ^6.0 || ^7.0" }, "require-dev": { "guzzlehttp/psr7": "^1.0", From 085570be588f7cbdc4601e78886eea5b7051ad71 Mon Sep 17 00:00:00 2001 From: David Buchmann Date: Fri, 3 Nov 2023 16:32:00 +0100 Subject: [PATCH 104/108] prepare release --- CHANGELOG.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 29183b4..33fbd59 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,12 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## 2.3.1 - 2023-11-03 + +### Added + +- Allow installation with Symfony 7. + ## 2.3.0 - 2023-04-28 ### Added From d6a9a4c997cedfdedef7d87245dc7d554e4828f2 Mon Sep 17 00:00:00 2001 From: Christopher Georg Date: Sat, 2 Mar 2024 12:44:42 +0100 Subject: [PATCH 105/108] chore: fix ci deprecation, add testrun for PHP 8.3 --- .github/workflows/Build-Test.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/Build-Test.yml b/.github/workflows/Build-Test.yml index cc6d0e2..337c785 100644 --- a/.github/workflows/Build-Test.yml +++ b/.github/workflows/Build-Test.yml @@ -15,7 +15,7 @@ jobs: fail-fast: false matrix: operating-system: [ubuntu-22.04] - php-versions: ['7.2', '7.3', '7.4', '8.0', '8.1', '8.2'] + php-versions: ['7.2', '7.3', '7.4', '8.0', '8.1', '8.2', '8.3'] include: - operating-system: ubuntu-20.04 php-versions: '7.1' @@ -31,7 +31,7 @@ jobs: steps: # Checks out a copy of your repository on the ubuntu machine - name: Checkout code - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Setup PHP Action uses: shivammathur/setup-php@v2 From 852c143927d5e05af11d09f751e2b3b92460a8a2 Mon Sep 17 00:00:00 2001 From: David Buchmann Date: Sun, 3 Mar 2024 09:14:19 +0100 Subject: [PATCH 106/108] re-enable and fix tests for guzzle integration --- .github/workflows/Build-Test.yml | 4 ++-- composer.json | 4 ++-- phpunit.xml.dist | 6 ------ tests/Functional/HttpAsyncClientGuzzleTest.php | 5 ++--- tests/Functional/HttpClientGuzzleTest.php | 9 ++++----- tests/Unit/ClientTest.php | 10 +++++----- 6 files changed, 15 insertions(+), 23 deletions(-) diff --git a/.github/workflows/Build-Test.yml b/.github/workflows/Build-Test.yml index 337c785..991180c 100644 --- a/.github/workflows/Build-Test.yml +++ b/.github/workflows/Build-Test.yml @@ -15,10 +15,10 @@ jobs: fail-fast: false matrix: operating-system: [ubuntu-22.04] - php-versions: ['7.2', '7.3', '7.4', '8.0', '8.1', '8.2', '8.3'] + php-versions: ['7.4', '8.0', '8.1', '8.2', '8.3'] include: - operating-system: ubuntu-20.04 - php-versions: '7.1' + php-versions: '7.4' COMPOSER_FLAGS: '--prefer-stable --prefer-lowest' PHPUNIT_FLAGS: '--coverage-clover build/coverage.xml' diff --git a/composer.json b/composer.json index f332007..9b8cd08 100644 --- a/composer.json +++ b/composer.json @@ -15,7 +15,7 @@ "prefer-stable": true, "minimum-stability": "dev", "require": { - "php": "^7.1 || ^8.0", + "php": "^7.4 || ^8.0", "ext-curl": "*", "php-http/discovery": "^1.6", "php-http/httplug": "^2.0", @@ -25,7 +25,7 @@ "symfony/options-resolver": "^3.4 || ^4.0 || ^5.0 || ^6.0 || ^7.0" }, "require-dev": { - "guzzlehttp/psr7": "^1.0", + "guzzlehttp/psr7": "^2.0", "php-http/client-integration-tests": "^3.0", "phpunit/phpunit": "^7.5 || ^9.4", "laminas/laminas-diactoros": "^2.0", diff --git a/phpunit.xml.dist b/phpunit.xml.dist index 3f3b615..1df4f8a 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -14,9 +14,6 @@ tests - - tests/Functional/HttpAsyncClientGuzzleTest.php - tests/Functional/HttpClientGuzzleTest.php @@ -25,9 +22,6 @@ tests/Functional - - tests/Functional/HttpAsyncClientGuzzleTest.php - tests/Functional/HttpClientGuzzleTest.php diff --git a/tests/Functional/HttpAsyncClientGuzzleTest.php b/tests/Functional/HttpAsyncClientGuzzleTest.php index f1d2808..2f6c47f 100644 --- a/tests/Functional/HttpAsyncClientGuzzleTest.php +++ b/tests/Functional/HttpAsyncClientGuzzleTest.php @@ -4,10 +4,9 @@ namespace Http\Client\Curl\Tests\Functional; +use GuzzleHttp\Psr7\HttpFactory; use Http\Client\Curl\Client; use Http\Client\HttpAsyncClient; -use Http\Message\MessageFactory\GuzzleMessageFactory; -use Http\Message\StreamFactory\GuzzleStreamFactory; /** * @covers \Http\Client\Curl\Client @@ -19,6 +18,6 @@ class HttpAsyncClientGuzzleTest extends HttpAsyncClientTestCase */ protected function createHttpAsyncClient(): HttpAsyncClient { - return new Client(new GuzzleMessageFactory(), new GuzzleStreamFactory()); + return new Client(new HttpFactory(), new HttpFactory()); } } diff --git a/tests/Functional/HttpClientGuzzleTest.php b/tests/Functional/HttpClientGuzzleTest.php index 9cbe9da..d7cf853 100644 --- a/tests/Functional/HttpClientGuzzleTest.php +++ b/tests/Functional/HttpClientGuzzleTest.php @@ -4,11 +4,10 @@ namespace Http\Client\Curl\Tests\Functional; +use GuzzleHttp\Psr7\HttpFactory; use GuzzleHttp\Psr7\Stream; use Http\Client\Curl\Client; -use Http\Client\HttpClient; -use Http\Message\MessageFactory\GuzzleMessageFactory; -use Http\Message\StreamFactory\GuzzleStreamFactory; +use Psr\Http\Client\ClientInterface; use Psr\Http\Message\StreamInterface; /** @@ -19,9 +18,9 @@ class HttpClientGuzzleTest extends HttpClientTestCase /** * {@inheritdoc} */ - protected function createHttpAdapter(): HttpClient + protected function createHttpAdapter(): ClientInterface { - return new Client(new GuzzleMessageFactory(), new GuzzleStreamFactory()); + return new Client(new HttpFactory(), new HttpFactory()); } /** diff --git a/tests/Unit/ClientTest.php b/tests/Unit/ClientTest.php index f198930..55d3e89 100644 --- a/tests/Unit/ClientTest.php +++ b/tests/Unit/ClientTest.php @@ -4,6 +4,7 @@ namespace Http\Client\Curl\Tests\Unit; +use GuzzleHttp\Psr7\Utils; use Http\Client\Curl\Client; use PHPUnit\Framework\TestCase; use Psr\Http\Message\ResponseFactoryInterface; @@ -83,14 +84,13 @@ public function testRewindLargeStream(): void } $length = strlen($content); - $body = \GuzzleHttp\Psr7\stream_for($content); + $body = Utils::streamFor($content); $body->seek(40); $request = new Request('http://foo.com', 'POST', $body); $options = $bodyOptions->invoke($client, $request, []); - static::assertTrue( - false !== strstr($options[CURLOPT_READFUNCTION](null, null, $length), 'abcdef'), - 'Steam was not rewinded' + static::assertNotFalse( + strpos($options[CURLOPT_READFUNCTION](null, null, $length), 'abcdef'), 'Steam was not rewinded' ); } @@ -101,7 +101,7 @@ public function testRewindStream(): void $bodyOptions = new \ReflectionMethod(Client::class, 'addRequestBodyOptions'); $bodyOptions->setAccessible(true); - $body = \GuzzleHttp\Psr7\stream_for('abcdef'); + $body = Utils::streamFor('abcdef'); $body->seek(3); $request = new Request('http://foo.com', 'POST', $body); $options = $bodyOptions->invoke($client, $request, []); From 0b869922458b1cde9137374545ed4fff7ac83623 Mon Sep 17 00:00:00 2001 From: David Buchmann Date: Sun, 3 Mar 2024 09:21:07 +0100 Subject: [PATCH 107/108] prepare release --- CHANGELOG.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 33fbd59..6933c25 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,12 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## 2.3.2 - 2024-03-03 + +### Fixed + +- Fixed running the tests with Guzzle PSR-7 and PSR-17 implementations. + ## 2.3.1 - 2023-11-03 ### Added From f3eb48d266341afec0229a7a37a03521d3646b81 Mon Sep 17 00:00:00 2001 From: George Steel Date: Thu, 31 Oct 2024 07:36:58 +0000 Subject: [PATCH 108/108] Add Support for PHP 8.4 (#87) * Add PHP 8.4 to the matrix * Fix implicitly nullable parameters * Permit installation of laminas-diactoros ^3 in dev --- .github/workflows/Build-Test.yml | 2 +- CHANGELOG.md | 6 ++++++ composer.json | 2 +- src/Client.php | 4 ++-- src/CurlPromise.php | 6 +++--- src/MultiRunner.php | 2 +- 6 files changed, 14 insertions(+), 8 deletions(-) diff --git a/.github/workflows/Build-Test.yml b/.github/workflows/Build-Test.yml index 991180c..3c8d40e 100644 --- a/.github/workflows/Build-Test.yml +++ b/.github/workflows/Build-Test.yml @@ -15,7 +15,7 @@ jobs: fail-fast: false matrix: operating-system: [ubuntu-22.04] - php-versions: ['7.4', '8.0', '8.1', '8.2', '8.3'] + php-versions: ['7.4', '8.0', '8.1', '8.2', '8.3', '8.4'] include: - operating-system: ubuntu-20.04 php-versions: '7.4' diff --git a/CHANGELOG.md b/CHANGELOG.md index 6933c25..caef53e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,12 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## 2.3.3 - 2024-10-31 + +### Added + +- Added support for PHP 8.4 + ## 2.3.2 - 2024-03-03 ### Fixed diff --git a/composer.json b/composer.json index 9b8cd08..cb1d971 100644 --- a/composer.json +++ b/composer.json @@ -28,7 +28,7 @@ "guzzlehttp/psr7": "^2.0", "php-http/client-integration-tests": "^3.0", "phpunit/phpunit": "^7.5 || ^9.4", - "laminas/laminas-diactoros": "^2.0", + "laminas/laminas-diactoros": "^2.0 || ^3.0", "php-http/message-factory": "^1.1" }, "autoload": { diff --git a/src/Client.php b/src/Client.php index 739fdc2..d3fd6bd 100644 --- a/src/Client.php +++ b/src/Client.php @@ -77,8 +77,8 @@ class Client implements HttpClient, HttpAsyncClient * @since 2.0 Accepts PSR-17 factories instead of HTTPlug ones. */ public function __construct( - ResponseFactoryInterface $responseFactory = null, - StreamFactoryInterface $streamFactory = null, + ?ResponseFactoryInterface $responseFactory = null, + ?StreamFactoryInterface $streamFactory = null, array $options = [] ) { $this->responseFactory = $responseFactory ?: Psr17FactoryDiscovery::findResponseFactory(); diff --git a/src/CurlPromise.php b/src/CurlPromise.php index f04ccf6..d69494c 100644 --- a/src/CurlPromise.php +++ b/src/CurlPromise.php @@ -51,15 +51,15 @@ public function __construct(PromiseCore $core, MultiRunner $runner) * If you do not care about one of the cases, you can set the corresponding callable to null * The callback will be called when the response or exception arrived and never more than once. * - * @param callable $onFulfilled Called when a response will be available - * @param callable $onRejected Called when an error happens. + * @param callable|null $onFulfilled Called when a response will be available + * @param callable|null $onRejected Called when an error happens. * * You must always return the Response in the interface or throw an Exception * * @return Promise Always returns a new promise which is resolved with value of the executed * callback (onFulfilled / onRejected) */ - public function then(callable $onFulfilled = null, callable $onRejected = null) + public function then(?callable $onFulfilled = null, ?callable $onRejected = null) { if ($onFulfilled) { $this->core->addOnFulfilled($onFulfilled); diff --git a/src/MultiRunner.php b/src/MultiRunner.php index 7a2c50c..13f43a9 100644 --- a/src/MultiRunner.php +++ b/src/MultiRunner.php @@ -81,7 +81,7 @@ public function remove(PromiseCore $core): void * * @param PromiseCore|null $targetCore */ - public function wait(PromiseCore $targetCore = null): void + public function wait(?PromiseCore $targetCore = null): void { do { $status = curl_multi_exec($this->multiHandle, $active);